Opensbi初始化分析:设备初始化

Opensbi初始化分析:设备初始化

    • 设备初始化
      • sbi_init函数
        • coldinit,冷启动初始化
        • sbi_scratch_init函数
        • sbi_domain_init函数
        • sbi_hsm_init
        • sbi_platform_early_init
        • sbi_hart_init
        • sbi_console_init
        • sbi_platform_irqchip_init中断控制器的初始化
        • sbi_ipi_init函数
        • sbi_tlb_init函数
          • sbi_timer_init函数
        • sbi_ecall_init函数
        • sbi_domain_finalize
        • sbi_hart_pmp_configure函数
        • sbi_platform_final_init
        • wake_coldboot_harts函数
        • sbi_hsm_prepare_next_jump函数
        • sbi_hart_switch_mode
      • 参考

设备初始化

紧接_start_warm,接下来将正式进入C阶段,调用到sbi_init,这里也会在终端出经典的Opensbi界面。

sbi_init函数

这个函数传入的参数为sbi_scratch结构体,也就是在前面的汇编阶段初始化的scratch空间,CSR_MSCRATCH存储着起始地址。

  • 首先需要判断启动模式
  • 随机选择满足条件的hart执行clodboot,其余的hart执行warmboot
    注意:Opensbi中的coolboot和warmboot不是传统意义上的冷启动和热启动,而是完全初始化和部分初始化的意思
/*** Initialize OpenSBI library for current HART and jump to next* booting stage.** The function expects following:* 1. The 'mscratch' CSR is pointing to sbi_scratch of current HART* 2. Stack pointer (SP) is setup for current HART* 3. Interrupts are disabled in MSTATUS CSR* 4. All interrupts are disabled in MIE CSR** @param scratch pointer to sbi_scratch of current HART*/
void __noreturn sbi_init(struct sbi_scratch *scratch)
{bool next_mode_supported	= FALSE;bool coldboot			= FALSE;u32 hartid			= current_hartid();const struct sbi_platform *plat = sbi_platform_ptr(scratch);if ((SBI_HARTMASK_MAX_BITS <= hartid) ||sbi_platform_hart_invalid(plat, hartid))sbi_hart_hang();switch (scratch->next_mode) {case PRV_M:next_mode_supported = TRUE;break;case PRV_S:if (misa_extension('S'))next_mode_supported = TRUE;break;case PRV_U:if (misa_extension('U'))next_mode_supported = TRUE;break;default:sbi_hart_hang();}/** Only the HART supporting privilege mode specified in the* scratch->next_mode should be allowed to become the coldboot* HART because the coldboot HART will be directly jumping to* the next booting stage.** We use a lottery mechanism to select coldboot HART among* HARTs which satisfy above condition.*/if (next_mode_supported && atomic_xchg(&coldboot_lottery, 1) == 0)coldboot = TRUE;if (coldboot)init_coldboot(scratch, hartid);elseinit_warmboot(scratch, hartid);
}
coldinit,冷启动初始化

这个函数执行的是冷启动初始化:传入的参数为scratch结构体和hartid,主要做了下面几件事:

  • 初始化scratch空间
  • 初始化domain加载的镜像模块
  • hsm\paltfrom早期\hart\console\中断控制器\核间中断\初始化
  • MMU的tlb初始化
  • timer初始化
  • ecall初始化:注册系统调用
  • 发出软中断,唤醒其他warmboot的核
  • 准备下一级的bootloader
static void __noreturn init_coldboot(struct sbi_scratch *scratch, u32 hartid)
{int rc;unsigned long *init_count;const struct sbi_platform *plat = sbi_platform_ptr(scratch);/* Note: This has to be first thing in coldboot init sequence */rc = sbi_scratch_init(scratch);if (rc)sbi_hart_hang();/* Note: This has to be second thing in coldboot init sequence */rc = sbi_domain_init(scratch, hartid);if (rc)sbi_hart_hang();init_count_offset = sbi_scratch_alloc_offset(__SIZEOF_POINTER__,"INIT_COUNT");if (!init_count_offset)sbi_hart_hang();rc = sbi_hsm_init(scratch, hartid, TRUE);if (rc)sbi_hart_hang();rc = sbi_platform_early_init(plat, TRUE);if (rc)sbi_hart_hang();rc = sbi_hart_init(scratch, TRUE);if (rc)sbi_hart_hang();rc = sbi_console_init(scratch);if (rc)sbi_hart_hang();sbi_boot_print_banner(scratch);rc = sbi_platform_irqchip_init(plat, TRUE);if (rc) {sbi_printf("%s: platform irqchip init failed (error %d)\n",__func__, rc);sbi_hart_hang();}rc = sbi_ipi_init(scratch, TRUE);if (rc) {sbi_printf("%s: ipi init failed (error %d)\n", __func__, rc);sbi_hart_hang();}rc = sbi_tlb_init(scratch, TRUE);if (rc) {sbi_printf("%s: tlb init failed (error %d)\n", __func__, rc);sbi_hart_hang();}rc = sbi_timer_init(scratch, TRUE);if (rc) {sbi_printf("%s: timer init failed (error %d)\n", __func__, rc);sbi_hart_hang();}rc = sbi_ecall_init();if (rc) {sbi_printf("%s: ecall init failed (error %d)\n", __func__, rc);sbi_hart_hang();}sbi_boot_print_general(scratch);/** Note: Finalize domains after HSM initialization so that we* can startup non-root domains.* Note: Finalize domains before HART PMP configuration so* that we use correct domain for configuring PMP.*/rc = sbi_domain_finalize(scratch, hartid);if (rc) {sbi_printf("%s: domain finalize failed (error %d)\n",__func__, rc);sbi_hart_hang();}sbi_boot_print_domains(scratch);rc = sbi_hart_pmp_configure(scratch);if (rc) {sbi_printf("%s: PMP configure failed (error %d)\n",__func__, rc);sbi_hart_hang();}/** Note: Platform final initialization should be last so that* it sees correct domain assignment and PMP configuration.*/rc = sbi_platform_final_init(plat, TRUE);if (rc) {sbi_printf("%s: platform final init failed (error %d)\n",__func__, rc);sbi_hart_hang();}sbi_boot_print_hart(scratch, hartid);wake_coldboot_harts(scratch, hartid);init_count = sbi_scratch_offset_ptr(scratch, init_count_offset);(*init_count)++;sbi_hsm_prepare_next_jump(scratch, hartid);sbi_hart_switch_mode(hartid, scratch->next_arg1, scratch->next_addr,scratch->next_mode, FALSE);
}
sbi_scratch_init函数

