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);
	else
		init_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_GIT
	sbi_printf("\nOpenSBI %s\n", OPENSBI_VERSION_GIT);
#else
	sbi_printf("\nOpenSBI v%d.%d\n", OPENSBI_VERSION_MAJOR,
		   OPENSBI_VERSION_MINOR);
#endif

	sbi_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 */;
		else
			return 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 == 32
	unsigned long val, valH;
#else
	unsigned long val;
#endif

	switch (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 == 32
	if (misa_extension('H')) {
		valH = csr_read(CSR_MSTATUSH);
		if (next_virt)
			valH = INSERT_FIELD(valH, MSTATUSH_MPV, 1);
		else
			valH = INSERT_FIELD(valH, MSTATUSH_MPV, 0);
		csr_write(CSR_MSTATUSH, valH);
	}
#else
	if (misa_extension('H')) {
		if (next_virt)
			val = INSERT_FIELD(val, MSTATUS_MPV, 1);
		else
			val = INSERT_FIELD(val, MSTATUS_MPV, 0);
	}
#endif
	csr_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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AutoPSA的埋地数据

用户在使用埋地算法时&#xff0c;问“不排水抗剪强度”什么意思&#xff1f; A:这个是土壤的参数&#xff0c;如果不填&#xff0c;软件会自动计算. 还有&#xff0c;垫片厚度&#xff0c;覆土压实乘子的数据没有从AutoPDMS导出时&#xff0c;需要手动输入一下。 其他&#…

【数据结构】二叉树链式结构的实现《遍历,实现》(题库+解析+源码)

前言 二叉树的学习离不开对堆的理解&#xff0c;这是上篇堆的传送门 http://t.csdnimg.cn/F6Jp3 1.二叉树链式结构的实现 1.1 前置说明 在学习二叉树的基本操作前&#xff0c;需先要创建一棵二叉树&#xff0c;然后才能学习其相关的基本操作。由于现在大家对二 叉树结构掌握还…

Linux中inode号与日志分析

一.inode号 1.inode表结构 元信息&#xff1a;每个文件的属性信息&#xff0c;比如&#xff1a;文件的大小&#xff0c;时间&#xff0c;类型&#xff0c;权限等&#xff0c;称为文件的元数据(meta data 元信息 ) 元数据是存放在inode&#xff08;index node&#xff09;表中…

python基础知识二(标识符和关键字、输出、输入)

目录 标识符和关键字&#xff1a; 什么是标识符&#xff1f; 1. 标识符 2. 标识符的命名规则 什么是关键字&#xff1f; 1. 关键字 2. 关键字的分类 标识符和关键字的区别&#xff1a; ​​​输出&#xff1a; 1. 普通的输出 2. 格式化输出 格式化操作的目的&#…

【电控笔记6.3】采样-Z转换-零阶保持器

本质 数字转模拟:零阶保持器 采样 z-1所描述的物理意义即为延迟T时间的拉氏转换e-sT 信号采样延时

AI音乐:探索变现之路的新篇章

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经渗透到我们生活的方方面面&#xff0c;音乐领域也不例外。AI音乐作为一种新兴的音乐创作方式&#xff0c;正逐渐改变着传统音乐产业的格局。然而&#xff0c;如何将AI音乐变现&#xff0c;成为了摆在众多…

SFP、SFP+、SFP28 与 QSFP28 收发器之间的差异:兼容性和性能

近年来&#xff0c;网络技术发展迅速&#xff0c;因此&#xff0c;计算专业人员面临着越来越令人困惑的术语和缩写词。 管理数据中心时必须了解的一个关键领域是收发器&#xff0c;特别是 SFP (1550nm/1310nm)、SFP (850nm) 和 QSFP28 (4x25G) 之间的差异。 这些型号在兼容性方…

密钥密码学(二)

原文&#xff1a;annas-archive.org/md5/b5abcf9a07e32fc6f42b907f001224a1 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十章&#xff1a;可变长度分数化 本章涵盖 基于摩尔斯电码的密码 混合字母和双字母 可变长度二进制码字 基于文本压缩的密码 本章涵盖…

YOLOv9有效改进专栏汇总|未来更新卷积、主干、检测头注意力机制、特征融合方式等创新![2024/4/21]

​ 专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;助力高效涨点&#xff01;&#xff01;&#xff01; 专栏介绍 YOLOv9作为最新的YOLO系列模型&#xff0c;对于做目标检测的同学是必不可少的。本专栏将针对2024年最新推出的YOLOv9检测模型&#xff0…

DevOps流程的简单总结

DevOps流程图&#xff1a; DevOps流程包含&#xff1a;计划&#xff08;plan&#xff09;、编码(code)、编译(build)、测试(test)、发布(release)、部署(deploy)、运营(operate)、监控&#xff08;monitor&#xff09;&#xff0c;这是一个循环的过程。DevOps是依托容器、自动化…

下级平台级联EasyCVR视频汇聚安防监控平台后,设备显示层级并存在重复的原因排查和解决

视频汇聚平台/视频监控系统/国标GB28181协议EasyCVR安防平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;平台支持7*24小时实时高清视频监控&#xff0c;能同时…

【爬取研招网指定学校专业信息】

前言 本文介绍了如何使用 Python 的 requests 库和 BeautifulSoup 库来爬取研究方向信息&#xff0c;并将其保存为 CSV 文件。爬取的网站为“中国研究生招生信息网”&#xff08;https://yz.chsi.com.cn/&#xff09;。代码从指定的专业目录页面爬取研究方向的相关信息&#x…

Pycharm/Dataspell中使用jupyter导入ros humble包

配置ros humble对应python包路径文件 首先在~/.local/lib/python3.10/site-packages目录下新建一个.pth文件&#xff0c;如下图所示。 将对应的ros humble的python包的路径配置在上述文件中&#xff0c;一行放置一个路径&#xff0c;对应的路径如下图所示。 完成上述操作后…

IP地址定位技术引发的个人隐私保护问题

IP地址定位技术对互联网的影响深远且多面&#xff0c;它不仅改变了网络管理与优化的方式&#xff0c;还极大地推动了在线广告营销、电子商务、地理信息服务等多个领域的发展。然而&#xff0c;与此同时&#xff0c;它也引发了一系列关于个人隐私保护的问题。 首先&#xff0c;I…

太奇怪了!99%的人没见过的Oracle故障:网络恢复后,集群的监听和vip无法启动

故障描述 15:46操作系统日志出现net4、net5网卡down&#xff0c;15:53分钟的网络恢复。网络中断是由于db汇聚交换机出现了问题。 网络恢复后&#xff0c;节点1的监听和vip无法启动。 故障分析 查看grid alert日志可以看到监听资源确实没有正常启动。 由于监听资源是crs的Ora…

15.Nacos服务分级存储模型

服务跨集群调用问题&#xff1a; 服务调用尽可能的选择本地集群的服务&#xff0c;跨集群调用延迟较高。 本地集群不可访问的情况下&#xff0c;再去访问其他集群。 如何配置集群的实例属性&#xff1a; spring: cloud:nacos:server-addr: localhost:8848 #nacos服务端地址d…

Hadoop 启动!

​2024/4/22 上个星期我们已经完成了Hadoop的安装及配置文件的修改 下面 我们将namenode进行一下初始化 hdfs namenode -format (创建文件存储目录&#xff1a;账本目录namenode datanode的目录) 我们在配置时 这就是用来设置账本目录的 我们做完格式化后 tmp目录就出现了 …

短视频素材哪里找?8个视频素材库免费下载无水印

是不是想要拓宽你的视频素材资源库&#xff0c;探索更多能够为你的视频项目注入新鲜血液的网站。这一次&#xff0c;我们将介绍一系列全球精选的视频素材网站&#xff0c;每一个都能为你的创作带来不同的视觉享受和灵感启发。 1. 蛙学府&#xff08;中国&#xff09; 提供广泛…

AI重建粒子轨迹,发现新物理学

目录 二Sora冲击还没来&#xff0c;但智能家居人已经开始焦虑了&#xff01; 一、智能家居新革命&#xff1a;AIoH 二、AI技术接入智能家居&#xff0c;未来价值几何&#xff1f; 三、AI 智能家居&#xff0c;不是纸上谈兵 四、结语 电子学在核物理领域从来都不是一帆风顺…

创新指南|解码购物者营销:吸引现代消费者的艺术

在当今竞争异常激烈的零售环境中&#xff0c;了解消费者行为的复杂性不仅仅是一种营销优势&#xff0c;更是一种必要。 当我们探索购物者营销的多方面领域时&#xff0c;我们将揭示这种方法如何重新定义购物体验&#xff0c;使每次互动都有意义&#xff0c;并将日常购物者变成忠…