开发板: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是失败(存取有问题)。
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);
};
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;
};
#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
#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");
#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);
}