Linux模块(3) - 资源映射

在上一章Linux模块(2) - 创建设备节点已经将设备节点创建好了,可以进行用户态程序的交互了,今天完善一下资源映射的动作。

1. 资源定义

第一章Linux模块(1) - 加载与卸载中我们定义了struct platform_device mac_platform_device结构体,但是并未定义该设备所需要的资源有哪些,比如寄存器 中断等。Linux内核集中托管了物理中断和内存映射,对内提供虚拟地址空间和虚拟中断号,我们需要先定义所需的物理资源,然后向内核申请对应的虚拟资源。

1.1 资源头文件

mac资源涉及的资源信息较多,我们定义一个头文件专门放置相关的信息。

#ifndef _MAC_MODULE_H_
#define _MAC_MODULE_H_

// 系统控制寄存器地址,控制I/O复用和MAC模式等信息
#define SYSCTRL_ADDR_SIZE 0x1000
#define SYSCTRL_BASE_ADDR 0xA0400000

// MAC配置寄存器地址
#define MAC_ADDR_SIZE 0x2000
#define MAC_BASE_ADDR 0xA0600000

// MDIO操作PHY的地址
#define MAC_PHY_ADDR 0
// MAC中断
#define MAC_IRQ_NUM  36

// 资源相关名称的定义
#define SYSCTRL_BASE_NAME  "sysctrl"
#define MAC_BASE_NAME      "base"
#define MAC_PHY_ADDR       "phy-addr"
#define MAC_IRQ_NAME       "irq"

// 模块等名称定义,用于将之前在源码中写的字符串替换成对应的宏
#define MODULE_NAME         "mac"
#define DRIVER_NAME         MODULE_NAME
#define DEVICE_NAME         MODULE_NAME
#define MODULE_MAC_IRQ_NAME MODULE_NAME":"MAC_IRQ_NAME

// 设置模块为杂项设备,也可以注释掉这个定义,使用普通的设备号
#define MODULE_MISC_DEVICE

#endif /* _MAC_MODULE_H_ */

1.2 资源结构体

struct platform_device中有两个成员和资源相关。

// include/linux/platform_device.h
struct platform_device {
	...
	u32 num_resources;  // 资源的数量,会用到ARRAY_SIZE()宏,该宏能够进行代码静态检查
	struct resource *resource;  // 资源数组
	...
}
// include/linux/ioport.h
struct resource {
	resoucre_size_t start; // u32
	resource_size_t end; // 通常需要减1,比如0x200000000+0x2000-1
	const char *name;  // 资源可以通过索引查找也可以通过名称查找,索引更快,但与资源定义的相对位置强相关
	unsigned long flags;  // 这里的一部分位段存储了资源的类型信息,资源查找时的必匹配项之一
		// IORESOURCE_BITS --- 0x000000FF --- 细分字段
		// IORESOURCE_IRQ_HIGHDGE --- 上升沿中断
		// IORESOURCE_IRQ_xxx
		// IORESOURCE_DMA_8BIT --- DMA8位宽
		...
		// 类型
		// IORESOURCE_TYPE_BITS --- 0x00001F00 --- 类型字段
		// IORESOURCE_IO --- PCI/ISA等内存I/O,需要通过专用接口或指令进行访问,无法通过总线直接访问,嵌入式中很少用
		// IORESOURCE_MEM --- 内存,可通过总线直接访问
		// IORESOURCE_REG --- 寄存器的偏移
		// IORESOURCE_IRQ --- 中断号
		// IORESOURCE_DMA --- DMA索引
		// IORESOURCE_BUS --- 总线索引
		// 访问特性
		// IORESOURCE_PREFETCH --- 支持预取
		// IORESOURCE_READONLY --- 只读
		// IORESOURCE_CACHEABLE --- 支持缓冲
		// IORESOURCE_RANGELENGTH --- 范围
		// ...
	unsigned long desc;
	struct resource *parent, *sibling, *child;  // 父,兄,子,作为树的节点插入树中
}

定义资源结构体。

