【嵌入式Linux驅動開發】五、LED驅動完善 - 面向對象·上下分層·左右分離

  除了知情權以外,人也應該擁有不知情權,後者的價值要大得多。它意味着高尚的靈魂不必被那些廢話和空談充斥。過度的信息對一個過着充實生活的人來說,是一種不必要的負擔。


一、面向對象·上下分層·左右分離思想

  • 面向對象
    • 字符設備驅動程序抽象出一個 file_operations 結構體;
    • 我們寫的程序針對硬件部分抽象出 led_operations 結構體。
  • 上下分層,
    • 比如我們前面寫的 LED 驅動程序就分爲 2 層:
    • ① 上層實現硬件無關的操作,比如註冊字符設備驅動: leddrv.c
    • ② 下層實現硬件相關的操作,比如 board_A.c 實現單板 A 的 LED 操作
      在這裏插入圖片描述

  這兩種思想在之前的程序中悄無聲息的使用着,但是這樣就完美了?在之前的程序基礎上,考慮這樣一種情況:如果硬件上更換一個引腳來控制 LED 怎麼辦?那就得去修改led_operations結構體初始化中的 init、 ctrl 函數實現。

  實際情況是,每一款芯片它的 GPIO 操作都是類似的。比如: GPIO1_3、 GPIO5_4 這 2個引腳接到 LED:

  • ① GPIO1_3 屬於第 1 組,即 GPIO1。
    有方向寄存器 DIR、數據寄存器 DR 等,基礎地址是 addr_base_addr_gpio1。
    設置爲 output 引腳:修改 GPIO1 的 DIR 寄存器的 bit3。
    設置輸出電平:修改 GPIO1 的 DR 寄存器的 bit3。
  • ② GPIO5_4 屬於第 5 組,即 GPIO5。
    有方向寄存器 DIR、數據寄存器 DR 等,基礎地址是 addr_base_addr_gpio5。
    設置爲 output 引腳:修改 GPIO5 的 DIR 寄存器的 bit4。
    設置輸出電平:修改 GPIO5 的 DR 寄存器的 bit4。

  既然引腳操作那麼有規律, 並且這是跟主芯片相關的,那可以針對該芯片寫出比較通用的硬件操作代碼。
  比如 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。

  這樣程序仍分爲上下結構:

  • 上層 leddrv.c 向內核註冊 file_operations 結構體;
  • 下層chipY_gpio.c 提供 led_operations 結構體來操作硬件。
    • 下層的代碼分爲 2 個:
    • chipY_gpio.c 實現通用的 GPIO 操作
    • board_A_led.c 指定使用哪個 GPIO,即“資源”( led_resource 結構體定義在led_resource.h 文件中)

二、編寫程序

程序框架如下:
在這裏插入圖片描述

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

資源結構體頭文件需要說明的:

  • 定義了一個成員變量32位的pin成員,來指定是哪一組的那一個引腳
    • pin成員低16位用作哪一個引腳
    • pin成員高16位用作哪一組引腳

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需要說明的是

  • 實現了 led_resouce 結構體,具體定義了“資源”是GPIO3_1
  • 該程序只是一個框架,並沒有初始化GPIO3_1的具體寄存器配置。

chipY_gpio.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#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操作程序需要說明的:

  • 該程序只是一個框架,並沒有具體的GPIO操作!
  • GROUP(led_rsc->pin), PIN(led_rsc->pin),這兩個宏在led_resource.h中定義,作用就是取出對應的組和引腳。
  • 其中的ioremap和iounmap自然放到了具體的單板GPIO初始化文件board_A_led.c中去了!

leddrv.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#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虛擬開發板,並做好準備工作!

  • 拷貝led.ko和ledtest到NFS中
cp *.ko ledtest ~/linux/qemu/NFS/
  • 在qemu終端,加載led.ko文件
insmod led.ko
  • 在qemu終端,運行應用程序打開LED0
./ledtest /dev/led0 on

在這裏插入圖片描述

  • 在qemu終端,運行應用程序關閉LED0
./ledtest /dev/led0 off

在這裏插入圖片描述

這一節也只是搭了框架哈,主要還是理解思想,下一節繼續深入!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章