input子系统分析

  1. 一、input子系统概述  
  2. 在linux下,按键、触摸屏、鼠标等都可以利用input接口函数来实现设备驱动。  
  3. 1,linux输入子系统主要分三层: 驱动,输入CORE, 事件处理层。  
  4. 驱动根据CORE提供的接口,向上报告发生的按键动作。然后CORE根据驱动的类型,分派这个报告给对应的事件处理层进行处事。  
  5. 事件处理层把数据变化反应到设备模型的文件中(事件缓冲区)。并通知在这些设备模型文件上等待的进程。  
  6.   
  7. 2,输入子系统在KERNEL初始化时被初始化。会创建所有类型输入输出设备的逻辑设备(及sysfs结点)。当硬件注册时,就会调用所有类型的input handler的connect函数,根据硬件注册的结构来判断是否与自己相关,然后再创建一个具体的设备结点。  
  8.   
  9. 3,驱动只负责的把输入设备注册到输入子系统中,然后输入子系统来创建对应的具体设备结点。而事件处理层,在初始化时,需要注册所一类设备的输入事件处理函数及相关接口  
  10.   
  11. 4,一类input handler可以和多个硬件设备相关联,创建多个设备节点。而一个设备也可能与多个input handler相关联,创建多个设备节点。  
  12.   
  13. 二.主要input通用数据结构  
  14. 1.input_dev 这是input设备基本的设备结构,每个input驱动程序中都必须分配初始化这样一个结构,成员比较多   
  15. struct input_dev {   
  16.     const char *name;   
  17.     const char *phys;   
  18.     const char *uniq;   
  19.     struct input_id id;//与input_handler匹配用的id,包括   
  20.     /*struct input_id {  
  21.      __u16 bustype; //总线类型  
  22.      __u16 vendor; //生产厂商  
  23.      __u16 product; //产品类型  
  24.      __u16 version; //版本  
  25.          }; */  
  26.          /* 
  27.       #define EV_SYN 0x00 //同步事件 
  28.     #define EV_KEY 0x01 //绝对二进制值,如键盘或按钮 
  29.     #define EV_REL 0x02 //绝对结果,如鼠标设备 
  30.     #define EV_ABS 0x03 //绝对整数值,如操纵杆或书写板 
  31.     #define EV_MSC 0x04 //其它类 
  32.     #define EV_SW 0x05 //开关事件 
  33.     #define EV_LED 0x11 //LED或其它指示设备 
  34.     #define EV_SND 0x12 //声音输出,如蜂鸣器 
  35.     #define EV_REP 0x14 //允许按键自重复 
  36.     #define EV_FF 0x15 //力反馈 
  37.     #define EV_PWR 0x16 //电源管理事件 
  38.     */  
  39.     unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //设备支持的事件类型如上   
  40.     unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //按键事件支持的子事件类型   
  41.     unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //相对坐标事件支持的子事件类型   
  42.     unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //绝对坐标事件支持的子事件类型   
  43.     unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];   
  44.     unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];   
  45.     unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];   
  46.     unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];   
  47.     unsigned long swbit[BITS_TO_LONGS(SW_CNT)];   
  48.     
  49.     unsigned int keycodemax;   
  50.     unsigned int keycodesize;   
  51.     void *keycode;   
  52.     int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);   
  53.     int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);   
  54.     
  55.     struct ff_device *ff;   
  56.     
  57.     unsigned int repeat_key; //最近一次的按键值   
  58.     struct timer_list timer;   
  59.     
  60.     int sync;   
  61.     
  62.     int abs[ABS_MAX + 1];   
  63.     int rep[REP_MAX + 1];   
  64.     
  65.     unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反应设备当前的按键状态   
  66.     unsigned long led[BITS_TO_LONGS(LED_CNT)];//反应设备当前的led状态   
  67.     unsigned long snd[BITS_TO_LONGS(SND_CNT)];//反应设备当前的声音输入状态   
  68.     unsigned long sw[BITS_TO_LONGS(SW_CNT)]; //反应设备当前的开关状态   
  69.     
  70.     int absmax[ABS_MAX + 1];//来自绝对坐标事件的最大键值   
  71.     int absmin[ABS_MAX + 1];//来自绝对坐标事件的最小键值   
  72.     int absfuzz[ABS_MAX + 1];   
  73.     int absflat[ABS_MAX + 1];   
  74.     
  75.     int (*open)(struct input_dev *dev); //第一次打开设备时调用,初始化设备用   
  76.     void (*close)(struct input_dev *dev);//最后一个应用程序释放设备时用,关闭设备   
  77.     int (*flush)(struct input_dev *dev, struct file *file);   
  78.     /*用于处理传递给设备的事件,如LED事件和声音事件*/   
  79.     int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);   
  80.     
  81.     struct input_handle *grab;//当前占有该设备的input_handle   
  82.     
  83.     spinlock_t event_lock;   
  84.     struct mutex mutex;   
  85.     
  86.     unsigned int users;//打开该设备的用户数量(input handlers)   
  87.     int going_away;   
  88.     
  89.     struct device dev;   
  90.     
  91.     struct list_head h_list;//该链表头用于链接此设备所关联的input_handle   
  92.     struct list_head node; //用于将此设备链接到input_dev_list   
  93. }  
  94.   
  95. 2. input_handler 这是事件处理器的数据结构,代表一个事件处理器  
  96. struct input_handler {   
  97.     void *private;   
  98.     /*event用于处理事件*/   
  99.     void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);   
  100.     /*connect用于建立handler和device的联系*/   
  101.     int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);   
  102.     /*disconnect用于解除handler和device的联系*/   
  103.     void (*disconnect)(struct input_handle *handle);   
  104.     void (*start)(struct input_handle *handle);   
  105.     const struct file_operations *fops;//handler的一些处理函数   
  106.     int minor;//次设备号   
  107.     const char *name;   
  108.     const struct input_device_id *id_table;//用于和device匹配 ,这个是事件处理器所支持的input设备  
  109.     const struct input_device_id *blacklist;//匹配黑名单,这个是事件处理器应该忽略的input设备   
  110.     struct list_head h_list;//这个链表用来链接他所支持的input_handle结构,input_dev与input_handler配对之后就会生成一个input_handle结构   
  111.     struct list_head node; //链接到input_handler_list,这个链表链接了所有注册到内核的事件处理器   
  112. };   
  113.   
  114. 3.input_handle 结构体代表一个成功配对的input_dev和input_handler  
  115. struct input_handle {   
  116.     void *private//每个配对的事件处理器都会分配一个对应的设备结构,如evdev事件处理器的evdev结构,注意这个结构与设备驱动层的input_dev不同,初始化handle时,保存到这里。   
  117.     int open; //打开标志,每个input_handle 打开后才能操作,这个一般通过事件处理器的open方法间接设置   
  118.     const char *name;   
  119.     struct input_dev *dev; //关联的input_dev结构   
  120.     struct input_handler *handler; //关联的input_handler结构   
  121.     struct list_head d_node; //input_handle通过d_node连接到了input_dev上的h_list链表上   
  122.     struct list_head h_node; //input_handle通过h_node连接到了input_handler的h_list链表上   
  123. };   
  124.   
  125. 4.三个数据结构之间的关系  
  126. input_dev 是硬件驱动层,代表一个input设备  
  127. input_handler 是事件处理层,代表一个事件处理器  
  128. input_handle 个人认为属于核心层,代表一个配对的input设备与input事件处理器  
  129. input_dev 通过全局的input_dev_list链接在一起。设备注册的时候实现这个操作。  
  130. input_handler 通过全局的input_handler_list链接在一起。事件处理器注册的时候实现这个操作(事件处理器一般内核自带,一般不需要我们来写)  
  131. input_hande 没有一个全局的链表,它注册的时候将自己分别挂在了input_dev 和 input_handler 的h_list上了。通过input_dev 和input_handler就可以找到input_handle在设备注册和事件处理器,注册的时候都要进行配对工作,配对后就会实现链接。通过input_handle也可以找到input_dev和input_handler。  
  132.        
  133. 我们可以看到,input_device和input_handler中都有一个h_list,而input_handle拥有指向input_dev和input_handler的指针,也就是说input_handle是用来关联input_dev和input_handler的。  
  134. 那么为什么一个input_device和input_handler中拥有的是h_list而不是一个handle呢?因为一个device可能对应多个handler,而一个handler也不能只处理一个device,比如说一个鼠标,它可以对应even handler,也可以对应mouse handler,因此当其注册时与系统中的handler进行匹配,就有可能产生两个实例,一个是evdev,另一个是mousedev,而任何一个实例中都只有一个handle。至于以何种方式来传递事件,就由用户程序打开哪个实例来决定。后面一个情况很容易理解,一个事件驱动不能只为一个甚至一种设备服务,系统中可能有多种设备都能使用这类handler,比如event handler就可以匹配所有的设备。在input子系统中,有8种事件驱动,每种事件驱动最多可以对应32个设备,因此dev实例总数最多可以达到256个。  
  135.   
  136.   
  137. 三、输入子系统驱动层分析(以tps6507x为例)  
  138. 1.platform device的注册   
  139.   
  140. 2.platform driver注册  
  141. static struct platform_driver tps6507x_ts_driver = {  
  142.     .driver = {  
  143.         .name = "tps6507x-ts",  
  144.         .owner = THIS_MODULE,  
  145.     },  
  146.     .probe = tps6507x_ts_probe,  
  147.     .remove = __devexit_p(tps6507x_ts_remove),  
  148. };  
  149.   
  150. //tps6507x触摸屏封装的设备结构  
  151. struct tps6507x_ts {  
  152.     struct input_dev *input_dev;  
  153.     struct device *dev;  
  154.     char phys[32];  
  155.     struct delayed_work work;  
  156.     unsigned polling; /* polling is active */  
  157.     struct ts_event tc;  
  158.     struct tps6507x_dev *mfd;  
  159.     u16 model;  
  160.     unsigned pendown;  
  161.     int irq;  
  162.     void (*clear_penirq)(void);  
  163.     unsigned long poll_period; /* ms */  
  164.     u16 min_pressure;  
  165.     int vref; /* non-zero to leave vref on */  
  166. };  
  167.    
  168. static int __init tps6507x_ts_init(void)  
  169. {  
  170.     return platform_driver_register(&tps6507x_ts_driver);  
  171. }  
  172.   
  173. //与platform device匹配成功后会调用tps6507x_ts_probe  
  174. static int tps6507x_ts_probe(struct platform_device *pdev)  
  175. {  
  176.     int error;  
  177.     struct tps6507x_ts *tsc;  
  178.     struct tps6507x_dev *tps6507x_dev = dev_get_drvdata(pdev->dev.parent);  
  179.     struct touchscreen_init_data *init_data;  
  180.     struct input_dev *input_dev;  
  181.     struct tps6507x_board *tps_board;  
  182.     int schd;  
  183.   
  184.     //找到tps6507x platform data  
  185.     tps_board = (struct tps6507x_board *)tps6507x_dev->dev->platform_data;  
  186.   
  187.     if (!tps_board) {  
  188.         dev_err(tps6507x_dev->dev,"Could not find tps6507x platform datan");  
  189.         return -EIO;  
  190.     }  
  191.   
  192.     //得到触摸屏的一些初始化信息,如厂商信息等  
  193.     init_data = tps_board->tps6507x_ts_init_data;  
  194.       
  195.     //分配tps6507x_ts结构体  
  196.     tsc = kzalloc(sizeof(struct tps6507x_ts), GFP_KERNEL);  
  197.     if (!tsc) {  
  198.         dev_err(tps6507x_dev->dev, "failed to allocate driver datan");  
  199.         error = -ENOMEM;  
  200.         goto err0;  
  201.     }  
  202.   
  203.     tps6507x_dev->ts = tsc;  
  204.     tsc->mfd = tps6507x_dev;  
  205.     tsc->dev = tps6507x_dev->dev;  
  206.       
  207.     //分配一个input_dev接口,并初始化一些基本的成员  
  208.     input_dev = input_allocate_device();  
  209.     if (!input_dev) {  
  210.         dev_err(tsc->dev, "Failed to allocate input device.n");  
  211.         error = -ENOMEM;  
  212.         goto err1;  
  213.     }  
  214.       
  215.     input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);//设备支持的事件类型为按键事件和绝对坐标事件   
  216.     input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);//按键事件支持的子事件类型   
  217.       
  218.     //MAX_10BIT=0x3FF,设置ABS_X编码值范围为0-0x3ff,因为mini2440的AD转换出的数据最大为10位,所以不会超过0x3ff  
  219.     input_set_abs_params(input_dev, ABS_X, 0, MAX_10BIT, 0, 0);//这个是设置ad转换的x坐标  
  220.     input_set_abs_params(input_dev, ABS_Y, 0, MAX_10BIT, 0, 0);//这个是设置ad转换的y坐标  
  221.     input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_10BIT, 0, 0);//这个是设置触摸屏是否按下的标志  
  222.       
  223.     input_dev->name = "TPS6507x Touchscreen";  
  224.     input_dev->id.bustype = BUS_I2C;//总线类型是I2C  
  225.     input_dev->dev.parent = tsc->dev;//父设备  
  226.   
  227.     snprintf(tsc->phys, sizeof(tsc->phys),"%s/input0", dev_name(tsc->dev));  
  228.     input_dev->phys = tsc->phys;  
  229.   
  230.     dev_dbg(tsc->dev, "device: %sn", input_dev->phys);  
  231.   
  232.     input_set_drvdata(input_dev, tsc);//保存tsc结构到input_dev中  
  233.     tsc->input_dev = input_dev;//tsc结构指向初始化过的input_dev设备  
  234.   
  235.     INIT_DELAYED_WORK(&tsc->work, tps6507x_ts_handler);  
  236.     tsc->wq = create_workqueue("TPS6507x Touchscreen");  
  237.   
  238.     if (init_data) {  
  239.         tsc->poll_period = init_data->poll_period;//触摸屏采样时间30ms  
  240.         tsc->vref = init_data->vref;//turn off vref when not using A/D  
  241.         tsc->min_pressure = init_data->min_pressure;//触摸屏最小压力0x30  
  242.         input_dev->id.vendor = init_data->vendor;//0  
  243.         input_dev->id.product = init_data->product;//65070  
  244.         input_dev->id.version = init_data->version;//0x100  
  245.     } else {  
  246.         tsc->poll_period = TSC_DEFAULT_POLL_PERIOD;  
  247.         tsc->min_pressure = TPS_DEFAULT_MIN_PRESSURE;  
  248.     }  
  249.       
  250.     //设置设备standby状态  
  251.     error = tps6507x_adc_standby(tsc);  
  252.     if (error)  
  253.         goto err2;  
  254.       
  255.     //注册一个input设备  
  256.     error = input_register_device(input_dev);  
  257.     if (error)  
  258.         goto err2;  
  259.   
  260.     schd = queue_delayed_work(tsc->wq, &tsc->work,msecs_to_jiffies(tsc->poll_period));  
  261.   
  262.     if (schd)  
  263.         tsc->polling = 1;  
  264.     else {  
  265.         tsc->polling = 0;  
  266.         dev_err(tsc->dev, "schedule failed");  
  267.         goto err2;  
  268.      }  
  269.     platform_set_drvdata(pdev, tps6507x_dev);  
  270.   
  271.     return 0;  
  272.   
  273. err2:  
  274.     cancel_delayed_work_sync(&tsc->work);  
  275.     destroy_workqueue(tsc->wq);  
  276.     input_free_device(input_dev);  
  277. err1:  
  278.     kfree(tsc);  
  279.     tps6507x_dev->ts = NULL;  
  280. err0:  
  281.     return error;  
  282. }  
  283.   
  284. int input_register_device(struct input_dev *dev)  
  285. {  
  286.     //这个原子变量,代表总共注册的input设备,每注册一个加1,因为是静态变量,所以每次调用都不会清零的   
  287.     static atomic_t input_no = ATOMIC_INIT(0);  
  288.     struct input_handler *handler;  
  289.     const char *path;  
  290.     int error;  
  291.   
  292.     //EN_SYN这个是设备都要支持的事件类型,所以要设置   
  293.     __set_bit(EV_SYN, dev->evbit);  
  294.   
  295.     /* KEY_RESERVED is not supposed to be transmitted to userspace. */  
  296.     __clear_bit(KEY_RESERVED, dev->keybit);  
  297.   
  298.     /* Make sure that bitmasks not mentioned in dev->evbit are clean. */  
  299.     input_cleanse_bitmasks(dev);  
  300.   
  301.     //这个内核定时器是为了重复按键而设置的  
  302.     //rep主要是处理重复按键,如果没有定义dev->rep[REP_DELAY]和dev->rep[REP_PERIOD],   
  303.   //则将其赋值为默认值。dev->rep[REP_DELAY]是指第一次按下多久算一次,这里是250ms,   
  304.   //dev->rep[REP_PERIOD]指如果按键没有被抬起,每33ms算一次。   
  305.     init_timer(&dev->timer);  
  306.     if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {  
  307.         dev->timer.data = (long) dev;  
  308.         dev->timer.function = input_repeat_key;  
  309.         dev->rep[REP_DELAY] = 250;  
  310.         dev->rep[REP_PERIOD] = 33;  
  311.         //如果没有定义有关重复按键的相关值,就用内核默认的   
  312.     }  
  313.       
  314.     /*如果dev没有定义getkeycode和setkeycode,则赋默认值。他们的作用一个是获得键的扫描码,一个是设置键的扫描码*/  
  315.     if (!dev->getkeycode && !dev->getkeycode_new)  
  316.         dev->getkeycode_new = input_default_getkeycode;  
  317.   
  318.     if (!dev->setkeycode && !dev->setkeycode_new)  
  319.         dev->setkeycode_new = input_default_setkeycode;  
  320.       
  321.     //设置input_dev中device的名字,这个名字会在/class/input中出现   
  322.     dev_set_name(&dev->dev, "input%ld",(unsigned long) atomic_inc_return(&input_no) - 1);  
  323.   
  324.     error = device_add(&dev->dev);//添加input设备,注册到linux设备模型中,生成一系列的sys相关文件,udev会根据dev文件生成设备节点  
  325.     if (error)  
  326.         return error;  
  327.   
  328.     path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);  
  329.     printk(KERN_INFO "input: %s as %sn",dev->name ? dev->name : "Unspecified device", path ? path : "N/A");  
  330.     kfree(path);  
  331.   
  332.     error = mutex_lock_interruptible(&input_mutex);  
  333.     if (error) {  
  334.         device_del(&dev->dev);  
  335.         return error;  
  336.     }  
  337.   
  338.     list_add_tail(&dev->node, &input_dev_list);//将新分配的input设备连接到input_dev_list链表上   
  339.   
  340.     list_for_each_entry(handler, &input_handler_list, node)//遍历input_handler_list链表,配对input_dev和input_handler  
  341.         input_attach_handler(dev, handler);  
  342.   
  343.     input_wakeup_procfs_readers();//与proc文件系统有关  
  344.   
  345.     mutex_unlock(&input_mutex);  
  346.   
  347.     return 0;  
  348. }  
  349.   
  350. 四、输入子系统核心分析  
  351. static const struct file_operations input_fops = {  
  352.     .owner = THIS_MODULE,  
  353.     .open = input_open_file,  
  354.     .llseek = noop_llseek,  
  355. };  
  356.   
  357. struct class input_class = {  
  358.     .name        = "input",  
  359.     .devnode    = input_devnode,  
  360. };  
  361.   
  362. static int __init input_init(void)  
  363. {  
  364.     int err;  
  365.     //向内核注册一个类,用于linux设备模型。注册后会在/sys/class下面出现input目录   
  366.     err = class_register(&input_class);  
  367.     if (err) {  
  368.         printk(KERN_ERR "input: unable to register input_dev classn");  
  369.         return err;  
  370.     }  
  371.       
  372.     //和proc文件系统有关,暂时不管   
  373.     err = input_proc_init();  
  374.     if (err)  
  375.         goto fail1;  
  376.       
  377.     //注册字符设备,以主设备号INPUT_MAJOR,次设备号0-255,注册266个设备,说明input设备最大只能有255个   
  378.     err = register_chrdev(INPUT_MAJOR, "input", &input_fops);  
  379.     if (err) {  
  380.         printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);  
  381.         goto fail2;  
  382.     }  
  383.   
  384.     return 0;  
  385.   
  386.  fail2:    input_proc_exit();  
  387.  fail1:    class_unregister(&input_class);  
  388.     return err;  
  389. }  
  390.   
  391.   
  392. 五. 事件处理层分析(以evdev事件处理器为例)  
  393. 1.主要数据结构  
  394. (1) evdev设备结构  
  395. struct evdev {   
  396.     int exist;   
  397.     int open; //打开标志   
  398.     int minor; //次设备号   
  399.     struct input_handle handle; //关联的input_handle   
  400.     wait_queue_head_t wait; //等待队列,当进程读取设备,而没有事件产生的时候,进程就会睡在其上面   
  401.     struct evdev_client *grab; //强制绑定的evdev_client结构,这个结构后面再分析   
  402.     struct list_head client_list; //evdev_client 链表,这说明一个evdev设备可以处理多个evdev_client,可以有多个进程访问evdev设备   
  403.     spinlock_t client_lock; /* protects client_list */   
  404.     struct mutex mutex;   
  405.     struct device dev; //device结构,说明这是一个设备结构   
  406. };   
  407. //evdev结构体在配对成功的时候生成,由handler->connect生成,对应设备文件为/class/input/event(n),如触摸屏驱动的event0,这个设备是用户空间要访问的设备,可以理解它是一个虚拟设备,因为没有对应的硬件,但是通过handle->dev 就可以找到input_dev结构,而它对应着触摸屏,设备文件为/class/input/input0。这个设备结构生成之后保存在evdev_table中,索引值是minor。  
  408.   
  409. (2)evdev用户端结构  
  410. struct evdev_client {   
  411.     struct input_event buffer[EVDEV_BUFFER_SIZE];//这个是一个input_event数据结构的数组,input_event代表一个事件,基本成员:类型(type),编码(code),值(value)   
  412.     int head; //针对buffer数组的索引   
  413.     int tail; //针对buffer数组的索引,当head与tail相等的时候,说明没有事件   
  414.     spinlock_t buffer_lock; /* protects access to buffer, head and tail */   
  415.     struct fasync_struct *fasync; //异步通知函数   
  416.     struct evdev *evdev; //evdev设备   
  417.     struct list_head node; // evdev_client 链表项   
  418. };   
  419. //这个结构在进程打开event0设备的时候调用evdev的open方法,在open中创建这个结构,并初始化。在关闭设备文件的时候释放这个结构。  
  420.   
  421. (3)input_event结构  
  422. struct input_event {   
  423.     struct timeval time; //事件发生的时间   
  424.     __u16 type; //事件类型   
  425.     __u16 code; //子事件   
  426.     __s32 value; //事件的value   
  427. };   
  428.   
  429. 2.事件处理层与用户程序和输入子系统核心打交道,是他们两层的桥梁。一般内核有好几个事件处理器,像evdev mousedev jotdev。evdev事件处理器可以处理所有的事件,触摸屏驱动就是用的这个,所以下面分析这个事件处理器的实现。它也是作为模块注册到内核中的,首先分析它的模块初始化函数。  
  430. static const struct file_operations evdev_fops = {  
  431.     .owner        = THIS_MODULE,  
  432.     .read        = evdev_read,  
  433.     .write        = evdev_write,  
  434.     .poll        = evdev_poll,  
  435.     .open        = evdev_open,  
  436.     .release    = evdev_release,  
  437.     .unlocked_ioctl    = evdev_ioctl,  
  438. #ifdef CONFIG_COMPAT  
  439.     .compat_ioctl    = evdev_ioctl_compat,  
  440. #endif  
  441.     .fasync        = evdev_fasync,  
  442.     .flush        = evdev_flush,  
  443.     .llseek        = no_llseek,  
  444. };  
  445.   
  446. static const struct input_device_id evdev_ids[] = {  
  447.     { .driver_info = 1 },    //适合所有的类型的设备  
  448.     { },            /* Terminating zero entry */  
  449. };  
  450.   
  451. static struct input_handler evdev_handler = {  
  452.     .event        = evdev_event, //向系统报告input事件,系统通过read方法读取  
  453.     .connect    = evdev_connect,//和input_dev匹配后调用connect构建  
  454.     .disconnect    = evdev_disconnect,  
  455.     .fops        = &evdev_fops,//event设备文件的操作方法  
  456.     .minor        = EVDEV_MINOR_BASE,//次设备号基准值  
  457.     .name        = "evdev",  
  458.     .id_table    = evdev_ids,//匹配规则  
  459. };  
  460.   
  461. static int __init evdev_init(void)  
  462. {  
  463.     //模块初始化函数就调用一个注册handler函数,将evdev_handler注册到系统中。  
  464.     return input_register_handler(&evdev_handler);  
  465. }  
  466.   
  467. int input_register_handler(struct input_handler *handler)  
  468. {  
  469.     struct input_dev *dev;  
  470.     int retval;  
  471.   
  472.     retval = mutex_lock_interruptible(&input_mutex);  
  473.     if (retval)  
  474.         return retval;  
  475.   
  476.     INIT_LIST_HEAD(&handler->h_list);  
  477.   
  478.     if (handler->fops != NULL) {  
  479.         if (input_table[handler->minor >> 5]) {  
  480.             retval = -EBUSY;  
  481.             goto out;  
  482.         }  
  483.         input_table[handler->minor >> 5] = handler;//添加到全局数组中  
  484.         //input_table,每个注册的handler都会将自己保存到这里,索引值为handler->minor右移5为,也就是除以32   
  485.     //为什么会这样呢,因为每个handler都会处理最大32个input_dev,所以要以minor的32为倍数对齐,这个minor是传进来的handler的MINOR_BASE   
  486.     //每一个handler都有一个这一个MINOR_BASE,以evdev为例,EVDEV_MINOR_BASE = 64,可以看出系统总共可以注册8个handler   
  487.     }  
  488.       
  489.     //连接到input_handler_list链表中  
  490.     list_add_tail(&handler->node, &input_handler_list);  
  491.   
  492.     list_for_each_entry(dev, &input_dev_list, node)//配对,遍历input_dev_list链表中的input_dev设备,与对应的input_handler结构配对,和注册input_dev过程一样的  
  493.         input_attach_handler(dev, handler);   
  494.   
  495.     input_wakeup_procfs_readers();//与proc文件系统有关  
  496.   
  497.  out:  
  498.     mutex_unlock(&input_mutex);  
  499.     return retval;  
  500. }  
  501.   
  502. static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)  
  503. {  
  504.     const struct input_device_id *id;  
  505.     int error;  
  506.   
  507.     id = input_match_device(handler, dev);//这个是主要的配对函数,主要比较id中的各项  
  508.     if (!id)  
  509.         return -ENODEV;  
  510.       
  511.     //配对成功调用handler的connect函数,这个函数在事件处理器中定义,主要生成一个input_handle结构,并初始化,还生成一个事件处理器相关的设备结构,  
  512.     error = handler->connect(handler, dev, id);//调用evdev_connect  
  513.     if (error && error != -ENODEV)  
  514.         printk(KERN_ERR"input: failed to attach handler %s to device %s, ""error: %dn",handler->name, kobject_name(&dev->dev.kobj), error);  
  515.   
  516.     return error;  
  517. }  
  518.   
  519. static const struct input_device_id *input_match_device(struct input_handler *handler,struct input_dev *dev)  
  520. {  
  521.     const struct input_device_id *id;  
  522.     int i;  
  523.   
  524.     for (id = handler->id_table; id->flags || id->driver_info; id++) {//id->driver_info=1,表示可以配对所有  
  525.   
  526.         if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)  
  527.             if (id->bustype != dev->id.bustype)  
  528.                 continue;  
  529.   
  530.         if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)  
  531.             if (id->vendor != dev->id.vendor)  
  532.                 continue;  
  533.   
  534.         if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)  
  535.             if (id->product != dev->id.product)  
  536.                 continue;  
  537.   
  538.         if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)  
  539.             if (id->version != dev->id.version)  
  540.                 continue;  
  541.           
  542.         //配对成功,进入下面的宏  
  543.         MATCH_BIT(evbit, EV_MAX);  
  544.         MATCH_BIT(keybit, KEY_MAX);  
  545.         MATCH_BIT(relbit, REL_MAX);  
  546.         MATCH_BIT(absbit, ABS_MAX);  
  547.         MATCH_BIT(mscbit, MSC_MAX);  
  548.         MATCH_BIT(ledbit, LED_MAX);  
  549.         MATCH_BIT(sndbit, SND_MAX);  
  550.         MATCH_BIT(ffbit, FF_MAX);  
  551.         MATCH_BIT(swbit, SW_MAX);  
  552.       
  553.         //没有match函数  
  554.         if (!handler->match || handler->match(handler, dev))  
  555.             return id;  
  556.     }  
  557.   
  558.     return NULL;  
  559. }  
  560.   
  561. //如果匹配上了就会创建一个evdev,它里边封装了一个handle,会把input_dev和input_handler关联到一起。关系如下:  
  562.   
  563. static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)  
  564. {  
  565.     struct evdev *evdev;  
  566.     int minor;  
  567.     int error;  
  568.       
  569.     //EVDEV_MINORS为32,说明evdev这个handler可以同时有32个输入设备和他配对,evdev_table中以minor(非次设备号,但是有一个换算关系)存放evdev结构体  
  570.     for (minor = 0; minor < EVDEV_MINORS; minor++)  
  571.         if (!evdev_table[minor])  
  572.             break;  
  573.   
  574.     if (minor == EVDEV_MINORS) {//这个说明32个位置全都被占用了,连接失败   
  575.         printk(KERN_ERR "evdev: no more free evdev devicesn");  
  576.         return -ENFILE;  
  577.     }  
  578.       
  579.     //分配一个evdev结构体,这个结构体是evdev事件处理器特有的  
  580.     evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);  
  581.     if (!evdev)  
  582.         return -ENOMEM;  
  583.       
  584.     //初始化结构体的一些成员  
  585.     INIT_LIST_HEAD(&evdev->client_list);  
  586.     spin_lock_init(&evdev->client_lock);  
  587.     mutex_init(&evdev->mutex);  
  588.     init_waitqueue_head(&evdev->wait);  
  589.       
  590.     //这个是设置evdev中device的名字,他将出现在/class/input中。   
  591.   //前面也有一个device是input_dev的,名字是input(n),注意与他的不同   
  592.   //这个结构是配对后的虚拟设备结构,没有对应的硬件,但是通过它可以找到相关的硬件   
  593.     dev_set_name(&evdev->dev, "event%d", minor);  
  594.     evdev->exist = true;  
  595.     evdev->minor = minor;  
  596.       
  597.     //因为evdev中包含handle了,所以初始化它就可以了,这样就连接了input_handler与input_dev  
  598.     evdev->handle.dev = input_get_device(dev);  
  599.     evdev->handle.name = dev_name(&evdev->dev);  
  600.     evdev->handle.handler = handler;  
  601.     evdev->handle.private = evdev;  
  602.     //注意:这个minor不是真正的次设备号,还要加上EVDEV_MINOR_BASE   
  603.     evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);  
  604.     evdev->dev.class = &input_class;  
  605.     evdev->dev.parent = &dev->dev;//配对生成的device,父设备是与他相关连的input_dev  
  606.     evdev->dev.release = evdev_free;  
  607.     device_initialize(&evdev->dev);//做一些初始化device结构  
  608.   
  609.     error = input_register_handle(&evdev->handle);// 注册一个input_handle结构体  
  610.     if (error)  
  611.         goto err_free_evdev;  
  612.       
  613.     //这个函数只做了一件事,就是把evdev结构保存到evdev_table中,这个数组也minor为索引,evdev_table[evdev->minor] = evdev;   
  614.     error = evdev_install_chrdev(evdev);  
  615.     if (error)  
  616.         goto err_unregister_handle;  
  617.   
  618.     error = device_add(&evdev->dev);//注册到linux设备模型中,生成一系列的sys相关文件,udev会根据dev文件生成设备节点  
  619.     if (error)  
  620.         goto err_cleanup_evdev;  
  621.   
  622.     return 0;  
  623.   
  624.  err_cleanup_evdev:  
  625.     evdev_cleanup(evdev);  
  626.  err_unregister_handle:  
  627.     input_unregister_handle(&evdev->handle);  
  628.  err_free_evdev:  
  629.     put_device(&evdev->dev);  
  630.     return error;  
  631. }  
  632.   
  633. //这个函数基本没做什么事,就是把一个handle结构体通过d_node链表项,分别链接到input_dev的h_list,input_handler的h_list上。以后通过这个h_list就可以遍历相关的input_handle了。  
  634. int input_register_handle(struct input_handle *handle)   
  635. {   
  636.     struct input_handler *handler = handle->handler;   
  637.     struct input_dev *dev = handle->dev;   
  638.     int error;   
  639.     
  640.     error = mutex_lock_interruptible(&dev->mutex);   
  641.     if (error)   
  642.         return error;   
  643.     list_add_tail_rcu(&handle->d_node, &dev->h_list); //将handle的d_node,链接到其相关的input_dev的h_list链表中   
  644.     mutex_unlock(&dev->mutex);   
  645.     
  646.     list_add_tail(&handle->h_node, &handler->h_list);//将handle的h_node,链接到其相关的input_handler的h_list链表中   
  647.     if (handler->start)//字段为空  
  648.         handler->start(handle);   
  649.     return 0;   
  650. }   
  651.   
  652. 3.evdev设备结点的open()操作  
  653. 我们知道.对主设备号为INPUT_MAJOR的设备节点进行操作,会将操作集转换成handler的操作集.在evdev中,这个操作集就是evdev_fops.对应的open函数如下示:  
  654. static int evdev_open(struct inode *inode, struct file *file)  
  655. {  
  656.     struct evdev *evdev;  
  657.     struct evdev_client *client;  
  658.     int i = iminor(inode) - EVDEV_MINOR_BASE;//就得到了在evdev_table[ ]中的序号  
  659.     unsigned int bufsize;  
  660.     int error;  
  661.   
  662.     if (i >= EVDEV_MINORS)  
  663.         return -ENODEV;  
  664.   
  665.     error = mutex_lock_interruptible(&evdev_table_mutex);  
  666.     if (error)  
  667.         return error;  
  668.           
  669.     evdev = evdev_table[i];//将数组中对应的evdev取出.  
  670.     if (evdev)  
  671.         get_device(&evdev->dev);//递增devdev中device的引用计数.  
  672.     mutex_unlock(&evdev_table_mutex);  
  673.   
  674.     if (!evdev)  
  675.         return -ENODEV;  
  676.       
  677.     //evdev_client的buffer大小  
  678.     bufsize = evdev_compute_buffer_size(evdev->handle.dev);  
  679.     //打开的时候创建一个evdev_client  
  680.     client = kzalloc(sizeof(struct evdev_client) +bufsize * sizeof(struct input_event),GFP_KERNEL);  
  681.     if (!client) {  
  682.         error = -ENOMEM;  
  683.         goto err_put_evdev;  
  684.     }  
  685.   
  686.     client->bufsize = bufsize;//buffer size  
  687.     spin_lock_init(&client->buffer_lock);  
  688.     client->evdev = evdev;//指向evdev结构,将evdev和client绑定到一起  
  689.     evdev_attach_client(evdev, client);  
  690.   
  691.     error = evdev_open_device(evdev);//调用打开真正的底层设备函数  
  692.     if (error)  
  693.         goto err_free_client;  
  694.   
  695.     file->private_data = client;//将file->private_data指向刚刚建的client,后边会用到的  
  696.     nonseekable_open(inode, file);  
  697.   
  698.     return 0;  
  699.   
  700.  err_free_client:  
  701.     evdev_detach_client(evdev, client);  
  702.     kfree(client);  
  703.  err_put_evdev:  
  704.     put_device(&evdev->dev);  
  705.     return error;  
  706. }  
  707.   
  708. static int evdev_open_device(struct evdev *evdev)  
  709. {  
  710.     int retval;  
  711.   
  712.     retval = mutex_lock_interruptible(&evdev->mutex);  
  713.     if (retval)  
  714.         return retval;  
  715.   
  716.     if (!evdev->exist)/*如果设备不存在,返回错误*/   
  717.         retval = -ENODEV;  
  718.     else if (!evdev->open++) {//递增打开计数  
  719.         retval = input_open_device(&evdev->handle);//如果是被第一次打开,则调用input_open_device  
  720.         if (retval)  
  721.             evdev->open--;  
  722.     }  
  723.   
  724.     mutex_unlock(&evdev->mutex);  
  725.     return retval;  
  726. }  
  727.   
  728. int input_open_device(struct input_handle *handle)  
  729. {  
  730.     struct input_dev *dev = handle->dev;//根据input_handle找到对应的input_dev设备  
  731.     int retval;  
  732.   
  733.     retval = mutex_lock_interruptible(&dev->mutex);  
  734.     if (retval)  
  735.         return retval;  
  736.   
  737.     if (dev->going_away) {  
  738.         retval = -ENODEV;  
  739.         goto out;  
  740.     }  
  741.   
  742.     handle->open++;//递增handle的打开计数  
  743.   
  744.     if (!dev->users++ && dev->open)//如果是第一次打开.则调用input device的open()函数  
  745.         retval = dev->open(dev);  
  746.   
  747.     if (retval) {  
  748.         dev->users--;  
  749.         if (!--handle->open) {  
  750.             synchronize_rcu();  
  751.         }  
  752.     }  
  753.   
  754.  out:  
  755.     mutex_unlock(&dev->mutex);  
  756.     return retval;  
  757. }  
  758.   
  759. 4.用户进程读取event的底层实现  
  760. static ssize_t evdev_read(struct file *file, char __user *buffer,size_t count, loff_t *ppos)  
  761. {  
  762.     struct evdev_client *client = file->private_data;//就是刚才在open函数中保存的evdev_client  
  763.     struct evdev *evdev = client->evdev;  
  764.     struct input_event event;  
  765.     int retval;  
  766.       
  767.     if (count < input_event_size())//count小于input_event结构的size,则返回  
  768.         return -EINVAL;  
  769.       
  770.     //如果client的环形缓冲区中没有数据并且是非阻塞的,那么返回-EAGAIN,也就是try again  
  771.     if (client->head == client->tail && evdev->exist &&(file->f_flags & O_NONBLOCK))  
  772.         return -EAGAIN;  
  773.       
  774.     //如果没有数据,并且是阻塞的,则在等待队列上等待吧  
  775.     retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);  
  776.     if (retval)  
  777.         return retval;  
  778.   
  779.     if (!evdev->exist)  
  780.         return -ENODEV;  
  781.           
  782.     //如果获得了数据则取出来,调用evdev_fetch_next_event  
  783.     while (retval + input_event_size() <= count && evdev_fetch_next_event(client, &event)) {  
  784.   
  785.         if (input_event_to_user(buffer + retval, &event))//input_event_to_user调用copy_to_user传入用户程序中,这样读取完成  
  786.             return -EFAULT;  
  787.   
  788.         retval += input_event_size();  
  789.     }  
  790.   
  791.     return retval;  
  792. }  
  793.   
  794. static int evdev_fetch_next_event(struct evdev_client *client, struct input_event *event)   
  795. {   
  796.     int have_event;   
  797.     
  798.     spin_lock_irq(&client->buffer_lock);   
  799.     /*先判断一下是否有数据*/   
  800.     have_event = client->head != client->tail;   
  801.     /*如果有就从环形缓冲区的取出来,记得是从head存储,tail取出*/   
  802.     if (have_event) {   
  803.         *event = client->buffer[client->tail++];   
  804.         client->tail &= EVDEV_BUFFER_SIZE - 1;   
  805.     }   
  806.     spin_unlock_irq(&client->buffer_lock);   
  807.     return have_event;   
  808. }   
  809.   
  810. int input_event_to_user(char __user *buffer, const struct input_event *event)   
  811. {   
  812.     /*如果设置了标志INPUT_COMPAT_TEST就将事件event包装成结构体compat_event*/   
  813.     if (INPUT_COMPAT_TEST) {   
  814.         struct input_event_compat compat_event;   
  815.         compat_event.time.tv_sec = event->time.tv_sec;   
  816.         compat_event.time.tv_usec = event->time.tv_usec;   
  817.         compat_event.type = event->type;   
  818.         compat_event.code = event->code;   
  819.         compat_event.value = event->value;   
  820.         /*将包装成的compat_event拷贝到用户空间*/   
  821.         if (copy_to_user(buffer, &compat_event, sizeof(struct input_event_compat)))   
  822.             return -EFAULT;   
  823.     } else {   
  824.         /*否则,将event拷贝到用户空间*/   
  825.         if (copy_to_user(buffer, event, sizeof(struct input_event)))   
  826.             return -EFAULT;   
  827.     }   
  828.     return 0;   
  829. }   
  830.   
  831. 六. 事件传递过程  
  832. 1. 事件产生  
  833. 当按下触摸屏时,进入触摸屏按下中断,开始ad转换,ad转换完成进入ad完成中断,在这个终端中将事件发送出去,调用  
  834. input_report_abs(dev, ABS_X, xp);  
  835. input_report_abs(dev, ABS_Y, yp); 这两个函数调用了 input_event(dev, EV_ABS, code, value)  
  836. 所有的事件报告函数都调用这个函数。  
  837. 2. 事件报告  
  838. 1)input_event 函数分析,这个函数定义在input.c中  
  839. void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)   
  840. {   
  841.     unsigned long flags;   
  842.       
  843.     if (is_event_supported(type, dev->evbit, EV_MAX)) {//判断是否支持此种事件类型和事件类型中的编码类型   
  844.         spin_lock_irqsave(&dev->event_lock, flags);   
  845.         add_input_randomness(type, code, value); //对系统随机熵池有贡献,因为这个也是一个随机过程   
  846.            
  847.         input_handle_event(dev, type, code, value); //这个函数是事件处理的关键函数  
  848.            
  849.         spin_unlock_irqrestore(&dev->event_lock, flags);   
  850.     }   
  851. }   
  852.   
  853. 2)input_handle_event 函数分析,这个函数定义在input.c中  
  854. static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)   
  855. {   
  856.     int disposition = INPUT_IGNORE_EVENT; //默认的操作:忽略  
  857.     
  858.     switch (type) {   
  859.         ......   
  860.     case EV_KEY:   
  861.             检查按键是否为驱动所支持,只有之前注册过的按键才会继续传递;检查报告的按键状态是否和上次相同,如果连续多次报告按键按下,则只处理第一次  
  862.         if (is_event_supported(code, dev->keybit, KEY_MAX) && !!test_bit(code, dev->key) != value) {   
  863.             if (value != 2) {//如果不是连击事件   
  864.                 __change_bit(code, dev->key);//翻转按键的当前状态(按下和释放)   
  865.                 if (value)//如果是按下,则开始连击计时  
  866.                     input_start_autorepeat(dev, code);   
  867.                 else   
  868.                     input_stop_autorepeat(dev);   
  869.             }   
  870.             disposition = INPUT_PASS_TO_HANDLERS;//标记消息传递方向   
  871.         }   
  872.         break;   
  873.         ......   
  874.     if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)   
  875.         dev->sync = 0;   
  876.     
  877.     if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)   
  878.         dev->event(dev, type, code, value);   
  879.     
  880.     if (disposition & INPUT_PASS_TO_HANDLERS)   
  881.         input_pass_event(dev, type, code, value);   
  882. }   
  883. 这个函数主要是根据事件类型的不同,做相应的处理。这里之关心EV_KEY类型,其他函数和事件传递关系不大,只要关心disposition这个是事件处理的方式,默认的是INPUT_IGNORE_EVENT,忽略这个事件,如果是INPUT_PASS_TO_HANDLERS则是传递给事件处理器,如果是INPUT_PASS_TO_DEVICE,则是传递给设备处理,触摸屏驱动没有定义这个。下面分析input_pass_event函数。  
  884.    
  885. static void input_pass_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)   
  886. {   
  887.     struct input_handle *handle;   
  888.     rcu_read_lock();   
  889.         
  890.       //获取独占设备的handle的指针。如果有独占设备的handle,则仅仅将事件传给独占的handle对应的handler  
  891.     handle = rcu_dereference(dev->grab);  
  892.     if (handle)   
  893.             //这里直接调用了handler事件驱动对应的XX_event函数,这个XX_event函数把事件数据包传递给了handler,当应用程序使用XX_read时就可以读取到这些数据包  
  894.         handle->handler->event(handle, type, code, value);   
  895.     else   
  896.         //如果没有绑定,则遍历dev的h_list链表,寻找handle,如果handle已经打开,说明有进程读取设备关联的evdev。   
  897.         list_for_each_entry_rcu(handle, &dev->h_list, d_node)   
  898.             if (handle->open)   
  899.                 handle->handler->event(handle, type, code, value);//调用相关的事件处理器的event函数,进行事件的处理,即evdev_event   
  900.     rcu_read_unlock();   
  901. }   
  902.   
  903. static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)   
  904. {   
  905.     struct evdev *evdev = handle->private;   
  906.     struct evdev_client *client;   
  907.     struct input_event event;   
  908.     
  909.       //将传过来的事件,赋值给input_event结构   
  910.     do_gettimeofday(&event.time);//获取事件时间   
  911.     event.type = type;   
  912.     event.code = code;   
  913.     event.value = value;   
  914.             
  915.     rcu_read_lock();   
  916.     
  917.     client = rcu_dereference(evdev->grab); //如果evdev绑定了client那么,处理这个客户端,触摸屏驱动没有绑定   
  918.     if (client)   
  919.         evdev_pass_event(client, &event);   
  920.     else   
  921.         //遍历client链表,调用evdev_pass_event函数   
  922.         list_for_each_entry_rcu(client, &evdev->client_list, node)   
  923.             evdev_pass_event(client, &event);   
  924.       
  925.     rcu_read_unlock();   
  926.     wake_up_interruptible(&evdev->wait); //唤醒等待的进程   
  927. }   
  928.   
  929.   
  930. static void evdev_pass_event(struct evdev_client *client, struct input_event *event)   
  931. {   
  932.     spin_lock(&client->buffer_lock);   
  933.     client->buffer[client->head++] = *event;//将事件赋值给客户端的input_event 数组   
  934.     client->head &= EVDEV_BUFFER_SIZE - 1;   
  935.     spin_unlock(&client->buffer_lock);   
  936.     
  937.     kill_fasync(&client->fasync, SIGIO, POLL_IN);   
  938. }   
  939. 总结一下事件的传递过程:首先在驱动层中,调用inport_report_abs,然后他调用了input core层的input_event,input_event调用了input_handle_event对事件进行分派,调用input_pass_event,在这里他会把事件传递给具体的handler层,然后在相应handler的event处理函数中,封装一个event,然后把它投入evdev的那个client_list上的client的事件buffer中,等待用户空间来读取。 



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值