static struct resource mac_resource[] = {
	{
		.name = SYSCTRL_BASE_NAME,
		.start = SYSCTRL_BASE_ADDR,
		.end = SYSCTRL_BASE_ADDR+SYSCTRL_ADDR_SIZE-1,
		.flags = IORESOURCE_MEM,
	}, {
		.name = MAC_BASE_NAME,
		.start = MAC_BASE_ADDR,
		.end = MAC_BASE_ADDR+MAC_ADDR_SIZE-1,
		.flags = IORESOURCE_MEM,
	}, {
		.name = MAC_PHY_NAME,
		.start = MAC_PHY_ADDR,
		.flags = IORESOURCE_REG,  // 暂且使用IORESOURCE_REG这个类型,使用时只需要读取start的值即可
	}, {
		.name = MAC_IRQ_NAME,
		.start = MAC_IRQ_NUM,
		.flags = IORESOURCE_IRQ,  // 中断类型,只有start有效
	},
};
static struct platform_device mac_platform_device = {
	.name = DEVICE_NAME,  // 使用宏替代字符串
	.id = 0,
	.dev = {
		.release = mac_release,
	},
	.num_resources = ARRAY_SIZE(mac_resource);  // 使用ARRAY_SIZE()宏
	.resource = mac_resource,  // 资源结构体数组
};

2. 资源获取

内核中资源的定义和获取是相互独立的,资源的定义可以通过struct platform_device结构体,也可以通过设备树,对于资源获取内核也提供了一组接口。

2.1 函数接口

// drivers/base/platform.c
// 查找第num个匹配type的资源信息
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
{
	int i;

	for (i = 0; i < dev->num_resources; i++) {  // 逐一对比
		struct resource *r = &dev->resource[i];
		if (type == resource_type(r) && num-- == 0)  // 前面匹配类型,后面匹配数量
			return r;
	}
	return NULL;  // 失败反回NULL
}
// 查找第num个中断资源
int platform_get_irq(struct platform_device *dev, unsigned int num)
{
	struct resource *r;
	if (IS_ENABLED(CONFIG_OF_IRQ) && dev->dev.of_node) {  // 通过设备树设置的中断信息
		...
		ret = of_irq_get(dev->dev.of_node, num);
		{
			...
			return irq_create_of_mapping(&oirq);  // 执行中断映射后返回
		}
		if (ret > 0 || ret == -EPROBE_DEFER) {  // 虚拟中断号必须大于0?
			return ret;  // 成功或-EPROBE_DEFER则返回,否则后面继续查找
		}
	}
	r = platform_get_resource(dev, IORESOURCE_IRQ, num);  // 就是个普通的查找
	if (r && r->flags & IORESOURCE_BITS) {
		...
		irqd_set_trigger_type(...);  // 若设置了资源的中断模式,则会运行到这里
	}
	return r ? r->start : -ENXIO;  // 返回值应判断是否<0
}
// 根据类型和名称查找
struct resource *platform_get_resource_byname(struct platform_device *dev, unsigned int type, const char *name)
{
	...
	if (type == resource_type(r) && !strcmp(r->name, name))  // 数字比较变成了字符串比较
		return r;
	...
}

2.2 probe

同样的我们需要在probe()函数中进行资源的获取,基于Linux模块(2) - 创建设备节点进行完善。

