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.如何获取串口输入的内容并将其存储在容器中传输到主机?

相关推荐
gopher951116 小时前
linux驱动开发-设备树
linux·驱动开发
三菱-Liu1 天前
三菱变频器以模拟量电流进行频率设定(电流输入)
驱动开发·单片机·嵌入式硬件·硬件工程·制造
三菱-Liu2 天前
三菱FX5U CPU 内置以太网功能
网络·驱动开发·硬件工程·制造·mr
让开,我要吃人了2 天前
OpenHarmony鸿蒙( Beta5.0)摄像头实践开发详解
驱动开发·华为·移动开发·harmonyos·鸿蒙·鸿蒙系统·openharmony
OH五星上将3 天前
如何更换OpenHarmony SDK API 10
驱动开发·嵌入式硬件·sdk·harmonyos·openharmony·鸿蒙开发
OH五星上将4 天前
OpenHarmony(鸿蒙南向开发)——标准系统移植指南(二)Linux内核
linux·驱动开发·嵌入式硬件·移动开发·harmonyos·鸿蒙开发·鸿蒙内核
芊言芊语4 天前
蓝牙驱动开发详解
驱动开发
让开,我要吃人了5 天前
OpenHarmony鸿蒙( Beta5.0)RTSPServer实现播放视频详解
驱动开发·嵌入式硬件·华为·移动开发·harmonyos·鸿蒙·openharmony
OH五星上将5 天前
OpenHarmony(鸿蒙南向开发)——轻量和小型系统三方库移植指南(二)
驱动开发·移动开发·harmonyos·内存管理·openharmony·鸿蒙内核·鸿蒙移植
CS_素锦少年5 天前
Linux_kernel驱动开发11
linux·运维·驱动开发