Skip to content

Commit e0cc6a9

Browse files
committed
[ot] hw/opentitan: ot_gpio: add optional chardev backend
Signed-off-by: Emmanuel Blot <[email protected]>
1 parent 9bb2f22 commit e0cc6a9

File tree

2 files changed

+246
-0
lines changed

2 files changed

+246
-0
lines changed

docs/opentitan/gpio.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,67 @@ OpenTitan GPIO driver accept a global option:
1313
```
1414
-global ot-gpio.in=0x00ffff00
1515
```
16+
17+
## Character Device
18+
19+
OpenTitan GPIO driver can be connected to any CharDev device to expose as a very simple ASCII data
20+
stream the GPIO input and output pins.
21+
22+
This CharDev device can be used to stimulate the GPIO and perform unit tests.
23+
24+
To connect the GPIO to its optional characted device, use the following QEMU option
25+
26+
```
27+
-chardev type,id=gpio -global ot-gpio.chardev=gpio
28+
```
29+
30+
where type is one of the supported QEMU chardev type, such as
31+
32+
- serial to connect to an existing serial communication device
33+
- socket to use a TCP connection
34+
- pipe to use a local pipe
35+
- ...
36+
37+
`id` is an arbitrary string that should always match the value defined with the `-global` option.
38+
39+
### Protocol
40+
41+
The communication protocol is ASCII based and very simple.
42+
Each frame follows the following format:
43+
44+
#### Format
45+
46+
```
47+
<TYPE> : <HEXVALUE> <CR> <LF>
48+
```
49+
50+
where:
51+
52+
1. `TYPE` is a single uppercase char
53+
2. `HEXVALUE` is a 32-bit hex encoded lowercase value
54+
3. `CR` is the carriage return character (0x0d)
55+
4. `LF` is the line feed character, or end-of-line (0x0a)
56+
57+
Each frame is delimited with `LF` characters. `CR` are ignored and accepted to ease compatibity with
58+
some terminals but are useless.
59+
60+
The hex value represents the 32-bit GPIO values.
61+
62+
#### Supported frame types
63+
64+
A type describes the meaning of the hex value. Supported types are:
65+
66+
* `D` direction, _i.e._ Output Enable in OpenTitan terminology (QEMU -> host)
67+
* `I` input GPIO values (host -> QEMU)
68+
* `O` output GPIO values (QEMU -> host)
69+
* `Q` query input (QEMU -> host). QEMU may emit this frame, so that the host replies with a new
70+
`I` frame (hexvalue of `Q` is ignored)
71+
* `R` repeat (host -> QEMU). The host may ask QEMU to repeat the last `D` and `O` frames
72+
73+
Frames are only emitted whenever the state of either peer (QEMU, host) change. QEMU should emit `D`
74+
and `O` frames whenever the GPIO configuration or output values are updated. The host should emit
75+
a `I` frame whenever its own input lines change.
76+
77+
`Q` and `R` are only emitted when a host connects to QEMU or when one side resets its internal
78+
state.
79+

hw/opentitan/ot_gpio.c

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "qemu/timer.h"
3434
#include "qemu/typedefs.h"
3535
#include "qapi/error.h"
36+
#include "chardev/char-fe.h"
3637
#include "hw/hw.h"
3738
#include "hw/opentitan/ot_alert.h"
3839
#include "hw/opentitan/ot_gpio.h"
@@ -44,6 +45,16 @@
4445
#include "hw/sysbus.h"
4546
#include "trace.h"
4647

48+
/*
49+
* Unfortunately, there is no QEMU API to properly disable serial control lines
50+
*/
51+
#ifndef _WIN32
52+
#include <termios.h>
53+
#include "chardev/char-fd.h"
54+
#include "io/channel-file.h"
55+
#endif
56+
57+
4758
#define PARAM_NUM_ALERTS 1u
4859

4960
/* clang-format off */
@@ -113,7 +124,12 @@ struct OtGpioState {
113124
uint32_t data_oe;
114125
uint32_t data_in;
115126

127+
char ibuf[32u]; /* backed input buffer */
128+
unsigned ipos;
129+
116130
uint32_t reset_in; /* initial input levels */
131+
CharBackend chr; /* communication device */
132+
guint watch_tag; /* tracker for comm device change */
117133
};
118134

119135
static void ot_gpio_update_irqs(OtGpioState *s)
@@ -161,6 +177,30 @@ static void ot_gpio_update_data_in(OtGpioState *s)
161177
ot_gpio_update_irqs(s);
162178
}
163179

