Linux内核模块驱动之---led驱动

开发板: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区域没有概念上的区别:他们都是通过向地址总线和控制总线发送电平信号进行访问,再通过数据总线读写数据。
详细请参考下面的连接::

第四步:混杂设备驱动程序设计
在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<linux/ioctl.h>
/*涉及到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<linux/module.h>
#include<linux/miscdevice.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/fs.h>
#include<linux/types.h>
#include<linux/errno.h>
#include<linux/ioctl.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/ioport.h>
#include<asm/io.h>
#include<asm/uaccess.h>
/*要了解各个头文件中所包含的函数*/
#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%lx\n",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 =%d\n",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<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#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就会根据你设置的数字来点亮对应的发光二极管

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值