int mac_probe(struct platform_device *pdev)
{
	struct driver_info *mac;
	int ret, instance_num;
	struct device *device;
	struct resource *res;  // 新增
	
	// 使用devm_前缀的函数,能够自动进行内存的释放,当然也可以手动释放
	// 申请一个结构体用于标记我们申请的各种内存,以便最后释放
	mac = mdev_kzalloc(&pdev->dev, sizeof(struct driver_info), GFP_KERNEL);  // 错误返回NULL
	if (unlikely(mac == NULL)) {  // 预期不会出现
		de_err(&pdev->dev, "alloc mac failed\n"); // 内核日志函数,具备打印等级控制的功能
		ret = -ENOMEM;  // 内存不足
		goto go_ret;  // 驱动加载过程较多故需要采用goto语句,逐层返回
	}
	
	/******************************************************/
	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, SYSCTRL_BASE_NAME);
	if (unlikely(res == NULL)) {
		de_err(&pdev->dev, "get resource[%s] failed\n", SYSCTRL_BASE_NAME); // 采用%s虽然运行时间更久,但能够节省rodata空间,而且这是个不期望的错误打印,故不将SYSCTRL_BASE_NAME在%s处直接展开
		ret = -ENXIO;  // IO资源错误
		goto go_kfree_mac;
	}
	// 因为SYSCTRL这块物理空间也被其他地方映射过,所以使用devm_ioremap_resource()会失败,使用devm_ioremap()即可。devm_ioremap_resource()会先申请这块物理地址,然后调用devm_ioremap()进行虚拟地址空间的映射,而物理地址空间只能申请一次,所以会失败,只要内核申请过这块物理空间就可以映射成功。
	//mac->sysctrl_base = devm_ioremap_resource(&pdev->dev, res); // 返回值使用IS_ERR()判断
	// resource_size() <==> (res->end - res->start + 1)
	mac->sysctrl_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); // 失败返回NULL
	if (IS_ERR_OR_NULL(mac->sysctrl_base)) { // 暂且检查IS_ERR和NULL,这样以上两个函数都可检查到位
		de_err(&pdev->dev, "ioremap[%s:01x%08x:0x08x] failed\n", SYSCTRL_BASE_NAME, res->start, resource_size(res)); 
		ret = -ENOMEM;  // 内存不足
		goto go_kfree_mac;
	}
	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, MAC_BASE_NAME);
	if (unlikely(res == NULL)) {
		de_err(&pdev->dev, "get resource[%s] failed\n", MAC_BASE_NAME);
		ret = -ENXIO;
		goto go_iounmap_sysctrl_base;
	}
	mac->base = devm_ioremap_resource(&pdev->dev, res); // 返回值使用IS_ERR()判断
	if (IS_ERR(mac->base)) {
		de_err(&pdev->dev, "ioremap[%s:01x%08x:0x08x] failed\n", MAC_BASE_NAME, res->start, resource_size(res)); 
		ret = -ENOMEM;
		goto go_iounmap_sysctrl_base;
	}
	res = platform_get_resource_byname(pdev, IORESOURCE_REG, MAC_PHY_NAME);  // 这里IORESOURCE_REG和定义资源结构体时对应
	if (unlikely(res == NULL)) {
		de_err(&pdev->dev, "get resource[%s] failed\n", MAC_PHY_NAME);
		ret = -ENXIO;
		goto go_iounmap_base;
	}
	mac->phy_addr = res->start;  // 这里和定义资源结构体时对应
	ret = platform_get_irq_byname(pdev, MAC_IRQ_NAME);  // 错误返回负值
	if (unlikely(ret < 0)) {
		de_err(&pdev->dev, "get irq[%s] failed\n", MAC_IRQ_NAME);
		goto go_iounmap_base;
	}
	mac->irq = irq_create_mapping(NULL, ret);  // 手动定义的资源数据,platform_get_irq_byname()不会自动映射,需要手动映射,这里NULL会使用默认的irq_default_domain,我使用的平台只有一个中断控制器应该是OK的,后面再理解吧
	// 注册中断回调函数mac_irq_handler(),错误返回负值,mac参数将作为参数传递给mac_irq_handler()使用,在使用共享中断时devm_request_irq()最后一个参数不能是NULL
	ret = devm_request_irq(&pdev->dev, mac->irq, mac_irq_handler, 0, MODULE_MAC_IRQ_NAME, mac);
	if (unlikely(ret < 0)) {
		de_err(&pdev->dev, "request irq[%s] failed\n", MODULE_MAC_IRQ_NAME);
		goto go_unmap_irq;
	}
	/******************************************************/
	
	instance_num = atomic_add_return(1, &mac_instance_num) - 1;  // 获取当前已注册设备的数量,从0开始,后续若注册失败也不再回减,以免发生冲突
	
#ifdef MODULE_MISC_DEVICE  // 杂项设备注册流程
	mac->miscdev.minor = MISC_DYNAMIC_MINOR;  // 设置次设备号进行动态分配,当然也可以手动指定
	mac->miscdev.fops = &mac_file_operations; // 设备文件操作接口,我们稍后定义
	mac->miscdev.parent = &pdev_dev;  // 暂不清楚作用
	// C库中也有一个asprintf()函数作用类似
	mac->miscdev.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s%d", "mac", instance_num);  // 申请一块内存,用于存放格式化的字符串,该字符串用于设备文件名,错误返回NULL
	if (unlikely(mac->miscdev.name == NULL)) {
		de_err(&pdev->dev, "alloc miscdev name failed\n");
		ret = -ENOMEM;  // 内存不足
		goto go_free_irq;  // 新修改
	}
	ret = misc_register(&mac->miscdev);  // 杂项设备注册,返回值使用IS_ERR()检查
	if (IS_ERR(ret)) {
		de_err(&pdev->dev, "misc register[%s] failed\n", mac->miscdev.name);
		goto go_kfree_name;
	}
