在上面的这篇博客中,我写的LED驱动是将LED作为字符设备写的,大家可能也发现了,在写驱动文件时特别冗长,这样是不利于程序员们写驱动的,Linux内核(2.6版内核之后的)添加了设备分类的概念,注意与设备模型概念的区分。
设备模型:(三要素)总线、驱动、设备
优点:电源管理(设计的初衷)
热插拔
增强驱动的可移植性
Linux 内核将所有的设备分为三大类:字符设备、块设备、网络设备、
要记住,Linux 内核只是给出了这种分类,并没有明确给出哪种设备是属于哪一类,这根据程序员自己的经验来判定,什么样的设备拿什么样类来写;
字符设备:是设备种类最多的一类,基本常见的设备都可以拿字符设备来写
块设备:很容易看出,主要是存储类的,SD卡,NAND,硬盘等
网络设备:常见的就是网卡了
但是在这三大类的基础上,是可以写了,但是还是会有诸多不便,2.6版本以后的内核又加上了设备分类
设备分类:实际将字符设备进一步分类了
输入设备
fb设备
sound设备
....
tty设备
....
misc
当然也可以自己创建类,通过函数class_create创建一个类
了解完了基本概念,我们现在将led作为misc设备重写,
基本的宏定义我就不重写了,都与本文开始提到的博客中的头文件相同,只是将led_info结构体修改一下,
struct led_info {
void __iomem *v;
int status;
int user;
dev_t no;
struct miscdevice dev;//misc设备的结构体
void (*on)(struct led_info *l, int no);
void (*off)(struct led_info *l, int no);
};
对于设备文件基本就不需要改了,对于一个相同的设备,设备文件基本不动
为了体现完整性,我将驱动文件完全的写以下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include "s3c_led.h"
void s3c_led_on(struct led_info *l, int no)
{
u32 tmp;
tmp = readl(l->v + GPMDAT);
tmp &= GPM_LOW(no);
writel(tmp, l->v + GPMDAT);
}
void s3c_led_off(struct led_info *l, int no)
{
u32 tmp;
tmp = readl(l->v + GPMDAT);
tmp |= GPM_HIGH(no);
writel(tmp, l->v + GPMDAT);
}
void s3c_led_init(struct led_info *l)
{
u32 tmp;
tmp = readl(l->v + GPMCON);
tmp &= GPM0_3_MASK;
tmp |= GPM0_3_OUT;
writel(tmp, l->v + GPMCON);
tmp = readl(l->v + GPMDAT);
tmp |= GPM0_3_HIGH;
writel(tmp, l->v + GPMDAT);
l->on = s3c_led_on;
l->off = s3c_led_off;
}
void s3c_led_exit(struct led_info *l)
{
u32 tmp;
tmp = readl(l->v + GPMDAT);
tmp |= GPM0_3_HIGH;
writel(tmp, l->v + GPMDAT);
}
//-----------------------------------------------------------以下是与硬件无关的,模式比较固定
struct led_info *led;
ssize_t led_read(struct file *fp, char __user *buffer, size_t count, loff_t *off)
{
char buf[64];
int ret = 0;
ret += sprintf(buf, "State:%x\n", led->status);
if(copy_to_user(buffer, buf, ret))
return -EFAULT;
return ret;
}
ssize_t led_write(struct file *fp, const char __user *buffer, size_t count, loff_t *off)
{
if(buffer[1] == '1'){
led->on(led, buffer[0] - '0');
led->status |= S_LED_ON(buffer[0] - '0');
}else if(buffer[1] == '0'){
led->off(led, buffer[0] - '0');
led->status &= S_LED_OFF(buffer[0] - '0');
}else{
return -EPERM;
}
return count;
}
int led_open(struct inode *no, struct file *fp)
{
if(!led->user)
led->user++;
else
return -EBUSY;
return 0;
}
int led_release(struct inode *no, struct file *fp)
{
if(led->user)
led->user--;
else
return -ENODEV;
return 0;
}
struct file_operations led_ops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
};
int led_probe(struct platform_device *pdev)
{
struct resource *led_res;
int ret;
led_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if(!led_res)
return -EBUSY;
led = kzalloc(sizeof(struct led_info), GFP_KERNEL);
if(!led)
return -ENOMEM;
led->v = ioremap(led_res->start, led_res->end - led_res->start + 1);
if(!led->v){
ret = -ENOMEM;
goto remap_v_error;
}
//可以看出,此处的代码比使用字符设备要少很多
led->dev.name = pdev->name;
led->dev.minor = MISC_DYNAMIC_MINOR;//动态分配次设备号,misc设备的主设备号固定是10
led->dev.fops = &led_ops;
ret = misc_register(&led->dev);/这里注册的是misc设备,就不是字符设备了
if(ret)
goto misc_register_error;
s3c_led_init(led);
led->status = S_LED_OFF_ALL;
return 0;
misc_register_error:
iounmap(led->v);
remap_v_error:
kfree(led);
return ret;
}
int led_remove(struct platform_device *pdev)
{
s3c_led_exit(led);
misc_deregister(&led->dev);
iounmap(led->v);
kfree(led);
return 0;
}
struct platform_driver drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "s3c-led",
},
};
static __init int module_test_init(void)
{
return platform_driver_register(&drv);
}
static __exit void module_test_exit(void)
{
platform_driver_unregister(&drv);
}
module_init(module_test_init);
module_exit(module_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Musesea");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("Test for module");
作为misc设备的LED驱动就写完了。
大家如果发现什么问题一定要告诉我,大家一起学习了。