继LED之后,学习使用A10中断,目的是学习U-boot中如何使用中断。
软件环境: 笔记本一台,安装WindowsXP sp3
XP下软件:Source Insight 3.5;SecureCRT;VMware Workstation7.0
虚拟机中安装ubuntu10.04
ubuntu中软件:Vim;编译工具链 arm-none-eabi- 版本4.7.2
硬件环境:cubieboard,淘宝自带的串口线和电源线
microSD卡一只,读卡器一个
准备工作:U-boot实现SD卡启动,请参考另一篇文章 http://blog.csdn.net/andy_wsj/article/details/8515197
CB的原理图
A10用户手册
一、代码分析进入点,厘清脉络
从上次打开LED控制就发现了 http://blog.csdn.net/andy_wsj/article/details/8973818,文件status_led.c文件内的函数status_led_tick将会在定时器中断中被
调用,在工程内搜索status_led_tick,获得4个结果,两个在interrupts.c下。进入查看,阅读上下文,可以看出,这是在定时器中断中调用,实现定时LED闪烁控制。
查看文件,希望获得中断处理部分代码。在任何平台下,使用中断之前都要初始化,因此在工程搜索这个文件里面的函数interrupt_init,获得很多结果。调用的地方暂时不理会,只看定义。A10是ARM平台,理所当然的查看ARM架构lib里面的定义:
Interrupts.c (d:\share\u-boot-sunxi-sunxi\arch\arm\lib):int interrupt_init (void)
可以看到,若要使用中断,需要定义预编译宏:
CONFIG_USE_IRQ
在include/configs/sunxi-common.h中定义之,然后编译u-boot,提示两个错误:
CONFIG_STACKSIZE_IRQ 未定义
CONFIG_STACKSIZE_FIQ 未定义
就是中断的栈大小没定义,先在include/configs/sunxi-common.h定义两个,不知到多大合适,暂时定义10K
#define CONFIG_STACKSIZE_IRQ (10 * 1024)
#define CONFIG_STACKSIZE_FIQ (10 * 1024)
再次编译u-boot,再次提示错误,start.S里面的do_irq没定义,打开
\u-boot-sunxi-sunxi\arch\arm\cpu\armv7\start.S,搜索do_irq,可以看到:
#ifdef CONFIG_USE_IRQ
.align
5
irq:
get_irq_stack
irq_save_user_regs
bl
do_irq
irq_restore_user_regs
.align
5
fiq:
get_fiq_stack
/* someone ought to write a more effective fiq_save_user_regs */
irq_save_user_regs
bl
do_fiq
irq_restore_user_regs
#else
.align
5
irq:
get_bad_stack
bad_save_user_regs
bl
do_irq
.align
5
fiq:
get_bad_stack
bad_save_user_regs
bl
do_fiq
#endif /* CONFIG_USE_IRQ */
很明显,这就是中断处理了。
继续看看标号irq,fiq,在文件start.S开头的地方:
.globl _start
_start: b
reset
ldr
pc, _undefined_instruction
ldr
pc, _software_interrupt
ldr
pc, _prefetch_abort
ldr
pc, _data_abort
ldr
pc, _not_used
ldr
pc, _irq
ldr
pc, _fiq
#ifdef CONFIG_SPL_BUILD
_undefined_instruction: .word _undefined_instruction
_software_interrupt:
.word _software_interrupt
_prefetch_abort:
.word _prefetch_abort
_data_abort:
.word _data_abort
_not_used:
.word _not_used
_irq:
.word _irq
_fiq:
.word _fiq
_pad:
.word 0x12345678 /* now 16*4=64 */
#else
_undefined_instruction: .word undefined_instruction
_software_interrupt:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_not_used:
.word not_used
_irq:
.word irq
_fiq:
.word fiq
_pad:
.word 0x12345678 /* now 16*4=64 */
#endif
/* CONFIG_SPL_BUILD */
我们已经来到整个u-boot开始的地方,即上电之后一条被执行的指令就是这里
_start: b
reset
接下来几句就是异常处理
ldr
pc, _undefined_instruction
ldr
pc, _software_interrupt
ldr
pc, _prefetch_abort
ldr
pc, _data_abort
ldr
pc, _not_used
ldr
pc, _irq
ldr
pc, _fiq
发生什么类型的异常就是跳到对应的位置进行处理,再看看
_irq:
.word irq
_fiq:
.word fiq
由此可见,假设发生一个irq,那么首先执行
ldr
pc, _irq
_irq就是irq
(_irq==irq)->do_irq,就跳到中断处理函数去了,如何实现do_irq呢?在工程内搜索之。结果不多,逐个查看,发现就是各种实现方式,对于A10,我觉得自己修改一种方式有利于学习,
最后挑了Interrupts.c (d:\share\u-boot-sunxi-sunxi\arch\arm\cpu\arm720t):void do_irq (struct pt_regs *pt_regs)
我决定把这一个文件修改成能在当前平台下使用,并将这个文件放在\u-boot-sunxi-sunxi\arch\arm\cpu\armv7\sunxi\下,
修改该目录Makefile文件,增加如下几句:
ifdef CONFIG_USE_IRQ
COBJS += interrupts.o
endif
再增加一个文件Interrupts.h,用来定义A10的中断寄存器地址和中断源,放在\u-boot-sunxi-sunxi\arch\arm\include\asm\arch-sunxi目录下。
目的是同过timer0中断控制LED闪烁,其他中断暂时都不会打开,但是会定义,为进一步学习其他模块做准备。
二、了解中断机制,修改代码
准备写代码了,应该看一看A10用户手册,仔细阅读中断和定时器相关的章节
在“intterrupt controller”这一章,所有中断都在这里描述
在“timer controller”这一章,也描述了与定时器相关的寄存器
1、代码编写和修改。
修改\u-boot-sunxi-sunxi\arch\arm\cpu\armv7\sunxi\timmer.c里面的timmer_init函数,使之符合中断要求。
/*
* (C) Copyright 2007-2011
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* Tom Cubie <
[email protected]>
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <common.h>
#include <asm/io.h>
#include <asm/arch/timer.h>
#include <asm/arch/interrupts.h>
DECLARE_GLOBAL_DATA_PTR;
#define TIMER_MODE (0 << 7) /* continuous mode */
#define TIMER_DIV (0 << 4) /* pre scale 1 */
#define TIMER_SRC (1 << 2) /* osc24m */
#define TIMER_RELOAD (1 << 1) /* reload internal value */
#define TIMER_EN (1 << 0) /* enable timer */
#define TIMER_CLOCK
(24 * 1000 * 1000)
#define COUNT_TO_USEC(x)
((x) / 24)
#define USEC_TO_COUNT(x)
((x) * 24)
#define TICKS_PER_HZ
(TIMER_CLOCK / CONFIG_SYS_HZ)
#define TICKS_TO_HZ(x)
((x) / TICKS_PER_HZ)
#define TIMER_LOAD_VAL TICKS_PER_HZ
#define TIMER_NUM (0) /* we use timer 0 */
static struct sunxi_timer *timer_base =
&((struct sunxi_timer_reg *)SUNXI_TIMER_BASE)->timer[TIMER_NUM];
/* macro to read the 32 bit timer: since it decrements, we invert read value */
#define READ_TIMER() (~readl(&timer_base->val))
/* init timer register */
int timer_init(void)
{
unsigned int val;
writel(TIMER_LOAD_VAL, &timer_base->inter);
writel(TIMER_MODE | TIMER_DIV | TIMER_SRC | TIMER_RELOAD | TIMER_EN, &timer_base->ctl);
#if defined(CONFIG_USE_IRQ)
val = readl(SUNXI_TIMER_BASE + 0x04);
val |= (0x1 << 0);
writel( val, SUNXI_TIMER_BASE + 0x04); /* clear timer0 irq pending */
/* enable timer0 interrupt*/
val = readl(SUNXI_TIMER_BASE);
val |= (0x1 << 0);
writel( val, SUNXI_TIMER_BASE); /* Enable timer0 irq */
writel( (1 << (INT_TIM0 % 32)), INTC_EN_REG0 + (INT_TIM0 >> 5));
#if defined(TIMER_DEBUG)
printf("Timer init done.\n");
#endif
#endif
return 0;
}
/* timer without interrupts */
ulong get_timer(ulong base)
{
return get_timer_masked() - base;
}
ulong get_timer_masked(void)
{
/* current tick value */
ulong now = TICKS_TO_HZ(READ_TIMER());
if (now >= gd->lastinc)
/* normal (non rollover) */
gd->tbl += (now - gd->lastinc);
else
/* rollover */
gd->tbl += (TICKS_TO_HZ(TIMER_LOAD_VAL) - gd->lastinc) + now;
gd->lastinc = now;
return gd->tbl;
}
/* delay x useconds */
void __udelay(unsigned long usec)
{
long tmo = usec * (TIMER_CLOCK / 1000) / 1000;
ulong now, last = READ_TIMER();
while (tmo > 0) {
now = READ_TIMER();
if (now > last)
/* normal (non rollover) */
tmo -= now - last;
else
/* rollover */
tmo -= TIMER_LOAD_VAL - last + now;
last = now;
}
}
/*
* This function is derived from PowerPC code (read timebase as long long).
* On ARM it just returns the timer value.
*/
unsigned long long get_ticks(void)
{
return get_timer(0);
}
/*
* This function is derived from PowerPC code (timebase clock frequency).
* On ARM it returns the number of timer ticks per second.
*/
ulong get_tbclk(void)
{
ulong tbclk;
tbclk = CONFIG_SYS_HZ;
return tbclk;
}
增加\u-boot-sunxi-sunxi\arch\arm\cpu\armv7\sunxi\interrupts.c,这个是修改过来的。修改Makefile,使之可编译到u-boot。
将status_led_tick增加到timer_isr调用中,若正常,板上的led将闪烁。
/*
* for sun4i A10
* by Andy 2013.5
*/
#include <common.h>
#include <asm/io.h>
#include <asm/arch/interrupts.h>
#include <asm/arch/timer.h>
#if defined(CONFIG_USE_IRQ) && defined(CONFIG_SUN4I)
static struct _irq_handler IRQ_HANDLER[N_IRQS];
static void default_isr( void *data) {
printf ("default_isr(): called for IRQ %d\n", (int)data);
}
static void timer_isr( void *data) {
unsigned int *pTime = (unsigned int *)data;
unsigned int val;
(*pTime)++;
status_led_tick ((unsigned long)(*pTime));
#if defined(TIMER_DEBUG)
printf("Timer_isr running.\n");
#endif
val = readl(SUNXI_TIMER_BASE + 0x04);
val |= (0x1 << 0);
writel( val, SUNXI_TIMER_BASE + 0x04); /* clear timer0 irq pending */
}
void do_irq (struct pt_regs *pt_regs)
{
unsigned int pending;
unsigned int vector;
unsigned int int_source;
vector = readl( INTC_VECTOR_REG);
if( (int_source = (vector >> 2)) < N_IRQS )
{
switch(int_source >> 5 ){
case 0:
pending = INTC_IRQ_PEND_REG0;
break;
case 1:
pending = INTC_IRQ_PEND_REG1;
break;
case 2:
break;
pending = INTC_IRQ_PEND_REG2;
default:
pending = INTC_IRQ_PEND_REG0;
break;
}
if( (readl(pending) >> ( int_source % 32 )) & 0x01 ){
IRQ_HANDLER[int_source].m_func( IRQ_HANDLER[int_source].m_data);
/* clear pending interrupt */
writel( ~(1 << (int_source % 32)), pending);
}
}else{
default_isr(&vector);
}
}
static ulong timestamp;
static ulong lastdec;
int arch_interrupt_init (void)
{
int i;
/* install default interrupt handlers */
for ( i = 0; i < N_IRQS; i++) {
IRQ_HANDLER[i].m_data = (void *)i;
IRQ_HANDLER[i].m_func = default_isr;
}
/* configure interrupts for IRQ mode */
writel( 0x0, INTC_IRQ_TYPE_SEL0);
writel( 0x0, INTC_IRQ_TYPE_SEL1);
writel( 0x0, INTC_IRQ_TYPE_SEL2);
/* clear any pending interrupts */
writel( 0x0, INTC_IRQ_PEND_REG0);
writel( 0x0, INTC_IRQ_PEND_REG1);
writel( 0x0, INTC_IRQ_PEND_REG2);
writel( 0x0, INTC_FIQ_PEND_REG0);
writel( 0x0, INTC_FIQ_PEND_REG1);
writel( 0x0, INTC_FIQ_PEND_REG2);
lastdec = 0;
timestamp = 0;
/* install interrupt handler for timer */
IRQ_HANDLER[INT_TIM0].m_data = (void *)×tamp;
IRQ_HANDLER[INT_TIM0].m_func = timer_isr;
#if defined(TIMER_DEBUG)
printf("Interrupts initialize done.\n");
#endif
return 0;
}
#else
#error do_irq no define for this CPU type
#endif
增加\u-boot-sunxi-sunxi\arch\arm\include\asm\arch-sunxi\interrupts.h,用来定义A10的中断寄存器地址,中断源和中断数据结构。
/************************************/
/* A10 interrupt register list */
/************************************/
#define INTC_REG_BASE 0x01C20400
#define INTC_VECTOR_REG (INTC_REG_BASE + 0x0000)
#define INTC_BASE_ADDR_REG (INTC_REG_BASE + 0x0004)
#define INTC_INTCTRL_REG (INTC_REG_BASE + 0x000C)
#define INTC_IRQ_PEND_REG0 (INTC_REG_BASE + 0x0010)
#define INTC_IRQ_PEND_REG1 (INTC_REG_BASE + 0x0014)
#define INTC_IRQ_PEND_REG2 (INTC_REG_BASE + 0x0018)
#define INTC_FIQ_PEND_REG0 (INTC_REG_BASE + 0x0020)
#define INTC_FIQ_PEND_REG1 (INTC_REG_BASE + 0x0024)
#define INTC_FIQ_PEND_REG2 (INTC_REG_BASE + 0x0028)
#define INTC_IRQ_TYPE_SEL0 (INTC_REG_BASE + 0x0030)
#define INTC_IRQ_TYPE_SEL1 (INTC_REG_BASE + 0x0034)
#define INTC_IRQ_TYPE_SEL2 (INTC_REG_BASE + 0x0038)
#define INTC_EN_REG0 (INTC_REG_BASE + 0x0040)
#define INTC_EN_REG1 (INTC_REG_BASE + 0x0044)
#define INTC_EN_REG2 (INTC_REG_BASE + 0x0048)
#define INTC_MASK_REG0 (INTC_REG_BASE + 0x0050)
#define INTC_MASK_REG1 (INTC_REG_BASE + 0x0054)
#define INTC_MASK_REG2 (INTC_REG_BASE + 0x0058)
#define INTC_RESP_REG0 (INTC_REG_BASE + 0x0060)
#define INTC_RESP_REG1 (INTC_REG_BASE + 0x0064)
#define INTC_RESP_REG2 (INTC_REG_BASE + 0x0068)
#define INTC_FF_REG0 (INTC_REG_BASE + 0x0070)
#define INTC_FF_REG1 (INTC_REG_BASE + 0x0074)
#define INTC_FF_REG2 (INTC_REG_BASE + 0x0078)
#define INTC_PRIO_REG0 (INTC_REG_BASE + 0x0080)
#define INTC_PRIO_REG1 (INTC_REG_BASE + 0x0084)
#define INTC_PRIO_REG2 (INTC_REG_BASE + 0x0088)
#define INTC_PRIO_REG3 (INTC_REG_BASE + 0x008C)
#define INTC_PRIO_REG4 (INTC_REG_BASE + 0x0090)
/************************************/
/* A10 interrupt source */
/************************************/
#define INT_EXTNMI 0
#define INT_UART0 1
#define INT_UART1 2
#define INT_UART2 3
#define INT_UART3 4
#define INT_IR0 5
#define INT_IR1 6
#define INT_TWI0 7
#define INT_TWI1 8
#define INT_TWI2 9
#define INT_SPI0 10
#define INT_SPI1 11
#define INT_SPI2 12
#define INT_NC 13
#define INT_AC97 14
#define INT_TS 15
#define INT_IIS 16
#define INT_UART4 17
#define INT_UART5 18
#define INT_UART6 19
#define INT_UART7 20
#define INT_KEYPAD 21
#define INT_TIM0 22
#define INT_TIM1 23
#define INT_TIM2_ALARM_WD 24
#define INT_TIM3 25
#define INT_CAN 26
#define INT_DMA 27
#define INT_PIO 28
#define INT_TOUCHPANEL 29
#define INT_AUDIOCODEC 30
#define INT_LRADC 31
#define INT_SD_MMC0 32
#define INT_SD_MMC1 33
#define INT_SD_MMC2 34
#define INT_SD_MMC3 35
#define INT_RSVD1 36
#define INT_NAND 37
#define INT_USB0 38
#define INT_USB1 39
#define INT_USB2 40
#define INT_SCR 41
#define INT_CSI0 42
#define INT_CSI1 43
#define INT_LCD_CON0 44
#define INT_LCD_CON1 45
#define INT_MP 46
#define INT_DE_FE0_BE0 47
#define INT_DE_FE1_BE1 48
#define INT_PMU 49
#define INT_SPI3 50
#define INT_TZASC 51
#define INT_PATA 52
#define INT_VE 53
#define INT_SS 54
#define INT_EMAC 55
#define INT_RSVD2 56
#define INT_RSVD3 57
#define INT_HDMI 58
#define INT_TVE0_1 59
#define INT_ACE 60
#define INT_TVD 61
#define INT_PS2_0 62
#define INT_PS2_1 63
#define INT_USB3 64
#define INT_USB4 65
#define INT_PLE_PREFMU 66
#define INT_TIM4 67
#define INT_TIM5 68
#define INT_GPU_GP 69
#define INT_GPU_GPMMU 70
#define INT_GPU_PP0 71
#define INT_GPU_PPMMU0 72
#define INT_GPU_PMU 73
#define INT_GPU_RSV0 74
#define INT_GPU_RSV1 75
#define INT_GPU_RSV2 76
#define INT_GPU_RSV3 77
#define INT_GPU_RSV4 78
#define INT_GPU_RSV5 79
#define INT_GPU_RSV6 80
#define N_IRQS (80)
#ifndef __ASSEMBLER__
struct _irq_handler {
void *m_data;
void (*m_func)( void *data);
};
#endif
2、了解调用关系,使编译之后的代码能正常运行
只要timmer0初始化和中断初始化正常,那么就可以产生timer0中断。
那么timer_init被谁调用了呢?工程中搜索timer_init,结果很多,逐个观察,可以肯定初始化在board.c里面,A10为ARM,所以一定在ARM平台的代码内:
Board.c (d:\share\u-boot-sunxi-sunxi\arch\arm\lib):
timer_init,
/* initialize timer */
阅读上下文,可以看到,在函数board_init_f内通过一个转移表init_sequence调用了timer_init。
再来看看,中断初始化在哪调用的,搜索interrupt_init,跟timer_init在同一个文件里
Board.c (d:\share\u-boot-sunxi-sunxi\arch\arm\lib):
interrupt_init();
这个interrupt_init就是这篇文章开始收搜索的函数,在文件:Interrupts.c (d:\share\u-boot-sunxi-sunxi\arch\arm\lib):int interrupt_init (void)
它调用了arch_interrupt_init函数,不用看,这就在我们自己写的文件\u-boot-sunxi-sunxi\arch\arm\cpu\armv7\sunxi\interrupts.c里面
确定调用完整。
3、写到SD卡内,测试之。
中断周期太大,采用24M的外部时钟,而CONFIG_SYS_HZ的值为1000,即中断1000次为1秒钟,每次中断为1ms,因此timer0的重载值为 24M/1000
修改:
#define TIMER_LOAD_VAL TICKS_PER_HZ
这个宏之后,可以获得正确的CONFIG_SYS_HZ时间值
设置CONFIG_SYS_HZ时,LED的闪烁可以做到1秒一次