#else  // 以下是普通设备的注册流程
	cdev_init(&mac->cdev, &mac_file_operations);  // 静态初始化cdev,设备文件操作接口我们稍后定义
	mac->cdev.owner = THIS_MODULE;  // 指定当前模块为cdev拥有者,THIS_MODULE,是一个结构体指针
	
	ret = alloc_chrdev_region(&mac->cdev.dev, instance_num, 1, "mac");  // 申请设备号,当然也可以不用该函数,手动指定
	if (IS_ERR(ret)) {
		de_err(&pdev->dev, "alloc chrdev failed\n");
		goto go_free_irq;  // 新修改
	}
	ret = cdev_add(&mac->cdev, mac->cdev.dev, 1);  // 注册cdev
	if (IS_ERR(ret)) {
		de_err(&pdev->dev, "cdev add failed\n");
		goto go_unregister_chrdev;
	}
	mac->class = class_create(THIS_MODULE, "mac");  // 创建class,用于设备文件的创建
	if (IS_ERR(mac->class)) {
		de_err(&pdev->dev, "class create failed\n");
		goto go_cdev_del;
	}
	// 创建设备文件,文件名为mac%u格式化后的字符串
	device = device_create(mac->class, NULL, mac->cdev.dev, NULL, "mac%u", instance_num);
	if (IS_ERR(device)) {
		de_err(&pdev->dev, "device create failed\n");
		ret = PTR_ERR(device);
		goto go_class_destroy;
	}
#endif

	platform_set_drvdata(pdev, mac); // 等同于pdev->dev.driver_data = mac;
	dev_info(&pdev->dev, "mac");
	return 0;
	
#ifdef MODULE_MISC_DEVICE
	goto go_misc_deregister;  // 不会运行到这
go_misc_deregister:
	misc_deregister(&mac->miscdev);
go_kfree_name:
	devm_kfree(&pdev->dev, (void *)mac->miscdev.name);
#else
	goto go_device_destory;  // 不会运行到这
go_device_destory:
	device_destory(mac->class, mac->cdev.dev);  // mac->cdev.dev是设备号
go_class_destroy:
	class_destroy(mac->class);
go_cdev_del:
	cdev_del(&mac->cdev);
go_unregister_chrdev:
	unregister_chrdev_region(mac->cdev.dev, 1);  // mac->cdev.dev是设备号
#endif
go_free_irq:
	devm_free_irq(&pdev->dev, mac->irq, mac);
go_unmap_irq:
	irq_dispose_mapping(mac->irq);  // 暂时不确定用法是否正确
go_iounmap_base:
	devm_iounmap(&pdev->dev, mac->base);
go_iounmap_sysctrl_base:
	devm_iounmap(&pdev->dev, mac->sysctrl_base);
go_kfree_mac:
	devm_kfree(&pdev->dev, mac);
go_ret:
	return ret;
}

2.3 remove

mac_remove()函数需要同样做些修改。

int mac_remove(struct platform_device *pdev)
{
	struct driver_info *mac = platform_get_drvdata(pdev); // 等同于pdev->dev.driver_data

#ifdef MODULE_MISC_DEVICE
	misc_deregister(&mac->miscdev);
	devm_kfree(&pdev->dev, (void *)mac->miscdev.name);
#else
	device_destory(mac->class, mac->cdev.dev);  // mac->cdev.dev是设备号
	class_destroy(mac->class);
	cdev_del(&mac->cdev);
	unregister_chrdev_region(mac->cdev.dev, 1);  // mac->cdev.dev是设备号
#endif
	devm_free_irq(&pdev->dev, mac->irq, mac);
	irq_dispose_mapping(mac->irq);  // 暂时不确定用法是否正确
	devm_iounmap(&pdev->dev, mac->base);
	devm_iounmap(&pdev->dev, mac->sysctrl_base);
	devm_kfree(&pdev->dev, mac);
}

3. 其他

3.1 全局数据

我们在mac_probe()中对struce device_info增加了一些成员,这里完善一下。

#include <linux/misdevice.h>
#include <linux/cdev.h>
struct driver_info {  // 自定义结构体类型
#ifdef MODULE_MISC_DEVICE // 将模块作为杂项设备自动注册
	struct miscdevice miscdev; // 杂项设备结构体,会自动创建设备节点
#else // 将模块作为普通设备自动注册
	struct cdev cdev; // 设备结构体,可以使用动态内存申请,也可以静态定义
		struct class *class; // 用于自动创建设备节点
#endif
	/**********************************/
	void __iomem *sysctrl_base;  // 裸驱操作时的寄存器位置
	void __iomem *base;
	uint32_t phy_addr;  // MDIO操作PHY时记录PHY的地址
	uint32_t irq;  // 中断释放的时候需要使用
	/**********************************/
}

3.2 中断服务函数

// 参数p,即struct driver_info *mac
irqreturn_t mac_irq_handler(int irq, void *p)
{
	printk("%s\n", __func__);
	return IRQ_HANDLED;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值