历史上的今天
返回首页

历史上的今天

今天是:2025年03月10日(星期一)

正在发生

2020年03月10日 | Linux内核模块驱动之---led驱动

2020-03-10 来源:eefocus

开发板:tiny6410


内核:linux2.6.38


要写led驱动首先要复习的是混杂设备驱动的设计流程、 内核file_ops结构体、硬件通信IO端口和IO内存、 ioctl系统调用的实现步骤和方法


按照顺序来对已经学习的内容复习 并把led模块驱动写出来


第一步:复习ioctl系统调用函数


一、什么是ioctl系统调用函数,驱动中的iodtl函数是什么样子的?为什么需要ioctl函数?》


虽然file_ops结构体提供了相当多的文件操作函数,但是要想对硬件操作,这个file_ops结构体无法完成了,所以操作系统又提供了另一个对硬件操作的函数,就是ioctl,ioctl分为在应用层 和 在驱动层。ioctl应用层的函数传递命令,从用户态将命令传递到内核态,调用驱动的ioctl函数来实现对硬件的操作。


上图::从网上找的




用户空间中的ioctl 和 驱动的ioctl有点不一样


用户空间的ioctl函数形式为:


int ioctl(int fd, unsigned long cmd,  ...);//参数的意义:::第一个代表:文件描述符,第二个代表命令 第三个为可选的参数


驱动中的ioctl从2.6.36开始改名称了-----改为unlocked_ioctl


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


参数意义:第一个 为用户空间传递下来的 文件指针,


第二个为参数的命令 cmd这个命令的定义和获取 接下来要详细的讲解一下


第三个为  用户空间传递 的 可选的参数


二、在写ioctl代码之前首先要搞懂如何定义命令,为了防止对错误的设备使用正确的命令,命令号应该在系统范围内是唯一的。。。


ioctl命令编码被划分为几个段,在include/asm/ioctl.h 中定义了这些字段:类型(幻数),基数,传送方向,参数大小等。在Documentation/ioctl-number.txt文件中 罗列了在内核中已经使用的幻数。


定义ioct命令的正确方法是 使用四个位段::


Type(8)-------------类型(幻数)表明是哪个设备的命令,要参考Documentation/ioctl-number.txt文档后钻出合适的命令


Number(8)----------序号,表明设备命令中的第几个,8位宽


Direction(2)--------数据传送的方向,可能的值是 ———_IOC_NONE(没有数据传输),_IOC_READ,_IOC_WRITE.数据的传送方向是从应用程序的观点来看待的,_IOC_READ意为从设备中读取数据


Size(8~14)----------用户数据的大小(8-14位宽,视处理器而定)。


上面是对一个命令所包含信息的解析,一个命令应该包含什么信息。


那么如何定义一个命令呢,内核提供了下列宏来定义命令


_IO(type,nr);//没有参数的命令


_IOR(type,nr,datatype);//从驱动中读数据


_IOW(type,nr,datatype);//向驱动中写入数据


_IOWR(type,nr,datatype);//双向传送


内核还定义了用来解开宏的字段


_IOC_DIR(nr);


_IOC_TYPE(nr);


_IOC_NR(nr);


_IOC_SIZE(nr); nr为要解析的命令


三、ioctl函数中如何实现命令


1、一般在ioctl函数的实现通常是根据命令执行的一个switch语句。但是当命令不能匹配任何一个设备所持有的命令的时候,通常返回-EINVAL.


2、ioctl函数的第三个参数arg的使用


如果arg是一个整数,那么可以直接使用,如果是指针,我们必须确保这个用户地址是有效的,因此使用前需要进行正确性检查,使用下面的函数对arg参数的正确性进行检查


int access_ok(int type, const void* addr, unsigned long size);//第一个是类型 参数为VERIFY_READ 或者VERIFY_WRITE,用来表明读用户内存 还是 写用户内存。addr参数是要操作的用户内存地址,size 是操作的长度。


如果需要同时读写 则使用参数VERIFY_WRITE。


access_ok()返回一个布尔类型的值:1 是成功(存取没问题) 0是失败(存取有问题)。



第二步:复习file_ops结构体

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 *);//对应select系统调用

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);

};

在这个结构体中并不是所有的函数都需要实现,只实现需要的函数即可。


第三部:复习硬件操作--IO内存和IO端口

真正要实现设备的操作时必须要设计到硬件的操作的,设备驱动程序是软件概念和硬件概念的一个抽象层,因此都要讨论。

