安卓驱动框架:字符设备和杂项设备


一、字符设备

在 Linux 中一切皆为文件,驱动加载成功以后会在 “/dev” 目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。

1.1 驱动模块的加载和卸载

module_init(xxx_init); //注册模块加载函数 
module_exit(xxx_exit); //注册模块卸载函数

module_init() 函数用来向 Linux 内核注册一个模块加载函数,参数xxx_init就是需要注册的具体函数,当使用 “insmod” 命令加载驱动的时候,xxx_init 这个函数就会被调用。

module_exit() 函数用来向 Linux 内核注册一个模块卸载函数,参数xxx_exit就是需要注册的具体函数,当使 用 “rmmod” 命令卸载具体驱动的时候 xxx_exit 函数就会被调用。

字符设备驱动模块加载和卸载模板如下所示:

/* 驱动入口函数 */
static int __init xxx_init(void)
{
    /* 入口函数具体内容 */
    return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
    /* 出口函数具体内容 */
}

// __init、__exit 定义在 inlucde/linux/init.h

// 声明在 inlucde/linux/init.h
/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

驱动编译完成以后扩展名为 .ko,有两种命令可以加载驱动模块:insmod modprobeinsmod 是最简单的模块加载命令,此命令用于加载指定的 .ko 模块,比如加载 drv.ko 这个驱动模块,命 令如下:

insmod drv.ko

insmod 命令不能解决模块的依赖关系,比如 drv.ko 依赖 first.ko 这个模块,就必须先使用 insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。

但是 modprobe 就不会存在这个问题,modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,因此 modprobe 命令相比 insmod 要智能一些。modprobe 命令主要智能在提供了模块的依赖性分析、 错误检查、错误报告等功能,推荐使用modprobe 命令来加载驱动。modprobe 命令默认会去 /lib/modules/< kernel-version> 目录中查找模块,比如本书使用的 Linux kernel 的版本号为 4.1.15, 因此modprobe 命令默认会到 /lib/modules/4.1.15 这个目录中查找相应的驱动模块,一般自己制作的根文件系统中是不会有这个目录的,所以需要自己手动创建。

驱动模块的卸载使用命令 “rmmod” 即可,比如要卸载 drv.ko,使用如下命令即可:

rmmod drv.ko

也可以使用 “modprobe -r” 命令卸载驱动,比如要卸载 drv.ko,命令如下:

modprobe -r drv.ko

使用 modprobe 命令可以卸载掉驱动模块所依赖的其他模块,前提是这些依赖模块已经没有被其他模块所使用,否则就不能使用modprobe 来卸载驱动模块。所以对于模块的卸载,还是推荐使用 rmmod 命令。

1.2 字符设备注册与注销

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。字符设备的注册和注销函数原型如下所示:

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)

register_chrdev 函数用于注册字符设备,此函数一共有三个参数,这三个参数的含义如下:
major:主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分。
name:设备名字,指向一串字符串。
fops:结构体 file_operations 类型指针,指向设备的操作函数集合变量。

unregister_chrdev 函数用户注销字符设备,此函数有两个参数,这两个参数含义如下:
major:要注销的设备对应的主设备号。
name:要注销的设备对应的设备名。
一般字符设备的注册在驱动模块的入口函数 xxx_init( ) 中进行,字符设备的注销在驱动模块的出口函数 xxx_exit( ) 中进行。模板如下:

static struct file_operations test_fops;

/* 驱动入口函数 */
static int __init xxx_init(void)
{
    /* 入口函数具体内容 */
    int retvalue = 0;
    
    /* 注册字符设备驱动 */
    retvalue = register_chrdev(200, "chartest", &test_fops);
    if(retvalue < 0){
        /* 字符设备注册失败,自行处理 */
    }
    
    return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
    /* 出口函数具体内容 */
    /* 注销字符设备驱动 */
    unregister_chrdev(200, "chartest");
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

1.3 实现设备的具体操作函数

file_operations 结构体就是设备的具体操作函数,我们定义了 file_operations 结构体类型的变量 test_fops,但是还没对其进行初始化,也就是初始化其中的 open、 release、read write 等具体的设备操作函数。

添加基本操作,添加后内容如下:

/* 打开设备 */
static int chrtest_open(struct inode *inode, struct file *filp)
{
    /* 用户实现具体功能 */
    return 0;
}

/* 从设备读取 */
static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    /* 用户实现具体功能 */
    return 0;
}

