linux驱动实践(三)--不大一般的LED驱动

        看了这么多内核代码,终于要自己开始做驱动了.按照由易到难,由浅入深的顺序,就从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函数.
        顺着调用其实设置寄存器的动作是在leds-s3c24xx.c中的s3c24xx_led_set实现的.从这里可以看出,LEDS这个子系统也做了抽象,与平台相关的放在一个文件中,而与平台无关的抽象出来放在led-class中.

        到此,就可以通过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 <dndxhej@gmail.com>
 *
 *
 * 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 <linux/module.h>
#include <linux/leds.h>
#include <linux/sched.h>

#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#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,
     "%s", led_cdev->name);

        改为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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

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.


发布了106 篇原创文章 · 获赞 16 · 访问量 54万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览