驱动LED灯
首先加入头文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
还要定义几个类
static struct class *leds_class;
static struct class_device *leds_class_devs[4];
定义一个类,然后再在类下定义4个设备,下面会用到。
然后创建open函数myled_open
static int myled_open(struct inode *inode, struct file *file)
{
*gpfcon &= ~((0x3<<4*2)|(0x03<<5*2)|(0x3<<6*2));
*gpfcon |= (1<<4*2)|(1<<5*2)|(1<<6*2);
return 0;
}
当再测试文件中执行open(“/dev/xxx”,”x”);时,系统就会根据file_operations结构体运行myled_open函数。所以在这个函数中初始化led引脚为输出。该函数参数为 设备节点结构体指针,和文件流指针。
然后创建write函数myled_write
static int myled_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int minor=MINOR(file->f_dentry->d_inode->i_rdev);
char val;
copy_from_user(&val,buf,count);//copy variable from user to kernel
switch(minor){
case 0:
{
if(val==1)*gpfdat &=(0<<4|0<<5|0<<6);//点灯
else *gpfdat |=(1<<4|1<<5|1<<6);//关灯
break;
}
case 1:
{
*gpfdat |=(1<<4|1<<5|1<<6);//关灯
if(val==1)
*gpfdat &=(~(1<<4));//点灯
break;
}
case 2:
{
*gpfdat |=(1<<4|1<<5|1<<6);//关灯
if(val==1)
*gpfdat &=(~(1<<5));
break;
}
case 3:
{
*gpfdat |=(1<<4|1<<5|1<<6);//关灯
if(val==1)
*gpfdat &=(~(1<<6));
break;
}
}
return 0;
}
myled_write函数的参数为文件流指针、传递进来的数据指针、数据大小
MINOR(file->f_dentry->d_inode->i_rdev);是取文件的次设备号,copy_from_user(void *to, const void __user *from, unsigned long n)从用户空间拷贝数据到内核空间,失败返回没有被拷贝的字节数,成功返回0.
当测试程序执行write(fd,&val,1);时,系统就会调用该函数。fd为执行open函数时返回的唯一的文件文件描述符,
然后接下来就是填充file_operations结构体
static struct .file_operations myled_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = myled_open,
.write = myled_write,
};
系统启动后,会自动执行这个结构体,然后将创建的函数与底层open,write函数关联起来。
接下来就是注册函数
static int myled_init(void)//加载
{
major=register_chrdev(111,"myled",&myled_fops);//major为自己定义的全局整型变量
if (major < 0) {
printk("myled can‘t register major number\n");
return major;
}
myled_class=class_create(THIS_MODULE, "myled");
if (IS_ERR(myled_class))
return PTR_ERR(myled_class);
int i;
for(i=0;i<4;i++){
myled_class_devs[i]=class_device_create(myled_class, NULL, MKDEV(111, i), NULL, "led%d",i);//
if (unlikely(IS_ERR(myled_class_devs[i])))//先建立一个类 再建立一个设备,然后自动创建一个xyz设备节点
return PTR_ERR(myled_class_devs[i]);
}
gpfcon=(unsigned long *)ioremap(0x56000050,16);
gpfdat=gpfcon+1;
return 0;
}
major=register_chrdev(111,”myled”,&myled_fops);就是将file_operations创建的myled_fops结构题告诉内核,然后给该设备的主设备号赋值为111,设备名字为myled.
myled_class=class_create(THIS_MODULE, “myled”);
myled_class_devs[i]=class_device_create(myled_class, NULL, MKDEV(111, i), NULL, “led%d”,i);
就是给该设备创建一个类,然后再创建的类下,再创建四个设备节点。最终在系统中节点是/dev/led0 /dev/led1 /dev/led2 /dev/led3.应用层序中open函数中的参数就是这些节点。它们的主设备号相同,但是次设备号不同。都归属于myled这个节点。
gpfcon=(unsigned long *)ioremap(0x56000050,16);
gpfdat=gpfcon+1;
ioremap函数就是将一段连续的物理地址映射为虚拟地址。因为指针大小为4字节,所以加一就表示加了4字节。在注册函数中映射地址,那么就得在卸载函数中取消映射地址。
下面是卸载函数
static void myled_exit(void)
{
int i;
unregister_chrdev(111,"myled");//卸载;
for(i=0;i<4;i++){
class_device_unregister(myled_class_devs[i]);
}
class_destroy(myled_class);
iounmap(gpfcon);
}
unregister_chrdev(111,”myled”);是从内核中删除创建的myled_fops结构体,这里只需要主设备号和设备名。
class_device_unregister(myled_class_devs[i]);class_destroy(myled_class);与上面的创建类的函数相对应
iounmap(gpfcon);就是取消虚拟地址映射。
最后还要加上如下几行
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
module_init(myled_init);创建一个结构体,里面有myled_init函数的地址,当执行insmod myled.ko时,系统会自动找到这个结构体,然后找到里面的函数地址,去执行myled_init。
module_exit(myled_exit);创建一个结构体,里面有first_drv_init函数的地址,当执行rmmod myled.ko时,系统会自动找到这个结构体,然后找到里面的函数地址,去执行myled_exit
MODULE_LICENSE(“GPL”);是将钥匙设置为GPL.
下面是测试函数
加入头文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
void print_usage(char *file)
{
printf("Usage:\n");
printf("%s /dev/led0 <on|off>\n", file);
printf("%s /dev/led1 <on|off>\n", file);
printf("%s /dev/led2 <on|off>\n", file);
printf("%s /dev/led3 <on|off>\n", file);
}
int main(int argc,char *argv[])
{
int fd;
int val=1;
if(argc<3){
print_usage(argv[0]);
return 0;
}
fd=open(argv[1],O_RDWR);
if(fd<0){
printf("error,can‘t open %s\n",argv[1]);
return 0;
}
if(strcmp(argv[2],"on")==0)
val=1;
else if(strcmp(argv[2],"off")==0)
val=0;
else print_usage(argv[0]);
write(fd,&val,1);
return 0;
}
例如执行/myledtest /dev/led0 on
当执行 fd=open(argv[1],O_RDWR);函数时,就会运行/dev/led0对应open函数,返回的是每个次设备号不同的描述符,每个主设备里的次设备的描述符不一样。
然后下面执行write(fd,&val,1);就会运行/dev/led0对应的write函数。
执行/myledtest /dev/led1 on /myledtest /dev/led2 on时也会这样。
是因为在注册函数中将同一主设备号的write,read函数填充在myled_fops结构体中了。然后又将这个结构体和主设备名绑定在一起并告诉内核了,然后又利用主设备名创建一个类,类里又创建四个次设备。所以每次调用次设备时就会执行主设备对应的底层函数,然后在该函数中分别是哪一个次设备调用的。如int minor=MINOR(file->f_dentry->d_inode->i_rdev);就是返回调用该函数的次设备号。