180+
static void ot_gpio_update_backend(OtGpioState *s, bool oe)
181+
{
182+
if (!qemu_chr_fe_backend_connected(&s->chr)) {
183+
return;
184+
}
185+
186+
char buf[32u];
187+
size_t len;
188+
189+
/*
190+
* use the infamous MS DOS CR LF syntax because people can't help using
191+
* Windows-style terminal
192+
*/
193+
if (oe) {
194+
len = snprintf(&buf[0], sizeof(buf), "D:%08x\r\n", s->data_oe);
195+
} else {
196+
len = 0;
197+
}
198+
199+
len += snprintf(&buf[len], sizeof(buf), "O:%08x\r\n", s->data_out);
200+
201+
qemu_chr_fe_write(&s->chr, (const uint8_t *)buf, len);
202+
}
203+
164204
static uint64_t ot_gpio_read(void *opaque, hwaddr addr, unsigned size)
165205
{
166206
OtGpioState *s = opaque;
@@ -246,39 +286,45 @@ static void ot_gpio_write(void *opaque, hwaddr addr, uint64_t val64,
246286
case R_DIRECT_OUT:
247287
s->regs[reg] = val32;
248288
s->data_out = val32;
289+
ot_gpio_update_backend(s, false);
249290
ot_gpio_update_data_in(s);
250291
break;
251292
case R_DIRECT_OE:
252293
s->regs[reg] = val32;
253294
s->data_oe = val32;
295+
ot_gpio_update_backend(s, true);
254296
ot_gpio_update_data_in(s);
255297
break;
256298
case R_MASKED_OUT_LOWER:
257299
s->regs[reg] = val32;
258300
mask = val32 >> MASKED_MASK_SHIFT;
259301
s->data_out &= ~mask;
260302
s->data_out |= val32 & mask;
303+
ot_gpio_update_backend(s, false);
261304
ot_gpio_update_data_in(s);
262305
break;
263306
case R_MASKED_OUT_UPPER:
264307
s->regs[reg] = val32;
265308
mask = val32 & MASKED_MASK_MASK;
266309
s->data_out &= ~mask;
267310
s->data_out |= (val32 << MASKED_MASK_SHIFT) & mask;
311+
ot_gpio_update_backend(s, false);
268312
ot_gpio_update_data_in(s);
269313
break;
270314
case R_MASKED_OE_LOWER:
271315
s->regs[reg] = val32;
272316
mask = val32 >> MASKED_MASK_SHIFT;
273317
s->data_oe &= ~mask;
274318
s->data_oe |= val32 & mask;
319+
ot_gpio_update_backend(s, true);
275320
ot_gpio_update_data_in(s);
276321
break;
277322
case R_MASKED_OE_UPPER:
278323
s->regs[reg] = val32;
279324
mask = val32 & MASKED_MASK_MASK;
280325
s->data_oe &= ~mask;
281326
s->data_oe |= (val32 << MASKED_MASK_SHIFT) & mask;
327+
ot_gpio_update_backend(s, true);
282328
ot_gpio_update_data_in(s);
283329
break;
284330
case R_INTR_CTRL_EN_RISING:
@@ -306,6 +352,124 @@ static void ot_gpio_write(void *opaque, hwaddr addr, uint64_t val64,
306352
}
307353
};
308354

355+
static int ot_gpio_chr_can_receive(void *opaque)
356+
{
357+
OtGpioState *s = opaque;
358+
359+
return (int)sizeof(s->ibuf) - (int)s->ipos;
360+
}
361+
362+
static void ot_gpio_chr_receive(void *opaque, const uint8_t *buf, int size)
363+
{
364+
OtGpioState *s = opaque;
365+
366+
if (s->ipos + (unsigned)size > sizeof(s->ibuf)) {
367+
qemu_log("%s: Incoherent chardev receive\n", __func__);
368+
return;
369+
}
370+
371+
memcpy(&s->ibuf[s->ipos], buf, (size_t)size);
372+
s->ipos += (unsigned)size;
373+
374+
for (;;) {
375+
const char *eol = memchr(s->ibuf, (int)'\n', s->ipos);
376+
if (!eol) {
377+
if (s->ipos > 10u) {
378+
/* discard any garbage */
379+
memset(s->ibuf, 0, sizeof(s->ibuf));
380+
s->ipos = 0;
381+
}
382+
return;
383+
}
384+
unsigned eolpos = eol - s->ibuf;
385+
if (eolpos < 10u) {
386+
memmove(s->ibuf, eol + 1u, eolpos + 1u);
387+
s->ipos = 0;
388+
continue;
389+
}
390+
uint32_t data_in = 0;
391+
char cmd = '\0';
392+
int ret = sscanf(s->ibuf, "%c:%08x", &cmd, &data_in);
393+
memmove(s->ibuf, eol + 1u, eolpos + 1u);
394+
s->ipos = 0;
395+
396+
if (ret == 2) {
397+
if (cmd == 'I') {
398+
s->data_in = data_in;
399+
ot_gpio_update_data_in(s);
400+
} else if (cmd == 'R') {
401+
ot_gpio_update_backend(s, true);
402+
}
403+
}
404+
}
405+
}
406+
407+
static void ot_gpio_chr_ignore_status_lines(OtGpioState *s)
408+
{
409+
/* it might be useful to move this to char-serial.c */
410+
#ifndef _WIN32
411+
FDChardev *cd = FD_CHARDEV(s->chr.chr);
412+
QIOChannelFile *fioc = QIO_CHANNEL_FILE(cd->ioc_in);
413+
414+
struct termios tty = { 0 };
415+
tcgetattr(fioc->fd, &tty);
416+
tty.c_cflag |= CLOCAL; /* ignore modem status lines */
417+
tcsetattr(fioc->fd, TCSANOW, &tty);
418+
#endif
419+
}
420+
421+
static void ot_gpio_chr_event_hander(void *opaque, QEMUChrEvent event)
422+
{
423+
OtGpioState *s = opaque;
424+
425+
if (event == CHR_EVENT_OPENED) {
426+
if (object_dynamic_cast(OBJECT(s->chr.chr), TYPE_CHARDEV_SERIAL)) {
427+
ot_gpio_chr_ignore_status_lines(s);
428+
}
429+
430+
ot_gpio_update_backend(s, true);
431+
432+
if (!qemu_chr_fe_backend_connected(&s->chr)) {
433+
return;
434+
}
435+
436+
/* query backend for current input status */
437+
char buf[16u];
438+
int len = snprintf(buf, sizeof(buf), "Q:%08x\r\n", s->data_oe);
439+
qemu_chr_fe_write(&s->chr, (const uint8_t *)buf, len);
440+
}
441+
}
442+
443+
static gboolean ot_gpio_chr_watch_cb(void *do_not_use, GIOCondition cond,
444+
void *opaque)
445+
{
446+
OtGpioState *s = opaque;
447+
448+
s->watch_tag = 0;
449+
450+
return FALSE;
451+
}
452+
453+
static int ot_gpio_chr_be_change(void *opaque)
454+
{
455+
OtGpioState *s = opaque;
456+
457+
qemu_chr_fe_set_handlers(&s->chr, &ot_gpio_chr_can_receive,
458+
&ot_gpio_chr_receive, &ot_gpio_chr_event_hander,
459+
&ot_gpio_chr_be_change, s, NULL, true);
460+
461+
memset(s->ibuf, 0, sizeof(s->ibuf));
462+
s->ipos = 0;
463+
464+
if (s->watch_tag > 0) {
465+
g_source_remove(s->watch_tag);
466+
s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
467+
&ot_gpio_chr_watch_cb, s);
468+
}
469+
470+
return 0;
471+
}
472+
309473
static const MemoryRegionOps ot_gpio_regs_ops = {
310474
.read = &ot_gpio_read,
311475
.write = &ot_gpio_write,
@@ -316,6 +480,7 @@ static const MemoryRegionOps ot_gpio_regs_ops = {
316480

317481
static Property ot_gpio_properties[] = {
318482
DEFINE_PROP_UINT32("in", OtGpioState, reset_in, 0u),
483+
DEFINE_PROP_CHR("chardev", OtGpioState, chr),
319484
DEFINE_PROP_END_OF_LIST(),
320485
};
321486

@@ -331,6 +496,22 @@ static void ot_gpio_reset(DeviceState *dev)
331496

332497
ot_gpio_update_irqs(s);
333498
ibex_irq_set(&s->alert, 0);
499+
500+
ot_gpio_update_backend(s, true);
501+
502+
/*
503+
* do not reset the input backed buffer as external GPIO changes is fully
504+
* async with OT reset. However, it should be reset when the backend changes
505+
*/
506+
}
507+
508+
static void ot_gpio_realize(DeviceState *dev, Error **errp)
509+
{
510+
OtGpioState *s = OT_GPIO(dev);
511+
512+
qemu_chr_fe_set_handlers(&s->chr, &ot_gpio_chr_can_receive,
513+
&ot_gpio_chr_receive, &ot_gpio_chr_event_hander,
514+
&ot_gpio_chr_be_change, s, NULL, true);
334515
}
335516

336517
static void ot_gpio_init(Object *obj)
@@ -352,6 +533,7 @@ static void ot_gpio_class_init(ObjectClass *klass, void *data)
352533
DeviceClass *dc = DEVICE_CLASS(klass);
353534

354535
dc->reset = &ot_gpio_reset;
536+
dc->realize = &ot_gpio_realize;
355537
device_class_set_props(dc, ot_gpio_properties);
356538
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
357539
}

0 commit comments

Comments
 (0)