Linux之分层分离思想

目录

 

一、分层与分离

二、总线设备驱动模型

2.1 dev部分

2.2 drv部分

三、利用总线驱动模型点灯

3.1 device部分

3.2 driver部分

3.3 测试部分

3.4 改进代码


一、分层与分离

对于输入子系统分为上下两层,分离下层为两部分,input_handler部分和input_device部分,对于buttons.c硬件相关,evdev.c纯软件稳定,输入子系统参考:输入子系统分析与测试,input.c向上提供统一的接口,不管硬件怎么变化,只需要我们完善硬件相关的操作

 

二、总线设备驱动模型

是一种左右建立联系的机制,将驱动程序分为两部分,总线设备驱动模型分为三个概念:总线、设备和驱动,即Bus/Dev/Drv模型,是一种编程的机制,例如做一个led的驱动程序,在dev中提供好相关的资源,在drv中根据dev的资源来操作,而在内核中有很多的drv和dev,在include\linux\platform_device.h中,dev部分会注册一个platform_device(平台设备),而drv会去注册一个platform_driver

struct platform_device {
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	*resource;

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
};

怎么去分辨哪个drv对应相应的dev,bus总线来管理,对于bus总线是一个虚拟、软件方面的概念,与硬件无关,以面向对象的思想,在drivers\base\platform.c中,根据bus中的match函数来匹配,其中"if (of_driver_match_device(dev, drv))"是根据设备树来匹配的,再到后面是根据drv部分的id_table与dev部分的name来匹配,若匹配不成功再尝试drv部分的name与dev部分的name是否相同,若相同则匹配成功

static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}


struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.dma_configure	= platform_dma_configure,
	.pm		= &platform_dev_pm_ops,
};

 

2.1 dev部分

在device部分我们更加关注硬件相关的操作,主要的过程如下

把device放入bus的dev链表

从bus的drv链表取出每一个drv,用bus的match函数判断drv能否支持dev

若可以支持,调用drv的probe函数(核心)

2.2 drv部分

在driver部分我们更加关注软件相关,这部分的代码一般比较稳定,不需要做太大的变动,主要的过程如下

把driver放入bus的drv链表

从bus的dev链表取出每一个dev,用bus的match函数判断dev能否支持drv

若可以支持,调用drv的probe函数(核心)

 

三、利用总线驱动模型点灯

 

3.1 device部分

先分配/设置/注册一个platform_device,在platform_device结构体设置:resource资源(在其中加入led的寄存器和引脚)、name(建立连接时名字需要一致)、 release释放函数(remove需要) ,在应用程序若没有close设备文件,但是驱动程序中的release函数可能还会被调用, 是因为当应用程序退出后,linux系统会帮我们清除应用程序所打开的文件,调用文件对应的close函数

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

#include <linux/init.h>

#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>



/* 分配/设置/注册一个platform_device */

static struct resource led_resource[] = {
    [0] = {
        .start = 0x56000050,             //资源有寄存器起始地址,换寄存器地址就改这里
        .end   = 0x56000050 + 8 - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = 5,     //哪个引脚,换灯的时候改这里就可以
        .end   = 5,
        .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_resource),  //资源
    .resource     = led_resource,
    .dev = { 
    	.release = led_release, 
	},
};

static int led_dev_init(void)
{
	platform_device_register(&led_dev); //注册一个平台设备 最终调用到device_add,放到平台总线的设备链表中去
	return 0;
}

static void led_dev_exit(void)
{
	platform_device_unregister(&led_dev);
}

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

 

3.2 driver部分

分配/设置/注册一个platform_driver,在platform_driver结构体设置:probe函数来配置led的资源,并注册字符设备驱动程序,在remove函数中卸载字符设备驱动程序  

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

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/* 分配/设置/注册一个platform_driver */
static int major;


static struct class *led_class;
static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
static int pin;

