除了知情权以外,人也应该拥有不知情权,后者的价值要大得多。它意味着高尚的灵魂不必被那些废话和空谈充斥。过度的信息对一个过着充实生活的人来说,是一种不必要的负担。
这两种思想在之前的程序中悄无声息的使用着,但是这样就完美了?在之前的程序基础上,考虑这样一种情况:如果硬件上更换一个引脚来控制 LED 怎么办?那就得去修改led_operations结构体初始化中的 init、 ctrl 函数实现。
实际情况是,每一款芯片它的 GPIO 操作都是类似的。比如: GPIO1_3、 GPIO5_4 这 2个引脚接到 LED:
既然引脚操作那么有规律, 并且这是跟主芯片相关的,那可以针对该芯片写出比较通用的硬件操作代码。
比如 board_A.c 使用芯片 chipY,那就可以写出: chipY_gpio.c,它实现芯片 Y 的 GPIO操作,适用于芯片 Y 的所有 GPIO 引脚。
使用时,我们只需要在 board_A_led.c 中指定使用哪一个引脚即可。
程序结构如下图所示:
以面向对象的思想,在 board_A_led.c 中实现 led_resouce 结构体,它定义“资源”──要用哪一个引脚。在 chipY_gpio.c 中仍是实现 led_operations 结构体,它要写得更完善,支持所有 GPIO。
这样程序仍分为上下结构:
LED_resource.h
#ifndef _LED_RESOURCE_H
#define _LED_RESOURCE_H
/* GPIO3_0 */
/* bit[31:16] = group */
/* bit[15:0] = which pin */
#define GROUP(x) (x>>16)
#define PIN(x) (x&0xFFFF)
#define GROUP_PIN(g,p) ((g<<16) | (p))
struct led_resource {
int pin;
};
struct led_resource *get_led_resouce(void);
#endif
资源结构体头文件需要说明的:
board_A_led.c
#include "led_resource.h"
static struct led_resource board_A_led = {
.pin = GROUP_PIN(3,1),
};
struct led_resource *get_led_resouce(void)
{
return &board_A_led;
}
单板A的GPIO需要说明的是
chipY_gpio.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "led_opr.h"
#include "led_resource.h"
static struct led_resource *led_rsc;
static int board_qemu_led_init (int which) /* 初始化LED, which-哪个LED */
{
//printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
if (!led_rsc)
{
led_rsc = get_led_resouce();
}
printk("init gpio: group %d, pin %d\n", GROUP(led_rsc->pin), PIN(led_rsc->pin));
switch(GROUP(led_rsc->pin))
{
case 0:
{
printk("init pin of group 0 ...\n");
break;
}
case 1:
{
printk("init pin of group 1 ...\n");
break;
}
case 2:
{
printk("init pin of group 2 ...\n");
break;
}
case 3:
{
printk("init pin of group 3 ...\n");
break;
}
}
return 0;
}
static int board_qemu_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{
//printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
printk("set led %s: group %d, pin %d\n", status ? "on" : "off", GROUP(led_rsc->pin), PIN(led_rsc->pin));
switch(GROUP(led_rsc->pin))
{
case 0:
{
printk("set pin of group 0 ...\n");
break;
}
case 1:
{
printk("set pin of group 1 ...\n");
break;
}
case 2:
{
printk("set pin of group 2 ...\n");
break;
}
case 3:
{
printk("set pin of group 3 ...\n");
break;
}
}
return 0;
}
static struct led_operations board_qemu_led_opr = {
.num = 4,
.init = board_qemu_led_init,
.ctl = board_qemu_led_ctl,
};
struct led_operations *get_board_led_opr(void)
{
return &board_qemu_led_opr;
}
chipY的所有GPIO操作程序需要说明的:
board_A_led.c
中去了!leddrv.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "led_opr.h"
/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
static int led_drv_open (struct inode *node, struct file *file)
{
int minor = iminor(node);
/* 根据次设备号初始化LED */
p_led_opr->init(minor);
return 0;
}
static int led_drv_close (struct inode *node, struct file *file)
{
return 0;
}
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
return 0;
}
/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
struct inode *node = file_inode(file);
int minor = iminor(node);
err = copy_from_user(&status, buf, 1);
/* 根据次设备号和status控制LED */
p_led_opr->ctl(minor, status);
return 1;
}
/* 2. 定义自己的file_operations结构体 */
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{
int err;
int i;
printk("LED init \r\n");
/* 4. 把file_operations结构体告诉内核:注册驱动程序 */
major = register_chrdev(0, "led", &led_drv); /* /dev/led */
/* 7. 其他完善:提供设备信息,自动创建设备节点 */
led_class = class_create(THIS_MODULE, "led_class");
err = PTR_ERR(led_class);
if (IS_ERR(led_class)) {
unregister_chrdev(major, "led");
return -1;
}
/* 注意要在创建设备之前获得led_operaions结构体(需要用到其中的num) */
p_led_opr = get_board_led_opr();
for (i = 0; i < p_led_opr->num; i++)
device_create(led_class, NULL, MKDEV(major, i), NULL, "led%d", i); /* /dev/led0,1,... */
return 0;
}
/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit led_exit(void)
{
int i;
printk("LED exit \r\n");
for (i = 0; i < p_led_opr->num; i++)
device_destroy(led_class, MKDEV(major, i)); /* /dev/led0,1,... */
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major, "led");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
LED驱动程序需要说明的
Makefile
KERN_DIR = /home/clay/linux/qemu/kernel/100ask_imx6ull-qemu/linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o ledtest ledtest.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f ledtest
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o
# leddrv.c chipY_gpio.c board_A_led.c 编译成 led.ko
led-y := leddrv.o chipY_gpio.o board_A_led.o
obj-m += led.o
Makefile需要说明的:
编译程序没有问题后,运行qemu虚拟开发板,并做好准备工作!
cp *.ko ledtest ~/linux/qemu/NFS/
insmod led.ko
./ledtest /dev/led0 on
./ledtest /dev/led0 off
这一节也只是搭了框架哈,主要还是理解思想,下一节继续深入!