linux的misc設備led--6410---學習筆記

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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章