static int led_open(struct inode *inode, struct file *file)
{
	printk("first_drv_open\n");
	/* 配置为输出 */
	*gpio_con &= ~(0x3<<(pin*2));
	*gpio_con |= (0x1<<(pin*2));
	
	return 0;
}

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

	if (val == 1)
	{
		// 点灯
		*gpio_dat &= ~(1<<pin);
	}
	else
	{
		// 灭灯
		*gpio_dat |= (1<<pin);
	}

	return 0;
}

static struct file_operations led_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   led_open,       
	.write	=	led_write,	
};

static int led_probe(struct platform_device *pdev)
{
	struct resource		*res;

	/* 根据platform_device的资源进行ioremap */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	gpio_con = ioremap(res->start, res->end - res->start + 1);
	gpio_dat = gpio_con + 1;

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);//参数三代表资源中的第零个
	pin = res->start;


	/* 注册字符设备驱动程序 */

	printk("led_probe, found led\n");
	
	major = register_chrdev(0, "myled", & led_fops);

	led_class = class_create(THIS_MODULE, "myled");
	
	class_device_create(led_class, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */

	
	return 0;
}

static int led_remove(struct platform_device *pdev)
{
	/* 卸载字符设备驱动程序           */
	/* iounmap */
	printk("led_remove, remove led\n");
	class_device_destroy(led_class, MKDEV(major, 0)); 
	class_destroy(led_class);
	unregister_chrdev(major, "myled");
	iounmap(gpio_con);
	return 0;
}

struct platform_driver led_driver = {
	.probe		= led_probe,
	.remove		= led_remove,
	.driver		= {
		.name	= "myled",
	}
};


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

static void led_drv_exit(void)
{
	platform_driver_unregister(&led_driver);	
}

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

 

  •  Makefile:编译程序
KERN_DIR = /work/system/linux-2.6.22.6

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= led_drv.o
obj-m	+= led_dev.o

 

3.3 测试部分


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

/* led_test on 
 * led_test off
 */
int main(int argc, char **argv)
{
	int fd;
	int val = 1;
	fd = open("dev/led",O_RDWR);
	if(fd < 0)
		printf("cant't open!\n");
	if(argc != 2){
		printf("Usage:\n");
		printf("%s <on|off>\n", argv[0]);
	}
	if(strcmp(argv[1], "on") == 0){
		val = 1;
	}else{
		val = 0;
	}
	
	write(fd, &val, 4);
	return 0;
}

 

编译驱动程序和测试程序,加载驱动并执行测试程序

# insmod led_drv.ko
# insmod led_dev.ko
led_probe, found led
# ./led_test on
first_drv_open
# ./led_test off
first_drv_open
# rmmod led_dev
led_remove, remove led

总结

  • 先加载led_drv.ko,根据name在bus总线中找不到drv支持的dev,加载led_dev.ko后,建立了联系,调用drv的probe函数,在probe函数打印led_probe, found led,卸载dev驱动后会调用drv的remove函数,在remove函数中打印led_remove, remove led
  • 一个简单的字符驱动程序被拆分成两部分dev硬件相关部分和drv(稳定,不需要大改动)部分,这就是分离
  • 因此只需修改dev中的pin引脚就可以改变其他led的控制,不需要改动drv

 

3.4 改进代码

对于上述代码是用内核2.6的,现在用内核4.19

drv部分,跟dev提供的资源来操作相关的不同寄存器实现点灯

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>

#define S3C2440_GPA(n)  (0<<16 | n)
#define S3C2440_GPB(n)  (1<<16 | n)
#define S3C2440_GPC(n)  (2<<16 | n)
#define S3C2440_GPD(n)  (3<<16 | n)
#define S3C2440_GPE(n)  (4<<16 | n)
#define S3C2440_GPF(n)  (5<<16 | n)
#define S3C2440_GPG(n)  (6<<16 | n)
#define S3C2440_GPH(n)  (7<<16 | n)
#define S3C2440_GPI(n)  (8<<16 | n)
#define S3C2440_GPJ(n)  (9<<16 | n)

