内存映射 I/O 函数详解

本文档详细解释 Linux 内核中用于内存映射 I/O 操作的核心函数,包括资源管理、地址映射和 I/O 访问函数。

目录

  1. 资源管理函数
  2. 地址映射函数
  3. I/O 访问函数
  4. 内存操作函数

资源管理函数

request_mem_region

函数原型:

struct resource *request_mem_region(unsigned long start, unsigned long n, const char *name)

宏定义:

#define request_region(start,n,name)	__request_region(&ioport_resource, (start), (n), (name))
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))

request_mem_region 是一个宏,展开为 __request_region(&iomem_resource, start, n, name),用于在内存资源树中注册并保留一段物理内存地址范围。

核心实现:__request_region

struct resource * __request_region(struct resource *parent, unsigned long start, unsigned long n, const char *name)
{
	struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);

	if (res) {
		memset(res, 0, sizeof(*res));
		res->name = name;
		res->start = start;
		res->end = start + n - 1;
		res->flags = IORESOURCE_BUSY;

		write_lock(&resource_lock);

		for (;;) {
			struct resource *conflict;

			conflict = __request_resource(parent, res);
			if (!conflict)
				break;
			if (conflict != parent) {
				parent = conflict;
				if (!(conflict->flags & IORESOURCE_BUSY))
					continue;
			}

			/* Uhhuh, that didn't work out.. */
			kfree(res);
			res = NULL;
			break;
		}
		write_unlock(&resource_lock);
	}
	return res;
}

逐行解释:

  1. struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);

    • 分配一个 resource 结构体,用于表示要保留的资源
    • GFP_KERNEL:在内核空间分配,可睡眠
    • 如果分配失败,resNULL
  2. if (res) { ... }

    • 检查内存分配是否成功
    • 只有分配成功才继续初始化
  3. memset(res, 0, sizeof(*res));

    • 将整个结构体清零,确保所有字段初始化为 0
    • 包括 parentsiblingchild 等指针字段
  4. res->name = name;

    • 设置资源名称,用于标识资源的所有者
    • /proc/ioports/proc/iomem 中显示
  5. res->start = start;

    • 设置资源的起始地址
  6. res->end = start + n - 1;

    • 计算并设置资源的结束地址
    • n 是资源大小,结束地址 = 起始地址 + 大小 - 1
  7. res->flags = IORESOURCE_BUSY;

    • 设置资源标志为"忙碌"状态
    • 表示该资源已被占用,不能被其他驱动使用
  8. write_lock(&resource_lock);

    • 获取资源树的写锁,保护资源树结构
    • 防止并发访问导致资源冲突
  9. for (;;) { ... }

    • 无限循环,直到成功分配或失败退出
  10. struct resource *conflict;

    • 声明冲突资源指针
  11. conflict = __request_resource(parent, res);

    • 调用内部函数尝试将资源插入资源树
    • 如果返回 NULL,表示没有冲突,分配成功
    • 如果返回非 NULL,表示存在冲突的资源
  12. if (!conflict) break;

    • 如果没有冲突,跳出循环,分配成功
  13. if (conflict != parent) { ... }

    • 如果冲突资源不是父资源本身,说明冲突发生在子资源中
  14. parent = conflict;

    • 将父资源更新为冲突资源
    • 尝试在冲突资源的子树中查找可用空间
  15. if (!(conflict->flags & IORESOURCE_BUSY)) continue;

    • 如果冲突资源不是忙碌状态(可能是预留资源)
    • 继续循环,尝试在其子树中分配
  16. kfree(res); res = NULL; break;

    • 如果冲突资源是忙碌的,释放已分配的资源结构
    • 设置返回值为 NULL,表示分配失败
    • 跳出循环
  17. write_unlock(&resource_lock);

    • 释放写锁
  18. return res;

    • 返回资源指针
    • 成功时返回有效的 resource 指针
    • 失败时返回 NULL

__request_resource 函数详解:

static struct resource * __request_resource(struct resource *root, struct resource *new)
{
	unsigned long start = new->start;
	unsigned long end = new->end;
	struct resource *tmp, **p;

	if (end < start)
		return root;
	if (start < root->start)
		return root;
	if (end > root->end)
		return root;
	p = &root->child;
	for (;;) {
		tmp = *p;
		if (!tmp || tmp->start > end) {
			new->sibling = tmp;
			*p = new;
			new->parent = root;
			return NULL;
		}
		p = &tmp->sibling;
		if (tmp->end < start)
			continue;
		return tmp;
	}
}

