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

字符设备驱动----LED驱动程序

程序员文章站 2022-03-22 16:42:41
...

一. 概念介绍

一般用户在应用程序里调用的 open, read, write 函数是 c 库的函数, 
这些函数会触发 swi val异常,从而引发系统调用,进入到内核空间, 
内核通过VFS(virtual Filesystem)来实现调用不同的驱动函数。 
字符设备驱动----LED驱动程序 
例如:我们有一个函数,

int main()
{
    int fd1, fd2;
    int val = 1;

    fd1 = open("/dev/led", O_RDWR);
    write(fd1, &val, 4);

    fd2 = open("hello.txt", O_RDWR);
    write(fd2, &val, 4);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

函数里相同的open、write函数,引发的不同的行为,一个是操控硬件,一个是写文件。 
简单的调用关系如下: 
用户 –> 系统调用 –> 驱动程序 
open –> sys.open –> led.open 
write –> sys.write –> led.write


二. 字符设备驱动框架

实现步骤: 
1. 实现驱动的 led.open, led.write, led.read 操作 
2. 定义file_operations结构体, 把驱动函数填充到里面 
3. 把这个结构告诉内核, 通个函数 register_chrdev(major, “first_drv”, &first_drv_fops) 来实现 
4. 谁来调用注册函数 –>驱动的入口函数来调用这个注册函数, first_drv_init 
5. 修饰一下这个函数入口函数,module_init(first_drv_init)

//第一步:驱动功能实现
static int first_drv_open(struct inode *inode, struct file *file)
{
    //printk("first_drv_open\n");
    return 0;
}

static ssize_t first_drv_write(struct file *file, 
        const char __user *buf, 
        size_t count, 
        loff_t * ppos)
{
    //printk("first_drv_write\n");
    return 0;
}

//第二步:定义结构体,并把驱动函数填充进去
static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,
    .open =   first_drv_open,
    .write =    first_drv_write,       
};

//第四步:实现驱动入口函数
int major;
static int first_drv_init(void)
{
    //第三步:把结构体告诉内核
    major = register_chrdev(0, "first_drv", &first_drv_fops);// 注册告诉内核

    return 0;
}

static void first_drv_exit(void)
{
    unregister_chrdev(major, "first_drv"); // 卸载
}

//第五步:修饰入口函数,及退出函数
module_init(first_drv_init);
module_exit(first_drv_exit);
  • 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

三. 关联 [设备号] 与 [设备节点]

设备号要与设备结点关联起来,才能通过open(“/dev/xyz”)方便的操作。 
1. 设置主设备号 
驱动程序可以自动分配主设备号, 也可以手工指定

// 设置为 0 时是系统自动分配主设备号
major = register_chrdev(0, "first_drv", &first_drv_fops); 
// 通过 [cat /proc/device] 看一下系统为我们的first_drv分配的设备号是多少

// 手动分配 111主设备号给 first_drv
register_chrdev(111, "first_drv", &first_drv_fops); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2. 设置设备节点 
当应该程序 执行 open(“/dev/xyz”) 操作时,这个/dev/xyz怎么来的 
2.1 手动创建

// 创建设备节点
mknod /dev/xyz c(表示是字符设备) 主设备号 次设备号

//查看设备信息:
ls -l /dev/xyz
  • 1
  • 2
  • 3
  • 4
  • 5

2.2 自动创建 
mdev – 根据系统信息创建设备节点

int major;
static int first_drv_init(void)
{
    major = register_chrdev(0, "first_drv", &first_drv_fops);

    //创建设备信息,执行后会出现 /sys/class/firstdrv
    firstdrv_class = class_create(THIS_MODULE, "firstdrv"); 

    //创建设备节点,就是根据上面的设备信息来的
    firstdrv_class_dev = class_device_create(firstdrv_class,
    NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */

    return 0;
}

static void first_drv_exit(void)
{
    unregister_chrdev(major, "first_drv");

    //删除节点及信息
    class_device_unregister(firstdrv_class_dev);
    class_destroy(firstdrv_class);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

四. 完善LED驱动

完善LED驱动,也就是硬件的操作。 
单片机中可以直接操作物理地址,但在驱动里只能操作虚拟地址 
虚拟地址怎么来的,用 ioremap( ) 函数来映射,映射完后操作虚拟地址就像操作物理地址一样。

static int first_drv_init(void)
{
    major = register_chrdev(0, "first_drv", &first_drv_fops);
    firstdrv_class = class_create(THIS_MODULE, "firstdrv");
    firstdrv_class_dev = class_device_create(firstdrv_class,
    NULL, MKDEV(major, 0), NULL, "xyz");

    //映射 GPIO 的物理地址 0x56000050 到虚拟地址, gpfcon操作的是虚拟地址
    gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); 
    gpfdat = gpfcon + 1;

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

映射完后,就可以操作这些地址,来控制硬件寄存器

static int first_drv_open(struct inode *inode, struct file *file)
{
    //printk("first_drv_open\n");
    /* 配制GPF4为输出 */
    *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
    *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

另外,当用户空间的数据要传到内核空间里,同样不能直接用, 
也要通过 copy_from_user() 函数,把用户空间的值传到内核空间里。

static ssize_t first_drv_write(struct file *file, 
    const char __user *buf, 
    size_t count, loff_t * ppos)
{
    int val;

    //printk("first_drv_write\n");

    //把用户空间的值 copy 给内核空间
    copy_from_user(&val, buf, count); //    copy_to_user();

    if (val == 1)
    {
        // 点灯
        *gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
    }
    else
    {
        // 灭灯
        *gpfdat |= (1<<4) | (1<<5) | (1<<6);
    }

    return 0;
}
相关标签: 驱动