1.内核驱动中,驱动注册,阻塞IO,gpio子系统,中断处理的整体结合示例

一,功能实现要求

/*功能实现 在stm32开发板上实现功能

           1.使用阻塞IO读取number变量的值,当number的值改变时打印number的值

           2.注册KEY1按键的驱动和LED1的驱动以及对应的设备文件,

           3.按键和指示灯设备信息放在同一个设备树的节点中

           4.当KEY1按下时LED1灯的状态取反,number的值取反,number值为0或1

           */

二,示例图

1.内核驱动中,驱动注册,阻塞IO,gpio子系统,中断处理的整体结合示例_第1张图片

 三,驱动注册(无实际功能)

        1.主要实现手动注册驱动,并自动提交目录以及myled1和mykey1两个设备文件

int request_dev(void)
{
    int i;
    // 1.实例化字符设备驱动对象
    cdev = cdev_alloc();
    if (cdev == NULL)
    {
        ret = -ENOMEM;
        goto OUT1;
    }
    // 2.部分初始化字符设备驱动对象
    cdev_init(cdev, &fops);
    // 3.申请设备号
    if (my_major == 0) // 动态申请设备号
    {
        ret = alloc_chrdev_region(&devno, my_minor, 3, "mycdev"); // 成功返回0,失败返回错误码
        if (ret)
        {
            printk("动态申请设备号失败\n");
            goto OUT2;
        }
        printk("动态申请设备号成功\n");
        my_major = MAJOR(devno); // 获取主设备号
        my_minor = MINOR(devno); // 获取次设备号
    }
    else // 静态制定设备号
    {
        // 通过自定义主设备号和次设备号获取设备号
        devno = MKDEV(my_major, 0);
        ret = register_chrdev_region(devno, 3, "mycdev"); // 成功申请设备号返回0,失败返回错误码
        if (ret)
        {
            printk("静态申请设备号失败\n");
            goto OUT2;
        }
        printk("静态申请设备号成功\n");
    }
    // 4.将字符设备驱动对象注册进内核
    ret = cdev_add(cdev, devno, 3);
    if (ret)
    {
        printk("字符设备驱动注册进内核失败\n");
        goto OUT3;
    }
    printk("字符设备驱动注册进内核成功\n");
    // 5.自动创建设备节点
    // 向上提交目录
    cls = class_create(THIS_MODULE, "mycdev");
    if (IS_ERR(cls)) // 为真表示cls指向4K的预留空间
    {
        printk("向上提交目录失败\n");
        ret = -PTR_ERR(cls);
        goto OUT4;
    }
    printk("向上提交目录成功\n");
    // 自动创建设备文件LED1
    dev = device_create(cls, NULL, MKDEV(my_major, 0), NULL, "myled1");
    if (IS_ERR(dev))
    {
        printk("自动创建设备文件myled1失败\n");
        ret = PTR_ERR(dev);
        goto OUT5;
    }
    printk("自动创建设备文件myled1成功\n");

    // 自动创建设备文件KEY1
    dev = device_create(cls, NULL, MKDEV(my_major, 1), NULL, "mykey1");
    if (IS_ERR(dev))
    {
        printk("自动创建设备文件mykey1失败\n");
        ret = PTR_ERR(dev);
        goto OUT5;
    }
    printk("自动创建设备文件mykey1成功\n");
    return 0;
OUT5:
    // 释放提交成功的设备节点信息
    for (--i; i >= 0; i--)
    {
        device_destroy(cls, MKDEV(my_major, i));
    }
    // 销毁目录
    class_destroy(cls);
OUT4:
    cdev_del(cdev);
OUT3:
    unregister_chrdev_region(devno, 3);
OUT2:
    kfree(cdev);
OUT1:
    return ret;
}

 四,LED1灯的初始化

        采用从自定义的设备树节点中获取LED1的GPIO对象信息,为LED1灯各个寄存器使能。只需手动调用电平改变函数gpiod-set-value即可实现灯的亮灭.

