鴻蒙 移植 樹莓派(下)修改原始碼

HarmonyOS技術社群發表於2021-01-22

目錄:

1、切換啟動模式

2、修改串列埠驅動

3、系統時鐘初始化

4、原始碼下載地址

 

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

相關文章