diff options
| author | Stefan Hajnoczi <stefanha@redhat.com> | 2026-07-02 10:44:50 +0200 |
|---|---|---|
| committer | Stefan Hajnoczi <stefanha@redhat.com> | 2026-07-02 10:44:50 +0200 |
| commit | a59157f98f0b69b0bbdb26bc15fbc4d6c8060799 (patch) | |
| tree | 103c55abdbcbf075a661eb2138fcf4540544b808 | |
| parent | 8241394f7e749401e1fb84d412eab40a0e7a23bc (diff) | |
| parent | 64ce9ac18757d79f3b5b337f7bcbdd0dabef3ce1 (diff) | |
| download | qemu-a59157f98f0b69b0bbdb26bc15fbc4d6c8060799.tar.gz qemu-a59157f98f0b69b0bbdb26bc15fbc4d6c8060799.zip | |
Merge tag 'pull-riscv-to-apply-20260701' of https://github.com/alistair23/qemu into staging
RISC-V PR for 11.1
* Fix IMSIC CSR write and add tests
* Parametrise debug trigger number
* Add 'svbare' satp-mode
* Fix RINTC PLIC context ID for KVM
* Avoid abort when reading vtype before env->xl is set
* Skip reset for KVM irqchip
* Skip FP/Vector sync on KVM_PUT_RUNTIME_STATE
* More FDT cleanups (PLIC)
* Make FCTL.BE in IOMMU read only 0
* Check DC.TC reserved bits in IOMMU
* Apply UXL WARL handling to vsstatus
* Set cmd_ill IOFENCE.C if rsvp bits are set in IOMMU
* Set RISCV_IOMMU_FQ_HDR_PV appropriately
* Fix MSI MRIF IOMMU interrupt-pending offset
* Report QEMU CPU archid as 42
* Check PMP before updating PTE
* Add the Tenstorrent Atlantis machine
# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCgAdFiEEaukCtqfKh31tZZKWr3yVEwxTgBMFAmpE6SoACgkQr3yVEwxT
# gBOxNQ//bI4BvnT65Kd2UNMgtAwwPPcehpsyPzC2S3BcflniXQL+fV6sQ7IreKta
# 6dclp/v5v+yhbB4bd/E1s/UPOF3YD4A9noUFifIhymBkafmqA4YRNsvPByeGiSD8
# xVkHhX5qUT9NW5wKnivEDjO8mndBMRm5YEXQ6uT5ulUsZr3Ir8wPOCJITZ8ZqKwb
# 6dbbXStf1aTIBzu53KaNhNpi9DQqKV5UeV7CiSuhuwWU0qmVg1RAZMg9X3oB80rE
# WpWqH0rg9Z0Cn+3XL+oKSzbLD5SrrTV+Ohq+K8zT2rEk+hIXOE3shAPm2xfTT9Q2
# g65nBOf2UmNWeHlvn3XC2LtmIWq10/A78ogGgm4XwHx8TXIeA2KIKboyS8T37XAb
# NwUllq9LRtfDVtDevpiTn6t7Oa7TC8zrxDJTT1rg/p+3D6MdfkonifwJJgVAwfuG
# NF7R2iePKPQliWr1hi6W+ghzQMRFXgNBwUNOL39/BQguy5IqvNmSk6ovhl8IFocf
# aXGh9U35DqgrsUvMa/7Fgf4uI2QNhERBGJrHfL0SPZ82sKb5CTrMw9URwg0DFnEF
# 8v/zQ9xL4eF0uZn0OtaNlLXRCblDxcHSgecwix9Vip5toFIc1P8ar9FX98Zd/H5l
# UD/a3ENtiwb6hnKhZ+45iM/NIFJeUK7A0944VnQzx00tA06wJLw=
# =a4hl
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 01 Jul 2026 12:17:14 CEST
# gpg: using RSA key 6AE902B6A7CA877D6D659296AF7C95130C538013
# gpg: Good signature from "Alistair Francis <alistair@alistair23.me>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg: There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 6AE9 02B6 A7CA 877D 6D65 9296 AF7C 9513 0C53 8013
* tag 'pull-riscv-to-apply-20260701' of https://github.com/alistair23/qemu: (39 commits)
hw/riscv/riscv-iommu.c: always fault with SADE=0 and A=0
hw/riscv/atlantis: Add some i2c peripherals
hw/riscv/atlantis: Integrate i2c controllers
hw/i2c: Add DesignWare I2C Controller
tests/functional/riscv64: Add tt-atlantis tests
hw/riscv/atlantis: Ensure OpenSBI has a non-zero next_addr
hw/riscv: Add Tenstorrent Atlantis machine
target/riscv: tt-ascalon: Enable Zkr extension
hw/riscv/aia: Configure stride for the M-mode IMSIC
hw/riscv/aia: Provide number of irq sources
hw/riscv/virt: Move AIA initialisation to helper file
hw/riscv/boot: Account for discontiguous memory when loading firmware
hw/riscv/boot: Describe discontiguous memory in boot_info
target/riscv: Check PMP before updating PTE
target/riscv: Report QEMU CPU archid as 42
hw/riscv/riscv-iommu.c: fix MSI MRIF interrupt-pending offset
hw/riscv/riscv-iommu.c: set RISCV_IOMMU_FQ_HDR_PV appropriately
hw/riscv/riscv-iommu: set cmd_ill IOFENCE.C rsvp bits are set
target/riscv: Apply UXL WARL handling to vsstatus
hw/riscv/riscv-iommu: check DC.TC reserved bits
...
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
46 files changed, 2196 insertions, 248 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 08e04b6cbf..f0695d3d2a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1790,6 +1790,19 @@ F: hw/*/*sifive*.c F: include/hw/*/*sifive*.h F: tests/functional/riscv64/test_sifive_u.py +Tenstorrent Machines +M: Joel Stanley <joel@jms.id.au> +M: Nicholas Piggin <npiggin@gmail.com> +R: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com> +R: Michael Ellerman <mpe@kernel.org> +R: Portia Stephens <portias@oss.tenstorrent.com> +L: qemu-riscv@nongnu.org +S: Supported +F: docs/system/riscv/tt_*.rst +F: hw/riscv/tt_*.c +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> S: Maintained @@ -2762,6 +2775,14 @@ S: Orphan F: hw/gpio/pcf8574.c F: include/hw/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/docs/system/riscv/tt_atlantis.rst b/docs/system/riscv/tt_atlantis.rst new file mode 100644 index 0000000000..1f2880d617 --- /dev/null +++ b/docs/system/riscv/tt_atlantis.rst @@ -0,0 +1,41 @@ +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. + +tt-atlantis QEMU model features +------------------------------- + +* 8-core Ascalon-X CPU Cluster +* RISC-V compliant Advanced Interrupt Architecture +* 16550A compatible UART + +Known limitations +----------------- + +The QEMU tt-atlantis machine does not yet model every device on the +real platform. Notably: + +* There is no PCI host bridge, so virtio-pci devices cannot be + attached. Boots that need block storage must use ``-initrd`` with + an initramfs. +* The DesignWare UART is modelled with QEMU's ns16550-compatible + ``serial_mm`` device; DesignWare-specific registers beyond that + set return 0. + +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 896f14e78b..2639866a3e 100644 --- a/docs/system/target-riscv.rst +++ b/docs/system/target-riscv.rst @@ -72,6 +72,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/hw/i2c/Kconfig b/hw/i2c/Kconfig index 596a7a3165..0766130b59 100644 --- a/hw/i2c/Kconfig +++ b/hw/i2c/Kconfig @@ -18,6 +18,11 @@ config ARM_SBCON_I2C bool select BITBANG_I2C +config DESIGNWARE_I2C + bool + select REGISTER + select I2C + config ACPI_SMBUS bool select SMBUS diff --git a/hw/i2c/designware_i2c.c b/hw/i2c/designware_i2c.c new file mode 100644 index 0000000000..daa9714cb7 --- /dev/null +++ b/hw/i2c/designware_i2c.c @@ -0,0 +1,745 @@ +/* + * 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/log.h" +#include "qemu/module.h" +#include "qemu/units.h" + +#ifndef DESIGNWARE_I2C_ERR_DEBUG +#define DESIGNWARE_I2C_ERR_DEBUG 0 +#endif + +REG32(DW_IC_CON, 0x00) /* I2C control */ + FIELD(DW_IC_CON, STOP_DET_IF_MASTER_ACTIV, 10, 1) + FIELD(DW_IC_CON, RX_FIFO_FULL_HLD_CTRL, 9, 1) + FIELD(DW_IC_CON, TX_EMPTY_CTRL, 8, 1) + FIELD(DW_IC_CON, STOP_IF_ADDRESSED, 7, 1) + FIELD(DW_IC_CON, SLAVE_DISABLE, 6, 1) + FIELD(DW_IC_CON, IC_RESTART_EN, 5, 1) + FIELD(DW_IC_CON, 10BITADDR_MASTER, 4, 1) + FIELD(DW_IC_CON, 10BITADDR_SLAVE, 3, 1) + FIELD(DW_IC_CON, SPEED, 1, 2) + FIELD(DW_IC_CON, MASTER_MODE, 0, 1) +REG32(DW_IC_TAR, 0x04) /* I2C target address */ + FIELD(DW_IC_TAR, IC_10BITADDR_MASTER, 12, 1) + FIELD(DW_IC_TAR, SPECIAL, 11, 1) + FIELD(DW_IC_TAR, GC_OR_START, 10, 1) + FIELD(DW_IC_TAR, ADDRESS, 0, 10) +REG32(DW_IC_SAR, 0x08) /* I2C slave address */ +REG32(DW_IC_DATA_CMD, 0x10) + FIELD(DW_IC_DATA_CMD, RESTART, 10, 1) + FIELD(DW_IC_DATA_CMD, STOP, 9, 1) + FIELD(DW_IC_DATA_CMD, CMD, 8, 1) + FIELD(DW_IC_DATA_CMD, DAT, 0, 8) +REG32(DW_IC_SS_SCL_HCNT, 0x14) /* Standard speed i2c clock scl high count */ +REG32(DW_IC_SS_SCL_LCNT, 0x18) /* Standard speed i2c clock scl low count */ +REG32(DW_IC_FS_SCL_HCNT, 0x1c) /* Fast or fast plus i2c clock scl high count */ +REG32(DW_IC_FS_SCL_LCNT, 0x20) /* Fast or fast plus i2c clock scl low count */ +REG32(DW_IC_INTR_STAT, 0x2c) +REG32(DW_IC_INTR_MASK, 0x30) /* I2C Interrupt Mask */ +REG32(DW_IC_RAW_INTR_STAT, 0x34) /* I2C raw interrupt status */ + /* DW_IC_INTR_STAT/INTR_MASK/RAW_INTR_STAT fields */ + SHARED_FIELD(DW_IC_INTR_RESTART_DET, 12, 1) + SHARED_FIELD(DW_IC_INTR_GEN_CALL, 11, 1) + SHARED_FIELD(DW_IC_INTR_START_DET, 10, 1) + SHARED_FIELD(DW_IC_INTR_STOP_DET, 9, 1) + SHARED_FIELD(DW_IC_INTR_ACTIVITY, 8, 1) + SHARED_FIELD(DW_IC_INTR_RX_DONE, 7, 1) + SHARED_FIELD(DW_IC_INTR_TX_ABRT, 6, 1) + SHARED_FIELD(DW_IC_INTR_RD_REQ, 5, 1) + SHARED_FIELD(DW_IC_INTR_TX_EMPTY, 4, 1) /* Hardware clear only. */ + SHARED_FIELD(DW_IC_INTR_TX_OVER, 3, 1) + SHARED_FIELD(DW_IC_INTR_RX_FULL, 2, 1) /* Hardware clear only. */ + SHARED_FIELD(DW_IC_INTR_RX_OVER, 1, 1) + SHARED_FIELD(DW_IC_INTR_RX_UNDER, 0, 1) + +#define DW_IC_INTR_ANY_MASK \ + (DW_IC_INTR_RESTART_DET_MASK | \ + DW_IC_INTR_GEN_CALL_MASK | \ + DW_IC_INTR_START_DET_MASK | \ + DW_IC_INTR_STOP_DET_MASK | \ + DW_IC_INTR_ACTIVITY_MASK | \ + DW_IC_INTR_RX_DONE_MASK | \ + DW_IC_INTR_TX_ABRT_MASK | \ + DW_IC_INTR_RD_REQ_MASK | \ + DW_IC_INTR_TX_EMPTY_MASK | \ + DW_IC_INTR_TX_OVER_MASK | \ + DW_IC_INTR_RX_FULL_MASK | \ + DW_IC_INTR_RX_OVER_MASK | \ + DW_IC_INTR_RX_UNDER_MASK) + +#define DW_IC_INTR_ANY_SW_CLEAR_MASK \ + (DW_IC_INTR_ANY_MASK & \ + ~(DW_IC_INTR_TX_EMPTY_MASK | \ + DW_IC_INTR_RX_FULL_MASK)) + +REG32(DW_IC_RX_TL, 0x38) /* I2C receive FIFO threshold */ +REG32(DW_IC_TX_TL, 0x3c) /* I2C transmit FIFO threshold */ +REG32(DW_IC_CLR_INTR, 0x40) +REG32(DW_IC_CLR_RX_UNDER, 0x44) +REG32(DW_IC_CLR_RX_OVER, 0x48) +REG32(DW_IC_CLR_TX_OVER, 0x4c) +REG32(DW_IC_CLR_RD_REQ, 0x50) +REG32(DW_IC_CLR_TX_ABRT, 0x54) +REG32(DW_IC_CLR_RX_DONE, 0x58) +REG32(DW_IC_CLR_ACTIVITY, 0x5c) +REG32(DW_IC_CLR_STOP_DET, 0x60) +REG32(DW_IC_CLR_START_DET, 0x64) +REG32(DW_IC_CLR_GEN_CALL, 0x68) +REG32(DW_IC_ENABLE, 0x6c) /* I2C enable */ + FIELD(DW_IC_ENABLE, TX_CMD_BLOCK, 2, 1) + FIELD(DW_IC_ENABLE, ABORT, 1, 1) + FIELD(DW_IC_ENABLE, ENABLE, 0, 1) +REG32(DW_IC_STATUS, 0x70) /* I2C status */ + FIELD(DW_IC_STATUS, SLV_ACTIVITY, 6, 1) + FIELD(DW_IC_STATUS, MST_ACTIVITY, 5, 1) + FIELD(DW_IC_STATUS, RFF, 4, 1) + FIELD(DW_IC_STATUS, RFNE, 3, 1) + FIELD(DW_IC_STATUS, TFE, 2, 1) + FIELD(DW_IC_STATUS, TFNF, 1, 1) + FIELD(DW_IC_STATUS, ACTIVITY, 0, 1) +REG32(DW_IC_TXFLR, 0x74) /* I2C transmit fifo level */ +REG32(DW_IC_RXFLR, 0x78) /* I2C receive fifo level */ +REG32(DW_IC_SDA_HOLD, 0x7c) /* I2C SDA hold time length */ +REG32(DW_IC_TX_ABRT_SOURCE, 0x80) /* The I2C transmit abort source */ + FIELD(DW_IC_TX_ABRT_SOURCE, USER_ABRT, 16, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, SLVRD_INTX, 15, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, SLV_ARBLOST, 14, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, SLVFLUSH_TXFIFO, 13, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, ARB_LOST, 12, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, MASTER_DIS, 11, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, 10B_RD_NORSTRT, 10, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, SBYTE_NORSTRT, 9, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, HS_NORSTRT, 8, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, SBYTE_ACKDET, 7, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, HS_ACKDET, 6, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, GCALL_READ, 5, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, GCALL_NOACK, 4, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, TXDATA_NOACK, 3, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, 10ADDR2_NOACK, 2, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, 10ADDR1_NOACK, 1, 1) + FIELD(DW_IC_TX_ABRT_SOURCE, 7B_ADDR_NOACK, 0, 1) +REG32(DW_IC_SLV_DATA_NACK_ONLY, 0x84) +REG32(DW_IC_DMA_CR, 0x88) +REG32(DW_IC_DMA_TDLR, 0x8c) +REG32(DW_IC_DMA_RDLR, 0x90) +REG32(DW_IC_SDA_SETUP, 0x94) /* I2C SDA setup */ +REG32(DW_IC_ACK_GENERAL_CALL, 0x98) +REG32(DW_IC_ENABLE_STATUS, 0x9c) /* I2C enable status */ + FIELD(DW_IC_ENABLE_STATUS, SLV_RX_DATA_LOST, 2, 1) + FIELD(DW_IC_ENABLE_STATUS, SLV_DISABLED_WHILE_BUSY, 1, 1) + FIELD(DW_IC_ENABLE_STATUS, IC_EN, 0, 1) +REG32(DW_IC_FS_SPKLEN, 0xa0) /* I2C SS, FS or FM+ spike suppression limit */ +REG32(DW_IC_CLR_RESTART_DET, 0xa8) +REG32(DW_IC_SMBUS_INTR_MASK, 0xcc) /* SMBus Interrupt Mask */ +REG32(DW_IC_COMP_PARAM_1, 0xf4) /* Component parameter */ + FIELD(DW_IC_COMP_PARAM_1, TX_FIFO_SIZE, 16, 8) + FIELD(DW_IC_COMP_PARAM_1, RX_FIFO_SIZE, 8, 8) + FIELD(DW_IC_COMP_PARAM_1, HAS_ENCODED_PARAMS, 7, 1) + FIELD(DW_IC_COMP_PARAM_1, HAS_DMA, 6, 1) + FIELD(DW_IC_COMP_PARAM_1, INTR_IO, 5, 1) + FIELD(DW_IC_COMP_PARAM_1, HC_COUNT_VAL, 4, 1) + FIELD(DW_IC_COMP_PARAM_1, HIGH_SPEED_MODE, 2, 2) + FIELD(DW_IC_COMP_PARAM_1, APB_DATA_WIDTH_32, 0, 2) +REG32(DW_IC_COMP_VERSION, 0xf8) /* I2C component version */ +REG32(DW_IC_COMP_TYPE, 0xfc) /* I2C component type */ + +static void dw_i2c_update_irq(DesignWareI2CState *s) +{ + uint32_t intr = s->regs[R_DW_IC_RAW_INTR_STAT] & s->regs[R_DW_IC_INTR_MASK]; + + qemu_set_irq(s->irq, !!(intr & DW_IC_INTR_ANY_MASK)); +} + +static uint64_t dw_ic_data_cmd_reg_post_read(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + g_assert(value == 0); + + 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) { + SHARED_ARRAY_FIELD_DP32(s->regs, R_DW_IC_RAW_INTR_STAT, + DW_IC_INTR_RX_UNDER, 1); + dw_i2c_update_irq(s); + } + return 0; + } + + g_assert(s->regs[R_DW_IC_RXFLR] == fifo8_num_used(&s->rx_fifo)); + + if (fifo8_is_empty(&s->rx_fifo)) { + SHARED_ARRAY_FIELD_DP32(s->regs, R_DW_IC_RAW_INTR_STAT, DW_IC_INTR_RX_UNDER, 1); + dw_i2c_update_irq(s); + return 0; + } + + s->regs[R_DW_IC_RXFLR]--; + if (s->regs[R_DW_IC_RXFLR] <= s->regs[R_DW_IC_RX_TL]) { + SHARED_ARRAY_FIELD_DP32(s->regs, R_DW_IC_RAW_INTR_STAT, DW_IC_INTR_RX_FULL, 0); + dw_i2c_update_irq(s); + } + + return fifo8_pop(&s->rx_fifo); +} + +static uint64_t dw_ic_clr_intr_reg_post_read(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + g_assert(value == 0); + + switch (reg->access->addr) { + case A_DW_IC_CLR_INTR: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_ANY_SW_CLEAR_MASK; + break; + case A_DW_IC_CLR_RX_UNDER: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_UNDER_MASK; + break; + case A_DW_IC_CLR_RX_OVER: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_OVER_MASK; + break; + case A_DW_IC_CLR_TX_OVER: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_TX_OVER_MASK; + break; + case A_DW_IC_CLR_RD_REQ: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RD_REQ_MASK; + break; + case A_DW_IC_CLR_TX_ABRT: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_TX_ABRT_MASK; + break; + case A_DW_IC_CLR_RX_DONE: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_DONE_MASK; + break; + case A_DW_IC_CLR_ACTIVITY: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_ACTIVITY_MASK; + break; + case A_DW_IC_CLR_STOP_DET: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_STOP_DET_MASK; + break; + case A_DW_IC_CLR_START_DET: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_START_DET_MASK; + break; + case A_DW_IC_CLR_GEN_CALL: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_GEN_CALL_MASK; + break; + case A_DW_IC_CLR_RESTART_DET: + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RESTART_DET_MASK; + break; + default: + g_assert_not_reached(); + } + + dw_i2c_update_irq(s); + + return 0; +} + +static uint64_t dw_ic_intr_stat_reg_post_read(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + g_assert(value == 0); + + return s->regs[R_DW_IC_RAW_INTR_STAT] & s->regs[R_DW_IC_INTR_MASK]; +} + +static uint64_t dw_ic_unsupported_reg_post_read(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + qemu_log_mask(LOG_UNIMP, "%s: unsupported read - %s\n", + DEVICE(s)->canonical_path, reg->access->name); + + return 0; +} + +static uint64_t dw_ic_unsupported_reg_pre_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + qemu_log_mask(LOG_UNIMP, "%s: unsupported write - %s\n", + DEVICE(s)->canonical_path, reg->access->name); + + return 0; +} + +static uint64_t dw_ic_con_reg_pre_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + if (s->regs[R_DW_IC_ENABLE] & R_DW_IC_ENABLE_ENABLE_MASK) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: invalid setting to ic_con %d when ic_enable[0]==1\n", + DEVICE(s)->canonical_path, (int)value); + return s->regs[R_DW_IC_CON]; /* keep old value */ + } + + return value; +} + +static void dw_i2c_reset_to_idle(DesignWareI2CState *s) +{ + s->regs[R_DW_IC_ENABLE_STATUS] &= ~R_DW_IC_ENABLE_STATUS_IC_EN_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_TX_EMPTY_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_FULL_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_UNDER_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_OVER_MASK; + s->regs[R_DW_IC_RXFLR] = 0; + fifo8_reset(&s->rx_fifo); + s->regs[R_DW_IC_STATUS] &= ~R_DW_IC_STATUS_ACTIVITY_MASK; + s->status = DW_I2C_STATUS_IDLE; + dw_i2c_update_irq(s); +} + +static void dw_ic_tx_abort(DesignWareI2CState *s, uint32_t src) +{ + s->regs[R_DW_IC_TX_ABRT_SOURCE] |= src; + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_TX_ABRT_MASK; + dw_i2c_reset_to_idle(s); + dw_i2c_update_irq(s); +} + +static void dw_ic_data_cmd_reg_post_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + int recv = !!(value & R_DW_IC_DATA_CMD_CMD_MASK); + + s->regs[R_DW_IC_DATA_CMD] = 0; /* Register has no storage */ + + if (!(s->regs[R_DW_IC_ENABLE] & R_DW_IC_ENABLE_ENABLE_MASK)) { + /* + * Controller is not enabled. The register_reset() path also lands + * here with value == 0, so silently ignore rather than reporting + * a spurious guest error. + */ + return; + } + + if (s->status == DW_I2C_STATUS_IDLE || + s->regs[R_DW_IC_RAW_INTR_STAT] & DW_IC_INTR_TX_ABRT_MASK) { + 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, + ARRAY_FIELD_EX32(s->regs, DW_IC_TAR, ADDRESS), recv); + if (rv) { + dw_ic_tx_abort(s, R_DW_IC_TX_ABRT_SOURCE_7B_ADDR_NOACK_MASK); + return; + } + s->status = recv ? DW_I2C_STATUS_RECEIVING : DW_I2C_STATUS_SENDING; + } + + /* Send data */ + if (!recv) { + int rv = i2c_send(s->bus, FIELD_EX32(value, DW_IC_DATA_CMD, DAT)); + if (rv) { + i2c_end_transfer(s->bus); + dw_ic_tx_abort(s, R_DW_IC_TX_ABRT_SOURCE_TXDATA_NOACK_MASK); + return; + } + dw_i2c_update_irq(s); + } + + /* Restart command */ + if (value & R_DW_IC_DATA_CMD_RESTART_MASK && + s->regs[R_DW_IC_CON] & R_DW_IC_CON_IC_RESTART_EN_MASK) { + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_RESTART_DET_MASK | + DW_IC_INTR_START_DET_MASK | + DW_IC_INTR_ACTIVITY_MASK; + s->regs[R_DW_IC_STATUS] |= R_DW_IC_STATUS_ACTIVITY_MASK; + dw_i2c_update_irq(s); + + if (i2c_start_transfer(s->bus, + ARRAY_FIELD_EX32(s->regs, DW_IC_TAR, ADDRESS), recv)) { + dw_ic_tx_abort(s, R_DW_IC_TX_ABRT_SOURCE_7B_ADDR_NOACK_MASK); + return; + } + + s->status = recv ? DW_I2C_STATUS_RECEIVING : DW_I2C_STATUS_SENDING; + } + + /* Receive data */ + if (recv) { + g_assert(s->regs[R_DW_IC_RXFLR] == fifo8_num_used(&s->rx_fifo)); + + if (!fifo8_is_full(&s->rx_fifo)) { + fifo8_push(&s->rx_fifo, i2c_recv(s->bus)); + s->regs[R_DW_IC_RXFLR]++; + } else { + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_RX_OVER_MASK; + dw_i2c_update_irq(s); + } + + if (s->regs[R_DW_IC_RXFLR] > s->regs[R_DW_IC_RX_TL]) { + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_RX_FULL_MASK; + dw_i2c_update_irq(s); + } + if (value & R_DW_IC_DATA_CMD_STOP_MASK) { + i2c_nack(s->bus); + } + } + + /* Stop command */ + if (value & R_DW_IC_DATA_CMD_STOP_MASK) { + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_STOP_DET_MASK; + s->regs[R_DW_IC_STATUS] &= ~R_DW_IC_STATUS_ACTIVITY_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_TX_EMPTY_MASK; + i2c_end_transfer(s->bus); + dw_i2c_update_irq(s); + } +} + +static void dw_ic_intr_mask_reg_post_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + dw_i2c_update_irq(s); +} + +static uint64_t dw_ic_enable_reg_pre_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + if (value & R_DW_IC_ENABLE_ENABLE_MASK && + !(s->regs[R_DW_IC_CON] & R_DW_IC_CON_SLAVE_DISABLE_MASK)) { + qemu_log_mask(LOG_UNIMP, + "%s: Designware I2C slave mode is not supported.\n", + DEVICE(s)->canonical_path); + return s->regs[R_DW_IC_ENABLE]; /* keep old value */ + } + + return value; +} + +static void dw_ic_enable_reg_post_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + s->regs[R_DW_IC_ENABLE] = value & R_DW_IC_ENABLE_ENABLE_MASK; + + if (value & R_DW_IC_ENABLE_ABORT_MASK || value & R_DW_IC_ENABLE_TX_CMD_BLOCK_MASK) { + dw_ic_tx_abort(s, R_DW_IC_TX_ABRT_SOURCE_USER_ABRT_MASK); + return; + } + + if (value & R_DW_IC_ENABLE_ENABLE_MASK) { + s->regs[R_DW_IC_ENABLE_STATUS] |= R_DW_IC_ENABLE_STATUS_IC_EN_MASK; + s->regs[R_DW_IC_STATUS] |= R_DW_IC_STATUS_ACTIVITY_MASK; + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_ACTIVITY_MASK | + DW_IC_INTR_START_DET_MASK | + DW_IC_INTR_TX_EMPTY_MASK; + s->status = DW_I2C_STATUS_SENDING_ADDRESS; + dw_i2c_update_irq(s); + } else if ((value & R_DW_IC_ENABLE_ENABLE_MASK) == 0) { + dw_i2c_reset_to_idle(s); + } +} + +static uint64_t dw_ic_rx_tl_reg_pre_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + /* Note that a value of 0 for ic_rx_tl indicates a threshold 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, (int)value); + return DESIGNWARE_I2C_RX_FIFO_SIZE - 1; + } + + return value; +} + +static void dw_ic_rx_tl_reg_post_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + if (s->regs[R_DW_IC_RXFLR] > s->regs[R_DW_IC_RX_TL] && + s->regs[R_DW_IC_ENABLE] & R_DW_IC_ENABLE_ENABLE_MASK) { + s->regs[R_DW_IC_RAW_INTR_STAT] |= DW_IC_INTR_RX_FULL_MASK; + } else { + s->regs[R_DW_IC_RAW_INTR_STAT] &= ~DW_IC_INTR_RX_FULL_MASK; + } + dw_i2c_update_irq(s); +} + +static uint64_t dw_ic_tx_tl_reg_pre_write(RegisterInfo *reg, uint64_t value) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(reg->opaque); + + /* + * Note that a value of 0 for ic_tx_tl indicates a threshold 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, (int)value); + return DESIGNWARE_I2C_TX_FIFO_SIZE - 1; + } + + return value; +} + +static const RegisterAccessInfo designware_i2c_regs_info[] = { + { .name = "DW_IC_CON", .addr = A_DW_IC_CON, + .reset = 0x7d, + .unimp = 0xfffffc00, + .pre_write = dw_ic_con_reg_pre_write, + },{ .name = "DW_IC_TAR", .addr = A_DW_IC_TAR, + .reset = 0x1055, + .unimp = 0xfffff000, + },{ .name = "DW_IC_SAR", .addr = A_DW_IC_SAR, + .reset = 0x55, + .unimp = 0xfffffc00, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_DATA_CMD", .addr = A_DW_IC_DATA_CMD, + .post_read = dw_ic_data_cmd_reg_post_read, + .post_write = dw_ic_data_cmd_reg_post_write, + },{ .name = "DW_IC_SS_SCL_HCNT", .addr = A_DW_IC_SS_SCL_HCNT, + .reset = 0x190, + .unimp = 0xffff0000, + },{ .name = "DW_IC_SS_SCL_LCNT", .addr = A_DW_IC_SS_SCL_LCNT, + .reset = 0x1d6, + .unimp = 0xffff0000, + },{ .name = "DW_IC_FS_SCL_HCNT", .addr = A_DW_IC_FS_SCL_HCNT, + .reset = 0x3c, + .unimp = 0xffff0000, + },{ .name = "DW_IC_FS_SCL_LCNT", .addr = A_DW_IC_FS_SCL_LCNT, + .reset = 0x82, + .unimp = 0xffff0000, + },{ .name = "DW_IC_INTR_STAT", .addr = A_DW_IC_INTR_STAT, + .ro = 0xffffffff, + .post_read = dw_ic_intr_stat_reg_post_read, + },{ .name = "DW_IC_INTR_MASK", .addr = A_DW_IC_INTR_MASK, + .reset = 0x8ff, + .unimp = 0xffff8000, + .post_write = dw_ic_intr_mask_reg_post_write, + },{ .name = "DW_IC_RAW_INTR_STAT", .addr = A_DW_IC_RAW_INTR_STAT, + .ro = 0xffffffff, + },{ .name = "DW_IC_RX_TL", .addr = A_DW_IC_RX_TL, + .pre_write = dw_ic_rx_tl_reg_pre_write, + .post_write = dw_ic_rx_tl_reg_post_write, + },{ .name = "DW_IC_TX_TL", .addr = A_DW_IC_TX_TL, + .pre_write = dw_ic_tx_tl_reg_pre_write, + },{ .name = "DW_IC_CLR_INTR", .addr = A_DW_IC_CLR_INTR, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_RX_UNDER", .addr = A_DW_IC_CLR_RX_UNDER, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_RX_OVER", .addr = A_DW_IC_CLR_RX_OVER, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_TX_OVER", .addr = A_DW_IC_CLR_TX_OVER, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_RD_REQ", .addr = A_DW_IC_CLR_RD_REQ, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_TX_ABRT", .addr = A_DW_IC_CLR_TX_ABRT, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_RX_DONE", .addr = A_DW_IC_CLR_RX_DONE, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_ACTIVITY", .addr = A_DW_IC_CLR_ACTIVITY, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_STOP_DET", .addr = A_DW_IC_CLR_STOP_DET, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_START_DET", .addr = A_DW_IC_CLR_START_DET, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_CLR_GEN_CALL", .addr = A_DW_IC_CLR_GEN_CALL, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_ENABLE", .addr = A_DW_IC_ENABLE, + .unimp = 0xfffffff8, + .pre_write = dw_ic_enable_reg_pre_write, + .post_write = dw_ic_enable_reg_post_write, + },{ .name = "DW_IC_STATUS", .addr = A_DW_IC_STATUS, + .reset = 0x6, + .ro = 0xffffffff, + },{ .name = "DW_IC_TXFLR", .addr = A_DW_IC_TXFLR, + .ro = 0xffffffff, + },{ .name = "DW_IC_RXFLR", .addr = A_DW_IC_RXFLR, + .ro = 0xffffffff, + },{ .name = "DW_IC_SDA_HOLD", .addr = A_DW_IC_SDA_HOLD, + .reset = 0x1, + .unimp = 0xff000000, + },{ .name = "DW_IC_TX_ABRT_SOURCE", .addr = A_DW_IC_TX_ABRT_SOURCE, + .ro = 0xffffffff, + },{ .name = "DW_IC_SLV_DATA_NACK_ONLY", .addr = A_DW_IC_SLV_DATA_NACK_ONLY, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_DMA_CR", .addr = A_DW_IC_DMA_CR, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_DMA_TDLR", .addr = A_DW_IC_DMA_TDLR, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_DMA_RDLR", .addr = A_DW_IC_DMA_RDLR, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_SDA_SETUP", .addr = A_DW_IC_SDA_SETUP, + .reset = 0x64, + .unimp = 0xffffff00, + },{ .name = "DW_IC_ACK_GENERAL_CALL", .addr = A_DW_IC_ACK_GENERAL_CALL, + .post_read = dw_ic_unsupported_reg_post_read, + .pre_write = dw_ic_unsupported_reg_pre_write, + },{ .name = "DW_IC_ENABLE_STATUS", .addr = A_DW_IC_ENABLE_STATUS, + .ro = 0xffffffff, + },{ .name = "DW_IC_FS_SPKLEN", .addr = A_DW_IC_FS_SPKLEN, + .reset = 0x2, + .ro = 0xffffff00, + },{ .name = "DW_IC_CLR_RESTART_DET", .addr = A_DW_IC_CLR_RESTART_DET, + .ro = 0xffffffff, + .post_read = dw_ic_clr_intr_reg_post_read, + },{ .name = "DW_IC_SMBUS_INTR_MASK", .addr = A_DW_IC_SMBUS_INTR_MASK, + /* No SMBus interrupts are implemented, Linux updates the mask */ + .reset = 0x7ff, + .unimp = 0xfffff800, + },{ .name = "DW_IC_COMP_PARAM_1", .addr = A_DW_IC_COMP_PARAM_1, + .reset = /* HAS_DMA and HC_COUNT_VAL are disabled */ + ((2 << R_DW_IC_COMP_PARAM_1_APB_DATA_WIDTH_32_SHIFT) | + R_DW_IC_COMP_PARAM_1_HIGH_SPEED_MODE_MASK | + R_DW_IC_COMP_PARAM_1_INTR_IO_MASK | + R_DW_IC_COMP_PARAM_1_HAS_ENCODED_PARAMS_MASK | + ((DESIGNWARE_I2C_RX_FIFO_SIZE - 1) + << R_DW_IC_COMP_PARAM_1_RX_FIFO_SIZE_SHIFT) | + ((DESIGNWARE_I2C_TX_FIFO_SIZE - 1) + << R_DW_IC_COMP_PARAM_1_TX_FIFO_SIZE_SHIFT)), + .ro = 0xffffffff, + },{ .name = "DW_IC_COMP_VERSION", .addr = A_DW_IC_COMP_VERSION, + .reset = 0x3132302a, + .ro = 0xffffffff, + },{ .name = "DW_IC_COMP_TYPE", .addr = A_DW_IC_COMP_TYPE, + .reset = 0x44570140, + .ro = 0xffffffff, + } +}; + +static const MemoryRegionOps designware_i2c_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .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); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(s->regs); ++i) { + register_reset(&s->regs_info[i]); + } + + fifo8_reset(&s->rx_fifo); + + 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 = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, DesignWareI2CState, DESIGNWARE_I2C_R_MAX), + VMSTATE_FIFO8(rx_fifo, DesignWareI2CState), + VMSTATE_UINT32(status, DesignWareI2CState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void designware_i2c_instance_init(Object *obj) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + RegisterInfoArray *reg_array; + + fifo8_create(&s->rx_fifo, DESIGNWARE_I2C_RX_FIFO_SIZE); + + s->bus = i2c_init_bus(DEVICE(s), "i2c-bus"); + + memory_region_init(&s->iomem, obj, TYPE_DESIGNWARE_I2C, 4 * KiB); + reg_array = register_init_block32(DEVICE(obj), designware_i2c_regs_info, + ARRAY_SIZE(designware_i2c_regs_info), + s->regs_info, s->regs, + &designware_i2c_ops, + DESIGNWARE_I2C_ERR_DEBUG, + DESIGNWARE_I2C_R_MAX * 4); + memory_region_add_subregion(&s->iomem, 0, ®_array->mem); + + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); +} + +static void designware_i2c_finalize(Object *obj) +{ + DesignWareI2CState *s = DESIGNWARE_I2C(obj); + + fifo8_destroy(&s->rx_fifo); +} + +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; + + set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); +} + +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_instance_init, + .instance_finalize = designware_i2c_finalize, + }, +}; +DEFINE_TYPES(designware_i2c_types); diff --git a/hw/i2c/meson.build b/hw/i2c/meson.build index c459adcb59..88aea35662 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/intc/riscv_aplic.c b/hw/intc/riscv_aplic.c index c2c67c29e6..84606e9f3d 100644 --- a/hw/intc/riscv_aplic.c +++ b/hw/intc/riscv_aplic.c @@ -910,6 +910,10 @@ static void riscv_aplic_reset_enter(Object *obj, ResetType type) RISCVAPLICState *aplic = RISCV_APLIC(obj); int i; + if (!riscv_use_emulated_aplic(aplic->msimode)) { + return; + } + aplic->domaincfg = 0; memset(aplic->sourcecfg, 0, sizeof(uint32_t) * aplic->num_irqs); memset(aplic->target, 0, sizeof(uint32_t) * aplic->num_irqs); diff --git a/hw/intc/riscv_imsic.c b/hw/intc/riscv_imsic.c index 1c86ecdb2c..7e5b5349ba 100644 --- a/hw/intc/riscv_imsic.c +++ b/hw/intc/riscv_imsic.c @@ -347,6 +347,10 @@ static void riscv_imsic_reset_enter(Object *obj, ResetType type) RISCVIMSICState *imsic = RISCV_IMSIC(obj); int i; + if (kvm_irqchip_in_kernel()) { + return; + } + memset(imsic->eidelivery, 0, sizeof(uint32_t) * imsic->num_pages); memset(imsic->eithreshold, 0, sizeof(uint32_t) * imsic->num_pages); diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 54e41a6afc..de37c08cae 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -120,6 +120,19 @@ config SPIKE select RISCV_ACLINT select SIFIVE_PLIC +config TENSTORRENT + bool + default y + depends on RISCV64 + select RISCV_ACLINT + select RISCV_APLIC + select RISCV_IMSIC + select SERIAL_MM + select DEVICE_TREE + select DESIGNWARE_I2C + select DS1338 + select TMP105 + config XIANGSHAN_KUNMINGHU bool default y diff --git a/hw/riscv/aia.c b/hw/riscv/aia.c new file mode 100644 index 0000000000..ed89160029 --- /dev/null +++ b/hw/riscv/aia.c @@ -0,0 +1,94 @@ +/* + * 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, + uint32_t m_imsic_stride, + uint16_t num_sources, + 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; + + /* 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 */ + addr = imsic_m->base + socket * (1U << IMSIC_MMIO_GROUP_MIN_SHIFT); + for (i = 0; i < hart_count; i++) { + riscv_imsic_create(addr + i * m_imsic_stride, + 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, + 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, + 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/aia.h b/hw/riscv/aia.h new file mode 100644 index 0000000000..565f91accc --- /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" + +uint32_t imsic_num_bits(uint32_t count); + +DeviceState *riscv_create_aia(bool msimode, int aia_guests, + uint32_t m_imsic_stride, + uint16_t num_sources, + 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/hw/riscv/boot.c b/hw/riscv/boot.c index 7c9cd61468..5e2dfa091a 100644 --- a/hw/riscv/boot.c +++ b/hw/riscv/boot.c @@ -70,11 +70,27 @@ 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); } +/* + * This can be used instead of riscv_boot_info_init() if the machine has + * discontiguous physical memory. The low memory range specified will be + * used to place firmware images. + */ +void riscv_boot_info_init_discontig_mem(RISCVBootInfo *info, + RISCVHartArrayState *harts, + hwaddr low_start, hwaddr low_size) +{ + riscv_boot_info_init(info, harts); + info->ram_low_start = low_start; + info->ram_low_size = low_size; +} + vaddr riscv_calc_kernel_start_addr(RISCVBootInfo *info, hwaddr firmware_end_addr) { if (info->is_32bit) { @@ -135,6 +151,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) @@ -147,7 +164,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); } @@ -155,10 +173,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, + const 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; @@ -187,7 +208,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) { @@ -202,7 +223,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; @@ -248,6 +269,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; @@ -279,7 +301,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; @@ -375,7 +397,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/fdt-common.c b/hw/riscv/fdt-common.c index b27ff13bca..e0e31af09b 100644 --- a/hw/riscv/fdt-common.c +++ b/hw/riscv/fdt-common.c @@ -200,3 +200,33 @@ create_fdt_socket_cpu_sifive(void *fdt, char *clust_name, socket_id, socket_hartid_base, phandle, intc_phandles, false, false); } + +void create_fdt_plic(void *fdt, hwaddr addr, uint64_t size, + uint32_t plic_phandle, uint32_t int_cells, + uint32_t addr_cells, uint32_t *plic_cells, + uint32_t cells_size, uint32_t ndev_sources, + bool numa_enabled, int socket_id) +{ + g_autofree char *nodename = NULL; + static const char * const plic_compat[2] = { + "sifive,plic-1.0.0", "riscv,plic0" + }; + + nodename = g_strdup_printf("/soc/interrupt-controller@%"HWADDR_PRIx, addr); + + qemu_fdt_add_subnode(fdt, nodename); + qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells", int_cells); + qemu_fdt_setprop_cell(fdt, nodename, "#address-cells", addr_cells); + qemu_fdt_setprop_string_array(fdt, nodename, "compatible", + (char **)&plic_compat, ARRAY_SIZE(plic_compat)); + qemu_fdt_setprop(fdt, nodename, "interrupt-controller", NULL, 0); + qemu_fdt_setprop(fdt, nodename, "interrupts-extended", + plic_cells, cells_size); + qemu_fdt_setprop_sized_cells(fdt, nodename, "reg", + 2, addr, 2, size); + qemu_fdt_setprop_cell(fdt, nodename, "riscv,ndev", ndev_sources); + if (numa_enabled) { + qemu_fdt_setprop_cell(fdt, nodename, "numa-node-id", socket_id); + } + qemu_fdt_setprop_cell(fdt, nodename, "phandle", plic_phandle); +} diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 502281c52c..656f28190c 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -424,7 +424,8 @@ static void k230_direct_boot(K230MachineState *s, MachineState *machine) riscv_load_fdt(K230_DIRECT_DTB_ADDR, machine->fdt); - 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); if (firmware_end_addr > K230_DIRECT_KERNEL_ADDR) { error_report("K230 firmware overlaps kernel address 0x%x", @@ -442,13 +443,16 @@ static void k230_firmware_boot(K230MachineState *s, MachineState *machine) { const char *firmware_name = riscv_default_firmware_name(&s->soc.c908_cpu); hwaddr start_addr = memmap[K230_DEV_DDRC].base; + RISCVBootInfo boot_info = {0}; if (machine->dtb || (machine->kernel_cmdline && *machine->kernel_cmdline)) { error_report("K230 firmware boot does not support -dtb or -append"); exit(EXIT_FAILURE); } - riscv_find_and_load_firmware(machine, firmware_name, &start_addr, NULL); + riscv_boot_info_init(&boot_info, &s->soc.c908_cpu); + riscv_find_and_load_firmware(machine, &boot_info, firmware_name, + &start_addr, NULL); riscv_setup_rom_reset_vec(machine, &s->soc.c908_cpu, start_addr, memmap[K230_DEV_BOOTROM].base, diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build index b70a054579..0d82ceacc4 100644 --- a/hw/riscv/meson.build +++ b/hw/riscv/meson.build @@ -1,4 +1,5 @@ riscv_ss = ss.source_set() +riscv_ss.add(files('aia.c')) riscv_ss.add(files('boot.c')) riscv_ss.add(files('fdt-common.c')) riscv_ss.add(when: 'CONFIG_RISCV_NUMA', if_true: files('numa.c')) @@ -10,6 +11,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')) diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c index 5e48a29708..4017129c83 100644 --- a/hw/riscv/microchip_pfsoc.c +++ b/hw/riscv/microchip_pfsoc.c @@ -619,18 +619,22 @@ static void microchip_icicle_kit_machine_init(MachineState *machine) firmware_load_addr = RESET_VECTOR; } + riscv_boot_info_init_discontig_mem(&boot_info, &s->soc.u_cpus, + memmap[MICROCHIP_PFSOC_DRAM_LO].base, + mem_low_size); + /* 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 c8b2f028f2..5b2f33d5ac 100644 --- a/hw/riscv/opentitan.c +++ b/hw/riscv/opentitan.c @@ -100,12 +100,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/riscv-iommu-bits.h b/hw/riscv/riscv-iommu-bits.h index a938fd3eb4..cc0b1c3ccd 100644 --- a/hw/riscv/riscv-iommu-bits.h +++ b/hw/riscv/riscv-iommu-bits.h @@ -308,6 +308,9 @@ struct riscv_iommu_dc { #define RISCV_IOMMU_DC_IOHGATP_GSCID GENMASK_ULL(59, 44) #define RISCV_IOMMU_DC_IOHGATP_MODE RISCV_IOMMU_ATP_MODE_FIELD +#define RISCV_IOMMU_DC_TC_RESERVED (GENMASK_ULL(23, 12) \ + | GENMASK_ULL(63, 32)) + enum riscv_iommu_dc_iohgatp_modes { RISCV_IOMMU_DC_IOHGATP_MODE_BARE = 0, RISCV_IOMMU_DC_IOHGATP_MODE_SV32X4 = 8, @@ -344,6 +347,7 @@ struct riscv_iommu_command { #define RISCV_IOMMU_CMD_IOFENCE_OPCODE 2 #define RISCV_IOMMU_CMD_IOFENCE_FUNC_C 0 #define RISCV_IOMMU_CMD_IOFENCE_AV BIT_ULL(10) +#define RISCV_IOMMU_CMD_IOFENCE_RESERVED GENMASK_ULL(31, 14) #define RISCV_IOMMU_CMD_IOFENCE_DATA GENMASK_ULL(63, 32) #define RISCV_IOMMU_CMD_IODIR_OPCODE 3 diff --git a/hw/riscv/riscv-iommu-hpm.c b/hw/riscv/riscv-iommu-hpm.c index 5bf80a8db9..9bfef6db45 100644 --- a/hw/riscv/riscv-iommu-hpm.c +++ b/hw/riscv/riscv-iommu-hpm.c @@ -60,8 +60,8 @@ static void hpm_incr_ctr(RISCVIOMMUState *s, uint32_t ctr_idx) const uint32_t off = ctr_idx << 3; uint64_t cntr_val; - cntr_val = ldq_le_p(&s->regs_rw[RISCV_IOMMU_REG_IOHPMCTR_BASE + off]); - stq_le_p(&s->regs_rw[RISCV_IOMMU_REG_IOHPMCTR_BASE + off], cntr_val + 1); + cntr_val = ldq_le_p(&s->regs[RISCV_IOMMU_REG_IOHPMCTR_BASE + off]); + stq_le_p(&s->regs[RISCV_IOMMU_REG_IOHPMCTR_BASE + off], cntr_val + 1); trace_riscv_iommu_hpm_incr_ctr(cntr_val); diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c index a500cb8440..974042d017 100644 --- a/hw/riscv/riscv-iommu.c +++ b/hw/riscv/riscv-iommu.c @@ -480,7 +480,7 @@ static int riscv_iommu_spa_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx, break; /* Read access check failed */ } else if ((iotlb->perm & IOMMU_WO) && !(pte & PTE_W)) { break; /* Write access check failed */ - } else if ((iotlb->perm & IOMMU_RO) && !ade && !(pte & PTE_A)) { + } else if (!ade && !(pte & PTE_A)) { break; /* Access bit not set */ } else if ((iotlb->perm & IOMMU_WO) && !ade && !(pte & PTE_D)) { break; /* Dirty bit not set */ @@ -580,7 +580,7 @@ static void riscv_iommu_report_fault(RISCVIOMMUState *s, ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_CAUSE, cause); ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_TTYPE, fault_type); ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_DID, ctx->devid); - ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_PV, true); + ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_PV, pv); if (pv) { ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_PID, ctx->process_id); @@ -686,7 +686,26 @@ static MemTxResult riscv_iommu_msi_write(RISCVIOMMUState *s, /* MRIF pending bit address */ addr = get_field(pte[0], RISCV_IOMMU_MSI_PTE_MRIF_ADDR) << 9; - addr = addr | ((data & 0x7c0) >> 3); + /* + * AIA spec section "Format of a memory-resident interrupt file": + * address offset 0x000 contains interrupt-pending bits for + * identities 1-63, offfset 0x010 for identities 64-127, and + * so it goes up to 0x1F0 for identities 1984-2047. + * + * Hence each batch of identities advances offset by 16 (0x010) + * for every interrupt-pending bits. This means that doing + * (data & 0x7c0) will filter out the first 6 bits, then + * a >> 2 will turn the result in the 0x10 steps we need. + * + * E.g: + * + * - (1-63 & 0x7c0) = 0, 0 >> 2 = 0, offset 0x000 + * - (64-127 & 0x7c0) = 64, 64 >> 2 = 16, offset 0x010 + * - (128-191 & 0x7c0) = 128, 128 >> 2 = 32, offset 0x020 + * + * and so on. + */ + addr = addr | ((data & 0x7c0) >> 2); trace_riscv_iommu_msi(s->parent_obj.id, PCI_BUS_NUM(ctx->devid), PCI_SLOT(ctx->devid), PCI_FUNC(ctx->devid), @@ -752,6 +771,10 @@ static bool riscv_iommu_validate_device_ctx(RISCVIOMMUState *s, uint32_t fsc_mode, msi_mode; uint64_t gatp; + if (ctx->tc & RISCV_IOMMU_DC_TC_RESERVED) { + return false; + } + if (!(s->cap & RISCV_IOMMU_CAP_ATS) && (ctx->tc & RISCV_IOMMU_DC_TC_EN_ATS || ctx->tc & RISCV_IOMMU_DC_TC_EN_PRI || @@ -1876,6 +1899,10 @@ static void riscv_iommu_process_cq_tail(RISCVIOMMUState *s) switch (cmd_opcode) { case RISCV_IOMMU_CMD(RISCV_IOMMU_CMD_IOFENCE_FUNC_C, RISCV_IOMMU_CMD_IOFENCE_OPCODE): + if (cmd.dword0 & RISCV_IOMMU_CMD_IOFENCE_RESERVED) { + goto cmd_ill; + } + res = riscv_iommu_iofence(s, cmd.dword0 & RISCV_IOMMU_CMD_IOFENCE_AV, cmd.dword1 << 2, get_field(cmd.dword0, RISCV_IOMMU_CMD_IOFENCE_DATA)); @@ -2034,8 +2061,8 @@ static void riscv_iommu_process_cq_control(RISCVIOMMUState *s) s->cq_mask = (2ULL << get_field(base, RISCV_IOMMU_CQB_LOG2SZ)) - 1; s->cq_addr = PPN_PHYS(get_field(base, RISCV_IOMMU_CQB_PPN)); stl_le_p(&s->regs_ro[RISCV_IOMMU_REG_CQT], ~s->cq_mask); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_CQH], 0); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_CQT], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_CQH], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_CQT], 0); ctrl_set = RISCV_IOMMU_CQCSR_CQON; ctrl_clr = RISCV_IOMMU_CQCSR_BUSY | RISCV_IOMMU_CQCSR_CQMF | RISCV_IOMMU_CQCSR_CMD_ILL | RISCV_IOMMU_CQCSR_CMD_TO | @@ -2075,8 +2102,8 @@ static void riscv_iommu_process_fq_control(RISCVIOMMUState *s) s->fq_mask = (2ULL << get_field(base, RISCV_IOMMU_FQB_LOG2SZ)) - 1; s->fq_addr = PPN_PHYS(get_field(base, RISCV_IOMMU_FQB_PPN)); stl_le_p(&s->regs_ro[RISCV_IOMMU_REG_FQH], ~s->fq_mask); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_FQH], 0); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_FQT], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_FQH], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_FQT], 0); ctrl_set = RISCV_IOMMU_FQCSR_FQON; ctrl_clr = RISCV_IOMMU_FQCSR_BUSY | RISCV_IOMMU_FQCSR_FQMF | RISCV_IOMMU_FQCSR_FQOF; @@ -2105,8 +2132,8 @@ static void riscv_iommu_process_pq_control(RISCVIOMMUState *s) s->pq_mask = (2ULL << get_field(base, RISCV_IOMMU_PQB_LOG2SZ)) - 1; s->pq_addr = PPN_PHYS(get_field(base, RISCV_IOMMU_PQB_PPN)); stl_le_p(&s->regs_ro[RISCV_IOMMU_REG_PQH], ~s->pq_mask); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_PQH], 0); - stl_le_p(&s->regs_rw[RISCV_IOMMU_REG_PQT], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_PQH], 0); + stl_le_p(&s->regs[RISCV_IOMMU_REG_PQT], 0); ctrl_set = RISCV_IOMMU_PQCSR_PQON; ctrl_clr = RISCV_IOMMU_PQCSR_BUSY | RISCV_IOMMU_PQCSR_PQMF | RISCV_IOMMU_PQCSR_PQOF; @@ -2276,9 +2303,9 @@ static void riscv_iommu_write_reg_val(RISCVIOMMUState *s, { uint64_t ro = ldn_le_p(&s->regs_ro[reg_addr], size); uint64_t wc = ldn_le_p(&s->regs_wc[reg_addr], size); - uint64_t rw = ldn_le_p(&s->regs_rw[reg_addr], size); + uint64_t curr_val = ldn_le_p(&s->regs[reg_addr], size); - stn_le_p(dest, size, ((rw & ro) | (data & ~ro)) & ~(data & wc)); + stn_le_p(dest, size, ((curr_val & ro) | (data & ~ro)) & ~(data & wc)); } static MemTxResult riscv_iommu_mmio_write(void *opaque, hwaddr addr, @@ -2378,12 +2405,12 @@ static MemTxResult riscv_iommu_mmio_write(void *opaque, hwaddr addr, * is set IOMMU behavior of additional writes to the register * is UNSPECIFIED. */ - riscv_iommu_write_reg_val(s, &s->regs_rw[addr], addr, size, data); + riscv_iommu_write_reg_val(s, &s->regs[addr], addr, size, data); /* Busy flag update, MSB 4-byte register. */ if (busy) { - uint32_t rw = ldl_le_p(&s->regs_rw[regb]); - stl_le_p(&s->regs_rw[regb], rw | busy); + uint32_t rw = ldl_le_p(&s->regs[regb]); + stl_le_p(&s->regs[regb], rw | busy); } /* Process HPM writes and update any internal state if needed. */ @@ -2428,13 +2455,13 @@ static MemTxResult riscv_iommu_mmio_read(void *opaque, hwaddr addr, * it's not dependent over the timer callback and is computed * from cycle overflow. */ - val = ldq_le_p(&s->regs_rw[addr]); + val = ldq_le_p(&s->regs[addr]); val |= (riscv_iommu_hpmcycle_read(s) & RISCV_IOMMU_IOHPMCYCLES_OVF) ? RISCV_IOMMU_IOCOUNTOVF_CY : 0; ptr = (uint8_t *)&val + (addr & 3); } else { - ptr = &s->regs_rw[addr]; + ptr = &s->regs[addr]; } val = ldn_le_p(ptr, size); @@ -2529,7 +2556,7 @@ static void riscv_iommu_instance_init(Object *obj) s->cap |= RISCV_IOMMU_CAP_PD8; /* register storage */ - s->regs_rw = g_new0(uint8_t, RISCV_IOMMU_REG_SIZE); + s->regs = g_new0(uint8_t, RISCV_IOMMU_REG_SIZE); s->regs_ro = g_new0(uint8_t, RISCV_IOMMU_REG_SIZE); s->regs_wc = g_new0(uint8_t, RISCV_IOMMU_REG_SIZE); @@ -2554,7 +2581,7 @@ static void riscv_iommu_instance_finalize(Object *obj) { RISCVIOMMUState *s = RISCV_IOMMU(obj); - g_free(s->regs_rw); + g_free(s->regs); g_free(s->regs_ro); g_free(s->regs_wc); @@ -2608,10 +2635,12 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) "riscv-iommu-regs", RISCV_IOMMU_REG_SIZE); /* Set power-on register state */ - stq_le_p(&s->regs_rw[RISCV_IOMMU_REG_CAP], s->cap); - stq_le_p(&s->regs_rw[RISCV_IOMMU_REG_FCTL], 0); + stq_le_p(&s->regs[RISCV_IOMMU_REG_CAP], s->cap); + + stq_le_p(&s->regs[RISCV_IOMMU_REG_FCTL], 0); stq_le_p(&s->regs_ro[RISCV_IOMMU_REG_FCTL], - ~(RISCV_IOMMU_FCTL_BE | RISCV_IOMMU_FCTL_WSI)); + ~(RISCV_IOMMU_FCTL_GXL | RISCV_IOMMU_FCTL_WSI)); + stq_le_p(&s->regs_ro[RISCV_IOMMU_REG_DDTP], ~(RISCV_IOMMU_DDTP_PPN | RISCV_IOMMU_DDTP_MODE)); stq_le_p(&s->regs_ro[RISCV_IOMMU_REG_CQB], @@ -2634,7 +2663,7 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp) RISCV_IOMMU_PQCSR_BUSY); stl_le_p(&s->regs_wc[RISCV_IOMMU_REG_IPSR], ~0); stl_le_p(&s->regs_ro[RISCV_IOMMU_REG_ICVEC], 0); - stq_le_p(&s->regs_rw[RISCV_IOMMU_REG_DDTP], s->ddtp); + stq_le_p(&s->regs[RISCV_IOMMU_REG_DDTP], s->ddtp); /* If debug registers enabled. */ if (s->cap & RISCV_IOMMU_CAP_DBG) { stq_le_p(&s->regs_ro[RISCV_IOMMU_REG_TR_REQ_IOVA], 0); diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h index 2a9f6fccd5..a778e86fb7 100644 --- a/hw/riscv/riscv-iommu.h +++ b/hw/riscv/riscv-iommu.h @@ -76,9 +76,13 @@ struct RISCVIOMMUState { /* MMIO Hardware Interface */ MemoryRegion regs_mr; - uint8_t *regs_rw; /* register state (user write) */ + uint8_t *regs; /* current register state */ uint8_t *regs_wc; /* write-1-to-clear mask */ - uint8_t *regs_ro; /* read-only mask */ + /* + * read-only mask. NOTE: bits not present in this RO + * mask are assumed to be read and write. + */ + uint8_t *regs_ro; QLIST_ENTRY(RISCVIOMMUState) iommus; QLIST_HEAD(, RISCVIOMMUSpace) spaces; @@ -120,39 +124,39 @@ struct RISCVIOMMUContext { static inline uint32_t riscv_iommu_reg_mod32(RISCVIOMMUState *s, unsigned idx, uint32_t set, uint32_t clr) { - uint32_t val = ldl_le_p(s->regs_rw + idx); - stl_le_p(s->regs_rw + idx, (val & ~clr) | set); + uint32_t val = ldl_le_p(s->regs + idx); + stl_le_p(s->regs + idx, (val & ~clr) | set); return val; } static inline void riscv_iommu_reg_set32(RISCVIOMMUState *s, unsigned idx, uint32_t set) { - stl_le_p(s->regs_rw + idx, set); + stl_le_p(s->regs + idx, set); } static inline uint32_t riscv_iommu_reg_get32(RISCVIOMMUState *s, unsigned idx) { - return ldl_le_p(s->regs_rw + idx); + return ldl_le_p(s->regs + idx); } static inline uint64_t riscv_iommu_reg_mod64(RISCVIOMMUState *s, unsigned idx, uint64_t set, uint64_t clr) { - uint64_t val = ldq_le_p(s->regs_rw + idx); - stq_le_p(s->regs_rw + idx, (val & ~clr) | set); + uint64_t val = ldq_le_p(s->regs + idx); + stq_le_p(s->regs + idx, (val & ~clr) | set); return val; } static inline void riscv_iommu_reg_set64(RISCVIOMMUState *s, unsigned idx, uint64_t set) { - stq_le_p(s->regs_rw + idx, set); + stq_le_p(s->regs + idx, set); } static inline uint64_t riscv_iommu_reg_get64(RISCVIOMMUState *s, - unsigned idx) + unsigned idx) { - return ldq_le_p(s->regs_rw + idx); + return ldq_le_p(s->regs + idx); } #endif diff --git a/hw/riscv/shakti_c.c b/hw/riscv/shakti_c.c index b1823a3125..835b1f879b 100644 --- a/hw/riscv/shakti_c.c +++ b/hw/riscv/shakti_c.c @@ -46,6 +46,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 */ @@ -58,8 +59,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 0c6e4204cb..99423ef472 100644 --- a/hw/riscv/sifive_u.c +++ b/hw/riscv/sifive_u.c @@ -100,14 +100,11 @@ static void create_fdt(SiFiveUState *s, const MemMapEntry *memmap, MachineState *ms = MACHINE(s); void *fdt; int cpu; - uint32_t *cells; + uint32_t *cells, cells_length; char *nodename; uint32_t plic_phandle, prci_phandle, gpio_phandle, phandle = 1; uint32_t hfclk_phandle, rtcclk_phandle, phy_phandle; static const char * const ethclk_names[2] = { "pclk", "hclk" }; - static const char * const plic_compat[2] = { - "sifive,plic-1.0.0", "riscv,plic0" - }; g_autofree uint32_t *intc_phandles = g_new0(uint32_t, ms->smp.cpus); g_autofree char *clust_name = NULL; @@ -197,41 +194,28 @@ static void create_fdt(SiFiveUState *s, const MemMapEntry *memmap, g_free(nodename); plic_phandle = phandle++; - cells = g_new0(uint32_t, ms->smp.cpus * 4 - 2); + cells_length = ms->smp.cpus * 4 - 2; + cells = g_new0(uint32_t, cells_length); for (cpu = 0; cpu < ms->smp.cpus; cpu++) { - nodename = - g_strdup_printf("/cpus/cpu@%d/interrupt-controller", cpu); - uint32_t intc_phandle = qemu_fdt_get_phandle(fdt, nodename); /* cpu 0 is the management hart that does not have S-mode */ if (cpu == 0) { - cells[0] = cpu_to_be32(intc_phandle); + cells[0] = cpu_to_be32(intc_phandles[cpu]); cells[1] = cpu_to_be32(IRQ_M_EXT); } else { - cells[cpu * 4 - 2] = cpu_to_be32(intc_phandle); + cells[cpu * 4 - 2] = cpu_to_be32(intc_phandles[cpu]); cells[cpu * 4 - 1] = cpu_to_be32(IRQ_M_EXT); - cells[cpu * 4 + 0] = cpu_to_be32(intc_phandle); + cells[cpu * 4 + 0] = cpu_to_be32(intc_phandles[cpu]); cells[cpu * 4 + 1] = cpu_to_be32(IRQ_S_EXT); } - g_free(nodename); } - nodename = g_strdup_printf("/soc/interrupt-controller@%lx", - (long)memmap[SIFIVE_U_DEV_PLIC].base); - qemu_fdt_add_subnode(fdt, nodename); - qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells", 1); - qemu_fdt_setprop_string_array(fdt, nodename, "compatible", - (char **)&plic_compat, ARRAY_SIZE(plic_compat)); - qemu_fdt_setprop(fdt, nodename, "interrupt-controller", NULL, 0); - qemu_fdt_setprop(fdt, nodename, "interrupts-extended", - cells, (ms->smp.cpus * 4 - 2) * sizeof(uint32_t)); - qemu_fdt_setprop_cells(fdt, nodename, "reg", - 0x0, memmap[SIFIVE_U_DEV_PLIC].base, - 0x0, memmap[SIFIVE_U_DEV_PLIC].size); - qemu_fdt_setprop_cell(fdt, nodename, "riscv,ndev", - SIFIVE_U_PLIC_NUM_SOURCES - 1); - qemu_fdt_setprop_cell(fdt, nodename, "phandle", plic_phandle); - plic_phandle = qemu_fdt_get_phandle(fdt, nodename); + + create_fdt_plic(fdt, memmap[SIFIVE_U_DEV_PLIC].base, + memmap[SIFIVE_U_DEV_PLIC].size, + plic_phandle, SIFIVE_U_PLIC_INT_CELLS, + SIFIVE_U_PLIC_ADDR_CELLS, cells, + cells_length * sizeof(uint32_t), + SIFIVE_U_PLIC_NUM_SOURCES - 1, false, 0); g_free(cells); - g_free(nodename); gpio_phandle = phandle++; nodename = g_strdup_printf("/soc/gpio@%lx", @@ -540,11 +524,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 4e60724c14..9fde0faf39 100644 --- a/hw/riscv/spike.c +++ b/hw/riscv/spike.c @@ -205,9 +205,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); @@ -217,7 +220,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/tt_atlantis.c b/hw/riscv/tt_atlantis.c new file mode 100644 index 0000000000..beaf64e0c1 --- /dev/null +++ b/hw/riscv/tt_atlantis.c @@ -0,0 +1,617 @@ +/* + * 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/fdt-common.h" +#include "hw/riscv/machines-qom.h" +#include "hw/riscv/riscv_hart.h" + +#include "hw/char/serial-mm.h" +#include "hw/intc/riscv_aclint.h" +#include "hw/misc/unimp.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 63 /* aia_guests, gives guest_index_bits=6 */ +#define TT_IRQCHIP_MIMSIC_STRIDE 0x40000 + +#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_MIMSIC] = { 0xa0000000, 0x200000 }, + [TT_ATL_ACLINT] = { 0xa2180000, 0x10000 }, + [TT_ATL_SIMSIC] = { 0xa4000000, 0x200000 }, + [TT_ATL_MAPLIC] = { 0xcc000000, 0x4000000 }, + [TT_ATL_I2C0] = { 0xd4040000, 0x10000 }, + [TT_ATL_I2C1] = { 0xd4050000, 0x10000 }, + [TT_ATL_I2C2] = { 0xd4060000, 0x10000 }, + [TT_ATL_I2C3] = { 0xd4070000, 0x10000 }, + [TT_ATL_I2C4] = { 0xd4080000, 0x10000 }, + [TT_ATL_UART1] = { 0xd4110000, 0x10000 }, + [TT_ATL_SAPLIC] = { 0xe8000000, 0x4000000 }, + [TT_ATL_DDR_HI] = { 0x100000000, 0x1000000000 }, +}; + +static I2CBus *i2c_get_bus(TTAtlantisState *s, unsigned busnr) +{ + assert(busnr < TT_ATL_NUM_I2C); + + return s->i2c[busnr].bus; +} + +static uint32_t fdt_phandle = 1; +static uint32_t next_phandle(void) +{ + return fdt_phandle++; +} + +static void create_fdt_memory(TTAtlantisState *s) +{ + void *fdt = MACHINE(s)->fdt; + 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_socket_memory(fdt, s->memmap[TT_ATL_DDR_LO].base, size_lo, + 0, false); + 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_socket_memory(fdt, s->memmap[TT_ATL_DDR_HI].base + size_lo, + size_hi, 0, false); + } +} + +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) +{ + char pmu_name[] = "/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 = g_new0(uint32_t, ms->smp.cpus); + + fdt_create_cpu_socket_subnode(fdt, TT_ACLINT_TIMEBASE_FREQ); + + create_fdt_socket_cpus(fdt, s->soc.harts, 0, s->soc.num_harts, + s->soc.hartid_base, &fdt_phandle, intc_phandles, + false, false); + + create_fdt_memory(s); + + create_fdt_aclint(s, intc_phandles); + + uint32_t imsic_guest_bits = imsic_num_bits(TT_IRQCHIP_GUESTS + 1); + + /* 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, imsic_guest_bits); + + /* 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_guest_bits); + + 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_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_rng(void *fdt) +{ + uint8_t rng_seed[32]; + + qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed)); + qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed)); +} + +static void create_fdt_clk(void *fdt, const char *clock_name, + uint32_t freq, uint32_t phandle) +{ + g_autofree char *name = g_strdup_printf("/clocks/%s", clock_name); + + qemu_fdt_add_path(fdt, name); + qemu_fdt_setprop_string(fdt, name, "compatible", "fixed-clock"); + qemu_fdt_setprop_string(fdt, name, "clock-output-names", clock_name); + qemu_fdt_setprop_cell(fdt, name, "#clock-cells", 0); + qemu_fdt_setprop_cell(fdt, name, "clock-frequency", freq); + qemu_fdt_setprop_cell(fdt, name, "phandle", 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 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(); + 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); + + /* + * 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_uart(fdt, &s->memmap[TT_ATL_UART1], TT_ATL_UART1_IRQ, + aplic_s_phandle); + + create_fdt_clk(fdt, "periph-clk", 100000000, 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); + } + + create_fdt_i2c_device(s, 0, "dallas,ds1338", 0x6f); + create_fdt_i2c_device(s, 4, "ti,tmp105", 0x48); +} + +static void create_fdt(TTAtlantisState *s) +{ + MachineState *ms = MACHINE(s); + + ms->fdt = create_board_device_tree("Tenstorrent Atlantis RISC-V Machine", + "tenstorrent,atlantis", &s->fdt_size); + + qemu_fdt_add_subnode(ms->fdt, "/chosen"); + + create_fdt_rng(ms->fdt); + + qemu_fdt_add_subnode(ms->fdt, "/aliases"); + + create_fdt_pmu(s); +} + +static void load_fdt(TTAtlantisState *s) +{ + MachineState *ms = MACHINE(s); + char **node_path; + Error *err = NULL; + + ms->fdt = load_device_tree(ms->dtb, &s->fdt_size); + if (!ms->fdt) { + error_report("load_device_tree() failed"); + exit(1); + } + + qemu_fdt_add_path(ms->fdt, "/chosen"); + + /* Clear memory nodes and update with the specified RAM size */ + node_path = qemu_fdt_node_unit_path(ms->fdt, "memory", &err); + if (err) { + warn_report_err(err); + } else { + for (int i = 0; node_path[i]; i++) { + warn_report("Replacing device tree %s with the requested RAM size", + node_path[i]); + qemu_fdt_nop_node(ms->fdt, node_path[i]); + } + g_strfreev(node_path); + } + + create_fdt_memory(s); +} + +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); + kernel_entry = boot_info.image_low_addr; + } else { + /* If we aren't loading a payload, OpenSBI thinks we are trying to boot + * address 0, which fails `sbi_domain_check_addr()` as that is where + * OpenSBI is running. Instead point OpenSBI to the end of the region + * where it was loaded, which avoids the early hang, allowing the + * system to proceed with the OpenSBI boot output. + */ + kernel_entry = kernel_start_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; + int hart_count = machine->smp.cpus; + + 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", 0, + &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_MIMSIC_STRIDE, + 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, 0, 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, + 0, hart_count, + TT_ACLINT_MTIMECMP, + TT_ACLINT_MTIME, + TT_ACLINT_TIMEBASE_FREQ, true); + + /* + * DDR + * + * The high address is where RAM lives. It is always present and may be + * up to 64GB. The low address is an alias of the first 2GB of that RAM. + */ + if (machine->ram_size > s->memmap[TT_ATL_DDR_HI].size) { + char *sz = size_to_str(s->memmap[TT_ATL_DDR_HI].size); + error_report("RAM size is too large, maximum is %s", sz); + g_free(sz); + exit(EXIT_FAILURE); + } + + memory_region_init_alias(ram_hi, OBJECT(machine), "ram.high", machine->ram, + 0, machine->ram_size); + memory_region_add_subregion(system_memory, + s->memmap[TT_ATL_DDR_HI].base, ram_hi); + + lo_ram_size = MIN(machine->ram_size, s->memmap[TT_ATL_DDR_LO].size); + memory_region_init_alias(ram_lo, OBJECT(machine), "ram.low", machine->ram, + 0, lo_ram_size); + memory_region_add_subregion(system_memory, + s->memmap[TT_ATL_DDR_LO].base, ram_lo); + + /* 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); + + /* UART1, the soc console (UART0 is for the boot microcontroller) */ + serial_mm_init(system_memory, s->memmap[TT_ATL_UART1].base, 2, + qdev_get_gpio_in(s->irqchip, TT_ATL_UART1_IRQ), + 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN); + /* + * Atlantis contains a DesignWare uart while the QEMU machine + * uses the serial_mm model with the base ns16550 register set. + * Linux's dw driver writes outside of serial_mm's 0x20 sized + * mapping and faults. + * + * Create an unimplemented device region so writes don't fault + * and reads return zero, which keeps Linux happy. + */ + create_unimplemented_device("tt-atlantis.uart0", + s->memmap[TT_ATL_UART1].base, + s->memmap[TT_ATL_UART1].size); + + /* I2C */ + for (int i = 0; i < TT_ATL_NUM_I2C; i++) { + SysBusDevice *sbd; + + object_initialize_child(OBJECT(s), "i2c[*]", &s->i2c[i], + TYPE_DESIGNWARE_I2C); + sbd = SYS_BUS_DEVICE(&s->i2c[i]); + sysbus_realize(sbd, &error_fatal); + memory_region_add_subregion(system_memory, + s->memmap[TT_ATL_I2C0 + i].base, + sysbus_mmio_get_region(sbd, 0)); + sysbus_connect_irq(sbd, 0, + qdev_get_gpio_in(s->irqchip, TT_ATL_I2C0_IRQ + i)); + } + + /* I2C peripherals: qemu specific */ + i2c_slave_create_simple(i2c_get_bus(s, 0), "ds1338", 0x6f); + i2c_slave_create_simple(i2c_get_bus(s, 4), "tmp105", 0x48); + + /* Load or create device tree */ + if (machine->dtb) { + load_fdt(s); + } 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 (Experimental)"; + mc->init = tt_atlantis_machine_init; + mc->max_cpus = 8; + mc->default_cpus = 8; + mc->default_ram_size = 4 * 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), + .interfaces = riscv64_machine_interfaces, + }, +}; + +DEFINE_TYPES(tt_atlantis_types) diff --git a/hw/riscv/virt-acpi-build.c b/hw/riscv/virt-acpi-build.c index 413d47d70e..59c454f4f9 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)) @@ -100,6 +102,8 @@ static void riscv_acpi_madt_add_rintc(uint32_t uid, build_append_int_noprefix(entry, ACPI_BUILD_INTC_ID( arch_ids->cpus[uid].props.node_id, + kvm_enabled() ? + local_cpu_id : 2 * local_cpu_id + 1), 4); } else { @@ -144,6 +148,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) { @@ -151,9 +156,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))); @@ -472,10 +480,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"); } @@ -492,15 +503,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); @@ -579,7 +590,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 33775a61fd..b68067cfdd 100644 --- a/hw/riscv/virt.c +++ b/hw/riscv/virt.c @@ -61,6 +61,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) { @@ -326,39 +328,25 @@ static void create_fdt_socket_plic(RISCVVirtState *s, int cpu; g_autofree char *plic_name = NULL; g_autofree uint32_t *plic_cells; - unsigned long plic_addr; MachineState *ms = MACHINE(s); - static const char * const plic_compat[2] = { - "sifive,plic-1.0.0", "riscv,plic0" - }; + unsigned long plic_addr = s->memmap[VIRT_PLIC].base + + (s->memmap[VIRT_PLIC].size * socket); + bool numa_enabled = riscv_numa_enabled(MACHINE(s)); + uint32_t cells_length; - plic_phandles[socket] = (*phandle)++; - plic_addr = s->memmap[VIRT_PLIC].base + - (s->memmap[VIRT_PLIC].size * socket); - plic_name = g_strdup_printf("/soc/plic@%lx", plic_addr); - qemu_fdt_add_subnode(ms->fdt, plic_name); - qemu_fdt_setprop_cell(ms->fdt, plic_name, - "#interrupt-cells", FDT_PLIC_INT_CELLS); - qemu_fdt_setprop_cell(ms->fdt, plic_name, - "#address-cells", FDT_PLIC_ADDR_CELLS); - qemu_fdt_setprop_string_array(ms->fdt, plic_name, "compatible", - (char **)&plic_compat, - ARRAY_SIZE(plic_compat)); - qemu_fdt_setprop(ms->fdt, plic_name, "interrupt-controller", NULL, 0); + plic_name = g_strdup_printf("/soc/interrupt-controller@%lx", plic_addr); if (kvm_enabled()) { - plic_cells = g_new0(uint32_t, s->soc[socket].num_harts * 2); + cells_length = s->soc[socket].num_harts * 2; + plic_cells = g_new0(uint32_t, cells_length); for (cpu = 0; cpu < s->soc[socket].num_harts; cpu++) { plic_cells[cpu * 2 + 0] = cpu_to_be32(intc_phandles[cpu]); plic_cells[cpu * 2 + 1] = cpu_to_be32(IRQ_S_EXT); } - - qemu_fdt_setprop(ms->fdt, plic_name, "interrupts-extended", - plic_cells, - s->soc[socket].num_harts * sizeof(uint32_t) * 2); } else { - plic_cells = g_new0(uint32_t, s->soc[socket].num_harts * 4); + cells_length = s->soc[socket].num_harts * 4; + plic_cells = g_new0(uint32_t, cells_length); for (cpu = 0; cpu < s->soc[socket].num_harts; cpu++) { plic_cells[cpu * 4 + 0] = cpu_to_be32(intc_phandles[cpu]); @@ -366,19 +354,16 @@ static void create_fdt_socket_plic(RISCVVirtState *s, plic_cells[cpu * 4 + 2] = cpu_to_be32(intc_phandles[cpu]); plic_cells[cpu * 4 + 3] = cpu_to_be32(IRQ_S_EXT); } - - qemu_fdt_setprop(ms->fdt, plic_name, "interrupts-extended", - plic_cells, - s->soc[socket].num_harts * sizeof(uint32_t) * 4); } - qemu_fdt_setprop_sized_cells(ms->fdt, plic_name, "reg", - 2, plic_addr, 2, s->memmap[VIRT_PLIC].size); - qemu_fdt_setprop_cell(ms->fdt, plic_name, "riscv,ndev", - VIRT_IRQCHIP_NUM_SOURCES - 1); - riscv_socket_fdt_write_id(ms, plic_name, socket); - qemu_fdt_setprop_cell(ms->fdt, plic_name, "phandle", - plic_phandles[socket]); + plic_phandles[socket] = (*phandle)++; + + create_fdt_plic(ms->fdt, plic_addr, s->memmap[VIRT_PLIC].size, + plic_phandles[socket], FDT_PLIC_INT_CELLS, + FDT_PLIC_ADDR_CELLS, plic_cells, + cells_length * sizeof(uint32_t), + VIRT_IRQCHIP_NUM_SOURCES - 1, + numa_enabled, socket); if (!socket) { platform_bus_add_all_fdt_nodes(ms->fdt, plic_name, @@ -388,17 +373,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) @@ -1163,68 +1137,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; @@ -1327,7 +1239,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]); @@ -1350,8 +1265,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); @@ -1486,9 +1399,17 @@ 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, + IMSIC_HART_SIZE(0), + s->num_sources, + &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 */ @@ -1638,6 +1559,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) diff --git a/hw/riscv/xiangshan_kmh.c b/hw/riscv/xiangshan_kmh.c index 76417ba7ab..384624d69a 100644 --- a/hw/riscv/xiangshan_kmh.c +++ b/hw/riscv/xiangshan_kmh.c @@ -167,6 +167,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, @@ -178,13 +179,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 */ diff --git a/include/hw/i2c/designware_i2c.h b/include/hw/i2c/designware_i2c.h new file mode 100644 index 0000000000..4d5ff5d973 --- /dev/null +++ b/include/hw/i2c/designware_i2c.h @@ -0,0 +1,56 @@ +/* + * DesignWare I2C Module. + * + * Copyright 2021 Google LLC + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef DESIGNWARE_I2C_H +#define DESIGNWARE_I2C_H + +#include "qemu/fifo8.h" +#include "hw/i2c/i2c.h" +#include "hw/core/irq.h" +#include "hw/core/register.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define DESIGNWARE_I2C_R_MAX (0x100 / 4) + +#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; + +#define TYPE_DESIGNWARE_I2C "designware-i2c" +OBJECT_DECLARE_SIMPLE_TYPE(DesignWareI2CState, DESIGNWARE_I2C) + +/* + * struct DesignWareI2CState - DesignWare I2C device state. + * @bus: The underlying I2C Bus + * @irq: Interrupt line fired on transaction events. + * @rx_fifo: The FIFO buffer for receiving in FIFO mode. + */ +struct DesignWareI2CState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + + I2CBus *bus; + qemu_irq irq; + + uint32_t regs[DESIGNWARE_I2C_R_MAX]; + RegisterInfo regs_info[DESIGNWARE_I2C_R_MAX]; + + /* fifo8_num_used(rx_fifo) should always equal DW_IC_RXFLR */ + Fifo8 rx_fifo; + + DesignWareI2CStatus status; +}; + +#endif /* DESIGNWARE_I2C_H */ diff --git a/include/hw/riscv/boot.h b/include/hw/riscv/boot.h index f00b3ca122..4e7bd9a225 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,16 +47,22 @@ 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 low_start, hwaddr low_size); 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, + const RISCVBootInfo *info, + const char *firmware_filename, hwaddr *firmware_load_addr, symbol_fn_t sym_cb); void riscv_load_kernel(MachineState *machine, diff --git a/include/hw/riscv/fdt-common.h b/include/hw/riscv/fdt-common.h index 2d6b9a5d03..017278b611 100644 --- a/include/hw/riscv/fdt-common.h +++ b/include/hw/riscv/fdt-common.h @@ -30,4 +30,9 @@ void create_fdt_socket_cpu_sifive(void *fdt, char *clust_name, int cpu_id, int socket_id, int socket_hartid_base, uint32_t *phandle, uint32_t *intc_phandles); +void create_fdt_plic(void *fdt, hwaddr addr, uint64_t size, + uint32_t plic_phandle, uint32_t int_cells, + uint32_t addr_cells, uint32_t *plic_cells, + uint32_t cells_size, uint32_t ndev_sources, + bool numa_enabled, int socket); #endif diff --git a/include/hw/riscv/sifive_u.h b/include/hw/riscv/sifive_u.h index e4c9860d50..aed966a62d 100644 --- a/include/hw/riscv/sifive_u.h +++ b/include/hw/riscv/sifive_u.h @@ -156,6 +156,8 @@ enum { #define SIFIVE_U_MANAGEMENT_CPU_COUNT 1 #define SIFIVE_U_COMPUTE_CPU_COUNT 4 +#define SIFIVE_U_PLIC_ADDR_CELLS 0 +#define SIFIVE_U_PLIC_INT_CELLS 1 #define SIFIVE_U_PLIC_NUM_SOURCES 54 #define SIFIVE_U_PLIC_NUM_PRIORITIES 7 #define SIFIVE_U_PLIC_PRIORITY_BASE 0x00 diff --git a/include/hw/riscv/tt_atlantis.h b/include/hw/riscv/tt_atlantis.h new file mode 100644 index 0000000000..7f7d4a5a59 --- /dev/null +++ b/include/hw/riscv/tt_atlantis.h @@ -0,0 +1,64 @@ +/* + * 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/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; + + /*< public >*/ + Notifier machine_done; + const MemMapEntry *memmap; + + RISCVHartArrayState soc; + DeviceState *irqchip; + DesignWareI2CState i2c[TT_ATL_NUM_I2C]; + + int fdt_size; +}; + +enum { + 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_UART1_IRQ = 39, +}; + +enum { + TT_ATL_ACLINT, + TT_ATL_BOOTROM, + TT_ATL_DDR_LO, + TT_ATL_DDR_HI, + TT_ATL_I2C0, + TT_ATL_I2C1, + TT_ATL_I2C2, + TT_ATL_I2C3, + TT_ATL_I2C4, + TT_ATL_MAPLIC, + TT_ATL_MIMSIC, + TT_ATL_SAPLIC, + TT_ATL_SIMSIC, + TT_ATL_UART1, +}; + +#endif diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h index 18a2a323a3..36a2def410 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 { @@ -135,7 +136,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/target/riscv/cpu.c b/target/riscv/cpu.c index fa497e5e8a..4d2eb281bf 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -47,6 +47,13 @@ static const char riscv_single_letter_exts[] = "IEMAFDQCBPVH"; const uint32_t misa_bits[] = {RVI, RVE, RVM, RVA, RVF, RVD, RVV, RVC, RVS, RVU, RVH, RVG, RVB, 0}; +#define RISCV_CPU_MVENDORID 0 +#define RISCV_CPU_MIMPID 0 +/* + * marchid allocated for qemu: + * https://github.com/riscv/riscv-isa-manual/blob/main/marchid.md + */ +#define RISCV_CPU_MARCHID 42 /* * From vector_helper.c @@ -418,7 +425,7 @@ int riscv_cpu_max_xlen(RISCVCPUClass *mcc) #ifndef CONFIG_USER_ONLY static uint8_t satp_mode_from_str(const char *satp_mode_str) { - if (!strncmp(satp_mode_str, "mbare", 5)) { + if (!strncmp(satp_mode_str, "svbare", 6)) { return VM_1_10_MBARE; } @@ -1009,6 +1016,19 @@ static void riscv_cpu_realize(DeviceState *dev, Error **errp) mcc->parent_realize(dev, errp); } +static void riscv_cpu_unrealize(DeviceState *dev) +{ + RISCVCPUClass *mcc = RISCV_CPU_GET_CLASS(dev); +#ifndef CONFIG_USER_ONLY + RISCVCPU *cpu = RISCV_CPU(dev); + + if (cpu->cfg.debug) { + riscv_trigger_unrealize(&cpu->env); + } +#endif + mcc->parent_unrealize(dev); +} + bool riscv_cpu_accelerator_compatible(RISCVCPU *cpu) { if (tcg_enabled()) { @@ -1050,6 +1070,9 @@ void riscv_add_satp_mode_properties(Object *obj) { RISCVCPU *cpu = RISCV_CPU(obj); + object_property_add(obj, "svbare", "bool", cpu_riscv_get_satp, + cpu_riscv_set_satp, NULL, &cpu->satp_modes); + if (cpu->env.misa_mxl == MXL_RV32) { object_property_add(obj, "sv32", "bool", cpu_riscv_get_satp, cpu_riscv_set_satp, NULL, &cpu->satp_modes); @@ -1151,6 +1174,11 @@ static void riscv_cpu_init(Object *obj) IRQ_LOCAL_MAX + IRQ_LOCAL_GUEST_MAX); qdev_init_gpio_in_named(DEVICE(cpu), riscv_cpu_set_nmi, "riscv.cpu.rnmi", RNMI_MAX); + + if (mcc->def->num_triggers) { + env->num_triggers = mcc->def->num_triggers; + } + #endif /* CONFIG_USER_ONLY */ cpu->user_options = g_hash_table_new(g_str_hash, g_str_equal); @@ -1183,6 +1211,10 @@ static void riscv_cpu_init(Object *obj) mcc->def->profile->enabled = true; } + cpu->cfg.mvendorid = RISCV_CPU_MVENDORID; + cpu->cfg.marchid = RISCV_CPU_MARCHID; + cpu->cfg.mimpid = RISCV_CPU_MIMPID; + env->misa_ext_mask = env->misa_ext = mcc->def->misa_ext; riscv_cpu_cfg_merge(&cpu->cfg, &mcc->def->cfg); @@ -2603,6 +2635,8 @@ static const Property riscv_cpu_properties[] = { DEFAULT_RNMI_IRQVEC), DEFINE_PROP_UINT64("rnmi-exception-vector", RISCVCPU, env.rnmi_excpvec, DEFAULT_RNMI_EXCPVEC), + DEFINE_PROP_UINT32("num-triggers", RISCVCPU, env.num_triggers, + RV_DEFAULT_NUM_TRIGGERS), #endif DEFINE_PROP_BOOL("short-isa-string", RISCVCPU, cfg.short_isa_string, false), @@ -2664,6 +2698,8 @@ static void riscv_cpu_common_class_init(ObjectClass *c, const void *data) device_class_set_parent_realize(dc, riscv_cpu_realize, &mcc->parent_realize); + device_class_set_parent_unrealize(dc, riscv_cpu_unrealize, + &mcc->parent_unrealize); resettable_class_set_parent_phases(rc, NULL, riscv_cpu_reset_hold, NULL, &mcc->parent_phases); @@ -2746,6 +2782,10 @@ static void riscv_cpu_class_base_init(ObjectClass *c, const void *data) !valid_vm_1_10_32[mcc->def->cfg.max_satp_mode]) { mcc->def->cfg.max_satp_mode = VM_1_10_SV32; } + + if (def->num_triggers) { + mcc->def->num_triggers = def->num_triggers; + } #endif } if (def->priv_spec != RISCV_PROFILE_ATTR_UNUSED) { @@ -3211,6 +3251,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, diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index 7582874c35..bdd28d329b 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -189,7 +189,22 @@ extern RISCVCPUImpliedExtsRule *riscv_multi_ext_implied_rules[]; #define RV_VLEN_MAX 1024 #define RV_MAX_MHPMEVENTS 32 #define RV_MAX_MHPMCOUNTERS 32 -#define RV_MAX_TRIGGERS 2 + +/* + * The Debug 1.0 spec allows a humongous amount of triggers. Section + * "Enumeration" says: "The above algorithm reads back tselect so that + * implementations which have 2^n triggers only need to implement n + * bits of tselect.". tselect can have up to XLEN bits, so the max + * theoretical RV_MAX_TRIGGERS value is 2^XLEN. + * + * Allowing 2^XLEN triggers per hart is silly so we'll set a max to a + * modest 1024 triggers, which is way more than what we see current + * hardware use (most chips uses 2-4 triggers per hart, RISC-V Server + * Ref requires at least 11). With a 1024 max per hart we'll be set + * for a long time ... hopefully. + */ +#define RV_MAX_TRIGGERS 1024 +#define RV_DEFAULT_NUM_TRIGGERS 2 FIELD(VTYPE, VLMUL, 0, 3) FIELD(VTYPE, VSEW, 3, 3) @@ -467,12 +482,18 @@ struct CPUArchState { /* trigger module */ uint16_t mcontext; uint8_t trigger_cur; - uint64_t tdata1[RV_MAX_TRIGGERS]; - uint64_t tdata2[RV_MAX_TRIGGERS]; - uint64_t tdata3[RV_MAX_TRIGGERS]; - struct CPUBreakpoint *cpu_breakpoint[RV_MAX_TRIGGERS]; - struct CPUWatchpoint *cpu_watchpoint[RV_MAX_TRIGGERS]; - QEMUTimer *itrigger_timer[RV_MAX_TRIGGERS]; + /* + * num_triggers is the length of tdata1, tdata2, tdata3, + * cpu_breakpoint, cpu_watchpoint and itrigger_timer + * arrays. + */ + uint32_t num_triggers; + uint64_t *tdata1; + uint64_t *tdata2; + uint64_t *tdata3; + struct CPUBreakpoint **cpu_breakpoint; + struct CPUWatchpoint **cpu_watchpoint; + QEMUTimer **itrigger_timer; int64_t last_icount; bool itrigger_enabled; @@ -571,11 +592,14 @@ typedef struct RISCVCPUDef { RISCVCPUConfig cfg; bool bare; const RISCVCSR *custom_csrs; + /* This is just a setter for env->num_triggers. */ + uint32_t num_triggers; } RISCVCPUDef; /** * RISCVCPUClass: * @parent_realize: The parent class' realize handler. + * @parent_unrealize: The parent class' unrealize handler. * @parent_phases: The parent class' reset phase handlers. * * A RISCV CPU model. @@ -584,6 +608,7 @@ struct RISCVCPUClass { CPUClass parent_class; DeviceRealize parent_realize; + DeviceUnrealize parent_unrealize; ResettablePhases parent_phases; RISCVCPUDef *def; }; diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 59edcdd370..2db07f5dfb 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -1655,10 +1655,18 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, /* Page table updates need to be atomic with MTTCG enabled */ if (updated_pte != pte && !is_debug) { + int pmp_prot, pmp_ret; + if (!adue) { return TRANSLATE_FAIL; } + pmp_ret = get_physical_address_pmp(env, &pmp_prot, pte_addr, + sxlen_bytes, MMU_DATA_STORE, PRV_S); + if (pmp_ret != TRANSLATE_SUCCESS) { + return TRANSLATE_PMP_FAIL; + } + /* * - if accessed or dirty bits need updating, and the PTE is * in RAM, then we do so atomically with a compare and swap. diff --git a/target/riscv/csr.c b/target/riscv/csr.c index dd9726fcf4..7168a4dc12 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -949,7 +949,16 @@ static RISCVException read_vtype(CPURISCVState *env, int csrno, target_ulong *val) { uint64_t vill; - switch (env->xl) { + int xl = env->xl; + /* + * TCG plugins can read registers before env->xl is initialized. + * Fall back to the CPU's maximum XLEN in that early-init case. + */ + if (xl == 0) { + xl = riscv_cpu_mxl(env); + } + + switch (xl) { case MXL_RV32: vill = (uint32_t)env->vill << 31; break; @@ -2012,6 +2021,20 @@ static target_ulong legalize_mpp(CPURISCVState *env, target_ulong old_mpp, return val; } +static uint64_t riscv_write_uxl(CPURISCVState *env, uint64_t val, + uint64_t field) +{ + RISCVMXL xl = riscv_cpu_mxl(env); + uint64_t uxl = get_field(val, field); + + if (uxl == MXL_RV128) { + uxl = xl == MXL_RV128 ? MXL_RV64 : xl; + val = set_field(val, field, uxl); + } + + return val; +} + static RISCVException write_mstatus(CPURISCVState *env, int csrno, target_ulong val, uintptr_t ra) { @@ -2058,17 +2081,8 @@ static RISCVException write_mstatus(CPURISCVState *env, int csrno, if (xl != MXL_RV32 || env->debugger) { if ((val & MSTATUS64_UXL) != 0) { - uint64_t uxl = val & MSTATUS64_UXL >> 32; mask |= MSTATUS64_UXL; - - /* - * uxl = 3 is reserved so write the current xl instead. - * In case xl = MXL_RV128 (3) write MXL_RV64. - */ - if (uxl == 3) { - uxl = xl == MXL_RV128 ? MXL_RV64 : xl; - val = deposit64(val, 32, 2, uxl); - } + val = riscv_write_uxl(env, val, MSTATUS64_UXL); } } @@ -2733,7 +2747,9 @@ static RISCVException rmw_xireg_aia(CPURISCVState *env, int csrno, AIA_MAKE_IREG(isel, priv, virt, vgein, riscv_cpu_mxl_bits(env)), &wide_val, new_val, wr_mask); - *val = wide_val; + if (val) { + *val = wide_val; + } } } else { isel_reserved = true; @@ -3009,7 +3025,9 @@ static RISCVException rmw_xtopei(CPURISCVState *env, int csrno, AIA_MAKE_IREG(ISELECT_IMSIC_TOPEI, priv, virt, vgein, riscv_cpu_mxl_bits(env)), &wide_val, new_val, wr_mask); - *val = wide_val; + if (val) { + *val = wide_val; + } done: if (ret) { @@ -5226,6 +5244,8 @@ static RISCVException write_vsstatus(CPURISCVState *env, int csrno, uint64_t mask = (target_ulong)-1; if ((val & VSSTATUS64_UXL) == 0) { mask &= ~VSSTATUS64_UXL; + } else { + val = riscv_write_uxl(env, val, VSSTATUS64_UXL); } if ((env->henvcfg & HENVCFG_DTE)) { if ((val & SSTATUS_SDT) != 0) { @@ -5437,7 +5457,7 @@ static RISCVException read_tdata(CPURISCVState *env, int csrno, target_ulong *val) { /* return 0 in tdata1 to end the trigger enumeration */ - if (env->trigger_cur >= RV_MAX_TRIGGERS && csrno == CSR_TDATA1) { + if (env->trigger_cur >= env->num_triggers && csrno == CSR_TDATA1) { *val = 0; return RISCV_EXCP_NONE; } diff --git a/target/riscv/debug.c b/target/riscv/debug.c index 30d39ee5cd..ba5bc6ae13 100644 --- a/target/riscv/debug.c +++ b/target/riscv/debug.c @@ -26,6 +26,7 @@ #include "qemu/osdep.h" #include "qemu/log.h" #include "qapi/error.h" +#include "qemu/error-report.h" #include "cpu.h" #include "target/riscv/debug.h" #include "trace.h" @@ -172,7 +173,7 @@ target_ulong tselect_csr_read(CPURISCVState *env) void tselect_csr_write(CPURISCVState *env, target_ulong val) { - if (val < RV_MAX_TRIGGERS) { + if (val < env->num_triggers) { env->trigger_cur = val; } } @@ -701,7 +702,7 @@ static bool check_itrigger_priv(CPURISCVState *env, int index) bool riscv_itrigger_enabled(CPURISCVState *env) { int count; - for (int i = 0; i < RV_MAX_TRIGGERS; i++) { + for (int i = 0; i < env->num_triggers; i++) { if (get_trigger_type(env, i) != TRIGGER_TYPE_INST_CNT) { continue; } @@ -721,7 +722,7 @@ bool riscv_itrigger_enabled(CPURISCVState *env) void helper_itrigger_match(CPURISCVState *env) { int count; - for (int i = 0; i < RV_MAX_TRIGGERS; i++) { + for (int i = 0; i < env->num_triggers; i++) { if (get_trigger_type(env, i) != TRIGGER_TYPE_INST_CNT) { continue; } @@ -750,7 +751,7 @@ static void riscv_itrigger_update_count(CPURISCVState *env) int64_t last_icount = env->last_icount, current_icount; current_icount = env->last_icount = icount_get_raw(); - for (int i = 0; i < RV_MAX_TRIGGERS; i++) { + for (int i = 0; i < env->num_triggers; i++) { if (get_trigger_type(env, i) != TRIGGER_TYPE_INST_CNT) { continue; } @@ -950,7 +951,7 @@ bool riscv_cpu_debug_check_breakpoint(CPUState *cs) int i; QTAILQ_FOREACH(bp, &cs->breakpoints, entry) { - for (i = 0; i < RV_MAX_TRIGGERS; i++) { + for (i = 0; i < env->num_triggers; i++) { trigger_type = get_trigger_type(env, i); if (!trigger_common_match(env, trigger_type, i)) { @@ -996,7 +997,7 @@ bool riscv_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp) int flags; int i; - for (i = 0; i < RV_MAX_TRIGGERS; i++) { + for (i = 0; i < env->num_triggers; i++) { trigger_type = get_trigger_type(env, i); if (!trigger_common_match(env, trigger_type, i)) { @@ -1049,19 +1050,48 @@ void riscv_trigger_realize(CPURISCVState *env) { int i; - for (i = 0; i < RV_MAX_TRIGGERS; i++) { + if (env->num_triggers > RV_MAX_TRIGGERS) { + error_report( + "Invalid configuration: 'num-triggers' must be less than %u", + RV_MAX_TRIGGERS); + exit(1); + } + + env->tdata1 = g_new0(uint64_t, env->num_triggers); + env->tdata2 = g_new0(uint64_t, env->num_triggers); + env->tdata3 = g_new0(uint64_t, env->num_triggers); + env->cpu_breakpoint = g_new0(struct CPUBreakpoint *, env->num_triggers); + env->cpu_watchpoint = g_new0(struct CPUWatchpoint *, env->num_triggers); + env->itrigger_timer = g_new0(QEMUTimer *, env->num_triggers); + + for (i = 0; i < env->num_triggers; i++) { env->itrigger_timer[i] = timer_new_ns(QEMU_CLOCK_VIRTUAL, riscv_itrigger_timer_cb, env); } } +void riscv_trigger_unrealize(CPURISCVState *env) +{ + g_free(env->tdata1); + g_free(env->tdata2); + g_free(env->tdata3); + + g_free(env->cpu_breakpoint); + g_free(env->cpu_watchpoint); + + for (int i = 0; i < env->num_triggers; i++) { + timer_del(env->itrigger_timer[i]); + } + g_free(env->itrigger_timer); +} + void riscv_trigger_reset_hold(CPURISCVState *env) { target_ulong tdata1 = build_tdata1(env, TRIGGER_TYPE_AD_MATCH, 0, 0); int i; /* init to type 2 triggers */ - for (i = 0; i < RV_MAX_TRIGGERS; i++) { + for (i = 0; i < env->num_triggers; i++) { /* * type = TRIGGER_TYPE_AD_MATCH * dmode = 0 (both debug and M-mode can write tdata) diff --git a/target/riscv/debug.h b/target/riscv/debug.h index 55a3ac72e6..a25d099b37 100644 --- a/target/riscv/debug.h +++ b/target/riscv/debug.h @@ -148,6 +148,7 @@ bool riscv_cpu_debug_check_breakpoint(CPUState *cs); bool riscv_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp); void riscv_trigger_realize(CPURISCVState *env); +void riscv_trigger_unrealize(CPURISCVState *env); void riscv_trigger_reset_hold(CPURISCVState *env); bool riscv_itrigger_enabled(CPURISCVState *env); diff --git a/target/riscv/kvm/kvm-cpu.c b/target/riscv/kvm/kvm-cpu.c index f0de5c3071..39d48a9db4 100644 --- a/target/riscv/kvm/kvm-cpu.c +++ b/target/riscv/kvm/kvm-cpu.c @@ -1395,6 +1395,17 @@ int kvm_arch_put_registers(CPUState *cs, KvmPutState level, Error **errp) return ret; } + /* + * For RUNTIME_STATE, KVM already has the correct FP and Vector state + * from the preceding KVM_RUN exit. QEMU never modifies these registers + * during exit handling, so re-syncing is unnecessary. This saves ~68 + * KVM_SET_ONE_REG ioctls per vCPU exit. See also s390x which uses + * the same pattern. + */ + if (KVM_PUT_RUNTIME_STATE == level) { + return ret; + } + ret = kvm_riscv_put_regs_fp(cs); if (ret) { return ret; @@ -1407,11 +1418,9 @@ int kvm_arch_put_registers(CPUState *cs, KvmPutState level, Error **errp) if (KVM_PUT_RESET_STATE == level) { RISCVCPU *cpu = RISCV_CPU(cs); - if (cs->cpu_index == 0) { - ret = kvm_riscv_sync_mpstate_to_kvm(cpu, KVM_MP_STATE_RUNNABLE); - } else { - ret = kvm_riscv_sync_mpstate_to_kvm(cpu, KVM_MP_STATE_STOPPED); - } + int state = cs->cpu_index == 0 ? KVM_MP_STATE_RUNNABLE + : KVM_MP_STATE_STOPPED; + ret = kvm_riscv_sync_mpstate_to_kvm(cpu, state); if (ret) { return ret; } diff --git a/target/riscv/machine.c b/target/riscv/machine.c index 6e70b145a5..ba96ceceef 100644 --- a/target/riscv/machine.c +++ b/target/riscv/machine.c @@ -240,16 +240,22 @@ static int debug_post_load(void *opaque, int version_id) static const VMStateDescription vmstate_debug = { .name = "cpu/debug", - .version_id = 3, - .minimum_version_id = 3, + .version_id = 4, + .minimum_version_id = 4, .needed = debug_needed, .post_load = debug_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT16(env.mcontext, RISCVCPU), VMSTATE_UINT8(env.trigger_cur, RISCVCPU), - VMSTATE_UINT64_ARRAY(env.tdata1, RISCVCPU, RV_MAX_TRIGGERS), - VMSTATE_UINT64_ARRAY(env.tdata2, RISCVCPU, RV_MAX_TRIGGERS), - VMSTATE_UINT64_ARRAY(env.tdata3, RISCVCPU, RV_MAX_TRIGGERS), + VMSTATE_VARRAY_UINT32(env.tdata1, RISCVCPU, + env.num_triggers, 0, + vmstate_info_uint64, uint64_t), + VMSTATE_VARRAY_UINT32(env.tdata2, RISCVCPU, + env.num_triggers, 0, + vmstate_info_uint64, uint64_t), + VMSTATE_VARRAY_UINT32(env.tdata3, RISCVCPU, + env.num_triggers, 0, + vmstate_info_uint64, uint64_t), VMSTATE_END_OF_LIST() } }; diff --git a/tests/functional/riscv64/meson.build b/tests/functional/riscv64/meson.build index 5871211e89..c9d4c090fb 100644 --- a/tests/functional/riscv64/meson.build +++ b/tests/functional/riscv64/meson.build @@ -14,5 +14,6 @@ tests_riscv64_system_thorough = [ 'endianness', 'boston', 'sifive_u', + 'tt_atlantis', 'tuxrun', ] diff --git a/tests/functional/riscv64/test_opensbi.py b/tests/functional/riscv64/test_opensbi.py index d077e40f42..0f8beb7e7a 100755 --- a/tests/functional/riscv64/test_opensbi.py +++ b/tests/functional/riscv64/test_opensbi.py @@ -28,6 +28,10 @@ class RiscvOpenSBI(QemuSystemTest): 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 0000000000..48abd5cd27 --- /dev/null +++ b/tests/functional/riscv64/test_tt_atlantis.py @@ -0,0 +1,57 @@ +#!/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 + +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) + + def test_riscv64_tt_atlantis(self): + # tt-atlantis machine has no PCI host yet, so no disk + self.do_test_riscv64_tt_atlantis(False) + + +if __name__ == '__main__': + LinuxKernelTest.main() diff --git a/tests/functional/riscv64/test_tuxrun.py b/tests/functional/riscv64/test_tuxrun.py index 0d8de36204..6203a45060 100755 --- a/tests/functional/riscv64/test_tuxrun.py +++ b/tests/functional/riscv64/test_tuxrun.py @@ -17,8 +17,8 @@ from qemu_test.tuxruntest import TuxRunBaselineTest class TuxRunRiscV64Test(TuxRunBaselineTest): ASSET_RISCV64_KERNEL = Asset( - 'https://storage.tuxboot.com/buildroot/20241119/riscv64/Image', - '2bd8132a3bf21570290042324fff48c987f42f2a00c08de979f43f0662ebadba') + 'https://storage.tuxboot.com/kernels/6.11.9/riscv64/Image', + '174f8bb87f08961e54fa3fcd954a8e31f4645f6d6af4dd43983d5e9841490fb0') ASSET_RISCV64_ROOTFS = Asset( 'https://storage.tuxboot.com/buildroot/20241119/riscv64/rootfs.ext4.zst', 'aa4736a9872651dfc0d95e709465eedf1134fd19d42b8cb305bfd776f9801004') @@ -41,6 +41,13 @@ class TuxRunRiscV64Test(TuxRunBaselineTest): self.common_tuxrun(kernel_asset=self.ASSET_RISCV64_KERNEL, rootfs_asset=self.ASSET_RISCV64_ROOTFS) + def test_riscv64_aia(self): + self.set_machine('virt') + self.vm.set_machine('virt,aia=aplic-imsic') + self.cpu='max' + self.common_tuxrun(kernel_asset=self.ASSET_RISCV64_KERNEL, + rootfs_asset=self.ASSET_RISCV64_ROOTFS) + def test_riscv64_rv32(self): self.set_machine('virt') self.cpu='rv32' |
