├── README.md └── document ├── chardev ├── my_chrdev.c ├── my_create_attr.c ├── my_create_group_attr.c ├── my_misc.c ├── proc │ ├── Makefile │ ├── irq_test.c │ └── proc_create.c ├── sleepy.c ├── sysfs属性节点创建.md ├── 字符设备注册.md ├── 杂项设备创建.md └── 设备节点创建.md ├── input ├── i2c输入子系统.md ├── input输入子系统.md ├── my_i2c.c └── my_key.c ├── irq ├── irq_test.c ├── tasklet_test.c ├── tasklet_workqueue的区别.md ├── workqueue_test.c ├── workqueue_thread_test.c └── 中断上下部.md ├── mem ├── 01alloc_pages │ └── test.c ├── 02kmalloc │ └── test.c ├── 03vmalloc │ └── test.c ├── 04kmem_cache │ └── test.c ├── 05mem │ └── test.c ├── 06dma │ └── test.c └── 07err │ └── test.c ├── platform ├── msm8953-mtp.diff ├── my_platform.c └── 平台设备创建.md ├── sync ├── README.md ├── atomic │ ├── Makefile │ ├── test.c │ └── test.sh ├── completion │ ├── Makefile │ ├── app.c │ ├── test.c │ └── test.sh ├── mutex │ ├── Makefile │ ├── test.c │ └── test.sh ├── semaphore │ ├── Makefile │ ├── test.c │ └── test.sh └── spinlock │ ├── Makefile │ ├── test.c │ └── test.sh ├── thread ├── Makefile └── test.c └── timer ├── jiffies ├── Makefile └── test.c └── timer ├── Makefile └── test.c /README.md: -------------------------------------------------------------------------------- 1 | 使用Linux挺久了,梳理一份关于Linux驱动的文档。 2 | 3 | # 字符设备 4 | 5 | * [字符设备注册](./document/chardev/字符设备注册.md) 6 | 7 | * [设备节点创建](./document/chardev/设备节点创建.md) 8 | 9 | * [sysfs属性添加](./document/chardev/sysfs属性节点创建.md) 10 | 11 | * [proc文件添加](./document/chardev/proc/proc_create.c) 12 | 13 | * [阻塞IO操作](./document/chardev/sleepy.c) 14 | 15 | * [杂项设备创建](./document/chardev/杂项设备创建.md) 16 | 17 | # 平台总线驱动 18 | 19 | * [平台设备创建](./document/platform/平台设备创建.md) 20 | 21 | # 并发和竞态 22 | 23 | * [spinlock自旋锁](./document/sync/spinlock/test.c) 24 | 25 | * [semaphore信号量](./document/sync/semaphore/test.c) 26 | 27 | * [mutex互斥量](./document/sync/mutex/test.c) 28 | 29 | * [atomic原子操作](./document/sync/atomic/test.c) 30 | 31 | * [completion完成量](./document/sync/completion/test.c) 32 | 33 | * [锁的区别](./document/sync/README.md) 34 | 35 | # 时钟滴答和定时器 36 | 37 | * [时钟滴答jiffies](./document/timer/jiffies/test.c) 38 | 39 | * [定时器](./document/timer/timer/test.c) 40 | 41 | # 内存分配 42 | 43 | * [alloc_pages](./document/mem/01alloc_pages/test.c) 44 | 45 | * [kmalloc](./document/mem/02kmalloc/test.c) 46 | 47 | * [vmalloc](./document/mem/03vmalloc/test.c) 48 | 49 | * [kmem_cache](./document/mem/04kmem_cache/test.c) 50 | 51 | * [memory address](./document/mem/05mem/test.c) 52 | 53 | * [dma](./document/mem/06dma/test.c) 54 | 55 | * [err处理](./document/mem/07err/test.c) 56 | 57 | # 线程 58 | 59 | * [thread](./document/thread/test.c) 60 | 61 | # 中断处理 62 | 63 | * [gpio按键中断](./document/irq/irq_test.c) 64 | 65 | * [中断上下部](./document/irq/中断上下部.md) 66 | 67 | * [tasklet](./document/irq/tasklet_test.c) 68 | 69 | * [workqueue](./document/irq/workqueue_thread_test.c) 70 | 71 | * [tasklet&workqueue区别](./document/irq/tasklet_workqueue的区别.md) 72 | 73 | # 输入子系统 74 | 75 | * [input输入子系统](./document/input/input输入子系统.md) 76 | 77 | * [i2c输入子系统](./document/input/i2c输入子系统.md) 78 | -------------------------------------------------------------------------------- /document/chardev/my_chrdev.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define DEVICE_NAME "my_chrdev" 12 | 13 | #define CDEV_MAJOR 255 14 | static int cdev_major = CDEV_MAJOR; 15 | 16 | struct cdev *cdev; 17 | dev_t devno; 18 | 19 | static struct class *my_class; 20 | 21 | struct my_private_data { 22 | int size; 23 | char buffer[20]; 24 | }; 25 | 26 | struct my_private_data *pdata; 27 | 28 | static ssize_t my_chrdev_read(struct file *filep,char *buf,size_t len,loff_t *off) 29 | { 30 | pr_err("%s,%d", __func__, __LINE__); 31 | return len; 32 | } 33 | 34 | static ssize_t my_chrdev_write(struct file *filep,const char *buf,size_t len,loff_t *off) 35 | { 36 | pr_err("%s,%d", __func__, __LINE__); 37 | return len; 38 | } 39 | 40 | static int my_chrdev_open(struct inode *inode,struct file *filep) 41 | { 42 | pr_err("%s,%d", __func__, __LINE__); 43 | // open的时候可以将自己的私有数据保存在private_data指针中,那么在read_write的时候,也可以通过这个指针访问对应的数据 44 | // 有时候还需要在这里面识别次设备号 45 | filep->private_data = pdata; 46 | return 0; 47 | } 48 | 49 | static int my_chrdev_release(struct inode *inode,struct file *filep) 50 | { 51 | pr_err("%s,%d", __func__, __LINE__); 52 | return 0; 53 | } 54 | 55 | static struct file_operations chrdev_fops = { 56 | .owner = THIS_MODULE, 57 | .read = my_chrdev_read, 58 | .write = my_chrdev_write, 59 | .open = my_chrdev_open, 60 | .release = my_chrdev_release, 61 | }; 62 | 63 | static int __init my_chrdev_init(void) 64 | { 65 | int rc = 0; 66 | 67 | pr_err("%s,%d", __func__, __LINE__); 68 | 69 | /* 申请设备号 70 | * major为0,表示动态分配,非0表示静态分配 71 | * 前面一定定义了major的值,所以这里是静态分配 72 | */ 73 | if (cdev_major) { 74 | // 设备编号,保存主设备号和次设备号 75 | devno = MKDEV(cdev_major, 0); 76 | // 静态分配设备号 77 | /* int register_chrdev_region(dev_t first, unsigned int count, const char *name); 78 | * first是申请的字符设备编号,count是连续设备的编号个数, 79 | * name是和设备编号范围关联的设备名称, 将出现在/proc/devices和sysfs中 80 | */ 81 | rc = register_chrdev_region(devno, 1, DEVICE_NAME); 82 | pr_err("%s,%d, major: %d", __func__, __LINE__, cdev_major); 83 | } else { 84 | // 动态分配设备号 85 | /* int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name); 86 | * dev是申请的字符设备编号,firstminor是要分配的设备的次设备号的起始值,count是连续设备的编号个数,name是设备名称 87 | */ 88 | rc = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME); 89 | // 获取主设备号 90 | cdev_major = MAJOR(devno); 91 | pr_err("%s,%d, major: %d", __func__, __LINE__, cdev_major); 92 | } 93 | // 申请字符设备空间,其实也可以不适用指针,直接定义一个变量,那么这个设备申请就可以省略 94 | // 把cdv结构嵌入到你自己封装的设备结构中 95 | cdev = cdev_alloc(); 96 | // 字符设备结构体指向当前的模块 97 | cdev->owner = THIS_MODULE; 98 | // struect cdev结构体和file_operations结构体绑定 99 | cdev_init(cdev, &chrdev_fops); 100 | // 注册字符设备驱动 101 | // 注册之后可以在/proc/devices中读取到这个设备 102 | cdev_add(cdev, devno, 1); 103 | /* 可以申请私有数据,然后在open的时候,保存到filep->private_data指针中 104 | */ 105 | // 可以用mknod /dev/my_chrdev c 255 0 创建字符设备节点 106 | // 为了省去手动创建设备节点,创建class,并将class注册到内核中 107 | // 在/sys/class/下创建chardev0的类 108 | my_class = class_create(THIS_MODULE, "chardev0"); 109 | // 用户空间的udev会响应device_create函数,去/sys/class下寻找对应的类,在/dev下自动创建节点 110 | device_create(my_class, NULL, devno, NULL, "chardev0"); 111 | 112 | // 申请私有数据的空间, 并赋初值 113 | pdata = kzalloc(sizeof(struct my_private_data), GFP_KERNEL); 114 | pdata->size = 100; 115 | memset(pdata->buffer, 0, sizeof(pdata->buffer)); 116 | memcpy(pdata->buffer, "tao", 3); 117 | 118 | return rc; 119 | } 120 | 121 | static void __exit my_chrdev_exit(void) 122 | { 123 | pr_err("%s,%d", __func__, __LINE__); 124 | // 首先取消掉对应的节点 125 | device_destroy(my_class, MKDEV(cdev_major, 0)); 126 | // 再删除对应的类 127 | class_destroy(my_class); 128 | // 取消注册驱动 129 | cdev_del(cdev); 130 | // 释放设备号 131 | unregister_chrdev_region(devno, 1); 132 | kfree(pdata); 133 | return; 134 | } 135 | 136 | module_init(my_chrdev_init); 137 | module_exit(my_chrdev_exit); 138 | 139 | MODULE_DESCRIPTION("TAO char driver"); 140 | MODULE_AUTHOR("TAO LIU"); 141 | MODULE_LICENSE("GPL"); 142 | -------------------------------------------------------------------------------- /document/chardev/my_create_attr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define DEVICE_NAME "my_chrdev" 12 | 13 | #define CDEV_MAJOR 255 14 | static int cdev_major = CDEV_MAJOR; 15 | 16 | struct cdev *cdev; 17 | dev_t devno; 18 | 19 | static struct class *my_class; 20 | 21 | static struct my_private_data { 22 | unsigned long size; 23 | char buffer[20]; 24 | }; 25 | 26 | static struct my_private_data *pdata; 27 | 28 | static ssize_t my_chrdev_size_show(struct device *dev, 29 | struct device_attribute *attr, char *buf) 30 | { 31 | ssize_t ret = 0; 32 | // 获取私有数据 33 | struct my_private_data *data = dev_get_drvdata(dev); 34 | 35 | pr_err("%s,%d, size: %d", __func__, __LINE__, data->size); 36 | 37 | ret = snprintf(buf, sizeof(int), "%d\n", data->size); 38 | 39 | return ret; 40 | } 41 | 42 | static ssize_t my_chrdev_size_store(struct device *dev, 43 | struct device_attribute *attr, 44 | const char *buf, size_t size) 45 | { 46 | ssize_t ret = 0; 47 | 48 | struct my_private_data *data = dev_get_drvdata(dev); 49 | 50 | pr_err("%s,%d, size: %s", __func__, __LINE__, *buf); 51 | 52 | kstrtoul(buf, 10, &data->size); 53 | 54 | return size; 55 | } 56 | 57 | static DEVICE_ATTR(size, S_IRWXU, my_chrdev_size_show, my_chrdev_size_store); 58 | 59 | static ssize_t my_chrdev_buffer_show(struct device *dev, 60 | struct device_attribute *attr, char *buf) 61 | { 62 | ssize_t ret = 0; 63 | 64 | // 获取私有数据 65 | struct my_private_data *data = dev_get_drvdata(dev); 66 | 67 | ret = snprintf(buf, strlen(data->buffer),"%s\n", data->buffer); 68 | 69 | pr_err("%s,%d, %s", __func__, __LINE__, data->buffer); 70 | 71 | return ret; 72 | } 73 | 74 | static ssize_t my_chrdev_buffer_store(struct device *dev, 75 | struct device_attribute *attr, 76 | const char *buf, size_t size) 77 | { 78 | ssize_t ret = 0; 79 | 80 | struct my_private_data *data = dev_get_drvdata(dev); 81 | 82 | memset(data->buffer, 0, sizeof(data->buffer)); 83 | memcpy(data->buffer, buf, size); 84 | 85 | pr_err("%s,%d", __func__, __LINE__); 86 | 87 | return size; 88 | } 89 | static DEVICE_ATTR(buffer, S_IRWXU, my_chrdev_buffer_show, my_chrdev_buffer_store); 90 | 91 | static ssize_t my_chrdev_read(struct file *filep,char *buf,size_t len,loff_t *off) 92 | { 93 | pr_err("%s,%d", __func__, __LINE__); 94 | return len; 95 | } 96 | 97 | static ssize_t my_chrdev_write(struct file *filep,const char *buf,size_t len,loff_t *off) 98 | { 99 | pr_err("%s,%d", __func__, __LINE__); 100 | return len; 101 | } 102 | 103 | static int my_chrdev_open(struct inode *inode,struct file *filep) 104 | { 105 | pr_err("%s,%d", __func__, __LINE__); 106 | // open的时候可以将自己的私有数据保存在private_data指针中,那么在read_write的时候,也可以通过这个指针访问对应的数据 107 | // 有时候还需要在这里面识别次设备号 108 | filep->private_data = pdata; 109 | return 0; 110 | } 111 | 112 | static int my_chrdev_release(struct inode *inode,struct file *filep) 113 | { 114 | pr_err("%s,%d", __func__, __LINE__); 115 | return 0; 116 | } 117 | 118 | static struct file_operations chrdev_fops = { 119 | .owner = THIS_MODULE, 120 | .read = my_chrdev_read, 121 | .write = my_chrdev_write, 122 | .open = my_chrdev_open, 123 | .release = my_chrdev_release, 124 | }; 125 | 126 | static int __init my_chrdev_init(void) 127 | { 128 | int rc = 0; 129 | struct device *dev; 130 | 131 | pr_err("%s,%d", __func__, __LINE__); 132 | 133 | /* 申请设备号 134 | * major为0,表示动态分配,非0表示静态分配 135 | * 前面一定定义了major的值,所以这里是静态分配 136 | */ 137 | if (cdev_major) { 138 | // 设备编号,保存主设备号和次设备号 139 | devno = MKDEV(cdev_major, 0); 140 | // 静态分配设备号 141 | /* int register_chrdev_region(dev_t first, unsigned int count, const char *name); 142 | * first是申请的字符设备编号,count是连续设备的编号个数, 143 | * name是和设备编号范围关联的设备名称, 将出现在/proc/devices和sysfs中 144 | */ 145 | rc = register_chrdev_region(devno, 1, DEVICE_NAME); 146 | pr_err("%s,%d, major: %d", __func__, __LINE__, cdev_major); 147 | } else { 148 | // 动态分配设备号 149 | /* int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name); 150 | * dev是申请的字符设备编号,firstminor是要分配的设备的次设备号的起始值,count是连续设备的编号个数,name是设备名称 151 | */ 152 | rc = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME); 153 | // 获取主设备号 154 | cdev_major = MAJOR(devno); 155 | pr_err("%s,%d, major: %d", __func__, __LINE__, cdev_major); 156 | } 157 | // 申请字符设备空间,其实也可以不适用指针,直接定义一个变量,那么这个设备申请就可以省略 158 | // 把cdv结构嵌入到你自己封装的设备结构中 159 | cdev = cdev_alloc(); 160 | // 字符设备结构体指向当前的模块 161 | cdev->owner = THIS_MODULE; 162 | // struect cdev结构体和file_operations结构体绑定 163 | cdev_init(cdev, &chrdev_fops); 164 | // 注册字符设备驱动 165 | // 注册之后可以在/proc/devices中读取到这个设备 166 | cdev_add(cdev, devno, 1); 167 | /* 可以申请私有数据,然后在open的时候,保存到filep->private_data指针中 168 | */ 169 | // 可以用mknod /dev/my_chrdev c 255 0 创建字符设备节点 170 | // 为了省去手动创建设备节点,创建class,并将class注册到内核中 171 | // 在/sys/class/下创建chardev0的类 172 | my_class = class_create(THIS_MODULE, "chardev0"); 173 | // 用户空间的udev会响应device_create函数,去/sys/class下寻找对应的类,在/dev下自动创建节点 174 | dev = device_create(my_class, NULL, devno, NULL, "chardev0"); 175 | // 创建属性值, dev是device_create函数返回的struct device, /sys/class/chrdev0/chrdev0/size 176 | rc = device_create_file(dev, &dev_attr_size); 177 | if (rc) { 178 | pr_err("%s,%d sys file create failed\n", __func__, __LINE__); 179 | } 180 | 181 | // 创建属性值, dev是device_create函数返回的struct device, /sys/class/chrdev0/chrdev0/buffer 182 | rc = device_create_file(dev, &dev_attr_buffer); 183 | if (rc) { 184 | pr_err("%s,%d sys file create failed\n", __func__, __LINE__); 185 | } 186 | 187 | 188 | // 申请私有数据的空间, 并赋初值 189 | pdata = kzalloc(sizeof(struct my_private_data), GFP_KERNEL); 190 | pdata->size = 100; 191 | memset(pdata->buffer, 0, sizeof(pdata->buffer)); 192 | memcpy(pdata->buffer, "tao", 3); 193 | 194 | // 将私有数据添加到struct device中 195 | dev_set_drvdata(dev, pdata); 196 | 197 | return rc; 198 | } 199 | 200 | static void __exit my_chrdev_exit(void) 201 | { 202 | pr_err("%s,%d", __func__, __LINE__); 203 | // 首先取消掉对应的节点 204 | device_destroy(my_class, MKDEV(cdev_major, 0)); 205 | // 再删除对应的类 206 | class_destroy(my_class); 207 | // 取消注册驱动 208 | cdev_del(cdev); 209 | // 释放设备号 210 | unregister_chrdev_region(devno, 1); 211 | kfree(pdata); 212 | return; 213 | } 214 | 215 | module_init(my_chrdev_init); 216 | module_exit(my_chrdev_exit); 217 | 218 | MODULE_DESCRIPTION("TAO char driver"); 219 | MODULE_AUTHOR("TAO LIU"); 220 | MODULE_LICENSE("GPL"); 221 | -------------------------------------------------------------------------------- /document/chardev/my_create_group_attr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define DEVICE_NAME "my_chrdev" 12 | 13 | #define CDEV_MAJOR 255 14 | static int cdev_major = CDEV_MAJOR; 15 | 16 | struct cdev *cdev; 17 | dev_t devno; 18 | 19 | static struct class *my_class; 20 | struct device *dev; 21 | 22 | struct my_private_data { 23 | unsigned long size; 24 | char buffer[20]; 25 | }; 26 | 27 | static struct my_private_data *pdata; 28 | 29 | static ssize_t my_chrdev_size_show(struct device *dev, 30 | struct device_attribute *attr, char *buf) 31 | { 32 | ssize_t ret = 0; 33 | // 获取私有数据 34 | struct my_private_data *data = dev_get_drvdata(dev); 35 | 36 | pr_err("%s,%d, size: %d", __func__, __LINE__, data->size); 37 | 38 | ret = snprintf(buf, sizeof(int), "%d\n", data->size); 39 | 40 | return ret; 41 | } 42 | 43 | static ssize_t my_chrdev_size_store(struct device *dev, 44 | struct device_attribute *attr, 45 | const char *buf, size_t size) 46 | { 47 | ssize_t ret = 0; 48 | 49 | struct my_private_data *data = dev_get_drvdata(dev); 50 | 51 | pr_err("%s,%d, size: %s", __func__, __LINE__, *buf); 52 | 53 | kstrtoul(buf, 10, &data->size); 54 | 55 | return size; 56 | } 57 | 58 | static DEVICE_ATTR(size, S_IRUSR|S_IWUSR, my_chrdev_size_show, my_chrdev_size_store); 59 | 60 | static ssize_t my_chrdev_buffer_show(struct device *dev, 61 | struct device_attribute *attr, char *buf) 62 | { 63 | ssize_t ret = 0; 64 | 65 | // 获取私有数据 66 | struct my_private_data *data = dev_get_drvdata(dev); 67 | // snprintf会把'\0'也算上,所以要+1才行 68 | ret = snprintf(buf, strlen(data->buffer) + 1,"%s\n", data->buffer); 69 | 70 | pr_err("%s,%d, length: %d, %s", __func__, __LINE__, strlen(data->buffer), data->buffer); 71 | pr_err("%s,%d, length: %d, buf: %s", __func__, __LINE__, strlen(buf), buf); 72 | 73 | return ret; 74 | } 75 | 76 | static ssize_t my_chrdev_buffer_store(struct device *dev, 77 | struct device_attribute *attr, 78 | const char *buf, size_t size) 79 | { 80 | ssize_t ret = 0; 81 | 82 | struct my_private_data *data = dev_get_drvdata(dev); 83 | 84 | memset(data->buffer, 0, sizeof(data->buffer)); 85 | memcpy(data->buffer, buf, size); 86 | 87 | pr_err("%s,%d", __func__, __LINE__); 88 | 89 | return size; 90 | } 91 | static DEVICE_ATTR(buffer, S_IRUSR|S_IWUSR, my_chrdev_buffer_show, my_chrdev_buffer_store); 92 | 93 | static struct attribute *my_chrdev_attrs[] = { 94 | &dev_attr_size.attr, 95 | &dev_attr_buffer.attr, 96 | NULL, 97 | }; 98 | 99 | static const struct attribute_group my_chrdev_attrs_group = { 100 | .attrs = my_chrdev_attrs, 101 | }; 102 | 103 | static ssize_t my_chrdev_read(struct file *filep,char *buf,size_t len,loff_t *off) 104 | { 105 | pr_err("%s,%d", __func__, __LINE__); 106 | return len; 107 | } 108 | 109 | static ssize_t my_chrdev_write(struct file *filep,const char *buf,size_t len,loff_t *off) 110 | { 111 | pr_err("%s,%d", __func__, __LINE__); 112 | return len; 113 | } 114 | 115 | static int my_chrdev_open(struct inode *inode,struct file *filep) 116 | { 117 | pr_err("%s,%d", __func__, __LINE__); 118 | // open的时候可以将自己的私有数据保存在private_data指针中,那么在read_write的时候,也可以通过这个指针访问对应的数据 119 | // 有时候还需要在这里面识别次设备号 120 | filep->private_data = pdata; 121 | return 0; 122 | } 123 | 124 | static int my_chrdev_release(struct inode *inode,struct file *filep) 125 | { 126 | pr_err("%s,%d", __func__, __LINE__); 127 | return 0; 128 | } 129 | 130 | static struct file_operations chrdev_fops = { 131 | .owner = THIS_MODULE, 132 | .read = my_chrdev_read, 133 | .write = my_chrdev_write, 134 | .open = my_chrdev_open, 135 | .release = my_chrdev_release, 136 | }; 137 | 138 | static int __init my_chrdev_init(void) 139 | { 140 | int rc = 0; 141 | 142 | pr_err("%s,%d", __func__, __LINE__); 143 | 144 | /* 申请设备号 145 | * major为0,表示动态分配,非0表示静态分配 146 | * 前面一定定义了major的值,所以这里是静态分配 147 | */ 148 | if (cdev_major) { 149 | // 设备编号,保存主设备号和次设备号 150 | devno = MKDEV(cdev_major, 0); 151 | // 静态分配设备号 152 | /* int register_chrdev_region(dev_t first, unsigned int count, const char *name); 153 | * first是申请的字符设备编号,count是连续设备的编号个数, 154 | * name是和设备编号范围关联的设备名称, 将出现在/proc/devices和sysfs中 155 | */ 156 | rc = register_chrdev_region(devno, 1, DEVICE_NAME); 157 | pr_err("%s,%d, major: %d", __func__, __LINE__, cdev_major); 158 | } else { 159 | // 动态分配设备号 160 | /* int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name); 161 | * dev是申请的字符设备编号,firstminor是要分配的设备的次设备号的起始值,count是连续设备的编号个数,name是设备名称 162 | */ 163 | rc = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME); 164 | // 获取主设备号 165 | cdev_major = MAJOR(devno); 166 | pr_err("%s,%d, major: %d", __func__, __LINE__, cdev_major); 167 | } 168 | // 申请字符设备空间,其实也可以不适用指针,直接定义一个变量,那么这个设备申请就可以省略 169 | // 把cdv结构嵌入到你自己封装的设备结构中 170 | cdev = cdev_alloc(); 171 | // 字符设备结构体指向当前的模块 172 | cdev->owner = THIS_MODULE; 173 | // struect cdev结构体和file_operations结构体绑定 174 | cdev_init(cdev, &chrdev_fops); 175 | // 注册字符设备驱动 176 | // 注册之后可以在/proc/devices中读取到这个设备 177 | cdev_add(cdev, devno, 1); 178 | /* 可以申请私有数据,然后在open的时候,保存到filep->private_data指针中 179 | */ 180 | // 可以用mknod /dev/my_chrdev c 255 0 创建字符设备节点 181 | // 为了省去手动创建设备节点,创建class,并将class注册到内核中 182 | // 在/sys/class/下创建chardev0的类 183 | my_class = class_create(THIS_MODULE, "chardev0"); 184 | // 用户空间的udev会响应device_create函数,去/sys/class下寻找对应的类,在/dev下自动创建节点 185 | dev = device_create(my_class, NULL, devno, NULL, "chardev0"); 186 | // 创建属性值, dev是device_create函数返回的struct device, /sys/class/chrdev0/chrdev0/size 187 | pr_err("%s,%d", __func__, __LINE__); 188 | 189 | // 创建属性值 190 | rc = sysfs_create_group(&dev->kobj, &my_chrdev_attrs_group); 191 | pr_err("%s,%d", __func__, __LINE__); 192 | if (rc) { 193 | pr_err("%s,%d sys file create failed\n", __func__, __LINE__); 194 | } 195 | 196 | // 申请私有数据的空间, 并赋初值 197 | pdata = kzalloc(sizeof(struct my_private_data), GFP_KERNEL); 198 | pdata->size = 100; 199 | memset(pdata->buffer, 0, sizeof(pdata->buffer)); 200 | memcpy(pdata->buffer, "tao", 4); 201 | 202 | // 将私有数据添加到struct device中 203 | dev_set_drvdata(dev, pdata); 204 | 205 | return rc; 206 | } 207 | 208 | static void __exit my_chrdev_exit(void) 209 | { 210 | pr_err("%s,%d", __func__, __LINE__); 211 | // 删除创建的属性值 212 | sysfs_remove_group(&dev->kobj, &my_chrdev_attrs_group); 213 | 214 | // 首先取消掉对应的节点 215 | device_destroy(my_class, MKDEV(cdev_major, 0)); 216 | // 再删除对应的类 217 | class_destroy(my_class); 218 | // 取消注册驱动 219 | cdev_del(cdev); 220 | // 释放设备号 221 | unregister_chrdev_region(devno, 1); 222 | kfree(pdata); 223 | return; 224 | } 225 | 226 | module_init(my_chrdev_init); 227 | module_exit(my_chrdev_exit); 228 | 229 | MODULE_DESCRIPTION("TAO char driver"); 230 | MODULE_AUTHOR("TAO LIU"); 231 | MODULE_LICENSE("GPL"); 232 | -------------------------------------------------------------------------------- /document/chardev/my_misc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | struct my_private_data { 17 | unsigned long size; 18 | char buffer[20]; 19 | }; 20 | static struct my_private_data *pdata; 21 | 22 | static ssize_t my_misc_size_show(struct device *dev, 23 | struct device_attribute *attr, char *buf) 24 | { 25 | ssize_t ret = 0; 26 | // 获取私有数据 27 | struct my_private_data *data = dev_get_drvdata(dev); 28 | 29 | pr_err("%s,%d, size: %d", __func__, __LINE__, data->size); 30 | 31 | ret = snprintf(buf, sizeof(int), "%d\n", data->size); 32 | 33 | return ret; 34 | } 35 | 36 | static ssize_t my_misc_size_store(struct device *dev, 37 | struct device_attribute *attr, 38 | const char *buf, size_t size) 39 | { 40 | ssize_t ret = 0; 41 | 42 | struct my_private_data *data = dev_get_drvdata(dev); 43 | 44 | pr_err("%s,%d, size: %s", __func__, __LINE__, *buf); 45 | 46 | kstrtoul(buf, 10, &data->size); 47 | 48 | return size; 49 | } 50 | 51 | static DEVICE_ATTR(size, S_IRUSR|S_IWUSR, my_misc_size_show, my_misc_size_store); 52 | 53 | static ssize_t my_misc_buffer_show(struct device *dev, 54 | struct device_attribute *attr, char *buf) 55 | { 56 | ssize_t ret = 0; 57 | 58 | // 获取私有数据 59 | struct my_private_data *data = dev_get_drvdata(dev); 60 | // snprintf会把'\0'也算上,所以要+1才行 61 | ret = snprintf(buf, strlen(data->buffer) + 1,"%s\n", data->buffer); 62 | 63 | pr_err("%s,%d, length: %d, %s", __func__, __LINE__, strlen(data->buffer), data->buffer); 64 | pr_err("%s,%d, length: %d, buf: %s", __func__, __LINE__, strlen(buf), buf); 65 | 66 | return ret; 67 | } 68 | 69 | static ssize_t my_misc_buffer_store(struct device *dev, 70 | struct device_attribute *attr, 71 | const char *buf, size_t size) 72 | { 73 | ssize_t ret = 0; 74 | 75 | struct my_private_data *data = dev_get_drvdata(dev); 76 | 77 | memset(data->buffer, 0, sizeof(data->buffer)); 78 | memcpy(data->buffer, buf, size); 79 | 80 | pr_err("%s,%d", __func__, __LINE__); 81 | 82 | return size; 83 | } 84 | static DEVICE_ATTR(buffer, S_IRUSR|S_IWUSR, my_misc_buffer_show, my_misc_buffer_store); 85 | 86 | static struct attribute *my_misc_attrs[] = { 87 | &dev_attr_size.attr, 88 | &dev_attr_buffer.attr, 89 | NULL, 90 | }; 91 | 92 | static const struct attribute_group my_misc_attrs_group = { 93 | .attrs = my_misc_attrs, 94 | }; 95 | 96 | static int my_misc_open(struct inode *node, struct file *filp) 97 | { 98 | // 可以将私有数据保存到filep->private_data中 99 | //filp->private_data = misc; 100 | pr_err("%s,%d", __func__, __LINE__); 101 | return 0; 102 | } 103 | 104 | static int my_misc_release(struct inode *node, struct file *filp) 105 | { 106 | pr_err("%s,%d", __func__, __LINE__); 107 | return 0; 108 | } 109 | 110 | static ssize_t my_misc_read(struct file *file, char __user *buf, 111 | size_t count, loff_t *ppos) 112 | { 113 | pr_err("%s,%d", __func__, __LINE__); 114 | 115 | return count; 116 | } 117 | 118 | static ssize_t my_misc_write(struct file *file, const char *buf, 119 | size_t count, loff_t *ppos) 120 | { 121 | pr_err("%s,%d", __func__, __LINE__); 122 | return count; 123 | } 124 | 125 | static const struct file_operations my_misc_fops = { 126 | .owner = THIS_MODULE, 127 | .open = my_misc_open, 128 | .release = my_misc_release, 129 | .read = my_misc_read, 130 | .write = my_misc_write, 131 | }; 132 | 133 | static struct miscdevice my_misc_dev = { 134 | .minor = MISC_DYNAMIC_MINOR, 135 | .name = "my_misc", 136 | .fops = &my_misc_fops, 137 | }; 138 | 139 | static int __init my_misc_init(void) 140 | { 141 | int rc = 0; 142 | 143 | pr_err("%s,%d", __func__, __LINE__); 144 | /* 创建misc设备节点,会创建/sys/class/misc/my_misc/, /dev/my_misc设备节点 145 | */ 146 | rc = misc_register(&my_misc_dev); 147 | if(rc < 0) { 148 | pr_err("%s,%d, fail to register misc\n", __func__, __LINE__); 149 | goto err_reg; 150 | } 151 | pr_err("%s,%d", __func__, __LINE__); 152 | rc = sysfs_create_group(&my_misc_dev.this_device->kobj, &my_misc_attrs_group); 153 | pr_err("%s,%d", __func__, __LINE__); 154 | if (rc) { 155 | pr_err("%s,%d sys file create failed\n", __func__, __LINE__); 156 | goto err; 157 | } 158 | // 申请私有数据的空间, 并赋初值 159 | pdata = kzalloc(sizeof(struct my_private_data), GFP_KERNEL); 160 | pdata->size = 100; 161 | memset(pdata->buffer, 0, sizeof(pdata->buffer)); 162 | memcpy(pdata->buffer, "tao", 4); 163 | 164 | pr_err("%s,%d", __func__, __LINE__); 165 | dev_set_drvdata(my_misc_dev.this_device, pdata); 166 | pr_err("%s,%d", __func__, __LINE__); 167 | 168 | err: 169 | pr_err("%s,%d", __func__, __LINE__); 170 | 171 | err_reg: 172 | return rc; 173 | } 174 | 175 | static void __exit my_misc_exit(void) 176 | { 177 | misc_deregister(&my_misc_dev); 178 | 179 | pr_err("%s,%d", __func__, __LINE__); 180 | 181 | 182 | return; 183 | } 184 | 185 | module_init(my_misc_init); 186 | module_exit(my_misc_exit); 187 | 188 | MODULE_DESCRIPTION("TAO MISC driver"); 189 | MODULE_AUTHOR("TAO LIU"); 190 | MODULE_LICENSE("GPL"); 191 | -------------------------------------------------------------------------------- /document/chardev/proc/Makefile: -------------------------------------------------------------------------------- 1 | ifneq ($(KERNELRELEASE),) 2 | 3 | obj-m := irq_test.o 4 | 5 | else 6 | 7 | KERNELDIR :=/lib/modules/`uname -r`/build 8 | PWD := $(shell pwd) 9 | 10 | all: 11 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 12 | clean: 13 | rm -f *.ko *.o *.mod.o *.mod.c *.order *.symvers 14 | 15 | endif 16 | -------------------------------------------------------------------------------- /document/chardev/proc/irq_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define GPIO_KEY 90 6 | 7 | static irqreturn_t irq_handler(int irq, void *arg) 8 | { 9 | int state = (gpio_get_value_cansleep(GPIO_KEY) ? 1 : 0) ^ 1; 10 | pr_err("%s, %d, state: %d\n", __func__, __LINE__, state); 11 | 12 | return IRQ_HANDLED; 13 | } 14 | 15 | int __init irq_test_init(void) 16 | { 17 | // 获取gpio的中断号 18 | int irq = gpio_to_irq(GPIO_KEY); 19 | 20 | pr_err("%s, %d\n", __func__, __LINE__); 21 | // 申请GPIO的使用权 22 | gpio_request(GPIO_KEY, "gpio_key"); 23 | // 设置为输入 24 | gpio_direction_input(GPIO_KEY); 25 | // 设置中断处理函数,中断触发方式为上升沿和下降沿 26 | request_irq(irq, irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "gpio_irq", NULL); 27 | 28 | return 0; 29 | } 30 | 31 | void __exit irq_test_exit(void) 32 | { 33 | free_irq(gpio_to_irq(GPIO_KEY), NULL); 34 | } 35 | 36 | module_init(irq_test_init); 37 | module_exit(irq_test_exit); 38 | -------------------------------------------------------------------------------- /document/chardev/proc/proc_create.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static int my_show(struct seq_file *file, void *data) 6 | { 7 | seq_printf(file, "hello %s, proc read test\n", "tao"); 8 | return 0; 9 | } 10 | 11 | static int test_open(struct inode *inode, struct file *file) 12 | { 13 | return single_open(file, my_show, NULL); 14 | } 15 | 16 | struct file_operations fops = { 17 | .owner = THIS_MODULE, 18 | .open = test_open, 19 | .release = single_release, 20 | .read = seq_read, 21 | .llseek = seq_lseek, 22 | }; 23 | 24 | struct proc_dir_entry *proc_dir; 25 | int __init proc_init(void) 26 | { 27 | //在proc文件系统上创建空目录 28 | proc_dir = proc_mkdir("proc_test_dir", NULL); 29 | if(!proc_dir) 30 | return -EFAULT; 31 | 32 | //在proc文件系统指定目录上创建文件 33 | proc_create("proc_test", 0644, proc_dir, &fops); 34 | // 另外一种创建方法create_proc_entry()现在已经被淘汰,就不写了。 35 | 36 | return 0; 37 | } 38 | 39 | void __exit proc_exit(void) 40 | { 41 | //先删除proc文件系统上的目录 42 | remove_proc_entry("proc_test", proc_dir); 43 | //删除proc文件系统上proc_test目录的文件 44 | remove_proc_entry("proc_test_dir", NULL); 45 | } 46 | 47 | module_init(proc_init); 48 | module_exit(proc_exit); 49 | 50 | MODULE_LICENSE("GPL"); 51 | MODULE_AUTHOR("Tao"); 52 | -------------------------------------------------------------------------------- /document/chardev/sleepy.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include /* current and everything */ 6 | #include /* printk() */ 7 | #include /* everything... */ 8 | #include /* size_t */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | MODULE_LICENSE("Dual BSD/GPL"); 15 | 16 | struct cdev *cdev; 17 | static int sleepy_major = 255; 18 | dev_t devno; 19 | static struct class *my_class; 20 | // 声明等待队列 21 | static DECLARE_WAIT_QUEUE_HEAD(wq); 22 | static int flag = 0; 23 | 24 | ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos) 25 | { 26 | printk(KERN_DEBUG "process %i (%s) going to sleep\n", 27 | current->pid, current->comm); 28 | // 进入睡眠,唤醒条件flag != 0的时候唤醒。 29 | /* 读的时候,会进入睡眠,需要其他的程序唤醒 30 | * 这里是在write函数中进行唤醒 31 | */ 32 | wait_event_interruptible(wq, flag != 0); 33 | flag = 0; 34 | printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm); 35 | return 0; /* EOF */ 36 | } 37 | 38 | ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count, 39 | loff_t *pos) 40 | { 41 | printk(KERN_DEBUG "process %i (%s) awakening the readers...\n", 42 | current->pid, current->comm); 43 | /* 写操作,更改标志,并试图唤醒工作队列的程序 44 | */ 45 | flag = 1; 46 | wake_up_interruptible(&wq); 47 | return count; /* succeed, to avoid retrial */ 48 | } 49 | 50 | struct file_operations sleepy_fops = { 51 | .owner = THIS_MODULE, 52 | .read = sleepy_read, 53 | .write = sleepy_write, 54 | }; 55 | 56 | int sleepy_init(void) 57 | { 58 | int result; 59 | 60 | devno = MKDEV(sleepy_major, 0); 61 | result = register_chrdev_region(devno, 1, "sleepy"); 62 | 63 | cdev = cdev_alloc(); 64 | cdev->owner = THIS_MODULE; 65 | cdev_init(cdev, &sleepy_fops); 66 | cdev_add(cdev, devno, 1); 67 | 68 | my_class = class_create(THIS_MODULE, "sleepy"); 69 | device_create(my_class, NULL, devno, NULL, "sleepy"); 70 | 71 | return result; 72 | } 73 | 74 | void sleepy_cleanup(void) 75 | { 76 | device_destroy(my_class, MKDEV(sleepy_major, 0)); 77 | class_destroy(my_class); 78 | cdev_del(cdev); 79 | unregister_chrdev_region(devno, 1); 80 | return; 81 | } 82 | 83 | module_init(sleepy_init); 84 | module_exit(sleepy_cleanup); 85 | /* 86 | * 安装模块之后,打开两个窗口 87 | * 运行 cat /dev/sleep, cat程序会阻塞 88 | * 另一个窗口运行echo hello > /dev/sleep, cat进程结束。 89 | * log输出: 90 | * process 3146 (cat) going to sleep 91 | * process 3186 (bash) awakening the readers... 92 | * awoken 3146 (cat) // cat结束,但是如果write里面的flag不更改,还是0,那么不会有这条输出 93 | */ 94 | 95 | -------------------------------------------------------------------------------- /document/chardev/sysfs属性节点创建.md: -------------------------------------------------------------------------------- 1 | sysfs属性节点有两种创建方式。主要是两个函数的区别。 2 | 3 | device_create_file是一个函数创建一个属性。 4 | 5 | sysfs_create_group运行一次,创建多个属性。 6 | 7 | #### device_create_file 8 | 9 | int device_create_file(struct device *dev, const struct device_attribute *attr) 10 | 11 | ``` 12 | struct device *dev; 13 | dev = device_create(my_class, NULL, devno, NULL, "chardev0"); 14 | // 创建属性值, dev是device_create函数返回的struct device, /sys/class/chrdev0/chrdev0/size 15 | rc = device_create_file(dev, &dev_attr_size); 16 | ``` 17 | 模块退出,记得删除对应的文件。 18 | 19 | ``` 20 | 21 | void driver_remove_file(struct device_driver * drv, const struct driver_attribute * attr); 22 | 23 | ``` 24 | 25 | 源码请参考[my_create_attr.c](./my_create_attr.c)。 26 | 27 | #### sysfs_create_group 28 | 29 | 原型如下,需要用到kobject结构体,而struct device中有这个结构体。 30 | int sysfs_create_group(struct kobject *kobj, 31 | const struct attribute_group *grp); 32 | ``` 33 | struct device *dev; 34 | dev = device_create(my_class, NULL, devno, NULL, "chardev0"); 35 | // 创建属性值, dev是device_create函数返回的struct device, /sys/class/chrdev0/chrdev0/size 36 | 37 | // 创建属性值 38 | rc = sysfs_create_group(&dev->kobj, &my_chrdev_attrs_group); 39 | ``` 40 | 41 | 模块退出,也要删除对应属性。 42 | 43 | ``` 44 | // 删除创建的属性值 45 | sysfs_remove_group(&dev->kobj, &my_chrdev_attrs_group); 46 | ``` 47 | 48 | 源码请参考[my_create_group_attr.c](./my_create_group_attr.c)。 49 | 50 | #### 私有数据的访问 51 | 52 | 在设置完属性之后,可以将自己设置的私有属性,保存到struct device中。 53 | ``` 54 | // 将私有数据添加到struct device中 55 | dev_set_drvdata(dev, pdata); 56 | ``` 57 | 58 | 再操作属性值时,通过`deb_get_drvdata`来获取自己的私有数据。 59 | 60 | ``` 61 | static ssize_t my_chrdev_buffer_show(struct device *dev, 62 | struct device_attribute *attr, char *buf) 63 | { 64 | // 获取私有数据 65 | struct my_private_data *data = dev_get_drvdata(dev); 66 | ``` -------------------------------------------------------------------------------- /document/chardev/字符设备注册.md: -------------------------------------------------------------------------------- 1 | 字符设备创建,首先需要确定设备号。设备号相当于设备的身份证号码。 2 | 3 | `dev_t devno`, 其中高12位是主设备号,低20位是次设备号。 4 | 5 | 设备号操作辅助宏: 6 | 7 | ``` 8 | major = MAJOR(devno); //主设备号 9 | minor = MINOR(devno); //次设备号 10 | devno = MKDEV(major, minor); //设备号 11 | ``` 12 | 13 | 设备号申请可以采用动态分配或者静态指定。 14 | 15 | * 动态分配,系统会分配空间的设备号。 16 | 17 | ``` 18 | int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name); 19 | ``` 20 | 21 | * 静态指定,根据传入的设备号申请,可能存在失败的情况。 22 | 23 | ``` 24 | int register_chrdev_region(dev_t from, unsigned count, const char *name) 25 | ``` 26 | 27 | ``` 28 | #define CDEV_MAJOR 255 29 | static int cdev_major = CDEV_MAJOR; 30 | 31 | if (cdev_major) { 32 | // 设备编号,保存主设备号和次设备号 33 | devno = MKDEV(cdev_major, 0); 34 | // 静态分配设备号 35 | /* int register_chrdev_region(dev_t first, unsigned int count, const char *name); 36 | * first是申请的字符设备编号,count是连续设备的编号个数, 37 | * name是和设备编号范围关联的设备名称, 将出现在/proc/devices和sysfs中 38 | */ 39 | rc = register_chrdev_region(devno, 1, DEVICE_NAME); 40 | pr_err("%s,%d, major: %d", __func__, __LINE__, cdev_major); 41 | } else { 42 | // 动态分配设备号 43 | /* int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name); 44 | * dev是申请的字符设备编号,firstminor是要分配的设备的次设备号的起始值,count是连续设备的编号个数,name是设备名称 45 | */ 46 | rc = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME); 47 | // 获取主设备号 48 | cdev_major = MAJOR(devno); 49 | pr_err("%s,%d, major: %d", __func__, __LINE__, cdev_major); 50 | } 51 | ``` 52 | 53 | 申请字符设备空间,并和当前模块,模块操作函数,设备号关联,之后注册到系统中。 54 | 55 | ``` 56 | struct cdev *cdev; 57 | 58 | // 申请字符设备空间,其实也可以不适用指针,直接定义一个变量,那么这个设备申请就可以省略 59 | // 把cdv结构嵌入到你自己封装的设备结构中 60 | cdev = cdev_alloc(); 61 | // 字符设备结构体指向当前的模块 62 | cdev->owner = THIS_MODULE; 63 | 64 | // struect cdev结构体和file_operations结构体绑定 65 | cdev_init(cdev, &chrdev_fops); 66 | 67 | // 注册字符设备驱动 68 | // 注册之后可以在/proc/devices中读取到这个设备 69 | cdev_add(cdev, devno, 1); 70 | ``` 71 | 72 | 模块退出时,同样不要忘记取消注册字符设备。 73 | 74 | ``` 75 | cdev_del(cdev); 76 | // 释放设备号 77 | unregister_chrdev_region(devno, 1); 78 | ``` 79 | 80 | 申请完成之后,可以在/proc/devices中读取设备。 81 | 82 | #### 私有数据访问 83 | 84 | 设备open的时候,可以将私有数据保存到struct file的private_data中。然后其他的read/write/release都可以通过struct file的private_data访问私有数据。 85 | ``` 86 | static int my_chrdev_open(struct inode *inode,struct file *filep) 87 | { 88 | pr_err("%s,%d", __func__, __LINE__); 89 | // open的时候可以将自己的私有数据保存在private_data指针中,那么在read_write的时候,也可以通过这个指针访问对应的数据 90 | // 有时候还需要在这里面识别次设备号 91 | filep->private_data = pdata; 92 | return 0; 93 | } 94 | ``` 95 | 96 | 字符设备源码请参考[my_chrdev.c](./my_chrdev.c)文件。 -------------------------------------------------------------------------------- /document/chardev/杂项设备创建.md: -------------------------------------------------------------------------------- 1 | 杂项设备是主设备号是10的字符设备,是对字符设备进行了封装。 2 | 3 | 申请的时候使用`misc_register`即可,对于此设备号,可以动态指定`MISC_DYNAMIC_MINO`,也可以手动指定。 4 | 5 | 请参考[my_misc.c](./my_misc.c)文件。 -------------------------------------------------------------------------------- /document/chardev/设备节点创建.md: -------------------------------------------------------------------------------- 1 | #### 手动创建设备节点 2 | 3 | 一般情况下,字符设备创建之后,在/dev/下是不会生成设备节点的。 4 | 5 | 所以需要手动创建,通过命令mknod,例如: 6 | 7 | `mknod /dev/my_chrdev c 255 0` 8 | 9 | #### 自动创建设备节点 10 | 11 | 手动创建异常麻烦,而且还需要确定主设备号和次设备号。 12 | 13 | 所以就可以采用自动创建的方法。 14 | 15 | ``` 16 | //由于没有device设备类,所以需要自己先申请一个设备类。 17 | static struct class *my_class; 18 | 19 | // 在/sys/class/下创建chardev0的类 20 | my_class = class_create(THIS_MODULE, "chardev0"); 21 | // 用户空间的udev会响应device_create函数,去/sys/class下寻找对应的类,在/dev下自动创建节点 22 | device_create(my_class, NULL, devno, NULL, "chardev0"); 23 | ``` 24 | 25 | 模块退出时,记得删除对应的设备文件 26 | 27 | ``` 28 | // 首先取消掉对应的节点 29 | device_destroy(my_class, MKDEV(cdev_major, 0)); 30 | // 再删除对应的类 31 | class_destroy(my_class); 32 | ``` 33 | 34 | 源码请参考[my_chrdev.c](./my_chrdev.c)。 -------------------------------------------------------------------------------- /document/input/i2c输入子系统.md: -------------------------------------------------------------------------------- 1 | 机器里有一个tsu6721芯片,是通过I2C通信的。通过读取其中的几个寄存器来验证i2c通信。 2 | 3 | 源码参考[my_i2c](./my_i2c.c) 4 | 5 | ### 添加设备树 6 | 7 | arch/arm64/boot/dts/IDH60/msm8953-mtp.dtsi 8 | 9 | ``` 10 | &i2c_2 { 11 | status = "okay"; 12 | 13 | tsu6721@25 { 14 | reg = <0x25>; 15 | compatible = "ti,tsu6721"; 16 | tsu6721,irq-gpio = <&tlmm 97 0x02>; /* IRQF_TRIGGER_FALLING */ 17 | }; 18 | }; 19 | ``` 20 | 21 | ### 编写驱动 22 | 23 | ##### 设备和驱动匹配 24 | 25 | * struct of_device_id 26 | 27 | 这种是通常使用的匹配设备树的方法,代码中先会使用这种方法来匹配`compatible = "ti,tsu6721";`。不成功才用下面的方法来匹配。 28 | 29 | 但是我自己尝试,只保留of_device_id,删掉i2c_device_id。并没有匹配上,也没有调用probe函数,后面再看原因。 30 | 31 | * struct i2c_device_id 32 | 33 | 可能一个驱动会支持很多设备。如下,有一个匹配和dts上的`tsu6721@25`匹配上即可。 34 | 35 | static const struct i2c_device_id tsu6721_id[] = { 36 | {"tsu6722", 0}, 37 | {"tsu6721", 0}, 38 | {}, 39 | }; 40 | 41 | ##### 发送数据 42 | 43 | 通过i2c_add_driver()注册i2c驱动。 44 | 45 | 驱动匹配成功之后,运行probe函数。 46 | 47 | probe函数运行后,可以通过struct i2c_client的addr获取设备地址25。 48 | 49 | 通信只需要填充struct i2c_msg结构体,然后通过i2c_transfer()发送即可。 50 | 51 | ### 查看效果 52 | 53 | `logcat -b kernel | grep "tsu6721"` 54 | 55 | ``` 56 | 01-01 05:37:54.992 0 0 E : tsu6721_init, 199 57 | 01-01 05:37:54.993 0 0 E tsu6721_probe, 147: DEVICEID: 12 58 | 01-01 05:37:54.995 0 0 E tsu6721_probe, 149: CONTROL: 1f 59 | 01-01 05:37:54.999 0 0 E tsu6721_probe, 153: CONTROL: 1e 60 | ``` -------------------------------------------------------------------------------- /document/input/input输入子系统.md: -------------------------------------------------------------------------------- 1 | 写了一个简单的demo,放在kernel中可以直接运行。源码参考[my_key.c](./my_key.c) 2 | 3 | #### 设备树 4 | 5 | 手上的机器刚好有一个按键,对应GPIO第90脚。删除原来的GPIO90的定义,以免被其他按键占用。 6 | 7 | 首先添加设备树,保证platform_driver可以正常的运行probe函数。 8 | 9 | ``` 10 | --- a/arch/arm64/boot/dts/IDH60/msm8953-mtp.dtsi 11 | +++ b/arch/arm64/boot/dts/IDH60/msm8953-mtp.dtsi 12 | @@ -375,6 +375,17 @@ 13 | gpios = <&tlmm 17 33>; 14 | }; 15 | }; 16 | + tao,gpio-key { 17 | + compatible = "tao,gpio-key"; 18 | + label = "tao key"; 19 | + gpios = <&tlmm 90 01>; 20 | + linux,input-type = <1>; 21 | + linux,code = <115>; 22 | + debounce-interval = <15>; 23 | + linux,can-disable; 24 | + gpio-key,wakeup; 25 | + 26 | + }; 27 | 28 | /* 29 | scanner_rkey { 30 | label = "scanner_rkey"; 31 | gpios = <&tlmm 90 0x1>; 32 | */ 33 | ``` 34 | 35 | ### 设置GPIO中断 36 | 37 | 为了省去步骤,就不写设备树解析的过程了,直接在驱动中设置GPIO。驱动注册之后,与设备树匹配,运行probe函数。 38 | 39 | 设置GPIO未输入,并设置为中断。 40 | ``` 41 | // 获取gpio的中断号 42 | int irq = gpio_to_irq(GPIO_KEY); 43 | // 申请GPIO的使用权 44 | gpio_request(GPIO_KEY, "gpio_key"); 45 | // 设置为输入 46 | gpio_direction_input(GPIO_KEY); 47 | // 设置中断处理函数,中断触发方式为上升沿和下降沿 48 | request_irq(irq, my_key_interrupt, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "gpio_irq", NULL); 49 | ``` 50 | 51 | ### 注册input事件 52 | 53 | ``` 54 | // 申请input设备空间 55 | input = input_allocate_device(); 56 | if (!input) { 57 | pr_err("failed to allocate input device\n"); 58 | return -ENOMEM; 59 | } 60 | 61 | input->name = "my_key"; // 设备名称 62 | input->phys = "my_key/input0"; 63 | input->dev.parent = &pdev->dev; 64 | input->open = my_key_open; 65 | input->close = my_key_close; 66 | input->id.bustype = BUS_HOST; // 总线类型 67 | 68 | input->id.vendor = 0x0001; // 生厂商编号 69 | input->id.product = 0x0001; // 产品编号 70 | input->id.version = 0x0100; // 版本编号 71 | /* 设置这个输入事件支持的事件类型,和对应的code 72 | * 从之前的设备树中可以看到VOLUMEUP对应的是115 73 | * 注册之后,在gpio中断函数中发送对应的input事件才有效 74 | */ 75 | input_set_capability(input, EV_KEY, KEY_VOLUMEDOWN); 76 | //input_set_capability(input, EV_KEY, 115); 77 | //input_set_capability(input, EV_KEY, KEY_VOLUMEUP); 78 | // 这样注册以后,按键的功能就是VOLUMEUP是一样的。 79 | // 同样可以增大音量 80 | error = input_register_device(input); 81 | ``` 82 | 83 | ### input事件如何与gpio关联? 84 | 85 | ``` 86 | input通过如下函数,添加支持的时间,KEY_VOLUMEDOWN,可以调节音量。 87 | input_set_capability(input, EV_KEY, KEY_VOLUMEDOWN); 88 | 89 | gpio中断的时候,发送对应的KEY_VOLUMEDOWN时间,这样就关联上了。 90 | input_report_key(input, KEY_VOLUMEDOWN, !!state); 91 | //input_report_key(input, KEY_VOLUMEUP, !!state); 92 | // 立刻更新按键的状态 93 | input_sync(input); 94 | ``` 95 | 96 | 如果input时间要支持多个按键,可以同样用input_set_capability(input, EV_KEY, KEY_VOLUMEUP);继续添加 97 | 98 | 采用同一个中断函数,但是需要再中断函数中对不同的按键进行区分,上报不同的键值。 99 | 100 | ### 查看结果 101 | 102 | 驱动完成之后,按对应的按键,效果和音量减的效果是一样的。 103 | 104 | 查看驱动的log: 105 | 106 | `logcat -b kernel | grep "my_key"` 107 | 108 | ``` 109 | 01-02 03:58:05.292 0 0 E : my_key_init, 132 110 | 01-02 03:58:05.293 0 0 E : my_key_probe, 75 111 | 01-02 03:58:05.293 0 0 I input : my_key as /devices/platform/soc/soc:tao,gpio-key/input/input3 112 | 01-02 03:58:05.293 0 0 D : counters_connect my_key 113 | 01-02 03:58:05.293 0 0 E : my_key_open, 48 114 | 01-02 03:58:05.510 0 0 E : my_key_interrupt, 32, state: 0 115 | 按键按下 116 | 04-01 07:21:21.630 0 0 E : my_key_interrupt, 32, state: 1 117 | 按键释放 118 | 04-01 07:21:22.385 0 0 E : my_key_interrupt, 32, state: 0 119 | ``` 120 | 121 | 查看新添加的系统在proc下的显示 122 | 123 | `cat /proc/bus/input/devices` 124 | 125 | ``` 126 | I: Bus=0019 Vendor=0001 Product=0001 Version=0100 ## 和代码的定义相同 127 | N: Name="my_key" ## name 128 | P: Phys=my_key/input0 ## phys名称 129 | S: Sysfs=/devices/platform/soc/soc:tao,gpio-key/input/input3 130 | U: Uniq= 131 | H: Handlers=event3 my_key cpufreq ## 中断函数的名称 132 | B: PROP=0 133 | B: EV=3 134 | B: KEY=4000000000000 0 135 | 136 | I: Bus=0019 Vendor=0001 Product=0001 Version=0100 137 | N: Name="gpio-keys" 138 | P: Phys=gpio-keys/input0 139 | S: Sysfs=/devices/platform/soc/soc:gpio_keys/input/input4 140 | U: Uniq= 141 | H: Handlers=event4 gpio-keys cpufreq 142 | B: PROP=0 143 | B: EV=3 144 | B: KEY=8000 8000000000000 0 145 | ``` 146 | 147 | 查看实时事件: 148 | 149 | `getevent -l` 150 | 151 | ``` 152 | could not get driver version for /dev/input/mice, Not a typewriter 153 | add device 1: /dev/input/event2 154 | name: "keyremap_virtual" 155 | add device 2: /dev/input/event1 156 | name: "goodix-ts" 157 | add device 3: /dev/input/event0 158 | name: "qpnp_pon" 159 | add device 4: /dev/input/event4 160 | name: "gpio-keys" 161 | add device 5: /dev/input/event3 162 | name: "my_key" ## my_key,和代码定义相同 163 | ## 按下 164 | /dev/input/event3: EV_KEY KEY_VOLUMEDOWN DOWN 165 | /dev/input/event3: EV_SYN SYN_REPORT 00000000 166 | ## 释放 167 | /dev/input/event3: EV_KEY KEY_VOLUMEDOWN UP 168 | /dev/input/event3: EV_SYN SYN_REPORT 00000000 169 | ``` -------------------------------------------------------------------------------- /document/input/my_i2c.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define TSU6721_WR_INC_FLG 0x80 26 | 27 | #define TSU6721_DEVICEID 0x01 28 | #define TSU6721_CONTROL 0x02 29 | #define TSU6721_INTERRUPT1 0x03 30 | #define TSU6721_INTERRUPT2 0x04 31 | #define TSU6721_INTERRUPTMASK1 0x05 32 | #define TSU6721_INTERRUPTMASK2 0x06 33 | #define TSU6721_ADC 0x07 34 | #define TSU6721_TIMINGSET1 0x08 35 | #define TSU6721_TIMINGSET2 0x09 36 | #define TSU6721_DEVICETYPE1 0x0A 37 | #define TSU6721_DEVICETYPE2 0x0B 38 | #define TSU6721_BUTTON1 0x0C 39 | #define TSU6721_BUTTON2 0x0D 40 | #define TSU6721_MANUAL_S_OR_W1 0x13 41 | #define TSU6721_MANUAL_S_OR_W2 0x14 42 | #define TSU6721_DEVICETYPE3 0x15 43 | #define TSU6721_RESET 0x1B 44 | #define TSU6721_TIMERSETTING 0x20 45 | #define TSU6721_OCL_OR_OCP_SETTING1 0x21 46 | #define TSU6721_OCL_OR_OCP_SETTING2 0x22 47 | #define TSU6721_DEVICETYPE4 0x23 48 | 49 | #define TSU6721_NUM_REGISER 21 50 | #define TSU6721_MAX_ADDR (TSU6721_DEVICETYPE4 + 1) 51 | #define TSU6721_ALL_REGS 0xff 52 | 53 | struct tsu6721_data { 54 | struct i2c_client *client; 55 | uint8_t reg_addr; 56 | uint8_t reg_data[TSU6721_MAX_ADDR]; 57 | } ; 58 | 59 | static int tsu6721_i2c_read_seq(struct tsu6721_data *pdata, uint8_t reg_addr, uint8_t *reg_data, uint8_t num_reg) 60 | { 61 | int ret = 0; 62 | uint8_t buf[2]; 63 | struct i2c_msg msgs[2]; 64 | // 检查传入的寄存器是否合法 65 | if ((num_reg < 1) || (num_reg > TSU6721_NUM_REGISER)) { 66 | return -EINVAL; 67 | } 68 | // 寄存器地址 69 | buf[0] = reg_addr | TSU6721_WR_INC_FLG; 70 | 71 | // 将i2c的地址和寄存器的地址发送给i2c客户端 72 | // 设备的i2c地址 73 | msgs[0].addr = pdata->client->addr, 74 | // 写操作 75 | msgs[0].flags = 0, 76 | // 长度 77 | msgs[0].len = 1, 78 | // 要读取的寄存器地址 79 | msgs[0].buf = &buf[0], 80 | 81 | // 开始读操作 82 | // 设备的i2c地址 83 | msgs[1].addr = pdata->client->addr, 84 | // 读操作 85 | msgs[1].flags = I2C_M_RD, 86 | // 读长度 87 | msgs[1].len = num_reg, 88 | // 读取的数据保存在reg_data中 89 | msgs[1].buf = reg_data, 90 | 91 | /* 将i2c数据发送出去,返回值表示操作是否成功。 92 | * 后面的2表示通信的次数,这里有2次,一次写操作,一次读操作。 93 | * 有一次寻址,发送i2c地址,就表示是一次操作。 94 | */ 95 | ret = i2c_transfer(pdata->client->adapter, msgs, 2); 96 | return ret; 97 | } 98 | 99 | // i2c写函数 100 | static int tsu6721_i2c_write_seq(struct tsu6721_data *pdata, uint8_t reg_addr, uint8_t *reg_data, uint8_t num_reg) 101 | { 102 | int ret = 0; 103 | struct i2c_msg msg; 104 | uint8_t buf[TSU6721_NUM_REGISER]; 105 | 106 | // if ((num_reg < 1) || (num_reg >= TSU6721_NUM_REGISER)) { 107 | // return -EINVAL; 108 | // } 109 | // 获取设备的寄存器地址 110 | buf[0] = reg_addr | TSU6721_WR_INC_FLG; 111 | memcpy(&buf[1], reg_data, num_reg); 112 | 113 | // i2c地址 114 | msg.addr = pdata->client->addr; 115 | // 写操作 116 | msg.flags = 0; 117 | // 数据长度, 数据长度num_reg,还有一次是发送的寄存器地址 118 | msg.len = num_reg + 1; 119 | // 数据 120 | msg.buf = buf; 121 | // 发送数据 122 | ret = i2c_transfer(pdata->client->adapter, &msg, 1); 123 | return ret; 124 | } 125 | 126 | static int tsu6721_probe(struct i2c_client *client, 127 | const struct i2c_device_id *id) 128 | { 129 | int err = 0; 130 | struct tsu6721_data *data; 131 | uint8_t reg_data; 132 | // 申请私有数据的空间 133 | data = devm_kzalloc(&client->dev, sizeof(struct tsu6721_data), GFP_KERNEL); 134 | if (!data) { 135 | err = -ENOMEM; 136 | pr_err("devm_kzalloc failed\n"); 137 | goto probe_exit_err; 138 | } 139 | 140 | data->client = client; 141 | data->reg_addr = TSU6721_DEVICEID; 142 | 143 | // 保存数据到i2c client结构体中 144 | i2c_set_clientdata(client, data); 145 | 146 | tsu6721_i2c_read_seq(data, TSU6721_DEVICEID, &data->reg_data[TSU6721_DEVICEID], 1); 147 | pr_err("%s, %d: DEVICEID: %x\n", __func__, __LINE__, data->reg_data[TSU6721_DEVICEID]); 148 | tsu6721_i2c_read_seq(data, TSU6721_CONTROL, &data->reg_data[TSU6721_CONTROL], 1); 149 | pr_err("%s, %d: CONTROL: %x\n", __func__, __LINE__, data->reg_data[TSU6721_CONTROL]); 150 | data->reg_data[TSU6721_CONTROL] = 0x1e; 151 | tsu6721_i2c_write_seq(data, TSU6721_CONTROL, &data->reg_data[TSU6721_CONTROL], 1); 152 | tsu6721_i2c_read_seq(data, TSU6721_CONTROL, &data->reg_data[TSU6721_CONTROL], 1); 153 | pr_err("%s, %d: CONTROL: %x\n", __func__, __LINE__, data->reg_data[TSU6721_CONTROL]); 154 | 155 | return 0; 156 | 157 | probe_exit_err: 158 | pr_err("%s, %d\n", __func__, __LINE__); 159 | i2c_unregister_device(client); 160 | 161 | return err; 162 | } 163 | 164 | static int tsu6721_remove(struct i2c_client *client) 165 | { 166 | // 从i2c client结构体中获取私有数据 167 | struct tsu6721_data *data = i2c_get_clientdata(client); 168 | pr_err("%s, %d\n", __func__, __LINE__); 169 | // 取消注册i2c设备 170 | i2c_unregister_device(client); 171 | return 0; 172 | } 173 | 174 | static struct of_device_id tsu6721_match_table[] = { 175 | // 与设备树匹配 176 | { .compatible = "ti,tsu6721",}, 177 | { }, 178 | }; 179 | 180 | static const struct i2c_device_id tsu6721_id[] = { 181 | {"tsu6721", 0}, 182 | {}, 183 | }; 184 | 185 | static struct i2c_driver tsu6721_driver = { 186 | // probe函数 187 | .probe = tsu6721_probe, 188 | .remove = tsu6721_remove, 189 | .driver = { 190 | .name = "tsu6721", 191 | .owner = THIS_MODULE, 192 | .of_match_table = tsu6721_match_table, 193 | }, 194 | .id_table = tsu6721_id, 195 | }; 196 | 197 | int __init tsu6721_init(void) 198 | { 199 | pr_err("%s, %d\n", __func__, __LINE__); 200 | return i2c_add_driver(&tsu6721_driver); 201 | } 202 | module_init(tsu6721_init); 203 | 204 | void __exit tsu6721_exit(void) 205 | { 206 | pr_err("%s, %d\n", __func__, __LINE__); 207 | i2c_del_driver(&tsu6721_driver); 208 | } 209 | module_exit(tsu6721_exit); 210 | 211 | MODULE_DESCRIPTION("Texas Instruments tsu6721 driver"); 212 | MODULE_LICENSE("GPL v2"); 213 | -------------------------------------------------------------------------------- /document/input/my_key.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | struct input_dev *input; 26 | 27 | #define GPIO_KEY 90 28 | 29 | static irqreturn_t my_key_interrupt(int irq, void *dev_id) 30 | { 31 | int state = (gpio_get_value_cansleep(GPIO_KEY) ? 1 : 0) ^ 1; 32 | pr_err("%s, %d, state: %d\n", __func__, __LINE__, state); 33 | // 上报按键按键的状态,按下还是释放 34 | input_report_key(input, KEY_VOLUMEDOWN, !!state); 35 | //input_report_key(input, KEY_VOLUMEUP, !!state); 36 | // 立刻更新按键的状态 37 | input_sync(input); 38 | 39 | return IRQ_HANDLED; 40 | } 41 | 42 | static const struct of_device_id my_key_of_match[] = { 43 | { .compatible = "tao,gpio-key", .data = NULL}, 44 | }; 45 | 46 | static int my_key_open(struct input_dev *input) 47 | { 48 | pr_err("%s, %d\n", __func__, __LINE__); 49 | 50 | return 0; 51 | } 52 | 53 | static void my_key_close(struct input_dev *input) 54 | { 55 | pr_err("%s, %d\n", __func__, __LINE__); 56 | } 57 | 58 | static int my_key_remove(struct platform_device *pdv) 59 | { 60 | struct gpio_keys_drvdata *ddata = platform_get_drvdata(pdv); 61 | 62 | pr_err("%s, %d\n", __func__, __LINE__); 63 | 64 | input_unregister_device(input); 65 | 66 | return 0; 67 | } 68 | 69 | static int my_key_probe(struct platform_device *pdev) 70 | { 71 | // 获取gpio的中断号 72 | int irq = gpio_to_irq(GPIO_KEY); 73 | int error = 0; 74 | 75 | pr_err("%s, %d\n", __func__, __LINE__); 76 | // 申请GPIO的使用权 77 | gpio_request(GPIO_KEY, "gpio_key"); 78 | // 设置为输入 79 | gpio_direction_input(GPIO_KEY); 80 | // 设置中断处理函数,中断触发方式为上升沿和下降沿 81 | request_irq(irq, my_key_interrupt, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "gpio_irq", NULL); 82 | 83 | // 申请input设备空间 84 | input = input_allocate_device(); 85 | if (!input) { 86 | pr_err("failed to allocate input device\n"); 87 | return -ENOMEM; 88 | } 89 | 90 | input->name = "my_key"; // 设备名称 91 | input->phys = "my_key/input0"; 92 | input->dev.parent = &pdev->dev; 93 | input->open = my_key_open; 94 | input->close = my_key_close; 95 | input->id.bustype = BUS_HOST; // 总线类型 96 | 97 | input->id.vendor = 0x0001; // 生厂商编号 98 | input->id.product = 0x0001; // 产品编号 99 | input->id.version = 0x0100; // 版本编号 100 | /* 设置这个输入事件支持的事件类型,和对应的code 101 | * 从之前的设备树中可以看到VOLUMEUP对应的是115 102 | * 注册之后,在gpio中断函数中发送对应的input事件才有效 103 | */ 104 | input_set_capability(input, EV_KEY, KEY_VOLUMEDOWN); 105 | //input_set_capability(input, EV_KEY, 115); 106 | //input_set_capability(input, EV_KEY, KEY_VOLUMEUP); 107 | // 这样注册以后,按键的功能就是VOLUMEUP是一样的。 108 | // 同样可以增大音量 109 | error = input_register_device(input); 110 | if (error) { 111 | pr_err("Unable to register input device, error: %d\n", 112 | error); 113 | goto err_remove_group; 114 | } 115 | 116 | return 0; 117 | 118 | err_remove_group: 119 | return error; 120 | } 121 | 122 | static struct platform_driver my_key_device_driver = { 123 | .probe = my_key_probe, 124 | .remove = my_key_remove, 125 | .driver = { 126 | .name = "gpio-key", 127 | .owner = THIS_MODULE, 128 | .of_match_table = my_key_of_match, 129 | } 130 | }; 131 | 132 | static int __init my_key_init(void) 133 | { 134 | pr_err("%s, %d\n", __func__, __LINE__); 135 | return platform_driver_register(&my_key_device_driver); 136 | } 137 | 138 | static void __exit my_key_exit(void) 139 | { 140 | pr_err("%s, %d\n", __func__, __LINE__); 141 | platform_driver_unregister(&my_key_device_driver); 142 | } 143 | 144 | module_init(my_key_init); 145 | module_exit(my_key_exit); 146 | 147 | MODULE_LICENSE("GPL"); 148 | MODULE_AUTHOR("Tao"); 149 | MODULE_DESCRIPTION("simple key input demo"); 150 | -------------------------------------------------------------------------------- /document/irq/irq_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define GPIO_KEY 90 6 | 7 | static irqreturn_t irq_handler(int irq, void *arg) 8 | { 9 | int state = (gpio_get_value_cansleep(GPIO_KEY) ? 1 : 0) ^ 1; 10 | pr_err("%s, %d, state: %d\n", __func__, __LINE__, state); 11 | 12 | return IRQ_HANDLED; 13 | } 14 | 15 | int __init irq_test_init(void) 16 | { 17 | // 获取gpio的中断号 18 | int irq = gpio_to_irq(GPIO_KEY); 19 | 20 | pr_err("%s, %d\n", __func__, __LINE__); 21 | // 申请GPIO的使用权 22 | gpio_request(GPIO_KEY, "gpio_key"); 23 | // 设置为输入 24 | gpio_direction_input(GPIO_KEY); 25 | // 设置中断处理函数,中断触发方式为上升沿和下降沿 26 | request_irq(irq, irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "gpio_irq", NULL); 27 | 28 | return 0; 29 | } 30 | 31 | void __exit irq_test_exit(void) 32 | { 33 | free_irq(gpio_to_irq(GPIO_KEY), NULL); 34 | } 35 | 36 | module_init(irq_test_init); 37 | module_exit(irq_test_exit); 38 | -------------------------------------------------------------------------------- /document/irq/tasklet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define GPIO_KEY 90 8 | 9 | struct tasklet_struct task; 10 | spinlock_t lock; 11 | 12 | int critical(const char *s, spinlock_t *lock) 13 | { 14 | int i; 15 | unsigned long flag; 16 | static int cnt = 0; 17 | 18 | /*spin_lock(lock);*/ 19 | /*local_irq_disable();*/ 20 | /*local_irq_save(flag);*/ 21 | /*spin_lock_irq(lock);*/ 22 | /*spin_lock_irqsave(lock, flag);*/ 23 | spin_lock_bh(lock); //bottom half 关闭软中断,加锁 24 | 25 | for(i = 0; i < 3; i++) 26 | { 27 | pr_err("%s, %d, count = %d, %s\n", __func__, __LINE__, cnt++, s); 28 | mdelay(1000); 29 | } 30 | 31 | spin_unlock_bh(lock); 32 | /*spin_unlock_irqrestore(lock, flag);*/ 33 | /*spin_unlock_irq(lock);*/ 34 | /*local_irq_restore(flag);*/ 35 | /*local_irq_enable();*/ 36 | /*spin_unlock(lock);*/ 37 | 38 | return 0; 39 | } 40 | 41 | void task_main(unsigned long data) 42 | { 43 | // 取出参数 44 | spinlock_t *p = (spinlock_t *)data; 45 | pr_err("%s, %d\n", __func__, __LINE__); 46 | 47 | critical("softirq.\n", p); 48 | } 49 | 50 | static irqreturn_t irq_handler(int irq, void *arg) 51 | { 52 | int state = (gpio_get_value_cansleep(GPIO_KEY) ? 1 : 0) ^ 1; 53 | // 中断传入的参数 54 | spinlock_t *p = (spinlock_t *)arg; 55 | 56 | pr_err("%s, %d, state: %d\n", __func__, __LINE__, state); 57 | // 调用队列,并传入参数,至于队列里面的任务什么时候运行,由cpu调度 58 | tasklet_schedule(&task); 59 | 60 | return IRQ_HANDLED; 61 | } 62 | 63 | int __init tasklet_test_init(void) 64 | { 65 | // 获取gpio的中断号 66 | int irq = gpio_to_irq(GPIO_KEY); 67 | // 初始化tasklet,定义处理函数,传入参数 68 | tasklet_init(&task, task_main, (unsigned long)&lock); 69 | // 初始化自旋锁 70 | spin_lock_init(&lock); 71 | 72 | pr_err("%s, %d\n", __func__, __LINE__); 73 | // 申请GPIO的使用权 74 | gpio_request(GPIO_KEY, "gpio_key"); 75 | // 设置为输入 76 | gpio_direction_input(GPIO_KEY); 77 | // 设置中断处理函数,中断触发方式为上升沿和下降沿, 将锁作为参数传入 78 | request_irq(irq, irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "gpio_irq", &lock); 79 | 80 | return 0; 81 | } 82 | 83 | void __exit tasklet_test_exit(void) 84 | { 85 | free_irq(gpio_to_irq(GPIO_KEY), NULL); 86 | } 87 | 88 | module_init(tasklet_test_init); 89 | module_exit(tasklet_test_exit); 90 | 91 | -------------------------------------------------------------------------------- /document/irq/tasklet_workqueue的区别.md: -------------------------------------------------------------------------------- 1 | 转载:https://www.cnblogs.com/alantu2018/p/8527205.html 2 | 3 | ``` 4 | 软中断、tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来。 5 | 下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任务队列的函数都消失了,只剩下了前三者。 6 | 介绍这三种下半部实现之前,有必要说一下上半部与下半部的区别。 7 | 上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务。 8 | 举个例子:在网络传输中,网卡接收到数据包这个事件不一定需要马上被处理,适合用下半部去实现;但是用户敲击键盘这样的事件就必须马上被响应,应该用中断实现。 9 | 两者的主要区别在于:中断不能被相同类型的中断打断,而下半部依然可以被中断打断;中断对于时间非常敏感,而下半部基本上都是一些可以延迟的工作。 10 | 由于二者的这种区别,所以对于一个工作是放在上半部还是放在下半部去执行,可以参考下面4条: 11 | 12 | 如果一个任务对时间非常敏感,将其放在中断处理程序中执行。 13 | 如果一个任务和硬件相关,将其放在中断处理程序中执行。 14 | 如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。 15 | 其他所有任务,考虑放在下半部去执行。 16 | 有写内核任务需要延后执行,因此才有的下半部,进而实现了三种实现下半部的方法。这就是本文要讨论的软中断、tasklet和工作队列。 17 | 18 | 19 | 1. 软中断 20 | 软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。 21 | 软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别, 22 | 使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括: 23 | 24 | 产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断(即单个cpu上软中断不能嵌套执行),只能被硬件中断打断(上半部)。 25 | 可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保其数据结构。 26 | 27 | 2. tasklet 28 | 由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行, 29 | 那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。它具有以下特性: 30 | a)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。 31 | b)多个不同类型的tasklet可以并行在多个CPU上。 32 | c)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。 33 | tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择。也就是说tasklet是软中断的一种特殊用法,即延迟情况下的串行执行。 34 | 35 | 3. 工作队列 36 | 从上面的介绍看以看出,软中断运行在中断上下文中,因此不能阻塞和睡眠,而tasklet使用软中断实现,当然也不能阻塞和睡眠。 37 | 但如果某延迟处理函数需要睡眠或者阻塞呢?没关系工作队列就可以如您所愿了。 38 | 把推后执行的任务叫做工作(work),描述它的数据结构为work_struct ,这些工作以队列结构组织成工作队列(workqueue), 39 | 其数据结构为workqueue_struct ,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events。 40 | 工作队列(work queue)是另外一种将工作推后执行的形式。工作队列可以把工作推后,交由一个内核线程去执行—这个下半部分总是会在进程上下文执行, 41 | 但由于是内核线程,其不能访问用户空间。最重要特点的就是工作队列允许重新调度甚至是睡眠。 42 | 通常,在工作队列和软中断/tasklet中作出选择非常容易。可使用以下规则: 43 | - 如果推后执行的任务需要睡眠,那么只能选择工作队列。 44 | - 如果推后执行的任务需要延时指定的时间再触发,那么使用工作队列,因为其可以利用timer延时(内核定时器实现)。 45 | - 如果推后执行的任务需要在一个tick之内处理,则使用软中断或tasklet,因为其可以抢占普通进程和内核线程,同时不可睡眠。 46 | - 如果推后执行的任务对延迟的时间没有任何要求,则使用工作队列,此时通常为无关紧要的任务。 47 | 实际上,工作队列的本质就是将工作交给内核线程处理,因此其可以用内核线程替换。但是内核线程的创建和销毁对编程者的要求较高, 48 | 而工作队列实现了内核线程的封装,不易出错,所以我们也推荐使用工作队列。 49 | 50 | ``` 51 | -------------------------------------------------------------------------------- /document/irq/workqueue_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define GPIO_KEY 90 8 | 9 | #define NUM 2 10 | 11 | struct work_struct t[NUM]; 12 | 13 | struct workqueue_struct *wq; 14 | 15 | void work_main(struct work_struct *work) 16 | { 17 | int i; 18 | static int cnt; 19 | 20 | for(i = 0; i < 3; i++) 21 | { 22 | pr_err("work, count = %d\n", cnt++); 23 | //在进程上下文执行,可以sleep 24 | msleep(1000); 25 | } 26 | } 27 | 28 | static irqreturn_t irq_handler(int irq, void *arg) 29 | { 30 | //int state = (gpio_get_value_cansleep(GPIO_KEY) ? 1 : 0) ^ 1; 31 | static int i; 32 | 33 | if(i == NUM) 34 | i = 0; 35 | 36 | if(!schedule_work(&t[i++])) 37 | pr_err("work is already in the queue.\n"); 38 | 39 | return IRQ_HANDLED; 40 | } 41 | 42 | int __init tasklet_test_init(void) 43 | { 44 | // 获取gpio的中断号 45 | int irq = gpio_to_irq(GPIO_KEY); 46 | 47 | int ret, i; 48 | 49 | //初始化工作结构, 工作队列及回调函数 50 | for(i = 0; i < NUM; i++) 51 | INIT_WORK(&t[i], work_main); 52 | 53 | pr_err("%s, %d\n", __func__, __LINE__); 54 | // 申请GPIO的使用权 55 | gpio_request(GPIO_KEY, "gpio_key"); 56 | // 设置为输入 57 | gpio_direction_input(GPIO_KEY); 58 | // 设置中断处理函数,中断触发方式为上升沿和下降沿, 将锁作为参数传入 59 | ret = request_irq(irq, irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "gpio_irq", NULL); 60 | if (ret) { 61 | pr_err("%s, %d request irq fail\n", __func__, __LINE__); 62 | goto err1; 63 | 64 | } 65 | return ret; 66 | 67 | err1: 68 | destroy_workqueue(wq); 69 | return ret; 70 | } 71 | 72 | void __exit tasklet_test_exit(void) 73 | { 74 | free_irq(gpio_to_irq(GPIO_KEY), NULL); 75 | destroy_workqueue(wq); 76 | } 77 | 78 | module_init(tasklet_test_init); 79 | module_exit(tasklet_test_exit); 80 | 81 | -------------------------------------------------------------------------------- /document/irq/workqueue_thread_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define GPIO_KEY 90 8 | 9 | #define NUM 2 10 | 11 | //定义延时工作 12 | //要和INIT_DELAYED_WORK()一起使用 13 | //struct delayed_work t[NUM]; 14 | 15 | struct work_struct t[NUM]; 16 | 17 | struct workqueue_struct *wq; 18 | 19 | static void work_main(struct work_struct *work) 20 | { 21 | int i; 22 | static int cnt; 23 | 24 | for(i = 0; i < 3; i++) 25 | { 26 | pr_err("work, count = %d\n", cnt++); 27 | //在进程上下文执行,可以sleep 28 | msleep(1000); 29 | } 30 | } 31 | 32 | static irqreturn_t irq_handler(int irq, void *arg) 33 | { 34 | //int state = (gpio_get_value_cansleep(GPIO_KEY) ? 1 : 0) ^ 1; 35 | static int i; 36 | 37 | if(i == NUM) 38 | i = 0; 39 | 40 | /*if(!my_schedule(&t[i++]))*/ 41 | /*if(!schedule_work(&t[i++]))*/ 42 | // 在线程中执行工作队列 43 | if(!queue_work(wq, &t[i++])) 44 | // 延时3秒来执行work queue 45 | //if(!queue_delayed_work(wq, &t[i++], 3 * HZ)) 46 | pr_err("work is already in the queue.\n"); 47 | 48 | return IRQ_HANDLED; 49 | } 50 | 51 | int __init tasklet_test_init(void) 52 | { 53 | // 获取gpio的中断号 54 | int irq = gpio_to_irq(GPIO_KEY); 55 | 56 | int ret, i; 57 | 58 | //初始化工作结构, 工作队列及回调函数 59 | for(i = 0; i < NUM; i++) 60 | INIT_WORK(&t[i], work_main); 61 | //INIT_DELAYED_WORK(&t[i], work_main); 62 | 63 | //创建工作队列, 多个cpu,每个cpu都会创建一个线程 64 | /*wq = create_workqueue("my_workqueue");*/ 65 | //只创建一个工作者线程 66 | wq = create_singlethread_workqueue("my_workqueue"); 67 | if(!wq) 68 | { 69 | pr_err("%s, %d workqueue create failed\n", __func__, __LINE__); 70 | ret = -ENOMEM; 71 | goto err0; 72 | } 73 | 74 | pr_err("%s, %d\n", __func__, __LINE__); 75 | // 申请GPIO的使用权 76 | gpio_request(GPIO_KEY, "gpio_key"); 77 | // 设置为输入 78 | gpio_direction_input(GPIO_KEY); 79 | // 设置中断处理函数,中断触发方式为上升沿和下降沿, 将锁作为参数传入 80 | ret = request_irq(irq, irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "gpio_irq", NULL); 81 | if (ret) { 82 | pr_err("%s, %d request irq fail\n", __func__, __LINE__); 83 | goto err1; 84 | 85 | } 86 | return ret; 87 | 88 | err1: 89 | destroy_workqueue(wq); 90 | err0: 91 | return ret; 92 | } 93 | 94 | void __exit tasklet_test_exit(void) 95 | { 96 | free_irq(gpio_to_irq(GPIO_KEY), NULL); 97 | destroy_workqueue(wq); 98 | } 99 | 100 | module_init(tasklet_test_init); 101 | module_exit(tasklet_test_exit); 102 | 103 | -------------------------------------------------------------------------------- /document/irq/中断上下部.md: -------------------------------------------------------------------------------- 1 | 中断上下部(top half、bottom half) 2 | 3 | 参考: 4 | 5 | https://blog.csdn.net/Wenlong_L/article/details/82020343 6 | 7 | https://www.cnblogs.com/mude918/p/9006371.html 8 | 9 | ``` 10 | 对于中断处理而言,linux将其分成了两个部分, 11 | 一个叫做中断handler(top half),是全程关闭中断的, 12 | 另外一部分是deferable task(bottom half),属于不那么紧急需要处理的事情。 13 | 在执行bottom half的时候,是开中断的。 14 | 有多种bottom half的机制,例如:softirq、tasklet、workqueue 15 | 16 | 中断处理程序处于中断上下文,而中断上下文是不参与调度的,这样中断上下文不能出现可能休眠的操作, 17 | 因为中断上下文一旦休眠就会交出cpu,而中断上下文不参与调度,也就是交出cpu时不会保存上下文, 18 | 这样一旦交出cpu就永远不会回来了。这也就决定了在中断中不能和用户空间进行数据交互,因为这种交互可能会引起休眠。 19 | 20 | 还有一点,为了尽量提高系统的响应速度,中断程序运行的时间应该尽可能短。比如说中断来临后系统在 21 | 处理中断服务程序,此时有一个事件发生,如果这个事件的优先级没有此时的中断优先级高,这个事件 22 | 就会等待中断处理完成,而中断处理的时间越长,该事件等待的时间就越长,因为中断是不能被调度的, 23 | 这样就会导致事件的响应速度很差,也就是响应性能不好。 24 | 25 | 在Linux内核,所有中断也划分为两类:硬件中断和软件中断。接下来看看Linux内核是如何定义硬中断和软中断的, 26 | 硬中断 27 | 指的是处理硬件中断和IPIs「Inter Processor Interrupts」的硬中断上下文「hard interrupt context」, 28 | 其既不可能出现睡眠也不可能被抢占「preemption」,对处理时间的要求比较苛刻,中断处理程序执行的速度越快越好, 29 | 这样能够提高系统的实时性。软中断指的是处理费时任务的软中断上下文,其既不可能出现睡眠也不可能被软中断上下文 30 | 执行的其它任务抢占,但可以被硬中断上下文执行的处理任务抢占, 对处理时间的要求较宽松,主要处理比较耗时的和可中断的任务。 31 | 不过,在硬中断上下文和软中断上下文之间出现共享数据时必须考虑数据同步「synchronization」问题。 32 | 33 | softirq「软中断」 34 | softirq也是Linux下半部分「bottom-half」机制中的一种延迟处理机制,运行在软中断上下文并能高效地处理延迟任务。 35 | ``` -------------------------------------------------------------------------------- /document/mem/01alloc_pages/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct page *p; 6 | /*void *virt = NULL;*/ 7 | unsigned long virt; 8 | int __init test_init(void) 9 | { 10 | /* 11 | *printk("order = %d\n", get_order(1234)); 12 | *printk("order = %d\n", get_order(5000)); 13 | */ 14 | /* 15 | * p = alloc_pages(GFP_KERNEL, get_order(1234)); 16 | * if(!p) 17 | * return -ENOMEM; 18 | * 19 | * virt = page_address(p); 20 | * printk("virt = %p.\n", virt); 21 | */ 22 | virt = __get_free_pages(GFP_KERNEL, get_order(1234)); 23 | if(!virt) 24 | return -ENOMEM; 25 | 26 | printk("virt = %p.\n", (void *)virt); 27 | 28 | 29 | return 0; 30 | } 31 | 32 | 33 | void __exit test_exit(void) 34 | { 35 | /*__free_pages(p, get_order(1234));*/ 36 | free_pages(virt, get_order(1234)); 37 | } 38 | 39 | module_init(test_init); 40 | module_exit(test_exit); 41 | 42 | MODULE_LICENSE("GPL"); 43 | 44 | -------------------------------------------------------------------------------- /document/mem/02kmalloc/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void *virt = NULL; 6 | int __init test_init(void) 7 | { 8 | /* 9 | * kmalloc使用GFP_KERNEL,有可能会休眠。 10 | * 如果在中断中申请内存,需要使用GFP_ATOMIC 11 | */ 12 | /*virt = kmalloc(1234, GFP_KERNEL);*/ 13 | /*virt = kmalloc(0x400000, GFP_KERNEL);*/ 14 | virt = kzalloc(0x400000, GFP_KERNEL); 15 | if(!virt) 16 | return -ENOMEM; 17 | 18 | printk("virt = %p.\n", virt); 19 | 20 | return 0; 21 | } 22 | 23 | 24 | void __exit test_exit(void) 25 | { 26 | kfree(virt); 27 | } 28 | 29 | module_init(test_init); 30 | module_exit(test_exit); 31 | 32 | MODULE_LICENSE("GPL"); 33 | 34 | 35 | -------------------------------------------------------------------------------- /document/mem/03vmalloc/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void *virt = NULL; 6 | int __init test_init(void) 7 | { 8 | virt = vmalloc(0x800000); 9 | if(!virt) 10 | return -ENOMEM; 11 | 12 | printk("virt = %p.\n", virt); 13 | 14 | return 0; 15 | } 16 | 17 | 18 | void __exit test_exit(void) 19 | { 20 | vfree(virt); 21 | } 22 | 23 | module_init(test_init); 24 | module_exit(test_exit); 25 | 26 | MODULE_LICENSE("GPL"); 27 | 28 | 29 | -------------------------------------------------------------------------------- /document/mem/04kmem_cache/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct kmem_cache *kc; 6 | void *p[5]; 7 | int __init test_init(void) 8 | { 9 | int i; 10 | kc = kmem_cache_create("kc", 16, 0, SLAB_HWCACHE_ALIGN, NULL); 11 | if(!kc) 12 | return -ENOMEM; 13 | 14 | for(i = 0; i < 5; i++) 15 | { 16 | p[i] = kmem_cache_alloc(kc, GFP_KERNEL); 17 | printk("p[%d] = %p.\n", i, p[i]); 18 | } 19 | 20 | return 0; 21 | } 22 | 23 | 24 | void __exit test_exit(void) 25 | { 26 | int i; 27 | 28 | for(i = 0; i < 5; i++) 29 | kmem_cache_free(kc, p[i]); 30 | kmem_cache_destroy(kc); 31 | } 32 | 33 | module_init(test_init); 34 | module_exit(test_exit); 35 | 36 | MODULE_LICENSE("GPL"); 37 | 38 | 39 | -------------------------------------------------------------------------------- /document/mem/05mem/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int __init test_init(void) 6 | { 7 | printk("high_memory = %p\n", high_memory); 8 | printk("VMALLOC_START = %#lx\n", VMALLOC_START); 9 | printk("VMALLOC_END = %#lx\n", VMALLOC_END); 10 | printk("PAGE_OFFSET = %#lx\n", PAGE_OFFSET); 11 | printk("PHYS_OFFSET = %#lx\n", PHYS_OFFSET); 12 | 13 | return 0; 14 | } 15 | 16 | 17 | void __exit test_exit(void) 18 | { 19 | } 20 | 21 | module_init(test_init); 22 | module_exit(test_exit); 23 | 24 | MODULE_LICENSE("GPL"); 25 | 26 | 27 | -------------------------------------------------------------------------------- /document/mem/06dma/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | dma_addr_t phys; //物理地址 physical 6 | void *virt; //虚拟地址 virtual 7 | int __init test_init(void) 8 | { 9 | int val; 10 | 11 | virt = dma_alloc_coherent(NULL, 100, &phys, GFP_KERNEL); 12 | if(!virt) 13 | return -ENOMEM; 14 | 15 | printk("phys = %#x\n", phys); 16 | printk("virt = %p\n", virt); 17 | 18 | *(int *)virt = 11223344; 19 | 20 | /*virt = phys + PAGE_OFFSET - PHYS_OFFSET*/ 21 | val = *(int *)(phys + PAGE_OFFSET - PHYS_OFFSET); 22 | printk("val = %d\n", val); 23 | 24 | 25 | 26 | return 0; 27 | } 28 | 29 | 30 | void __exit test_exit(void) 31 | { 32 | dma_free_coherent(NULL, 100, virt, phys); 33 | } 34 | 35 | module_init(test_init); 36 | module_exit(test_exit); 37 | 38 | MODULE_LICENSE("GPL"); 39 | 40 | -------------------------------------------------------------------------------- /document/mem/07err/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct test_t { 7 | int n; 8 | }; 9 | 10 | struct test_t *test_create(void) 11 | { 12 | struct test_t *po; 13 | 14 | po = kmalloc(sizeof(struct test_t), GFP_KERNEL); 15 | /*po = kmalloc(0x900000, GFP_KERNEL);*/ 16 | if(NULL == po) 17 | return ERR_PTR(-ENOMEM); 18 | 19 | return po; 20 | } 21 | 22 | void test_destroy(struct test_t *p) 23 | { 24 | kfree(p); 25 | } 26 | 27 | struct test_t *p; 28 | int __init test_init(void) 29 | { 30 | p = test_create(); 31 | if(IS_ERR(p)) 32 | return PTR_ERR(p); 33 | 34 | return 0; 35 | } 36 | 37 | 38 | void __exit test_exit(void) 39 | { 40 | test_destroy(p); 41 | } 42 | 43 | module_init(test_init); 44 | module_exit(test_exit); 45 | 46 | MODULE_LICENSE("GPL"); 47 | 48 | 49 | -------------------------------------------------------------------------------- /document/platform/msm8953-mtp.diff: -------------------------------------------------------------------------------- 1 | diff --git a/arch/arm64/boot/dts/IDH60/msm8953-mtp.dtsi b/arch/arm64/boot/dts/IDH60/msm8953-mtp.dtsi 2 | index e08f20d..ea0887b 100755 3 | --- a/arch/arm64/boot/dts/IDH60/msm8953-mtp.dtsi 4 | +++ b/arch/arm64/boot/dts/IDH60/msm8953-mtp.dtsi 5 | @@ -454,6 +454,12 @@ 6 | }; 7 | 8 | }; 9 | + tao,my_platform { 10 | + compatible = "tao,my_platform"; 11 | + status = "okay"; 12 | + tao,size = <1>; 13 | + tao,name = "my_platform_test"; 14 | + }; 15 | 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /document/platform/my_platform.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define DEVICE_NAME "my_platform_chrdev" 13 | 14 | #define CDEV_MAJOR 254 15 | static int cdev_major = CDEV_MAJOR; 16 | static int cdev_minor = 22; 17 | dev_t devno; 18 | 19 | struct my_platform_drvdata { 20 | struct platform_device *pdev; 21 | struct cdev *cdev; 22 | uint32_t size; 23 | char name[64]; 24 | }; 25 | 26 | static struct class *my_class; 27 | 28 | static ssize_t my_platform_chrdev_read(struct file *filep,char *buf,size_t len,loff_t *off) 29 | { 30 | pr_err("%s,%d", __func__, __LINE__); 31 | return len; 32 | } 33 | 34 | static ssize_t my_platform_chrdev_write(struct file *filep,const char *buf,size_t len,loff_t *off) 35 | { 36 | pr_err("%s,%d", __func__, __LINE__); 37 | return len; 38 | } 39 | 40 | static int my_platform_chrdev_open(struct inode *inode,struct file *filep) 41 | { 42 | pr_err("%s,%d", __func__, __LINE__); 43 | // open的时候可以将自己的私有数据保存在private_data指针中,那么在read_write的时候,也可以通过这个指针访问对应的数据 44 | // 有时候还需要在这里面识别次设备号 45 | // filep->private_data = xxxx; 46 | return 0; 47 | } 48 | 49 | static int my_platform_chrdev_release(struct inode *inode,struct file *filep) 50 | { 51 | pr_err("%s,%d", __func__, __LINE__); 52 | return 0; 53 | } 54 | 55 | static struct file_operations platform_chrdev_fops = { 56 | .owner = THIS_MODULE, 57 | .read = my_platform_chrdev_read, 58 | .write = my_platform_chrdev_write, 59 | .open = my_platform_chrdev_open, 60 | .release = my_platform_chrdev_release, 61 | }; 62 | 63 | static int32_t my_platform_probe(struct platform_device *pdev) 64 | { 65 | int32_t rc = 0; 66 | struct my_platform_drvdata *drvdata; 67 | struct device_node *node; 68 | const char *name; 69 | struct device *dev; 70 | 71 | pr_err("%s,%d", __func__, __LINE__); 72 | // 申请空间 73 | drvdata = kzalloc(sizeof(struct my_platform_drvdata), GFP_KERNEL); 74 | if (!drvdata) { 75 | pr_err("%s,%d: failed to malloc drvdata", __func__, __LINE__); 76 | rc = -ENOMEM; 77 | goto err; 78 | } 79 | 80 | 81 | /*********************** 读取设备树参数 ***************************/ 82 | node = pdev->dev.of_node; 83 | if (node == NULL) { 84 | pr_err("%s,%d: failed to parse my driver dtsi ", __func__, __LINE__); 85 | return -ENODEV; 86 | } 87 | // 读取设备树的参数 88 | rc = of_property_read_u32(node, "tao,size", &drvdata->size); 89 | if (rc < 0) { 90 | pr_err("%s,%d: failed to read size", __func__, __LINE__); 91 | rc = -EINVAL; 92 | goto err_dt; 93 | } 94 | 95 | // 读取设备树的参数 96 | rc = of_property_read_string(node, "tao,name", &name); 97 | if (rc < 0) { 98 | pr_err("%s,%d: failed to read name", __func__, __LINE__); 99 | rc = -EINVAL; 100 | goto err_dt; 101 | } 102 | 103 | memset(drvdata->name, 0, sizeof(drvdata->name)); 104 | memcpy(drvdata->name, name, strlen(name)); 105 | pr_err("%s,%d, name:%s", __func__, __LINE__, drvdata->name); 106 | /*****************************************************************/ 107 | 108 | // 将私有数据保存到pdev中 109 | platform_set_drvdata(pdev, drvdata); 110 | 111 | if (cdev_major) { 112 | devno = MKDEV(cdev_major, cdev_minor); 113 | rc = register_chrdev_region(devno, 1, DEVICE_NAME); 114 | pr_err("%s,%d, major: %d", __func__, __LINE__, cdev_major); 115 | } else { 116 | rc = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME); 117 | cdev_major = MAJOR(devno); 118 | pr_err("%s,%d, major: %d", __func__, __LINE__, cdev_major); 119 | } 120 | // 添加字符设备 121 | drvdata->cdev = cdev_alloc(); 122 | drvdata->cdev->owner = THIS_MODULE; 123 | cdev_init(drvdata->cdev, &platform_chrdev_fops); 124 | cdev_add(drvdata->cdev, devno, 1); 125 | my_class = class_create(THIS_MODULE, "platform_chardev0"); 126 | dev = device_create(my_class, NULL, devno, NULL, "platform_chardev0"); 127 | pr_err("%s,%d", __func__, __LINE__); 128 | 129 | return 0; 130 | 131 | err_dt: 132 | kfree(drvdata); 133 | drvdata = NULL; 134 | err: 135 | return rc; 136 | } 137 | 138 | static int32_t my_platform_remove(struct platform_device *pdev) 139 | { 140 | // 获取私有数据 141 | struct my_platform_drvdata *drvdata = platform_get_drvdata(pdev); 142 | class_destroy(my_class); 143 | cdev_del(drvdata->cdev); 144 | unregister_chrdev_region(devno, 1); 145 | kfree(drvdata); 146 | return 0; 147 | } 148 | 149 | static int my_platform_suspend(struct platform_device *pdev,pm_message_t state) 150 | { 151 | return 0; 152 | } 153 | 154 | static int my_platform_resume(struct platform_device *pdev) 155 | { 156 | return 0; 157 | } 158 | 159 | static void my_platform_shutdown(struct platform_device *pdev) 160 | { 161 | return; 162 | } 163 | 164 | // 与设备树匹配 165 | static const struct of_device_id my_platform_dt_match[] = { 166 | {.compatible = "tao,my_platform", .data = NULL}, 167 | }; 168 | 169 | struct platform_driver my_platform_device_driver={ 170 | .driver = { 171 | .name = "tao,my_platform", 172 | .owner = THIS_MODULE, 173 | .of_match_table = my_platform_dt_match, 174 | }, 175 | .remove = my_platform_remove, 176 | .suspend = my_platform_suspend, 177 | .resume = my_platform_resume, 178 | .shutdown = my_platform_shutdown , 179 | .probe = my_platform_probe, 180 | }; 181 | 182 | static int __init my_platform_init(void) 183 | { 184 | int32_t rc = 0; 185 | 186 | pr_err("%s,%d", __func__, __LINE__); 187 | rc = platform_driver_register(&my_platform_device_driver); 188 | 189 | return 0; 190 | } 191 | 192 | static void __init my_platform_exit(void) 193 | { 194 | pr_err("%s,%d", __func__, __LINE__); 195 | platform_driver_unregister(&my_platform_device_driver); 196 | 197 | return; 198 | } 199 | 200 | module_init(my_platform_init); 201 | module_exit(my_platform_exit); 202 | 203 | MODULE_DESCRIPTION("Tao platform driver test"); 204 | MODULE_AUTHOR("Tao"); 205 | MODULE_LICENSE("GPL"); 206 | -------------------------------------------------------------------------------- /document/platform/平台设备创建.md: -------------------------------------------------------------------------------- 1 | 设备需要注册,需要设置好match table。注册之后,与dts中匹配上了就会运行probe函数。 2 | 3 | #### 添加设备树 4 | 5 | 所以先要完成设备树中的内容,我随意添加了一个设备。如下: 6 | 7 | [msm8953-mtp.diff](./msm8953-mtp.diff) 8 | 9 | ``` 10 | diff --git a/arch/arm64/boot/dts/IDH60/msm8953-mtp.dtsi b/arch/arm64/boot/dts/IDH60/msm8953-mtp.dtsi 11 | index e08f20d..ea0887b 100755 12 | --- a/arch/arm64/boot/dts/IDH60/msm8953-mtp.dtsi 13 | +++ b/arch/arm64/boot/dts/IDH60/msm8953-mtp.dtsi 14 | @@ -454,6 +454,12 @@ 15 | }; 16 | 17 | }; 18 | + tao,my_platform { 19 | + compatible = "tao,my_platform"; 20 | + status = "okay"; 21 | + tao,size = <1>; 22 | + tao,name = "my_platform_test"; 23 | + }; 24 | 25 | }; 26 | ``` 27 | 28 | #### platform驱动注册 29 | 30 | 请参考[my_platform.c](./my_platform.c) 31 | 32 | 设置好match table,和设备树中的内容匹配。 33 | 34 | ``` 35 | // 与设备树匹配 36 | static const struct of_device_id my_platform_dt_match[] = { 37 | {.compatible = "tao,my_platform", .data = NULL}, 38 | }; 39 | 40 | struct platform_driver my_platform_device_driver={ 41 | .driver = { 42 | .name = "tao,my_platform", 43 | .owner = THIS_MODULE, 44 | .of_match_table = my_platform_dt_match, 45 | }, 46 | .remove = my_platform_remove, 47 | .suspend = my_platform_suspend, 48 | .resume = my_platform_resume, 49 | .shutdown = my_platform_shutdown , 50 | .probe = my_platform_probe, 51 | }; 52 | 53 | static int __init my_platform_init(void) 54 | { 55 | int32_t rc = 0; 56 | 57 | pr_err("%s,%d", __func__, __LINE__); 58 | rc = platform_driver_register(&my_platform_device_driver); 59 | 60 | return 0; 61 | } 62 | ``` 63 | 64 | #### 设备树解读 65 | 66 | 驱动匹配上,运行probe函数,可以解读其中的设备树。 67 | 68 | ``` 69 | node = pdev->dev.of_node; 70 | if (node == NULL) { 71 | pr_err("%s,%d: failed to parse my driver dtsi ", __func__, __LINE__); 72 | return -ENODEV; 73 | } 74 | // 读取设备树的参数 75 | rc = of_property_read_u32(node, "tao,size", &drvdata->size); 76 | if (rc < 0) { 77 | pr_err("%s,%d: failed to read size", __func__, __LINE__); 78 | rc = -EINVAL; 79 | goto err_dt; 80 | } 81 | 82 | // 读取设备树的参数 83 | rc = of_property_read_string(node, "tao,name", &name); 84 | if (rc < 0) { 85 | pr_err("%s,%d: failed to read name", __func__, __LINE__); 86 | rc = -EINVAL; 87 | goto err_dt; 88 | } 89 | ``` 90 | 91 | #### 添加字符设备 92 | 93 | 为了是功能更加完善,可以在probe函数中添加自己的字符设备,并实现其中的函数。 94 | 95 | ``` 96 | // 添加字符设备,创建设备节点。 97 | drvdata->cdev = cdev_alloc(); 98 | drvdata->cdev->owner = THIS_MODULE; 99 | cdev_init(drvdata->cdev, &platform_chrdev_fops); 100 | cdev_add(drvdata->cdev, devno, 1); 101 | my_class = class_create(THIS_MODULE, "platform_chardev0"); 102 | dev = device_create(my_class, NULL, devno, NULL, "platform_chardev0"); 103 | ``` 104 | 105 | #### 保存私有数据 106 | 107 | 同样可以在probe中可以保存私有数据,然后可以在其他函数中继续访问这个数据。 108 | 109 | ``` 110 | // 将私有数据保存到pdev中 111 | platform_set_drvdata(pdev, drvdata); 112 | ``` 113 | 然后在remove等函数中通过同样的struct platform_device 进行访问。 114 | 115 | ``` 116 | static int32_t my_platform_remove(struct platform_device *pdev) 117 | { 118 | // 获取私有数据 119 | struct my_platform_drvdata *drvdata = platform_get_drvdata(pdev); 120 | ``` 121 | 122 | 另外字符设备在open的时候,也可以通过struct file的private_data保存数据。 123 | 124 | 可以参考my_chrdev.c文件。 125 | -------------------------------------------------------------------------------- /document/sync/README.md: -------------------------------------------------------------------------------- 1 | ### spinlock自旋锁 2 | 3 | * 加锁 4 | 5 | spin_lock(&lock); 阻塞版本 6 | 7 | spin_trylock(&lock); 不阻塞版本 8 | 9 | * 解锁 10 | 11 | spin_unlock(&lock); 12 | 13 | * 使用场合: 14 | 15 | 保持锁时间很短 16 | 17 | 执行环境不允许睡眠 18 | 19 | 等待锁的过程中不休眠,而是占着CPU空转,优点是避免了上下文切换的开销,缺点是该CPU空转属于浪费, 20 | 21 | spinlock适合用来保护快进快出的临界区。 22 | 23 | 自旋锁可以使用在中断处理程序中(此处不能使用信号量,因为他们会导致睡眠)。 24 | 25 | ### semaphore信号量 26 | 27 | * 定义 28 | 29 | struct semaphore sem; 30 | 31 | * 初始化 32 | 33 | sema_init(&test.sem, 1); 34 | 35 | * 获取信号量 36 | 37 | down(&p->sem); 38 | 39 | down_interruptible(&p->sem); 40 | 41 | down_trylock(&p->sem); 42 | 43 | * 释放信号量 44 | 45 | up(&p->sem); 46 | 47 | * 定义一个信号量,并初始化 48 | 49 | DEFINE_SEMAPHORE(name); 50 | 51 | 信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。 52 | 53 | 信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒。 54 | 55 | ### mutex互斥锁 56 | 57 | * 定义 58 | 59 | struct mutex lock; 60 | 61 | * 初始化 62 | 63 | mutex_init(&lock); 64 | 65 | * 加锁 66 | 67 | mutex_lock(&lock); 68 | 69 | mutex_lock_interruptible(&lock); 70 | 71 | 成功返回0 72 | 73 | mutex_trylock(&lock); 74 | 75 | 成功返回1 76 | 77 | * 解锁 78 | 79 | mutex_unlock(&lock); 80 | 81 | * 定义一个互斥锁,并初始化 82 | 83 | DEFINE_MUTEX(mutexname); 84 | 85 | 互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex))。互斥体禁止多个线程同时进入受保护的代码“临界区”(critical section)。因此,在任意时刻,只有一个线程被允许进入这样的代码保护区。mutex实际上是count=1情况下的semaphore。 86 | 87 | 信号量/互斥体允许进程睡眠属于睡眠锁,自旋锁则不允许调用者睡眠,而是让其循环等待: 88 | 89 | 1. 信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因而自旋锁适合于保持时间非常短的情况 90 | 91 | 2. 自旋锁可以用于中断,不能用于进程上下文(会引起死锁)。而信号量不允许使用在中断中,而可以用于进程上下文 92 | 93 | 3. 自旋锁保持期间是抢占失效的,自旋锁被持有时,内核不能被抢占,而信号量和读写信号量保持期间是可以被抢占的 94 | 95 | ### atomic原子操作 96 | 97 | atomic_t val; 98 | 99 | atomic_t * v = &val; 100 | 101 | 读,修改原子变量的值 102 | 103 | atomic_read(v); 104 | 105 | atomic_set(v, i); 106 | 107 | atomic_inc(v); ---> v += 1; 108 | 109 | atomic_dec(v); ---> v -= 1; 110 | 111 | atomic_inc_and_test(v) v += 1,判断结果是否为0 112 | 113 | atomic_dec_and_test(v) v -= 1,判断结果是否为0 114 | 115 | atomic_inc_return(v) 116 | 117 | atomic_dec_return(v) 118 | 119 | atomic_sub_and_test(i, v) 120 | 121 | 原子操作指在执行过程中不会被别的代码中断的操作。 122 | 123 | ### completion完成量 124 | 125 | * 定义 126 | 127 | struct completion com; 128 | 129 | * 初始化 130 | 131 | init_completion(&com); 132 | 133 | * 等待完成 134 | 135 | wait_for_completion(&com); 136 | 137 | wait_for_completion_interruptible(&com); 138 | 139 | * 通知完成量 140 | 141 | complete(&com); 142 | 143 | complete_all(&com); 144 | 145 | * 定义一个完成量,并初始化 146 | 147 | DECLARE_COMPLETION(work) 148 | 149 | 一般信号量的的处理会限制在一个函数内,但是有时会函数A的处理的前提条件是函数B,A必须等待B处理后才能继续,可以用信号量来进行处理,但linux kernel提供complete的方式。 -------------------------------------------------------------------------------- /document/sync/atomic/Makefile: -------------------------------------------------------------------------------- 1 | CONFIG_MODULE_SIG=n 2 | 3 | ifneq ($(KERNELRELEASE),) 4 | 5 | obj-m := test.o 6 | 7 | else 8 | 9 | KERNELDIR :=/lib/modules/`uname -r`/build 10 | PWD := $(shell pwd) 11 | 12 | all: 13 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 14 | clean: 15 | rm -f *.ko *.o *.mod.o *.mod.c *.order *.symvers 16 | 17 | endif 18 | -------------------------------------------------------------------------------- /document/sync/atomic/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct test_s { 8 | struct miscdevice misc_dev; 9 | atomic_t v; 10 | spinlock_t lock; 11 | }; 12 | 13 | static int my_misc_open(struct inode *node, struct file *file); 14 | static int my_misc_release(struct inode *node, struct file *file); 15 | 16 | 17 | static struct file_operations misc_fops = { 18 | .owner = THIS_MODULE, 19 | .open = my_misc_open, 20 | .release = my_misc_release, 21 | }; 22 | 23 | static struct test_s test_t = { 24 | .misc_dev = { 25 | .name = "my_misc", 26 | .minor = MISC_DYNAMIC_MINOR, 27 | .fops = &misc_fops, 28 | }, 29 | }; 30 | 31 | static int my_misc_open(struct inode *node, struct file *file) 32 | { 33 | struct test_s *p; 34 | pr_err("%s,%d", __func__, __LINE__); 35 | 36 | file->private_data = &test_t; 37 | p = &test_t; 38 | // 从原子类型的变量中减去1,并判断结果是否为0,如果为0,返回真,否则返回假。 39 | if(!atomic_dec_and_test(&p->v)) 40 | { 41 | pr_err("%s,%d", __func__, __LINE__); 42 | atomic_inc(&p->v); 43 | return -EAGAIN; 44 | } 45 | 46 | return 0; 47 | } 48 | 49 | static int my_misc_release(struct inode *node, struct file *file) 50 | { 51 | struct test_s *p = file->private_data; 52 | pr_err("%s,%d", __func__, __LINE__); 53 | // 原子类型变量增加1 54 | atomic_inc(&p->v); 55 | return 0; 56 | } 57 | 58 | static int __init my_misc_init(void) 59 | { 60 | int rc = 0; 61 | 62 | pr_err("%s,%d", __func__, __LINE__); 63 | // 设置原子类型的变量的值为i 64 | atomic_set(&test_t.v, 1); 65 | 66 | rc = misc_register(&test_t.misc_dev); 67 | if(rc < 0) { 68 | pr_err("%s,%d, fail to register misc\n", __func__, __LINE__); 69 | } 70 | 71 | return rc; 72 | } 73 | 74 | static void __exit my_misc_exit(void) 75 | { 76 | pr_err("%s,%d", __func__, __LINE__); 77 | 78 | misc_deregister(&test_t.misc_dev); 79 | 80 | return; 81 | } 82 | 83 | module_init(my_misc_init); 84 | module_exit(my_misc_exit); 85 | 86 | MODULE_DESCRIPTION("TAO driver"); 87 | MODULE_AUTHOR("TAO LIU"); 88 | MODULE_LICENSE("GPL"); 89 | -------------------------------------------------------------------------------- /document/sync/atomic/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 后台运行,同时访问通过一个资源 3 | echo 11111 > /dev/my_misc & 4 | echo 22222 > /dev/my_misc & 5 | -------------------------------------------------------------------------------- /document/sync/completion/Makefile: -------------------------------------------------------------------------------- 1 | CONFIG_MODULE_SIG=n 2 | 3 | ifneq ($(KERNELRELEASE),) 4 | 5 | obj-m := test.o 6 | 7 | else 8 | 9 | KERNELDIR :=/lib/modules/`uname -r`/build 10 | PWD := $(shell pwd) 11 | 12 | all: 13 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 14 | clean: 15 | rm -f *.ko *.o *.mod.o *.mod.c *.order *.symvers 16 | 17 | endif 18 | -------------------------------------------------------------------------------- /document/sync/completion/app.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main(int argc, char **argv) 8 | { 9 | int fd, ret; 10 | char buf[1024]; 11 | 12 | fd = open(argv[1], O_RDWR); 13 | if(-1 == fd) 14 | { 15 | perror("open"); 16 | exit(1); 17 | } 18 | 19 | ret = read(fd, buf, sizeof(buf)); 20 | if(-1 == ret) 21 | perror("read"); 22 | 23 | close(fd); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /document/sync/completion/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct test_s { 8 | struct miscdevice misc_dev; 9 | struct completion com; 10 | }; 11 | 12 | static int my_misc_open(struct inode *node, struct file *file); 13 | static int my_misc_release(struct inode *node, struct file *file); 14 | static ssize_t my_misc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos); 15 | static ssize_t my_misc_write(struct file *file, const char *buf, size_t count, loff_t *ppos); 16 | 17 | 18 | static struct file_operations misc_fops = { 19 | .owner = THIS_MODULE, 20 | .open = my_misc_open, 21 | .release = my_misc_release, 22 | .write = my_misc_write, 23 | .read = my_misc_read, 24 | }; 25 | 26 | static struct test_s test_t = { 27 | .misc_dev = { 28 | .name = "my_misc", 29 | .minor = MISC_DYNAMIC_MINOR, 30 | .fops = &misc_fops, 31 | }, 32 | }; 33 | 34 | static int my_misc_open(struct inode *node, struct file *file) 35 | { 36 | pr_err("%s,%d", __func__, __LINE__); 37 | 38 | file->private_data = &test_t; 39 | 40 | return 0; 41 | } 42 | 43 | static int my_misc_release(struct inode *node, struct file *file) 44 | { 45 | pr_err("%s,%d", __func__, __LINE__); 46 | return 0; 47 | } 48 | 49 | static ssize_t my_misc_read(struct file *file, char __user *buf, 50 | size_t count, loff_t *ppos) 51 | { 52 | struct test_s *p = file->private_data; 53 | 54 | //在完成量com上阻塞 55 | /*wait_for_completion(&p->com);*/ 56 | 57 | if(wait_for_completion_interruptible(&p->com)) 58 | return -ERESTARTSYS; 59 | 60 | printk("Read data.\n"); 61 | 62 | return count; 63 | } 64 | 65 | static ssize_t my_misc_write(struct file *file, const char *buf, 66 | size_t count, loff_t *ppos) 67 | { 68 | struct test_s *p = file->private_data; 69 | 70 | printk("Write data.\n"); 71 | 72 | /*complete(&p->com);*/ 73 | 74 | //通知所有阻塞的进程 75 | complete_all(&p->com); 76 | 77 | return count; 78 | } 79 | 80 | static int __init my_misc_init(void) 81 | { 82 | int rc = 0; 83 | 84 | pr_err("%s,%d", __func__, __LINE__); 85 | 86 | init_completion(&test_t.com); 87 | 88 | rc = misc_register(&test_t.misc_dev); 89 | if(rc < 0) { 90 | pr_err("%s,%d, fail to register misc\n", __func__, __LINE__); 91 | } 92 | 93 | return rc; 94 | } 95 | 96 | static void __exit my_misc_exit(void) 97 | { 98 | pr_err("%s,%d", __func__, __LINE__); 99 | 100 | misc_deregister(&test_t.misc_dev); 101 | 102 | return; 103 | } 104 | 105 | module_init(my_misc_init); 106 | module_exit(my_misc_exit); 107 | 108 | MODULE_DESCRIPTION("TAO driver"); 109 | MODULE_AUTHOR("TAO LIU"); 110 | MODULE_LICENSE("GPL"); 111 | -------------------------------------------------------------------------------- /document/sync/completion/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 后台运行,同时访问通过一个资源 3 | echo 11111 > /dev/my_misc & 4 | echo 22222 > /dev/my_misc & 5 | -------------------------------------------------------------------------------- /document/sync/mutex/Makefile: -------------------------------------------------------------------------------- 1 | CONFIG_MODULE_SIG=n 2 | 3 | ifneq ($(KERNELRELEASE),) 4 | 5 | obj-m := test.o 6 | 7 | else 8 | 9 | KERNELDIR :=/lib/modules/`uname -r`/build 10 | PWD := $(shell pwd) 11 | 12 | all: 13 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 14 | clean: 15 | rm -f *.ko *.o *.mod.o *.mod.c *.order *.symvers 16 | 17 | endif 18 | -------------------------------------------------------------------------------- /document/sync/mutex/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct test_s { 8 | struct miscdevice misc_dev; 9 | struct mutex lock; 10 | }; 11 | 12 | static int my_misc_open(struct inode *node, struct file *file); 13 | static int my_misc_release(struct inode *node, struct file *file); 14 | static ssize_t my_misc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos); 15 | static ssize_t my_misc_write(struct file *file, const char *buf, size_t count, loff_t *ppos); 16 | 17 | 18 | static struct file_operations misc_fops = { 19 | .owner = THIS_MODULE, 20 | .open = my_misc_open, 21 | .release = my_misc_release, 22 | .write = my_misc_write, 23 | .read = my_misc_read, 24 | }; 25 | 26 | static struct test_s test_t = { 27 | .misc_dev = { 28 | .name = "my_misc", 29 | .minor = MISC_DYNAMIC_MINOR, 30 | .fops = &misc_fops, 31 | }, 32 | }; 33 | 34 | static int my_misc_open(struct inode *node, struct file *file) 35 | { 36 | pr_err("%s,%d", __func__, __LINE__); 37 | 38 | file->private_data = &test_t; 39 | 40 | return 0; 41 | } 42 | 43 | static int my_misc_release(struct inode *node, struct file *file) 44 | { 45 | pr_err("%s,%d", __func__, __LINE__); 46 | return 0; 47 | } 48 | 49 | static ssize_t my_misc_read(struct file *file, char __user *buf, 50 | size_t count, loff_t *ppos) 51 | { 52 | pr_err("%s,%d", __func__, __LINE__); 53 | 54 | return count; 55 | } 56 | 57 | static ssize_t my_misc_write(struct file *file, const char *buf, 58 | size_t count, loff_t *ppos) 59 | { 60 | int i, ret; 61 | static int cnt = 0; 62 | char kbuf[count + 1]; 63 | struct test_s *p = file->private_data; 64 | 65 | pr_err("%s,%d", __func__, __LINE__); 66 | 67 | ret = copy_from_user(kbuf, buf, count); 68 | if(ret) 69 | return -EFAULT; 70 | kbuf[count] = '\0'; 71 | // 尝试获得锁,没有获得就进行休眠 72 | mutex_lock(&p->lock); 73 | 74 | /* 75 | *if(mutex_lock_interruptible(&p->lock)) 76 | * return -EINTR; 77 | */ 78 | // 没有获得锁直接退出 79 | /* 80 | if(!mutex_trylock(&p->lock)) 81 | return -EAGAIN; 82 | */ 83 | for(i = 0; i < 3; i++) 84 | { 85 | printk("count = %d, %s", cnt++, kbuf); 86 | /*mdelay(10);*/ 87 | msleep(10); 88 | } 89 | 90 | mutex_unlock(&p->lock); 91 | 92 | return count; 93 | } 94 | 95 | static int __init my_misc_init(void) 96 | { 97 | int rc = 0; 98 | 99 | pr_err("%s,%d", __func__, __LINE__); 100 | 101 | mutex_init(&test_t.lock); 102 | 103 | rc = misc_register(&test_t.misc_dev); 104 | if(rc < 0) { 105 | pr_err("%s,%d, fail to register misc\n", __func__, __LINE__); 106 | } 107 | 108 | return rc; 109 | } 110 | 111 | static void __exit my_misc_exit(void) 112 | { 113 | pr_err("%s,%d", __func__, __LINE__); 114 | 115 | misc_deregister(&test_t.misc_dev); 116 | 117 | return; 118 | } 119 | 120 | module_init(my_misc_init); 121 | module_exit(my_misc_exit); 122 | 123 | MODULE_DESCRIPTION("TAO driver"); 124 | MODULE_AUTHOR("TAO LIU"); 125 | MODULE_LICENSE("GPL"); 126 | -------------------------------------------------------------------------------- /document/sync/mutex/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 后台运行,同时访问通过一个资源 3 | echo 11111 > /dev/my_misc & 4 | echo 22222 > /dev/my_misc & 5 | -------------------------------------------------------------------------------- /document/sync/semaphore/Makefile: -------------------------------------------------------------------------------- 1 | CONFIG_MODULE_SIG=n 2 | 3 | ifneq ($(KERNELRELEASE),) 4 | 5 | obj-m := test.o 6 | 7 | else 8 | 9 | KERNELDIR :=/lib/modules/`uname -r`/build 10 | PWD := $(shell pwd) 11 | 12 | all: 13 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 14 | clean: 15 | rm -f *.ko *.o *.mod.o *.mod.c *.order *.symvers 16 | 17 | endif 18 | -------------------------------------------------------------------------------- /document/sync/semaphore/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct test_s { 8 | struct miscdevice misc_dev; 9 | struct semaphore sem; 10 | spinlock_t lock; 11 | }; 12 | 13 | static int my_misc_open(struct inode *node, struct file *file); 14 | static int my_misc_release(struct inode *node, struct file *file); 15 | static ssize_t my_misc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos); 16 | static ssize_t my_misc_write(struct file *file, const char *buf, size_t count, loff_t *ppos); 17 | 18 | 19 | static struct file_operations misc_fops = { 20 | .owner = THIS_MODULE, 21 | .open = my_misc_open, 22 | .release = my_misc_release, 23 | .write = my_misc_write, 24 | .read = my_misc_read, 25 | }; 26 | 27 | static struct test_s test_t = { 28 | .misc_dev = { 29 | .name = "my_misc", 30 | .minor = MISC_DYNAMIC_MINOR, 31 | .fops = &misc_fops, 32 | }, 33 | }; 34 | 35 | static int my_misc_open(struct inode *node, struct file *file) 36 | { 37 | pr_err("%s,%d", __func__, __LINE__); 38 | 39 | file->private_data = &test_t; 40 | 41 | return 0; 42 | } 43 | 44 | static int my_misc_release(struct inode *node, struct file *file) 45 | { 46 | pr_err("%s,%d", __func__, __LINE__); 47 | return 0; 48 | } 49 | 50 | static ssize_t my_misc_read(struct file *file, char __user *buf, 51 | size_t count, loff_t *ppos) 52 | { 53 | pr_err("%s,%d", __func__, __LINE__); 54 | 55 | return count; 56 | } 57 | 58 | static ssize_t my_misc_write(struct file *file, const char *buf, 59 | size_t count, loff_t *ppos) 60 | { 61 | int i, ret; 62 | static int cnt = 0; 63 | char kbuf[count + 1]; 64 | /*test_t *p = file->private_data;*/ 65 | 66 | pr_err("%s,%d", __func__, __LINE__); 67 | ret = copy_from_user(kbuf, buf, count); 68 | if(ret) 69 | return -EFAULT; 70 | kbuf[count] = '\0'; 71 | 72 | // 信号量值减1 73 | //加不了锁,睡眠等待 74 | down(&test_t.sem); 75 | /* // 操作可中断,返回非零,表示操作被中断 76 | if (down_interruptible(&test_t.sem)) 77 | return -ERESTARTSYS; 78 | */ 79 | /* // 加不了锁,就直接退出 80 | if(down_trylock(&test_t.sem)) 81 | return -EAGAIN; 82 | */ 83 | for(i = 0; i < 3; i++) 84 | { 85 | pr_err("count = %d, %s", cnt++, kbuf); 86 | /*mdelay(10);*/ 87 | msleep(10); 88 | } 89 | // 信号量值加1 90 | up(&test_t.sem); 91 | 92 | return count; 93 | } 94 | 95 | static int __init my_misc_init(void) 96 | { 97 | int rc = 0; 98 | 99 | pr_err("%s,%d", __func__, __LINE__); 100 | // 初始化信号量,并设置可以同时几个程序可以获得锁。 101 | // 设置成1和2的结果是不一样的。 102 | sema_init(&test_t.sem, 2); 103 | //sema_init(&test_t.sem, 1); 104 | 105 | rc = misc_register(&test_t.misc_dev); 106 | if(rc < 0) { 107 | pr_err("%s,%d, fail to register misc\n", __func__, __LINE__); 108 | } 109 | 110 | return rc; 111 | } 112 | 113 | static void __exit my_misc_exit(void) 114 | { 115 | pr_err("%s,%d", __func__, __LINE__); 116 | 117 | misc_deregister(&test_t.misc_dev); 118 | 119 | return; 120 | } 121 | 122 | module_init(my_misc_init); 123 | module_exit(my_misc_exit); 124 | 125 | MODULE_DESCRIPTION("TAO driver"); 126 | MODULE_AUTHOR("TAO LIU"); 127 | MODULE_LICENSE("GPL"); 128 | -------------------------------------------------------------------------------- /document/sync/semaphore/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 后台运行,同时访问通过一个资源 3 | echo 11111 > /dev/my_misc & 4 | echo 22222 > /dev/my_misc & 5 | -------------------------------------------------------------------------------- /document/sync/spinlock/Makefile: -------------------------------------------------------------------------------- 1 | CONFIG_MODULE_SIG=n 2 | 3 | ifneq ($(KERNELRELEASE),) 4 | 5 | obj-m := test.o 6 | 7 | else 8 | 9 | KERNELDIR :=/lib/modules/`uname -r`/build 10 | PWD := $(shell pwd) 11 | 12 | all: 13 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 14 | clean: 15 | rm -f *.ko *.o *.mod.o *.mod.c *.order *.symvers 16 | 17 | endif 18 | -------------------------------------------------------------------------------- /document/sync/spinlock/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct test_s { 8 | struct miscdevice misc_dev; 9 | spinlock_t lock; 10 | }; 11 | 12 | static int my_misc_open(struct inode *node, struct file *file); 13 | static int my_misc_release(struct inode *node, struct file *file); 14 | static ssize_t my_misc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos); 15 | static ssize_t my_misc_write(struct file *file, const char *buf, size_t count, loff_t *ppos); 16 | 17 | static struct file_operations misc_fops = { 18 | .owner = THIS_MODULE, 19 | .open = my_misc_open, 20 | .release = my_misc_release, 21 | .write = my_misc_write, 22 | .read = my_misc_read, 23 | }; 24 | 25 | static struct test_s test_t = { 26 | .misc_dev = { 27 | .name = "my_misc", 28 | .minor = MISC_DYNAMIC_MINOR, 29 | .fops = &misc_fops, 30 | }, 31 | }; 32 | 33 | static int my_misc_open(struct inode *node, struct file *file) 34 | { 35 | file->private_data = &test_t; 36 | 37 | return 0; 38 | } 39 | 40 | static int my_misc_release(struct inode *node, struct file *file) 41 | { 42 | pr_err("%s,%d", __func__, __LINE__); 43 | return 0; 44 | } 45 | 46 | static ssize_t my_misc_read(struct file *file, char __user *buf, 47 | size_t count, loff_t *ppos) 48 | { 49 | pr_err("%s,%d", __func__, __LINE__); 50 | 51 | return count; 52 | } 53 | 54 | static ssize_t my_misc_write(struct file *file, const char *buf, 55 | size_t count, loff_t *ppos) 56 | { 57 | int i, ret; 58 | static int cnt = 0; 59 | char kbuf[count + 1]; 60 | struct test_s *p = file->private_data; 61 | 62 | pr_err("%s,%d", __func__, __LINE__); 63 | 64 | ret = copy_from_user(kbuf, buf, count); 65 | if(ret) 66 | return -EFAULT; 67 | kbuf[count] = '\0'; 68 | 69 | //提高系统负载,开启其他处理器核心 70 | mdelay(3000); 71 | 72 | //进入临界区之前加锁 73 | spin_lock(&p->lock); 74 | 75 | /* 76 | if(!spin_trylock(&p->lock)) 77 | return -EAGAIN; 78 | */ 79 | 80 | for(i = 0; i < 3; i++) 81 | { 82 | printk("count = %d, %s", cnt++, kbuf); 83 | } 84 | 85 | //出临界区之后解锁 86 | spin_unlock(&p->lock); 87 | 88 | return count; 89 | } 90 | 91 | 92 | static int __init my_misc_init(void) 93 | { 94 | int rc = 0; 95 | 96 | pr_err("%s,%d", __func__, __LINE__); 97 | 98 | spin_lock_init(&test_t.lock); 99 | 100 | rc = misc_register(&test_t.misc_dev); 101 | if(rc < 0) { 102 | pr_err("%s,%d, fail to register misc\n", __func__, __LINE__); 103 | } 104 | 105 | return rc; 106 | } 107 | 108 | static void __exit my_misc_exit(void) 109 | { 110 | misc_deregister(&test_t.misc_dev); 111 | 112 | pr_err("%s,%d", __func__, __LINE__); 113 | 114 | return; 115 | } 116 | 117 | module_init(my_misc_init); 118 | module_exit(my_misc_exit); 119 | 120 | MODULE_DESCRIPTION("TAO driver"); 121 | MODULE_AUTHOR("TAO LIU"); 122 | MODULE_LICENSE("GPL"); 123 | -------------------------------------------------------------------------------- /document/sync/spinlock/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo 11111 > /dev/my_misc & 4 | echo 22222 > /dev/my_misc & 5 | -------------------------------------------------------------------------------- /document/thread/Makefile: -------------------------------------------------------------------------------- 1 | CONFIG_MODULE_SIG=n 2 | 3 | ifneq ($(KERNELRELEASE),) 4 | 5 | obj-m := test.o 6 | 7 | else 8 | 9 | KERNELDIR :=/lib/modules/`uname -r`/build 10 | PWD := $(shell pwd) 11 | 12 | all: 13 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 14 | clean: 15 | rm -f *.ko *.o *.mod.o *.mod.c *.order *.symvers 16 | 17 | endif 18 | -------------------------------------------------------------------------------- /document/thread/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct task_struct *t; 6 | 7 | int thread_main(void *data) 8 | { 9 | printk("pid = %d\n", t->pid); 10 | while(1) 11 | { 12 | if(kthread_should_stop()) 13 | break; 14 | 15 | /*printk("thread running.\n");*/ 16 | msleep(3000); 17 | } 18 | 19 | return 123; 20 | } 21 | 22 | int __init test_init(void) 23 | { 24 | //两中方法都可以创建线程 25 | /* // 第二个参数可以是传入线程的数据 26 | * t = kthread_create(thread_main, NULL, "my_thread%d", 0); 27 | * if(IS_ERR(t)) 28 | * return PTR_ERR(t); 29 | * 30 | * wake_up_process(t); 31 | */ 32 | t = kthread_run(thread_main, NULL, "my_thread%d", 0); 33 | if(IS_ERR(t)) 34 | return PTR_ERR(t); 35 | 36 | return 0; 37 | } 38 | 39 | 40 | 41 | void __exit test_exit(void) 42 | { 43 | int ret; 44 | ret = kthread_stop(t); 45 | printk("ret = %d\n", ret); 46 | } 47 | 48 | module_init(test_init); 49 | module_exit(test_exit); 50 | 51 | MODULE_LICENSE("GPL"); 52 | MODULE_AUTHOR("Tao Liu"); 53 | -------------------------------------------------------------------------------- /document/timer/jiffies/Makefile: -------------------------------------------------------------------------------- 1 | ifneq ($(KERNELRELEASE),) 2 | 3 | obj-m := test.o 4 | 5 | else 6 | 7 | KERNELDIR :=/lib/modules/`uname -r`/build 8 | PWD := $(shell pwd) 9 | 10 | all: 11 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 12 | clean: 13 | rm -f *.ko *.o *.mod.o *.mod.c *.order *.symvers 14 | 15 | endif 16 | -------------------------------------------------------------------------------- /document/timer/jiffies/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void my_mdelay(int msec) 5 | { 6 | unsigned long expire = jiffies + msecs_to_jiffies(msec); 7 | 8 | while(time_is_after_jiffies(expire)) 9 | ; 10 | } 11 | 12 | int __init test_init(void) 13 | { 14 | printk("jifffies is %lu\n", jiffies); 15 | 16 | /*msleep(2000);*/ 17 | /*mdelay(2000);*/ 18 | my_mdelay(2000); 19 | printk("jifffies is %lu\n", jiffies); 20 | 21 | return 0; 22 | } 23 | 24 | 25 | void __exit test_exit(void) 26 | { 27 | } 28 | 29 | module_init(test_init); 30 | module_exit(test_exit); 31 | 32 | MODULE_LICENSE("GPL"); 33 | MODULE_AUTHOR("Tao"); 34 | 35 | -------------------------------------------------------------------------------- /document/timer/timer/Makefile: -------------------------------------------------------------------------------- 1 | ifneq ($(KERNELRELEASE),) 2 | 3 | obj-m := test.o 4 | 5 | else 6 | 7 | KERNELDIR :=/lib/modules/`uname -r`/build 8 | PWD := $(shell pwd) 9 | 10 | all: 11 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 12 | clean: 13 | rm -f *.ko *.o *.mod.o *.mod.c *.order *.symvers 14 | 15 | endif 16 | -------------------------------------------------------------------------------- /document/timer/timer/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct timer_list timer; 6 | void timer_main(unsigned long data) 7 | { 8 | // 打印传入的数据 9 | printk("timer expire! data = %lu\n", data); 10 | 11 | /* 12 | *if(timer_pending(&timer)) 13 | * printk("timer_main: timer pending\n"); 14 | *else 15 | * printk("timer_main: timer NOT pending\n"); 16 | */ 17 | 18 | if(in_interrupt()) 19 | printk("in interrupt context.\n"); 20 | if(in_softirq()) 21 | printk("in softirq context.\n"); 22 | if(in_irq()) 23 | printk("in irq context.\n"); 24 | 25 | /*msleep(3000);*/ 26 | /* 27 | *mdelay(3000); 28 | *printk("timer fn end\n"); 29 | */ 30 | /*mod_timer(&timer, jiffies + HZ);*/ 31 | } 32 | 33 | int __init test_init(void) 34 | { 35 | // 设置定时器调用的函数,以及传入的参数 36 | setup_timer(&timer, timer_main, 11223344); 37 | 38 | //设置时间,并激活定时器 39 | mod_timer(&timer, jiffies + 3 * HZ); // 定时3秒 40 | mod_timer(&timer, jiffies + 1 * HZ); // 定时1秒 41 | // 判断一个定时器是否被添加到了内核链表中以等待被调度运行 42 | if(timer_pending(&timer)) 43 | printk("timer pending\n"); 44 | else 45 | printk("timer NOT pending\n"); 46 | 47 | return 0; 48 | } 49 | 50 | 51 | void __exit test_exit(void) 52 | { 53 | /*del_timer(&timer);*/ 54 | del_timer_sync(&timer); 55 | } 56 | 57 | module_init(test_init); 58 | module_exit(test_exit); 59 | 60 | MODULE_LICENSE("GPL"); 61 | MODULE_AUTHOR("Tao"); 62 | 63 | --------------------------------------------------------------------------------