1. 程式人生 > >裝置、驅動、匯流排模型簡介

裝置、驅動、匯流排模型簡介

前言

        在Linux系統中,有很多的框架提供驅動編寫者使用,比如前面我寫過的一篇文章:input子系統的架構分析及應用。都是將純軟體相關的程式碼和操作硬體相關的程式碼分離開,這樣就使得驅動的編寫者省去編寫大量的、重複的程式碼,只要專注於底層硬體相關的程式碼就可以了。這次我們介紹Linux系統中另外一個常見的框架模型:device-driver-bus模型,也就是裝置、驅動、匯流排模型。

正文

下面先給出模型的整體概念圖:

       由上面的圖可以看出來,和我們以前的input子系統框架有點像,也是講軟硬體的程式碼分離,從而做到抽象上的分層。以前我們寫過一個點燈的驅動程式:

一個簡單點亮LED燈的字元裝置驅動。這次我們使用裝置、驅動、匯流排模型對這個驅動程式進行改造。按照老規矩,先給出程式碼,再做解釋。

裝置

/*
 *  led_dev.c
 */
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>

#include <linux/module.h>
#include <linux/version.h>



/*
  * 分配、設定、註冊platform_device
  */

static struct resource led_source[] = {
	[0] = {
		.start = 0x56000050,
		.end   = 0x56000050 + 8 - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = 4,
		.end   = 4,
		.flags = IORESOURCE_IRQ,
	},
};

static void led_release(struct device *dev)
{
    /*為了簡單,先不寫任何東西*/
}

static struct platform_device led_dev = {
    .name = "myLed",
    .id   = -1,
    .num_resources    = ARRAY_SIZE(led_source),
    .resource     = led_source,
    .dev = {
        .release = led_release,
    },
};

static int led_dev_init(void)
{
	platform_device_register(&led_dev);
	return 0;
}

static int led_dev_exit(void)
{
	platform_device_unregister(&led_dev);
	return 0;
}

module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");

(1)init函式裡面是將自己註冊進匯流排

(2)struct resource用於描述我們物理裝置的一些資源,比如GPFCON暫存器的實體地址,如下圖所示:

還有4表示的是希望點亮的led燈的暫存器GPF4。

(3)platform_device結構體則包含上面所說的資源結構體外,還有一個重要的成員就是name,註冊後匯流排將會呼叫match函式去尋找同名的驅動

驅動

/*
 * led_drv.c
 */
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#include <linux/module.h>
#include <linux/version.h>


/*
  * 分配、設定、註冊platform_driver
  */

static int major;
static struct class *led_class =NULL;
static struct class_device *led_class_device = NULL;
static volatile unsigned long *gpio_con = NULL;
static volatile unsigned long *gpio_dat = NULL;
static int pin;

static int led_open(struct inode *inode, struct file *file)
{
	/*配置為輸出引腳*/
	*gpio_con &= ~(0x3<<pin*2); //先清空初始化
	*gpio_con |= (0x1<<pin*2); //設定為輸出引腳
	return 0;
}

static void led_write(struct file *file, const __user *buf, size_t count, loff_t *ppos)
{
	int val;
	copy_from_user(&val, buf, count);

	if (1 == val)
	{
		//開燈,低電平有效
		*gpio_dat &= ~(0x1<<pin);
	}
	else
	{
		//關燈
		*gpio_dat |= (0x1<<pin);
	}
	return 0;
}

static struct file_operations led_fop = {
	.open   = led_open,
	.write  = led_write,
	.owner  = THIS_MODULE,
};

static int led_probe(struct platform_device *pdev)
{
	struct resource *res;
	
	/*根據platform_device的資源進行ioremap*/
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res){
		goto err0;
	}
	gpio_con = (volatile unsigned long *)ioremap(res->start, res->end - res->start + 1);
	gpio_dat = gpio_con + 1;

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (!res){
		goto err0;
	}
	pin = res->start;

	/*註冊字元裝置驅動程式*/
	printk("led_probe, found led\n");

	major  = register_chrdev(0, "myLed", &led_fop);
	led_class = class_create(THIS_MODULE, "myLed");
	led_class_device = class_device_create(led_class, NULL, MKDEV(major, 0), NULL, "led");
	return 0;
err0:
	printk("platform_get_resource failed\n");
	return -ENOMEM;
}

static int led_remove(struct platform_device *pdev)
{
	/*解除安裝字元裝置驅動程式*/
	
	/* iounmap */
	printk("led_remove, remove led\n");
	class_device_destroy(led_class_device, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "myLed");
	iounmap(gpio_con);
	return 0;
	
}

static struct platform_driver led_drv = {
	.probe = led_probe,
	.remove = led_remove,
	.driver = {
		.name = "myLed",
	},
};

static int led_drv_init(void)
{
	platform_driver_register(&led_drv);
	return 0;
}

static int led_drv_exit(void)
{
	platform_driver_unregister(&led_drv);
	return 0;
}

module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");

(1)以前我們驅動程式的init函式裡面都是建立字元裝置和做GPIO暫存器的ioremap等等的一些硬體相關的操作,但是現在只是簡單的使用platform_driver_register函式將驅動註冊進匯流排,並且只有當找到了匹配的(相同的.name)裝置後才會呼叫.probe函式

(2)以前做的一些建立字元裝置和ioremap操作我們放到了probe函式裡面。值得關注的就是platform_get_resource函式,通過使用flags(IORESOURCE_MEM或者IORESOURCE_IRQ)找到不同的資源。

(3)probe成功後,剩下的就是以前的常規操作了,也是open和write函式等等

最後

我們將上面兩個程式分別編譯成led_dev.ko和led_drv.ko,同時insmod進系統,就能看到相關成功匹配的列印了。上面我們只是點亮了GPF4的燈,如果想點亮其他的燈,只要修改裝置程式碼中的資源結構體中的值就可以了。