4.11 驱动开发

作业

3.1 实验现象

crw------- 1 root root 236, 0 Apr 11 15:55 /dev/myled0
crw------- 1 root root 236, 1 Apr 11 15:55 /dev/myled1
crw------- 1 root root 236, 2 Apr 11 15:55 /dev/myled2

在串口工具,输入如下命令实现控制对应的LED灯进行工作
    echo 1 > /dev/myled0 ========> led1灯点亮
    echo 0 > /dev/myled0 ========> led1灯熄灭
    echo 1 > /dev/myled1 ========> led2灯点亮
    echo 0 > /dev/myled1 ========> led2灯熄灭
    echo 1 > /dev/myled2 ========> led3灯点亮
    echo 0 > /dev/myled2 ========> led3灯熄灭

3.2 编程思路

    struct inode {
        dev_t          i_rdev; //设备号
    };


int myled_open(struct inode *inode, struct file *file)
{
    //获取次设备的值,将次设备号的值,通过私有数据传参,传递给write函数
    return 0;
}
--------------------------------------------------------------------------------
ssize_t myled_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff)
{
    //判断私有数据的值,也就是次设备号的值,决定操作哪一盏灯
    return 0;     
}

3.3 编程要求

  • 分步注册字符设备驱动
  • 自动创建设备节点
  • 三盏灯地址映射
  • 三盏灯初始化
  • 私有数据传参
  • 用户空间和内核空间数据传输
  • 控制灯亮灭

demo.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/device.h>
#include "myled.h"

struct cdev *cdev;
#if 1
unsigned int major = 0;
#else
unsigned int major = 500;
#endif
unsigned int minor = 0;
unsigned int count = 3;
#define CNAME "myled"
struct class *cls;
struct device *device;

char kbuf[128] = "";
unsigned int *rcc_virt = NULL;
gpio_t *gpioe_virt = NULL;
gpio_t *gpiof_virt = NULL;

#define LED1_ON (gpioe_virt->ODR |= (0x1 << 10))
#define LED1_OFF (gpioe_virt->ODR &= (~(0x1 << 10)))

#define LED2_ON (gpiof_virt->ODR |= (0x1 << 10))
#define LED2_OFF (gpiof_virt->ODR &= (~(0x1 << 10)))

#define LED3_ON (gpioe_virt->ODR |= (0x1 << 8))
#define LED3_OFF (gpioe_virt->ODR &= (~(0x1 << 8)))

struct inode
{
    dev_t i_rdev; // 设备号
};


int myled_open(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

ssize_t myled_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

ssize_t myled_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    // 如果用户空间写的数据大小,大于内核空间大小,需要更正写的大小
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size); // 将用户空间的数据,拷贝到内核空间
    if (ret)
    {
        printk("copy form user is error\n");
        return -EIO;
    }
    // kbuf[0] = 0 ==> 操作LED1 ==> kbuf[1] = 0 灯熄灭 kbuf[1] = 1 灯点亮
    // kbuf[0] = 1 ==> 操作LED2 ==> kbuf[1] = 0 灯熄灭 kbuf[1] = 1 灯点亮
    // kbuf[0] = 2 ==> 操作LED3 ==> kbuf[1] = 0 灯熄灭 kbuf[1] = 1 灯点亮
    switch (kbuf[0])
    {
    case LED1:
        kbuf[1] == 1 ? LED1_ON : LED1_OFF;
        break;
    case LED2:
        kbuf[1] == 1 ? LED2_ON : LED2_OFF;
        break;
    case LED3:
        kbuf[1] == 1 ? LED3_ON : LED3_OFF;
        break;
    }
    return size; //!!!!!!!!!!!
    return 0;
}