在这里插入图片描述

这个函数的主要作用:根据hart id和scratch结构的映射关系,初始化hartid_to_scratch_table这个数组。值得注意的是它记住了最后一个hart。

int sbi_scratch_init(struct sbi_scratch *scratch)
{u32 i;const struct sbi_platform *plat = sbi_platform_ptr(scratch);for (i = 0; i < SBI_HARTMASK_MAX_BITS; i++) {if (sbi_platform_hart_invalid(plat, i))continue;hartid_to_scratch_table[i] =((hartid2scratch)scratch->hartid_to_scratch)(i,sbi_platform_hart_index(plat, i));if (hartid_to_scratch_table[i])last_hartid_having_scratch = i;}return 0;
}
sbi_domain_init函数

这个函数本质是初始化动态加载的镜像模块,首先会初始化一个ROOT的内存域,最后会将root域注册到opensbi的西欧统中

int sbi_domain_init(struct sbi_scratch *scratch, u32 cold_hartid)
{u32 i;struct sbi_domain_memregion *memregs;const struct sbi_platform *plat = sbi_platform_ptr(scratch);/* Root domain firmware memory region */root_memregs[ROOT_FW_REGION].order = log2roundup(scratch->fw_size);root_memregs[ROOT_FW_REGION].base = scratch->fw_start &~((1UL << root_memregs[0].order) - 1UL);root_memregs[ROOT_FW_REGION].flags = 0;/* Root domain allow everything memory region */root_memregs[ROOT_ALL_REGION].order = __riscv_xlen;root_memregs[ROOT_ALL_REGION].base = 0;root_memregs[ROOT_ALL_REGION].flags = (SBI_DOMAIN_MEMREGION_READABLE |SBI_DOMAIN_MEMREGION_WRITEABLE |SBI_DOMAIN_MEMREGION_EXECUTABLE);/* Root domain memory region end */root_memregs[ROOT_END_REGION].order = 0;/* Use platform specific root memory regions when available */memregs = sbi_platform_domains_root_regions(plat);if (memregs)root.regions = memregs;/* Root domain boot HART id is same as coldboot HART id */root.boot_hartid = cold_hartid;/* Root domain next booting stage details */root.next_arg1 = scratch->next_arg1;root.next_addr = scratch->next_addr;root.next_mode = scratch->next_mode;/* Root domain possible and assigned HARTs */for (i = 0; i < SBI_HARTMASK_MAX_BITS; i++) {if (sbi_platform_hart_invalid(plat, i))continue;sbi_hartmask_set_hart(i, &root_hmask);}return sbi_domain_register(&root, &root_hmask);
}
sbi_hsm_init

这个函数主要作用就是初始化hsm状态,以处理安全性相关问题(加密、解密、签名、认证),在opensbi中hsm的状态要对所有的hart共享,所以需要初始化每个hart。
因为HSM启动可能需要一段时间,当hart需要等待其他hart都启动,才能开始安全启动想相关操作。

int sbi_hsm_init(struct sbi_scratch *scratch, u32 hartid, bool cold_boot)
{u32 i;struct sbi_scratch *rscratch;struct sbi_hsm_data *hdata;if (cold_boot) {hart_data_offset = sbi_scratch_alloc_offset(sizeof(*hdata),"HART_DATA");if (!hart_data_offset)return SBI_ENOMEM;/* Initialize hart state data for every hart */for (i = 0; i <= sbi_scratch_last_hartid(); i++) {rscratch = sbi_hartid_to_scratch(i);if (!rscratch)continue;hdata = sbi_scratch_offset_ptr(rscratch,hart_data_offset);ATOMIC_INIT(&hdata->state,(i == hartid) ? SBI_HART_STARTING : SBI_HART_STOPPED);}} else {sbi_hsm_hart_wait(scratch, hartid);}return 0;
}
sbi_platform_early_init

这个是平台的一些早期初始化操作,这里是一贯的面向的对象的思想,根据不用的platfrom执行其对应的early_init函数,当平台为generic时,会调用ops中的.early_init指向的eneric_early_init。

