I/O驱动相关__raw_readl和__raw_writel

本文详细介绍了S3C2410微处理器中GPIO端口的工作原理及操作方法,包括端口编号定义、功能配置、操作函数、地址映射等内容,并解释了关键寄存器的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.S3C2410_GPB5是端口编号,定义在regs-gpio.h中,

#define S3C2410_GPIO_BANKB   (32*1)
#define S3C2410_GPIONO(bank,offset)   ((bank) + (offset))
#define S3C2410_GPB5         S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5)

S3C2410共有130个GPIO,分为9组(GPA~GPJ),每组最多可以有 32个,每个GPIO有2~4个可选功能,每组的控制寄存器空间有4个,例如对于GPB,有GPBCON、GPBDAT、GPBUP和Reserved, 分别是功能配置、数据缓存、上拉使能和保留。

上面的S3C2410_GPB5就是GPIO的编号,也就是在号码空间(0~32*9-1)中的位置,bank是分组的基号码,offset是组内偏移量。(也就是说把所有的IO口从0开始进行统一的编号如:S3C2410_GPA0=0,S3C2410_GPA1=1,S3C2410_GPB0=32,S3C2410_GPC0=48等)



2.S3C2410_GPB5_OUTP是端口功能,定义在regs-gpio.h中,

#define S3C2410_GPB5_INP     (0x00 << 10)
#define S3C2410_GPB5_OUTP    (0x01 << 10)

GPBCON的第10、11两位用于配置GPB5的功能,00 = Input ,01 = Output



3.S3C2410 GPIO的操作函数

在hardware.h文件中有:

s3c2410_gpio_cfgpin     //配置端口的GPIO的功能
s3c2410_gpio_getcfg     //读取功能配置
s3c2410_gpio_pullup     //配置上拉电阻
s3c2410_modify_misccr //杂项配置

s3c2410_gpio_getirq      //给定端口,转换出IRQ号
s3c2410_gpio_irqfilter    //配置IRQ过滤使能与否

s3c2410_gpio_setpin     //写数据到端口
s3c2410_gpio_getpin     //从端口读数据

这些函数的实现在gpio.h中

void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
{
void __iomem *base = S3C2410_GPIO_BASE(pin);//算出端口所在组虚拟基址如://GPA=0xF0E00000   

                                                                                                                               //GPB=0XF0E00010

unsigned long offs = S3C2410_GPIO_OFFSET(pin);        //算出端口所在组的偏移量(0~31)
unsigned long flags;
unsigned long dat;

local_irq_save(flags);

dat = __raw_readl(base + 0x04);    //虚拟基址加0x04为 GP*DAT寄存器,加0x00为GP*ON等  

                                                     //读出当前GP*DAT寄存器的值                
dat &= ~(1 << offs);     //根据offs偏移量对该寄存器中选中的 位 清零,其他位保持不变
dat |= to << offs;         
//根据形参对要求的位进行位操作,来实现对具体某个IO口的配置
__raw_writel(dat, base + 0x04);     //将配置写入到寄存器(这里是虚拟地址)

local_irq_restore(flags);
}



4.S3C2410_GPIO_BASE和S3C2410_GPIO_OFFSET也是在regs-gpio.h文件中定义,

#define S3C2410_GPIO_BASE(pin)       ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
#define S3C2410_GPIO_OFFSET(pin)   ((pin) & 31)

而在map.h中有:

/* GPIO ports */
#define S3C24XX_VA_GPIO    S3C2410_ADDR(0x00E00000) //虚拟地址S3C24XX_VA_GPIO= 0xF0E00000
#define S3C2400_PA_GPIO    (0x15600000)
#define S3C2410_PA_GPIO    (0x56000000) //GPACON 物理地址
#define S3C24XX_SZ_GPIO    SZ_1M //0x100000 = 1024 *1024

S3C2410_GPIO_BASE作用是:根 据端口编号pin,算出端口所在组的虚拟基址。((pin) & ~31)是去掉pin当中小于等于31的零头(清0低5位),>>1的原因是每组GPIO中最多可以有32个端口,控制这些端口需要4个寄存 器空间,4个寄存器空间就需要4*4=16个字节进行编址,32/16=2,左移一位刚好满足。也就是说,上一组端口和下一组端口的编号相差32,而控制 寄存器的地址相差16。