/* 向设备写数据 */
static ssize_t chrtest_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    /* 用户实现具体功能 */
    return 0;
}

/* 关闭/释放设备 */
static int chrtest_release(struct inode *inode, struct file *filp)
{
    /* 用户实现具体功能 */
    return 0;
}

static struct file_operations test_fops = {
    .owner = THIS_MODULE,
    .open = chrtest_open,
    .read = chrtest_read,
    .write = chrtest_write,
    .release = chrtest_release,
};

/* 驱动入口函数 */
static int __init xxx_init(void)
{
    /* 入口函数具体内容 */
    int retvalue = 0;
    
    /* 注册字符设备驱动 */
    retvalue = register_chrdev(200, "chartest", &test_fops);
    if(retvalue < 0){
        /* 字符设备注册失败,自行处理 */
    }
    
    return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
    /* 出口函数具体内容 */
    /* 注销字符设备驱动 */
    unregister_chrdev(200, "chartest");
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

1.4 添加LICENSE 和作者信息

我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加。LICENSE 和作者信息的添加使用 如下两个函数:

MODULE_LICENSE() //添加模块LICENSE信息
MODULE_AUTHOR()  //添加模块作者信息

添加后内容如下:

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

// 声明在 inlucde/linux/module.h
/* LICENSE 采用GPL 协议 */
MODULE_LICENSE("GPL");

1.5 创建设备节点文件

驱动加载成功需要在 /dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。输入如下命令创建 /dev/chrdevbase 这个设备节 点文件:

mknod /dev/chrdevbase c 200 0

其中 “mknod” 是创建节点命令,“/dev/chrdevbase” 是要创建的节点文件,“c” 表示这是个字符设备,“200” 是设备的主设备号,“0” 是设备的次设备号。创建完成以后就会存在 /dev/chrdevbase 这个文件,可以使用 “ls /dev/chrdevbase -l” 命令查看,结果下所示:

/dev # ls /dev/chrdevbase -l
crw-r--r--    1 0        0         200,   0 Jan  1 01:38 /dev/chrdevbase

如果想要读写 chrdevbase 设备,直接对 /dev/chrdevbase 进行读写操作即可。相当于 /dev/chrdevbase 这个文件是 chrdevbase 设备在用户空间中的实现。

如果不再使用某个设备的话可以将其驱动卸载掉,比如输入如下命令卸载掉 chrdevbase 这个设备:

rmmod chrdevbase.ko

卸载以后使用 lsmod 命令查看 chrdevbase 这个模块还存不存在。

/ # lsmod
Module                  Size  Used by    Tainted: G
chrdevbase              1282  0
/ # rmmod chrdevbase.ko
/ # lsmod
Module                  Size  Used by    Tainted: G
/ # ls /dev/chrdevbase -l
ls: /dev/chrdevbase: No such file or directory
/ #

二、杂项设备

linux里面的misc杂项设备是主设备号为10的驱动设备
定义头文件<linux/miscdevice.h>

2.1 杂项设备的结构体

struct miscdevice{
  int minor; //杂项设备的此设备号(如果设置为MISC_DYNAMIC_MINOR,表示系统自动分配未使用的minor)
  const char *name;
  const stuct file_operations *fops;//驱动主题函数入口指针
  struct list_head list;
  struct device *parent;
  struct device *this device;
  const char *nodename;(/dev下面创建的设备驱动节点)
  mode_t mode;
};

2.2 注册和释放

注册:int misc_register(struct miscdevice *misc)
释放:int misc_deregister(struct miscdevice *misc)

misc_device 是特殊字符设备。注册驱动程序时采用 misc_register 函数注册,此函数中会自动创建设备节点,即设备文件。无需 mknod 指令创建设备文件。因为 misc_register() 会调用 class_device_creat 或者 device_creat().

2.3 杂项字符设备和一般字符设备的区别

  • 1.一般字符设备首先申请设备号。 但是杂项字符设备的主设备号为10次设备号通过结构体 struct miscdevice 中的minor来设置。
  • 2.一般字符设备要创建设备文件。 但是杂项字符设备在注册时会自动创建。
  • 3.一般字符设备要分配一个 cdev(字符设备)。 但是杂项字符设备只要创建 struct miscdevice 结构即可。
  • 4.一般字符设备需要初始化 cdev (即给字符设备设置对应的操作函数集 struct file_operation). 但是杂项字符设备在结构体 struct miscdevice 中定义。
  • 5.一般字符设备使用注册函数 int cdev_add(struct cdev *p, dev_t dev, unsigned count)(第一个参数为之前初始化的字符设备,第二个参数为设备号,第三个参数为要添加设备的个数) 而杂项字符设备使用 int misc_register(struct miscdevice *misc) 来注册

驱动调用的实质
就是通过 设备文件 找到与之 对应设备号的设备,再通过设备初始化时绑定的操作函数对硬件进行控制的。

2.4 杂项设备驱动示例

2.4.1 驱动层注册

#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "led_ctrl"
#define LED_NUM 4

// IOCTL命令定义
#define LED_ON  _IO('L', 1)
#define LED_OFF _IO('L', 0)



static unsigned long led_table[] =
    {
        S3C2410_GPB(5),
        S3C2410_GPB(6),
        S3C2410_GPB(7),
        S3C2410_GPB(8),
};

static unsigned int led_cfg_table[] =
    {
        S3C2410_GPIO_OUTPUT,
        S3C2410_GPIO_OUTPUT,
        S3C2410_GPIO_OUTPUT,
        S3C2410_GPIO_OUTPUT,
};
//定义4个GPIO引脚(GPB5-GPB8)及其配置模式(输出模式)


static long led_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 
{
    int index = (int)arg;
    if (index < 0 || index >= LED_NUM) {
        return -EINVAL;
    }
    switch (cmd) {
        case LED_ON:
            s3c2410_gpio_setpin(led_table[index], 1);
            break;
        case LED_OFF:
            s3c2410_gpio_setpin(led_table[index], 0);
            break;
        default:
            return -ENOTTY;
    }

    return 0;
}

static struct file_operations dev_fops = {
  .owner = THIS_MODULE,
  .unlocked_ioctl = phy_ioctl,
};
//unlocked_ioctl用于处理用户空间的ioctl请求

static struct miscdevice misc = {
  .minor = MISC_DYNAMIC_MINOR,// 动态分配次设备号
  .name = "misc_phy",		  // 设备名(/dev/misc_phy)
  .fops = &dev_fops,		// 关联 file_operations
};

static int __init dev_init(void)
{
    int ret;
    int i;
    for (i = 0; i < 4; i++)
    {
     	//初始化GPIO为输出模式,并关闭LED(置0)
        s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
        s3c2410_gpio_setpin(led_table[i], 0);
    }
    ret = misc_register(&misc);
    printk(DEVICE_NAME " initialized\n");
    return ret;
}

static void __exit dev_exit(void)
{
	//注销设备
    misc_deregister(&misc);
}

module_init(dev_init);
module_exit(dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.e-online.cc");
MODULE_DESCRIPTION("LEDS control for GT2440 Board");

该代码实现了一个混杂设备驱动(miscdevice),主要功能包括:

  • GPIO初始化:配置开发板(如S3C2410)上的4个GPIO引脚为输出模式,用于控制LED。
  • 用户空间交互:通过ioctl接口实现与用户空间的通信,支持读取PHY寄存器(代码中功能不完整)。
  • 模块管理:支持动态加载和卸载内核模块。

2.4.2 用户空间测试

整体流程简单如下

int fd = open("/dev/misc_led", O_RDWR);
ioctl(fd, LED_ON, 0);  // 打开第0号LED
ioctl(fd, LED_OFF, 0); // 关闭第0号LED

三、参考链接

字符设备:
Linux 驱动开发 三:字符设备驱动框架

杂项设备:
misc_register 杂项设备

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值