每种外设都通过寄存器来进行控制。大部分外设都设有几个寄存器,不管是在内存地址,还是在IO地址空间,这些寄存器的访问地址都是连续的。

在硬件层,内存区域和IO区域没有概念上的区别:他们都是通过向地址总线和控制总线发送电平信号进行访问,再通过数据总线读写数据。

详细请参考下面的连接::

IO端口、IO内存详细解析


第四步:混杂设备驱动程序设计

在linux系统 中,存在一类字符设备,他们共享一个主设备号(10),但次设备号不同,我们称这类设备为混杂设备(miscdevice),所有混杂设备形成一个链表,对设备访问时内核根据次设备号查找到相应的miscdevice设备。

描述混杂设备的结构体:

struct miscdevice  {

int minor;//次设备号--一般是有设备自动分配的--MISC_DYNANIC_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;

};

如何使用混杂设备:

在初始化的时候注册 混杂设备就好

int misc_register(struct miscdevice * misc);

在模块卸载的时候将混杂设备注销:

misc_deregister(struct miscdevice *misc);


上LED模块驱动的例程:::

/**************************************************************************/

/***************************led.h**********************************************/

#ifndef _LED_H_

#define _LED_H_

#include

/*涉及到ioctl命令的定义方法的问题*/

#define DEVICE_NAME "tiny6410-led"

/**/

#define LED_IOC_MAGIC 'l'/*定义命令类型*/

/**/

#define LED_IOCGETDATA _IOR(LED_IOC_MAGIC,1,int)/*利用宏来辅助定义命令--定义从驱动中获得数据*/

#define LED_IOCSETDATA _IOW(LED_IOC_MAGIC,2,int)/*利用宏来辅助定义命令--定义向驱动中写入数据*/

#define LED_IOC_MAXNR 2

#endif

/**************************************************************************/

/***************************led.c**********************************************/

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

/*要了解各个头文件中所包含的函数*/

#include"led.h"

/*led对应GPIOK虚拟地址*/

unsigned long GPIOK_VA_BASE;

#define GPIOK_CON0_VA GPIOK_VA_BASE

#define GPIOK_CON1_VA GPIOK_VA_BASE+0x4

#define GPIOK_DAT_VA  GPIOK_VA_BASE+0x8

#define GPIO_PUD_VA   GPIOK_VA_BASE+0xc

/*led对应的物理地址*/

#define GPIOK_PA_BASE 0x7f008800

/*linux 采用resource描述挂接在cpu总线上的结构实体*/

struct resource tiny6410_led_resource =

{

.name  = "led io-mem",

.start = GPIOK_PA_BASE,

.end = GPIOK_PA_BASE + 0x10,

.flags = IORESOURCE_MEM, 

};

static void tiny6410_led_pin_setup(void)

{

unsigned long start = tiny6410_led_resource.start;

unsigned long size = tiny6410_led_resource.end - tiny6410_led_resource.start;

unsigned long tmp;


/*申请io内存*/

request_mem_region(start,size,tiny6410_led_resource.name);


/*映射io内存*/

GPIOK_VA_BASE = (unsigned long)ioremap(start,size);//映射的地址 由系统自动分配

printk("映射的虚拟地址 GPIOK_VA_BASE = 0x%lxn",GPIOK_VA_BASE);


/*对应管教设置为输出*/

tmp = readl(GPIOK_CON0_VA);

tmp = (tmp & ((0xffffU<<16)|(0x1111U<<16)));

writel(tmp,GPIOK_CON0_VA);


/*对应管脚置高--使led全灭*/

tmp = readl(GPIOK_DAT_VA);//将寄存器在内存中的虚拟地址的数据读出来

tmp |= (0xf<<4);

writel(tmp,GPIOK_DAT_VA);

}

/****************************************************/

static void tiny6410_led_pin_release(void)

{

/**首先要解除映射**/

iounmap((void*)GPIOK_VA_BASE);//参数是 映射到内存的地址

release_mem_region(tiny6410_led_resource.start,tiny6410_led_resource.end - tiny6410_led_resource.start);

}

/**************************************************************************************************/

static unsigned long tiny6410_led_getdata(void)

{

return ((readl(GPIOK_DAT_VA)>>4)&0xF);//返回虚拟io内存中寄存器的值

}

/*设置led对应的GPIO数据寄存器的值*/

static void tiny6410_led_setdata(int data)

{

unsigned long tmp;

tmp = readl(GPIOK_DAT_VA);

tmp = ~(0xF<<4) | ((data&0xF)<<4);

writel(tmp,GPIOK_DAT_VA);

}

