linux设备树 设备树是什么 设备树就是一个用来描述硬件的接口 相互的子节点关系 在写驱动程序的时候 要通过特定的接口 来找到这和设备的硬件信息 写出驱动
dts语法 先上一个设备树的代码
1 2 3 4 5 6 7 8 9 10 11 12 alphaled{ #address-cell=<1> ; #size-cell=<1> ; compatible="atkalpha-led" ; status="okay" ; reg=< 0X020C406C 0X04 0X020E0068 0X04 0X020E02F4 0X04 0X0209C000 0X04 0X0209C004 0X04 >; };
这就是一个简单的设备树块 他描述了led这个硬件的结构 我们可以在驱动程序中 利用一系列的接口来找到他的信息
#address-cell #size-cell
这个是告诉 reg
地址单元的数量为1大小单元的数量为1 也对应了reg里每个都为1
compatible
这个在总线里用的比较多 就是匹配驱动和设备 这个到时候说
alphaled
这个就是可以利用这个来找到硬件的节点 然后进行匹配
内核解析 内核在启动的时候 会调用 start_kernel() 函数 这个函数会调用解析设备树的函数 然后会在设备的 /proc/devicetree 来体现
特殊节点 可以发现在这里有两个比较特殊的接口分别是
aliases:这个是用来定义别名访问节点的
chosen:uboot的bootargs 就是往这里传递的
常见操作 在下一节写代码上
LED驱动 硬件原理图 可以看出led是连在gpio1_3 上的 我们需要做的步骤时
使能时钟GPIO1 的时钟由 CCM_CCGR1 的 bit27 和 bit26 这两个位控制 地址为
设置 GPIO1_IO03 的复用功能 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 找到地址和复用信息 可以看出她想要gpio1_3 就要设置成5
配置 GPIO1_IO03 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
设置 GPIO 就是每个寄存器都有各自的功能本实验中 GPIO1_IO03 是作为输出功能的,因此 GPIO1_GDIR 的 bit3 要设置为 1,表示输出。
代码 led驱动代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #define DTSLED_CNT 1 #define DTSLED_NAME "dtsled" #define LEDOFF 0 #define LEDON 1 static void __iomem *IMX6U_CCM_CCGR1;static void __iomem *SW_MUX_GPIO1_IO03;static void __iomem *SW_PAD_GPIO1_IO03;static void __iomem *GPIO1_DR;static void __iomem *GPIO1_GDIR;struct dtsled_dev { dev_t devid; struct cdev cdev ; struct class *class ; struct device *device ; int major; int minor; struct device_node *nd ; }; struct dtsled_dev dtsled ; void led_switch (u8 sta) { u32 val = 0 ; if (sta == LEDON) { val = readl(GPIO1_DR); val &= ~(1 << 3 ); writel(val, GPIO1_DR); }else if (sta == LEDOFF) { val = readl(GPIO1_DR); val|= (1 << 3 ); writel(val, GPIO1_DR); } } static int led_open (struct inode *inode, struct file *filp) { filp->private_data = &dtsled; return 0 ; } static ssize_t led_read (struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0 ; } static ssize_t led_write (struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { int retvalue; unsigned char databuf[1 ]; unsigned char ledstat; retvalue = copy_from_user(databuf, buf, cnt); if (retvalue < 0 ) { printk("kernel write failed!\r\n" ); return -EFAULT; } ledstat = databuf[0 ]; if (ledstat == LEDON) { led_switch(LEDON); } else if (ledstat == LEDOFF) { led_switch(LEDOFF); } return 0 ; } static int led_release (struct inode *inode, struct file *filp) { return 0 ; } static struct file_operations dtsled_fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .release = led_release, }; static int __init led_init (void ) { u32 val = 0 ; int ret; u32 regdata[14 ]; const char *str; struct property *proper ; dtsled.nd = of_find_node_by_path("/alphaled" ); if (dtsled.nd == NULL ) { printk("alphaled node nost find!\r\n" ); return -EINVAL; } else { printk("alphaled node find!\r\n" ); } proper = of_find_property(dtsled.nd, "compatible" , NULL ); if (proper == NULL ) { printk("compatible property find failed\r\n" ); } else { printk("compatible = %s\r\n" , (char *)proper->value); } ret = of_property_read_string(dtsled.nd, "status" , &str); if (ret < 0 ){ printk("status read failed!\r\n" ); } else { printk("status = %s\r\n" ,str); } ret = of_property_read_u32_array(dtsled.nd, "reg" , regdata, 10 ); if (ret < 0 ) { printk("reg property read failed!\r\n" ); } else { u8 i = 0 ; printk("reg data:\r\n" ); for (i = 0 ; i < 10 ; i++) printk("%#X " , regdata[i]); printk("\r\n" ); } #if 0 IMX6U_CCM_CCGR1 = ioremap(regdata[0 ], regdata[1 ]); SW_MUX_GPIO1_IO03 = ioremap(regdata[2 ], regdata[3 ]); SW_PAD_GPIO1_IO03 = ioremap(regdata[4 ], regdata[5 ]); GPIO1_DR = ioremap(regdata[6 ], regdata[7 ]); GPIO1_GDIR = ioremap(regdata[8 ], regdata[9 ]); #else IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0 ); SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1 ); SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2 ); GPIO1_DR = of_iomap(dtsled.nd, 3 ); GPIO1_GDIR = of_iomap(dtsled.nd, 4 ); #endif val = readl(IMX6U_CCM_CCGR1); val &= ~(3 << 26 ); val |= (3 << 26 ); writel(val, IMX6U_CCM_CCGR1); writel(5 , SW_MUX_GPIO1_IO03); writel(0x10B0 , SW_PAD_GPIO1_IO03); val = readl(GPIO1_GDIR); val &= ~(1 << 3 ); val |= (1 << 3 ); writel(val, GPIO1_GDIR); val = readl(GPIO1_DR); val |= (1 << 3 ); writel(val, GPIO1_DR); if (dtsled.major) { dtsled.devid = MKDEV(dtsled.major, 0 ); register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME); } else { alloc_chrdev_region(&dtsled.devid, 0 , DTSLED_CNT, DTSLED_NAME); dtsled.major = MAJOR(dtsled.devid); dtsled.minor = MINOR(dtsled.devid); } printk("dtsled major=%d,minor=%d\r\n" ,dtsled.major, dtsled.minor); dtsled.cdev.owner = THIS_MODULE; cdev_init(&dtsled.cdev, &dtsled_fops); cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT); dtsled.class = class_create(THIS_MODULE, DTSLED_NAME); if (IS_ERR(dtsled.class)) { return PTR_ERR(dtsled.class); } dtsled. dtsled.class = device_create(dtsled.class, NULL , dtsled.devid, NULL , DTSLED_NAME); if (IS_ERR(dtsled.device)) { return PTR_ERR(dtsled.device); } return 0 ; } static void __exit led_exit (void ) { iounmap(IMX6U_CCM_CCGR1); iounmap(SW_MUX_GPIO1_IO03); iounmap(SW_PAD_GPIO1_IO03); iounmap(GPIO1_DR); iounmap(GPIO1_GDIR); cdev_del(&dtsled.cdev); unregister_chrdev_region(dtsled.devid, DTSLED_CNT); device_destroy(dtsled.class, dtsled.devid); class_destroy(dtsled.class); } module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("zuozhongkai" );
解释一下这个代码 其实前面也没啥好解释的 主要就时候后面这一段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 if (dtsled.major) { dtsled.devid = MKDEV(dtsled.major, 0 ); register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME); } else { alloc_chrdev_region(&dtsled.devid, 0 , DTSLED_CNT, DTSLED_NAME); dtsled.major = MAJOR(dtsled.devid); dtsled.minor = MINOR(dtsled.devid); } printk("dtsled major=%d,minor=%d\r\n" ,dtsled.major, dtsled.minor); dtsled.cdev.owner = THIS_MODULE; cdev_init(&dtsled.cdev, &dtsled_fops); cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT); dtsled.class = class_create(THIS_MODULE, DTSLED_NAME); if (IS_ERR(dtsled.class)) { return PTR_ERR(dtsled.class); } dtsled. dtsled.class = device_create(dtsled.class, NULL , dtsled.devid, NULL , DTSLED_NAME); if (IS_ERR(dtsled.device)) { return PTR_ERR(dtsled.device); } return 0 ;
这段代码的作用就是 在/dev文档里生出来一个设备节点文件 通过调用这个文件 来联系驱动 进而驱使硬件 其实这个过程哥们也不太明白这里卖弄的cdev和clss 但是我明白这就是创建一个节点的过程 通过它来 链接硬件的ops 然后 create直接创建对应的文件 然后就可以直接操作 。
pinctrl 和 gpio 子系统实验 pinctrl 刚才我们编写led的程序 又臭又长 主要是由这些代码
1 2 3 4 5 IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0 ); SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1 ); SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2 ); GPIO1_DR = of_iomap(dtsled.nd, 3 ); GPIO1_GDIR = of_iomap(dtsled.nd, 4 );
写pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)我们就引入了 pinxtrl 的概念 主要就是为了 解决在驱动文件里直接配置寄存器的办法 举个例子pinctrl 是怎么工作的 打开 .dts文件 里面有
1 2 3 4 5 6 7 8 &iomuxc { pinctrl-names = "default" ; pinctrl-0 = <&pinctrl_hog_1 > ; imx6ul-evk { pinctrl_hog_1: hoggrp-1 { fsl,pins = < MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 }
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19
代表一个宏名 可以去文件中找到 对应这个 #define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
这就是配置这个io复用 以及电器属性 的配置 用这个fsl,pin
来配置 就可以直接配置好接口 不用在驱动文件里配置 那么问题又来了 我只是配置好了 那在驱动里 哥们要写这个寄存器的配置啊 咋办 别急 看下一集
gpio 我们可以在设备树加上 例如 cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
“&gpio1”表示 CD 引脚所使用的 IO 属于 GPIO1 组,“19” 表示 GPIO1 组的第 19 号 IO,“GPIO_ACTIVE_LOW”表示低电平有效,如果改为“GPIO_ACTIVE_HIGH”就表示高电平有效。然后我们就可以通过 函数的接口配置 往寄存器中读写数据 我就贴几个常见的吧 算了 一会实例编程就有了 (偷懒 )
实例编程 来吧 我们实战一下 我们首先修改
pinctrl: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 pinctrl_led: ledgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 >; }; 2. gpio:```c gpioled { #address-cells = <1> ; #size-cells = <1> ; compatible = "atkalpha-gpioled" ; pinctrl-names = "default" ; pinctrl-0 = <&pinctrl_led>; led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; status = "okay" ; };
3.编程 其实跟之前差不多 就是在结构体处 加上int led_gpio;
在改变led状态的时候 gpio_set_value(dev->led_gpio, 0);
读取led_gpiogpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
就可以了
并发与竞争 这都是很老生常谈的问题了 就不用多说了 咱们主要来研究解决竞争的几种方法
原子操作 咱们先来说说正常操作的隐患 比如我的a程序写了 a=3 其实真是a=3? 他中间要有很多过程 比如
1 2 3 ldr r0, =0X30000000 /* 变量 a 地址 */ ldr r1, = 3 /* 要写入的值 */ str r1, [r0] /* 将 3 写入到 a 变量中 */
这个时候如果时间片用完了 轮到了别的程序 就会把这个结果覆盖掉,Linux 内核定义了叫做 atomic_t 的结构体来完成整形数据的原子操作,在使用中用原子变量来代替整形变量,它的用法atomic_t a; //定义 a
atomic_t b = ATOMIC_INIT(0);
函数
描述
ATOMIC_INIT(int i)
定义原子变量的时候对其初始化。
int atomic_read(atomic_t *v)
读取 v 的值,并且返回。
懒得写了 到时候自己查吧
现在的问题是 我们要保护的东西可不止是整形 有可能是一段资源 那咋办 到了上锁这步了
自旋锁 自旋锁的“自旋”也就是“原地打转”的意思,“原地打转”的目的是为了等待自旋锁可以用,可以访问共享资源 适合段时间占有锁 代码一会贴 先来看看 长见应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 DEFINE_SPINLOCK(lock) void functionA () {unsigned long flags; spin_lock_irqsave(&lock, flags) spin_unlock_irqrestore(&lock, flags) } void irq () {spin_lock(&lock) spin_unlock(&lock) }
信号量 相比于自旋锁,信号量可以使线程进入休眠状态,比如 A 与 B、C 合租了一套房子,这个房子只有一个厕所,一次只能一个人使用。某一天早上 A 去上厕所了,过了一会 B 也想用厕所,因为 A 在厕所里面,所以 B 只能等到 A 用来了才能进去。B 要么就一直在厕所门口等着,等 A 出来,这个时候就相当于自旋锁。B 也可以告诉 A,让 A 出来以后通知他一下,然后 B 继续回房间睡觉,这个时候相当于信号量 而且信号量可以规定多个进程共享资源
1 2 3 4 5 6 struct semaphore sem ; sema_init(&sem, 1 ); down(&sem); up(&sem);
互斥体 将信号量的值设置为 1 就可以使用信号量进行互斥访问了,虽然可以通过信号量实现互斥,但是 Linux 提供了一个比信号量更专业的机制来进行互斥,它就是互斥体—mutex
1 2 3 4 5 struct mutex lock; /* 定义一个互斥体 */ mutex_init(&lock); /* 初始化互斥体 */ mutex_lock(&lock); /* 上锁 */ /* 临界区 */ mutex_unlock(&lock); /* 解锁 */
并发竞争实验 原子变量 就是定义了一个一原子变量 程序打开时判断是否为0 不为零减一 为零就返回错误 以上个led代码为例 我们要想使用led 可以在结构提处加上atomic_t lock;
在init上初始化变量atomic_set(&gpioled.lock, 1);
关闭区驱动释放变量atomic_inc(&dev->lock);
在打开文件是 使用
1 2 3 4 if (!atomic_dec_and_test(&gpioled.lock)) { atomic_inc (&gpioled.lock); return -EBUSY; }
自旋锁 涛声依旧还是在之前的代码上进行修改 在结构体处加上int dev_stats; spinlock_t lock;
代表锁 设备状态,0,设备未使用;>0,设备已经被使用 init spin_lock_init(&gpioled.lock);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 spin_lock_irqsave(&gpioled.lock, flags); if (gpioled.dev_stats) { spin_unlock_irqrestore(&gpioled.lock, flags); return -EBUSY; } gpioled.dev_stats++; spin_unlock_irqrestore(&gpioled.lock, flags); return 0 ; spin_lock_irqsave(&dev->lock, flags); if (dev->dev_stats) { dev->dev_stats--; } spin_unlock_irqrestore(&dev->lock, flags); return 0 ;
信号量 struct semaphore sem;
结构提出加上信号量函数sema_init(&gpioled.sem, 1);
init 初始化
1 2 3 4 5 6 if (down_interruptible(&gpioled.sem)) { return -ERESTARTSYS; } up(&dev->sem);
按键输入实验 直接贴代码吧 感觉和led一样 就是换成了输入 然后在里面加了一个原子变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <l #include <linux/semaphore.h> inux/of.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #define KEY_CNT 1 #define KEY_NAME "key" #define KEY0VALUE 0XF0 #define INVAKEY 0X00 struct key_dev { dev_t devid; struct cdev cdev ; struct class *class ; struct device *device ; int major; int minor; struct device_node *nd ; int key_gpio; atomic_t keyvalue; }; struct key_dev keydev ; static int keyio_init (void ) { keydev.nd = of_find_node_by_path("/key" ); if (keydev.nd== NULL ) { return -EINVAL; } keydev.key_gpio = of_get_named_gpio(keydev.nd ,"key-gpio" , 0 ); if (keydev.key_gpio < 0 ) { printk("can't get key0\r\n" ); return -EINVAL; } printk("key_gpio=%d\r\n" , keydev.key_gpio); gpio_request(keydev.key_gpio, "key0" ); gpio_direction_input(keydev.key_gpio); return 0 ; } static int key_open (struct inode *inode, struct file *filp) { int ret = 0 ; filp->private_data = &keydev; ret = keyio_init(); if (ret < 0 ) { return ret; } return 0 ; } static ssize_t key_read (struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret = 0 ; int value; struct key_dev *dev = filp->private_data; if (gpio_get_value(dev->key_gpio) == 0 ) { while (!gpio_get_value(dev->key_gpio)); atomic_set (&dev->keyvalue, KEY0VALUE); } else { atomic_set (&dev->keyvalue, INVAKEY); } value = atomic_read (&dev->keyvalue); ret = copy_to_user(buf, &value, sizeof (value)); return ret; } static ssize_t key_write (struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { return 0 ; } static int key_release (struct inode *inode, struct file *filp) { return 0 ; } static struct file_operations key_fops = { .owner = THIS_MODULE, .open = key_open, .read = key_read, .write = key_write, .release = key_release, }; static int __init mykey_init (void ) { atomic_set (&keydev.keyvalue, INVAKEY); if (keydev.major) { keydev.devid = MKDEV(keydev.major, 0 ); register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME); } else { alloc_chrdev_region(&keydev.devid, 0 , KEY_CNT, KEY_NAME); keydev.major = MAJOR(keydev.devid); keydev.minor = MINOR(keydev.devid); } keydev.cdev.owner = THIS_MODULE; cdev_init(&keydev.cdev, &key_fops); cdev_add(&keydev.cdev, keydev.devid, KEY_CNT); keydev.class = class_create(THIS_MODULE, KEY_NAME); if (IS_ERR(keydev.class)) { return PTR_ERR(keydev.class); } keydev.device = device_create(keydev.class, NULL , keydev.devid, NULL , KEY_NAME); if (IS_ERR(keydev.device)) { return PTR_ERR(keydev.device); } return 0 ; } static void __exit mykey_exit (void ) { gpio_free(keydev.key_gpio); cdev_del(&keydev.cdev); unregister_chrdev_region(keydev.devid, KEY_CNT); device_destroy(keydev.class, keydev.devid); class_destroy(keydev.class); } module_init(mykey_init); module_exit(mykey_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("zuozhongkai" );
Linux 内核定时器实验 这代码又臭又长 我贴个思路吧
计时器的思路 首先定义一个定时器 struct timer_list timer;
然后定义一个函数 以及初始化函数 1 2 3 4 5 6 7 8 9 10 11 12 13 void function (unsigned long arg) { mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000 )); } 17 void init (void ) { init_timer(&timer); timer.function = function; timer.expires=jffies + msecs_to_jiffies(2000 ); timer.data = (unsigned long )&dev; add_timer(&timer); }
这样就完成了一个周期定时器的创建
控制函数 首先定义好 1 2 3 #define CLOSE_CMD (_IO(0XEF, 0x1)) #define OPEN_CMD (_IO(0XEF, 0x2)) #define SETPERIOD_CMD (_IO(0XEF, 0x3))
然后再 ioctl 这个函数里 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 struct timer_dev *dev = (struct timer_dev *)filp->private_data; int timerperiod; unsigned long flags; switch (cmd) { case CLOSE_CMD: del_timer_sync(&dev->timer); break ; case OPEN_CMD: spin_lock_irqsave(&dev->lock, flags); timerperiod = dev->timeperiod; spin_unlock_irqrestore(&dev->lock, flags); mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod)); break ; case SETPERIOD_CMD: spin_lock_irqsave(&dev->lock, flags); dev->timeperiod = arg; spin_unlock_irqrestore(&dev->lock, flags); mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg)); break ; default : break ; } ret
中断实验 中断流程 就是我们每一个引脚区域 都对应着不同的中断号 我们可以利用函数 多这些中短号注册函数 在定义的状态发生改变时会通知注册的函数 达到终端启用函数的效果 例如 我们的gpio5节点 的设备树为 1 2 3 4 5 6 7 8 9 10 gpio5: gpio@020 ac000 { compatible = "fsl,imx6ul-gpio" , "fsl,imx35-gpio" ; reg = <0x020ac000 0x4000 >; interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>; gpio-controller; #gpio-cells = <2> ; interrupt-controller; #interrupt-cells = <2> ; };
在这里interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
描述了gpio5 对应了两个中断号 74和75 interrupt-controller
表明了 gpio5 节点也是个中断控制器,用于控制 gpio5 所有 IO的中断。 而使用该节点的具体设备 1 2 3 4 5 6 7 fxls8471@1 e { compatible = "fsl,fxls8471" ; reg = <0x1e >; position = <0 >; interrupt-parent = <&gpio5>; interrupts = <0 8 >; };
interrupt-parent
属性设置中断控制器,这里使用 gpio5 作为中断控制器。interrupts
设置中断信息,0 表示 GPIO5_IO00,8 表示低电平触发。
中断相关的函数接口 获取中断号 irq_of_parse_and_map
函数从 interupts
属性中提取到对应的设备号
编程实例 先来说一下想要的流程 就是现在init 函数里注册中断 按键按下 中断开启 启动定时器 目的是为了消抖 然后把读取出来的结果传递给应用程序
设备树修改 就是在key的设备书里 加上 8 interrupt-parent = <&gpio1>;9 interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
驱动程序编写 代码 我就挑着写吧
定义结构体 在终端里定义两个结构体 irq_keydesc 和 imx6uirq_dev
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct irq_keydesc { int gpio; int irqnum; unsigned char value; char name[10 ]; irqreturn_t (*handler)(int , void *); }; struct imx6uirq_dev { dev_t devid; struct cdev cdev ; struct class *class ; struct device *device ; int major; int minor; struct device_node *nd ; atomic_t keyvalue; atomic_t releasekey; struct timer_list timer ; struct irq_keydesc irqkeydesc [KEY_NUM ]; unsigned char curkeynum; };
注册中断 首先获取节点 nd
然后找到gpio 注册中段号 设置成中断模式 然后写中断函数 最后申请中断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 unsigned char i = 0 ; int ret = 0 ; imx6uirq.nd = of_find_node_by_path("/key" ); if (imx6uirq.nd== NULL ){ printk("key node not find!\r\n" ); return -EINVAL; } for (i = 0 ; i < KEY_NUM; i++) { imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio" , i); if (imx6uirq.irqkeydesc[i].gpio < 0 ) { printk("can't get key%d\r\n" , i); } } for (i = 0 ; i < KEY_NUM; i++) { memset (imx6uirq.irqkeydesc[i].name, 0 , sizeof (imx6uirq.irqkeydesc[i].name)); sprintf (imx6uirq.irqkeydesc[i].name, "KEY%d" , i); gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name); gpio_direction_input(imx6uirq.irqkeydesc[i].gpio); imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i); #if 0 imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio); #endif printk("key%d:gpio=%d, irqnum=%d\r\n" ,i, imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].irqnum); } imx6uirq.irqkeydesc[0 ].handler = key0_handler; imx6uirq.irqkeydesc[0 ].value = KEY0VALUE; for (i = 0 ; i < KEY_NUM; i++) { ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq); if (ret < 0 ){ printk("irq %d request failed!\r\n" , imx6uirq.irqkeydesc[i].irqnum); return -EFAULT; } }
写计时器函数 和中断函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static irqreturn_t key0_handler (int irq, void *dev_id) { struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id; dev->curkeynum = 0 ; dev->timer.data = (volatile long )dev_id; mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10 )); return IRQ_RETVAL(IRQ_HANDLED); } void timer_function (unsigned long arg) { unsigned char value; unsigned char num; struct irq_keydesc *keydesc ; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg; num = dev->curkeynum; keydesc = &dev->irqkeydesc[num]; value = gpio_get_value(keydesc->gpio); if (value == 0 ){ atomic_set (&dev->keyvalue, keydesc->value); } else { atomic_set (&dev->keyvalue, 0x80 | keydesc->value); atomic_set (&dev->releasekey, 1 ); } }
作用 如果我在三个平台 A、B 和 C 都使用了i2c 去传递MPU6050 这个 I2C接口的六轴传感器 按之前的逻辑 我们一定会在三个平台都在写好 ioc驱动 然后到mpu 再写一次 这样会造成大量的代码 我们采用总线的这个思路 把 每个平台的 I2C 控制器都提供一个统一的接口 ,每个设备通过统一的 I2C接口驱动来访问,这样就可以大大简化驱动文件 就类似于 变成: 他的代码逻辑是这样 : 首先在init 函数里调用 platform_driver_register
然后这个函数里面会哦有一个结构体 platform_driver led_driver
他是里面可以和设备树挂钩 of_device_id led_of_match
寻找设备树 匹配上之后会调用 led_probe
来注册led驱动 其实在这个函数里体现不出来总线的作用 我们一会举一个i2c总线的例子就明白了
i2c驱动 首先捋一下代码流程然后说代码i2c_add_driver(&ap3216c_driver);
类似于 platform_driver_register
然后带哦用寻找设备树的结构体 最后在prope函数中 static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
直接把client 放在ap3216cdev.private_data = client;
这样就完成了 匹配 能够普直接使用 i2c 传输了 省的写太多冗余代码 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/i2c.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include "ap3216creg.h" #define AP3216C_CNT 1 #define AP3216C_NAME "ap3216c" struct ap3216c_dev { dev_t devid; struct cdev cdev ; struct class *class ; struct device *device ; struct device_node *nd ; int major; void *private_data; unsigned short ir, als, ps; }; static struct ap3216c_dev ap3216cdev ;static int ap3216c_read_regs (struct ap3216c_dev *dev, u8 reg, void *val, int len) { int ret; struct i2c_msg msg [2]; struct i2c_client *client = (struct i2c_client *)dev->private_data; msg[0 ].addr = client->addr; msg[0 ].flags = 0 ; msg[0 ].buf = ® msg[0 ].len = 1 ; msg[1 ].addr = client->addr; msg[1 ].flags = I2C_M_RD; msg[1 ].buf = val; msg[1 ].len = len; ret = i2c_transfer(client->adapter, msg, 2 ); if (ret == 2 ) { ret = 0 ; } else { printk("i2c rd failed=%d reg=%06x len=%d\n" ,ret, reg, len); ret = -EREMOTEIO; } return ret; } static s32 ap3216c_write_regs (struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len) { u8 b[256 ]; struct i2c_msg msg ; struct i2c_client *client = (struct i2c_client *)dev->private_data; b[0 ] = reg; memcpy (&b[1 ],buf,len); msg.addr = client->addr; msg.flags = 0 ; msg.buf = b; msg.len = len + 1 ; return i2c_transfer(client->adapter, &msg, 1 ); } static unsigned char ap3216c_read_reg (struct ap3216c_dev *dev, u8 reg) { u8 data = 0 ; ap3216c_read_regs(dev, reg, &data, 1 ); return data; #if 0 struct i2c_client *client = (struct i2c_client *)dev->private_data; return i2c_smbus_read_byte_data(client, reg); #endif } static void ap3216c_write_reg (struct ap3216c_dev *dev, u8 reg, u8 data) { u8 buf = 0 ; buf = data; ap3216c_write_regs(dev, reg, &buf, 1 ); } void ap3216c_readdata (struct ap3216c_dev *dev) { unsigned char i =0 ; unsigned char buf[6 ]; for (i = 0 ; i < 6 ; i++) { buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i); } if (buf[0 ] & 0X80 ) dev->ir = 0 ; else dev->ir = ((unsigned short )buf[1 ] << 2 ) | (buf[0 ] & 0X03 ); dev->als = ((unsigned short )buf[3 ] << 8 ) | buf[2 ]; if (buf[4 ] & 0x40 ) dev->ps = 0 ; else dev->ps = ((unsigned short )(buf[5 ] & 0X3F ) << 4 ) | (buf[4 ] & 0X0F ); } static int ap3216c_open (struct inode *inode, struct file *filp) { filp->private_data = &ap3216cdev; ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04 ); mdelay(50 ); ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03 ); return 0 ; } static ssize_t ap3216c_read (struct file *filp, char __user *buf, size_t cnt, loff_t *off) { short data[3 ]; long err = 0 ; struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data; ap3216c_readdata(dev); data[0 ] = dev->ir; data[1 ] = dev->als; data[2 ] = dev->ps; err = copy_to_user(buf, data, sizeof (data)); return 0 ; } static int ap3216c_release (struct inode *inode, struct file *filp) { return 0 ; } static const struct file_operations ap3216c_ops = { .owner = THIS_MODULE, .open = ap3216c_open, .read = ap3216c_read, .release = ap3216c_release, }; static int ap3216c_probe (struct i2c_client *client, const struct i2c_device_id *id) { if (ap3216cdev.major) { ap3216cdev.devid = MKDEV(ap3216cdev.major, 0 ); register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME); } else { alloc_chrdev_region(&ap3216cdev.devid, 0 , AP3216C_CNT, AP3216C_NAME); ap3216cdev.major = MAJOR(ap3216cdev.devid); } cdev_init(&ap3216cdev.cdev, &ap3216c_ops); cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT); ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME); if (IS_ERR(ap3216cdev.class)) { return PTR_ERR(ap3216cdev.class); } ap3216cdev.device = device_create(ap3216cdev.class, NULL , ap3216cdev.devid, NULL , AP3216C_NAME); if (IS_ERR(ap3216cdev.device)) { return PTR_ERR(ap3216cdev.device); } ap3216cdev.private_data = client; return 0 ; } static int ap3216c_remove (struct i2c_client *client) { cdev_del(&ap3216cdev.cdev); unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT); device_destroy(ap3216cdev.class, ap3216cdev.devid); class_destroy(ap3216cdev.class); return 0 ; } static const struct i2c_device_id ap3216c_id [] = { {"alientek,ap3216c" , 0 }, {} }; static const struct of_device_id ap3216c_of_match [] = { { .compatible = "alientek,ap3216c" }, { } }; static struct i2c_driver ap3216c_driver = { .probe = ap3216c_probe, .remove = ap3216c_remove, .driver = { .owner = THIS_MODULE, .name = "ap3216c" , .of_match_table = ap3216c_of_match, }, .id_table = ap3216c_id, }; static int __init ap3216c_init (void ) { int ret = 0 ; ret = i2c_add_driver(&ap3216c_driver); return ret; } static void __exit ap3216c_exit (void ) { i2c_del_driver(&ap3216c_driver); } module_init(ap3216c_init); module_exit(ap3216c_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("zuozhongkai" );
MISC 驱动实验 是什么 杂项驱动 就是你在一些写之前的驱动的时候 你有一部分的代码是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 if (ap3216cdev.major) { ap3216cdev.devid = MKDEV(ap3216cdev.major, 0 ); register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME); } else { alloc_chrdev_region(&ap3216cdev.devid, 0 , AP3216C_CNT, AP3216C_NAME); ap3216cdev.major = MAJOR(ap3216cdev.devid); } cdev_init(&ap3216cdev.cdev, &ap3216c_ops); cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT); ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME); if (IS_ERR(ap3216cdev.class)) { return PTR_ERR(ap3216cdev.class); } ap3216cdev.device = device_create(ap3216cdev.class, NULL , ap3216cdev.devid, NULL , AP3216C_NAME); if (IS_ERR(ap3216cdev.device)) { return PTR_ERR(ap3216cdev.device); }
这个过程就像是裹脚布 又臭又长 哦我们可以把一些有各自性质的进行封装 然后为他们规定好主设备号 然后只需要调用对应的 api 就可以完成注册 比如说 beep 就可以用之前的杂项 只需要在对应的prope
函数里 ret = misc_register(&beep_miscdev);
就可以完成驱动的注册
input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息 就是把之前的 misc 杂项 具体为某个输入的设备 唯一有不一样的就是 你要告诉你的系统 你注册的是什么驱动
代码流程 首先要在你的结构体上定义input struct input_dev *inputdev;
然后按键被按下input_report_key(dev->inputdev, keydesc->value, 1);/*1,按下*/
1 2 3 4 5 6 7 8 9 keyinputdev.inputdev = input_allocate_device(); keyinputdev.inputdev->name = KEYINPUT_NAME; keyinputdev.inputdev->evbit[0 ] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0); ret = input_register_device(keyinputdev.inputdev);
申请定义和注册
音频驱动 音频是我们最常用到的功能,音频也是 linux 和安卓的重点应用场合。I.MX6ULL 带有 SAI接口,正点原子的 I.MX6ULL ALPHA 开发板通过此接口外接了一个 WM8960 音频 DAC 芯片,本章我们就来学习一下如何使能 WM8960 驱动,并且通过 WM8960 芯片来完成音乐播放与录音 首先要把模拟信号转化成数字信号 所以需要 adc和dac 音频一般利用的协议时 i2s ADCDAT:ADC 数据输出引脚,采集到的音频数据转换为数字信号以后通过此引脚传输给 主控制器。 ADCLRC:ADC 数据对齐时钟,也就是帧时钟(LRCK),用于切换左右声道数据,此信号 的频率就是采样率。此引脚可以配置为 GPIO 功能,配置为 GPIO 以后 ADC 就会使用 DACLRC 引脚作为帧时钟。 DACDAT:DAC 数据输入引脚,主控器通过此引脚将数字信号输入给 WM8960 的 DAC。 DACLRC:DAC 数据对齐时钟,功能和 ADCLRC 一样,都是帧时钟(LRCK),用于切换左 右声道数据,此信号的频率等于采样率。 BCLK:位时钟,用于同步。 MCLK:主时钟,WM8960 工作的时候还需要一路主时钟,此时钟由 I.MX6ULL 提供, MCLK 频率等于采样率的 256 或 384 倍,因此大家在 WM8960 的数据手册里面常看到 MCLK=256fs 或 MCLK=384fs。 我们的audio电路图如图: 主要关注两点
SAI 接口一共用到了 6 根数据线,这 6 根数据线用于 I.MX6ULL 与 WM8960 之间的音 频数据收发。
WM8960 在使用的时候需要进行配置,配置接口为 I2C,
修改设备树 wm8960 i2c 接口设备 1 2 3 4 5 6 7 codec: wm8960@1 a { compatible = "wlf,wm8960" ; reg = <0x1a >; clocks = <&clks IMX6UL_CLK_SAI2>; clock-names = "mclk" ; wlf,shared-lrclk; };
把这个放到对应的 I2C2 节点下,
SAI 音频接口设备树 1 2 3 4 5 6 7 8 9 10 &sai2 { pinctrl-names = "default" ; pinctrl-0 = <&pinctrl_sai2 &pinctrl_sai2_hp_det_b>; assigned-clocks = <&clks IMX6UL_CLK_SAI2_SEL>, <&clks IMX6UL_CLK_SAI2>; assigned-clock-parents = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>; assigned-clock-rates = <0 >, <12288000 >; status = "okay" ; };
然后一直对应音频需要的配置就可以了 这里就不罗嗦了
CAN CAN 的全称为 Controller Area Network,也就是控制局域网络, CAN 的特点主要有一下几点:
多主控制
系统的柔软性与总线相连的单元没有类似于“地址”的信息。因此在总线上增加单元时,连接在总线上
通信速度快,距离远
具有错误检测、错误通知和错误恢复功能
连接节点多
CAN 电气属性 CAN 总线使用两根线来连接各个单元:CAN_H 和 CAN_L,CAN 控制器通过判断这两根 线上的电位差来得到总线电平,CAN 总线电平分为显性电平和隐性电平两种。显性电平表示逻 辑“0”,此时 CAN_H 电平比 CAN_L 高,分别为 3.5V 和 1.5V,电位差为 2V。隐形电平表示 逻辑“1”,此时 CAN_H 和 CAN_L 电压都为 2.5V 左右,电位差为 0V。CAN 总线就通过显性 和隐形电平的变化来将具体的数据发送出去
CAN 协议 我就说一下他的每个帧都是用来干什么的 起始位: 0 识别码:就是每个设备都有自己的编号 控制码 6位 剩下的都是纠错用的码
修改设备树 首先要配置pinctrl 吧引脚配置成can
1 2 3 4 5 6 pinctrl_flexcan1: flexcan1grp{ fsl,pins = < MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020 MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020 >; };
示例代码:
1 2 3 4 5 6 &flexcan1 { pinctrl-names = "default" ; pinctrl-0 = <&pinctrl_flexcan1>; xceiver-supply = <®_can_3v3>; status = "okay" ; };
需要注意的是 can总线在linux中以网卡的形式存在
块设备驱动实验 块设备是针对存储设备的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等linux 内 核 使 用 block_device 表 示 块 设 备 , block_device 为 一 个 结 构 体 , 定 义 在include/linux/fs.h 文件中,在这里有一个重要的结构体struct gendisk *bd_disk;
block_device 来表示一个具体的块设备对象,比如一个硬盘或者分区,如果是硬盘的话 bd_disk 就指向通用磁盘结构 gendisk。 驱动书写流程: 首先 写结构体 块设备的结构题
1 2 3 4 5 6 7 8 9 struct ramdisk_dev { int major; unsigned char *ramdiskbuf; spinlock_t lock; struct gendisk *gendisk ; struct request_queue *queue ; };
那么在init函数里 我们要做的就是初始化这几个结构体里的数据 还有把去操作及放在ramdisk.gendisk
这个结构体里
init 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 static int __init ramdisk_init (void ) { int ret = 0 ; printk("ramdisk init\r\n" ); ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL); if (ramdisk.ramdiskbuf == NULL ) { ret = -EINVAL; goto ram_fail; } spin_lock_init(&ramdisk.lock); ramdisk.major = register_blkdev(0 , RAMDISK_NAME); if (ramdisk.major < 0 ) { goto register_blkdev_fail; } printk("ramdisk major = %d\r\n" , ramdisk.major); ramdisk.gendisk = alloc_disk(RADMISK_MINOR); if (!ramdisk.gendisk) { ret = -EINVAL; goto gendisk_alloc_fail; } ramdisk.queue = blk_init_queue(ramdisk_request_fn, &ramdisk.lock); if (!ramdisk.queue ) { ret = EINVAL; goto blk_init_fail; } ramdisk.gendisk->major = ramdisk.major; ramdisk.gendisk->first_minor = 0 ; ramdisk.gendisk->fops = &ramdisk_fops; ramdisk.gendisk->private_data = &ramdisk; ramdisk.gendisk->queue = ramdisk.queue ; sprintf (ramdisk.gendisk->disk_name, RAMDISK_NAME); set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512 ); add_disk(ramdisk.gendisk); return 0 ; blk_init_fail: put_disk(ramdisk.gendisk); gendisk_alloc_fail: unregister_blkdev(ramdisk.major, RAMDISK_NAME); register_blkdev_fail: kfree(ramdisk.ramdiskbuf); ram_fail: return ret; }
初始化好之后 写入操作集 示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 int ramdisk_open (struct block_device *dev, fmode_t mode) { printk("ramdisk open\r\n" ); return 0 ; } void ramdisk_release (struct gendisk *disk, fmode_t mode) { printk("ramdisk release\r\n" ); } int ramdisk_getgeo (struct block_device *dev, struct hd_geometry *geo) { geo->heads = 2 ; geo->cylinders = 32 ; geo->sectors = RAMDISK_SIZE / (2 * 32 *512 ); return 0 ; }
还有比较重要的函数 是传输和处理队列的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 */ static void ramdisk_transfer (struct request *req) { unsigned long start = blk_rq_pos(req) << 9 ; unsigned long len = blk_rq_cur_bytes(req); void *buffer = bio_data(req->bio); if (rq_data_dir(req) == READ) memcpy (buffer, ramdisk.ramdiskbuf + start, len); else if (rq_data_dir(req) == WRITE) memcpy (ramdisk.ramdiskbuf + start, buffer, len); } void ramdisk_request_fn (struct request_queue *q) { int err = 0 ; struct request *req ; req = blk_fetch_request(q); while (req != NULL ) { ramdisk_transfer(req); if (!__blk_end_request_cur(req, err)) req = blk_fetch_request(q); } }
Linux 网络驱动实验 1