diff options
Diffstat (limited to '')
47 files changed, 2695 insertions, 1351 deletions
diff --git a/docs/devel/rust.rst b/docs/devel/rust.rst index 5d8aa3a45b..88bdec1eb2 100644 --- a/docs/devel/rust.rst +++ b/docs/devel/rust.rst @@ -296,15 +296,35 @@ of ``&mut self``; access to internal fields must use *interior mutability* to go from a shared reference to a ``&mut``. Whenever C code provides you with an opaque ``void *``, avoid converting it -to a Rust mutable reference, and use a shared reference instead. Rust code -will then have to use QEMU's ``BqlRefCell`` and ``BqlCell`` type, which -enforce that locking rules for the "Big QEMU Lock" are respected. These cell -types are also known to the ``vmstate`` crate, which is able to "look inside" -them when building an in-memory representation of a ``struct``'s layout. -Note that the same is not true of a ``RefCell`` or ``Mutex``. - -In the future, similar cell types might also be provided for ``AioContext``-based -locking as well. +to a Rust mutable reference, and use a shared reference instead. The +``qemu_api::cell`` module provides wrappers that can be used to tell the +Rust compiler about interior mutability, and optionally to enforce locking +rules for the "Big QEMU Lock". In the future, similar cell types might +also be provided for ``AioContext``-based locking as well. + +In particular, device code will usually rely on the ``BqlRefCell`` and +``BqlCell`` type to ensure that data is accessed correctly under the +"Big QEMU Lock". These cell types are also known to the ``vmstate`` +crate, which is able to "look inside" them when building an in-memory +representation of a ``struct``'s layout. Note that the same is not true +of a ``RefCell`` or ``Mutex``. + +Bindings code instead will usually use the ``Opaque`` type, which hides +the contents of the underlying struct and can be easily converted to +a raw pointer, for use in calls to C functions. It can be used for +example as follows:: + + #[repr(transparent)] + #[derive(Debug, qemu_api_macros::Wrapper)] + pub struct Object(Opaque<bindings::Object>); + +where the special ``derive`` macro provides useful methods such as +``from_raw``, ``as_ptr`, ``as_mut_ptr`` and ``raw_get``. The bindings will +then manually check for the big QEMU lock with assertions, which allows +the wrapper to be declared thread-safe:: + + unsafe impl Send for Object {} + unsafe impl Sync for Object {} Writing bindings to C code '''''''''''''''''''''''''' diff --git a/docs/specs/aspeed-intc.rst b/docs/specs/aspeed-intc.rst new file mode 100644 index 0000000000..9cefd7f37f --- /dev/null +++ b/docs/specs/aspeed-intc.rst @@ -0,0 +1,136 @@ +=========================== +ASPEED Interrupt Controller +=========================== + +AST2700 +------- +There are a total of 480 interrupt sources in AST2700. Due to the limitation of +interrupt numbers of processors, the interrupts are merged every 32 sources for +interrupt numbers greater than 127. + +There are two levels of interrupt controllers, INTC (CPU Die) and INTCIO +(I/O Die). + +Interrupt Mapping +----------------- +- INTC: Handles interrupt sources 0 - 127 and integrates signals from INTCIO. +- INTCIO: Handles interrupt sources 128 - 319 independently. + +QEMU Support +------------ +Currently, only GIC 192 to 201 are supported, and their source interrupts are +from INTCIO and connected to INTC at input pin 0 and output pins 0 to 9 for +GIC 192-201. + +Design for GICINT 196 +--------------------- +The orgate has interrupt sources ranging from 0 to 31, with its output pin +connected to INTCIO "T0 GICINT_196". The output pin is then connected to INTC +"GIC_192_201" at bit 4, and its bit 4 output pin is connected to GIC 196. + +INTC GIC_192_201 Output Pin Mapping +----------------------------------- +The design of INTC GIC_192_201 have 10 output pins, mapped as following: + +==== ==== +Bit GIC +==== ==== +0 192 +1 193 +2 194 +3 195 +4 196 +5 197 +6 198 +7 199 +8 200 +9 201 +==== ==== + +AST2700 A0 +---------- +It has only one INTC controller, and currently, only GIC 128-136 is supported. +To support both AST2700 A1 and AST2700 A0, there are 10 OR gates in the INTC, +with gates 1 to 9 supporting GIC 128-136. + +Design for GICINT 132 +--------------------- +The orgate has interrupt sources ranging from 0 to 31, with its output pin +connected to INTC. The output pin is then connected to GIC 132. + +Block Diagram of GICINT 196 for AST2700 A1 and GICINT 132 for AST2700 A0 +------------------------------------------------------------------------ + +.. code-block:: + + |-------------------------------------------------------------------------------------------------------| + | AST2700 A1 Design | + | To GICINT196 | + | | + | ETH1 |-----------| |--------------------------| |--------------| | + | -------->|0 | | INTCIO | | orgates[0] | | + | ETH2 | 4| orgates[0]------>|inpin[0]-------->outpin[0]|------->| 0 | | + | -------->|1 5| orgates[1]------>|inpin[1]-------->outpin[1]|------->| 1 | | + | ETH3 | 6| orgates[2]------>|inpin[2]-------->outpin[2]|------->| 2 | | + | -------->|2 19| orgates[3]------>|inpin[3]-------->outpin[3]|------->| 3 OR[0:9] |-----| | + | UART0 | 20|-->orgates[4]------>|inpin[4]-------->outpin[4]|------->| 4 | | | + | -------->|7 21| orgates[5]------>|inpin[5]-------->outpin[5]|------->| 5 | | | + | UART1 | 22| orgates[6]------>|inpin[6]-------->outpin[6]|------->| 6 | | | + | -------->|8 23| orgates[7]------>|inpin[7]-------->outpin[7]|------->| 7 | | | + | UART2 | 24| orgates[8]------>|inpin[8]-------->outpin[8]|------->| 8 | | | + | -------->|9 25| orgates[9]------>|inpin[9]-------->outpin[9]|------->| 9 | | | + | UART3 | 26| |--------------------------| |--------------| | | + | ---------|10 27| | | + | UART5 | 28| | | + | -------->|11 29| | | + | UART6 | | | | + | -------->|12 30| |-----------------------------------------------------------------------| | + | UART7 | 31| | | + | -------->|13 | | | + | UART8 | OR[0:31] | | |------------------------------| |----------| | + | -------->|14 | | | INTC | | GIC | | + | UART9 | | | |inpin[0:0]--------->outpin[0] |---------->|192 | | + | -------->|15 | | |inpin[0:1]--------->outpin[1] |---------->|193 | | + | UART10 | | | |inpin[0:2]--------->outpin[2] |---------->|194 | | + | -------->|16 | | |inpin[0:3]--------->outpin[3] |---------->|195 | | + | UART11 | | |--------------> |inpin[0:4]--------->outpin[4] |---------->|196 | | + | -------->|17 | |inpin[0:5]--------->outpin[5] |---------->|197 | | + | UART12 | | |inpin[0:6]--------->outpin[6] |---------->|198 | | + | -------->|18 | |inpin[0:7]--------->outpin[7] |---------->|199 | | + | |-----------| |inpin[0:8]--------->outpin[8] |---------->|200 | | + | |inpin[0:9]--------->outpin[9] |---------->|201 | | + |-------------------------------------------------------------------------------------------------------| + |-------------------------------------------------------------------------------------------------------| + | ETH1 |-----------| orgates[1]------->|inpin[1]----------->outpin[10]|---------->|128 | | + | -------->|0 | orgates[2]------->|inpin[2]----------->outpin[11]|---------->|129 | | + | ETH2 | 4| orgates[3]------->|inpin[3]----------->outpin[12]|---------->|130 | | + | -------->|1 5| orgates[4]------->|inpin[4]----------->outpin[13]|---------->|131 | | + | ETH3 | 6|---->orgates[5]------->|inpin[5]----------->outpin[14]|---------->|132 | | + | -------->|2 19| orgates[6]------->|inpin[6]----------->outpin[15]|---------->|133 | | + | UART0 | 20| orgates[7]------->|inpin[7]----------->outpin[16]|---------->|134 | | + | -------->|7 21| orgates[8]------->|inpin[8]----------->outpin[17]|---------->|135 | | + | UART1 | 22| orgates[9]------->|inpin[9]----------->outpin[18]|---------->|136 | | + | -------->|8 23| |------------------------------| |----------| | + | UART2 | 24| | + | -------->|9 25| AST2700 A0 Design | + | UART3 | 26| | + | -------->|10 27| | + | UART5 | 28| | + | -------->|11 29| GICINT132 | + | UART6 | | | + | -------->|12 30| | + | UART7 | 31| | + | -------->|13 | | + | UART8 | OR[0:31] | | + | -------->|14 | | + | UART9 | | | + | -------->|15 | | + | UART10 | | | + | -------->|16 | | + | UART11 | | | + | -------->|17 | | + | UART12 | | | + | -------->|18 | | + | |-----------| | + | | + |-------------------------------------------------------------------------------------------------------| diff --git a/docs/specs/index.rst b/docs/specs/index.rst index d7675cebc2..f19d73c9f6 100644 --- a/docs/specs/index.rst +++ b/docs/specs/index.rst @@ -38,3 +38,4 @@ guest hardware that is specific to QEMU. rocker riscv-iommu riscv-aia + aspeed-intc diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c index 98bf071139..82f42582fa 100644 --- a/hw/arm/aspeed.c +++ b/hw/arm/aspeed.c @@ -181,8 +181,10 @@ struct AspeedMachineState { #ifdef TARGET_AARCH64 /* AST2700 evb hardware value */ -#define AST2700_EVB_HW_STRAP1 0x000000C0 -#define AST2700_EVB_HW_STRAP2 0x00000003 +/* SCU HW Strap1 */ +#define AST2700_EVB_HW_STRAP1 0x00000800 +/* SCUIO HW Strap1 */ +#define AST2700_EVB_HW_STRAP2 0x00000700 #endif /* Rainier hardware value: (QEMU prototype) */ @@ -1671,12 +1673,13 @@ static void ast2700_evb_i2c_init(AspeedMachineState *bmc) TYPE_TMP105, 0x4d); } -static void aspeed_machine_ast2700_evb_class_init(ObjectClass *oc, void *data) +static void aspeed_machine_ast2700a0_evb_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc); - mc->desc = "Aspeed AST2700 EVB (Cortex-A35)"; + mc->alias = "ast2700-evb"; + mc->desc = "Aspeed AST2700 A0 EVB (Cortex-A35)"; amc->soc_name = "ast2700-a0"; amc->hw_strap1 = AST2700_EVB_HW_STRAP1; amc->hw_strap2 = AST2700_EVB_HW_STRAP2; @@ -1690,6 +1693,26 @@ static void aspeed_machine_ast2700_evb_class_init(ObjectClass *oc, void *data) mc->default_ram_size = 1 * GiB; aspeed_machine_class_init_cpus_defaults(mc); } + +static void aspeed_machine_ast2700a1_evb_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + AspeedMachineClass *amc = ASPEED_MACHINE_CLASS(oc); + + mc->desc = "Aspeed AST2700 A1 EVB (Cortex-A35)"; + amc->soc_name = "ast2700-a1"; + amc->hw_strap1 = AST2700_EVB_HW_STRAP1; + amc->hw_strap2 = AST2700_EVB_HW_STRAP2; + amc->fmc_model = "w25q01jvq"; + amc->spi_model = "w25q512jv"; + amc->num_cs = 2; + amc->macs_mask = ASPEED_MAC0_ON | ASPEED_MAC1_ON | ASPEED_MAC2_ON; + amc->uart_default = ASPEED_DEV_UART12; + amc->i2c_init = ast2700_evb_i2c_init; + mc->auto_create_sdcard = true; + mc->default_ram_size = 1 * GiB; + aspeed_machine_class_init_cpus_defaults(mc); +} #endif static void aspeed_machine_qcom_dc_scm_v1_class_init(ObjectClass *oc, @@ -1815,9 +1838,13 @@ static const TypeInfo aspeed_machine_types[] = { .class_init = aspeed_minibmc_machine_ast1030_evb_class_init, #ifdef TARGET_AARCH64 }, { - .name = MACHINE_TYPE_NAME("ast2700-evb"), + .name = MACHINE_TYPE_NAME("ast2700a0-evb"), + .parent = TYPE_ASPEED_MACHINE, + .class_init = aspeed_machine_ast2700a0_evb_class_init, + }, { + .name = MACHINE_TYPE_NAME("ast2700a1-evb"), .parent = TYPE_ASPEED_MACHINE, - .class_init = aspeed_machine_ast2700_evb_class_init, + .class_init = aspeed_machine_ast2700a1_evb_class_init, #endif }, { .name = TYPE_ASPEED_MACHINE, diff --git a/hw/arm/aspeed_ast10x0.c b/hw/arm/aspeed_ast10x0.c index e76c7100a1..ec329f4991 100644 --- a/hw/arm/aspeed_ast10x0.c +++ b/hw/arm/aspeed_ast10x0.c @@ -116,7 +116,7 @@ static void aspeed_soc_ast1030_init(Object *obj) char typename[64]; int i; - if (sscanf(sc->name, "%7s", socname) != 1) { + if (sscanf(object_get_typename(obj), "%7s", socname) != 1) { g_assert_not_reached(); } @@ -428,7 +428,6 @@ static void aspeed_soc_ast1030_class_init(ObjectClass *klass, void *data) dc->user_creatable = false; dc->realize = aspeed_soc_ast1030_realize; - sc->name = "ast1030-a1"; sc->valid_cpu_types = valid_cpu_types; sc->silicon_rev = AST1030_A1_SILICON_REV; sc->sram_size = 0xc0000; diff --git a/hw/arm/aspeed_ast2400.c b/hw/arm/aspeed_ast2400.c index 8784b6e793..0158f6e9c2 100644 --- a/hw/arm/aspeed_ast2400.c +++ b/hw/arm/aspeed_ast2400.c @@ -151,7 +151,7 @@ static void aspeed_ast2400_soc_init(Object *obj) char socname[8]; char typename[64]; - if (sscanf(sc->name, "%7s", socname) != 1) { + if (sscanf(object_get_typename(obj), "%7s", socname) != 1) { g_assert_not_reached(); } @@ -515,7 +515,6 @@ static void aspeed_soc_ast2400_class_init(ObjectClass *oc, void *data) /* Reason: Uses serial_hds and nd_table in realize() directly */ dc->user_creatable = false; - sc->name = "ast2400-a1"; sc->valid_cpu_types = valid_cpu_types; sc->silicon_rev = AST2400_A1_SILICON_REV; sc->sram_size = 0x8000; @@ -544,7 +543,6 @@ static void aspeed_soc_ast2500_class_init(ObjectClass *oc, void *data) /* Reason: Uses serial_hds and nd_table in realize() directly */ dc->user_creatable = false; - sc->name = "ast2500-a1"; sc->valid_cpu_types = valid_cpu_types; sc->silicon_rev = AST2500_A1_SILICON_REV; sc->sram_size = 0x9000; diff --git a/hw/arm/aspeed_ast2600.c b/hw/arm/aspeed_ast2600.c index 07210483bb..1f994ba26c 100644 --- a/hw/arm/aspeed_ast2600.c +++ b/hw/arm/aspeed_ast2600.c @@ -157,7 +157,7 @@ static void aspeed_soc_ast2600_init(Object *obj) char socname[8]; char typename[64]; - if (sscanf(sc->name, "%7s", socname) != 1) { + if (sscanf(object_get_typename(obj), "%7s", socname) != 1) { g_assert_not_reached(); } @@ -666,7 +666,6 @@ static void aspeed_soc_ast2600_class_init(ObjectClass *oc, void *data) /* Reason: The Aspeed SoC can only be instantiated from a board */ dc->user_creatable = false; - sc->name = "ast2600-a3"; sc->valid_cpu_types = valid_cpu_types; sc->silicon_rev = AST2600_A3_SILICON_REV; sc->sram_size = 0x16400; diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c index 2d0c99f159..dce7255a2c 100644 --- a/hw/arm/aspeed_ast27x0.c +++ b/hw/arm/aspeed_ast27x0.c @@ -24,16 +24,40 @@ #include "qemu/log.h" static const hwaddr aspeed_soc_ast2700_memmap[] = { - [ASPEED_DEV_SPI_BOOT] = 0x400000000, [ASPEED_DEV_SRAM] = 0x10000000, + [ASPEED_DEV_HACE] = 0x12070000, + [ASPEED_DEV_EMMC] = 0x12090000, + [ASPEED_DEV_INTC] = 0x12100000, + [ASPEED_GIC_DIST] = 0x12200000, + [ASPEED_GIC_REDIST] = 0x12280000, [ASPEED_DEV_SDMC] = 0x12C00000, [ASPEED_DEV_SCU] = 0x12C02000, + [ASPEED_DEV_RTC] = 0x12C0F000, + [ASPEED_DEV_TIMER1] = 0x12C10000, + [ASPEED_DEV_SLI] = 0x12C17000, + [ASPEED_DEV_UART4] = 0X12C1A000, + [ASPEED_DEV_FMC] = 0x14000000, + [ASPEED_DEV_SPI0] = 0x14010000, + [ASPEED_DEV_SPI1] = 0x14020000, + [ASPEED_DEV_SPI2] = 0x14030000, + [ASPEED_DEV_MII1] = 0x14040000, + [ASPEED_DEV_MII2] = 0x14040008, + [ASPEED_DEV_MII3] = 0x14040010, + [ASPEED_DEV_ETH1] = 0x14050000, + [ASPEED_DEV_ETH2] = 0x14060000, + [ASPEED_DEV_ETH3] = 0x14070000, + [ASPEED_DEV_SDHCI] = 0x14080000, + [ASPEED_DEV_ADC] = 0x14C00000, [ASPEED_DEV_SCUIO] = 0x14C02000, + [ASPEED_DEV_GPIO] = 0x14C0B000, + [ASPEED_DEV_I2C] = 0x14C0F000, + [ASPEED_DEV_INTCIO] = 0x14C18000, + [ASPEED_DEV_SLIIO] = 0x14C1E000, + [ASPEED_DEV_VUART] = 0X14C30000, [ASPEED_DEV_UART0] = 0X14C33000, [ASPEED_DEV_UART1] = 0X14C33100, [ASPEED_DEV_UART2] = 0X14C33200, [ASPEED_DEV_UART3] = 0X14C33300, - [ASPEED_DEV_UART4] = 0X12C1A000, [ASPEED_DEV_UART5] = 0X14C33400, [ASPEED_DEV_UART6] = 0X14C33500, [ASPEED_DEV_UART7] = 0X14C33600, @@ -43,41 +67,44 @@ static const hwaddr aspeed_soc_ast2700_memmap[] = { [ASPEED_DEV_UART11] = 0X14C33A00, [ASPEED_DEV_UART12] = 0X14C33B00, [ASPEED_DEV_WDT] = 0x14C37000, - [ASPEED_DEV_VUART] = 0X14C30000, - [ASPEED_DEV_FMC] = 0x14000000, - [ASPEED_DEV_SPI0] = 0x14010000, - [ASPEED_DEV_SPI1] = 0x14020000, - [ASPEED_DEV_SPI2] = 0x14030000, + [ASPEED_DEV_SPI_BOOT] = 0x100000000, [ASPEED_DEV_SDRAM] = 0x400000000, - [ASPEED_DEV_MII1] = 0x14040000, - [ASPEED_DEV_MII2] = 0x14040008, - [ASPEED_DEV_MII3] = 0x14040010, - [ASPEED_DEV_ETH1] = 0x14050000, - [ASPEED_DEV_ETH2] = 0x14060000, - [ASPEED_DEV_ETH3] = 0x14070000, - [ASPEED_DEV_EMMC] = 0x12090000, - [ASPEED_DEV_INTC] = 0x12100000, - [ASPEED_DEV_SLI] = 0x12C17000, - [ASPEED_DEV_SLIIO] = 0x14C1E000, - [ASPEED_GIC_DIST] = 0x12200000, - [ASPEED_GIC_REDIST] = 0x12280000, - [ASPEED_DEV_ADC] = 0x14C00000, - [ASPEED_DEV_I2C] = 0x14C0F000, - [ASPEED_DEV_GPIO] = 0x14C0B000, - [ASPEED_DEV_RTC] = 0x12C0F000, - [ASPEED_DEV_SDHCI] = 0x14080000, - [ASPEED_DEV_TIMER1] = 0x12C10000, }; #define AST2700_MAX_IRQ 256 /* Shared Peripheral Interrupt values below are offset by -32 from datasheet */ -static const int aspeed_soc_ast2700_irqmap[] = { +static const int aspeed_soc_ast2700a0_irqmap[] = { + [ASPEED_DEV_SDMC] = 0, + [ASPEED_DEV_HACE] = 4, + [ASPEED_DEV_XDMA] = 5, + [ASPEED_DEV_UART4] = 8, + [ASPEED_DEV_SCU] = 12, + [ASPEED_DEV_RTC] = 13, + [ASPEED_DEV_EMMC] = 15, + [ASPEED_DEV_TIMER1] = 16, + [ASPEED_DEV_TIMER2] = 17, + [ASPEED_DEV_TIMER3] = 18, + [ASPEED_DEV_TIMER4] = 19, + [ASPEED_DEV_TIMER5] = 20, + [ASPEED_DEV_TIMER6] = 21, + [ASPEED_DEV_TIMER7] = 22, + [ASPEED_DEV_TIMER8] = 23, + [ASPEED_DEV_DP] = 28, + [ASPEED_DEV_LPC] = 128, + [ASPEED_DEV_IBT] = 128, + [ASPEED_DEV_KCS] = 128, + [ASPEED_DEV_ADC] = 130, + [ASPEED_DEV_GPIO] = 130, + [ASPEED_DEV_I2C] = 130, + [ASPEED_DEV_FMC] = 131, + [ASPEED_DEV_WDT] = 131, + [ASPEED_DEV_PWM] = 131, + [ASPEED_DEV_I3C] = 131, [ASPEED_DEV_UART0] = 132, [ASPEED_DEV_UART1] = 132, [ASPEED_DEV_UART2] = 132, [ASPEED_DEV_UART3] = 132, - [ASPEED_DEV_UART4] = 8, [ASPEED_DEV_UART5] = 132, [ASPEED_DEV_UART6] = 132, [ASPEED_DEV_UART7] = 132, @@ -86,14 +113,21 @@ static const int aspeed_soc_ast2700_irqmap[] = { [ASPEED_DEV_UART10] = 132, [ASPEED_DEV_UART11] = 132, [ASPEED_DEV_UART12] = 132, - [ASPEED_DEV_FMC] = 131, + [ASPEED_DEV_ETH1] = 132, + [ASPEED_DEV_ETH2] = 132, + [ASPEED_DEV_ETH3] = 132, + [ASPEED_DEV_PECI] = 133, + [ASPEED_DEV_SDHCI] = 133, +}; + +static const int aspeed_soc_ast2700a1_irqmap[] = { [ASPEED_DEV_SDMC] = 0, - [ASPEED_DEV_SCU] = 12, - [ASPEED_DEV_ADC] = 130, + [ASPEED_DEV_HACE] = 4, [ASPEED_DEV_XDMA] = 5, - [ASPEED_DEV_EMMC] = 15, - [ASPEED_DEV_GPIO] = 130, + [ASPEED_DEV_UART4] = 8, + [ASPEED_DEV_SCU] = 12, [ASPEED_DEV_RTC] = 13, + [ASPEED_DEV_EMMC] = 15, [ASPEED_DEV_TIMER1] = 16, [ASPEED_DEV_TIMER2] = 17, [ASPEED_DEV_TIMER3] = 18, @@ -102,38 +136,58 @@ static const int aspeed_soc_ast2700_irqmap[] = { [ASPEED_DEV_TIMER6] = 21, [ASPEED_DEV_TIMER7] = 22, [ASPEED_DEV_TIMER8] = 23, - [ASPEED_DEV_WDT] = 131, - [ASPEED_DEV_PWM] = 131, - [ASPEED_DEV_LPC] = 128, - [ASPEED_DEV_IBT] = 128, - [ASPEED_DEV_I2C] = 130, - [ASPEED_DEV_PECI] = 133, - [ASPEED_DEV_ETH1] = 132, - [ASPEED_DEV_ETH2] = 132, - [ASPEED_DEV_ETH3] = 132, - [ASPEED_DEV_HACE] = 4, - [ASPEED_DEV_KCS] = 128, [ASPEED_DEV_DP] = 28, - [ASPEED_DEV_I3C] = 131, - [ASPEED_DEV_SDHCI] = 133, + [ASPEED_DEV_LPC] = 192, + [ASPEED_DEV_IBT] = 192, + [ASPEED_DEV_KCS] = 192, + [ASPEED_DEV_I2C] = 194, + [ASPEED_DEV_ADC] = 194, + [ASPEED_DEV_GPIO] = 194, + [ASPEED_DEV_FMC] = 195, + [ASPEED_DEV_WDT] = 195, + [ASPEED_DEV_PWM] = 195, + [ASPEED_DEV_I3C] = 195, + [ASPEED_DEV_UART0] = 196, + [ASPEED_DEV_UART1] = 196, + [ASPEED_DEV_UART2] = 196, + [ASPEED_DEV_UART3] = 196, + [ASPEED_DEV_UART5] = 196, + [ASPEED_DEV_UART6] = 196, + [ASPEED_DEV_UART7] = 196, + [ASPEED_DEV_UART8] = 196, + [ASPEED_DEV_UART9] = 196, + [ASPEED_DEV_UART10] = 196, + [ASPEED_DEV_UART11] = 196, + [ASPEED_DEV_UART12] = 196, + [ASPEED_DEV_ETH1] = 196, + [ASPEED_DEV_ETH2] = 196, + [ASPEED_DEV_ETH3] = 196, + [ASPEED_DEV_PECI] = 197, + [ASPEED_DEV_SDHCI] = 197, }; /* GICINT 128 */ -static const int aspeed_soc_ast2700_gic128_intcmap[] = { +/* GICINT 192 */ +static const int ast2700_gic128_gic192_intcmap[] = { [ASPEED_DEV_LPC] = 0, [ASPEED_DEV_IBT] = 2, [ASPEED_DEV_KCS] = 4, }; +/* GICINT 129 */ +/* GICINT 193 */ + /* GICINT 130 */ -static const int aspeed_soc_ast2700_gic130_intcmap[] = { +/* GICINT 194 */ +static const int ast2700_gic130_gic194_intcmap[] = { [ASPEED_DEV_I2C] = 0, [ASPEED_DEV_ADC] = 16, [ASPEED_DEV_GPIO] = 18, }; /* GICINT 131 */ -static const int aspeed_soc_ast2700_gic131_intcmap[] = { +/* GICINT 195 */ +static const int ast2700_gic131_gic195_intcmap[] = { [ASPEED_DEV_I3C] = 0, [ASPEED_DEV_WDT] = 16, [ASPEED_DEV_FMC] = 25, @@ -141,7 +195,8 @@ static const int aspeed_soc_ast2700_gic131_intcmap[] = { }; /* GICINT 132 */ -static const int aspeed_soc_ast2700_gic132_intcmap[] = { +/* GICINT 196 */ +static const int ast2700_gic132_gic196_intcmap[] = { [ASPEED_DEV_ETH1] = 0, [ASPEED_DEV_ETH2] = 1, [ASPEED_DEV_ETH3] = 2, @@ -160,40 +215,58 @@ static const int aspeed_soc_ast2700_gic132_intcmap[] = { }; /* GICINT 133 */ -static const int aspeed_soc_ast2700_gic133_intcmap[] = { +/* GICINT 197 */ +static const int ast2700_gic133_gic197_intcmap[] = { [ASPEED_DEV_SDHCI] = 1, [ASPEED_DEV_PECI] = 4, }; /* GICINT 128 ~ 136 */ +/* GICINT 192 ~ 201 */ struct gic_intc_irq_info { int irq; + int intc_idx; + int orgate_idx; const int *ptr; }; -static const struct gic_intc_irq_info aspeed_soc_ast2700_gic_intcmap[] = { - {128, aspeed_soc_ast2700_gic128_intcmap}, - {129, NULL}, - {130, aspeed_soc_ast2700_gic130_intcmap}, - {131, aspeed_soc_ast2700_gic131_intcmap}, - {132, aspeed_soc_ast2700_gic132_intcmap}, - {133, aspeed_soc_ast2700_gic133_intcmap}, - {134, NULL}, - {135, NULL}, - {136, NULL}, +static const struct gic_intc_irq_info ast2700_gic_intcmap[] = { + {192, 1, 0, ast2700_gic128_gic192_intcmap}, + {193, 1, 1, NULL}, + {194, 1, 2, ast2700_gic130_gic194_intcmap}, + {195, 1, 3, ast2700_gic131_gic195_intcmap}, + {196, 1, 4, ast2700_gic132_gic196_intcmap}, + {197, 1, 5, ast2700_gic133_gic197_intcmap}, + {198, 1, 6, NULL}, + {199, 1, 7, NULL}, + {200, 1, 8, NULL}, + {201, 1, 9, NULL}, + {128, 0, 1, ast2700_gic128_gic192_intcmap}, + {129, 0, 2, NULL}, + {130, 0, 3, ast2700_gic130_gic194_intcmap}, + {131, 0, 4, ast2700_gic131_gic195_intcmap}, + {132, 0, 5, ast2700_gic132_gic196_intcmap}, + {133, 0, 6, ast2700_gic133_gic197_intcmap}, + {134, 0, 7, NULL}, + {135, 0, 8, NULL}, + {136, 0, 9, NULL}, }; static qemu_irq aspeed_soc_ast2700_get_irq(AspeedSoCState *s, int dev) { Aspeed27x0SoCState *a = ASPEED27X0_SOC(s); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); + int or_idx; + int idx; int i; - for (i = 0; i < ARRAY_SIZE(aspeed_soc_ast2700_gic_intcmap); i++) { - if (sc->irqmap[dev] == aspeed_soc_ast2700_gic_intcmap[i].irq) { - assert(aspeed_soc_ast2700_gic_intcmap[i].ptr); - return qdev_get_gpio_in(DEVICE(&a->intc.orgates[i]), - aspeed_soc_ast2700_gic_intcmap[i].ptr[dev]); + for (i = 0; i < ARRAY_SIZE(ast2700_gic_intcmap); i++) { + if (sc->irqmap[dev] == ast2700_gic_intcmap[i].irq) { + assert(ast2700_gic_intcmap[i].ptr); + or_idx = ast2700_gic_intcmap[i].orgate_idx; + idx = ast2700_gic_intcmap[i].intc_idx; + return qdev_get_gpio_in(DEVICE(&a->intc[idx].orgates[or_idx]), + ast2700_gic_intcmap[i].ptr[dev]); } } @@ -205,18 +278,23 @@ static qemu_irq aspeed_soc_ast2700_get_irq_index(AspeedSoCState *s, int dev, { Aspeed27x0SoCState *a = ASPEED27X0_SOC(s); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); + int or_idx; + int idx; int i; - for (i = 0; i < ARRAY_SIZE(aspeed_soc_ast2700_gic_intcmap); i++) { - if (sc->irqmap[dev] == aspeed_soc_ast2700_gic_intcmap[i].irq) { - assert(aspeed_soc_ast2700_gic_intcmap[i].ptr); - return qdev_get_gpio_in(DEVICE(&a->intc.orgates[i]), - aspeed_soc_ast2700_gic_intcmap[i].ptr[dev] + index); + for (i = 0; i < ARRAY_SIZE(ast2700_gic_intcmap); i++) { + if (sc->irqmap[dev] == ast2700_gic_intcmap[i].irq) { + assert(ast2700_gic_intcmap[i].ptr); + or_idx = ast2700_gic_intcmap[i].orgate_idx; + idx = ast2700_gic_intcmap[i].intc_idx; + return qdev_get_gpio_in(DEVICE(&a->intc[idx].orgates[or_idx]), + ast2700_gic_intcmap[i].ptr[dev] + index); } } /* - * Invalid orgate index, device irq should be 128 to 136. + * Invalid OR gate index, device IRQ should be between 128 to 136 + * and 192 to 201. */ g_assert_not_reached(); } @@ -316,7 +394,7 @@ static void aspeed_soc_ast2700_init(Object *obj) char socname[8]; char typename[64]; - if (sscanf(sc->name, "%7s", socname) != 1) { + if (sscanf(object_get_typename(obj), "%7s", socname) != 1) { g_assert_not_reached(); } @@ -332,14 +410,21 @@ static void aspeed_soc_ast2700_init(Object *obj) sc->silicon_rev); object_property_add_alias(obj, "hw-strap1", OBJECT(&s->scu), "hw-strap1"); - object_property_add_alias(obj, "hw-strap2", OBJECT(&s->scu), - "hw-strap2"); object_property_add_alias(obj, "hw-prot-key", OBJECT(&s->scu), "hw-prot-key"); object_initialize_child(obj, "scuio", &s->scuio, TYPE_ASPEED_2700_SCUIO); qdev_prop_set_uint32(DEVICE(&s->scuio), "silicon-rev", sc->silicon_rev); + /* + * There is one hw-strap1 register in the SCU (CPU DIE) and another + * hw-strap1 register in the SCUIO (IO DIE). To reuse the current design + * of hw-strap, hw-strap1 is assigned to the SCU and sets the value in the + * SCU hw-strap1 register, while hw-strap2 is assigned to the SCUIO and + * sets the value in the SCUIO hw-strap1 register. + */ + object_property_add_alias(obj, "hw-strap2", OBJECT(&s->scuio), + "hw-strap1"); snprintf(typename, sizeof(typename), "aspeed.fmc-%s", socname); object_initialize_child(obj, "fmc", &s->fmc, typename); @@ -372,7 +457,9 @@ static void aspeed_soc_ast2700_init(Object *obj) object_initialize_child(obj, "sli", &s->sli, TYPE_ASPEED_2700_SLI); object_initialize_child(obj, "sliio", &s->sliio, TYPE_ASPEED_2700_SLIIO); - object_initialize_child(obj, "intc", &a->intc, TYPE_ASPEED_2700_INTC); + object_initialize_child(obj, "intc", &a->intc[0], TYPE_ASPEED_2700_INTC); + object_initialize_child(obj, "intcio", &a->intc[1], + TYPE_ASPEED_2700_INTCIO); snprintf(typename, sizeof(typename), "aspeed.adc-%s", socname); object_initialize_child(obj, "adc", &s->adc, typename); @@ -401,6 +488,9 @@ static void aspeed_soc_ast2700_init(Object *obj) snprintf(typename, sizeof(typename), "aspeed.timer-%s", socname); object_initialize_child(obj, "timerctrl", &s->timerctrl, typename); + + snprintf(typename, sizeof(typename), "aspeed.hace-%s", socname); + object_initialize_child(obj, "hace", &s->hace, typename); } /* @@ -470,6 +560,10 @@ static bool aspeed_soc_ast2700_gic_realize(DeviceState *dev, Error **errp) qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ)); sysbus_connect_irq(gicbusdev, i + 3 * sc->num_cpus, qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ)); + sysbus_connect_irq(gicbusdev, i + 4 * sc->num_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_NMI)); + sysbus_connect_irq(gicbusdev, i + 5 * sc->num_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_VINMI)); } return true; @@ -481,7 +575,8 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) Aspeed27x0SoCState *a = ASPEED27X0_SOC(dev); AspeedSoCState *s = ASPEED_SOC(dev); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); - AspeedINTCClass *ic = ASPEED_INTC_GET_CLASS(&a->intc); + AspeedINTCClass *ic = ASPEED_INTC_GET_CLASS(&a->intc[0]); + AspeedINTCClass *icio = ASPEED_INTC_GET_CLASS(&a->intc[1]); g_autofree char *sram_name = NULL; qemu_irq irq; @@ -512,20 +607,45 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) } /* INTC */ - if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc), errp)) { + if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[0]), errp)) { return; } - aspeed_mmio_map(s, SYS_BUS_DEVICE(&a->intc), 0, + aspeed_mmio_map(s, SYS_BUS_DEVICE(&a->intc[0]), 0, sc->memmap[ASPEED_DEV_INTC]); - /* GICINT orgates -> INTC -> GIC */ - for (i = 0; i < ic->num_ints; i++) { - qdev_connect_gpio_out(DEVICE(&a->intc.orgates[i]), 0, - qdev_get_gpio_in(DEVICE(&a->intc), i)); - sysbus_connect_irq(SYS_BUS_DEVICE(&a->intc), i, + /* INTCIO */ + if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[1]), errp)) { + return; + } + + aspeed_mmio_map(s, SYS_BUS_DEVICE(&a->intc[1]), 0, + sc->memmap[ASPEED_DEV_INTCIO]); + + /* irq sources -> orgates -> INTC */ + for (i = 0; i < ic->num_inpins; i++) { + qdev_connect_gpio_out(DEVICE(&a->intc[0].orgates[i]), 0, + qdev_get_gpio_in(DEVICE(&a->intc[0]), i)); + } + + /* INTC -> GIC192 - GIC201 */ + /* INTC -> GIC128 - GIC136 */ + for (i = 0; i < ic->num_outpins; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(&a->intc[0]), i, qdev_get_gpio_in(DEVICE(&a->gic), - aspeed_soc_ast2700_gic_intcmap[i].irq)); + ast2700_gic_intcmap[i].irq)); + } + + /* irq source -> orgates -> INTCIO */ + for (i = 0; i < icio->num_inpins; i++) { + qdev_connect_gpio_out(DEVICE(&a->intc[1].orgates[i]), 0, + qdev_get_gpio_in(DEVICE(&a->intc[1]), i)); + } + + /* INTCIO -> INTC */ + for (i = 0; i < icio->num_outpins; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(&a->intc[1]), i, + qdev_get_gpio_in(DEVICE(&a->intc[0].orgates[0]), i)); } /* SRAM */ @@ -676,10 +796,22 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) for (i = 0; i < ASPEED_I2C_GET_CLASS(&s->i2c)->num_busses; i++) { /* * The AST2700 I2C controller has one source INTC per bus. - * I2C buses interrupt are connected to GICINT130_INTC - * from bit 0 to bit 15. - * I2C bus 0 is connected to GICINT130_INTC at bit 0. - * I2C bus 15 is connected to GICINT130_INTC at bit 15. + * + * For AST2700 A0: + * I2C bus interrupts are connected to the OR gate from bit 0 to bit + * 15, and the OR gate output pin is connected to the input pin of + * GICINT130 of INTC (CPU Die). Then, the output pin is connected to + * the GIC. + * + * For AST2700 A1: + * I2C bus interrupts are connected to the OR gate from bit 0 to bit + * 15, and the OR gate output pin is connected to the input pin of + * GICINT194 of INTCIO (IO Die). Then, the output pin is connected + * to the INTC (CPU Die) input pin, and its output pin is connected + * to the GIC. + * + * I2C bus 0 is connected to the OR gate at bit 0. + * I2C bus 15 is connected to the OR gate at bit 15. */ irq = aspeed_soc_ast2700_get_irq_index(s, ASPEED_DEV_I2C, i); sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c.busses[i]), 0, irq); @@ -733,6 +865,17 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->timerctrl), i, irq); } + /* HACE */ + object_property_set_link(OBJECT(&s->hace), "dram", OBJECT(s->dram_mr), + &error_abort); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->hace), errp)) { + return; + } + aspeed_mmio_map(s, SYS_BUS_DEVICE(&s->hace), 0, + sc->memmap[ASPEED_DEV_HACE]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->hace), 0, + aspeed_soc_get_irq(s, ASPEED_DEV_HACE)); + create_unimplemented_device("ast2700.dpmcu", 0x11000000, 0x40000); create_unimplemented_device("ast2700.iomem0", 0x12000000, 0x01000000); create_unimplemented_device("ast2700.iomem1", 0x14000000, 0x01000000); @@ -740,7 +883,7 @@ static void aspeed_soc_ast2700_realize(DeviceState *dev, Error **errp) create_unimplemented_device("ast2700.io", 0x0, 0x4000000); } -static void aspeed_soc_ast2700_class_init(ObjectClass *oc, void *data) +static void aspeed_soc_ast2700a0_class_init(ObjectClass *oc, void *data) { static const char * const valid_cpu_types[] = { ARM_CPU_TYPE_NAME("cortex-a35"), @@ -753,7 +896,6 @@ static void aspeed_soc_ast2700_class_init(ObjectClass *oc, void *data) dc->user_creatable = false; dc->realize = aspeed_soc_ast2700_realize; - sc->name = "ast2700-a0"; sc->valid_cpu_types = valid_cpu_types; sc->silicon_rev = AST2700_A0_SILICON_REV; sc->sram_size = 0x20000; @@ -763,7 +905,34 @@ static void aspeed_soc_ast2700_class_init(ObjectClass *oc, void *data) sc->uarts_num = 13; sc->num_cpus = 4; sc->uarts_base = ASPEED_DEV_UART0; - sc->irqmap = aspeed_soc_ast2700_irqmap; + sc->irqmap = aspeed_soc_ast2700a0_irqmap; + sc->memmap = aspeed_soc_ast2700_memmap; + sc->get_irq = aspeed_soc_ast2700_get_irq; +} + +static void aspeed_soc_ast2700a1_class_init(ObjectClass *oc, void *data) +{ + static const char * const valid_cpu_types[] = { + ARM_CPU_TYPE_NAME("cortex-a35"), + NULL + }; + DeviceClass *dc = DEVICE_CLASS(oc); + AspeedSoCClass *sc = ASPEED_SOC_CLASS(oc); + + /* Reason: The Aspeed SoC can only be instantiated from a board */ + dc->user_creatable = false; + dc->realize = aspeed_soc_ast2700_realize; + + sc->valid_cpu_types = valid_cpu_types; + sc->silicon_rev = AST2700_A1_SILICON_REV; + sc->sram_size = 0x20000; + sc->spis_num = 3; + sc->wdts_num = 8; + sc->macs_num = 3; + sc->uarts_num = 13; + sc->num_cpus = 4; + sc->uarts_base = ASPEED_DEV_UART0; + sc->irqmap = aspeed_soc_ast2700a1_irqmap; sc->memmap = aspeed_soc_ast2700_memmap; sc->get_irq = aspeed_soc_ast2700_get_irq; } @@ -778,7 +947,13 @@ static const TypeInfo aspeed_soc_ast27x0_types[] = { .name = "ast2700-a0", .parent = TYPE_ASPEED27X0_SOC, .instance_init = aspeed_soc_ast2700_init, - .class_init = aspeed_soc_ast2700_class_init, + .class_init = aspeed_soc_ast2700a0_class_init, + }, + { + .name = "ast2700-a1", + .parent = TYPE_ASPEED27X0_SOC, + .instance_init = aspeed_soc_ast2700_init, + .class_init = aspeed_soc_ast2700a1_class_init, }, }; diff --git a/hw/intc/aspeed_intc.c b/hw/intc/aspeed_intc.c index 126b711b94..3fd417084f 100644 --- a/hw/intc/aspeed_intc.c +++ b/hw/intc/aspeed_intc.c @@ -14,72 +14,202 @@ #include "hw/registerfields.h" #include "qapi/error.h" -/* INTC Registers */ -REG32(GICINT128_EN, 0x1000) -REG32(GICINT128_STATUS, 0x1004) -REG32(GICINT129_EN, 0x1100) -REG32(GICINT129_STATUS, 0x1104) -REG32(GICINT130_EN, 0x1200) -REG32(GICINT130_STATUS, 0x1204) -REG32(GICINT131_EN, 0x1300) -REG32(GICINT131_STATUS, 0x1304) -REG32(GICINT132_EN, 0x1400) -REG32(GICINT132_STATUS, 0x1404) -REG32(GICINT133_EN, 0x1500) -REG32(GICINT133_STATUS, 0x1504) -REG32(GICINT134_EN, 0x1600) -REG32(GICINT134_STATUS, 0x1604) -REG32(GICINT135_EN, 0x1700) -REG32(GICINT135_STATUS, 0x1704) -REG32(GICINT136_EN, 0x1800) -REG32(GICINT136_STATUS, 0x1804) - -#define GICINT_STATUS_BASE R_GICINT128_STATUS - -static void aspeed_intc_update(AspeedINTCState *s, int irq, int level) +/* + * INTC Registers + * + * values below are offset by - 0x1000 from datasheet + * because its memory region is start at 0x1000 + * + */ +REG32(GICINT128_EN, 0x000) +REG32(GICINT128_STATUS, 0x004) +REG32(GICINT129_EN, 0x100) +REG32(GICINT129_STATUS, 0x104) +REG32(GICINT130_EN, 0x200) +REG32(GICINT130_STATUS, 0x204) +REG32(GICINT131_EN, 0x300) +REG32(GICINT131_STATUS, 0x304) +REG32(GICINT132_EN, 0x400) +REG32(GICINT132_STATUS, 0x404) +REG32(GICINT133_EN, 0x500) +REG32(GICINT133_STATUS, 0x504) +REG32(GICINT134_EN, 0x600) +REG32(GICINT134_STATUS, 0x604) +REG32(GICINT135_EN, 0x700) +REG32(GICINT135_STATUS, 0x704) +REG32(GICINT136_EN, 0x800) +REG32(GICINT136_STATUS, 0x804) +REG32(GICINT192_201_EN, 0xB00) +REG32(GICINT192_201_STATUS, 0xB04) + +/* + * INTCIO Registers + * + * values below are offset by - 0x100 from datasheet + * because its memory region is start at 0x100 + * + */ +REG32(GICINT192_EN, 0x00) +REG32(GICINT192_STATUS, 0x04) +REG32(GICINT193_EN, 0x10) +REG32(GICINT193_STATUS, 0x14) +REG32(GICINT194_EN, 0x20) +REG32(GICINT194_STATUS, 0x24) +REG32(GICINT195_EN, 0x30) +REG32(GICINT195_STATUS, 0x34) +REG32(GICINT196_EN, 0x40) +REG32(GICINT196_STATUS, 0x44) +REG32(GICINT197_EN, 0x50) +REG32(GICINT197_STATUS, 0x54) + +static const AspeedINTCIRQ *aspeed_intc_get_irq(AspeedINTCClass *aic, + uint32_t reg) +{ + int i; + + for (i = 0; i < aic->irq_table_count; i++) { + if (aic->irq_table[i].enable_reg == reg || + aic->irq_table[i].status_reg == reg) { + return &aic->irq_table[i]; + } + } + + /* + * Invalid reg. + */ + g_assert_not_reached(); +} + +/* + * Update the state of an interrupt controller pin by setting + * the specified output pin to the given level. + * The input pin index should be between 0 and the number of input pins. + * The output pin index should be between 0 and the number of output pins. + */ +static void aspeed_intc_update(AspeedINTCState *s, int inpin_idx, + int outpin_idx, int level) { AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); + const char *name = object_get_typename(OBJECT(s)); - if (irq >= aic->num_ints) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", - __func__, irq); - return; + assert((outpin_idx < aic->num_outpins) && (inpin_idx < aic->num_inpins)); + + trace_aspeed_intc_update_irq(name, inpin_idx, outpin_idx, level); + qemu_set_irq(s->output_pins[outpin_idx], level); +} + +static void aspeed_intc_set_irq_handler(AspeedINTCState *s, + const AspeedINTCIRQ *intc_irq, + uint32_t select) +{ + const char *name = object_get_typename(OBJECT(s)); + uint32_t status_reg; + int outpin_idx; + int inpin_idx; + + status_reg = intc_irq->status_reg; + outpin_idx = intc_irq->outpin_idx; + inpin_idx = intc_irq->inpin_idx; + + if (s->mask[inpin_idx] || s->regs[status_reg]) { + /* + * a. mask is not 0 means in ISR mode + * sources interrupt routine are executing. + * b. status register value is not 0 means previous + * source interrupt does not be executed, yet. + * + * save source interrupt to pending variable. + */ + s->pending[inpin_idx] |= select; + trace_aspeed_intc_pending_irq(name, inpin_idx, s->pending[inpin_idx]); + } else { + /* + * notify firmware which source interrupt are coming + * by setting status register + */ + s->regs[status_reg] = select; + trace_aspeed_intc_trigger_irq(name, inpin_idx, outpin_idx, + s->regs[status_reg]); + aspeed_intc_update(s, inpin_idx, outpin_idx, 1); } +} + +static void aspeed_intc_set_irq_handler_multi_outpins(AspeedINTCState *s, + const AspeedINTCIRQ *intc_irq, uint32_t select) +{ + const char *name = object_get_typename(OBJECT(s)); + uint32_t status_reg; + int num_outpins; + int outpin_idx; + int inpin_idx; + int i; - trace_aspeed_intc_update_irq(irq, level); - qemu_set_irq(s->output_pins[irq], level); + num_outpins = intc_irq->num_outpins; + status_reg = intc_irq->status_reg; + outpin_idx = intc_irq->outpin_idx; + inpin_idx = intc_irq->inpin_idx; + + for (i = 0; i < num_outpins; i++) { + if (select & BIT(i)) { + if (s->mask[inpin_idx] & BIT(i) || + s->regs[status_reg] & BIT(i)) { + /* + * a. mask bit is not 0 means in ISR mode sources interrupt + * routine are executing. + * b. status bit is not 0 means previous source interrupt + * does not be executed, yet. + * + * save source interrupt to pending bit. + */ + s->pending[inpin_idx] |= BIT(i); + trace_aspeed_intc_pending_irq(name, inpin_idx, + s->pending[inpin_idx]); + } else { + /* + * notify firmware which source interrupt are coming + * by setting status bit + */ + s->regs[status_reg] |= BIT(i); + trace_aspeed_intc_trigger_irq(name, inpin_idx, outpin_idx + i, + s->regs[status_reg]); + aspeed_intc_update(s, inpin_idx, outpin_idx + i, 1); + } + } + } } /* - * The address of GICINT128 to GICINT136 are from 0x1000 to 0x1804. - * Utilize "address & 0x0f00" to get the irq and irq output pin index - * The value of irq should be 0 to num_ints. - * The irq 0 indicates GICINT128, irq 1 indicates GICINT129 and so on. + * GICINT192_201 maps 1:10 to input IRQ 0 and output IRQs 0 to 9. + * GICINT128 to GICINT136 map 1:1 to input IRQs 1 to 9 and output + * IRQs 10 to 18. The value of input IRQ should be between 0 and + * the number of input pins. */ static void aspeed_intc_set_irq(void *opaque, int irq, int level) { AspeedINTCState *s = (AspeedINTCState *)opaque; AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); - uint32_t status_addr = GICINT_STATUS_BASE + ((0x100 * irq) >> 2); + const char *name = object_get_typename(OBJECT(s)); + const AspeedINTCIRQ *intc_irq; uint32_t select = 0; uint32_t enable; + int num_outpins; + int inpin_idx; int i; - if (irq >= aic->num_ints) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", - __func__, irq); - return; - } + assert(irq < aic->num_inpins); - trace_aspeed_intc_set_irq(irq, level); - enable = s->enable[irq]; + intc_irq = &aic->irq_table[irq]; + num_outpins = intc_irq->num_outpins; + inpin_idx = intc_irq->inpin_idx; + trace_aspeed_intc_set_irq(name, inpin_idx, level); + enable = s->enable[inpin_idx]; if (!level) { return; } for (i = 0; i < aic->num_lines; i++) { - if (s->orgates[irq].levels[i]) { + if (s->orgates[inpin_idx].levels[i]) { if (enable & BIT(i)) { select |= BIT(i); } @@ -90,45 +220,190 @@ static void aspeed_intc_set_irq(void *opaque, int irq, int level) return; } - trace_aspeed_intc_select(select); + trace_aspeed_intc_select(name, select); + if (num_outpins > 1) { + aspeed_intc_set_irq_handler_multi_outpins(s, intc_irq, select); + } else { + aspeed_intc_set_irq_handler(s, intc_irq, select); + } +} - if (s->mask[irq] || s->regs[status_addr]) { - /* - * a. mask is not 0 means in ISR mode - * sources interrupt routine are executing. - * b. status register value is not 0 means previous - * source interrupt does not be executed, yet. - * - * save source interrupt to pending variable. - */ - s->pending[irq] |= select; - trace_aspeed_intc_pending_irq(irq, s->pending[irq]); +static void aspeed_intc_enable_handler(AspeedINTCState *s, hwaddr offset, + uint64_t data) +{ + AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); + const char *name = object_get_typename(OBJECT(s)); + const AspeedINTCIRQ *intc_irq; + uint32_t reg = offset >> 2; + uint32_t old_enable; + uint32_t change; + int inpin_idx; + + intc_irq = aspeed_intc_get_irq(aic, reg); + inpin_idx = intc_irq->inpin_idx; + + assert(inpin_idx < aic->num_inpins); + + /* + * The enable registers are used to enable source interrupts. + * They also handle masking and unmasking of source interrupts + * during the execution of the source ISR. + */ + + /* disable all source interrupt */ + if (!data && !s->enable[inpin_idx]) { + s->regs[reg] = data; + return; + } + + old_enable = s->enable[inpin_idx]; + s->enable[inpin_idx] |= data; + + /* enable new source interrupt */ + if (old_enable != s->enable[inpin_idx]) { + trace_aspeed_intc_enable(name, s->enable[inpin_idx]); + s->regs[reg] = data; + return; + } + + /* mask and unmask source interrupt */ + change = s->regs[reg] ^ data; + if (change & data) { + s->mask[inpin_idx] &= ~change; + trace_aspeed_intc_unmask(name, change, s->mask[inpin_idx]); } else { - /* - * notify firmware which source interrupt are coming - * by setting status register - */ - s->regs[status_addr] = select; - trace_aspeed_intc_trigger_irq(irq, s->regs[status_addr]); - aspeed_intc_update(s, irq, 1); + s->mask[inpin_idx] |= change; + trace_aspeed_intc_mask(name, change, s->mask[inpin_idx]); + } + + s->regs[reg] = data; +} + +static void aspeed_intc_status_handler(AspeedINTCState *s, hwaddr offset, + uint64_t data) +{ + AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); + const char *name = object_get_typename(OBJECT(s)); + const AspeedINTCIRQ *intc_irq; + uint32_t reg = offset >> 2; + int outpin_idx; + int inpin_idx; + + if (!data) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid data 0\n", __func__); + return; + } + + intc_irq = aspeed_intc_get_irq(aic, reg); + outpin_idx = intc_irq->outpin_idx; + inpin_idx = intc_irq->inpin_idx; + + assert(inpin_idx < aic->num_inpins); + + /* clear status */ + s->regs[reg] &= ~data; + + /* + * These status registers are used for notify sources ISR are executed. + * If one source ISR is executed, it will clear one bit. + * If it clear all bits, it means to initialize this register status + * rather than sources ISR are executed. + */ + if (data == 0xffffffff) { + return; + } + + /* All source ISR execution are done */ + if (!s->regs[reg]) { + trace_aspeed_intc_all_isr_done(name, inpin_idx); + if (s->pending[inpin_idx]) { + /* + * handle pending source interrupt + * notify firmware which source interrupt are pending + * by setting status register + */ + s->regs[reg] = s->pending[inpin_idx]; + s->pending[inpin_idx] = 0; + trace_aspeed_intc_trigger_irq(name, inpin_idx, outpin_idx, + s->regs[reg]); + aspeed_intc_update(s, inpin_idx, outpin_idx, 1); + } else { + /* clear irq */ + trace_aspeed_intc_clear_irq(name, inpin_idx, outpin_idx, 0); + aspeed_intc_update(s, inpin_idx, outpin_idx, 0); + } + } +} + +static void aspeed_intc_status_handler_multi_outpins(AspeedINTCState *s, + hwaddr offset, uint64_t data) +{ + const char *name = object_get_typename(OBJECT(s)); + AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); + const AspeedINTCIRQ *intc_irq; + uint32_t reg = offset >> 2; + int num_outpins; + int outpin_idx; + int inpin_idx; + int i; + + if (!data) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid data 0\n", __func__); + return; + } + + intc_irq = aspeed_intc_get_irq(aic, reg); + num_outpins = intc_irq->num_outpins; + outpin_idx = intc_irq->outpin_idx; + inpin_idx = intc_irq->inpin_idx; + assert(inpin_idx < aic->num_inpins); + + /* clear status */ + s->regs[reg] &= ~data; + + /* + * The status registers are used for notify sources ISR are executed. + * If one source ISR is executed, it will clear one bit. + * If it clear all bits, it means to initialize this register status + * rather than sources ISR are executed. + */ + if (data == 0xffffffff) { + return; + } + + for (i = 0; i < num_outpins; i++) { + /* All source ISR executions are done from a specific bit */ + if (data & BIT(i)) { + trace_aspeed_intc_all_isr_done_bit(name, inpin_idx, i); + if (s->pending[inpin_idx] & BIT(i)) { + /* + * Handle pending source interrupt. + * Notify firmware which source interrupt is pending + * by setting the status bit. + */ + s->regs[reg] |= BIT(i); + s->pending[inpin_idx] &= ~BIT(i); + trace_aspeed_intc_trigger_irq(name, inpin_idx, outpin_idx + i, + s->regs[reg]); + aspeed_intc_update(s, inpin_idx, outpin_idx + i, 1); + } else { + /* clear irq for the specific bit */ + trace_aspeed_intc_clear_irq(name, inpin_idx, outpin_idx + i, 0); + aspeed_intc_update(s, inpin_idx, outpin_idx + i, 0); + } + } } } static uint64_t aspeed_intc_read(void *opaque, hwaddr offset, unsigned int size) { AspeedINTCState *s = ASPEED_INTC(opaque); - uint32_t addr = offset >> 2; + const char *name = object_get_typename(OBJECT(s)); + uint32_t reg = offset >> 2; uint32_t value = 0; - if (addr >= ASPEED_INTC_NR_REGS) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n", - __func__, offset); - return 0; - } - - value = s->regs[addr]; - trace_aspeed_intc_read(offset, size, value); + value = s->regs[reg]; + trace_aspeed_intc_read(name, offset, size, value); return value; } @@ -137,22 +412,12 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, unsigned size) { AspeedINTCState *s = ASPEED_INTC(opaque); - AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); - uint32_t addr = offset >> 2; - uint32_t old_enable; - uint32_t change; - uint32_t irq; - - if (addr >= ASPEED_INTC_NR_REGS) { - qemu_log_mask(LOG_GUEST_ERROR, - "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n", - __func__, offset); - return; - } + const char *name = object_get_typename(OBJECT(s)); + uint32_t reg = offset >> 2; - trace_aspeed_intc_write(offset, size, data); + trace_aspeed_intc_write(name, offset, size, data); - switch (addr) { + switch (reg) { case R_GICINT128_EN: case R_GICINT129_EN: case R_GICINT130_EN: @@ -162,45 +427,8 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, case R_GICINT134_EN: case R_GICINT135_EN: case R_GICINT136_EN: - irq = (offset & 0x0f00) >> 8; - - if (irq >= aic->num_ints) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", - __func__, irq); - return; - } - - /* - * These registers are used for enable sources interrupt and - * mask and unmask source interrupt while executing source ISR. - */ - - /* disable all source interrupt */ - if (!data && !s->enable[irq]) { - s->regs[addr] = data; - return; - } - - old_enable = s->enable[irq]; - s->enable[irq] |= data; - - /* enable new source interrupt */ - if (old_enable != s->enable[irq]) { - trace_aspeed_intc_enable(s->enable[irq]); - s->regs[addr] = data; - return; - } - - /* mask and unmask source interrupt */ - change = s->regs[addr] ^ data; - if (change & data) { - s->mask[irq] &= ~change; - trace_aspeed_intc_unmask(change, s->mask[irq]); - } else { - s->mask[irq] |= change; - trace_aspeed_intc_mask(change, s->mask[irq]); - } - s->regs[addr] = data; + case R_GICINT192_201_EN: + aspeed_intc_enable_handler(s, offset, data); break; case R_GICINT128_STATUS: case R_GICINT129_STATUS: @@ -211,55 +439,68 @@ static void aspeed_intc_write(void *opaque, hwaddr offset, uint64_t data, case R_GICINT134_STATUS: case R_GICINT135_STATUS: case R_GICINT136_STATUS: - irq = (offset & 0x0f00) >> 8; + aspeed_intc_status_handler(s, offset, data); + break; + case R_GICINT192_201_STATUS: + aspeed_intc_status_handler_multi_outpins(s, offset, data); + break; + default: + s->regs[reg] = data; + break; + } - if (irq >= aic->num_ints) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n", - __func__, irq); - return; - } + return; +} + +static uint64_t aspeed_intcio_read(void *opaque, hwaddr offset, + unsigned int size) +{ + AspeedINTCState *s = ASPEED_INTC(opaque); + const char *name = object_get_typename(OBJECT(s)); + uint32_t reg = offset >> 2; + uint32_t value = 0; - /* clear status */ - s->regs[addr] &= ~data; + value = s->regs[reg]; + trace_aspeed_intc_read(name, offset, size, value); - /* - * These status registers are used for notify sources ISR are executed. - * If one source ISR is executed, it will clear one bit. - * If it clear all bits, it means to initialize this register status - * rather than sources ISR are executed. - */ - if (data == 0xffffffff) { - return; - } + return value; +} - /* All source ISR execution are done */ - if (!s->regs[addr]) { - trace_aspeed_intc_all_isr_done(irq); - if (s->pending[irq]) { - /* - * handle pending source interrupt - * notify firmware which source interrupt are pending - * by setting status register - */ - s->regs[addr] = s->pending[irq]; - s->pending[irq] = 0; - trace_aspeed_intc_trigger_irq(irq, s->regs[addr]); - aspeed_intc_update(s, irq, 1); - } else { - /* clear irq */ - trace_aspeed_intc_clear_irq(irq, 0); - aspeed_intc_update(s, irq, 0); - } - } +static void aspeed_intcio_write(void *opaque, hwaddr offset, uint64_t data, + unsigned size) +{ + AspeedINTCState *s = ASPEED_INTC(opaque); + const char *name = object_get_typename(OBJECT(s)); + uint32_t reg = offset >> 2; + + trace_aspeed_intc_write(name, offset, size, data); + + switch (reg) { + case R_GICINT192_EN: + case R_GICINT193_EN: + case R_GICINT194_EN: + case R_GICINT195_EN: + case R_GICINT196_EN: + case R_GICINT197_EN: + aspeed_intc_enable_handler(s, offset, data); + break; + case R_GICINT192_STATUS: + case R_GICINT193_STATUS: + case R_GICINT194_STATUS: + case R_GICINT195_STATUS: + case R_GICINT196_STATUS: + case R_GICINT197_STATUS: + aspeed_intc_status_handler(s, offset, data); break; default: - s->regs[addr] = data; + s->regs[reg] = data; break; } return; } + static const MemoryRegionOps aspeed_intc_ops = { .read = aspeed_intc_read, .write = aspeed_intc_write, @@ -270,14 +511,24 @@ static const MemoryRegionOps aspeed_intc_ops = { } }; +static const MemoryRegionOps aspeed_intcio_ops = { + .read = aspeed_intcio_read, + .write = aspeed_intcio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + } +}; + static void aspeed_intc_instance_init(Object *obj) { AspeedINTCState *s = ASPEED_INTC(obj); AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); int i; - assert(aic->num_ints <= ASPEED_INTC_NR_INTS); - for (i = 0; i < aic->num_ints; i++) { + assert(aic->num_inpins <= ASPEED_INTC_MAX_INPINS); + for (i = 0; i < aic->num_inpins; i++) { object_initialize_child(obj, "intc-orgates[*]", &s->orgates[i], TYPE_OR_IRQ); object_property_set_int(OBJECT(&s->orgates[i]), "num-lines", @@ -288,8 +539,9 @@ static void aspeed_intc_instance_init(Object *obj) static void aspeed_intc_reset(DeviceState *dev) { AspeedINTCState *s = ASPEED_INTC(dev); + AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); - memset(s->regs, 0, sizeof(s->regs)); + memset(s->regs, 0, aic->nr_regs << 2); memset(s->enable, 0, sizeof(s->enable)); memset(s->mask, 0, sizeof(s->mask)); memset(s->pending, 0, sizeof(s->pending)); @@ -302,28 +554,51 @@ static void aspeed_intc_realize(DeviceState *dev, Error **errp) AspeedINTCClass *aic = ASPEED_INTC_GET_CLASS(s); int i; - memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_intc_ops, s, - TYPE_ASPEED_INTC ".regs", ASPEED_INTC_NR_REGS << 2); + memory_region_init(&s->iomem_container, OBJECT(s), + TYPE_ASPEED_INTC ".container", aic->mem_size); + + sysbus_init_mmio(sbd, &s->iomem_container); - sysbus_init_mmio(sbd, &s->iomem); - qdev_init_gpio_in(dev, aspeed_intc_set_irq, aic->num_ints); + s->regs = g_new(uint32_t, aic->nr_regs); + memory_region_init_io(&s->iomem, OBJECT(s), aic->reg_ops, s, + TYPE_ASPEED_INTC ".regs", aic->nr_regs << 2); - for (i = 0; i < aic->num_ints; i++) { + memory_region_add_subregion(&s->iomem_container, aic->reg_offset, + &s->iomem); + + qdev_init_gpio_in(dev, aspeed_intc_set_irq, aic->num_inpins); + + for (i = 0; i < aic->num_inpins; i++) { if (!qdev_realize(DEVICE(&s->orgates[i]), NULL, errp)) { return; } + } + + for (i = 0; i < aic->num_outpins; i++) { sysbus_init_irq(sbd, &s->output_pins[i]); } } +static void aspeed_intc_unrealize(DeviceState *dev) +{ + AspeedINTCState *s = ASPEED_INTC(dev); + + g_free(s->regs); + s->regs = NULL; +} + static void aspeed_intc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); + AspeedINTCClass *aic = ASPEED_INTC_CLASS(klass); dc->desc = "ASPEED INTC Controller"; dc->realize = aspeed_intc_realize; + dc->unrealize = aspeed_intc_unrealize; device_class_set_legacy_reset(dc, aspeed_intc_reset); dc->vmsd = NULL; + + aic->reg_ops = &aspeed_intc_ops; } static const TypeInfo aspeed_intc_info = { @@ -336,6 +611,19 @@ static const TypeInfo aspeed_intc_info = { .abstract = true, }; +static AspeedINTCIRQ aspeed_2700_intc_irqs[ASPEED_INTC_MAX_INPINS] = { + {0, 0, 10, R_GICINT192_201_EN, R_GICINT192_201_STATUS}, + {1, 10, 1, R_GICINT128_EN, R_GICINT128_STATUS}, + {2, 11, 1, R_GICINT129_EN, R_GICINT129_STATUS}, + {3, 12, 1, R_GICINT130_EN, R_GICINT130_STATUS}, + {4, 13, 1, R_GICINT131_EN, R_GICINT131_STATUS}, + {5, 14, 1, R_GICINT132_EN, R_GICINT132_STATUS}, + {6, 15, 1, R_GICINT133_EN, R_GICINT133_STATUS}, + {7, 16, 1, R_GICINT134_EN, R_GICINT134_STATUS}, + {8, 17, 1, R_GICINT135_EN, R_GICINT135_STATUS}, + {9, 18, 1, R_GICINT136_EN, R_GICINT136_STATUS}, +}; + static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -343,7 +631,13 @@ static void aspeed_2700_intc_class_init(ObjectClass *klass, void *data) dc->desc = "ASPEED 2700 INTC Controller"; aic->num_lines = 32; - aic->num_ints = 9; + aic->num_inpins = 10; + aic->num_outpins = 19; + aic->mem_size = 0x4000; + aic->nr_regs = 0xB08 >> 2; + aic->reg_offset = 0x1000; + aic->irq_table = aspeed_2700_intc_irqs; + aic->irq_table_count = ARRAY_SIZE(aspeed_2700_intc_irqs); } static const TypeInfo aspeed_2700_intc_info = { @@ -352,10 +646,43 @@ static const TypeInfo aspeed_2700_intc_info = { .class_init = aspeed_2700_intc_class_init, }; +static AspeedINTCIRQ aspeed_2700_intcio_irqs[ASPEED_INTC_MAX_INPINS] = { + {0, 0, 1, R_GICINT192_EN, R_GICINT192_STATUS}, + {1, 1, 1, R_GICINT193_EN, R_GICINT193_STATUS}, + {2, 2, 1, R_GICINT194_EN, R_GICINT194_STATUS}, + {3, 3, 1, R_GICINT195_EN, R_GICINT195_STATUS}, + {4, 4, 1, R_GICINT196_EN, R_GICINT196_STATUS}, + {5, 5, 1, R_GICINT197_EN, R_GICINT197_STATUS}, +}; + +static void aspeed_2700_intcio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + AspeedINTCClass *aic = ASPEED_INTC_CLASS(klass); + + dc->desc = "ASPEED 2700 INTC IO Controller"; + aic->num_lines = 32; + aic->num_inpins = 6; + aic->num_outpins = 6; + aic->mem_size = 0x400; + aic->nr_regs = 0x58 >> 2; + aic->reg_offset = 0x100; + aic->reg_ops = &aspeed_intcio_ops; + aic->irq_table = aspeed_2700_intcio_irqs; + aic->irq_table_count = ARRAY_SIZE(aspeed_2700_intcio_irqs); +} + +static const TypeInfo aspeed_2700_intcio_info = { + .name = TYPE_ASPEED_2700_INTCIO, + .parent = TYPE_ASPEED_INTC, + .class_init = aspeed_2700_intcio_class_init, +}; + static void aspeed_intc_register_types(void) { type_register_static(&aspeed_intc_info); type_register_static(&aspeed_2700_intc_info); + type_register_static(&aspeed_2700_intcio_info); } type_init(aspeed_intc_register_types); diff --git a/hw/intc/trace-events b/hw/intc/trace-events index 3dcf147198..913197a181 100644 --- a/hw/intc/trace-events +++ b/hw/intc/trace-events @@ -80,18 +80,19 @@ aspeed_vic_update_irq(int flags) "Raising IRQ: %d" aspeed_vic_read(uint64_t offset, unsigned size, uint32_t value) "From 0x%" PRIx64 " of size %u: 0x%" PRIx32 aspeed_vic_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 # aspeed_intc.c -aspeed_intc_read(uint64_t offset, unsigned size, uint32_t value) "From 0x%" PRIx64 " of size %u: 0x%" PRIx32 -aspeed_intc_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 -aspeed_intc_set_irq(int irq, int level) "Set IRQ %d: %d" -aspeed_intc_clear_irq(int irq, int level) "Clear IRQ %d: %d" -aspeed_intc_update_irq(int irq, int level) "Update IRQ: %d: %d" -aspeed_intc_pending_irq(int irq, uint32_t value) "Pending IRQ: %d: 0x%x" -aspeed_intc_trigger_irq(int irq, uint32_t value) "Trigger IRQ: %d: 0x%x" -aspeed_intc_all_isr_done(int irq) "All source ISR execution are done: %d" -aspeed_intc_enable(uint32_t value) "Enable: 0x%x" -aspeed_intc_select(uint32_t value) "Select: 0x%x" -aspeed_intc_mask(uint32_t change, uint32_t value) "Mask: 0x%x: 0x%x" -aspeed_intc_unmask(uint32_t change, uint32_t value) "UnMask: 0x%x: 0x%x" +aspeed_intc_read(const char *s, uint64_t offset, unsigned size, uint32_t value) "%s: From 0x%" PRIx64 " of size %u: 0x%" PRIx32 +aspeed_intc_write(const char *s, uint64_t offset, unsigned size, uint32_t data) "%s: To 0x%" PRIx64 " of size %u: 0x%" PRIx32 +aspeed_intc_set_irq(const char *s, int inpin_idx, int level) "%s: Set IRQ %d: %d" +aspeed_intc_clear_irq(const char *s, int inpin_idx, int outpin_idx, int level) "%s: Clear IRQ %d-%d: %d" +aspeed_intc_update_irq(const char *s, int inpin_idx, int outpin_idx, int level) "%s: Update IRQ: %d-%d: %d" +aspeed_intc_pending_irq(const char *s, int inpin_idx, uint32_t value) "%s: Pending IRQ: %d: 0x%x" +aspeed_intc_trigger_irq(const char *s, int inpin_idx, int outpin_idx, uint32_t value) "%s: Trigger IRQ: %d-%d: 0x%x" +aspeed_intc_all_isr_done(const char *s, int inpin_idx) "%s: All source ISR execution are done: %d" +aspeed_intc_enable(const char *s, uint32_t value) "%s: Enable: 0x%x" +aspeed_intc_select(const char *s, uint32_t value) "%s: Select: 0x%x" +aspeed_intc_mask(const char *s, uint32_t change, uint32_t value) "%s: Mask: 0x%x: 0x%x" +aspeed_intc_unmask(const char *s, uint32_t change, uint32_t value) "%s: UnMask: 0x%x: 0x%x" +aspeed_intc_all_isr_done_bit(const char *s, int inpin_idx, int bit) "%s: All source ISR execution are done from specific bit: %d-%d" # arm_gic.c gic_enable_irq(int irq) "irq %d enabled" diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c index e3f7df2e86..32a5dbded3 100644 --- a/hw/misc/aspeed_hace.c +++ b/hw/misc/aspeed_hace.c @@ -59,6 +59,7 @@ /* Other cmd bits */ #define HASH_IRQ_EN BIT(9) #define HASH_SG_EN BIT(18) +#define CRYPT_IRQ_EN BIT(12) /* Scatter-gather data list */ #define SG_LIST_LEN_SIZE 4 #define SG_LIST_LEN_MASK 0x0FFFFFFF @@ -75,9 +76,12 @@ static const struct { { HASH_ALGO_SHA1, QCRYPTO_HASH_ALGO_SHA1 }, { HASH_ALGO_SHA224, QCRYPTO_HASH_ALGO_SHA224 }, { HASH_ALGO_SHA256, QCRYPTO_HASH_ALGO_SHA256 }, - { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA512, QCRYPTO_HASH_ALGO_SHA512 }, - { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA384, QCRYPTO_HASH_ALGO_SHA384 }, - { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA256, QCRYPTO_HASH_ALGO_SHA256 }, + { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA512, + QCRYPTO_HASH_ALGO_SHA512 }, + { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA384, + QCRYPTO_HASH_ALGO_SHA384 }, + { HASH_ALGO_SHA512_SERIES | HASH_ALGO_SHA512_SHA256, + QCRYPTO_HASH_ALGO_SHA256 }, }; static int hash_algo_lookup(uint32_t reg) @@ -201,7 +205,8 @@ static void do_hash_operation(AspeedHACEState *s, int algo, bool sg_mode, haddr = address_space_map(&s->dram_as, addr, &plen, false, MEMTXATTRS_UNSPECIFIED); if (haddr == NULL) { - qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto failed\n", __func__); + qemu_log_mask(LOG_GUEST_ERROR, + "%s: qcrypto failed\n", __func__); return; } iov[i].iov_base = haddr; @@ -339,6 +344,15 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data, qemu_irq_lower(s->irq); } } + if (ahc->raise_crypt_interrupt_workaround) { + if (data & CRYPT_IRQ) { + data &= ~CRYPT_IRQ; + + if (s->regs[addr] & CRYPT_IRQ) { + qemu_irq_lower(s->irq); + } + } + } break; case R_HASH_SRC: data &= ahc->src_mask; @@ -384,6 +398,12 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data, case R_CRYPT_CMD: qemu_log_mask(LOG_UNIMP, "%s: Crypt commands not implemented\n", __func__); + if (ahc->raise_crypt_interrupt_workaround) { + s->regs[R_STATUS] |= CRYPT_IRQ; + if (data & CRYPT_IRQ_EN) { + qemu_irq_raise(s->irq); + } + } break; default: break; @@ -548,12 +568,39 @@ static const TypeInfo aspeed_ast1030_hace_info = { .class_init = aspeed_ast1030_hace_class_init, }; +static void aspeed_ast2700_hace_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + AspeedHACEClass *ahc = ASPEED_HACE_CLASS(klass); + + dc->desc = "AST2700 Hash and Crypto Engine"; + + ahc->src_mask = 0x7FFFFFFF; + ahc->dest_mask = 0x7FFFFFF8; + ahc->key_mask = 0x7FFFFFF8; + ahc->hash_mask = 0x00147FFF; + + /* + * Currently, it does not support the CRYPT command. Instead, it only + * sends an interrupt to notify the firmware that the crypt command + * has completed. It is a temporary workaround. + */ + ahc->raise_crypt_interrupt_workaround = true; +} + +static const TypeInfo aspeed_ast2700_hace_info = { + .name = TYPE_ASPEED_AST2700_HACE, + .parent = TYPE_ASPEED_HACE, + .class_init = aspeed_ast2700_hace_class_init, +}; + static void aspeed_hace_register_types(void) { type_register_static(&aspeed_ast2400_hace_info); type_register_static(&aspeed_ast2500_hace_info); type_register_static(&aspeed_ast2600_hace_info); type_register_static(&aspeed_ast1030_hace_info); + type_register_static(&aspeed_ast2700_hace_info); type_register_static(&aspeed_hace_info); } diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c index bac1441b06..76cfd91671 100644 --- a/hw/misc/aspeed_scu.c +++ b/hw/misc/aspeed_scu.c @@ -157,6 +157,7 @@ #define AST2700_SCU_FREQ_CNTR TO_REG(0x3b0) #define AST2700_SCU_CPU_SCRATCH_0 TO_REG(0x780) #define AST2700_SCU_CPU_SCRATCH_1 TO_REG(0x784) +#define AST2700_SCU_VGA_SCRATCH_0 TO_REG(0x900) #define AST2700_SCUIO_CLK_STOP_CTL_1 TO_REG(0x240) #define AST2700_SCUIO_CLK_STOP_CLR_1 TO_REG(0x244) @@ -559,6 +560,8 @@ static uint32_t aspeed_silicon_revs[] = { AST2700_A0_SILICON_REV, AST2720_A0_SILICON_REV, AST2750_A0_SILICON_REV, + AST2700_A1_SILICON_REV, + AST2750_A1_SILICON_REV, }; bool is_supported_silicon_rev(uint32_t silicon_rev) @@ -909,8 +912,6 @@ static const MemoryRegionOps aspeed_ast2700_scu_ops = { }; static const uint32_t ast2700_a0_resets[ASPEED_AST2700_SCU_NR_REGS] = { - [AST2700_SILICON_REV] = AST2700_A0_SILICON_REV, - [AST2700_HW_STRAP1] = 0x00000800, [AST2700_HW_STRAP1_CLR] = 0xFFF0FFF0, [AST2700_HW_STRAP1_LOCK] = 0x00000FFF, [AST2700_HW_STRAP1_SEC1] = 0x000000FF, @@ -930,6 +931,7 @@ static const uint32_t ast2700_a0_resets[ASPEED_AST2700_SCU_NR_REGS] = { [AST2700_SCU_FREQ_CNTR] = 0x000375eb, [AST2700_SCU_CPU_SCRATCH_0] = 0x00000000, [AST2700_SCU_CPU_SCRATCH_1] = 0x00000004, + [AST2700_SCU_VGA_SCRATCH_0] = 0x00000040, }; static void aspeed_ast2700_scu_reset(DeviceState *dev) @@ -938,6 +940,8 @@ static void aspeed_ast2700_scu_reset(DeviceState *dev) AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(dev); memcpy(s->regs, asc->resets, asc->nr_regs * 4); + s->regs[AST2700_SILICON_REV] = s->silicon_rev; + s->regs[AST2700_HW_STRAP1] = s->hw_strap1; } static void aspeed_2700_scu_class_init(ObjectClass *klass, void *data) @@ -1030,8 +1034,6 @@ static const MemoryRegionOps aspeed_ast2700_scuio_ops = { }; static const uint32_t ast2700_a0_resets_io[ASPEED_AST2700_SCU_NR_REGS] = { - [AST2700_SILICON_REV] = 0x06000003, - [AST2700_HW_STRAP1] = 0x00000504, [AST2700_HW_STRAP1_CLR] = 0xFFF0FFF0, [AST2700_HW_STRAP1_LOCK] = 0x00000FFF, [AST2700_HW_STRAP1_SEC1] = 0x000000FF, diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h index 689f52dae8..f899356ed9 100644 --- a/include/hw/arm/aspeed_soc.h +++ b/include/hw/arm/aspeed_soc.h @@ -128,7 +128,7 @@ struct Aspeed27x0SoCState { AspeedSoCState parent; ARMCPU cpu[ASPEED_CPUS_NUM]; - AspeedINTCState intc; + AspeedINTCState intc[2]; GICv3State gic; MemoryRegion dram_empty; }; @@ -148,7 +148,6 @@ OBJECT_DECLARE_SIMPLE_TYPE(Aspeed10x0SoCState, ASPEED10X0_SOC) struct AspeedSoCClass { DeviceClass parent_class; - const char *name; /** valid_cpu_types: NULL terminated array of a single CPU type. */ const char * const *valid_cpu_types; uint32_t silicon_rev; @@ -195,6 +194,7 @@ enum { ASPEED_DEV_EHCI2, ASPEED_DEV_VIC, ASPEED_DEV_INTC, + ASPEED_DEV_INTCIO, ASPEED_DEV_SDMC, ASPEED_DEV_SCU, ASPEED_DEV_ADC, diff --git a/include/hw/intc/aspeed_intc.h b/include/hw/intc/aspeed_intc.h index 18cb43476c..3727ba24be 100644 --- a/include/hw/intc/aspeed_intc.h +++ b/include/hw/intc/aspeed_intc.h @@ -14,10 +14,19 @@ #define TYPE_ASPEED_INTC "aspeed.intc" #define TYPE_ASPEED_2700_INTC TYPE_ASPEED_INTC "-ast2700" +#define TYPE_ASPEED_2700_INTCIO TYPE_ASPEED_INTC "io-ast2700" OBJECT_DECLARE_TYPE(AspeedINTCState, AspeedINTCClass, ASPEED_INTC) -#define ASPEED_INTC_NR_REGS (0x2000 >> 2) -#define ASPEED_INTC_NR_INTS 9 +#define ASPEED_INTC_MAX_INPINS 10 +#define ASPEED_INTC_MAX_OUTPINS 19 + +typedef struct AspeedINTCIRQ { + int inpin_idx; + int outpin_idx; + int num_outpins; + uint32_t enable_reg; + uint32_t status_reg; +} AspeedINTCIRQ; struct AspeedINTCState { /*< private >*/ @@ -25,20 +34,29 @@ struct AspeedINTCState { /*< public >*/ MemoryRegion iomem; - uint32_t regs[ASPEED_INTC_NR_REGS]; - OrIRQState orgates[ASPEED_INTC_NR_INTS]; - qemu_irq output_pins[ASPEED_INTC_NR_INTS]; + MemoryRegion iomem_container; + + uint32_t *regs; + OrIRQState orgates[ASPEED_INTC_MAX_INPINS]; + qemu_irq output_pins[ASPEED_INTC_MAX_OUTPINS]; - uint32_t enable[ASPEED_INTC_NR_INTS]; - uint32_t mask[ASPEED_INTC_NR_INTS]; - uint32_t pending[ASPEED_INTC_NR_INTS]; + uint32_t enable[ASPEED_INTC_MAX_INPINS]; + uint32_t mask[ASPEED_INTC_MAX_INPINS]; + uint32_t pending[ASPEED_INTC_MAX_INPINS]; }; struct AspeedINTCClass { SysBusDeviceClass parent_class; uint32_t num_lines; - uint32_t num_ints; + uint32_t num_inpins; + uint32_t num_outpins; + uint64_t mem_size; + uint64_t nr_regs; + uint64_t reg_offset; + const MemoryRegionOps *reg_ops; + const AspeedINTCIRQ *irq_table; + int irq_table_count; }; #endif /* ASPEED_INTC_H */ diff --git a/include/hw/misc/aspeed_hace.h b/include/hw/misc/aspeed_hace.h index 4af9919195..5d4aa19cfe 100644 --- a/include/hw/misc/aspeed_hace.h +++ b/include/hw/misc/aspeed_hace.h @@ -18,6 +18,7 @@ #define TYPE_ASPEED_AST2500_HACE TYPE_ASPEED_HACE "-ast2500" #define TYPE_ASPEED_AST2600_HACE TYPE_ASPEED_HACE "-ast2600" #define TYPE_ASPEED_AST1030_HACE TYPE_ASPEED_HACE "-ast1030" +#define TYPE_ASPEED_AST2700_HACE TYPE_ASPEED_HACE "-ast2700" OBJECT_DECLARE_TYPE(AspeedHACEState, AspeedHACEClass, ASPEED_HACE) @@ -49,6 +50,7 @@ struct AspeedHACEClass { uint32_t dest_mask; uint32_t key_mask; uint32_t hash_mask; + bool raise_crypt_interrupt_workaround; }; #endif /* ASPEED_HACE_H */ diff --git a/include/hw/misc/aspeed_scu.h b/include/hw/misc/aspeed_scu.h index 356be95e45..684b48b722 100644 --- a/include/hw/misc/aspeed_scu.h +++ b/include/hw/misc/aspeed_scu.h @@ -54,6 +54,8 @@ struct AspeedSCUState { #define AST2700_A0_SILICON_REV 0x06000103U #define AST2720_A0_SILICON_REV 0x06000203U #define AST2750_A0_SILICON_REV 0x06000003U +#define AST2700_A1_SILICON_REV 0x06010103U +#define AST2750_A1_SILICON_REV 0x06010003U #define ASPEED_IS_AST2500(si_rev) ((((si_rev) >> 24) & 0xff) == 0x04) diff --git a/meson.build b/meson.build index 8b9fda4d95..4899d896de 100644 --- a/meson.build +++ b/meson.build @@ -601,6 +601,10 @@ if get_option('tsan') qemu_ldflags = ['-fsanitize=thread'] + qemu_ldflags endif +if get_option('debug') and get_option('split_debug') + qemu_cflags += '-gsplit-dwarf' +endif + # Detect support for PT_GNU_RELRO + DT_BIND_NOW. # The combination is known as "full relro", because .got.plt is read-only too. qemu_ldflags += cc.get_supported_link_arguments('-Wl,-z,relro', '-Wl,-z,now') @@ -4015,7 +4019,7 @@ libchardev = static_library('chardev', chardev_ss.sources() + genh, build_by_default: false) chardev = declare_dependency(objects: libchardev.extract_all_objects(recursive: false), - dependencies: chardev_ss.dependencies()) + dependencies: [chardev_ss.dependencies(), io]) hwcore_ss = hwcore_ss.apply({}) libhwcore = static_library('hwcore', sources: hwcore_ss.sources() + genh, @@ -4100,13 +4104,6 @@ if have_rust foreach enum : c_bitfields bindgen_args += ['--bitfield-enum', enum] endforeach - c_nocopy = [ - 'QEMUTimer', - ] - # Used to customize Drop trait - foreach struct : c_nocopy - bindgen_args += ['--no-copy', struct] - endforeach # TODO: Remove this comment when the clang/libclang mismatch issue is solved. # @@ -4590,6 +4587,8 @@ if have_rust summary_info += {'bindgen': bindgen.full_path()} summary_info += {'bindgen version': bindgen.version()} endif +# option_cflags is purely for the summary display, meson will pass +# -g/-O options directly option_cflags = (get_option('debug') ? ['-g'] : []) if get_option('optimization') != 'plain' option_cflags += ['-O' + get_option('optimization')] diff --git a/meson_options.txt b/meson_options.txt index 59d973bca0..3432123fee 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -362,6 +362,8 @@ option('debug_mutex', type: 'boolean', value: false, description: 'mutex debugging support') option('debug_stack_usage', type: 'boolean', value: false, description: 'measure coroutine stack usage') +option('split_debug', type: 'boolean', value: true, + description: 'split debug info from object files') option('qom_cast_debug', type: 'boolean', value: true, description: 'cast debugging support') option('slirp_smbd', type : 'feature', value : 'auto', diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 5041d6291f..ab1185a814 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -37,6 +37,8 @@ result_unit_err = "allow" should_implement_trait = "deny" # can be for a reason, e.g. in callbacks unused_self = "allow" +# common in device crates +upper_case_acronyms = "allow" # default-allow lints as_ptr_cast_mut = "deny" diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index d0857b470c..f137b49fea 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -2,18 +2,10 @@ // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> // SPDX-License-Identifier: GPL-2.0-or-later -use std::{ - ffi::CStr, - os::raw::{c_int, c_void}, - ptr::{addr_of, addr_of_mut, NonNull}, -}; +use std::{ffi::CStr, ptr::addr_of_mut}; use qemu_api::{ - bindings::{ - qemu_chr_fe_accept_input, qemu_chr_fe_ioctl, qemu_chr_fe_set_handlers, - qemu_chr_fe_write_all, CharBackend, QEMUChrEvent, CHR_IOCTL_SERIAL_SET_BREAK, - }, - chardev::Chardev, + chardev::{CharBackend, Chardev, Event}, impl_vmstate_forward, irq::{IRQState, InterruptSource}, memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder}, @@ -26,10 +18,13 @@ use qemu_api::{ use crate::{ device_class, - registers::{self, Interrupt}, - RegisterOffset, + registers::{self, Interrupt, RegisterOffset}, }; +// TODO: You must disable the UART before any of the control registers are +// reprogrammed. When the UART is disabled in the middle of transmission or +// reception, it completes the current character before stopping + /// Integer Baud Rate Divider, `UARTIBRD` const IBRD_MASK: u32 = 0xffff; @@ -232,14 +227,14 @@ impl PL011Registers { &mut self, offset: RegisterOffset, value: u32, - char_backend: *mut CharBackend, + char_backend: &CharBackend, ) -> bool { // eprintln!("write offset {offset} value {value}"); use RegisterOffset::*; match offset { DR => { // interrupts always checked - let _ = self.loopback_tx(value); + let _ = self.loopback_tx(value.into()); self.int_level |= Interrupt::TX.0; return true; } @@ -266,17 +261,9 @@ impl PL011Registers { self.reset_tx_fifo(); } let update = (self.line_control.send_break() != new_val.send_break()) && { - let mut break_enable: c_int = new_val.send_break().into(); - // SAFETY: self.char_backend is a valid CharBackend instance after it's been - // initialized in realize(). - unsafe { - qemu_chr_fe_ioctl( - char_backend, - CHR_IOCTL_SERIAL_SET_BREAK as i32, - addr_of_mut!(break_enable).cast::<c_void>(), - ); - } - self.loopback_break(break_enable > 0) + let break_enable = new_val.send_break(); + let _ = char_backend.send_break(break_enable); + self.loopback_break(break_enable) }; self.line_control = new_val; self.set_read_trigger(); @@ -314,7 +301,7 @@ impl PL011Registers { #[inline] #[must_use] - fn loopback_tx(&mut self, value: u32) -> bool { + fn loopback_tx(&mut self, value: registers::Data) -> bool { // Caveat: // // In real hardware, TX loopback happens at the serial-bit level @@ -383,7 +370,7 @@ impl PL011Registers { } fn loopback_break(&mut self, enable: bool) -> bool { - enable && self.loopback_tx(registers::Data::BREAK.into()) + enable && self.loopback_tx(registers::Data::BREAK) } fn set_read_trigger(&mut self) { @@ -442,11 +429,11 @@ impl PL011Registers { } #[must_use] - pub fn put_fifo(&mut self, value: u32) -> bool { + pub fn put_fifo(&mut self, value: registers::Data) -> bool { let depth = self.fifo_depth(); assert!(depth > 0); let slot = (self.read_pos + self.read_count) & (depth - 1); - self.read_fifo[slot] = registers::Data::from(value); + self.read_fifo[slot] = value; self.read_count += 1; self.flags.set_receive_fifo_empty(false); if self.read_count == depth { @@ -534,7 +521,7 @@ impl PL011State { } } - pub fn read(&self, offset: hwaddr, _size: u32) -> u64 { + fn read(&self, offset: hwaddr, _size: u32) -> u64 { match RegisterOffset::try_from(offset) { Err(v) if (0x3f8..0x400).contains(&(v >> 2)) => { let device_id = self.get_class().device_id; @@ -548,37 +535,30 @@ impl PL011State { let (update_irq, result) = self.regs.borrow_mut().read(field); if update_irq { self.update(); - unsafe { - qemu_chr_fe_accept_input(addr_of!(self.char_backend) as *mut _); - } + self.char_backend.accept_input(); } result.into() } } } - pub fn write(&self, offset: hwaddr, value: u64, _size: u32) { + fn write(&self, offset: hwaddr, value: u64, _size: u32) { let mut update_irq = false; if let Ok(field) = RegisterOffset::try_from(offset) { // qemu_chr_fe_write_all() calls into the can_receive // callback, so handle writes before entering PL011Registers. if field == RegisterOffset::DR { // ??? Check if transmitter is enabled. - let ch: u8 = value as u8; - // SAFETY: char_backend is a valid CharBackend instance after it's been - // initialized in realize(). + let ch: [u8; 1] = [value as u8]; // XXX this blocks entire thread. Rewrite to use // qemu_chr_fe_write and background I/O callbacks - unsafe { - qemu_chr_fe_write_all(addr_of!(self.char_backend) as *mut _, &ch, 1); - } + let _ = self.char_backend.write_all(&ch); } - update_irq = self.regs.borrow_mut().write( - field, - value as u32, - addr_of!(self.char_backend) as *mut _, - ); + update_irq = self + .regs + .borrow_mut() + .write(field, value as u32, &self.char_backend); } else { eprintln!("write bad offset {offset} value {value}"); } @@ -587,15 +567,19 @@ impl PL011State { } } - pub fn can_receive(&self) -> bool { - // trace_pl011_can_receive(s->lcr, s->read_count, r); + fn can_receive(&self) -> u32 { let regs = self.regs.borrow(); - regs.read_count < regs.fifo_depth() + // trace_pl011_can_receive(s->lcr, s->read_count, r); + u32::from(regs.read_count < regs.fifo_depth()) } - pub fn receive(&self, ch: u32) { + fn receive(&self, buf: &[u8]) { + if buf.is_empty() { + return; + } let mut regs = self.regs.borrow_mut(); - let update_irq = !regs.loopback_enabled() && regs.put_fifo(ch); + let c: u32 = buf[0].into(); + let update_irq = !regs.loopback_enabled() && regs.put_fifo(c.into()); // Release the BqlRefCell before calling self.update() drop(regs); @@ -604,11 +588,11 @@ impl PL011State { } } - pub fn event(&self, event: QEMUChrEvent) { + fn event(&self, event: Event) { let mut update_irq = false; let mut regs = self.regs.borrow_mut(); - if event == QEMUChrEvent::CHR_EVENT_BREAK && !regs.loopback_enabled() { - update_irq = regs.put_fifo(registers::Data::BREAK.into()); + if event == Event::CHR_EVENT_BREAK && !regs.loopback_enabled() { + update_irq = regs.put_fifo(registers::Data::BREAK); } // Release the BqlRefCell before calling self.update() drop(regs); @@ -618,28 +602,16 @@ impl PL011State { } } - pub fn realize(&self) { - // SAFETY: self.char_backend has the correct size and alignment for a - // CharBackend object, and its callbacks are of the correct types. - unsafe { - qemu_chr_fe_set_handlers( - addr_of!(self.char_backend) as *mut CharBackend, - Some(pl011_can_receive), - Some(pl011_receive), - Some(pl011_event), - None, - addr_of!(*self).cast::<c_void>() as *mut c_void, - core::ptr::null_mut(), - true, - ); - } + fn realize(&self) { + self.char_backend + .enable_handlers(self, Self::can_receive, Self::receive, Self::event); } - pub fn reset_hold(&self, _type: ResetType) { + fn reset_hold(&self, _type: ResetType) { self.regs.borrow_mut().reset(); } - pub fn update(&self) { + fn update(&self) { let regs = self.regs.borrow(); let flags = regs.int_level & regs.int_enabled; for (irq, i) in self.interrupts.iter().zip(IRQMASK) { @@ -665,43 +637,6 @@ const IRQMASK: [u32; 6] = [ /// # Safety /// -/// We expect the FFI user of this function to pass a valid pointer, that has -/// the same size as [`PL011State`]. We also expect the device is -/// readable/writeable from one thread at any time. -pub unsafe extern "C" fn pl011_can_receive(opaque: *mut c_void) -> c_int { - let state = NonNull::new(opaque).unwrap().cast::<PL011State>(); - unsafe { state.as_ref().can_receive().into() } -} - -/// # Safety -/// -/// We expect the FFI user of this function to pass a valid pointer, that has -/// the same size as [`PL011State`]. We also expect the device is -/// readable/writeable from one thread at any time. -/// -/// The buffer and size arguments must also be valid. -pub unsafe extern "C" fn pl011_receive(opaque: *mut c_void, buf: *const u8, size: c_int) { - let state = NonNull::new(opaque).unwrap().cast::<PL011State>(); - unsafe { - if size > 0 { - debug_assert!(!buf.is_null()); - state.as_ref().receive(u32::from(buf.read_volatile())); - } - } -} - -/// # Safety -/// -/// We expect the FFI user of this function to pass a valid pointer, that has -/// the same size as [`PL011State`]. We also expect the device is -/// readable/writeable from one thread at any time. -pub unsafe extern "C" fn pl011_event(opaque: *mut c_void, event: QEMUChrEvent) { - let state = NonNull::new(opaque).unwrap().cast::<PL011State>(); - unsafe { state.as_ref().event(event) } -} - -/// # Safety -/// /// We expect the FFI user of this function to pass a valid pointer for `chr` /// and `irq`. #[no_mangle] @@ -713,10 +648,12 @@ pub unsafe extern "C" fn pl011_create( // SAFETY: The callers promise that they have owned references. // They do not gift them to pl011_create, so use `Owned::from`. let irq = unsafe { Owned::<IRQState>::from(&*irq) }; - let chr = unsafe { Owned::<Chardev>::from(&*chr) }; let dev = PL011State::new(); - dev.prop_set_chr("chardev", &chr); + if !chr.is_null() { + let chr = unsafe { Owned::<Chardev>::from(&*chr) }; + dev.prop_set_chr("chardev", &chr); + } dev.sysbus_realize(); dev.mmio_map(0, addr); dev.connect_irq(0, &irq); diff --git a/rust/hw/char/pl011/src/lib.rs b/rust/hw/char/pl011/src/lib.rs index 1bf46c65af..dbae76991c 100644 --- a/rust/hw/char/pl011/src/lib.rs +++ b/rust/hw/char/pl011/src/lib.rs @@ -12,522 +12,13 @@ //! See [`PL011State`](crate::device::PL011State) for the device model type and //! the [`registers`] module for register types. -#![allow(clippy::upper_case_acronyms)] - use qemu_api::c_str; mod device; mod device_class; +mod registers; pub use device::pl011_create; pub const TYPE_PL011: &::std::ffi::CStr = c_str!("pl011"); pub const TYPE_PL011_LUMINARY: &::std::ffi::CStr = c_str!("pl011_luminary"); - -/// Offset of each register from the base memory address of the device. -/// -/// # Source -/// ARM DDI 0183G, Table 3-1 p.3-3 -#[doc(alias = "offset")] -#[allow(non_camel_case_types)] -#[repr(u64)] -#[derive(Debug, Eq, PartialEq, qemu_api_macros::TryInto)] -enum RegisterOffset { - /// Data Register - /// - /// A write to this register initiates the actual data transmission - #[doc(alias = "UARTDR")] - DR = 0x000, - /// Receive Status Register or Error Clear Register - #[doc(alias = "UARTRSR")] - #[doc(alias = "UARTECR")] - RSR = 0x004, - /// Flag Register - /// - /// A read of this register shows if transmission is complete - #[doc(alias = "UARTFR")] - FR = 0x018, - /// Fractional Baud Rate Register - /// - /// responsible for baud rate speed - #[doc(alias = "UARTFBRD")] - FBRD = 0x028, - /// `IrDA` Low-Power Counter Register - #[doc(alias = "UARTILPR")] - ILPR = 0x020, - /// Integer Baud Rate Register - /// - /// Responsible for baud rate speed - #[doc(alias = "UARTIBRD")] - IBRD = 0x024, - /// line control register (data frame format) - #[doc(alias = "UARTLCR_H")] - LCR_H = 0x02C, - /// Toggle UART, transmission or reception - #[doc(alias = "UARTCR")] - CR = 0x030, - /// Interrupt FIFO Level Select Register - #[doc(alias = "UARTIFLS")] - FLS = 0x034, - /// Interrupt Mask Set/Clear Register - #[doc(alias = "UARTIMSC")] - IMSC = 0x038, - /// Raw Interrupt Status Register - #[doc(alias = "UARTRIS")] - RIS = 0x03C, - /// Masked Interrupt Status Register - #[doc(alias = "UARTMIS")] - MIS = 0x040, - /// Interrupt Clear Register - #[doc(alias = "UARTICR")] - ICR = 0x044, - /// DMA control Register - #[doc(alias = "UARTDMACR")] - DMACR = 0x048, - ///// Reserved, offsets `0x04C` to `0x07C`. - //Reserved = 0x04C, -} - -mod registers { - //! Device registers exposed as typed structs which are backed by arbitrary - //! integer bitmaps. [`Data`], [`Control`], [`LineControl`], etc. - use bilge::prelude::*; - use qemu_api::impl_vmstate_bitsized; - - /// Receive Status Register / Data Register common error bits - /// - /// The `UARTRSR` register is updated only when a read occurs - /// from the `UARTDR` register with the same status information - /// that can also be obtained by reading the `UARTDR` register - #[bitsize(8)] - #[derive(Clone, Copy, Default, DebugBits, FromBits)] - pub struct Errors { - pub framing_error: bool, - pub parity_error: bool, - pub break_error: bool, - pub overrun_error: bool, - _reserved_unpredictable: u4, - } - - // TODO: FIFO Mode has different semantics - /// Data Register, `UARTDR` - /// - /// The `UARTDR` register is the data register. - /// - /// For words to be transmitted: - /// - /// - if the FIFOs are enabled, data written to this location is pushed onto - /// the transmit - /// FIFO - /// - if the FIFOs are not enabled, data is stored in the transmitter - /// holding register (the - /// bottom word of the transmit FIFO). - /// - /// The write operation initiates transmission from the UART. The data is - /// prefixed with a start bit, appended with the appropriate parity bit - /// (if parity is enabled), and a stop bit. The resultant word is then - /// transmitted. - /// - /// For received words: - /// - /// - if the FIFOs are enabled, the data byte and the 4-bit status (break, - /// frame, parity, - /// and overrun) is pushed onto the 12-bit wide receive FIFO - /// - if the FIFOs are not enabled, the data byte and status are stored in - /// the receiving - /// holding register (the bottom word of the receive FIFO). - /// - /// The received data byte is read by performing reads from the `UARTDR` - /// register along with the corresponding status information. The status - /// information can also be read by a read of the `UARTRSR/UARTECR` - /// register. - /// - /// # Note - /// - /// You must disable the UART before any of the control registers are - /// reprogrammed. When the UART is disabled in the middle of - /// transmission or reception, it completes the current character before - /// stopping. - /// - /// # Source - /// ARM DDI 0183G 3.3.1 Data Register, UARTDR - #[bitsize(32)] - #[derive(Clone, Copy, Default, DebugBits, FromBits)] - #[doc(alias = "UARTDR")] - pub struct Data { - pub data: u8, - pub errors: Errors, - _reserved: u16, - } - impl_vmstate_bitsized!(Data); - - impl Data { - // bilge is not very const-friendly, unfortunately - pub const BREAK: Self = Self { value: 1 << 10 }; - } - - // TODO: FIFO Mode has different semantics - /// Receive Status Register / Error Clear Register, `UARTRSR/UARTECR` - /// - /// The UARTRSR/UARTECR register is the receive status register/error clear - /// register. Receive status can also be read from the `UARTRSR` - /// register. If the status is read from this register, then the status - /// information for break, framing and parity corresponds to the - /// data character read from the [Data register](Data), `UARTDR` prior to - /// reading the UARTRSR register. The status information for overrun is - /// set immediately when an overrun condition occurs. - /// - /// - /// # Note - /// The received data character must be read first from the [Data - /// Register](Data), `UARTDR` before reading the error status associated - /// with that data character from the `UARTRSR` register. This read - /// sequence cannot be reversed, because the `UARTRSR` register is - /// updated only when a read occurs from the `UARTDR` register. However, - /// the status information can also be obtained by reading the `UARTDR` - /// register - /// - /// # Source - /// ARM DDI 0183G 3.3.2 Receive Status Register/Error Clear Register, - /// UARTRSR/UARTECR - #[bitsize(32)] - #[derive(Clone, Copy, DebugBits, FromBits)] - pub struct ReceiveStatusErrorClear { - pub errors: Errors, - _reserved_unpredictable: u24, - } - impl_vmstate_bitsized!(ReceiveStatusErrorClear); - - impl ReceiveStatusErrorClear { - pub fn set_from_data(&mut self, data: Data) { - self.set_errors(data.errors()); - } - - pub fn reset(&mut self) { - // All the bits are cleared to 0 on reset. - *self = Self::default(); - } - } - - impl Default for ReceiveStatusErrorClear { - fn default() -> Self { - 0.into() - } - } - - #[bitsize(32)] - #[derive(Clone, Copy, DebugBits, FromBits)] - /// Flag Register, `UARTFR` - #[doc(alias = "UARTFR")] - pub struct Flags { - /// CTS Clear to send. This bit is the complement of the UART clear to - /// send, `nUARTCTS`, modem status input. That is, the bit is 1 - /// when `nUARTCTS` is LOW. - pub clear_to_send: bool, - /// DSR Data set ready. This bit is the complement of the UART data set - /// ready, `nUARTDSR`, modem status input. That is, the bit is 1 when - /// `nUARTDSR` is LOW. - pub data_set_ready: bool, - /// DCD Data carrier detect. This bit is the complement of the UART data - /// carrier detect, `nUARTDCD`, modem status input. That is, the bit is - /// 1 when `nUARTDCD` is LOW. - pub data_carrier_detect: bool, - /// BUSY UART busy. If this bit is set to 1, the UART is busy - /// transmitting data. This bit remains set until the complete - /// byte, including all the stop bits, has been sent from the - /// shift register. This bit is set as soon as the transmit FIFO - /// becomes non-empty, regardless of whether the UART is enabled - /// or not. - pub busy: bool, - /// RXFE Receive FIFO empty. The meaning of this bit depends on the - /// state of the FEN bit in the UARTLCR_H register. If the FIFO - /// is disabled, this bit is set when the receive holding - /// register is empty. If the FIFO is enabled, the RXFE bit is - /// set when the receive FIFO is empty. - pub receive_fifo_empty: bool, - /// TXFF Transmit FIFO full. The meaning of this bit depends on the - /// state of the FEN bit in the UARTLCR_H register. If the FIFO - /// is disabled, this bit is set when the transmit holding - /// register is full. If the FIFO is enabled, the TXFF bit is - /// set when the transmit FIFO is full. - pub transmit_fifo_full: bool, - /// RXFF Receive FIFO full. The meaning of this bit depends on the state - /// of the FEN bit in the UARTLCR_H register. If the FIFO is - /// disabled, this bit is set when the receive holding register - /// is full. If the FIFO is enabled, the RXFF bit is set when - /// the receive FIFO is full. - pub receive_fifo_full: bool, - /// Transmit FIFO empty. The meaning of this bit depends on the state of - /// the FEN bit in the [Line Control register](LineControl), - /// `UARTLCR_H`. If the FIFO is disabled, this bit is set when the - /// transmit holding register is empty. If the FIFO is enabled, - /// the TXFE bit is set when the transmit FIFO is empty. This - /// bit does not indicate if there is data in the transmit shift - /// register. - pub transmit_fifo_empty: bool, - /// `RI`, is `true` when `nUARTRI` is `LOW`. - pub ring_indicator: bool, - _reserved_zero_no_modify: u23, - } - impl_vmstate_bitsized!(Flags); - - impl Flags { - pub fn reset(&mut self) { - *self = Self::default(); - } - } - - impl Default for Flags { - fn default() -> Self { - let mut ret: Self = 0.into(); - // After reset TXFF, RXFF, and BUSY are 0, and TXFE and RXFE are 1 - ret.set_receive_fifo_empty(true); - ret.set_transmit_fifo_empty(true); - ret - } - } - - #[bitsize(32)] - #[derive(Clone, Copy, DebugBits, FromBits)] - /// Line Control Register, `UARTLCR_H` - #[doc(alias = "UARTLCR_H")] - pub struct LineControl { - /// BRK Send break. - /// - /// If this bit is set to `1`, a low-level is continually output on the - /// `UARTTXD` output, after completing transmission of the - /// current character. For the proper execution of the break command, - /// the software must set this bit for at least two complete - /// frames. For normal use, this bit must be cleared to `0`. - pub send_break: bool, - /// 1 PEN Parity enable: - /// - /// - 0 = parity is disabled and no parity bit added to the data frame - /// - 1 = parity checking and generation is enabled. - /// - /// See Table 3-11 on page 3-14 for the parity truth table. - pub parity_enabled: bool, - /// EPS Even parity select. Controls the type of parity the UART uses - /// during transmission and reception: - /// - 0 = odd parity. The UART generates or checks for an odd number of - /// 1s in the data and parity bits. - /// - 1 = even parity. The UART generates or checks for an even number - /// of 1s in the data and parity bits. - /// This bit has no effect when the `PEN` bit disables parity checking - /// and generation. See Table 3-11 on page 3-14 for the parity - /// truth table. - pub parity: Parity, - /// 3 STP2 Two stop bits select. If this bit is set to 1, two stop bits - /// are transmitted at the end of the frame. The receive - /// logic does not check for two stop bits being received. - pub two_stops_bits: bool, - /// FEN Enable FIFOs: - /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become - /// 1-byte-deep holding registers 1 = transmit and receive FIFO - /// buffers are enabled (FIFO mode). - pub fifos_enabled: Mode, - /// WLEN Word length. These bits indicate the number of data bits - /// transmitted or received in a frame as follows: b11 = 8 bits - /// b10 = 7 bits - /// b01 = 6 bits - /// b00 = 5 bits. - pub word_length: WordLength, - /// 7 SPS Stick parity select. - /// 0 = stick parity is disabled - /// 1 = either: - /// • if the EPS bit is 0 then the parity bit is transmitted and checked - /// as a 1 • if the EPS bit is 1 then the parity bit is - /// transmitted and checked as a 0. This bit has no effect when - /// the PEN bit disables parity checking and generation. See Table 3-11 - /// on page 3-14 for the parity truth table. - pub sticky_parity: bool, - /// 31:8 - Reserved, do not modify, read as zero. - _reserved_zero_no_modify: u24, - } - impl_vmstate_bitsized!(LineControl); - - impl LineControl { - pub fn reset(&mut self) { - // All the bits are cleared to 0 when reset. - *self = 0.into(); - } - } - - impl Default for LineControl { - fn default() -> Self { - 0.into() - } - } - - #[bitsize(1)] - #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] - /// `EPS` "Even parity select", field of [Line Control - /// register](LineControl). - pub enum Parity { - /// - 0 = odd parity. The UART generates or checks for an odd number of - /// 1s in the data and parity bits. - Odd = 0, - /// - 1 = even parity. The UART generates or checks for an even number - /// of 1s in the data and parity bits. - Even = 1, - } - - #[bitsize(1)] - #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] - /// `FEN` "Enable FIFOs" or Device mode, field of [Line Control - /// register](LineControl). - pub enum Mode { - /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become - /// 1-byte-deep holding registers - Character = 0, - /// 1 = transmit and receive FIFO buffers are enabled (FIFO mode). - FIFO = 1, - } - - #[bitsize(2)] - #[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] - /// `WLEN` Word length, field of [Line Control register](LineControl). - /// - /// These bits indicate the number of data bits transmitted or received in a - /// frame as follows: - pub enum WordLength { - /// b11 = 8 bits - _8Bits = 0b11, - /// b10 = 7 bits - _7Bits = 0b10, - /// b01 = 6 bits - _6Bits = 0b01, - /// b00 = 5 bits. - _5Bits = 0b00, - } - - /// Control Register, `UARTCR` - /// - /// The `UARTCR` register is the control register. All the bits are cleared - /// to `0` on reset except for bits `9` and `8` that are set to `1`. - /// - /// # Source - /// ARM DDI 0183G, 3.3.8 Control Register, `UARTCR`, Table 3-12 - #[bitsize(32)] - #[doc(alias = "UARTCR")] - #[derive(Clone, Copy, DebugBits, FromBits)] - pub struct Control { - /// `UARTEN` UART enable: 0 = UART is disabled. If the UART is disabled - /// in the middle of transmission or reception, it completes the current - /// character before stopping. 1 = the UART is enabled. Data - /// transmission and reception occurs for either UART signals or SIR - /// signals depending on the setting of the SIREN bit. - pub enable_uart: bool, - /// `SIREN` `SIR` enable: 0 = IrDA SIR ENDEC is disabled. `nSIROUT` - /// remains LOW (no light pulse generated), and signal transitions on - /// SIRIN have no effect. 1 = IrDA SIR ENDEC is enabled. Data is - /// transmitted and received on nSIROUT and SIRIN. UARTTXD remains HIGH, - /// in the marking state. Signal transitions on UARTRXD or modem status - /// inputs have no effect. This bit has no effect if the UARTEN bit - /// disables the UART. - pub enable_sir: bool, - /// `SIRLP` SIR low-power IrDA mode. This bit selects the IrDA encoding - /// mode. If this bit is cleared to 0, low-level bits are transmitted as - /// an active high pulse with a width of 3/ 16th of the bit period. If - /// this bit is set to 1, low-level bits are transmitted with a pulse - /// width that is 3 times the period of the IrLPBaud16 input signal, - /// regardless of the selected bit rate. Setting this bit uses less - /// power, but might reduce transmission distances. - pub sir_lowpower_irda_mode: u1, - /// Reserved, do not modify, read as zero. - _reserved_zero_no_modify: u4, - /// `LBE` Loopback enable. If this bit is set to 1 and the SIREN bit is - /// set to 1 and the SIRTEST bit in the Test Control register, UARTTCR - /// on page 4-5 is set to 1, then the nSIROUT path is inverted, and fed - /// through to the SIRIN path. The SIRTEST bit in the test register must - /// be set to 1 to override the normal half-duplex SIR operation. This - /// must be the requirement for accessing the test registers during - /// normal operation, and SIRTEST must be cleared to 0 when loopback - /// testing is finished. This feature reduces the amount of external - /// coupling required during system test. If this bit is set to 1, and - /// the SIRTEST bit is set to 0, the UARTTXD path is fed through to the - /// UARTRXD path. In either SIR mode or UART mode, when this bit is set, - /// the modem outputs are also fed through to the modem inputs. This bit - /// is cleared to 0 on reset, to disable loopback. - pub enable_loopback: bool, - /// `TXE` Transmit enable. If this bit is set to 1, the transmit section - /// of the UART is enabled. Data transmission occurs for either UART - /// signals, or SIR signals depending on the setting of the SIREN bit. - /// When the UART is disabled in the middle of transmission, it - /// completes the current character before stopping. - pub enable_transmit: bool, - /// `RXE` Receive enable. If this bit is set to 1, the receive section - /// of the UART is enabled. Data reception occurs for either UART - /// signals or SIR signals depending on the setting of the SIREN bit. - /// When the UART is disabled in the middle of reception, it completes - /// the current character before stopping. - pub enable_receive: bool, - /// `DTR` Data transmit ready. This bit is the complement of the UART - /// data transmit ready, `nUARTDTR`, modem status output. That is, when - /// the bit is programmed to a 1 then `nUARTDTR` is LOW. - pub data_transmit_ready: bool, - /// `RTS` Request to send. This bit is the complement of the UART - /// request to send, `nUARTRTS`, modem status output. That is, when the - /// bit is programmed to a 1 then `nUARTRTS` is LOW. - pub request_to_send: bool, - /// `Out1` This bit is the complement of the UART Out1 (`nUARTOut1`) - /// modem status output. That is, when the bit is programmed to a 1 the - /// output is 0. For DTE this can be used as Data Carrier Detect (DCD). - pub out_1: bool, - /// `Out2` This bit is the complement of the UART Out2 (`nUARTOut2`) - /// modem status output. That is, when the bit is programmed to a 1, the - /// output is 0. For DTE this can be used as Ring Indicator (RI). - pub out_2: bool, - /// `RTSEn` RTS hardware flow control enable. If this bit is set to 1, - /// RTS hardware flow control is enabled. Data is only requested when - /// there is space in the receive FIFO for it to be received. - pub rts_hardware_flow_control_enable: bool, - /// `CTSEn` CTS hardware flow control enable. If this bit is set to 1, - /// CTS hardware flow control is enabled. Data is only transmitted when - /// the `nUARTCTS` signal is asserted. - pub cts_hardware_flow_control_enable: bool, - /// 31:16 - Reserved, do not modify, read as zero. - _reserved_zero_no_modify2: u16, - } - impl_vmstate_bitsized!(Control); - - impl Control { - pub fn reset(&mut self) { - *self = 0.into(); - self.set_enable_receive(true); - self.set_enable_transmit(true); - } - } - - impl Default for Control { - fn default() -> Self { - let mut ret: Self = 0.into(); - ret.reset(); - ret - } - } - - /// Interrupt status bits in UARTRIS, UARTMIS, UARTIMSC - pub struct Interrupt(pub u32); - - impl Interrupt { - pub const OE: Self = Self(1 << 10); - pub const BE: Self = Self(1 << 9); - pub const PE: Self = Self(1 << 8); - pub const FE: Self = Self(1 << 7); - pub const RT: Self = Self(1 << 6); - pub const TX: Self = Self(1 << 5); - pub const RX: Self = Self(1 << 4); - pub const DSR: Self = Self(1 << 3); - pub const DCD: Self = Self(1 << 2); - pub const CTS: Self = Self(1 << 1); - pub const RI: Self = Self(1 << 0); - - pub const E: Self = Self(Self::OE.0 | Self::BE.0 | Self::PE.0 | Self::FE.0); - pub const MS: Self = Self(Self::RI.0 | Self::DSR.0 | Self::DCD.0 | Self::CTS.0); - } -} - -// TODO: You must disable the UART before any of the control registers are -// reprogrammed. When the UART is disabled in the middle of transmission or -// reception, it completes the current character before stopping diff --git a/rust/hw/char/pl011/src/registers.rs b/rust/hw/char/pl011/src/registers.rs new file mode 100644 index 0000000000..cd92fa2c30 --- /dev/null +++ b/rust/hw/char/pl011/src/registers.rs @@ -0,0 +1,506 @@ +// Copyright 2024, Linaro Limited +// Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Device registers exposed as typed structs which are backed by arbitrary +//! integer bitmaps. [`Data`], [`Control`], [`LineControl`], etc. + +use bilge::prelude::*; +use qemu_api::impl_vmstate_bitsized; + +/// Offset of each register from the base memory address of the device. +/// +/// # Source +/// ARM DDI 0183G, Table 3-1 p.3-3 +#[doc(alias = "offset")] +#[allow(non_camel_case_types)] +#[repr(u64)] +#[derive(Debug, Eq, PartialEq, qemu_api_macros::TryInto)] +pub enum RegisterOffset { + /// Data Register + /// + /// A write to this register initiates the actual data transmission + #[doc(alias = "UARTDR")] + DR = 0x000, + /// Receive Status Register or Error Clear Register + #[doc(alias = "UARTRSR")] + #[doc(alias = "UARTECR")] + RSR = 0x004, + /// Flag Register + /// + /// A read of this register shows if transmission is complete + #[doc(alias = "UARTFR")] + FR = 0x018, + /// Fractional Baud Rate Register + /// + /// responsible for baud rate speed + #[doc(alias = "UARTFBRD")] + FBRD = 0x028, + /// `IrDA` Low-Power Counter Register + #[doc(alias = "UARTILPR")] + ILPR = 0x020, + /// Integer Baud Rate Register + /// + /// Responsible for baud rate speed + #[doc(alias = "UARTIBRD")] + IBRD = 0x024, + /// line control register (data frame format) + #[doc(alias = "UARTLCR_H")] + LCR_H = 0x02C, + /// Toggle UART, transmission or reception + #[doc(alias = "UARTCR")] + CR = 0x030, + /// Interrupt FIFO Level Select Register + #[doc(alias = "UARTIFLS")] + FLS = 0x034, + /// Interrupt Mask Set/Clear Register + #[doc(alias = "UARTIMSC")] + IMSC = 0x038, + /// Raw Interrupt Status Register + #[doc(alias = "UARTRIS")] + RIS = 0x03C, + /// Masked Interrupt Status Register + #[doc(alias = "UARTMIS")] + MIS = 0x040, + /// Interrupt Clear Register + #[doc(alias = "UARTICR")] + ICR = 0x044, + /// DMA control Register + #[doc(alias = "UARTDMACR")] + DMACR = 0x048, + ///// Reserved, offsets `0x04C` to `0x07C`. + //Reserved = 0x04C, +} + +/// Receive Status Register / Data Register common error bits +/// +/// The `UARTRSR` register is updated only when a read occurs +/// from the `UARTDR` register with the same status information +/// that can also be obtained by reading the `UARTDR` register +#[bitsize(8)] +#[derive(Clone, Copy, Default, DebugBits, FromBits)] +pub struct Errors { + pub framing_error: bool, + pub parity_error: bool, + pub break_error: bool, + pub overrun_error: bool, + _reserved_unpredictable: u4, +} + +// TODO: FIFO Mode has different semantics +/// Data Register, `UARTDR` +/// +/// The `UARTDR` register is the data register. +/// +/// For words to be transmitted: +/// +/// - if the FIFOs are enabled, data written to this location is pushed onto the +/// transmit +/// FIFO +/// - if the FIFOs are not enabled, data is stored in the transmitter holding +/// register (the +/// bottom word of the transmit FIFO). +/// +/// The write operation initiates transmission from the UART. The data is +/// prefixed with a start bit, appended with the appropriate parity bit +/// (if parity is enabled), and a stop bit. The resultant word is then +/// transmitted. +/// +/// For received words: +/// +/// - if the FIFOs are enabled, the data byte and the 4-bit status (break, +/// frame, parity, +/// and overrun) is pushed onto the 12-bit wide receive FIFO +/// - if the FIFOs are not enabled, the data byte and status are stored in the +/// receiving +/// holding register (the bottom word of the receive FIFO). +/// +/// The received data byte is read by performing reads from the `UARTDR` +/// register along with the corresponding status information. The status +/// information can also be read by a read of the `UARTRSR/UARTECR` +/// register. +/// +/// # Note +/// +/// You must disable the UART before any of the control registers are +/// reprogrammed. When the UART is disabled in the middle of +/// transmission or reception, it completes the current character before +/// stopping. +/// +/// # Source +/// ARM DDI 0183G 3.3.1 Data Register, UARTDR +#[bitsize(32)] +#[derive(Clone, Copy, Default, DebugBits, FromBits)] +#[doc(alias = "UARTDR")] +pub struct Data { + pub data: u8, + pub errors: Errors, + _reserved: u16, +} +impl_vmstate_bitsized!(Data); + +impl Data { + // bilge is not very const-friendly, unfortunately + pub const BREAK: Self = Self { value: 1 << 10 }; +} + +// TODO: FIFO Mode has different semantics +/// Receive Status Register / Error Clear Register, `UARTRSR/UARTECR` +/// +/// The UARTRSR/UARTECR register is the receive status register/error clear +/// register. Receive status can also be read from the `UARTRSR` +/// register. If the status is read from this register, then the status +/// information for break, framing and parity corresponds to the +/// data character read from the [Data register](Data), `UARTDR` prior to +/// reading the UARTRSR register. The status information for overrun is +/// set immediately when an overrun condition occurs. +/// +/// +/// # Note +/// The received data character must be read first from the [Data +/// Register](Data), `UARTDR` before reading the error status associated +/// with that data character from the `UARTRSR` register. This read +/// sequence cannot be reversed, because the `UARTRSR` register is +/// updated only when a read occurs from the `UARTDR` register. However, +/// the status information can also be obtained by reading the `UARTDR` +/// register +/// +/// # Source +/// ARM DDI 0183G 3.3.2 Receive Status Register/Error Clear Register, +/// UARTRSR/UARTECR +#[bitsize(32)] +#[derive(Clone, Copy, DebugBits, FromBits)] +pub struct ReceiveStatusErrorClear { + pub errors: Errors, + _reserved_unpredictable: u24, +} +impl_vmstate_bitsized!(ReceiveStatusErrorClear); + +impl ReceiveStatusErrorClear { + pub fn set_from_data(&mut self, data: Data) { + self.set_errors(data.errors()); + } + + pub fn reset(&mut self) { + // All the bits are cleared to 0 on reset. + *self = Self::default(); + } +} + +impl Default for ReceiveStatusErrorClear { + fn default() -> Self { + 0.into() + } +} + +#[bitsize(32)] +#[derive(Clone, Copy, DebugBits, FromBits)] +/// Flag Register, `UARTFR` +#[doc(alias = "UARTFR")] +pub struct Flags { + /// CTS Clear to send. This bit is the complement of the UART clear to + /// send, `nUARTCTS`, modem status input. That is, the bit is 1 + /// when `nUARTCTS` is LOW. + pub clear_to_send: bool, + /// DSR Data set ready. This bit is the complement of the UART data set + /// ready, `nUARTDSR`, modem status input. That is, the bit is 1 when + /// `nUARTDSR` is LOW. + pub data_set_ready: bool, + /// DCD Data carrier detect. This bit is the complement of the UART data + /// carrier detect, `nUARTDCD`, modem status input. That is, the bit is + /// 1 when `nUARTDCD` is LOW. + pub data_carrier_detect: bool, + /// BUSY UART busy. If this bit is set to 1, the UART is busy + /// transmitting data. This bit remains set until the complete + /// byte, including all the stop bits, has been sent from the + /// shift register. This bit is set as soon as the transmit FIFO + /// becomes non-empty, regardless of whether the UART is enabled + /// or not. + pub busy: bool, + /// RXFE Receive FIFO empty. The meaning of this bit depends on the + /// state of the FEN bit in the UARTLCR_H register. If the FIFO + /// is disabled, this bit is set when the receive holding + /// register is empty. If the FIFO is enabled, the RXFE bit is + /// set when the receive FIFO is empty. + pub receive_fifo_empty: bool, + /// TXFF Transmit FIFO full. The meaning of this bit depends on the + /// state of the FEN bit in the UARTLCR_H register. If the FIFO + /// is disabled, this bit is set when the transmit holding + /// register is full. If the FIFO is enabled, the TXFF bit is + /// set when the transmit FIFO is full. + pub transmit_fifo_full: bool, + /// RXFF Receive FIFO full. The meaning of this bit depends on the state + /// of the FEN bit in the UARTLCR_H register. If the FIFO is + /// disabled, this bit is set when the receive holding register + /// is full. If the FIFO is enabled, the RXFF bit is set when + /// the receive FIFO is full. + pub receive_fifo_full: bool, + /// Transmit FIFO empty. The meaning of this bit depends on the state of + /// the FEN bit in the [Line Control register](LineControl), + /// `UARTLCR_H`. If the FIFO is disabled, this bit is set when the + /// transmit holding register is empty. If the FIFO is enabled, + /// the TXFE bit is set when the transmit FIFO is empty. This + /// bit does not indicate if there is data in the transmit shift + /// register. + pub transmit_fifo_empty: bool, + /// `RI`, is `true` when `nUARTRI` is `LOW`. + pub ring_indicator: bool, + _reserved_zero_no_modify: u23, +} +impl_vmstate_bitsized!(Flags); + +impl Flags { + pub fn reset(&mut self) { + *self = Self::default(); + } +} + +impl Default for Flags { + fn default() -> Self { + let mut ret: Self = 0.into(); + // After reset TXFF, RXFF, and BUSY are 0, and TXFE and RXFE are 1 + ret.set_receive_fifo_empty(true); + ret.set_transmit_fifo_empty(true); + ret + } +} + +#[bitsize(32)] +#[derive(Clone, Copy, DebugBits, FromBits)] +/// Line Control Register, `UARTLCR_H` +#[doc(alias = "UARTLCR_H")] +pub struct LineControl { + /// BRK Send break. + /// + /// If this bit is set to `1`, a low-level is continually output on the + /// `UARTTXD` output, after completing transmission of the + /// current character. For the proper execution of the break command, + /// the software must set this bit for at least two complete + /// frames. For normal use, this bit must be cleared to `0`. + pub send_break: bool, + /// 1 PEN Parity enable: + /// + /// - 0 = parity is disabled and no parity bit added to the data frame + /// - 1 = parity checking and generation is enabled. + /// + /// See Table 3-11 on page 3-14 for the parity truth table. + pub parity_enabled: bool, + /// EPS Even parity select. Controls the type of parity the UART uses + /// during transmission and reception: + /// - 0 = odd parity. The UART generates or checks for an odd number of 1s + /// in the data and parity bits. + /// - 1 = even parity. The UART generates or checks for an even number of 1s + /// in the data and parity bits. + /// This bit has no effect when the `PEN` bit disables parity checking + /// and generation. See Table 3-11 on page 3-14 for the parity + /// truth table. + pub parity: Parity, + /// 3 STP2 Two stop bits select. If this bit is set to 1, two stop bits + /// are transmitted at the end of the frame. The receive + /// logic does not check for two stop bits being received. + pub two_stops_bits: bool, + /// FEN Enable FIFOs: + /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become + /// 1-byte-deep holding registers 1 = transmit and receive FIFO + /// buffers are enabled (FIFO mode). + pub fifos_enabled: Mode, + /// WLEN Word length. These bits indicate the number of data bits + /// transmitted or received in a frame as follows: b11 = 8 bits + /// b10 = 7 bits + /// b01 = 6 bits + /// b00 = 5 bits. + pub word_length: WordLength, + /// 7 SPS Stick parity select. + /// 0 = stick parity is disabled + /// 1 = either: + /// • if the EPS bit is 0 then the parity bit is transmitted and checked + /// as a 1 • if the EPS bit is 1 then the parity bit is + /// transmitted and checked as a 0. This bit has no effect when + /// the PEN bit disables parity checking and generation. See Table 3-11 + /// on page 3-14 for the parity truth table. + pub sticky_parity: bool, + /// 31:8 - Reserved, do not modify, read as zero. + _reserved_zero_no_modify: u24, +} +impl_vmstate_bitsized!(LineControl); + +impl LineControl { + pub fn reset(&mut self) { + // All the bits are cleared to 0 when reset. + *self = 0.into(); + } +} + +impl Default for LineControl { + fn default() -> Self { + 0.into() + } +} + +#[bitsize(1)] +#[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] +/// `EPS` "Even parity select", field of [Line Control +/// register](LineControl). +pub enum Parity { + /// - 0 = odd parity. The UART generates or checks for an odd number of 1s + /// in the data and parity bits. + Odd = 0, + /// - 1 = even parity. The UART generates or checks for an even number of 1s + /// in the data and parity bits. + Even = 1, +} + +#[bitsize(1)] +#[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] +/// `FEN` "Enable FIFOs" or Device mode, field of [Line Control +/// register](LineControl). +pub enum Mode { + /// 0 = FIFOs are disabled (character mode) that is, the FIFOs become + /// 1-byte-deep holding registers + Character = 0, + /// 1 = transmit and receive FIFO buffers are enabled (FIFO mode). + FIFO = 1, +} + +#[bitsize(2)] +#[derive(Clone, Copy, Debug, Eq, FromBits, PartialEq)] +/// `WLEN` Word length, field of [Line Control register](LineControl). +/// +/// These bits indicate the number of data bits transmitted or received in a +/// frame as follows: +pub enum WordLength { + /// b11 = 8 bits + _8Bits = 0b11, + /// b10 = 7 bits + _7Bits = 0b10, + /// b01 = 6 bits + _6Bits = 0b01, + /// b00 = 5 bits. + _5Bits = 0b00, +} + +/// Control Register, `UARTCR` +/// +/// The `UARTCR` register is the control register. All the bits are cleared +/// to `0` on reset except for bits `9` and `8` that are set to `1`. +/// +/// # Source +/// ARM DDI 0183G, 3.3.8 Control Register, `UARTCR`, Table 3-12 +#[bitsize(32)] +#[doc(alias = "UARTCR")] +#[derive(Clone, Copy, DebugBits, FromBits)] +pub struct Control { + /// `UARTEN` UART enable: 0 = UART is disabled. If the UART is disabled + /// in the middle of transmission or reception, it completes the current + /// character before stopping. 1 = the UART is enabled. Data + /// transmission and reception occurs for either UART signals or SIR + /// signals depending on the setting of the SIREN bit. + pub enable_uart: bool, + /// `SIREN` `SIR` enable: 0 = IrDA SIR ENDEC is disabled. `nSIROUT` + /// remains LOW (no light pulse generated), and signal transitions on + /// SIRIN have no effect. 1 = IrDA SIR ENDEC is enabled. Data is + /// transmitted and received on nSIROUT and SIRIN. UARTTXD remains HIGH, + /// in the marking state. Signal transitions on UARTRXD or modem status + /// inputs have no effect. This bit has no effect if the UARTEN bit + /// disables the UART. + pub enable_sir: bool, + /// `SIRLP` SIR low-power IrDA mode. This bit selects the IrDA encoding + /// mode. If this bit is cleared to 0, low-level bits are transmitted as + /// an active high pulse with a width of 3/ 16th of the bit period. If + /// this bit is set to 1, low-level bits are transmitted with a pulse + /// width that is 3 times the period of the IrLPBaud16 input signal, + /// regardless of the selected bit rate. Setting this bit uses less + /// power, but might reduce transmission distances. + pub sir_lowpower_irda_mode: u1, + /// Reserved, do not modify, read as zero. + _reserved_zero_no_modify: u4, + /// `LBE` Loopback enable. If this bit is set to 1 and the SIREN bit is + /// set to 1 and the SIRTEST bit in the Test Control register, UARTTCR + /// on page 4-5 is set to 1, then the nSIROUT path is inverted, and fed + /// through to the SIRIN path. The SIRTEST bit in the test register must + /// be set to 1 to override the normal half-duplex SIR operation. This + /// must be the requirement for accessing the test registers during + /// normal operation, and SIRTEST must be cleared to 0 when loopback + /// testing is finished. This feature reduces the amount of external + /// coupling required during system test. If this bit is set to 1, and + /// the SIRTEST bit is set to 0, the UARTTXD path is fed through to the + /// UARTRXD path. In either SIR mode or UART mode, when this bit is set, + /// the modem outputs are also fed through to the modem inputs. This bit + /// is cleared to 0 on reset, to disable loopback. + pub enable_loopback: bool, + /// `TXE` Transmit enable. If this bit is set to 1, the transmit section + /// of the UART is enabled. Data transmission occurs for either UART + /// signals, or SIR signals depending on the setting of the SIREN bit. + /// When the UART is disabled in the middle of transmission, it + /// completes the current character before stopping. + pub enable_transmit: bool, + /// `RXE` Receive enable. If this bit is set to 1, the receive section + /// of the UART is enabled. Data reception occurs for either UART + /// signals or SIR signals depending on the setting of the SIREN bit. + /// When the UART is disabled in the middle of reception, it completes + /// the current character before stopping. + pub enable_receive: bool, + /// `DTR` Data transmit ready. This bit is the complement of the UART + /// data transmit ready, `nUARTDTR`, modem status output. That is, when + /// the bit is programmed to a 1 then `nUARTDTR` is LOW. + pub data_transmit_ready: bool, + /// `RTS` Request to send. This bit is the complement of the UART + /// request to send, `nUARTRTS`, modem status output. That is, when the + /// bit is programmed to a 1 then `nUARTRTS` is LOW. + pub request_to_send: bool, + /// `Out1` This bit is the complement of the UART Out1 (`nUARTOut1`) + /// modem status output. That is, when the bit is programmed to a 1 the + /// output is 0. For DTE this can be used as Data Carrier Detect (DCD). + pub out_1: bool, + /// `Out2` This bit is the complement of the UART Out2 (`nUARTOut2`) + /// modem status output. That is, when the bit is programmed to a 1, the + /// output is 0. For DTE this can be used as Ring Indicator (RI). + pub out_2: bool, + /// `RTSEn` RTS hardware flow control enable. If this bit is set to 1, + /// RTS hardware flow control is enabled. Data is only requested when + /// there is space in the receive FIFO for it to be received. + pub rts_hardware_flow_control_enable: bool, + /// `CTSEn` CTS hardware flow control enable. If this bit is set to 1, + /// CTS hardware flow control is enabled. Data is only transmitted when + /// the `nUARTCTS` signal is asserted. + pub cts_hardware_flow_control_enable: bool, + /// 31:16 - Reserved, do not modify, read as zero. + _reserved_zero_no_modify2: u16, +} +impl_vmstate_bitsized!(Control); + +impl Control { + pub fn reset(&mut self) { + *self = 0.into(); + self.set_enable_receive(true); + self.set_enable_transmit(true); + } +} + +impl Default for Control { + fn default() -> Self { + let mut ret: Self = 0.into(); + ret.reset(); + ret + } +} + +/// Interrupt status bits in UARTRIS, UARTMIS, UARTIMSC +pub struct Interrupt(pub u32); + +impl Interrupt { + pub const OE: Self = Self(1 << 10); + pub const BE: Self = Self(1 << 9); + pub const PE: Self = Self(1 << 8); + pub const FE: Self = Self(1 << 7); + pub const RT: Self = Self(1 << 6); + pub const TX: Self = Self(1 << 5); + pub const RX: Self = Self(1 << 4); + pub const DSR: Self = Self(1 << 3); + pub const DCD: Self = Self(1 << 2); + pub const CTS: Self = Self(1 << 1); + pub const RI: Self = Self(1 << 0); + + pub const E: Self = Self(Self::OE.0 | Self::BE.0 | Self::PE.0 | Self::FE.0); + pub const MS: Self = Self(Self::RI.0 | Self::DSR.0 | Self::DCD.0 | Self::CTS.0); +} diff --git a/rust/hw/timer/hpet/src/hpet.rs b/rust/hw/timer/hpet/src/hpet.rs index be27eb0eff..20e0afdfca 100644 --- a/rust/hw/timer/hpet/src/hpet.rs +++ b/rust/hw/timer/hpet/src/hpet.rs @@ -4,6 +4,7 @@ use std::{ ffi::CStr, + pin::Pin, ptr::{addr_of_mut, null_mut, NonNull}, slice::from_ref, }; @@ -47,8 +48,6 @@ const RTC_ISA_IRQ: usize = 8; const HPET_CLK_PERIOD: u64 = 10; // 10 ns const FS_PER_NS: u64 = 1000000; // 1000000 femtoseconds == 1 ns -/// General Capabilities and ID Register -const HPET_CAP_REG: u64 = 0x000; /// Revision ID (bits 0:7). Revision 1 is implemented (refer to v1.0a spec). const HPET_CAP_REV_ID_VALUE: u64 = 0x1; const HPET_CAP_REV_ID_SHIFT: usize = 0; @@ -64,8 +63,6 @@ const HPET_CAP_VENDER_ID_SHIFT: usize = 16; /// Main Counter Tick Period (bits 32:63) const HPET_CAP_CNT_CLK_PERIOD_SHIFT: usize = 32; -/// General Configuration Register -const HPET_CFG_REG: u64 = 0x010; /// Overall Enable (bit 0) const HPET_CFG_ENABLE_SHIFT: usize = 0; /// Legacy Replacement Route (bit 1) @@ -73,14 +70,6 @@ const HPET_CFG_LEG_RT_SHIFT: usize = 1; /// Other bits are reserved. const HPET_CFG_WRITE_MASK: u64 = 0x003; -/// General Interrupt Status Register -const HPET_INT_STATUS_REG: u64 = 0x020; - -/// Main Counter Value Register -const HPET_COUNTER_REG: u64 = 0x0f0; - -/// Timer N Configuration and Capability Register (masked by 0x18) -const HPET_TN_CFG_REG: u64 = 0x000; /// bit 0, 7, and bits 16:31 are reserved. /// bit 4, 5, 15, and bits 32:64 are read-only. const HPET_TN_CFG_WRITE_MASK: u64 = 0x7f4e; @@ -108,11 +97,51 @@ const HPET_TN_CFG_FSB_CAP_SHIFT: usize = 15; /// Timer N Interrupt Routing Capability (bits 32:63) const HPET_TN_CFG_INT_ROUTE_CAP_SHIFT: usize = 32; -/// Timer N Comparator Value Register (masked by 0x18) -const HPET_TN_CMP_REG: u64 = 0x008; +#[derive(qemu_api_macros::TryInto)] +#[repr(u64)] +#[allow(non_camel_case_types)] +/// Timer registers, masked by 0x18 +enum TimerRegister { + /// Timer N Configuration and Capability Register + CFG = 0, + /// Timer N Comparator Value Register + CMP = 8, + /// Timer N FSB Interrupt Route Register + ROUTE = 16, +} + +#[derive(qemu_api_macros::TryInto)] +#[repr(u64)] +#[allow(non_camel_case_types)] +/// Global registers +enum GlobalRegister { + /// General Capabilities and ID Register + CAP = 0, + /// General Configuration Register + CFG = 0x10, + /// General Interrupt Status Register + INT_STATUS = 0x20, + /// Main Counter Value Register + COUNTER = 0xF0, +} -/// Timer N FSB Interrupt Route Register (masked by 0x18) -const HPET_TN_FSB_ROUTE_REG: u64 = 0x010; +enum HPETRegister<'a> { + /// Global register in the range from `0` to `0xff` + Global(GlobalRegister), + + /// Register in the timer block `0x100`...`0x3ff` + Timer(&'a BqlRefCell<HPETTimer>, TimerRegister), + + /// Invalid address + #[allow(dead_code)] + Unknown(hwaddr), +} + +struct HPETAddrDecode<'a> { + shift: u32, + len: u32, + reg: HPETRegister<'a>, +} const fn hpet_next_wrap(cur_tick: u64) -> u64 { (cur_tick | 0xffffffff) + 1 @@ -151,14 +180,14 @@ fn timer_handler(timer_cell: &BqlRefCell<HPETTimer>) { /// HPET Timer Abstraction #[repr(C)] -#[derive(Debug, Default, qemu_api_macros::offsets)] +#[derive(Debug, qemu_api_macros::offsets)] pub struct HPETTimer { /// timer N index within the timer block (`HPETState`) #[doc(alias = "tn")] index: usize, - qemu_timer: Option<Box<Timer>>, + qemu_timer: Timer, /// timer block abstraction containing this timer - state: Option<NonNull<HPETState>>, + state: NonNull<HPETState>, // Memory-mapped, software visible timer registers /// Timer N Configuration and Capability Register @@ -181,32 +210,39 @@ pub struct HPETTimer { } impl HPETTimer { - fn init(&mut self, index: usize, state_ptr: *mut HPETState) -> &mut Self { - *self = HPETTimer::default(); - self.index = index; - self.state = NonNull::new(state_ptr); - self - } - - fn init_timer_with_state(&mut self) { - self.qemu_timer = Some(Box::new({ - let mut t = Timer::new(); - t.init_full( - None, - CLOCK_VIRTUAL, - Timer::NS, - 0, - timer_handler, - &self.get_state().timers[self.index], - ); - t - })); + fn init(&mut self, index: usize, state: &HPETState) { + *self = HPETTimer { + index, + // SAFETY: the HPETTimer will only be used after the timer + // is initialized below. + qemu_timer: unsafe { Timer::new() }, + state: NonNull::new(state as *const _ as *mut _).unwrap(), + config: 0, + cmp: 0, + fsb: 0, + cmp64: 0, + period: 0, + wrap_flag: 0, + last: 0, + }; + + // SAFETY: HPETTimer is only used as part of HPETState, which is + // always pinned. + let qemu_timer = unsafe { Pin::new_unchecked(&mut self.qemu_timer) }; + qemu_timer.init_full( + None, + CLOCK_VIRTUAL, + Timer::NS, + 0, + timer_handler, + &state.timers[self.index], + ) } fn get_state(&self) -> &HPETState { // SAFETY: // the pointer is convertible to a reference - unsafe { self.state.unwrap().as_ref() } + unsafe { self.state.as_ref() } } fn is_int_active(&self) -> bool { @@ -330,7 +366,7 @@ impl HPETTimer { } self.last = ns; - self.qemu_timer.as_ref().unwrap().modify(self.last); + self.qemu_timer.modify(self.last); } fn set_timer(&mut self) { @@ -353,7 +389,7 @@ impl HPETTimer { fn del_timer(&mut self) { // Just remove the timer from the timer_list without destroying // this timer instance. - self.qemu_timer.as_ref().unwrap().delete(); + self.qemu_timer.delete(); if self.is_int_active() { // For level-triggered interrupt, this leaves interrupt status @@ -463,33 +499,21 @@ impl HPETTimer { self.update_irq(true); } - const fn read(&self, addr: hwaddr, _size: u32) -> u64 { - let shift: u64 = (addr & 4) * 8; - - match addr & !4 { - HPET_TN_CFG_REG => self.config >> shift, // including interrupt capabilities - HPET_TN_CMP_REG => self.cmp >> shift, // comparator register - HPET_TN_FSB_ROUTE_REG => self.fsb >> shift, - _ => { - // TODO: Add trace point - trace_hpet_ram_read_invalid() - // Reserved. - 0 - } + const fn read(&self, reg: TimerRegister) -> u64 { + use TimerRegister::*; + match reg { + CFG => self.config, // including interrupt capabilities + CMP => self.cmp, // comparator register + ROUTE => self.fsb, } } - fn write(&mut self, addr: hwaddr, value: u64, size: u32) { - let shift = ((addr & 4) * 8) as u32; - let len = std::cmp::min(size * 8, 64 - shift); - - match addr & !4 { - HPET_TN_CFG_REG => self.set_tn_cfg_reg(shift, len, value), - HPET_TN_CMP_REG => self.set_tn_cmp_reg(shift, len, value), - HPET_TN_FSB_ROUTE_REG => self.set_tn_fsb_route_reg(shift, len, value), - _ => { - // TODO: Add trace point - trace_hpet_ram_write_invalid() - // Reserved. - } + fn write(&mut self, reg: TimerRegister, value: u64, shift: u32, len: u32) { + use TimerRegister::*; + match reg { + CFG => self.set_tn_cfg_reg(shift, len, value), + CMP => self.set_tn_cmp_reg(shift, len, value), + ROUTE => self.set_tn_fsb_route_reg(shift, len, value), } } } @@ -581,13 +605,8 @@ impl HPETState { } fn init_timer(&self) { - let raw_ptr: *mut HPETState = self as *const HPETState as *mut HPETState; - for (index, timer) in self.timers.iter().enumerate() { - timer - .borrow_mut() - .init(index, raw_ptr) - .init_timer_with_state(); + timer.borrow_mut().init(index, self); } } @@ -727,8 +746,6 @@ impl HPETState { } fn reset_hold(&self, _type: ResetType) { - let sbd = self.upcast::<SysBusDevice>(); - for timer in self.timers.iter().take(self.num_timers.get()) { timer.borrow_mut().reset(); } @@ -741,83 +758,79 @@ impl HPETState { HPETFwConfig::update_hpet_cfg( self.hpet_id.get(), self.capability.get() as u32, - sbd.mmio[0].addr, + self.mmio_addr(0).unwrap(), ); // to document that the RTC lowers its output on reset as well self.rtc_irq_level.set(0); } - fn timer_and_addr(&self, addr: hwaddr) -> Option<(&BqlRefCell<HPETTimer>, hwaddr)> { - let timer_id: usize = ((addr - 0x100) / 0x20) as usize; + fn decode(&self, mut addr: hwaddr, size: u32) -> HPETAddrDecode { + let shift = ((addr & 4) * 8) as u32; + let len = std::cmp::min(size * 8, 64 - shift); - // TODO: Add trace point - trace_hpet_ram_[read|write]_timer_id(timer_id) - if timer_id > self.num_timers.get() { - // TODO: Add trace point - trace_hpet_timer_id_out_of_range(timer_id) - None + addr &= !4; + let reg = if (0..=0xff).contains(&addr) { + GlobalRegister::try_from(addr).map(HPETRegister::Global) } else { - // Keep the complete address so that HPETTimer's read and write could - // detect the invalid access. - Some((&self.timers[timer_id], addr & 0x1F)) - } + let timer_id: usize = ((addr - 0x100) / 0x20) as usize; + if timer_id <= self.num_timers.get() { + // TODO: Add trace point - trace_hpet_ram_[read|write]_timer_id(timer_id) + TimerRegister::try_from(addr) + .map(|reg| HPETRegister::Timer(&self.timers[timer_id], reg)) + } else { + // TODO: Add trace point - trace_hpet_timer_id_out_of_range(timer_id) + Err(addr) + } + }; + + // reg is now a Result<HPETRegister, hwaddr> + // convert the Err case into HPETRegister as well + let reg = reg.unwrap_or_else(HPETRegister::Unknown); + HPETAddrDecode { shift, len, reg } } fn read(&self, addr: hwaddr, size: u32) -> u64 { - let shift: u64 = (addr & 4) * 8; - - // address range of all TN regs // TODO: Add trace point - trace_hpet_ram_read(addr) - if (0x100..=0x3ff).contains(&addr) { - match self.timer_and_addr(addr) { - None => 0, // Reserved, - Some((timer, tn_addr)) => timer.borrow_mut().read(tn_addr, size), - } - } else { - match addr & !4 { - HPET_CAP_REG => self.capability.get() >> shift, /* including HPET_PERIOD 0x004 */ - // (CNT_CLK_PERIOD field) - HPET_CFG_REG => self.config.get() >> shift, - HPET_COUNTER_REG => { - let cur_tick: u64 = if self.is_hpet_enabled() { - self.get_ticks() - } else { - self.counter.get() - }; - - // TODO: Add trace point - trace_hpet_ram_read_reading_counter(addr & 4, - // cur_tick) - cur_tick >> shift - } - HPET_INT_STATUS_REG => self.int_status.get() >> shift, - _ => { - // TODO: Add trace point- trace_hpet_ram_read_invalid() - // Reserved. - 0 + let HPETAddrDecode { shift, reg, .. } = self.decode(addr, size); + + use GlobalRegister::*; + use HPETRegister::*; + (match reg { + Timer(timer, tn_reg) => timer.borrow_mut().read(tn_reg), + Global(CAP) => self.capability.get(), /* including HPET_PERIOD 0x004 */ + Global(CFG) => self.config.get(), + Global(INT_STATUS) => self.int_status.get(), + Global(COUNTER) => { + // TODO: Add trace point + // trace_hpet_ram_read_reading_counter(addr & 4, cur_tick) + if self.is_hpet_enabled() { + self.get_ticks() + } else { + self.counter.get() } } - } + Unknown(_) => { + // TODO: Add trace point- trace_hpet_ram_read_invalid() + 0 + } + }) >> shift } fn write(&self, addr: hwaddr, value: u64, size: u32) { - let shift = ((addr & 4) * 8) as u32; - let len = std::cmp::min(size * 8, 64 - shift); + let HPETAddrDecode { shift, len, reg } = self.decode(addr, size); // TODO: Add trace point - trace_hpet_ram_write(addr, value) - if (0x100..=0x3ff).contains(&addr) { - match self.timer_and_addr(addr) { - None => (), // Reserved. - Some((timer, tn_addr)) => timer.borrow_mut().write(tn_addr, value, size), - } - } else { - match addr & !0x4 { - HPET_CAP_REG => {} // General Capabilities and ID Register: Read Only - HPET_CFG_REG => self.set_cfg_reg(shift, len, value), - HPET_INT_STATUS_REG => self.set_int_status_reg(shift, len, value), - HPET_COUNTER_REG => self.set_counter_reg(shift, len, value), - _ => { - // TODO: Add trace point - trace_hpet_ram_write_invalid() - // Reserved. - } + use GlobalRegister::*; + use HPETRegister::*; + match reg { + Timer(timer, tn_reg) => timer.borrow_mut().write(tn_reg, value, shift, len), + Global(CAP) => {} // General Capabilities and ID Register: Read Only + Global(CFG) => self.set_cfg_reg(shift, len, value), + Global(INT_STATUS) => self.set_int_status_reg(shift, len, value), + Global(COUNTER) => self.set_counter_reg(shift, len, value), + Unknown(_) => { + // TODO: Add trace point - trace_hpet_ram_write_invalid() } } } diff --git a/rust/qemu-api-macros/src/lib.rs b/rust/qemu-api-macros/src/lib.rs index 7ec218202f..eda0d46d12 100644 --- a/rust/qemu-api-macros/src/lib.rs +++ b/rust/qemu-api-macros/src/lib.rs @@ -6,7 +6,7 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Data, - DeriveInput, Field, Fields, Ident, Meta, Path, Token, Type, Variant, Visibility, + DeriveInput, Field, Fields, FieldsUnnamed, Ident, Meta, Path, Token, Type, Variant, Visibility, }; mod utils; @@ -33,6 +33,35 @@ fn get_fields<'a>( } } +fn get_unnamed_field<'a>(input: &'a DeriveInput, msg: &str) -> Result<&'a Field, MacroError> { + if let Data::Struct(s) = &input.data { + let unnamed = match &s.fields { + Fields::Unnamed(FieldsUnnamed { + unnamed: ref fields, + .. + }) => fields, + _ => { + return Err(MacroError::Message( + format!("Tuple struct required for {}", msg), + s.fields.span(), + )) + } + }; + if unnamed.len() != 1 { + return Err(MacroError::Message( + format!("A single field is required for {}", msg), + s.fields.span(), + )); + } + Ok(&unnamed[0]) + } else { + Err(MacroError::Message( + format!("Struct required for {}", msg), + input.ident.span(), + )) + } +} + fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { let expected = parse_quote! { #[repr(C)] }; @@ -46,6 +75,19 @@ fn is_c_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { } } +fn is_transparent_repr(input: &DeriveInput, msg: &str) -> Result<(), MacroError> { + let expected = parse_quote! { #[repr(transparent)] }; + + if input.attrs.iter().any(|attr| attr == &expected) { + Ok(()) + } else { + Err(MacroError::Message( + format!("#[repr(transparent)] required for {}", msg), + input.ident.span(), + )) + } +} + fn derive_object_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, MacroError> { is_c_repr(&input, "#[derive(Object)]")?; @@ -72,6 +114,52 @@ pub fn derive_object(input: TokenStream) -> TokenStream { TokenStream::from(expanded) } +fn derive_opaque_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, MacroError> { + is_transparent_repr(&input, "#[derive(Wrapper)]")?; + + let name = &input.ident; + let field = &get_unnamed_field(&input, "#[derive(Wrapper)]")?; + let typ = &field.ty; + + // TODO: how to add "::qemu_api"? For now, this is only used in the + // qemu_api crate so it's not a problem. + Ok(quote! { + unsafe impl crate::cell::Wrapper for #name { + type Wrapped = <#typ as crate::cell::Wrapper>::Wrapped; + } + impl #name { + pub unsafe fn from_raw<'a>(ptr: *mut <Self as crate::cell::Wrapper>::Wrapped) -> &'a Self { + let ptr = ::std::ptr::NonNull::new(ptr).unwrap().cast::<Self>(); + unsafe { ptr.as_ref() } + } + + pub const fn as_mut_ptr(&self) -> *mut <Self as crate::cell::Wrapper>::Wrapped { + self.0.as_mut_ptr() + } + + pub const fn as_ptr(&self) -> *const <Self as crate::cell::Wrapper>::Wrapped { + self.0.as_ptr() + } + + pub const fn as_void_ptr(&self) -> *mut ::core::ffi::c_void { + self.0.as_void_ptr() + } + + pub const fn raw_get(slot: *mut Self) -> *mut <Self as crate::cell::Wrapper>::Wrapped { + slot.cast() + } + } + }) +} + +#[proc_macro_derive(Wrapper)] +pub fn derive_opaque(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let expanded = derive_opaque_or_error(input).unwrap_or_else(Into::into); + + TokenStream::from(expanded) +} + #[rustfmt::skip::macros(quote)] fn derive_offsets_or_error(input: DeriveInput) -> Result<proc_macro2::TokenStream, MacroError> { is_c_repr(&input, "#[derive(offsets)]")?; diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index bcf1cf780f..a3f226ccc2 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -42,22 +42,31 @@ _qemu_api_rs = static_library( override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_abi: 'rust', rust_args: _qemu_api_cfg, - dependencies: libc_dep, + dependencies: [libc_dep, qemu_api_macros], ) rust.test('rust-qemu-api-tests', _qemu_api_rs, suite: ['unit', 'rust']) -qemu_api = declare_dependency( - link_with: _qemu_api_rs, - dependencies: qemu_api_macros, -) +qemu_api = declare_dependency(link_with: _qemu_api_rs) # Rust executables do not support objects, so add an intermediate step. rust_qemu_api_objs = static_library( 'rust_qemu_api_objs', objects: [libqom.extract_all_objects(recursive: false), - libhwcore.extract_all_objects(recursive: false)]) + libhwcore.extract_all_objects(recursive: false), + libchardev.extract_all_objects(recursive: false), + libcrypto.extract_all_objects(recursive: false), + libauthz.extract_all_objects(recursive: false), + libio.extract_all_objects(recursive: false)]) +rust_qemu_api_deps = declare_dependency( + dependencies: [ + qom_ss.dependencies(), + chardev_ss.dependencies(), + crypto_ss.dependencies(), + authz_ss.dependencies(), + io_ss.dependencies()], + link_whole: [rust_qemu_api_objs, libqemuutil]) test('rust-qemu-api-integration', executable( @@ -66,8 +75,7 @@ test('rust-qemu-api-integration', override_options: ['rust_std=2021', 'build.rust_std=2021'], rust_args: ['--test'], install: false, - dependencies: [qemu_api, qemu_api_macros], - link_whole: [rust_qemu_api_objs, libqemuutil]), + dependencies: [qemu_api, qemu_api_macros, rust_qemu_api_deps]), args: [ '--test', '--test-threads', '1', '--format', 'pretty', diff --git a/rust/qemu-api/src/bindings.rs b/rust/qemu-api/src/bindings.rs index d2868639ff..3c1d297581 100644 --- a/rust/qemu-api/src/bindings.rs +++ b/rust/qemu-api/src/bindings.rs @@ -25,33 +25,11 @@ include!(concat!(env!("OUT_DIR"), "/bindings.inc.rs")); // SAFETY: these are implemented in C; the bindings need to assert that the // BQL is taken, either directly or via `BqlCell` and `BqlRefCell`. -unsafe impl Send for BusState {} -unsafe impl Sync for BusState {} - +// When bindings for character devices are introduced, this can be +// moved to the Opaque<> wrapper in src/chardev.rs. unsafe impl Send for CharBackend {} unsafe impl Sync for CharBackend {} -unsafe impl Send for Chardev {} -unsafe impl Sync for Chardev {} - -unsafe impl Send for Clock {} -unsafe impl Sync for Clock {} - -unsafe impl Send for DeviceState {} -unsafe impl Sync for DeviceState {} - -unsafe impl Send for MemoryRegion {} -unsafe impl Sync for MemoryRegion {} - -unsafe impl Send for ObjectClass {} -unsafe impl Sync for ObjectClass {} - -unsafe impl Send for Object {} -unsafe impl Sync for Object {} - -unsafe impl Send for SysBusDevice {} -unsafe impl Sync for SysBusDevice {} - // SAFETY: this is a pure data struct unsafe impl Send for CoalescedMemoryRange {} unsafe impl Sync for CoalescedMemoryRange {} diff --git a/rust/qemu-api/src/cell.rs b/rust/qemu-api/src/cell.rs index eae4e2ce78..ab0785a269 100644 --- a/rust/qemu-api/src/cell.rs +++ b/rust/qemu-api/src/cell.rs @@ -27,7 +27,7 @@ // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! BQL-protected mutable containers. +//! QEMU-specific mutable containers //! //! Rust memory safety is based on this rule: Given an object `T`, it is only //! possible to have one of the following: @@ -43,8 +43,10 @@ //! usually have their pointer shared with the "outside world very early in //! their lifetime", for example when they create their //! [`MemoryRegion`s](crate::bindings::MemoryRegion). Therefore, individual -//! parts of a device must be made mutable in a controlled manner through the -//! use of cell types. +//! parts of a device must be made mutable in a controlled manner; this module +//! provides the tools to do so. +//! +//! ## Cell types //! //! [`BqlCell<T>`] and [`BqlRefCell<T>`] allow doing this via the Big QEMU Lock. //! While they are essentially the same single-threaded primitives that are @@ -71,7 +73,35 @@ //! QEMU device implementations is usually incorrect and can lead to //! thread-safety issues. //! -//! ## `BqlCell<T>` +//! ### Example +//! +//! ``` +//! # use qemu_api::prelude::*; +//! # use qemu_api::{c_str, cell::BqlRefCell, irq::InterruptSource, irq::IRQState}; +//! # use qemu_api::{sysbus::SysBusDevice, qom::Owned, qom::ParentField}; +//! # const N_GPIOS: usize = 8; +//! # struct PL061Registers { /* ... */ } +//! # unsafe impl ObjectType for PL061State { +//! # type Class = <SysBusDevice as ObjectType>::Class; +//! # const TYPE_NAME: &'static std::ffi::CStr = c_str!("pl061"); +//! # } +//! struct PL061State { +//! parent_obj: ParentField<SysBusDevice>, +//! +//! // Configuration is read-only after initialization +//! pullups: u32, +//! pulldowns: u32, +//! +//! // Single values shared with C code use BqlCell, in this case via InterruptSource +//! out: [InterruptSource; N_GPIOS], +//! interrupt: InterruptSource, +//! +//! // Larger state accessed by device methods uses BqlRefCell or Mutex +//! registers: BqlRefCell<PL061Registers>, +//! } +//! ``` +//! +//! ### `BqlCell<T>` //! //! [`BqlCell<T>`] implements interior mutability by moving values in and out of //! the cell. That is, an `&mut T` to the inner value can never be obtained as @@ -91,7 +121,7 @@ //! - [`set`](BqlCell::set): this method replaces the interior value, //! dropping the replaced value. //! -//! ## `BqlRefCell<T>` +//! ### `BqlRefCell<T>` //! //! [`BqlRefCell<T>`] uses Rust's lifetimes to implement "dynamic borrowing", a //! process whereby one can claim temporary, exclusive, mutable access to the @@ -111,13 +141,82 @@ //! Multiple immutable borrows are allowed via [`borrow`](BqlRefCell::borrow), //! or a single mutable borrow via [`borrow_mut`](BqlRefCell::borrow_mut). The //! thread will panic if these rules are violated or if the BQL is not held. +//! +//! ## Opaque wrappers +//! +//! The cell types from the previous section are useful at the boundaries +//! of code that requires interior mutability. When writing glue code that +//! interacts directly with C structs, however, it is useful to operate +//! at a lower level. +//! +//! C functions often violate Rust's fundamental assumptions about memory +//! safety by modifying memory even if it is shared. Furthermore, C structs +//! often start their life uninitialized and may be populated lazily. +//! +//! For this reason, this module provides the [`Opaque<T>`] type to opt out +//! of Rust's usual guarantees about the wrapped type. Access to the wrapped +//! value is always through raw pointers, obtained via methods like +//! [`as_mut_ptr()`](Opaque::as_mut_ptr) and [`as_ptr()`](Opaque::as_ptr). These +//! pointers can then be passed to C functions or dereferenced; both actions +//! require `unsafe` blocks, making it clear where safety guarantees must be +//! manually verified. For example +//! +//! ```ignore +//! unsafe { +//! let state = Opaque::<MyStruct>::uninit(); +//! qemu_struct_init(state.as_mut_ptr()); +//! } +//! ``` +//! +//! [`Opaque<T>`] will usually be wrapped one level further, so that +//! bridge methods can be added to the wrapper: +//! +//! ```ignore +//! pub struct MyStruct(Opaque<bindings::MyStruct>); +//! +//! impl MyStruct { +//! fn new() -> Pin<Box<MyStruct>> { +//! let result = Box::pin(unsafe { Opaque::uninit() }); +//! unsafe { qemu_struct_init(result.as_mut_ptr()) }; +//! result +//! } +//! } +//! ``` +//! +//! This pattern of wrapping bindgen-generated types in [`Opaque<T>`] provides +//! several advantages: +//! +//! * The choice of traits to be implemented is not limited by the +//! bindgen-generated code. For example, [`Drop`] can be added without +//! disabling [`Copy`] on the underlying bindgen type +//! +//! * [`Send`] and [`Sync`] implementations can be controlled by the wrapper +//! type rather than being automatically derived from the C struct's layout +//! +//! * Methods can be implemented in a separate crate from the bindgen-generated +//! bindings +//! +//! * [`Debug`](std::fmt::Debug) and [`Display`](std::fmt::Display) +//! implementations can be customized to be more readable than the raw C +//! struct representation +//! +//! The [`Opaque<T>`] type does not include BQL validation; it is possible to +//! assert in the code that the right lock is taken, to use it together +//! with a custom lock guard type, or to let C code take the lock, as +//! appropriate. It is also possible to use it with non-thread-safe +//! types, since by default (unlike [`BqlCell`] and [`BqlRefCell`] +//! it is neither `Sync` nor `Send`. +//! +//! While [`Opaque<T>`] is necessary for C interop, it should be used sparingly +//! and only at FFI boundaries. For QEMU-specific types that need interior +//! mutability, prefer [`BqlCell`] or [`BqlRefCell`]. use std::{ cell::{Cell, UnsafeCell}, cmp::Ordering, fmt, - marker::PhantomData, - mem, + marker::{PhantomData, PhantomPinned}, + mem::{self, MaybeUninit}, ops::{Deref, DerefMut}, ptr::NonNull, }; @@ -840,3 +939,167 @@ impl<T: fmt::Display> fmt::Display for BqlRefMut<'_, T> { (**self).fmt(f) } } + +/// Stores an opaque value that is shared with C code. +/// +/// Often, C structs can changed when calling a C function even if they are +/// behind a shared Rust reference, or they can be initialized lazily and have +/// invalid bit patterns (e.g. `3` for a [`bool`]). This goes against Rust's +/// strict aliasing rules, which normally prevent mutation through shared +/// references. +/// +/// Wrapping the struct with `Opaque<T>` ensures that the Rust compiler does not +/// assume the usual constraints that Rust structs require, and allows using +/// shared references on the Rust side. +/// +/// `Opaque<T>` is `#[repr(transparent)]`, so that it matches the memory layout +/// of `T`. +#[repr(transparent)] +pub struct Opaque<T> { + value: UnsafeCell<MaybeUninit<T>>, + // PhantomPinned also allows multiple references to the `Opaque<T>`, i.e. + // one `&mut Opaque<T>` can coexist with a `&mut T` or any number of `&T`; + // see https://docs.rs/pinned-aliasable/latest/pinned_aliasable/. + _pin: PhantomPinned, +} + +impl<T> Opaque<T> { + /// Creates a new shared reference from a C pointer + /// + /// # Safety + /// + /// The pointer must be valid, though it need not point to a valid value. + pub unsafe fn from_raw<'a>(ptr: *mut T) -> &'a Self { + let ptr = NonNull::new(ptr).unwrap().cast::<Self>(); + // SAFETY: Self is a transparent wrapper over T + unsafe { ptr.as_ref() } + } + + /// Creates a new opaque object with uninitialized contents. + /// + /// # Safety + /// + /// Ultimately the pointer to the returned value will be dereferenced + /// in another `unsafe` block, for example when passing it to a C function, + /// but the functions containing the dereference are usually safe. The + /// value returned from `uninit()` must be initialized and pinned before + /// calling them. + #[allow(clippy::missing_const_for_fn)] + pub unsafe fn uninit() -> Self { + Self { + value: UnsafeCell::new(MaybeUninit::uninit()), + _pin: PhantomPinned, + } + } + + /// Creates a new opaque object with zeroed contents. + /// + /// # Safety + /// + /// Ultimately the pointer to the returned value will be dereferenced + /// in another `unsafe` block, for example when passing it to a C function, + /// but the functions containing the dereference are usually safe. The + /// value returned from `uninit()` must be pinned (and possibly initialized) + /// before calling them. + #[allow(clippy::missing_const_for_fn)] + pub unsafe fn zeroed() -> Self { + Self { + value: UnsafeCell::new(MaybeUninit::zeroed()), + _pin: PhantomPinned, + } + } + + /// Returns a raw mutable pointer to the opaque data. + pub const fn as_mut_ptr(&self) -> *mut T { + UnsafeCell::get(&self.value).cast() + } + + /// Returns a raw pointer to the opaque data. + pub const fn as_ptr(&self) -> *const T { + self.as_mut_ptr() as *const _ + } + + /// Returns a raw pointer to the opaque data that can be passed to a + /// C function as `void *`. + pub const fn as_void_ptr(&self) -> *mut std::ffi::c_void { + UnsafeCell::get(&self.value).cast() + } + + /// Converts a raw pointer to the wrapped type. + pub const fn raw_get(slot: *mut Self) -> *mut T { + // Compare with Linux's raw_get method, which goes through an UnsafeCell + // because it takes a *const Self instead. + slot.cast() + } +} + +impl<T> fmt::Debug for Opaque<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut name: String = "Opaque<".to_string(); + name += std::any::type_name::<T>(); + name += ">"; + f.debug_tuple(&name).field(&self.as_ptr()).finish() + } +} + +impl<T: Default> Opaque<T> { + /// Creates a new opaque object with default contents. + /// + /// # Safety + /// + /// Ultimately the pointer to the returned value will be dereferenced + /// in another `unsafe` block, for example when passing it to a C function, + /// but the functions containing the dereference are usually safe. The + /// value returned from `uninit()` must be pinned before calling them. + pub unsafe fn new() -> Self { + Self { + value: UnsafeCell::new(MaybeUninit::new(T::default())), + _pin: PhantomPinned, + } + } +} + +/// Annotates [`Self`] as a transparent wrapper for another type. +/// +/// Usually defined via the [`qemu_api_macros::Wrapper`] derive macro. +/// +/// # Examples +/// +/// ``` +/// # use std::mem::ManuallyDrop; +/// # use qemu_api::cell::Wrapper; +/// #[repr(transparent)] +/// pub struct Example { +/// inner: ManuallyDrop<String>, +/// } +/// +/// unsafe impl Wrapper for Example { +/// type Wrapped = String; +/// } +/// ``` +/// +/// # Safety +/// +/// `Self` must be a `#[repr(transparent)]` wrapper for the `Wrapped` type, +/// whether directly or indirectly. +/// +/// # Methods +/// +/// By convention, types that implement Wrapper also implement the following +/// methods: +/// +/// ```ignore +/// pub const unsafe fn from_raw<'a>(value: *mut Self::Wrapped) -> &'a Self; +/// pub const unsafe fn as_mut_ptr(&self) -> *mut Self::Wrapped; +/// pub const unsafe fn as_ptr(&self) -> *const Self::Wrapped; +/// pub const unsafe fn raw_get(slot: *mut Self) -> *const Self::Wrapped; +/// ``` +/// +/// They are not defined here to allow them to be `const`. +pub unsafe trait Wrapper { + type Wrapped; +} + +unsafe impl<T> Wrapper for Opaque<T> { + type Wrapped = T; +} diff --git a/rust/qemu-api/src/chardev.rs b/rust/qemu-api/src/chardev.rs index 74cfb634e5..11e6c45afa 100644 --- a/rust/qemu-api/src/chardev.rs +++ b/rust/qemu-api/src/chardev.rs @@ -3,13 +3,255 @@ // SPDX-License-Identifier: GPL-2.0-or-later //! Bindings for character devices +//! +//! Character devices in QEMU can run under the big QEMU lock or in a separate +//! `GMainContext`. Here we only support the former, because the bindings +//! enforce that the BQL is taken whenever the functions in [`CharBackend`] are +//! called. -use std::ffi::CStr; +use std::{ + ffi::CStr, + fmt::{self, Debug}, + io::{self, ErrorKind, Write}, + marker::PhantomPinned, + os::raw::{c_int, c_void}, + ptr::addr_of_mut, + slice, +}; -use crate::{bindings, prelude::*}; +use crate::{ + bindings, + callbacks::FnCall, + cell::{BqlRefMut, Opaque}, + prelude::*, +}; + +/// A safe wrapper around [`bindings::Chardev`]. +#[repr(transparent)] +#[derive(qemu_api_macros::Wrapper)] +pub struct Chardev(Opaque<bindings::Chardev>); -pub type Chardev = bindings::Chardev; pub type ChardevClass = bindings::ChardevClass; +pub type Event = bindings::QEMUChrEvent; + +/// A safe wrapper around [`bindings::CharBackend`], denoting the character +/// back-end that is used for example by a device. Compared to the +/// underlying C struct it adds BQL protection, and is marked as pinned +/// because the QOM object ([`bindings::Chardev`]) contains a pointer to +/// the `CharBackend`. +pub struct CharBackend { + inner: BqlRefCell<bindings::CharBackend>, + _pin: PhantomPinned, +} + +impl Write for BqlRefMut<'_, bindings::CharBackend> { + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + let chr: &mut bindings::CharBackend = self; + + let len = buf.len().try_into().unwrap(); + let r = unsafe { bindings::qemu_chr_fe_write(addr_of_mut!(*chr), buf.as_ptr(), len) }; + errno::into_io_result(r).map(|cnt| cnt as usize) + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + let chr: &mut bindings::CharBackend = self; + + let len = buf.len().try_into().unwrap(); + let r = unsafe { bindings::qemu_chr_fe_write_all(addr_of_mut!(*chr), buf.as_ptr(), len) }; + errno::into_io_result(r).and_then(|cnt| { + if cnt as usize == buf.len() { + Ok(()) + } else { + Err(ErrorKind::WriteZero.into()) + } + }) + } +} + +impl Debug for CharBackend { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // SAFETY: accessed just to print the values + let chr = self.inner.as_ptr(); + Debug::fmt(unsafe { &*chr }, f) + } +} + +// FIXME: use something like PinnedDrop from the pinned_init crate +impl Drop for CharBackend { + fn drop(&mut self) { + self.disable_handlers(); + } +} + +impl CharBackend { + /// Enable the front-end's character device handlers, if there is an + /// associated `Chardev`. + pub fn enable_handlers< + 'chardev, + 'owner: 'chardev, + T, + CanReceiveFn: for<'a> FnCall<(&'a T,), u32>, + ReceiveFn: for<'a, 'b> FnCall<(&'a T, &'b [u8])>, + EventFn: for<'a> FnCall<(&'a T, Event)>, + >( + // When "self" is dropped, the handlers are automatically disabled. + // However, this is not necessarily true if the owner is dropped. + // So require the owner to outlive the character device. + &'chardev self, + owner: &'owner T, + _can_receive: CanReceiveFn, + _receive: ReceiveFn, + _event: EventFn, + ) { + unsafe extern "C" fn rust_can_receive_cb<T, F: for<'a> FnCall<(&'a T,), u32>>( + opaque: *mut c_void, + ) -> c_int { + // SAFETY: the values are safe according to the contract of + // enable_handlers() and qemu_chr_fe_set_handlers() + let owner: &T = unsafe { &*(opaque.cast::<T>()) }; + let r = F::call((owner,)); + r.try_into().unwrap() + } + + unsafe extern "C" fn rust_receive_cb<T, F: for<'a, 'b> FnCall<(&'a T, &'b [u8])>>( + opaque: *mut c_void, + buf: *const u8, + size: c_int, + ) { + // SAFETY: the values are safe according to the contract of + // enable_handlers() and qemu_chr_fe_set_handlers() + let owner: &T = unsafe { &*(opaque.cast::<T>()) }; + let buf = unsafe { slice::from_raw_parts(buf, size.try_into().unwrap()) }; + F::call((owner, buf)) + } + + unsafe extern "C" fn rust_event_cb<T, F: for<'a> FnCall<(&'a T, Event)>>( + opaque: *mut c_void, + event: Event, + ) { + // SAFETY: the values are safe according to the contract of + // enable_handlers() and qemu_chr_fe_set_handlers() + let owner: &T = unsafe { &*(opaque.cast::<T>()) }; + F::call((owner, event)) + } + + let _: () = CanReceiveFn::ASSERT_IS_SOME; + let receive_cb: Option<unsafe extern "C" fn(*mut c_void, *const u8, c_int)> = + if ReceiveFn::is_some() { + Some(rust_receive_cb::<T, ReceiveFn>) + } else { + None + }; + let event_cb: Option<unsafe extern "C" fn(*mut c_void, Event)> = if EventFn::is_some() { + Some(rust_event_cb::<T, EventFn>) + } else { + None + }; + + let mut chr = self.inner.borrow_mut(); + // SAFETY: the borrow promises that the BQL is taken + unsafe { + bindings::qemu_chr_fe_set_handlers( + addr_of_mut!(*chr), + Some(rust_can_receive_cb::<T, CanReceiveFn>), + receive_cb, + event_cb, + None, + (owner as *const T as *mut T).cast::<c_void>(), + core::ptr::null_mut(), + true, + ); + } + } + + /// Disable the front-end's character device handlers. + pub fn disable_handlers(&self) { + let mut chr = self.inner.borrow_mut(); + // SAFETY: the borrow promises that the BQL is taken + unsafe { + bindings::qemu_chr_fe_set_handlers( + addr_of_mut!(*chr), + None, + None, + None, + None, + core::ptr::null_mut(), + core::ptr::null_mut(), + true, + ); + } + } + + /// Notify that the frontend is ready to receive data. + pub fn accept_input(&self) { + let mut chr = self.inner.borrow_mut(); + // SAFETY: the borrow promises that the BQL is taken + unsafe { bindings::qemu_chr_fe_accept_input(addr_of_mut!(*chr)) } + } + + /// Temporarily borrow the character device, allowing it to be used + /// as an implementor of `Write`. Note that it is not valid to drop + /// the big QEMU lock while the character device is borrowed, as + /// that might cause C code to write to the character device. + pub fn borrow_mut(&self) -> impl Write + '_ { + self.inner.borrow_mut() + } + + /// Send a continuous stream of zero bits on the line if `enabled` is + /// true, or a short stream if `enabled` is false. + pub fn send_break(&self, long: bool) -> io::Result<()> { + let mut chr = self.inner.borrow_mut(); + let mut duration: c_int = long.into(); + // SAFETY: the borrow promises that the BQL is taken + let r = unsafe { + bindings::qemu_chr_fe_ioctl( + addr_of_mut!(*chr), + bindings::CHR_IOCTL_SERIAL_SET_BREAK as i32, + addr_of_mut!(duration).cast::<c_void>(), + ) + }; + + errno::into_io_result(r).map(|_| ()) + } + + /// Write data to a character backend from the front end. This function + /// will send data from the front end to the back end. Unlike + /// `write`, this function will block if the back end cannot + /// consume all of the data attempted to be written. + /// + /// Returns the number of bytes consumed (0 if no associated Chardev) or an + /// error. + pub fn write(&self, buf: &[u8]) -> io::Result<usize> { + let len = buf.len().try_into().unwrap(); + // SAFETY: qemu_chr_fe_write is thread-safe + let r = unsafe { bindings::qemu_chr_fe_write(self.inner.as_ptr(), buf.as_ptr(), len) }; + errno::into_io_result(r).map(|cnt| cnt as usize) + } + + /// Write data to a character backend from the front end. This function + /// will send data from the front end to the back end. Unlike + /// `write`, this function will block if the back end cannot + /// consume all of the data attempted to be written. + /// + /// Returns the number of bytes consumed (0 if no associated Chardev) or an + /// error. + pub fn write_all(&self, buf: &[u8]) -> io::Result<()> { + let len = buf.len().try_into().unwrap(); + // SAFETY: qemu_chr_fe_write_all is thread-safe + let r = unsafe { bindings::qemu_chr_fe_write_all(self.inner.as_ptr(), buf.as_ptr(), len) }; + errno::into_io_result(r).and_then(|cnt| { + if cnt as usize == buf.len() { + Ok(()) + } else { + Err(ErrorKind::WriteZero.into()) + } + }) + } +} unsafe impl ObjectType for Chardev { type Class = ChardevClass; diff --git a/rust/qemu-api/src/irq.rs b/rust/qemu-api/src/irq.rs index 34c19263c2..1222d4fde3 100644 --- a/rust/qemu-api/src/irq.rs +++ b/rust/qemu-api/src/irq.rs @@ -8,10 +8,16 @@ use std::{ffi::CStr, marker::PhantomData, os::raw::c_int, ptr}; use crate::{ bindings::{self, qemu_set_irq}, + cell::Opaque, prelude::*, qom::ObjectClass, }; +/// An opaque wrapper around [`bindings::IRQState`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct IRQState(Opaque<bindings::IRQState>); + /// Interrupt sources are used by devices to pass changes to a value (typically /// a boolean). The interrupt sink is usually an interrupt controller or /// GPIO controller. @@ -21,8 +27,7 @@ use crate::{ /// method sends a `true` value to the sink. If the guest has to see a /// different polarity, that change is performed by the board between the /// device and the interrupt controller. -pub type IRQState = bindings::IRQState; - +/// /// Interrupts are implemented as a pointer to the interrupt "sink", which has /// type [`IRQState`]. A device exposes its source as a QOM link property using /// a function such as [`SysBusDeviceMethods::init_irq`], and @@ -40,7 +45,7 @@ pub struct InterruptSource<T = bool> where c_int: From<T>, { - cell: BqlCell<*mut IRQState>, + cell: BqlCell<*mut bindings::IRQState>, _marker: PhantomData<T>, } @@ -79,11 +84,11 @@ where } } - pub(crate) const fn as_ptr(&self) -> *mut *mut IRQState { + pub(crate) const fn as_ptr(&self) -> *mut *mut bindings::IRQState { self.cell.as_ptr() } - pub(crate) const fn slice_as_ptr(slice: &[Self]) -> *mut *mut IRQState { + pub(crate) const fn slice_as_ptr(slice: &[Self]) -> *mut *mut bindings::IRQState { assert!(!slice.is_empty()); slice[0].as_ptr() } diff --git a/rust/qemu-api/src/memory.rs b/rust/qemu-api/src/memory.rs index 682951ab44..fdb1ea11fc 100644 --- a/rust/qemu-api/src/memory.rs +++ b/rust/qemu-api/src/memory.rs @@ -6,9 +6,8 @@ use std::{ ffi::{CStr, CString}, - marker::{PhantomData, PhantomPinned}, + marker::PhantomData, os::raw::{c_uint, c_void}, - ptr::addr_of, }; pub use bindings::{hwaddr, MemTxAttrs}; @@ -16,6 +15,7 @@ pub use bindings::{hwaddr, MemTxAttrs}; use crate::{ bindings::{self, device_endian, memory_region_init_io}, callbacks::FnCall, + cell::Opaque, prelude::*, zeroable::Zeroable, }; @@ -132,13 +132,13 @@ impl<T> Default for MemoryRegionOpsBuilder<T> { } } -/// A safe wrapper around [`bindings::MemoryRegion`]. Compared to the -/// underlying C struct it is marked as pinned because the QOM tree -/// contains a pointer to it. -pub struct MemoryRegion { - inner: bindings::MemoryRegion, - _pin: PhantomPinned, -} +/// A safe wrapper around [`bindings::MemoryRegion`]. +#[repr(transparent)] +#[derive(qemu_api_macros::Wrapper)] +pub struct MemoryRegion(Opaque<bindings::MemoryRegion>); + +unsafe impl Send for MemoryRegion {} +unsafe impl Sync for MemoryRegion {} impl MemoryRegion { // inline to ensure that it is not included in tests, which only @@ -157,7 +157,7 @@ impl MemoryRegion { let cstr = CString::new(name).unwrap(); memory_region_init_io( slot, - owner.cast::<Object>(), + owner.cast::<bindings::Object>(), ops, owner.cast::<c_void>(), cstr.as_ptr(), @@ -174,13 +174,15 @@ impl MemoryRegion { size: u64, ) { unsafe { - Self::do_init_io(&mut self.inner, owner.cast::<Object>(), &ops.0, name, size); + Self::do_init_io( + self.0.as_mut_ptr(), + owner.cast::<Object>(), + &ops.0, + name, + size, + ); } } - - pub(crate) const fn as_mut_ptr(&self) -> *mut bindings::MemoryRegion { - addr_of!(self.inner) as *mut _ - } } unsafe impl ObjectType for MemoryRegion { diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index 634acf37a8..43bfcd5fca 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -17,7 +17,6 @@ pub use crate::qom::InterfaceType; pub use crate::qom::IsA; pub use crate::qom::Object; pub use crate::qom::ObjectCast; -pub use crate::qom::ObjectCastMut; pub use crate::qom::ObjectDeref; pub use crate::qom::ObjectClassMethods; pub use crate::qom::ObjectMethods; diff --git a/rust/qemu-api/src/qdev.rs b/rust/qemu-api/src/qdev.rs index c136457090..18b4a9ba68 100644 --- a/rust/qemu-api/src/qdev.rs +++ b/rust/qemu-api/src/qdev.rs @@ -10,12 +10,12 @@ use std::{ ptr::NonNull, }; -pub use bindings::{Clock, ClockEvent, DeviceClass, DeviceState, Property, ResetType}; +pub use bindings::{ClockEvent, DeviceClass, Property, ResetType}; use crate::{ bindings::{self, qdev_init_gpio_in, qdev_init_gpio_out, Error, ResettableClass}, callbacks::FnCall, - cell::bql_locked, + cell::{bql_locked, Opaque}, chardev::Chardev, irq::InterruptSource, prelude::*, @@ -23,6 +23,22 @@ use crate::{ vmstate::VMStateDescription, }; +/// A safe wrapper around [`bindings::Clock`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct Clock(Opaque<bindings::Clock>); + +unsafe impl Send for Clock {} +unsafe impl Sync for Clock {} + +/// A safe wrapper around [`bindings::DeviceState`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct DeviceState(Opaque<bindings::DeviceState>); + +unsafe impl Send for DeviceState {} +unsafe impl Sync for DeviceState {} + /// Trait providing the contents of the `ResettablePhases` struct, /// which is part of the QOM `Resettable` interface. pub trait ResettablePhasesImpl { @@ -52,7 +68,7 @@ pub trait ResettablePhasesImpl { /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_resettable_enter_fn<T: ResettablePhasesImpl>( - obj: *mut Object, + obj: *mut bindings::Object, typ: ResetType, ) { let state = NonNull::new(obj).unwrap().cast::<T>(); @@ -65,7 +81,7 @@ unsafe extern "C" fn rust_resettable_enter_fn<T: ResettablePhasesImpl>( /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_resettable_hold_fn<T: ResettablePhasesImpl>( - obj: *mut Object, + obj: *mut bindings::Object, typ: ResetType, ) { let state = NonNull::new(obj).unwrap().cast::<T>(); @@ -78,7 +94,7 @@ unsafe extern "C" fn rust_resettable_hold_fn<T: ResettablePhasesImpl>( /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. unsafe extern "C" fn rust_resettable_exit_fn<T: ResettablePhasesImpl>( - obj: *mut Object, + obj: *mut bindings::Object, typ: ResetType, ) { let state = NonNull::new(obj).unwrap().cast::<T>(); @@ -117,7 +133,10 @@ pub trait DeviceImpl: ObjectImpl + ResettablePhasesImpl + IsA<DeviceState> { /// We expect the FFI user of this function to pass a valid pointer that /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. -unsafe extern "C" fn rust_realize_fn<T: DeviceImpl>(dev: *mut DeviceState, _errp: *mut *mut Error) { +unsafe extern "C" fn rust_realize_fn<T: DeviceImpl>( + dev: *mut bindings::DeviceState, + _errp: *mut *mut Error, +) { let state = NonNull::new(dev).unwrap().cast::<T>(); T::REALIZE.unwrap()(unsafe { state.as_ref() }); } @@ -251,7 +270,7 @@ where events: ClockEvent, ) -> Owned<Clock> { fn do_init_clock_in( - dev: *mut DeviceState, + dev: &DeviceState, name: &str, cb: Option<unsafe extern "C" fn(*mut c_void, ClockEvent)>, events: ClockEvent, @@ -265,14 +284,15 @@ where unsafe { let cstr = CString::new(name).unwrap(); let clk = bindings::qdev_init_clock_in( - dev, + dev.as_mut_ptr(), cstr.as_ptr(), cb, - dev.cast::<c_void>(), + dev.as_void_ptr(), events.0, ); - Owned::from(&*clk) + let clk: &Clock = Clock::from_raw(clk); + Owned::from(clk) } } @@ -289,7 +309,7 @@ where None }; - do_init_clock_in(self.as_mut_ptr(), name, cb, events) + do_init_clock_in(self.upcast(), name, cb, events) } /// Add an output clock named `name`. @@ -304,17 +324,23 @@ where fn init_clock_out(&self, name: &str) -> Owned<Clock> { unsafe { let cstr = CString::new(name).unwrap(); - let clk = bindings::qdev_init_clock_out(self.as_mut_ptr(), cstr.as_ptr()); + let clk = bindings::qdev_init_clock_out(self.upcast().as_mut_ptr(), cstr.as_ptr()); - Owned::from(&*clk) + let clk: &Clock = Clock::from_raw(clk); + Owned::from(clk) } } fn prop_set_chr(&self, propname: &str, chr: &Owned<Chardev>) { assert!(bql_locked()); let c_propname = CString::new(propname).unwrap(); + let chr: &Chardev = chr; unsafe { - bindings::qdev_prop_set_chr(self.as_mut_ptr(), c_propname.as_ptr(), chr.as_mut_ptr()); + bindings::qdev_prop_set_chr( + self.upcast().as_mut_ptr(), + c_propname.as_ptr(), + chr.as_mut_ptr(), + ); } } @@ -323,8 +349,17 @@ where num_lines: u32, _cb: F, ) { - let _: () = F::ASSERT_IS_SOME; + fn do_init_gpio_in( + dev: &DeviceState, + num_lines: u32, + gpio_in_cb: unsafe extern "C" fn(*mut c_void, c_int, c_int), + ) { + unsafe { + qdev_init_gpio_in(dev.as_mut_ptr(), Some(gpio_in_cb), num_lines as c_int); + } + } + let _: () = F::ASSERT_IS_SOME; unsafe extern "C" fn rust_irq_handler<T, F: for<'a> FnCall<(&'a T, u32, u32)>>( opaque: *mut c_void, line: c_int, @@ -337,19 +372,13 @@ where let gpio_in_cb: unsafe extern "C" fn(*mut c_void, c_int, c_int) = rust_irq_handler::<Self::Target, F>; - unsafe { - qdev_init_gpio_in( - self.as_mut_ptr::<DeviceState>(), - Some(gpio_in_cb), - num_lines as c_int, - ); - } + do_init_gpio_in(self.upcast(), num_lines, gpio_in_cb); } fn init_gpio_out(&self, pins: &[InterruptSource]) { unsafe { qdev_init_gpio_out( - self.as_mut_ptr::<DeviceState>(), + self.upcast().as_mut_ptr(), InterruptSource::slice_as_ptr(pins), pins.len() as c_int, ); diff --git a/rust/qemu-api/src/qom.rs b/rust/qemu-api/src/qom.rs index 5488643a2f..34d7bc0ef9 100644 --- a/rust/qemu-api/src/qom.rs +++ b/rust/qemu-api/src/qom.rs @@ -101,16 +101,24 @@ use std::{ ptr::NonNull, }; -pub use bindings::{Object, ObjectClass}; +pub use bindings::ObjectClass; use crate::{ bindings::{ self, object_class_dynamic_cast, object_dynamic_cast, object_get_class, object_get_typename, object_new, object_ref, object_unref, TypeInfo, }, - cell::bql_locked, + cell::{bql_locked, Opaque}, }; +/// A safe wrapper around [`bindings::Object`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct Object(Opaque<bindings::Object>); + +unsafe impl Send for Object {} +unsafe impl Sync for Object {} + /// Marker trait: `Self` can be statically upcasted to `P` (i.e. `P` is a direct /// or indirect parent of `Self`). /// @@ -199,7 +207,7 @@ impl<T: fmt::Display + ObjectType> fmt::Display for ParentField<T> { } } -unsafe extern "C" fn rust_instance_init<T: ObjectImpl>(obj: *mut Object) { +unsafe extern "C" fn rust_instance_init<T: ObjectImpl>(obj: *mut bindings::Object) { let mut state = NonNull::new(obj).unwrap().cast::<T>(); // SAFETY: obj is an instance of T, since rust_instance_init<T> // is called from QOM core as the instance_init function @@ -209,7 +217,7 @@ unsafe extern "C" fn rust_instance_init<T: ObjectImpl>(obj: *mut Object) { } } -unsafe extern "C" fn rust_instance_post_init<T: ObjectImpl>(obj: *mut Object) { +unsafe extern "C" fn rust_instance_post_init<T: ObjectImpl>(obj: *mut bindings::Object) { let state = NonNull::new(obj).unwrap().cast::<T>(); // SAFETY: obj is an instance of T, since rust_instance_post_init<T> // is called from QOM core as the instance_post_init function @@ -230,7 +238,7 @@ unsafe extern "C" fn rust_class_init<T: ObjectType + ObjectImpl>( <T as ObjectImpl>::CLASS_INIT(unsafe { klass.as_mut() }) } -unsafe extern "C" fn drop_object<T: ObjectImpl>(obj: *mut Object) { +unsafe extern "C" fn drop_object<T: ObjectImpl>(obj: *mut bindings::Object) { // SAFETY: obj is an instance of T, since drop_object<T> is called // from the QOM core function object_deinit() as the instance_finalize // function for class T. Note that while object_deinit() will drop the @@ -280,14 +288,14 @@ pub unsafe trait ObjectType: Sized { /// Return the receiver as an Object. This is always safe, even /// if this type represents an interface. fn as_object(&self) -> &Object { - unsafe { &*self.as_object_ptr() } + unsafe { &*self.as_ptr().cast() } } /// Return the receiver as a const raw pointer to Object. /// This is preferrable to `as_object_mut_ptr()` if a C /// function only needs a `const Object *`. - fn as_object_ptr(&self) -> *const Object { - self.as_ptr().cast() + fn as_object_ptr(&self) -> *const bindings::Object { + self.as_object().as_ptr() } /// Return the receiver as a mutable raw pointer to Object. @@ -297,8 +305,8 @@ pub unsafe trait ObjectType: Sized { /// This cast is always safe, but because the result is mutable /// and the incoming reference is not, this should only be used /// for calls to C functions, and only if needed. - unsafe fn as_object_mut_ptr(&self) -> *mut Object { - self.as_object_ptr() as *mut _ + unsafe fn as_object_mut_ptr(&self) -> *mut bindings::Object { + self.as_object().as_mut_ptr() } } @@ -455,90 +463,7 @@ where impl<T: ObjectType> ObjectDeref for &T {} impl<T: ObjectType> ObjectCast for &T {} -/// Trait for mutable type casting operations in the QOM hierarchy. -/// -/// This trait provides the mutable counterparts to [`ObjectCast`]'s conversion -/// functions. Unlike `ObjectCast`, this trait returns `Result` for fallible -/// conversions to preserve the original smart pointer if the cast fails. This -/// is necessary because mutable references cannot be copied, so a failed cast -/// must return ownership of the original reference. For example: -/// -/// ```ignore -/// let mut dev = get_device(); -/// // If this fails, we need the original `dev` back to try something else -/// match dev.dynamic_cast_mut::<FooDevice>() { -/// Ok(foodev) => /* use foodev */, -/// Err(dev) => /* still have ownership of dev */ -/// } -/// ``` -pub trait ObjectCastMut: Sized + ObjectDeref + DerefMut -where - Self::Target: ObjectType, -{ - /// Safely convert from a derived type to one of its parent types. - /// - /// This is always safe; the [`IsA`] trait provides static verification - /// that `Self` dereferences to `U` or a child of `U`. - fn upcast_mut<'a, U: ObjectType>(self) -> &'a mut U - where - Self::Target: IsA<U>, - Self: 'a, - { - // SAFETY: soundness is declared via IsA<U>, which is an unsafe trait - unsafe { self.unsafe_cast_mut::<U>() } - } - - /// Attempt to convert to a derived type. - /// - /// Returns `Ok(..)` if the object is of type `U`, or `Err(self)` if the - /// object if the conversion failed. This is verified at runtime by - /// checking the object's type information. - fn downcast_mut<'a, U: IsA<Self::Target>>(self) -> Result<&'a mut U, Self> - where - Self: 'a, - { - self.dynamic_cast_mut::<U>() - } - - /// Attempt to convert between any two types in the QOM hierarchy. - /// - /// Returns `Ok(..)` if the object is of type `U`, or `Err(self)` if the - /// object if the conversion failed. This is verified at runtime by - /// checking the object's type information. - fn dynamic_cast_mut<'a, U: ObjectType>(self) -> Result<&'a mut U, Self> - where - Self: 'a, - { - unsafe { - // SAFETY: upcasting to Object is always valid, and the - // return type is either NULL or the argument itself - let result: *mut U = - object_dynamic_cast(self.as_object_mut_ptr(), U::TYPE_NAME.as_ptr()).cast(); - - result.as_mut().ok_or(self) - } - } - - /// Convert to any QOM type without verification. - /// - /// # Safety - /// - /// What safety? You need to know yourself that the cast is correct; only - /// use when performance is paramount. It is still better than a raw - /// pointer `cast()`, which does not even check that you remain in the - /// realm of QOM `ObjectType`s. - /// - /// `unsafe_cast::<Object>()` is always safe. - unsafe fn unsafe_cast_mut<'a, U: ObjectType>(self) -> &'a mut U - where - Self: 'a, - { - unsafe { &mut *self.as_mut_ptr::<Self::Target>().cast::<U>() } - } -} - impl<T: ObjectType> ObjectDeref for &mut T {} -impl<T: ObjectType> ObjectCastMut for &mut T {} /// Trait a type must implement to be registered with QEMU. pub trait ObjectImpl: ObjectType + IsA<Object> { @@ -621,7 +546,7 @@ pub trait ObjectImpl: ObjectType + IsA<Object> { /// We expect the FFI user of this function to pass a valid pointer that /// can be downcasted to type `T`. We also expect the device is /// readable/writeable from one thread at any time. -unsafe extern "C" fn rust_unparent_fn<T: ObjectImpl>(dev: *mut Object) { +unsafe extern "C" fn rust_unparent_fn<T: ObjectImpl>(dev: *mut bindings::Object) { let state = NonNull::new(dev).unwrap().cast::<T>(); T::UNPARENT.unwrap()(unsafe { state.as_ref() }); } @@ -796,8 +721,9 @@ pub trait ObjectClassMethods: IsA<Object> { // SAFETY: the object created by object_new is allocated on // the heap and has a reference count of 1 unsafe { - let obj = &*object_new(Self::TYPE_NAME.as_ptr()); - Owned::from_raw(obj.unsafe_cast::<Self>()) + let raw_obj = object_new(Self::TYPE_NAME.as_ptr()); + let obj = Object::from_raw(raw_obj).unsafe_cast::<Self>(); + Owned::from_raw(obj) } } } diff --git a/rust/qemu-api/src/sysbus.rs b/rust/qemu-api/src/sysbus.rs index 04821a2b9b..e92502a8fe 100644 --- a/rust/qemu-api/src/sysbus.rs +++ b/rust/qemu-api/src/sysbus.rs @@ -6,11 +6,11 @@ use std::{ffi::CStr, ptr::addr_of_mut}; -pub use bindings::{SysBusDevice, SysBusDeviceClass}; +pub use bindings::SysBusDeviceClass; use crate::{ bindings, - cell::bql_locked, + cell::{bql_locked, Opaque}, irq::{IRQState, InterruptSource}, memory::MemoryRegion, prelude::*, @@ -18,6 +18,14 @@ use crate::{ qom::Owned, }; +/// A safe wrapper around [`bindings::SysBusDevice`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct SysBusDevice(Opaque<bindings::SysBusDevice>); + +unsafe impl Send for SysBusDevice {} +unsafe impl Sync for SysBusDevice {} + unsafe impl ObjectType for SysBusDevice { type Class = SysBusDeviceClass; const TYPE_NAME: &'static CStr = @@ -49,7 +57,7 @@ where fn init_mmio(&self, iomem: &MemoryRegion) { assert!(bql_locked()); unsafe { - bindings::sysbus_init_mmio(self.as_mut_ptr(), iomem.as_mut_ptr()); + bindings::sysbus_init_mmio(self.upcast().as_mut_ptr(), iomem.as_mut_ptr()); } } @@ -60,7 +68,21 @@ where fn init_irq(&self, irq: &InterruptSource) { assert!(bql_locked()); unsafe { - bindings::sysbus_init_irq(self.as_mut_ptr(), irq.as_ptr()); + bindings::sysbus_init_irq(self.upcast().as_mut_ptr(), irq.as_ptr()); + } + } + + // TODO: do we want a type like GuestAddress here? + fn mmio_addr(&self, id: u32) -> Option<u64> { + assert!(bql_locked()); + // SAFETY: the BQL ensures that no one else writes to sbd.mmio[], and + // the SysBusDevice must be initialized to get an IsA<SysBusDevice>. + let sbd = unsafe { *self.upcast().as_ptr() }; + let id: usize = id.try_into().unwrap(); + if sbd.mmio[id].memory.is_null() { + None + } else { + Some(sbd.mmio[id].addr) } } @@ -69,7 +91,7 @@ where assert!(bql_locked()); let id: i32 = id.try_into().unwrap(); unsafe { - bindings::sysbus_mmio_map(self.as_mut_ptr(), id, addr); + bindings::sysbus_mmio_map(self.upcast().as_mut_ptr(), id, addr); } } @@ -79,8 +101,9 @@ where fn connect_irq(&self, id: u32, irq: &Owned<IRQState>) { assert!(bql_locked()); let id: i32 = id.try_into().unwrap(); + let irq: &IRQState = irq; unsafe { - bindings::sysbus_connect_irq(self.as_mut_ptr(), id, irq.as_mut_ptr()); + bindings::sysbus_connect_irq(self.upcast().as_mut_ptr(), id, irq.as_mut_ptr()); } } @@ -88,7 +111,10 @@ where // TODO: return an Error assert!(bql_locked()); unsafe { - bindings::sysbus_realize(self.as_mut_ptr(), addr_of_mut!(bindings::error_fatal)); + bindings::sysbus_realize( + self.upcast().as_mut_ptr(), + addr_of_mut!(bindings::error_fatal), + ); } } } diff --git a/rust/qemu-api/src/timer.rs b/rust/qemu-api/src/timer.rs index a593538917..f0b04ef95d 100644 --- a/rust/qemu-api/src/timer.rs +++ b/rust/qemu-api/src/timer.rs @@ -2,31 +2,51 @@ // Author(s): Zhao Liu <zhai1.liu@intel.com> // SPDX-License-Identifier: GPL-2.0-or-later -use std::os::raw::{c_int, c_void}; +use std::{ + os::raw::{c_int, c_void}, + pin::Pin, +}; use crate::{ bindings::{self, qemu_clock_get_ns, timer_del, timer_init_full, timer_mod, QEMUClockType}, callbacks::FnCall, + cell::Opaque, }; -pub type Timer = bindings::QEMUTimer; -pub type TimerListGroup = bindings::QEMUTimerListGroup; +/// A safe wrapper around [`bindings::QEMUTimer`]. +#[repr(transparent)] +#[derive(Debug, qemu_api_macros::Wrapper)] +pub struct Timer(Opaque<bindings::QEMUTimer>); + +unsafe impl Send for Timer {} +unsafe impl Sync for Timer {} + +#[repr(transparent)] +#[derive(qemu_api_macros::Wrapper)] +pub struct TimerListGroup(Opaque<bindings::QEMUTimerListGroup>); + +unsafe impl Send for TimerListGroup {} +unsafe impl Sync for TimerListGroup {} impl Timer { pub const MS: u32 = bindings::SCALE_MS; pub const US: u32 = bindings::SCALE_US; pub const NS: u32 = bindings::SCALE_NS; - pub fn new() -> Self { - Default::default() - } - - const fn as_mut_ptr(&self) -> *mut Self { - self as *const Timer as *mut _ + /// Create a `Timer` struct without initializing it. + /// + /// # Safety + /// + /// The timer must be initialized before it is armed with + /// [`modify`](Self::modify). + pub unsafe fn new() -> Self { + // SAFETY: requirements relayed to callers of Timer::new + Self(unsafe { Opaque::zeroed() }) } + /// Create a new timer with the given attributes. pub fn init_full<'timer, 'opaque: 'timer, T, F>( - &'timer mut self, + self: Pin<&'timer mut Self>, timer_list_group: Option<&TimerListGroup>, clk_type: ClockType, scale: u32, @@ -51,7 +71,7 @@ impl Timer { // SAFETY: the opaque outlives the timer unsafe { timer_init_full( - self, + self.as_mut_ptr(), if let Some(g) = timer_list_group { g as *const TimerListGroup as *mut _ } else { @@ -67,14 +87,19 @@ impl Timer { } pub fn modify(&self, expire_time: u64) { + // SAFETY: the only way to obtain a Timer safely is via methods that + // take a Pin<&mut Self>, therefore the timer is pinned unsafe { timer_mod(self.as_mut_ptr(), expire_time as i64) } } pub fn delete(&self) { + // SAFETY: the only way to obtain a Timer safely is via methods that + // take a Pin<&mut Self>, therefore the timer is pinned unsafe { timer_del(self.as_mut_ptr()) } } } +// FIXME: use something like PinnedDrop from the pinned_init crate impl Drop for Timer { fn drop(&mut self) { self.delete() diff --git a/rust/qemu-api/src/vmstate.rs b/rust/qemu-api/src/vmstate.rs index 24a4dc81e7..f0510ae769 100644 --- a/rust/qemu-api/src/vmstate.rs +++ b/rust/qemu-api/src/vmstate.rs @@ -330,6 +330,7 @@ macro_rules! impl_vmstate_transparent { impl_vmstate_transparent!(std::cell::Cell<T> where T: VMState); impl_vmstate_transparent!(std::cell::UnsafeCell<T> where T: VMState); +impl_vmstate_transparent!(std::pin::Pin<T> where T: VMState); impl_vmstate_transparent!(crate::cell::BqlCell<T> where T: VMState); impl_vmstate_transparent!(crate::cell::BqlRefCell<T> where T: VMState); @@ -469,7 +470,7 @@ macro_rules! vmstate_clock { $crate::assert_field_type!( $struct_name, $field_name, - $crate::qom::Owned<$crate::bindings::Clock> + $crate::qom::Owned<$crate::qdev::Clock> ); $crate::offset_of!($struct_name, $field_name) }, diff --git a/rust/qemu-api/src/zeroable.rs b/rust/qemu-api/src/zeroable.rs index 47b6977828..a3415a2ebc 100644 --- a/rust/qemu-api/src/zeroable.rs +++ b/rust/qemu-api/src/zeroable.rs @@ -106,3 +106,4 @@ impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_1); impl_zeroable!(crate::bindings::MemoryRegionOps__bindgen_ty_2); impl_zeroable!(crate::bindings::MemoryRegionOps); impl_zeroable!(crate::bindings::MemTxAttrs); +impl_zeroable!(crate::bindings::CharBackend); diff --git a/rust/qemu-api/tests/tests.rs b/rust/qemu-api/tests/tests.rs index e3985782a3..269122e7ec 100644 --- a/rust/qemu-api/tests/tests.rs +++ b/rust/qemu-api/tests/tests.rs @@ -2,13 +2,10 @@ // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org> // SPDX-License-Identifier: GPL-2.0-or-later -use std::{ - ffi::{c_void, CStr}, - ptr::{addr_of, addr_of_mut}, -}; +use std::{ffi::CStr, ptr::addr_of}; use qemu_api::{ - bindings::{module_call_init, module_init_type, object_new, object_unref, qdev_prop_bool}, + bindings::{module_call_init, module_init_type, qdev_prop_bool}, c_str, cell::{self, BqlCell}, declare_properties, define_property, @@ -182,30 +179,3 @@ fn test_cast() { assert_eq!(addr_of!(*sbd_ref), p_ptr.cast()); } } - -#[test] -#[allow(clippy::shadow_unrelated)] -/// Test casts on mutable references. -fn test_cast_mut() { - init_qom(); - let p: *mut DummyState = unsafe { object_new(DummyState::TYPE_NAME.as_ptr()).cast() }; - - let p_ref: &mut DummyState = unsafe { &mut *p }; - let obj_ref: &mut Object = p_ref.upcast_mut(); - assert_eq!(addr_of_mut!(*obj_ref), p.cast()); - - let sbd_ref: Result<&mut SysBusDevice, &mut Object> = obj_ref.dynamic_cast_mut(); - let obj_ref = sbd_ref.unwrap_err(); - - let dev_ref: Result<&mut DeviceState, &mut Object> = obj_ref.downcast_mut(); - let dev_ref = dev_ref.unwrap(); - assert_eq!(addr_of_mut!(*dev_ref), p.cast()); - - // SAFETY: the cast is wrong, but the value is only used for comparison - unsafe { - let sbd_ref: &mut SysBusDevice = obj_ref.unsafe_cast_mut(); - assert_eq!(addr_of_mut!(*sbd_ref), p.cast()); - - object_unref(p_ref.as_object_mut_ptr().cast::<c_void>()); - } -} diff --git a/scripts/meson-buildoptions.py b/scripts/meson-buildoptions.py index 4814a8ff61..a3e22471b2 100644 --- a/scripts/meson-buildoptions.py +++ b/scripts/meson-buildoptions.py @@ -241,8 +241,14 @@ def print_parse(options): print(" esac") print("}") - -options = load_options(json.load(sys.stdin)) +json_data = sys.stdin.read() +try: + options = load_options(json.loads(json_data)) +except: + print("Failure in scripts/meson-buildoptions.py parsing stdin as json", + file=sys.stderr) + print(json_data, file=sys.stderr) + sys.exit(1) print("# This file is generated by meson-buildoptions.py, do not edit!") print_help(options) print_parse(options) diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh index 3e8e00852b..aca6e68830 100644 --- a/scripts/meson-buildoptions.sh +++ b/scripts/meson-buildoptions.sh @@ -504,6 +504,8 @@ _meson_option_parse() { --disable-strict-rust-lints) printf "%s" -Dstrict_rust_lints=false ;; --enable-strip) printf "%s" -Dstrip=true ;; --disable-strip) printf "%s" -Dstrip=false ;; + --enable-split-debug) printf "%s" -Dsplit_debug=true ;; + --disable-split-debug) printf "%s" -Dsplit_debug=false ;; --sysconfdir=*) quote_sh "-Dsysconfdir=$2" ;; --enable-tcg) printf "%s" -Dtcg=enabled ;; --disable-tcg) printf "%s" -Dtcg=disabled ;; diff --git a/tests/functional/aspeed.py b/tests/functional/aspeed.py index b52358bb8c..77dc8930fa 100644 --- a/tests/functional/aspeed.py +++ b/tests/functional/aspeed.py @@ -7,21 +7,23 @@ from qemu_test import LinuxKernelTest class AspeedTest(LinuxKernelTest): - def do_test_arm_aspeed(self, machine, image): + def do_test_arm_aspeed_openbmc(self, machine, image, uboot='2019.04', + cpu_id='0x0', soc='AST2500 rev A1'): + hostname = machine.removesuffix('-bmc') + self.set_machine(machine) self.vm.set_console() - self.vm.add_args('-drive', 'file=' + image + ',if=mtd,format=raw', - '-net', 'nic', '-snapshot') + self.vm.add_args('-drive', f'file={image},if=mtd,format=raw', + '-snapshot') self.vm.launch() - self.wait_for_console_pattern("U-Boot 2016.07") - self.wait_for_console_pattern("## Loading kernel from FIT Image at 20080000") - self.wait_for_console_pattern("Starting kernel ...") - self.wait_for_console_pattern("Booting Linux on physical CPU 0x0") - self.wait_for_console_pattern( - "aspeed-smc 1e620000.spi: read control register: 203b0641") - self.wait_for_console_pattern("ftgmac100 1e660000.ethernet eth0: irq ") - self.wait_for_console_pattern("systemd[1]: Set hostname to") + self.wait_for_console_pattern(f'U-Boot {uboot}') + self.wait_for_console_pattern('## Loading kernel from FIT Image') + self.wait_for_console_pattern('Starting kernel ...') + self.wait_for_console_pattern(f'Booting Linux on physical CPU {cpu_id}') + self.wait_for_console_pattern(f'ASPEED {soc}') + self.wait_for_console_pattern('/init as init process') + self.wait_for_console_pattern(f'systemd[1]: Hostname set to <{hostname}>.') def do_test_arm_aspeed_buildroot_start(self, image, cpu_id, pattern='Aspeed EVB'): self.require_netdev('user') diff --git a/tests/functional/meson.build b/tests/functional/meson.build index 97c3f4ad4e..5dc66c03fc 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -22,8 +22,10 @@ test_timeouts = { 'acpi_bits' : 420, 'arm_aspeed_palmetto' : 120, 'arm_aspeed_romulus' : 120, + 'arm_aspeed_witherspoon' : 120, 'arm_aspeed_ast2500' : 720, 'arm_aspeed_ast2600' : 1200, + 'arm_aspeed_bletchley' : 120, 'arm_aspeed_rainier' : 480, 'arm_bpim2u' : 500, 'arm_collie' : 180, @@ -104,8 +106,10 @@ tests_arm_system_thorough = [ 'arm_aspeed_ast1030', 'arm_aspeed_palmetto', 'arm_aspeed_romulus', + 'arm_aspeed_witherspoon', 'arm_aspeed_ast2500', 'arm_aspeed_ast2600', + 'arm_aspeed_bletchley', 'arm_aspeed_rainier', 'arm_bpim2u', 'arm_canona1100', diff --git a/tests/functional/test_aarch64_aspeed.py b/tests/functional/test_aarch64_aspeed.py index 9595498ace..c25c966278 100755 --- a/tests/functional/test_aarch64_aspeed.py +++ b/tests/functional/test_aarch64_aspeed.py @@ -27,37 +27,37 @@ class AST2x00MachineSDK(QemuSystemTest): wait_for_console_pattern(self, '## Loading kernel from FIT Image') wait_for_console_pattern(self, 'Starting kernel ...') - ASSET_SDK_V903_AST2700 = Asset( - 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.03/ast2700-default-obmc.tar.gz', - '91225f50d255e2905ba8d8e0c80b71b9d157c3609770c7a740cd786370d85a77') + ASSET_SDK_V905_AST2700 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.05/ast2700-a0-default-obmc.tar.gz', + 'cfbbd1cce72f2a3b73b9080c41eecdadebb7077fba4f7806d72ac99f3e84b74a') - def test_aarch64_ast2700_evb_sdk_v09_03(self): - self.set_machine('ast2700-evb') - - self.archive_extract(self.ASSET_SDK_V903_AST2700) + ASSET_SDK_V905_AST2700A1 = Asset( + 'https://github.com/AspeedTech-BMC/openbmc/releases/download/v09.05/ast2700-default-obmc.tar.gz', + 'c1f4496aec06743c812a6e9a1a18d032f34d62f3ddb6956e924fef62aa2046a5') + def start_ast2700_test(self, name): num_cpu = 4 - uboot_size = os.path.getsize(self.scratch_file('ast2700-default', + uboot_size = os.path.getsize(self.scratch_file(name, 'u-boot-nodtb.bin')) uboot_dtb_load_addr = hex(0x400000000 + uboot_size) load_images_list = [ { 'addr': '0x400000000', - 'file': self.scratch_file('ast2700-default', + 'file': self.scratch_file(name, 'u-boot-nodtb.bin') }, { 'addr': str(uboot_dtb_load_addr), - 'file': self.scratch_file('ast2700-default', 'u-boot.dtb') + 'file': self.scratch_file(name, 'u-boot.dtb') }, { 'addr': '0x430000000', - 'file': self.scratch_file('ast2700-default', 'bl31.bin') + 'file': self.scratch_file(name, 'bl31.bin') }, { 'addr': '0x430080000', - 'file': self.scratch_file('ast2700-default', 'optee', + 'file': self.scratch_file(name, 'optee', 'tee-raw.bin') } ] @@ -76,23 +76,34 @@ class AST2x00MachineSDK(QemuSystemTest): self.vm.add_args('-device', 'tmp105,bus=aspeed.i2c.bus.1,address=0x4d,id=tmp-test') self.do_test_aarch64_aspeed_sdk_start( - self.scratch_file('ast2700-default', 'image-bmc')) + self.scratch_file(name, 'image-bmc')) - wait_for_console_pattern(self, 'ast2700-default login:') + wait_for_console_pattern(self, f'{name} login:') exec_command_and_wait_for_pattern(self, 'root', 'Password:') - exec_command_and_wait_for_pattern(self, - '0penBmc', 'root@ast2700-default:~#') + exec_command_and_wait_for_pattern(self, '0penBmc', f'root@{name}:~#') exec_command_and_wait_for_pattern(self, 'echo lm75 0x4d > /sys/class/i2c-dev/i2c-1/device/new_device ', 'i2c i2c-1: new_device: Instantiated device lm75 at 0x4d'); exec_command_and_wait_for_pattern(self, - 'cat /sys/class/hwmon/hwmon20/temp1_input', '0') + 'cat /sys/bus/i2c/devices/1-004d/hwmon/hwmon*/temp1_input', '0') self.vm.cmd('qom-set', path='/machine/peripheral/tmp-test', property='temperature', value=18000) exec_command_and_wait_for_pattern(self, - 'cat /sys/class/hwmon/hwmon20/temp1_input', '18000') + 'cat /sys/bus/i2c/devices/1-004d/hwmon/hwmon*/temp1_input', '18000') + + def test_aarch64_ast2700_evb_sdk_v09_05(self): + self.set_machine('ast2700-evb') + + self.archive_extract(self.ASSET_SDK_V905_AST2700) + self.start_ast2700_test('ast2700-a0-default') + + def test_aarch64_ast2700a1_evb_sdk_v09_05(self): + self.set_machine('ast2700a1-evb') + + self.archive_extract(self.ASSET_SDK_V905_AST2700A1) + self.start_ast2700_test('ast2700-default') if __name__ == '__main__': diff --git a/tests/functional/test_arm_aspeed_bletchley.py b/tests/functional/test_arm_aspeed_bletchley.py new file mode 100644 index 0000000000..0da856c5ed --- /dev/null +++ b/tests/functional/test_arm_aspeed_bletchley.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# +# Functional test that boots the ASPEED machines +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from aspeed import AspeedTest + + +class BletchleyMachine(AspeedTest): + + ASSET_BLETCHLEY_FLASH = Asset( + 'https://github.com/legoater/qemu-aspeed-boot/raw/master/images/bletchley-bmc/openbmc-20250128071329/obmc-phosphor-image-bletchley-20250128071329.static.mtd.xz', + 'db21d04d47d7bb2a276f59d308614b4dfb70b9c7c81facbbca40a3977a2d8844'); + + def test_arm_ast2600_bletchley_openbmc(self): + image_path = self.uncompress(self.ASSET_BLETCHLEY_FLASH) + + self.do_test_arm_aspeed_openbmc('bletchley-bmc', image=image_path, + uboot='2019.04', cpu_id='0xf00', + soc='AST2600 rev A3'); + +if __name__ == '__main__': + AspeedTest.main() diff --git a/tests/functional/test_arm_aspeed_palmetto.py b/tests/functional/test_arm_aspeed_palmetto.py index 6588c02aad..35d832bc98 100755 --- a/tests/functional/test_arm_aspeed_palmetto.py +++ b/tests/functional/test_arm_aspeed_palmetto.py @@ -7,18 +7,19 @@ from qemu_test import Asset from aspeed import AspeedTest + class PalmettoMachine(AspeedTest): ASSET_PALMETTO_FLASH = Asset( - ('https://github.com/openbmc/openbmc/releases/download/2.9.0/' - 'obmc-phosphor-image-palmetto.static.mtd'), - '3e13bbbc28e424865dc42f35ad672b10f2e82cdb11846bb28fa625b48beafd0d'); + 'https://github.com/legoater/qemu-aspeed-boot/raw/master/images/palmetto-bmc/openbmc-20250128071432/obmc-phosphor-image-palmetto-20250128071432.static.mtd', + 'bce7c392eec75c707a91cfc8fad7ca9a69d7e4f10df936930d65c1cb9897ac81'); - def test_arm_ast2400_palmetto_openbmc_v2_9_0(self): + def test_arm_ast2400_palmetto_openbmc(self): image_path = self.ASSET_PALMETTO_FLASH.fetch() - self.do_test_arm_aspeed('palmetto-bmc', image_path) - + self.do_test_arm_aspeed_openbmc('palmetto-bmc', image=image_path, + uboot='2019.04', cpu_id='0x0', + soc='AST2400 rev A1'); if __name__ == '__main__': AspeedTest.main() diff --git a/tests/functional/test_arm_aspeed_romulus.py b/tests/functional/test_arm_aspeed_romulus.py index 747b616201..b97ed951b1 100755 --- a/tests/functional/test_arm_aspeed_romulus.py +++ b/tests/functional/test_arm_aspeed_romulus.py @@ -7,18 +7,19 @@ from qemu_test import Asset from aspeed import AspeedTest + class RomulusMachine(AspeedTest): ASSET_ROMULUS_FLASH = Asset( - ('https://github.com/openbmc/openbmc/releases/download/2.9.0/' - 'obmc-phosphor-image-romulus.static.mtd'), - '820341076803f1955bc31e647a512c79f9add4f5233d0697678bab4604c7bb25') + 'https://github.com/legoater/qemu-aspeed-boot/raw/master/images/romulus-bmc/openbmc-20250128071340/obmc-phosphor-image-romulus-20250128071340.static.mtd', + '6d031376440c82ed9d087d25e9fa76aea75b42f80daa252ec402c0bc3cf6cf5b'); - def test_arm_ast2500_romulus_openbmc_v2_9_0(self): + def test_arm_ast2500_romulus_openbmc(self): image_path = self.ASSET_ROMULUS_FLASH.fetch() - self.do_test_arm_aspeed('romulus-bmc', image_path) - + self.do_test_arm_aspeed_openbmc('romulus-bmc', image=image_path, + uboot='2019.04', cpu_id='0x0', + soc='AST2500 rev A1'); if __name__ == '__main__': AspeedTest.main() diff --git a/tests/functional/test_arm_aspeed_witherspoon.py b/tests/functional/test_arm_aspeed_witherspoon.py new file mode 100644 index 0000000000..ea1ce89b05 --- /dev/null +++ b/tests/functional/test_arm_aspeed_witherspoon.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# +# Functional test that boots the ASPEED machines +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset +from aspeed import AspeedTest + + +class WitherspoonMachine(AspeedTest): + + ASSET_WITHERSPOON_FLASH = Asset( + 'https://github.com/legoater/qemu-aspeed-boot/raw/master/images/witherspoon-bmc/openbmc-20240618035022/obmc-phosphor-image-witherspoon-20240618035022.ubi.mtd', + '937d9ed449ea6c6cbed983519088a42d0cafe276bcfe4fce07772ca6673f9213'); + + def test_arm_ast2500_witherspoon_openbmc(self): + image_path = self.ASSET_WITHERSPOON_FLASH.fetch() + + self.do_test_arm_aspeed_openbmc('witherspoon-bmc', image=image_path, + uboot='2016.07', cpu_id='0x0', + soc='AST2500 rev A1'); + +if __name__ == '__main__': + AspeedTest.main() |