字符设备驱动LED驱动程序编写

一. 概念介绍

一般用户在应用程序里调用的 open, read, write 函数是 c 库的函数, 这些函数会触发 swi val异常,从而引发系统调用,进入到内核空间, 内核通过VFS(virtual Filesystem)来实现调用不同的驱动函数(因此驱动程序必须挂载到VFS中)。


例如:我们有一个函数,

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);

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

设备号要与设备结点(VFS节点)关联起来,才能通过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);
}

四. 完善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;
}

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

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;
}
另外,当用户空间的数据要传到内核空间里,同样不能直接用, 

也要通过 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;
}







。 

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值