欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-14 16:26:46
...

原文地址:http://blog.csdn.net/PZ0605/article/details/53343077

回顾:
与驱动有关的几个重要结构体
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// 私有数据
…..
}
Linux设备驱动第四天(自动创建设备节点、LED驱动程序)

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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

自动创建设备文件
手工创建设备文件: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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123

然后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");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153

对应的测试程序:

#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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

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

(function () {('pre.prettyprint code').each(function () { var lines = (this).text().split(\n).length;varnumbering = $('
    ').addClass('pre-numbering').hide(); (this).addClass(hasnumbering).parent().append(numbering); for (i = 1; i