int request_LED(void)
{
    // 1.解析设备树节点信息通过名字
    dnode = of_find_node_by_name(NULL, "numbers");
    if (dnode == NULL)
    {
        printk("设备树节点解析失败\n");
        return -ENOMEM;
    }
    printk("设备树节点解析成功\n");

    // 2.通过节点信息获取GPIO对象
    // 2.1申请LED1对应资源
    gpiono_led1 = gpiod_get_from_of_node(dnode, "led1", 0, GPIOD_OUT_LOW, NULL);
    if (IS_ERR(gpiono_led1))
    {
        printk("LED1资源申请失败\n");
        return -PTR_ERR(gpiono_led1);
    }
    printk("LED1资源申请成功\n");
    return 0;
}

5.按键中断的初始化

        也是根据自己定义的设备树节点中获取信息,申请对应的软中断号,然后根据获取到的软终断号初始化中断号,从而实现中断发生回调中断处理函数处理中断时间。

int request_interrupts(void)
{
    // 准备:在设备树中添加gpiof组控制按键的节点信息
    // 1.在设备数中寻找到对应设备树节点的信息 of_find_node_by_name
    dnode = of_find_node_by_name(NULL, "numbers");
    if (dnode == NULL)
    {
        printk("节点信息获取失败\n");
        return -ENXIO;
    }
    printk("节点信息获取成功\n");
    // 2.根据节点地址寻找key对应的软中断号 irq_of_parse_and_map
    // 获取KEY按键对应的软中断号 参2的索引的值在于你的自定义节点GPIO控制所在位置,
    // myirq{
    //     interrupts-extended=<&gpiof 9 0>,<&gpiof 7 0>,<&gpiof8 0>;
    // };KEDY1对应<&gpiof 9 0>索引为0,KEY2对应<&gpiof 7 0>,索引为1,类似数组依次对应

    myirq_key[0] = irq_of_parse_and_map(dnode, 0);
    if (!myirq_key[0])
    {
        printk("key1软中断号获取失败\n");
        return ENOMEM;
    }
    printk("key1软中断号获取成功\n");

    // 3.注册中断号 包括对应的软中断号,中断执行的处理函数,中断的检测方式,中断名
    // 注册KEY对应的软中断号
    // 参1:中断号对应的软中断号 参2:中断的处理函数 参3:中断触发的方式 参4:为中断起一个名字 参5:给中断函数传递的值

    // KEY1
    ret = request_irq(myirq_key[0], myirq_handler, IRQF_TRIGGER_FALLING, irq_key1_name, 0);
    if (ret < 0)
    {
        printk("key1中断注册失败\n");
        return ret;
    }
    printk("key1中断注册成功\n");
    return 0;
}

6.阻塞IO

        当中断没有发生时,函数一直处于等待状态,当中断发生,标志位的值改变,从而使函数往下运行,改变number内核变量的值

int my_wait(void)
{
    // 判断condition的值,为1则改变number的值
    wait_event_interruptible(wq_head, condition);
    if (number==1)
    {
        number = 0;
    }
    else if(number==0)
    {
        number = 1;
    }
    condition = 0;
    return 0;
}

7.按键中断处理函数

        根据LED1现在的状态改变,更改标志位并唤醒睡眠中的进程。实现功能的要求。

// 中断处理函数
irqreturn_t myirq_handler(int irqno, void *dev_id)
{
    printk("软中断号%d的处理\n", irqno);
    // 改变LED灯的状态
    gpiod_set_value(gpiono_led1, !gpiod_get_value(gpiono_led1));
    // 改变number的值
    //  改变标志
    condition = 1;
    // 唤醒可中断进程
    wake_up_interruptible(&wq_head);
    return IRQ_HANDLED;
}

你可能感兴趣的:(驱动,驱动开发)