/*** Early initialization for current HART** @param plat pointer to struct sbi_platform* @param cold_boot whether cold boot (TRUE) or warm_boot (FALSE)** @return 0 on success and negative error code on failure*/
static inline int sbi_platform_early_init(const struct sbi_platform *plat,bool cold_boot)
{if (plat && sbi_platform_ops(plat)->early_init)return sbi_platform_ops(plat)->early_init(cold_boot);return 0;
}
sbi_hart_init

主要作用是初始化当前的hart:

  • 检测当前hart的feature
  • 初始化状态寄存器
  • 初始化浮点寄存器,需要根据isa
  • 将中断、异常委托给S模式进行处理
int sbi_hart_init(struct sbi_scratch *scratch, bool cold_boot)
{int rc;if (cold_boot) {if (misa_extension('H'))sbi_hart_expected_trap = &__sbi_expected_trap_hext;hart_features_offset = sbi_scratch_alloc_offset(sizeof(struct hart_features),"HART_FEATURES");if (!hart_features_offset)return SBI_ENOMEM;}hart_detect_features(scratch);mstatus_init(scratch);rc = fp_init(scratch);if (rc)return rc;rc = delegate_traps(scratch);if (rc)return rc;return 0;
}

在上述代码中,可以看到冷启动hart初始化了hart_featuress结构体,定义如下:

struct hart_features {unsigned long features;unsigned int pmp_count;unsigned int pmp_addr_bits;unsigned long pmp_gran;unsigned int mhpm_count;
};

这里分析下delegate_traps函数:
正如字面意思委托,它会将M模式的中断和异常委托给S模式,并写入两个状态寄存器CSR_MIDELEG和CSR_MEDELEG。

static int delegate_traps(struct sbi_scratch *scratch)
{const struct sbi_platform *plat = sbi_platform_ptr(scratch);unsigned long interrupts, exceptions;if (!misa_extension('S'))/* No delegation possible as mideleg does not exist */return 0;/* Send M-mode interrupts and most exceptions to S-mode */interrupts = MIP_SSIP | MIP_STIP | MIP_SEIP;exceptions = (1U << CAUSE_MISALIGNED_FETCH) | (1U << CAUSE_BREAKPOINT) |(1U << CAUSE_USER_ECALL);if (sbi_platform_has_mfaults_delegation(plat))exceptions |= (1U << CAUSE_FETCH_PAGE_FAULT) |(1U << CAUSE_LOAD_PAGE_FAULT) |(1U << CAUSE_STORE_PAGE_FAULT);/** If hypervisor extension available then we only handle hypervisor* calls (i.e. ecalls from HS-mode) in M-mode.** The HS-mode will additionally handle supervisor calls (i.e. ecalls* from VS-mode), Guest page faults and Virtual interrupts.*/if (misa_extension('H')) {exceptions |= (1U << CAUSE_VIRTUAL_SUPERVISOR_ECALL);exceptions |= (1U << CAUSE_FETCH_GUEST_PAGE_FAULT);exceptions |= (1U << CAUSE_LOAD_GUEST_PAGE_FAULT);exceptions |= (1U << CAUSE_VIRTUAL_INST_FAULT);exceptions |= (1U << CAUSE_STORE_GUEST_PAGE_FAULT);}csr_write(CSR_MIDELEG, interrupts);csr_write(CSR_MEDELEG, exceptions);return 0;
}
sbi_console_init

这个函数主要做的就是console的初始化,可以看出这部分是跟platfrom相关的。

int sbi_console_init(struct sbi_scratch *scratch)
{console_plat = sbi_platform_ptr(scratch);return sbi_platform_console_init(console_plat);
}/*** Initialize the platform console** @param plat pointer to struct sbi_platform** @return 0 on success and negative error code on failure*/
static inline int sbi_platform_console_init(const struct sbi_platform *plat)
{if (plat && sbi_platform_ops(plat)->console_init)return sbi_platform_ops(plat)->console_init();return 0;
}

console初始化完成后,会调用sbi_boot_print_banner函数,这样可以在控制台上看到相应的logo。

#define BANNER                                              \"   ____                    _____ ____ _____\n"     \"  / __ \\                  / ____|  _ \\_   _|\n"  \" | |  | |_ __   ___ _ __ | (___ | |_) || |\n"      \" | |  | | '_ \\ / _ \\ '_ \\ \\___ \\|  _ < | |\n" \" | |__| | |_) |  __/ | | |____) | |_) || |_\n"     \"  \\____/| .__/ \\___|_| |_|_____/|____/_____|\n"  \"        | |\n"                                     \"        |_|\n\n"static void sbi_boot_print_banner(struct sbi_scratch *scratch)
{if (scratch->options & SBI_SCRATCH_NO_BOOT_PRINTS)return;#ifdef OPENSBI_VERSION_GITsbi_printf("\nOpenSBI %s\n", OPENSBI_VERSION_GIT);
#elsesbi_printf("\nOpenSBI v%d.%d\n", OPENSBI_VERSION_MAJOR,OPENSBI_VERSION_MINOR);
#endifsbi_printf(BANNER);
}
sbi_platform_irqchip_init中断控制器的初始化

这部分同样跟platfrom相关,generic平台调用fdt_irqchip_init函数,作用为初始化中断控制器,这里一般会区分coldboot和warmboot。

