Linux设备驱动第四天(自动创建设备节点、LED驱动程序)

来源:互联网 发布:冯小刚 王思聪 知乎 编辑:程序博客网 时间:2024/05/24 02:33

回顾:
与驱动有关的几个重要结构体
1,struct cdev //从软件上代表硬件设备
{
dev_t dev;//设备号 = 主设备号+次设备号
struct file_operations f_ops;
}

2,struct file_operations{
open
read
write
release
ioctrl
mmap
}

3,struct file//对应内核里面的文件表中的一项
{
f_pos;//读写的偏移位置
f_op;//文件的操作函数集合
f_flags;//文件打时传递的参数 读写模式
private_data// 私有数据
…..
}
这里写图片描述

4,struct inode
{
dev_t i_rdev;//设备号
file_opeartions *i_fop;//操作函数集合,内核会给它赋值
struct cdev *i_cdev;//字符设备集合
….
}

以上两个结构体多用于一套驱动代码驱动多个同类型的设备。如下面的案例。

问题:找到串口驱动程序源码文件
KCofnfig:make menuconfig 时生成菜单选项
Makefile:和源码在同一级目录,决定编译动作
make menuconfig
-> Device Drivers
->Character devices
->Samsung S5PV210 Serail port support
宏: CONFIG_SERIAL_S5PV210
路径:devices/serail/Kconfig确定源码在drivers/serail/目录下
打开该路径下的Makefile查找 CONFIG_SERIAL_S5PV210,找到对应的.c文件

Uart案例:
创建设备节点
mknod /dev/serial0 c 204 64 ==> open(“/dev/serial0”); uart_open
mknod /dev/serial1 c 204 65 ==> open(“/dev/serial1”); uart_open
mknod /dev/serial2 c 204 66 ==> open(“/dev/serial2”); uart_open
mknod /dev/serial3 c 204 67 ==> open(“/dev/serial3”); uart_open

其中open(“/dev/serial0”)为应用程序中的代码;
在uart_open中如何确定初始化哪一个串口?
通过设备号来确定初始化哪一个串口。
如何拿到设备号?
在uart_open方法中可以通过传递进来的inode->i_rdev拿到设备号,通过设备号拿到次设备号

在urat_write中如何确定通过哪串口向外发送数据?

#include<linux/init.h>#include<linux/module.h>static int uart_open(struct inode *inode, struct file *file){   MINOR(inode -> i_rdev);//次设备号   //private_data这块空间留给驱动开发人员使用的,放什么数据都可以   file->private_data = MINOR(inode -> i_rdev);//记录要操作的设备   //初始化相应寄存器   return 0; }static int uart_close(struct inode *inode, struct file *file){   printlk("enter uart_close! \n");   return 0; }static ssize_t uart_write (struct file *file, const char __user *buf, size_t count, loff_t *offset){   return 0; }static ssize_t uart_read(struct file *file,char __user *buf, size_t count, loff_t *offset){   return 0; }static int uart_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val){    printlk("enter led_ioctl! \n");}//需要实现的函数static struct file_operations led_fops = {    .owner   = THIS_MOUDLE,    .open    = uart_open,    .release = uart_close,    .write   = uart_write,    .read    = uart_read,    .ioctl   = uart_ioctl,};static int major = 0;/*1,分配一个cdev */static struct cdev led_cdev;static int led_status;//记录灯的状态static int chardevice_init(void){     dev_t dev;     if(major != 0){//静态分配         dev = MKDEV(major,0);//构建一个设备号         //向内核注册设备号         register_chrdev_region(dev,1,"tarena");     }else{         //动态申请注册设备号          alloc_chrdev_region(&dev,1001,"test");         major = MAJOR(dev);//获取主设备号   相当于dev>>20         unsigned int minor = MINOR(dev);//获取次设备号  相当于把高20位清0         printk("major = %d ! \n",major);     }     //2, 初始化cdev      cdev_init(&led_cdev,&led_fops);     //3,把cdev添加到内核里面去     cdev_add(&led_cdev,dev,1);     //申请GPIO管脚     gpio_request(S5PV210_GPC1(3),"LED1");//从原图中找管脚名称GPC1_3     //配置为输出     gpio_dirction_output(S5PV210_GPC1(3),0);     led_status = 0;     return 0;}static void chardevice_exit(void){    dev_t dev;    //4,按照对内核产生的影响,逆序销毁cdev    cdev_del(&led_cdev);    dev = MKDEV(major,0);    //释放    unregist_chrdev_region(dev,1);}module_init(chardevice_init);module_exit(chardevice_exit);MODULE_LICENSE("GPL");