int myled_close(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
// 操作方法结构体
const struct file_operations fops = {
    .open = myled_open,
    .read = myled_read,
    .write = myled_write,
    .release = myled_close,
};



// 入口函数
static int __init demo_init(void)
{
    int ret;
    int i;
    dev_t devno;
    // 分配对象
    cdev = cdev_alloc();
    if (cdev == NULL)
    {
        printk("cdev alloc is error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 对象初始化
    cdev_init(cdev, &fops);

    if (major > 0)
    { // 静态指定设备号
        ret = register_chrdev_region(MKDEV(major, minor), count, CNAME);
        if (ret)
        {
            printk("register chrdev region is error\n");
            ret = -EIO;
            goto ERR2;
        }
    }
    else
    { // 动态指定设备号
        ret = alloc_chrdev_region(&devno, minor, count, CNAME);
        if (ret)
        {
            printk("alloc chrdev region is error\n");
            ret = -EIO;
            goto ERR2;
        }
        major = MAJOR(devno); // 通过设备号,获取到主设备号的值
        minor = MINOR(devno); // 通过设备号,获取到次设备号的值
    }

    // 注册
    ret = cdev_add(cdev, MKDEV(major, minor), count);
    if (ret)
    {
        printk("cdev add is error\n");
        ret = -EIO;
        goto ERR3;
    }

    // 向上层提交目录信息
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls))
    {
        ret = PTR_ERR(cls);
        goto ERR4;
    }

    for (i = 0; i < count; i++)
    {
        // 向上层提交设备节点信息
        device = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);
        if (IS_ERR(device))
        {
            ret = PTR_ERR(device);
            goto ERR5;
        }
    }

    // 自动创建设备节点
    // 注册字符设备驱动
    major = register_chrdev(0, CNAME, &fops);
    if (major < 0)
    {
        printk("register chrdev is error\n");
        return -EIO;
    }
    printk("major = %d\n", major); // 打印主设备号的值

    // 向上层提交目录信息
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls))
    {
        return PTR_ERR(cls);
    }
    // 向上层提交设备节点信息
    device = device_create(cls, NULL, MKDEV(major, 0), NULL, CNAME);
    if (IS_ERR(device))
    {
        return PTR_ERR(device);
    }
    //

    // rcc物理地址映射
    rcc_virt = ioremap(RCC_MP_AHB4ENSETR_PHY, 4);
    if (rcc_virt == NULL)
    {
        printk("rcc ioremap is error\n");
        return -EIO;
    }
    // GPIOE组寄存器物理地址映射
    gpioe_virt = ioremap(GPIOE, sizeof(gpio_t));
    if (gpioe_virt == NULL)
    {
        printk("gpioe ioremap is error\n");
        return -EIO;
    }
    // GPIOF组寄存器物理地址映射
    gpiof_virt = ioremap(GPIOF, sizeof(gpio_t));
    if (gpiof_virt == NULL)
    {
        printk("gpiof ioremap is error\n");
        return -EIO;
    }
    // LED1灯初始化 PE10
    *rcc_virt |= (0x1 << 4);             // 使能GPIOE组控制器
    gpioe_virt->MODER &= (~(0x3 << 20)); // 设置PE10引脚为输出模式
    gpioe_virt->MODER |= (0x1 << 20);
    gpioe_virt->ODR &= (~(0x1 << 10)); // 设置PE10引脚输出低电平

    // LED2灯初始化 PF10
    *rcc_virt |= (0x1 << 5);             // 使能GPIOF组控制器
    gpiof_virt->MODER &= (~(0x3 << 20)); // 设置PF10引脚为输出模式
    gpiof_virt->MODER |= (0x1 << 20);
    gpiof_virt->ODR &= (~(0x1 << 10)); // 设置PF10引脚输出低电平

    // LED3灯初始化 PE8
    gpioe_virt->MODER &= (~(0x3 << 16)); // 设置PE8引脚为输出模式
    gpioe_virt->MODER |= (0x1 << 16);
    gpioe_virt->ODR &= (~(0x1 << 8)); // 设置PE8引脚输出低电平
    return 0;

    return 0; //!!!!!!!!!!!!!!!!!!

ERR5:
    // 创建三个设备节点时,第一个设备节点和第二个设备节点创建成功,第三个设备节点创建失败
    // 需要将第一个设备节点和第二个设备节点创建成功,需要进行释放
    for (--i; i >= 0; i--)
    {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), count);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}

// 出口
static void __exit demo_exit(void)
{
    int i = 0;
    for (i = 0; i < count; i++)
    {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), count);
    kfree(cdev);
    // 取消地址映射
    iounmap(rcc_virt);
    iounmap(gpioe_virt);
    iounmap(gpiof_virt);
    // 注销字符设备驱动
    unregister_chrdev(major, CNAME);
}

module_init(demo_init); // 指定入口地址
module_exit(demo_exit); // 指定出口地址
MODULE_LICENSE("GPL");  // 遵循GPL协议