/*** Initialize the platform interrupt controller for current HART** @param plat pointer to struct sbi_platform* @param cold_boot whether cold boot (TRUE) or warm_boot (FALSE)** @return 0 on success and negative error code on failure*/
static inline int sbi_platform_irqchip_init(const struct sbi_platform *plat,bool cold_boot)
{if (plat && sbi_platform_ops(plat)->irqchip_init)return sbi_platform_ops(plat)->irqchip_init(cold_boot);return 0;
}
sbi_ipi_init函数

这个函数的主要核间中断的初始化,主要做的事情如下:

  • 分配IPI的时间空间,保存偏移地址
  • 创建IPI事件,将事件的句柄保存在ipi_smode_event和ipi_halt_event
  • 初始化平台特有的IPI初始化操作
  • 启用软中断,将状态控制寄存器CDR_MIE的MIP_MSIP位置置一
int sbi_ipi_init(struct sbi_scratch *scratch, bool cold_boot)
{int ret;struct sbi_ipi_data *ipi_data;if (cold_boot) {ipi_data_off = sbi_scratch_alloc_offset(sizeof(*ipi_data),"IPI_DATA");if (!ipi_data_off)return SBI_ENOMEM;ret = sbi_ipi_event_create(&ipi_smode_ops);if (ret < 0)return ret;ipi_smode_event = ret;ret = sbi_ipi_event_create(&ipi_halt_ops);if (ret < 0)return ret;ipi_halt_event = ret;} else {if (!ipi_data_off)return SBI_ENOMEM;if (SBI_IPI_EVENT_MAX <= ipi_smode_event ||SBI_IPI_EVENT_MAX <= ipi_halt_event)return SBI_ENOSPC;}ipi_data = sbi_scratch_offset_ptr(scratch, ipi_data_off);ipi_data->ipi_type = 0x00;/* Platform init */ret = sbi_platform_ipi_init(sbi_platform_ptr(scratch), cold_boot);if (ret)return ret;/* Enable software interrupts */csr_set(CSR_MIE, MIP_MSIP);return 0;
}

sbi_platform_ipi_init函数:

/*** Initialize the platform IPI support for current HART** @param plat pointer to struct sbi_platform* @param cold_boot whether cold boot (TRUE) or warm_boot (FALSE)** @return 0 on success and negative error code on failure*/
static inline int sbi_platform_ipi_init(const struct sbi_platform *plat,bool cold_boot)
{if (plat && sbi_platform_ops(plat)->ipi_init)return sbi_platform_ops(plat)->ipi_init(cold_boot);return 0;
}
sbi_tlb_init函数

主要对TLB表进行初始化:一个sync变量、一个fifo队列还有一个TLB的刷新时间

int sbi_tlb_init(struct sbi_scratch *scratch, bool cold_boot)
{int ret;void *tlb_mem;unsigned long *tlb_sync;struct sbi_fifo *tlb_q;const struct sbi_platform *plat = sbi_platform_ptr(scratch);if (cold_boot) {tlb_sync_off = sbi_scratch_alloc_offset(sizeof(*tlb_sync),"IPI_TLB_SYNC");if (!tlb_sync_off)return SBI_ENOMEM;tlb_fifo_off = sbi_scratch_alloc_offset(sizeof(*tlb_q),"IPI_TLB_FIFO");if (!tlb_fifo_off) {sbi_scratch_free_offset(tlb_sync_off);return SBI_ENOMEM;}tlb_fifo_mem_off = sbi_scratch_alloc_offset(SBI_TLB_FIFO_NUM_ENTRIES * SBI_TLB_INFO_SIZE,"IPI_TLB_FIFO_MEM");if (!tlb_fifo_mem_off) {sbi_scratch_free_offset(tlb_fifo_off);sbi_scratch_free_offset(tlb_sync_off);return SBI_ENOMEM;}ret = sbi_ipi_event_create(&tlb_ops);if (ret < 0) {sbi_scratch_free_offset(tlb_fifo_mem_off);sbi_scratch_free_offset(tlb_fifo_off);sbi_scratch_free_offset(tlb_sync_off);return ret;}tlb_event = ret;tlb_range_flush_limit = sbi_platform_tlbr_flush_limit(plat);} else {if (!tlb_sync_off ||!tlb_fifo_off ||!tlb_fifo_mem_off)return SBI_ENOMEM;if (SBI_IPI_EVENT_MAX <= tlb_event)return SBI_ENOSPC;}tlb_sync = sbi_scratch_offset_ptr(scratch, tlb_sync_off);tlb_q = sbi_scratch_offset_ptr(scratch, tlb_fifo_off);tlb_mem = sbi_scratch_offset_ptr(scratch, tlb_fifo_mem_off);*tlb_sync = 0;sbi_fifo_init(tlb_q, tlb_mem,SBI_TLB_FIFO_NUM_ENTRIES, SBI_TLB_INFO_SIZE);return 0;
}

** 这个函数的工作机理:为TLB刷新创建一个tlb_event,当每次刷新TLB时,将刷新信息写入队列FIFO中,然后通过tlb_event去通知其他的hart刷新TLB。**
创建TLB刷新事件的句柄具体的数据结构和事件创建的函数如下:

