历史上的今天
返回首页

历史上的今天

今天是:2025年01月17日(星期五)

正在发生

2019年01月17日 | 2416开发记录五: misc设备驱动

2019-01-17 来源:eefocus

对于linux的驱动程序来说,主要分为三种:miscdevice、platform_device、platform_driver 。


这三个结构体关系: 

(基类) 

kobject ——————– 

/ \ \ 

/ \ \ 

device cdev driver 

/ \ (设备驱动操作方法) \ 

/ \ \ 

miscdevice platform_device platform_driver 

(设备驱动操作方法) (设备的资源) (设备驱动)


这时,我们先不讨论这几个间的关系与驱别,对于新手来说,上手最重要!


首先我们先看看混杂项:


在Linux驱动中把无法归类的五花八门的设备定义为混杂设备(用miscdevice结构体表述)。miscdevice共享一个主设备号MISC_MAJOR(即10),但次设备号不同。 所有的miscdevice设备形成了一个链表,对设备访问时内核根据次设备号查找对应的miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。 在内核中用struct miscdevice表示miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。miscdevice的API实现在drivers/char/misc.c中。


第二,我们再看看混杂项设备驱动的程序组织架构:


新建一个first_led.c,先可能用到的头文件都引用上吧!


#include

#include

#include

#include

#include

#include

#include

#include

#include

#include


//对应着相应机器平台的头文件

#include

#include

#include



//给自己设备驱动定义一个名字


#define DEVICE_NAME "First_led"



名字有了,但样子是怎样的呢?现在就开始定义一个“样子”!


如果一个字符设备驱动要驱动多个设备,那么它就不应该用misc设备来实现。


通常情况下,一个字符设备都不得不在初始化的过程中进行下面的步骤:


通过alloc_chrdev_region()分配主次设备号。


使用cdev_init()和cdev_add()来以一个字符设备注册自己。


而一个misc驱动,则可以只用一个调用misc_register()


来完成这所有的步骤。(所以miscdevice是一种特殊的chrdev字符设备驱动)


所有的miscdevice设备形成一个链表,对设备访问时,内核根据次设备号查找


对应的miscdevice设备,然后调用其file_operations中注册的文件操作方法进行操作。


在Linux内核中,使用struct miscdevice来表示miscdevice。这个结构体的定义为:


struct miscdevice  


{


int minor;


const char *name;


const struct file_operations *fops;


struct list_head list;


struct device *parent;


struct device *this_device;


const char *nodename;


mode_t mode;


};


minor是这个混杂设备的次设备号,若由系统自动配置,则可以设置为


MISC_DYNANIC_MINOR,name是设备名


为了容易理解,我们先打大概的“样子”做好。只做minor、name、fops;


定义一个myfirst_led_dev设备:


static struct miscdevice myfirst_led_dev = {

    .minor          =    MISC_DYNAMIC_MINOR,

    .name           =    DEVICE_NAME,

    .fops               =    &myfirst_led_dev_fops,

};



Minor name 都已经定义好了。那么接下来实现一下myfirst_led_dev_fops方法。


内核中关于file_operations的结构体如下:


struct file_operations {


struct module *owner;


loff_t (*llseek) (struct file *, loff_t, int);


ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);


ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);


ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);


ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);


int (*readdir) (struct file *, void *, filldir_t);


unsigned int (*poll) (struct file *, struct poll_table_struct *);


long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);


long (*compat_ioctl) (struct file *, unsigned int, unsigned long);


int (*mmap) (struct file *, struct vm_area_struct *);


int (*open) (struct inode *, struct file *);


int (*flush) (struct file *, fl_owner_t id);


int (*release) (struct inode *, struct file *);


int (*fsync) (struct file *, int datasync);


int (*aio_fsync) (struct kiocb *, int datasync);


int (*fasync) (int, struct file *, int);


int (*lock) (struct file *, int, struct file_lock *);


ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);


unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);


int (*check_flags)(int);


int (*flock) (struct file *, int, struct file_lock *);


ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);


ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);


int (*setlease)(struct file *, long, struct file_lock **);


long (*fallocate)(struct file *file, int mode, loff_t offset,


  loff_t len);


};



对于LED的操作,只需要简单实现io操作就可以了,所以只实现


long (unlocked_ioctl) (struct file , unsigned int, unsigned long);


(该函数是在linux2.6.5以后才出现在设备的操作方法中的。)


函数参数为文件节点、命令、参数


static struct file_operations myfirst_led_dev_fops = { 

.owner = THIS_MODULE, 

.unlocked_ioctl = myfirst_led_ioctl, 

};


到了这里,我们就考虑一下LED的物理层面是怎样的实现了,通过开发板的引脚我们可以知道,四个LED是分别接到了GPJ2的0~3号管脚上。因此,我们定义一个数组来引用这几个管脚(当然不能像祼机那样对IO的物理地址进行操作了,是需要经过内核的内存映射得来的IO内存操作!而内核把ARM的IO管脚地址按一个线性地址进行了编排)