自动创建设备文件
手工创建设备文件:mknode /dev/xxx c major minor
自动创建:在安装模块时自动创建
编程的角度,需要调用以下两个函数:
class_create(….);//生成一个树枝
deivce_create(…)//生成一个果实

class_destroy(...);device_destroy(...);

其他要完成的工作
1,用busybox添加,要选择支持medv
2,rootfs/ect/rcS添加mount -a
3,rootfs/etc添加fstab文件
4, 在fstab中添加sysfs和proc支持
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
5,在rootfs/ect/rcS中添加
echo /sbin/mdev > / proc/sys/kernel/hotplub

当执行insmod xx.ko时,如果xx_init函数中包含了class_create(THIS_MODULE,”tanena”),生成文件夹:/sys/class/tarena;当执行device_create(cls,NULL,dev,NULL,”leds”),会生成文件夹:/sys/class/tarena/leds;以上操作内核也理解为热插拔事件,产生热插拔事件以后,内核会调用/proc/sys/kernel/hotplug,实则就是调用/sbin/mdev程序,该程序扫描/sys/目录下的变化,根据变化去自动在/dev目录下创建leds文件;

procfs基于内存的文件系统,导出内核的执行信息;如:cat /proc/cpuinfo
sysfs基于内存的文件系统,导出系统中硬件驱动的组织结构;

案例:

#include<linux/init.h>#include<linux/module.h>#include<linux/cdev.h>#include<linux/kernel.h>#include<linux/device.h>#include<linux/fs.h>#include<linux/uaccess.h>#include<plat/gpio-cfg.h>#include<asm/gpio.h>static struce class *cls;//记录树枝 (其实属于一个设备类)static int led_open(struct inode *inode, struct file *file){   printlk("enter led_open! \n");   return 0; }static int led_close(struct inode *inode, struct file *file){   printlk("enter led_close! \n");   return 0; }//假如在用户空间写入:write(fd,buf,1) buf中的值1亮  0 灭static ssize_t led_write (struct file *file, const char __user *buf, size_t count, loff_t *offset){   int cmd;   copy_from_user(&cmd,buf,sizeof(cmd));//将用户空间的数据拷贝到内核空间   if(cmd == 1){       gpio_set_value(S5PV210_GPC1(3),1);//点亮       led_status = 1;   }else{       gpio_set_value(S5PV210_GPC1(3),-);       led_status = 0;   }   return count; }//read(fd,buf,cnt)static ssize_t led_read(struct file *file,char __user *buf, size_t count, loff_t *offset){   //把灯的状态返回给用户空间   copy_to_user(buf,&led_status,sizeof(led_status));   return count; }static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val){    printlk("enter led_ioctl! \n");}//需要实现的函数static struct file_operations led_fops = {    .owner   = THIS_MOUDLE,    .open    = led_open,    .release = led_close,    .write   = led_write,    .read    = led_read,    .ioctl   = led_ioctl,};static int major = 0;/*1,分配一个cdev */static struct cdev led_cdev;static int led_status;//记录灯的状态static int chardevice_init(void){     dev_t dev;     if(major != 0){//静态分配         dev = MKDEV(major,0);//构建一个设备号         //向内核注册设备号         register_chrdev_region(dev,1,"tarena");     }else{         //动态申请注册设备号          alloc_chrdev_region(&dev,1001,"test");         major = MAJOR(dev);//获取主设备号   相当于dev>>20         unsigned int minor = MINOR(dev);//获取次设备号  相当于把高20位清0         printk("major = %d ! \n",major);     }     //2, 初始化cdev      cdev_init(&led_cdev,&led_fops);     //3,把cdev添加到内核里面去     cdev_add(&led_cdev,dev,1);     //自动创建设备节点文件     cls =  class_create(THIS_MODULE,"tarena");//见到owner就传THIS_MODULE     device_create(cls,NULL,dev,NULL,"leds");//这个设备要挂到哪个枝上(cls),父设备为NULL,      //申请GPIO管脚     gpio_request(S5PV210_GPC1(3),"LED1");//从原图中找管脚名称GPC1_3     //配置为输出     gpio_dirction_output(S5PV210_GPC1(3),0);     led_status = 0;     return 0;}static void chardevice_exit(void){    dev_t dev;    gpio_free(S5pV210_GCP(3));    dev = MKDEV(major,0);     //自动删除设备节点,注意要逆序销毁     device_destory(cls,dev);//相当于从这个树枝(cls)把水果摘掉(dev)     class_destory(cls);//砍树枝    //4,按照对内核产生的影响,逆序销毁cdev    cdev_del(&led_cdev);    //释放    unregist_chrdev_region(dev,1);}module_init(chardevice_init);module_exit(chardevice_exit);MODULE_LICENSE("GPL");