S3C2410_GPIO_OFFSET作用是:根据端口编号pin,算出端口所在组的偏移量。((pin) & 31)即去掉比31大的数(清0第6位以上的位)。     



5. __raw_readl和__raw_writel

Linux对I/O的操作都定义在asm/io.h中,相应的在arm平台下,就在asm-arm/io.h中。

#define __raw_readl(a)   (__chk_io_ptr(a), *(volatile unsigned int __force   *)(a))
#define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force   *)(a) = (v))

在include\linux\compiler.h中:

#ifdef __CHECKER__
……
extern void __chk_io_ptr(void __iomem *);
#else
……
# define __chk_io_ptr(x) (void)0
……
#endif

__raw_readl(a)展开是:((void)0, *(volatile unsigned int _force *)(a))。在定义了__CHECKER__的时候先调用__chk_io_ptr检查该地址,否则__chk_io_ptr什么也不做,* (volatile unsigned int _force *)(a)就是返回地址为a处的值。(void)xx的做法有时候是有用的,例如编译器打开了检查未使用的参数的时候需要将没有用到的参数这么弄一下才能 编译通过。

CPU对I/O的物理地址的编程方式有两种:一种是I/O映射,一种是内存映射。 __raw_readl和__raw_writel等是原始的操作I/O的方法,由此派生出来的操作方法有:inb、outb、 _memcpy_fromio、readb、writeb、ioread8、iowrite8等。



6.local_irq_save和local_irq_restore

关中断和开中断,在asm-arm/system.h中定义。

#define local_irq_save(x)      \
({        \
__asm__ __volatile__(      \
"mrs %0, cpsr   @ local_irq_save\n" \
"cpsid i"       \
: "=r" (x) : : "memory", "cc");     \
})

#define local_irq_save(x)      \
({        \
   unsigned long temp;     \
   (void) (&temp == &x);     \
__asm__ __volatile__(      \
"mrs %0, cpsr   @ local_irq_save\n" \
" orr %1, %0, #128\n"      \
" msr cpsr_c, %1"      \
: "=r" (x), "=r" (temp)      \
:        \
: "memory", "cc");      \
})


