diff mbox series

[v2,10/15] hw/riscv/riscv-iommu: add ATS support

Message ID 20240307160319.675044-11-dbarboza@ventanamicro.com (mailing list archive)
State New, archived
Headers show
Series riscv: QEMU RISC-V IOMMU Support | expand

Commit Message

Daniel Henrique Barboza March 7, 2024, 4:03 p.m. UTC
From: Tomasz Jeznach <tjeznach@rivosinc.com>

Add PCIe Address Translation Services (ATS) capabilities to the IOMMU.
This will add support for ATS translation requests in Fault/Event
queues, Page-request queue and IOATC invalidations.

Signed-off-by: Tomasz Jeznach <tjeznach@rivosinc.com>
Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
---
 hw/riscv/riscv-iommu-bits.h |  43 ++++++++++++++-
 hw/riscv/riscv-iommu.c      | 107 +++++++++++++++++++++++++++++++++---
 hw/riscv/riscv-iommu.h      |   1 +
 hw/riscv/trace-events       |   3 +
 4 files changed, 145 insertions(+), 9 deletions(-)

Comments

Frank Chang May 8, 2024, 2:57 a.m. UTC | #1
Hi Daniel,

Daniel Henrique Barboza <dbarboza@ventanamicro.com> 於 2024年3月8日 週五 上午12:06寫道:
>
> From: Tomasz Jeznach <tjeznach@rivosinc.com>
>
> Add PCIe Address Translation Services (ATS) capabilities to the IOMMU.
> This will add support for ATS translation requests in Fault/Event
> queues, Page-request queue and IOATC invalidations.
>
> Signed-off-by: Tomasz Jeznach <tjeznach@rivosinc.com>
> Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
> ---
>  hw/riscv/riscv-iommu-bits.h |  43 ++++++++++++++-
>  hw/riscv/riscv-iommu.c      | 107 +++++++++++++++++++++++++++++++++---
>  hw/riscv/riscv-iommu.h      |   1 +
>  hw/riscv/trace-events       |   3 +
>  4 files changed, 145 insertions(+), 9 deletions(-)
>
> diff --git a/hw/riscv/riscv-iommu-bits.h b/hw/riscv/riscv-iommu-bits.h
> index 9d645d69ea..0994f5ce48 100644
> --- a/hw/riscv/riscv-iommu-bits.h
> +++ b/hw/riscv/riscv-iommu-bits.h
> @@ -81,6 +81,7 @@ struct riscv_iommu_pq_record {
>  #define RISCV_IOMMU_CAP_SV57X4          BIT_ULL(19)
>  #define RISCV_IOMMU_CAP_MSI_FLAT        BIT_ULL(22)
>  #define RISCV_IOMMU_CAP_MSI_MRIF        BIT_ULL(23)
> +#define RISCV_IOMMU_CAP_ATS             BIT_ULL(25)
>  #define RISCV_IOMMU_CAP_IGS             GENMASK_ULL(29, 28)
>  #define RISCV_IOMMU_CAP_PAS             GENMASK_ULL(37, 32)
>  #define RISCV_IOMMU_CAP_PD8             BIT_ULL(38)
> @@ -201,6 +202,7 @@ struct riscv_iommu_dc {
>
>  /* Translation control fields */
>  #define RISCV_IOMMU_DC_TC_V             BIT_ULL(0)
> +#define RISCV_IOMMU_DC_TC_EN_ATS        BIT_ULL(1)
>  #define RISCV_IOMMU_DC_TC_DTF           BIT_ULL(4)
>  #define RISCV_IOMMU_DC_TC_PDTV          BIT_ULL(5)
>  #define RISCV_IOMMU_DC_TC_PRPR          BIT_ULL(6)
> @@ -259,6 +261,20 @@ struct riscv_iommu_command {
>  #define RISCV_IOMMU_CMD_IODIR_DV        BIT_ULL(33)
>  #define RISCV_IOMMU_CMD_IODIR_DID       GENMASK_ULL(63, 40)
>
> +/* 3.1.4 I/O MMU PCIe ATS */
> +#define RISCV_IOMMU_CMD_ATS_OPCODE              4
> +#define RISCV_IOMMU_CMD_ATS_FUNC_INVAL          0
> +#define RISCV_IOMMU_CMD_ATS_FUNC_PRGR           1
> +#define RISCV_IOMMU_CMD_ATS_PID         GENMASK_ULL(31, 12)
> +#define RISCV_IOMMU_CMD_ATS_PV          BIT_ULL(32)
> +#define RISCV_IOMMU_CMD_ATS_DSV         BIT_ULL(33)
> +#define RISCV_IOMMU_CMD_ATS_RID         GENMASK_ULL(55, 40)
> +#define RISCV_IOMMU_CMD_ATS_DSEG        GENMASK_ULL(63, 56)
> +/* dword1 is the ATS payload, two different payload types for INVAL and PRGR */
> +
> +/* ATS.PRGR payload */
> +#define RISCV_IOMMU_CMD_ATS_PRGR_RESP_CODE      GENMASK_ULL(47, 44)
> +
>  enum riscv_iommu_dc_fsc_atp_modes {
>      RISCV_IOMMU_DC_FSC_MODE_BARE = 0,
>      RISCV_IOMMU_DC_FSC_IOSATP_MODE_SV32 = 8,
> @@ -322,7 +338,32 @@ enum riscv_iommu_fq_ttypes {
>      RISCV_IOMMU_FQ_TTYPE_TADDR_INST_FETCH = 5,
>      RISCV_IOMMU_FQ_TTYPE_TADDR_RD = 6,
>      RISCV_IOMMU_FQ_TTYPE_TADDR_WR = 7,
> -    RISCV_IOMMU_FW_TTYPE_PCIE_MSG_REQ = 8,
> +    RISCV_IOMMU_FQ_TTYPE_PCIE_ATS_REQ = 8,
> +    RISCV_IOMMU_FW_TTYPE_PCIE_MSG_REQ = 9,
> +};
> +
> +/* Header fields */
> +#define RISCV_IOMMU_PREQ_HDR_PID        GENMASK_ULL(31, 12)
> +#define RISCV_IOMMU_PREQ_HDR_PV         BIT_ULL(32)
> +#define RISCV_IOMMU_PREQ_HDR_PRIV       BIT_ULL(33)
> +#define RISCV_IOMMU_PREQ_HDR_EXEC       BIT_ULL(34)
> +#define RISCV_IOMMU_PREQ_HDR_DID        GENMASK_ULL(63, 40)
> +
> +/* Payload fields */
> +#define RISCV_IOMMU_PREQ_PAYLOAD_R      BIT_ULL(0)
> +#define RISCV_IOMMU_PREQ_PAYLOAD_W      BIT_ULL(1)
> +#define RISCV_IOMMU_PREQ_PAYLOAD_L      BIT_ULL(2)
> +#define RISCV_IOMMU_PREQ_PAYLOAD_M      GENMASK_ULL(2, 0)
> +#define RISCV_IOMMU_PREQ_PRG_INDEX      GENMASK_ULL(11, 3)
> +#define RISCV_IOMMU_PREQ_UADDR          GENMASK_ULL(63, 12)
> +
> +
> +/*
> + * struct riscv_iommu_msi_pte - MSI Page Table Entry
> + */
> +struct riscv_iommu_msi_pte {
> +      uint64_t pte;
> +      uint64_t mrif_info;
>  };
>
>  /* Fields on pte */
> diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c
> index 03a610fa75..7af5929b10 100644
> --- a/hw/riscv/riscv-iommu.c
> +++ b/hw/riscv/riscv-iommu.c
> @@ -576,7 +576,7 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx)
>              RISCV_IOMMU_DC_IOHGATP_MODE_BARE);
>          ctx->satp = set_field(0, RISCV_IOMMU_ATP_MODE_FIELD,
>              RISCV_IOMMU_DC_FSC_MODE_BARE);
> -        ctx->tc = RISCV_IOMMU_DC_TC_V;
> +        ctx->tc = RISCV_IOMMU_DC_TC_EN_ATS | RISCV_IOMMU_DC_TC_V;

We should OR RISCV_IOMMU_DC_TC_EN_ATS only when IOMMU has ATS capability.
(i.e. s->enable_ats == true).

>          ctx->ta = 0;
>          ctx->msiptp = 0;
>          return 0;
> @@ -1021,6 +1021,18 @@ static int riscv_iommu_translate(RISCVIOMMUState *s, RISCVIOMMUContext *ctx,
>      enable_pri = (iotlb->perm == IOMMU_NONE) && (ctx->tc & BIT_ULL(32));
>      enable_pasid = (ctx->tc & RISCV_IOMMU_DC_TC_PDTV);
>
> +    /* Check for ATS request. */
> +    if (iotlb->perm == IOMMU_NONE) {
> +        /* Check if ATS is disabled. */
> +        if (!(ctx->tc & RISCV_IOMMU_DC_TC_EN_ATS)) {
> +            enable_pri = false;
> +            fault = RISCV_IOMMU_FQ_CAUSE_TTYPE_BLOCKED;
> +            goto done;
> +        }
> +        trace_riscv_iommu_ats(s->parent_obj.id, PCI_BUS_NUM(ctx->devid),
> +                PCI_SLOT(ctx->devid), PCI_FUNC(ctx->devid), iotlb->iova);

It's possible that iotlb->perm == IOMMU_NONE,
but the translation request comes from riscv_iommu_process_dbg().

> +    }
> +
>      iot = riscv_iommu_iot_lookup(ctx, iot_cache, iotlb->iova);
>      perm = iot ? iot->perm : IOMMU_NONE;
>      if (perm != IOMMU_NONE) {
> @@ -1067,13 +1079,10 @@ done:
>
>      if (enable_faults && fault) {
>          struct riscv_iommu_fq_record ev;
> -        unsigned ttype;
> -
> -        if (iotlb->perm & IOMMU_RW) {
> -            ttype = RISCV_IOMMU_FQ_TTYPE_UADDR_WR;
> -        } else {
> -            ttype = RISCV_IOMMU_FQ_TTYPE_UADDR_RD;
> -        }
> +        const unsigned ttype =
> +            (iotlb->perm & IOMMU_RW) ? RISCV_IOMMU_FQ_TTYPE_UADDR_WR :
> +            ((iotlb->perm & IOMMU_RO) ? RISCV_IOMMU_FQ_TTYPE_UADDR_RD :
> +            RISCV_IOMMU_FQ_TTYPE_PCIE_ATS_REQ);
>          ev.hdr = set_field(0, RISCV_IOMMU_FQ_HDR_CAUSE, fault);
>          ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_TTYPE, ttype);
>          ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_PV, enable_pasid);
> @@ -1105,6 +1114,73 @@ static MemTxResult riscv_iommu_iofence(RISCVIOMMUState *s, bool notify,
>          MEMTXATTRS_UNSPECIFIED);
>  }
>
> +static void riscv_iommu_ats(RISCVIOMMUState *s,
> +    struct riscv_iommu_command *cmd, IOMMUNotifierFlag flag,
> +    IOMMUAccessFlags perm,
> +    void (*trace_fn)(const char *id))
> +{
> +    RISCVIOMMUSpace *as = NULL;
> +    IOMMUNotifier *n;
> +    IOMMUTLBEvent event;
> +    uint32_t pasid;
> +    uint32_t devid;
> +    const bool pv = cmd->dword0 & RISCV_IOMMU_CMD_ATS_PV;
> +
> +    if (cmd->dword0 & RISCV_IOMMU_CMD_ATS_DSV) {
> +        /* Use device segment and requester id */
> +        devid = get_field(cmd->dword0,
> +            RISCV_IOMMU_CMD_ATS_DSEG | RISCV_IOMMU_CMD_ATS_RID);
> +    } else {
> +        devid = get_field(cmd->dword0, RISCV_IOMMU_CMD_ATS_RID);
> +    }
> +
> +    pasid = get_field(cmd->dword0, RISCV_IOMMU_CMD_ATS_PID);
> +
> +    qemu_mutex_lock(&s->core_lock);
> +    QLIST_FOREACH(as, &s->spaces, list) {
> +        if (as->devid == devid) {
> +            break;
> +        }
> +    }
> +    qemu_mutex_unlock(&s->core_lock);
> +
> +    if (!as || !as->notifier) {
> +        return;
> +    }
> +
> +    event.type = flag;
> +    event.entry.perm = perm;
> +    event.entry.target_as = s->target_as;
> +
> +    IOMMU_NOTIFIER_FOREACH(n, &as->iova_mr) {
> +        if (!pv || n->iommu_idx == pasid) {
> +            event.entry.iova = n->start;
> +            event.entry.addr_mask = n->end - n->start;
> +            trace_fn(as->iova_mr.parent_obj.name);
> +            memory_region_notify_iommu_one(n, &event);
> +        }
> +    }
> +}
> +
> +static void riscv_iommu_ats_inval(RISCVIOMMUState *s,
> +    struct riscv_iommu_command *cmd)
> +{
> +    return riscv_iommu_ats(s, cmd, IOMMU_NOTIFIER_DEVIOTLB_UNMAP, IOMMU_NONE,
> +                           trace_riscv_iommu_ats_inval);
> +}
> +
> +static void riscv_iommu_ats_prgr(RISCVIOMMUState *s,
> +    struct riscv_iommu_command *cmd)
> +{
> +    unsigned resp_code = get_field(cmd->dword1,
> +                                   RISCV_IOMMU_CMD_ATS_PRGR_RESP_CODE);
> +
> +    /* Using the access flag to carry response code information */
> +    IOMMUAccessFlags perm = resp_code ? IOMMU_NONE : IOMMU_RW;
> +    return riscv_iommu_ats(s, cmd, IOMMU_NOTIFIER_MAP, perm,
> +                           trace_riscv_iommu_ats_prgr);
> +}
> +
>  static void riscv_iommu_process_ddtp(RISCVIOMMUState *s)
>  {
>      uint64_t old_ddtp = s->ddtp;
> @@ -1260,6 +1336,17 @@ static void riscv_iommu_process_cq_tail(RISCVIOMMUState *s)
>                  get_field(cmd.dword0, RISCV_IOMMU_CMD_IODIR_PID));
>              break;
>
> +        /* ATS commands */
> +        case RISCV_IOMMU_CMD(RISCV_IOMMU_CMD_ATS_FUNC_INVAL,
> +                             RISCV_IOMMU_CMD_ATS_OPCODE):
> +            riscv_iommu_ats_inval(s, &cmd);
> +            break;
> +
> +        case RISCV_IOMMU_CMD(RISCV_IOMMU_CMD_ATS_FUNC_PRGR,
> +                             RISCV_IOMMU_CMD_ATS_OPCODE):
> +            riscv_iommu_ats_prgr(s, &cmd);
> +            break;
> +

PCIe ATS commands are supported only when capabilities.ATS is set to 1
(i.e. s->enable_ats == true).

Regards,
Frank Chang

>          default:
>          cmd_ill:
>              /* Invalid instruction, do not advance instruction index. */
> @@ -1648,6 +1735,9 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp)
>      if (s->enable_msi) {
>          s->cap |= RISCV_IOMMU_CAP_MSI_FLAT | RISCV_IOMMU_CAP_MSI_MRIF;
>      }
> +    if (s->enable_ats) {
> +        s->cap |= RISCV_IOMMU_CAP_ATS;
> +    }
>      if (s->enable_s_stage) {
>          s->cap |= RISCV_IOMMU_CAP_SV32 | RISCV_IOMMU_CAP_SV39 |
>                    RISCV_IOMMU_CAP_SV48 | RISCV_IOMMU_CAP_SV57;
> @@ -1765,6 +1855,7 @@ static Property riscv_iommu_properties[] = {
>      DEFINE_PROP_UINT32("ioatc-limit", RISCVIOMMUState, iot_limit,
>          LIMIT_CACHE_IOT),
>      DEFINE_PROP_BOOL("intremap", RISCVIOMMUState, enable_msi, TRUE),
> +    DEFINE_PROP_BOOL("ats", RISCVIOMMUState, enable_ats, TRUE),
>      DEFINE_PROP_BOOL("off", RISCVIOMMUState, enable_off, TRUE),
>      DEFINE_PROP_BOOL("s-stage", RISCVIOMMUState, enable_s_stage, TRUE),
>      DEFINE_PROP_BOOL("g-stage", RISCVIOMMUState, enable_g_stage, TRUE),
> diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h
> index 9b33fb97ef..47f3fdad58 100644
> --- a/hw/riscv/riscv-iommu.h
> +++ b/hw/riscv/riscv-iommu.h
> @@ -38,6 +38,7 @@ struct RISCVIOMMUState {
>
>      bool enable_off;      /* Enable out-of-reset OFF mode (DMA disabled) */
>      bool enable_msi;      /* Enable MSI remapping */
> +    bool enable_ats;      /* Enable ATS support */
>      bool enable_s_stage;  /* Enable S/VS-Stage translation */
>      bool enable_g_stage;  /* Enable G-Stage translation */
>
> diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
> index 42a97caffa..4b486b6420 100644
> --- a/hw/riscv/trace-events
> +++ b/hw/riscv/trace-events
> @@ -9,3 +9,6 @@ riscv_iommu_msi(const char *id, unsigned b, unsigned d, unsigned f, uint64_t iov
>  riscv_iommu_cmd(const char *id, uint64_t l, uint64_t u) "%s: command 0x%"PRIx64" 0x%"PRIx64
>  riscv_iommu_notifier_add(const char *id) "%s: dev-iotlb notifier added"
>  riscv_iommu_notifier_del(const char *id) "%s: dev-iotlb notifier removed"
> +riscv_iommu_ats(const char *id, unsigned b, unsigned d, unsigned f, uint64_t iova) "%s: translate request %04x:%02x.%u iova: 0x%"PRIx64
> +riscv_iommu_ats_inval(const char *id) "%s: dev-iotlb invalidate"
> +riscv_iommu_ats_prgr(const char *id) "%s: dev-iotlb page request group response"
> --
> 2.43.2
>
>
Daniel Henrique Barboza May 17, 2024, 9:29 a.m. UTC | #2
Hi Frank,


On 5/7/24 23:57, Frank Chang wrote:
> Hi Daniel,
> 
> Daniel Henrique Barboza <dbarboza@ventanamicro.com> 於 2024年3月8日 週五 上午12:06寫道:
>>
>> From: Tomasz Jeznach <tjeznach@rivosinc.com>
>>
>> Add PCIe Address Translation Services (ATS) capabilities to the IOMMU.
>> This will add support for ATS translation requests in Fault/Event
>> queues, Page-request queue and IOATC invalidations.
>>
>> Signed-off-by: Tomasz Jeznach <tjeznach@rivosinc.com>
>> Signed-off-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
>> ---
>>   hw/riscv/riscv-iommu-bits.h |  43 ++++++++++++++-
>>   hw/riscv/riscv-iommu.c      | 107 +++++++++++++++++++++++++++++++++---
>>   hw/riscv/riscv-iommu.h      |   1 +
>>   hw/riscv/trace-events       |   3 +
>>   4 files changed, 145 insertions(+), 9 deletions(-)
>>
>> diff --git a/hw/riscv/riscv-iommu-bits.h b/hw/riscv/riscv-iommu-bits.h
>> index 9d645d69ea..0994f5ce48 100644
>> --- a/hw/riscv/riscv-iommu-bits.h
>> +++ b/hw/riscv/riscv-iommu-bits.h
>> @@ -81,6 +81,7 @@ struct riscv_iommu_pq_record {
>>   #define RISCV_IOMMU_CAP_SV57X4          BIT_ULL(19)
>>   #define RISCV_IOMMU_CAP_MSI_FLAT        BIT_ULL(22)
>>   #define RISCV_IOMMU_CAP_MSI_MRIF        BIT_ULL(23)
>> +#define RISCV_IOMMU_CAP_ATS             BIT_ULL(25)
>>   #define RISCV_IOMMU_CAP_IGS             GENMASK_ULL(29, 28)
>>   #define RISCV_IOMMU_CAP_PAS             GENMASK_ULL(37, 32)
>>   #define RISCV_IOMMU_CAP_PD8             BIT_ULL(38)
>> @@ -201,6 +202,7 @@ struct riscv_iommu_dc {
>>
>>   /* Translation control fields */
>>   #define RISCV_IOMMU_DC_TC_V             BIT_ULL(0)
>> +#define RISCV_IOMMU_DC_TC_EN_ATS        BIT_ULL(1)
>>   #define RISCV_IOMMU_DC_TC_DTF           BIT_ULL(4)
>>   #define RISCV_IOMMU_DC_TC_PDTV          BIT_ULL(5)
>>   #define RISCV_IOMMU_DC_TC_PRPR          BIT_ULL(6)
>> @@ -259,6 +261,20 @@ struct riscv_iommu_command {
>>   #define RISCV_IOMMU_CMD_IODIR_DV        BIT_ULL(33)
>>   #define RISCV_IOMMU_CMD_IODIR_DID       GENMASK_ULL(63, 40)
>>
>> +/* 3.1.4 I/O MMU PCIe ATS */
>> +#define RISCV_IOMMU_CMD_ATS_OPCODE              4
>> +#define RISCV_IOMMU_CMD_ATS_FUNC_INVAL          0
>> +#define RISCV_IOMMU_CMD_ATS_FUNC_PRGR           1
>> +#define RISCV_IOMMU_CMD_ATS_PID         GENMASK_ULL(31, 12)
>> +#define RISCV_IOMMU_CMD_ATS_PV          BIT_ULL(32)
>> +#define RISCV_IOMMU_CMD_ATS_DSV         BIT_ULL(33)
>> +#define RISCV_IOMMU_CMD_ATS_RID         GENMASK_ULL(55, 40)
>> +#define RISCV_IOMMU_CMD_ATS_DSEG        GENMASK_ULL(63, 56)
>> +/* dword1 is the ATS payload, two different payload types for INVAL and PRGR */
>> +
>> +/* ATS.PRGR payload */
>> +#define RISCV_IOMMU_CMD_ATS_PRGR_RESP_CODE      GENMASK_ULL(47, 44)
>> +
>>   enum riscv_iommu_dc_fsc_atp_modes {
>>       RISCV_IOMMU_DC_FSC_MODE_BARE = 0,
>>       RISCV_IOMMU_DC_FSC_IOSATP_MODE_SV32 = 8,
>> @@ -322,7 +338,32 @@ enum riscv_iommu_fq_ttypes {
>>       RISCV_IOMMU_FQ_TTYPE_TADDR_INST_FETCH = 5,
>>       RISCV_IOMMU_FQ_TTYPE_TADDR_RD = 6,
>>       RISCV_IOMMU_FQ_TTYPE_TADDR_WR = 7,
>> -    RISCV_IOMMU_FW_TTYPE_PCIE_MSG_REQ = 8,
>> +    RISCV_IOMMU_FQ_TTYPE_PCIE_ATS_REQ = 8,
>> +    RISCV_IOMMU_FW_TTYPE_PCIE_MSG_REQ = 9,
>> +};
>> +
>> +/* Header fields */
>> +#define RISCV_IOMMU_PREQ_HDR_PID        GENMASK_ULL(31, 12)
>> +#define RISCV_IOMMU_PREQ_HDR_PV         BIT_ULL(32)
>> +#define RISCV_IOMMU_PREQ_HDR_PRIV       BIT_ULL(33)
>> +#define RISCV_IOMMU_PREQ_HDR_EXEC       BIT_ULL(34)
>> +#define RISCV_IOMMU_PREQ_HDR_DID        GENMASK_ULL(63, 40)
>> +
>> +/* Payload fields */
>> +#define RISCV_IOMMU_PREQ_PAYLOAD_R      BIT_ULL(0)
>> +#define RISCV_IOMMU_PREQ_PAYLOAD_W      BIT_ULL(1)
>> +#define RISCV_IOMMU_PREQ_PAYLOAD_L      BIT_ULL(2)
>> +#define RISCV_IOMMU_PREQ_PAYLOAD_M      GENMASK_ULL(2, 0)
>> +#define RISCV_IOMMU_PREQ_PRG_INDEX      GENMASK_ULL(11, 3)
>> +#define RISCV_IOMMU_PREQ_UADDR          GENMASK_ULL(63, 12)
>> +
>> +
>> +/*
>> + * struct riscv_iommu_msi_pte - MSI Page Table Entry
>> + */
>> +struct riscv_iommu_msi_pte {
>> +      uint64_t pte;
>> +      uint64_t mrif_info;
>>   };
>>
>>   /* Fields on pte */
>> diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c
>> index 03a610fa75..7af5929b10 100644
>> --- a/hw/riscv/riscv-iommu.c
>> +++ b/hw/riscv/riscv-iommu.c
>> @@ -576,7 +576,7 @@ static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx)
>>               RISCV_IOMMU_DC_IOHGATP_MODE_BARE);
>>           ctx->satp = set_field(0, RISCV_IOMMU_ATP_MODE_FIELD,
>>               RISCV_IOMMU_DC_FSC_MODE_BARE);
>> -        ctx->tc = RISCV_IOMMU_DC_TC_V;
>> +        ctx->tc = RISCV_IOMMU_DC_TC_EN_ATS | RISCV_IOMMU_DC_TC_V;
> 
> We should OR RISCV_IOMMU_DC_TC_EN_ATS only when IOMMU has ATS capability.
> (i.e. s->enable_ats == true).
> 
>>           ctx->ta = 0;
>>           ctx->msiptp = 0;
>>           return 0;
>> @@ -1021,6 +1021,18 @@ static int riscv_iommu_translate(RISCVIOMMUState *s, RISCVIOMMUContext *ctx,
>>       enable_pri = (iotlb->perm == IOMMU_NONE) && (ctx->tc & BIT_ULL(32));
>>       enable_pasid = (ctx->tc & RISCV_IOMMU_DC_TC_PDTV);
>>
>> +    /* Check for ATS request. */
>> +    if (iotlb->perm == IOMMU_NONE) {
>> +        /* Check if ATS is disabled. */
>> +        if (!(ctx->tc & RISCV_IOMMU_DC_TC_EN_ATS)) {
>> +            enable_pri = false;
>> +            fault = RISCV_IOMMU_FQ_CAUSE_TTYPE_BLOCKED;
>> +            goto done;
>> +        }
>> +        trace_riscv_iommu_ats(s->parent_obj.id, PCI_BUS_NUM(ctx->devid),
>> +                PCI_SLOT(ctx->devid), PCI_FUNC(ctx->devid), iotlb->iova);
> 
> It's possible that iotlb->perm == IOMMU_NONE,
> but the translation request comes from riscv_iommu_process_dbg().

That's true. I don't see an easy way to distinguish at this point whether
the translation was triggered by an actual ATS request or a DBG request.

I'll remove this trace since it's ambiguous.  There are enough traces in ATS
code in riscv_iommu_ats_inval() and riscv_iommu_ats_prgr(). We also have a trace
for each command being processed in riscv_iommu_process_cq_tail().


Thanks,


Daniel

> 
>> +    }
>> +
>>       iot = riscv_iommu_iot_lookup(ctx, iot_cache, iotlb->iova);
>>       perm = iot ? iot->perm : IOMMU_NONE;
>>       if (perm != IOMMU_NONE) {
>> @@ -1067,13 +1079,10 @@ done:
>>
>>       if (enable_faults && fault) {
>>           struct riscv_iommu_fq_record ev;
>> -        unsigned ttype;
>> -
>> -        if (iotlb->perm & IOMMU_RW) {
>> -            ttype = RISCV_IOMMU_FQ_TTYPE_UADDR_WR;
>> -        } else {
>> -            ttype = RISCV_IOMMU_FQ_TTYPE_UADDR_RD;
>> -        }
>> +        const unsigned ttype =
>> +            (iotlb->perm & IOMMU_RW) ? RISCV_IOMMU_FQ_TTYPE_UADDR_WR :
>> +            ((iotlb->perm & IOMMU_RO) ? RISCV_IOMMU_FQ_TTYPE_UADDR_RD :
>> +            RISCV_IOMMU_FQ_TTYPE_PCIE_ATS_REQ);
>>           ev.hdr = set_field(0, RISCV_IOMMU_FQ_HDR_CAUSE, fault);
>>           ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_TTYPE, ttype);
>>           ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_PV, enable_pasid);
>> @@ -1105,6 +1114,73 @@ static MemTxResult riscv_iommu_iofence(RISCVIOMMUState *s, bool notify,
>>           MEMTXATTRS_UNSPECIFIED);
>>   }
>>
>> +static void riscv_iommu_ats(RISCVIOMMUState *s,
>> +    struct riscv_iommu_command *cmd, IOMMUNotifierFlag flag,
>> +    IOMMUAccessFlags perm,
>> +    void (*trace_fn)(const char *id))
>> +{
>> +    RISCVIOMMUSpace *as = NULL;
>> +    IOMMUNotifier *n;
>> +    IOMMUTLBEvent event;
>> +    uint32_t pasid;
>> +    uint32_t devid;
>> +    const bool pv = cmd->dword0 & RISCV_IOMMU_CMD_ATS_PV;
>> +
>> +    if (cmd->dword0 & RISCV_IOMMU_CMD_ATS_DSV) {
>> +        /* Use device segment and requester id */
>> +        devid = get_field(cmd->dword0,
>> +            RISCV_IOMMU_CMD_ATS_DSEG | RISCV_IOMMU_CMD_ATS_RID);
>> +    } else {
>> +        devid = get_field(cmd->dword0, RISCV_IOMMU_CMD_ATS_RID);
>> +    }
>> +
>> +    pasid = get_field(cmd->dword0, RISCV_IOMMU_CMD_ATS_PID);
>> +
>> +    qemu_mutex_lock(&s->core_lock);
>> +    QLIST_FOREACH(as, &s->spaces, list) {
>> +        if (as->devid == devid) {
>> +            break;
>> +        }
>> +    }
>> +    qemu_mutex_unlock(&s->core_lock);
>> +
>> +    if (!as || !as->notifier) {
>> +        return;
>> +    }
>> +
>> +    event.type = flag;
>> +    event.entry.perm = perm;
>> +    event.entry.target_as = s->target_as;
>> +
>> +    IOMMU_NOTIFIER_FOREACH(n, &as->iova_mr) {
>> +        if (!pv || n->iommu_idx == pasid) {
>> +            event.entry.iova = n->start;
>> +            event.entry.addr_mask = n->end - n->start;
>> +            trace_fn(as->iova_mr.parent_obj.name);
>> +            memory_region_notify_iommu_one(n, &event);
>> +        }
>> +    }
>> +}
>> +
>> +static void riscv_iommu_ats_inval(RISCVIOMMUState *s,
>> +    struct riscv_iommu_command *cmd)
>> +{
>> +    return riscv_iommu_ats(s, cmd, IOMMU_NOTIFIER_DEVIOTLB_UNMAP, IOMMU_NONE,
>> +                           trace_riscv_iommu_ats_inval);
>> +}
>> +
>> +static void riscv_iommu_ats_prgr(RISCVIOMMUState *s,
>> +    struct riscv_iommu_command *cmd)
>> +{
>> +    unsigned resp_code = get_field(cmd->dword1,
>> +                                   RISCV_IOMMU_CMD_ATS_PRGR_RESP_CODE);
>> +
>> +    /* Using the access flag to carry response code information */
>> +    IOMMUAccessFlags perm = resp_code ? IOMMU_NONE : IOMMU_RW;
>> +    return riscv_iommu_ats(s, cmd, IOMMU_NOTIFIER_MAP, perm,
>> +                           trace_riscv_iommu_ats_prgr);
>> +}
>> +
>>   static void riscv_iommu_process_ddtp(RISCVIOMMUState *s)
>>   {
>>       uint64_t old_ddtp = s->ddtp;
>> @@ -1260,6 +1336,17 @@ static void riscv_iommu_process_cq_tail(RISCVIOMMUState *s)
>>                   get_field(cmd.dword0, RISCV_IOMMU_CMD_IODIR_PID));
>>               break;
>>
>> +        /* ATS commands */
>> +        case RISCV_IOMMU_CMD(RISCV_IOMMU_CMD_ATS_FUNC_INVAL,
>> +                             RISCV_IOMMU_CMD_ATS_OPCODE):
>> +            riscv_iommu_ats_inval(s, &cmd);
>> +            break;
>> +
>> +        case RISCV_IOMMU_CMD(RISCV_IOMMU_CMD_ATS_FUNC_PRGR,
>> +                             RISCV_IOMMU_CMD_ATS_OPCODE):
>> +            riscv_iommu_ats_prgr(s, &cmd);
>> +            break;
>> +
> 
> PCIe ATS commands are supported only when capabilities.ATS is set to 1
> (i.e. s->enable_ats == true).
> 
> Regards,
> Frank Chang
> 
>>           default:
>>           cmd_ill:
>>               /* Invalid instruction, do not advance instruction index. */
>> @@ -1648,6 +1735,9 @@ static void riscv_iommu_realize(DeviceState *dev, Error **errp)
>>       if (s->enable_msi) {
>>           s->cap |= RISCV_IOMMU_CAP_MSI_FLAT | RISCV_IOMMU_CAP_MSI_MRIF;
>>       }
>> +    if (s->enable_ats) {
>> +        s->cap |= RISCV_IOMMU_CAP_ATS;
>> +    }
>>       if (s->enable_s_stage) {
>>           s->cap |= RISCV_IOMMU_CAP_SV32 | RISCV_IOMMU_CAP_SV39 |
>>                     RISCV_IOMMU_CAP_SV48 | RISCV_IOMMU_CAP_SV57;
>> @@ -1765,6 +1855,7 @@ static Property riscv_iommu_properties[] = {
>>       DEFINE_PROP_UINT32("ioatc-limit", RISCVIOMMUState, iot_limit,
>>           LIMIT_CACHE_IOT),
>>       DEFINE_PROP_BOOL("intremap", RISCVIOMMUState, enable_msi, TRUE),
>> +    DEFINE_PROP_BOOL("ats", RISCVIOMMUState, enable_ats, TRUE),
>>       DEFINE_PROP_BOOL("off", RISCVIOMMUState, enable_off, TRUE),
>>       DEFINE_PROP_BOOL("s-stage", RISCVIOMMUState, enable_s_stage, TRUE),
>>       DEFINE_PROP_BOOL("g-stage", RISCVIOMMUState, enable_g_stage, TRUE),
>> diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h
>> index 9b33fb97ef..47f3fdad58 100644
>> --- a/hw/riscv/riscv-iommu.h
>> +++ b/hw/riscv/riscv-iommu.h
>> @@ -38,6 +38,7 @@ struct RISCVIOMMUState {
>>
>>       bool enable_off;      /* Enable out-of-reset OFF mode (DMA disabled) */
>>       bool enable_msi;      /* Enable MSI remapping */
>> +    bool enable_ats;      /* Enable ATS support */
>>       bool enable_s_stage;  /* Enable S/VS-Stage translation */
>>       bool enable_g_stage;  /* Enable G-Stage translation */
>>
>> diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
>> index 42a97caffa..4b486b6420 100644
>> --- a/hw/riscv/trace-events
>> +++ b/hw/riscv/trace-events
>> @@ -9,3 +9,6 @@ riscv_iommu_msi(const char *id, unsigned b, unsigned d, unsigned f, uint64_t iov
>>   riscv_iommu_cmd(const char *id, uint64_t l, uint64_t u) "%s: command 0x%"PRIx64" 0x%"PRIx64
>>   riscv_iommu_notifier_add(const char *id) "%s: dev-iotlb notifier added"
>>   riscv_iommu_notifier_del(const char *id) "%s: dev-iotlb notifier removed"
>> +riscv_iommu_ats(const char *id, unsigned b, unsigned d, unsigned f, uint64_t iova) "%s: translate request %04x:%02x.%u iova: 0x%"PRIx64
>> +riscv_iommu_ats_inval(const char *id) "%s: dev-iotlb invalidate"
>> +riscv_iommu_ats_prgr(const char *id) "%s: dev-iotlb page request group response"
>> --
>> 2.43.2
>>
>>
diff mbox series

Patch

diff --git a/hw/riscv/riscv-iommu-bits.h b/hw/riscv/riscv-iommu-bits.h
index 9d645d69ea..0994f5ce48 100644
--- a/hw/riscv/riscv-iommu-bits.h
+++ b/hw/riscv/riscv-iommu-bits.h
@@ -81,6 +81,7 @@  struct riscv_iommu_pq_record {
 #define RISCV_IOMMU_CAP_SV57X4          BIT_ULL(19)
 #define RISCV_IOMMU_CAP_MSI_FLAT        BIT_ULL(22)
 #define RISCV_IOMMU_CAP_MSI_MRIF        BIT_ULL(23)
+#define RISCV_IOMMU_CAP_ATS             BIT_ULL(25)
 #define RISCV_IOMMU_CAP_IGS             GENMASK_ULL(29, 28)
 #define RISCV_IOMMU_CAP_PAS             GENMASK_ULL(37, 32)
 #define RISCV_IOMMU_CAP_PD8             BIT_ULL(38)
@@ -201,6 +202,7 @@  struct riscv_iommu_dc {
 
 /* Translation control fields */
 #define RISCV_IOMMU_DC_TC_V             BIT_ULL(0)
+#define RISCV_IOMMU_DC_TC_EN_ATS        BIT_ULL(1)
 #define RISCV_IOMMU_DC_TC_DTF           BIT_ULL(4)
 #define RISCV_IOMMU_DC_TC_PDTV          BIT_ULL(5)
 #define RISCV_IOMMU_DC_TC_PRPR          BIT_ULL(6)
@@ -259,6 +261,20 @@  struct riscv_iommu_command {
 #define RISCV_IOMMU_CMD_IODIR_DV        BIT_ULL(33)
 #define RISCV_IOMMU_CMD_IODIR_DID       GENMASK_ULL(63, 40)
 
+/* 3.1.4 I/O MMU PCIe ATS */
+#define RISCV_IOMMU_CMD_ATS_OPCODE              4
+#define RISCV_IOMMU_CMD_ATS_FUNC_INVAL          0
+#define RISCV_IOMMU_CMD_ATS_FUNC_PRGR           1
+#define RISCV_IOMMU_CMD_ATS_PID         GENMASK_ULL(31, 12)
+#define RISCV_IOMMU_CMD_ATS_PV          BIT_ULL(32)
+#define RISCV_IOMMU_CMD_ATS_DSV         BIT_ULL(33)
+#define RISCV_IOMMU_CMD_ATS_RID         GENMASK_ULL(55, 40)
+#define RISCV_IOMMU_CMD_ATS_DSEG        GENMASK_ULL(63, 56)
+/* dword1 is the ATS payload, two different payload types for INVAL and PRGR */
+
+/* ATS.PRGR payload */
+#define RISCV_IOMMU_CMD_ATS_PRGR_RESP_CODE      GENMASK_ULL(47, 44)
+
 enum riscv_iommu_dc_fsc_atp_modes {
     RISCV_IOMMU_DC_FSC_MODE_BARE = 0,
     RISCV_IOMMU_DC_FSC_IOSATP_MODE_SV32 = 8,
@@ -322,7 +338,32 @@  enum riscv_iommu_fq_ttypes {
     RISCV_IOMMU_FQ_TTYPE_TADDR_INST_FETCH = 5,
     RISCV_IOMMU_FQ_TTYPE_TADDR_RD = 6,
     RISCV_IOMMU_FQ_TTYPE_TADDR_WR = 7,
-    RISCV_IOMMU_FW_TTYPE_PCIE_MSG_REQ = 8,
+    RISCV_IOMMU_FQ_TTYPE_PCIE_ATS_REQ = 8,
+    RISCV_IOMMU_FW_TTYPE_PCIE_MSG_REQ = 9,
+};
+
+/* Header fields */
+#define RISCV_IOMMU_PREQ_HDR_PID        GENMASK_ULL(31, 12)
+#define RISCV_IOMMU_PREQ_HDR_PV         BIT_ULL(32)
+#define RISCV_IOMMU_PREQ_HDR_PRIV       BIT_ULL(33)
+#define RISCV_IOMMU_PREQ_HDR_EXEC       BIT_ULL(34)
+#define RISCV_IOMMU_PREQ_HDR_DID        GENMASK_ULL(63, 40)
+
+/* Payload fields */
+#define RISCV_IOMMU_PREQ_PAYLOAD_R      BIT_ULL(0)
+#define RISCV_IOMMU_PREQ_PAYLOAD_W      BIT_ULL(1)
+#define RISCV_IOMMU_PREQ_PAYLOAD_L      BIT_ULL(2)
+#define RISCV_IOMMU_PREQ_PAYLOAD_M      GENMASK_ULL(2, 0)
+#define RISCV_IOMMU_PREQ_PRG_INDEX      GENMASK_ULL(11, 3)
+#define RISCV_IOMMU_PREQ_UADDR          GENMASK_ULL(63, 12)
+
+
+/*
+ * struct riscv_iommu_msi_pte - MSI Page Table Entry
+ */
+struct riscv_iommu_msi_pte {
+      uint64_t pte;
+      uint64_t mrif_info;
 };
 
 /* Fields on pte */
diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c
index 03a610fa75..7af5929b10 100644
--- a/hw/riscv/riscv-iommu.c
+++ b/hw/riscv/riscv-iommu.c
@@ -576,7 +576,7 @@  static int riscv_iommu_ctx_fetch(RISCVIOMMUState *s, RISCVIOMMUContext *ctx)
             RISCV_IOMMU_DC_IOHGATP_MODE_BARE);
         ctx->satp = set_field(0, RISCV_IOMMU_ATP_MODE_FIELD,
             RISCV_IOMMU_DC_FSC_MODE_BARE);
-        ctx->tc = RISCV_IOMMU_DC_TC_V;
+        ctx->tc = RISCV_IOMMU_DC_TC_EN_ATS | RISCV_IOMMU_DC_TC_V;
         ctx->ta = 0;
         ctx->msiptp = 0;
         return 0;
@@ -1021,6 +1021,18 @@  static int riscv_iommu_translate(RISCVIOMMUState *s, RISCVIOMMUContext *ctx,
     enable_pri = (iotlb->perm == IOMMU_NONE) && (ctx->tc & BIT_ULL(32));
     enable_pasid = (ctx->tc & RISCV_IOMMU_DC_TC_PDTV);
 
+    /* Check for ATS request. */
+    if (iotlb->perm == IOMMU_NONE) {
+        /* Check if ATS is disabled. */
+        if (!(ctx->tc & RISCV_IOMMU_DC_TC_EN_ATS)) {
+            enable_pri = false;
+            fault = RISCV_IOMMU_FQ_CAUSE_TTYPE_BLOCKED;
+            goto done;
+        }
+        trace_riscv_iommu_ats(s->parent_obj.id, PCI_BUS_NUM(ctx->devid),
+                PCI_SLOT(ctx->devid), PCI_FUNC(ctx->devid), iotlb->iova);
+    }
+
     iot = riscv_iommu_iot_lookup(ctx, iot_cache, iotlb->iova);
     perm = iot ? iot->perm : IOMMU_NONE;
     if (perm != IOMMU_NONE) {
@@ -1067,13 +1079,10 @@  done:
 
     if (enable_faults && fault) {
         struct riscv_iommu_fq_record ev;
-        unsigned ttype;
-
-        if (iotlb->perm & IOMMU_RW) {
-            ttype = RISCV_IOMMU_FQ_TTYPE_UADDR_WR;
-        } else {
-            ttype = RISCV_IOMMU_FQ_TTYPE_UADDR_RD;
-        }
+        const unsigned ttype =
+            (iotlb->perm & IOMMU_RW) ? RISCV_IOMMU_FQ_TTYPE_UADDR_WR :
+            ((iotlb->perm & IOMMU_RO) ? RISCV_IOMMU_FQ_TTYPE_UADDR_RD :
+            RISCV_IOMMU_FQ_TTYPE_PCIE_ATS_REQ);
         ev.hdr = set_field(0, RISCV_IOMMU_FQ_HDR_CAUSE, fault);
         ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_TTYPE, ttype);
         ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_PV, enable_pasid);
@@ -1105,6 +1114,73 @@  static MemTxResult riscv_iommu_iofence(RISCVIOMMUState *s, bool notify,
         MEMTXATTRS_UNSPECIFIED);
 }
 
+static void riscv_iommu_ats(RISCVIOMMUState *s,
+    struct riscv_iommu_command *cmd, IOMMUNotifierFlag flag,
+    IOMMUAccessFlags perm,
+    void (*trace_fn)(const char *id))
+{
+    RISCVIOMMUSpace *as = NULL;
+    IOMMUNotifier *n;
+    IOMMUTLBEvent event;
+    uint32_t pasid;
+    uint32_t devid;
+    const bool pv = cmd->dword0 & RISCV_IOMMU_CMD_ATS_PV;
+
+    if (cmd->dword0 & RISCV_IOMMU_CMD_ATS_DSV) {
+        /* Use device segment and requester id */
+        devid = get_field(cmd->dword0,
+            RISCV_IOMMU_CMD_ATS_DSEG | RISCV_IOMMU_CMD_ATS_RID);
+    } else {
+        devid = get_field(cmd->dword0, RISCV_IOMMU_CMD_ATS_RID);
+    }
+
+    pasid = get_field(cmd->dword0, RISCV_IOMMU_CMD_ATS_PID);
+
+    qemu_mutex_lock(&s->core_lock);
+    QLIST_FOREACH(as, &s->spaces, list) {
+        if (as->devid == devid) {
+            break;
+        }
+    }
+    qemu_mutex_unlock(&s->core_lock);
+
+    if (!as || !as->notifier) {
+        return;
+    }
+
+    event.type = flag;
+    event.entry.perm = perm;
+    event.entry.target_as = s->target_as;
+
+    IOMMU_NOTIFIER_FOREACH(n, &as->iova_mr) {
+        if (!pv || n->iommu_idx == pasid) {
+            event.entry.iova = n->start;
+            event.entry.addr_mask = n->end - n->start;
+            trace_fn(as->iova_mr.parent_obj.name);
+            memory_region_notify_iommu_one(n, &event);
+        }
+    }
+}
+
+static void riscv_iommu_ats_inval(RISCVIOMMUState *s,
+    struct riscv_iommu_command *cmd)
+{
+    return riscv_iommu_ats(s, cmd, IOMMU_NOTIFIER_DEVIOTLB_UNMAP, IOMMU_NONE,
+                           trace_riscv_iommu_ats_inval);
+}
+
+static void riscv_iommu_ats_prgr(RISCVIOMMUState *s,
+    struct riscv_iommu_command *cmd)
+{
+    unsigned resp_code = get_field(cmd->dword1,
+                                   RISCV_IOMMU_CMD_ATS_PRGR_RESP_CODE);
+
+    /* Using the access flag to carry response code information */
+    IOMMUAccessFlags perm = resp_code ? IOMMU_NONE : IOMMU_RW;
+    return riscv_iommu_ats(s, cmd, IOMMU_NOTIFIER_MAP, perm,
+                           trace_riscv_iommu_ats_prgr);
+}
+
 static void riscv_iommu_process_ddtp(RISCVIOMMUState *s)
 {
     uint64_t old_ddtp = s->ddtp;
@@ -1260,6 +1336,17 @@  static void riscv_iommu_process_cq_tail(RISCVIOMMUState *s)
                 get_field(cmd.dword0, RISCV_IOMMU_CMD_IODIR_PID));
             break;
 
+        /* ATS commands */
+        case RISCV_IOMMU_CMD(RISCV_IOMMU_CMD_ATS_FUNC_INVAL,
+                             RISCV_IOMMU_CMD_ATS_OPCODE):
+            riscv_iommu_ats_inval(s, &cmd);
+            break;
+
+        case RISCV_IOMMU_CMD(RISCV_IOMMU_CMD_ATS_FUNC_PRGR,
+                             RISCV_IOMMU_CMD_ATS_OPCODE):
+            riscv_iommu_ats_prgr(s, &cmd);
+            break;
+
         default:
         cmd_ill:
             /* Invalid instruction, do not advance instruction index. */
@@ -1648,6 +1735,9 @@  static void riscv_iommu_realize(DeviceState *dev, Error **errp)
     if (s->enable_msi) {
         s->cap |= RISCV_IOMMU_CAP_MSI_FLAT | RISCV_IOMMU_CAP_MSI_MRIF;
     }
+    if (s->enable_ats) {
+        s->cap |= RISCV_IOMMU_CAP_ATS;
+    }
     if (s->enable_s_stage) {
         s->cap |= RISCV_IOMMU_CAP_SV32 | RISCV_IOMMU_CAP_SV39 |
                   RISCV_IOMMU_CAP_SV48 | RISCV_IOMMU_CAP_SV57;
@@ -1765,6 +1855,7 @@  static Property riscv_iommu_properties[] = {
     DEFINE_PROP_UINT32("ioatc-limit", RISCVIOMMUState, iot_limit,
         LIMIT_CACHE_IOT),
     DEFINE_PROP_BOOL("intremap", RISCVIOMMUState, enable_msi, TRUE),
+    DEFINE_PROP_BOOL("ats", RISCVIOMMUState, enable_ats, TRUE),
     DEFINE_PROP_BOOL("off", RISCVIOMMUState, enable_off, TRUE),
     DEFINE_PROP_BOOL("s-stage", RISCVIOMMUState, enable_s_stage, TRUE),
     DEFINE_PROP_BOOL("g-stage", RISCVIOMMUState, enable_g_stage, TRUE),
diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h
index 9b33fb97ef..47f3fdad58 100644
--- a/hw/riscv/riscv-iommu.h
+++ b/hw/riscv/riscv-iommu.h
@@ -38,6 +38,7 @@  struct RISCVIOMMUState {
 
     bool enable_off;      /* Enable out-of-reset OFF mode (DMA disabled) */
     bool enable_msi;      /* Enable MSI remapping */
+    bool enable_ats;      /* Enable ATS support */
     bool enable_s_stage;  /* Enable S/VS-Stage translation */
     bool enable_g_stage;  /* Enable G-Stage translation */
 
diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
index 42a97caffa..4b486b6420 100644
--- a/hw/riscv/trace-events
+++ b/hw/riscv/trace-events
@@ -9,3 +9,6 @@  riscv_iommu_msi(const char *id, unsigned b, unsigned d, unsigned f, uint64_t iov
 riscv_iommu_cmd(const char *id, uint64_t l, uint64_t u) "%s: command 0x%"PRIx64" 0x%"PRIx64
 riscv_iommu_notifier_add(const char *id) "%s: dev-iotlb notifier added"
 riscv_iommu_notifier_del(const char *id) "%s: dev-iotlb notifier removed"
+riscv_iommu_ats(const char *id, unsigned b, unsigned d, unsigned f, uint64_t iova) "%s: translate request %04x:%02x.%u iova: 0x%"PRIx64
+riscv_iommu_ats_inval(const char *id) "%s: dev-iotlb invalidate"
+riscv_iommu_ats_prgr(const char *id) "%s: dev-iotlb page request group response"