/**************************************************************************** * * list_head,proc file system,GPIO,ioremap * * 声明: * 1. 本系列文档是在vim下编辑,请尽量是用vim来阅读,在其它编辑器下可能会 * 不对齐,从而影响阅读. * 2. 本文中有些源代码没有全部帖出来,主要是因为篇幅太大的原因; * 3. 基于2中的原因,本文借鉴了python中的缩进代码风格进行代码的体现: * 1. 有些代码中的"..."代表省略了不影响阅读的代码; * 2. 如下代码缩进代表在一个函数内部的代码,至于在什么函数里,不影响阅读: * ... //省略代码 * struct test_s { * }; * ... //省略代码 * * //进入临界区之前加锁 } * spin_lock(&p->lock); | * | | * /* 有效代码 */ |-->|采用缩进,代表在一个函数内 * | |的代码 * //出临界区之后解锁 | * spin_unlock(&p->lock); } * * ... //省略代码 * int __init test_init(void) * { * ... //省略代码 * } * ... //省略代码 * * 2015-3-11 阴 深圳 尚观 Var 曾剑锋 ****************************************************************************/ \\\\\\\\\\\--*目录*--////////// | 一. list_head常用接口: | 二. proc文件系统相关操作: | 三. gpio_request相关操作: | 四. ioremap相关操作: | 五. LED驱动写法: | 六. 测试LED驱动: \\\\\\\\\\\\\\\\/////////////// 一. list_head常用接口: 1. 定义内核链表头,并初始化: LIST_HEAD(test_head); 2. 两种链表添加方式: 1. 头插: list_add(); 2. 尾插: list_add_tail(); 3. 两种特殊的for each循环遍历,内部使用了typeof查询类型,再加上container_of: 1. list_for_each_entry(); 2. list_for_each_entry_reverse(); 4. 普通的for each循环遍历: list_for_each(); 5. 内核链表Demo: ... // "..."代表省略一些不影响分析代码的代码 struct test_list { int data; struct list_head entry; //内核链表 }; //定义一个内核链表头,并初始化 LIST_HEAD(test_head); struct test_list item[NUM]; int __init test_init(void) { int i; struct test_list *tail; struct list_head *p; for(i = 0; i < NUM; i++) { item[i].data = i; //头插方式 /*list_add(&item[i].entry, &test_head);*/ //尾插方式 list_add_tal(&item[i].entry, &test_head); } //遍历链表,这两个宏里面都使用了typeof来配合container_of获取结构体指针 /*list_for_each_entry(tail, &test_head, entry)*/ list_for_each_entry_reverse(tail, &test_head, entry) { printk("%d ", tail->data); } printk("\n"); //没有像上面那样得到结构体指针,需要自己使用container_of获取结构体指针 list_for_each(p, &test_head) { tail = container_of(p, struct test_list, entry); printk("%d ", tail->data); } printk("\n"); return 0; } ... 二. proc文件系统相关操作: //这里的资料不足,需要另外参考网络资料 1. 头文件: #include <linux/proc_fs.h> #include <linux/seq_file.h> 2. 创建文件: proc_create(); 3. 创建目录: proc_mkdir(); 4. 删除文件,目录: remove_proc_entry(); 5. 访问方法,此处的资料不够,需要另外查相关资料: 1. seq_printf(); 2. single_open(); 3. single_release(); 4. seq_read(); 5. seq_lseek(); 6. proc文件系统访问Demo: #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> static int num = 11223344; static int my_show(struct seq_file *file, void *data) { return seq_printf(file, "num is %d\n", num); } static int test_open(struct inode *inode, struct file *file) { return single_open(file, my_show, NULL); } struct file_operations fops = { .owner = THIS_MODULE, .open = test_open, .release = single_release, .read = seq_read, .llseek = seq_lseek, }; struct proc_dir_entry *pde; int __init test_init(void) { //在proc文件系统上创建空目录 pde = proc_mkdir("test_dir", NULL); if(!pde) return -EFAULT; //在proc文件系统指定目录上创建文件 /** * 1. "proc_test" : 创建的文件名; * 2. 0644 : 八进制,表示创建的文件权限; * 3. pde : 前面创建文件夹的指针; * 4. &fops : 文件操作指针; */ proc_create("proc_test", 0644, pde, &fops); return 0; } void __exit test_exit(void) { //先删除proc文件系统上的目录 remove_proc_entry("proc_test", pde); //删除proc文件系统上proc_test目录的文件 remove_proc_entry("test_dir", NULL); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); 三. gpio_request相关操作: 1. 头文件: #include <linux/gpio.h> 2. 获取GPIO在系统中的编号: EXYNOS4X12_GPXn(nr),比如:获取GPM4的0号引脚: unsigned int gpio = EXYNOS4X12_GPM4(0); 3. 向系统申请引脚编号为gpio的引脚,并命名为"name",成功返回0,主要是为了查看引脚是否被占用: int gpio_request(gpio, "name"), 4. 将系统编号为gpio的引脚设置为输出管脚,并把管脚的值设置为data_value: int gpio_direction_output(gpio, data_value); 5. 将系统编号为gpio的引脚设置为输入管脚: int gpio_direction_input(gpio); 6. 三星提供的专门设置管脚的接口: int s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(config_value)); 7. 将系统编号为gpio的引脚值设为data_value,主要是针对输出管脚: void gpio_set_value(gpio, data_value); 8. 获取系统编号为gpio的管脚值: int gpio_get_value(gpio); 9. 释放系统编号为gpio的管脚: void gpio_free(gpio); 10. gpio_request操作Demo大致操作: ... // "..."代表省略部分代码,但不影响阅读 struct test_s { struct file_operations fops; int major; unsigned int led_tbl[LEDNO]; }; typedef struct test_s test_t; struct test_s test = { .fops = { .owner = THIS_MODULE, .open = test_open, .release = test_close, .unlocked_ioctl = test_ioctl, }, .major = 0, .led_tbl = { [0] = EXYNOS4X12_GPM4(0), //申请系统对芯片引脚的gpio编号 [1] = EXYNOS4X12_GPM4(1), [2] = EXYNOS4X12_GPM4(2), [3] = EXYNOS4X12_GPM4(3), }, }; ... for(i = 0; i < ARRAY_SIZE(p->led_tbl); i++) { ret = gpio_request(p->led_tbl[i], "led"); //主要是为了查看引脚是否被占用 if(ret) { for(--i; i >= 0; i--) //释放之前成功申请的管脚 { gpio_free(p->led_tbl[i]); } return ret; } } //把io口配置成输出功能,输出高电平 for(i = 0; i < ARRAY_SIZE(p->led_tbl); i++) gpio_direction_output(p->led_tbl[i], LED_OFF); //设置输出方向和初始化值 ... /* 通过ioctl来控制灯的两灭 */ static long test_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int i; test_t *p = file->private_data; if(arg < 0 || arg > 4) return -EINVAL; switch(cmd) { case LED_ON: if(!arg) { for(i = 0; i < ARRAY_SIZE(p->led_tbl); i++) gpio_set_value(p->led_tbl[i], LED_ON); } else gpio_set_value(p->led_tbl[arg - 1], LED_ON); break; case LED_OFF: if(!arg) { for(i = 0; i < ARRAY_SIZE(p->led_tbl); i++) gpio_set_value(p->led_tbl[i], LED_OFF); } else gpio_set_value(p->led_tbl[arg - 1], LED_OFF); break; default: return -EINVAL; } return 0; } ... 四. ioremap相关操作: 1. 在内核中下面两个缩写代表的意义: 1. pa = physical addr 物理地址 2. va = virtual addr 虚拟地址 2. 建立映射关系,给出实际引脚控制寄存器的物理地址,以及控制寄存器的大小size(占用字节数): void *va = ioremap(pa, size); 3. 访问外设地址: 1. 向32位寄存器中读取,写入数据: data = ioread32(addr); iowrite32(data, addr); 2. 向16位寄存器中读取,写入数据: data = ioread16(addr); iowrite16(data, addr); 3. 向8位寄存器中读取,写入数据: data = ioread8(addr); iowrite8(data, addr); 4. 取消映射关系: iounmap(va); 五. LED驱动写法: #include <linux/module.h> #include <linux/fs.h> #include <linux/gpio.h> #include <linux/uaccess.h> #include <linux/io.h> #define rGPM4CON 0x110002E0 #define SIZE SZ_8 //因为GPM4CON和GPM4DAT都是32位寄存器,共8字节 #define GPM4CON 0 //GPM4CON相对rGPM4CON地址的偏移量 #define GPM4DAT 4 //GPM4CON相对rGPM4CON地址的偏移量 #define LED_ON 0 #define LED_OFF 1 #define DEV_NAME "test" #define LEDNO 4 struct test_s { struct file_operations fops; int major; void __iomem *reg; }; typedef struct test_s test_t; static int test_open(struct inode *inode, struct file *file) { unsigned long val; test_t *p; p = container_of(file->f_op, test_t, fops); file->private_data = p; //映射外设地址,GPM4CON和GPM4DAT都是32位寄存器,共8字节 p->reg = ioremap(rGPM4CON, SIZE); if(!p->reg) //映射出错 return -ENOMEM; //配置IO口为输出功能 val = ioread32(p->reg + GPM4CON); val &= ~0xffff; val |= 0x1111; iowrite32(val, p->reg + GPM4CON); return 0; } static int test_close(struct inode *inode, struct file *file) { test_t *p = file->private_data; iounmap(p->reg); //取消io映射 return 0; } static ssize_t test_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { int ret, i; unsigned long val; char kbuf[LEDNO]; test_t *p = file->private_data; if(count <= 0 || count > LEDNO) return -EINVAL; if(count > LEDNO - *pos) //防止获取数据时超出数组下标范围 count = LEDNO - *pos; val = ioread32(p->reg + GPM4DAT); for(i = 0; i < count; i++) { if(test_bit(i, &val)) //测试val中对应的位是否是1 kbuf[i] = LED_OFF; else kbuf[i] = LED_ON; } ret = copy_to_user(buf, kbuf + *pos, count); if(ret) return -EFAULT; *pos += count; return count; } static ssize_t test_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { int ret, i; unsigned long val; char kbuf[LEDNO]; test_t *p = file->private_data; if(count <= 0 || count > LEDNO) return -EINVAL; if(count > LEDNO - *pos) //和read中的原因一致 count = LEDNO - *pos; ret = copy_from_user(kbuf, buf, count); if(ret) return -EFAULT; val = ioread32(p->reg + GPM4DAT); for(i = 0; i < count; i++) { if(kbuf[i] == LED_ON) val &= ~(1 << (i + *pos)); else val |= (1 << (i + *pos)); } iowrite32(val, p->reg + GPM4DAT); *pos += count; return count; } static loff_t test_llseek(struct file *file, loff_t offset, int whence) { loff_t pos = file->f_pos; switch(whence) { case SEEK_SET: pos = offset; break; case SEEK_CUR: pos += offset; break; case SEEK_END: pos = LEDNO + offset; break; default: return -EINVAL; //参数非法 } file->f_pos = pos; return pos; } struct test_s test = { .fops = { .owner = THIS_MODULE, .open = test_open, .release = test_close, .read = test_read, .write = test_write, .llseek = test_llseek, }, .major = 0, }; int __init test_init(void) { int ret; ret = register_chrdev(test.major, DEV_NAME, &test.fops); if(ret > 0) { test.major = ret; printk("major = %d\n", test.major); ret = 0; } return ret; } void __exit test_exit(void) { unregister_chrdev(test.major, DEV_NAME); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL"); 六. 测试LED驱动: #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include "led.h" int main(int argc, char **argv) { int fd, ret, i, j; char buf[4]; fd = open(argv[1], O_RDWR); if(-1 == fd) { perror("open"); exit(1); } while(1) { lseek(fd, 0, SEEK_SET); memset(buf, LED_OFF, sizeof(buf)); write(fd, buf, sizeof(buf)); usleep(300 * 1000); lseek(fd, 0, SEEK_SET); for(i = 0; i < 4; i++) { buf[0] = LED_ON; write(fd, buf, 1); lseek(fd, 0, SEEK_SET); ret = read(fd, buf, sizeof(buf)); if(-1 == ret) perror("read"); for(j = 0; j < sizeof(buf); j++) { if(LED_ON == buf[j]) printf("led%d is on\n", j + 1); else printf("led%d is off\n", j + 1); } usleep(300 * 1000); lseek(fd, i + 1, SEEK_SET); } } close(fd); return 0; }