/*****************************************************************************************************/

static long led_ioctl(struct file* filp,unsigned int cmd,unsigned long arg)

{

int ioarg,ret;

/*检测命令的有效性*/

if(_IOC_TYPE(cmd) != LED_IOC_MAGIC)//检测命令

return -EINVAL;

if(_IOC_NR(cmd) > LED_IOC_MAXNR)//检测命令号  如果大于最大的命令号

return -EINVAL;



/*根据命令来执行不同的操作*/


switch(cmd)  {

case LED_IOCGETDATA:{


ioarg = tiny6410_led_getdata();

ret = put_user(ioarg,(int*)arg);

break;

}

case LED_IOCSETDATA:{


ret = get_user(ioarg,(int*) arg);

tiny6410_led_setdata(ioarg);

break;

}

default:

return -EINVAL;

}


return ret;

}

static ssize_t led_read(struct file* filp,char __user* buf,size_t size,loff_t *ppos)

{



}

/********************************************************************************************/

static struct file_operations dev_fops =

{

.owner = THIS_MODULE,

.unlocked_ioctl = led_ioctl,

.read = led_read,

};

static struct miscdevice misc = 

{

.minor = MISC_DYNAMIC_MINOR,//次设备号由系统动态分配

.name = DEVICE_NAME,

.fops = &dev_fops,//文件操作

};

/****************************************************************************************/

static int __init dev_init(void)

{

int ret;



tiny6410_led_pin_setup();//在板子初始化的时候 要将寄存器映射到内存中去

ret = misc_register(&misc);


printk("initialized minor =%dn",misc.minor);


return ret;

}

static void __exit dev_exit(void)

{

tiny6410_led_pin_release();

misc_deregister(&misc);//混杂字符设备驱动的解除

}

module_init(dev_init);

