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 dynamatically
struct light_dev{ //Make the device an object , now it declar a device class and you can think it making an abstract
struct cdev cdev; //Declar a char device abstract
unsigned 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_abstraction
dev_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 struct
struct 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 file
filp -> 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 flag
light_on();
break;
case 0:
dev->value = 0; //just a flag
light_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-intergred
int err = 0;
cdev_init(&dev->cdev , &light_fops ); //only fill the struct cdev
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &light_fops; //? Re
if(( 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 struct
if( ( light_devp = kmalloc(sizeof(struct light_dev),GFP_KERNEL)) <0 ){
result = -ENOMEM;
printk(KERN_NOTICE "kmalloc failed!~");
goto failmalloc;
}
//Init the memory
memset(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 point
kfree(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.o
KERNELDIR := /usr/local/arm/3.4.1/arm-linux/yle2440_2.6.12
default:
$(MAKE) -C $(KERNELDIR) M=$(shell pwd) modules
install:
insmod LED.ko
uninstall:
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全亮。成功!~