看了这么多内核代码,终于要自己开始做驱动了.按照由易到难,由浅入深的顺序,就从LED开始.
LED驱动可以说是hello world之后最简单的驱动模块了.如果自己写一个LED驱动那是很简单的,其实用linux内核中的leds子系统来做也是比较简单的,内核中的leds子系统是将led抽象成platform_device,并有leds_class.这样,在/sys/class/leds/目录下面就可以利用sysfs文件系统来实现LED的操作.其实,也可以在此基础上添加/dev访问的接口,甚至用不着包含mknod的脚本和udev等工具,在模块注册后就可以生成/dev下的设备文件.
一步一步的来,首先利用platform虚拟总线来实现sysfs文件系统下的LED操作.
在mach-smdk2440.c中新增platform_device如下:
static struct platform_device smdk_led5 = {
.name = "s3c24xx_led",
.id = 0,
.dev = {
.platform_data = &smdk_pdata_led5,
.release = &platform_led_release,
},
};
对于具体的板子,是GPB5-8对应四个LED,所以这里smdk_pdata_led5的定义如下:
static struct s3c24xx_led_platdata smdk_pdata_led5 = {
.gpio = S3C2410_GPB(5),
.flags = S3C24XX_LEDF_ACTLOW ,//| S3C24XX_LEDF_TRISTATE,
.name = "led5",
//.def_trigger = "nand-disk",
};
接着在static struct platform_device *smdk2440_devices[] __initdata中新增&smdk_led5,这样系统在启动时就会由smdk2440_machine_init调用platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));来添加刚才加上的platform_device.
看完了platform_device再看下platform_driver是如何注册的:
在drivers/leds目录下的Makefile中有obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o
所以在make menuconfig的时候记得将其选为M.
这样,注册leds-s3c24xx.ko就会在/sys/class/leds中有
led5 led6 led7 led8四个目录,进入led5目录,有
brightness device power trigger
dev max_brightness subsystem uevent
执行echo 0 > brightness就可以关闭led,而echo 1 > brightness就可以打开led.
因为有leds_class,而在led-class.c中leds-init函数中有leds_class->dev_attrs = led_class_attrs;
其中,led_class_attrs定义如下
static struct device_attribute led_class_attrs[] = {
__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
__ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
#ifdef CONFIG_LEDS_TRIGGERS
__ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
#endif
__ATTR_NULL,
};
根据sysfs的属性文件,读写属性文件最终调用的就是show和store函数,在这里就是led_brightness_show和led_brightness_store函数.
到此,就可以通过sysfs文件系统来访问led设备了.
那么,开头说的在此基础如何做/dev访问的接口呢?
在leds目录下新建一个文件led-dev.c
在Makefile中添加obj-$(CONFIG_LEDS_INTF_DEV) += led-dev.o
记得在Kconfig文件中也要添加对应的信息.
led-dev.c文件内容如下:
/*
* LEDS subsystem, dev interface
*
* Copyright (C) 2012 Baikal
* Author: Baikal
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include
#include
#include
#include
#include
#include
#include "leds.h"
static dev_t led_devt;
#define LED_DEV_MAX 8 /*actually,there is 4 leds on the 2440 board*/
static int led_dev_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "debug by baikal: led dev open\n");
printk(KERN_INFO "i_rdev:0x%x\n",inode->i_rdev);
printk(KERN_INFO "i_rdev:%d\n",inode->i_rdev);
printk(KERN_INFO "i_rdev:major:%d minor:%d\n",MAJOR(inode->i_rdev),MINOR(inode->i_rdev));
struct led_classdev *led = container_of(inode->i_cdev,
struct led_classdev, char_dev);
file->private_data = led;
return 0;
}
static ssize_t
led_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
printk(KERN_INFO "debug by baikal: led dev read\n");
}
static ssize_t
led_dev_write(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
char data;
printk(KERN_INFO "debug by baikal: led dev write\n");
copy_from_user(&data,buf,count);
if(data == '0')
led_set_brightness(file->private_data,0);
else
led_set_brightness(file->private_data,1);
}
static const struct file_operations led_dev_fops = {
.owner = THIS_MODULE,
.read = led_dev_read,
.write = led_dev_write,
.open = led_dev_open,
// .release = led_dev_release,
};
void led_dev_prepare(struct led_classdev *led)
{
static int minor = 0;
printk(KERN_INFO "debug by baikal: led dev pre\n");
led_devt = MKDEV(254,minor); //add by baikal
minor++;
led->dev->devt = MKDEV(MAJOR(led_devt), MINOR(led_devt));
cdev_init(&led->char_dev, &led_dev_fops);
//led->char_dev.owner = led->owner;
}
void led_dev_add_device(struct led_classdev *led)
{
printk(KERN_INFO "debug by baikal: led dev add \n");
if (cdev_add(&led->char_dev, led->dev->devt, 1))
printk(KERN_WARNING " failed to add char device ");
else
pr_debug("%s: dev (%d:)\n", led->name,
MAJOR(led_devt));
}
void led_dev_del_device(struct led_classdev *led)
{
if (led->dev->devt)
cdev_del(&led->char_dev);
}
void __init led_dev_init(void)
{
/*
int err;
err = alloc_chrdev_region(&led_devt, 0, LED_DEV_MAX, "led");
if(err < 0)
printk(KERN_ERR "%s: failed to allocate char dev region\n",__FILE__);
*/
}
void __exit led_dev_exit(void)
{
/*
if(led_devt)
unregister_chrdev_region(led_devt, LED_DEV_MAX);
*/
}
并在led-class.c中的led_classdev_register函数的开头处添加
static int minor = 0;
dev_t led = MKDEV(254,minor); //add by baikal
printk(KERN_INFO "debug by baikal: led class reg 1\n");
led_cdev->dev = kzalloc(sizeof(struct device), GFP_KERNEL);
if (led_cdev->dev == NULL) {
return -ENOMEM;
}
led_dev_prepare(led_cdev); //add by baikal
printk(KERN_INFO "debug by baikal: led class reg 2\n");
minor++;
并将led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
改为led_cdev->dev = device_create(leds_class, parent, led, led_cdev,
"%s", led_cdev->name);
在函数的结尾处再调用 led_dev_add_device(led_cdev);
这样,在模块注册后在/dev目录下就有
[root@BaikalHu led5]# ls /dev/l*
/dev/led5 /dev/led7 /dev/loop0 /dev/loop2 /dev/loop4 /dev/loop6
/dev/led6 /dev/led8 /dev/loop1 /dev/loop3 /dev/loop5 /dev/loop7
这样就在/dev下生成了设备文件.
可以照下面的方式来测试/dev下的led设备文件的功能:
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int fd;
if(argc != 3)
{
printf("usage : ./led led5-8 on/off\n");
return -1;
}
char file[20] = "/dev/";
strcat(file, argv[1]);
fd = open(file, O_RDWR);
if(fd < 0)
{
perror("open led");
return -1;
}
if(!strcmp(argv[2],"on"))
{
write(fd,"1",1);
}
else if(!strcmp(argv[2],"off"))
{
write(fd,"0",1);
}
close(fd);
return 0;
}
测试一切正常.
回头解释下为什么这样就可以自己生成/dev下的设备文件,原因在于在新内核中的device_add函数中有这么一段:
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;
devtmpfs_create_node(dev);
}
看到devtmpfs_create_node了没,这就是答案.如果对devtmpfs有兴趣的话,以前写的《 linux设备模型之字符设备》大概提到了devtmpfs.
通过自己的添加,可以用sysfs和dev两种接口方式来访问LED.但是实际设置寄存器的函数都是同一处地方.
linux下的驱动,最终还是要操作寄存器的,但是与裸机不同的是linux将硬件设备抽象为文件,那我们就必须按照这种机制来书写linux下的驱动,机制的根本是文件系统和设备模型,但是可以通过不同的策略来达到目的,比如sysfs和devtmpfs.