Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3125348
  • 博文数量: 685
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 5303
  • 用 户 组: 普通用户
  • 注册时间: 2014-04-19 14:17
个人简介

文章分类

全部博文(685)

文章存档

2015年(116)

2014年(569)

分类: LINUX

2014-11-04 14:13:47

原文地址:

看了这么多内核代码,终于要自己开始做驱动了.按照由易到难,由浅入深的顺序,就从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 <>
 *
 *
 * 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,

"%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
#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.

 


阅读(907) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~