文章使用misc设备实现led驱动,其中驱动函数包括ioctl驱动方式,故文章分为两部分。
第一部分:misc设备驱动实现框架
第二部分:ioctl函数实现。
</pre><p></p><p><span style="font-size:24px"><strong>第一部分</strong></span></p><p></p><p style="font-size:24px">简介</p><p><span style="font-size:18px">Misc(或miscellaneous)驱动是一些拥有着共同特性的简单字符设备驱动。内核抽象出这些特性而形成一些API(在文件drivers/char/misc.c中实现),以简化这些设备驱动程序的初始化。<span style="color:rgb(0,0,255)">如果一个字符设备驱动要驱动多个设备,那么它就不应该用misc设备来实现</span>。 在linux系统中,存在一类字符设备,它们共享一个主设备号(10),但此设备号不同,我们称这类设备为混杂设备,所有的混杂设备形成一个链表,对设备访问时内核依据次设备号查到相应的miscdevice设备。miscdevice的API实现在drivers/char/misc.c中。</span></p><p><span style="font-size:18px"><span style="color:rgb(255,0,0)"><span style="white-space:pre"></span>struct</span> <span style="color:rgb(0,0,255)">miscdevice {</span></span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)"><span style="white-space:pre"></span>int </span><span style="color:rgb(0,0,255)">minor;/次设备号/</span></span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)"><span style="white-space:pre"></span>const char *</span><span style="color:rgb(0,0,255)">name;/*设备名*/</span></span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)"><span style="white-space:pre"></span>const</span> <span style="color:rgb(255,0,0)">struct</span> <span style="color:rgb(0,128,0)">file_operations *</span><span style="color:rgb(0,0,255)">fops; /*文件操作*/</span></span></p><p><span style="font-size:18px"><span style="color:rgb(255,102,0)"><span style="white-space:pre"></span>struct</span> <span style="color:rgb(0,128,0)">list_head</span> <span style="color:rgb(0,0,255)">list;</span></span></p><p><span style="font-size:18px"><span style="color:rgb(255,0,0)"><span style="white-space:pre"></span>struct device</span> <span style="color:rgb(0,128,0)">*</span><span style="color:rgb(0,0,255)">parent;</span></span></p><p><span style="font-size:18px"><span style="color:rgb(255,0,0)"><span style="white-space:pre"></span>struct device </span><span style="color:rgb(0,128,0)">*</span><span style="color:rgb(0,0,255)">this_device;</span></span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)"><span style="white-space:pre"></span>const char *</span><span style="color:rgb(0,0,255)">nodename;</span></span></p><p><span style="font-size:18px"><span style="color:rgb(0,0,255)"><span style="white-space:pre"></span>mode_t</span> <span style="color:rgb(0,0,255)">mode;</span></span></p><p><span style="font-size:18px"><span style="white-space:pre"></span>};</span></p><p style="font-size:24px"> <img src="https://img-blog.csdn.net/20141018195031796?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuaGVzaGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /></p><p><span style="font-size:18px">1<span style="font-family:宋体">、设备注册</span></span></p><p><span style="font-size:18px">通常情况下,一个字符设备都不得不在初始化的过程中进行下面的步骤: 通过alloc_chrdev_region()分配主/次设备号。使用cdev_init()和cdev_add()来以一个字符设备注册自己。 而一个misc驱动,则可以只用一个调用misc_register()来完成这所有的步骤。 所有的miscdevice设备形成一个链表,对设备访问时,内核根据次设备号查找对应的miscdevice设备,然后调用其file_operations中注册的文件操作方法进行操作。</span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px">Linux内核使用misc_register函数来注册一个混杂设备驱动</span></p><p><span style="font-size:18px">Int misc_register(struct miscdevice *misc)</span></p><p><span style="font-size:18px">返回值为0表示注册成功,负数表示未成功</span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px">unregister_chrdev函数来注销一个混杂设备驱动</span></p><p><span style="font-size:18px">int unregister_chrdev(struct miscdevice *misc) </span></p><p><span style="font-size:18px">返回值为0表示注册成功,负数表示未成功</span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px">2、初始化话设备</span></p><p><span style="font-size:18px"> struct miscdevcie my_misc= {</span></p><p><span style="font-size:18px">.minor = MISC_MINOR,</span></p><p><span style="font-size:18px">.name = "my_misc",</span></p><p><span style="font-size:18px">.fops = &lmisc_fops,</span></p><p><span style="font-size:18px">};</span></p><p><span style="font-size:18px"></span></p><p><span style="font-size:18px">3、关于访问IO寄存器</span></p><p><span style="font-size:18px">内核的GPIO操作函数是通过一些的运算将GPIO接口换算成虚拟内存地址然后进行访问的。</span></p><p><span style="font-size:18px">那你找下页表,可能在开MMU的时候把这些寄存器重定位到了现在的地址,如果是不开MMU跑的话那寄存器地址肯定应该以DATASHEET为准……</span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)">#define</span> <span style="color:rgb(0,0,255)">S3C64XX_GPMCON</span> (S3C64XX_GPM_BASE + 0x00)</span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)">#define</span> <span style="color:rgb(0,0,255)">S3C64XX_GPM_BASE</span> S3C64XX_GPIOREG(0x0820)</span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)">#define</span> <span style="color:rgb(0,0,255)">S3C64XX_GPIOREG(reg)</span> (S3C64XX_VA_GPIO + (reg))</span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)">#define</span> <span style="color:rgb(0,0,255)">S3C64XX_VA_GPIO</span> S3C_ADDR_CPU(0x00000000)</span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)">#define</span> <span style="color:rgb(0,0,255)">S3C_ADDR_CPU(x)</span> S3C_ADDR(0x00500000 + (x))</span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)">#ifndef</span> __ASSEMBLY__</span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)">#define</span> S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))</span></p><p><span style="color:rgb(0,128,0)"><span style="font-size:18px">#else</span></span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)">#define</span> S3C_ADDR(x) (S3C_ADDR_BASE + (x))</span></p><p><span style="color:rgb(0,128,0)"><span style="font-size:18px">#endif</span></span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px"><span style="color:rgb(0,128,0)">#define</span> <span style="color:rgb(0,0,255)">S3C_ADDR_BASE</span> (0xF4000000)</span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px">关于readl和writel</span></p><p><span style="font-size:18px">readb/writeb 就是操作 8 bit 寄存器,</span></p><p><span style="font-size:18px">readw/writew 操作 16 bit 寄存器,</span></p><p><span style="font-size:18px">readl/writel 操作32 bit 寄存器。</span></p><p style="font-size:24px"> </p><p><span style="font-size:24px"></span></p><p><span style="font-size:24px"></span></p><p><span style="font-size:24px"><strong>第二部分</strong></span></p><p><span style="font-size:24px">Ioctl 驱动</span></p><p><span style="font-size:18px">在用户空间,使用ioctl系统调用来控制设备,原型如下:</span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">Int ioctl(int fd, unsigned long cmd,…)</span></span></p><p><span style="font-size:18px">原型中的省略号表示这是一个可选参数,存在与否依赖于控制命(第2个参数)是否涉及到与设备的数据交互。</span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px">Linux-2.6.36.2的原型是:</span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">Long(*unlocked_ioctl)(struct *flip, unsigned int cmd,unsigned long arg)</span></span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:24px">Ioctl实现</span></p><p><span style="font-size:18px">1、定义命令</span></p><p><span style="font-size:18px">命令号应该在系统范围内是唯一的,ioctl命令编码被划分为几个阶段,include/asm/ioctl.h中定义了这些位字段:</span></p><p><span style="font-size:18px">类型(幻数)(占8位),序号,传送方向,参数大小</span></p><p><span style="color:rgb(255,0,0)"><span style="font-size:18px">Documents/ioctl-number.txt文件罗列了在内核中已经使用了的幻数。</span></span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px">内核提供了下列宏来帮助定义命令</span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">_IO(type,nr)</span></span></p><p><span style="font-size:18px">没有参数的命令</span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">_IOR(type,nr,datatype)</span></span></p><p><span style="font-size:18px">从驱动中读数据(读的参数的类型)</span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">_IOW(type,nr,datatype)</span></span></p><p><span style="font-size:18px">写数据到驱动(写的数据的类型)</span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">_IOWR(type,nr,datatype)</span></span></p><p><span style="font-size:18px">双向传送,type和number成员作为参数被传递。</span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">_IOC_SIZE(cmd)</span></span></p><p><span style="font-size:18px">命令的字节数。</span></p><p><span style="font-size:18px">定义命令范例</span></p><p><span style="font-size:18px">#define MEM_IOC_MAGIC ‘m’ //定义幻数</span></p><p><span style="font-size:18px">#define MEM_IOCSET _IOW(MEM_IOC_MAGIC, 0,int) //定义一个向驱动写一个int型数据命令。</span></p><p><span style="font-size:18px">#define MEM_IOCREAD _IOR(MEM_LOC_MAGIC,1,int)//定义一个向驱动读一个int型数据的命令。</span></p><p><span style="font-size:18px"> </span></p><p><span style="font-size:18px">2、函数实现</span></p><p><span style="font-size:18px">返回值,参数适用,命令操作</span></p><p><span style="font-size:18px">Unlock_ioctl函数的实现是根据命令执行一个switch语句。但是,当命令不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(非法参数)。</span></p><p><span style="font-size:18px">参数arg</span></p><p><span style="font-size:18px">如果是一个整数,可以直接适用。如果是指针,我么必须确保这个用户地址是有效的,因此使用前要进行有效性检查。</span></p><p><span style="font-size:18px">凡是从用户空间的数据到内核,都需要检测。</span></p><p><span style="font-size:18px">不需要检测</span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">Copy_from_user</span></span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">Copy_to_user</span></span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">Get_user</span></span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">Put_user</span></span></p><p><span style="font-size:18px">需要检测</span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">_get_user</span></span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">_put_user</span></span></p><p><span style="font-size:18px">参数检测函数</span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px">Int access_ok(int type ,const *addr, unsigend long size)</span></span></p><p><span style="color:rgb(255,0,255)"><span style="font-size:18px"> <img src="https://img-blog.csdn.net/20141018194753647?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuaGVzaGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /></span></span></p><p><span style="font-size:18px">第一个参数是VERIFY_READ或者VERITY_WRITE,用来表明是读用户内存还是写用户内存。Addr参数是要操作的用户内存地址,size是要操作的长度。如果ioctl需要从用户空间读一个整数,那么size参数就是sizeof(int)</span></p><p><span style="font-size:18px">Access_ok返回一个布尔值:1成功(存取没问题)和0失败(存取有问题),如果该函数返回失败,则ioctl应该返回-EFAULT。</span></p><p><span style="font-size:18px"> <img src="https://img-blog.csdn.net/20141018194437703?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuaGVzaGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /></span></p><p><span style="font-size:18px">3、命令操作</span></p><p><span style="font-size:18px"><img src="https://img-blog.csdn.net/20141018194840275?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3VuaGVzaGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /></span></p><p><span style="font-size:18px"></span></p><p><span style="font-size:18px"></span></p><p><span style="font-size:24px">第三部分:驱动代码及解释</span></p><p></p><pre name="code" class="html"><span style="font-size:18px;">#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <mach/gpio-bank-m.h>
#include <mach/regs-gpio.h>
#include <mach/map.h>
#include <plat/gpio-cfg.h>
#include <linux/delay.h>
#include "led.h"
#define DEVICE_NAME "LED"
static int led_open(struct inode *inode,struct file *filp)
{
unsigned int tmp;
tmp = readl(S3C64XX_GPMCON);
tmp = (tmp & ~(0xFFFFU)) | (0x1111U);
writel(tmp,S3C64XX_GPMCON);
tmp = readl(S3C64XX_GPMDAT);
tmp |= (0xFU);
writel(tmp,S3C64XX_GPMDAT);
printk("configure led init\n");
return 0;
}
static ssize_t led_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos)
{
unsigned int tmp;
tmp = readl(S3C64XX_GPMDAT);
if(copy_to_user(buf,&tmp,1))
{
printk("led read copy to user fail\n");
return -EFAULT;;
}
return 0;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_pos)
{
char wbuf[10]; //防止传递下来数据过多
unsigned int tmp;
copy_from_user(wbuf,buf,count);
wbuf[0] = wbuf[0]&0xFU;
tmp = readl(S3C64XX_GPMDAT);
tmp = (tmp & ~(0xFU)) | (~wbuf[0]);
writel(tmp,S3C64XX_GPMDAT);
return count;
}
static int led_release(struct inode *inode,struct file *filp)
{
printk("#########led module release########\n");
return 0;
}
static long led_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0;
unsigned int tmp = 0;
int count = 0;
/* 检测命令的有效性 */
if (_IOC_TYPE(cmd) != LED_IOC_MAGIC)
return -EINVAL;
if (_IOC_NR(cmd) > LED_IOC_MAXNR)
return -EINVAL;
/* 根据命令类型,检测参数空间是否可以访问 */
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); //
if (err)
return -EFAULT;
switch(cmd)
{
case LED_LEFT:
for(count = 1;count<32;)
{
if(count<16)
{
tmp = readl(S3C64XX_GPMDAT);
tmp = (tmp & ~(0xFU)) | (~count);
writel(tmp,S3C64XX_GPMDAT);
}
else
{
tmp = readl(S3C64XX_GPMDAT);
tmp = (tmp & ~(0xFU));
writel(tmp,S3C64XX_GPMDAT);
}
msleep(200);
count = count * 2;
}
break;
case LED_RIGHT:
for(count = 8;count!=0;)
{
tmp = readl(S3C64XX_GPMDAT);
tmp = (tmp & ~(0xFU)) | (~count);
writel(tmp,S3C64XX_GPMDAT);
msleep(200);
count = count / 2;
}
break;
case LED_COUNT:
for(count = 0;count<16;)
{
tmp = readl(S3C64XX_GPMDAT);
tmp = (tmp & ~(0xFU)) | (~count);
writel(tmp,S3C64XX_GPMDAT);
msleep(200);
count++;
}
break;
default:
return -EINVAL;
}
return 0;
}
struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.read = led_read,
.unlocked_ioctl = led_ioctl,
.release = led_release,
};
static struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &led_fops,
};
static int __init led_init(void)
{
int rc;
if((rc = misc_register(&led_misc)) < 0)
{
printk("led misc register error\n");
return 1;
}
printk("led misc register successfully\n");
return 0;
}
static void __exit led_exit(void)
{
misc_deregister(&led_misc);
printk("led module remove now\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");</span><span style="font-size: 24px;">
</span>