文章目录

  • 为什么需要输入子系统框架
  • 输入子系统框架
    • 驱动层
    • 事件处理层
    • 注册输入设备函数:
    • 注销输入设备函数:
    • 驱动实现——初始化(事件支持)
    • 驱动实现——报告事件
    • 驱动实现——报告结束
    • 关于事件报告的实现方法
      • 中断实现 常用于实体按键
      • 内核定时器实现,用于没有触发信号的输入事件
  • PS2驱动开发
    • 硬件原理
    • 软件实现

为什么需要输入子系统框架

我们首先来看字符类驱动框架:

1)写file_operations结构体的成员函数: .open()、.read()、.write()
2)在入口函数里通过register_chrdev()创建驱动名,生成主设备号,赋入file_operations结构体
3)在出口函数里通过unregister_chrdev() 卸载驱动

若有多个不同的驱动程序时,应用程序就要打开多个不同的驱动设备,由于是自己写肯定会很清楚,如果给别人来使用时是不是很麻烦?
所以需要使用输入子系统, 使应用程序无需打开多个不同的驱动设备便能实现。

输入子系统框架

驱动层

将底层的硬件输入转化为统一事件形式,想输入核心(Input Core)汇报。
输入子系统核心层
它承上启下为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。

事件处理层

主要是和用户空间交互(Linux中在用户空间将所有的设备都当作文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。
设备描述

input_dev结构是实现设备驱动核心工作:向系统报告按键、触摸屏等输入事件(event,通过input_event结构描述),不再需要关心文件操作接口。驱动报告事件经过inputCore和Eventhandler到达用户空间。

注册输入设备函数:

int input_register_device(struct input_dev *dev)

注销输入设备函数:

void input_unregister_device(struct input_dev *dev)

驱动实现——初始化(事件支持)

set_bit()告诉input输入子系统支持哪些事件,哪些按键。例如:

set_bit(EV_KEY,button_dev.evbit) (其中button_dev是struct input_dev类型)

struct input_dev中有两个成员为:
1)evbit事件类型(包括EV_RST,EV_REL,EV_MSC,EV_KEY,EV_ABS,EV_REP等)。
2)keybit按键类型(当事件类型为EV_KEY时包括BTN_LEFT,BTN_0,BTN_1,BTN_MIDDLE等)。

驱动实现——报告事件

用于报告EV_KEY,EV_REL,EV_ABS事件的函数分别为:
void input_report_key(struct input_dev *dev,unsigned int code,int value)
void input_report_rel(struct input_dev *dev,unsigned int code,int value)
void input_report_abs(struct input_dev *dev,unsigned int code,int value)

驱动实现——报告结束

input_sync()同步用于告诉input core子系统报告结束,触摸屏设备驱动中,一次点击的整个报告过程如下:
input_reprot_abs(input_dev,ABS_X,x); //x坐标
input_reprot_abs(input_dev,ABS_Y,y); // y坐标
input_reprot_abs(input_dev,ABS_PRESSURE,1);
input_sync(input_dev);//同步结束

实例分析(按键中断程序):

//按键初始化

static int __init button_init(void)
{//申请中断if(request_irq(BUTTON_IRQ,button_interrupt,0,”button”,NUll))return –EBUSY;set_bit(EV_KEY,button_dev.evbit); //支持EV_KEY事件set_bit(BTN_0,button_dev.keybit); //支持设备两个键set_bit(BTN_1,button_dev.keybit); //input_register_device(&button_dev);//注册input设备
}/*在按键中断中报告事件*/
Static void button_interrupt(int irq,void *dummy,struct pt_regs *fp)
{input_report_key(&button_dev,BTN_0,inb(BUTTON_PORT0));//读取寄存器BUTTON_PORT0的值input_report_key(&button_dev,BTN_1,inb(BUTTON_PORT1));input_sync(&button_dev);
}

总结:input子系统仍然是字符设备驱动程序,但是代码量减少很多,input子系统只需要完成两个工作:初始化和事件报告(这里在linux中是通过中断来实现的)。

关于事件报告的实现方法

中断实现 常用于实体按键

按键输入示例:


