ARM-uClinux下编写加载驱动程序详细过程本文主要介绍在uClinux下,通过加载模块的方式调试IO控制蜂鸣器的驱动程序。
实验过程与上篇文章所讲的过程基本相似,更多注重细节及注意事项。
本文适合学习ARM—Linux的初学者。
//==================================================================硬件平台:MagicARM2200教学试验开发平台(LPC2290)Linux version 2.4.24,gcc version 2.95.3电路连接:P0.7——蜂鸣器,低电平发声。
实验条件:uClinux内核已经下载到开发板上,能够正常运行;与宿主机相连的网络、串口连接正常。
//==================================================================编写蜂鸣器的驱动程序相对来说容易实现,不需要处理中断等繁琐的过程,本文以蜂鸣器的驱动程序为例,详细说明模块化驱动程序设计的主要过程和注意事项。
一、编写驱动程序驱动程序的编写与上文所说的编写过程基本相同,这里再详细说明一下。
//==========================================//蜂鸣器驱动程序:beep.c文件//-------------------------------------------------------------------#include <linux/module.h> /*模块相关*/#include <linux/kernel.h> /*内核相关*/#include <linux/types.h> /*linux定义类型*/#include <linux/fs.h> /*文件系统 file_opertions 结构体定义*/#include <linux/errno.h> /*出错信息*//*PINSEL0 注意:低2位是UART0复用口,不要改动*/#define PINSEL0 (*((volatile unsigned*) 0xE002C000))/*P0口控制寄存器*/#define IO0PIN (*((volatile unsigned*) 0xE0028000))#define IO0SET (*((volatile unsigned*) 0xE0028004))#define IO0DIR (*((volatile unsigned*) 0xE0028008))#define IO0CLR (*((volatile unsigned*) 0xE002800C))#define MAJOR_NUMBER 254 /*自定义的主设备号*/#define BEEP_CMD 0 /*自定义的控制命令*//*函数声明*/static int beep_open(struct inode *inode, struct file *file);static int beep_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);static int beep_release(struct inode *inode, struct file *file);static int beep_init(void);static void beep_cleanup(void);/********************************************************/volatile static int beep_major = MAJOR_NUMBER; /*全局变量:主设备号自定义为254*//********************************************************//*注册函数:用到file_operations结构体。
将蜂鸣器结构体自命名为 beep_test ,在注册模块时要用到*/static struct file_operations beep_test ={owner : THIS_MODULE,ioctl : beep_ioctl,open : beep_open,release : beep_release,}; /*注意:此处的分号(;)不要丢掉*//*********************************************************/#define BEEPCON 0x00000080static void beep_port_init(void) //蜂鸣器端口初始化:设置P0.7口为输出,初始值为高(蜂鸣器不发声){IO0DIR = BEEPCON;IO0SET = BEEPCON;}static void beep(int beep_status) //蜂鸣器操作:根据参数(beep_status)状态判断是否发声{if(beep_status == 0)IO0CLR = BEEPCON;elseIO0SET = BEEPCON;}static int beep_open(struct inode *inode, struct file *file) //beep_test结构体中的open()函数实体,以下同{MOD_INC_USE_COUNT; //注册模块数加1beep_port_init();return 0;}static int beep_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){if(cmd == 0){printk("beep on!\n");beep(0);}else{printk("beep off!\n");beep(1);}return 0;}static int beep_release(struct inode *inode, struct file *file){MOD_DEC_USE_COUNT; //模块数减1return 0;}static int beep_init(void) //模块加载、初始化函数:将模块加载到内核运行{int result;result = register_chrdev(beep_major, "named_beep", &beep_test);if(result < 0){printk(KERN_INFO"beep: can't get major number\n");return result;}if(beep_major == 0) beep_major = result;printk(KERN_INFO"beep: init OK!\n");/*注意:驱动程序运行在内核空间,从内核打印信息要用printk()函数而不是printf()函数,而且要配有优先级*/return 0;}static void beep_cleanup(void) //模块卸载函数:将模块从内核卸载出去{unregister_chrdev(beep_major, "named_beep");}/************************************************************//*以下部分是驱动程序的关键,后面做详细说明*///module_init(beep_init);//module_exit(beep_cleanup);int init_module(void) //加载模块{return beep_init();}void cleanup_module(void) //卸载模块{beep_cleanup();}//-------------------------------------------------------------------//驱动程序文件结束//==========================================以上是整个驱动程序文件的全部内容,将文件保存,这里将其命名为beep.c。
整个驱动程序很简单,只填写了几个操作函数 beep_open()、beep_release()和beep_ioctl()。
其实控制蜂鸣器用beep_ioctl()一个函数即可,其它函数基本都是空操作。
在驱动文件最后的两个函数对驱动程序来数是及其重要的。
应用程序与内核的区别就是应用程序从头到尾完成一个任务,而内核则为以后处理某些请求而注册自己,完成这个任务后,他的“主”函数就立即终止。
换句话说,init_module()函数(名称不能更改)是模块入口点,如同应用程序的main()函数一样,换句话说,模块入口点init_module()函数的任务就是为以后调用模块的函数做准备;cleanup_module()函数(名称不能更改)是模块的第二个入口点,此函数仅当模块被卸载前才被调用。
它的功能是去掉init_module()函数所作的事情。
这两个函数由<linus/modele.h>头文件声明,有关模块实现的源代码可以参见../kernel/module.c。
init_module()函数在模块被加载时执行,模块的初始化就是通过调用init_module()函数完成的。
它注册驱动设备,需调用register_chrdev()函数实现。
register_chrdev有3个参数:(1):希望获得的设备主号,即beep_major全局变量,如果是0,系统将选择一个没有被占用的设备号返回;(2):设备文件名,自定义设备文件名,这里用named_beep,它返回这个驱动程序所使用的主设备号;(3):用来登记驱动程序实际执行操作的函数指针,即beep_test结构体。
如果登记成功,register_chrdev返回设备的主设备号;否则返回一个负值。
模块是内核的一部分,但并未被编辑到内核中,他们被分别编译和连接成目标文件。
用命令insmod 插入一个模块到内核中,用命令rmmod卸载一个模块。
这两个命令分别调用init_module()函数和cleanup_module()函数。