I/O函数 writel __raw_writel mb()/rmb()/wmb()

在邮件列表里讨论了一下writel是如何实现的,这个函数实现在操作系统层,有内存保护的情况下,往一个寄存器或者内存地址写一个数据。
 
在arch/alpha/kernel/io.c中有
188 void writel(u32 b, volatile void __iomem *addr)
189 {
190     __raw_writel(b, addr);
191     mb();
192 }
 
 
这样一个writel函数的作用应该是向一个地址上写一个值, 我想知道这个函数底下具体实现的细节,于是往下继续跟踪代码:_ _raw_writel(b, addr);
 
129 void __raw_writel(u32 b, volatile void __iomem *addr)
130 {
131     IO_CONCAT(__IO_PREFIX,writel)( b, addr);
132 }
 
再往下跟踪 IO_CONCAT,在对应的io. h中的定义如下:
134 #define IO_CONCAT(a,b)  _IO_CONCAT(a,b)
135 #define _IO_CONCAT(a,b) a ## _ ## b
这段代码前几天问过了,是标示将两边的字符串连接起来的意思。

跟踪__IO_PREFIX 定义如下
501 #undef __IO_PREFIX
502 #define __IO_PREFIX     apecs
 
到这里就结束了,再往下我就晕了,有问题如下:
1、到底是怎么将数据写入地址的?我把这些单独提取出来, 进行预编译,宏展开后,发现是这样的:
void __raw_writel(                                 )
{
    apecs_writel(b, addr);
}
但是在内核里根本就没找到apecs_writel函数, 请帮忙解释下。
 
 
For the first question,
you should refer to the file "arch\alpha\kernle\Machvec_ impl.h"
"~\Machve.h" "~\io.c" "~\io.h" "~\core_**.h".

as you have analysized before, in the file Machvec_impl.h and Machve.h,
DO_CIA_IO,IO,IO_LITE, these three macros implement the symbole
connection between ** arch and writel function, and the function
pointer initializations.
so, the details implementation to writel is to init the
alpha_machine_vector structure and the definition to the relevant
function pointer invoked to complete the low-level write operation.

.mv_writel =CAT(low,_writel),<---IO(CIA, cia)<-->cia_writel(b, addr); <---

                                |
writel(b, addr)-->__raw_writel(b, addr);--->cia_writel(b,addr)-- -------------


For the second quesiton,
mb()--->__asm__ __volatile__("mb": : :"memory");
so, it is a memory barrier for alpha architecture to ensure some
operations before some actions could be occured.
and, it is similiar with the barrier() in x86 platform/arm platform.
 
 
继续阅读代码,看看定义__IO_ PREFIX之后紧接着包含了哪个头文件。在哪个头文
件里面寻找答案。对于你的apsec,看看以下代码段( linux-2.6.28-rc4)

arch/alpha/include/asm/core_ apecs.h
------------------------------ ------------
#undef __IO_PREFIX
#define __IO_PREFIX             apecs
#define apecs_trivial_io_bw     0
#define apecs_trivial_io_lq     0
#define apecs_trivial_rw_bw     2
#define apecs_trivial_rw_lq     1
#define apecs_trivial_iounmap   1
#include <asm/io_trivial.h>
------------------------------ ------------

arch/alpha/include/asm/io_ trivial.h
------------------------------ ------------
__EXTERN_INLINE void
IO_CONCAT(__IO_PREFIX,writel)( u32 b, volatile void __iomem *a)
{
       *(volatile u32 __force *)a = b;
}
 就是最终通过*(volatile u32 __force *)a = b;
来写入数据的。
 如果在没有os,没有mmu的情况下,当开发板裸跑的时候,我们只需要一句话就一切ok:
*(unsigned long *)addr = value;

 

在阅读linux 2.6.23内核代码中遇到mb()/rmb()/wmb() 这几个宏,不明白如何使用,
在分析其汇编代码后,大概的了解了这和内存屏障有关,代码如下:

#define X86_FEATURE_XMM2 (0*32+26) /* Streaming SIMD Extensions-2 */

......

#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
#define rmb() alternative("lock; addl $0,0(%%esp)", "lfence", X86_FEATURE_XMM2)