static struct sbi_ipi_event_ops tlb_ops = {.name = "IPI_TLB",.update = sbi_tlb_update,.sync = sbi_tlb_sync,.process = sbi_tlb_process,
};//sbi_tlb_init函数
----------------------------------------------ret = sbi_ipi_event_create(&tlb_ops);if (ret < 0) {sbi_scratch_free_offset(tlb_fifo_mem_off);sbi_scratch_free_offset(tlb_fifo_off);sbi_scratch_free_offset(tlb_sync_off);return ret;}tlb_event = ret;
------------------------------------------------------int sbi_ipi_event_create(const struct sbi_ipi_event_ops *ops)
{int i, ret = SBI_ENOSPC;if (!ops || !ops->process)return SBI_EINVAL;for (i = 0; i < SBI_IPI_EVENT_MAX; i++) {if (!ipi_ops_array[i]) {ret = i;ipi_ops_array[i] = ops;break;}}return ret;
}

下面需要注意的是,TLB的刷新是需要被限制的,过多的刷新操作会导致额外的开销和负面影响,这个限制可能会跟platfrom有较大关系(有默认阈值)。

/*** Get platform specific tlb range flush maximum value. Any request with size* higher than this is upgraded to a full flush.** @param plat pointer to struct sbi_platform** @return tlb range flush limit value. Returns a default (page size) if not* defined by platform.*/
static inline u64 sbi_platform_tlbr_flush_limit(const struct sbi_platform *plat)
{if (plat && sbi_platform_ops(plat)->get_tlbr_flush_limit)return sbi_platform_ops(plat)->get_tlbr_flush_limit();return SBI_PLATFORM_TLB_RANGE_FLUSH_LIMIT_DEFAULT;
}#define SBI_PLATFORM_TLB_RANGE_FLUSH_LIMIT_DEFAULT		(1UL << 12)

紧接着是获取sync、fifo和fifo内存空间的指针,并且初始化fifo(每个元素条目个数和队列的最大长度等信息)

tlb_sync = sbi_scratch_offset_ptr(scratch, tlb_sync_off);tlb_q = sbi_scratch_offset_ptr(scratch, tlb_fifo_off);tlb_mem = sbi_scratch_offset_ptr(scratch, tlb_fifo_mem_off);*tlb_sync = 0;sbi_fifo_init(tlb_q, tlb_mem,SBI_TLB_FIFO_NUM_ENTRIES, SBI_TLB_INFO_SIZE);void sbi_fifo_init(struct sbi_fifo *fifo, void *queue_mem, u16 entries,u16 entry_size)
{fifo->queue	  = queue_mem;fifo->num_entries = entries;fifo->entry_size  = entry_size;SPIN_LOCK_INIT(&fifo->qlock);fifo->avail = fifo->tail = 0;sbi_memset(fifo->queue, 0, (size_t)entries * entry_size);
}              
sbi_timer_init函数

这个函数主要是针对timer的初始化:

  • 刷新time_delta_off变量,用于追踪当前haer上次根本更新定时器的时间戳
  • 初始化platfrom的始终中断
  • 获取时钟相关feature(目前看来无具体作用)
int sbi_timer_init(struct sbi_scratch *scratch, bool cold_boot)
{u64 *time_delta;const struct sbi_platform *plat = sbi_platform_ptr(scratch);int ret;if (cold_boot) {time_delta_off = sbi_scratch_alloc_offset(sizeof(*time_delta),"TIME_DELTA");if (!time_delta_off)return SBI_ENOMEM;} else {if (!time_delta_off)return SBI_ENOMEM;}time_delta = sbi_scratch_offset_ptr(scratch, time_delta_off);*time_delta = 0;ret = sbi_platform_timer_init(plat, cold_boot);if (ret)return ret;if (sbi_hart_has_feature(scratch, SBI_HART_HAS_TIME))get_time_val = get_ticks;else if (sbi_platform_has_timer_value(plat))get_time_val = sbi_platform_timer_value;else/* There is no method to provide timer value */return SBI_ENODEV;return 0;
}
sbi_ecall_init函数

系统调用的初始化函数,OpsenSBI的ecall机制会提供一种从S模式向M模式请求的方式;从下面的函数可以看出其支持一种扩展机制,OpenSBI的ecall机制还包括了一系列的扩展,通过扩展同样可以向应用程序提供不同的服务。
在V0.9中实现是写死的,后面的版本会提供一个数组便利,更利于维护。这里主要添加的ecall包括(time,rfence,ipi,base,hsm,srst,legacy,vendor)


int sbi_ecall_init(void)
{int ret;/* The order of below registrations is performance optimized */ret = sbi_ecall_register_extension(&ecall_time);if (ret)return ret;ret = sbi_ecall_register_extension(&ecall_rfence);if (ret)return ret;ret = sbi_ecall_register_extension(&ecall_ipi);if (ret)return ret;ret = sbi_ecall_register_extension(&ecall_base);if (ret)return ret;ret = sbi_ecall_register_extension(&ecall_hsm);if (ret)return ret;ret = sbi_ecall_register_extension(&ecall_srst);if (ret)return ret;ret = sbi_ecall_register_extension(&ecall_legacy);if (ret)return ret;ret = sbi_ecall_register_extension(&ecall_vendor);if (ret)return ret;return 0;
}

sbi_ecall_register_extension函数则具体实现了将sbi_ecall_extension注册到OpenSBI的ecall机制中,也就是插入ecall_exts_list这个全局链表中尾部