逐行解释:

  1. unsigned long start = new->start;unsigned long end = new->end;

    • 提取新资源的起始和结束地址
  2. struct resource *tmp, **p;

    • tmp:遍历时指向当前资源节点
    • p:指向资源指针的指针,用于插入新节点
  3. if (end < start) return root;

    • 检查结束地址是否小于起始地址(无效范围)
    • 如果无效,返回 root 表示错误
  4. if (start < root->start) return root;

    • 检查新资源是否超出父资源的起始范围
    • 如果超出,返回 root 表示错误
  5. if (end > root->end) return root;

    • 检查新资源是否超出父资源的结束范围
    • 如果超出,返回 root 表示错误
  6. p = &root->child;

    • 初始化指针,指向父资源的第一个子资源
  7. for (;;) { ... }

    • 无限循环,遍历兄弟资源链表
  8. tmp = *p;

    • 获取当前指针指向的资源节点
  9. if (!tmp || tmp->start > end) { ... }

    • 如果没有子资源,或者当前资源的起始地址大于新资源的结束地址
    • 说明找到了插入位置(在当前资源之前)
  10. new->sibling = tmp;

    • 将新资源的兄弟指针指向当前资源
    • 新资源插入到当前资源之前
  11. *p = new;

    • 将父资源的子指针指向新资源
    • 完成插入操作
  12. new->parent = root;

    • 设置新资源的父指针
  13. return NULL;

    • 返回 NULL 表示没有冲突,插入成功
  14. p = &tmp->sibling;

    • 移动到下一个兄弟资源
  15. if (tmp->end < start) continue;

    • 如果当前资源的结束地址小于新资源的起始地址
    • 说明没有重叠,继续查找下一个
  16. return tmp;

    • 如果执行到这里,说明当前资源与新资源有重叠
    • 返回冲突的资源指针

使用示例:

struct resource *res;
res = request_mem_region(0xE0000000, 0x1000, "my_device");
if (!res) {
    printk("Failed to request memory region\n");
    return -EBUSY;
}
// 使用资源...

release_mem_region

函数原型:

void release_mem_region(unsigned long start, unsigned long n)

宏定义:

#define release_mem_region(start,n)	__release_region(&iomem_resource, (start), (n))

release_mem_region 用于释放之前通过 request_mem_region 保留的内存资源。

核心实现:__release_region

void __release_region(struct resource *parent, unsigned long start, unsigned long n)
{
	struct resource **p;
	unsigned long end;

	p = &parent->child;
	end = start + n - 1;

	write_lock(&resource_lock);

	for (;;) {
		struct resource *res = *p;

		if (!res)
			break;
		if (res->start <= start && res->end >= end) {
			if (!(res->flags & IORESOURCE_BUSY)) {
				p = &res->child;
				continue;
			}
			if (res->start != start || res->end != end)
				break;
			*p = res->sibling;
			write_unlock(&resource_lock);
			kfree(res);
			return;
		}
		p = &res->sibling;
	}

	write_unlock(&resource_lock);

	printk(KERN_WARNING "Trying to free nonexistent resource <%08lx-%08lx>\n", start, end);
}

逐行解释:

  1. struct resource **p;

    • 指向资源指针的指针,用于遍历资源树
  2. unsigned long end;

    • 要释放资源的结束地址
  3. p = &parent->child;

    • 初始化指针,指向父资源的第一个子资源
  4. end = start + n - 1;

    • 计算结束地址
  5. write_lock(&resource_lock);

    • 获取写锁,保护资源树
  6. for (;;) { ... }

    • 无限循环,遍历资源树查找要释放的资源
  7. struct resource *res = *p;

    • 获取当前指针指向的资源
  8. if (!res) break;

    • 如果没有更多资源,跳出循环(未找到)
  9. if (res->start <= start && res->end >= end) { ... }

    • 检查当前资源是否包含要释放的范围
    • res->start <= start:当前资源起始地址小于等于要释放的起始地址
    • res->end >= end:当前资源结束地址大于等于要释放的结束地址
  10. if (!(res->flags & IORESOURCE_BUSY)) { ... }

    • 如果当前资源不是忙碌状态(可能是父资源或预留资源)
    • 继续在其子树中查找
  11. p = &res->child; continue;

    • 进入子树查找
    • 继续循环
  12. if (res->start != start || res->end != end) break;

    • 如果资源范围不完全匹配(部分重叠)
    • 这是错误情况,跳出循环
  13. *p = res->sibling;

    • 从链表中移除资源节点
    • 将当前指针指向下一个兄弟资源
  14. write_unlock(&resource_lock);

    • 释放写锁
  15. kfree(res);

    • 释放资源结构体的内存
  16. return;

    • 成功释放,返回
  17. p = &res->sibling;

    • 移动到下一个兄弟资源,继续查找
  18. printk(KERN_WARNING "...");

    • 如果执行到这里,说明未找到要释放的资源
    • 打印警告信息