static int led_pin;
static volatile unsigned int *gpio_con;
static volatile unsigned int *gpio_dat;

/* 123. 分配/设置/注册file_operations 
 * 4. 入口
 * 5. 出口
 */

static int major;
static struct class *led_class;

static unsigned int gpio_base[] = {
	0x56000000, /* GPACON */
	0x56000010, /* GPBCON */
	0x56000020, /* GPCCON */
	0x56000030, /* GPDCON */
	0x56000040, /* GPECON */
	0x56000050, /* GPFCON */
	0x56000060, /* GPGCON */
	0x56000070, /* GPHCON */
	0,           /* GPICON */
	0x560000D0, /* GPJCON */
};

static int led_open (struct inode *node, struct file *filp)
{
	/* 把LED引脚配置为输出引脚 */
	/* GPF5 - 0x56000050 */
	int bank = led_pin >> 16;
	int base = gpio_base[bank];

	int pin = led_pin & 0xffff;
	gpio_con = ioremap(base, 8);
	if (gpio_con) {
		printk("ioremap(0x%x) = 0x%x\n", base, gpio_con);
	}
	else {
		return -EINVAL;
	}
	
	gpio_dat = gpio_con + 1;

	*gpio_con &= ~(3<<(pin * 2));
	*gpio_con |= (1<<(pin * 2));  

	return 0;
}

static ssize_t led_write (struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
	/* 根据APP传入的值来设置LED引脚 */
	unsigned char val;
	int pin = led_pin & 0xffff;
	
	copy_from_user(&val, buf, 1);

	if (val)
	{
		/* 点灯 */
		*gpio_dat &= ~(1<<pin);
	}
	else
	{
		/* 灭灯 */
		*gpio_dat |= (1<<pin);
	}

	return 1; /* 已写入1个数据 */
}

static int led_release (struct inode *node, struct file *filp)
{
	printk("iounmap(0x%x)\n", gpio_con);
	iounmap(gpio_con);
	return 0;
}


static struct file_operations myled_oprs = {
	.owner = THIS_MODULE,
	.open  = led_open,
	.write = led_write,
	.release = led_release,
};


static int led_probe(struct platform_device *pdev)
{
	struct resource		*res;

	/* 根据platform_device的资源进行ioremap */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	led_pin = res->start;

	major = register_chrdev(0, "myled", &myled_oprs);

	led_class = class_create(THIS_MODULE, "myled");
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "led"); /* /dev/led */
	
	return 0;
}

static int led_remove(struct platform_device *pdev)
{
	unregister_chrdev(major, "myled");
	device_destroy(led_class,  MKDEV(major, 0));
	class_destroy(led_class);
	
	return 0;
}


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


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

static void myled_exit(void)
{
	platform_driver_unregister(&led_drv);
}


module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

dev部分,当我们想改变led的引脚时,更改led_resource资源就可以了

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

#include <linux/init.h>

#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>

#define S3C2440_GPA(n)  (0<<16 | n)
#define S3C2440_GPB(n)  (1<<16 | n)
#define S3C2440_GPC(n)  (2<<16 | n)
#define S3C2440_GPD(n)  (3<<16 | n)
#define S3C2440_GPE(n)  (4<<16 | n)
#define S3C2440_GPF(n)  (5<<16 | n)
#define S3C2440_GPG(n)  (6<<16 | n)
#define S3C2440_GPH(n)  (7<<16 | n)
#define S3C2440_GPI(n)  (8<<16 | n)
#define S3C2440_GPJ(n)  (9<<16 | n)


/* 分配/设置/注册一个platform_device */

static struct resource led_resource[] = {
    [0] = {
        .start = S3C2440_GPF(5),
        .end   = S3C2440_GPF(5),
        .flags = IORESOURCE_MEM,
    },
};

static void led_release(struct device * dev)
{
}


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

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

static void led_dev_exit(void)
{
	platform_device_unregister(&led_dev);
}

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

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值