内存映射 I/O 函数详解
内存映射 I/O 函数详解
本文档详细解释 Linux 内核中用于内存映射 I/O 操作的核心函数,包括资源管理、地址映射和 I/O 访问函数。
目录
资源管理函数
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;
}
逐行解释:
-
struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);- 分配一个
resource结构体,用于表示要保留的资源 GFP_KERNEL:在内核空间分配,可睡眠- 如果分配失败,
res为NULL
- 分配一个
-
if (res) { ... }- 检查内存分配是否成功
- 只有分配成功才继续初始化
-
memset(res, 0, sizeof(*res));- 将整个结构体清零,确保所有字段初始化为 0
- 包括
parent、sibling、child等指针字段
-
res->name = name;- 设置资源名称,用于标识资源的所有者
- 在
/proc/ioports或/proc/iomem中显示
-
res->start = start;- 设置资源的起始地址
-
res->end = start + n - 1;- 计算并设置资源的结束地址
n是资源大小,结束地址 = 起始地址 + 大小 - 1
-
res->flags = IORESOURCE_BUSY;- 设置资源标志为"忙碌"状态
- 表示该资源已被占用,不能被其他驱动使用
-
write_lock(&resource_lock);- 获取资源树的写锁,保护资源树结构
- 防止并发访问导致资源冲突
-
for (;;) { ... }- 无限循环,直到成功分配或失败退出
-
struct resource *conflict;- 声明冲突资源指针
-
conflict = __request_resource(parent, res);- 调用内部函数尝试将资源插入资源树
- 如果返回
NULL,表示没有冲突,分配成功 - 如果返回非
NULL,表示存在冲突的资源
-
if (!conflict) break;- 如果没有冲突,跳出循环,分配成功
-
if (conflict != parent) { ... }- 如果冲突资源不是父资源本身,说明冲突发生在子资源中
-
parent = conflict;- 将父资源更新为冲突资源
- 尝试在冲突资源的子树中查找可用空间
-
if (!(conflict->flags & IORESOURCE_BUSY)) continue;- 如果冲突资源不是忙碌状态(可能是预留资源)
- 继续循环,尝试在其子树中分配
-
kfree(res); res = NULL; break;- 如果冲突资源是忙碌的,释放已分配的资源结构
- 设置返回值为
NULL,表示分配失败 - 跳出循环
-
write_unlock(&resource_lock);- 释放写锁
-
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;
}
}
逐行解释:
-
unsigned long start = new->start;和unsigned long end = new->end;- 提取新资源的起始和结束地址
-
struct resource *tmp, **p;
tmp:遍历时指向当前资源节点p:指向资源指针的指针,用于插入新节点
-
if (end < start) return root;- 检查结束地址是否小于起始地址(无效范围)
- 如果无效,返回
root表示错误
-
if (start < root->start) return root;- 检查新资源是否超出父资源的起始范围
- 如果超出,返回
root表示错误
-
if (end > root->end) return root;- 检查新资源是否超出父资源的结束范围
- 如果超出,返回
root表示错误
-
p = &root->child;- 初始化指针,指向父资源的第一个子资源
-
for (;;) { ... }- 无限循环,遍历兄弟资源链表
-
tmp = *p;- 获取当前指针指向的资源节点
-
if (!tmp || tmp->start > end) { ... }- 如果没有子资源,或者当前资源的起始地址大于新资源的结束地址
- 说明找到了插入位置(在当前资源之前)
-
new->sibling = tmp;- 将新资源的兄弟指针指向当前资源
- 新资源插入到当前资源之前
-
*p = new;- 将父资源的子指针指向新资源
- 完成插入操作
-
new->parent = root;- 设置新资源的父指针
-
return NULL;- 返回
NULL表示没有冲突,插入成功
- 返回
-
p = &tmp->sibling;- 移动到下一个兄弟资源
-
if (tmp->end < start) continue;- 如果当前资源的结束地址小于新资源的起始地址
- 说明没有重叠,继续查找下一个
-
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);
}
逐行解释:
-
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) { ... }- 检查当前资源是否包含要释放的范围
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;- 移动到下一个兄弟资源,继续查找
-
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);
}
逐行解释:
-
void __iomem * addr;- 返回的虚拟地址指针
__iomem标记表示这是 I/O 内存地址,编译器会进行类型检查
-
struct vm_struct * area;- 虚拟内存区域结构,用于管理映射的虚拟地址空间
-
unsigned long offset, last_addr;offset:物理地址在页内的偏移量last_addr:要映射区域的最后一个地址
-
last_addr = phys_addr + size - 1;- 计算最后一个地址
-
if (!size || last_addr < phys_addr) return NULL;- 检查参数有效性
!size:大小为 0last_addr < phys_addr:地址回绕(溢出)- 如果无效,返回
NULL
-
if (phys_addr >= ISA_START_ADDRESS && last_addr < ISA_END_ADDRESS) { ... }- 检查是否在 ISA 地址范围内(0xa0000 - 0x100000)
- ISA 地址已经映射,直接返回对应的虚拟地址
-
return (void __iomem *) phys_to_virt(phys_addr);- 将物理地址转换为虚拟地址
- 不需要创建新的映射
-
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- 比较虚拟地址,直接返回(低内存区域不需要释放)
-
char *t_addr, *t_end;和struct page *page;t_addr、t_end:转换后的虚拟地址范围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++)- 遍历范围内的所有页
virt_to_page:将虚拟地址转换为页结构指针
-
if(!PageReserved(page)) return NULL;- 检查页是否被标记为保留(
PageReserved) - 如果页不是保留的,说明是正常 RAM,不允许映射
- 返回
NULL表示错误
- 检查页是否被标记为保留(
-
offset = phys_addr & ~PAGE_MASK;- 计算物理地址在页内的偏移量
PAGE_MASK是页大小的掩~PAGE_MASK取反得到页内偏移掩码& ~PAGE_MASK提取页内偏移
-
phys_addr &= PAGE_MASK;- 将物理地址向下对齐到页边界
- 移除页内偏移,得到页起始地址
-
size = PAGE_ALIGN(last_addr+1) - phys_addr;- 将大小向上对齐到页边界
PAGE_ALIGN宏将地址向上对齐到页边界- 计算对齐后的大小
-
area = get_vm_area(size, VM_IOREMAP | (flags << 20));- 分配虚拟地址空间
VM_IOREMAP:标记为 I/O 映射区域flags << 20:将标志位左移 20 位,存储在area->flags的高位
-
if (!area) return NULL;- 如果分配失败,返回
NULL
- 如果分配失败,返回
-
area->phys_addr = phys_addr;- 保存物理地址,用于后续的
iounmap
- 保存物理地址,用于后续的
-
addr = (void __iomem *) area->addr;- 获取分配的虚拟地址
-
if (ioremap_page_range(...)) { ... }- 建立页表映射,将物理地址映射到虚拟地址
- 如果失败,执行清理并返回
NULL
-
vunmap((void __force *) addr);和return NULL;- 如果映射失败,释放虚拟地址空间
- 返回
NULL表示错误
-
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;
}
逐行解释:
-
BUG_ON(addr >= end);- 断言检查,确保起始地址小于结束地址
-
flush_cache_all();- 刷新所有 CPU 缓存,确保一致性
-
phys_addr -= addr;- 计算物理地址相对于虚拟地址的偏移
- 后续通过
phys_addr + addr计算每个页的物理地址
-
pgd = pgd_offset_k(addr);- 获取页全局目录(PGD)项
pgd_offset_k返回内核页表中对应虚拟地址的 PGD 项
-
spin_lock(&init_mm.page_table_lock);- 获取页表锁,保护页表操作
-
do { ... } while (...);- 循环处理每个 PGD 项
-
next = pgd_addr_end(addr, end);- 计算当前 PGD 项覆盖的地址范围结束位置
-
err = ioremap_pud_range(...);- 递归处理页上级目录(PUD)、页中间目录(PMD)和页表项(PTE)
-
if (err) break;- 如果出错,跳出循环
-
pgd++, addr = next, addr != end- 移动到下一个 PGD 项
- 继续处理直到所有地址映射完成
-
spin_unlock(&init_mm.page_table_lock);- 释放页表锁
-
flush_tlb_all();- 刷新所有 TLB(转换后备缓冲区)
- 使新的页表映射生效
-
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);
}
逐行解释:
-
struct vm_struct *p;- 虚拟内存区域结构指针
-
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释放)
-
if (addr >= phys_to_virt(ISA_START_ADDRESS) && addr < phys_to_virt(ISA_END_ADDRESS)) return;- 检查是否是 ISA 地址范围
- ISA 地址是直接映射的,不需要释放,直接返回
-
write_lock(&vmlist_lock);- 获取虚拟内存列表的写锁
-
p = __remove_vm_area((void *) (PAGE_MASK & (unsigned long __force) addr));- 从虚拟内存列表中移除对应的区域
PAGE_MASK & addr:将地址向下对齐到页边界__remove_vm_area返回对应的vm_struct结构
-
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) { ... }- 检查是否有特殊标志(从
flags的高位提取) - 检查物理地址是否在系统 RAM 范围内
- 如果是,需要恢复页属性
- 检查是否有特殊标志(从
-
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:计算页数
- 将页属性改回
-
global_flush_tlb();- 刷新所有 TLB,使页属性更改生效
-
write_unlock(&vmlist_lock);- 释放写锁
-
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));
}
逐行解释:
-
fastcall- 函数调用约定,参数通过寄存器传递,提高性能
-
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)
逐行解释:
-
unsigned long port = (unsigned long __force)addr;- 将地址转换为无符号长整型
__force强制类型转换,忽略类型检查
-
if (port < PIO_RESERVED) { ... }PIO_RESERVED定义为0x40000UL- 如果地址小于此值,认为是 PIO 地址
-
VERIFY_PIO(port);- 验证 PIO 地址格式
VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)- 检查地址的高位是否为
PIO_OFFSET(0x10000)
-
port &= PIO_MASK;- 提取实际的端口号(低 16 位)
PIO_MASK为0x0ffffUL
-
is_pio;- 执行 PIO 分支代码(
return inb(port))
- 执行 PIO 分支代码(
-
else { is_mmio; }- 执行 MMIO 分支代码(
return readb(addr))
- 执行 MMIO 分支代码(
使用示例:
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));
}
逐行解释:
IO_COND(addr, outb(val,port), writeb(val, addr));- 如果是 PIO,使用
outb(val, port)写入端口 - 如果是 MMIO,使用
writeb(val, addr)写入内存
- 如果是 PIO,使用
使用示例:
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));
}
逐行解释:
IO_COND(addr, insb(port,dst,count), mmio_insb(addr, dst, count));- 如果是 PIO,使用
insb(端口字符串输入) - 如果是 MMIO,使用
mmio_insb(内存映射 I/O 字符串输入)
- 如果是 PIO,使用
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++;
}
}
逐行解释:
-
while (--count >= 0) { ... }- 循环
count次 --count >= 0:先减 1,再判断是否大于等于 0
- 循环
-
u8 data = __raw_readb(addr);- 使用原始读取函数读取一个字节
__raw_readb不进行字节序转换
-
*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));
}
逐行解释:
IO_COND(addr, outsb(port, src, count), mmio_outsb(addr, src, count));- 如果是 PIO,使用
outsb(端口字符串输出) - 如果是 MMIO,使用
mmio_outsb(内存映射 I/O 字符串输出)
- 如果是 PIO,使用
mmio_outsb 实现:
static inline void mmio_outsb(void __iomem *addr, const u8 *src, int count)
{
while (--count >= 0) {
__raw_writeb(*src, addr);
src++;
}
}
逐行解释:
-
while (--count >= 0) { ... }- 循环
count次
- 循环
-
__raw_writeb(*src, addr);- 使用原始写入函数写入一个字节
-
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++;
}
}
逐行解释:
-
while (count) { ... }- 循环直到
count为 0
- 循环直到
-
count--;- 递减计数器
-
writeb(c, dst);- 写入一个字节值
c到 I/O 内存地址
- 写入一个字节值
-
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++;
}
}
逐行解释:
-
unsigned char *t = to;- 将目标指针转换为
unsigned char *,便于字节操作
- 将目标指针转换为
-
while (count) { ... }- 循环直到复制完所有字节
-
count--;- 递减计数器
-
*t = readb(from);- 从 I/O 内存读取一个字节
- 写入到普通内存
-
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++;
}
}
逐行解释:
-
const unsigned char *f = from;- 将源指针转换为
const unsigned char *
- 将源指针转换为
-
while (count) { ... }- 循环直到复制完所有字节
-
count--;- 递减计数器
-
writeb(*f, to);- 从普通内存读取一个字节
- 写入到 I/O 内存
-
f++;和to++;- 移动源和目标指针
使用示例:
u8 buffer[256] = { ... };
memcpy_toio(base + 0x100, buffer, 256);
总结
函数使用流程
典型的设备驱动使用这些函数的流程:
- 请求资源:
request_mem_region(start, size, name) - 映射地址:
ioremap(phys_addr, size) - 访问设备: 使用
ioread8/iowrite8等函数 - 取消映射:
iounmap(addr) - 释放资源:
release_mem_region(start, size)
注意事项
- 资源管理: 必须先请求资源再映射,使用完后先取消映射再释放资源
- 地址类型: 使用
__iomem标记 I/O 内存地址,编译器会进行类型检查 - 并发安全: 资源管理函数内部使用锁保护,但设备访问需要驱动自己保证同步
- 错误处理: 所有函数都可能失败,必须检查返回值
- 性能考虑: 重复 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 内核代码分析
更多推荐

所有评论(0)