#ifdef CONFIG_X86_OOSTORE
/* Actually there are no OOO store capable CPUs for now that do SSE, 
but make it already an possibility. */
#define wmb() alternative("lock; addl $0,0(%%esp)", "sfence", X86_FEATURE_XMM)
#else
#define wmb() __asm__ __volatile__ ("": : :"memory")
#endif

.......

/*
* Alternative instructions for different CPU types or capabilities.
*
* This allows to use optimized instructions even on generic binary
* kernels.
*
* length of oldinstr must be longer or equal the length of newinstr
* It can be padded with nops as needed.
*
* For non barrier like inlines please define new variants
* without volatile and memory clobber.
*/
#define alternative(oldinstr, newinstr, feature) \
asm volatile ("661:\n\t" oldinstr "\n662:\n" \
      ".section .altinstructions,\"a\"\n" \
      "   .align 4\n" \
      "   .long 661b\n"          /* label */ \
      "   .long 663f\n"    /* new instruction */ \
      "   .byte %c0\n"          /* feature bit */ \
      "   .byte 662b-661b\n"    /* sourcelen */ \
      "   .byte 664f-663f\n"    /* replacementlen */ \
      ".previous\n" \
      ".section .altinstr_replacement,\"ax\"\n" \
      "663:\n\t" newinstr "\n664:\n" /* replacement */\
      ".previous" :: "i" (feature) : "memory")

内存屏障主要解决的问题是编译器的优化和CPU的乱序执行。
编译器在优化的时候,生成的汇编指令可能和c语言程序的执行顺序不一样,在需要 程序严格按照c语言顺序执行时,需要显式的告诉编译不需要优化,这在linux下是通过barrier()宏完成的,它依靠volidate关键字和 memory关键字,前者告诉编译barrier()周围的指令不要被优化,后者作用是告诉编译器汇编代码会使内存里面的值更改,编译器应使用内存里的新 值而非寄存器里保存的老值。
同样,CPU执行会通过乱序以提高性能。汇编里的指令不一定是按照我们看到的顺序执行的。linux中通过mb()系 列宏来保证执行的顺序。具体做法是通过mfence/lfence指令(它们是奔4后引进的,早期x86没有)以及x86指令中带有串行特性的指令(这样 的指令很多,例如linux中实现时用到的lock指令,I/O指令,操作控制寄存器、系统寄存器、调试寄存器的指令、iret指令等等)。简单的说,如 果在程序某处插入了mb()/rmb()/wmb()宏,则宏之前的程序保证比宏之后的程序先执行,从而实现串行化。wmb的实现和barrier()类 似,是因为在x86平台上,写内存的操作不会被乱序执行。
实际上在RSIC平台上,这些串行工作都有专门的指令由程序员显式的完成,比如在需要的地方调用串行指令,而不像x86上有这么多隐性的带有串行特性指令(例如lock指令)。所以在risc平台下工作的朋友通常对串行化操作理解的容易些。


因为对别的平台不了解,下面仅谈它们在ARM上的区别


__raw_writel: 因为有volatile关键字, 所以编译器不会打乱多个__raw_writel的执行顺序。

对于ARM而言,当多个写以代码的顺序到达相同设备时,执行的顺序也是被保证的,不过

对于不同的设备,执行的顺序就不被保证了。


write_relaxed: 在ARM平台上与__raw_writel一样,因为与__raw_writel相比,它只多做

了一个大端到小端转换。


writel: 当CONFIG_ARM_DMA_MEM_BUFFERABLE被定义时,在写之前,它会多做

一个DSB和L2的sync。 为什么需要这样呢? 因为,DMA buffer都是 bufferable了,

(详见加入CONFIG_ARM_DMA_MEM_BUFFERABLE的commit log)

在DMA进行之前要保证write buffer里的数据都到memory里。

所以说: 只有在有DMA操作的代码里,writel才是必须的,其实它是一个

当DMA buffer变成bufferable之后的一个补充品。因为要保证DMA buffer里的

内容在DMA开始之前都到memory里,所以开始DMA的指令(writel)里加了

sync memory的指令。


其实: writel也不能保证写的东西一定完成,它只能保证写的指令或写的内容