使用示例:

release_mem_region(0xE0000000, 0x1000);

地址映射函数

ioremap

函数原型:

void __iomem *ioremap(unsigned long phys_addr, unsigned long size)

ioremap 将物理地址空间映射到内核虚拟地址空间,使得内核可以访问设备的内存映射 I/O 区域。

核心实现:__ioremap

void __iomem * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
	void __iomem * addr;
	struct vm_struct * area;
	unsigned long offset, last_addr;

	/* Don't allow wraparound or zero size */
	last_addr = phys_addr + size - 1;
	if (!size || last_addr < phys_addr)
		return NULL;

	/*
	 * Don't remap the low PCI/ISA area, it's always mapped..
	 */
	if (phys_addr >= ISA_START_ADDRESS && last_addr < ISA_END_ADDRESS)
		return (void __iomem *) phys_to_virt(phys_addr);

	/*
	 * Don't allow anybody to remap normal RAM that we're using..
	 */
	if (phys_addr <= virt_to_phys(high_memory - 1)) {
		char *t_addr, *t_end;
		struct page *page;

		t_addr = __va(phys_addr);
		t_end = t_addr + (size - 1);
	   
		for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++)
			if(!PageReserved(page))
				return NULL;
	}

	/*
	 * Mappings have to be page-aligned
	 */
	offset = phys_addr & ~PAGE_MASK;
	phys_addr &= PAGE_MASK;
	size = PAGE_ALIGN(last_addr+1) - phys_addr;

	/*
	 * Ok, go for it..
	 */
	area = get_vm_area(size, VM_IOREMAP | (flags << 20));
	if (!area)
		return NULL;
	area->phys_addr = phys_addr;
	addr = (void __iomem *) area->addr;
	if (ioremap_page_range((unsigned long) addr,
			(unsigned long) addr + size, phys_addr, flags)) {
		vunmap((void __force *) addr);
		return NULL;
	}
	return (void __iomem *) (offset + (char __iomem *)addr);
}

