From 4b7bf8810e6910766c2a3f294c3ee5157f8d60a0 Mon Sep 17 00:00:00 2001 From: Emmanuel Blot Date: Wed, 6 Dec 2023 19:47:13 +0100 Subject: [PATCH] [ot] hw/opentitan: devproxy: add a new feature to remotely trigger IRQs Signed-off-by: Emmanuel Blot --- docs/opentitan/devproxy.md | 113 +++++++++++++++++++++++++++++++- hw/opentitan/ot_dev_proxy.c | 118 ++++++++++++++++++++++++++++++++++ scripts/opentitan/devproxy.py | 83 +++++++++++++++++++++++- 3 files changed, 311 insertions(+), 3 deletions(-) diff --git a/docs/opentitan/devproxy.md b/docs/opentitan/devproxy.md index ed1f396d40e0..bf21057e09c3 100644 --- a/docs/opentitan/devproxy.md +++ b/docs/opentitan/devproxy.md @@ -782,7 +782,6 @@ This is the last command, as QEMU should exit upon receiving this request. Route one or more device output interrupt to the proxy (vs. the internal PLIC) -* `Role` is the initiator role to use to access the device * `Device` is the device to access (see [Enumerate](#enumerate-devices)) * `Interrupt mask` define which interrupt should be routed (1 bit per interrupt) @@ -819,7 +818,6 @@ Route one or more device output interrupt to the proxy (vs. the internal PLIC) Revert any previous interception, reconnecting selected IRQ to their original destination device. -* `Role` is the initiator role to use to access the device * `Device` is the device to access (see [Enumerate](#enumerate-devices)) * `Interrupt mask` define which interrupt should be released (1 bit per interrupt) @@ -851,6 +849,117 @@ destination device. +---------------+---------------+---------------+---------------+ ``` +#### Signal Interrupt [signal-interrupt] + +Set or Reset an input interrupt line. + +* `Device` is the device to access (see [Enumerate](#enumerate-devices)) +* `GID` the identifier of the IRQ group. + * The group identifier can be retrieved using the [Enumerate Device Interrupt](#enumerate-irq) + API. +* `Interrupt line` the number of the interrupt line to signal within the group. The interrupt line + should range between 0 and the input IRQ count for this group. +* `Level` the new interrupt line level. Usually `1` to assert/set (1) or `0` to deassert/release, + even if any 32-bit value is accepted. + +##### Request +``` ++---------------+---------------+---------------+---------------+ +| 0 | 1 | 2 | 3 | +|0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F| ++---------------+---------------+---------------+---------------+ +| 'IS' | 12 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| GID | Device | - | ++---------------+---------------+---------------+---------------+ +| Interrupt line | - | ++---------------+---------------+---------------+---------------+ +| Level | ++---------------+---------------+---------------+---------------+ +``` + +##### Response +``` ++---------------+---------------+---------------+---------------+ +| 0 | 1 | 2 | 3 | +|0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F| ++---------------+---------------+---------------+---------------+ +| 'is' | 0 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +``` + +#### Enumerate Device Interrupt [enumerate-irq] + +Enumerate can be called by the Application to retrieve the list of interrupt +group of a supported device. The group position in the response can be further +use with the [Signal Interrupt API](#signal-interrupt) to set the level of +each individual IRQ line. + +##### Request +``` ++---------------+---------------+---------------+---------------+ +| 0 | 1 | 2 | 3 | ++---------------+---------------+---------------+---------------+ +|0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F| ++---------------+---------------+---------------+---------------+ +| 'IE' | 4 | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| - | Device | - | ++---------------+---------------+---------------+---------------+ +``` + +##### Response +``` ++---------------+---------------+---------------+---------------+ +| 0 | 1 | 2 | 3 | ++---------------+---------------+---------------+---------------+ +|0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F| ++---------------+---------------+---------------+---------------+ +| 'ie' | 0..20N | ++---------------+---------------+---------------+---------------+ +| UID |0| ++---------------+---------------+---------------+---------------+ +| IRQ input count | IRQ output count | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +| IRQ input count | IRQ output count | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +| .... | ++---------------+---------------+---------------+---------------+ +| IRQ input count | IRQ output count | ++---------------+---------------+---------------+---------------+ +| | +| | +| Identifier | +| | +| | ++---------------+---------------+---------------+---------------+ +``` +Reponse contains 0 up to N interrupt groups, each group is described with a 20-byte entry, where: + +* `IRQ input count` is the count of the input interrupts for this group, +* `IRQ output count` is the count of the output interrupts for this group, +* `Identifier` is an arbitrary 16-character string that describes this group. + +The count of address spaces can be retrieved from the `LENGTH` field. + #### Intercept arbitrary MMIO region It is possible to get intercept access to a memory region. Any intercepted region cannot diff --git a/hw/opentitan/ot_dev_proxy.c b/hw/opentitan/ot_dev_proxy.c index 7033812f9ac5..c7aeaf656011 100644 --- a/hw/opentitan/ot_dev_proxy.c +++ b/hw/opentitan/ot_dev_proxy.c @@ -934,6 +934,118 @@ static void ot_dev_proxy_intercept_interrupts(OtDevProxyState *s, bool enable) 0); } +static void ot_dev_proxy_signal_interrupt(OtDevProxyState *s) +{ + if (s->rx_hdr.length != 3u * sizeof(uint32_t)) { + ot_dev_proxy_reply_error(s, PE_INVALID_COMMAND_LENGTH, NULL); + return; + } + + unsigned devix = (s->rx_buffer[0] >> 16u) & 0x3ffu; + unsigned gid = s->rx_buffer[0u] & 0xffffu; + + if (devix >= s->dev_count) { + ot_dev_proxy_reply_error(s, PE_INVALID_DEVICE_ID, NULL); + return; + } + + OtDevProxyItem *item = &s->items[devix]; + + if (!object_dynamic_cast(item->obj, TYPE_DEVICE)) { + ot_dev_proxy_reply_error(s, PE_UNSUPPORTED_DEVICE, NULL); + return; + } + + DeviceState *dev = DEVICE(item->obj); + + unsigned irq_num = s->rx_buffer[1u] & 0xffffu; + int irq_level = (int)s->rx_buffer[2u]; + + NamedGPIOList *gl = NULL; + NamedGPIOList *ngl; + QLIST_FOREACH(ngl, &dev->gpios, node) { + if (!gid) { + gl = ngl; + break; + } + gid--; + } + + if (!gl) { + ot_dev_proxy_reply_error(s, PE_INVALID_SPECIFIER_ID, "no such group"); + return; + } + + if (irq_num >= gl->num_in) { + ot_dev_proxy_reply_error(s, PE_INVALID_IRQ, "no such irq"); + return; + } + + qemu_irq irq = gl->in[irq_num]; + qemu_set_irq(irq, irq_level); + + ot_dev_proxy_reply_payload(s, PROXY_COMMAND('i', 's'), NULL, 0); +} + +static void ot_dev_proxy_enumerate_interrupts(OtDevProxyState *s) +{ + if (s->rx_hdr.length != 1u * sizeof(uint32_t)) { + ot_dev_proxy_reply_error(s, PE_INVALID_COMMAND_LENGTH, NULL); + return; + } + + unsigned devix = (s->rx_buffer[0] >> 16u) & 0x3ffu; + + if (devix >= s->dev_count) { + ot_dev_proxy_reply_error(s, PE_INVALID_DEVICE_ID, NULL); + return; + } + + OtDevProxyItem *item = &s->items[devix]; + + if (!object_dynamic_cast(item->obj, TYPE_DEVICE)) { + ot_dev_proxy_reply_error(s, PE_UNSUPPORTED_DEVICE, NULL); + return; + } + + DeviceState *dev = DEVICE(item->obj); + + unsigned group_count = 0; + NamedGPIOList *ngl; + QLIST_FOREACH(ngl, &dev->gpios, node) { + group_count++; + } + + struct irq_id { + uint16_t in_count; + uint16_t out_count; + char name[16u]; + }; + static_assert(sizeof(struct irq_id) == 5 * sizeof(uint32_t), + "invalid struct irq_id, need packing"); + + struct irq_id *entries; + + if (group_count) { + entries = g_new0(struct irq_id, group_count); + struct irq_id *irq_id = entries; + QLIST_FOREACH(ngl, &dev->gpios, node) { + irq_id->in_count = ngl->num_in; + irq_id->out_count = ngl->num_out; + if (ngl->name) { + strncpy(irq_id->name, ngl->name, sizeof(irq_id->name)); + } + irq_id++; + } + } else { + entries = NULL; + } + + ot_dev_proxy_reply_payload(s, PROXY_COMMAND('i', 'e'), entries, + group_count * sizeof(struct irq_id)); + g_free(entries); +} + static void ot_dev_proxy_intercept_mmio(OtDevProxyState *s) { if (s->rx_hdr.length != 3u * sizeof(uint32_t)) { @@ -1161,6 +1273,12 @@ static void ot_dev_proxy_dispatch_request(OtDevProxyState *s) case PROXY_COMMAND('I', 'R'): ot_dev_proxy_intercept_interrupts(s, false); break; + case PROXY_COMMAND('I', 'S'): + ot_dev_proxy_signal_interrupt(s); + break; + case PROXY_COMMAND('I', 'E'): + ot_dev_proxy_enumerate_interrupts(s); + break; case PROXY_COMMAND('M', 'I'): ot_dev_proxy_intercept_mmio(s); break; diff --git a/scripts/opentitan/devproxy.py b/scripts/opentitan/devproxy.py index 3b9619660044..47609efbe04e 100644 --- a/scripts/opentitan/devproxy.py +++ b/scripts/opentitan/devproxy.py @@ -208,6 +208,7 @@ def __init__(self, proxy: 'Proxy', name: str, devid: int, addr: int, self._regcount = count self._offset = offset * 4 self._end = offset + count * 4 + self._interrupts: Optional[Dict[str, Tuple[int, int, int]]] = None self._new() @property @@ -344,6 +345,51 @@ def capture_interrupt(self, irq: int, enable: bool): else: self.release_interrupts(mask) + def enumerate_interrupts(self, out: bool) -> Iterator[Tuple[str, int]]: + """Enumerate supported interrupt lines. + + :param out: True to enumerate output IRQ lines, False for input ones + :yield: enumerated tuples that contain the IRQ group name, and the + count of interrupt line in this group. + """ + if self._interrupts is None: + self._enumerate_interrupts() + for name, (_, gin, gout) in self._interrupts.items(): + if not out: + if gin: + yield name, gin + else: + if gout: + yield name, gout + + def signal_interrupt(self, group: str, irq: int, level: int | bool) -> None: + """Set the level of an input interrupt line. + + :param group: the name of the group + :param irq: the IRQ line to signal + :param level: the new level for this IRQ line + + The group name may be retrieved with the #enumerate_interrupts API. + """ + if self._interrupts is None: + self._enumerate_interrupts() + if group not in self._interrupts: + raise ValueError(f'No such interrupt group "{group}"') + grp = self._interrupts[group] + grp_num, irq_count = grp[0:2] + if irq >= irq_count: + raise ValueError(f'No such interrupt {irq} in {group}') + level = int(level) + if level >= (1 << 32): + raise ValueError(f'Invalied interrupt level {level}') + request = spack(' int: if not isinstance(device, int) or not 0 <= device <= 0xfff: @@ -356,6 +402,35 @@ def _new(self): self._log.debug('Device %s @ 0x%08x: %s', str(self), self._addr, self._name) + def _enumerate_interrupts(self) -> None: + # lazy initialization interrupt enumeration is rarely used + request = spack(' bool: """Lock address range (base and limit registers). """ res = self.read_word(self._role, self.REGS['ADDRESS_RANGE_REGWEN']) \ - != self.MB4_TRUE + != self.MB4_TRUE self._log.debug('%d', res) return res @@ -902,6 +977,12 @@ def enable_interrupts(self, intrs: int) -> None: self._log.debug('0x%08x', intrs) self.write_word(self._role, self.REGS['INTR_ENABLE'], intrs) + @property + def enabled_interrupts(self) -> int: + """Get enabled interrupt channels. + """ + return self.read_word(self._role, self.REGS['INTR_ENABLE']) + def clear_interrupts(self, intrs: int) -> None: """Clear interrupts.