1 硬驱动带来的问题
仅devfs,导致开发不方便以及一些功能难以支持:
- 热插拔
- 不支持一些针对所有设备的统一操作(如电源管理)
- 不能自动mknod
- 用户查看不了设备信息
- 设备信息硬编码,导致驱动代码通用性差,即没有分离设备和驱动
2 新方案
uevent机制:sysfs + uevent + udevd(上层app)
2.1 sysfs
一种用内存模拟的文件系统,系统启动时mount到/sys目录
sysfs用途:(类似于windows的设备管理器)
- 建立系统中总线、驱动、设备三者之间的桥梁
- 向用户空间展示内核中各种设备的拓扑图
- 提供给用户空间对设备获取信息和操作的接口,部分取代ioctl功能
sysfs在内核中的组成要素 | 在用户空间/sys下的显示 |
---|---|
对象属性(attribute) | 文件 |
内核对象(kobject) | 目录 |
对象关系(relationship) | 链接(Symbolic Link) |
2.2 四个基本结构
类型 | 所包含的内容 | 内核数据结构 | 对应/sys项 |
---|---|---|---|
设备(Devices) | 设备是此模型中最基本的类型,以设备本身的连接按层次组织 | struct device | sys/devices/?/?/…/ |
驱动(Drivers) | 在一个系统中安装多个相同设备,只需要一份驱动程序的支持 | struct device_driver | /sys/bus/pci/drivers/?/ |
总线(Bus) | 在整个总线级别对此总线上连接的所有设备进行管理 | struct bus_type | /sys/bus/?/ |
类别(Classes) | 这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在/sys/class/input/下 | struct class | /sys/class/?/ |
2.3 目录组织结构
/sys下的子目录 | 所包含的内容 |
---|---|
/sys/devices | 这是内核对系统中所有设备的分层次表达模型,也是/sys文件系统管理设备的最重要的目录结构; |
/sys/dev | 这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件 |
/sys/bus | 这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成Linux 统一设备模型的一部分; |
/sys/class | 这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在/sys/class/input 之下,而不论它们是以何种总线连接到系统。它也是构成 Linux 统一设备模型的一部分 |
//sys/kernel | 这里是内核所有可调整参数的位置,目前只有uevent_helper, kexec_loaded, mm, 和新式的slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于sysctl(/proc/sys/kernel) 接口中; |
/sys/module | 这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在/sys/module 中 |
/sys/power | 这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。 |
3 相关操作函数
/*
* 功能:在/sys/class生成一个目录,目录名由name指定
* 参数:
struct module *owner - THIS_MODULE
const char *name - 目录名
* 返回值 成功:class指针 失败:NULL
*/
/*
辅助接口:可以定义一个struct class 的指针变量cls来接受返回值,然后通过IS_ERR(cls)判断是否失败;
IS_ERR(cls);成功----------------->0
IS_ERR(cls);失败----------------->非0
PTR_ERR(cls);来获得失败的返回错误码;
*/
struct class *class_create(struct module *owner, const char *name);
/*
* 功能:删除class_create生成目录
* 参数:
struct class *cls - class指针
* 返回值
*/
void class_destroy(struct class *cls)
/*
* 功能:在/sys/class目录下class_create生成目录再生成一个子目录与该设备相对应,发uevent让应用程序udevd创建设备文件
* 参数:
struct class *class - class指针
struct device *parent - 父对象,一般NULL
dev_t devt - 设备号
void *drvdata - 驱动私有数据,一般NULL
const char *fmt - 字符串的格式
... - 不定参数
* 返回值
成功:device指针
失败:NULL
*/
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
/*
* 功能:删除device_create生成目录
* 参数:
struct class *class - class指针
dev_t devt - 设备号
* 返回值
*/
void device_destroy(struct class *class, dev_t devt);
4 ##### 代码中自动mknod步骤
1 包含头文件#include"<linux/device.h>"
2 定义两个指针
struct class *pcls;
struct device *pdev;
在init函数里
3 调用 class_create(…)函数
4 调用 device_create(…)函数
在exit中调用
device_destroy();
class_destroy();
5 ##### 参考代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/atomic.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/device.h>
#include "myled.h"
#define myled_DEV_CNT 1
int major =11;//主设备号
int minor = 0;//次设备号
int myled_num =myled_DEV_CNT;//设备数量
struct myled_dev
{
struct cdev mydev;//建立一个设备对象
//volatile为不做优化
volatile unsigned long * led2con;
volatile unsigned long * led2dat;
volatile unsigned long * led3con;
volatile unsigned long * led3dat;
volatile unsigned long * led4con;
volatile unsigned long * led4dat;
volatile unsigned long * led5con;
volatile unsigned long * led5dat;
struct class *pcls;
struct device *pdev;
};
struct myled_dev *gmydev_arr[myled_DEV_CNT];
//打开
int myled_open(struct inode *pnode,struct file *pfile)
{
/*为避免使用全局变量*/
/*
*container_of是一个宏,可以用来求全局结构体变量的地址
*已知其成员的地址和成员名字,就可以求出其地址
*pnode->i_cdev:指向插入系统的设备对象(这里也是gmydev的成员mydev的地址)
*pnode->private_data:本次打开文件的私有数据,驱动中常来在几个操作函数间传递共用数据
*/
pfile->private_data=(void *)(container_of(pnode->i_cdev,struct myled_dev,mydev));
return 0;
}
//关闭
int myled_close(struct inode *pnode,struct file *pfile)
{
return 0;
}
void led_on(struct myled_dev * pmydev,int ledno)
{
switch(ledno)
{
case 2:
printk("2\n");
writel(readl(pmydev->led2dat) | (0x1<<7),pmydev->led2dat);//关闭led2
break;
case 3:
printk("3\n");
writel(readl(pmydev->led3dat) | (0x1<<0),pmydev->led3dat);//关闭led2
break;
case 4:
printk("4\n");
writel(readl(pmydev->led4dat) | (0x1<<4),pmydev->led4dat);//关闭led2
break;
case 5:
printk("5\n");
writel(readl(pmydev->led5dat) | (0x1<<5),pmydev->led5dat);//关闭led2
break;
}
}
void led_off(struct myled_dev * pmydev,int ledno)
{
switch(ledno)
{
case 2:
writel(readl(pmydev->led2dat) & (~(1<<7)),pmydev->led2dat);//关闭led2
break;
case 3:
writel(readl(pmydev->led3dat) & (~(1<<0)),pmydev->led3dat);//关闭led3
break;
case 4:
writel(readl(pmydev->led4dat) & (~(1<<4)),pmydev->led4dat);//关闭led4
break;
case 5:
writel(readl(pmydev->led5dat) & (~(1<<5)),pmydev->led5dat);//关闭led5
break;
}
}
long myled_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)
{
struct myled_dev * pmydev = (struct myled_dev*)pfile->private_data;
if(arg<2||arg>5)
{
return -1;
}
switch(cmd)
{
case MY_LED_ON:
led_on(pmydev,arg);
break;
case MY_LED_OFF:
led_off(pmydev,arg);
break;
default:
return -1;
}
return 0;
}
struct file_operations myops={
.owner=THIS_MODULE,
.open=myled_open,
.unlocked_ioctl=myled_ioctl,
.release =myled_close,
};
/**
* @brief 将寄存器物理地址映射为虚拟地址
* @param[in] pmydev:设备对象结构体
* @retval NULL
*
*/
void ioremap_ledreg(struct myled_dev *pmydev)
{
/*得到虚拟内存地址*/
pmydev->led2con=ioremap(GPX2CON,4);
pmydev->led2dat=ioremap(GPX2DAT,4);
pmydev->led3con=ioremap(GPX1CON,4);
pmydev->led3dat=ioremap(GPX1DAT,4);
pmydev->led4con=ioremap(GPF3CON,4);
pmydev->led4dat=ioremap(GPF3DAT,4);
pmydev->led5con=pmydev->led4con;
pmydev->led5dat=pmydev->led4dat;
}
/**
* @brief 将寄存器物理地址映射为虚拟地址
* @param[in] pmydev:设备对象结构体
* @retval NULL
*
*/
void set_output_ledconreg(struct myled_dev *pmydev)
{
writel((readl(pmydev->led2con) & (~(0xF<<28))) | (0x1<<28),pmydev->led2con);//设置GPX2_7为输出模式
writel((readl(pmydev->led3con) & (~(0xF<<0))) | (0x1<<0),pmydev->led3con);//设置GPX1_0为输出模式
writel((readl(pmydev->led4con) & (~(0xF<<16))) | (0x1<<16),pmydev->led4con);//设置GPF3_4为输出模式
writel((readl(pmydev->led5con) & (~(0xF<<20))) | (0x1<<20),pmydev->led5con);//设置GPF3_5为输出模式
writel(readl(pmydev->led2dat) & (~(1<<7)),pmydev->led2dat);//关闭led2
writel(readl(pmydev->led3dat) & (~(1<<0)),pmydev->led3dat);//关闭led3
writel(readl(pmydev->led4dat) & (~(1<<4)),pmydev->led4dat);//关闭led4
writel(readl(pmydev->led5dat) & (~(1<<5)),pmydev->led5dat);//关闭led5
}
/**
* @brief 解除寄存器物理地址和虚拟地址映射
* @param[in] pmydev:设备对象结构体
* @retval NULL
*
*/
void iounremap_ledreg(struct myled_dev *pmydev)
{
iounmap(pmydev->led2con);
pmydev->led2con=NULL;
iounmap(pmydev->led2dat);
pmydev->led2dat=NULL;
iounmap(pmydev->led3con);
pmydev->led3con=NULL;
iounmap(pmydev->led3dat);
pmydev->led3dat=NULL;
iounmap(pmydev->led4con);
pmydev->led4con=NULL;
iounmap(pmydev->led4dat);
pmydev->led4dat=NULL;
pmydev->led5con=NULL;
pmydev->led5dat=NULL;
}
//初始化
int __init myled_init(void)
{
int ret = 0;
int i = 0;
dev_t devno =MKDEV(major,minor);//组合成一个完整的设备号
/*
* 手动申请设备号
*int register_chrdev_region(dev_t from, unsigned count, const char *name)
*参数分别为 设备号,设备数量,设备名字
*/
ret=register_chrdev_region(devno,myled_num,"myled");
if(ret)
{
/*
*自动申请设备号
*int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count, const char *name)
* dev:分配设备号成功后用来存放分配到的设备号
* baseminior:起始的次设备号,一般为0
* count:申请的设备数量
* name:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号
*/
ret = alloc_chrdev_region(&devno,minor,myled_num,"myled");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//分离主设备号(手动申请失败需要)
}
for(i=0;i<myled_DEV_CNT;i++)
{
gmydev_arr[i]=(struct myled_dev*)kmalloc(sizeof(struct myled_dev),GFP_KERNEL);
if(NULL==gmydev_arr[i])
{
printk("kmalloc failed\n");
unregister_chrdev_region(devno,myled_num);//注销设备号
return -1;
}
memset(gmydev_arr[i],0,sizeof(struct myled_dev));//初始化
}
/*指定操作函数集*/
for(i=0;i<myled_DEV_CNT;i++)
{
devno =MKDEV(major,minor+i);
cdev_init(&gmydev_arr[i]->mydev,&myops);//初始化设备对象
/*将struct_cdev对象添加到内核对应的数据结构里*/
gmydev_arr[i]->mydev.owner=THIS_MODULE;
cdev_add(&gmydev_arr[i]->mydev,devno,1);//添加设备
/*ioremap*/
ioremap_ledreg(gmydev_arr[i]);//映射地址
set_output_ledconreg(gmydev_arr[i]);//led初始化
/*con_register set output*/
gmydev_arr[i]->pcls=class_create(THIS_MODULE,"myled");
if(IS_ERR(gmydev_arr[i]->pcls))
{
printk("class_create failed\n");
cdev_del(&gmydev_arr[i]->mydev);
iounremap_ledreg(gmydev_arr[i]);//取消映射
kfree(gmydev_arr[i]);
unregister_chrdev_region(devno,myled_num);//注销设备号
gmydev_arr[i]=NULL;
return -1;
}
gmydev_arr[i]->pdev=device_create(gmydev_arr[i]->pcls,NULL,devno,"NULL","led");
if(NULL==gmydev_arr[i]->pdev)
{
printk("device_create failed\n");
cdev_del(&gmydev_arr[i]->mydev);
iounremap_ledreg(gmydev_arr[i]);//取消映射
kfree(gmydev_arr[i]);
unregister_chrdev_region(devno,myled_num);//注销设备号
gmydev_arr[i]=NULL;
class_destroy(gmydev_arr[i]->pcls);
return -1;
}
}
return 0;
}
void __exit myled_exit(void)
{
dev_t devno = MKDEV(major,minor);
int i=0;
/*iounremap*/
for(i=0;i<myled_DEV_CNT;i++)
{
iounremap_ledreg(gmydev_arr[i]);//取消映射
device_destroy(gmydev_arr[i]->pcls,devno);
class_destroy(gmydev_arr[i]->pcls);
cdev_del(&gmydev_arr[i]->mydev);
}
for(i=0;i<myled_DEV_CNT;i++)
{
kfree(gmydev_arr[i]);
gmydev_arr[i]=NULL;
}
unregister_chrdev_region(devno,myled_num);//注销设备号
}
MODULE_LICENSE("GPL");
module_init(myled_init);
module_exit(myled_exit);