* [PATCH v3 01/13] hw/i2c: Add designware i2c controller
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-21 5:31 ` [PATCH v3 02/13] hw/riscv/boot: Describe discontiguous memory in boot_info Joel Stanley
` (11 subsequent siblings)
12 siblings, 0 replies; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Chris Rauer, Michael Ellerman, Nicholas Piggin, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel, Hao Wu
From: Chris Rauer <crauer@google.com>
In the past this model has been submitted for use with the arm virt
machine, however in this case it will be used by the upcoming
Tenstorrent Atlantis RISC-V machine.
This is a re-submission of the model with Chris' permission, with a
light touch of updates to make it build with qemu master.
Reviewed-by: Hao Wu <wuhaotsh@google.com>
Signed-off-by: Chris Rauer <crauer@google.com>
Link: https://lore.kernel.org/qemu-devel/20220110214755.810343-2-venture@google.com
[jms: rebase and minor build fixes for class_init and reset callback]
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
v2: Add trace event for read and write, document Alano and myself as
reviewers.
---
MAINTAINERS | 8 +
include/hw/i2c/designware_i2c.h | 101 ++++
hw/i2c/designware_i2c.c | 818 ++++++++++++++++++++++++++++++++
hw/i2c/Kconfig | 4 +
hw/i2c/meson.build | 1 +
hw/i2c/trace-events | 4 +
6 files changed, 936 insertions(+)
create mode 100644 include/hw/i2c/designware_i2c.h
create mode 100644 hw/i2c/designware_i2c.c
diff --git a/MAINTAINERS b/MAINTAINERS
index aa4267b15806..e1942a86eba5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2716,6 +2716,14 @@ S: Orphan
F: hw/gpio/pcf8574.c
F: include/gpio/pcf8574.h
+DesignWare I2C
+M: Chris Rauer <crauer@google.com>
+R: Alano Song <alanosong@163.com>
+R: Joel Stanley <joel@jms.id.au>
+S: Maintained
+F: hw/i2c/designware_i2c.c
+F: include/hw/i2c/designware_i2c.h
+
Generic Loader
M: Alistair Francis <alistair@alistair23.me>
S: Maintained
diff --git a/include/hw/i2c/designware_i2c.h b/include/hw/i2c/designware_i2c.h
new file mode 100644
index 000000000000..0d8f904f51b7
--- /dev/null
+++ b/include/hw/i2c/designware_i2c.h
@@ -0,0 +1,101 @@
+/*
+ * DesignWare I2C Module.
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef DESIGNWARE_I2C_H
+#define DESIGNWARE_I2C_H
+
+#include "hw/i2c/i2c.h"
+#include "hw/core/irq.h"
+#include "hw/core/sysbus.h"
+
+/* Size of the FIFO buffers. */
+#define DESIGNWARE_I2C_RX_FIFO_SIZE 16
+#define DESIGNWARE_I2C_TX_FIFO_SIZE 16
+
+typedef enum DesignWareI2CStatus {
+ DW_I2C_STATUS_IDLE,
+ DW_I2C_STATUS_SENDING_ADDRESS,
+ DW_I2C_STATUS_SENDING,
+ DW_I2C_STATUS_RECEIVING,
+} DesignWareI2CStatus;
+
+/*
+ * struct DesignWareI2CState - DesignWare I2C device state.
+ * @bus: The underlying I2C Bus
+ * @irq: GIC interrupt line to fire on events
+ * @ic_con: : I2C control register
+ * @ic_tar: I2C target address register
+ * @ic_sar: I2C slave address register
+ * @ic_ss_scl_hcnt: Standard speed i2c clock scl high count register
+ * @ic_ss_scl_lcnt: Standard speed i2c clock scl low count register
+ * @ic_fs_scl_hcnt: Fast mode or fast mode plus i2c clock scl high count
+ * register
+ * @ic_fs_scl_lcnt:Fast mode or fast mode plus i2c clock scl low count
+ * register
+ * @ic_intr_mask: I2C Interrupt Mask Register
+ * @ic_raw_intr_stat: I2C raw interrupt status register
+ * @ic_rx_tl: I2C receive FIFO threshold register
+ * @ic_tx_tl: I2C transmit FIFO threshold register
+ * @ic_enable: I2C enable register
+ * @ic_status: I2C status register
+ * @ic_txflr: I2C transmit fifo level register
+ * @ic_rxflr: I2C receive fifo level register
+ * @ic_sda_hold: I2C SDA hold time length register
+ * @ic_tx_abrt_source: The I2C transmit abort source register
+ * @ic_sda_setup: I2C SDA setup register
+ * @ic_enable_status: I2C enable status register
+ * @ic_fs_spklen: I2C SS, FS or FM+ spike suppression limit
+ * @ic_comp_param_1: Component parameter register
+ * @ic_comp_version: I2C component version register
+ * @ic_comp_type: I2C component type register
+ * @rx_fifo: The FIFO buffer for receiving in FIFO mode.
+ * @rx_cur: The current position of rx_fifo.
+ * @status: The current status of the SMBus.
+ */
+typedef struct DesignWareI2CState {
+ SysBusDevice parent;
+
+ MemoryRegion iomem;
+
+ I2CBus *bus;
+ qemu_irq irq;
+
+ uint32_t ic_con;
+ uint32_t ic_tar;
+ uint32_t ic_sar;
+ uint32_t ic_ss_scl_hcnt;
+ uint32_t ic_ss_scl_lcnt;
+ uint32_t ic_fs_scl_hcnt;
+ uint32_t ic_fs_scl_lcnt;
+ uint32_t ic_intr_mask;
+ uint32_t ic_raw_intr_stat;
+ uint32_t ic_rx_tl;
+ uint32_t ic_tx_tl;
+ uint32_t ic_enable;
+ uint32_t ic_status;
+ uint32_t ic_txflr;
+ uint32_t ic_rxflr;
+ uint32_t ic_sda_hold;
+ uint32_t ic_tx_abrt_source;
+ uint32_t ic_sda_setup;
+ uint32_t ic_enable_status;
+ uint32_t ic_fs_spklen;
+ uint32_t ic_comp_param_1;
+ uint32_t ic_comp_version;
+ uint32_t ic_comp_type;
+
+ uint8_t rx_fifo[DESIGNWARE_I2C_RX_FIFO_SIZE];
+ uint8_t rx_cur;
+
+ DesignWareI2CStatus status;
+} DesignWareI2CState;
+
+#define TYPE_DESIGNWARE_I2C "designware-i2c"
+#define DESIGNWARE_I2C(obj) OBJECT_CHECK(DesignWareI2CState, (obj), \
+ TYPE_DESIGNWARE_I2C)
+
+#endif /* DESIGNWARE_I2C_H */
diff --git a/hw/i2c/designware_i2c.c b/hw/i2c/designware_i2c.c
new file mode 100644
index 000000000000..3e0e7dab2333
--- /dev/null
+++ b/hw/i2c/designware_i2c.c
@@ -0,0 +1,818 @@
+/*
+ * DesignWare I2C Module.
+ *
+ * Copyright 2021 Google LLC
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/i2c/designware_i2c.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/guest-random.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "trace.h"
+
+enum DesignWareI2CRegister {
+ DW_IC_CON = 0x00,
+ DW_IC_TAR = 0x04,
+ DW_IC_SAR = 0x08,
+ DW_IC_DATA_CMD = 0x10,
+ DW_IC_SS_SCL_HCNT = 0x14,
+ DW_IC_SS_SCL_LCNT = 0x18,
+ DW_IC_FS_SCL_HCNT = 0x1c,
+ DW_IC_FS_SCL_LCNT = 0x20,
+ DW_IC_INTR_STAT = 0x2c,
+ DW_IC_INTR_MASK = 0x30,
+ DW_IC_RAW_INTR_STAT = 0x34,
+ DW_IC_RX_TL = 0x38,
+ DW_IC_TX_TL = 0x3c,
+ DW_IC_CLR_INTR = 0x40,
+ DW_IC_CLR_RX_UNDER = 0x44,
+ DW_IC_CLR_RX_OVER = 0x48,
+ DW_IC_CLR_TX_OVER = 0x4c,
+ DW_IC_CLR_RD_REQ = 0x50,
+ DW_IC_CLR_TX_ABRT = 0x54,
+ DW_IC_CLR_RX_DONE = 0x58,
+ DW_IC_CLR_ACTIVITY = 0x5c,
+ DW_IC_CLR_STOP_DET = 0x60,
+ DW_IC_CLR_START_DET = 0x64,
+ DW_IC_CLR_GEN_CALL = 0x68,
+ DW_IC_ENABLE = 0x6c,
+ DW_IC_STATUS = 0x70,
+ DW_IC_TXFLR = 0x74,
+ DW_IC_RXFLR = 0x78,
+ DW_IC_SDA_HOLD = 0x7c,
+ DW_IC_TX_ABRT_SOURCE = 0x80,
+ DW_IC_SLV_DATA_NACK_ONLY = 0x84,
+ DW_IC_DMA_CR = 0x88,
+ DW_IC_DMA_TDLR = 0x8c,
+ DW_IC_DMA_RDLR = 0x90,
+ DW_IC_SDA_SETUP = 0x94,
+ DW_IC_ACK_GENERAL_CALL = 0x98,
+ DW_IC_ENABLE_STATUS = 0x9c,
+ DW_IC_FS_SPKLEN = 0xa0,
+ DW_IC_CLR_RESTART_DET = 0xa8,
+ DW_IC_COMP_PARAM_1 = 0xf4,
+ DW_IC_COMP_VERSION = 0xf8,
+ DW_IC_COMP_TYPE = 0xfc,
+};
+
+/* DW_IC_CON fields */
+#define DW_IC_CON_STOP_DET_IF_MASTER_ACTIV BIT(10)
+#define DW_IC_CON_RX_FIFO_FULL_HLD_CTRL BIT(9)
+#define DW_IC_CON_TX_EMPTY_CTRL BIT(8)
+#define DW_IC_CON_STOP_IF_ADDRESSED BIT(7)
+#define DW_IC_CON_SLAVE_DISABLE BIT(6)
+#define DW_IC_CON_IC_RESTART_EN BIT(5)
+#define DW_IC_CON_10BITADDR_MASTER BIT(4)
+#define DW_IC_CON_10BITADDR_SLAVE BIT(3)
+#define DW_IC_CON_SPEED(rv) extract32((rv), 1, 2)
+#define DW_IC_CON_MASTER_MODE BIT(0)
+
+/* DW_IC_TAR fields */
+#define DW_IC_TAR_IC_10BITADDR_MASTER BIT(12)
+#define DW_IC_TAR_SPECIAL BIT(11)
+#define DW_IC_TAR_GC_OR_START BIT(10)
+#define DW_IC_TAR_ADDRESS(rv) extract32((rv), 0, 10)
+
+/* DW_IC_DATA_CMD fields */
+#define DW_IC_DATA_CMD_RESTART BIT(10)
+#define DW_IC_DATA_CMD_STOP BIT(9)
+#define DW_IC_DATA_CMD_CMD BIT(8)
+#define DW_IC_DATA_CMD_DAT(rv) extract32((rv), 0, 8)
+
+/* DW_IC_INTR_STAT/INTR_MASK/RAW_INTR_STAT fields */
+#define DW_IC_INTR_RESTART_DET BIT(12)
+#define DW_IC_INTR_GEN_CALL BIT(11)
+#define DW_IC_INTR_START_DET BIT(10)
+#define DW_IC_INTR_STOP_DET BIT(9)
+#define DW_IC_INTR_ACTIVITY BIT(8)
+#define DW_IC_INTR_RX_DONE BIT(7)
+#define DW_IC_INTR_TX_ABRT BIT(6)
+#define DW_IC_INTR_RD_REQ BIT(5)
+#define DW_IC_INTR_TX_EMPTY BIT(4) /* Hardware clear only. */
+#define DW_IC_INTR_TX_OVER BIT(3)
+#define DW_IC_INTR_RX_FULL BIT(2) /* Hardware clear only. */
+#define DW_IC_INTR_RX_OVER BIT(1)
+#define DW_IC_INTR_RX_UNDER BIT(0)
+
+/* DW_IC_ENABLE fields */
+#define DW_IC_ENABLE_TX_CMD_BLOCK BIT(2)
+#define DW_IC_ENABLE_ABORT BIT(1)
+#define DW_IC_ENABLE_ENABLE BIT(0)
+
+/* DW_IC_STATUS fields */
+#define DW_IC_STATUS_SLV_ACTIVITY BIT(6)
+#define DW_IC_STATUS_MST_ACTIVITY BIT(5)
+#define DW_IC_STATUS_RFF BIT(4)
+#define DW_IC_STATUS_RFNE BIT(3)
+#define DW_IC_STATUS_TFE BIT(2)
+#define DW_IC_STATUS_TFNF BIT(1)
+#define DW_IC_STATUS_ACTIVITY BIT(0)
+
+/* DW_IC_TX_ABRT_SOURCE fields */
+#define DW_IC_TX_TX_FLUSH_CNT extract32((rv), 23, 9)
+#define DW_IC_TX_ABRT_USER_ABRT BIT(16)
+#define DW_IC_TX_ABRT_SLVRD_INTX BIT(15)
+#define DW_IC_TX_ABRT_SLV_ARBLOST BIT(14)
+#define DW_IC_TX_ABRT_SLVFLUSH_TXFIFO BIT(13)
+#define DW_IC_TX_ARB_LOST BIT(12)
+#define DW_IC_TX_ABRT_MASTER_DIS BIT(11)
+#define DW_IC_TX_ABRT_10B_RD_NORSTRT BIT(10)
+#define DW_IC_TX_ABRT_SBYTE_NORSTRT BIT(9)
+#define DW_IC_TX_ABRT_HS_NORSTRT BIT(8)
+#define DW_IC_TX_ABRT_SBYTE_ACKDET BIT(7)
+#define DW_IC_TX_ABRT_HS_ACKDET BIT(6)
+#define DW_IC_TX_ABRT_GCALL_READ BIT(5)
+#define DW_IC_TX_ABRT_GCALL_NOACK BIT(4)
+#define DW_IC_TX_ABRT_TXDATA_NOACK BIT(3)
+#define DW_IC_TX_ABRT_10ADDR2_NOACK BIT(2)
+#define DW_IC_TX_ABRT_10ADDR1_NOACK BIT(1)
+#define DW_IC_TX_ABRT_7B_ADDR_NOACK BIT(0)
+
+
+/* IC_ENABLE_STATUS fields */
+#define DW_IC_ENABLE_STATUS_SLV_RX_DATA_LOST BIT(2)
+#define DW_IC_ENABLE_STATUS_SLV_DISABLED_WHILE_BUSY BIT(1)
+#define DW_IC_ENABLE_STATUS_IC_EN BIT(0)
+
+/* Masks for writable registers. */
+#define DW_IC_CON_MASK 0x000003ff
+#define DW_IC_TAR_MASK 0x00000fff
+#define DW_IC_SAR_MASK 0x000003ff
+#define DW_IC_SS_SCL_HCNT_MASK 0x0000ffff
+#define DW_IC_SS_SCL_LCNT_MASK 0x0000ffff
+#define DW_IC_FS_SCL_HCNT_MASK 0x0000ffff
+#define DW_IC_FS_SCL_LCNT_MASK 0x0000ffff
+#define DW_IC_INTR_MASK_MASK 0x00001fff
+#define DW_IC_ENABLE_MASK 0x00000007
+#define DW_IC_SDA_HOLD_MASK 0x00ffffff
+#define DW_IC_SDA_SETUP_MASK 0x000000ff
+#define DW_IC_FS_SPKLEN_MASK 0x000000ff
+
+/* Reset values */
+#define DW_IC_CON_INIT_VAL 0x7d
+#define DW_IC_TAR_INIT_VAL 0x1055
+#define DW_IC_SAR_INIT_VAL 0x55
+#define DW_IC_SS_SCL_HCNT_INIT_VAL 0x190
+#define DW_IC_SS_SCL_LCNT_INIT_VAL 0x1d6
+#define DW_IC_FS_SCL_HCNT_INIT_VAL 0x3c
+#define DW_IC_FS_SCL_LCNT_INIT_VAL 0x82
+#define DW_IC_INTR_MASK_INIT_VAL 0x8ff
+#define DW_IC_STATUS_INIT_VAL 0x6
+#define DW_IC_SDA_HOLD_INIT_VAL 0x1
+#define DW_IC_SDA_SETUP_INIT_VAL 0x64
+#define DW_IC_FS_SPKLEN_INIT_VAL 0x2
+
+#define DW_IC_COMP_PARAM_1_HAS_ENCODED_PARAMS BIT(7)
+#define DW_IC_COMP_PARAM_1_HAS_DMA 0 /* bit 6 - DMA disabled. */
+#define DW_IC_COMP_PARAM_1_INTR_IO BIT(5)
+#define DW_IC_COMP_PARAM_1_HC_COUNT_VAL 0 /* bit 4 - disabled */
+#define DW_IC_COMP_PARAM_1_HIGH_SPEED_MODE (BIT(2) | BIT(3))
+#define DW_IC_COMP_PARAM_1_APB_DATA_WIDTH_32 BIT(1) /* bits 0, 1 */
+#define DW_IC_COMP_PARAM_1_INIT_VAL \
+ (DW_IC_COMP_PARAM_1_APB_DATA_WIDTH_32 | \
+ DW_IC_COMP_PARAM_1_HIGH_SPEED_MODE | \
+ DW_IC_COMP_PARAM_1_HC_COUNT_VAL | \
+ DW_IC_COMP_PARAM_1_INTR_IO | \
+ DW_IC_COMP_PARAM_1_HAS_DMA | \
+ DW_IC_COMP_PARAM_1_HAS_ENCODED_PARAMS | \
+ ((DESIGNWARE_I2C_RX_FIFO_SIZE - 1) << 8) | \
+ ((DESIGNWARE_I2C_TX_FIFO_SIZE - 1) << 16))
+#define DW_IC_COMP_VERSION_INIT_VAL 0x3132302a
+#define DW_IC_COMP_TYPE_INIT_VAL 0x44570140
+
+static void dw_i2c_update_irq(DesignWareI2CState *s)
+{
+ int level;
+ uint32_t intr = s->ic_raw_intr_stat & s->ic_intr_mask;
+
+ level = !!((intr & DW_IC_INTR_RX_UNDER) |
+ (intr & DW_IC_INTR_RX_OVER) |
+ (intr & DW_IC_INTR_RX_FULL) |
+ (intr & DW_IC_INTR_TX_OVER) |
+ (intr & DW_IC_INTR_TX_EMPTY) |
+ (intr & DW_IC_INTR_RD_REQ) |
+ (intr & DW_IC_INTR_TX_ABRT) |
+ (intr & DW_IC_INTR_RX_DONE) |
+ (intr & DW_IC_INTR_ACTIVITY) |
+ (intr & DW_IC_INTR_STOP_DET) |
+ (intr & DW_IC_INTR_START_DET) |
+ (intr & DW_IC_INTR_GEN_CALL) |
+ (intr & DW_IC_INTR_RESTART_DET)
+ );
+ qemu_set_irq(s->irq, level);
+}
+
+static uint32_t dw_i2c_read_ic_data_cmd(DesignWareI2CState *s)
+{
+ uint32_t value = s->rx_fifo[s->rx_cur];
+
+ if (s->status != DW_I2C_STATUS_RECEIVING) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Attempted to read from RX fifo when not in receive "
+ "state.\n", DEVICE(s)->canonical_path);
+ if (s->status != DW_I2C_STATUS_IDLE) {
+ s->ic_raw_intr_stat &= ~DW_IC_INTR_RX_UNDER;
+ dw_i2c_update_irq(s);
+ }
+ return 0;
+ }
+
+ s->rx_cur = (s->rx_cur + 1) % DESIGNWARE_I2C_RX_FIFO_SIZE;
+
+ if (s->ic_rxflr > 0) {
+ s->ic_rxflr--;
+ } else {
+ s->ic_raw_intr_stat &= ~DW_IC_INTR_RX_UNDER;
+ dw_i2c_update_irq(s);
+ return 0;
+ }
+
+ if (s->ic_rxflr <= s->ic_rx_tl) {
+ s->ic_raw_intr_stat &= ~DW_IC_INTR_RX_FULL;
+ dw_i2c_update_irq(s);
+ }
+
+ return value;
+}
+
+static uint64_t dw_i2c_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint64_t value = 0;
+
+ DesignWareI2CState *s = opaque;
+
+ switch (offset) {
+ case DW_IC_CON:
+ value = s->ic_con;
+ break;
+ case DW_IC_TAR:
+ value = s->ic_tar;
+ break;
+ case DW_IC_SAR:
+ qemu_log_mask(LOG_UNIMP, "%s: unsupported read - ic_sar\n",
+ DEVICE(s)->canonical_path);
+ value = s->ic_sar;
+ break;
+ case DW_IC_DATA_CMD:
+ value = dw_i2c_read_ic_data_cmd(s);
+ break;
+ case DW_IC_SS_SCL_HCNT:
+ value = s->ic_ss_scl_hcnt;
+ break;
+ case DW_IC_SS_SCL_LCNT:
+ value = s->ic_ss_scl_lcnt;
+ break;
+ case DW_IC_FS_SCL_HCNT:
+ value = s->ic_fs_scl_hcnt;
+ break;
+ case DW_IC_FS_SCL_LCNT:
+ value = s->ic_fs_scl_lcnt;
+ break;
+ case DW_IC_INTR_STAT:
+ value = s->ic_raw_intr_stat & s->ic_intr_mask;
+ break;
+ case DW_IC_INTR_MASK:
+ value = s->ic_intr_mask;
+ break;
+ case DW_IC_RAW_INTR_STAT:
+ value = s->ic_raw_intr_stat;
+ break;
+ case DW_IC_RX_TL:
+ value = s->ic_rx_tl;
+ break;
+ case DW_IC_TX_TL:
+ value = s->ic_tx_tl;
+ break;
+ case DW_IC_CLR_INTR:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_GEN_CALL |
+ DW_IC_INTR_RESTART_DET |
+ DW_IC_INTR_START_DET |
+ DW_IC_INTR_STOP_DET |
+ DW_IC_INTR_ACTIVITY |
+ DW_IC_INTR_RX_DONE |
+ DW_IC_INTR_TX_ABRT |
+ DW_IC_INTR_RD_REQ |
+ DW_IC_INTR_TX_OVER |
+ DW_IC_INTR_RX_OVER |
+ DW_IC_INTR_RX_UNDER);
+ s->ic_tx_abrt_source = 0;
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_CLR_RX_UNDER:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_RX_UNDER);
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_CLR_RX_OVER:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_RX_OVER);
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_CLR_TX_OVER:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_TX_OVER);
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_CLR_RD_REQ:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_RD_REQ);
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_CLR_TX_ABRT:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_TX_ABRT);
+ s->ic_tx_abrt_source = 0;
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_CLR_RX_DONE:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_RX_DONE);
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_CLR_ACTIVITY:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_ACTIVITY);
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_CLR_STOP_DET:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_STOP_DET);
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_CLR_START_DET:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_START_DET);
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_CLR_GEN_CALL:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_GEN_CALL);
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_ENABLE:
+ value = s->ic_enable;
+ break;
+ case DW_IC_STATUS:
+ value = s->ic_status;
+ break;
+ case DW_IC_TXFLR:
+ value = s->ic_txflr;
+ break;
+ case DW_IC_RXFLR:
+ value = s->ic_rxflr;
+ break;
+ case DW_IC_SDA_HOLD:
+ value = s->ic_sda_hold;
+ break;
+ case DW_IC_TX_ABRT_SOURCE:
+ value = s->ic_tx_abrt_source;
+ break;
+ case DW_IC_SLV_DATA_NACK_ONLY:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unsupported read - ic_slv_data_nack_only\n",
+ DEVICE(s)->canonical_path);
+ break;
+ case DW_IC_DMA_CR:
+ qemu_log_mask(LOG_UNIMP, "%s: unsupported read - ic_dma_cr\n",
+ DEVICE(s)->canonical_path);
+ break;
+ case DW_IC_DMA_TDLR:
+ qemu_log_mask(LOG_UNIMP, "%s: unsupported read - ic_dma_tdlr\n",
+ DEVICE(s)->canonical_path);
+ break;
+ case DW_IC_DMA_RDLR:
+ qemu_log_mask(LOG_UNIMP, "%s: unsupported read - ic_dma_rdlr\n",
+ DEVICE(s)->canonical_path);
+ break;
+ case DW_IC_SDA_SETUP:
+ value = s->ic_sda_setup;
+ break;
+ case DW_IC_ACK_GENERAL_CALL:
+ qemu_log_mask(LOG_UNIMP, "%s: unsupported read - ic_ack_general_call\n",
+ DEVICE(s)->canonical_path);
+ break;
+ case DW_IC_ENABLE_STATUS:
+ value = s->ic_enable_status;
+ break;
+ case DW_IC_FS_SPKLEN:
+ value = s->ic_fs_spklen;
+ break;
+ case DW_IC_CLR_RESTART_DET:
+ s->ic_raw_intr_stat &= ~(DW_IC_INTR_RESTART_DET);
+ break;
+ case DW_IC_COMP_PARAM_1:
+ value = s->ic_comp_param_1;
+ break;
+ case DW_IC_COMP_VERSION:
+ value = s->ic_comp_version;
+ break;
+ case DW_IC_COMP_TYPE:
+ value = s->ic_comp_type;
+ break;
+
+ /* This register is invalid at this point. */
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: read from invalid offset 0x%" HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, offset);
+ break;
+ }
+
+ trace_dw_i2c_read(DEVICE(s)->canonical_path, offset, value);
+
+ return value;
+}
+
+static void dw_i2c_write_ic_con(DesignWareI2CState *s, uint32_t value)
+{
+ if (value & DW_IC_CON_RX_FIFO_FULL_HLD_CTRL) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unsupported ic_con flag - RX_FIFO_FULL_HLD_CTRL\n",
+ DEVICE(s)->canonical_path);
+ }
+
+ if (!(s->ic_enable & DW_IC_ENABLE_ENABLE)) {
+ s->ic_con = value & DW_IC_CON_MASK;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid setting to ic_con %d when ic_enable[0]==1\n",
+ DEVICE(s)->canonical_path, value);
+ }
+}
+
+static void dw_i2c_reset_to_idle(DesignWareI2CState *s)
+{
+ s->ic_enable_status &= ~DW_IC_ENABLE_STATUS_IC_EN;
+ s->ic_raw_intr_stat &= ~DW_IC_INTR_TX_EMPTY;
+ s->ic_raw_intr_stat &= ~DW_IC_INTR_RX_FULL;
+ s->ic_raw_intr_stat &= ~DW_IC_INTR_RX_UNDER;
+ s->ic_raw_intr_stat &= ~DW_IC_INTR_RX_OVER;
+ s->ic_rxflr = 0;
+ s->ic_status &= ~DW_IC_STATUS_ACTIVITY;
+ s->status = DW_I2C_STATUS_IDLE;
+ dw_i2c_update_irq(s);
+}
+
+static void dw_ic_tx_abort(DesignWareI2CState *s, uint32_t src)
+{
+ s->ic_tx_abrt_source |= src;
+ s->ic_raw_intr_stat |= DW_IC_INTR_TX_ABRT;
+ dw_i2c_reset_to_idle(s);
+ dw_i2c_update_irq(s);
+}
+
+static void dw_i2c_write_ic_data_cmd(DesignWareI2CState *s, uint32_t value)
+{
+ int recv = !!(value & DW_IC_DATA_CMD_CMD);
+
+ if (s->status == DW_I2C_STATUS_IDLE ||
+ s->ic_raw_intr_stat & DW_IC_INTR_TX_ABRT) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Attempted to write to TX fifo when it is held in "
+ "reset.\n", DEVICE(s)->canonical_path);
+ return;
+ }
+
+ /* Send the address if it hasn't been sent yet. */
+ if (s->status == DW_I2C_STATUS_SENDING_ADDRESS) {
+ int rv = i2c_start_transfer(s->bus, DW_IC_TAR_ADDRESS(s->ic_tar), recv);
+ if (rv) {
+ dw_ic_tx_abort(s, DW_IC_TX_ABRT_7B_ADDR_NOACK);
+ return;
+ }
+ s->status = recv ? DW_I2C_STATUS_RECEIVING : DW_I2C_STATUS_SENDING;
+ }
+
+ /* Send data */
+ if (!recv) {
+ int rv = i2c_send(s->bus, DW_IC_DATA_CMD_DAT(value));
+ if (rv) {
+ i2c_end_transfer(s->bus);
+ dw_ic_tx_abort(s, DW_IC_TX_ABRT_TXDATA_NOACK);
+ return;
+ }
+ dw_i2c_update_irq(s);
+ }
+
+ /* Restart command */
+ if (value & DW_IC_DATA_CMD_RESTART && s->ic_con & DW_IC_CON_IC_RESTART_EN) {
+ s->ic_raw_intr_stat |= DW_IC_INTR_RESTART_DET |
+ DW_IC_INTR_START_DET |
+ DW_IC_INTR_ACTIVITY;
+ s->ic_status |= DW_IC_STATUS_ACTIVITY;
+ dw_i2c_update_irq(s);
+
+ if (i2c_start_transfer(s->bus, DW_IC_TAR_ADDRESS(s->ic_tar), recv)) {
+ dw_ic_tx_abort(s, DW_IC_TX_ABRT_7B_ADDR_NOACK);
+ return;
+ }
+
+ s->status = recv ? DW_I2C_STATUS_RECEIVING : DW_I2C_STATUS_SENDING;
+ }
+
+ /* Receive data */
+ if (recv) {
+ uint8_t pos = (s->rx_cur + s->ic_rxflr) % DESIGNWARE_I2C_RX_FIFO_SIZE;
+
+ if (s->ic_rxflr < DESIGNWARE_I2C_RX_FIFO_SIZE) {
+ s->rx_fifo[pos] = i2c_recv(s->bus);
+ s->ic_rxflr++;
+ } else {
+ s->ic_raw_intr_stat |= DW_IC_INTR_RX_OVER;
+ dw_i2c_update_irq(s);
+ }
+
+ if (s->ic_rxflr > s->ic_rx_tl) {
+ s->ic_raw_intr_stat |= DW_IC_INTR_RX_FULL;
+ dw_i2c_update_irq(s);
+ }
+ if (value & DW_IC_DATA_CMD_STOP) {
+ i2c_nack(s->bus);
+ }
+ }
+
+ /* Stop command */
+ if (value & DW_IC_DATA_CMD_STOP) {
+ s->ic_raw_intr_stat |= DW_IC_INTR_STOP_DET;
+ s->ic_status &= ~DW_IC_STATUS_ACTIVITY;
+ s->ic_raw_intr_stat &= ~DW_IC_INTR_TX_EMPTY;
+ i2c_end_transfer(s->bus);
+ dw_i2c_update_irq(s);
+ }
+}
+
+static void dw_i2c_write_ic_enable(DesignWareI2CState *s, uint32_t value)
+{
+ if (value & DW_IC_ENABLE_ENABLE && !(s->ic_con & DW_IC_CON_SLAVE_DISABLE)) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Designware I2C slave mode is not supported.\n",
+ DEVICE(s)->canonical_path);
+ return;
+ }
+
+ s->ic_enable = value & DW_IC_ENABLE_MASK;
+
+ if (value & DW_IC_ENABLE_ABORT || value & DW_IC_ENABLE_TX_CMD_BLOCK) {
+ dw_ic_tx_abort(s, DW_IC_TX_ABRT_USER_ABRT);
+ return;
+ }
+
+ if (value & DW_IC_ENABLE_ENABLE) {
+ s->ic_enable_status |= DW_IC_ENABLE_STATUS_IC_EN;
+ s->ic_status |= DW_IC_STATUS_ACTIVITY;
+ s->ic_raw_intr_stat |= DW_IC_INTR_ACTIVITY |
+ DW_IC_INTR_START_DET |
+ DW_IC_INTR_TX_EMPTY;
+ s->status = DW_I2C_STATUS_SENDING_ADDRESS;
+ dw_i2c_update_irq(s);
+ } else if ((value & DW_IC_ENABLE_ENABLE) == 0) {
+ dw_i2c_reset_to_idle(s);
+ }
+
+}
+
+static void dw_i2c_write_ic_rx_tl(DesignWareI2CState *s, uint32_t value)
+{
+ /* Note that a value of 0 for ic_rx_tl indicates a threashold of 1. */
+ if (value > DESIGNWARE_I2C_RX_FIFO_SIZE - 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid setting to ic_rx_tl %d\n",
+ DEVICE(s)->canonical_path, value);
+ s->ic_rx_tl = DESIGNWARE_I2C_RX_FIFO_SIZE - 1;
+ } else {
+ s->ic_rx_tl = value;
+ }
+
+ if (s->ic_rxflr > s->ic_rx_tl && s->ic_enable & DW_IC_ENABLE_ENABLE) {
+ s->ic_raw_intr_stat |= DW_IC_INTR_RX_FULL;
+ } else {
+ s->ic_raw_intr_stat &= ~DW_IC_INTR_RX_FULL;
+ }
+ dw_i2c_update_irq(s);
+}
+
+static void dw_i2c_write_ic_tx_tl(DesignWareI2CState *s, uint32_t value)
+{
+ /*
+ * Note that a value of 0 for ic_tx_tl indicates a threashold of 1.
+ * However, the tx threshold is not used in the model because commands are
+ * always sent out as soon as they are written.
+ */
+ if (value > DESIGNWARE_I2C_TX_FIFO_SIZE - 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid setting to ic_tx_tl %d\n",
+ DEVICE(s)->canonical_path, value);
+ s->ic_tx_tl = DESIGNWARE_I2C_TX_FIFO_SIZE - 1;
+ } else {
+ s->ic_tx_tl = value;
+ }
+}
+
+static void dw_i2c_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ DesignWareI2CState *s = opaque;
+
+ trace_dw_i2c_write(DEVICE(s)->canonical_path, offset, value);
+
+ /* The order of the registers are their order in memory. */
+ switch (offset) {
+ case DW_IC_CON:
+ dw_i2c_write_ic_con(s, value);
+ break;
+ case DW_IC_TAR:
+ s->ic_tar = value & DW_IC_TAR_MASK;
+ break;
+ case DW_IC_SAR:
+ qemu_log_mask(LOG_UNIMP, "%s: unsupported write - ic_sar\n",
+ DEVICE(s)->canonical_path);
+ s->ic_sar = value & DW_IC_SAR_MASK;
+ break;
+ case DW_IC_DATA_CMD:
+ dw_i2c_write_ic_data_cmd(s, value);
+ break;
+ case DW_IC_SS_SCL_HCNT:
+ s->ic_ss_scl_hcnt = value & DW_IC_SS_SCL_HCNT_MASK;
+ break;
+ case DW_IC_SS_SCL_LCNT:
+ s->ic_ss_scl_lcnt = value & DW_IC_SS_SCL_LCNT_MASK;
+ break;
+ case DW_IC_FS_SCL_HCNT:
+ s->ic_fs_scl_hcnt = value & DW_IC_FS_SCL_HCNT_MASK;
+ break;
+ case DW_IC_FS_SCL_LCNT:
+ s->ic_fs_scl_lcnt = value & DW_IC_FS_SCL_LCNT_MASK;
+ break;
+ case DW_IC_INTR_MASK:
+ s->ic_intr_mask = value & DW_IC_INTR_MASK_MASK;
+ dw_i2c_update_irq(s);
+ break;
+ case DW_IC_RX_TL:
+ dw_i2c_write_ic_rx_tl(s, value);
+ break;
+ case DW_IC_TX_TL:
+ dw_i2c_write_ic_tx_tl(s, value);
+ break;
+ case DW_IC_ENABLE:
+ dw_i2c_write_ic_enable(s, value);
+ break;
+ case DW_IC_SDA_HOLD:
+ s->ic_sda_hold = value & DW_IC_SDA_HOLD_MASK;
+ break;
+ case DW_IC_SLV_DATA_NACK_ONLY:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unsupported write - ic_slv_data_nack_only\n",
+ DEVICE(s)->canonical_path);
+ break;
+ case DW_IC_DMA_CR:
+ qemu_log_mask(LOG_UNIMP, "%s: unsupported write - ic_dma_cr\n",
+ DEVICE(s)->canonical_path);
+ break;
+ case DW_IC_DMA_TDLR:
+ qemu_log_mask(LOG_UNIMP, "%s: unsupported write - ic_dma_tdlr\n",
+ DEVICE(s)->canonical_path);
+ break;
+ case DW_IC_DMA_RDLR:
+ qemu_log_mask(LOG_UNIMP, "%s: unsupported write - ic_dma_rdlr\n",
+ DEVICE(s)->canonical_path);
+ break;
+ case DW_IC_SDA_SETUP:
+ s->ic_sda_setup = value & DW_IC_SDA_SETUP_MASK;
+ break;
+ case DW_IC_ACK_GENERAL_CALL:
+ qemu_log_mask(LOG_UNIMP,
+ "%s: unsupported write - ic_ack_general_call\n",
+ DEVICE(s)->canonical_path);
+ break;
+ case DW_IC_FS_SPKLEN:
+ s->ic_fs_spklen = value & DW_IC_FS_SPKLEN_MASK;
+ break;
+
+ /* This register is invalid at this point. */
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: write to invalid offset or readonly register 0x%"
+ HWADDR_PRIx "\n",
+ DEVICE(s)->canonical_path, offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps designware_i2c_ops = {
+ .read = dw_i2c_read,
+ .write = dw_i2c_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void designware_i2c_enter_reset(Object *obj, ResetType type)
+{
+ DesignWareI2CState *s = DESIGNWARE_I2C(obj);
+
+ s->ic_con = DW_IC_CON_INIT_VAL;
+ s->ic_tar = DW_IC_TAR_INIT_VAL;
+ s->ic_sar = DW_IC_SAR_INIT_VAL;
+ s->ic_ss_scl_hcnt = DW_IC_SS_SCL_HCNT_INIT_VAL;
+ s->ic_ss_scl_lcnt = DW_IC_SS_SCL_LCNT_INIT_VAL;
+ s->ic_fs_scl_hcnt = DW_IC_FS_SCL_HCNT_INIT_VAL;
+ s->ic_fs_scl_lcnt = DW_IC_FS_SCL_LCNT_INIT_VAL;
+ s->ic_intr_mask = DW_IC_INTR_MASK_INIT_VAL;
+ s->ic_raw_intr_stat = 0;
+ s->ic_rx_tl = 0;
+ s->ic_tx_tl = 0;
+ s->ic_enable = 0;
+ s->ic_status = DW_IC_STATUS_INIT_VAL;
+ s->ic_txflr = 0;
+ s->ic_rxflr = 0;
+ s->ic_sda_hold = DW_IC_SDA_HOLD_INIT_VAL;
+ s->ic_tx_abrt_source = 0;
+ s->ic_sda_setup = DW_IC_SDA_SETUP_INIT_VAL;
+ s->ic_enable_status = 0;
+ s->ic_fs_spklen = DW_IC_FS_SPKLEN_INIT_VAL;
+ s->ic_comp_param_1 = DW_IC_COMP_PARAM_1_INIT_VAL;
+ s->ic_comp_version = DW_IC_COMP_VERSION_INIT_VAL;
+ s->ic_comp_type = DW_IC_COMP_TYPE_INIT_VAL;
+
+ s->rx_cur = 0;
+ s->status = DW_I2C_STATUS_IDLE;
+}
+
+static void designware_i2c_hold_reset(Object *obj, ResetType type)
+{
+ DesignWareI2CState *s = DESIGNWARE_I2C(obj);
+
+ qemu_irq_lower(s->irq);
+}
+
+static const VMStateDescription vmstate_designware_i2c = {
+ .name = TYPE_DESIGNWARE_I2C,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(ic_con, DesignWareI2CState),
+ VMSTATE_UINT32(ic_tar, DesignWareI2CState),
+ VMSTATE_UINT32(ic_sar, DesignWareI2CState),
+ VMSTATE_UINT32(ic_ss_scl_hcnt, DesignWareI2CState),
+ VMSTATE_UINT32(ic_ss_scl_lcnt, DesignWareI2CState),
+ VMSTATE_UINT32(ic_fs_scl_hcnt, DesignWareI2CState),
+ VMSTATE_UINT32(ic_fs_scl_lcnt, DesignWareI2CState),
+ VMSTATE_UINT32(ic_intr_mask, DesignWareI2CState),
+ VMSTATE_UINT32(ic_raw_intr_stat, DesignWareI2CState),
+ VMSTATE_UINT32(ic_rx_tl, DesignWareI2CState),
+ VMSTATE_UINT32(ic_tx_tl, DesignWareI2CState),
+ VMSTATE_UINT32(ic_enable, DesignWareI2CState),
+ VMSTATE_UINT32(ic_status, DesignWareI2CState),
+ VMSTATE_UINT32(ic_txflr, DesignWareI2CState),
+ VMSTATE_UINT32(ic_rxflr, DesignWareI2CState),
+ VMSTATE_UINT32(ic_sda_hold, DesignWareI2CState),
+ VMSTATE_UINT32(ic_tx_abrt_source, DesignWareI2CState),
+ VMSTATE_UINT32(ic_sda_setup, DesignWareI2CState),
+ VMSTATE_UINT32(ic_enable_status, DesignWareI2CState),
+ VMSTATE_UINT32(ic_fs_spklen, DesignWareI2CState),
+ VMSTATE_UINT32(ic_comp_param_1, DesignWareI2CState),
+ VMSTATE_UINT32(ic_comp_version, DesignWareI2CState),
+ VMSTATE_UINT32(ic_comp_type, DesignWareI2CState),
+ VMSTATE_UINT32(status, DesignWareI2CState),
+ VMSTATE_UINT8_ARRAY(rx_fifo, DesignWareI2CState,
+ DESIGNWARE_I2C_RX_FIFO_SIZE),
+ VMSTATE_UINT8(rx_cur, DesignWareI2CState),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void designware_i2c_smbus_init(Object *obj)
+{
+ DesignWareI2CState *s = DESIGNWARE_I2C(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ memory_region_init_io(&s->iomem, obj, &designware_i2c_ops, s,
+ "regs", 4 * KiB);
+ sysbus_init_mmio(sbd, &s->iomem);
+
+ s->bus = i2c_init_bus(DEVICE(s), "i2c-bus");
+}
+
+static void designware_i2c_class_init(ObjectClass *klass, const void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "Designware I2C";
+ dc->vmsd = &vmstate_designware_i2c;
+ rc->phases.enter = designware_i2c_enter_reset;
+ rc->phases.hold = designware_i2c_hold_reset;
+}
+
+static const TypeInfo designware_i2c_types[] = {
+ {
+ .name = TYPE_DESIGNWARE_I2C,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(DesignWareI2CState),
+ .class_init = designware_i2c_class_init,
+ .instance_init = designware_i2c_smbus_init,
+ },
+};
+DEFINE_TYPES(designware_i2c_types);
diff --git a/hw/i2c/Kconfig b/hw/i2c/Kconfig
index 596a7a3165ad..d3f394edeb9c 100644
--- a/hw/i2c/Kconfig
+++ b/hw/i2c/Kconfig
@@ -18,6 +18,10 @@ config ARM_SBCON_I2C
bool
select BITBANG_I2C
+config DESIGNWARE_I2C
+ bool
+ select I2C
+
config ACPI_SMBUS
bool
select SMBUS
diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build
index c459adcb596c..88aea35662dd 100644
--- a/hw/i2c/meson.build
+++ b/hw/i2c/meson.build
@@ -11,6 +11,7 @@ i2c_ss.add(when: 'CONFIG_MPC_I2C', if_true: files('mpc_i2c.c'))
i2c_ss.add(when: 'CONFIG_ALLWINNER_I2C', if_true: files('allwinner-i2c.c'))
i2c_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('microbit_i2c.c'))
i2c_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_smbus.c'))
+i2c_ss.add(when: 'CONFIG_DESIGNWARE_I2C', if_true: files('designware_i2c.c'))
i2c_ss.add(when: 'CONFIG_SMBUS_EEPROM', if_true: files('smbus_eeprom.c'))
i2c_ss.add(when: 'CONFIG_ARM_SBCON_I2C', if_true: files('arm_sbcon_i2c.c'))
i2c_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_i2c.c'))
diff --git a/hw/i2c/trace-events b/hw/i2c/trace-events
index 1ad0e95c0e60..8a78d2d3c8de 100644
--- a/hw/i2c/trace-events
+++ b/hw/i2c/trace-events
@@ -61,3 +61,7 @@ pca954x_read_data(uint8_t value) "PCA954X read data: 0x%02x"
imx_i2c_read(const char *id, const char *reg, uint64_t ofs, uint64_t value) "%s:[%s (0x%" PRIx64 ")] -> 0x%02" PRIx64
imx_i2c_write(const char *id, const char *reg, uint64_t ofs, uint64_t value) "%s:[%s (0x%" PRIx64 ")] <- 0x%02" PRIx64
+
+# designware_i2c.c
+dw_i2c_read(const char *id, uint64_t ofs, uint64_t value) "%s: offset 0x%02" PRIx64 " -> value: 0x%02" PRIx64
+dw_i2c_write(const char *id, uint64_t ofs, uint64_t value) "%s: offset: 0x%02" PRIx64 " <- value: 0x%02" PRIx64
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 02/13] hw/riscv/boot: Describe discontiguous memory in boot_info
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
2026-04-21 5:31 ` [PATCH v3 01/13] hw/i2c: Add designware i2c controller Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-22 0:04 ` Alistair Francis
2026-04-21 5:31 ` [PATCH v3 03/13] hw/riscv/boot: Account for discontiguous memory when loading firmware Joel Stanley
` (10 subsequent siblings)
12 siblings, 1 reply; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Nicholas Piggin, Michael Ellerman, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
From: Nicholas Piggin <npiggin@gmail.com>
Machines that have discontiguous memory may need to adjust where
firmware and images are loaded at boot. Provide an interface for
machines to describe a discontiguous low/high RAM scheme for this
purpose.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
include/hw/riscv/boot.h | 7 +++++++
hw/riscv/boot.c | 11 +++++++++++
2 files changed, 18 insertions(+)
diff --git a/include/hw/riscv/boot.h b/include/hw/riscv/boot.h
index f00b3ca12245..115e3222174f 100644
--- a/include/hw/riscv/boot.h
+++ b/include/hw/riscv/boot.h
@@ -28,6 +28,10 @@
#define RISCV64_BIOS_BIN "opensbi-riscv64-generic-fw_dynamic.bin"
typedef struct RISCVBootInfo {
+ /* First contiguous RAM region. If size is zero then assume entire RAM */
+ hwaddr ram_low_start;
+ hwaddr ram_low_size;
+
ssize_t kernel_size;
hwaddr image_low_addr;
hwaddr image_high_addr;
@@ -43,6 +47,9 @@ bool riscv_is_32bit(RISCVHartArrayState *harts);
char *riscv_plic_hart_config_string(int hart_count);
void riscv_boot_info_init(RISCVBootInfo *info, RISCVHartArrayState *harts);
+void riscv_boot_info_init_discontig_mem(RISCVBootInfo *info,
+ RISCVHartArrayState *harts,
+ hwaddr start, hwaddr size);
vaddr riscv_calc_kernel_start_addr(RISCVBootInfo *info,
hwaddr firmware_end_addr);
hwaddr riscv_find_and_load_firmware(MachineState *machine,
diff --git a/hw/riscv/boot.c b/hw/riscv/boot.c
index 9086793b7a7b..5c9547429a36 100644
--- a/hw/riscv/boot.c
+++ b/hw/riscv/boot.c
@@ -69,11 +69,22 @@ char *riscv_plic_hart_config_string(int hart_count)
void riscv_boot_info_init(RISCVBootInfo *info, RISCVHartArrayState *harts)
{
+ info->ram_low_start = 0;
+ info->ram_low_size = 0;
info->kernel_size = 0;
info->initrd_size = 0;
info->is_32bit = riscv_is_32bit(harts);
}
+void riscv_boot_info_init_discontig_mem(RISCVBootInfo *info,
+ RISCVHartArrayState *harts,
+ hwaddr start, hwaddr size)
+{
+ riscv_boot_info_init(info, harts);
+ info->ram_low_start = start;
+ info->ram_low_size = size;
+}
+
vaddr riscv_calc_kernel_start_addr(RISCVBootInfo *info,
hwaddr firmware_end_addr) {
if (info->is_32bit) {
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 02/13] hw/riscv/boot: Describe discontiguous memory in boot_info
2026-04-21 5:31 ` [PATCH v3 02/13] hw/riscv/boot: Describe discontiguous memory in boot_info Joel Stanley
@ 2026-04-22 0:04 ` Alistair Francis
0 siblings, 0 replies; 35+ messages in thread
From: Alistair Francis @ 2026-04-22 0:04 UTC (permalink / raw)
To: Joel Stanley
Cc: Alistair Francis, Daniel Henrique Barboza, Nicholas Piggin,
Michael Ellerman, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel
On Tue, Apr 21, 2026 at 3:34 PM Joel Stanley <joel@jms.id.au> wrote:
>
> From: Nicholas Piggin <npiggin@gmail.com>
>
> Machines that have discontiguous memory may need to adjust where
> firmware and images are loaded at boot. Provide an interface for
> machines to describe a discontiguous low/high RAM scheme for this
> purpose.
>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
Reviewed-by: Alistair Francis <alistair.francis@wdc.com>
Alistair
> ---
> include/hw/riscv/boot.h | 7 +++++++
> hw/riscv/boot.c | 11 +++++++++++
> 2 files changed, 18 insertions(+)
>
> diff --git a/include/hw/riscv/boot.h b/include/hw/riscv/boot.h
> index f00b3ca12245..115e3222174f 100644
> --- a/include/hw/riscv/boot.h
> +++ b/include/hw/riscv/boot.h
> @@ -28,6 +28,10 @@
> #define RISCV64_BIOS_BIN "opensbi-riscv64-generic-fw_dynamic.bin"
>
> typedef struct RISCVBootInfo {
> + /* First contiguous RAM region. If size is zero then assume entire RAM */
> + hwaddr ram_low_start;
> + hwaddr ram_low_size;
> +
> ssize_t kernel_size;
> hwaddr image_low_addr;
> hwaddr image_high_addr;
> @@ -43,6 +47,9 @@ bool riscv_is_32bit(RISCVHartArrayState *harts);
> char *riscv_plic_hart_config_string(int hart_count);
>
> void riscv_boot_info_init(RISCVBootInfo *info, RISCVHartArrayState *harts);
> +void riscv_boot_info_init_discontig_mem(RISCVBootInfo *info,
> + RISCVHartArrayState *harts,
> + hwaddr start, hwaddr size);
> vaddr riscv_calc_kernel_start_addr(RISCVBootInfo *info,
> hwaddr firmware_end_addr);
> hwaddr riscv_find_and_load_firmware(MachineState *machine,
> diff --git a/hw/riscv/boot.c b/hw/riscv/boot.c
> index 9086793b7a7b..5c9547429a36 100644
> --- a/hw/riscv/boot.c
> +++ b/hw/riscv/boot.c
> @@ -69,11 +69,22 @@ char *riscv_plic_hart_config_string(int hart_count)
>
> void riscv_boot_info_init(RISCVBootInfo *info, RISCVHartArrayState *harts)
> {
> + info->ram_low_start = 0;
> + info->ram_low_size = 0;
> info->kernel_size = 0;
> info->initrd_size = 0;
> info->is_32bit = riscv_is_32bit(harts);
> }
>
> +void riscv_boot_info_init_discontig_mem(RISCVBootInfo *info,
> + RISCVHartArrayState *harts,
> + hwaddr start, hwaddr size)
> +{
> + riscv_boot_info_init(info, harts);
> + info->ram_low_start = start;
> + info->ram_low_size = size;
> +}
> +
> vaddr riscv_calc_kernel_start_addr(RISCVBootInfo *info,
> hwaddr firmware_end_addr) {
> if (info->is_32bit) {
> --
> 2.47.3
>
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 03/13] hw/riscv/boot: Account for discontiguous memory when loading firmware
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
2026-04-21 5:31 ` [PATCH v3 01/13] hw/i2c: Add designware i2c controller Joel Stanley
2026-04-21 5:31 ` [PATCH v3 02/13] hw/riscv/boot: Describe discontiguous memory in boot_info Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-21 5:47 ` Philippe Mathieu-Daudé
2026-04-21 5:31 ` [PATCH v3 04/13] hw/riscv/boot: Provide a simple halting payload Joel Stanley
` (9 subsequent siblings)
12 siblings, 1 reply; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Nicholas Piggin, Michael Ellerman, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
From: Nicholas Piggin <npiggin@gmail.com>
This loads firmware into the first (low) memory range,
accounting for machines having discontiguous memory regions.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
v3: Call riscv_boot_info_init before riscv_find_and_load_firmware in
sifive_u
---
include/hw/riscv/boot.h | 5 ++++-
hw/riscv/boot.c | 18 ++++++++++++------
hw/riscv/microchip_pfsoc.c | 6 ++++--
hw/riscv/opentitan.c | 6 ++++--
hw/riscv/shakti_c.c | 6 +++++-
hw/riscv/sifive_u.c | 6 ++++--
hw/riscv/spike.c | 6 ++++--
hw/riscv/virt.c | 7 ++++---
hw/riscv/xiangshan_kmh.c | 6 +++++-
9 files changed, 46 insertions(+), 20 deletions(-)
diff --git a/include/hw/riscv/boot.h b/include/hw/riscv/boot.h
index 115e3222174f..fb90bf12399e 100644
--- a/include/hw/riscv/boot.h
+++ b/include/hw/riscv/boot.h
@@ -53,13 +53,16 @@ void riscv_boot_info_init_discontig_mem(RISCVBootInfo *info,
vaddr riscv_calc_kernel_start_addr(RISCVBootInfo *info,
hwaddr firmware_end_addr);
hwaddr riscv_find_and_load_firmware(MachineState *machine,
+ RISCVBootInfo *info,
const char *default_machine_firmware,
hwaddr *firmware_load_addr,
symbol_fn_t sym_cb);
const char *riscv_default_firmware_name(RISCVHartArrayState *harts);
char *riscv_find_firmware(const char *firmware_filename,
const char *default_machine_firmware);
-hwaddr riscv_load_firmware(const char *firmware_filename,
+hwaddr riscv_load_firmware(MachineState *machine,
+ RISCVBootInfo *info,
+ const char *firmware_filename,
hwaddr *firmware_load_addr,
symbol_fn_t sym_cb);
void riscv_load_kernel(MachineState *machine,
diff --git a/hw/riscv/boot.c b/hw/riscv/boot.c
index 5c9547429a36..3ea95c175c14 100644
--- a/hw/riscv/boot.c
+++ b/hw/riscv/boot.c
@@ -145,6 +145,7 @@ char *riscv_find_firmware(const char *firmware_filename,
}
hwaddr riscv_find_and_load_firmware(MachineState *machine,
+ RISCVBootInfo *info,
const char *default_machine_firmware,
hwaddr *firmware_load_addr,
symbol_fn_t sym_cb)
@@ -157,7 +158,8 @@ hwaddr riscv_find_and_load_firmware(MachineState *machine,
if (firmware_filename) {
/* If not "none" load the firmware */
- firmware_end_addr = riscv_load_firmware(firmware_filename,
+ firmware_end_addr = riscv_load_firmware(machine, info,
+ firmware_filename,
firmware_load_addr, sym_cb);
g_free(firmware_filename);
}
@@ -165,10 +167,13 @@ hwaddr riscv_find_and_load_firmware(MachineState *machine,
return firmware_end_addr;
}
-hwaddr riscv_load_firmware(const char *firmware_filename,
+hwaddr riscv_load_firmware(MachineState *machine,
+ RISCVBootInfo *info,
+ const char *firmware_filename,
hwaddr *firmware_load_addr,
symbol_fn_t sym_cb)
{
+ uint64_t mem_size = info->ram_low_size ?: machine->ram_size;
uint64_t firmware_entry, firmware_end;
ssize_t firmware_size;
@@ -197,7 +202,7 @@ hwaddr riscv_load_firmware(const char *firmware_filename,
firmware_size = load_image_targphys_as(firmware_filename,
*firmware_load_addr,
- current_machine->ram_size, NULL,
+ mem_size, NULL,
NULL);
if (firmware_size > 0) {
@@ -212,7 +217,7 @@ hwaddr riscv_load_firmware(const char *firmware_filename,
static void riscv_load_initrd(MachineState *machine, RISCVBootInfo *info)
{
const char *filename = machine->initrd_filename;
- uint64_t mem_size = machine->ram_size;
+ uint64_t mem_size = info->ram_low_size ?: machine->ram_size;
void *fdt = machine->fdt;
hwaddr start, end;
ssize_t size;
@@ -258,6 +263,7 @@ void riscv_load_kernel(MachineState *machine,
bool load_initrd,
symbol_fn_t sym_cb)
{
+ uint64_t mem_size = info->ram_low_size ?: machine->ram_size;
const char *kernel_filename = machine->kernel_filename;
ssize_t kernel_size;
void *fdt = machine->fdt;
@@ -289,7 +295,7 @@ void riscv_load_kernel(MachineState *machine,
}
kernel_size = load_image_targphys_as(kernel_filename, kernel_start_addr,
- current_machine->ram_size, NULL, NULL);
+ mem_size, NULL, NULL);
if (kernel_size > 0) {
info->kernel_size = kernel_size;
info->image_low_addr = kernel_start_addr;
@@ -385,7 +391,7 @@ uint64_t riscv_compute_fdt_addr(hwaddr dram_base, hwaddr dram_size,
dtb_start = QEMU_ALIGN_DOWN(temp - fdtsize, 2 * MiB);
if (dtb_start_limit && (dtb_start < dtb_start_limit)) {
- error_report("No enough memory to place DTB after kernel/initrd");
+ error_report("Not enough memory to place DTB after kernel/initrd");
exit(1);
}
diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c
index 743f31f00578..1d1ddb05a882 100644
--- a/hw/riscv/microchip_pfsoc.c
+++ b/hw/riscv/microchip_pfsoc.c
@@ -618,18 +618,20 @@ static void microchip_icicle_kit_machine_init(MachineState *machine)
firmware_load_addr = RESET_VECTOR;
}
+ riscv_boot_info_init(&boot_info, &s->soc.u_cpus);
+
/* Load the firmware if necessary */
firmware_end_addr = firmware_load_addr;
if (firmware_name) {
char *filename = riscv_find_firmware(firmware_name, NULL);
if (filename) {
- firmware_end_addr = riscv_load_firmware(filename,
+ firmware_end_addr = riscv_load_firmware(machine, &boot_info,
+ filename,
&firmware_load_addr, NULL);
g_free(filename);
}
}
- riscv_boot_info_init(&boot_info, &s->soc.u_cpus);
if (machine->kernel_filename) {
kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info,
firmware_end_addr);
diff --git a/hw/riscv/opentitan.c b/hw/riscv/opentitan.c
index 309125e854bc..8cd660dd4154 100644
--- a/hw/riscv/opentitan.c
+++ b/hw/riscv/opentitan.c
@@ -99,12 +99,14 @@ static void opentitan_machine_init(MachineState *machine)
memory_region_add_subregion(sys_mem,
memmap[IBEX_DEV_RAM].base, machine->ram);
+ riscv_boot_info_init(&boot_info, &s->soc.cpus);
+
if (machine->firmware) {
hwaddr firmware_load_addr = memmap[IBEX_DEV_RAM].base;
- riscv_load_firmware(machine->firmware, &firmware_load_addr, NULL);
+ riscv_load_firmware(machine, &boot_info, machine->firmware,
+ &firmware_load_addr, NULL);
}
- riscv_boot_info_init(&boot_info, &s->soc.cpus);
if (machine->kernel_filename) {
riscv_load_kernel(machine, &boot_info,
memmap[IBEX_DEV_RAM].base,
diff --git a/hw/riscv/shakti_c.c b/hw/riscv/shakti_c.c
index 49a39b30212d..eb720d9cdf5d 100644
--- a/hw/riscv/shakti_c.c
+++ b/hw/riscv/shakti_c.c
@@ -45,6 +45,7 @@ static void shakti_c_machine_state_init(MachineState *mstate)
{
ShaktiCMachineState *sms = RISCV_SHAKTI_MACHINE(mstate);
MemoryRegion *system_memory = get_system_memory();
+ RISCVBootInfo boot_info;
hwaddr firmware_load_addr = shakti_c_memmap[SHAKTI_C_RAM].base;
/* Initialize SoC */
@@ -57,8 +58,11 @@ static void shakti_c_machine_state_init(MachineState *mstate)
shakti_c_memmap[SHAKTI_C_RAM].base,
mstate->ram);
+ riscv_boot_info_init(&boot_info, &sms->soc.cpus);
+
if (mstate->firmware) {
- riscv_load_firmware(mstate->firmware, &firmware_load_addr, NULL);
+ riscv_load_firmware(mstate, &boot_info, mstate->firmware,
+ &firmware_load_addr, NULL);
}
/* ROM reset vector */
diff --git a/hw/riscv/sifive_u.c b/hw/riscv/sifive_u.c
index 7ec67b256514..dda8687bfdd8 100644
--- a/hw/riscv/sifive_u.c
+++ b/hw/riscv/sifive_u.c
@@ -589,11 +589,13 @@ static void sifive_u_machine_init(MachineState *machine)
break;
}
+ riscv_boot_info_init(&boot_info, &s->soc.u_cpus);
+
firmware_name = riscv_default_firmware_name(&s->soc.u_cpus);
- firmware_end_addr = riscv_find_and_load_firmware(machine, firmware_name,
+ firmware_end_addr = riscv_find_and_load_firmware(machine, &boot_info,
+ firmware_name,
&start_addr, NULL);
- riscv_boot_info_init(&boot_info, &s->soc.u_cpus);
if (machine->kernel_filename) {
kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info,
firmware_end_addr);
diff --git a/hw/riscv/spike.c b/hw/riscv/spike.c
index 35c696f891d8..6ee915a8ba4e 100644
--- a/hw/riscv/spike.c
+++ b/hw/riscv/spike.c
@@ -281,9 +281,12 @@ static void spike_board_init(MachineState *machine)
}
}
+ riscv_boot_info_init(&boot_info, &s->soc[0]);
+
/* Load firmware */
if (firmware_name) {
- firmware_end_addr = riscv_load_firmware(firmware_name,
+ firmware_end_addr = riscv_load_firmware(machine, &boot_info,
+ firmware_name,
&firmware_load_addr,
htif_symbol_callback);
g_free(firmware_name);
@@ -293,7 +296,6 @@ static void spike_board_init(MachineState *machine)
create_fdt(s, memmap, riscv_is_32bit(&s->soc[0]), htif_custom_base);
/* Load kernel */
- riscv_boot_info_init(&boot_info, &s->soc[0]);
if (machine->kernel_filename) {
kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info,
firmware_end_addr);
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index a1c323e66dfd..4501d5581b62 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -1457,7 +1457,10 @@ static void virt_machine_done(Notifier *notifier, void *data)
}
}
- firmware_end_addr = riscv_find_and_load_firmware(machine, firmware_name,
+ riscv_boot_info_init(&boot_info, &s->soc[0]);
+
+ firmware_end_addr = riscv_find_and_load_firmware(machine, &boot_info,
+ firmware_name,
&start_addr, NULL);
pflash_blk0 = pflash_cfi01_get_blk(s->flash[0]);
@@ -1480,8 +1483,6 @@ static void virt_machine_done(Notifier *notifier, void *data)
}
}
- riscv_boot_info_init(&boot_info, &s->soc[0]);
-
if (machine->kernel_filename && !kernel_entry) {
kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info,
firmware_end_addr);
diff --git a/hw/riscv/xiangshan_kmh.c b/hw/riscv/xiangshan_kmh.c
index 436e51c1c593..247a0b5d1f21 100644
--- a/hw/riscv/xiangshan_kmh.c
+++ b/hw/riscv/xiangshan_kmh.c
@@ -166,6 +166,7 @@ static void xiangshan_kmh_machine_init(MachineState *machine)
const MemMapEntry *memmap = xiangshan_kmh_memmap;
MemoryRegion *system_memory = get_system_memory();
hwaddr start_addr = memmap[XIANGSHAN_KMH_DRAM].base;
+ RISCVBootInfo boot_info;
/* Initialize SoC */
object_initialize_child(OBJECT(machine), "soc", &s->soc,
@@ -177,13 +178,16 @@ static void xiangshan_kmh_machine_init(MachineState *machine)
memmap[XIANGSHAN_KMH_DRAM].base,
machine->ram);
+ riscv_boot_info_init(&boot_info, &s->soc.cpus);
+
/* ROM reset vector */
riscv_setup_rom_reset_vec(machine, &s->soc.cpus,
start_addr,
memmap[XIANGSHAN_KMH_ROM].base,
memmap[XIANGSHAN_KMH_ROM].size, 0, 0);
if (machine->firmware) {
- riscv_load_firmware(machine->firmware, &start_addr, NULL);
+ riscv_load_firmware(machine, &boot_info, machine->firmware,
+ &start_addr, NULL);
}
/* Note: dtb has been integrated into firmware(OpenSBI) when compiling */
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 03/13] hw/riscv/boot: Account for discontiguous memory when loading firmware
2026-04-21 5:31 ` [PATCH v3 03/13] hw/riscv/boot: Account for discontiguous memory when loading firmware Joel Stanley
@ 2026-04-21 5:47 ` Philippe Mathieu-Daudé
2026-04-23 1:58 ` Joel Stanley
0 siblings, 1 reply; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-21 5:47 UTC (permalink / raw)
To: Joel Stanley, Alistair Francis, Daniel Henrique Barboza
Cc: Nicholas Piggin, Michael Ellerman, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
On 21/4/26 07:31, Joel Stanley wrote:
> From: Nicholas Piggin <npiggin@gmail.com>
>
> This loads firmware into the first (low) memory range,
> accounting for machines having discontiguous memory regions.
>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
> ---
> v3: Call riscv_boot_info_init before riscv_find_and_load_firmware in
> sifive_u
> ---
> include/hw/riscv/boot.h | 5 ++++-
> hw/riscv/boot.c | 18 ++++++++++++------
> hw/riscv/microchip_pfsoc.c | 6 ++++--
> hw/riscv/opentitan.c | 6 ++++--
> hw/riscv/shakti_c.c | 6 +++++-
> hw/riscv/sifive_u.c | 6 ++++--
> hw/riscv/spike.c | 6 ++++--
> hw/riscv/virt.c | 7 ++++---
> hw/riscv/xiangshan_kmh.c | 6 +++++-
> 9 files changed, 46 insertions(+), 20 deletions(-)
>
> diff --git a/include/hw/riscv/boot.h b/include/hw/riscv/boot.h
> index 115e3222174f..fb90bf12399e 100644
> --- a/include/hw/riscv/boot.h
> +++ b/include/hw/riscv/boot.h
> @@ -53,13 +53,16 @@ void riscv_boot_info_init_discontig_mem(RISCVBootInfo *info,
> vaddr riscv_calc_kernel_start_addr(RISCVBootInfo *info,
> hwaddr firmware_end_addr);
> hwaddr riscv_find_and_load_firmware(MachineState *machine,
> + RISCVBootInfo *info,
> const char *default_machine_firmware,
> hwaddr *firmware_load_addr,
> symbol_fn_t sym_cb);
> const char *riscv_default_firmware_name(RISCVHartArrayState *harts);
> char *riscv_find_firmware(const char *firmware_filename,
> const char *default_machine_firmware);
> -hwaddr riscv_load_firmware(const char *firmware_filename,
> +hwaddr riscv_load_firmware(MachineState *machine,
> + RISCVBootInfo *info,
> + const char *firmware_filename,
> hwaddr *firmware_load_addr,
> symbol_fn_t sym_cb);
Except in _init(), this structure should be const, so preferably
'const RISCVBootInfo *info'. Can be done on a cleanup patch of
course.
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 03/13] hw/riscv/boot: Account for discontiguous memory when loading firmware
2026-04-21 5:47 ` Philippe Mathieu-Daudé
@ 2026-04-23 1:58 ` Joel Stanley
0 siblings, 0 replies; 35+ messages in thread
From: Joel Stanley @ 2026-04-23 1:58 UTC (permalink / raw)
To: Philippe Mathieu-Daudé
Cc: Alistair Francis, Daniel Henrique Barboza, Nicholas Piggin,
Michael Ellerman, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel
On Tue, 21 Apr 2026 at 15:48, Philippe Mathieu-Daudé <philmd@linaro.org> wrote:
>
> On 21/4/26 07:31, Joel Stanley wrote:
> > From: Nicholas Piggin <npiggin@gmail.com>
> >
> > This loads firmware into the first (low) memory range,
> > accounting for machines having discontiguous memory regions.
> >
> > Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> > Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
> > Signed-off-by: Joel Stanley <joel@jms.id.au>
> > ---
> > v3: Call riscv_boot_info_init before riscv_find_and_load_firmware in
> > sifive_u
> > ---
> > include/hw/riscv/boot.h | 5 ++++-
> > hw/riscv/boot.c | 18 ++++++++++++------
> > hw/riscv/microchip_pfsoc.c | 6 ++++--
> > hw/riscv/opentitan.c | 6 ++++--
> > hw/riscv/shakti_c.c | 6 +++++-
> > hw/riscv/sifive_u.c | 6 ++++--
> > hw/riscv/spike.c | 6 ++++--
> > hw/riscv/virt.c | 7 ++++---
> > hw/riscv/xiangshan_kmh.c | 6 +++++-
> > 9 files changed, 46 insertions(+), 20 deletions(-)
> >
> > diff --git a/include/hw/riscv/boot.h b/include/hw/riscv/boot.h
> > index 115e3222174f..fb90bf12399e 100644
> > --- a/include/hw/riscv/boot.h
> > +++ b/include/hw/riscv/boot.h
> > @@ -53,13 +53,16 @@ void riscv_boot_info_init_discontig_mem(RISCVBootInfo *info,
> > vaddr riscv_calc_kernel_start_addr(RISCVBootInfo *info,
> > hwaddr firmware_end_addr);
> > hwaddr riscv_find_and_load_firmware(MachineState *machine,
> > + RISCVBootInfo *info,
> > const char *default_machine_firmware,
> > hwaddr *firmware_load_addr,
> > symbol_fn_t sym_cb);
> > const char *riscv_default_firmware_name(RISCVHartArrayState *harts);
> > char *riscv_find_firmware(const char *firmware_filename,
> > const char *default_machine_firmware);
> > -hwaddr riscv_load_firmware(const char *firmware_filename,
> > +hwaddr riscv_load_firmware(MachineState *machine,
> > + RISCVBootInfo *info,
> > + const char *firmware_filename,
> > hwaddr *firmware_load_addr,
> > symbol_fn_t sym_cb);
>
> Except in _init(), this structure should be const, so preferably
> 'const RISCVBootInfo *info'. Can be done on a cleanup patch of
> course.
This was easy enough to fold in.
A future cleanup could make all of the riscv_load_ functions take a const *.
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 04/13] hw/riscv/boot: Provide a simple halting payload
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
` (2 preceding siblings ...)
2026-04-21 5:31 ` [PATCH v3 03/13] hw/riscv/boot: Account for discontiguous memory when loading firmware Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-21 5:48 ` Philippe Mathieu-Daudé
2026-04-21 5:31 ` [PATCH v3 05/13] hw/riscv/virt: Move AIA initialisation to helper file Joel Stanley
` (8 subsequent siblings)
12 siblings, 1 reply; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Nicholas Piggin, Michael Ellerman, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
From: Nicholas Piggin <npiggin@gmail.com>
OpenSBI hangs before any console output if the domain init code sees the
next stage is not in an executable region.
If no kernel payload is provided to QEMU, the next stage address is
NULL, and the riscv virt machine memory map ends up covering the 0
address with the catch all S-mode RWX region and so OpenSBI prints
console messages and does not hang until the next stage boot.
The soon to be added Tenstorrent Atlantis board address map has RAM
starting at 0 and it loads OpenSBI there, so it is M-mode and not
accessible by S-mode, tripping the early check and hang.
Add a helper to set up a simple payload that gets OpenSBI messages
to console.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
v3: MachineState argument was unused
---
include/hw/riscv/boot.h | 1 +
hw/riscv/boot.c | 20 ++++++++++++++++++++
2 files changed, 21 insertions(+)
diff --git a/include/hw/riscv/boot.h b/include/hw/riscv/boot.h
index fb90bf12399e..a1eb377474b9 100644
--- a/include/hw/riscv/boot.h
+++ b/include/hw/riscv/boot.h
@@ -78,6 +78,7 @@ void riscv_setup_rom_reset_vec(MachineState *machine, RISCVHartArrayState *harts
hwaddr rom_base, hwaddr rom_size,
uint64_t kernel_entry,
uint64_t fdt_load_addr);
+void riscv_setup_halting_payload(RISCVBootInfo *info, hwaddr addr);
void riscv_rom_copy_firmware_info(MachineState *machine,
RISCVHartArrayState *harts,
hwaddr rom_base,
diff --git a/hw/riscv/boot.c b/hw/riscv/boot.c
index 3ea95c175c14..3a97fd1441f7 100644
--- a/hw/riscv/boot.c
+++ b/hw/riscv/boot.c
@@ -518,6 +518,26 @@ void riscv_setup_rom_reset_vec(MachineState *machine, RISCVHartArrayState *harts
kernel_entry);
}
+/* Simple payload so OpenSBI does not hang early with no output */
+void riscv_setup_halting_payload(RISCVBootInfo *info, hwaddr addr)
+{
+ int i;
+ uint32_t payload_vec[] = {
+ 0x10500073, /* 1: wfi */
+ 0xffdff06f, /* j 1b */
+ };
+ /* copy in the payload vector in little_endian byte order */
+ for (i = 0; i < ARRAY_SIZE(payload_vec); i++) {
+ payload_vec[i] = cpu_to_le32(payload_vec[i]);
+ }
+ rom_add_blob_fixed_as("mrom.payload", payload_vec, sizeof(payload_vec),
+ addr, &address_space_memory);
+
+ info->kernel_size = sizeof(payload_vec);
+ info->image_low_addr = addr;
+ info->image_high_addr = info->image_low_addr + info->kernel_size;
+}
+
void riscv_setup_direct_kernel(hwaddr kernel_addr, hwaddr fdt_addr)
{
CPUState *cs;
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 04/13] hw/riscv/boot: Provide a simple halting payload
2026-04-21 5:31 ` [PATCH v3 04/13] hw/riscv/boot: Provide a simple halting payload Joel Stanley
@ 2026-04-21 5:48 ` Philippe Mathieu-Daudé
2026-04-23 2:04 ` Joel Stanley
0 siblings, 1 reply; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-21 5:48 UTC (permalink / raw)
To: Joel Stanley, Alistair Francis, Daniel Henrique Barboza
Cc: Nicholas Piggin, Michael Ellerman, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
On 21/4/26 07:31, Joel Stanley wrote:
> From: Nicholas Piggin <npiggin@gmail.com>
>
> OpenSBI hangs before any console output if the domain init code sees the
> next stage is not in an executable region.
>
> If no kernel payload is provided to QEMU, the next stage address is
> NULL, and the riscv virt machine memory map ends up covering the 0
> address with the catch all S-mode RWX region and so OpenSBI prints
> console messages and does not hang until the next stage boot.
>
> The soon to be added Tenstorrent Atlantis board address map has RAM
> starting at 0 and it loads OpenSBI there, so it is M-mode and not
> accessible by S-mode, tripping the early check and hang.
>
> Add a helper to set up a simple payload that gets OpenSBI messages
> to console.
>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
> ---
> v3: MachineState argument was unused
> ---
> include/hw/riscv/boot.h | 1 +
> hw/riscv/boot.c | 20 ++++++++++++++++++++
> 2 files changed, 21 insertions(+)
>
> diff --git a/include/hw/riscv/boot.h b/include/hw/riscv/boot.h
> index fb90bf12399e..a1eb377474b9 100644
> --- a/include/hw/riscv/boot.h
> +++ b/include/hw/riscv/boot.h
> @@ -78,6 +78,7 @@ void riscv_setup_rom_reset_vec(MachineState *machine, RISCVHartArrayState *harts
> hwaddr rom_base, hwaddr rom_size,
> uint64_t kernel_entry,
> uint64_t fdt_load_addr);
> +void riscv_setup_halting_payload(RISCVBootInfo *info, hwaddr addr);
> void riscv_rom_copy_firmware_info(MachineState *machine,
> RISCVHartArrayState *harts,
> hwaddr rom_base,
> diff --git a/hw/riscv/boot.c b/hw/riscv/boot.c
> index 3ea95c175c14..3a97fd1441f7 100644
> --- a/hw/riscv/boot.c
> +++ b/hw/riscv/boot.c
> @@ -518,6 +518,26 @@ void riscv_setup_rom_reset_vec(MachineState *machine, RISCVHartArrayState *harts
> kernel_entry);
> }
>
> +/* Simple payload so OpenSBI does not hang early with no output */
> +void riscv_setup_halting_payload(RISCVBootInfo *info, hwaddr addr)
> +{
> + int i;
> + uint32_t payload_vec[] = {
static const?
> + 0x10500073, /* 1: wfi */
> + 0xffdff06f, /* j 1b */
> + };
> + /* copy in the payload vector in little_endian byte order */
> + for (i = 0; i < ARRAY_SIZE(payload_vec); i++) {
> + payload_vec[i] = cpu_to_le32(payload_vec[i]);
> + }
> + rom_add_blob_fixed_as("mrom.payload", payload_vec, sizeof(payload_vec),
> + addr, &address_space_memory);
> +
> + info->kernel_size = sizeof(payload_vec);
> + info->image_low_addr = addr;
> + info->image_high_addr = info->image_low_addr + info->kernel_size;
> +}
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v3 04/13] hw/riscv/boot: Provide a simple halting payload
2026-04-21 5:48 ` Philippe Mathieu-Daudé
@ 2026-04-23 2:04 ` Joel Stanley
2026-04-23 8:29 ` Philippe Mathieu-Daudé
0 siblings, 1 reply; 35+ messages in thread
From: Joel Stanley @ 2026-04-23 2:04 UTC (permalink / raw)
To: Philippe Mathieu-Daudé
Cc: Alistair Francis, Daniel Henrique Barboza, Nicholas Piggin,
Michael Ellerman, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel
On Tue, 21 Apr 2026 at 15:48, Philippe Mathieu-Daudé <philmd@linaro.org> wrote:
>
> On 21/4/26 07:31, Joel Stanley wrote:
> > From: Nicholas Piggin <npiggin@gmail.com>
> >
> > OpenSBI hangs before any console output if the domain init code sees the
> > next stage is not in an executable region.
> >
> > If no kernel payload is provided to QEMU, the next stage address is
> > NULL, and the riscv virt machine memory map ends up covering the 0
> > address with the catch all S-mode RWX region and so OpenSBI prints
> > console messages and does not hang until the next stage boot.
> >
> > The soon to be added Tenstorrent Atlantis board address map has RAM
> > starting at 0 and it loads OpenSBI there, so it is M-mode and not
> > accessible by S-mode, tripping the early check and hang.
> >
> > Add a helper to set up a simple payload that gets OpenSBI messages
> > to console.
> >
> > Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> > Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
> > Signed-off-by: Joel Stanley <joel@jms.id.au>
> > ---
> > v3: MachineState argument was unused
> > ---
> > include/hw/riscv/boot.h | 1 +
> > hw/riscv/boot.c | 20 ++++++++++++++++++++
> > 2 files changed, 21 insertions(+)
> >
> > diff --git a/include/hw/riscv/boot.h b/include/hw/riscv/boot.h
> > index fb90bf12399e..a1eb377474b9 100644
> > --- a/include/hw/riscv/boot.h
> > +++ b/include/hw/riscv/boot.h
> > @@ -78,6 +78,7 @@ void riscv_setup_rom_reset_vec(MachineState *machine, RISCVHartArrayState *harts
> > hwaddr rom_base, hwaddr rom_size,
> > uint64_t kernel_entry,
> > uint64_t fdt_load_addr);
> > +void riscv_setup_halting_payload(RISCVBootInfo *info, hwaddr addr);
> > void riscv_rom_copy_firmware_info(MachineState *machine,
> > RISCVHartArrayState *harts,
> > hwaddr rom_base,
> > diff --git a/hw/riscv/boot.c b/hw/riscv/boot.c
> > index 3ea95c175c14..3a97fd1441f7 100644
> > --- a/hw/riscv/boot.c
> > +++ b/hw/riscv/boot.c
> > @@ -518,6 +518,26 @@ void riscv_setup_rom_reset_vec(MachineState *machine, RISCVHartArrayState *harts
> > kernel_entry);
> > }
> >
> > +/* Simple payload so OpenSBI does not hang early with no output */
> > +void riscv_setup_halting_payload(RISCVBootInfo *info, hwaddr addr)
> > +{
> > + int i;
> > + uint32_t payload_vec[] = {
>
> static const?
This gets written to below, but I think we can do this:
static const uint32_t payload_vec[] = {
const_le32(0x10500073),
const_le32(0xffdff06f)
};
>
> > + 0x10500073, /* 1: wfi */
> > + 0xffdff06f, /* j 1b */
> > + };
> > + /* copy in the payload vector in little_endian byte order */
> > + for (i = 0; i < ARRAY_SIZE(payload_vec); i++) {
> > + payload_vec[i] = cpu_to_le32(payload_vec[i]);
> > + }
> > + rom_add_blob_fixed_as("mrom.payload", payload_vec, sizeof(payload_vec),
> > + addr, &address_space_memory);
> > +
> > + info->kernel_size = sizeof(payload_vec);
> > + info->image_low_addr = addr;
> > + info->image_high_addr = info->image_low_addr + info->kernel_size;
> > +}
>
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v3 04/13] hw/riscv/boot: Provide a simple halting payload
2026-04-23 2:04 ` Joel Stanley
@ 2026-04-23 8:29 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-23 8:29 UTC (permalink / raw)
To: Joel Stanley
Cc: Alistair Francis, Daniel Henrique Barboza, Nicholas Piggin,
Michael Ellerman, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel
On 23/4/26 04:04, Joel Stanley wrote:
> On Tue, 21 Apr 2026 at 15:48, Philippe Mathieu-Daudé <philmd@linaro.org> wrote:
>>
>> On 21/4/26 07:31, Joel Stanley wrote:
>>> From: Nicholas Piggin <npiggin@gmail.com>
>>>
>>> OpenSBI hangs before any console output if the domain init code sees the
>>> next stage is not in an executable region.
>>>
>>> If no kernel payload is provided to QEMU, the next stage address is
>>> NULL, and the riscv virt machine memory map ends up covering the 0
>>> address with the catch all S-mode RWX region and so OpenSBI prints
>>> console messages and does not hang until the next stage boot.
>>>
>>> The soon to be added Tenstorrent Atlantis board address map has RAM
>>> starting at 0 and it loads OpenSBI there, so it is M-mode and not
>>> accessible by S-mode, tripping the early check and hang.
>>>
>>> Add a helper to set up a simple payload that gets OpenSBI messages
>>> to console.
>>>
>>> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
>>> Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
>>> Signed-off-by: Joel Stanley <joel@jms.id.au>
>>> ---
>>> v3: MachineState argument was unused
>>> ---
>>> include/hw/riscv/boot.h | 1 +
>>> hw/riscv/boot.c | 20 ++++++++++++++++++++
>>> 2 files changed, 21 insertions(+)
>>>
>>> diff --git a/include/hw/riscv/boot.h b/include/hw/riscv/boot.h
>>> index fb90bf12399e..a1eb377474b9 100644
>>> --- a/include/hw/riscv/boot.h
>>> +++ b/include/hw/riscv/boot.h
>>> @@ -78,6 +78,7 @@ void riscv_setup_rom_reset_vec(MachineState *machine, RISCVHartArrayState *harts
>>> hwaddr rom_base, hwaddr rom_size,
>>> uint64_t kernel_entry,
>>> uint64_t fdt_load_addr);
>>> +void riscv_setup_halting_payload(RISCVBootInfo *info, hwaddr addr);
>>> void riscv_rom_copy_firmware_info(MachineState *machine,
>>> RISCVHartArrayState *harts,
>>> hwaddr rom_base,
>>> diff --git a/hw/riscv/boot.c b/hw/riscv/boot.c
>>> index 3ea95c175c14..3a97fd1441f7 100644
>>> --- a/hw/riscv/boot.c
>>> +++ b/hw/riscv/boot.c
>>> @@ -518,6 +518,26 @@ void riscv_setup_rom_reset_vec(MachineState *machine, RISCVHartArrayState *harts
>>> kernel_entry);
>>> }
>>>
>>> +/* Simple payload so OpenSBI does not hang early with no output */
>>> +void riscv_setup_halting_payload(RISCVBootInfo *info, hwaddr addr)
>>> +{
>>> + int i;
>>> + uint32_t payload_vec[] = {
>>
>> static const?
>
> This gets written to below, but I think we can do this:
>
> static const uint32_t payload_vec[] = {
> const_le32(0x10500073),
> const_le32(0xffdff06f)
> };
Yes you read my mind ;)
(i.e.
https://lore.kernel.org/qemu-devel/20221222215549.86872-3-philmd@linaro.org/)
>
>>
>>> + 0x10500073, /* 1: wfi */
>>> + 0xffdff06f, /* j 1b */
>>> + };
>>> + /* copy in the payload vector in little_endian byte order */
>>> + for (i = 0; i < ARRAY_SIZE(payload_vec); i++) {
>>> + payload_vec[i] = cpu_to_le32(payload_vec[i]);
>>> + }
>>> + rom_add_blob_fixed_as("mrom.payload", payload_vec, sizeof(payload_vec),
>>> + addr, &address_space_memory);
>>> +
>>> + info->kernel_size = sizeof(payload_vec);
>>> + info->image_low_addr = addr;
>>> + info->image_high_addr = info->image_low_addr + info->kernel_size;
>>> +}
>>
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 05/13] hw/riscv/virt: Move AIA initialisation to helper file
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
` (3 preceding siblings ...)
2026-04-21 5:31 ` [PATCH v3 04/13] hw/riscv/boot: Provide a simple halting payload Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-21 6:11 ` Philippe Mathieu-Daudé
2026-04-21 5:31 ` [PATCH v3 06/13] hw/riscv/aia: Provide number of irq sources Joel Stanley
` (7 subsequent siblings)
12 siblings, 1 reply; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Michael Ellerman, Nicholas Piggin, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel, Nutty Liu
The AIA init will be used by any server class riscv machine. Separate it
out in order to share code with such systems.
The virt machine keeps machine specific #defines such as
VIRT_IRQCHIP_NUM_MSIS, VIRT_IRQCHIP_NUM_PRIO_BITS.
Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
Reviewed-by: Nutty Liu <nutty.liu@hotmail.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
v3:
- Add r-b
- Move fewer things out of virt machine into aia common code, as they
are virt machine specific and not part of aia. Done as part of this
movement patch to avoid moving them out only put them back in the
next patch.
---
hw/riscv/aia.h | 26 +++++++++++
include/hw/riscv/virt.h | 1 -
hw/riscv/aia.c | 89 ++++++++++++++++++++++++++++++++++++++
hw/riscv/virt-acpi-build.c | 2 +
hw/riscv/virt.c | 87 +++++--------------------------------
hw/riscv/meson.build | 2 +-
6 files changed, 129 insertions(+), 78 deletions(-)
create mode 100644 hw/riscv/aia.h
create mode 100644 hw/riscv/aia.c
diff --git a/hw/riscv/aia.h b/hw/riscv/aia.h
new file mode 100644
index 000000000000..dbb833340276
--- /dev/null
+++ b/hw/riscv/aia.h
@@ -0,0 +1,26 @@
+/*
+ * QEMU RISC-V Advanced Interrupt Architecture (AIA)
+ *
+ * Copyright (C) 2019 Western Digital Corporation or its affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_RISCV_AIA_H
+#define HW_RISCV_AIA_H
+
+#include "exec/hwaddr.h"
+
+#define VIRT_IRQCHIP_NUM_SOURCES 96
+
+uint32_t imsic_num_bits(uint32_t count);
+
+DeviceState *riscv_create_aia(bool msimode, int aia_guests,
+ const MemMapEntry *aplic_m,
+ const MemMapEntry *aplic_s,
+ const MemMapEntry *imsic_m,
+ const MemMapEntry *imsic_s,
+ int socket, int base_hartid, int hart_count,
+ uint32_t num_msis, uint32_t num_prio_bits);
+
+#endif
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index 18a2a323a344..ad858deb76ad 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -135,7 +135,6 @@ enum {
bool virt_is_acpi_enabled(RISCVVirtState *s);
bool virt_is_iommu_sys_enabled(RISCVVirtState *s);
void virt_acpi_setup(RISCVVirtState *vms);
-uint32_t imsic_num_bits(uint32_t count);
/*
* The virt machine physical address space used by some of the devices
diff --git a/hw/riscv/aia.c b/hw/riscv/aia.c
new file mode 100644
index 000000000000..c724612a50a5
--- /dev/null
+++ b/hw/riscv/aia.c
@@ -0,0 +1,89 @@
+/*
+ * QEMU RISC-V Advanced Interrupt Architecture (AIA)
+ *
+ * Copyright (C) 2019 Western Digital Corporation or its affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "system/kvm.h"
+#include "hw/intc/riscv_aplic.h"
+#include "hw/intc/riscv_imsic.h"
+
+#include "aia.h"
+
+uint32_t imsic_num_bits(uint32_t count)
+{
+ uint32_t ret = 0;
+
+ while (BIT(ret) < count) {
+ ret++;
+ }
+
+ return ret;
+}
+
+DeviceState *riscv_create_aia(bool msimode, int aia_guests,
+ const MemMapEntry *aplic_m,
+ const MemMapEntry *aplic_s,
+ const MemMapEntry *imsic_m,
+ const MemMapEntry *imsic_s,
+ int socket, int base_hartid, int hart_count,
+ uint32_t num_msis, uint32_t num_prio_bits)
+{
+ int i;
+ hwaddr addr = 0;
+ uint32_t guest_bits;
+ DeviceState *aplic_s_dev = NULL;
+ DeviceState *aplic_m_dev = NULL;
+
+ if (msimode) {
+ if (!kvm_enabled()) {
+ /* Per-socket M-level IMSICs */
+ addr = imsic_m->base + socket * (1U << IMSIC_MMIO_GROUP_MIN_SHIFT);
+ for (i = 0; i < hart_count; i++) {
+ riscv_imsic_create(addr + i * IMSIC_HART_SIZE(0),
+ base_hartid + i, true, 1,
+ num_msis);
+ }
+ }
+
+ /* Per-socket S-level IMSICs */
+ guest_bits = imsic_num_bits(aia_guests + 1);
+ addr = imsic_s->base + socket * (1U << IMSIC_MMIO_GROUP_MIN_SHIFT);
+ for (i = 0; i < hart_count; i++) {
+ riscv_imsic_create(addr + i * IMSIC_HART_SIZE(guest_bits),
+ base_hartid + i, false, 1 + aia_guests,
+ num_msis);
+ }
+ }
+
+ if (!kvm_enabled()) {
+ /* Per-socket M-level APLIC */
+ aplic_m_dev = riscv_aplic_create(aplic_m->base +
+ socket * aplic_m->size,
+ aplic_m->size,
+ (msimode) ? 0 : base_hartid,
+ (msimode) ? 0 : hart_count,
+ VIRT_IRQCHIP_NUM_SOURCES,
+ num_prio_bits,
+ msimode, true, NULL);
+ }
+
+ /* Per-socket S-level APLIC */
+ aplic_s_dev = riscv_aplic_create(aplic_s->base +
+ socket * aplic_s->size,
+ aplic_s->size,
+ (msimode) ? 0 : base_hartid,
+ (msimode) ? 0 : hart_count,
+ VIRT_IRQCHIP_NUM_SOURCES,
+ num_prio_bits,
+ msimode, false, aplic_m_dev);
+
+ if (kvm_enabled() && msimode) {
+ riscv_aplic_set_kvm_msicfgaddr(RISCV_APLIC(aplic_s_dev), addr);
+ }
+
+ return kvm_enabled() ? aplic_s_dev : aplic_m_dev;
+}
diff --git a/hw/riscv/virt-acpi-build.c b/hw/riscv/virt-acpi-build.c
index fd6ca5dbc4ff..145f8d92ad69 100644
--- a/hw/riscv/virt-acpi-build.c
+++ b/hw/riscv/virt-acpi-build.c
@@ -42,6 +42,8 @@
#include "system/kvm.h"
#include "system/reset.h"
+#include "aia.h"
+
#define ACPI_BUILD_TABLE_SIZE 0x20000
#define ACPI_BUILD_INTC_ID(socket, index) ((socket << 24) | (index))
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 4501d5581b62..ce0fd6f50c4a 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -59,6 +59,8 @@
#include "hw/virtio/virtio-iommu.h"
#include "hw/uefi/var-service-api.h"
+#include "aia.h"
+
/* KVM AIA only supports APLIC MSI. APLIC Wired is always emulated by QEMU. */
static bool virt_use_kvm_aia_aplic_imsic(RISCVVirtAIAType aia_type)
{
@@ -509,17 +511,6 @@ static void create_fdt_socket_plic(RISCVVirtState *s,
}
}
-uint32_t imsic_num_bits(uint32_t count)
-{
- uint32_t ret = 0;
-
- while (BIT(ret) < count) {
- ret++;
- }
-
- return ret;
-}
-
static void create_fdt_one_imsic(RISCVVirtState *s, hwaddr base_addr,
uint32_t *intc_phandles, uint32_t msi_phandle,
bool m_mode, uint32_t imsic_guest_bits)
@@ -1293,68 +1284,6 @@ static DeviceState *virt_create_plic(const MemMapEntry *memmap, int socket,
memmap[VIRT_PLIC].size);
}
-static DeviceState *virt_create_aia(RISCVVirtAIAType aia_type, int aia_guests,
- const MemMapEntry *memmap, int socket,
- int base_hartid, int hart_count)
-{
- int i;
- hwaddr addr = 0;
- uint32_t guest_bits;
- DeviceState *aplic_s = NULL;
- DeviceState *aplic_m = NULL;
- bool msimode = aia_type == VIRT_AIA_TYPE_APLIC_IMSIC;
-
- if (msimode) {
- if (!kvm_enabled()) {
- /* Per-socket M-level IMSICs */
- addr = memmap[VIRT_IMSIC_M].base +
- socket * VIRT_IMSIC_GROUP_MAX_SIZE;
- for (i = 0; i < hart_count; i++) {
- riscv_imsic_create(addr + i * IMSIC_HART_SIZE(0),
- base_hartid + i, true, 1,
- VIRT_IRQCHIP_NUM_MSIS);
- }
- }
-
- /* Per-socket S-level IMSICs */
- guest_bits = imsic_num_bits(aia_guests + 1);
- addr = memmap[VIRT_IMSIC_S].base + socket * VIRT_IMSIC_GROUP_MAX_SIZE;
- for (i = 0; i < hart_count; i++) {
- riscv_imsic_create(addr + i * IMSIC_HART_SIZE(guest_bits),
- base_hartid + i, false, 1 + aia_guests,
- VIRT_IRQCHIP_NUM_MSIS);
- }
- }
-
- if (!kvm_enabled()) {
- /* Per-socket M-level APLIC */
- aplic_m = riscv_aplic_create(memmap[VIRT_APLIC_M].base +
- socket * memmap[VIRT_APLIC_M].size,
- memmap[VIRT_APLIC_M].size,
- (msimode) ? 0 : base_hartid,
- (msimode) ? 0 : hart_count,
- VIRT_IRQCHIP_NUM_SOURCES,
- VIRT_IRQCHIP_NUM_PRIO_BITS,
- msimode, true, NULL);
- }
-
- /* Per-socket S-level APLIC */
- aplic_s = riscv_aplic_create(memmap[VIRT_APLIC_S].base +
- socket * memmap[VIRT_APLIC_S].size,
- memmap[VIRT_APLIC_S].size,
- (msimode) ? 0 : base_hartid,
- (msimode) ? 0 : hart_count,
- VIRT_IRQCHIP_NUM_SOURCES,
- VIRT_IRQCHIP_NUM_PRIO_BITS,
- msimode, false, aplic_m);
-
- if (kvm_enabled() && msimode) {
- riscv_aplic_set_kvm_msicfgaddr(RISCV_APLIC(aplic_s), addr);
- }
-
- return kvm_enabled() ? aplic_s : aplic_m;
-}
-
static void create_platform_bus(RISCVVirtState *s, DeviceState *irqchip)
{
DeviceState *dev;
@@ -1617,9 +1546,15 @@ static void virt_machine_init(MachineState *machine)
s->irqchip[i] = virt_create_plic(s->memmap, i,
base_hartid, hart_count);
} else {
- s->irqchip[i] = virt_create_aia(s->aia_type, s->aia_guests,
- s->memmap, i, base_hartid,
- hart_count);
+ s->irqchip[i] = riscv_create_aia(s->aia_type == VIRT_AIA_TYPE_APLIC_IMSIC,
+ s->aia_guests,
+ &s->memmap[VIRT_APLIC_M],
+ &s->memmap[VIRT_APLIC_S],
+ &s->memmap[VIRT_IMSIC_M],
+ &s->memmap[VIRT_IMSIC_S],
+ i, base_hartid, hart_count,
+ VIRT_IRQCHIP_NUM_MSIS,
+ VIRT_IRQCHIP_NUM_PRIO_BITS);
}
/* Try to use different IRQCHIP instance based device type */
diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index 533472e22aef..e53c180d0d10 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -1,5 +1,5 @@
riscv_ss = ss.source_set()
-riscv_ss.add(files('boot.c'))
+riscv_ss.add(files('boot.c', 'aia.c'))
riscv_ss.add(when: 'CONFIG_RISCV_NUMA', if_true: files('numa.c'))
riscv_ss.add(files('riscv_hart.c'))
riscv_ss.add(when: 'CONFIG_OPENTITAN', if_true: files('opentitan.c'))
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 05/13] hw/riscv/virt: Move AIA initialisation to helper file
2026-04-21 5:31 ` [PATCH v3 05/13] hw/riscv/virt: Move AIA initialisation to helper file Joel Stanley
@ 2026-04-21 6:11 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-21 6:11 UTC (permalink / raw)
To: Joel Stanley, Alistair Francis, Daniel Henrique Barboza
Cc: Michael Ellerman, Nicholas Piggin, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel, Nutty Liu
On 21/4/26 07:31, Joel Stanley wrote:
> The AIA init will be used by any server class riscv machine. Separate it
> out in order to share code with such systems.
>
> The virt machine keeps machine specific #defines such as
> VIRT_IRQCHIP_NUM_MSIS, VIRT_IRQCHIP_NUM_PRIO_BITS.
>
> Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
> Reviewed-by: Nutty Liu <nutty.liu@hotmail.com>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
> ---
> v3:
> - Add r-b
> - Move fewer things out of virt machine into aia common code, as they
> are virt machine specific and not part of aia. Done as part of this
> movement patch to avoid moving them out only put them back in the
> next patch.
> ---
> hw/riscv/aia.h | 26 +++++++++++
> include/hw/riscv/virt.h | 1 -
> hw/riscv/aia.c | 89 ++++++++++++++++++++++++++++++++++++++
> hw/riscv/virt-acpi-build.c | 2 +
> hw/riscv/virt.c | 87 +++++--------------------------------
> hw/riscv/meson.build | 2 +-
> 6 files changed, 129 insertions(+), 78 deletions(-)
> create mode 100644 hw/riscv/aia.h
> create mode 100644 hw/riscv/aia.c
IMHO, since used by distinct machines, AIA should have its own entry
in MAINTAINERS.
Otherwise:
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 06/13] hw/riscv/aia: Provide number of irq sources
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
` (4 preceding siblings ...)
2026-04-21 5:31 ` [PATCH v3 05/13] hw/riscv/virt: Move AIA initialisation to helper file Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-21 5:49 ` Philippe Mathieu-Daudé
2026-04-21 5:31 ` [PATCH v3 07/13] target/riscv: tt-ascalon: Enable Zkr extension Joel Stanley
` (6 subsequent siblings)
12 siblings, 1 reply; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Michael Ellerman, Nicholas Piggin, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel, Nutty Liu
Instead of hard coding the number of IRQ sources used by the APLIC pass
it in as a parameter. This allows other machines to configure this as
required.
The maximum number of sources is 1023.
Reviewed-by: Nutty Liu <nutty.liu@hotmail.com>
Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
v3: Add r-b
---
hw/riscv/aia.h | 3 +--
include/hw/riscv/virt.h | 1 +
hw/riscv/aia.c | 8 ++++++--
hw/riscv/virt-acpi-build.c | 25 ++++++++++++++++---------
hw/riscv/virt.c | 2 ++
5 files changed, 26 insertions(+), 13 deletions(-)
diff --git a/hw/riscv/aia.h b/hw/riscv/aia.h
index dbb833340276..5ad0a902be0d 100644
--- a/hw/riscv/aia.h
+++ b/hw/riscv/aia.h
@@ -11,11 +11,10 @@
#include "exec/hwaddr.h"
-#define VIRT_IRQCHIP_NUM_SOURCES 96
-
uint32_t imsic_num_bits(uint32_t count);
DeviceState *riscv_create_aia(bool msimode, int aia_guests,
+ uint16_t num_sources,
const MemMapEntry *aplic_m,
const MemMapEntry *aplic_s,
const MemMapEntry *imsic_m,
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index ad858deb76ad..36a2def41096 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -64,6 +64,7 @@ struct RISCVVirtState {
struct GPEXHost *gpex_host;
OnOffAuto iommu_sys;
uint16_t pci_iommu_bdf;
+ uint16_t num_sources;
};
enum {
diff --git a/hw/riscv/aia.c b/hw/riscv/aia.c
index c724612a50a5..82ea9d48ea75 100644
--- a/hw/riscv/aia.c
+++ b/hw/riscv/aia.c
@@ -25,6 +25,7 @@ uint32_t imsic_num_bits(uint32_t count)
}
DeviceState *riscv_create_aia(bool msimode, int aia_guests,
+ uint16_t num_sources,
const MemMapEntry *aplic_m,
const MemMapEntry *aplic_s,
const MemMapEntry *imsic_m,
@@ -38,6 +39,9 @@ DeviceState *riscv_create_aia(bool msimode, int aia_guests,
DeviceState *aplic_s_dev = NULL;
DeviceState *aplic_m_dev = NULL;
+ /* The RISC-V Advanced Interrupt Architecture, Chapter 1.2. Limits */
+ g_assert(num_sources <= 1023);
+
if (msimode) {
if (!kvm_enabled()) {
/* Per-socket M-level IMSICs */
@@ -66,7 +70,7 @@ DeviceState *riscv_create_aia(bool msimode, int aia_guests,
aplic_m->size,
(msimode) ? 0 : base_hartid,
(msimode) ? 0 : hart_count,
- VIRT_IRQCHIP_NUM_SOURCES,
+ num_sources,
num_prio_bits,
msimode, true, NULL);
}
@@ -77,7 +81,7 @@ DeviceState *riscv_create_aia(bool msimode, int aia_guests,
aplic_s->size,
(msimode) ? 0 : base_hartid,
(msimode) ? 0 : hart_count,
- VIRT_IRQCHIP_NUM_SOURCES,
+ num_sources,
num_prio_bits,
msimode, false, aplic_m_dev);
diff --git a/hw/riscv/virt-acpi-build.c b/hw/riscv/virt-acpi-build.c
index 145f8d92ad69..9ef3ef842a28 100644
--- a/hw/riscv/virt-acpi-build.c
+++ b/hw/riscv/virt-acpi-build.c
@@ -146,6 +146,7 @@ static void acpi_dsdt_add_cpus(Aml *scope, RISCVVirtState *s)
}
static void acpi_dsdt_add_plic_aplic(Aml *scope, uint8_t socket_count,
+ uint16_t num_sources,
uint64_t mmio_base, uint64_t mmio_size,
const char *hid)
{
@@ -153,9 +154,12 @@ static void acpi_dsdt_add_plic_aplic(Aml *scope, uint8_t socket_count,
uint32_t gsi_base;
uint8_t socket;
+ /* The RISC-V Advanced Interrupt Architecture, Chapter 1.2. Limits */
+ g_assert(num_sources <= 1023);
+
for (socket = 0; socket < socket_count; socket++) {
plic_aplic_addr = mmio_base + mmio_size * socket;
- gsi_base = VIRT_IRQCHIP_NUM_SOURCES * socket;
+ gsi_base = num_sources * socket;
Aml *dev = aml_device("IC%.02X", socket);
aml_append(dev, aml_name_decl("_HID", aml_string("%s", hid)));
aml_append(dev, aml_name_decl("_UID", aml_int(socket)));
@@ -474,10 +478,13 @@ static void build_dsdt(GArray *table_data,
socket_count = riscv_socket_count(ms);
if (s->aia_type == VIRT_AIA_TYPE_NONE) {
- acpi_dsdt_add_plic_aplic(scope, socket_count, memmap[VIRT_PLIC].base,
- memmap[VIRT_PLIC].size, "RSCV0001");
+ acpi_dsdt_add_plic_aplic(scope, socket_count, s->num_sources,
+ memmap[VIRT_PLIC].base,
+ memmap[VIRT_PLIC].size,
+ "RSCV0001");
} else {
- acpi_dsdt_add_plic_aplic(scope, socket_count, memmap[VIRT_APLIC_S].base,
+ acpi_dsdt_add_plic_aplic(scope, socket_count, s->num_sources,
+ memmap[VIRT_APLIC_S].base,
memmap[VIRT_APLIC_S].size, "RSCV0002");
}
@@ -494,15 +501,15 @@ static void build_dsdt(GArray *table_data,
} else if (socket_count == 2) {
virtio_acpi_dsdt_add(scope, memmap[VIRT_VIRTIO].base,
memmap[VIRT_VIRTIO].size,
- VIRTIO_IRQ + VIRT_IRQCHIP_NUM_SOURCES, 0,
+ VIRTIO_IRQ + s->num_sources, 0,
VIRTIO_COUNT);
- acpi_dsdt_add_gpex_host(scope, PCIE_IRQ + VIRT_IRQCHIP_NUM_SOURCES);
+ acpi_dsdt_add_gpex_host(scope, PCIE_IRQ + s->num_sources);
} else {
virtio_acpi_dsdt_add(scope, memmap[VIRT_VIRTIO].base,
memmap[VIRT_VIRTIO].size,
- VIRTIO_IRQ + VIRT_IRQCHIP_NUM_SOURCES, 0,
+ VIRTIO_IRQ + s->num_sources, 0,
VIRTIO_COUNT);
- acpi_dsdt_add_gpex_host(scope, PCIE_IRQ + VIRT_IRQCHIP_NUM_SOURCES * 2);
+ acpi_dsdt_add_gpex_host(scope, PCIE_IRQ + s->num_sources * 2);
}
aml_append(dsdt, scope);
@@ -581,7 +588,7 @@ static void build_madt(GArray *table_data,
for (socket = 0; socket < riscv_socket_count(ms); socket++) {
aplic_addr = s->memmap[VIRT_APLIC_S].base +
s->memmap[VIRT_APLIC_S].size * socket;
- gsi_base = VIRT_IRQCHIP_NUM_SOURCES * socket;
+ gsi_base = s->num_sources * socket;
build_append_int_noprefix(table_data, 0x1A, 1); /* Type */
build_append_int_noprefix(table_data, 36, 1); /* Length */
build_append_int_noprefix(table_data, 1, 1); /* Version */
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index ce0fd6f50c4a..6c5bcd43dc54 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -1548,6 +1548,7 @@ static void virt_machine_init(MachineState *machine)
} else {
s->irqchip[i] = riscv_create_aia(s->aia_type == VIRT_AIA_TYPE_APLIC_IMSIC,
s->aia_guests,
+ s->num_sources,
&s->memmap[VIRT_APLIC_M],
&s->memmap[VIRT_APLIC_S],
&s->memmap[VIRT_IMSIC_M],
@@ -1691,6 +1692,7 @@ static void virt_machine_instance_init(Object *obj)
s->oem_table_id = g_strndup(ACPI_BUILD_APPNAME8, 8);
s->acpi = ON_OFF_AUTO_AUTO;
s->iommu_sys = ON_OFF_AUTO_AUTO;
+ s->num_sources = VIRT_IRQCHIP_NUM_SOURCES;
}
static char *virt_get_aia_guests(Object *obj, Error **errp)
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 06/13] hw/riscv/aia: Provide number of irq sources
2026-04-21 5:31 ` [PATCH v3 06/13] hw/riscv/aia: Provide number of irq sources Joel Stanley
@ 2026-04-21 5:49 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-21 5:49 UTC (permalink / raw)
To: Joel Stanley, Alistair Francis, Daniel Henrique Barboza
Cc: Michael Ellerman, Nicholas Piggin, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel, Nutty Liu
On 21/4/26 07:31, Joel Stanley wrote:
> Instead of hard coding the number of IRQ sources used by the APLIC pass
> it in as a parameter. This allows other machines to configure this as
> required.
>
> The maximum number of sources is 1023.
>
> Reviewed-by: Nutty Liu <nutty.liu@hotmail.com>
> Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
> ---
> v3: Add r-b
> ---
> hw/riscv/aia.h | 3 +--
> include/hw/riscv/virt.h | 1 +
> hw/riscv/aia.c | 8 ++++++--
> hw/riscv/virt-acpi-build.c | 25 ++++++++++++++++---------
> hw/riscv/virt.c | 2 ++
> 5 files changed, 26 insertions(+), 13 deletions(-)
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 07/13] target/riscv: tt-ascalon: Enable Zkr extension
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
` (5 preceding siblings ...)
2026-04-21 5:31 ` [PATCH v3 06/13] hw/riscv/aia: Provide number of irq sources Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-22 8:24 ` Chao Liu
2026-04-21 5:31 ` [PATCH v3 08/13] target/riscv: tt-ascalon: Add Svadu extension Joel Stanley
` (5 subsequent siblings)
12 siblings, 1 reply; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Nicholas Piggin, Michael Ellerman, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
From: Nicholas Piggin <npiggin@gmail.com>
Ascalon supports Zkr and the SEED CSR.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
target/riscv/cpu.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index 72c6f4f0f14b..df01a729bc6d 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -3189,6 +3189,7 @@ static const TypeInfo riscv_cpu_type_infos[] = {
.cfg.ext_zba = true,
.cfg.ext_zbb = true,
.cfg.ext_zbs = true,
+ .cfg.ext_zkr = true,
.cfg.ext_zkt = true,
.cfg.ext_zvbb = true,
.cfg.ext_zvbc = true,
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 07/13] target/riscv: tt-ascalon: Enable Zkr extension
2026-04-21 5:31 ` [PATCH v3 07/13] target/riscv: tt-ascalon: Enable Zkr extension Joel Stanley
@ 2026-04-22 8:24 ` Chao Liu
0 siblings, 0 replies; 35+ messages in thread
From: Chao Liu @ 2026-04-22 8:24 UTC (permalink / raw)
To: Joel Stanley
Cc: Alistair Francis, Daniel Henrique Barboza, Nicholas Piggin,
Michael Ellerman, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel
On Tue, Apr 21, 2026 at 03:31:32PM +1000, Joel Stanley wrote:
> From: Nicholas Piggin <npiggin@gmail.com>
>
> Ascalon supports Zkr and the SEED CSR.
>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
Reviewed-by: Chao Liu <chao.liu.zevorn@gmail.com>
Thanks,
Chao
> ---
> target/riscv/cpu.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
> index 72c6f4f0f14b..df01a729bc6d 100644
> --- a/target/riscv/cpu.c
> +++ b/target/riscv/cpu.c
> @@ -3189,6 +3189,7 @@ static const TypeInfo riscv_cpu_type_infos[] = {
> .cfg.ext_zba = true,
> .cfg.ext_zbb = true,
> .cfg.ext_zbs = true,
> + .cfg.ext_zkr = true,
> .cfg.ext_zkt = true,
> .cfg.ext_zvbb = true,
> .cfg.ext_zvbc = true,
> --
> 2.47.3
>
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 08/13] target/riscv: tt-ascalon: Add Svadu extension
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
` (6 preceding siblings ...)
2026-04-21 5:31 ` [PATCH v3 07/13] target/riscv: tt-ascalon: Enable Zkr extension Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-21 5:31 ` [PATCH v3 09/13] hw/riscv: Add Tenstorrent Atlantis machine Joel Stanley
` (4 subsequent siblings)
12 siblings, 0 replies; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Nicholas Piggin, Michael Ellerman, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
From: Nicholas Piggin <npiggin@gmail.com>
Ascalon supports Svadu (hardware A/D bit updates).
QEMU makes Svadu and Svade mutually exclusive, remove Svade so
Ascalon comes up with Svadu working.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
target/riscv/cpu.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index df01a729bc6d..0997bd3324b1 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -3203,7 +3203,6 @@ static const TypeInfo riscv_cpu_type_infos[] = {
.cfg.ext_ssaia = true,
.cfg.ext_sscofpmf = true,
.cfg.ext_sstc = true,
- .cfg.ext_svade = true,
.cfg.ext_svinval = true,
.cfg.ext_svnapot = true,
.cfg.ext_svpbmt = true,
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 09/13] hw/riscv: Add Tenstorrent Atlantis machine
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
` (7 preceding siblings ...)
2026-04-21 5:31 ` [PATCH v3 08/13] target/riscv: tt-ascalon: Add Svadu extension Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-21 5:52 ` Philippe Mathieu-Daudé
2026-04-22 9:12 ` Chao Liu
2026-04-21 5:31 ` [PATCH v3 10/13] hw/riscv/atlantis: Add PCIe controller Joel Stanley
` (3 subsequent siblings)
12 siblings, 2 replies; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Michael Ellerman, Nicholas Piggin, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
The Tenstorrent Atlantis platform is a collaboration between Tenstorrent
and CoreLab Technology. It is based on the Atlantis SoC, which includes
the Ascalon-X CPU and other IP from Tenstorrent and CoreLab Technology.
The Tenstorrent Ascalon-X is a high performance 64-bit RVA23 compliant
RISC-V CPU.
This adds the machine containing serial console, interrupt controllers
and device tree support.
qemu-system-riscv64 -M tt-atlantis -m 512M \
-kernel Image -initrd rootfs.cpio -nographic
If there is no kernel provided the simple payload is loaded to avoid
OpenSBI hanging with no messages.
Signed-off-by: Joel Stanley <joel@jms.id.au>
Co-Developed-by: Nicholas Piggin <npiggin@gmail.com>
---
v3:
- Merge simple payload change into this patch
- Fix fw_cfg_init_mem_dma API change
- Clean up irqchip #defines
- Add Michael and Nick as reviewers
- create_fdt_pmu was copying RISCVCPU by value
- Remove unused platform_bus
---
MAINTAINERS | 11 +
docs/system/riscv/tt_atlantis.rst | 38 ++
docs/system/target-riscv.rst | 1 +
include/hw/riscv/tt_atlantis.h | 77 ++++
hw/riscv/tt_atlantis.c | 638 ++++++++++++++++++++++++++++++
hw/riscv/Kconfig | 15 +
hw/riscv/meson.build | 1 +
7 files changed, 781 insertions(+)
create mode 100644 docs/system/riscv/tt_atlantis.rst
create mode 100644 include/hw/riscv/tt_atlantis.h
create mode 100644 hw/riscv/tt_atlantis.c
diff --git a/MAINTAINERS b/MAINTAINERS
index e1942a86eba5..635a380945fd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1760,6 +1760,17 @@ F: hw/*/*sifive*.c
F: include/hw/*/*sifive*.h
F: tests/functional/test_riscv64_sifive_u.py
+Tenstorrent Machines
+M: Joel Stanley <joel@jms.id.au>
+R: Nicholas Piggin <npiggin@gmail.com>
+R: Michael Ellerman <mpe@kernel.org>
+L: qemu-riscv@nongnu.org
+S: Supported
+F: docs/system/riscv/tt_*.rst
+F: hw/riscv/tt_*.c
+F: hw/riscv/aia.[ch]
+F: include/hw/riscv/tt_*.h
+
AMD Microblaze-V Generic Board
M: Sai Pavan Boddu <sai.pavan.boddu@amd.com>
S: Maintained
diff --git a/docs/system/riscv/tt_atlantis.rst b/docs/system/riscv/tt_atlantis.rst
new file mode 100644
index 000000000000..640cabf7b046
--- /dev/null
+++ b/docs/system/riscv/tt_atlantis.rst
@@ -0,0 +1,38 @@
+Tenstorrent Atlantis (``tt-atlantis``)
+======================================
+
+The Tenstorrent Atlantis platform is a collaboration between Tenstorrent
+and CoreLab Technology. It is based on the Atlantis SoC, which includes
+the Ascalon-X CPU and other IP from Tenstorrent and CoreLab Technology.
+
+The Tenstorrent Ascalon-X is a high performance 64-bit RVA23 compliant
+RISC-V CPU.
+
+Features
+--------
+
+* 8-core Ascalon-X CPU Cluster
+* Dual x32 LPDDR5 @ 6400 MT/s
+* RISC-V compliant Advanced Interrupt Architecture
+* PCIe Gen4
+* RISC-V compliant IOMMU
+* GPU and Video subsystem
+* 2x USB3.1 & 2x USB2.0
+* 2x 1GbE Ethernet
+* 2x eMMC5.1/SDIO3.0 storage
+* Extensive connectivity (SPI, I2C, UART, GPIO, CANFD)
+
+Note: the QEMU tt-atlantis machine does not model the platform
+exactly or all devices, but it is undergoing improvement.
+
+Supported software
+------------------
+
+The Tenstorrent Ascalon CPUs avoid proprietary or non-standard
+extensions, so compatibility with existing software is generally
+good. The QEMU tt-atlantis machine works with upstream OpenSBI
+and Linux with default configurations.
+
+The development board hardware will require some implementation
+specific setup in firmware which is being developed and may
+become a requirement or option for the tt-atlantis machine.
diff --git a/docs/system/target-riscv.rst b/docs/system/target-riscv.rst
index 3ad5d1ddafbb..a8e6b3342186 100644
--- a/docs/system/target-riscv.rst
+++ b/docs/system/target-riscv.rst
@@ -71,6 +71,7 @@ undocumented; you can get a complete list by running
riscv/mips
riscv/shakti-c
riscv/sifive_u
+ riscv/tt_atlantis
riscv/virt
riscv/xiangshan-kunminghu
diff --git a/include/hw/riscv/tt_atlantis.h b/include/hw/riscv/tt_atlantis.h
new file mode 100644
index 000000000000..f7f79e9f2c53
--- /dev/null
+++ b/include/hw/riscv/tt_atlantis.h
@@ -0,0 +1,77 @@
+/*
+ * Tenstorrent Atlantis RISC-V System on Chip
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright 2025 Tenstorrent, Joel Stanley <joel@jms.id.au>
+ */
+
+#ifndef HW_RISCV_TT_ATLANTIS_H
+#define HW_RISCV_TT_ATLANTIS_H
+
+#include "hw/core/boards.h"
+#include "hw/core/sysbus.h"
+#include "hw/intc/riscv_imsic.h"
+#include "hw/riscv/riscv_hart.h"
+
+#define TYPE_TT_ATLANTIS_MACHINE MACHINE_TYPE_NAME("tt-atlantis")
+OBJECT_DECLARE_SIMPLE_TYPE(TTAtlantisState, TT_ATLANTIS_MACHINE)
+
+struct TTAtlantisState {
+ /*< private >*/
+ MachineState parent;
+
+ /*< public >*/
+ Notifier machine_done;
+ FWCfgState *fw_cfg;
+ const MemMapEntry *memmap;
+
+ RISCVHartArrayState soc;
+ DeviceState *irqchip;
+
+ int fdt_size;
+};
+
+enum {
+ TT_ATL_SYSCON_IRQ = 10,
+ TT_ATL_UART0_IRQ = 38,
+ TT_ATL_UART1_IRQ = 39,
+ TT_ATL_UART2_IRQ = 40,
+ TT_ATL_UART3_IRQ = 41,
+ TT_ATL_UART4_IRQ = 42,
+};
+
+enum {
+ TT_ATL_ACLINT,
+ TT_ATL_BOOTROM,
+ TT_ATL_DDR_LO,
+ TT_ATL_DDR_HI,
+ TT_ATL_FW_CFG,
+ TT_ATL_I2C0,
+ TT_ATL_MAPLIC,
+ TT_ATL_MIMSIC,
+ TT_ATL_PCIE_ECAM0,
+ TT_ATL_PCIE_ECAM1,
+ TT_ATL_PCIE_ECAM2,
+ TT_ATL_PCIE_MMIO0,
+ TT_ATL_PCIE_PIO0,
+ TT_ATL_PCIE_MMIO0_32,
+ TT_ATL_PCIE_MMIO0_64,
+ TT_ATL_PCIE_MMIO1,
+ TT_ATL_PCIE_PIO1,
+ TT_ATL_PCIE_MMIO1_32,
+ TT_ATL_PCIE_MMIO1_64,
+ TT_ATL_PCIE_MMIO2,
+ TT_ATL_PCIE_PIO2,
+ TT_ATL_PCIE_MMIO2_32,
+ TT_ATL_PCIE_MMIO2_64,
+ TT_ATL_PCI_MMU_CFG,
+ TT_ATL_SAPLIC,
+ TT_ATL_SIMSIC,
+ TT_ATL_SYSCON,
+ TT_ATL_TIMER,
+ TT_ATL_UART0,
+ TT_ATL_WDT0,
+};
+
+#endif
diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
new file mode 100644
index 000000000000..a556ff79c8c8
--- /dev/null
+++ b/hw/riscv/tt_atlantis.c
@@ -0,0 +1,638 @@
+/*
+ * Tenstorrent Atlantis RISC-V System on Chip
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Copyright 2025 Tenstorrent, Joel Stanley <joel@jms.id.au>
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/cutils.h"
+#include "qemu/error-report.h"
+#include "qemu/guest-random.h"
+#include "qemu/units.h"
+
+#include "hw/core/boards.h"
+#include "hw/core/loader.h"
+#include "hw/core/sysbus.h"
+
+#include "target/riscv/cpu.h"
+#include "target/riscv/pmu.h"
+
+#include "hw/riscv/boot.h"
+#include "hw/riscv/numa.h"
+#include "hw/riscv/riscv_hart.h"
+
+#include "hw/char/serial-mm.h"
+#include "hw/intc/riscv_aclint.h"
+#include "hw/intc/riscv_aplic.h"
+#include "hw/misc/pvpanic.h"
+
+#include "system/system.h"
+#include "system/device_tree.h"
+
+#include "hw/riscv/tt_atlantis.h"
+
+#include "aia.h"
+
+#define TT_IRQCHIP_NUM_MSIS 255
+#define TT_IRQCHIP_NUM_SOURCES 128
+#define TT_IRQCHIP_NUM_PRIO_BITS 3
+#define TT_IRQCHIP_GUESTS 7 /* aia_guests */
+
+#define FDT_PCI_ADDR_CELLS 3
+#define FDT_PCI_INT_CELLS 1
+#define FDT_MAX_INT_CELLS 2
+#define FDT_MAX_INT_MAP_WIDTH (FDT_PCI_ADDR_CELLS + FDT_PCI_INT_CELLS + \
+ 1 + FDT_MAX_INT_CELLS)
+
+#define TT_ACLINT_MTIME_SIZE 0x8050
+#define TT_ACLINT_MTIME 0x0
+#define TT_ACLINT_MTIMECMP 0x8000
+#define TT_ACLINT_TIMEBASE_FREQ 1000000000
+
+static const MemMapEntry tt_atlantis_memmap[] = {
+ /* Keep sorted with :'<,'>!sort -g -k 4 */
+ [TT_ATL_DDR_LO] = { 0x00000000, 0x80000000 },
+ [TT_ATL_BOOTROM] = { 0x80000000, 0x2000 },
+ [TT_ATL_FW_CFG] = { 0x80002000, 0xff }, /* qemu only */
+ [TT_ATL_SYSCON] = { 0x80004000, 0x1000 }, /* qemu only */
+ [TT_ATL_MIMSIC] = { 0xa0000000, 0x200000 },
+ [TT_ATL_ACLINT] = { 0xa2180000, 0x10000 },
+ [TT_ATL_SIMSIC] = { 0xa4000000, 0x200000 },
+ [TT_ATL_TIMER] = { 0xa8020000, 0x10000 },
+ [TT_ATL_WDT0] = { 0xa8030000, 0x10000 },
+ [TT_ATL_UART0] = { 0xb0100000, 0x10000 },
+ [TT_ATL_MAPLIC] = { 0xcc000000, 0x4000000 },
+ [TT_ATL_SAPLIC] = { 0xe8000000, 0x4000000 },
+ [TT_ATL_DDR_HI] = { 0x100000000, 0x1000000000 },
+ [TT_ATL_PCIE_ECAM0] = { 0x01110000000, 0x10000000 },
+ [TT_ATL_PCIE_ECAM1] = { 0x01120000000, 0x10000000 },
+ [TT_ATL_PCIE_ECAM2] = { 0x01130000000, 0x10000000 },
+ [TT_ATL_PCIE_MMIO0] = { 0x10000000000, 0x10000000000 },
+ [TT_ATL_PCIE_MMIO1] = { 0x20000000000, 0x10000000000 },
+ [TT_ATL_PCIE_MMIO2] = { 0x30000000000, 0x10000000000 },
+};
+
+static uint32_t next_phandle(void)
+{
+ static uint32_t phandle = 1;
+ return phandle++;
+}
+
+static void create_fdt_cpus(TTAtlantisState *s, uint32_t *intc_phandles)
+{
+ uint32_t cpu_phandle;
+ void *fdt = MACHINE(s)->fdt;
+
+ for (int cpu = s->soc.num_harts - 1; cpu >= 0; cpu--) {
+ RISCVCPU *cpu_ptr = &s->soc.harts[cpu];
+ g_autofree char *cpu_name = NULL;
+ g_autofree char *intc_name = NULL;
+
+ cpu_phandle = next_phandle();
+
+ cpu_name = g_strdup_printf("/cpus/cpu@%d", s->soc.hartid_base + cpu);
+ qemu_fdt_add_subnode(fdt, cpu_name);
+
+ qemu_fdt_setprop_string(fdt, cpu_name, "mmu-type", "riscv,sv57");
+
+ riscv_isa_write_fdt(cpu_ptr, fdt, cpu_name);
+
+ qemu_fdt_setprop_cell(fdt, cpu_name, "riscv,cbom-block-size",
+ cpu_ptr->cfg.cbom_blocksize);
+
+ qemu_fdt_setprop_cell(fdt, cpu_name, "riscv,cboz-block-size",
+ cpu_ptr->cfg.cboz_blocksize);
+
+ qemu_fdt_setprop_cell(fdt, cpu_name, "riscv,cbop-block-size",
+ cpu_ptr->cfg.cbop_blocksize);
+
+ qemu_fdt_setprop_string(fdt, cpu_name, "compatible", "riscv");
+ qemu_fdt_setprop_string(fdt, cpu_name, "status", "okay");
+ qemu_fdt_setprop_cell(fdt, cpu_name, "reg", s->soc.hartid_base + cpu);
+ qemu_fdt_setprop_string(fdt, cpu_name, "device_type", "cpu");
+ qemu_fdt_setprop_cell(fdt, cpu_name, "phandle", cpu_phandle);
+
+ intc_phandles[cpu] = next_phandle();
+
+ intc_name = g_strdup_printf("%s/interrupt-controller", cpu_name);
+ qemu_fdt_add_subnode(fdt, intc_name);
+ qemu_fdt_setprop_cell(fdt, intc_name, "phandle",
+ intc_phandles[cpu]);
+ qemu_fdt_setprop_string(fdt, intc_name, "compatible",
+ "riscv,cpu-intc");
+ qemu_fdt_setprop(fdt, intc_name, "interrupt-controller", NULL, 0);
+ qemu_fdt_setprop_cell(fdt, intc_name, "#interrupt-cells", 1);
+ }
+}
+
+static void create_fdt_memory_node(TTAtlantisState *s,
+ hwaddr addr, hwaddr size)
+{
+ void *fdt = MACHINE(s)->fdt;
+ g_autofree char *name = g_strdup_printf("/memory@%"HWADDR_PRIX, addr);
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, addr, 2, size);
+ qemu_fdt_setprop_string(fdt, name, "device_type", "memory");
+}
+
+static void create_fdt_memory(TTAtlantisState *s)
+{
+ hwaddr size_lo = MACHINE(s)->ram_size;
+ hwaddr size_hi = 0;
+
+ if (size_lo > s->memmap[TT_ATL_DDR_LO].size) {
+ size_lo = s->memmap[TT_ATL_DDR_LO].size;
+ size_hi = MACHINE(s)->ram_size - size_lo;
+ }
+
+ create_fdt_memory_node(s, s->memmap[TT_ATL_DDR_LO].base, size_lo);
+ if (size_hi) {
+ /*
+ * The first part of the HI address is aliased at the LO address
+ * so do not include that as usable memory. Is there any way
+ * (or good reason) to describe that aliasing 2GB with DT?
+ */
+ create_fdt_memory_node(s, s->memmap[TT_ATL_DDR_HI].base + size_lo,
+ size_hi);
+ }
+}
+
+static void create_fdt_aclint(TTAtlantisState *s, uint32_t *intc_phandles)
+{
+ void *fdt = MACHINE(s)->fdt;
+ g_autofree char *name = NULL;
+ g_autofree uint32_t *aclint_mtimer_cells = NULL;
+ uint32_t aclint_cells_size;
+ hwaddr addr;
+
+ aclint_mtimer_cells = g_new0(uint32_t, s->soc.num_harts * 2);
+
+ for (int cpu = 0; cpu < s->soc.num_harts; cpu++) {
+ aclint_mtimer_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
+ aclint_mtimer_cells[cpu * 2 + 1] = cpu_to_be32(IRQ_M_TIMER);
+ }
+ aclint_cells_size = s->soc.num_harts * sizeof(uint32_t) * 2;
+
+ addr = s->memmap[TT_ATL_ACLINT].base;
+
+ name = g_strdup_printf("/soc/mtimer@%"HWADDR_PRIX, addr);
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,aclint-mtimer");
+ qemu_fdt_setprop_sized_cells(fdt, name, "reg",
+ 2, addr + TT_ACLINT_MTIME,
+ 2, 0x1000,
+ 2, addr + TT_ACLINT_MTIMECMP,
+ 2, 0x1000);
+ qemu_fdt_setprop(fdt, name, "interrupts-extended",
+ aclint_mtimer_cells, aclint_cells_size);
+}
+
+static void create_fdt_one_imsic(void *fdt, const MemMapEntry *mem, int cpus,
+ uint32_t *intc_phandles, uint32_t msi_phandle,
+ int irq_line, uint32_t imsic_guest_bits)
+{
+ g_autofree char *name = NULL;
+ g_autofree uint32_t *imsic_cells = g_new0(uint32_t, cpus * 2);
+
+ for (int cpu = 0; cpu < cpus; cpu++) {
+ imsic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
+ imsic_cells[cpu * 2 + 1] = cpu_to_be32(irq_line);
+ }
+
+ name = g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIX, mem->base);
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,imsics");
+
+ qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", 0);
+ qemu_fdt_setprop(fdt, name, "interrupt-controller", NULL, 0);
+ qemu_fdt_setprop(fdt, name, "msi-controller", NULL, 0);
+ qemu_fdt_setprop(fdt, name, "interrupts-extended",
+ imsic_cells, sizeof(uint32_t) * cpus * 2);
+ qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
+ qemu_fdt_setprop_cell(fdt, name, "riscv,num-ids", TT_IRQCHIP_NUM_MSIS);
+
+ if (imsic_guest_bits) {
+ qemu_fdt_setprop_cell(fdt, name, "riscv,guest-index-bits",
+ imsic_guest_bits);
+ }
+ qemu_fdt_setprop_cell(fdt, name, "phandle", msi_phandle);
+}
+
+static void create_fdt_one_aplic(void *fdt,
+ const MemMapEntry *mem,
+ uint32_t msi_phandle,
+ uint32_t *intc_phandles,
+ uint32_t aplic_phandle,
+ uint32_t aplic_child_phandle,
+ int irq_line, int num_harts)
+{
+ g_autofree char *name =
+ g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIX, mem->base);
+ g_autofree uint32_t *aplic_cells = g_new0(uint32_t, num_harts * 2);
+
+ for (int cpu = 0; cpu < num_harts; cpu++) {
+ aplic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
+ aplic_cells[cpu * 2 + 1] = cpu_to_be32(irq_line);
+ }
+
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,aplic");
+ qemu_fdt_setprop_cell(fdt, name, "#address-cells", 0);
+ qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", 2);
+ qemu_fdt_setprop(fdt, name, "interrupt-controller", NULL, 0);
+
+ qemu_fdt_setprop(fdt, name, "interrupts-extended",
+ aplic_cells, num_harts * sizeof(uint32_t) * 2);
+ qemu_fdt_setprop_cell(fdt, name, "msi-parent", msi_phandle);
+
+ qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
+ qemu_fdt_setprop_cell(fdt, name, "riscv,num-sources",
+ TT_IRQCHIP_NUM_SOURCES);
+
+ if (aplic_child_phandle) {
+ qemu_fdt_setprop_cell(fdt, name, "riscv,children",
+ aplic_child_phandle);
+ qemu_fdt_setprop_cells(fdt, name, "riscv,delegation",
+ aplic_child_phandle, 1, TT_IRQCHIP_NUM_SOURCES);
+ }
+
+ qemu_fdt_setprop_cell(fdt, name, "phandle", aplic_phandle);
+}
+
+static void create_fdt_pmu(TTAtlantisState *s)
+{
+ g_autofree char *pmu_name = g_strdup_printf("/pmu");
+ void *fdt = MACHINE(s)->fdt;
+ RISCVCPU *hart = &s->soc.harts[0];
+
+ qemu_fdt_add_subnode(fdt, pmu_name);
+ qemu_fdt_setprop_string(fdt, pmu_name, "compatible", "riscv,pmu");
+ riscv_pmu_generate_fdt_node(fdt, hart->pmu_avail_ctrs, pmu_name);
+}
+
+static void create_fdt_cpu(TTAtlantisState *s, const MemMapEntry *memmap,
+ uint32_t aplic_s_phandle,
+ uint32_t imsic_s_phandle)
+{
+ MachineState *ms = MACHINE(s);
+ void *fdt = MACHINE(s)->fdt;
+ g_autofree uint32_t *intc_phandles = NULL;
+
+ qemu_fdt_add_subnode(fdt, "/cpus");
+ qemu_fdt_setprop_cell(fdt, "/cpus", "timebase-frequency",
+ TT_ACLINT_TIMEBASE_FREQ);
+ qemu_fdt_setprop_cell(fdt, "/cpus", "#size-cells", 0x0);
+ qemu_fdt_setprop_cell(fdt, "/cpus", "#address-cells", 0x1);
+
+ intc_phandles = g_new0(uint32_t, ms->smp.cpus);
+
+ create_fdt_cpus(s, intc_phandles);
+
+ create_fdt_memory(s);
+
+ create_fdt_aclint(s, intc_phandles);
+
+ /* M-level IMSIC node */
+ uint32_t msi_m_phandle = next_phandle();
+ create_fdt_one_imsic(fdt, &s->memmap[TT_ATL_MIMSIC], ms->smp.cpus,
+ intc_phandles, msi_m_phandle,
+ IRQ_M_EXT, 0);
+
+ /* S-level IMSIC node */
+ create_fdt_one_imsic(fdt, &s->memmap[TT_ATL_SIMSIC], ms->smp.cpus,
+ intc_phandles, imsic_s_phandle,
+ IRQ_S_EXT, imsic_num_bits(TT_IRQCHIP_GUESTS + 1));
+
+ uint32_t aplic_m_phandle = next_phandle();
+
+ /* M-level APLIC node */
+ create_fdt_one_aplic(fdt, &s->memmap[TT_ATL_MAPLIC],
+ msi_m_phandle, intc_phandles,
+ aplic_m_phandle, aplic_s_phandle,
+ IRQ_M_EXT, s->soc.num_harts);
+
+ /* S-level APLIC node */
+ create_fdt_one_aplic(fdt, &s->memmap[TT_ATL_SAPLIC],
+ imsic_s_phandle, intc_phandles,
+ aplic_s_phandle, 0,
+ IRQ_S_EXT, s->soc.num_harts);
+}
+
+static void create_fdt_reset(void *fdt, const MemMapEntry *mem)
+{
+ uint32_t syscon_phandle = next_phandle();
+ char *name;
+
+ name = g_strdup_printf("/soc/syscon@%"HWADDR_PRIX, mem->base);
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "syscon");
+ qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
+ qemu_fdt_setprop_cell(fdt, name, "phandle", syscon_phandle);
+ g_free(name);
+
+ name = g_strdup_printf("/poweroff");
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "syscon-poweroff");
+ qemu_fdt_setprop_cell(fdt, name, "regmap", syscon_phandle);
+ qemu_fdt_setprop_cell(fdt, name, "offset", 0x0);
+ qemu_fdt_setprop_cell(fdt, name, "value", PVPANIC_SHUTDOWN);
+ g_free(name);
+}
+
+static void create_fdt_uart(void *fdt, const MemMapEntry *mem, int irq,
+ int irqchip_phandle)
+{
+ g_autofree char *name = g_strdup_printf("/soc/serial@%"HWADDR_PRIX,
+ mem->base);
+
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "ns16550a");
+ qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
+ qemu_fdt_setprop_cell(fdt, name, "reg-shift", 2);
+ qemu_fdt_setprop_cell(fdt, name, "reg-io-width", 4);
+ qemu_fdt_setprop_cell(fdt, name, "clock-frequency", 3686400);
+ qemu_fdt_setprop_cell(fdt, name, "interrupt-parent", irqchip_phandle);
+ qemu_fdt_setprop_cells(fdt, name, "interrupts", irq, 0x4);
+
+ qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", name);
+ qemu_fdt_setprop_string(fdt, "/aliases", "serial0", name);
+}
+
+static void create_fdt_fw_cfg(void *fdt, const MemMapEntry *mem)
+{
+ g_autofree char *name = g_strdup_printf("/fw-cfg@%"HWADDR_PRIX, mem->base);
+
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "qemu,fw-cfg-mmio");
+ qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
+ qemu_fdt_setprop(fdt, name, "dma-coherent", NULL, 0);
+}
+
+static void finalize_fdt(TTAtlantisState *s)
+{
+ uint32_t aplic_s_phandle = next_phandle();
+ uint32_t imsic_s_phandle = next_phandle();
+ void *fdt = MACHINE(s)->fdt;
+
+ create_fdt_cpu(s, s->memmap, aplic_s_phandle, imsic_s_phandle);
+
+ /*
+ * We want to do this, but the Linux aplic driver was broken before v6.16
+ *
+ * qemu_fdt_setprop_cell(MACHINE(s)->fdt, "/soc", "interrupt-parent",
+ * aplic_s_phandle);
+ */
+
+ create_fdt_reset(fdt, &s->memmap[TT_ATL_SYSCON]);
+
+ create_fdt_uart(fdt, &s->memmap[TT_ATL_UART0], TT_ATL_UART0_IRQ,
+ aplic_s_phandle);
+}
+
+static void create_fdt(TTAtlantisState *s)
+{
+ MachineState *ms = MACHINE(s);
+ uint8_t rng_seed[32];
+ g_autofree char *name = NULL;
+ void *fdt;
+
+ fdt = create_device_tree(&s->fdt_size);
+ if (!fdt) {
+ error_report("create_device_tree() failed");
+ exit(1);
+ }
+ ms->fdt = fdt;
+
+ qemu_fdt_setprop_string(fdt, "/", "model",
+ "Tenstorrent Atlantis RISC-V Machine");
+ qemu_fdt_setprop_string(fdt, "/", "compatible", "tenstorrent,atlantis");
+ qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x2);
+ qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x2);
+
+ qemu_fdt_add_subnode(fdt, "/soc");
+ qemu_fdt_setprop(fdt, "/soc", "ranges", NULL, 0);
+ qemu_fdt_setprop_string(fdt, "/soc", "compatible", "simple-bus");
+ qemu_fdt_setprop_cell(fdt, "/soc", "#size-cells", 0x2);
+ qemu_fdt_setprop_cell(fdt, "/soc", "#address-cells", 0x2);
+
+ qemu_fdt_add_subnode(fdt, "/chosen");
+
+ /* Pass seed to RNG */
+ qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed));
+ qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed));
+
+ qemu_fdt_add_subnode(fdt, "/aliases");
+
+ create_fdt_fw_cfg(fdt, &s->memmap[TT_ATL_FW_CFG]);
+ create_fdt_pmu(s);
+}
+
+static DeviceState *create_reboot_device(const MemMapEntry *mem)
+{
+ DeviceState *dev = qdev_new(TYPE_PVPANIC_MMIO_DEVICE);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ qdev_prop_set_uint32(dev, "events", PVPANIC_SHUTDOWN | PVPANIC_PANICKED);
+
+ sysbus_realize_and_unref(sbd, &error_fatal);
+ sysbus_mmio_map(sbd, 0, mem->base);
+
+ return dev;
+}
+
+static FWCfgState *create_fw_cfg(const MemMapEntry *mem, int num_cpus)
+{
+ FWCfgState *fw_cfg;
+ hwaddr base = mem->base;
+
+ fw_cfg = fw_cfg_init_mem_dma(base + 8, base, 8, base + 16,
+ &address_space_memory);
+ fw_cfg_add_i16(fw_cfg, FW_CFG_NB_CPUS, num_cpus);
+
+ return fw_cfg;
+}
+
+static void tt_atlantis_machine_done(Notifier *notifier, void *data)
+{
+ TTAtlantisState *s = container_of(notifier, TTAtlantisState, machine_done);
+ MachineState *machine = MACHINE(s);
+ hwaddr start_addr = s->memmap[TT_ATL_DDR_LO].base;
+ hwaddr mem_size;
+ target_ulong firmware_end_addr, kernel_start_addr;
+ const char *firmware_name = riscv_default_firmware_name(&s->soc);
+ uint64_t fdt_load_addr;
+ uint64_t kernel_entry;
+ RISCVBootInfo boot_info;
+
+ /*
+ * A user provided dtb must include everything, including
+ * dynamic sysbus devices. Our FDT needs to be finalized.
+ */
+ if (machine->dtb == NULL) {
+ finalize_fdt(s);
+ }
+
+ mem_size = machine->ram_size;
+ if (mem_size > s->memmap[TT_ATL_DDR_LO].size) {
+ mem_size = s->memmap[TT_ATL_DDR_LO].size;
+ }
+ riscv_boot_info_init_discontig_mem(&boot_info, &s->soc,
+ s->memmap[TT_ATL_DDR_LO].base,
+ mem_size);
+
+ firmware_end_addr = riscv_find_and_load_firmware(machine, &boot_info,
+ firmware_name,
+ &start_addr, NULL);
+
+ kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info,
+ firmware_end_addr);
+ if (machine->kernel_filename) {
+ riscv_load_kernel(machine, &boot_info, kernel_start_addr,
+ true, NULL);
+ } else {
+ riscv_setup_halting_payload(&boot_info, kernel_start_addr);
+ }
+ kernel_entry = boot_info.image_low_addr;
+
+ fdt_load_addr = riscv_compute_fdt_addr(s->memmap[TT_ATL_DDR_LO].base,
+ s->memmap[TT_ATL_DDR_LO].size,
+ machine, &boot_info);
+ riscv_load_fdt(fdt_load_addr, machine->fdt);
+
+ /* load the reset vector */
+ riscv_setup_rom_reset_vec(machine, &s->soc, start_addr,
+ s->memmap[TT_ATL_BOOTROM].base,
+ s->memmap[TT_ATL_BOOTROM].size,
+ kernel_entry,
+ fdt_load_addr);
+
+}
+
+static void tt_atlantis_machine_init(MachineState *machine)
+{
+ TTAtlantisState *s = TT_ATLANTIS_MACHINE(machine);
+
+ MemoryRegion *system_memory = get_system_memory();
+ MemoryRegion *ram_hi = g_new(MemoryRegion, 1);
+ MemoryRegion *ram_lo = g_new(MemoryRegion, 1);
+ MemoryRegion *bootrom = g_new(MemoryRegion, 1);
+ ram_addr_t lo_ram_size, hi_ram_size;
+ int hart_count = machine->smp.cpus;
+ int base_hartid = 0;
+
+ s->memmap = tt_atlantis_memmap;
+
+ object_initialize_child(OBJECT(machine), "soc", &s->soc,
+ TYPE_RISCV_HART_ARRAY);
+ object_property_set_str(OBJECT(&s->soc), "cpu-type", machine->cpu_type,
+ &error_abort);
+ object_property_set_int(OBJECT(&s->soc), "hartid-base", base_hartid,
+ &error_abort);
+ object_property_set_int(OBJECT(&s->soc), "num-harts", hart_count,
+ &error_abort);
+ object_property_set_int(OBJECT(&s->soc), "resetvec",
+ s->memmap[TT_ATL_BOOTROM].base,
+ &error_abort);
+ sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal);
+
+ s->irqchip = riscv_create_aia(true, TT_IRQCHIP_GUESTS,
+ TT_IRQCHIP_NUM_SOURCES,
+ &s->memmap[TT_ATL_MAPLIC],
+ &s->memmap[TT_ATL_SAPLIC],
+ &s->memmap[TT_ATL_MIMSIC],
+ &s->memmap[TT_ATL_SIMSIC],
+ 0, base_hartid, hart_count,
+ TT_IRQCHIP_NUM_MSIS,
+ TT_IRQCHIP_NUM_PRIO_BITS);
+
+ riscv_aclint_mtimer_create(s->memmap[TT_ATL_ACLINT].base,
+ TT_ACLINT_MTIME_SIZE,
+ base_hartid, hart_count,
+ TT_ACLINT_MTIMECMP,
+ TT_ACLINT_MTIME,
+ TT_ACLINT_TIMEBASE_FREQ, true);
+
+ /* DDR */
+
+ /* The high address covers all of RAM, the low address just the first 2GB */
+ lo_ram_size = s->memmap[TT_ATL_DDR_LO].size;
+ hi_ram_size = s->memmap[TT_ATL_DDR_HI].size;
+ if (machine->ram_size > hi_ram_size) {
+ char *sz = size_to_str(hi_ram_size);
+ error_report("RAM size is too large, maximum is %s", sz);
+ g_free(sz);
+ exit(EXIT_FAILURE);
+ }
+
+ memory_region_init_alias(ram_lo, OBJECT(machine), "ram.low", machine->ram,
+ 0, lo_ram_size);
+ memory_region_init_alias(ram_hi, OBJECT(machine), "ram.high", machine->ram,
+ 0, hi_ram_size);
+ memory_region_add_subregion(system_memory,
+ s->memmap[TT_ATL_DDR_LO].base, ram_lo);
+ memory_region_add_subregion(system_memory,
+ s->memmap[TT_ATL_DDR_HI].base, ram_hi);
+
+ /* Boot ROM */
+ memory_region_init_rom(bootrom, NULL, "tt-atlantis.bootrom",
+ s->memmap[TT_ATL_BOOTROM].size, &error_fatal);
+ memory_region_add_subregion(system_memory, s->memmap[TT_ATL_BOOTROM].base,
+ bootrom);
+
+ /*
+ * Init fw_cfg. Must be done before riscv_load_fdt, otherwise the
+ * device tree cannot be altered and we get FDT_ERR_NOSPACE.
+ */
+ s->fw_cfg = create_fw_cfg(&s->memmap[TT_ATL_FW_CFG], machine->smp.cpus);
+ rom_set_fw(s->fw_cfg);
+
+ /* Reboot and exit */
+ create_reboot_device(&s->memmap[TT_ATL_SYSCON]);
+
+ /* UART */
+ serial_mm_init(system_memory, s->memmap[TT_ATL_UART0].base, 2,
+ qdev_get_gpio_in(s->irqchip, TT_ATL_UART0_IRQ),
+ 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN);
+
+ /* Load or create device tree */
+ if (machine->dtb) {
+ machine->fdt = load_device_tree(machine->dtb, &s->fdt_size);
+ if (!machine->fdt) {
+ error_report("load_device_tree() failed");
+ exit(1);
+ }
+ } else {
+ create_fdt(s);
+ }
+
+ s->machine_done.notify = tt_atlantis_machine_done;
+ qemu_add_machine_init_done_notifier(&s->machine_done);
+}
+
+static void tt_atlantis_machine_class_init(ObjectClass *oc, const void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->desc = "Tenstorrent Atlantis RISC-V SoC";
+ mc->init = tt_atlantis_machine_init;
+ mc->max_cpus = 8;
+ mc->default_cpus = 8;
+ mc->default_ram_size = 2 * GiB;
+ mc->default_cpu_type = TYPE_RISCV_CPU_TT_ASCALON;
+ mc->block_default_type = IF_VIRTIO;
+ mc->no_cdrom = 1;
+ mc->default_ram_id = "tt_atlantis.ram";
+}
+
+static const TypeInfo tt_atlantis_types[] = {
+ {
+ .name = MACHINE_TYPE_NAME("tt-atlantis"),
+ .parent = TYPE_MACHINE,
+ .class_init = tt_atlantis_machine_class_init,
+ .instance_size = sizeof(TTAtlantisState),
+ },
+};
+
+DEFINE_TYPES(tt_atlantis_types)
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 0222c93f878b..0601ae1a7494 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -120,6 +120,21 @@ config SPIKE
select RISCV_ACLINT
select SIFIVE_PLIC
+config TENSTORRENT
+ bool
+ default y
+ depends on RISCV64
+ imply PCI_DEVICES
+ imply TEST_DEVICES
+ select DEVICE_TREE
+ select RISCV_NUMA
+ select PVPANIC_MMIO
+ select SERIAL_MM
+ select RISCV_ACLINT
+ select RISCV_APLIC
+ select RISCV_IMSIC
+ select FW_CFG_DMA
+
config XIANGSHAN_KUNMINGHU
bool
default y
diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index e53c180d0d10..026e79591f4b 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -9,6 +9,7 @@ riscv_ss.add(when: 'CONFIG_SIFIVE_E', if_true: files('sifive_e.c'))
riscv_ss.add(when: 'CONFIG_SIFIVE_U', if_true: files('sifive_u.c'))
riscv_ss.add(when: 'CONFIG_SPIKE', if_true: files('spike.c'))
riscv_ss.add(when: 'CONFIG_MICROCHIP_PFSOC', if_true: files('microchip_pfsoc.c'))
+riscv_ss.add(when: 'CONFIG_TENSTORRENT', if_true: files('tt_atlantis.c'))
riscv_ss.add(when: 'CONFIG_ACPI', if_true: files('virt-acpi-build.c'))
riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 09/13] hw/riscv: Add Tenstorrent Atlantis machine
2026-04-21 5:31 ` [PATCH v3 09/13] hw/riscv: Add Tenstorrent Atlantis machine Joel Stanley
@ 2026-04-21 5:52 ` Philippe Mathieu-Daudé
2026-04-22 9:12 ` Chao Liu
1 sibling, 0 replies; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-21 5:52 UTC (permalink / raw)
To: Joel Stanley, Alistair Francis, Daniel Henrique Barboza
Cc: Michael Ellerman, Nicholas Piggin, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
On 21/4/26 07:31, Joel Stanley wrote:
> The Tenstorrent Atlantis platform is a collaboration between Tenstorrent
> and CoreLab Technology. It is based on the Atlantis SoC, which includes
> the Ascalon-X CPU and other IP from Tenstorrent and CoreLab Technology.
>
> The Tenstorrent Ascalon-X is a high performance 64-bit RVA23 compliant
> RISC-V CPU.
>
> This adds the machine containing serial console, interrupt controllers
> and device tree support.
>
> qemu-system-riscv64 -M tt-atlantis -m 512M \
> -kernel Image -initrd rootfs.cpio -nographic
>
> If there is no kernel provided the simple payload is loaded to avoid
> OpenSBI hanging with no messages.
>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
> Co-Developed-by: Nicholas Piggin <npiggin@gmail.com>
> ---
> v3:
> - Merge simple payload change into this patch
> - Fix fw_cfg_init_mem_dma API change
> - Clean up irqchip #defines
> - Add Michael and Nick as reviewers
> - create_fdt_pmu was copying RISCVCPU by value
> - Remove unused platform_bus
> ---
> MAINTAINERS | 11 +
> docs/system/riscv/tt_atlantis.rst | 38 ++
> docs/system/target-riscv.rst | 1 +
> include/hw/riscv/tt_atlantis.h | 77 ++++
> hw/riscv/tt_atlantis.c | 638 ++++++++++++++++++++++++++++++
> hw/riscv/Kconfig | 15 +
> hw/riscv/meson.build | 1 +
> 7 files changed, 781 insertions(+)
> create mode 100644 docs/system/riscv/tt_atlantis.rst
> create mode 100644 include/hw/riscv/tt_atlantis.h
> create mode 100644 hw/riscv/tt_atlantis.c
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 09/13] hw/riscv: Add Tenstorrent Atlantis machine
2026-04-21 5:31 ` [PATCH v3 09/13] hw/riscv: Add Tenstorrent Atlantis machine Joel Stanley
2026-04-21 5:52 ` Philippe Mathieu-Daudé
@ 2026-04-22 9:12 ` Chao Liu
1 sibling, 0 replies; 35+ messages in thread
From: Chao Liu @ 2026-04-22 9:12 UTC (permalink / raw)
To: Joel Stanley
Cc: Alistair Francis, Daniel Henrique Barboza, Michael Ellerman,
Nicholas Piggin, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel
On Tue, Apr 21, 2026 at 03:31:34PM +1000, Joel Stanley wrote:
> The Tenstorrent Atlantis platform is a collaboration between Tenstorrent
> and CoreLab Technology. It is based on the Atlantis SoC, which includes
> the Ascalon-X CPU and other IP from Tenstorrent and CoreLab Technology.
>
> The Tenstorrent Ascalon-X is a high performance 64-bit RVA23 compliant
> RISC-V CPU.
>
> This adds the machine containing serial console, interrupt controllers
> and device tree support.
>
> qemu-system-riscv64 -M tt-atlantis -m 512M \
> -kernel Image -initrd rootfs.cpio -nographic
>
> If there is no kernel provided the simple payload is loaded to avoid
> OpenSBI hanging with no messages.
>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
> Co-Developed-by: Nicholas Piggin <npiggin@gmail.com>
> ---
> v3:
> - Merge simple payload change into this patch
> - Fix fw_cfg_init_mem_dma API change
> - Clean up irqchip #defines
> - Add Michael and Nick as reviewers
> - create_fdt_pmu was copying RISCVCPU by value
> - Remove unused platform_bus
Reviewed-by: Chao Liu <chao.liu.zevorn@gmail.com>
Thanks,
Chao
> ---
> MAINTAINERS | 11 +
> docs/system/riscv/tt_atlantis.rst | 38 ++
> docs/system/target-riscv.rst | 1 +
> include/hw/riscv/tt_atlantis.h | 77 ++++
> hw/riscv/tt_atlantis.c | 638 ++++++++++++++++++++++++++++++
> hw/riscv/Kconfig | 15 +
> hw/riscv/meson.build | 1 +
> 7 files changed, 781 insertions(+)
> create mode 100644 docs/system/riscv/tt_atlantis.rst
> create mode 100644 include/hw/riscv/tt_atlantis.h
> create mode 100644 hw/riscv/tt_atlantis.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e1942a86eba5..635a380945fd 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1760,6 +1760,17 @@ F: hw/*/*sifive*.c
> F: include/hw/*/*sifive*.h
> F: tests/functional/test_riscv64_sifive_u.py
>
> +Tenstorrent Machines
> +M: Joel Stanley <joel@jms.id.au>
> +R: Nicholas Piggin <npiggin@gmail.com>
> +R: Michael Ellerman <mpe@kernel.org>
> +L: qemu-riscv@nongnu.org
> +S: Supported
> +F: docs/system/riscv/tt_*.rst
> +F: hw/riscv/tt_*.c
> +F: hw/riscv/aia.[ch]
> +F: include/hw/riscv/tt_*.h
> +
> AMD Microblaze-V Generic Board
> M: Sai Pavan Boddu <sai.pavan.boddu@amd.com>
> S: Maintained
> diff --git a/docs/system/riscv/tt_atlantis.rst b/docs/system/riscv/tt_atlantis.rst
> new file mode 100644
> index 000000000000..640cabf7b046
> --- /dev/null
> +++ b/docs/system/riscv/tt_atlantis.rst
> @@ -0,0 +1,38 @@
> +Tenstorrent Atlantis (``tt-atlantis``)
> +======================================
> +
> +The Tenstorrent Atlantis platform is a collaboration between Tenstorrent
> +and CoreLab Technology. It is based on the Atlantis SoC, which includes
> +the Ascalon-X CPU and other IP from Tenstorrent and CoreLab Technology.
> +
> +The Tenstorrent Ascalon-X is a high performance 64-bit RVA23 compliant
> +RISC-V CPU.
> +
> +Features
> +--------
> +
> +* 8-core Ascalon-X CPU Cluster
> +* Dual x32 LPDDR5 @ 6400 MT/s
> +* RISC-V compliant Advanced Interrupt Architecture
> +* PCIe Gen4
> +* RISC-V compliant IOMMU
> +* GPU and Video subsystem
> +* 2x USB3.1 & 2x USB2.0
> +* 2x 1GbE Ethernet
> +* 2x eMMC5.1/SDIO3.0 storage
> +* Extensive connectivity (SPI, I2C, UART, GPIO, CANFD)
> +
> +Note: the QEMU tt-atlantis machine does not model the platform
> +exactly or all devices, but it is undergoing improvement.
> +
> +Supported software
> +------------------
> +
> +The Tenstorrent Ascalon CPUs avoid proprietary or non-standard
> +extensions, so compatibility with existing software is generally
> +good. The QEMU tt-atlantis machine works with upstream OpenSBI
> +and Linux with default configurations.
> +
> +The development board hardware will require some implementation
> +specific setup in firmware which is being developed and may
> +become a requirement or option for the tt-atlantis machine.
> diff --git a/docs/system/target-riscv.rst b/docs/system/target-riscv.rst
> index 3ad5d1ddafbb..a8e6b3342186 100644
> --- a/docs/system/target-riscv.rst
> +++ b/docs/system/target-riscv.rst
> @@ -71,6 +71,7 @@ undocumented; you can get a complete list by running
> riscv/mips
> riscv/shakti-c
> riscv/sifive_u
> + riscv/tt_atlantis
> riscv/virt
> riscv/xiangshan-kunminghu
>
> diff --git a/include/hw/riscv/tt_atlantis.h b/include/hw/riscv/tt_atlantis.h
> new file mode 100644
> index 000000000000..f7f79e9f2c53
> --- /dev/null
> +++ b/include/hw/riscv/tt_atlantis.h
> @@ -0,0 +1,77 @@
> +/*
> + * Tenstorrent Atlantis RISC-V System on Chip
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright 2025 Tenstorrent, Joel Stanley <joel@jms.id.au>
> + */
> +
> +#ifndef HW_RISCV_TT_ATLANTIS_H
> +#define HW_RISCV_TT_ATLANTIS_H
> +
> +#include "hw/core/boards.h"
> +#include "hw/core/sysbus.h"
> +#include "hw/intc/riscv_imsic.h"
> +#include "hw/riscv/riscv_hart.h"
> +
> +#define TYPE_TT_ATLANTIS_MACHINE MACHINE_TYPE_NAME("tt-atlantis")
> +OBJECT_DECLARE_SIMPLE_TYPE(TTAtlantisState, TT_ATLANTIS_MACHINE)
> +
> +struct TTAtlantisState {
> + /*< private >*/
> + MachineState parent;
> +
> + /*< public >*/
> + Notifier machine_done;
> + FWCfgState *fw_cfg;
> + const MemMapEntry *memmap;
> +
> + RISCVHartArrayState soc;
> + DeviceState *irqchip;
> +
> + int fdt_size;
> +};
> +
> +enum {
> + TT_ATL_SYSCON_IRQ = 10,
> + TT_ATL_UART0_IRQ = 38,
> + TT_ATL_UART1_IRQ = 39,
> + TT_ATL_UART2_IRQ = 40,
> + TT_ATL_UART3_IRQ = 41,
> + TT_ATL_UART4_IRQ = 42,
> +};
> +
> +enum {
> + TT_ATL_ACLINT,
> + TT_ATL_BOOTROM,
> + TT_ATL_DDR_LO,
> + TT_ATL_DDR_HI,
> + TT_ATL_FW_CFG,
> + TT_ATL_I2C0,
> + TT_ATL_MAPLIC,
> + TT_ATL_MIMSIC,
> + TT_ATL_PCIE_ECAM0,
> + TT_ATL_PCIE_ECAM1,
> + TT_ATL_PCIE_ECAM2,
> + TT_ATL_PCIE_MMIO0,
> + TT_ATL_PCIE_PIO0,
> + TT_ATL_PCIE_MMIO0_32,
> + TT_ATL_PCIE_MMIO0_64,
> + TT_ATL_PCIE_MMIO1,
> + TT_ATL_PCIE_PIO1,
> + TT_ATL_PCIE_MMIO1_32,
> + TT_ATL_PCIE_MMIO1_64,
> + TT_ATL_PCIE_MMIO2,
> + TT_ATL_PCIE_PIO2,
> + TT_ATL_PCIE_MMIO2_32,
> + TT_ATL_PCIE_MMIO2_64,
> + TT_ATL_PCI_MMU_CFG,
> + TT_ATL_SAPLIC,
> + TT_ATL_SIMSIC,
> + TT_ATL_SYSCON,
> + TT_ATL_TIMER,
> + TT_ATL_UART0,
> + TT_ATL_WDT0,
> +};
> +
> +#endif
> diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
> new file mode 100644
> index 000000000000..a556ff79c8c8
> --- /dev/null
> +++ b/hw/riscv/tt_atlantis.c
> @@ -0,0 +1,638 @@
> +/*
> + * Tenstorrent Atlantis RISC-V System on Chip
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * Copyright 2025 Tenstorrent, Joel Stanley <joel@jms.id.au>
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/cutils.h"
> +#include "qemu/error-report.h"
> +#include "qemu/guest-random.h"
> +#include "qemu/units.h"
> +
> +#include "hw/core/boards.h"
> +#include "hw/core/loader.h"
> +#include "hw/core/sysbus.h"
> +
> +#include "target/riscv/cpu.h"
> +#include "target/riscv/pmu.h"
> +
> +#include "hw/riscv/boot.h"
> +#include "hw/riscv/numa.h"
> +#include "hw/riscv/riscv_hart.h"
> +
> +#include "hw/char/serial-mm.h"
> +#include "hw/intc/riscv_aclint.h"
> +#include "hw/intc/riscv_aplic.h"
> +#include "hw/misc/pvpanic.h"
> +
> +#include "system/system.h"
> +#include "system/device_tree.h"
> +
> +#include "hw/riscv/tt_atlantis.h"
> +
> +#include "aia.h"
> +
> +#define TT_IRQCHIP_NUM_MSIS 255
> +#define TT_IRQCHIP_NUM_SOURCES 128
> +#define TT_IRQCHIP_NUM_PRIO_BITS 3
> +#define TT_IRQCHIP_GUESTS 7 /* aia_guests */
> +
> +#define FDT_PCI_ADDR_CELLS 3
> +#define FDT_PCI_INT_CELLS 1
> +#define FDT_MAX_INT_CELLS 2
> +#define FDT_MAX_INT_MAP_WIDTH (FDT_PCI_ADDR_CELLS + FDT_PCI_INT_CELLS + \
> + 1 + FDT_MAX_INT_CELLS)
> +
> +#define TT_ACLINT_MTIME_SIZE 0x8050
> +#define TT_ACLINT_MTIME 0x0
> +#define TT_ACLINT_MTIMECMP 0x8000
> +#define TT_ACLINT_TIMEBASE_FREQ 1000000000
> +
> +static const MemMapEntry tt_atlantis_memmap[] = {
> + /* Keep sorted with :'<,'>!sort -g -k 4 */
> + [TT_ATL_DDR_LO] = { 0x00000000, 0x80000000 },
> + [TT_ATL_BOOTROM] = { 0x80000000, 0x2000 },
> + [TT_ATL_FW_CFG] = { 0x80002000, 0xff }, /* qemu only */
> + [TT_ATL_SYSCON] = { 0x80004000, 0x1000 }, /* qemu only */
> + [TT_ATL_MIMSIC] = { 0xa0000000, 0x200000 },
> + [TT_ATL_ACLINT] = { 0xa2180000, 0x10000 },
> + [TT_ATL_SIMSIC] = { 0xa4000000, 0x200000 },
> + [TT_ATL_TIMER] = { 0xa8020000, 0x10000 },
> + [TT_ATL_WDT0] = { 0xa8030000, 0x10000 },
> + [TT_ATL_UART0] = { 0xb0100000, 0x10000 },
> + [TT_ATL_MAPLIC] = { 0xcc000000, 0x4000000 },
> + [TT_ATL_SAPLIC] = { 0xe8000000, 0x4000000 },
> + [TT_ATL_DDR_HI] = { 0x100000000, 0x1000000000 },
> + [TT_ATL_PCIE_ECAM0] = { 0x01110000000, 0x10000000 },
> + [TT_ATL_PCIE_ECAM1] = { 0x01120000000, 0x10000000 },
> + [TT_ATL_PCIE_ECAM2] = { 0x01130000000, 0x10000000 },
> + [TT_ATL_PCIE_MMIO0] = { 0x10000000000, 0x10000000000 },
> + [TT_ATL_PCIE_MMIO1] = { 0x20000000000, 0x10000000000 },
> + [TT_ATL_PCIE_MMIO2] = { 0x30000000000, 0x10000000000 },
> +};
> +
> +static uint32_t next_phandle(void)
> +{
> + static uint32_t phandle = 1;
> + return phandle++;
> +}
> +
> +static void create_fdt_cpus(TTAtlantisState *s, uint32_t *intc_phandles)
> +{
> + uint32_t cpu_phandle;
> + void *fdt = MACHINE(s)->fdt;
> +
> + for (int cpu = s->soc.num_harts - 1; cpu >= 0; cpu--) {
> + RISCVCPU *cpu_ptr = &s->soc.harts[cpu];
> + g_autofree char *cpu_name = NULL;
> + g_autofree char *intc_name = NULL;
> +
> + cpu_phandle = next_phandle();
> +
> + cpu_name = g_strdup_printf("/cpus/cpu@%d", s->soc.hartid_base + cpu);
> + qemu_fdt_add_subnode(fdt, cpu_name);
> +
> + qemu_fdt_setprop_string(fdt, cpu_name, "mmu-type", "riscv,sv57");
> +
> + riscv_isa_write_fdt(cpu_ptr, fdt, cpu_name);
> +
> + qemu_fdt_setprop_cell(fdt, cpu_name, "riscv,cbom-block-size",
> + cpu_ptr->cfg.cbom_blocksize);
> +
> + qemu_fdt_setprop_cell(fdt, cpu_name, "riscv,cboz-block-size",
> + cpu_ptr->cfg.cboz_blocksize);
> +
> + qemu_fdt_setprop_cell(fdt, cpu_name, "riscv,cbop-block-size",
> + cpu_ptr->cfg.cbop_blocksize);
> +
> + qemu_fdt_setprop_string(fdt, cpu_name, "compatible", "riscv");
> + qemu_fdt_setprop_string(fdt, cpu_name, "status", "okay");
> + qemu_fdt_setprop_cell(fdt, cpu_name, "reg", s->soc.hartid_base + cpu);
> + qemu_fdt_setprop_string(fdt, cpu_name, "device_type", "cpu");
> + qemu_fdt_setprop_cell(fdt, cpu_name, "phandle", cpu_phandle);
> +
> + intc_phandles[cpu] = next_phandle();
> +
> + intc_name = g_strdup_printf("%s/interrupt-controller", cpu_name);
> + qemu_fdt_add_subnode(fdt, intc_name);
> + qemu_fdt_setprop_cell(fdt, intc_name, "phandle",
> + intc_phandles[cpu]);
> + qemu_fdt_setprop_string(fdt, intc_name, "compatible",
> + "riscv,cpu-intc");
> + qemu_fdt_setprop(fdt, intc_name, "interrupt-controller", NULL, 0);
> + qemu_fdt_setprop_cell(fdt, intc_name, "#interrupt-cells", 1);
> + }
> +}
> +
> +static void create_fdt_memory_node(TTAtlantisState *s,
> + hwaddr addr, hwaddr size)
> +{
> + void *fdt = MACHINE(s)->fdt;
> + g_autofree char *name = g_strdup_printf("/memory@%"HWADDR_PRIX, addr);
> + qemu_fdt_add_subnode(fdt, name);
> + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, addr, 2, size);
> + qemu_fdt_setprop_string(fdt, name, "device_type", "memory");
> +}
> +
> +static void create_fdt_memory(TTAtlantisState *s)
> +{
> + hwaddr size_lo = MACHINE(s)->ram_size;
> + hwaddr size_hi = 0;
> +
> + if (size_lo > s->memmap[TT_ATL_DDR_LO].size) {
> + size_lo = s->memmap[TT_ATL_DDR_LO].size;
> + size_hi = MACHINE(s)->ram_size - size_lo;
> + }
> +
> + create_fdt_memory_node(s, s->memmap[TT_ATL_DDR_LO].base, size_lo);
> + if (size_hi) {
> + /*
> + * The first part of the HI address is aliased at the LO address
> + * so do not include that as usable memory. Is there any way
> + * (or good reason) to describe that aliasing 2GB with DT?
> + */
> + create_fdt_memory_node(s, s->memmap[TT_ATL_DDR_HI].base + size_lo,
> + size_hi);
> + }
> +}
> +
> +static void create_fdt_aclint(TTAtlantisState *s, uint32_t *intc_phandles)
> +{
> + void *fdt = MACHINE(s)->fdt;
> + g_autofree char *name = NULL;
> + g_autofree uint32_t *aclint_mtimer_cells = NULL;
> + uint32_t aclint_cells_size;
> + hwaddr addr;
> +
> + aclint_mtimer_cells = g_new0(uint32_t, s->soc.num_harts * 2);
> +
> + for (int cpu = 0; cpu < s->soc.num_harts; cpu++) {
> + aclint_mtimer_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
> + aclint_mtimer_cells[cpu * 2 + 1] = cpu_to_be32(IRQ_M_TIMER);
> + }
> + aclint_cells_size = s->soc.num_harts * sizeof(uint32_t) * 2;
> +
> + addr = s->memmap[TT_ATL_ACLINT].base;
> +
> + name = g_strdup_printf("/soc/mtimer@%"HWADDR_PRIX, addr);
> + qemu_fdt_add_subnode(fdt, name);
> + qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,aclint-mtimer");
> + qemu_fdt_setprop_sized_cells(fdt, name, "reg",
> + 2, addr + TT_ACLINT_MTIME,
> + 2, 0x1000,
> + 2, addr + TT_ACLINT_MTIMECMP,
> + 2, 0x1000);
> + qemu_fdt_setprop(fdt, name, "interrupts-extended",
> + aclint_mtimer_cells, aclint_cells_size);
> +}
> +
> +static void create_fdt_one_imsic(void *fdt, const MemMapEntry *mem, int cpus,
> + uint32_t *intc_phandles, uint32_t msi_phandle,
> + int irq_line, uint32_t imsic_guest_bits)
> +{
> + g_autofree char *name = NULL;
> + g_autofree uint32_t *imsic_cells = g_new0(uint32_t, cpus * 2);
> +
> + for (int cpu = 0; cpu < cpus; cpu++) {
> + imsic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
> + imsic_cells[cpu * 2 + 1] = cpu_to_be32(irq_line);
> + }
> +
> + name = g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIX, mem->base);
> + qemu_fdt_add_subnode(fdt, name);
> + qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,imsics");
> +
> + qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", 0);
> + qemu_fdt_setprop(fdt, name, "interrupt-controller", NULL, 0);
> + qemu_fdt_setprop(fdt, name, "msi-controller", NULL, 0);
> + qemu_fdt_setprop(fdt, name, "interrupts-extended",
> + imsic_cells, sizeof(uint32_t) * cpus * 2);
> + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
> + qemu_fdt_setprop_cell(fdt, name, "riscv,num-ids", TT_IRQCHIP_NUM_MSIS);
> +
> + if (imsic_guest_bits) {
> + qemu_fdt_setprop_cell(fdt, name, "riscv,guest-index-bits",
> + imsic_guest_bits);
> + }
> + qemu_fdt_setprop_cell(fdt, name, "phandle", msi_phandle);
> +}
> +
> +static void create_fdt_one_aplic(void *fdt,
> + const MemMapEntry *mem,
> + uint32_t msi_phandle,
> + uint32_t *intc_phandles,
> + uint32_t aplic_phandle,
> + uint32_t aplic_child_phandle,
> + int irq_line, int num_harts)
> +{
> + g_autofree char *name =
> + g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIX, mem->base);
> + g_autofree uint32_t *aplic_cells = g_new0(uint32_t, num_harts * 2);
> +
> + for (int cpu = 0; cpu < num_harts; cpu++) {
> + aplic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]);
> + aplic_cells[cpu * 2 + 1] = cpu_to_be32(irq_line);
> + }
> +
> + qemu_fdt_add_subnode(fdt, name);
> + qemu_fdt_setprop_string(fdt, name, "compatible", "riscv,aplic");
> + qemu_fdt_setprop_cell(fdt, name, "#address-cells", 0);
> + qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", 2);
> + qemu_fdt_setprop(fdt, name, "interrupt-controller", NULL, 0);
> +
> + qemu_fdt_setprop(fdt, name, "interrupts-extended",
> + aplic_cells, num_harts * sizeof(uint32_t) * 2);
> + qemu_fdt_setprop_cell(fdt, name, "msi-parent", msi_phandle);
> +
> + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
> + qemu_fdt_setprop_cell(fdt, name, "riscv,num-sources",
> + TT_IRQCHIP_NUM_SOURCES);
> +
> + if (aplic_child_phandle) {
> + qemu_fdt_setprop_cell(fdt, name, "riscv,children",
> + aplic_child_phandle);
> + qemu_fdt_setprop_cells(fdt, name, "riscv,delegation",
> + aplic_child_phandle, 1, TT_IRQCHIP_NUM_SOURCES);
> + }
> +
> + qemu_fdt_setprop_cell(fdt, name, "phandle", aplic_phandle);
> +}
> +
> +static void create_fdt_pmu(TTAtlantisState *s)
> +{
> + g_autofree char *pmu_name = g_strdup_printf("/pmu");
> + void *fdt = MACHINE(s)->fdt;
> + RISCVCPU *hart = &s->soc.harts[0];
> +
> + qemu_fdt_add_subnode(fdt, pmu_name);
> + qemu_fdt_setprop_string(fdt, pmu_name, "compatible", "riscv,pmu");
> + riscv_pmu_generate_fdt_node(fdt, hart->pmu_avail_ctrs, pmu_name);
> +}
> +
> +static void create_fdt_cpu(TTAtlantisState *s, const MemMapEntry *memmap,
> + uint32_t aplic_s_phandle,
> + uint32_t imsic_s_phandle)
> +{
> + MachineState *ms = MACHINE(s);
> + void *fdt = MACHINE(s)->fdt;
> + g_autofree uint32_t *intc_phandles = NULL;
> +
> + qemu_fdt_add_subnode(fdt, "/cpus");
> + qemu_fdt_setprop_cell(fdt, "/cpus", "timebase-frequency",
> + TT_ACLINT_TIMEBASE_FREQ);
> + qemu_fdt_setprop_cell(fdt, "/cpus", "#size-cells", 0x0);
> + qemu_fdt_setprop_cell(fdt, "/cpus", "#address-cells", 0x1);
> +
> + intc_phandles = g_new0(uint32_t, ms->smp.cpus);
> +
> + create_fdt_cpus(s, intc_phandles);
> +
> + create_fdt_memory(s);
> +
> + create_fdt_aclint(s, intc_phandles);
> +
> + /* M-level IMSIC node */
> + uint32_t msi_m_phandle = next_phandle();
> + create_fdt_one_imsic(fdt, &s->memmap[TT_ATL_MIMSIC], ms->smp.cpus,
> + intc_phandles, msi_m_phandle,
> + IRQ_M_EXT, 0);
> +
> + /* S-level IMSIC node */
> + create_fdt_one_imsic(fdt, &s->memmap[TT_ATL_SIMSIC], ms->smp.cpus,
> + intc_phandles, imsic_s_phandle,
> + IRQ_S_EXT, imsic_num_bits(TT_IRQCHIP_GUESTS + 1));
> +
> + uint32_t aplic_m_phandle = next_phandle();
> +
> + /* M-level APLIC node */
> + create_fdt_one_aplic(fdt, &s->memmap[TT_ATL_MAPLIC],
> + msi_m_phandle, intc_phandles,
> + aplic_m_phandle, aplic_s_phandle,
> + IRQ_M_EXT, s->soc.num_harts);
> +
> + /* S-level APLIC node */
> + create_fdt_one_aplic(fdt, &s->memmap[TT_ATL_SAPLIC],
> + imsic_s_phandle, intc_phandles,
> + aplic_s_phandle, 0,
> + IRQ_S_EXT, s->soc.num_harts);
> +}
> +
> +static void create_fdt_reset(void *fdt, const MemMapEntry *mem)
> +{
> + uint32_t syscon_phandle = next_phandle();
> + char *name;
> +
> + name = g_strdup_printf("/soc/syscon@%"HWADDR_PRIX, mem->base);
> + qemu_fdt_add_subnode(fdt, name);
> + qemu_fdt_setprop_string(fdt, name, "compatible", "syscon");
> + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
> + qemu_fdt_setprop_cell(fdt, name, "phandle", syscon_phandle);
> + g_free(name);
> +
> + name = g_strdup_printf("/poweroff");
> + qemu_fdt_add_subnode(fdt, name);
> + qemu_fdt_setprop_string(fdt, name, "compatible", "syscon-poweroff");
> + qemu_fdt_setprop_cell(fdt, name, "regmap", syscon_phandle);
> + qemu_fdt_setprop_cell(fdt, name, "offset", 0x0);
> + qemu_fdt_setprop_cell(fdt, name, "value", PVPANIC_SHUTDOWN);
> + g_free(name);
> +}
> +
> +static void create_fdt_uart(void *fdt, const MemMapEntry *mem, int irq,
> + int irqchip_phandle)
> +{
> + g_autofree char *name = g_strdup_printf("/soc/serial@%"HWADDR_PRIX,
> + mem->base);
> +
> + qemu_fdt_add_subnode(fdt, name);
> + qemu_fdt_setprop_string(fdt, name, "compatible", "ns16550a");
> + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
> + qemu_fdt_setprop_cell(fdt, name, "reg-shift", 2);
> + qemu_fdt_setprop_cell(fdt, name, "reg-io-width", 4);
> + qemu_fdt_setprop_cell(fdt, name, "clock-frequency", 3686400);
> + qemu_fdt_setprop_cell(fdt, name, "interrupt-parent", irqchip_phandle);
> + qemu_fdt_setprop_cells(fdt, name, "interrupts", irq, 0x4);
> +
> + qemu_fdt_setprop_string(fdt, "/chosen", "stdout-path", name);
> + qemu_fdt_setprop_string(fdt, "/aliases", "serial0", name);
> +}
> +
> +static void create_fdt_fw_cfg(void *fdt, const MemMapEntry *mem)
> +{
> + g_autofree char *name = g_strdup_printf("/fw-cfg@%"HWADDR_PRIX, mem->base);
> +
> + qemu_fdt_add_subnode(fdt, name);
> + qemu_fdt_setprop_string(fdt, name, "compatible", "qemu,fw-cfg-mmio");
> + qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
> + qemu_fdt_setprop(fdt, name, "dma-coherent", NULL, 0);
> +}
> +
> +static void finalize_fdt(TTAtlantisState *s)
> +{
> + uint32_t aplic_s_phandle = next_phandle();
> + uint32_t imsic_s_phandle = next_phandle();
> + void *fdt = MACHINE(s)->fdt;
> +
> + create_fdt_cpu(s, s->memmap, aplic_s_phandle, imsic_s_phandle);
> +
> + /*
> + * We want to do this, but the Linux aplic driver was broken before v6.16
> + *
> + * qemu_fdt_setprop_cell(MACHINE(s)->fdt, "/soc", "interrupt-parent",
> + * aplic_s_phandle);
> + */
> +
> + create_fdt_reset(fdt, &s->memmap[TT_ATL_SYSCON]);
> +
> + create_fdt_uart(fdt, &s->memmap[TT_ATL_UART0], TT_ATL_UART0_IRQ,
> + aplic_s_phandle);
> +}
> +
> +static void create_fdt(TTAtlantisState *s)
> +{
> + MachineState *ms = MACHINE(s);
> + uint8_t rng_seed[32];
> + g_autofree char *name = NULL;
> + void *fdt;
> +
> + fdt = create_device_tree(&s->fdt_size);
> + if (!fdt) {
> + error_report("create_device_tree() failed");
> + exit(1);
> + }
> + ms->fdt = fdt;
> +
> + qemu_fdt_setprop_string(fdt, "/", "model",
> + "Tenstorrent Atlantis RISC-V Machine");
> + qemu_fdt_setprop_string(fdt, "/", "compatible", "tenstorrent,atlantis");
> + qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x2);
> + qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x2);
> +
> + qemu_fdt_add_subnode(fdt, "/soc");
> + qemu_fdt_setprop(fdt, "/soc", "ranges", NULL, 0);
> + qemu_fdt_setprop_string(fdt, "/soc", "compatible", "simple-bus");
> + qemu_fdt_setprop_cell(fdt, "/soc", "#size-cells", 0x2);
> + qemu_fdt_setprop_cell(fdt, "/soc", "#address-cells", 0x2);
> +
> + qemu_fdt_add_subnode(fdt, "/chosen");
> +
> + /* Pass seed to RNG */
> + qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed));
> + qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed));
> +
> + qemu_fdt_add_subnode(fdt, "/aliases");
> +
> + create_fdt_fw_cfg(fdt, &s->memmap[TT_ATL_FW_CFG]);
> + create_fdt_pmu(s);
> +}
> +
> +static DeviceState *create_reboot_device(const MemMapEntry *mem)
> +{
> + DeviceState *dev = qdev_new(TYPE_PVPANIC_MMIO_DEVICE);
> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +
> + qdev_prop_set_uint32(dev, "events", PVPANIC_SHUTDOWN | PVPANIC_PANICKED);
> +
> + sysbus_realize_and_unref(sbd, &error_fatal);
> + sysbus_mmio_map(sbd, 0, mem->base);
> +
> + return dev;
> +}
> +
> +static FWCfgState *create_fw_cfg(const MemMapEntry *mem, int num_cpus)
> +{
> + FWCfgState *fw_cfg;
> + hwaddr base = mem->base;
> +
> + fw_cfg = fw_cfg_init_mem_dma(base + 8, base, 8, base + 16,
> + &address_space_memory);
> + fw_cfg_add_i16(fw_cfg, FW_CFG_NB_CPUS, num_cpus);
> +
> + return fw_cfg;
> +}
> +
> +static void tt_atlantis_machine_done(Notifier *notifier, void *data)
> +{
> + TTAtlantisState *s = container_of(notifier, TTAtlantisState, machine_done);
> + MachineState *machine = MACHINE(s);
> + hwaddr start_addr = s->memmap[TT_ATL_DDR_LO].base;
> + hwaddr mem_size;
> + target_ulong firmware_end_addr, kernel_start_addr;
> + const char *firmware_name = riscv_default_firmware_name(&s->soc);
> + uint64_t fdt_load_addr;
> + uint64_t kernel_entry;
> + RISCVBootInfo boot_info;
> +
> + /*
> + * A user provided dtb must include everything, including
> + * dynamic sysbus devices. Our FDT needs to be finalized.
> + */
> + if (machine->dtb == NULL) {
> + finalize_fdt(s);
> + }
> +
> + mem_size = machine->ram_size;
> + if (mem_size > s->memmap[TT_ATL_DDR_LO].size) {
> + mem_size = s->memmap[TT_ATL_DDR_LO].size;
> + }
> + riscv_boot_info_init_discontig_mem(&boot_info, &s->soc,
> + s->memmap[TT_ATL_DDR_LO].base,
> + mem_size);
> +
> + firmware_end_addr = riscv_find_and_load_firmware(machine, &boot_info,
> + firmware_name,
> + &start_addr, NULL);
> +
> + kernel_start_addr = riscv_calc_kernel_start_addr(&boot_info,
> + firmware_end_addr);
> + if (machine->kernel_filename) {
> + riscv_load_kernel(machine, &boot_info, kernel_start_addr,
> + true, NULL);
> + } else {
> + riscv_setup_halting_payload(&boot_info, kernel_start_addr);
> + }
> + kernel_entry = boot_info.image_low_addr;
> +
> + fdt_load_addr = riscv_compute_fdt_addr(s->memmap[TT_ATL_DDR_LO].base,
> + s->memmap[TT_ATL_DDR_LO].size,
> + machine, &boot_info);
> + riscv_load_fdt(fdt_load_addr, machine->fdt);
> +
> + /* load the reset vector */
> + riscv_setup_rom_reset_vec(machine, &s->soc, start_addr,
> + s->memmap[TT_ATL_BOOTROM].base,
> + s->memmap[TT_ATL_BOOTROM].size,
> + kernel_entry,
> + fdt_load_addr);
> +
> +}
> +
> +static void tt_atlantis_machine_init(MachineState *machine)
> +{
> + TTAtlantisState *s = TT_ATLANTIS_MACHINE(machine);
> +
> + MemoryRegion *system_memory = get_system_memory();
> + MemoryRegion *ram_hi = g_new(MemoryRegion, 1);
> + MemoryRegion *ram_lo = g_new(MemoryRegion, 1);
> + MemoryRegion *bootrom = g_new(MemoryRegion, 1);
> + ram_addr_t lo_ram_size, hi_ram_size;
> + int hart_count = machine->smp.cpus;
> + int base_hartid = 0;
> +
> + s->memmap = tt_atlantis_memmap;
> +
> + object_initialize_child(OBJECT(machine), "soc", &s->soc,
> + TYPE_RISCV_HART_ARRAY);
> + object_property_set_str(OBJECT(&s->soc), "cpu-type", machine->cpu_type,
> + &error_abort);
> + object_property_set_int(OBJECT(&s->soc), "hartid-base", base_hartid,
> + &error_abort);
> + object_property_set_int(OBJECT(&s->soc), "num-harts", hart_count,
> + &error_abort);
> + object_property_set_int(OBJECT(&s->soc), "resetvec",
> + s->memmap[TT_ATL_BOOTROM].base,
> + &error_abort);
> + sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal);
> +
> + s->irqchip = riscv_create_aia(true, TT_IRQCHIP_GUESTS,
> + TT_IRQCHIP_NUM_SOURCES,
> + &s->memmap[TT_ATL_MAPLIC],
> + &s->memmap[TT_ATL_SAPLIC],
> + &s->memmap[TT_ATL_MIMSIC],
> + &s->memmap[TT_ATL_SIMSIC],
> + 0, base_hartid, hart_count,
> + TT_IRQCHIP_NUM_MSIS,
> + TT_IRQCHIP_NUM_PRIO_BITS);
> +
> + riscv_aclint_mtimer_create(s->memmap[TT_ATL_ACLINT].base,
> + TT_ACLINT_MTIME_SIZE,
> + base_hartid, hart_count,
> + TT_ACLINT_MTIMECMP,
> + TT_ACLINT_MTIME,
> + TT_ACLINT_TIMEBASE_FREQ, true);
> +
> + /* DDR */
> +
> + /* The high address covers all of RAM, the low address just the first 2GB */
> + lo_ram_size = s->memmap[TT_ATL_DDR_LO].size;
> + hi_ram_size = s->memmap[TT_ATL_DDR_HI].size;
> + if (machine->ram_size > hi_ram_size) {
> + char *sz = size_to_str(hi_ram_size);
> + error_report("RAM size is too large, maximum is %s", sz);
> + g_free(sz);
> + exit(EXIT_FAILURE);
> + }
> +
> + memory_region_init_alias(ram_lo, OBJECT(machine), "ram.low", machine->ram,
> + 0, lo_ram_size);
> + memory_region_init_alias(ram_hi, OBJECT(machine), "ram.high", machine->ram,
> + 0, hi_ram_size);
> + memory_region_add_subregion(system_memory,
> + s->memmap[TT_ATL_DDR_LO].base, ram_lo);
> + memory_region_add_subregion(system_memory,
> + s->memmap[TT_ATL_DDR_HI].base, ram_hi);
> +
> + /* Boot ROM */
> + memory_region_init_rom(bootrom, NULL, "tt-atlantis.bootrom",
> + s->memmap[TT_ATL_BOOTROM].size, &error_fatal);
> + memory_region_add_subregion(system_memory, s->memmap[TT_ATL_BOOTROM].base,
> + bootrom);
> +
> + /*
> + * Init fw_cfg. Must be done before riscv_load_fdt, otherwise the
> + * device tree cannot be altered and we get FDT_ERR_NOSPACE.
> + */
> + s->fw_cfg = create_fw_cfg(&s->memmap[TT_ATL_FW_CFG], machine->smp.cpus);
> + rom_set_fw(s->fw_cfg);
> +
> + /* Reboot and exit */
> + create_reboot_device(&s->memmap[TT_ATL_SYSCON]);
> +
> + /* UART */
> + serial_mm_init(system_memory, s->memmap[TT_ATL_UART0].base, 2,
> + qdev_get_gpio_in(s->irqchip, TT_ATL_UART0_IRQ),
> + 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN);
> +
> + /* Load or create device tree */
> + if (machine->dtb) {
> + machine->fdt = load_device_tree(machine->dtb, &s->fdt_size);
> + if (!machine->fdt) {
> + error_report("load_device_tree() failed");
> + exit(1);
> + }
> + } else {
> + create_fdt(s);
> + }
> +
> + s->machine_done.notify = tt_atlantis_machine_done;
> + qemu_add_machine_init_done_notifier(&s->machine_done);
> +}
> +
> +static void tt_atlantis_machine_class_init(ObjectClass *oc, const void *data)
> +{
> + MachineClass *mc = MACHINE_CLASS(oc);
> +
> + mc->desc = "Tenstorrent Atlantis RISC-V SoC";
> + mc->init = tt_atlantis_machine_init;
> + mc->max_cpus = 8;
> + mc->default_cpus = 8;
> + mc->default_ram_size = 2 * GiB;
> + mc->default_cpu_type = TYPE_RISCV_CPU_TT_ASCALON;
> + mc->block_default_type = IF_VIRTIO;
> + mc->no_cdrom = 1;
> + mc->default_ram_id = "tt_atlantis.ram";
> +}
> +
> +static const TypeInfo tt_atlantis_types[] = {
> + {
> + .name = MACHINE_TYPE_NAME("tt-atlantis"),
> + .parent = TYPE_MACHINE,
> + .class_init = tt_atlantis_machine_class_init,
> + .instance_size = sizeof(TTAtlantisState),
> + },
> +};
> +
> +DEFINE_TYPES(tt_atlantis_types)
> diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> index 0222c93f878b..0601ae1a7494 100644
> --- a/hw/riscv/Kconfig
> +++ b/hw/riscv/Kconfig
> @@ -120,6 +120,21 @@ config SPIKE
> select RISCV_ACLINT
> select SIFIVE_PLIC
>
> +config TENSTORRENT
> + bool
> + default y
> + depends on RISCV64
> + imply PCI_DEVICES
> + imply TEST_DEVICES
> + select DEVICE_TREE
> + select RISCV_NUMA
> + select PVPANIC_MMIO
> + select SERIAL_MM
> + select RISCV_ACLINT
> + select RISCV_APLIC
> + select RISCV_IMSIC
> + select FW_CFG_DMA
> +
> config XIANGSHAN_KUNMINGHU
> bool
> default y
> diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
> index e53c180d0d10..026e79591f4b 100644
> --- a/hw/riscv/meson.build
> +++ b/hw/riscv/meson.build
> @@ -9,6 +9,7 @@ riscv_ss.add(when: 'CONFIG_SIFIVE_E', if_true: files('sifive_e.c'))
> riscv_ss.add(when: 'CONFIG_SIFIVE_U', if_true: files('sifive_u.c'))
> riscv_ss.add(when: 'CONFIG_SPIKE', if_true: files('spike.c'))
> riscv_ss.add(when: 'CONFIG_MICROCHIP_PFSOC', if_true: files('microchip_pfsoc.c'))
> +riscv_ss.add(when: 'CONFIG_TENSTORRENT', if_true: files('tt_atlantis.c'))
> riscv_ss.add(when: 'CONFIG_ACPI', if_true: files('virt-acpi-build.c'))
> riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
> 'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
> --
> 2.47.3
>
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 10/13] hw/riscv/atlantis: Add PCIe controller
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
` (8 preceding siblings ...)
2026-04-21 5:31 ` [PATCH v3 09/13] hw/riscv: Add Tenstorrent Atlantis machine Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-21 5:59 ` Philippe Mathieu-Daudé
2026-04-21 5:31 ` [PATCH v3 11/13] tests/functional/riscv64: Add tt-atlantis tests Joel Stanley
` (2 subsequent siblings)
12 siblings, 1 reply; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Nicholas Piggin, Michael Ellerman, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
From: Nicholas Piggin <npiggin@gmail.com>
tt-atlantis is likely to use a generic ECAM compatible PCIe memory map,
so gpex is not far off the OS programming model
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
v3: Avoid leaks in the dt string allocation
---
include/hw/riscv/tt_atlantis.h | 2 +
hw/riscv/tt_atlantis.c | 225 ++++++++++++++++++++++++++++++++-
hw/riscv/Kconfig | 2 +
3 files changed, 228 insertions(+), 1 deletion(-)
diff --git a/include/hw/riscv/tt_atlantis.h b/include/hw/riscv/tt_atlantis.h
index f7f79e9f2c53..c0a0827a5167 100644
--- a/include/hw/riscv/tt_atlantis.h
+++ b/include/hw/riscv/tt_atlantis.h
@@ -28,6 +28,7 @@ struct TTAtlantisState {
RISCVHartArrayState soc;
DeviceState *irqchip;
+ GPEXHost gpex_host;
int fdt_size;
};
@@ -39,6 +40,7 @@ enum {
TT_ATL_UART2_IRQ = 40,
TT_ATL_UART3_IRQ = 41,
TT_ATL_UART4_IRQ = 42,
+ TT_ATL_PCIE0_INTA_IRQ = 96,
};
enum {
diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
index a556ff79c8c8..e1753d3c1f18 100644
--- a/hw/riscv/tt_atlantis.c
+++ b/hw/riscv/tt_atlantis.c
@@ -27,6 +27,7 @@
#include "hw/intc/riscv_aclint.h"
#include "hw/intc/riscv_aplic.h"
#include "hw/misc/pvpanic.h"
+#include "hw/pci-host/gpex.h"
#include "system/system.h"
#include "system/device_tree.h"
@@ -70,6 +71,9 @@ static const MemMapEntry tt_atlantis_memmap[] = {
[TT_ATL_PCIE_ECAM1] = { 0x01120000000, 0x10000000 },
[TT_ATL_PCIE_ECAM2] = { 0x01130000000, 0x10000000 },
[TT_ATL_PCIE_MMIO0] = { 0x10000000000, 0x10000000000 },
+ [TT_ATL_PCIE_PIO0] = { 0x10000000000, 0x10000 }, /* qemu only */
+ [TT_ATL_PCIE_MMIO0_32] = { 0x10004000000, 0x4000000 }, /* qemu only */
+ [TT_ATL_PCIE_MMIO0_64] = { 0x10010000000, 0x0fff0000000 }, /* qemu only */
[TT_ATL_PCIE_MMIO1] = { 0x20000000000, 0x10000000000 },
[TT_ATL_PCIE_MMIO2] = { 0x30000000000, 0x10000000000 },
};
@@ -80,6 +84,59 @@ static uint32_t next_phandle(void)
return phandle++;
}
+static void create_pcie_irq_map(void *fdt, char *nodename, int legacy_irq,
+ uint32_t irqchip_phandle)
+{
+ int pin, dev;
+ uint32_t irq_map_stride = 0;
+ uint32_t full_irq_map[PCI_NUM_PINS * PCI_NUM_PINS *
+ FDT_MAX_INT_MAP_WIDTH] = {};
+ uint32_t *irq_map = full_irq_map;
+
+ /*
+ * This code creates a standard swizzle of interrupts such that
+ * each device's first interrupt is based on it's PCI_SLOT number.
+ * (See pci_swizzle_map_irq_fn())
+ *
+ * We only need one entry per interrupt in the table (not one per
+ * possible slot) seeing the interrupt-map-mask will allow the table
+ * to wrap to any number of devices.
+ */
+ for (dev = 0; dev < PCI_NUM_PINS; dev++) {
+ int devfn = dev * 0x8;
+
+ for (pin = 0; pin < PCI_NUM_PINS; pin++) {
+ int irq_nr = legacy_irq + ((pin + PCI_SLOT(devfn)) % PCI_NUM_PINS);
+ int i = 0;
+
+ /* Fill PCI address cells */
+ irq_map[i] = cpu_to_be32(devfn << 8);
+ i += FDT_PCI_ADDR_CELLS;
+
+ /* Fill PCI Interrupt cells */
+ irq_map[i] = cpu_to_be32(pin + 1);
+ i += FDT_PCI_INT_CELLS;
+
+ /* Fill interrupt controller phandle and cells */
+ irq_map[i++] = cpu_to_be32(irqchip_phandle);
+ irq_map[i++] = cpu_to_be32(irq_nr);
+ irq_map[i++] = cpu_to_be32(0x4);
+
+ if (!irq_map_stride) {
+ irq_map_stride = i;
+ }
+ irq_map += irq_map_stride;
+ }
+ }
+
+ qemu_fdt_setprop(fdt, nodename, "interrupt-map", full_irq_map,
+ PCI_NUM_PINS * PCI_NUM_PINS *
+ irq_map_stride * sizeof(uint32_t));
+
+ qemu_fdt_setprop_cells(fdt, nodename, "interrupt-map-mask",
+ 0x1800, 0, 0, 0x7);
+}
+
static void create_fdt_cpus(TTAtlantisState *s, uint32_t *intc_phandles)
{
uint32_t cpu_phandle;
@@ -320,6 +377,54 @@ static void create_fdt_cpu(TTAtlantisState *s, const MemMapEntry *memmap,
IRQ_S_EXT, s->soc.num_harts);
}
+static void create_fdt_pcie(void *fdt,
+ const MemMapEntry *mem_ecam,
+ const MemMapEntry *mem_pio,
+ const MemMapEntry *mem_mmio32,
+ const MemMapEntry *mem_mmio64,
+ int legacy_irq,
+ uint32_t aplic_s_phandle,
+ uint32_t imsic_s_phandle)
+{
+ g_autofree char *name = g_strdup_printf("/soc/pci@%"HWADDR_PRIX,
+ mem_ecam->base);
+
+ qemu_fdt_setprop_cell(fdt, name, "#address-cells", FDT_PCI_ADDR_CELLS);
+ qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", FDT_PCI_INT_CELLS);
+ qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0x2);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "pci-host-ecam-generic");
+ qemu_fdt_setprop_string(fdt, name, "device_type", "pci");
+ qemu_fdt_setprop_cells(fdt, name, "bus-range", 0,
+ mem_ecam->size / PCIE_MMCFG_SIZE_MIN - 1);
+ qemu_fdt_setprop(fdt, name, "dma-coherent", NULL, 0);
+ qemu_fdt_setprop_cell(fdt, name, "msi-parent", imsic_s_phandle);
+
+ qemu_fdt_setprop_sized_cells(fdt, name, "reg",
+ 2, mem_ecam->base,
+ 2, mem_ecam->size);
+ if (!(mem_mmio32->base & 0xffffffffUL)) {
+ /* XXX: this is a silly hack because it would collide with PIO */
+ error_report("mmio32 base must not be 0 mod 2^32");
+ exit(1);
+ }
+ uint32_t flags = FDT_PCI_RANGE_MMIO_64BIT | FDT_PCI_RANGE_PREFETCHABLE;
+ qemu_fdt_setprop_sized_cells(fdt, name, "ranges",
+ 1, FDT_PCI_RANGE_IOPORT,
+ 2, 0x0,
+ 2, mem_pio->base,
+ 2, mem_pio->size,
+ 1, FDT_PCI_RANGE_MMIO,
+ 2, (mem_mmio32->base & 0xffffffffUL),
+ 2, mem_mmio32->base,
+ 2, mem_mmio32->size,
+ 1, flags,
+ 2, mem_mmio64->base,
+ 2, mem_mmio64->base,
+ 2, mem_mmio64->size);
+
+ create_pcie_irq_map(fdt, name, legacy_irq, aplic_s_phandle);
+}
+
static void create_fdt_reset(void *fdt, const MemMapEntry *mem)
{
uint32_t syscon_phandle = next_phandle();
@@ -385,6 +490,14 @@ static void finalize_fdt(TTAtlantisState *s)
* aplic_s_phandle);
*/
+ create_fdt_pcie(fdt,
+ &s->memmap[TT_ATL_PCIE_ECAM0],
+ &s->memmap[TT_ATL_PCIE_PIO0],
+ &s->memmap[TT_ATL_PCIE_MMIO0_32],
+ &s->memmap[TT_ATL_PCIE_MMIO0_64],
+ TT_ATL_PCIE0_INTA_IRQ,
+ aplic_s_phandle, imsic_s_phandle);
+
create_fdt_reset(fdt, &s->memmap[TT_ATL_SYSCON]);
create_fdt_uart(fdt, &s->memmap[TT_ATL_UART0], TT_ATL_UART0_IRQ,
@@ -395,7 +508,7 @@ static void create_fdt(TTAtlantisState *s)
{
MachineState *ms = MACHINE(s);
uint8_t rng_seed[32];
- g_autofree char *name = NULL;
+ char *name;
void *fdt;
fdt = create_device_tree(&s->fdt_size);
@@ -417,6 +530,25 @@ static void create_fdt(TTAtlantisState *s)
qemu_fdt_setprop_cell(fdt, "/soc", "#size-cells", 0x2);
qemu_fdt_setprop_cell(fdt, "/soc", "#address-cells", 0x2);
+ /*
+ * The "/soc/pci@..." node is needed for PCIE hotplugs
+ * that might happen before finalize_fdt().
+ */
+ name = g_strdup_printf("/soc/pci@%"HWADDR_PRIX,
+ s->memmap[TT_ATL_PCIE_ECAM0].base);
+ qemu_fdt_add_subnode(fdt, name);
+ g_free(name);
+
+ name = g_strdup_printf("/soc/pci@%"HWADDR_PRIX,
+ s->memmap[TT_ATL_PCIE_ECAM1].base);
+ qemu_fdt_add_subnode(fdt, name);
+ g_free(name);
+
+ name = g_strdup_printf("/soc/pci@%"HWADDR_PRIX,
+ s->memmap[TT_ATL_PCIE_ECAM2].base);
+ qemu_fdt_add_subnode(fdt, name);
+ g_free(name);
+
qemu_fdt_add_subnode(fdt, "/chosen");
/* Pass seed to RNG */
@@ -429,6 +561,93 @@ static void create_fdt(TTAtlantisState *s)
create_fdt_pmu(s);
}
+static void gpex_pcie_init_one(TTAtlantisState *s, GPEXHost *gpex_host,
+ MemoryRegion *mr,
+ const MemMapEntry *mem_ecam,
+ const MemMapEntry *mem_pio,
+ const MemMapEntry *mem_mmio32,
+ const MemMapEntry *mem_mmio64,
+ int legacy_irq)
+{
+ DeviceState *dev;
+ Object *obj;
+ MemoryRegion *ecam_alias, *ecam_reg;
+ MemoryRegion *mmio32_alias, *mmio64_alias, *mmio_reg;
+ hwaddr ecam_base = mem_ecam->base;
+ hwaddr ecam_size = mem_ecam->size;
+ hwaddr pio_base = mem_pio->base;
+ hwaddr pio_size = mem_pio->size;
+ hwaddr mmio32_base = mem_mmio32->base;
+ hwaddr mmio32_size = mem_mmio32->size;
+ hwaddr mmio64_base = mem_mmio64->base;
+ hwaddr mmio64_size = mem_mmio64->size;
+ qemu_irq irq;
+ char name[16];
+ int i;
+
+ snprintf(name, sizeof(name), "pcie");
+ object_initialize_child(OBJECT(s), name, gpex_host, TYPE_GPEX_HOST);
+ dev = DEVICE(gpex_host);
+ obj = OBJECT(dev);
+
+ object_property_set_uint(obj, PCI_HOST_ECAM_BASE, ecam_base, &error_abort);
+ object_property_set_int(obj, PCI_HOST_ECAM_SIZE, ecam_size, &error_abort);
+ object_property_set_uint(obj, PCI_HOST_BELOW_4G_MMIO_BASE, mmio32_base,
+ &error_abort);
+ object_property_set_int(obj, PCI_HOST_BELOW_4G_MMIO_SIZE, mmio32_size,
+ &error_abort);
+ object_property_set_uint(obj, PCI_HOST_ABOVE_4G_MMIO_BASE, mmio64_base,
+ &error_abort);
+ object_property_set_int(obj, PCI_HOST_ABOVE_4G_MMIO_SIZE, mmio64_size,
+ &error_abort);
+ object_property_set_uint(obj, PCI_HOST_PIO_BASE, pio_base, &error_abort);
+ object_property_set_int(obj, PCI_HOST_PIO_SIZE, pio_size, &error_abort);
+
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+ ecam_alias = g_new0(MemoryRegion, 1);
+ ecam_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0);
+ snprintf(name, sizeof(name), "pcie.ecam");
+ memory_region_init_alias(ecam_alias, obj, name,
+ ecam_reg, 0, ecam_size);
+ memory_region_add_subregion(mr, ecam_base, ecam_alias);
+
+ mmio_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1);
+
+ mmio32_alias = g_new0(MemoryRegion, 1);
+ snprintf(name, sizeof(name), "pcie.mmio32");
+ memory_region_init_alias(mmio32_alias, obj, name,
+ mmio_reg, mmio32_base & 0xffffffffUL, mmio32_size);
+ memory_region_add_subregion(mr, mmio32_base, mmio32_alias);
+
+ mmio64_alias = g_new0(MemoryRegion, 1);
+ snprintf(name, sizeof(name), "pcie.mmio64");
+ memory_region_init_alias(mmio64_alias, obj, name,
+ mmio_reg, mmio64_base, mmio64_size);
+ memory_region_add_subregion(mr, mmio64_base, mmio64_alias);
+
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 2, pio_base);
+
+ for (i = 0; i < PCI_NUM_PINS; i++) {
+ irq = qdev_get_gpio_in(s->irqchip, legacy_irq + i);
+
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), i, irq);
+ gpex_set_irq_num(GPEX_HOST(dev), i, legacy_irq + i);
+ }
+
+ gpex_host->gpex_cfg.bus = PCI_HOST_BRIDGE(dev)->bus;
+}
+
+static void gpex_pcie_init(TTAtlantisState *s, MemoryRegion *mr)
+{
+ gpex_pcie_init_one(s, &s->gpex_host, mr,
+ &s->memmap[TT_ATL_PCIE_ECAM0],
+ &s->memmap[TT_ATL_PCIE_PIO0],
+ &s->memmap[TT_ATL_PCIE_MMIO0_32],
+ &s->memmap[TT_ATL_PCIE_MMIO0_64],
+ TT_ATL_PCIE0_INTA_IRQ);
+}
+
static DeviceState *create_reboot_device(const MemMapEntry *mem)
{
DeviceState *dev = qdev_new(TYPE_PVPANIC_MMIO_DEVICE);
@@ -588,6 +807,9 @@ static void tt_atlantis_machine_init(MachineState *machine)
s->fw_cfg = create_fw_cfg(&s->memmap[TT_ATL_FW_CFG], machine->smp.cpus);
rom_set_fw(s->fw_cfg);
+ /* PCIe */
+ gpex_pcie_init(s, system_memory);
+
/* Reboot and exit */
create_reboot_device(&s->memmap[TT_ATL_SYSCON]);
@@ -623,6 +845,7 @@ static void tt_atlantis_machine_class_init(ObjectClass *oc, const void *data)
mc->default_cpu_type = TYPE_RISCV_CPU_TT_ASCALON;
mc->block_default_type = IF_VIRTIO;
mc->no_cdrom = 1;
+ mc->pci_allow_0_address = true;
mc->default_ram_id = "tt_atlantis.ram";
}
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 0601ae1a7494..2ddee591eb90 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -129,6 +129,8 @@ config TENSTORRENT
select DEVICE_TREE
select RISCV_NUMA
select PVPANIC_MMIO
+ select PCI
+ select PCI_EXPRESS_GENERIC_BRIDGE
select SERIAL_MM
select RISCV_ACLINT
select RISCV_APLIC
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 10/13] hw/riscv/atlantis: Add PCIe controller
2026-04-21 5:31 ` [PATCH v3 10/13] hw/riscv/atlantis: Add PCIe controller Joel Stanley
@ 2026-04-21 5:59 ` Philippe Mathieu-Daudé
2026-04-21 9:10 ` Nicholas Piggin
0 siblings, 1 reply; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-21 5:59 UTC (permalink / raw)
To: Joel Stanley, Alistair Francis, Daniel Henrique Barboza
Cc: Nicholas Piggin, Michael Ellerman, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
On 21/4/26 07:31, Joel Stanley wrote:
> From: Nicholas Piggin <npiggin@gmail.com>
>
> tt-atlantis is likely to use a generic ECAM compatible PCIe memory map,
> so gpex is not far off the OS programming model
>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
> ---
> v3: Avoid leaks in the dt string allocation
> ---
> include/hw/riscv/tt_atlantis.h | 2 +
> hw/riscv/tt_atlantis.c | 225 ++++++++++++++++++++++++++++++++-
> hw/riscv/Kconfig | 2 +
> 3 files changed, 228 insertions(+), 1 deletion(-)
> +static void create_fdt_pcie(void *fdt,
> + const MemMapEntry *mem_ecam,
> + const MemMapEntry *mem_pio,
> + const MemMapEntry *mem_mmio32,
> + const MemMapEntry *mem_mmio64,
> + int legacy_irq,
> + uint32_t aplic_s_phandle,
> + uint32_t imsic_s_phandle)
> +{
> + g_autofree char *name = g_strdup_printf("/soc/pci@%"HWADDR_PRIX,
> + mem_ecam->base);
> +
> + qemu_fdt_setprop_cell(fdt, name, "#address-cells", FDT_PCI_ADDR_CELLS);
> + qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", FDT_PCI_INT_CELLS);
> + qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0x2);
> + qemu_fdt_setprop_string(fdt, name, "compatible", "pci-host-ecam-generic");
> + qemu_fdt_setprop_string(fdt, name, "device_type", "pci");
> + qemu_fdt_setprop_cells(fdt, name, "bus-range", 0,
> + mem_ecam->size / PCIE_MMCFG_SIZE_MIN - 1);
> + qemu_fdt_setprop(fdt, name, "dma-coherent", NULL, 0);
> + qemu_fdt_setprop_cell(fdt, name, "msi-parent", imsic_s_phandle);
> +
> + qemu_fdt_setprop_sized_cells(fdt, name, "reg",
> + 2, mem_ecam->base,
> + 2, mem_ecam->size);
> + if (!(mem_mmio32->base & 0xffffffffUL)) {
> + /* XXX: this is a silly hack because it would collide with PIO */
Could you explain a bit more?
> + error_report("mmio32 base must not be 0 mod 2^32");
> + exit(1);
> + }
> + uint32_t flags = FDT_PCI_RANGE_MMIO_64BIT | FDT_PCI_RANGE_PREFETCHABLE;
> + qemu_fdt_setprop_sized_cells(fdt, name, "ranges",
> + 1, FDT_PCI_RANGE_IOPORT,
> + 2, 0x0,
> + 2, mem_pio->base,
> + 2, mem_pio->size,
> + 1, FDT_PCI_RANGE_MMIO,
> + 2, (mem_mmio32->base & 0xffffffffUL),
> + 2, mem_mmio32->base,
> + 2, mem_mmio32->size,
> + 1, flags,
> + 2, mem_mmio64->base,
> + 2, mem_mmio64->base,
> + 2, mem_mmio64->size);
> +
> + create_pcie_irq_map(fdt, name, legacy_irq, aplic_s_phandle);
> +}
> diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> index 0601ae1a7494..2ddee591eb90 100644
> --- a/hw/riscv/Kconfig
> +++ b/hw/riscv/Kconfig
> @@ -129,6 +129,8 @@ config TENSTORRENT
> select DEVICE_TREE
> select RISCV_NUMA
> select PVPANIC_MMIO
> + select PCI
Do not select PCI explicitly, let the bridge (below) do it.
Rationale is this machine does not expose a PCI bus directly,
the bridge device does.
> + select PCI_EXPRESS_GENERIC_BRIDGE
> select SERIAL_MM
> select RISCV_ACLINT
> select RISCV_APLIC
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v3 10/13] hw/riscv/atlantis: Add PCIe controller
2026-04-21 5:59 ` Philippe Mathieu-Daudé
@ 2026-04-21 9:10 ` Nicholas Piggin
2026-04-21 12:59 ` Philippe Mathieu-Daudé
0 siblings, 1 reply; 35+ messages in thread
From: Nicholas Piggin @ 2026-04-21 9:10 UTC (permalink / raw)
To: Philippe Mathieu-Daudé
Cc: Joel Stanley, Alistair Francis, Daniel Henrique Barboza,
Michael Ellerman, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel
On Tue, Apr 21, 2026 at 07:59:32AM +0200, Philippe Mathieu-Daudé wrote:
> On 21/4/26 07:31, Joel Stanley wrote:
> > From: Nicholas Piggin <npiggin@gmail.com>
> >
> > tt-atlantis is likely to use a generic ECAM compatible PCIe memory map,
> > so gpex is not far off the OS programming model
> >
> > Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> > Signed-off-by: Joel Stanley <joel@jms.id.au>
> > ---
> > v3: Avoid leaks in the dt string allocation
> > ---
> > include/hw/riscv/tt_atlantis.h | 2 +
> > hw/riscv/tt_atlantis.c | 225 ++++++++++++++++++++++++++++++++-
> > hw/riscv/Kconfig | 2 +
> > 3 files changed, 228 insertions(+), 1 deletion(-)
>
>
> > +static void create_fdt_pcie(void *fdt,
> > + const MemMapEntry *mem_ecam,
> > + const MemMapEntry *mem_pio,
> > + const MemMapEntry *mem_mmio32,
> > + const MemMapEntry *mem_mmio64,
> > + int legacy_irq,
> > + uint32_t aplic_s_phandle,
> > + uint32_t imsic_s_phandle)
> > +{
> > + g_autofree char *name = g_strdup_printf("/soc/pci@%"HWADDR_PRIX,
> > + mem_ecam->base);
> > +
> > + qemu_fdt_setprop_cell(fdt, name, "#address-cells", FDT_PCI_ADDR_CELLS);
> > + qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", FDT_PCI_INT_CELLS);
> > + qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0x2);
> > + qemu_fdt_setprop_string(fdt, name, "compatible", "pci-host-ecam-generic");
> > + qemu_fdt_setprop_string(fdt, name, "device_type", "pci");
> > + qemu_fdt_setprop_cells(fdt, name, "bus-range", 0,
> > + mem_ecam->size / PCIE_MMCFG_SIZE_MIN - 1);
> > + qemu_fdt_setprop(fdt, name, "dma-coherent", NULL, 0);
> > + qemu_fdt_setprop_cell(fdt, name, "msi-parent", imsic_s_phandle);
> > +
> > + qemu_fdt_setprop_sized_cells(fdt, name, "reg",
> > + 2, mem_ecam->base,
> > + 2, mem_ecam->size);
> > + if (!(mem_mmio32->base & 0xffffffffUL)) {
> > + /* XXX: this is a silly hack because it would collide with PIO */
>
> Could you explain a bit more?
Ah this is a bit incomplete sorry I didn't catch it earlier. Before the
mapping was finalised I just added this hacky way to determining PCIe IO
address based on the physical address.
This should just go away and the IO addresses come from a different table.
>
> > + error_report("mmio32 base must not be 0 mod 2^32");
> > + exit(1);
> > + }
> > + uint32_t flags = FDT_PCI_RANGE_MMIO_64BIT | FDT_PCI_RANGE_PREFETCHABLE;
> > + qemu_fdt_setprop_sized_cells(fdt, name, "ranges",
> > + 1, FDT_PCI_RANGE_IOPORT,
> > + 2, 0x0,
> > + 2, mem_pio->base,
> > + 2, mem_pio->size,
> > + 1, FDT_PCI_RANGE_MMIO,
> > + 2, (mem_mmio32->base & 0xffffffffUL),
> > + 2, mem_mmio32->base,
> > + 2, mem_mmio32->size,
> > + 1, flags,
> > + 2, mem_mmio64->base,
> > + 2, mem_mmio64->base,
> > + 2, mem_mmio64->size);
> > +
> > + create_pcie_irq_map(fdt, name, legacy_irq, aplic_s_phandle);
> > +}
>
>
> > diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> > index 0601ae1a7494..2ddee591eb90 100644
> > --- a/hw/riscv/Kconfig
> > +++ b/hw/riscv/Kconfig
> > @@ -129,6 +129,8 @@ config TENSTORRENT
> > select DEVICE_TREE
> > select RISCV_NUMA
> > select PVPANIC_MMIO
> > + select PCI
>
> Do not select PCI explicitly, let the bridge (below) do it.
> Rationale is this machine does not expose a PCI bus directly,
> the bridge device does.
Good to know, thank you.
Thanks,
Nick
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v3 10/13] hw/riscv/atlantis: Add PCIe controller
2026-04-21 9:10 ` Nicholas Piggin
@ 2026-04-21 12:59 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-21 12:59 UTC (permalink / raw)
To: Nicholas Piggin
Cc: Joel Stanley, Alistair Francis, Daniel Henrique Barboza,
Michael Ellerman, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel
On 21/4/26 11:10, Nicholas Piggin wrote:
> On Tue, Apr 21, 2026 at 07:59:32AM +0200, Philippe Mathieu-Daudé wrote:
>> On 21/4/26 07:31, Joel Stanley wrote:
>>> From: Nicholas Piggin <npiggin@gmail.com>
>>>
>>> tt-atlantis is likely to use a generic ECAM compatible PCIe memory map,
>>> so gpex is not far off the OS programming model
>>>
>>> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
>>> Signed-off-by: Joel Stanley <joel@jms.id.au>
>>> ---
>>> v3: Avoid leaks in the dt string allocation
>>> ---
>>> include/hw/riscv/tt_atlantis.h | 2 +
>>> hw/riscv/tt_atlantis.c | 225 ++++++++++++++++++++++++++++++++-
>>> hw/riscv/Kconfig | 2 +
>>> 3 files changed, 228 insertions(+), 1 deletion(-)
>>
>>
>>> +static void create_fdt_pcie(void *fdt,
>>> + const MemMapEntry *mem_ecam,
>>> + const MemMapEntry *mem_pio,
>>> + const MemMapEntry *mem_mmio32,
>>> + const MemMapEntry *mem_mmio64,
>>> + int legacy_irq,
>>> + uint32_t aplic_s_phandle,
>>> + uint32_t imsic_s_phandle)
>>> +{
>>> + g_autofree char *name = g_strdup_printf("/soc/pci@%"HWADDR_PRIX,
>>> + mem_ecam->base);
>>> +
>>> + qemu_fdt_setprop_cell(fdt, name, "#address-cells", FDT_PCI_ADDR_CELLS);
>>> + qemu_fdt_setprop_cell(fdt, name, "#interrupt-cells", FDT_PCI_INT_CELLS);
>>> + qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0x2);
>>> + qemu_fdt_setprop_string(fdt, name, "compatible", "pci-host-ecam-generic");
>>> + qemu_fdt_setprop_string(fdt, name, "device_type", "pci");
>>> + qemu_fdt_setprop_cells(fdt, name, "bus-range", 0,
>>> + mem_ecam->size / PCIE_MMCFG_SIZE_MIN - 1);
>>> + qemu_fdt_setprop(fdt, name, "dma-coherent", NULL, 0);
>>> + qemu_fdt_setprop_cell(fdt, name, "msi-parent", imsic_s_phandle);
>>> +
>>> + qemu_fdt_setprop_sized_cells(fdt, name, "reg",
>>> + 2, mem_ecam->base,
>>> + 2, mem_ecam->size);
>>> + if (!(mem_mmio32->base & 0xffffffffUL)) {
>>> + /* XXX: this is a silly hack because it would collide with PIO */
>>
>> Could you explain a bit more?
>
> Ah this is a bit incomplete sorry I didn't catch it earlier. Before the
> mapping was finalised I just added this hacky way to determining PCIe IO
> address based on the physical address.
>
> This should just go away and the IO addresses come from a different table.
Ah good, thanks for the explanation.
Regards,
Phil.
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 11/13] tests/functional/riscv64: Add tt-atlantis tests
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
` (9 preceding siblings ...)
2026-04-21 5:31 ` [PATCH v3 10/13] hw/riscv/atlantis: Add PCIe controller Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-21 6:05 ` Philippe Mathieu-Daudé
2026-04-21 5:31 ` [PATCH v3 12/13] hw/riscv/atlantis: Integrate i2c buses Joel Stanley
2026-04-21 5:31 ` [PATCH v3 13/13] hw/riscv/atlantis: Add some i2c peripherals Joel Stanley
12 siblings, 1 reply; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Nicholas Piggin, Michael Ellerman, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
From: Nicholas Piggin <npiggin@gmail.com>
Add OpenSBI and Linux boot tests for the tt-atlantis machine. Based on
tests/functional/riscv64/test_sifive_u.py.
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
v3:
- Fix path to test files
- Dedupe command line root argument
- Remove unused import
---
MAINTAINERS | 1 +
tests/functional/riscv64/meson.build | 1 +
tests/functional/riscv64/test_opensbi.py | 4 ++
tests/functional/riscv64/test_tt_atlantis.py | 63 ++++++++++++++++++++
4 files changed, 69 insertions(+)
create mode 100755 tests/functional/riscv64/test_tt_atlantis.py
diff --git a/MAINTAINERS b/MAINTAINERS
index 635a380945fd..9622ffc9edb2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1770,6 +1770,7 @@ F: docs/system/riscv/tt_*.rst
F: hw/riscv/tt_*.c
F: hw/riscv/aia.[ch]
F: include/hw/riscv/tt_*.h
+F: tests/functional/riscv64/test_tt_*.py
AMD Microblaze-V Generic Board
M: Sai Pavan Boddu <sai.pavan.boddu@amd.com>
diff --git a/tests/functional/riscv64/meson.build b/tests/functional/riscv64/meson.build
index b996c89d7df9..c4456fabd757 100644
--- a/tests/functional/riscv64/meson.build
+++ b/tests/functional/riscv64/meson.build
@@ -13,5 +13,6 @@ tests_riscv64_system_quick = [
tests_riscv64_system_thorough = [
'boston',
'sifive_u',
+ 'tt_atlantis',
'tuxrun',
]
diff --git a/tests/functional/riscv64/test_opensbi.py b/tests/functional/riscv64/test_opensbi.py
index d077e40f4278..0f8beb7e7a8c 100755
--- a/tests/functional/riscv64/test_opensbi.py
+++ b/tests/functional/riscv64/test_opensbi.py
@@ -28,6 +28,10 @@ def test_riscv_sifive_u(self):
self.set_machine('sifive_u')
self.boot_opensbi()
+ def test_riscv_tt_atlantis(self):
+ self.set_machine('tt-atlantis')
+ self.boot_opensbi()
+
def test_riscv_virt(self):
self.set_machine('virt')
self.boot_opensbi()
diff --git a/tests/functional/riscv64/test_tt_atlantis.py b/tests/functional/riscv64/test_tt_atlantis.py
new file mode 100755
index 000000000000..f6f4b7ceec34
--- /dev/null
+++ b/tests/functional/riscv64/test_tt_atlantis.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+# Functional test that boots a Linux kernel on a Tenstorrent Atlantis machine
+# and checks the console
+#
+# Copyright (c) Linaro Ltd.
+# Copyright 2026 Tenstorrent
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import os
+
+from qemu_test import Asset, LinuxKernelTest
+
+
+class TTAtlantis(LinuxKernelTest):
+
+ ASSET_KERNEL = Asset(
+ 'https://storage.tuxboot.com/kernels/6.11.9/riscv64/Image',
+ '174f8bb87f08961e54fa3fcd954a8e31f4645f6d6af4dd43983d5e9841490fb0')
+ ASSET_ROOTFS = Asset(
+ ('https://github.com/groeck/linux-build-test/raw/'
+ '9819da19e6eef291686fdd7b029ea00e764dc62f/rootfs/riscv64/'
+ 'rootfs.ext2.gz'),
+ 'b6ed95610310b7956f9bf20c4c9c0c05fea647900df441da9dfe767d24e8b28b')
+
+ def do_test_riscv64_tt_atlantis(self, connect_disk):
+ self.set_machine('tt-atlantis')
+ kernel_path = self.ASSET_KERNEL.fetch()
+ rootfs_path = self.uncompress(self.ASSET_ROOTFS)
+
+ self.vm.set_console()
+ kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'earlycon=sbi '
+
+ if connect_disk:
+ kernel_command_line += 'root=/dev/vda panic=-1 noreboot rootwait '
+ self.vm.add_args('-device',
+ 'virtio-blk,drive=drive0,serial=0x1234,bus=pcie.0')
+ self.vm.add_args('-drive',
+ f'file={rootfs_path},if=none,id=drive0,format=raw')
+ pattern = 'Boot successful.'
+ else:
+ kernel_command_line += 'panic=0 noreboot '
+ pattern = 'Cannot open root device'
+
+ self.vm.add_args('-kernel', kernel_path,
+ '-append', kernel_command_line,
+ '-no-reboot')
+
+ self.vm.launch()
+ self.wait_for_console_pattern(pattern)
+
+ os.remove(rootfs_path)
+
+ def test_riscv64_tt_atlantis(self):
+ self.do_test_riscv64_tt_atlantis(False)
+
+ def test_riscv64_tt_atlantis_disk(self):
+ self.do_test_riscv64_tt_atlantis(True)
+
+
+if __name__ == '__main__':
+ LinuxKernelTest.main()
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 11/13] tests/functional/riscv64: Add tt-atlantis tests
2026-04-21 5:31 ` [PATCH v3 11/13] tests/functional/riscv64: Add tt-atlantis tests Joel Stanley
@ 2026-04-21 6:05 ` Philippe Mathieu-Daudé
2026-04-21 9:45 ` Nicholas Piggin
0 siblings, 1 reply; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-21 6:05 UTC (permalink / raw)
To: Joel Stanley, Alistair Francis, Daniel Henrique Barboza
Cc: Nicholas Piggin, Michael Ellerman, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel, Thomas Huth
On 21/4/26 07:31, Joel Stanley wrote:
> From: Nicholas Piggin <npiggin@gmail.com>
>
> Add OpenSBI and Linux boot tests for the tt-atlantis machine. Based on
> tests/functional/riscv64/test_sifive_u.py.
>
> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
> ---
> v3:
> - Fix path to test files
> - Dedupe command line root argument
> - Remove unused import
> ---
> MAINTAINERS | 1 +
> tests/functional/riscv64/meson.build | 1 +
> tests/functional/riscv64/test_opensbi.py | 4 ++
> tests/functional/riscv64/test_tt_atlantis.py | 63 ++++++++++++++++++++
> 4 files changed, 69 insertions(+)
> create mode 100755 tests/functional/riscv64/test_tt_atlantis.py
> diff --git a/tests/functional/riscv64/test_tt_atlantis.py b/tests/functional/riscv64/test_tt_atlantis.py
> new file mode 100755
> index 000000000000..f6f4b7ceec34
> --- /dev/null
> +++ b/tests/functional/riscv64/test_tt_atlantis.py
> @@ -0,0 +1,63 @@
> +#!/usr/bin/env python3
> +#
> +# Functional test that boots a Linux kernel on a Tenstorrent Atlantis machine
> +# and checks the console
> +#
> +# Copyright (c) Linaro Ltd.
> +# Copyright 2026 Tenstorrent
> +#
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +
> +import os
> +
> +from qemu_test import Asset, LinuxKernelTest
> +
> +
> +class TTAtlantis(LinuxKernelTest):
> +
> + ASSET_KERNEL = Asset(
> + 'https://storage.tuxboot.com/kernels/6.11.9/riscv64/Image',
> + '174f8bb87f08961e54fa3fcd954a8e31f4645f6d6af4dd43983d5e9841490fb0')
> + ASSET_ROOTFS = Asset(
> + ('https://github.com/groeck/linux-build-test/raw/'
> + '9819da19e6eef291686fdd7b029ea00e764dc62f/rootfs/riscv64/'
> + 'rootfs.ext2.gz'),
> + 'b6ed95610310b7956f9bf20c4c9c0c05fea647900df441da9dfe767d24e8b28b')
> +
> + def do_test_riscv64_tt_atlantis(self, connect_disk):
> + self.set_machine('tt-atlantis')
> + kernel_path = self.ASSET_KERNEL.fetch()
> + rootfs_path = self.uncompress(self.ASSET_ROOTFS)
> +
> + self.vm.set_console()
> + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'earlycon=sbi '
> +
> + if connect_disk:
> + kernel_command_line += 'root=/dev/vda panic=-1 noreboot rootwait '
> + self.vm.add_args('-device',
> + 'virtio-blk,drive=drive0,serial=0x1234,bus=pcie.0')
> + self.vm.add_args('-drive',
> + f'file={rootfs_path},if=none,id=drive0,format=raw')
> + pattern = 'Boot successful.'
Should we run a full shutdown to test up to the halting payload
(patch #4)?
> + else:
> + kernel_command_line += 'panic=0 noreboot '
> + pattern = 'Cannot open root device'
> +
> + self.vm.add_args('-kernel', kernel_path,
> + '-append', kernel_command_line,
> + '-no-reboot')
> +
> + self.vm.launch()
> + self.wait_for_console_pattern(pattern)
> +
> + os.remove(rootfs_path)
Since you call uncompress() with implicit @target=None argument,
scratch_file() is used:
Construct a path for accessing/creating a scratch file
located relative to a temporary directory dedicated to
this test case. The directory and its contents will be
purged upon completion of the test.
So IIUC no need to remove manually. Do you have an issue with it
to remove it manually?
Otherwise,
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
> +
> + def test_riscv64_tt_atlantis(self):
> + self.do_test_riscv64_tt_atlantis(False)
> +
> + def test_riscv64_tt_atlantis_disk(self):
> + self.do_test_riscv64_tt_atlantis(True)
> +
> +
> +if __name__ == '__main__':
> + LinuxKernelTest.main()
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v3 11/13] tests/functional/riscv64: Add tt-atlantis tests
2026-04-21 6:05 ` Philippe Mathieu-Daudé
@ 2026-04-21 9:45 ` Nicholas Piggin
2026-04-21 15:35 ` Philippe Mathieu-Daudé
0 siblings, 1 reply; 35+ messages in thread
From: Nicholas Piggin @ 2026-04-21 9:45 UTC (permalink / raw)
To: Philippe Mathieu-Daudé
Cc: Joel Stanley, Alistair Francis, Daniel Henrique Barboza,
Michael Ellerman, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel, Thomas Huth
On Tue, Apr 21, 2026 at 08:05:48AM +0200, Philippe Mathieu-Daudé wrote:
> On 21/4/26 07:31, Joel Stanley wrote:
> > From: Nicholas Piggin <npiggin@gmail.com>
> >
> > Add OpenSBI and Linux boot tests for the tt-atlantis machine. Based on
> > tests/functional/riscv64/test_sifive_u.py.
> >
> > Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> > Signed-off-by: Joel Stanley <joel@jms.id.au>
> > ---
> > v3:
> > - Fix path to test files
> > - Dedupe command line root argument
> > - Remove unused import
> > ---
> > MAINTAINERS | 1 +
> > tests/functional/riscv64/meson.build | 1 +
> > tests/functional/riscv64/test_opensbi.py | 4 ++
> > tests/functional/riscv64/test_tt_atlantis.py | 63 ++++++++++++++++++++
> > 4 files changed, 69 insertions(+)
> > create mode 100755 tests/functional/riscv64/test_tt_atlantis.py
>
>
> > diff --git a/tests/functional/riscv64/test_tt_atlantis.py b/tests/functional/riscv64/test_tt_atlantis.py
> > new file mode 100755
> > index 000000000000..f6f4b7ceec34
> > --- /dev/null
> > +++ b/tests/functional/riscv64/test_tt_atlantis.py
> > @@ -0,0 +1,63 @@
> > +#!/usr/bin/env python3
> > +#
> > +# Functional test that boots a Linux kernel on a Tenstorrent Atlantis machine
> > +# and checks the console
> > +#
> > +# Copyright (c) Linaro Ltd.
> > +# Copyright 2026 Tenstorrent
> > +#
> > +# SPDX-License-Identifier: GPL-2.0-or-later
> > +
> > +import os
> > +
> > +from qemu_test import Asset, LinuxKernelTest
> > +
> > +
> > +class TTAtlantis(LinuxKernelTest):
> > +
> > + ASSET_KERNEL = Asset(
> > + 'https://storage.tuxboot.com/kernels/6.11.9/riscv64/Image',
> > + '174f8bb87f08961e54fa3fcd954a8e31f4645f6d6af4dd43983d5e9841490fb0')
> > + ASSET_ROOTFS = Asset(
> > + ('https://github.com/groeck/linux-build-test/raw/'
> > + '9819da19e6eef291686fdd7b029ea00e764dc62f/rootfs/riscv64/'
> > + 'rootfs.ext2.gz'),
> > + 'b6ed95610310b7956f9bf20c4c9c0c05fea647900df441da9dfe767d24e8b28b')
> > +
> > + def do_test_riscv64_tt_atlantis(self, connect_disk):
> > + self.set_machine('tt-atlantis')
> > + kernel_path = self.ASSET_KERNEL.fetch()
> > + rootfs_path = self.uncompress(self.ASSET_ROOTFS)
> > +
> > + self.vm.set_console()
> > + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'earlycon=sbi '
> > +
> > + if connect_disk:
> > + kernel_command_line += 'root=/dev/vda panic=-1 noreboot rootwait '
> > + self.vm.add_args('-device',
> > + 'virtio-blk,drive=drive0,serial=0x1234,bus=pcie.0')
> > + self.vm.add_args('-drive',
> > + f'file={rootfs_path},if=none,id=drive0,format=raw')
> > + pattern = 'Boot successful.'
>
> Should we run a full shutdown to test up to the halting payload
> (patch #4)?
Halting payload is only used when there is no kernel given, not sure if
it's good cost/benefit to test that, it's just in case user screw up
they don't get blank screen apparent hang.
A shutdown test all the way to machine halt would be good, although I
don't know if we model that properly in the atlantis machine yet. Maybe
something to expand on later.
>
> > + else:
> > + kernel_command_line += 'panic=0 noreboot '
> > + pattern = 'Cannot open root device'
> > +
> > + self.vm.add_args('-kernel', kernel_path,
> > + '-append', kernel_command_line,
> > + '-no-reboot')
> > +
> > + self.vm.launch()
> > + self.wait_for_console_pattern(pattern)
> > +
> > + os.remove(rootfs_path)
>
> Since you call uncompress() with implicit @target=None argument,
> scratch_file() is used:
>
> Construct a path for accessing/creating a scratch file
> located relative to a temporary directory dedicated to
> this test case. The directory and its contents will be
> purged upon completion of the test.
>
> So IIUC no need to remove manually. Do you have an issue with it
> to remove it manually?
No, I just copied it from tests/functional/riscv64/test_sifive_u.py :)
and looks like same pattern exists in a couple of other tests as well.
>
> Otherwise,
> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Thanks,
Nick
>
> > +
> > + def test_riscv64_tt_atlantis(self):
> > + self.do_test_riscv64_tt_atlantis(False)
> > +
> > + def test_riscv64_tt_atlantis_disk(self):
> > + self.do_test_riscv64_tt_atlantis(True)
> > +
> > +
> > +if __name__ == '__main__':
> > + LinuxKernelTest.main()
>
>
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v3 11/13] tests/functional/riscv64: Add tt-atlantis tests
2026-04-21 9:45 ` Nicholas Piggin
@ 2026-04-21 15:35 ` Philippe Mathieu-Daudé
2026-04-23 2:25 ` Joel Stanley
0 siblings, 1 reply; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-21 15:35 UTC (permalink / raw)
To: Nicholas Piggin
Cc: Joel Stanley, Alistair Francis, Daniel Henrique Barboza,
Michael Ellerman, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel, Thomas Huth
On 21/4/26 11:45, Nicholas Piggin wrote:
> On Tue, Apr 21, 2026 at 08:05:48AM +0200, Philippe Mathieu-Daudé wrote:
>> On 21/4/26 07:31, Joel Stanley wrote:
>>> From: Nicholas Piggin <npiggin@gmail.com>
>>>
>>> Add OpenSBI and Linux boot tests for the tt-atlantis machine. Based on
>>> tests/functional/riscv64/test_sifive_u.py.
>>>
>>> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
>>> Signed-off-by: Joel Stanley <joel@jms.id.au>
>>> ---
>>> v3:
>>> - Fix path to test files
>>> - Dedupe command line root argument
>>> - Remove unused import
>>> ---
>>> MAINTAINERS | 1 +
>>> tests/functional/riscv64/meson.build | 1 +
>>> tests/functional/riscv64/test_opensbi.py | 4 ++
>>> tests/functional/riscv64/test_tt_atlantis.py | 63 ++++++++++++++++++++
>>> 4 files changed, 69 insertions(+)
>>> create mode 100755 tests/functional/riscv64/test_tt_atlantis.py
>>
>>
>>> diff --git a/tests/functional/riscv64/test_tt_atlantis.py b/tests/functional/riscv64/test_tt_atlantis.py
>>> new file mode 100755
>>> index 000000000000..f6f4b7ceec34
>>> --- /dev/null
>>> +++ b/tests/functional/riscv64/test_tt_atlantis.py
>>> @@ -0,0 +1,63 @@
>>> +#!/usr/bin/env python3
>>> +#
>>> +# Functional test that boots a Linux kernel on a Tenstorrent Atlantis machine
>>> +# and checks the console
>>> +#
>>> +# Copyright (c) Linaro Ltd.
>>> +# Copyright 2026 Tenstorrent
>>> +#
>>> +# SPDX-License-Identifier: GPL-2.0-or-later
>>> +
>>> +import os
>>> +
>>> +from qemu_test import Asset, LinuxKernelTest
>>> +
>>> +
>>> +class TTAtlantis(LinuxKernelTest):
>>> +
>>> + ASSET_KERNEL = Asset(
>>> + 'https://storage.tuxboot.com/kernels/6.11.9/riscv64/Image',
>>> + '174f8bb87f08961e54fa3fcd954a8e31f4645f6d6af4dd43983d5e9841490fb0')
>>> + ASSET_ROOTFS = Asset(
>>> + ('https://github.com/groeck/linux-build-test/raw/'
>>> + '9819da19e6eef291686fdd7b029ea00e764dc62f/rootfs/riscv64/'
>>> + 'rootfs.ext2.gz'),
>>> + 'b6ed95610310b7956f9bf20c4c9c0c05fea647900df441da9dfe767d24e8b28b')
>>> +
>>> + def do_test_riscv64_tt_atlantis(self, connect_disk):
>>> + self.set_machine('tt-atlantis')
>>> + kernel_path = self.ASSET_KERNEL.fetch()
>>> + rootfs_path = self.uncompress(self.ASSET_ROOTFS)
>>> +
>>> + self.vm.set_console()
>>> + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'earlycon=sbi '
>>> +
>>> + if connect_disk:
>>> + kernel_command_line += 'root=/dev/vda panic=-1 noreboot rootwait '
>>> + self.vm.add_args('-device',
>>> + 'virtio-blk,drive=drive0,serial=0x1234,bus=pcie.0')
>>> + self.vm.add_args('-drive',
>>> + f'file={rootfs_path},if=none,id=drive0,format=raw')
>>> + pattern = 'Boot successful.'
>>
>> Should we run a full shutdown to test up to the halting payload
>> (patch #4)?
>
> Halting payload is only used when there is no kernel given, not sure if
> it's good cost/benefit to test that, it's just in case user screw up
> they don't get blank screen apparent hang.
>
> A shutdown test all the way to machine halt would be good, although I
> don't know if we model that properly in the atlantis machine yet. Maybe
> something to expand on later.
OK, then we are good.
>>> + else:
>>> + kernel_command_line += 'panic=0 noreboot '
>>> + pattern = 'Cannot open root device'
>>> +
>>> + self.vm.add_args('-kernel', kernel_path,
>>> + '-append', kernel_command_line,
>>> + '-no-reboot')
>>> +
>>> + self.vm.launch()
>>> + self.wait_for_console_pattern(pattern)
>>> +
>>> + os.remove(rootfs_path)
>>
>> Since you call uncompress() with implicit @target=None argument,
>> scratch_file() is used:
>>
>> Construct a path for accessing/creating a scratch file
>> located relative to a temporary directory dedicated to
>> this test case. The directory and its contents will be
>> purged upon completion of the test.
>>
>> So IIUC no need to remove manually. Do you have an issue with it
>> to remove it manually?
>
> No, I just copied it from tests/functional/riscv64/test_sifive_u.py :)
> and looks like same pattern exists in a couple of other tests as well.
Good opportunity to clean it ;)
>
>>
>> Otherwise,
>> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
>
> Thanks,
> Nick
^ permalink raw reply [flat|nested] 35+ messages in thread* Re: [PATCH v3 11/13] tests/functional/riscv64: Add tt-atlantis tests
2026-04-21 15:35 ` Philippe Mathieu-Daudé
@ 2026-04-23 2:25 ` Joel Stanley
0 siblings, 0 replies; 35+ messages in thread
From: Joel Stanley @ 2026-04-23 2:25 UTC (permalink / raw)
To: Philippe Mathieu-Daudé
Cc: Nicholas Piggin, Alistair Francis, Daniel Henrique Barboza,
Michael Ellerman, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel, Thomas Huth
On Wed, 22 Apr 2026 at 01:35, Philippe Mathieu-Daudé <philmd@linaro.org> wrote:
>
> On 21/4/26 11:45, Nicholas Piggin wrote:
> > On Tue, Apr 21, 2026 at 08:05:48AM +0200, Philippe Mathieu-Daudé wrote:
> >> On 21/4/26 07:31, Joel Stanley wrote:
> >>> From: Nicholas Piggin <npiggin@gmail.com>
> >>>
> >>> Add OpenSBI and Linux boot tests for the tt-atlantis machine. Based on
> >>> tests/functional/riscv64/test_sifive_u.py.
> >>>
> >>> Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
> >>> Signed-off-by: Joel Stanley <joel@jms.id.au>
> >>> ---
> >>> v3:
> >>> - Fix path to test files
> >>> - Dedupe command line root argument
> >>> - Remove unused import
> >>> ---
> >>> MAINTAINERS | 1 +
> >>> tests/functional/riscv64/meson.build | 1 +
> >>> tests/functional/riscv64/test_opensbi.py | 4 ++
> >>> tests/functional/riscv64/test_tt_atlantis.py | 63 ++++++++++++++++++++
> >>> 4 files changed, 69 insertions(+)
> >>> create mode 100755 tests/functional/riscv64/test_tt_atlantis.py
> >>
> >>
> >>> diff --git a/tests/functional/riscv64/test_tt_atlantis.py b/tests/functional/riscv64/test_tt_atlantis.py
> >>> new file mode 100755
> >>> index 000000000000..f6f4b7ceec34
> >>> --- /dev/null
> >>> +++ b/tests/functional/riscv64/test_tt_atlantis.py
> >>> @@ -0,0 +1,63 @@
> >>> +#!/usr/bin/env python3
> >>> +#
> >>> +# Functional test that boots a Linux kernel on a Tenstorrent Atlantis machine
> >>> +# and checks the console
> >>> +#
> >>> +# Copyright (c) Linaro Ltd.
> >>> +# Copyright 2026 Tenstorrent
> >>> +#
> >>> +# SPDX-License-Identifier: GPL-2.0-or-later
> >>> +
> >>> +import os
> >>> +
> >>> +from qemu_test import Asset, LinuxKernelTest
> >>> +
> >>> +
> >>> +class TTAtlantis(LinuxKernelTest):
> >>> +
> >>> + ASSET_KERNEL = Asset(
> >>> + 'https://storage.tuxboot.com/kernels/6.11.9/riscv64/Image',
> >>> + '174f8bb87f08961e54fa3fcd954a8e31f4645f6d6af4dd43983d5e9841490fb0')
> >>> + ASSET_ROOTFS = Asset(
> >>> + ('https://github.com/groeck/linux-build-test/raw/'
> >>> + '9819da19e6eef291686fdd7b029ea00e764dc62f/rootfs/riscv64/'
> >>> + 'rootfs.ext2.gz'),
> >>> + 'b6ed95610310b7956f9bf20c4c9c0c05fea647900df441da9dfe767d24e8b28b')
> >>> +
> >>> + def do_test_riscv64_tt_atlantis(self, connect_disk):
> >>> + self.set_machine('tt-atlantis')
> >>> + kernel_path = self.ASSET_KERNEL.fetch()
> >>> + rootfs_path = self.uncompress(self.ASSET_ROOTFS)
> >>> +
> >>> + self.vm.set_console()
> >>> + kernel_command_line = self.KERNEL_COMMON_COMMAND_LINE + 'earlycon=sbi '
> >>> +
> >>> + if connect_disk:
> >>> + kernel_command_line += 'root=/dev/vda panic=-1 noreboot rootwait '
> >>> + self.vm.add_args('-device',
> >>> + 'virtio-blk,drive=drive0,serial=0x1234,bus=pcie.0')
> >>> + self.vm.add_args('-drive',
> >>> + f'file={rootfs_path},if=none,id=drive0,format=raw')
> >>> + pattern = 'Boot successful.'
> >>
> >> Should we run a full shutdown to test up to the halting payload
> >> (patch #4)?
> >
> > Halting payload is only used when there is no kernel given, not sure if
> > it's good cost/benefit to test that, it's just in case user screw up
> > they don't get blank screen apparent hang.
> >
> > A shutdown test all the way to machine halt would be good, although I
> > don't know if we model that properly in the atlantis machine yet. Maybe
> > something to expand on later.
>
> OK, then we are good.
Yep. I plan to add more to this test as we model more of the machine.
>
> >>> + else:
> >>> + kernel_command_line += 'panic=0 noreboot '
> >>> + pattern = 'Cannot open root device'
> >>> +
> >>> + self.vm.add_args('-kernel', kernel_path,
> >>> + '-append', kernel_command_line,
> >>> + '-no-reboot')
> >>> +
> >>> + self.vm.launch()
> >>> + self.wait_for_console_pattern(pattern)
> >>> +
> >>> + os.remove(rootfs_path)
> >>
> >> Since you call uncompress() with implicit @target=None argument,
> >> scratch_file() is used:
> >>
> >> Construct a path for accessing/creating a scratch file
> >> located relative to a temporary directory dedicated to
> >> this test case. The directory and its contents will be
> >> purged upon completion of the test.
> >>
> >> So IIUC no need to remove manually. Do you have an issue with it
> >> to remove it manually?
> >
> > No, I just copied it from tests/functional/riscv64/test_sifive_u.py :)
> > and looks like same pattern exists in a couple of other tests as well.
>
> Good opportunity to clean it ;)
I dropped the os.remove from our test. I'll send a cleanup patch for
the sifive machine when I get a chance.
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 12/13] hw/riscv/atlantis: Integrate i2c buses
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
` (10 preceding siblings ...)
2026-04-21 5:31 ` [PATCH v3 11/13] tests/functional/riscv64: Add tt-atlantis tests Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-21 6:07 ` Philippe Mathieu-Daudé
2026-04-21 5:31 ` [PATCH v3 13/13] hw/riscv/atlantis: Add some i2c peripherals Joel Stanley
12 siblings, 1 reply; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Michael Ellerman, Nicholas Piggin, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
Now that we have the DesignWare model we can add buses to the
tt-atlantis machine.
Provide a fixed clock in the device tree so that the Linux driver probes
without WARNing.
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
v3:
- Add device tree clock
v2:
- Correct count from 4 to 5
- Fix headers location
- Use HWADDR_PRIX to be consistent
---
include/hw/riscv/tt_atlantis.h | 13 +++++++++
hw/riscv/tt_atlantis.c | 53 ++++++++++++++++++++++++++++++++++
hw/riscv/Kconfig | 1 +
3 files changed, 67 insertions(+)
diff --git a/include/hw/riscv/tt_atlantis.h b/include/hw/riscv/tt_atlantis.h
index c0a0827a5167..b39c7e090e0b 100644
--- a/include/hw/riscv/tt_atlantis.h
+++ b/include/hw/riscv/tt_atlantis.h
@@ -11,12 +11,15 @@
#include "hw/core/boards.h"
#include "hw/core/sysbus.h"
+#include "hw/i2c/designware_i2c.h"
#include "hw/intc/riscv_imsic.h"
#include "hw/riscv/riscv_hart.h"
#define TYPE_TT_ATLANTIS_MACHINE MACHINE_TYPE_NAME("tt-atlantis")
OBJECT_DECLARE_SIMPLE_TYPE(TTAtlantisState, TT_ATLANTIS_MACHINE)
+#define TT_ATL_NUM_I2C 5
+
struct TTAtlantisState {
/*< private >*/
MachineState parent;
@@ -29,12 +32,18 @@ struct TTAtlantisState {
RISCVHartArrayState soc;
DeviceState *irqchip;
GPEXHost gpex_host;
+ DesignWareI2CState i2c[TT_ATL_NUM_I2C];
int fdt_size;
};
enum {
TT_ATL_SYSCON_IRQ = 10,
+ TT_ATL_I2C0_IRQ = 33,
+ TT_ATL_I2C1_IRQ = 34,
+ TT_ATL_I2C2_IRQ = 35,
+ TT_ATL_I2C3_IRQ = 36,
+ TT_ATL_I2C4_IRQ = 37,
TT_ATL_UART0_IRQ = 38,
TT_ATL_UART1_IRQ = 39,
TT_ATL_UART2_IRQ = 40,
@@ -50,6 +59,10 @@ enum {
TT_ATL_DDR_HI,
TT_ATL_FW_CFG,
TT_ATL_I2C0,
+ TT_ATL_I2C1,
+ TT_ATL_I2C2,
+ TT_ATL_I2C3,
+ TT_ATL_I2C4,
TT_ATL_MAPLIC,
TT_ATL_MIMSIC,
TT_ATL_PCIE_ECAM0,
diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
index e1753d3c1f18..bf08b7ed320e 100644
--- a/hw/riscv/tt_atlantis.c
+++ b/hw/riscv/tt_atlantis.c
@@ -64,6 +64,11 @@ static const MemMapEntry tt_atlantis_memmap[] = {
[TT_ATL_TIMER] = { 0xa8020000, 0x10000 },
[TT_ATL_WDT0] = { 0xa8030000, 0x10000 },
[TT_ATL_UART0] = { 0xb0100000, 0x10000 },
+ [TT_ATL_I2C0] = { 0xb0400000, 0x10000 },
+ [TT_ATL_I2C1] = { 0xb0500000, 0x10000 },
+ [TT_ATL_I2C2] = { 0xb0600000, 0x10000 },
+ [TT_ATL_I2C3] = { 0xb0700000, 0x10000 },
+ [TT_ATL_I2C4] = { 0xb0800000, 0x10000 },
[TT_ATL_MAPLIC] = { 0xcc000000, 0x4000000 },
[TT_ATL_SAPLIC] = { 0xe8000000, 0x4000000 },
[TT_ATL_DDR_HI] = { 0x100000000, 0x1000000000 },
@@ -475,10 +480,36 @@ static void create_fdt_fw_cfg(void *fdt, const MemMapEntry *mem)
qemu_fdt_setprop(fdt, name, "dma-coherent", NULL, 0);
}
+static void create_fdt_clk(void *fdt, const char *name, uint32_t clk_phandle)
+{
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "fixed-clock");
+ qemu_fdt_setprop_cell(fdt, name, "#clock-cells", 0);
+ qemu_fdt_setprop_cell(fdt, name, "clock-frequency", 100000000);
+ qemu_fdt_setprop_cell(fdt, name, "phandle", clk_phandle);
+}
+
+static void create_fdt_i2c(void *fdt, const MemMapEntry *mem, uint32_t irq,
+ uint32_t irqchip_phandle, uint32_t clk_phandle)
+{
+ g_autofree char *name = g_strdup_printf("/soc/i2c@%"HWADDR_PRIX, mem->base);
+
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", "snps,designware-i2c");
+ qemu_fdt_setprop_sized_cells(fdt, name, "reg", 2, mem->base, 2, mem->size);
+ qemu_fdt_setprop_cell(fdt, name, "interrupt-parent", irqchip_phandle);
+ qemu_fdt_setprop_cells(fdt, name, "interrupts", irq, 0x4);
+ qemu_fdt_setprop_cell(fdt, name, "clocks", clk_phandle);
+ qemu_fdt_setprop_cell(fdt, name, "clock-frequency", 100000);
+ qemu_fdt_setprop_cell(fdt, name, "#address-cells", 1);
+ qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0);
+}
+
static void finalize_fdt(TTAtlantisState *s)
{
uint32_t aplic_s_phandle = next_phandle();
uint32_t imsic_s_phandle = next_phandle();
+ uint32_t periph_clk_phandle = next_phandle();
void *fdt = MACHINE(s)->fdt;
create_fdt_cpu(s, s->memmap, aplic_s_phandle, imsic_s_phandle);
@@ -502,6 +533,15 @@ static void finalize_fdt(TTAtlantisState *s)
create_fdt_uart(fdt, &s->memmap[TT_ATL_UART0], TT_ATL_UART0_IRQ,
aplic_s_phandle);
+
+ create_fdt_clk(fdt, "/periph-clk", periph_clk_phandle);
+
+ for (int i = 0; i < TT_ATL_NUM_I2C; i++) {
+ create_fdt_i2c(fdt,
+ &s->memmap[TT_ATL_I2C0 + i],
+ TT_ATL_I2C0_IRQ + i,
+ aplic_s_phandle, periph_clk_phandle);
+ }
}
static void create_fdt(TTAtlantisState *s)
@@ -818,6 +858,19 @@ static void tt_atlantis_machine_init(MachineState *machine)
qdev_get_gpio_in(s->irqchip, TT_ATL_UART0_IRQ),
115200, serial_hd(0), DEVICE_LITTLE_ENDIAN);
+ /* I2C */
+ for (int i = 0; i < TT_ATL_NUM_I2C; i++) {
+ object_initialize_child(OBJECT(s), "i2c[*]", &s->i2c[i],
+ TYPE_DESIGNWARE_I2C);
+ sysbus_realize(SYS_BUS_DEVICE(&s->i2c[i]), &error_fatal);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(&s->i2c[i]);
+ memory_region_add_subregion(system_memory,
+ s->memmap[TT_ATL_I2C0 + i].base,
+ sysbus_mmio_get_region(sbd, 0));
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c[i]), 0,
+ qdev_get_gpio_in(s->irqchip, TT_ATL_I2C0_IRQ + i));
+ }
+
/* Load or create device tree */
if (machine->dtb) {
machine->fdt = load_device_tree(machine->dtb, &s->fdt_size);
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 2ddee591eb90..4c2cf01995e7 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -136,6 +136,7 @@ config TENSTORRENT
select RISCV_APLIC
select RISCV_IMSIC
select FW_CFG_DMA
+ select DESIGNWARE_I2C
config XIANGSHAN_KUNMINGHU
bool
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 12/13] hw/riscv/atlantis: Integrate i2c buses
2026-04-21 5:31 ` [PATCH v3 12/13] hw/riscv/atlantis: Integrate i2c buses Joel Stanley
@ 2026-04-21 6:07 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-21 6:07 UTC (permalink / raw)
To: Joel Stanley, Alistair Francis, Daniel Henrique Barboza
Cc: Michael Ellerman, Nicholas Piggin, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
On 21/4/26 07:31, Joel Stanley wrote:
> Now that we have the DesignWare model we can add buses to the
> tt-atlantis machine.
>
> Provide a fixed clock in the device tree so that the Linux driver probes
> without WARNing.
>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
> ---
> v3:
> - Add device tree clock
> v2:
> - Correct count from 4 to 5
> - Fix headers location
> - Use HWADDR_PRIX to be consistent
> ---
> include/hw/riscv/tt_atlantis.h | 13 +++++++++
> hw/riscv/tt_atlantis.c | 53 ++++++++++++++++++++++++++++++++++
> hw/riscv/Kconfig | 1 +
> 3 files changed, 67 insertions(+)
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 13/13] hw/riscv/atlantis: Add some i2c peripherals
2026-04-21 5:31 [PATCH v3 00/13] hw/riscv: Add the Tenstorrent Atlantis machine Joel Stanley
` (11 preceding siblings ...)
2026-04-21 5:31 ` [PATCH v3 12/13] hw/riscv/atlantis: Integrate i2c buses Joel Stanley
@ 2026-04-21 5:31 ` Joel Stanley
2026-04-21 6:09 ` Philippe Mathieu-Daudé
12 siblings, 1 reply; 35+ messages in thread
From: Joel Stanley @ 2026-04-21 5:31 UTC (permalink / raw)
To: Alistair Francis, Daniel Henrique Barboza
Cc: Michael Ellerman, Nicholas Piggin, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
Add an I2C RTC device and a temperature sensor. These are not present
on the board but help for testing.
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
v3: Use HWADDR_PRIX in i2c device string consistent
---
hw/riscv/tt_atlantis.c | 18 ++++++++++++++++++
hw/riscv/Kconfig | 2 ++
2 files changed, 20 insertions(+)
diff --git a/hw/riscv/tt_atlantis.c b/hw/riscv/tt_atlantis.c
index bf08b7ed320e..5a6149064f1a 100644
--- a/hw/riscv/tt_atlantis.c
+++ b/hw/riscv/tt_atlantis.c
@@ -505,6 +505,19 @@ static void create_fdt_i2c(void *fdt, const MemMapEntry *mem, uint32_t irq,
qemu_fdt_setprop_cell(fdt, name, "#size-cells", 0);
}
+static void create_fdt_i2c_device(TTAtlantisState *s, int bus,
+ const char *compat, int addr)
+{
+ void *fdt = MACHINE(s)->fdt;
+ hwaddr base = s->memmap[TT_ATL_I2C0 + bus].base;
+ g_autofree char *name = g_strdup_printf("/soc/i2c@%"HWADDR_PRIX"/sensor@%x",
+ base, addr);
+
+ qemu_fdt_add_subnode(fdt, name);
+ qemu_fdt_setprop_string(fdt, name, "compatible", compat);
+ qemu_fdt_setprop_cell(fdt, name, "reg", addr);
+}
+
static void finalize_fdt(TTAtlantisState *s)
{
uint32_t aplic_s_phandle = next_phandle();
@@ -542,6 +555,9 @@ static void finalize_fdt(TTAtlantisState *s)
TT_ATL_I2C0_IRQ + i,
aplic_s_phandle, periph_clk_phandle);
}
+
+ create_fdt_i2c_device(s, 0, "national,lm75", 0x48);
+ create_fdt_i2c_device(s, 0, "dallas,ds1338", 0x6f);
}
static void create_fdt(TTAtlantisState *s)
@@ -870,6 +886,8 @@ static void tt_atlantis_machine_init(MachineState *machine)
sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c[i]), 0,
qdev_get_gpio_in(s->irqchip, TT_ATL_I2C0_IRQ + i));
}
+ i2c_slave_create_simple(s->i2c[0].bus, "ds1338", 0x6f);
+ i2c_slave_create_simple(s->i2c[0].bus, "tmp105", 0x48);
/* Load or create device tree */
if (machine->dtb) {
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 4c2cf01995e7..a4427ba79807 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -137,6 +137,8 @@ config TENSTORRENT
select RISCV_IMSIC
select FW_CFG_DMA
select DESIGNWARE_I2C
+ select DS1338
+ select TMP105
config XIANGSHAN_KUNMINGHU
bool
--
2.47.3
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 13/13] hw/riscv/atlantis: Add some i2c peripherals
2026-04-21 5:31 ` [PATCH v3 13/13] hw/riscv/atlantis: Add some i2c peripherals Joel Stanley
@ 2026-04-21 6:09 ` Philippe Mathieu-Daudé
2026-04-23 2:23 ` Joel Stanley
0 siblings, 1 reply; 35+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-21 6:09 UTC (permalink / raw)
To: Joel Stanley, Alistair Francis, Daniel Henrique Barboza
Cc: Michael Ellerman, Nicholas Piggin, Joel Stanley,
Anirudh Srinivasan, qemu-riscv, qemu-devel
On 21/4/26 07:31, Joel Stanley wrote:
> Add an I2C RTC device and a temperature sensor. These are not present
> on the board but help for testing.
>
> Signed-off-by: Joel Stanley <joel@jms.id.au>
> ---
> v3: Use HWADDR_PRIX in i2c device string consistent
> ---
> hw/riscv/tt_atlantis.c | 18 ++++++++++++++++++
> hw/riscv/Kconfig | 2 ++
> 2 files changed, 20 insertions(+)
> @@ -542,6 +555,9 @@ static void finalize_fdt(TTAtlantisState *s)
> TT_ATL_I2C0_IRQ + i,
> aplic_s_phandle, periph_clk_phandle);
> }
> +
Worth mentioning /* QEMU specific */ ?
> + create_fdt_i2c_device(s, 0, "national,lm75", 0x48);
> + create_fdt_i2c_device(s, 0, "dallas,ds1338", 0x6f);
Maybe use another bus to extend coverage, like #4.
> }
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH v3 13/13] hw/riscv/atlantis: Add some i2c peripherals
2026-04-21 6:09 ` Philippe Mathieu-Daudé
@ 2026-04-23 2:23 ` Joel Stanley
0 siblings, 0 replies; 35+ messages in thread
From: Joel Stanley @ 2026-04-23 2:23 UTC (permalink / raw)
To: Philippe Mathieu-Daudé
Cc: Alistair Francis, Daniel Henrique Barboza, Michael Ellerman,
Nicholas Piggin, Joel Stanley, Anirudh Srinivasan, qemu-riscv,
qemu-devel
On Tue, 21 Apr 2026 at 16:09, Philippe Mathieu-Daudé <philmd@linaro.org> wrote:
>
> On 21/4/26 07:31, Joel Stanley wrote:
> > Add an I2C RTC device and a temperature sensor. These are not present
> > on the board but help for testing.
> >
> > Signed-off-by: Joel Stanley <joel@jms.id.au>
> > ---
> > v3: Use HWADDR_PRIX in i2c device string consistent
> > ---
> > hw/riscv/tt_atlantis.c | 18 ++++++++++++++++++
> > hw/riscv/Kconfig | 2 ++
> > 2 files changed, 20 insertions(+)
>
>
> > @@ -542,6 +555,9 @@ static void finalize_fdt(TTAtlantisState *s)
> > TT_ATL_I2C0_IRQ + i,
> > aplic_s_phandle, periph_clk_phandle);
> > }
> > +
>
> Worth mentioning /* QEMU specific */ ?
>
> > + create_fdt_i2c_device(s, 0, "national,lm75", 0x48);
> > + create_fdt_i2c_device(s, 0, "dallas,ds1338", 0x6f);
>
> Maybe use another bus to extend coverage, like #4.
Both good ideas, thanks.
^ permalink raw reply [flat|nested] 35+ messages in thread