ARM+Linux驱动----点亮开发板的LED 使用FS2440开发板2.6.4内核
1)关于fs_operations的问题fs_operations数据结构是有文件系统(虚拟文件系统VFS)提供的,其主要作用是向上(应用层)提供统一的系统调用接口,比如open(),read(),write(),ioctl()等文件(Linux把所有的设备也当作文件)操作,向下屏蔽各种不同平台的差异。fs_operations内部是一个指针实现,链接了向上的接口和向下的具体实现。设备驱动的层次在文件系统之下,就是这个道理。因为设备驱动文件包含了对与底层硬件具体实现,虚拟文件系统通过fs_operation与之链接。注意一点:字符设备是没有对应的文件系统的,所以字字符设备的fs_operations需要在驱动文件当中提供。2)关于主设备号和次设备号linux 2.6.4设备通过一个dev_t来定义自身的设备号。其实也就是一个unsigned int 类型的数据。在这个32位的数据里面,前12位用于保存主设备号,后10位用于保存次设备号。所有功能相同且使用同一个驱动程序的设备有相同的主设备号,然后需要用次设备好去具体区分。理论上,一个主设备可以带有1024个次设备。3)关于register_chrdev_region()和alloca_chrdev_region()函数的大致实现register_chrdev_region()和alloca_chrdev_region()函数的功能类似与向windows的注册表添加一个注册项。注册项是一个char_device_struct的结构体数组。register_chrdev_region()和alloca_chrdev_region()函数在运行过程中都会调用__register_chrdev_region().__register_chrdev_region()这个函数利用哈希列表分配主设备号。具体的算法是搜索char_device_struct的结构体散列桶,找到空位,就用空位的位号作为设备的主设备号。建议任何驱动都使用动态分配主设备号的原则。通过register_chrdev_region()和alloca_chrdev_region()最后获得了一个可以用的主设备号,然后要通过MKDEV(主,次)(次设备好自己手动指定),获得最后的设备号用cdev_add()向系统注册。4)对硬件的操作: 可以使用内核提供的接口 s3c2410_gpio_setpin(管脚号,管脚状态代号)设置管脚状态 (设置GPnDAT) , s3c2410_gpio_cfgpin(管脚号,管脚功能代号) 设置管脚功能(相当于设置GPnCON)源代码:include <linux/config.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/devfs_fs_kernel.h>#include <linux/miscdevice.h>#include <linux/delay.h>#include <asm/irq.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>#include"linux/module.h"#include"linux/types.h"#include"linux/fs.h"#include"linux/errno.h"#include"linux/mm.h"#include"linux/sched.h"#include"linux/init.h"#include"linux/cdev.h"#include"asm/io.h"#include"asm/uaccess.h"#include"asm/arch-s3c2410/regs-gpio.h" // S3C2410_GPF4 and S3C2410_GPF4_OUTP#include "asm/arch-s3c2410/hardware.h"#define DEVICE_NAME "leds" #define LIGHT_MAJOR 0 //i want a device number dynamaticallystruct light_dev{ //Make the device an object , now it declar a device class and you can think it making an abstractstruct cdev cdev; //Declar a char device abstractunsigned char value; //usr can read and write this car};struct light_dev *light_devp; //now programe make a half abstract for the divice,and in light_init(),when kmalloc() allocate a space,it can think manke a real_abstractiondev_t light_major = LIGHT_MAJOR;void light_GPIO_init(void){s3c2410_gpio_cfgpin( S3C2410_GPF4 , S3C2410_GPF4_OUTP ); s3c2410_gpio_cfgpin( S3C2410_GPF5 , S3C2410_GPF5_OUTP );s3c2410_gpio_cfgpin( S3C2410_GPF6 , S3C2410_GPF6_OUTP );s3c2410_gpio_cfgpin( S3C2410_GPF7 , S3C2410_GPF7_OUTP ); }void light_on(void){s3c2410_gpio_setpin(S3C2410_GPF4 , 0);s3c2410_gpio_setpin(S3C2410_GPF5 , 0);s3c2410_gpio_setpin(S3C2410_GPF6 , 0);s3c2410_gpio_setpin(S3C2410_GPF7 , 0);}void light_off(void){s3c2410_gpio_setpin(S3C2410_GPF4 , 1);s3c2410_gpio_setpin(S3C2410_GPF5 , 1);s3c2410_gpio_setpin(S3C2410_GPF6 , 1);s3c2410_gpio_setpin(S3C2410_GPF7 , 1);}int light_open(struct inode * inode , struct file * filp) {//when device driver open.fill the device structstruct light_dev * dev;dev = container_of(inode->i_cdev,struct light_dev,cdev ); //contariner_of is a macros//make the device struct be a private in device filefilp -> private_data = dev; //make the releationship between the file and device in Linux,because Linux regard the device as a file!~return 0;}ssize_t light_release(struct inode * inode, struct file * filp ){return 0;}ssize_t light_read(struct file * filp , char __user * buff , size_t count , loff_t * f_pos){struct light_dev * dev = filp->private_data;if(copy_to_user(buff , &(dev->value) , 1)){return - EFAULT;}else{return 1;}}ssize_t light_write(struct file * filp , const char __user * buff , size_t count , loff_t * f_pos){struct light_dev *dev = filp -> private_data;if(copy_from_user(&(dev->value) , buff ,1) ){return -EFAULT;}else {if(dev->value){light_on(); }else{light_off();}return 0;}}static int light_ioctl(struct inode * inode ,struct file * filp , unsigned int _cmd , unsigned long arg ){struct light_dev * dev = filp-> private_data;switch(_cmd){case 1:dev->value = 1; //just a flaglight_on();break;case 0:dev->value = 0; //just a flaglight_off();break;default: return -ENOTTY; //CMD CAN NOT SUPPORT}return 0;}static struct file_operations light_fops = {//fill the file_operations struct.owner = THIS_MODULE,.read = light_read,.write = light_write,.ioctl = light_ioctl,.open = light_open,.release = light_release,};static void light_setup_cdev(struct light_dev * dev , int index){dev_t devno = MKDEV(light_major , index); //make a device number and save it in a 32-intergredint err = 0;cdev_init(&dev->cdev , &light_fops ); //only fill the struct cdev dev->cdev.owner = THIS_MODULE;dev->cdev.ops = &light_fops; //? Reif(( err = cdev_add(&dev->cdev , devno , 1) ) == 1){ // some key !!!!!!!!!!!!!!!!printk(KERN_NOTICE " Error adding LED ");}}int light_init(void){int result = 0;dev_t devno = 0;devno = MKDEV(light_major,0);if(light_major){if(( result = register_chrdev_region(devno,1,"LED")) < 0){printk(KERN_NOTICE "add a register item failed");printk(DEVICE_NAME "Allocate the device nomber failed!~"); return result;}}else{if( ( result = alloc_chrdev_region(&devno,0,1,"LED") ) < 0 ){printk(KERN_NOTICE "add a register item failed");printk(DEVICE_NAME "Allocate the device number failed!~");return result;}light_major = MAJOR(devno); // fill the majot_device_number to var light_major}//allocate zhe spcae to device structif( ( light_devp = kmalloc(sizeof(struct light_dev),GFP_KERNEL)) <0 ){result = -ENOMEM;printk(KERN_NOTICE "kmalloc failed!~"); goto failmalloc;}//Init the memorymemset(light_devp,0,sizeof(struct light_dev));light_setup_cdev(light_devp,0);light_GPIO_init();s3c2410_gpio_setpin(S3C2410_GPF4 , 0);s3c2410_gpio_setpin(S3C2410_GPF5 , 1);s3c2410_gpio_setpin(S3C2410_GPF6 , 0);s3c2410_gpio_setpin(S3C2410_GPF7 , 1);//all successful~return 0;failmalloc: unregister_chrdev_region(devno,1);printk(DEVICE_NAME "unregister~");return result;}void light_cleanup(void){ cdev_del(&light_devp->cdev); //add the &light_devp->cdev to system as well,~Remember add the device point and del the device pointkfree(light_devp);unregister_chrdev_region(MKDEV(light_major,0),1); }module_init(light_init);module_exit(light_cleanup); MODULE_AUTHOR("Cz.");MODULE_LICENSE("Dual BSD/GPL");Makefile:#################################################obj-m := ToggleLed.oKERNELDIR := /usr/local/arm/3.4.1/arm-linux/yle2440_2.6.12default:$(MAKE) -C $(KERNELDIR) M=$(shell pwd) modulesinstall:insmod LED.kouninstall:rmmod LED.ko#################################################修改KERNELDIR的路径,主要是进入移植上的源代码路径,用内核编译工具进行编译然后上传Toggle.ko用insmod Toggle.ko 挂载,然后 cat /proc/devices 查看LED设备号然后创建特殊文件节点: mknod /dev/LED c 设备号 0 当设备挂载成功FS2440开发板上的灯量的状态是0b1010.然后编译用户空间测试代码: 功能控制灯的全亮(目的在于验证能正确控制I/O管脚)#include"stdio.h"#include"unistd.h"#include"fcntl.h"#include"sys/types.h"#include"sys/stat.h"unsigned char cmd = 0x01;int main(){int fd_led = 0;if((fd_led = open("/dev/LED",O_RDWR)) < 0 ){printf("fd_led = %d",fd_led);printf("open failed!~");exit(1);}if(( write(fd_led,&cmd,1)) != 1){printf("write failed!~");exit(1);}close(fd_led);return 0;}然后编译: arm-linux-gcc -o ControlLed.o ControlLed.c 然后用FTP上传至FS2440的开发板运行,得LED全亮。成功!~