test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 #include <unistd.h>
 #include <string.h>
int main(int argc, const char *argv[])
{
    int fd = -1;
    char buf[128] = {};
    fd = open("/dev/myled",O_RDWR);
    if(fd == -1){
        perror("open is error");
        return -1;
    }
        while(1)
    {
        buf[0] = 0;
        write(fd,buf,sizeof(buf)); //将用户空间中buf中的内容,写入到内核空间kbuf
        buf[1] = 1;
        write(fd,buf,sizeof(buf)); //将用户空间中buf中的内容,写入到内核空间kbuf
        sleep(1);
        buf[1] = 0;
        write(fd,buf,sizeof(buf)); //将用户空间中buf中的内容,写入到内核空间kbuf
        sleep(1);

        buf[0] = 1;
        write(fd,buf,sizeof(buf)); //将用户空间中buf中的内容,写入到内核空间kbuf
        buf[1] = 1;
        write(fd,buf,sizeof(buf)); //将用户空间中buf中的内容,写入到内核空间kbuf
        sleep(1);
        buf[1] = 0;
        write(fd,buf,sizeof(buf)); //将用户空间中buf中的内容,写入到内核空间kbuf
        sleep(1);

        buf[0] = 2;
        write(fd,buf,sizeof(buf)); //将用户空间中buf中的内容,写入到内核空间kbuf
        buf[1] = 1;
        write(fd,buf,sizeof(buf)); //将用户空间中buf中的内容,写入到内核空间kbuf
        sleep(1);
        buf[1] = 0;
        write(fd,buf,sizeof(buf)); //将用户空间中buf中的内容,写入到内核空间kbuf
        sleep(1);
    }
    close(fd);
    return 0;
}

test.h

#ifndef __MYLED_H__
#define __MYLED_H__

#define RCC_MP_AHB4ENSETR_PHY 0x50000A28


enum{
    LED1,
    LED2,
    LED3,
};

typedef struct {
    volatile unsigned int MODER;   // 0x00
    volatile unsigned int OTYPER;  // 0x04
    volatile unsigned int OSPEEDR; // 0x08
    volatile unsigned int PUPDR;   // 0x0C
    volatile unsigned int IDR;     // 0x10                                                                        
    volatile unsigned int ODR;     // 0x14
    volatile unsigned int BSRR;    // 0x18
    volatile unsigned int LCKR;    // 0x1C 
    volatile unsigned int AFRL;    // 0x20 
    volatile unsigned int AFRH;    // 0x24
    volatile unsigned int BRR;     // 0x28
    volatile unsigned int res;
    volatile unsigned int SECCFGR; // 0x30

}gpio_t;

#define  GPIOE   (0x50006000)
#define  GPIOF   (0x50007000)


#endif

不足之处:

1.分步注册字符设备驱动后是否需要再搞一次自动创建设备节点,再搞一次是否有意义?

2.如何获取串口输入的内容并将其存储在容器中传输到主机?

相关推荐
疯狂飙车的蜗牛8 小时前
从零玩转CanMV-K230(4)-小核Linux驱动开发参考
linux·运维·驱动开发
嵌入式进阶行者20 小时前
【驱动开发初级】内核模块静态和动态添加功能的步骤
驱动开发
逝灮21 小时前
【蓝桥杯——物联网设计与开发】拓展模块3 - 温度传感器模块
驱动开发·stm32·单片机·嵌入式硬件·物联网·蓝桥杯·温度传感器
__NULL__USER2 天前
petalinux-adi ---添加AD9361驱动(二)
linux·驱动开发
7yewh2 天前
嵌入式驱动RK3566 HDMI eDP MIPI 背光 屏幕选型与调试提升篇-eDP屏
linux·arm开发·驱动开发·嵌入式硬件·嵌入式linux·rk·edp
少年、潜行4 天前
树莓派3B+驱动开发(8)- i2c控制PCF8591
驱动开发·树莓派·3b+
千千道4 天前
深入理解 Linux 内核启动流程
linux·arm开发·驱动开发
SunshineBooming4 天前
qemu源码解析【05】qemu启动初始化流程
c++·驱动开发·源码软件
嵌入式大圣5 天前
单片机MQTT通信
驱动开发·单片机·嵌入式硬件·物联网
嵌入(师)5 天前
嵌入式驱动开发详解19(regmap驱动架构)
驱动开发·架构