int sbi_ecall_register_extension(struct sbi_ecall_extension *ext)
{struct sbi_ecall_extension *t;if (!ext || (ext->extid_end < ext->extid_start) || !ext->handle)return SBI_EINVAL;sbi_list_for_each_entry(t, &ecall_exts_list, head) {unsigned long start = t->extid_start;unsigned long end = t->extid_end;if (end < ext->extid_start || ext->extid_end < start)/* no overlap */;elsereturn SBI_EINVAL;}SBI_INIT_LIST_HEAD(&ext->head);sbi_list_add_tail(&ext->head, &ecall_exts_list);return 0;
}
sbi_domain_finalize

这个函数主要完成domian域的最终初始化,具体的函数分析就不展开了。
需要注意两点:
*在HSM初始化之后完成域的最终配置,这样可以启动non-root domains
*在hart pmp之前需要完成域的最终配置,这样我们才能保证用正确的域去配置pmp

int sbi_domain_finalize(struct sbi_scratch *scratch, u32 cold_hartid)
{int rc;u32 i, dhart;struct sbi_domain *dom;const struct sbi_platform *plat = sbi_platform_ptr(scratch);/* Initialize and populate domains for the platform */rc = sbi_platform_domains_init(plat);if (rc) {sbi_printf("%s: platform domains_init() failed (error %d)\n",__func__, rc);return rc;}/* Startup boot HART of domains */sbi_domain_for_each(i, dom) {/* Domain boot HART */dhart = dom->boot_hartid;/* Ignore of boot HART is off limits */if (SBI_HARTMASK_MAX_BITS <= dhart)continue;/* Ignore if boot HART not possible for this domain */if (!sbi_hartmask_test_hart(dhart, dom->possible_harts))continue;/* Ignore if boot HART assigned different domain */if (sbi_hartid_to_domain(dhart) != dom ||!sbi_hartmask_test_hart(dhart, &dom->assigned_harts))continue;/* Startup boot HART of domain */if (dhart == cold_hartid) {scratch->next_addr = dom->next_addr;scratch->next_mode = dom->next_mode;scratch->next_arg1 = dom->next_arg1;} else {rc = sbi_hsm_hart_start(scratch, NULL, dhart,dom->next_addr,dom->next_mode,dom->next_arg1);if (rc) {sbi_printf("%s: failed to start boot HART %d"" for %s (error %d)\n", __func__,dhart, dom->name, rc);return rc;}}}return 0;
}

domain阶段是有打印提示的:

	sbi_boot_print_domains(scratch);static void sbi_boot_print_domains(struct sbi_scratch *scratch)
{if (scratch->options & SBI_SCRATCH_NO_BOOT_PRINTS)return;/* Domain details */sbi_domain_dump_all("      ");
}
sbi_hart_pmp_configure函数

对PMP配置进行初始化:
对当前hart的PMP配置进行初始化。PMP是RISC-V体系结构中提供的硬件机制,用于限制物理内存的访问。而在RISC-V系统中,一个hart可以占有多个物理内存保护区域,而PMP机制就是通过配置这些保护区域来限制内存的访问。

int sbi_hart_pmp_configure(struct sbi_scratch *scratch)
{/** dom指向sbi_domain结构体* pmp_idx 代表PMP寄存器的索引* pmp_flags PMP寄存器的标志位* pmp_bits PMP地址位数* pmp_gran_log2 PMP寄存器数量* pmp_addr PMP寄存器地址* pmp_addr_max PMP地址最大值*/     struct sbi_domain_memregion *reg;struct sbi_domain *dom = sbi_domain_thishart_ptr();unsigned int pmp_idx = 0, pmp_flags, pmp_bits, pmp_gran_log2;unsigned int pmp_count = sbi_hart_pmp_count(scratch);unsigned long pmp_addr = 0, pmp_addr_max = 0;if (!pmp_count)return 0;pmp_gran_log2 = log2roundup(sbi_hart_pmp_granularity(scratch));pmp_bits = sbi_hart_pmp_addrbits(scratch) - 1;pmp_addr_max = (1UL << pmp_bits) | ((1UL << pmp_bits) - 1);sbi_domain_for_each_memregion(dom, reg) {if (pmp_count <= pmp_idx)break;pmp_flags = 0;if (reg->flags & SBI_DOMAIN_MEMREGION_READABLE)pmp_flags |= PMP_R;if (reg->flags & SBI_DOMAIN_MEMREGION_WRITEABLE)pmp_flags |= PMP_W;if (reg->flags & SBI_DOMAIN_MEMREGION_EXECUTABLE)pmp_flags |= PMP_X;if (reg->flags & SBI_DOMAIN_MEMREGION_MMODE)pmp_flags |= PMP_L;pmp_addr =  reg->base >> PMP_SHIFT;if (pmp_gran_log2 <= reg->order && pmp_addr < pmp_addr_max)pmp_set(pmp_idx++, pmp_flags, reg->base, reg->order);else {sbi_printf("Can not configure pmp for domain %s", dom->name);sbi_printf("because memory region address %lx or size %lx is not in range\n",reg->base, reg->order);}}return 0;
}

需要注意的是:在V1.12版本中,加入了对缓存的刷新,因为CPU可能会在实际访问之前执行虚拟地址翻译,而PMP规则或导致访问失败,因此需要确保翻译信息与PMP规则保持一致,保证在特权模式下对内存访问的正确性。

    if (misa_extension('S')) {__asm__ __volatile__("sfence.vma");/** If hypervisor mode is supported, flush caching* structures in guest mode too.*/if (misa_extension('H'))__sbi_hfence_gvma_all();}return 0;