<think>嗯,用户之前问过关于__raw_readl宏定义的问题,现在又接着问memcpy_toiofromio函数的功能原型。看来他们可能是在学习Linux内核驱动开发,或者是在处理硬件相关的编程问题。需要先确认这两个函数的用途区别,以及它们在内核中的作用。 首先,memcpy_toiomemcpy_fromio这两个函数应该内存映射的I/O操作有关,可能用于设备驱动中,用来在用户空间硬件设备之间复制数据。用户之前已经接触过直接读取寄存器的宏,现在转向数据传输函数,可能是在处理更复杂的数据传输需求,比如DMA或者块数据传输。 接下来需要回忆这两个函数的原型功能。memcpy_toio应该是将数据从主内存复制到I/O内存,而memcpy_fromio则是相反方向。由于涉及到硬件访问,这些函数可能需要处理一些底层细节,比如内存屏障、字节序或者volatile指针的问题,确保数据正确无误地传输。 需要注意的是,内核中的I/O函数通常会使用volatile关键字来防止编译器优化,确保每次访问都是实际的硬件操作。同时,这些函数可能包含内存屏障,保证访问顺序,这在多核系统中尤为重要。 用户可能想知道这两个函数的具体参数用法,比如源地址目标地址的类型,以及是否需要考虑对齐或者大小限制。此外,他们可能想知道与普通memcpy的区别,比如是否涉及缓存、是否需要特殊处理等。 另外,用户可能在使用这些函数时遇到问题,比如数据不一致或者性能问题,所以解释中需要提到注意事项,比如确保地址正确,处理可能的字节序问题,以及正确使用内存屏障。 还需要比较这两个函数其他I/O函数(比如readl/writel)的不同,指出在什么情况下应该使用memcpy类函数而不是单次访问的宏。例如,当需要传输大量数据时,使用memcpy_toio会更高效。 最后,确保回答结构清晰,分点解释功能原型,可能举例子说明使用场景,以及注意事项,帮助用户全面理解这两个函数的作用正确使用方法。</think>`memcpy_toio` `memcpy_fromio` 是 Linux 内核中用于在 **内存映射的 I/O 空间** **系统内存** 之间**批量传输数据**的底层函数,专为设备驱动与硬件交互设计。以下是它们的详细解析: --- ### **核心功能** | 函数 | 功能描述 | |----------------------|--------------------------------------------------------------------------| | `memcpy_toio` | 从**系统内存**(普通内存)复制数据到**I/O 内存空间**(设备寄存器或硬件缓冲区)。 | | `memcpy_fromio` | 从**I/O 内存空间**复制数据到**系统内存**(普通内存)。 | - **用途**:批量操作硬件数据缓冲区(如 DMA 缓冲区、设备寄存器组)。 - **特点**: - 处理 `volatile` 访问,确保编译器不优化硬件访问。 - 可能隐式包含内存屏障(依赖架构实现),保证访问顺序。 - 直接操作物理地址,无虚拟内存映射转换。 --- ### **函数原型** ```c // 定义于 <linux/io.h> void memcpy_toio(volatile void __iomem *dest, const void *src, size_t count); void memcpy_fromio(void *dest, const volatile void __iomem *src, size_t count); ``` #### **参数说明** | 参数 | 类型 | 说明 | |-----------|--------------------------|----------------------------------------------------------------------| | `dest` | `volatile void __iomem*` | 目标地址(I/O 内存或系统内存,取决于函数)。 | | `src` | `const void*` 或 `volatile void __iomem*` | 源地址(系统内存或 I/O 内存)。 | | `count` | `size_t` | 要复制的字节数。 | --- ### **与普通 `memcpy` 的区别** | 特性 | `memcpy_toio`/`memcpy_fromio` | 普通 `memcpy` | |---------------------|----------------------------------------|----------------------------------| | **目标地址类型** | 操作 `__iomem` 标记的 I/O 内存空间 | 操作普通内存 | | `volatile` 语义 | 强制编译器生成实际读写指令,禁止优化 | 无特殊处理 | | **内存屏障** | 可能隐式插入屏障(如 ARM 的 `__iomem`) | 无 | | **适用场景** | 硬件寄存器或设备缓冲区 | 普通内存间数据拷贝 | --- ### **典型使用场景** 1. **设备驱动与硬件交互**: ```c // 示例:向硬件 FIFO 写入数据块 char buffer[128]; memcpy_toio(device->fifo_addr, buffer, sizeof(buffer)); // 示例:从硬件接收缓冲区读取数据 memcpy_fromio(buffer, device->rx_buf_addr, sizeof(buffer)); ``` 2. **DMA 缓冲区操作**:在内核与 DMA 控制器之间传递数据描述符。 3. **大批量寄存器配置**:快速初始化硬件寄存器组。 --- ### **注意事项** 1. **地址有效性**: - I/O 内存地址需通过 `ioremap` 映射到内核虚拟地址空间后使用。 - 确保地址对齐(某些硬件要求特定对齐访问)。 2. **字节序处理**: - 若设备与 CPU 字节序不同(如大端设备 vs 小端 CPU),需手动转换数据。 3. **并发与同步**: - 多核/中断场景中,可能需要结合锁或内存屏障(如 `mmiowb()`)保证数据一致性。 4. **性能优化**: - 对高频小数据操作,优先使用 `readl`/`writel` 等单次访问接口。 --- ### **底层实现依赖** - **架构相关**:不同 CPU 架构(如 x86、ARM)的实现可能不同: - x86:直接通过 `mov` 指令操作内存。 - ARM:可能使用 `ldr`/`str` 汇编指令,并包含屏障(如 `dsb`)。 --- 若需进一步了解具体架构的实现细节,可查阅内核源码(如 `arch/arm/include/asm/io.h`)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值