逐行解释:

  1. void __iomem * addr;

    • 返回的虚拟地址指针
    • __iomem 标记表示这是 I/O 内存地址,编译器会进行类型检查
  2. struct vm_struct * area;

    • 虚拟内存区域结构,用于管理映射的虚拟地址空间
  3. unsigned long offset, last_addr;

    • offset:物理地址在页内的偏移量
    • last_addr:要映射区域的最后一个地址
  4. last_addr = phys_addr + size - 1;

    • 计算最后一个地址
  5. if (!size || last_addr < phys_addr) return NULL;

    • 检查参数有效性
    • !size:大小为 0
    • last_addr < phys_addr:地址回绕(溢出)
    • 如果无效,返回 NULL
  6. if (phys_addr >= ISA_START_ADDRESS && last_addr < ISA_END_ADDRESS) { ... }

    • 检查是否在 ISA 地址范围内(0xa0000 - 0x100000)
    • ISA 地址已经映射,直接返回对应的虚拟地址
  7. return (void __iomem *) phys_to_virt(phys_addr);

    • 将物理地址转换为虚拟地址
    • 不需要创建新的映射
  8. if (phys_addr <= virt_to_phys(high_memory - 1)) { ... }

    • 检查物理地址是否在正常 RAM 范围内
    • 防止映射正在使用的系统内存
    • 注意:这里比较的是物理地址,与 iounmap 中第 644 行的检查不同:
      • __ioremap(第 416 行)phys_addr <= virt_to_phys(high_memory - 1) - 比较物理地址,需要进一步检查页属性(PageReserved
      • iounmap(第 644 行)addr <= high_memory - 比较虚拟地址,直接返回(低内存区域不需要释放)
  9. char *t_addr, *t_end;struct page *page;

    • t_addrt_end:转换后的虚拟地址范围
    • page:页结构指针
  10. t_addr = __va(phys_addr);t_end = t_addr + (size - 1);

    • 将物理地址转换为内核虚拟地址
    • 计算结束地址
  11. for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++)

    • 遍历范围内的所有页
    • virt_to_page:将虚拟地址转换为页结构指针
  12. if(!PageReserved(page)) return NULL;

    • 检查页是否被标记为保留(PageReserved
    • 如果页不是保留的,说明是正常 RAM,不允许映射
    • 返回 NULL 表示错误
  13. offset = phys_addr & ~PAGE_MASK;

    • 计算物理地址在页内的偏移量
    • PAGE_MASK 是页大小的掩
    • ~PAGE_MASK 取反得到页内偏移掩码
    • & ~PAGE_MASK 提取页内偏移
  14. phys_addr &= PAGE_MASK;

    • 将物理地址向下对齐到页边界
    • 移除页内偏移,得到页起始地址
  15. size = PAGE_ALIGN(last_addr+1) - phys_addr;

    • 将大小向上对齐到页边界
    • PAGE_ALIGN 宏将地址向上对齐到页边界
    • 计算对齐后的大小
  16. area = get_vm_area(size, VM_IOREMAP | (flags << 20));

    • 分配虚拟地址空间
    • VM_IOREMAP:标记为 I/O 映射区域
    • flags << 20:将标志位左移 20 位,存储在 area->flags 的高位
  17. if (!area) return NULL;

    • 如果分配失败,返回 NULL
  18. area->phys_addr = phys_addr;

    • 保存物理地址,用于后续的 iounmap
  19. addr = (void __iomem *) area->addr;

    • 获取分配的虚拟地址
  20. if (ioremap_page_range(...)) { ... }

    • 建立页表映射,将物理地址映射到虚拟地址
    • 如果失败,执行清理并返回 NULL
  21. vunmap((void __force *) addr);return NULL;

    • 如果映射失败,释放虚拟地址空间
    • 返回 NULL 表示错误
  22. return (void __iomem *) (offset + (char __iomem *)addr);

    • 返回虚拟地址加上页内偏移
    • 这样返回的地址对应原始物理地址(可能不是页对齐的)

页表映射函数:ioremap_page_range

static int ioremap_page_range(unsigned long addr,
		unsigned long end, unsigned long phys_addr, unsigned long flags)
{
	pgd_t *pgd;
	unsigned long next;
	int err;

	BUG_ON(addr >= end);
	flush_cache_all();
	phys_addr -= addr;
	pgd = pgd_offset_k(addr);
	spin_lock(&init_mm.page_table_lock);
	do {
		next = pgd_addr_end(addr, end);
		err = ioremap_pud_range(pgd, addr, next, phys_addr+addr, flags);
		if (err)
			break;
	} while (pgd++, addr = next, addr != end);
	spin_unlock(&init_mm.page_table_lock);
	flush_tlb_all();
	return err;
}

逐行解释:

  1. BUG_ON(addr >= end);

    • 断言检查,确保起始地址小于结束地址
  2. flush_cache_all();

    • 刷新所有 CPU 缓存,确保一致性
  3. phys_addr -= addr;

    • 计算物理地址相对于虚拟地址的偏移
    • 后续通过 phys_addr + addr 计算每个页的物理地址
  4. pgd = pgd_offset_k(addr);

    • 获取页全局目录(PGD)项
    • pgd_offset_k 返回内核页表中对应虚拟地址的 PGD 项
  5. spin_lock(&init_mm.page_table_lock);

    • 获取页表锁,保护页表操作
  6. do { ... } while (...);

    • 循环处理每个 PGD 项
  7. next = pgd_addr_end(addr, end);

    • 计算当前 PGD 项覆盖的地址范围结束位置
  8. err = ioremap_pud_range(...);

    • 递归处理页上级目录(PUD)、页中间目录(PMD)和页表项(PTE)
  9. if (err) break;

    • 如果出错,跳出循环
  10. pgd++, addr = next, addr != end

    • 移动到下一个 PGD 项
    • 继续处理直到所有地址映射完成
  11. spin_unlock(&init_mm.page_table_lock);

    • 释放页表锁
  12. flush_tlb_all();

    • 刷新所有 TLB(转换后备缓冲区)
    • 使新的页表映射生效
  13. return err;

    • 返回错误码(0 表示成功)

使用示例:

void __iomem *base;
base = ioremap(0xE0000000, 0x1000);
if (!base) {
    printk("Failed to ioremap\n");
    return -ENOMEM;
}
// 使用 base 访问设备寄存器...

iounmap

函数原型:

void iounmap(volatile void __iomem *addr)

iounmap 释放通过 ioremap 创建的虚拟地址映射。

实现:

void iounmap(volatile void __iomem *addr)
{
	struct vm_struct *p;
	if ((void __force *) addr <= high_memory) 
		return;

	/*
	 * __ioremap special-cases the PCI/ISA range by not instantiating a
	 * vm_area and by simply returning an address into the kernel mapping
	 * of ISA space.   So handle that here.
	 */
	if (addr >= phys_to_virt(ISA_START_ADDRESS) &&
			addr < phys_to_virt(ISA_END_ADDRESS))
		return;

	write_lock(&vmlist_lock);
	p = __remove_vm_area((void *) (PAGE_MASK & (unsigned long __force) addr));
	if (!p) { 
		printk("iounmap: bad address %p\n", addr);
		goto out_unlock;
	}

	if ((p->flags >> 20) && p->phys_addr < virt_to_phys(high_memory) - 1) {
		change_page_attr(virt_to_page(__va(p->phys_addr)),
				 p->size >> PAGE_SHIFT,
				 PAGE_KERNEL);
		global_flush_tlb();
	} 
out_unlock:
	write_unlock(&vmlist_lock);
	kfree(p); 
}

逐行解释:

  1. struct vm_struct *p;

    • 虚拟内存区域结构指针
  2. if ((void __force *) addr <= high_memory) return;

    • 检查虚拟地址是否在低内存区域(系统 RAM 的虚拟地址范围)
    • 如果是,说明不是通过 ioremap 映射的,直接返回
    • high_memory 是系统 RAM 的最高虚拟地址
    • 注意:这里比较的是虚拟地址,与 __ioremap 中第 416 行的检查不同:
      • __ioremap(第 416 行)phys_addr <= virt_to_phys(high_memory - 1) - 比较物理地址,检查要映射的物理地址是否在系统 RAM 范围内
      • iounmap(第 644 行)addr <= high_memory - 比较虚拟地址,快速判断虚拟地址是否在低内存区域
      • 前者需要进一步检查页属性(PageReserved),后者直接返回(因为低内存区域的虚拟地址不需要通过 iounmap 释放)
  3. if (addr >= phys_to_virt(ISA_START_ADDRESS) && addr < phys_to_virt(ISA_END_ADDRESS)) return;

    • 检查是否是 ISA 地址范围
    • ISA 地址是直接映射的,不需要释放,直接返回
  4. write_lock(&vmlist_lock);

    • 获取虚拟内存列表的写锁
  5. p = __remove_vm_area((void *) (PAGE_MASK & (unsigned long __force) addr));

    • 从虚拟内存列表中移除对应的区域
    • PAGE_MASK & addr:将地址向下对齐到页边界
    • __remove_vm_area 返回对应的 vm_struct 结构
  6. if (!p) { ... }

    • 如果未找到对应的区域,说明地址无效
  7. printk("iounmap: bad address %p\n", addr);

    • 打印错误信息
  8. goto out_unlock;

    • 跳转到解锁处
  9. if ((p->flags >> 20) && p->phys_addr < virt_to_phys(high_memory) - 1) { ... }

    • 检查是否有特殊标志(从 flags 的高位提取)
    • 检查物理地址是否在系统 RAM 范围内
    • 如果是,需要恢复页属性
  10. change_page_attr(virt_to_page(__va(p->phys_addr)), p->size >> PAGE_SHIFT, PAGE_KERNEL);

    • 将页属性改回 PAGE_KERNEL(正常内核页)
    • virt_to_page:将虚拟地址转换为页结构
    • p->size >> PAGE_SHIFT:计算页数
  11. global_flush_tlb();

    • 刷新所有 TLB,使页属性更改生效
  12. write_unlock(&vmlist_lock);

    • 释放写锁
  13. kfree(p);

    • 释放 vm_struct 结构的内存

使用示例:

iounmap(base);

I/O 访问函数

ioread8

函数原型:

unsigned int ioread8(void __iomem *addr)

ioread8 从 I/O 内存地址读取一个 8 位(字节)值。

实现:

unsigned int fastcall ioread8(void __iomem *addr)
{
	IO_COND(addr, return inb(port), return readb(addr));
}

逐行解释:

  1. fastcall

    • 函数调用约定,参数通过寄存器传递,提高性能
  2. IO_COND(addr, return inb(port), return readb(addr));

    • 宏展开,根据地址类型选择不同的读取方式
    • 如果是 PIO(端口 I/O),使用 inb(port)
    • 如果是 MMIO(内存映射 I/O),使用 readb(addr)

IO_COND 宏定义:

#define IO_COND(addr, is_pio, is_mmio) do {			\
	unsigned long port = (unsigned long __force)addr;	\
	if (port < PIO_RESERVED) {				\
		VERIFY_PIO(port);				\
		port &= PIO_MASK;				\
		is_pio;						\
	} else {						\
		is_mmio;					\
	}							\
} while (0)

