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>



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值