fl2440字符设备led驱动
首先要明白字符设备驱动注册的基本流程
当我们调用insomd命令加载驱动后,驱动程序从module_init函数开始执行:硬件初始化 -> 申请主次设备号 -> 定义fops(file_operations)结构体 -> 申请cdev结构体并把fops结构体嵌入cdev结构体中与之绑定 -> cdev字符设备的注册。
有一点需要明确的是,在Linux内核中,所有的设备都是以文件。我们对设备的操作即是对Linux内核中文件的操作。设备都在/dev目录下。而inode则是设备索引节点,每个文件产生后都会有相应的inode来标示。
驱动加载完成后,file_operations结构体中的成员可以为应用程序提供对设备进行各种操作的函数指针:open,read,write等。
cdev结构体是用来描述字符设备的,内核中每个字符设备都对应一个 cdev 结构的变量,下面是它的定义:
1 linux-2.6.22/include/linux/cdev.h 2 struct cdev { 3 struct kobject kobj; // 每个 cdev 都是一个 kobject 4 struct module *owner; // 指向实现驱动的模块 5 const struct file_operations *ops; // 操纵这个字符设备文件的方法 6 struct list_head list; // 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头 7 dev_t dev; // 起始设备编号 8 unsigned int count; // 设备范围号大小 9 };
一个 cdev 一般它有两种定义初始化方式:静态的和动态的。
静态内存定义初始化:
struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
动态内存定义初始化:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;
两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。
s3c_led代码:
1 /********************************************************************************* 2 * Copyright: (C) 2012 Guo Wenxue<guowenxue@gmail.com> 3 * All rights reserved. 4 * 5 * Filename: s3c_led.c 6 * Description: This file 7 * 8 * Version: 1.0.0(07/26/2012~) 9 * Author: Guo Wenxue <guowenxue@gmail.com> 10 * ChangeLog: 1, Release initial version on "07/26/2012 10:03:40 PM" 11 * 12 ********************************************************************************/ 13 14 #include <linux/module.h> /* Every Linux kernel module must include this head */ 15 #include <linux/init.h> /* Every Linux kernel module must include this head */ 16 #include <linux/kernel.h> /* printk() */ 17 #include <linux/fs.h> /* struct fops */ 18 #include <linux/errno.h> /* error codes */ 19 #include <linux/cdev.h> /* cdev_alloc() */ 20 #include <asm/io.h> /* ioremap() */ 21 #include <linux/ioport.h> /* request_mem_region() */ 22 23 #include <asm/ioctl.h> /* Linux kernel space head file for macro _IO() to generate ioctl command */ 24 #ifndef __KERNEL__ 25 #include <sys/ioctl.h> /* User space head file for macro _IO() to generate ioctl command */ 26 #endif 27 //#include <linux/printk.h> /* Define log level KERN_DEBUG, no need include here */ 28 29 30 #define DRV_AUTHOR "Guo Wenxue <guowenxue@gmail.com>" 31 #define DRV_DESC "S3C24XX LED driver" 32 33 #define DEV_NAME "led" //定义设备名称 34 #define LED_NUM 4 //定义设备数量 35 36 /* Set the LED dev major number */ 37 //#define LED_MAJOR 79 38 #ifndef LED_MAJOR 39 #define LED_MAJOR 0 //定义默认的设备号为0,一般这个设备号是不可用的,但是这位自动分配主设备号的逻辑提供了方便 40 #endif 41 42 #define DRV_MAJOR_VER 1 43 #define DRV_MINOR_VER 0 44 #define DRV_REVER_VER 0 45 46 #define DISABLE 0 //禁用某个特性的宏 47 #define ENABLE 1 //使能某个特性的宏 48 49 #define GPIO_INPUT 0x00 //定义GPIO输入模式用0x00代替 50 #define GPIO_OUTPUT 0x01 //定义GPIO输出模式用0x01代替 51 52 53 #define PLATDRV_MAGIC 0x60 //定义一个魔术字 54 //魔术字有着特殊的功能,定义一个系统未用的魔术字,然后让魔术字生成我们定义的LED_OFF与LED_ON,这样我们的定义就不会和系统中别的宏定义相同了 55 #define LED_OFF _IO (PLATDRV_MAGIC, 0x18) 56 #define LED_ON _IO (PLATDRV_MAGIC, 0x19) 57 58 #define S3C_GPB_BASE 0x56000010 //定义GPB管脚控制寄存器的基址地址 59 60 #define GPBCON_OFFSET 0 //定义GPBCON的偏移地址,用来选定引脚并设置输入输出模式 61 //GPBDAT用于读写引脚数据。输入模式时,读此寄存器可知相应引脚的电平是低还是高;输出模式写此引脚可设置输出高低电平 62 #define GPBDAT_OFFSET 4 //定义GPBDAT的偏移地址 63 /*GPBUP的某位为1时,相应引脚无内部上拉电阻;为0时相应引脚使用内部上拉电阻。 64 上拉电阻的作用是当GPIO引脚出去第三态时,即既不是输出高电平也不是输出低电平, 65 而是呈现高阻态,相当于没有接芯片。它的电平状态由上拉电阻下拉电阻决定。*/ 66 #define GPBUP_OFFSET 8 67 #define S3C_GPB_LEN 0x10 /* 0x56000010~0x56000020 */ //GPB寄存器内存地址总长度 68 69 int led[LED_NUM] = {5,6,8,10}; /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */ 70 71 static void __iomem *s3c_gpb_membase; //定义一个指向一个IO内存空间的指针,后面做虚拟内存的映射 72 73 74 #define s3c_gpio_write(val, reg) __raw_writel((val), (reg)+s3c_gpb_membase) //将val的值写入reg地址中 75 #define s3c_gpio_read(reg) __raw_readl((reg)+s3c_gpb_membase) //读取地址为reg中的值 76 77 78 int dev_count = ARRAY_SIZE(led); //设备结构体中设备的个数 79 int dev_major = LED_MAJOR; //主设备号赋值给dev_major 80 int dev_minor = 0; //次设备号为0 81 int debug = DISABLE; //出错定义赋值 82 83 static struct cdev *led_cdev; //定义一个cdev结构体类型的指针 84 85 static int s3c_hw_init(void) //硬件初始化函数 86 { 87 int i; 88 volatile unsigned long gpb_con, gpb_dat, gpb_up; //因为是保存寄存器地址,所以要用volatile防止被优化 89 90 if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led")) //申请一段IO内存空间,失败返回0 91 { 92 return -EBUSY; 93 } 94 95 if( !(s3c_gpb_membase=ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) ) //物理地址映射到虚拟地址,此时开启MMU 96 { 97 release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); //映射失败则释放申请的IO内存空间···· 98 return -ENOMEM; 99 } 100 101 for(i=0; i<dev_count; i++) 102 { 103 /* Set GPBCON register, set correspond GPIO port as input or output mode */ 104 gpb_con = s3c_gpio_read(GPBCON_OFFSET); 105 gpb_con &= ~(0x3<<(2*led[i])); /* Clear the currespond LED GPIO configure register */ 106 gpb_con |= GPIO_OUTPUT<<(2*led[i]); /* Set the currespond LED GPIO as output mode */ 107 s3c_gpio_write(gpb_con, GPBCON_OFFSET); 108 109 /* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable */ 110 gpb_up = s3c_gpio_read(GPBUP_OFFSET); 111 //gpb_up &= ~(0x1<<led[i]); /* Enable pull up resister */ 112 gpb_up |= (0x1<<led[i]); /* Disable pull up resister */ 113 s3c_gpio_write(gpb_up, GPBUP_OFFSET); 114 115 /* Set GPBDAT register, set correspond GPIO port power level as high level or low level */ 116 gpb_dat = s3c_gpio_read(GPBDAT_OFFSET); 117 //gpb_dat &= ~(0x1<<led[i]); /* This port set to low level, then turn LED on */ 118 gpb_dat |= (0x1<<led[i]); /* This port set to high level, then turn LED off */ 119 s3c_gpio_write(gpb_dat, GPBDAT_OFFSET); 120 } 121 122 return 0; 123 } 124 125 126 static void turn_led(int which, unsigned int cmd) //调用ioct控制单个led灯的亮灭 127 { 128 volatile unsigned long gpb_dat; 129 130 gpb_dat = s3c_gpio_read(GPBDAT_OFFSET); 131 132 if(LED_ON == cmd) 133 { 134 gpb_dat &= ~(0x1<<led[which]); /* Turn LED On */ 135 } 136 else if(LED_OFF == cmd) 137 { 138 gpb_dat |= (0x1<<led[which]); /* Turn LED off */ 139 } 140 141 s3c_gpio_write(gpb_dat, GPBDAT_OFFSET); 142 } 143 144 static void s3c_hw_term(void) //调用LED结束后释放内存所占用的资源及设置相应的GPIO引脚关闭LED 145 { 146 int i; 147 volatile unsigned long gpb_dat; 148 149 for(i=0; i<dev_count; i++) 150 { 151 gpb_dat = s3c_gpio_read(GPBDAT_OFFSET); 152 gpb_dat |= (0x1<<led[i]); /* Turn LED off */ 153 s3c_gpio_write(gpb_dat, GPBDAT_OFFSET); 154 } 155 156 release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); //释放指定的IO内存资源 157 iounmap(s3c_gpb_membase); //取消ioremap所做的映射 158 } 159 160 161 static int led_open(struct inode *inode, struct file *file) //驱动功能函数 162 { 163 int minor = iminor(inode); //通过索引节点获得次设备号 164 165 file->private_data = (void *)minor; //将次设备号保存到private_data中,方便在函数中调用 166 //private_data在系统调用期间保存各种状态信息 167 printk(KERN_DEBUG "/dev/led%d opened.\n", minor); //打印成功信息 168 return 0; 169 } 170 171 static int led_release(struct inode *inode, struct file *file) //关闭led函数 172 { 173 printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode)); 174 175 return 0; 176 } 177 178 static void print_help(void) 179 { 180 printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME); 181 //printk("Enable Driver debug command: %u\n", SET_DRV_DEBUG); 182 printk("Turn LED on command : %u\n", LED_ON); 183 printk("Turn LED off command : %u\n", LED_OFF); 184 185 return; 186 } 187 188 static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) //led中断控制函数 189 { 190 int which = (int)file->private_data; //接受次设备号 191 192 switch (cmd) //判断参数要实现的功能 193 { 194 case LED_ON: 195 196 turn_led(which, LED_ON); 197 break; 198 199 case LED_OFF: 200 turn_led(which, LED_OFF); 201 break; 202 203 default: //如果传进来的既不是ON也不是OFF则打印出错并打印HELP结构体 204 printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd); 205 print_help(); 206 break; 207 } 208 209 return 0; 210 } 211 212 213 //结构体file_operationgs用来存储驱动内核模块提供的对设备进行各种操作的函数的指针 214 static struct file_operations led_fops = 215 { 216 .owner = THIS_MODULE, //指向拥有该结构体模块的指针,避免正在操作时被卸载。 217 .open = led_open, //传递led_open函数打开设备 218 .release = led_release, //传递led_release函数关闭设备 219 .unlocked_ioctl = led_ioctl, //传递中断函数,不使用BLK的文件系统,将使用此种函数的指针代替ioctl 220 }; 221 222 static int __init s3c_led_init(void) //底层函数 223 { 224 int result; 225 dev_t devno; 226 227 if( 0 != s3c_hw_init() ) //初始化led硬件并判断是否初始化成功 228 { 229 printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n"); 230 return -ENODEV; 231 } 232 233 /* Alloc the device for driver */ 234 if (0 != dev_major) /* Static */ 235 { 236 devno = MKDEV(dev_major, 0); //通过主设备号与次设备号构建32位设备号 237 result = register_chrdev_region (devno, dev_count, DEV_NAME); //已知主设备号,静态获得设备号并注册 238 } 239 else 240 { 241 result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME); //设备号未知,动态分配设备号并注册 242 dev_major = MAJOR(devno); //根据设备号devno获得主设备号 243 } 244 245 /* Alloc for device major failure */ 246 if (result < 0) 247 { 248 printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major); //设备号分配失败打印错误 249 return -ENODEV; 250 } 251 printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major); //设备号分配成功打印主设备号 252 253 if(NULL == (led_cdev=cdev_alloc()) ) //创建一个cdev结构体 254 { 255 printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME); 256 unregister_chrdev_region(devno, dev_count); 257 return -ENOMEM; 258 } 259 //cdev_alloc主要完成空间的申请和简单的初始化操作,分配一个cdev如果失败则打印错误信息并释放分配的设备号。 260 261 led_cdev->owner = THIS_MODULE; //指明设备所属模块,这里永远指向THIS_MODULE 262 //初始化cdev的file_operations,使字符设备与操作的函数绑定,led_fops结构体中的成员指向驱动提供的功能函数 263 cdev_init(led_cdev, &led_fops); 264 265 //cdev_add为注册设备函数,通常发生在驱动模块的加载函数中并将返回值传递给result,为0则表示成功。 266 result = cdev_add(led_cdev, devno, dev_count); 267 if (0 != result) 268 { 269 printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result); 270 goto ERROR; //注册失败打印错误并跳转到出错处理 271 } 272 273 274 printk(KERN_ERR "S3C %s driver[major=%d] version %d.%d.%d installed successfully!\n", 275 DEV_NAME, dev_major, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER); //注册成功则打印设备信息 276 return 0; 277 278 279 ERROR: 280 printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME); 281 cdev_del(led_cdev); //注销设备,通常发生在驱动模块的卸载函数中 282 unregister_chrdev_region(devno, dev_count); //释放注册的设备号 283 return result; 284 } 285 286 static void __exit s3c_led_exit(void) //卸载驱动模块函数 287 { 288 dev_t devno = MKDEV(dev_major, dev_minor); 289 290 s3c_hw_term(); 291 292 cdev_del(led_cdev); 293 unregister_chrdev_region(devno, dev_count); 294 295 printk(KERN_ERR "S3C %s driver version %d.%d.%d removed!\n", 296 DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER,DRV_REVER_VER); 297 298 return ; 299 } 300 301 302 303 /* These two functions defined in <linux/init.h> */ 304 module_init(s3c_led_init); //insmod加载内核模块定义的宏 305 module_exit(s3c_led_exit); //rmmod卸载模块定义的宏,退出内核模块 306 307 module_param(debug, int, S_IRUGO); 308 module_param(dev_major, int, S_IRUGO); 309 310 MODULE_AUTHOR(DRV_AUTHOR); 311 MODULE_DESCRIPTION(DRV_DESC); 312 MODULE_LICENSE("GPL");
在此驱动中,第一步装载驱动调用module_init函数 -> 调用s3c_led_init函数 -> 调用s3c_hw_init函数初始化硬件;第二步s3c_led_init函数 -> 调用MKDEV或者alloc_chrdev_region函数分配主次设备号;第三步调用file_operations结构体定义led_fops结构体;第四步s3c_led_init函数 -> 调用cdev_alloc函数创建一个cdev的结构体 -> 调用cdev_init函数将字符设备与led_fops函数绑定;第五步调用cdev_add函数注册cdev字符设备。
驱动程序写好了那么fl2440开发板上已经有了led的驱动,这个时候需要一个应用程序来控制LED的亮灭:
1 #include <stdio.h> 2 #include <sys/ioctl.h> 3 #include <sys/stat.h> 4 #include <unistd.h> 5 #include <stdarg.h> 6 #include <errno.h> 7 #include <sys/types.h> 8 #include <fcntl.h> 9 #include <string.h> 10 11 #define LED_NUM 4 12 #define DEVNAME_LEN 10 13 14 #define PLATDRV_MAGIC 0x60 15 #define LED_OFF _IO (PLATDRV_MAGIC, 0x18) 16 #define LED_ON _IO (PLATDRV_MAGIC, 0x19) 17 18 int main(int argc, char **argv) 19 { 20 int fd[LED_NUM]; 21 int i, j; 22 char devname[DEVNAME_LEN] = {0}; 23 24 for(i = 0; i < LED_NUM; i++) 25 { 26 snprintf(devname, sizeof(devname), "/dev/led%i",i); 27 fd[i] = open(devname, O_RDWR,0755); 28 if(fd[i] < 0) 29 { 30 printf("Can not open %s: %s", devname, strerror(errno)); 31 for(j = 0; j < i; j++) 32 { 33 if(fd[j] > 0) 34 close(fd[j]); 35 } 36 return -1; 37 } 38 } 39 40 while(1) 41 { 42 for(i = 0; i < LED_NUM; i++) 43 { 44 ioctl(fd[i], LED_ON); 45 sleep(1); 46 ioctl(fd[i], LED_OFF); 47 } 48 } 49 50 for(i = 0; i < LED_NUM; i++) 51 { 52 close(fd[i]); 53 } 54 return 0; 55 }
因为这个应用程序是要在fl2440开发板上面跑的,所以Linux自带的gcc编译器编译出来的可执行文件是不行的,这个时候我们需要用交叉编译器来完成这个操作了:
[xiaohexiansheng@centos6 led_s3c2440]$ /opt/buildroot-2012.08/arm920t/usr/bin/arm-linux-gcc ledapp.c -o ledapp
[xiaohexiansheng@centos6 led_s3c2440]$ ls
ledapp ledapp.c Makefile s3c_led.c s3c_led.ko
将ledapp与s3c_led.ko用tftp拷贝到开发板上面,下面是在开发板上面的操作:
>: tftp -gr s3c_led.ko 192.168.1.38
s3c_led.ko 100% |*******************************| 6214 0:00:00 ETA
>: tftp -gr ledapp 192.168.1.38
ledapp 100% |*******************************| 6005 0:00:00 ETA
>: chmod 777 ledapp
>: insmod s3c_led.ko
S3C led driver[major=252] version 1.0.0 installed successfully! (这里我们可以获取到注册的主设备号是【252】)
>: Dec 31 17:01:19 root kern.debug kernel: S3C led driver use major 252
Dec 31 17:01:19 root kern.err kernel: S3C led driver[major=252] version 1.0.0 installed successfully
因为在驱动程序中没有自动的创建次设备节点,所以在这里需要手动创建次设备节点。
>: mknod /dev/led0 c 252 0
>: mknod /dev/led1 c 252 1
>: mknod /dev/led2 c 252 2
>: mknod /dev/led3 c 252 3
>: ./ledapp