逐行解释:

  1. unsigned long port = (unsigned long __force)addr;

    • 将地址转换为无符号长整型
    • __force 强制类型转换,忽略类型检查
  2. if (port < PIO_RESERVED) { ... }

    • PIO_RESERVED 定义为 0x40000UL
    • 如果地址小于此值,认为是 PIO 地址
  3. VERIFY_PIO(port);

    • 验证 PIO 地址格式
    • VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)
    • 检查地址的高位是否为 PIO_OFFSET(0x10000)
  4. port &= PIO_MASK;

    • 提取实际的端口号(低 16 位)
    • PIO_MASK0x0ffffUL
  5. is_pio;

    • 执行 PIO 分支代码(return inb(port)
  6. else { is_mmio; }

    • 执行 MMIO 分支代码(return readb(addr)

使用示例:

u8 value = ioread8(base + 0x10);

iowrite8

函数原型:

void iowrite8(u8 val, void __iomem *addr)

iowrite8 向 I/O 内存地址写入一个 8 位(字节)值。

实现:

void fastcall iowrite8(u8 val, void __iomem *addr)
{
	IO_COND(addr, outb(val,port), writeb(val, addr));
}

逐行解释:

  1. IO_COND(addr, outb(val,port), writeb(val, addr));
    • 如果是 PIO,使用 outb(val, port) 写入端口
    • 如果是 MMIO,使用 writeb(val, addr) 写入内存

使用示例:

iowrite8(0x42, base + 0x10);

ioread8_rep

函数原型:

void ioread8_rep(void __iomem *addr, void *dst, unsigned long count)

ioread8_rep 从 I/O 内存地址重复读取多个 8 位值到缓冲区。

实现:

void fastcall ioread8_rep(void __iomem *addr, void *dst, unsigned long count)
{
	IO_COND(addr, insb(port,dst,count), mmio_insb(addr, dst, count));
}

逐行解释:

  1. IO_COND(addr, insb(port,dst,count), mmio_insb(addr, dst, count));
    • 如果是 PIO,使用 insb(端口字符串输入)
    • 如果是 MMIO,使用 mmio_insb(内存映射 I/O 字符串输入)

mmio_insb 实现:

static inline void mmio_insb(void __iomem *addr, u8 *dst, int count)
{
	while (--count >= 0) {
		u8 data = __raw_readb(addr);
		*dst = data;
		dst++;
	}
}

逐行解释:

  1. while (--count >= 0) { ... }

    • 循环 count
    • --count >= 0:先减 1,再判断是否大于等于 0
  2. u8 data = __raw_readb(addr);

    • 使用原始读取函数读取一个字节
    • __raw_readb 不进行字节序转换
  3. *dst = data;dst++;

    • 将数据写入目标缓冲区
    • 移动目标指针

使用示例:

u8 buffer[256];
ioread8_rep(base + 0x100, buffer, 256);

iowrite8_rep

函数原型:

void iowrite8_rep(void __iomem *addr, const void *src, unsigned long count)

iowrite8_rep 从缓冲区重复写入多个 8 位值到 I/O 内存地址。

实现:

void fastcall iowrite8_rep(void __iomem *addr, const void *src, unsigned long count)
{
	IO_COND(addr, outsb(port, src, count), mmio_outsb(addr, src, count));
}

逐行解释:

  1. IO_COND(addr, outsb(port, src, count), mmio_outsb(addr, src, count));
    • 如果是 PIO,使用 outsb(端口字符串输出)
    • 如果是 MMIO,使用 mmio_outsb(内存映射 I/O 字符串输出)

mmio_outsb 实现:

static inline void mmio_outsb(void __iomem *addr, const u8 *src, int count)
{
	while (--count >= 0) {
		__raw_writeb(*src, addr);
		src++;
	}
}

逐行解释:

  1. while (--count >= 0) { ... }

    • 循环 count
  2. __raw_writeb(*src, addr);

    • 使用原始写入函数写入一个字节
  3. src++;

    • 移动源指针

使用示例:

u8 buffer[256] = { ... };
iowrite8_rep(base + 0x100, buffer, 256);

内存操作函数

memset_io

函数原型:

void memset_io(void __iomem *dst, int c, size_t count)

memset_io 将 I/O 内存区域填充为指定值。

实现(ARM 架构):

void _memset_io(void __iomem *dst, int c, size_t count)
{
	while (count) {
		count--;
		writeb(c, dst);
		dst++;
	}
}

逐行解释:

  1. while (count) { ... }

    • 循环直到 count 为 0
  2. count--;

    • 递减计数器
  3. writeb(c, dst);

    • 写入一个字节值 c 到 I/O 内存地址
  4. dst++;

    • 移动到下一个地址

注意: 某些架构(如 x86_64)可能使用优化的实现,直接调用 memset,但需要确保 I/O 内存访问的正确性。

使用示例:

memset_io(base + 0x200, 0, 1024);  // 清零 1024 字节

memcpy_fromio

函数原型:

void memcpy_fromio(void *to, void __iomem *from, size_t count)

memcpy_fromio 从 I/O 内存空间复制数据到普通内存空间。

实现(ARM 架构):

void _memcpy_fromio(void *to, void __iomem *from, size_t count)
{
	unsigned char *t = to;
	while (count) {
		count--;
		*t = readb(from);
		t++;
		from++;
	}
}

逐行解释:

  1. unsigned char *t = to;

    • 将目标指针转换为 unsigned char *,便于字节操作
  2. while (count) { ... }

    • 循环直到复制完所有字节
  3. count--;

    • 递减计数器
  4. *t = readb(from);

    • 从 I/O 内存读取一个字节
    • 写入到普通内存
  5. t++;from++;

    • 移动源和目标指针

使用示例:

u8 buffer[256];
memcpy_fromio(buffer, base + 0x100, 256);

memcpy_toio

函数原型:

void memcpy_toio(void __iomem *to, const void *from, size_t count)

memcpy_toio 从普通内存空间复制数据到 I/O 内存空间。

实现(ARM 架构):

void _memcpy_toio(void __iomem *to, const void *from, size_t count)
{
	const unsigned char *f = from;
	while (count) {
		count--;
		writeb(*f, to);
		f++;
		to++;
	}
}

逐行解释:

  1. const unsigned char *f = from;

    • 将源指针转换为 const unsigned char *
  2. while (count) { ... }

    • 循环直到复制完所有字节
  3. count--;

    • 递减计数器
  4. writeb(*f, to);

    • 从普通内存读取一个字节
    • 写入到 I/O 内存
  5. f++;to++;

    • 移动源和目标指针

使用示例:

u8 buffer[256] = { ... };
memcpy_toio(base + 0x100, buffer, 256);

总结

函数使用流程

典型的设备驱动使用这些函数的流程:

  1. 请求资源: request_mem_region(start, size, name)
  2. 映射地址: ioremap(phys_addr, size)
  3. 访问设备: 使用 ioread8/iowrite8 等函数
  4. 取消映射: iounmap(addr)
  5. 释放资源: release_mem_region(start, size)

注意事项

  1. 资源管理: 必须先请求资源再映射,使用完后先取消映射再释放资源
  2. 地址类型: 使用 __iomem 标记 I/O 内存地址,编译器会进行类型检查
  3. 并发安全: 资源管理函数内部使用锁保护,但设备访问需要驱动自己保证同步
  4. 错误处理: 所有函数都可能失败,必须检查返回值
  5. 性能考虑: 重复 I/O 操作使用 *_rep 函数可能更高效

相关函数

  • ioread16 / iowrite16:16 位访问
  • ioread32 / iowrite32:32 位访问
  • ioread16_rep / iowrite16_rep:16 位重复访问
  • ioread32_rep / iowrite32_rep:32 位重复访问
  • ioremap_nocache:映射非缓存内存(用于设备寄存器)

本文档基于 Linux 2.6.10 内核代码分析

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