在系统运行时,外设 IO 资源的物理地址是已知的,由硬件的设计决定(参考SOC的datesheet,一般会有memorymap)。驱动程序不能通过物理地址访问IO资源,必须将其映射到内核态的虚拟地址空间。常见的接口就是 ioremap。而在 Linux 中,还有其他的一些常见的类似接口,ioremap_wc、ioremap_wc、ioremap_np 等,他们的区别又是什么呢?
注:本篇文章,都以 ARM64 架构为例。
代码路径:arch/arm64/include/asm/io.h
#define _PAGE_IOREMAP PROT_DEVICE_nGnRE
#define ioremap_wc(addr, size) \
ioremap_prot((addr), (size), PROT_NORMAL_NC)
#define ioremap_np(addr, size) \
ioremap_prot((addr), (size), PROT_DEVICE_nGnRnE)
看到这,不知大家会不会有个疑问, arch/arm64/
目录下没有 ioremap 函数的声明或实现呢?按照 Linux 源码的风格,include/asm-generic/
提供了架构无关的通用默认实现,而 arch/xxx/ 目录下的实现用于特定架构的覆盖或替代。
代码路径:include/asm-generic/io.h
static inline void __iomem *ioremap(phys_addr_t addr, size_t size)
{
/* _PAGE_IOREMAP needs to be supplied by the architecture */
return ioremap_prot(addr, size, _PAGE_IOREMAP);
}
#ifndef ioremap_wt
#define ioremap_wt ioremap
#endif
arch/arm64/include/asm/memory.h
/*
* Memory types available.
*
* IMPORTANT: MT_NORMAL must be index 0 since vm_get_page_prot() may 'or' in
* the MT_NORMAL_TAGGED memory type for PROT_MTE mappings. Note
* that protection_map[] only contains MT_NORMAL attributes.
*/
#define MT_NORMAL 0
#define MT_NORMAL_TAGGED 1
#define MT_NORMAL_NC 2
#define MT_DEVICE_nGnRnE 3
#define MT_DEVICE_nGnRE 4
/*
* Memory types for Stage-2 translation
*/
#define MT_S2_NORMAL 0xf
#define MT_S2_DEVICE_nGnRE 0x1
上面 ioremap_xxx 接口中用到的属性参数主要是 PROT_DEVICE_nGnRE
和 PROT_NORMAL_NC
。看下这两个 prot_val 分别代表什么含义:
arch/arm64/include/asm/pgtable-prot.h
#define PROT_DEFAULT (_PROT_DEFAULT | PTE_MAYBE_NG)
#define PROT_SECT_DEFAULT (_PROT_SECT_DEFAULT | PMD_MAYBE_NG)
#define PROT_DEVICE_nGnRnE (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_DEVICE_nGnRnE))
#define PROT_DEVICE_nGnRE (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_DEVICE_nGnRE))
#define PROT_NORMAL_NC (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL_NC))
#define PROT_NORMAL (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL))
#define PROT_NORMAL_TAGGED (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL_TAGGED))
#define PROT_SECT_DEVICE_nGnRE (PROT_SECT_DEFAULT | PMD_SECT_PXN | PMD_SECT_UXN | PMD_ATTRINDX(MT_DEVICE_nGnRE))
#define PROT_SECT_NORMAL (PROT_SECT_DEFAULT | PMD_SECT_PXN | PMD_SECT_UXN | PTE_WRITE | PMD_ATTRINDX(MT_NORMAL))
#define PROT_SECT_NORMAL_EXEC (PROT_SECT_DEFAULT | PMD_SECT_UXN | PMD_ATTRINDX(MT_NORMAL))
参照 ARMv8 手册中对内存属性的描述,内存可以分为 DEVICE
和 NORMAL
两大类型以及 Device memory 依据是否可合并等属性。
Device属性的内存空间还有下面三种子属性,都有打开和关闭的定义。
static int rockchip_canfd_probe(struct platform_device *pdev)
{
struct net_device *ndev;
struct rockchip_canfd *rcan;
struct resource *res;
void __iomem *addr;
int err, irq;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "could not get a valid irq\n");
return -ENODEV;
}
/* 从设备树获取外设控制器的寄存器地址资源,并做映射 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
addr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(addr))
return -EBUSY;
......
......
}
以 rk3568 Can 总线驱动中,调用的是 devm_ioremap_resource
接口。我在来看该接口的详细实现。
/**
* devm_ioremap_resource() - check, request region, and ioremap resource
* @dev: generic device to handle the resource for
* @res: resource to be handled
*
* Checks that a resource is a valid memory region, requests the memory
* region and ioremaps it. All operations are managed and will be undone
* on driver detach.
*
* Usage example:
*
* res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
* base = devm_ioremap_resource(&pdev->dev, res);
* if (IS_ERR(base))
* return PTR_ERR(base);
*
* Return: a pointer to the remapped memory or an ERR_PTR() encoded error code
* on failure.
*/
void __iomem *devm_ioremap_resource(struct device *dev,
const struct resource *res)
{
return __devm_ioremap_resource(dev, res, DEVM_IOREMAP);
}
EXPORT_SYMBOL(devm_ioremap_resource);
static void __iomem *
__devm_ioremap_resource(struct device *dev, const struct resource *res,
enum devm_ioremap_type type)
{
......
dest_ptr = __devm_ioremap(dev, res->start, size, type);
if (!dest_ptr) {
devm_release_mem_region(dev, res->start, size);
ret = dev_err_probe(dev, -ENOMEM, "ioremap failed for resource %pR\n", res);
return IOMEM_ERR_PTR(ret);
}
return dest_ptr;
}
static void __iomem *__devm_ioremap(struct device *dev, resource_size_t offset,
resource_size_t size,
enum devm_ioremap_type type)
{
void __iomem **ptr, *addr = NULL;
ptr = devres_alloc_node(devm_ioremap_release, sizeof(*ptr), GFP_KERNEL,
dev_to_node(dev));
if (!ptr)
return NULL;
switch (type) {
case DEVM_IOREMAP:
addr = ioremap(offset, size);
break;
case DEVM_IOREMAP_UC:
addr = ioremap_uc(offset, size);
break;
case DEVM_IOREMAP_WC:
addr = ioremap_wc(offset, size);
break;
case DEVM_IOREMAP_NP:
addr = ioremap_np(offset, size);
break;
}
if (addr) {
*ptr = addr;
devres_add(dev, ptr);
} else
devres_free(ptr);
return addr;
}
从上面我们可以看到,默认将设备的寄存器地址资源,使用 ioremap
接口,映射成了 PROT_DEVICE_nGnRE
属性,即 Device 属性。