混杂字符设备的主要特点是主设备号(10)公用,通过一个链表将各个设备关联起来,设备的识别主要依靠次设备号。
混杂设备存在自己的结构体:
- struct device;
-
-
struct miscdevice {
-
int minor;
-
const char *name;
-
const struct file_operations *fops;
-
struct list_head list;
-
struct device *parent;
-
struct device *this_device;
-
};
其中struct device*可以联想到自动加载设备文件中的class_create()和device_create()两个函数。因此乐意推测混杂字符设备是自动加载设备文件的设备驱动。
其中主要的两个函数分别是misc_register()和misc_deregister(),分别用来添加和去除混杂设备。这两个函数分别在初始化函数和卸载函数中调用。
- static struct class *misc_class;
-
-
static const struct file_operations misc_fops = {
-
.owner = THIS_MODULE,
-
.open = misc_open,
-
};
-
-
int misc_register(struct miscdevice * misc)
-
{
-
struct miscdevice *c;
-
dev_t dev;
-
int err = 0;
-
-
INIT_LIST_HEAD(&misc->list);
-
-
mutex_lock(&misc_mtx);
-
list_for_each_entry(c, &misc_list, list) {
-
if (c->minor == misc->minor) {
-
mutex_unlock(&misc_mtx);
-
return -EBUSY;
-
}
-
}
-
-
if (misc->minor == MISC_DYNAMIC_MINOR) {
-
int i = DYNAMIC_MINORS;
-
while (--i >= 0)
-
if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)
-
break;
-
if (i<0) {
-
mutex_unlock(&misc_mtx);
-
return -EBUSY;
-
}
-
misc->minor = i;
-
}
-
-
if (misc->minor < DYNAMIC_MINORS)
-
misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
-
dev = MKDEV(MISC_MAJOR, misc->minor);
- /*创建设备*/
-
misc->this_device = device_create(misc_class, misc->parent, dev, NULL,
-
"%s", misc->name);
-
if (IS_ERR(misc->this_device)) {
-
err = PTR_ERR(misc->this_device);
-
goto out;
-
}
-
list_add(&misc->list, &misc_list);
-
out:
-
mutex_unlock(&misc_mtx);
-
return err;
-
}
int misc_deregister(struct miscdevice *misc)
{
int i = misc->minor;
if (list_empty(&misc->list))
return -EINVAL;
mutex_lock(&misc_mtx);
list_del(&misc->list);
/*释放设备*/
device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
if (i < DYNAMIC_MINORS && i>0) {
misc_minors[i>>3] &= ~(1 << (misc->minor & 7));
}
mutex_unlock(&misc_mtx);
return 0;
}
static int __init misc_init(void)
{
int err;
...
/*创建一个设备类*/
misc_class = class_create(THIS_MODULE, "misc");
...
return err;
}
从源码中可以知道混杂字符设备就是自动创建设备文件的设备驱动。
LED的字符设备驱动,由于没有读写操作,只需要完成最控制操作,也就是ioctl函数的实现。由于open函数默认情况下就是打开,所以不去实现也是可以的。ioctl函数的实现主要包含两个步骤,分别是定义命令和实现命令。其中的定义命令包含类型、方向、数据大小、以及命令序号,这些都可以按着一定宏定义实现。实现控制也就是ioctl函数的定义,其中包含,三部分:(1)、命令的检查,类型和序号;(2)、指针参数的可读可写检查;(3)具体命令的实现(switch-case)。
LED的实现主要就是控制全亮、全灭,某一个亮,某一个灭。我的开发板是TQ2440,利用了GPB5-GPB8来控制4个LED,只要当端口为低电平时,LED亮,高电平时,LED灭。
具体的实现如下:
- #include<linux/module.h>
-
#include<linux/types.h>
-
#include<linux/fs.h>
-
#include<linux/sched.h>
-
#include<linux/init.h>
-
#include<linux/cdev.h>
-
#include<linux/device.h>
-
#include<linux/mm.h>
-
#include<linux/miscdevice.h>
-
/*平台相关的头文件*/
-
#include
-
#include<mach/hardware.h>
-
#include<linux/errno.h>
-
#include
-
#include<linux/cdev.h>
-
#include<linux/slab.h>
-
#include<linux/string.h>
-
#include<linux/kernel.h>
-
-
-
/*定义自己的命令*/
-
/*定义幻数,表示具体的设备*/
-
#define LED_MAGIC_NUMBER 'k'
-
#define LED_ALL_ON _IO(LED_MAGIC_NUMBER,0)
-
#define LED_ALL_OFF _IO(LED_MAGIC_NUMBER,1)
-
#define LED_ON _IO(LED_MAGIC_NUMBER,2)
-
#define LED_OFF _IO(LED_MAGIC_NUMBER,3)
-
#define LED_MAX_CMD 4
-
-
/*设备名*/
-
#define DEVICE_NAME "GP_LED"
/*具体的端口号*/
-
static unsigned int led_table[] =
-
{
-
S3C2410_GPB5,
-
S3C2410_GPB6,
-
S3C2410_GPB7,
-
S3C2410_GPB8,
-
};
- /*端口的功能数组*/
-
static unsigned int led_cfg_table[]=
-
{
-
S3C2410_GPB5_OUTP,
-
S3C2410_GPB6_OUTP,
-
S3C2410_GPB7_OUTP,
-
S3C2410_GPB8_OUTP,
- /*或者采用通用功能*/
- /*
- S3C2410_GPIO_OUTPUT,
- S3C2410_GPIO_OUTPUT,
- S3C2410_GPIO_OUTPUT,
- S3C2410_GPIO_OUTPUT,
- */
-
};
-
-
static int s3c2440_led_ioctl(
-
struct inode * inode,
-
struct file *file,
-
unsigned int cmd,
-
unsigned long arg
-
)
-
{
-
int i = 0;
-
/*检测参数的正确性*/
-
if(_IOC_TYPE(cmd)!=LED_MAGIC_NUMBER)
-
return -EINVAL;
-
/*检查命令是否超过一定的界限*/
-
if(_IOC_NR(cmd) >= LED_MAX_CMD)
-
return -EINVAL;
-
-
/*检查arg参数的正确性*/
-
if(arg<0 || arg >4)
-
{
-
return -EINVAL;
-
}
-
/*命令控制语句*/
-
switch(cmd)
-
{
-
case LED_ALL_ON:
-
{
-
for(i = 0; i < 4; ++ i)
-
s3c2410_gpio_setpin(led_table[arg-i-1],0);
-
break;
-
}
-
case LED_ALL_OFF:
-
{
-
for(i = 0; i < 4; ++ i)
-
s3c2410_gpio_setpin(led_table[arg-i-1],1);
-
break;
-
}
-
case LED_ON:
-
{
-
s3c2410_gpio_setpin(led_table[arg],0);
-
break;
-
}
-
case LED_OFF:
-
{
-
s3c2410_gpio_setpin(led_table[arg],1);
-
break;
-
}
-
default:
-
{
-
return -EINVAL;
-
break;
-
}
-
}
-
-
-
return 0;
-
}
-
-
/*具体函数*/
-
static const struct file_operations led_fops =
-
{
-
.owner = THIS_MODULE,
-
.ioctl = s3c2440_led_ioctl,
-
};
-
-
/*混杂设备类*/
-
static const struct miscdevice misc =
-
{
-
.minor = MISC_DYNAMIC_MINOR,
-
.name = DEVICE_NAME,
-
/*此处是一个地址,而不是一个数*/
-
.fops = &led_fops,
-
};
-
-
/*初始化*/
-
static int __init dev_init(void)
-
{
-
int ret;
-
-
int i;
-
-
for(i = 0; i<4; ++i)
-
{
-
s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]);
-
s3c2410_gpio_setpin(led_table[i],1);
-
}
-
-
ret = misc_register(&misc);
-
printk(DEVICE_NAME"\tinitialized\n");
-
-
return ret;
-
}
-
-
/*退出*/
-
static void __exit dev_exit(void)
-
{
-
misc_deregister(&misc);
-
}
-
-
module_init(dev_init);
-
module_exit(dev_exit);
-
-
MODULE_LICENSE("GPL");
-
MODULE_AUTHOR("GP");
测试应用程序如下:
- #include<stdio.h>
-
#include<stdlib.h>
-
#include<unistd.h>
-
#include<fcntl.h>
-
#include<sys/ioctl.h>
-
-
#define LED_MAGIC_NUMBER 'k'
-
#define LED_ALL_ON _IO(LED_MAGIC_NUMBER,0)
-
#define LED_ALL_OFF _IO(LED_MAGIC_NUMBER,1)
-
#define LED_ON _IO(LED_MAGIC_NUMBER,2)
-
#define LED_OFF _IO(LED_MAGIC_NUMBER,3)
-
-
int main(int argc,char *argv[])
-
{
-
int fd,cmd;
-
unsigned int arg;
-
if(argc != 3)
-
{
-
printf("parameter is not right");
-
exit(-1);
-
}
-
-
cmd = atoi(argv[1]);
-
arg = atoi(argv[2]);
-
-
if(cmd > 3 || cmd < 0 || arg > 4 || arg < 0)
-
{
-
printf("The style of command is not right\n");
-
exit(-1);
-
}
-
-
fd = open("/dev/GP_LED",O_RDWR);
-
-
if(fd == -1)
-
{
-
printf("Open File wrong!!\n");
-
exit(-1);
-
}
-
switch(cmd)
-
{
-
case 0:
-
cmd = LED_ALL_ON;
-
arg = 4;
-
break;
-
case 1:
-
cmd = LED_ALL_OFF;
-
arg = 4;
-
break;
-
case 2:
-
cmd = LED_ON;
-
break;
-
case 3:
-
cmd = LED_OFF;
-
break;
-
default:
-
exit(-1);
-
}
-
int isOk = ioctl(fd,cmd,arg);
-
printf("%d",isOk);
-
-
close(fd);
-
-
exit(0);
-
}
分析代码:
应用程序没什么好分析的,关键是驱动代码中的几个重要的数据结构S3C2410_GPB5-S3C2410_GPB8以及S3C2410_GPB5_OUTP--S3C2410_GPB8_OUTP和两个函数s3c2410_gpio_cfgpin(),s3c2410_gpio_setpin()。
其中3C2410_GPB5-S3C2410_GPB8是指GPB5-GPB8这四个IO口,Linux中对端口都进行了编号,给予每一个IO口唯一的端口号。同时又将端口分成了很多块GPx,包括GPA,GPB,...,GPH等。每一块的起始端口号为(x-1)*32+0,也就是GPA的起始端口号为0,而GPB的起始端口号为32,依此类推。而S3C2410_GPB5_OUTP是指将GPB5配置为输出口,每一个IO口都是多功能IO,使用前都需要进行配置。
具体的源码如下:
- ...
- /*得到端口号,每一个IO口的端口号是唯一的*/
- #define S3C2410_GPB5 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5)
- /*定义端口的功能,其中的<<10,是因为在GPBCON的第10bit开始是配置端口B的功能,其他的也类似,只是位不同*/
-
#define S3C2410_GPB5_INP (0x00 << 10)
-
#define S3C2410_GPB5_OUTP (0x01 << 10)
-
#define S3C2410_GPB5_nXBACK (0x02 << 10)
-
#define S3C2443_GPB5_XBACK (0x03 << 10)
-
#define S3C2400_GPB5_DATA21 (0x02 << 10)
-
#define S3C2400_GPB5_nCTS1 (0x03 << 10)
-
-
#define S3C2410_GPB6 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 6)
-
#define S3C2410_GPB6_INP (0x00 << 12)
-
#define S3C2410_GPB6_OUTP (0x01 << 12)
-
#define S3C2410_GPB6_nXBREQ (0x02 << 12)
-
#define S3C2443_GPB6_XBREQ (0x03 << 12)
-
#define S3C2400_GPB6_DATA22 (0x02 << 12)
-
#define S3C2400_GPB6_nRTS1 (0x03 << 12)
-
-
#define S3C2410_GPB7 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 7)
-
#define S3C2410_GPB7_INP (0x00 << 14)
-
#define S3C2410_GPB7_OUTP (0x01 << 14)
-
#define S3C2410_GPB7_nXDACK1 (0x02 << 14)
-
#define S3C2443_GPB7_XDACK1 (0x03 << 14)
-
#define S3C2400_GPB7_DATA23 (0x02 << 14)
-
-
#define S3C2410_GPB8 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 8)
-
#define S3C2410_GPB8_INP (0x00 << 16)
-
#define S3C2410_GPB8_OUTP (0x01 << 16)
-
#define S3C2410_GPB8_nXDREQ1 (0x02 << 16)
-
#define S3C2400_GPB8_DATA24 (0x02 << 16)
-
...
...
-
#define S3C2410_GPIONO(bank,offset) ((bank) + (offset))
- /*将IO分成8块,便于管理同一类型的端口*/
-
#define S3C2410_GPIO_BANKA (32*0)
-
#define S3C2410_GPIO_BANKB (32*1)
-
#define S3C2410_GPIO_BANKC (32*2)
-
#define S3C2410_GPIO_BANKD (32*3)
-
#define S3C2410_GPIO_BANKE (32*4)
-
#define S3C2410_GPIO_BANKF (32*5)
-
#define S3C2410_GPIO_BANKG (32*6)
-
#define S3C2410_GPIO_BANKH (32*7)
两个函数s3c2410_gpio_cfgpin(),s3c2410_gpio_setpin()分别表示配置端口(配置功能寄存器)和设置端口(写读数据寄存器)。
在linux内核中,通常将将CPU和外设的寄存器从物理地址静态的映射到了虚拟地址空间中以固定地址开始的一段内存空间上。
S3C24XXCPU的CPU和外设寄存器映射关系分布如下图所示:
具体的实现参看源码:
映射虚拟地址的关系建议自己绘图,可能更加的直观,快捷。在这段代码中的两个宏语句是比较难以理解的。这两句宏定义充分利用了位操作的优势。
/*求端口所在块寄存器的起始虚拟地址*/
#define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
/*求端口在所在块的偏移量*/
#define S3C2410_GPIO_OFFSET(pin) ((pin) & 31)
我们加以分析。我们以GPB5、GPA5作为分析对象。
GPB5的端口ID号是32*(2-1)+5=37,GPA5的端口ID号是32*(1-1)+5=5;
根据上面的图可知,S3C24XX_VA_GPIO的值为0xFB000000.
S3C2410_GPIO_BASE(GPB5) = ((37&~31)>>1)+0xFB000000=0xFB000010 (刚好对应于GPBCON的虚拟地址)
S3C2410_GPIO_BASE(GPA5) = ((5&~31)>>1)+0xFB000000=0xFB000000 (刚好对应于GPBACON的虚拟地址)
也就是相当于得到每一块IO口的基地址也就是GPA,GPB,GPC,...等对应的地址。
S3C2410_GPIO_OFFSET(GPB5) = 37 & 31 = 5;相当于求32的余数,也就是得到端口在所在块中的偏移量。
LED的驱动很简单,该驱动是按着ioctl实现的一般步骤实现的,具有延续性。
阅读(2785) | 评论(0) | 转发(2) |