linux 驱动开发之搞懂杂项设备(1)

前言

        Linux中将设备分为三大类:字符设备(I2C、USB、SPI等)、块设备(存储器相关的设备如EMMC、SD卡、U盘等)和网络设备(网络相关的设备WIFI等)。

        杂项设备归属于字符设备,每个设备节点都有主设备号次设备号 ,设备号是识别设备的一种方式,Linux系统中有很多杂项设备,而杂项设备的主设备号固定为10。                                   使用命令<cat /proc/misc>可以查看各杂项设备。

 

相关文件

杂项(misc)设备相关文件:kernel/include/linux/miscdevice.h

misc设备结构体miscdevice,其中minor、name、fops需要自己配置。

struct miscdevice  {
    int minor;      //次设备号
    const char *name;   //设备名
    const struct file_operations *fops;   //文件操作集
    struct list_head list;
    struct device *parent;
    struct device *this_device;
    const struct attribute_group **groups;
    const char *nodename;
    umode_t mode;
};

misc设备需要配置次设备号,而次设备号不能配置成系统中已经使用过的,为了便捷可以将minor配置为MISC_DYNAMIC_MINOR(定义在miscdevice.h中),此时加载该设备时系统会自动设置次设备号。

#define PSMOUSE_MINOR       1
#define MS_BUSMOUSE_MINOR   2   /* unused */
#define ATIXL_BUSMOUSE_MINOR    3   /* unused */
/*#define AMIGAMOUSE_MINOR  4   FIXME OBSOLETE */
#define ATARIMOUSE_MINOR    5   /* unused */
#define SUN_MOUSE_MINOR     6   /* unused */
。
。
。
#define UHID_MINOR      239
#define USERIO_MINOR        240
#define MISC_DYNAMIC_MINOR  255

misc_register注册misc设备函数: 

原型:int misc_register(struct miscdevice * misc)
参数:misc 之前创建好的miscdevice 结构体
返回值:成功返回0,失败返回负数。

misc_deregister注销misc函数:

原型:int misc_deregister(struct miscdevice *misc)
参数:misc 要注销的miscdevice 结构体。
返回值:无。

注册设备三步骤

① 填充miscdevice结构体;

② 填充file_operations结构体;

③ 调用注册、卸载函数注册、卸载设备节点。

1 驱动编写详解

1.1  头文件调用

#include <linux/module.h>   //模块相关
#include <linux/init.h>
#include <linux/fs.h>  //文件操作
#include <linux/miscdevice.h> 
#include <linux/uaccess.h>   //copy kernel or user fuction

1.2  结构体填充

        在file_operations结构体中,owner默认配置成THIS_MODULE,open、read、write、release函数是在linux应用程序中定义的函数,运行在用户空间,misc_open、misc_read、misc_write、misc_release在驱动程序的定义,运行在内核空间,应用程序中的函数通过系统调用执行驱动函数。

        在miscdevice结构体中,配置次设备号minor、设备节点名name和文件操作函数集配置;file_operations 中的成员函数实际是由drivers/char/misc.c 中misc 驱动核心层的misc_fops 成员函数间接调用的。

struct file_operations misc_fops=
{
	.owner = THIS_MODULE,
	.open = misc_open,
	.release = misc_release,
	.read = misc_read,
	.write = misc_write,
};

struct miscdevice misc_dev = 
{
	.minor = MISC_DYNAMIC_MINOR,
	.name = "qurry_misc",
	.fops = &misc_fops,
};

1.3  file操作函数集

驱动程序中,misc_open和misc_release打开、释放设备函数中不需要实现复杂的操作;应用程序中调用read函数读取设备节点信息,misc_read函数中copy_to_user()将内核空间信息复制到用户空间供read读取;应用程序中调用write函数写入信息到设备节点,misc_write函数中copy_from_user()将用户空间信息复制到内核空间。

static char writebuf[64];
const char kernel_data[]="this is kernel data.";

int misc_open(struct inode *inode,struct file *file)
{
    printk("misc_open ok.\r\n");
    return 0;
}

ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    if(copy_to_user(ubuf, kernel_data, sizeof(kernel_data)) != 0){
        printk("copy_to_user fail!\r\n");
        return -1;
    }
    else{
        printk("copy_to_user succeed.\r\n");
    }
    printk("misc_read ok.\r\n");
    return 0;
}

ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
    if(copy_from_user(writebuf, ubuf, size) != 0){
        printk("copy_from_user fail!\r\n");
        return -1;
    }
    else{
        printk("copy_from_user data:%s\r\n",writebuf);
    }
    printk("misc_write ok.\r\n");
    return 0;
}

int misc_release(struct inode *inode,struct file *file)
{
    printk("misc_release over.\r\n");
    return 0;
}

1.4  注册、卸载设备

        misc_init和misc_exit是驱动的入、出口函数,调用module_init、module_exit来声明它们。  调用misc_register、misc_deregister函数对杂项设备注册和卸载。

(注意:因为驱动程序是运行在内核空间的,所以调试打印使用printk)。

static int misc_init(void)
{
    if(misc_register(&misc_dev) < 0){
        printk("misc_register failed!\r\n");
    }
    else{
        printk("misc_register succeed.\r\n");
    }
}

static void misc_exit(void)
{
    misc_deregister(&misc_dev);
    printk("misc_exit.\r\n");
}

module_init(misc_init);
module_exit(misc_exit);

1.5 其它

        写完基本驱动后还需要使用MODULE_LICENSE()注明协议,否则可能会报错,也可以使用MODULE_AUTHOR()标明作者。

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Qurry");

2  驱动程序编译成模块

        将驱动程序编译成模块之前,需要先将linux源码编译通过。可以在对应驱动程序的目录下编写makefile,使用make命令将其编译,编译成功会生成 .ko 文件。

2.1 Makefile模板

obj-m += miscdev.o     #先写生成的中间文件的名字是什么,-m 的意思是把我们的驱动编译成模块
KDIR:=/home/workspace/linux_sdk/kernel   #内核源码目录
PWD?=$(shell pwd)         #获取当前目录路径的变量
all:
    make -C $(KDIR) M=$(PWD) modules 
#make 会进入内核源码的路径,然后把当前路径下的代码编译成模块

3  编写应用程序

        测试程序主要目的就是对前面注册的设备节点进行读写操作。

通过main函数传参,argv[1] 传递要打开的设备节点文件,argv[2] 传递读写操作,‘0’为读取文件信息,‘1’为向设备文件写入信息。最后将其编译成可以在目标板上执行的文件即可。

char *read_buf[64];
const char *user_data = "this is user data.";

int main(int argc,char *argv[])
{
    int fd = -1;
    char *filename = argv[1];
    fd = open(filename,O_RDWR);
    if(fd < 0){
        printf("open device file failed!\r\n");
        return -1;
    }

    if(atoi(argv[2]) ==0){
        read(fd,read_buf,sizeof(read_buf));
        printf("read_buf:%s\r\n",read_buf);
    }
    else if(atoi(argv[2]) == 1){
        write(fd,user_data,sizeof(user_data));
    }
    else{
        printf("error!\r\n");
    }

    return 0;
}

4 测试

        测试文件通过nfs或者U盘拷贝到目标板上,使用u盘的话要先mount挂载u盘。

        加载模块命令:insmod 模块名.ko

        查看模块是否加载成功命令:lsmod

        执行测试程序:./程序名   /dev/设备节点名   0(或者1)

        卸载模块命令:rmmod  模块名.ko

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值