sbi_platform_final_init

这个函数将对platfrom的初始化进行最后的工作,可以看到在generic平台中,主要是进行fdt的修正。

/*** Final initialization for current HART** @param plat pointer to struct sbi_platform* @param cold_boot whether cold boot (TRUE) or warm_boot (FALSE)** @return 0 on success and negative error code on failure*/
static inline int sbi_platform_final_init(const struct sbi_platform *plat,bool cold_boot)
{if (plat && sbi_platform_ops(plat)->final_init)return sbi_platform_ops(plat)->final_init(cold_boot);return 0;
}static int generic_final_init(bool cold_boot)
{void *fdt;int rc;if (generic_plat && generic_plat->final_init) {rc = generic_plat->final_init(cold_boot, generic_plat_match);if (rc)return rc;}if (!cold_boot)return 0;fdt = sbi_scratch_thishart_arg1_ptr();fdt_cpu_fixup(fdt);fdt_fixups(fdt);fdt_domain_fixup(fdt);if (generic_plat && generic_plat->fdt_fixup) {rc = generic_plat->fdt_fixup(fdt, generic_plat_match);if (rc)return rc;}return 0;
}
wake_coldboot_harts函数

这个函数主要为了标志cold_boot的完成,向其他hart发送核间中断进行通知

static void wake_coldboot_harts(struct sbi_scratch *scratch, u32 hartid)
{const struct sbi_platform *plat = sbi_platform_ptr(scratch);/* Mark coldboot done */__smp_store_release(&coldboot_done, 1);/* Acquire coldboot lock */spin_lock(&coldboot_lock);/* Send an IPI to all HARTs waiting for coldboot */for (int i = 0; i <= sbi_scratch_last_hartid(); i++) {if ((i != hartid) &&sbi_hartmask_test_hart(i, &coldboot_wait_hmask))sbi_platform_ipi_send(plat, i);}/* Release coldboot lock */spin_unlock(&coldboot_lock);
}
sbi_hsm_prepare_next_jump函数

准备进行下一级的BOOT阶段,通过管理sbi_hsm_data中原子变量state,将hart的状态从SBI_HSM_HART_STATUS_START_PENDING更改为SBI_HSM_HART_STATUS_STARTED,若无法更改状态则进入wfi

struct sbi_hsm_data {atomic_t state;
};int sbi_hsm_hart_state_to_status(int state)
{int ret;switch (state) {case SBI_HART_STOPPED:ret = SBI_HSM_HART_STATUS_STOPPED;break;case SBI_HART_STOPPING:ret = SBI_HSM_HART_STATUS_STOP_PENDING;break;case SBI_HART_STARTING:ret = SBI_HSM_HART_STATUS_START_PENDING;break;case SBI_HART_STARTED:ret = SBI_HSM_HART_STATUS_STARTED;break;default:ret = SBI_EINVAL;}return ret;
}void sbi_hsm_prepare_next_jump(struct sbi_scratch *scratch, u32 hartid)
{u32 oldstate;struct sbi_hsm_data *hdata = sbi_scratch_offset_ptr(scratch,hart_data_offset);oldstate = atomic_cmpxchg(&hdata->state, SBI_HART_STARTING,SBI_HART_STARTED);if (oldstate != SBI_HART_STARTING)sbi_hart_hang();
}
sbi_hart_switch_mode

最后进入上下文切换的阶段,将hart执行的上下文切换到下一个指定的特权模式(S或U),并进行了一系列的传参(填写很多CSR寄存器)

void __attribute__((noreturn))
sbi_hart_switch_mode(unsigned long arg0, unsigned long arg1,unsigned long next_addr, unsigned long next_mode,bool next_virt)
{
#if __riscv_xlen == 32unsigned long val, valH;
#elseunsigned long val;
#endifswitch (next_mode) {case PRV_M:break;case PRV_S:if (!misa_extension('S'))sbi_hart_hang();break;case PRV_U:if (!misa_extension('U'))sbi_hart_hang();break;default:sbi_hart_hang();}val = csr_read(CSR_MSTATUS);val = INSERT_FIELD(val, MSTATUS_MPP, next_mode);val = INSERT_FIELD(val, MSTATUS_MPIE, 0);
#if __riscv_xlen == 32if (misa_extension('H')) {valH = csr_read(CSR_MSTATUSH);if (next_virt)valH = INSERT_FIELD(valH, MSTATUSH_MPV, 1);elsevalH = INSERT_FIELD(valH, MSTATUSH_MPV, 0);csr_write(CSR_MSTATUSH, valH);}
#elseif (misa_extension('H')) {if (next_virt)val = INSERT_FIELD(val, MSTATUS_MPV, 1);elseval = INSERT_FIELD(val, MSTATUS_MPV, 0);}
#endifcsr_write(CSR_MSTATUS, val);csr_write(CSR_MEPC, next_addr);if (next_mode == PRV_S) {csr_write(CSR_STVEC, next_addr);csr_write(CSR_SSCRATCH, 0);csr_write(CSR_SIE, 0);csr_write(CSR_SATP, 0);} else if (next_mode == PRV_U) {if (misa_extension('N')) {csr_write(CSR_UTVEC, next_addr);csr_write(CSR_USCRATCH, 0);csr_write(CSR_UIE, 0);}}register unsigned long a0 asm("a0") = arg0;register unsigned long a1 asm("a1") = arg1;__asm__ __volatile__("mret" : : "r"(a0), "r"(a1));__builtin_unreachable();
}

