📄 at91_fiq.c
字号:
/* * Copyright 2007 Andy Green <andy@warmcat.com> */#include <linux/tty.h>#include <linux/ioport.h>#include <linux/init.h>#include <linux/console.h>#include <linux/sysrq.h>#include <linux/module.h>#include <linux/kernel.h>#include <asm/arch/AT91RM9200.h>#include <asm/arch/hardware.h>#include <asm/arch/pio.h>#include <asm/cacheflush.h>#include <asm/serial.h>#include <asm/io.h>#include <asm/irq.h>#include <linux/serial_core.h>#include <asm/arch/at91rm9200_fiq_ipc_type.h>#if 1#define PRINTK1(fmt...) printk(fmt)#else#define PRINTK1(fmt...) do { } while (0)#endif/* * HOW TO USE * * 1) Customize the struct in asm/archat91rm9200_fiq_ipc_type.h for your task * * 2) Customize the FIQ ISR routine at91rm9200_fiq_isr() below for your task * at "Your C Code goes here" * * 3) Add the following in your kernel module so you * can communicate with the FIQ ISR using at91rm9200_fiq_ipc * * #include <asm/arch/at91rm9200_fiq_ipc_type.h> * extern struct at91rm9200_fiq_ipc at91rm9200_fiq_ipc; * * 4) Have your kernel module call at91rm9200_fiq_init() on init and * at91rm9200_fiq_exit() on exit -- prototypes are already in * asm/arch/at91rm9200_fiq_ipc_type.h we included in step 3 * * 5) Trigger a FIQ by giving a falling edge to PB28 * * * Major Caveats for using FIQ * --------------------------- * * 1) it CANNOT touch any vmalloc()'d memory, only memory * that was kmalloc()'d. Static allocations in the monolithic kernel * are kmalloc()'d so they are okay. You can touch memory-mapped IO, but * the pointer for it has to have been stored in kmalloc'd memory. The * reason for this is simple: every now and then Linux turns off interrupts * and reorders the paging tables. If a FIQ happens during this time, the * virtual memory space can be partly or entirely disordered or missing. * * 2) Because vmalloc() is used when a module is inserted, THIS FIQ * ISR HAS TO BE IN THE MONOLITHIC KERNEL, not a module. But the way * it is set up, you can all to enable and disable it from your module * and intercommunicate with it through struct at91rm9200_fiq_ipc * at91rm9200_fiq_ipc which you can define in * asm/archat91rm9200_fiq_ipc_type.h. The reason is the same as above, a * FIQ could happen while even the ISR is not present in virtual memory * space due to pagetables being changed at the time. * * 3) You can't call any Linux API code except simple macros * - understand that FIQ can come in at any time, no matter what * state of undress the kernel may privately be in, thinking it * locked the door by turning off interrupts... FIQ is an * unstoppable monster force (which is its value) * - they are not vmalloc()'d memory safe * - they might do crazy stuff like sleep: FIQ pisses fire and * is not interested in 'sleep' that the weak seem to need * - calling APIs from FIQ can re-enter un-renterable things * - summary: you cannot interoperate with linux APIs directly in the FIQ ISR * * If you follow these rules, it is fantastic, an extremely powerful, solid, * genuine hard realtime feature. * */#define AT91_PB28_FIQ (1 << 28) /* A: Fast Interrupt *//* actual FIQ vector address where execution starts after FIQ */#define AT91RM9200_FIQ_VECTOR 0xffff001c/* more than enough to cover our jump instruction to the isr */#define SIZEOF_FIQ_JUMP 8/* more than enough to cover at91rm9200_fiq_isr() in 4K blocks */#define SIZEOF_FIQ_ISR 0x4000/* increase the size of the stack that is active during FIQ as needed */static u8 u8aFiqStack[16384];/* contains stuff FIQ ISR modifies and normal kernel code can see and use * this is defined in <asm/archat91rm9200_fiq_ipc_type.h>, you should customize * the definition in there and include the same definition in your kernel * module that wants to interoperate with your FIQ code. */struct at91rm9200_fiq_ipc at91rm9200_fiq_ipc;EXPORT_SYMBOL(at91rm9200_fiq_ipc);static struct uart_8250_port serial8250_ports[4];EXPORT_SYMBOL(serial8250_ports);static _INLINE_ voidreceive_chars(struct uart_8250_port *up, int *status, struct pt_regs *regs){ struct tty_struct *tty = up->port.info->tty; unsigned char ch, flag; int max_count = 256; PRINTK1("entering %s\n",__FUNCTION__); do { ch = serial_inp(up, UART_RX); flag = TTY_NORMAL; up->port.icount.rx++; if (unlikely(*status & (UART_LSR_BI | UART_LSR_PE | UART_LSR_FE | UART_LSR_OE))) { /* * For statistics only */ if (*status & UART_LSR_BI) { *status &= ~(UART_LSR_FE | UART_LSR_PE); up->port.icount.brk++; /* * We do the SysRQ and SAK checking * here because otherwise the break * may get masked by ignore_status_mask * or read_status_mask. */ if (uart_handle_break(&up->port)) goto ignore_char; } else if (*status & UART_LSR_PE) up->port.icount.parity++; else if (*status & UART_LSR_FE) up->port.icount.frame++; if (*status & UART_LSR_OE) up->port.icount.overrun++; /* * Mask off conditions which should be ingored. */ *status &= up->port.read_status_mask; if (*status & UART_LSR_BI) { PRINTK1("handling break...."); flag = TTY_BREAK; } else if (*status & UART_LSR_PE) flag = TTY_PARITY; else if (*status & UART_LSR_FE) flag = TTY_FRAME; } if (uart_handle_sysrq_char(&up->port, ch, regs)) goto ignore_char; if ((*status & up->port.ignore_status_mask) == 0) tty_insert_flip_char(tty, ch, flag); if (*status & UART_LSR_OE) /* * Overrun is special, since it's reported * immediately, and doesn't affect the current * character. */ tty_insert_flip_char(tty, 0, TTY_OVERRUN);// }ignore_char: *status = serial_inp(up, UART_LSR); } while ((*status & UART_LSR_DR) && (max_count-- > 0)); spin_unlock(&up->port.lock); tty_flip_buffer_push(tty); spin_lock(&up->port.lock);}static _INLINE_ void transmit_chars(struct uart_8250_port *up){ struct circ_buf *xmit = &up->port.info->xmit; int count; PRINTK1("entering %s\n",__FUNCTION__); if (up->port.x_char) { serial_outp(up, UART_TX, up->port.x_char); up->port.icount.tx++; up->port.x_char = 0; return; } if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) {// serial8250_stop_tx(&up->port,0); return; } count = up->port.fifosize; do { serial_out(up, UART_TX, xmit->buf[xmit->tail]); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); up->port.icount.tx++; if (uart_circ_empty(xmit)) break; } while (--count > 0); if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&up->port); PRINTK1("THRE...");// if (uart_circ_empty(xmit))// serial8250_stop_tx(&up->port,0);}static void serial8250_handle_port(struct uart_8250_port *up, struct pt_regs *regs){ unsigned int status = serial_inp(up, UART_LSR); PRINTK1("status = %x...", status); if (status & UART_LSR_DR) receive_chars(up, &status, regs); if (status & UART_LSR_THRE) transmit_chars(up);}/* the actual FIQ ISR */static void __attribute__ ((naked))at91rm9200_fiq_isr(void){ int i; unsigned int iir; struct pt_regs *regs; struct uart_8250_port *up; /* *you can declare local vars here, take care to set the frame size * below accordingly if there are more than a few dozen bytes of them */ /* entry takes care to store registers we will be treading on here */ asm __volatile__ ( "mov ip, sp ;" /* stash FIQ and r0-r8 normal regs */ "stmdb sp!, {r0-r8, r10-r12, lr};" /* stash R9 separtely so we can have it first on exit */ "stmdb sp!, {r9};" /* !! THIS SETS THE FRAME, adjust to > sizeof locals */ "sub fp, ip, #256 ;" : : :"r9" );/* * your C code goes here * * as an example, we bump a counter, you can rip that out when you * customize */ for(i=0;i<4;i++) { up=&serial8250_ports[i]; iir = serial_in(up, UART_IIR); if (!(iir & UART_IIR_NO_INT)) { serial8250_handle_port(up, regs); } } /* exit back to normal mode restoring everything */ asm __volatile__ ( /* pop R9 to contain &AT91_SYS->AIC_FVR */ "ldmia sp!, {r9};" /* read from it to acknowledge FIQ source */ "ldr r0, [r9];" /* return FIQ regs back to pristine state * and get normal regs back */ "ldmia sp!, {r0-r8, r10-r12, lr};" /* return */ "subs pc, lr, #4;" );}/* this is copied into the hard FIQ vector during init */static void __attribute__ ((naked))at91rm9200_FIQ_Branch(void){ asm __volatile__ ( "mov pc, r8 ; " );}/* call this from your kernel module to set up the FIQ ISR to service FIQs, * to enable the FIQ pin (PB28 on AT91RM9200) and to start accepting FIQs */voidat91rm9200_fiq_init(void){ struct pt_regs regs; register unsigned long tmp; printk("Enabling FIQ\n"); /* set up PB28, the FIQ input pin */ AT91_SYS->PIOB_PDR |=AT91_PB28_FIQ; AT91_SYS->PIOB_ODR |=AT91_PB28_FIQ; AT91_SYS->PIOB_ASR |=AT91_PB28_FIQ; AT91_SYS->AIC_IDCR |=1 << AT91C_ID_FIQ; local_fiq_disable(); /* prep the special FIQ mode regs */ memset(®s,0,sizeof(regs)); regs.ARM_r8 = (long)at91rm9200_fiq_isr; regs.ARM_r9 = (long)AT91C_VA_BASE_SYS+66; //66,AIC_FVR regs.ARM_sp = (long)u8aFiqStack + sizeof(u8aFiqStack) - 4; /* set up the special FIQ-mode-only registers from our regs */ asm volatile ( "mrs %0, cpsr\n\ msr cpsr_c, %2 @ select FIQ mode\n\ mov r0, r0\n\ ldmia %1, {r8 - r14}\n\ msr cpsr_c, %0 @ return to SVC mode\n\ mov r0, r0\n" : "=&r" (tmp) : "r" (®s.ARM_r8), "I" (PSR_I_BIT | PSR_F_BIT | FIQ_MODE) : "r0" ); /* copy our jump to the real ISR into the hard vector address */ memcpy((void *)AT91RM9200_FIQ_VECTOR, at91rm9200_FIQ_Branch, SIZEOF_FIQ_JUMP); /* flush this area */ flush_icache_range(AT91RM9200_FIQ_VECTOR, AT91RM9200_FIQ_VECTOR + SIZEOF_FIQ_JUMP); flush_icache_range((long)at91rm9200_fiq_isr, (long)at91rm9200_fiq_isr + SIZEOF_FIQ_ISR); /* switch FIQ to edge triggered mode */ AT91_SYS->AIC_SMR[0]=7 | AT91C_AIC_SRCTYPE;// at91_sys_write(AT91_AIC_SMR(0), 7 | AT91C_AIC_SRCTYPE_FALLING); /* oho... set it going! */ local_fiq_enable();// at91_sys_write(AT91_AIC_IECR, 1 << AT91_ID_FIQ); AT91_SYS->AIC_IECR=1 << AT91C_ID_FIQ;}EXPORT_SYMBOL(at91rm9200_fiq_init);/* call this from your kernel module disable generation of FIQ actions */voidat91rm9200_fiq_exit(void){// at91_sys_write(AT91_AIC_IDCR, 1 << AT91_ID_FIQ); AT91_SYS->AIC_IDCR=1 << AT91C_ID_FIQ; local_fiq_disable();}EXPORT_SYMBOL(at91rm9200_fiq_exit);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -