在WIN从零开始在QMUE上添加一块自己的开发板(二)

news2024/11/28 6:50:55

文章目录

  • 一、前言
    • 往期回顾
  • 二、CPU虚拟化
    • (一)相关源码
    • (二)举个例子
    • (三)测试
  • 三、内存虚拟化
    • (一)相关源码
    • (二)举个例子
    • 测试
  • 参考资料

一、前言

笔者这篇博客作为平时学习时的笔记记录,如有不对还望指正,本博客大量借鉴资料,笔者只是拾人牙慧的小屁孩。
QEMU是一种通用的开源计算机仿真器和虚拟器。而QUME内置支持了一些开发板,我们可以基于这些内置的板子来做操作系统等软件的配置,但是实际市面上很多板子QUME中是没有提供支持的,这需要我们根据QUME的源码自定义一些开发板,然后再重新编译。

往期回顾

在WIN从零开始在QMUE上添加一块自己的开发板(一)

二、CPU虚拟化

(一)相关源码

QEMU中RISC-V CPU的支持
QOM的TYPE定义
target/riscv/cpu.h:

#define TYPE_RISCV_CPU "riscv-cpu"

#define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
#define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)
#define CPU_RESOLVING_TYPE TYPE_RISCV_CPU

#define TYPE_RISCV_CPU_ANY RISCV_CPU_TYPE_NAME("any")
#define TYPE_RISCV_CPU_BASE32 RISCV_CPU_TYPE_NAME("rv32")
#define TYPE_RISCV_CPU_BASE64 RISCV_CPU_TYPE_NAME("rv64")
#define TYPE_RISCV_CPU_IBEX RISCV_CPU_TYPE_NAME("lowrisc-ibex")
#define TYPE_RISCV_CPU_SIFIVE_E31 RISCV_CPU_TYPE_NAME("sifive-e31")
#define TYPE_RISCV_CPU_SIFIVE_E34 RISCV_CPU_TYPE_NAME("sifive-e34")
#define TYPE_RISCV_CPU_SIFIVE_E51 RISCV_CPU_TYPE_NAME("sifive-e51")
#define TYPE_RISCV_CPU_SIFIVE_U34 RISCV_CPU_TYPE_NAME("sifive-u34")
#define TYPE_RISCV_CPU_SIFIVE_U54 RISCV_CPU_TYPE_NAME("sifive-u54")

TYPE用于hash表的key(GHashTable.key),而hash表的值也就是ObjectClassObject

Class
RISCVCPUClass继承于CPUClass,然后继承于DeviceClass
CPUClass中有很多接口:

struct CPUClass {
	DeviceClass parent_class;
	ObjectClass *(*class_by_name)();
	void (*parse_features)();
	int reset_dump_flags;
	bool (*has_work)();
	bool (*virtio_is_big_endian)();
	int (*memory_rw_debug)();
	void (*dump_state)();
	GuestPanicInformation* (*get_crash_info)();
	void (*dump_statistics)();
	int64_t (*get_arch_id)();
	bool (*get_paging_enabled)();
	void (*get_memory_mapping)();
	void (*set_pc)();
	hwaddr (*get_phys_page_debug)();
	hwaddr (*get_phys_page_attrs_debug)();
	int (*asidx_from_attrs)();
	int (*gdb_read_register)();
	int (*gdb_write_register)();
	int (*write_elf64_note)();
	int (*write_elf64_qemunote)();
	int (*write_elf32_note)();
	int (*write_elf32_qemunote)();
	const VMStateDescription *vmsd;
	const char *gdb_core_xml_file;
	gchar * (*gdb_arch_name)();
	const char * (*gdb_get_dynamic_xml)();
	void (*disas_set_info)();
	const char *deprecation_note;
	int gdb_num_core_regs;
	bool gdb_stop_before_watchpoint;
	struct AccelCPUClass *accel_cpu;
	struct TCGCPUOps *tcg_ops;
};

我们着重看一下struct TCGCPUOps

struct TCGCPUOps {
	void (*initialize)(void);
	void (*synchronize_from_tb)(CPUState *cpu, const TranslationBlock *tb);
	void (*cpu_exec_enter)(CPUState *cpu);
	void (*cpu_exec_exit)(CPUState *cpu);
	bool (*cpu_exec_interrupt)(CPUState *cpu, int interrupt_request);
	void (*do_interrupt)(CPUState *cpu);
	bool (*tlb_fill)(CPUState *cpu, vaddr address, int size,
	MMUAccessType access_type, int mmu_idx,
	bool probe, uintptr_t retaddr);
	void (*debug_excp_handler)(CPUState *cpu);
#ifdef NEED_CPU_H
#ifdef CONFIG_SOFTMMU
	void (*do_transaction_failed)(CPUState *cpu, hwaddr physaddr, vaddr addr,
	unsigned size, MMUAccessType access_type,
	int mmu_idx, MemTxAttrs attrs,
	MemTxResult response, uintptr_t retaddr);
	void (*do_unaligned_access)(CPUState *cpu, vaddr addr,
	MMUAccessType access_type,
	int mmu_idx, uintptr_t retaddr);
	vaddr (*adjust_watchpoint_address)(CPUState *cpu, vaddr addr, int len);
	bool (*debug_check_watchpoint)(CPUState *cpu, CPUWatchpoint *wp);
	bool (*io_recompile_replay_branch)(CPUState *cpu,
	const TranslationBlock *tb);
#endif /* CONFIG_SOFTMMU */
#endif /* NEED_CPU_H */
};

可以看见里面有很多CPU运行时的接口。
在不同架构中,我们进行相应的实现,对于RISCVCPU,已经有相应的实现了:

static struct TCGCPUOps riscv_tcg_ops = {
	.initialize = riscv_translate_init,
	.synchronize_from_tb = riscv_cpu_synchronize_from_tb,
	.cpu_exec_interrupt = riscv_cpu_exec_interrupt,
	.tlb_fill = riscv_cpu_tlb_fill,
#ifndef CONFIG_USER_ONLY
	.do_interrupt = riscv_cpu_do_interrupt,
	.do_transaction_failed = riscv_cpu_do_transaction_failed,
	.do_unaligned_access = riscv_cpu_do_unaligned_access,
#endif /* !CONFIG_USER_ONLY */
};

Object
对于CPU中寄存器的定义都在Object中:

struct CPURISCVState {
	target_ulong gpr[32];
	uint64_t fpr[32]; /* assume both F and D extensions */
	/* vector coprocessor state. */
	uint64_t vreg[32 * RV_VLEN_MAX / 64] QEMU_ALIGNED(16);
	//vector reg
	target_ulong pc;
	target_ulong misa;
	uint32_t features;
	/* Hypervisor CSRs */
	/* Virtual CSRs */
	/* HS Backup CSRs */
	/* temporary htif regs */
	/* physical memory protection */
	/* machine specific rdtime callback */
	/* True if in debugger mode. */
	bool debugger;
	float_status fp_status;
	/* Fields from here on are preserved across CPU reset. */
	QEMUTimer *timer; /* Internal timer */
};

实例化

	struct RISCVCPU {
	/*< private >*/
	CPUState parent_obj;
	/*< public >*/
	CPUNegativeOffsetState neg;
	CPURISCVState env;
	char *dyn_csr_xml;
	/* Configuration Settings */
	struct {
	……
	} cfg;
};

RISCV CPU TypeInfo注册:

.instance_init = riscv_cpu_init,
.class_init = riscv_cpu_class_init,

特殊的CPU使用特殊的函数进行实例

.instance_init = rvxx_sifive_e_cpu_init,
.class_init = riscv_cpu_class_init,

在特殊的函数中,将会针对不同CPU的特性进行个性化实例:

static void rvxx_sifive_e_cpu_init(Object *obj)
{
	CPURISCVState *env = &RISCV_CPU(obj)->env;
	set_misa(env, RVXLEN | RVI | RVM | RVA | RVC | RVU);
	set_priv_version(env, PRIV_VERSION_1_10_0);
	set_resetvec(env, 0x1004);
	qdev_prop_set_bit(DEVICE(obj), "mmu", false);
}

(二)举个例子

我们为之前创建的开发板增加CPU。
我们去target\riscv\cpu-qom.h,添加一个我们自己的CPU:

...
#define RISCV_CPU_TYPE_SUFFIX "-" TYPE_RISCV_CPU
#define RISCV_CPU_TYPE_NAME(name) (name RISCV_CPU_TYPE_SUFFIX)

#define TYPE_RISCV_CPU_ANY              RISCV_CPU_TYPE_NAME("any")
#define TYPE_RISCV_CPU_MAX              RISCV_CPU_TYPE_NAME("max")
#define TYPE_RISCV_CPU_BASE32           RISCV_CPU_TYPE_NAME("rv32")
#define TYPE_RISCV_CPU_BASE64           RISCV_CPU_TYPE_NAME("rv64")
#define TYPE_RISCV_CPU_BASE128          RISCV_CPU_TYPE_NAME("x-rv128")
#define TYPE_RISCV_CPU_IBEX             RISCV_CPU_TYPE_NAME("lowrisc-ibex")
#define TYPE_RISCV_CPU_SHAKTI_C         RISCV_CPU_TYPE_NAME("shakti-c")
#define TYPE_RISCV_CPU_SIFIVE_E31       RISCV_CPU_TYPE_NAME("sifive-e31")
#define TYPE_RISCV_CPU_SIFIVE_E34       RISCV_CPU_TYPE_NAME("sifive-e34")
#define TYPE_RISCV_CPU_SIFIVE_E51       RISCV_CPU_TYPE_NAME("sifive-e51")
#define TYPE_RISCV_CPU_SIFIVE_U34       RISCV_CPU_TYPE_NAME("sifive-u34")
#define TYPE_RISCV_CPU_SIFIVE_U54       RISCV_CPU_TYPE_NAME("sifive-u54")
#define TYPE_RISCV_CPU_THEAD_C906       RISCV_CPU_TYPE_NAME("thead-c906")
#define TYPE_RISCV_CPU_VEYRON_V1        RISCV_CPU_TYPE_NAME("veyron-v1")
#define TYPE_RISCV_CPU_HOST             RISCV_CPU_TYPE_NAME("host")
/* 添加自己的CPU */
#define TYPE_RISCV_CPU_NUCLEI_N600       RISCV_CPU_TYPE_NAME("nuclei-n600")

并在./target/riscv/cpu.c中,添加其初始化函数:

#if defined(TARGET_RISCV32)
/* 自己的CPU */
static void rv32_nuclei_n_cpu_init(Object *obj)
{
    CPURISCVState *env = &RISCV_CPU(obj)->env;
    RISCVCPU *cpu = RISCV_CPU(obj);
    riscv_cpu_set_misa(env, MXL_RV32, RVI | RVM | RVA | RVC | RVF | RVD | RVU);
    env->priv_ver=  PRIV_VERSION_1_10_0;
    #ifndef CONFIG_USER_ONLY
    set_satp_mode_max_supported(cpu, VM_1_10_MBARE);
#endif

    /* inherited from parent obj via riscv_cpu_init() */
    cpu->cfg.ext_zifencei = true;
    cpu->cfg.ext_zicsr = true;
    cpu->cfg.pmp = true;
}
#endif

并在riscv_cpu_type_infos中添加DEFINE

static const TypeInfo riscv_cpu_type_infos[] = {
    {
        .name = TYPE_RISCV_CPU,
        .parent = TYPE_CPU,
        .instance_size = sizeof(RISCVCPU),
        .instance_align = __alignof(RISCVCPU),
        .instance_init = riscv_cpu_init,
        .instance_post_init = riscv_cpu_post_init,
        .abstract = true,
        .class_size = sizeof(RISCVCPUClass),
        .class_init = riscv_cpu_class_init,
    },
    {
        .name = TYPE_RISCV_DYNAMIC_CPU,
        .parent = TYPE_RISCV_CPU,
        .abstract = true,
    },
    DEFINE_DYNAMIC_CPU(TYPE_RISCV_CPU_ANY,      riscv_any_cpu_init),
    DEFINE_DYNAMIC_CPU(TYPE_RISCV_CPU_MAX,      riscv_max_cpu_init),
#if defined(TARGET_RISCV32)
    DEFINE_DYNAMIC_CPU(TYPE_RISCV_CPU_BASE32,   rv32_base_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_IBEX,             rv32_ibex_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_SIFIVE_E31,       rv32_sifive_e_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_SIFIVE_E34,       rv32_imafcu_nommu_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_SIFIVE_U34,       rv32_sifive_u_cpu_init),
    /* 自己的CPU */
    DEFINE_CPU(TYPE_RISCV_CPU_NUCLEI_N600,      rv32_nuclei_n_cpu_init),
    
#elif defined(TARGET_RISCV64)
    DEFINE_DYNAMIC_CPU(TYPE_RISCV_CPU_BASE64,   rv64_base_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_SIFIVE_E51,       rv64_sifive_e_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_SIFIVE_U54,       rv64_sifive_u_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_SHAKTI_C,         rv64_sifive_u_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_THEAD_C906,       rv64_thead_c906_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_VEYRON_V1,        rv64_veyron_v1_cpu_init),
    DEFINE_DYNAMIC_CPU(TYPE_RISCV_CPU_BASE128,  rv128_base_cpu_init),
#endif
};

接着我们回到我们的开发板文件,
这里讲个小技巧,也就是从include\hw\riscv\virt.h(官方虚拟开发板的例程)看相关的引用,从而确定相关头文件的所在位置和相关代码的实现。
在我们的nuclei_n.h中(请注意,为了方便理解,这里对往期变量及函数的名字进行了更改):
引入头文件

#include "hw/riscv/riscv_hart.h"

并在SOC中添加CPU

/* CPU 定义 */
#define NUCLEI_N_CPU TYPE_RISCV_CPU_NUCLEI_N600

typedef struct NucLeiNSoCState
{
	/*< private >*/
	SysBusDevice parent_obj;
	/*< public >*/
	RISCVHartArrayState cpus;
} NucLeiNSoCState;

以及一些TYPE

#define NUCLEI_N_CPU 						TYPE_RISCV_CPU_NUCLEI_N600

之后我们在Machine的Class里面增加最小CPU个数和默认CPU的TYPE

static void nuclei_machine_class_init(ObjectClass *oc, void *data)
{
	qemu_log(">>nuclei_machine_class_init \n");
	MachineClass *mc = MACHINE_CLASS(oc);
    mc->desc = "Nuclei MCU 200T FPGA Evaluation Kit";
	mc->init = nuclei_mcu_machine_init;
	mc->max_cpus = 1;
    mc->default_cpu_type = NUCLEI_N_CPU;
}

我们在SOC实例初始化函数中,对CPU初始化:

static void nuclei_n_soc_instance_init(Object *obj)
{
	qemu_log(">>nuclei_n_soc_instance_init \n");
	NucLeiNSoCState *s = NUCLEI_N_SOC(obj);
	object_initialize_child(obj, "cpus", &s->cpus, TYPE_RISCV_HART_ARRAY);  //初始化CPU
}

并在SOC实现中(nuclei_n_soc_realize)进行CPU的实现:

static void nuclei_n_soc_realize(DeviceState *dev, Error **errp)
{
	qemu_log(">>nuclei_n_soc_realize \n");
	MachineState *ms = MACHINE(qdev_get_machine());
    NucLeiNSoCState *s = NUCLEI_N_SOC(dev);

    object_property_set_str(OBJECT(&s->cpus), "cpu-type", ms->cpu_type, &error_abort);      
    object_property_set_int(OBJECT(&s->cpus), "num-harts", ms->smp.cpus, &error_abort);
    sysbus_realize(SYS_BUS_DEVICE(&s->cpus), &error_abort);  //CPU实例化
}

附上完整:

nuclei_n.h:

#include "hw/sysbus.h"
#include "hw/riscv/riscv_hart.h"

#define TYPE_NUCLEI_N_SOC "riscv.nuclei.n.soc"
#define NUCLEI_N_SOC(obj) \
    OBJECT_CHECK(NucLeiNSoCState, (obj), TYPE_NUCLEI_N_SOC)

/* CPU 定义 */
#define NUCLEI_N_CPU TYPE_RISCV_CPU_NUCLEI_N600

typedef struct NucLeiNSoCState
{
	/*< private >*/
	SysBusDevice parent_obj;
	/*< public >*/
	RISCVHartArrayState cpus;
} NucLeiNSoCState;


/* Machine state定义 */
#define TYPE_NUCLEI_MCU_FPGA_MACHINE MACHINE_TYPE_NAME("mcu_200t")
#define MCU_FPGA_MACHINE(obj) \
    OBJECT_CHECK(NucLeiNState, (obj), TYPE_NUCLEI_MCU_FPGA_MACHINE)

typedef struct NucLeiNState
{
	/*< private >*/
	SysBusDevice parent_obj;
	/*< public >*/
	NucLeiNSoCState soc;
} NucLeiNState;

nuclei_n.c:

#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "hw/riscv/nuclei_n.h"
#include "hw/boards.h"


static void nuclei_n_soc_instance_init(Object *obj)
{
	qemu_log(">>nuclei_n_soc_instance_init \n");
	NucLeiNSoCState *s = NUCLEI_N_SOC(obj);
	object_initialize_child(obj, "cpus", &s->cpus, TYPE_RISCV_HART_ARRAY);  //初始化CPU
}

static void nuclei_n_soc_realize(DeviceState *dev, Error **errp)
{
	qemu_log(">>nuclei_n_soc_realize \n");
	MachineState *ms = MACHINE(qdev_get_machine());
    NucLeiNSoCState *s = NUCLEI_N_SOC(dev);

    object_property_set_str(OBJECT(&s->cpus), "cpu-type", ms->cpu_type, &error_abort);      
    object_property_set_int(OBJECT(&s->cpus), "num-harts", ms->smp.cpus, &error_abort);
    sysbus_realize(SYS_BUS_DEVICE(&s->cpus), &error_abort);            //CPU实例化
}
static void nuclei_n_soc_class_init(ObjectClass *oc, void *data)
{
	qemu_log(">>nuclei_n_soc_class_init \n");
	DeviceClass *dc = DEVICE_CLASS(oc);
	dc->realize = nuclei_n_soc_realize;
	dc->user_creatable = false;
}

static const TypeInfo nuclei_n_soc_type_info = {
	.name = TYPE_NUCLEI_N_SOC,
	.parent = TYPE_DEVICE,
	.instance_size = sizeof(NucLeiNSoCState),
	.instance_init = nuclei_n_soc_instance_init,
	.class_init = nuclei_n_soc_class_init,
};
static void nuclei_n_soc_register_types(void)
{
	type_register_static(&nuclei_n_soc_type_info);
}
type_init(nuclei_n_soc_register_types)

static void nuclei_mcu_machine_init(MachineState *machine)
{
	NucLeiNState *s = MCU_FPGA_MACHINE(machine);
	qemu_log(">>nuclei_mcu_machine_init \n");
	/* Initialize SOC */
	object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_NUCLEI_N_SOC);
	qdev_realize(DEVICE(&s->soc), NULL, &error_abort);
}
static void nuclei_machine_instance_init(Object *obj)
{
	qemu_log(">>nuclei_machine_instance_init \n");
}
static void nuclei_machine_class_init(ObjectClass *oc, void *data)
{
	qemu_log(">>nuclei_machine_class_init \n");
	MachineClass *mc = MACHINE_CLASS(oc);
    mc->desc = "Nuclei MCU 200T FPGA Evaluation Kit";
	mc->init = nuclei_mcu_machine_init;
	mc->max_cpus = 1;
    mc->default_cpu_type = NUCLEI_N_CPU;
}

static const TypeInfo nuclei_machine_typeinfo = {
	.name = TYPE_NUCLEI_MCU_FPGA_MACHINE,
	.parent = TYPE_MACHINE,
	.class_init = nuclei_machine_class_init,
	.instance_init = nuclei_machine_instance_init,
	.instance_size = sizeof(NucLeiNState),
};
static void nuclei_machine_init_register_types(void)
{
	type_register_static(&nuclei_machine_typeinfo);
}
type_init(nuclei_machine_init_register_types)

不用忘记在./target/riscv/cpu.c去定义CPU哦。

(三)测试

执行run.sh:

SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
$SHELL_FOLDER/output/qemu/qemu-system-riscv32.exe \
-M mcu_200t

得到以下效果:
CPU虚拟化

三、内存虚拟化

(一)相关源码

有了之前的开发经验,我们这次直接看相关API:

Types of regionsinitialize
RAMmemory_region_init_ram()
MMIOmemory_region_init_io()
ROMmemory_region_init_rom()
ROM_evicememory_region_init_rom_device()
IOMMU regionmemory_region_init_iommu()
containermemory_region_init()
aliasmemory_region_init_alias()
reservation regionmemory_region_init_io()

其次是添加硬件的地址和映射的地址长度的结构体:
(注意这里的长度不能为0,不然会报错)

typedef struct MemMapEntry {
    hwaddr base; //基址
    hwaddr size; //长度
} MemMapEntry;

然后是关于ROM的指令初始化:

/* reset vector */
uint32_t reset_vec[8] = {
	0x00000297, /* 1: auipc t0, %pcrel_hi(dtb) */
	0x02028593, /* addi a1, t0, %pcrel_lo(1b) */
	0xf1402573, /* csrr a0, mhartid */
	#if defined(TARGET_RISCV32)
	0x0182a283, /* lw t0, 24(t0) */
	#elif defined(TARGET_RISCV64)
	0x0182b283, /* ld t0, 24(t0) */
	#endif
	0x00028067, /* jr t0 */
	0x00000000,
	start_addr, /* start: .dword DRAM_BASE */
	0x00000000,
};
/* copy in the reset vector in little_endian byte order */
for (i = 0; i < sizeof(reset_vec) >> 2; i++)
{
	reset_vec[i] = cpu_to_le32(reset_vec[i]);
}
rom_add_blob_fixed_as("mrom.reset", reset_vec, sizeof(reset_vec),memmap[NUCLEI_N_ROM].base, &address_space_memory);
/* boot rom */
if (machine->kernel_filename)
{
	riscv_load_kernel(machine>kernel_filename, start_addr, NULL);
}

这种是手动执行指令初始化,当然,因为我们使用的RISCV架构,直接使用riscv_setup_rom_reset_vec进行指令初始化也是可以的。

这里我们再讲一下内存模拟的一个步骤:

  1. 执行初始化函数,例如ROM的就是memory_region_init_rom
  2. 分配/挂载,memory_region_add_subregion,当然还会用上系统根节点获取get_system_memory
  3. ROM设置(指令初始化)或者加载kernel

(二)举个例子

根据SOC指定地址编写MemMapEntry
MemMapEntry

enum
{
    NUCLEI_N_DEBUG,
    NUCLEI_N_ROM,
    NUCLEI_N_TIMER,
    NUCLEI_N_ECLIC,
    NUCLEI_N_GPIO,
    NUCLEI_N_UART0,
    NUCLEI_N_QSPI0,
    NUCLEI_N_PWM0,
    NUCLEI_N_UART1,
    NUCLEI_N_QSPI1,
    NUCLEI_N_PWM1,
    NUCLEI_N_QSPI2,
    NUCLEI_N_PWM2,
    NUCLEI_N_XIP,
    NUCLEI_N_DRAM,
    NUCLEI_N_ILM,
    NUCLEI_N_DLM
};

static MemMapEntry nuclei_n_memmap[] = {
    [NUCLEI_N_DEBUG] 	= 	{0x0, 0x1000},
    [NUCLEI_N_ROM] 		= 	{0x1000, 0x1000},
    [NUCLEI_N_TIMER] 	= 	{0x2000000, 0x1000},
    [NUCLEI_N_ECLIC] 	= 	{0xc000000, 0x10000},
    [NUCLEI_N_GPIO] 	= 	{0x10012000, 0x1000},
    [NUCLEI_N_UART0] 	= 	{0x10013000, 0x1000},
    [NUCLEI_N_QSPI0] 	= 	{0x10014000, 0x1000},
    [NUCLEI_N_PWM0] 	= 	{0x10015000, 0x1000},
    [NUCLEI_N_UART1] 	= 	{0x10023000, 0x1000},
    [NUCLEI_N_QSPI1] 	= 	{0x10024000, 0x1000},
    [NUCLEI_N_PWM1] 	= 	{0x10025000, 0x1000},
    [NUCLEI_N_QSPI2] 	= 	{0x10034000, 0x1000},
    [NUCLEI_N_PWM2] 	= 	{0x10035000, 0x1000},
    [NUCLEI_N_XIP] 		= 	{0x20000000, 0x10000000},
    [NUCLEI_N_DRAM] 	= 	{0xa0000000, 0x0},
    [NUCLEI_N_ILM] 		= 	{0x80000000, 0x20000},
    [NUCLEI_N_DLM] 		= 	rub{0x90000000, 0x20000},
};

之后我们初始化ROM地址:

/* Internal ROM */
	memory_region_init_rom(&s->internal_rom, OBJECT(obj), "riscv.nuclei.n.irom", memmap[NUCLEI_N_ROM].size, &error_fatal);
    memory_region_add_subregion(sys_mem, memmap[NUCLEI_N_ROM].base, &s->internal_rom);

这里我们假设idlm和ROM都为Soc外设:
于是我们编写相关函数:

static void nuclei_n_soc_memory_create(Object *obj)
{
	NucLeiNSoCState *s = NUCLEI_N_SOC(obj);
	const MemMapEntry *memmap = nuclei_n_memmap;
	MemoryRegion *sys_mem = get_system_memory();

	/* Internal ROM */
	memory_region_init_rom(&s->internal_rom, OBJECT(obj), "riscv.nuclei.n.irom", memmap[NUCLEI_N_ROM].size, &error_fatal);
    memory_region_add_subregion(sys_mem, memmap[NUCLEI_N_ROM].base, &s->internal_rom);

		
	/* Initialize ilm dlm */
    memory_region_init_ram(&s->ilm, NULL, "riscv.nuclei.n.ilm", memmap[NUCLEI_N_ILM].size, &error_fatal);
    memory_region_add_subregion(sys_mem, memmap[NUCLEI_N_ILM].base, &s->ilm);
    memory_region_init_ram(&s->dlm, NULL, "riscv.nuclei.n.dlm", memmap[NUCLEI_N_DLM].size, &error_fatal);
    memory_region_add_subregion(sys_mem, memmap[NUCLEI_N_DLM].base, &s->dlm);

	 /* SysTimer */
    create_unimplemented_device("riscv.nuclei.n.timer", memmap[NUCLEI_N_TIMER].base, memmap[NUCLEI_N_TIMER].size);
    /* Eclic */
    create_unimplemented_device("riscv.nuclei.n.eclic", memmap[NUCLEI_N_ECLIC].base, memmap[NUCLEI_N_ECLIC].size);
    /* GPIO */
    create_unimplemented_device("riscv.nuclei.n.gpio", memmap[NUCLEI_N_GPIO].base, memmap[NUCLEI_N_GPIO].size);
}

因为还没有实现一些设备,所以我们创建unimplemented设备来占用内存:

	 /* SysTimer */
    create_unimplemented_device("riscv.nuclei.n.timer", memmap[NUCLEI_N_TIMER].base, memmap[NUCLEI_N_TIMER].size);
    /* Eclic */
    create_unimplemented_device("riscv.nuclei.n.eclic", memmap[NUCLEI_N_ECLIC].base, memmap[NUCLEI_N_ECLIC].size);
    /* GPIO */
    create_unimplemented_device("riscv.nuclei.n.gpio", memmap[NUCLEI_N_GPIO].base, memmap[NUCLEI_N_GPIO].size);

这次我们把CPU的初始化和实例化也类似封装成一个函数:

static void nuclei_n_soc_cpu_create(Object *obj)
{
	MachineState *ms = MACHINE(qdev_get_machine());
	NucLeiNSoCState *s = NUCLEI_N_SOC(obj);
	object_initialize_child(obj, "cpus", &s->cpus, TYPE_RISCV_HART_ARRAY);  //初始化CPU

    object_property_set_str(OBJECT(&s->cpus), "cpu-type", ms->cpu_type, &error_abort);      
    object_property_set_int(OBJECT(&s->cpus), "num-harts", ms->smp.cpus, &error_abort);
    sysbus_realize(SYS_BUS_DEVICE(&s->cpus), &error_abort);            		//CPU实例化
}

然后我们在nuclei_n_soc_instance_init中调用:

static void nuclei_n_soc_instance_init(Object *obj)
{
	/* SOC CPU */
	nuclei_n_soc_cpu_create(obj);
	/* SOC Memory */
	nuclei_n_soc_memory_create(obj);
}

其次是设置ROM和加载kernel,我们在整个Machine实例中进行初始化:

static void nuclei_mcu_machine_init(MachineState *machine)
{
	NucLeiNState *s = MCU_FPGA_MACHINE(machine);
	const MemMapEntry *memmap = nuclei_n_memmap;
    target_ulong start_addr;
	int i;

	/* Initialize SOC */
	object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_NUCLEI_N_SOC);
	qdev_realize(DEVICE(&s->soc), NULL, &error_abort);

	//选择启动方式
     switch (s->msel)
    {
    case MSEL_ILM:
        start_addr = memmap[NUCLEI_N_ILM].base;
        break;
    case MSEL_FLASH:
        start_addr = memmap[NUCLEI_N_XIP].base;
        break;
    case MSEL_FLASHXIP:
        start_addr = memmap[NUCLEI_N_XIP].base;
        break;
    case MSEL_DDR:
        start_addr = memmap[NUCLEI_N_DRAM].base;
        break;
    default:
        start_addr = memmap[NUCLEI_N_ILM].base;
        break;
    }

       /* reset vector */
    uint32_t reset_vec[8] = {
        0x00000297, /* 1:  auipc  t0, %pcrel_hi(dtb) */
        0x02028593, /*     addi   a1, t0, %pcrel_lo(1b) */
        0xf1402573, /*     csrr   a0, mhartid  */
#if defined(TARGET_RISCV32)
        0x0182a283, /*     lw     t0, 24(t0) */
#elif defined(TARGET_RISCV64)
        0x0182b283, /*     ld     t0, 24(t0) */
#endif
        0x00028067, /*     jr     t0 */
        0x00000000,
        start_addr, /* start: .dword DRAM_BASE */
        0x00000000,
    };

    /* copy in the reset vector in little_endian byte order */
    for (i = 0; i < sizeof(reset_vec) >> 2; i++)
    {
        reset_vec[i] = cpu_to_le32(reset_vec[i]);
    }
    rom_add_blob_fixed_as("mrom.reset", reset_vec, sizeof(reset_vec),
                          memmap[NUCLEI_N_ROM].base, &address_space_memory); //CPU初始化地址

    /* boot rom */
    if (machine->kernel_filename)
    {
		riscv_load_kernel(machine, &s->soc.cpus, start_addr, true, NULL);	//将裸机代码加载到地址start_addr
       // riscv_load_kernel(machine->kernel_filename, start_addr, NULL);  
    }
}

测试

我们进行测试:
编译完成后运行程序并执行:

info mtree

测试结果

结尾附上完整代码:
nuclei_n.h

#include "hw/boards.h"
#include "hw/riscv/riscv_hart.h"
#include "hw/sysbus.h"

#define TYPE_NUCLEI_N_SOC "riscv.nuclei.n.soc"
#define NUCLEI_N_SOC(obj) \
    OBJECT_CHECK(NucLeiNSoCState, (obj), TYPE_NUCLEI_N_SOC)

/* CPU 定义 */
#define NUCLEI_N_CPU TYPE_RISCV_CPU_NUCLEI_N600

typedef struct NucLeiNSoCState
{
	/*< private >*/
	DeviceState parent_obj;
	/*< public >*/
	RISCVHartArrayState cpus;

	MemoryRegion internal_rom;
    MemoryRegion ilm;
    MemoryRegion dlm;
    MemoryRegion xip_mem;
} NucLeiNSoCState;


/* Machine state定义 */
#define TYPE_NUCLEI_MCU_FPGA_MACHINE MACHINE_TYPE_NAME("mcu_200t")
#define MCU_FPGA_MACHINE(obj) \
    OBJECT_CHECK(NucLeiNState, (obj), TYPE_NUCLEI_MCU_FPGA_MACHINE)

typedef struct NucLeiNState
{
	/*< private >*/
	MachineState parent;
	/*< public >*/
	NucLeiNSoCState soc;
	
	 uint32_t msel;
} NucLeiNState;

enum
{
    MSEL_ILM = 1,
    MSEL_FLASH = 2,
    MSEL_FLASHXIP = 3,
    MSEL_DDR = 4
};

enum
{
    NUCLEI_N_DEBUG,
    NUCLEI_N_ROM,
    NUCLEI_N_TIMER,
    NUCLEI_N_ECLIC,
    NUCLEI_N_GPIO,
    NUCLEI_N_UART0,
    NUCLEI_N_QSPI0,
    NUCLEI_N_PWM0,
    NUCLEI_N_UART1,
    NUCLEI_N_QSPI1,
    NUCLEI_N_PWM1,
    NUCLEI_N_QSPI2,
    NUCLEI_N_PWM2,
    NUCLEI_N_XIP,
    NUCLEI_N_DRAM,
    NUCLEI_N_ILM,
    NUCLEI_N_DLM
};

nuclei_n.c

#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "hw/riscv/nuclei_n.h"
#include "qapi/visitor.h"
#include "hw/boards.h"
#include "hw/loader.h"
#include "hw/sysbus.h"
#include "target/riscv/cpu.h"
#include "hw/misc/unimp.h"
#include "hw/riscv/riscv_hart.h"
#include "hw/riscv/boot.h"


static MemMapEntry nuclei_n_memmap[] = {
    [NUCLEI_N_DEBUG] 	= 	{0x0, 0x1000},
    [NUCLEI_N_ROM] 		= 	{0x1000, 0x1000},
    [NUCLEI_N_TIMER] 	= 	{0x2000000, 0x1000},
    [NUCLEI_N_ECLIC] 	= 	{0xc000000, 0x10000},
    [NUCLEI_N_GPIO] 	= 	{0x10012000, 0x1000},
    [NUCLEI_N_UART0] 	= 	{0x10013000, 0x1000},
    [NUCLEI_N_QSPI0] 	= 	{0x10014000, 0x1000},
    [NUCLEI_N_PWM0] 	= 	{0x10015000, 0x1000},
    [NUCLEI_N_UART1] 	= 	{0x10023000, 0x1000},
    [NUCLEI_N_QSPI1] 	= 	{0x10024000, 0x1000},
    [NUCLEI_N_PWM1] 	= 	{0x10025000, 0x1000},
    [NUCLEI_N_QSPI2] 	= 	{0x10034000, 0x1000},
    [NUCLEI_N_PWM2] 	= 	{0x10035000, 0x1000},
    [NUCLEI_N_XIP] 		= 	{0x20000000, 0x10000000},
    [NUCLEI_N_DRAM] 	= 	{0xa0000000, 0x0},
    [NUCLEI_N_ILM] 		= 	{0x80000000, 0x20000},
    [NUCLEI_N_DLM] 		= 	{0x90000000, 0x20000},
};


static void nuclei_n_soc_cpu_create(Object *obj)
{
	MachineState *ms = MACHINE(qdev_get_machine());
	NucLeiNSoCState *s = NUCLEI_N_SOC(obj);
	object_initialize_child(obj, "cpus", &s->cpus, TYPE_RISCV_HART_ARRAY);  //初始化CPU

    object_property_set_str(OBJECT(&s->cpus), "cpu-type", ms->cpu_type, &error_abort);      
    object_property_set_int(OBJECT(&s->cpus), "num-harts", ms->smp.cpus, &error_abort);
    sysbus_realize(SYS_BUS_DEVICE(&s->cpus), &error_abort);            		//CPU实例化
}

static void nuclei_n_soc_memory_create(Object *obj)
{
	NucLeiNSoCState *s = NUCLEI_N_SOC(obj);
	const MemMapEntry *memmap = nuclei_n_memmap;
	MemoryRegion *sys_mem = get_system_memory();

	/* Internal ROM */
	memory_region_init_rom(&s->internal_rom, OBJECT(obj), "riscv.nuclei.n.irom", memmap[NUCLEI_N_ROM].size, &error_fatal);
    memory_region_add_subregion(sys_mem, memmap[NUCLEI_N_ROM].base, &s->internal_rom);

		
	/* Initialize ilm dlm */
    memory_region_init_ram(&s->ilm, NULL, "riscv.nuclei.n.ilm", memmap[NUCLEI_N_ILM].size, &error_fatal);
    memory_region_add_subregion(sys_mem, memmap[NUCLEI_N_ILM].base, &s->ilm);
    memory_region_init_ram(&s->dlm, NULL, "riscv.nuclei.n.dlm", memmap[NUCLEI_N_DLM].size, &error_fatal);
    memory_region_add_subregion(sys_mem, memmap[NUCLEI_N_DLM].base, &s->dlm);

	 /* SysTimer */
    create_unimplemented_device("riscv.nuclei.n.timer",
    memmap[NUCLEI_N_TIMER].base, memmap[NUCLEI_N_TIMER].size);
    /* Eclic */
    create_unimplemented_device("riscv.nuclei.n.eclic",
    memmap[NUCLEI_N_ECLIC].base, memmap[NUCLEI_N_ECLIC].size);
    /* GPIO */
    create_unimplemented_device("riscv.nuclei.n.gpio",
    memmap[NUCLEI_N_GPIO].base, memmap[NUCLEI_N_GPIO].size);
}

static void nuclei_n_soc_instance_init(Object *obj)
{
	/* SOC CPU */
	nuclei_n_soc_cpu_create(obj);
	/* SOC Memory */
	nuclei_n_soc_memory_create(obj);
}

static void nuclei_n_soc_class_init(ObjectClass *oc, void *data)
{
	DeviceClass *dc = DEVICE_CLASS(oc);
	dc->user_creatable = false;
}

static const TypeInfo nuclei_n_soc_type_info = {
	.name = TYPE_NUCLEI_N_SOC,
	.parent = TYPE_DEVICE,
	.instance_size = sizeof(NucLeiNSoCState),
	.instance_init = nuclei_n_soc_instance_init,
	.class_init = nuclei_n_soc_class_init,
};
static void nuclei_n_soc_register_types(void)
{
	type_register_static(&nuclei_n_soc_type_info);
}
type_init(nuclei_n_soc_register_types)

static void nuclei_mcu_machine_init(MachineState *machine)
{
	NucLeiNState *s = MCU_FPGA_MACHINE(machine);
	const MemMapEntry *memmap = nuclei_n_memmap;
    target_ulong start_addr;
	int i;

	/* Initialize SOC */
	object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_NUCLEI_N_SOC);
	qdev_realize(DEVICE(&s->soc), NULL, &error_abort);

	//选择启动方式
     switch (s->msel)
    {
    case MSEL_ILM:
        start_addr = memmap[NUCLEI_N_ILM].base;
        break;
    case MSEL_FLASH:
        start_addr = memmap[NUCLEI_N_XIP].base;
        break;
    case MSEL_FLASHXIP:
        start_addr = memmap[NUCLEI_N_XIP].base;
        break;
    case MSEL_DDR:
        start_addr = memmap[NUCLEI_N_DRAM].base;
        break;
    default:
        start_addr = memmap[NUCLEI_N_ILM].base;
        break;
    }

       /* reset vector */
    uint32_t reset_vec[8] = {
        0x00000297, /* 1:  auipc  t0, %pcrel_hi(dtb) */
        0x02028593, /*     addi   a1, t0, %pcrel_lo(1b) */
        0xf1402573, /*     csrr   a0, mhartid  */
#if defined(TARGET_RISCV32)
        0x0182a283, /*     lw     t0, 24(t0) */
#elif defined(TARGET_RISCV64)
        0x0182b283, /*     ld     t0, 24(t0) */
#endif
        0x00028067, /*     jr     t0 */
        0x00000000,
        start_addr, /* start: .dword DRAM_BASE */
        0x00000000,
    };

    /* copy in the reset vector in little_endian byte order */
    for (i = 0; i < sizeof(reset_vec) >> 2; i++)
    {
        reset_vec[i] = cpu_to_le32(reset_vec[i]);
    }
    rom_add_blob_fixed_as("mrom.reset", reset_vec, sizeof(reset_vec),
                          memmap[NUCLEI_N_ROM].base, &address_space_memory); //CPU初始化地址

    /* boot rom */
    if (machine->kernel_filename)
    {
		riscv_load_kernel(machine, &s->soc.cpus, start_addr, true, NULL);	//将裸机代码加载到地址start_addr
       // riscv_load_kernel(machine->kernel_filename, start_addr, NULL);  
    }
}


static void nuclei_machine_class_init(ObjectClass *oc, void *data)
{
	MachineClass *mc = MACHINE_CLASS(oc);
    mc->desc = "Nuclei MCU 200T FPGA Evaluation Kit";
	mc->init = nuclei_mcu_machine_init;
	mc->max_cpus = 1;
    mc->default_cpu_type = NUCLEI_N_CPU;
}

static const TypeInfo nuclei_machine_typeinfo = {
	.name = TYPE_NUCLEI_MCU_FPGA_MACHINE,
	.parent = TYPE_MACHINE,
	.class_init = nuclei_machine_class_init,
	.instance_size = sizeof(NucLeiNState),
};
static void nuclei_machine_init_register_types(void)
{
	type_register_static(&nuclei_machine_typeinfo);
}
type_init(nuclei_machine_init_register_types)


参考资料

  1. [完结]从零开始的RISC-V模拟器开发·第一季·2021春季
  2. 新建quard-star开发板

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电力能源三维可视化合集 | 图扑数字孪生

电力能源是现代社会发展和运行的基石&#xff0c;渗透于工业、商业、农业、家庭生活等方方面面&#xff0c;它为经济、生活质量、环境保护和社会发展提供了巨大的机会和潜力。图扑软件应用自研 HT for Web 强大的渲染引擎&#xff0c;助力现代化的电力能源数字孪生场景&#xf…

新手也能看懂的【前端自动化测试入门】!

前言 最近在网上搜索前端自动化测试相关的文档&#xff0c;但是发现网上的文章都是偏使用&#xff0c;没有把一些基础概念说清楚&#xff0c;导致后续一口气遇到一些karma、Jasmine、jest、Mocha、Chai、BDD等词汇的时候很容易一头雾水&#xff0c;这次一方面整理一下收获的知…

Mysql运维篇(一) 日志类型

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人&#xff0c;如有侵权请留言&#xff0c;我及时删除。 一、mysql相关日志 首先&#xff0c;我们能接触到的&#xff0c;一般我们排查慢查询时&#xff0c;会去看慢查询…

Dicom标准里的 RescaleType

DCM_RescaleType 0x0028, 0x1054 这个 HU 和 us 是代表什么含义 之前去一个公司面试&#xff0c;问我&#xff0c; MR里灰阶是什么 CT里才叫CT值&#xff0c; MR里叫什么呢&#xff1f; DICOMLookup

LabVIEW振动筛螺栓松动故障诊断

LabVIEW振动筛螺栓松动故障诊断 概述&#xff1a;利用LabVIEW解决振动筛螺栓松动的故障诊断问题。通过集成的方法&#xff0c;不仅提高了故障检测的准确性&#xff0c;还优化了维护流程&#xff0c;为类似的机械设备故障提供了可靠的解决方案。 由于工作条件复杂&#xff0c;…

MySQL(五)——多表查询

上期文章 MySQL&#xff08;四&#xff09;——约束 文章目录 上期文章多表关系一对多&#xff08;多对一&#xff09;多对多多表外键关系可视化一对一 多表查询概述笛卡尔积多表查询分类连接查询 内连接隐式内连接显式内连接 外连接左外连接右外连接 自连接联合查询 union&am…

Redis 安装与入门,全文干货

1、简介 Redis 是一个开源的&#xff0c;基于内存的数据存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构&#xff0c;如字符串&#xff08;strings&#xff09;&#xff0c;散列&#xff08;hashes&#xff09;&#xff0c;列表&#xff08…

非科班转码的秋招复盘:地理信息科学GIS专业到后端研发、软件开发

本文介绍地理信息科学&#xff08;GIS&#xff09;专业的2024届应届生&#xff0c;在研三上学期期间&#xff0c;寻找后端研发、软件开发等IT方向工作的非科班转码秋招情况。 首先&#xff0c;这篇文章一开始写于2023年年底&#xff0c;当时为了参加一个征文活动&#xff0c;所…

多维表格产品vika多维表、Flowus、Wolai体验记录

昨天从下午6点肝到凌晨2点多体验低代码平台多维表格产品&#xff0c;体验了3个国内产品&#xff0c;vika多维表、Flowus、Wolai。 具有多维表格新型关系数据库的鼻祖是 Airtable&#xff0c;国内模仿产品有vika多维表、飞书多维表格等。 还有一种类型就是以在国内鼎鼎大名的N…

【Linux】信号量基于环形队列的生产消费模型

信号量 信号量的本质是一个计数器&#xff0c;可以用来衡量临界资源中资源数量多少 信号量的PV操作 P操作&#xff1a;申请信号量称为P操作&#xff0c;P操作的本质就是让计数器减1。 V操作&#xff1a;释放信号量称为V操作&#xff0c;V操作的本质就是让计数器加1 POSIX信号量…

javaWebssh运动会管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh运动会管理系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,M…

SystemVerilog中数组内置函数sum()的一个注意点

Systemverilog内置了数组求和运算方法(sum())&#xff0c;将数组的所有元素累加起来&#xff0c;返回一个最终值。在使用时要注意数组类型的位宽&#xff0c;通常情况下&#xff0c;如果你将一组单bit的值加起来&#xff0c;Systemverilog会使用足够的精度来确保不丢失任何bit的…

Flink处理函数(2)—— 按键分区处理函数

按键分区处理函数&#xff08;KeyedProcessFunction&#xff09;&#xff1a;先进行分区&#xff0c;然后定义处理操作 1.定时器&#xff08;Timer&#xff09;和定时服务&#xff08;TimerService&#xff09; 定时器&#xff08;timers&#xff09;是处理函数中进行时间相关…

python开发之远程开发工具对比

前言 除了本地开发外&#xff0c;还有一种常见的开发方式就是远程开发&#xff0c;一般情况是一台Windows或mac笔记本作为日常使用的电脑&#xff0c;另有一台linux服务器作为开发服务器。开发服务器的性能往往较强&#xff0c;这样远程开发的方式一方面可以让我们在习惯的系统…

PWM实现呼吸灯

PWM也属于51中的重要章节&#xff0c;本节主要介绍呼吸灯&#xff0c;目的是理解PWM的工作原理&#xff0c;PWM的实验案例重点还得看后续的舵机&#xff08;下一节会讲到&#xff09; 那么何为呼吸灯。呼吸灯的定义是&#xff1a;灯光实现由亮到暗的变化或由暗到亮的逐渐变化。…

一篇文章搞懂什么是测试,测试是干什么的?

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

信号处理专题设计-基于边缘检测的数字图像分类识别

目录 一、实验目的 二、实验要求 三、实验原理 1.卷积神经网络&#xff08;CNN&#xff09;模型 2.边缘检测 3.形态学操作 4.鲁棒性 四、实验过程 1.数据预处理 2. 网络的构建 3.模型的训练 4.边缘检测和形态学操作相关代码 5.模型训练结果 6.关键信息的保存 五、实验测试与评估…

网络安全全栈培训笔记(54-服务攻防-数据库安全RedisHadoopMysqla未授权访问RCE)

第54天 服务攻防-数据库安全&Redis&Hadoop&Mysqla&未授权访问&RCE 知识点&#xff1a; 1、服务攻防数据库类型安全 2、Redis&Hadoop&Mysql安全 3、Mysql-CVE-2012-2122漏洞 4、Hadoop-配置不当未授权三重奏&RCE漏洞 3、Redis-配置不当未授权…

金蝶云星空表单插件获取单据体数据

文章目录 金蝶云星空表单插件获取单据体数据 金蝶云星空表单插件获取单据体数据 使用标识报错 var thisEntry this.View.Model.DataObject["FEntity"] as DynamicObjectCollection;应该使用实体属性 var thisEntry this.View.Model.DataObject["BillEntry&q…

Python连接数据库的梳理

我们通常用的数据库类型主要有关系型数据库&#xff0c;非关系型数据库等&#xff0c;其中关系型数据库主要有Microsoft SQL Server ,MySQL,Oracle&#xff0c;SQLite等&#xff0c;常用的非关系型数据库包括Redis、DynamoDB&#xff0c;MongoDB等 ​​​​​​​ 一 关系型…