1.单片机LED的驱动程序实例:
#include"stm32f10x.h" #define ON 1 //定义一个宏,为1,LED亮 #define OFF 0 //为0,LED灭 #define DELAY_TIME 0x3FFFFF enum //使用enmu枚举,快速定义LED { LED1 = 0, LED2, LED3, LED4, MAX_LED, }; typedef struct led_gpio_s{ int num; /* LED编号 */ GPIO_TypeDef *group; /* LED使用的GPIO在哪一组: GPIOB or GPIOD */ uint16_t pin; /* LED使用的GPIO组中的那一个pin: GPIO_Pin_x */ } led_gpio_t; led_gpio_t leds_gpio[MAX_LED] = { {LED1, GPIOB, GPIO_Pin_5}, /* LED1 用的GPB5 */ {LED2, GPIOD, GPIO_Pin_6}, /* LED2 用的GPD6 */ {LED3, GPIOD, GPIO_Pin_3}, /* LED3 用的GPD3 */ }; void init_led_gpio(void) { int i; GPIO_InitTypeDef GPIO_InitStructure; /* 使能PB和PD组 GPIO的时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOD , ENABLE); /*设置 PB5(LED1), PD6(LED2), PD3(LED3)为 GPIO 输出推免模式,口线翻转速度为50MHz */ for(i=0; i<MAX_LED; i++) { GPIO_InitStructure.GPIO_Pin = leds_gpio[i].pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(leds_gpio[i].group, &GPIO_InitStructure); } }
void turn_led(int which, int cmd) { if(which<0 || which> MAX_LED ) return; if(OFF == cmd) GPIO_ResetBits(leds_gpio[which].group, leds_gpio[which].pin); else GPIO_SetBits(leds_gpio[which].group, leds_gpio[which].pin); }
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--) ;
}
int main(void) { SystemInit(); /* 初始化系统时钟 */init_led_gpio(); /* 初始化各个LED的GPIO管脚 */ while(1) { turn_led(LED1, ON); turn_led(LED2, OFF); turn_led(LED3, OFF); Delay(DELAY_TIME); turn_led(LED2, ON); turn_led(LED1, OFF); turn_led(LED3, OFF); Delay(DELAY_TIME); turn_led(LED3, ON); turn_led(LED2, OFF); turn_led(LED1, OFF); Delay(DELAY_TIME); } }
Linux驱动简介:
在单片机中,硬件的设备驱动是我们自己编写,然后在代码中调用,没有统一的规范。在linux中,有严格的规范,哪些该驱动做,哪些该应用程序做;驱动程序编写要先做什么、然后再做什么都有严格的定义。 也因此将嵌入式linux的开发分为两大类。一个是底层驱动开发的BSP(Board Support Packet)开发, 另外就是应用程序(Application)开发。
对于BSP开发的岗位,相应的开发人员应该了解各种硬件知识,如电路基础、各种接口技术和硬件调试工具的使用(如万用表、示波器、甚至逻辑分析仪等),此外还需要计算机组成、操作系统原理等理论基础,同时还要了解各种体系架构的CPU、汇编语言等,当然最重要的是C语言编程能力和数据结构的知识以及对Linux内核源码的大量阅读和分析。
对于嵌入式应用程序开发的岗位,我们不需要了解底层驱动的具体实现细节,而只需要怎么使用他们即可,因为Linux是一个模块化、严格分层的系统,所以应用程序人员只需要了解Linux的驱动调用的统一API(Application Program Interface,应用程序编程接口)即可 。
Linux内核设计的哲学是把所有的东西都抽象成文件进行访问,这样对设备的访问都是通过文件I/O来进行操作。Linux内核将设备按照访问特性分为三类:字符设备、块设备、网络设备:
字符设备 :
一个字符设备是一种可以当作一个字节流来存取的设备( 如同一个文件 ),对于这些设备他一次I/O只访问一个字节。 一个字符驱动负责实现这种行为,这样的驱动常常至少实现 open,close, read, 和 write 系统调用. 文本控制台( /dev/console )和串口( /dev/ttyS0 )是字符设备的例子, 因为它们很好地展现了流的抽象. 字符设备通过文件系统设备结点来存取。Linux内核中,绝大部分设备都是字符设备。
块设备:
我们常见的块设备有磁盘、固态硬盘、光盘、U盘、SD卡、Nandflash、Norflash等。一个块设备一次I/O通常访问一个块的大小数据,这个大小通常是2的幂次方字节( 如磁盘512字节,Nandflash使用2048个字节)。对于块设备的使用,需要分区格式化后建立文件系统,之后在应用程序空间中使用mount命令挂载起来之后使用。
网络设备:
网络设备包括有线网卡(eth0)、无线网卡(wlan0)、回环设备(lo0)、拨号网络设备(ppp0)等,他们在Linux内核里由网络协议栈实现,在/dev路径下并没有相应的设备节点,通过ifconfig -a命令可以查看所有的网络设备。在应用程序编程时,所有对网络设备的访问都是通过socket()来访问的。
字符设备驱动:
字符设备通过文件系统中的设备名来存取,惯例上它们位于 /dev 目录. 字符驱动的特殊文件由使用 ls -l 的输出的第一列的"c"标识. 块设备也出现在 /dev 中, 但是它们由"b"标识。如果你发出 ls -l 命令, 你会看到在设备文件项中有 2 个数(由一个逗号分隔)在最后修改日期前面, 这里通常是文件长度出现的地方. 这些数字是给特殊设备的主次设备编号。其中逗号前面的为主设备号,逗号后面的为次设备号。其中主设备号表示一类设备,而次设备号表示它是这类设备的第几个设备。现代 Linux 内核允许多个驱动共享主编号, 但是你看到的大部分设备仍然按照一个主编号一个驱动的原则来组织。
在linux内核中使用dev_t类型(在<linux/types.h>中定义)来定义设备编号。对于 2.6.0 内核, dev_t 是 32 位的量, 其中12 位用作主编号, 20 位用作次编号. 在编码时,我们不应该管哪些位是主设备号,哪些位是次设备号。而是应当利用在 <linux/kdev_t.h>中的一套宏定义来获取一个 dev_t 的主、次编号:
MAJOR(dev_t dev); MINOR(dev_t dev);
相反,如果有主次编号需要将其转换为一个dev_t,则使用MKDEV宏:
MKDEV(int major, int minor)
获取主次编号:
在建立一个字符驱动时你的驱动需要做的第一件事是获取一个或多个设备编号来使用,在Linux内核里,一些主设备编号是静态分派给最普通的设备的,这些设备列表在内核源码树的Documentation/devices.txt 中列出. 因此,获取主设备编号可以是我们自己简单的找一个看来没有用的主设备号,或者让内核以动态的方式分配一个主设备号。一般我们都会选择让内核动态分配,因为自己分配的内核可能与未来的新驱动产生冲突。所以,还是最好使用动态分配,来获取主设备编号,而不是随机的。
静态设定主次设备号:
静态设备指定主次设备号是指我们根据当前系统的主设备号分配情况,自己选择一个主设备号。 Linux系统中
正在使用的主设备号会保存在 /proc/devices 文件中,从这里可以查看系统正在使用的主设备号。如253---led来使用。
dev t devn0; int result; int major=251; devno = MDEV(major,0); result = register_chrdev_region(devon, 4, "led"); if(result < 0) { printk(KERN ERR "LED driver can`t use major %d\n, major); return -ENODEV; }
这里register_chrdev_region()函数的原型为:
register_chrdev_region(dev_t first, unsigned int count, char *name);
first 是你要分配的起始设备编号. first 的次编号部分通常是从0开始,但不是强制的. count 是你请求的连续设备编号的总数. 注意, 如果 count 太大, 你要求的范围可能溢出到下一个次编号;但是只要你要求的编号范围可用, 一切都仍然会正确工作. 最后, name 是应当连接到这个编号范围的设备的名子; 它会出现在 /proc/devices 和 sysfs 中。如同大部分内核函数, 如果分配成功进行, register_chrdev_region 的返回值是 0. 出错的情况下, 返回一个负的错误码, 你不能存取请求的区域。当驱动的主、次设备号申请成功后,/proc/devices里将会出现该设备,但是/dev 路径下并不会创建该设备文件。 如果我们需要创建该文件,则需要使用mknod命令创建。当然我们也可以在驱动里调用相应的函数,来通知应用程序空间自动创建该设备文件。
动态申请主次设备号:
int result; int dev major; dev_t devn; result = alloc_chrdev_region(&devno, 0, 4, "led"); dev_major = MAJOR(devon); /*Alloc for device major failure*/ if(result < 0) { return -ENODEV; } print(KERN_ERR "led driver choose device major number: %d\n",dev_major);
alloc_chrdev_region函数的原型如下
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
dev 是一个只输出的参数, 它在函数成功完成时持有你的分配范围的第一个数. fisetminor 应当是请求的第一个要用的次编号; 它常常是0. count 和 name 参数如同给 request_chrdev_region 的一样。
因为是动态分配主设备号,所以只有查看/proc/devices文件才知道它的值。
释放主设备号:
在linux内核中,主次设备号是一种资源,在不用的时候释放它们。设备编号的释放使用下面函数:
void unregister_chrdev_region(dev_t, unsigned int count);
字符驱动重要设备:
注册设备编号仅仅是驱动代码必须进行的诸多任务中的第一个,在编写字符驱动的过程中,我们将涉及到 3 个重要的内核数据结构, 称为 file_operations, file 和 inode. 我们需要对这些结构有一些基本了解才能够了解整个Linux内核设备驱动的工作机制。
file_operation结构
file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用,相应的系统调用将读取file_operation中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。 在系统内部,I/O设备的存取操作通过特定的入口点来进行,而这组特定的入口点恰恰是由设备驱动程序提供的。通常这组设备驱动程
序接口是由结构file_operations结构体向系统说明的,它定义在include/linux/fs.h中。传统上,一个 file_operation 结构或者其一个指针称为 fops( 或者它的一些变体). 结构中的每个成员必须指向驱动中的函数, 这些函数实现一个特别的操作, 或者对于不支持的操作留置为 NULL. 当指定为 NULL 指针时内核的确切的行为,每个函数是不同的 。
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
inode结构
当我们在Linux中创建一个文件时,就会在相应的文件系统中创建一个inode与之对应。创建好一个inode会存在存储器中。第一次open就会将inode在内存中有一个备份,多次打开同一文件不会产生多个inode。同理,当我们使用mknode(或其他方法)创建一个设备时,也会在文件系统中创建一个inode来存储这个文件的静态信息(不变的),包括它对应的设备号,文件路径以及对应的驱动的对象等。
不同的对象inode被填充的成员也不同。例如创建字符设备,add_chrdev_region其实是把一个驱动对象和一个(或一组)设备号联系到一起。而创建设备文件,是把文件和设备号联系到一起,到这里,驱动对象,设备号,文件就被绑定在一起了。内核就可以创建一个struct inode实例了。
inode一样定义在include/linux/fs.h文件中
struct inode { /* RCU path lookup touches following: */ umode_t i_mode; uid_t i_uid; gid_t i_gid; const struct inode_operations *i_op; struct super_block *i_sb; spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */ unsigned int i_flags; unsigned long i_state; #ifdef CONFIG_SECURITY void *i_security; #endif struct mutex i_mutex; unsigned long dirtied_when; /* jiffies of first dirtying */ struct hlist_node i_hash; struct list_head i_wb_list; /* backing dev IO list */ struct list_head i_lru; /* inode LRU list */ struct list_head i_sb_list; union { struct list_head i_dentry; struct rcu_head i_rcu; }; unsigned long i_ino; atomic_t i_count; unsigned int i_nlink; dev_t i_rdev; unsigned int i_blkbits; u64 i_version; loff_t i_size; #ifdef __NEED_I_SIZE_ORDERED seqcount_t i_size_seqcount; #endif struct timespec i_atime; struct timespec i_mtime; struct timespec i_ctime; blkcnt_t i_blocks; unsigned short i_bytes; struct rw_semaphore i_alloc_sem; const struct file_operations *i_fop; /* former ->i_op->default_file_ops */ struct file_lock *i_flock; struct address_space *i_mapping; struct address_space i_data; #ifdef CONFIG_QUOTA struct dquot *i_dquot[MAXQUOTAS]; #endif struct list_head i_devices; union { struct pipe_inode_info *i_pipe; struct block_device *i_bdev; struct cdev *i_cdev; }; __u32 i_generation; #ifdef CONFIG_FSNOTIFY __u32 i_fsnotify_mask; /* all events this inode cares about */ struct hlist_head i_fsnotify_marks; #endif #ifdef CONFIG_IMA atomic_t i_readcount; /* struct files open RO */ #endif atomic_t i_writecount; #ifdef CONFIG_FS_POSIX_ACL struct posix_acl *i_acl; struct posix_acl *i_default_acl; #endif void *i_private; /* fs or device private pointer */ };
file结构
Linux内核会为每一个进程维护一个文件描述符,这个就是struct file的索引。open()的过程其实就是根据传入的路径填充好一个file结构并将其赋值到数组中并返回其索引。file一样定义在include/linux/fs.h文件中:
struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op;
spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
#ifdef CONFIG_SMP
int f_sb_list_cpu;
#endif
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
字符设备注册
cdev结构体
内核内部使用类型struct cdev的结构来代表字符设备。在内核调用设备操作前,必须分配一个这样的结构体并注册给内核。在结构体里有对于这个设备进行操作的函数,具体定义在file_operation结构体中。该结构体定义在include/linux/cdev.h文件中
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
在linux内核中,我们可以使用两种方法获取该结构体。
一是直接通过变量定义来获取它:
struct cdev led_cdev;
另外一种是通过cdev_alloc()来动态分配
if(NULL == (led_cdev=cdev_alloc()) ) { printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME); unregister_chrdev_region(devno, dev_count); return -ENOMEM; }
注册cdev到内核:
在分配到cdev结构体后,下一步初始化,它将对该设备驱动所支持的系统调用函数存放的fie_operation结构体添加进来,然后通过cdev_add函数将它们注册给linux内核,这样就完成了整个Linux字符设备的注册过程。其中cdev_add的函数原型如下,其中dev 是 cdev 结构,num 是这个设备响应的第一个设备号, count 是应当关联到设备的设备号的数目.int cdev_add(struct cdev *dev, dev_t num, unsigned int count);下面是字符设备驱动cdev的分配和注册的代码;
static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, unlocked_ioctl = led_ioctl, }; struct cdev *led_cdev; if(NULL == (led_cdev=cdev_alloc()) ) { printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME); unregister_chrdev_region(devno, dev_count); return -ENOMEM; } led_cdev->owner = THIS_MODULE; cdev_init(led_cdev, &led_fops); result = cdev_add(led_cdev, devno, dev_count); if (0 != result) { printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result); goto ERROR; }
linux内核和系统调用之间的联系
LED驱动源码和Makefile
#include <linux/module.h> /* Every Linux kernel module must include this head */ #include <linux/init.h> /* Every Linux kernel module must include this head */ #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* struct fops */ #include <linux/errno.h> /* error codes */ #include <linux/cdev.h> /* cdev_alloc() */ #include <asm/io.h> /* ioremap() */ #include <linux/ioport.h> /* request_mem_region() */ #include <asm/ioctl.h> /* Linux kernel space head file for macro _IO() to generate ioctl command */ #include <linux/printk.h> /* Define log level KERN_DEBUG */ #define DEV_NAME "led" #define LED_NUM 4 /* Set the LED dev major number */ //#define LED_MAJOR 79 #ifndef LED_MAJOR #define LED_MAJOR 0 #endif #define DISABLE 0 #define ENABLE 1 #define GPIO_INPUT 0x00 #define GPIO_OUTPUT 0x01 #define PLATDRV_MAGIC 0x60 #define LED_OFF _IO (PLATDRV_MAGIC, 0x18) #define LED_ON _IO (PLATDRV_MAGIC, 0x19) #define S3C_GPB_BASE 0x56000010 #define S3C_GPB_LEN 0x10 /* 0x56000010~0x56000020 */ #define GPBCON_OFFSET 0 #define GPBDAT_OFFSET 4 #define GPBUP_OFFSET 8 static void __iomem *gpbbase = NULL; #define read_reg32(addr) *(volatile unsigned int *)(addr) #define write_reg32(addr, val) *(volatile unsigned int *)(addr) = (val) int led[LED_NUM] = {5,6,8,10}; /* Four LEDs use GPB5,GPB6,GPB8,GPB10 */ int dev_count = ARRAY_SIZE(led); int dev_major = LED_MAJOR; int dev_minor = 0; int debug = DISABLE; static struct cdev *led_cdev; static int s3c_hw_init(void) { int i; unsigned int regval; if(!request_mem_region(S3C_GPB_BASE, S3C_GPB_LEN, "s3c2440 led")) { printk(KERN_ERR "request_mem_region failure!\n"); return -EBUSY; } if( !(gpbbase=(unsigned int *)ioremap(S3C_GPB_BASE, S3C_GPB_LEN)) ) { release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); printk(KERN_ERR "release_mem_region failure!\n"); return -ENOMEM; } for(i=0; i<dev_count; i++) { /* Set GPBCON register, set correspond GPIO port as input or output mode */ regval = read_reg32(gpbbase+GPBCON_OFFSET); regval &= ~(0x3<<(2*led[i])); /* Clear the currespond LED GPIO configure register */ regval |= GPIO_OUTPUT<<(2*led[i]); /* Set the currespond LED GPIO as output mode */ write_reg32(gpbbase+GPBCON_OFFSET, regval); /* Set GPBUP register, set correspond GPIO port pull up resister as enable or disable */ regval = read_reg32(gpbbase+GPBUP_OFFSET); regval |= (0x1<<led[i]); /* Disable pull up resister */ write_reg32(gpbbase+GPBUP_OFFSET, regval); /* Set GPBDAT register, set correspond GPIO port power level as high level or low level */ regval = read_reg32(gpbbase+GPBDAT_OFFSET); regval |= (0x1<<led[i]); /* This port set to high level, then turn LED off */ write_reg32(gpbbase+GPBDAT_OFFSET, regval); } return 0; } static void turn_led(int which, unsigned int cmd) { unsigned int regval; regval = read_reg32(gpbbase+GPBDAT_OFFSET); if(LED_ON == cmd) { regval &= ~(0x1<<led[which]); /* Turn LED On */ }e lse if(LED_OFF == cmd) { regval |= (0x1<<led[which]); /* Turn LED off */ } w rite_reg32(gpbbase+GPBDAT_OFFSET, regval); }s tatic void s3c_hw_term(void) { int i; unsigned int regval; for(i=0; i<dev_count; i++) { regval = read_reg32(gpbbase+GPBDAT_OFFSET); regval |= (0x1<<led[i]); /* Turn LED off */ write_reg32(gpbbase+GPBDAT_OFFSET, regval); } release_mem_region(S3C_GPB_BASE, S3C_GPB_LEN); iounmap(gpbbase); } static int led_open(struct inode *inode, struct file *file) { int minor = iminor(inode); file->private_data = (void *)minor; printk(KERN_DEBUG "/dev/led%d opened.\n", minor); return 0; } static int led_release(struct inode *inode, struct file *file) { printk(KERN_DEBUG "/dev/led%d closed.\n", iminor(inode)); return 0; } static void print_help(void) { printk("Follow is the ioctl() commands for %s driver:\n", DEV_NAME); printk("Turn LED on command : %u\n", LED_ON); printk("Turn LED off command : %u\n", LED_OFF); return; } static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int which = (int)file->private_data; switch (cmd) { case LED_ON: turn_led(which, LED_ON); break; case LED_OFF: turn_led(which, LED_OFF); break; default: printk(KERN_ERR "%s driver don't support ioctl command=%d\n", DEV_NAME, cmd); print_help(); break; } return 0; } static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .unlocked_ioctl = led_ioctl, }; static int __init s3c_led_init(void) { int result; dev_t devno; // 字符设备驱动注册流程第一步: 相应的设备硬件初始化 if( 0 != s3c_hw_init() ) { printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n"); return -ENODEV; } /*字符设备驱动注册流程第二步: 分配主次设备号,这里既支持静态指定,也支持动态申请*/ /* Alloc the device for driver */ if (0 != dev_major) /* Static */ { devno = MKDEV(dev_major, 0); result = register_chrdev_region (devno, dev_count, DEV_NAME); } else { result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME); dev_major = MAJOR(devno); } /* Alloc for device major failure */ if (result < 0) { printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major); return -ENODEV; printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major); // 字符设备驱动注册流程第三步:分配cdev结构体,我们这里使用动态申请的方式 if(NULL == (led_cdev=cdev_alloc()) ) { printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME); unregister_chrdev_region(devno, dev_count); return -ENOMEM; } / / 字符设备驱动注册流程第四步: 绑定主次设备号、fops到cdev结构体中,并注册给Linux内 核 led_cdev->owner = THIS_MODULE; cdev_init(led_cdev, &led_fops); result = cdev_add(led_cdev, devno, dev_count); if (0 != result) { printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result); goto ERROR; } printk(KERN_ERR "S3C %s driver[major=%d] version 1.0.0 installed successfully!\n", DEV_NAME, dev_major); return 0; ERROR: printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME); cdev_del(led_cdev); unregister_chrdev_region(devno, dev_count); return result; } static void __exit s3c_led_exit(void) { dev_t devno = MKDEV(dev_major, dev_minor); s3c_hw_term(); cdev_del(led_cdev); unregister_chrdev_region(devno, dev_count); printk(KERN_ERR "S3C %s driver version 1.0.0 removed!\n", DEV_NAME); return ; } /* These two functions defined in <linux/init.h> */ module_init(s3c_led_init); module_exit(s3c_led_exit); // Linux内核安装驱动时支持模块参数,在这里debug和dev_major可以在insmod时通过参数传 递传进来,如:insmod s3c_led.ko debug=1 dev_major=218 module_param(debug, int, S_IRUGO); module_param(dev_major, int, S_IRUGO); MODULE_AUTHOR("guowenxue@LingYun IoT Studio"); MODULE_DESCRIPTION("FL2440 LED linux lowlevel API driver"); MODULE_LICENSE("GPL");
Makefile
LINUX_SRC = ${shell pwd}/../linux/linux-3.0/ 在编译的文件下用ls命令找内核的文件路径 CROSS_COMPILE=/opt/xtools/arm920t/bin/arm-linuxINST_PATH=/tftp //自己交叉编译器的路径 PWD := $(shell pwd) EXTRA_CFLAGS+=-DMODULE obj-m += kernel_hello.o obj-m += s3c_led.o modules: @echo ${LINUX_SRC} @make -C $(LINUX_SRC) M=$(PWD) modules @make clear uninstall: rm -f ${INST_PATH}/*.ko install: uninstall cp -af *.ko ${INST_PATH} clear: @rm -f *.o *.cmd *.mod.c //删除一些编译后产生的不需要的文件 @rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f @rm -f .*ko.cmd .*.o.cmd .*.o.d clean: clear @rm -f *.k
在linux下编译产生开发板可以编译的.ko文件。用tftp下载开发板下,注意这里的ip是自己电脑有线网卡的ip地址,
是从电脑上下载到开发板上。
ARM板上下载并安装驱动
uname -a
下载驱动文件
tftp下载
安装驱动
insmod s3c_led.ko
因为是动态分配设备号,所以要查看自己led的设备号
cat /proc/devices | grep led
选择设备
mknod -m 755 /dev/led0 c 251 0
。
。
。
mknod -m 755 /dev/led3 c 251 3
编写LED测试程序
代码入下
#include <stdio.h> #include <stdarg.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <unistd.h> #include <sys/select.h> #define LED_CNT 4 #define DEVNAME_LEN 10 #define PLATDRV_MAGIC 0x60 #define LED_OFF _IO (PLATDRV_MAGIC, 0x18) #define LED_ON _IO (PLATDRV_MAGIC, 0x19) static inline msleep(unsigned long ms) { struct timeval tv; tv.tv_sec = ms/1000; tv.tv_usec = (ms%1000)*1000; select(0, NULL, NULL, NULL, &tv); } int main (int argc, char **argv) { int i; int fd[LED_CNT]; char dev_name[DEVNAME_LEN]={0,0,0,0}; for(i=0; i<LED_CNT; i++) { snprintf(dev_name, sizeof(dev_name), "/dev/led%d", i); fd[i] = open(dev_name, O_RDWR, 0755); if(fd[i] < 0) goto err; } while(1) { for(i=0; i<LED_CNT; i++) { ioctl(fd[i], LED_ON); msleep(300); ioctl(fd[i], LED_OFF); msleep(300); } } for(i=0; i<LED_CNT; i++) { close(fd[i]); } return 0; err: for(i=0; i<LED_CNT; i++) { if(fd[i] >= 0) { close(fd[i]); } } return -1; }
编写Makefile
PWD=$(shell pwd) INSTPATH=/tftp CROSS_COMPILE=/opt/xtools/arm920t/bin/arm-linuxexport CC=${CROSS_COMPILE}gcc CFLAGS+=-I`dirname ${PWD}` SRCFILES = $(wildcard *.c) BINARIES=$(SRCFILES:%.c=%) all: binaries install binaries: ${BINARIES} @echo " Compile over" %: %.c $(CC) -o $@ $< $(CFLAGS) install: cp $(BINARIES) ${INSTPATH} clean: @rm -f version.h @rm -f *.o $(BINARIES) @rm -rf *.gdb *.a *.so *.elf* distclean: clean @rm -f tags cscope* .PHONY: cleantftp下载编写好的程序,赋予执行权限,然后执行便可以观察到现象。