然后Makefile文件,将ko文件拷贝到开发板,然后在开发板中执行。。。

LED标准程序的写法:
关于uart用户空间编程
串口初始化 xxx_init 或 open
通过串口发送数据 write函数
通过串口接收数据 read函数
修改传输的速率 有效位个数 校验方式 ioctl

led_open(){
gpio_reqiest(….);
gpio_direction_output(…);
}

led_close(){
gpio_free(…);
}

led_ioctl(inode,filep,cmd,val){

if(cmd == 1){//亮   if(val == 1){//led1_on 第一个灯亮   }else if(val == 2 ){//led2_on 第二个灯亮   }}else if(cmd == 0){}

}

led_read(){//来获取灯的亮灭状态
int led_status
}

init(){
1,动态申请设备号
2,创建并初始化cdev
3,向内核添加cdev
4,自动创建设备节点文件
}

exit(){
逆序消除init函数中对内核的影响
}

案例:

#include<linux/init.h>#include<linux/module.h>#include<linux/cdev.h>#include<linux/kernel.h>#include<linux/device.h>#include<linux/fs.h>#include<linux/uaccess.h>#include<plat/gpio-cfg.h>#include<asm/gpio.h>#define LED_ON     0x100001;#define LED_OFF    0x100002;#define LED_STATUS 0x100003;static struce class *cls;//记录树枝 (其实属于一个设备类)static int led_status;//记录灯的状态static int led_open(struct inode *inode, struct file *file){   //6,申请GPIO管脚   gpio_request(S5PV210_GPC1(3),"LED1");   gpio_request(S5PV210_GPC1(4),"LED2");   //7,配置为输出   gpio_direction_output(S5PV210_GPC1(3),0);   gpio_direction_output(S5PV210_GPC1(4),0);   led_status = 0;   printlk("enter led_open! \n");   return 0; }static int led_close(struct inode *inode, struct file *file){   //8,释放GPIO管脚   gpio_free(S5PV210_GPC1(3));   gpio_free(S5PV210_GPC1(4));   printlk("enter led_close! \n");   return 0; }//read(fd,buf,cnt)static ssize_t led_read(struct file *file,char __user *buf, size_t count, loff_t *offset){   int ret;   //把灯的状态返回给用户空间   ret = copy_to_user(buf,&led_status,sizeof(led_status));   return count; }/***用户空间调用ioctl(fd,cmd,&val);*产生软中断,调用sys_ioctl* sys_ioctl(...){*   led_ioctl(inode,file,cmd,val);* }***/static int led_ioctl(struct inode *inode, struct file *file, unsigned int cmd,unsigned long val){    int index = 0;    int ret = 0;    int status = 0;    //因为不能直接操作用户空间的地址,    //所以把用户空间的4个字节的数据拷贝到index里面,这就得到了具体操作了哪个灯    ret = copy_from_user(&index,(int *)val,4);    if(index > 1 || index <0 ){//只能为0 1,其他的非法        return -EINVAL;//返回非法参数    }    switch(cmd){        case: LED_ON:             gpio_set_value(S5PV210_GPC(3)+index,1);             led_status |= (0x01<<index);             break;        case: LED_OFF:             gpio_set_value(S5PV210_GPC(3)+index,0);             led_status &= ~(0x01<<index);             break;        case: LED_STATUS:            // printk("status = %d \n",status);             if(led_status & (0x01<<index)){                status = 1;             }else{                status = 0;             }            // printk("status = %d \n",status);             ret = copy_to_user((int*)val,&status,4);             break;        default:             return -EINVAL;    }    return 0;}//需要实现的函数static struct file_operations led_fops = {    .owner   = THIS_MOUDLE,    .open    = led_open,    .release = led_close,    //.write   = led_write,    .read    = led_read,    .ioctl   = led_ioctl,};static int major = 0;/*2,分配一个cdev */static struct cdev led_cdev;static int chardevice_init(void){     dev_t dev;     //1,动态申请注册设备号      alloc_chrdev_region(&dev,1001,"test");     major = MAJOR(dev);//获取主设备号   相当于dev>>20     // unsigned int minor = MINOR(dev);//获取次设备号  相当于把高20位清0     //3, 初始化cdev      cdev_init(&led_cdev,&led_fops);     //4,把cdev添加到内核里面去     cdev_add(&led_cdev,dev,1);     //5,自动创建设备节点文件     cls =  class_create(THIS_MODULE,"tarena");//见到owner就传THIS_MODULE     //"leds" 就是/dev要生成的设备文件的名字     device_create(cls,NULL,dev,NULL,"leds");//这个设备要挂到哪个枝上(cls),父设备为NULL     return 0;}static void chardevice_exit(void){    dev_t dev;    dev = MKDEV(major,100);     //自动删除设备节点,注意要逆序销毁    device_destory(cls,dev);//相当于从这个树枝(cls)把水果摘掉(dev)    class_destory(cls);//砍树枝    //10,从内核中删除led_cdev  按照对内核产生的影响,逆序销毁cdev    cdev_del(&led_cdev);    //注销设备号    unregist_chrdev_region(dev,1);}module_init(chardevice_init);module_exit(chardevice_exit);MODULE_LICENSE("GPL");

对应的测试程序:

#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#define LED_ON     0x100001;#define LED_OFF    0x100002;#define LED_STATUS 0x100003;int main(void){   int fd;   int cmd = 0;   int val = ;   fd = open("/dev/leds",o_RDWR);   if(fd<0){      printlf("open /dev/leds failed");      return -1;   }   //LED2 on   cmd = LED_ON;   val = 1;//代表第二个灯   ioctl(fd,cmd,&val);//cmd对应驱动程序ioctl方法中的cmd,val对应驱动程序的val   cmd = LED_STATUS;   ioctl(fd,cmd,&val);//cmd对应驱动程序ioctl方法中的cmd,val对应驱动程序的val   if(val = 1){       printf("LED2 ON \n");   }else if(val = 0){       printf("LED2 OFF \n");   }   sleep(5);   //LED1 OFF   cmd = LED_OFF;   val = 1;   ioctl(fd,cmd,&val);   cmd = LED_STATUS;   ioctl(fd,cmd,&val);   if(val = 0){      printf("LED2 OFF\n");   }else{      printf("LED2 ON\n");   }   return 0;}

作业:计算S5PV210_GPC1(3)的值是多少并验证(打印)

0 0
原创粉丝点击