module_exit(dev_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("kong-hua-sheng");

/************************************************************************************/

/***********************************测试程序app-led.c************************************************/

#include

#include

#include

#include

#include

#include

#include

#include"led.h"

#define DEV_NAME "/dev/"DEVICE_NAME

/*将传送进来的字符转换为int型数据*/

int binstr_to_int(char* binstr)

{

int ret =0,i=0;

char bnum[5];


memset(bnum,'0',4);


int len = strlen(binstr);

if(len>4)

{

strcpy(bnum,binstr+len-4);

}else

{

strcpy(bnum+4-len,binstr);

}


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

{

ret <<=1;

ret +=(bnum[i]=='0'? 1:0);

}


return ret;

}

int main(int argc,char* argv[])

{

if(argc>2)

{

printf("can shu > 2 error!!n");

_exit(EXIT_FAILURE);

}


int fd,ioarg;

if(-1 == (fd = open(DEV_NAME,O_RDWR)))

{

ioarg = ioctl(fd,LED_IOCGETDATA,&ioarg);

printf("canshu wei 1 shi duqu shu ju! %d n",ioarg);

}else{

ioarg = binstr_to_int(argv[1]);

ioctl(fd,LED_IOCSETDATA,&ioarg);

}


_exit(EXIT_SUCCESS);

}


/**********写好之后如何测试命令******************/

#arm-linux-gcc -o app_led app-led.c

#./app_led 1101 这样led就会根据你设置的数字来点亮对应的发光二极管

推荐阅读

史海拾趣

Bedford Opto公司的发展小趣事

近年来,电子行业经历了深刻的变革,新兴技术的不断涌现给Bedford Opto公司带来了前所未有的挑战。然而,公司凭借敏锐的市场洞察力和灵活的战略调整,成功应对了这些变革。通过加大研发投入,积极引进新技术,公司不断推出新产品,保持了市场竞争优势。

ETA Electric Industry Co Ltd公司的发展小趣事

在市场不断拓展的同时,ETA Electric Industry Co Ltd非常注重产品质量管理。他们引入了国际先进的质量管理体系,并严格执行每一项质量控制标准。公司还设立了专门的质量检测部门,对每一批出厂的产品进行严格把关。这种对质量的极致追求,赢得了客户的广泛认可和信赖。

CLAIREX公司的发展小趣事

随着市场的不断发展,Clairex意识到,要想在激烈的竞争中脱颖而出,必须不断创新。于是,公司加大了对研发的投入,积极探索新技术、新材料和新工艺。经过数年的努力,Clairex成功开发出了一系列具有自主知识产权的光电子组件。这些组件不仅性能优异,而且成本更低、更易于集成。它们的推出,不仅进一步巩固了Clairex在市场上的地位,也为公司带来了可观的利润。

Allen Avionics Inc公司的发展小趣事

随着订单的不断增加,Clairex意识到必须提升生产能力以满足市场需求。于是,公司投入大量资金引进了先进的生产设备和技术,并对生产线进行了优化改造。同时,Clairex还建立了严格的质量控制体系,从原材料采购到产品出厂的每一个环节都进行严格把关。这些措施确保了Clairex产品的质量和稳定性,赢得了客户的信任和好评。

Conflux公司的发展小趣事

Conflux深知人才是企业发展的核心动力。因此,公司一直注重人才培养和引进。通过与高校和研究机构的合作,Conflux吸引了大量优秀的科研人才加入公司。同时,公司还建立了完善的培训体系,不断提升员工的技能和素质。这些措施为公司的持续创新和发展提供了坚实的人才保障,也构筑了公司的核心竞争力。

BVLED公司的发展小趣事

为了进一步扩大市场份额和提升品牌影响力,BVLED公司开始积极拓展国内外市场。在国内,公司加强了与大型照明企业的合作,通过联合推广和定制服务等方式提高了产品知名度。在国外,公司积极参加国际展览和贸易洽谈会,与国际知名企业建立了合作关系,成功打开了国际市场的大门。

问答坊 | AI 解惑

大功率芯片的散热方案

利用金属底壳,加垫MAP软性硅胶导热片达到散热效果。 原来大家使用导热硅脂作为导热材料的很多: 当今的电子产品朝着两个方向发展:一方面产品的集成度越来越高、功耗不断增大;另一方面产品越来越轻、薄、短。这就使得产品的散热矛盾越来越突出 ...…

查看全部问答>

求助液晶高手

本人无意间淘到一块TFT-2.4的屏,看着市场上也买有这样的屏但是好贵,我想把这个屏利用一下,但是只找到了一个pdf,而且介绍有点简单,那位高手整过,给小弟指点一下 屏的型号:GIANTPLUS  KFM281E01-1D 图片和资料如下: …

查看全部问答>

迷茫者向各位问路!

我在现在这家公司做测试一年整,工作主要是产品验证与报告输出,倍感枯燥与无味,一心想往研发方向走,可现在的技术功底太一般,而且我只是个大专毕业。在校是电子专业,像模拟 、数字电路 、c语言等基础都一般。工作之余,用一年的业余时间从头学 ...…

查看全部问答>

请教几个TOPWIN2005烧录器的问题

在运行TOPWIN2005的时候出现了EAcess violation的错误,我点了确定后显示 Acess violation at address 740b0cc2,read of address 740b0cc2,是怎么回事? 我是第一次用TOPWIN2005,不好意思! 请哪位大侠帮个忙,小妹我不胜感激!…

查看全部问答>

嵌入式技术交流QQ群

        群号:37829738 希望各位同仁共同交流有关ARM,单片机,LINUX,人工智能技术…

查看全部问答>

学习STM8的好文章(转)

第一节: 心情和时钟 说实话我能够使用的单片机不多,我总是以为无论什么单片机都能开发出好的产品。 前些年用51,总是向各位大大学习,无休止的索取,在网上狂览一通。心里感激的同时也想奉献一些,可是我会什么?后来使用avr(公司要求 ...…

查看全部问答>

关于L298驱动4相6线步进电机

步进电机应该算是直流电机吧?。 驱动的话只用一个298是不是就可以了?我在网上查了好多人都说可以 但是老师怎么说不对呢?…

查看全部问答>

【转载好文】FPGA设计的四种常用思想与技巧

本文讨论的四种常用FPGA/CPLD设计思想与技巧:乒乓操作、串并转换、流水线操作、数据接口同步化,都是FPGA/CPLD逻辑设计的内在规律的体现,合理地采用这些设计思想能在FPGA/CPLD设计工作种取得事半功倍的效果。 FPGA/CPLD的设计思想与技巧是一个非 ...…

查看全部问答>

开发板申请

本人学习430不久,基本上掌握了基础应用,希望能申请到这板子能够学到更多应用! 主要针对485通讯和矩阵键盘应用。…

查看全部问答>

学模拟+如何最大限度减少线缆设计中的串扰

本帖最后由 dontium 于 2015-1-23 11:37 编辑 如何最大限度减少线缆设计中的串扰 deyisupport./blog/b/analogwire/archive/2014/01/22/51614.aspx 解决串扰的最好办法就是屏蔽它,可是参考点怎么选,最后还是会有电容存在的,尽可能减小的话就只 ...…

查看全部问答>