PCI的I/O空间、PCI的存储空间和PCI的配置空间
I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用。
内核在启动时负责对所有PCI设备进行初始化,配置好所有的PCI设备,包括中断号以及I/O基址,并在文件/proc/pci中列出所有找到的PCI设备,以及这些设备的参数和属性。
0x1000 — 0x1FFF
GICC_CTLR(0x0000):CPU Interface Control,控制中断是否传给CPU核,控制中断是IRQ还是FIQ
GICC_PMR(0x0004):Priority Mask,控制优先级门限,低于门限的中断不会转发到CPU
GICC_BPR(0x0008):Binary Point Register,是将优先级分成2部分:Group priority 、Subpriority,只使用优先级的Group priority部分,忽略Subpriority部分
详解:比如GIC-400的默认值是2,则优先级的[7:3]作为Group priority ,[2:0]作为Subpriority,则总共可生成32个优先级,与GICD_IPRIORITYRn保持一致
GICC_IAR(0x000C):Interrupt Acknowledge,只读,可以查看当前pending的最高优先级中断
GICC_EOIR(0x0010):End of Interrupt,只写,中断处理完成后需要写该寄存器
GICC_RPR(0x0014):Running Priority,只读,当前处理中断的优先级
GICC_HPPIR(0x0018):Highest Priority Pending ,只读,处于active状态的中断里优先级最高的
GICC_IIDR(0x00FC):ID寄存器,GIC-400的值是0x0202143B
GICC_DIR(0x1000):Deactivate Interrupt ,只写,移除中断的Active状态
GICC_IIDR(0x00FC):ID寄存器,GIC-400的值是0x0202143B
GICC_DIR(0x1000):Deactivate Interrupt ,只写,移除中断的Active状态
GICC_APRn 为在电源管理中保存和恢复状态提供支持 。
虚拟内存布局:
[ 0.000000] Memory: 1024MB = 1024MB total ---->内存的大小是1GB
[ 0.000000] Memory: 810820k/810820k available, 237756k reserved, 272384K highmem
[ 0.000000] Virtual kernel memory layout:
[ 0.000000] vector : 0xffff0000 - 0xffff1000 ( 4 kB) ---->存放中断向量表
[ 0.000000] fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB) ---->固定映射区,留作特定用途
[ 0.000000] vmalloc : 0xef800000 - 0xfee00000 ( 246 MB) ---->vmalloc()分配内存的区
[ 0.000000] lowmem : 0xc0000000 - 0xef600000 ( 758 MB) ---->低端内存区,属于GFP_KERNEL标志
[ 0.000000] pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB) ---->永久映射,高端内存页框到内核地址空间的长期映射
[ 0.000000] modules : 0xbf000000 - 0xbfe00000 ( 14 MB) ---->#insmod *.ko module存放段
[ 0.000000] .text : 0xc0008000 - 0xc0a51018 (10533 kB) ---->代码段和只读的数据段
[ 0.000000] .init : 0xc0a52000 - 0xc0a8f100 ( 245 kB) ---->初始化段,使用__init修饰初始化函数
[ 0.000000] .data : 0xc0a90000 - 0xc0b297d8 ( 614 kB) ---->可读写的数据段
[ 0.000000] .bss : 0xc0b297fc - 0xc0d09488 (1920 kB) ---->未初始化的数据段
[ 0.000000] SLUB: Genslabs=11, HWalign=64, Order=0-3, MinObjects=0, CPUs=8, Nodes=1
cpu的模式:
模式切换:硬件自动完成
arm在irq/svc/abort几种模式下sp是不能共用的
user
用户模式
fiq
irq
svc
abt
und
sys
mon
31 30 29 28 27 26 25 24 23 20 19 16 15 10 9 8 7 6 5 4 0
N | Z| C |V |Q| IT J reser GE IT E A I F T M
M[4:0]
10000 user
10001 fiq
10010 irq
10011 svc
10111 abt
11011 und
11111 sys
10110 mon
此时lr中保存的实际上是异常的返回地址,异常发生,切换到svc模式后,会将lr保存到svc模式栈中
工作:
1)cpu状态的切换
cpu -> irq -> svc
2)在不同模式保存数据到栈中
sp — irq 报错lr 和 cpsr 信息
sp — svc
3)调用 handle_arch_irq 函数
.macro vector_stub, name, mode, correction=0
.align 5 //强制对齐32字节对齐
vector_\name:
.if \correction
/*
*需要调整返回值,对应irq异常将lr减去4,因为异常发生时,arm将pc地址+4赋值给了lr。
*/
sub lr, lr, #\correction
.endif
@save r0,lr;将r0和lr保存到异常模式的栈上[sp] = r0;[sp+4] = lr_irq;
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr //得到异常发生时所处模式得信息
/*
*将spsr保存到异常模式的栈上[sp+8]=spsr_irq=lr
*/
str lr, [sp, #8] @ save spsr
/*
*1. dabt处理时,r0=r0^(0x17^0x13)=r0^0x4,bit3取反之后10011变为svc模式;
*2. irq处理时:r0=10010=r0^(0x12^0x13)=r0^0x1=10011变为svc模式
*/
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0
/*
*r0=sp;
*注意:
*1. 此时r0中保存了异常状态下sp栈地址,这个栈上保存了r0,lr(异常返回地址),spsr(异常发生时,cpu
* 的状态,当然异常返回时需要恢复该状态)
*2. 之后的函数会把r0中保存的异常模式的sp上信息,加载到svc模式下的sp栈上。异常处理返回时再将svc
* mode 的栈加载到arm寄存器上。
*/
mov r0, sp
//应该再切换到svc 之前把sp保存到r0中 ***************
/*
*lr中保存发生异常时arm的cpsr状态到spsr
*1. usr模式发生异常则lr=10000&0x0f;lr=pc+lr<<2 pc+0时执行 __irq_usr;
*2. svc模式发生异常则lr=10011&0x0f;lr=pc+lr<<2 pc+12时执行 __irq_svc
*/
ARM( ldr lr, [pc, lr, lsl #2] )
/* movs中s表示把spsr恢复给cpsr,上面可知spsr保存的是svc模式,不过此时中断还是关闭的
* 异常处理一定要进入svc模式原因:
*(1)异常处理一定要PL1特权级。
*(2)使能嵌套中断。
* 如果一个中断模式(例如用户态发生中断,arm从usr进入irq模式)中重新允许中断,
* 且这个中断模式中使用了bl指令,bl会把pc放到lr_irq中,这个地址会被当前模式下产生的中断破坏
* 这种情况下中断无法返回。所以为了避免这种情况,中断处理过程应该切换到svc模式,bl指令可以把
* pc(即子程序返回地址)保存到lr_svc.
*/
movs pc, lr @ branch to handler in SVC mode
ENDPROC(vector_\name)
.long __irq_usr @ 0 (USR_26 / USR_32) 从用户态下进入的irq,执行__irq_usr代码
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32) 从内核态下进入的irq,执行__irq_svc代码
.long __irq_invalid @ 4 .long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
//此时arm处于svc模式执行下面代码
__irq_usr:
usr_entry
kuser_cmpxchg_check
irq_handler //irq处理函数
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq //中断处理完成返回
UNWIND(.fnend )
ENDPROC(__irq_usr)/*
.macro usr_entry, trace=1, uaccess=1
UNWIND(.fnstart )
UNWIND(.cantunwind ) @ don’t unwind the user space
/*
*arch/arm/kernel/asm-offsets.c中定义DEFINE(S_FRAME_SIZE,sizeof(struct pt_regs))
*S_FRAME_SIZE=72
*/
sub sp, sp, #S_FRAME_SIZE
/*
*注释:
*宏CONFIG_MULTI_IRQ_HANDLER在.config中有定义,会将handle_arch_irq里的值赋值给pc去执行。
*给handle_arch_irq请看后面的C语言阶段分析
/
/
Interrupt handling.
*/
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
//调用中断函数
ldr r1, =handle_arch_irq
mov r0, sp
badr lr, 9997f
ldr pc, [r1] //进入C语言阶段的中断处理
#else
arch_irq_handler_default
#endif
9997:
.endm
handle_arch_irq 赋值在/drivers/irqchip/irq-vic.c的vic_register函数中调用set_handle_irq进行赋值
static void __init vic_register(void __iomem *base, unsigned int parent_irq,
unsigned int irq,
u32 valid_sources, u32 resume_sources,
struct device_node *node)
struct vic_device *v;
//The base address of the VIC
v = &vic_devices[vic_id];
v->base = base;
v->valid_sources = valid_sources;
v->resume_sources = resume_sources;
//handle_arch_irq = vic_handle_irq 最终调用 vic_handle_irq 函数
set_handle_irq(vic_handle_irq); //此处被调用
vic_id++;
//drivers/irqchip/irq-vic.c
/*
Keep iterating over all registered VIC’s until there are no pending
interrupts.
*/
static void __exception_irq_entry vic_handle_irq(struct pt_regs *regs)
{
int i, handled;
do {
for (i = 0, handled = 0; i < vic_id; ++i)
handled |= handle_one_vic(&vic_devices[i], regs);
} while (handled);
}
static int handle_one_vic(struct vic_device *vic, struct pt_regs *regs)
{
u32 stat, irq;
int handled = 0;
while ((stat = readl_relaxed(vic->base + VIC_IRQ_STATUS))) {
irq = ffs(stat) - 1;
handle_domain_irq(vic->domain, irq, regs);
handled = 1;
}
return handled;
}
static inline int handle_domain_irq(struct irq_domain *domain,
unsigned int hwirq, struct pt_regs *regs)
{
return __handle_domain_irq(domain, hwirq, true, regs);
}
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
unsigned int irq = hwirq;
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {
generic_handle_irq(irq);
}
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
generic_handle_irq_desc(desc);
static inline void generic_handle_irq_desc(struct irq_desc desc)
{
/
*handle_irq在struct domain的struct domain_ops的map函数中调用
*irq_set_chip_and_handler 赋值为 handle_level_irq
*/
desc->handle_irq(desc);
}
void handle_level_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
mask_ack_irq(desc);
if (!irq_may_run(desc))
goto out_unlock;
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
kstat_incr_irqs_this_cpu(desc);
/*
* If its disabled or no action available
* keep it masked and get out of here
*/
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
desc->istate |= IRQS_PENDING;
goto out_unlock;
}
//继续调
handle_irq_event(desc);
//kernel/irq/chip.c
static inline void mask_ack_irq(struct irq_desc *desc)
{
/*desc->irq_data.chip在struct domain的struct domain_ops的map函数中调用
*irq_set_chip_and_handler赋值为vic_chip.
*/
if (desc->irq_data.chip->irq_mask_ack)
desc->irq_data.chip->irq_mask_ack(&desc->irq_data);
else {
desc->irq_data.chip->irq_mask(&desc->irq_data);
if (desc->irq_data.chip->irq_ack)
desc->irq_data.chip->irq_ack(&desc->irq_data);
}
irq_state_set_masked(desc);
}
//drivers/irqchip/irq-vic.c 在
static struct irq_chip vic_chip = {
.name = “VIC”,
.irq_ack = vic_ack_irq,
.irq_mask = vic_mask_irq,
.irq_unmask = vic_unmask_irq,
.irq_set_wake = vic_set_wake,
};
//kernel/irq/handle.c
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
irqreturn_t ret;
desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
ret = handle_irq_event_percpu(desc);
//kernel/irq/handle.c
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
irqreturn_t retval = IRQ_NONE;
unsigned int flags = 0, irq = desc->irq_data.irq;
struct irqaction *action = desc->action;
/* action might have become NULL since we dropped the lock */
while (action) {
irqreturn_t res;
trace_irq_handler_entry(irq, action);
/*
*action->handler处理函数为驱动中注册的处理函数
*/
res = action->handler(irq, action->dev_id);
interrupt-controler
interrupts =
#interrupt-cells = <3>
interrupt-controller;
interrupt-parent = <&gpc>;
interrupts =
interrupt-names = “gpmi0”, “gpmi1”, “gpmi2”, “gpmi3”;
interrupts =
interrupt-names = “bch”;
======================================中断向量表和中断处理部分代码的搬移
early_trap_init函数的调用流程为:
start_kernel(init/main.c)—>
setup_arch(arch/arm/kernel/setup.c)—>
paging_init(arch/arm/mm/mmu.c)—>
devicemaps_init(arch/arm/mm/mmu.c)—>
early_trap_init(arch/arm/kernel/traps.c)
static void __init devicemaps_init(const struct machine_desc *mdesc)
{
struct map_desc map;
unsigned long addr;
void vectors;
/
* Allocate the vector page early.
*分配两个页的内存空间,arm中每个页的大小为4K,这两个页的内存空间,一个是为保存中断向量
*表,一个是为了保存中断的处理部分代码,这两部分代码的排布可以在
*(arch/arm/kernel/vmlinux.lds和arch/arm/kernel/entry-armv.S)中可以具体分析出来
*/
vectors = early_alloc(PAGE_SIZE * 2);
early_trap_init(vectors);
/*
*创建一个页的内存地址映射,虚拟地址为0xffff0000,此地址为中断向量表的高端地址
*设置中断向量表的高端地址在汇编的v7_setup中,使用的v7_crval设置了cp15的c1寄存器
*v7_crval定义在arch/arm/mm/proc-v7-2level.S。
*/
map.pfn = __phys_to_pfn(virt_to_phys(vectors));
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
#ifdef CONFIG_KUSER_HELPERS //此宏有定义
map.type = MT_HIGH_VECTORS;
#else
map.type = MT_LOW_VECTORS;
#endif
create_mapping(&map);
/*
*判断中断向量表的位置是否设置在高端地址,如果中断向量表没有设置在高端地址,
*在映射低端中断向量表地址。
*/
if (!vectors_high()) {
map.virtual = 0;
map.length = PAGE_SIZE * 2;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
}
/* Now create a kernel read-only mapping */
map.pfn += 1;
map.virtual = 0xffff0000 + PAGE_SIZE;
map.length = PAGE_SIZE;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
.align 2
.type v7_crval, #object
v7_crval:
crval clear=0x2120c302, mmuset=0x10c03c7d, ucset=0x00c01c7c
/*
* 将申请的4K先设置为未定义指令,防止在发生其他中断时,没有处理导致cpu错误
*/
for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
((u32 *)vectors_base)[i] = 0xe7fddef1;/*
/*
*将中断向量表和中断处理的代码搬移到申请的两页地址空间内
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
kuser_init(vectors_base);
flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
void __init early_trap_init(void *vectors_base)
{
unsigned long vectors = (unsigned long)vectors_base;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
unsigned i;
/*
* 将申请的4K先设置为未定义指令,防止在发生其他中断时,没有处理导致cpu错误
*/
for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
((u32 *)vectors_base)[i] = 0xe7fddef1;
/*
*将中断向量表和中断处理的代码搬移到申请的两页地址空间内
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
kuser_init(vectors_base);
flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
//init/main.c
asmlinkage void __init start_kernel(void)
{
……
trap_init(); //空函数
……
early_irq_init();
init_IRQ();
……
}
//arch/arm/kernel/irq/irqdesc.c/
int __init early_irq_init(void)
{
int i, initcnt, node = first_online_node;
struct irq_desc *desc;
init_irq_default_affinity(); //由CONFIG_SMP宏决定是否为空函数,见下面分析一
/* Let arch update nr_irqs and return the nr of preallocated irqs */
initcnt = arch_probe_nr_irqs();//体系结构相关的代码来决定预先分配的中断描述符的个数见分析二
if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS)) //见下面分析三
nr_irqs = IRQ_BITMAP_BITS;
if (WARN_ON(initcnt > IRQ_BITMAP_BITS))
initcnt = IRQ_BITMAP_BITS;
if (initcnt > nr_irqs)
nr_irqs = initcnt;
//事先构造好 irq_desc 结构体
for (i = 0; i < initcnt; i++) {
desc = alloc_desc(i, node, NULL); //分配 struct irq_desc结构,分析四
//用于标记那些irq 申请占用 allocated_irqs 关键变量
set_bit(i, allocated_irqs); //设置相应的位数组,分析三
irq_insert_desc(i, desc); //保存irq_desc指针,分析四
}
return arch_early_irq_init(); //kernel/softirq.c中定义,为空函数
}
//arch/arm/kernel/irq.c
#ifdef CONFIG_SPARSE_IRQ
int __init arch_probe_nr_irqs(void)
{
nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS;//NR_IRQS 16
return nr_irqs; //分析下面的分析,得知返回的是NR_IRQS
}
#endif
//kernel/irq/internals.h
//include/linux/types.h
#define DECLARE_BITMAP(name,bits)
unsigned long name[BITS_TO_LONGS(bits)]
//arch/arm/include/asm/bitops.h
#define set_bit(nr,p) ATOMIC_BITOP(set_bit,nr,p) //_set_bit
#define clear_bit(nr,p) ATOMIC_BITOP(clear_bit,nr,p)
#define change_bit(nr,p) ATOMIC_BITOP(change_bit,nr,p)
#define test_and_set_bit(nr,p) ATOMIC_BITOP(test_and_set_bit,nr,p)
#define test_and_clear_bit(nr,p) ATOMIC_BITOP(test_and_clear_bit,nr,p)
#define test_and_change_bit(nr,p) ATOMIC_BITOP(test_and_change_bit,nr,p)
/*
//arch/arm/lib/setbit.S
#include
#include
#include “bitops.h”
.text
//bitop 为宏
bitop _set_bit, orr
//arch/arm/lib/bitops.h
.macro bitop, name, instr
ENTRY( \name )
UNWIND( .fnstart )
ands ip, r1, #3
strneb r1, [ip] @ assert word-aligned
mov r2, #1
and r3, r0, #31 @ Get bit offset
mov r0, r0, lsr #5
add r1, r1, r0, lsl #2 @ Get word offset
#if LINUX_ARM_ARCH >= 7 && defined(CONFIG_SMP)
.arch_extension mp
ALT_SMP(W(pldw) [r1])
ALT_UP(W(nop))
#endif
mov r3, r2, lsl r3
1: ldrex r2, [r1]
\instr r2, r2, r3
strex r0, r2, [r1]
cmp r0, #0
bne 1b
bx lr
UNWIND( .fnend )
ENDPROC(\name )
.endm
//driver/irqchip/irq-vic.c
/*
*通过IRQCHIP_DECLARE定义一个位于__irqchip_of_table段的全局量struct of_device_id
*__of_table_arm_pl192_vic,linux在中断初始化阶段在设备树中寻找是否有对应的节点,找到
*相对应的节点后,随即执行vic_of_init
*/
IRQCHIP_DECLARE(arm_pl192_vic, “arm,pl192-vic”, vic_of_init);
======================================内存分配
内核中内存申请的一些注意事项:
1)是否允许睡眠,在申请内存的时候
可以睡眠,指定 GFP_KERNEL。
不能睡眠,就指定 GFP_ATOMIC
2)是否允许DMA访问,
DMA 可以访问的内存,比如ISA或者有些PCI设备,就需要指定 GFP_DMA
3)检查返回地址
需要对返回值检查 NULL
4)内核申请的内存是否连续地址
内核通常需要 连续的物理地址
5)注意内存不能泄露
不能在死循环中申请内存,申请玩对应的内存,必须注意释放内存
1.kmalloc
kmalloc() 申请的内存位于物理内存映射区域(即虚拟内存地址)。但是在物理上它是
连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的
内存大小有限制,不能超过128KB。
都是以字节为单位进行分配。
所分配的内存,在虚拟地址上连续。
kmalloc 和 kzalloc 分配的内存大小有限制(128KB)
2.kzalloc
kzalloc() 函数与 kmalloc() 非常相似,参数及返回值是一样的,可以说是前者是后者的一个变
种,因为 kzalloc() 实际上只是额外附加了 __GFP_ZERO 标志。所以它除了申请内核内存外,还
会对申请到的内存内容清零。
kmalloc 和 kzalloc 分配的内存大小有限制(128KB)
kzalloc()函数一般用在Linux驱动代码中的 probe 函数中芯片驱动的内存开辟操作
都是以字节为单位进行分配。
所分配的内存,在虚拟地址上连续。
3.vmalloc()
vmalloc() 函数则会在 虚拟内存空间 给出一块连续的内存区,但这片连续的虚拟内存在
物理内存中并不一定连续。由于 vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内
存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。
vmalloc() 和 vfree() 可以睡眠,因此不能从中断上下文调用。
都是以字节为单位进行分配。
所分配的内存,在虚拟地址上连续。
vmalloc 分配内存时则可能产生阻塞
4、dma_alloc_wc
比如我们在学习Framebuffer驱动程序的时候,要分配显存(显存是物理地址连续的大内存,不能使用kmalloc)
====================== 内存读写函数
//具体代码参看arch/arm/include/asm/io.h
/*
#define writeb_relaxed(v,c) __raw_writeb(v,c)
#define writew_relaxed(v,c) __raw_writew((__force u16) cpu_to_le16(v),c)
#define writel_relaxed(v,c) __raw_writel((__force u32) cpu_to_le32(v),c)
#define readb© ({ u8 __v = readb_relaxed©; __iormb(); __v; })
#define readw© ({ u16 __v = readw_relaxed©; __iormb(); __v; })
#define readl© ({ u32 __v = readl_relaxed©; __iormb(); __v; })
#define writeb(v,c) ({ __iowmb(); writeb_relaxed(v,c); })
#define writew(v,c) ({ __iowmb(); writew_relaxed(v,c); })
#define writel(v,c) ({ __iowmb(); writel_relaxed(v,c); })
#define readsb(p,d,l) __raw_readsb(p,d,l)
#define readsw(p,d,l) __raw_readsw(p,d,l)
#define readsl(p,d,l) __raw_readsl(p,d,l)
#define writesb(p,d,l) __raw_writesb(p,d,l)
#define writesw(p,d,l) __raw_writesw(p,d,l)
#define writesl(p,d,l) __raw_writesl(p,d,l)