static int led_gpios[] = {

    S5PV210_GPJ2(0),

    S5PV210_GPJ2(1),

    S5PV210_GPJ2(2),

    S5PV210_GPJ2(3),

};

#define LED_NUM ARRAY_SIZE(led_gpios)//判断led_gpio有多少个


S5PV210_GPJ2(*)的定义如下


#define S5PV210_GPJ2(_nr)  (S5PV210_GPIO_J2_START + (_nr))





enum s5p_gpio_number {


S5PV210_GPIO_A0_START = 0,


...................................


S5PV210_GPIO_J2_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_J1),


.....................................


}



#define S5PV210_GPIO_NEXT(__gpio) \


((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)




(注:##是粘贴运算,具体用法请自行找度娘或谷哥)



给用户空间的接口操作:


static long myfirst_led_ioctl(struct file *filp, unsigned int cmd,

        unsigned long arg)

{

    switch(cmd) {

        case 0:

        case 1:

            if (arg > LED_NUM) {

                return -EINVAL;//判读用户的参数是否有误

            }


            gpio_set_value(led_gpios[arg], !cmd);//用户选定的LED并设置值

            //printk(DEVICE_NAME": %d %d\n", arg, cmd);

            break;

        default:

            return -EINVAL;

    }

    return 0;

}



对于gpio_set_value(unsigned int gpio, int value),内核有以下定义:


static inline void gpio_set_value(unsigned int gpio, int value)


{


__gpio_set_value(gpio, value);


}





void __gpio_set_value(unsigned gpio, int value)


{


struct gpio_chip *chip;




chip = gpio_to_chip(gpio);


WARN_ON(chip->can_sleep);


trace_gpio_value(gpio, 0, value);


chip->set(chip, gpio - chip->base, value);


}//到这里我们就不再分析下去了 ,无非就是判定是哪一个芯片



程序写到这里,对于用户空间来说,已经有了完整的操作方法接口,但对于内核模块来说,还缺少驱动模块的进入与退出。以下接着写驱动模块的初始化(即进入)和退出。


static int __init myfirst_led_dev_init(void) {;}


static void __exit myfirst_led_dev_exit(void) {;}


函数如上。双下划线表示模块在内核启动和关闭时自动运行和退出


对于驱动模块的初始化函数,要写些什么呢?我们这样考虑:


对于用户空间接口来说,我们的实现函数只是给出了IO的值设置的,但是ARM的IO管脚使用还是需要配置方向、上拉下拉…..才能正常使用的,并且所有的硬件资源,都是受内核所支配的,驱动程序必需向内核申请硬件资源才能对硬件进行操作。另外还需要对设备进行注册,内核才知道你这个设备是什么东东,用到哪些东西。这些操作,我们安排在init里实现!


static int __init myfirst_led_dev_init(void) 

{

    int ret;

    int i;


    for (i = 0; i < LED_NUM; i++) 

  {

        ret = gpio_request(led_gpios[i], "LED");//申请IO引脚

        if (ret) {

                printk("%s: request GPIO %d for LED failed, ret = %d\n", DEVICE_NAME,

                    led_gpios[i], ret);

                return ret;

                }

        s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);

        gpio_set_value(led_gpios[i], 1);

    }

    ret = misc_register(&myfirst_led_dev);

    printk(DEVICE_NAME"\tinitialized\n");

    return ret;

}


pio_request(unsigned gpio, const char *label)


gpio则为你要申请的哪一个管脚,label为其名字 。


int s3c_gpio_cfgpin(unsigned int pin, unsigned int config);


对芯片进行判断,并设置引脚的方向。


ret = misc_register(&myfirst_led_dev);.


该函数中、内核会自动为你的设备创建一个设备节点


对设备进行注册


到这里,设备的初始化与注册已经完成!


当用户不再需要该驱动资源时,我们必需在驱动模块中,对占用内核的资源进行主动的释放!


因此在驱动模块退出时,完成这些工作!


static void __exit myfirst_led_dev_exit(void) {

    int i;


    for (i = 0; i < LED_NUM; i++) {

        gpio_free(led_gpios[i]);

    }


    misc_deregister(&myfirst_led_dev);

}


gpio_free(led_gpios[i]);


释放IO资源


misc_deregister(&myfirst_led_dev);


注销设备


还需要模块的初始化与退出函数声明


module_init(myfirst_led_dev_init);


module_exit(myfirst_led_dev_init);


最后,为了保持内核驱动模块的风格,我们还要加上相应的许可跟作者


MODULE_LICENSE(“GPL”);


MODULE_AUTHOR(“jjvip136@163.com”);


推荐阅读

史海拾趣

Global Navigation Systems公司的发展小趣事
安装远程无线防盗报警系统时,首先需要将入侵探测器安装在需要防范的门窗等位置,并确保其能够准确检测到异常情况。然后,将微型无线报警发射机安装在探测器附近,并确保其能够接收到探测器的信号并发射出去。最后,将无线报警接收控制器安装在便于监控和接收报警信息的位置,并进行相应的调试和设置。在安装过程中,需要注意避免电磁干扰和信号衰减等问题,确保系统的稳定性和可靠性。
Eris Technology Corp公司的发展小趣事

进入21世纪后,汽车电子市场蓬勃发展,为Eris Tech提供了新的发展机遇。公司凭借在半导体技术方面的积累,成功开发出多款适用于汽车电子系统的半导体产品,如功率管理芯片、传感器等。这些产品不仅提高了汽车电子系统的性能,还降低了成本,赢得了汽车厂商的广泛认可。随着汽车电子市场的不断扩大,Eris Tech的营收也实现了快速增长。

Bkc Semiconductors Inc公司的发展小趣事

随着技术的成熟和产品的不断完善,Bkc开始积极拓展市场。公司不仅加强了与国内外知名电子厂商的合作,还积极参与国际半导体展会和技术交流,不断提升品牌知名度。同时,Bkc还通过战略投资和并购等方式,进一步扩大了自身的业务规模和市场份额。

振华(CEC)公司的发展小趣事

为了进一步提升企业的竞争力和市场份额,振华积极实施国际化战略。公司加强与国外企业的合作与交流,积极参与国际市场竞争,通过引进外资、设立海外研发机构等方式,不断拓展海外市场。同时,振华还注重提升产品的国际竞争力,加强与国际标准的对接和认证工作,确保产品能够满足不同国家和地区的市场需求。

辉芒微(FMD)公司的发展小趣事

辉芒微(FMD)成立于2005年6月,作为一家新兴的芯片设计企业,其成立之初便专注于EEPROM(电可擦除可编程只读存储芯片)的研发与生产。在成立的同一年,辉芒微便成功实现了EEPROM芯片的量产销售,这一里程碑式的成就为公司后续的快速发展奠定了坚实的基础。随着技术的不断积累和市场的持续拓展,辉芒微在集成电路设计领域逐渐崭露头角。

ebm-papst公司的发展小趣事

为了更好地服务中国市场,ebm-papst在中国不断推进本地化生产。公司在上海和西安设立了研发中心和生产基地,配备了与德国同样先进的实验设备。这些本地化生产和研发设施使得ebm-papst能够更快地响应中国市场的需求,提供更符合当地市场特点的产品和解决方案。同时,本地化生产也降低了公司的生产成本,提高了产品的竞争力。

问答坊 | AI 解惑

综合布线相关术语

综合布线相关术语 术语或符号 英文名 中文名或解释 ACR Attenuation to Crosstalk Ratio 衷减—串音衰减比率 ADU Asynchronous Data Unit 异步数据单元 ATM Asynchronous Transfer Mode 异步传输模式 BA Building Autom ...…

查看全部问答>

各路高手进来看看

在测运放的失调电流时,接下面的电路很稳定,但是此时在第一个运放的同相或反相端接根导线就自激了,不懂。。。。求救…

查看全部问答>

打破现实与虚幻隔阂的神奇第六感装置

虽然这个视频已经在网上流传已久,但是以截图的方式展示出来还是第一次,来秀秀 想像一下,不用掏出相机,只需用手摆出照相的POSE,就会有咔嚓一声将照片拍下来。然后随意找一面墙,浏览刚刚拍下的照片,还能对照片进行拖拽、缩放等编辑处理。伸 ...…

查看全部问答>

求助HT单片机uart的问题

请问大家有没有用过HT单片机的UART口,有什么例程或者源代码吗?  麻烦发我邮箱fjchen_ok@126.com 现在在做用HT单片机UART口控制GPRS模块发送短信的,用51单片机作的已经是可以的了,但是改为HT出现了问题,GPRS返回的值总是00H,不知道 ...…

查看全部问答>

protel dxp信号层管理问题

在使用Protel DXP管理PCB层时,发现,在使用向导或模板生成的PCB中,除了top和buttom两个层外,不管怎么设置,总是至少会出现8个MidLayer层,而且删除不掉,总是说在使用中,不能删除,但是internalplane层就不会出现这种情况,不知道问题出在什么 ...…

查看全部问答>

ReadProcessMemory读内内存失败

我用OpenProcess 获得了all 的权限 为什么还是失败呢。我用同样的方法换成计算器进程就会成功。 是不是那个进程里面动了什么手脚。.…

查看全部问答>

RMB 急求GIF降低颜色数和相同区域透明化压缩 DLL或COM组件

RMB 急求GIF降低颜色数和相同区域透明化压缩 (DLL或COM组件) 要求:GIF有5桢左右,每桢一张图,要求能实现相同区域透明化;       能把256色的GIF 压缩为24色或8色的GIF;       开发成组件。 联系Q ...…

查看全部问答>

交流基础器知识普及

交流接触器利用主接点来开闭电路,用辅助接点来执行控制指令。 主接点一般只有color=#0000ff>常开接点,而辅助接点常有两对具有常开和常闭功能的接点,小型的接触器也经常作为中间继电器配合主电路使用。交流接触器的接点,由银钨合金制成,具有良 ...…

查看全部问答>