文章使用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>