#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>#include <../arch/arm/mach-mx28/mx28_pins.h>#define DEVICE_NAME  "input_key"struct input_dev *inputdev;struct imx28x_key_struct {int key_code;  /* 按键能产生的键值*/int gpio;  /* 按键连接的 GPIO */struct work_struct work;  /* 按键的工作队列  */
};struct imx28x_key_struct keys_list[] ={{.key_code = KEY_A, .gpio = MXS_PIN_TO_GPIO(PINID_LCD_D17)},{.key_code = KEY_B, .gpio = MXS_PIN_TO_GPIO(PINID_LCD_D18)},{.key_code = KEY_C, .gpio = MXS_PIN_TO_GPIO(PINID_SSP0_DATA4)},{.key_code = KEY_D, .gpio = MXS_PIN_TO_GPIO(PINID_SSP0_DATA5)},{.key_code = KEY_E, .gpio = MXS_PIN_TO_GPIO(PINID_SSP0_DATA6)}
};static irqreturn_t imx28x_key_intnerrupt(int irq, void *dev_id, struct pt_regs *regs)
{int i = (int)dev_id;int gpio = keys_list[i].gpio; /* 获取按键的 GPIO */int code = keys_list[i].key_code; /* 获取按键的键值  *//** 延迟 20uS,看按键是不是按下,如果不是,就是抖动*/printk(KERN_INFO "%d was pressed",code);udelay(20);if (gpio_get_value(gpio)) {return IRQ_HANDLED;}input_report_key(inputdev, code, 1);  /* 先报告键按下事件 */input_sync(inputdev);schedule_work(&(keys_list[i].work));  /* 提交工作队列,实现中断的下半部处理 */return IRQ_HANDLED;
}static void imx28x_scankeypad(struct work_struct *_work)
{/* 通过工作队列指针而获得它所属的 imx28x_key_struct 类型的对象 */struct imx28x_key_struct *key_tmp = container_of(_work, struct imx28x_key_struct, work);int gpio = key_tmp->gpio;int code = key_tmp->key_code;/* 每隔 10mS 检查按键是否已经提起,如果没有提起就一直等待 */while(!gpio_get_value(gpio)){ mdelay(10); }input_report_key(inputdev, code, 0);  /* 报告按键提起事件 */input_sync(inputdev);
}static int __devinit iMX28x_key_init(void)
{int i = 0, ret = 0;int irq_no = 0;int code, gpio;inputdev = input_allocate_device(); /* 为输入设备驱动对象申请内存空间*/if (!inputdev) {return -ENOMEM;}inputdev->name = DEVICE_NAME;set_bit(EV_KEY, inputdev->evbit); /* 设置输入设备支持按键事件 */for (i = 0; i < sizeof(keys_list)/sizeof(keys_list[0]); i++) {code = keys_list[i].key_code;gpio = keys_list[i].gpio;/* 为每个按键都初始化工作队列  */INIT_WORK(&(keys_list[i].work), imx28x_scankeypad);set_bit(code, inputdev->keybit); /* 设置输入设备支持的键值  *//* 为每个按键都初始化 GPIO */gpio_free(gpio);ret = gpio_request(gpio, "key_gpio");if (ret) {printk("request gpio failed %d \n", gpio);return -EBUSY;}/* 当 GPIO 被设置为输入工作状态后,就可以检测中断信号 */gpio_direction_input(gpio);/* 把每个 GPIO 中断响应方式都设置为下降沿响应 */irq_no = gpio_to_irq(gpio);set_irq_type(gpio, IRQF_TRIGGER_FALLING);/* 为每个按键的中断都安装中断处理函数,其私有数据为按键信息在 keys_list 数组下的索引 */ret = request_irq(irq_no, imx28x_key_intnerrupt, IRQF_DISABLED, "imx28x_key", (void *)i);if (ret) {printk("request irq faile %d!\n", irq_no);return -EBUSY;}}input_register_device(inputdev); /* 注册设备驱动  */printk("EasyARM-i.MX28x key driver up \n");return 0;
}static void __exit iMX28x_key_exit(void)
{int i = 0;int irq_no;for (i = 0; i < sizeof(keys_list)/sizeof(keys_list[0]); i++) {irq_no = gpio_to_irq(keys_list[i].gpio); /* 为每个按键释放 GPIO */free_irq(irq_no, (void *)i); /* 为每个按键卸载中断处理函数 */}input_unregister_device(inputdev); /* 注销输入设备驱动 */printk(" key driver remove \n");
}module_init(iMX28x_key_init);
module_exit(iMX28x_key_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("YangYue");

内核定时器实现,用于没有触发信号的输入事件

例如没有触发的触摸屏事件。

动态 timer 由内核自身使用,其实也是其他 Timer 的实现基础。使用动态 Timer 的接口函数有三个:

add_timer()
del_timer()
init_timer()

使用时,先调用 init_timer() 初始化一个定时器,指定到期时间和到期处理函数;初始化完成后,内核代码可以用 add_timer() 启动定时器,或者用 del_timer() 来取消一个已经启动的定时器。
add_timer 采用时间轮算法将定时器加入 per CUP 变量 tvec_bases 中,根据其 expire 时间,可能被加入 5 个 Timer Vector 之一。此后,tick 中断将根据时间轮算法处理。当本 timer 到期时,触发其处理函数。
动态 Timer 有两个方面的用途:一是内核自己使用,比如某些驱动程序需要定时服务的时候使用它;二是用来实现用户层 Timer。下面首先讲解间隔 Timer。

比如要实现一个100ms的中断
可以这样设置:
ps2timer.expires=jiffies +HZ100/1000;
mod_timer(&ps2timer,jiffies+ HZ
100/1000 );

PS2驱动开发

硬件原理


DI/DAT:信号流向,从手柄到主机,此信号是一个 8bit 的串行数据,同步传送于时钟
的下降沿。信号的读取在时钟由高到低的变化过程中完成。
DO/CMD:信号流向,从主机到手柄,此信号和 DI 相对,信号是一个 8bit 的串行数据,
同步传送于时钟的下降沿。
NC:空端口;
GND:电源地;
VDD:接收器工作电源,电源范围 3~5V;
CS/SEL:用于提供手柄触发信号。在通讯期间,处于低电平;
CLK:时钟信号,由主机发出,用于保持数据同步;
NC:空端口;
ACK:从手柄到主机的应答信号。此信号在每个 8bits 数据发送的最后一个周期变低并
且 CS 一直保持低电平,如果 CS 信号不变低,约 60 微秒 PS 主机会试另一个外设。在编程
时未使用 ACK 端口。

软件实现

/** @Author: your name* @Date: 2021-04-11 18:25:16* @LastEditTime: 2021-04-11 23:46:33* @LastEditors: Please set LastEditors* @Description: In User Settings Edit* @FilePath: /DriverDefine/PS2/PS2.c*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/timer.h>#define DEVICE_NAME  "joystick"#define PSB_SELECT      1
#define PSB_L3          2
#define PSB_R3          3
#define PSB_START       4
#define PSB_PAD_UP      5
#define PSB_PAD_RIGHT   6
#define PSB_PAD_DOWN    7
#define PSB_PAD_LEFT    8
#define PSB_L2          9
#define PSB_R2          10
#define PSB_L1          11
#define PSB_R1          12
#define PSB_GREEN       13
#define PSB_RED         14
#define PSB_BLUE        15
#define PSB_PINK        16
#define PSB_TRIANGLE    13
#define PSB_CIRCLE      14
#define PSB_CROSS       15
#define PSB_SQUARE      16#define PSS_RX          5
#define PSS_RY          6
#define PSS_LX          7
#define PSS_LY          8static struct timer_list ps2timer;
u16 Handkey;
u8 Comd[2]={0x01,0x42};
u8 Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; //Êý¾Ý´æ´¢Êý×é
u16 MASK[]={PSB_SELECT,PSB_L3,PSB_R3 ,PSB_START,PSB_PAD_UP,PSB_PAD_RIGHT,PSB_PAD_DOWN,PSB_PAD_LEFT,PSB_L2,PSB_R2,PSB_L1,PSB_R1 ,PSB_GREEN,PSB_RED,PSB_BLUE,PSB_PINK
};  typedef struct
{int data_pin;int cmd_pin;int cs_pin;int clk_pin;
}ps2_handler;ps2_handler ps_define={2*32+5,2*32+7,2*32+13,2*32+15,
};#define DI      gpio_get_value(ps_define.data_pin)
#define DO_H    gpio_direction_output(ps_define.cmd_pin,1)
#define DO_L    gpio_direction_output(ps_define.cmd_pin,0)#define CS_H    gpio_direction_output(ps_define.cs_pin,1)
#define CS_L    gpio_direction_output(ps_define.cs_pin,0)    #define CLK_H   gpio_direction_output(ps_define.clk_pin,1)
#define CLK_L   gpio_direction_output(ps_define.clk_pin,0)   typedef struct
{int PHYKEY;int LOGICKEY;
}KEY_MAP;
KEY_MAP key_map[16]={{PSB_SELECT,    KEY_1},{PSB_L3,        KEY_A},{PSB_R3,        KEY_B},{PSB_START,     KEY_C},{PSB_PAD_UP,    KEY_A},{PSB_PAD_RIGHT, KEY_A},{PSB_PAD_DOWN,  KEY_A},{PSB_PAD_LEFT,  KEY_A},{PSB_L2,        KEY_1},{PSB_R2,        KEY_2},{PSB_L1,        KEY_3},{PSB_R1,        KEY_4},{PSB_GREEN,     KEY_A},{PSB_RED,       KEY_ENTER},{PSB_BLUE,      KEY_7},{PSB_PINK,      KEY_8},
};struct input_dev *inputdev;
void PS2_Cmd(u8 CMD)
{volatile u16 ref=0x01;Data[1] = 0;for(ref=0x01;ref<0x0100;ref<<=1){if(ref&CMD){DO_H;               }else DO_L;CLK_H;                     udelay(50);CLK_L;udelay(50);CLK_H;if(DI)Data[1] = ref|Data[1];}
}
void PS2_ReadData(void)
{volatile u8 byte=0;volatile u16 ref=0x01;CS_L;PS2_Cmd(Comd[0]); PS2_Cmd(Comd[1]);  for(byte=2;byte<9;byte++)        {for(ref=0x01;ref<0x100;ref<<=1){CLK_H;CLK_L;udelay(50);CLK_H;if(DI)Data[byte] = ref|Data[byte];}udelay(50);}CS_H;
}void PS2_ClearData()
{u8 a;for(a=0;a<9;a++)Data[a]=0x00;
}
u8 PS2_DataKey()
{u8 index;PS2_ClearData();PS2_ReadData();Handkey=(Data[4]<<8)|Data[3];    for(index=0;index<16;index++){       if((Handkey&(1<<(MASK[index]-1)))==0)return index+1;}return 0;
}
u8 PS2_AnologData(u8 button)
{return Data[button];
}
void PS2_ShortPoll(void)
{CS_L;udelay(16);PS2_Cmd(0x01);PS2_Cmd(0x42);PS2_Cmd(0X00);PS2_Cmd(0x00);PS2_Cmd(0x00);CS_H;udelay(16);
}void PS2_EnterConfing(void)
{CS_L;udelay(16);PS2_Cmd(0x01);PS2_Cmd(0x43);PS2_Cmd(0X00);PS2_Cmd(0x01);PS2_Cmd(0x00);PS2_Cmd(0X00);PS2_Cmd(0X00);PS2_Cmd(0X00);PS2_Cmd(0X00);CS_H;udelay(16);
}void PS2_TurnOnAnalogMode(void)
{CS_L;PS2_Cmd(0x01);PS2_Cmd(0x44);PS2_Cmd(0X00);PS2_Cmd(0x01);PS2_Cmd(0xEE); PS2_Cmd(0X00);PS2_Cmd(0X00);PS2_Cmd(0X00);PS2_Cmd(0X00);CS_H;udelay(16);
}void PS2_VibrationMode(void)
{CS_L;udelay(16);PS2_Cmd(0x01);PS2_Cmd(0x4D);PS2_Cmd(0X00);PS2_Cmd(0x00);PS2_Cmd(0X01);CS_H;udelay(16);
}void PS2_ExitConfing(void)
{CS_L;udelay(16);PS2_Cmd(0x01);PS2_Cmd(0x43);PS2_Cmd(0X00);PS2_Cmd(0x00);PS2_Cmd(0x5A);PS2_Cmd(0x5A);PS2_Cmd(0x5A);PS2_Cmd(0x5A);PS2_Cmd(0x5A);CS_H;udelay(16);
}void PS2_SetInit(void)
{PS2_ShortPoll();PS2_ShortPoll();PS2_ShortPoll();PS2_EnterConfing(); PS2_TurnOnAnalogMode(); PS2_VibrationMode(); PS2_ExitConfing();
}void PS2_Vibration(u8 motor1, u8 motor2)
{CS_L;udelay(16);PS2_Cmd(0x01); PS2_Cmd(0x42); PS2_Cmd(0X00);PS2_Cmd(motor1);PS2_Cmd(motor2);PS2_Cmd(0X00);PS2_Cmd(0X00);PS2_Cmd(0X00);PS2_Cmd(0X00);CS_H;udelay(16);
}void ps2_timer_function(unsigned long data)
{u8 val;val=PS2_DataKey();printk("%d \t\n",val);if(val==0){input_event(inputdev,EV_KEY,key_map[val].LOGICKEY, 0);  //上报EV_KEY类型,button按键,0(没按下)input_sync(inputdev);}else{input_event(inputdev,EV_KEY,key_map[val].LOGICKEY, 1);  //上报EV_KEY类型,button按键,0(没按下)input_event(inputdev,EV_REL,PS2_AnologData(), 1); input_sync(inputdev);        //printk("%d \t\n",key_map[val].LOGICKEY);}mod_timer(&ps2timer,jiffies+ HZ*100/1000  );
}static int PS2_init(void)
{int i = 0;inputdev = input_allocate_device(); /* 为输入设备驱动对象申请内存空间*/if (!inputdev) {return -ENOMEM;}inputdev->name = DEVICE_NAME;set_bit(EV_KEY, inputdev->evbit); /* 设置输入设备支持按键事件 */set_bit(EV_REL, inputdev->evbit); set_bit(KEY_A,inputdev->keybit);                  //支持按键 Lset_bit(KEY_B,inputdev->keybit);                //支持按键 Sset_bit(KEY_C,inputdev->keybit);      //支持按键 空格set_bit(KEY_ENTER,inputdev->keybit);//GPIOgpio_request(ps_define.data_pin, "data");gpio_direction_input(ps_define.data_pin);gpio_request(ps_define.cs_pin, "cs");gpio_direction_output(ps_define.cs_pin,0); gpio_request(ps_define.clk_pin, "clk");gpio_direction_output(ps_define.clk_pin,0);gpio_request(ps_define.cmd_pin, "cmd");gpio_direction_output(ps_define.cmd_pin,0);PS2_SetInit();input_register_device(inputdev); /* 注册设备驱动  */init_timer(&ps2timer);ps2timer.function=ps2_timer_function;ps2timer.expires=jiffies +HZ*100/1000;add_timer(&ps2timer);printk("YURI PS2 driver up \n");return 0;
}static void PS2_exit(void)
{del_timer(&ps2timer);input_unregister_device(inputdev); /* 注销输入设备驱动 */input_free_device(inputdev);printk("PS2 driver remove \n");
}module_init(PS2_init);
module_exit(PS2_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("YURI YANG");

【Linux驱动开发】PS2游戏手柄驱动开发与输入子系统框架相关推荐

  1. 4. Linux - 输入子系统框架详解

    输入子系统概述 Linux内核为了能够处理各种不同类型的输入设备,比如 触摸屏 ,鼠标 , 键盘 , 操纵杆 ,设计并实现了为驱动层程序的实现提供统一接口函数:为上层应用提供试图统一的抽象层 , 即是 ...

  2. linux GPIO模拟PS2 键盘驱动

    背景:公司有一个PS2键盘驱动的项目,没有控制器,需要模拟PS2协议,检测按键并通过input子系统将按键时间上报 一.准备: 1.PS2协议: PS2有两个控制线,时钟线和数据线.当按键按下或抬起, ...

  3. Linux输入子系统框架

    输入子系统 自己写的驱动程序,自己可以调用,我们自己写驱动的流程一般是,建立fops结构,使用register_chrdev在初始化函数中进行注册,在应用中使用open函数打开该设备.这种驱动不标准只 ...

  4. linux输入子系统之按键驱动

    上一节中,我们讲解了Linux  input子系统的框架,到内核源码里详细分析了输入子系统的分离分层的框架等. 上一节文章链接:http://blog.csdn.net/lwj103862095/ar ...

  5. 嵌入式Linux驱动笔记(二十五)------Input子系统框架

    你好!这里是风筝的博客, 欢迎和我一起交流. 一.Input子系统概述 二.Input子系统架构 三.Input子系统工作机制 3.1 核心层(input.c) 3.1.1 input_init函数 ...

  6. 基于GD32VF103 的vga显示器 和ps2键盘 驱动

    基于GD32VF103的vga和ps2键盘驱动 前言 gd32vf103 是国内一款很不错的riscv架构微处理器,但是网上gd32vf103的应用还比较少,这里我决定分享一下利用这个微处理器制作的v ...

  7. 7.Linux 输入子系统分析

    为什么要引入输入子系统? 在前面我们写了一些简单的字符设备的驱动程序,我们是怎么样打开一个设备并操作的呢? 一般都是在执行应用程序时,open一个特定的设备文件,如:/dev/buttons 1 .. ...

  8. linux input输入子系统分析《一》:初识input输入子系统

    主要讲述本人在学习Linux内核input子系统的全部过程,如有分析不当,多谢指正.以下交流方式,文章欢迎转载,保留联系信息,以便交流. 邮箱:eabi010@gmail.com 主页:www.iel ...

  9. 137.Linux输入子系统基本概念

    文章目录 0.前言 1.简介 2.框架 3.输入子系统核心层 4.输入子系统驱动编写 4.1 基本变量 4.2 input_dev的注册 4.2.1 设置事件值的方法代码 4.3 上报输入事件 4.4 ...

  10. linux input输入子系统分析《四》:input子系统整体流程全面分析

    1      input输入子系统整体流程 本节分析input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层.由于上节代码讲解了设备 ...

最新文章

  1. 阳江市2021高考成绩查询一下,广东省阳江市2021年高级会计师考试结束后去哪查成绩?...
  2. Analysis of Web.xml in Hello1 project
  3. fuzzy k means
  4. 设计模式之--原型模式
  5. 文本获取和搜索引擎中的反馈模型
  6. 选择checkbox显示隐藏内容
  7. 黄金分割Android程序,黄金分割法程序.doc
  8. c语言 char作用,c语言中char型数据能直接运算吗?
  9. Redis的攻击手法
  10. MySQL sum()函数
  11. Window下python安装metis
  12. 盘点8款流行的网红纱帘,以及它们的特点 - 江南爱窗帘十大品牌
  13. T120624 哪有那么花里胡哨这题就叫徐悦然哈哈哈
  14. 舔狗舔到最后一无所有 (线性dp)
  15. 直播视频app源码,自定义可点击可滑动的通用RatingBar
  16. 用matlab模拟机械运动
  17. 计算机会计知识竞赛,全国企业会计信息化知识竞赛试题及答案
  18. The 19th Zhejiang Provincial Collegiate Programming Contest(部分题解)
  19. Hutool---Excel
  20. 一款Android图片预览的开源库,几乎百分百还原微信的图片预览。

热门文章

  1. Android开发环境
  2. 修改窗口的风格ModifyStyle
  3. Java设计模式——简单工厂模式
  4. 来客推电商快报之教育产业的未来发展
  5. 海尔计算机显卡驱动,海尔t621显卡驱动(最新更新)海尔t68d显卡驱动
  6. 飞信2009_AfterShip CTO 洪小军—我的移动互联网十年造梦记:飞信时代
  7. 计算机vfp知识要点,2017年职称计算机VisualFoxPro基础知识:表的基本操作
  8. 【JS】经典的对联广告代码,JS代码实现
  9. 工厂模式(Factory Method)
  10. 4 curses库基垫(pad)处理