Skip to content

Commit

Permalink
[ot] hw/opentitan: devproxy: add a new feature to remotely trigger IRQs
Browse files Browse the repository at this point in the history
Signed-off-by: Emmanuel Blot <[email protected]>
  • Loading branch information
rivos-eblot committed Dec 7, 2023
1 parent b4ec5c6 commit 4b7bf88
Show file tree
Hide file tree
Showing 3 changed files with 311 additions and 3 deletions.
113 changes: 111 additions & 2 deletions docs/opentitan/devproxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down
118 changes: 118 additions & 0 deletions hw/opentitan/ot_dev_proxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
Expand Down
83 changes: 82 additions & 1 deletion scripts/opentitan/devproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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('<HHHHI', grp_num, self._make_sel(self._devid), irq, 0,
level)
try:
self._proxy.exchange('IS', request)
except ProxyCommandError as exc:
self._log.fatal('%s', exc)
raise

@classmethod
def _make_sel(cls, device: int, role: int = 0xf) -> int:
if not isinstance(device, int) or not 0 <= device <= 0xfff:
Expand All @@ -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('<HH', 0, self._make_sel(self._devid))
try:
irqgroups = self._proxy.exchange('IE', request)
except ProxyCommandError as exc:
self._log.fatal('%s', exc)
raise
grpfmt = '<HH16s'
grplen = scalc(grpfmt)
if len(irqgroups) % grplen:
raise ValueError('Unexpected response length')
grpcount = len(irqgroups) // grplen
self._log.info('Found %d remote interrupt groups for %s', grpcount,
str(self))
self._interrupts = {}
gnum = 0
while irqgroups:
grphdr = irqgroups[0:grplen]
irqgroups = irqgroups[grplen:]
gin, gout, gname = sunpack(grpfmt, grphdr)
grpname = gname.rstrip(b'\x00').decode().lower()
if grpname in self._interrupts:
self._log.error("Multiple devices w/ identical identifier: "
"'%s'", grpname)
else:
self._interrupts[grpname] = (gnum, gin, gout)
gnum += 1


class MbxHostProxy(DeviceProxy):
"""DOE Mailbox Host-side proxy.
Expand Down Expand Up @@ -552,7 +627,7 @@ def is_locked_address_range(self) -> 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

Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 4b7bf88

Please sign in to comment.