目錄:
1、切換啟動模式
樹莓派默認啟動在HYP模式,我們需要在內核啟動前改為SVC模式
kernel\liteos_a\arch\arm\arm\src\startup\reset_vector_up.S 在115行左右,reset_vector:下面添加
mrs r0,cpsr //讀取CPU模式寄存器 bic r0,r0,#0x1F //清除CPU模式位(如果處於催眠模式,它將是1A)保留所有其他 orr r0,r0,#0x13 //設置CPU_MODE為SVC_MODE (0x13),而ORR仍然保留所有其他位 msr spsr_cxsf,r0 //將其寫入spsr_cxsf寄存器,以便在調用交換機時加載該寄存器。 add r0,pc,#4 //從pc計算要進入SVC_MODE的地址(后面的兩個操作碼很長) msr ELR_hyp,r0 //將地址值寫入ELR_hyp寄存器 eret //執行了回車指令
2、修改串口驅動
2.1、為了方便調試,先設置一個字符打印函數
kernel\liteos_a\platform\uart\amba_pl011\amba_pl011.c在46行左右處添加下面的代碼,uart_putc_phy使用物理地址打印字符,uart_putc_virt使用虛擬地址打印。當內核代碼啟動MMU之后,需用使用uart_putc_virt來打印字符。
/*---------自定義函數----------*/ #define RPI_BASE_UART_REGISTER (0x3f201000) //HI3516:0x120A0000 rpi2:0x3F201000 #define AMBA_UART_DR (*(volatile unsigned char *)(RPI_BASE_UART_REGISTER + 0x00)) #define AMBA_UART_FR (*(volatile unsigned char *)(RPI_BASE_UART_REGISTER + 0x18)) #define RPI_BASE_UART_REGISTER1 IO_DEVICE_ADDR(0x3F201000) //HI3516:0x120A0000 rpi2:0x3F201000 #define AMBA_UART_DR1 (*(volatile unsigned char *)(RPI_BASE_UART_REGISTER1 + 0x00)) #define AMBA_UART_FR1 (*(volatile unsigned char *)(RPI_BASE_UART_REGISTER1 + 0x18)) /*---------------------------*/ void uart_putc_phy(unsigned char c) { //UART_Type *uartRegs = (UART_Type *)UART4_REG_PBASE; //while ((uartRegs->USART_ISR & (1<<5)) == 0); //uartRegs->USART_TDR = c; while (AMBA_UART_FR & (1 << 5)); AMBA_UART_DR = c; } void uart_putc_virt(unsigned char c) { //UART_Type *uartRegs = (UART_Type *)UART_REG_BASE; //while ((uartRegs->USART_ISR & (1<<5)) == 0); //uartRegs->USART_TDR = c; while (AMBA_UART_FR1 & (1 << 5)); AMBA_UART_DR1 = c; }
例如:kernel\liteos_a\arch\arm\arm\src\startup\reset_vector_up.S
ldr sp, =0x00000000 + 0x5000000 //調用C函數前,得先設置棧,樹莓派物理內存從0x0開始 mov r0, #'m' bl uart_putc_phy //在MMU啟動之前使用的是物理地址打印 bl mmu_setup /* set up the mmu */ mov r0, #'M' bl uart_putc_virt //在MMU啟動之后使用的是虛擬地址打印
2.2、添加串口中斷,串口輸入代碼
vendor\broadcom\BCM2836\driver\uart\uart_hardware.c
2.2.1、 串口的中斷函數,產生中斷時,這個函數調用
static irqreturn_t BCM2836_uart_irq(int irq, void *data) { char buf[FIFO_SIZE]; unsigned int count = 0; struct BCM2836_port *port = NULL; struct uart_driver_data *udd = (struct uart_driver_data *)data; UART_Type *uartRegs; uint32_t status; if (udd == NULL) { uart_error("udd is null!\n"); return IRQ_HANDLED; } port = (struct BCM2836_port *)udd->private; uartRegs = (UART_Type *)port->phys_base; READ_UINT32(status, UART_REG_BASE + UART_FR); if ((UARTREG(UART_REG_BASE,UART_FR)&(1<<4)) == 0) { do { buf[count++] = UARTREG(UART_REG_BASE,UART_DR);//*(volatile UINT32 *)((UINTPTR)(UART_REG_BASE + UART_DR)); //去讀取硬件得到數據 if (udd->num != CONSOLE_UART) { continue; } if (CheckMagicKey(buf[count - 1])) { //數據放在buf里 goto end; } if (buf[count-1] == '\r') //對windows和liteos回車換行的處理 buf[count-1] = '\n'; } while (UARTREG(UART_REG_BASE,UART_DR)); udd->recv(udd, buf, count); //調用udd里的recv函數把數據發送給上一級 } UARTREG(UART_REG_BASE, UART_ICR) = 0x3ff; end: /* clear all interrupt */ return 0; }
2.2.2、串口的初始化函數
static int BCM2836_startup(struct uart_driver_data *udd) { int ret = 0; struct BCM2836_port *port = NULL; if (udd == NULL) { uart_error("udd is null!\n"); return -EFAULT; } port = (struct BCM2836_port *)udd->private;//*private是一個指針,指向 struct {enable,phys_base,irq_num,*udd} if (!port) { uart_error("port is null!"); return -EFAULT; } /* enable the clock */ LOS_TaskLock(); LOS_TaskUnlock(); ret = request_irq(port->irq_num, (irq_handler_t)BCM2836_uart_irq,0, "uart_dw", udd); //去注冊一個串口的接收中斷函數 /* 1.uart interrupt priority should be the highest in interrupt preemption mode */ //ret = LOS_HwiCreate(NUM_HAL_INTERRUPT_UART, 0, 0, (HWI_PROC_FUNC)uart_handler, NULL); /* 2.clear all irqs */ UARTREG(UART_REG_BASE, UART_ICR) = 0x3ff; //*(volatile UINT32 *)((UINTPTR)IO_DEVICE_ADDR(0x3F201044)) = 0x3ff; /* disable FIFO mode */ //uartRegs->USART_CR1 &= ~(1<<29); //*(volatile UINT32 *)((UINTPTR)IO_DEVICE_ADDR(0x3F20102C)) = 0x60; UARTREG(UART_REG_BASE, UART_LCR_H) = (1 << 6 | 1 << 5| 1 << 4); /* 3.set fifo trigger level */ //*(volatile UINT32 *)((UINTPTR)IO_DEVICE_ADDR(0x3F201034)) = 0x0; UARTREG(UART_REG_BASE, UART_IFLS) = 0; /* 4.enable rx interrupt 開啟串口接收中斷,第4位*/ UARTREG(UART_REG_BASE, UART_IMSC) = (1 << 4 | 1 << 6); //*(volatile UINT32 *)((UINTPTR)IO_DEVICE_ADDR(0x3F201038)) = 0x10; /* 5.enable receive */ UARTREG(UART_REG_BASE, UART_CR) |= (1 << 9); //*(volatile UINT32 *)((UINTPTR)IO_DEVICE_ADDR(0x3F201030)) = 0x301; //HalIrqUnmask(NUM_HAL_INTERRUPT_UART);//6. *(volatile UINT32 *)((UINTPTR)IO_DEVICE_ADDR(0x3F00B214)) = 0x02000000;//Unmask接收25號中斷 BCM2836_config_in(udd); return ret; }
2.2.3、串口寫函數
static int BCM2836_start_tx(struct uart_driver_data *udd, const char *buf, size_t count) { unsigned int tx_len = count; struct BCM2836_port *port = NULL; char value; unsigned int i; int ret = 0; if (udd == NULL) { uart_error("udd is null!\n"); return -EFAULT; } port = (struct BCM2836_port *)udd->private; if (!port) { uart_error("port is null!"); return -EFAULT; } /* UART_WITH_LOCK: there is a spinlock in the function to write reg in order. */ for (i = 0; i < tx_len; i++ ){ ret = LOS_CopyToKernel((void *)&value, sizeof(char),(void *)(buf++), sizeof(char)); if (ret) { return i; } (void)UartPutsReg(port->phys_base, &value, 1, UART_WITH_LOCK); } return count; }
3、系統時鍾初始化
3.1、main函數的各種調用,驗證參數
kernel\liteos_a\platform\main.c->main()
kernel\liteos_a\kernel\common\los_config.c->OsMain()
kernel\liteos_a\arch\arm\arm\src\los_hw_tick.c->OsTickInit()
systemClock //vendor里設置的是50000000 tickPerSecond //鴻蒙默認設置的是100 LITE_OS_SEC_TEXT_INIT UINT32 OsTickInit(UINT32 systemClock, UINT32 tickPerSecond) { //只是驗證了下傳入的這兩個參數,並未使用 HalClockInit(); return LOS_OK; }
3.2、先獲取當前時鍾頻率,注冊中斷
kernel\liteos_a\platform\hw\arm\timer\arm_generic\arm_generic_timer.c
OS_TICK_INT_NUM//中斷號,在vendor\***\***\board\include\asm\hal_platform_ints.h下定義,查手冊確定 MIN_INTERRUPT_PRIORITY//優先級 OsTickEntry//中斷函數 LITE_OS_SEC_TEXT_INIT VOID HalClockInit(VOID) { ... g_sysClock = HalClockFreqRead(); //先獲取當前時鍾頻率 //調用LOS_HwiCreate函數新建中斷,系統中斷由它注冊 ret = LOS_HwiCreate(OS_TICK_INT_NUM, MIN_INTERRUPT_PRIORITY, 0, OsTickEntry, 0);//參數1:中斷號、參數4:執行函數 //這個函數就不深入了,大體就是將中斷號好和對應的執行函數放到一個數組 //比如這里就是,當發生OS_TICK_INT_NUM這個中斷時,執行OsTickEntry()函數 ... }
3.3、時鍾中斷的執行函數OsTickEntry()
kernel\liteos_a\platform\hw\arm\timer\arm_generic\arm_generic_timer.c
不過此時這是注冊了這個函數,時鍾並未啟動,得執行了(三.啟動時鍾)之后才會調用這個函數
LITE_OS_SEC_TEXT VOID OsTickEntry(VOID) { TimerCtlWrite(0); OsTickHandler(); TimerCvalWrite(TimerCvalRead() + OS_CYCLE_PER_TICK); TimerCtlWrite(1); //使用最后一個cval生成下一個tick的時間是絕對和准確的。不要使用tval來驅動一般時間,在這種情況下tick會變慢。 }
3.3、啟動時鍾
main() => OsStart(VOID) => OsTickStart() => HalClockStart(VOID)
kernel\liteos_a\platform\hw\arm\timer\arm_generic\arm_generic_timer.c => HalClockStart(VOID)
//樹莓派2沒有GIC所以這個函數要爆改 LITE_OS_SEC_TEXT_INIT VOID HalClockStart(VOID) { HalIrqUnmask(OS_TICK_INT_NUM); //wendor里定義的 OS_TICK_INT_NUM = 29 TimerCtlWrite(0); TimerTvalWrite(OS_CYCLE_PER_TICK); TimerCtlWrite(1); }
3.3.1、HalIrqUnmask; //接收中斷(通過設置寄存器,允許CPU響應該中斷)
HalIrqUnmask(OS_TICK_INT_NUM); HalIrqUnmask(29); GIC_REG_32(GICD_ISENABLER(29 >> 5)) = 1U << (29 % 32); (GICD_ISENABLER(29 >> 5))拆開 GIC_REG_32(GICD_OFFSET + 0x100 + (29 >> 5) * 4) = 1U << (29 % 32);/* 中斷使能 Registers */ GIC_REG_32拆開,(29 % 32)=1D GIC_BASE_ADDR + (GICD_OFFSET + 0x100 + (29 >> 5) * 4) = 1U << (29 % 32) #define GIC_BASE_ADDR IO_DEVICE_ADDR(0x3F00A100) #define GICD_OFFSET 0x1000 /* interrupt distributor offset */
3.3.2、TimerCtlWrite(0); //關閉Timer
參考:ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf
《B3.17 Organization of the CP15 registers in a VMSA implementation》
WRITE_TIMER_REG32(TIMER_REG_CTL, 0); ARM_SYSREG_WRITE(TIMER_REG_CTL, 0) ARM_SYSREG_WRITE(TIMER_REG(_CTL), 0) ARM_SYSREG_WRITE(CP15_REG(c14, 0, c2, 1)), 0) "mcr " (CP15_REG(c14, 0, c2, 1) :: "r" (val) 反匯編 r8 0 mcr p15, #0, r8, c14, c2, #1 CNTP_CTL,PL1物理定時器控制寄存器
3.3.3、TimerTvalWrite(OS_CYCLE_PER_TICK); //設置Tval
反匯編 r0 192000 mcr p15, #0, r0, c14, c2, #0 CNTP_TVAL,PL1物理時間值寄存器
3.3.4、TimerCtlWrite(1); //再開啟Timer
反匯編 r5 1 mcr p15, #0, r5, c14, c2, #1 CNTP_CTL,PL1物理定時器控制寄存器
3.4、代碼移植
Z:\bright\harmony-100ask\kernel\liteos_a\platform\hw\arm\interrupt\gic\gic_v2.c
VOID HalIrqUnmask(UINT32 vector) { if ((vector > OS_USER_HWI_MAX) || (vector < OS_USER_HWI_MIN)) { return; } //GIC_REG_32(GICD_ISENABLER(vector >> 5)) = 1U << (vector % 32); //替換 *(volatile UINT32 *)((UINTPTR)IO_DEVICE_ADDR(0x3F00B218)) = 1; //使能ARM Timer IRQ }
Z:\bright\harmony-100ask\kernel\liteos_a\platform\hw\arm\timer\arm_generic\arm_generic_timer.c
STATIC_INLINE VOID TimerCtlWrite(UINT32 cntpCtl) { //WRITE_TIMER_REG32(TIMER_REG_CTL, cntpCtl);//替換 if(cntpCtl == 0){ *(volatile UINT32 *)((UINTPTR)IO_DEVICE_ADDR(0x3F00B408)) = 0x003E0000; } else { *(volatile UINT32 *)((UINTPTR)IO_DEVICE_ADDR(0x3F00B408)) = 0x003E00A2; } } STATIC_INLINE VOID TimerTvalWrite(UINT32 tval) { //WRITE_TIMER_REG32(TIMER_REG_TVAL, tval);//替換 *(volatile UINT32 *)((UINTPTR)IO_DEVICE_ADDR(0x3F00B400)) = tval; //設置倒計時時間,鴻蒙是10ms }
作者: 亮子力
想了解更多內容,請訪問: 51CTO和華為官方戰略合作共建的鴻蒙技術社區https://harmonyos.51cto.com