** 下面将执行U-boot或者Linux阶段的代码了,Opensbi将常驻于内存中 **

参考

https://zhuanlan.zhihu.com/p/630065971

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/640727.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++奇迹之旅:从0开始实现日期时间计算器

文章目录 &#x1f4dd;前言&#x1f320; 头文件Date.h&#x1f309;日期计算函数&#x1f320;前后置&#x1f309;前后置-- &#x1f320;两对象日期相减&#x1f309;自定义流输入和输出 &#x1f309; 代码&#x1f309; 头文件Date.h&#x1f320;Date.cpp&#x1f309; …

数据挖掘实验(Apriori,fpgrowth)

Apriori&#xff1a;这里做了个小优化&#xff0c;比如abcde和adcef自连接出的新项集abcdef&#xff0c;可以用abcde的位置和f的位置取交集&#xff0c;这样第n项集的计算可以用n-1项集的信息和数字本身的位置信息计算出来&#xff0c;只需要保存第n-1项集的位置信息就可以提速…

在Qt creator中使用多光标

2024年4月22日&#xff0c;周一下午 Qt Creator 支持多光标模式。 多光标模式允许你在同一时间在多个光标位置进行编辑&#xff0c;从而可以更快地进行一些重复性的编辑操作。 要启用多光标模式&#xff0c;请按住 Alt 键&#xff0c;并用鼠标左键在文本编辑器中选择多个光标…

标题Selenium IDE 常见错误笔记

Selenium IDE 常见错误笔记 错误1&#xff1a;Failed:Exceeded waiting time for new window to appear 2000ms 这个错误通常出现在第一次运行时&#xff0c;有两个原因&#xff1a; Firefox阻止了弹出式窗口&#xff0c;在浏览器设置里允许这个操作即可。 有些网站设置了反…

Elasticsearch单机部署(Linux)

1. 准备环境 本文中Elasticsearch版本为7.12.0&#xff0c;JDK版本为1.8.0&#xff0c;Linux环境部署。 扩展&#xff1a; &#xff08;1&#xff09;查看Elasticsearch对应的常用的jdk版本如下&#xff1a;&#xff08;详情可看官网的支持一览表&#xff09; Elasticsearch a…

基于51单片机的电子秤LCD1602液晶显示( proteus仿真+程序+设计报告+讲解视频)

基于51单片机电子秤LCD显示 1. 主要功能&#xff1a;2. 讲解视频&#xff1a;3. 仿真设计4. 程序代码5. 设计报告6. 设计资料内容清单&&下载链接 基于51单片机电子秤LCD显示( proteus仿真程序设计报告讲解视频&#xff09; 仿真图proteus8.9及以上 程序编译器&#xf…

基于SSM+Vue的护工预约服务小程序和后台管理系统

1、系统演示视频&#xff08;演示视频&#xff09; 2、需要请联系

五种服务异步通信(MQ)-详解、代码案例

简介&#xff1a;本篇文章主要是介绍了常用的异步通信原理&#xff0c;主要是RabbitMQ技术 目录 1、初始MQ&#xff08;异步通讯&#xff09; 1.1 同步通讯 1.2 异步通讯 1.3 MQ常见框架 2、RabbitMQ快速入门 2.1 RabbitMQ概述和安装 2.2 常见消息模型 2.3 快速入门 3、…

解决“该扩展程序未列在 Chrome 网上应用店中,并可能是在您不知情的情况下添加的”的方法

一、问题 安装插件出现时“该扩展程序未列在 Chrome 网上应用店中&#xff0c;并可能是在您不知情的情况下添加的” 二、解决方法 1、把需要安装的第三方插件&#xff0c;后缀.crx 改成 .rar&#xff0c;然后解压&#xff0c;得到一个文件夹 2、再打开chrome://extensions/谷歌…

node.js如何实现留言板功能?

一、实现效果如下&#xff1a; 20240422_160404 二、前提配置&#xff1a; 配置&#xff1a;需要安装并且导入underscore模板引擎 安装&#xff1a;在控制台输入npm install underscore -save 文件目录配置&#xff1a; 1》在文件里建一个data文件夹&#xff0c;此文件夹下…

boss:整个卡尔曼滤波器的简单案例——估计机器人位置

⭐️ 卡尔曼滤波 卡尔曼滤波&#xff08;Kalman Filtering&#xff09;是一种用于状态估计的强大技术&#xff0c;常用于处理具有随机噪声的系统的状态估计问题。在目标跟踪等应用中&#xff0c;卡尔曼滤波常被用来预测目标的位置和速度等状态变量&#xff0c;并根据观测数据进…

Go并发安全,锁和原子操作

一. 并发安全 有时候在Go代码中可能存在多个goroutine同时操作一个资源(临界区)&#xff0c;这种情况会发生竞态问题(数据竞态)。 1.1 互斥锁 互斥锁是一种常见的控制共享资源访问的方法&#xff0c;它能够保证同时只有一个goroutine可以访问共享资源。Go语言中使用sync包的Mut…