已经到了设备端,但具体有没有写完成是不知道的。如果有一定要设备端

写完成才能做下面的指令的要求,最好再用readl把它读回来。


几个常用的宏:likely和unlikely __raw_writel


在源码中,宏likely和unlikely 是这么定义的(位于include/linux/compiler.h):

#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

要理解宏likely和unlikely ,很明显必须理解__builtin_expect。__builtin_expect是GCC(version>=2.9)引进的宏,其作用就是帮助编译器判断条件跳转的预期值,避免跳转造成时间浪费。拿下面的代码来说:

if (likely(acat == 1))     //表示大多数情况下if里面是真,程序大多数直接执行if里面的程序

if (unlikely (thread_memory_magazine1_is_empty (tmem, ix)))//表示大多数情况if里面为假,程序大多数直接执行else里面的程序

两段代码编译生成的汇编语句所使用到的跳转指令不一样,仔细分析下会发现__builtin_expect实际上是为了满足在大多数情况不执行跳转指令,所以__builtin_expect仅仅是告诉编译器优化,并没有改变其对真值的判断。

有关文章:http://kernelnewbies.org/FAQ/LikelyUnlikely

              http://hi.baidu.com/lammy/blog/item/bc5e3d4e869073c3d1c86a89.html

              http://www.cublog.cn/u/31100/showart_240658.html

__raw_writel到底干了些什么?

在linux/arch/arm/mach-at91/gpio.c 中 at91_set_gpio_output函数看到这个宏

int __init_or_module at91_set_gpio_output(unsigned pin, int value)

{

void __iomem *pio = pin_to_controller(pin);

unsigned mask = pin_to_mask(pin);

 

if (!pio)

return -EINVAL;

 

__raw_writel(mask, pio + PIO_IDR);

__raw_writel(mask, pio + PIO_PUDR);

__raw_writel(mask, pio + (value ? PIO_SODR : PIO_CODR));

__raw_writel(mask, pio + PIO_OER);

__raw_writel(mask, pio + PIO_PER);

return 0;

}

定义是:

#define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v)) 
# define __chk_io_ptr(x) (void)0

这样看就是赋值, 
__chk_io_ptr()是编译器为了更细致地检查参数的属性,用于调试,正常编译时没有作用。 
volatile为了防止Compiler优化,说明它是一个随时被改变的量,每次使用它的时候,不应该被假设,而要从相应的寄存器中读取。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的示例内核模块,用于演示如何使用内核相关接口来申请、读写和释放 I/O 内存。该模块申请了 4KB 的 I/O 内存,将其填充为测试数据,并将其写入硬件设备的寄存器中,然后再读回数据并打印输出。最后,模块释放了所申请的 I/O 内存。 ```c #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/io.h> #define MEM_SIZE 4096 static void __iomem *io_mem; static int __init io_mem_init(void) { int ret; // 申请 I/O 内存 io_mem = ioremap_nocache(0x80000000, MEM_SIZE); if (!io_mem) { printk(KERN_ERR "Failed to allocate I/O memory.\n"); return -ENOMEM; } // 填充测试数据并写入硬件寄存器 memset_io(io_mem, 0xAA, MEM_SIZE); ret = writel(0x12345678, io_mem); if (ret) { printk(KERN_ERR "Failed to write I/O memory.\n"); iounmap(io_mem); return ret; } // 读回数据并打印输出 u32 data = readl(io_mem); printk(KERN_INFO "Read I/O memory: 0x%x\n", data); return 0; } static void __exit io_mem_exit(void) { // 释放 I/O 内存 iounmap(io_mem); } module_init(io_mem_init); module_exit(io_mem_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("A simple driver for I/O memory."); ``` 在该示例中,我们使用了以下内核接口: - `ioremap_nocache()`:申请 I/O 内存,并返回内核虚拟地址; - `memset_io()`:将内存区域填充为指定的值; - `writel()`:写入指定的数据到 I/O 内存; - `readl()`:从 I/O 内存读取数据。 如果你需要在实际应用中使用 I/O 内存,建议使用 `ioremap_wc()` 来申请内存,以获得更好的性能和稳定性。同时,还需要注意内存对齐和释放 I/O 内存的正确方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值