Skip to content

Commit aa42875

Browse files
Merge pull request #304 from smartobjectoriented/feat/rpi4-console-sigint
rpi4: make Ctrl-C work on the mini UART console
2 parents c45d89c + ce16a78 commit aa42875

8 files changed

Lines changed: 307 additions & 21 deletions

File tree

so3/so3/devices/serial/bcm283x_mu.c

Lines changed: 143 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@
2020
#include <heap.h>
2121
#include <limits.h>
2222
#include <memory.h>
23+
#include <mutex.h>
24+
#include <process.h>
25+
#include <signal.h>
2326

2427
#include <device/device.h>
2528
#include <device/driver.h>
29+
#include <device/irq.h>
2630

2731
#include <device/serial.h>
2832

@@ -31,11 +35,19 @@
3135

3236
#include <asm/io.h> /* ioread/iowrite macros */
3337

38+
#define SERIAL_BUFFER_SIZE 80
39+
3440
void *__uart_vaddr = (void *) CONFIG_UART_LL_PADDR;
3541

42+
static volatile char serial_buffer[SERIAL_BUFFER_SIZE];
43+
static volatile uint32_t prod = 0, cons = 0;
44+
45+
extern mutex_t read_lock;
46+
3647
typedef struct {
3748
addr_t base;
38-
bcm283x_mu_t *dev;
49+
irq_def_t irq_def;
50+
bool irq_enabled;
3951
} bcm283x_mu_dev_t;
4052

4153
static bcm283x_mu_dev_t bcm283x_mu_dev = {
@@ -75,40 +87,162 @@ void __ll_put_byte(char c)
7587
static char bcm283x_mu_get_byte(bool polling)
7688
{
7789
bcm283x_mu_t *bcm283x_mu = (bcm283x_mu_t *) bcm283x_mu_dev.base;
90+
char tmp;
91+
92+
/* Without an RX interrupt there is no producer filling the ring, so the
93+
* only thing we can do is spin on the FIFO — the behaviour this driver
94+
* had before interrupts were wired up.
95+
*/
96+
if (polling || !bcm283x_mu_dev.irq_enabled) {
97+
while (!(ioread32(&bcm283x_mu->lsr) & UART_LSR_RX_READY))
98+
;
7899

79-
while (!(ioread32(&bcm283x_mu->lsr) & UART_LSR_RX_READY))
80-
;
100+
return (char) ioread32(&bcm283x_mu->io);
101+
}
102+
103+
while (prod == cons) {
104+
/* Ctrl-C arrived while we were blocked reading: abandon the
105+
* read and report ETX so console_getc cancels the line. */
106+
if (serial_intr) {
107+
serial_intr = false;
108+
return 3; /* ETX */
109+
}
110+
111+
schedule();
112+
113+
smp_mb();
114+
wfi();
115+
}
116+
117+
tmp = serial_buffer[cons];
118+
cons = (cons + 1) % SERIAL_BUFFER_SIZE;
119+
120+
return tmp;
121+
}
122+
123+
/*
124+
* Drain the RX FIFO into the serial buffer. Reading MU_IO is what clears the
125+
* interrupt, so we loop on LSR rather than decoding IIR — that also sidesteps
126+
* the mini UART's muddled IIR documentation.
127+
*
128+
* Ctrl-C handling mirrors pl011_int(): if a thread holds read_lock it is
129+
* blocked reading the console (e.g. the shell), so we only cancel its line
130+
* instead of killing it; otherwise a foreground app is running and gets SIGINT.
131+
*/
132+
static irq_return_t bcm283x_mu_int(int irq, void *dummy)
133+
{
134+
bcm283x_mu_t *bcm283x_mu = (bcm283x_mu_t *) bcm283x_mu_dev.base;
135+
char c;
136+
137+
while (ioread32(&bcm283x_mu->lsr) & UART_LSR_RX_READY) {
138+
c = (char) ioread32(&bcm283x_mu->io);
139+
140+
/* Check for SIGINT to be raised on Ctrl^C */
141+
if (c == 3) {
142+
bcm283x_mu_put_byte('^');
143+
bcm283x_mu_put_byte('C');
144+
bcm283x_mu_put_byte('\n');
145+
146+
#ifdef CONFIG_IPC_SIGNAL
147+
if (mutex_is_locked(&read_lock)) {
148+
serial_intr = true;
149+
} else {
150+
/* Use fg_pcb (the shell's foreground job), NOT
151+
* current(): in IRQ context current() is just the
152+
* thread running when the key arrived (usually the
153+
* idle thread, since the foreground app is typically
154+
* asleep). Fall back to current() if no foreground is
155+
* tracked yet. */
156+
pcb_t *target = fg_pcb ? fg_pcb : current()->pcb;
157+
if (target != NULL)
158+
sys_do_kill(target->pid, SIGINT);
159+
}
160+
#endif
161+
/* Already echoed above; keep it out of the ring. */
162+
continue;
163+
}
81164

82-
return (char) ioread32(&bcm283x_mu->io);
165+
serial_buffer[prod] = c;
166+
prod = (prod + 1) % SERIAL_BUFFER_SIZE;
167+
}
168+
169+
return IRQ_COMPLETED;
170+
}
171+
172+
static void bcm283x_mu_enable_irq(void)
173+
{
174+
irq_ops.enable(bcm283x_mu_dev.irq_def.irqnr);
175+
}
176+
177+
static void bcm283x_mu_disable_irq(void)
178+
{
179+
irq_ops.disable(bcm283x_mu_dev.irq_def.irqnr);
83180
}
84181

85182
static int bcm283x_mu_init(dev_t *dev, int fdt_offset)
86183
{
87184
const struct fdt_property *prop;
88185
int prop_len;
186+
addr_t new_base_vaddr;
187+
bcm283x_mu_t *bcm283x_mu;
89188

90189
/* Pins multiplexing skipped here for simplicity (done by bootloader) */
91190
/* Clocks init skipped here for simplicity (done by bootloader) */
92191

93-
/* Initialize UART controller */
94-
memset(&bcm283x_mu_dev, 0, sizeof(bcm283x_mu_dev_t));
95-
96192
prop = fdt_get_property(__fdt_addr, fdt_offset, "reg", &prop_len);
97193
BUG_ON(!prop);
98194

99195
BUG_ON(prop_len != 2 * sizeof(unsigned long));
100196

197+
/* Keep the boot-time UART base usable until io_map() has returned, so
198+
* that io_map() itself can still print. */
101199
#ifdef CONFIG_ARCH_ARM32
102-
bcm283x_mu_dev.base =
200+
new_base_vaddr =
103201
io_map(fdt32_to_cpu(((const fdt32_t *) prop->data)[0]), fdt32_to_cpu(((const fdt32_t *) prop->data)[1]));
104202
#else
105-
bcm283x_mu_dev.base =
203+
new_base_vaddr =
106204
io_map(fdt64_to_cpu(((const fdt64_t *) prop->data)[0]), fdt64_to_cpu(((const fdt64_t *) prop->data)[1]));
107205
#endif
206+
BUG_ON(!new_base_vaddr);
207+
208+
/* Initialize UART controller */
209+
memset(&bcm283x_mu_dev, 0, sizeof(bcm283x_mu_dev_t));
210+
bcm283x_mu_dev.base = new_base_vaddr;
108211

109212
serial_ops.put_byte = bcm283x_mu_put_byte;
110213
serial_ops.get_byte = bcm283x_mu_get_byte;
111214

215+
/* The interrupt is optional: fdt_interrupt_node() would BUG_ON a
216+
* missing "interrupts", and a device tree that does not declare one
217+
* simply keeps the polled console this driver started out with. Ctrl-C
218+
* then cannot work, since nothing scans the RX line asynchronously.
219+
*/
220+
prop = fdt_get_property(__fdt_addr, fdt_offset, "interrupts", &prop_len);
221+
if (!prop)
222+
return 0;
223+
224+
fdt_interrupt_node(fdt_offset, &bcm283x_mu_dev.irq_def);
225+
226+
#ifndef CONFIG_AVZ
227+
/* Same reasoning as pl011_init(): under AVZ the RX interrupt belongs to
228+
* the agency guest, whose own console driver attaches a handler. Binding
229+
* one here would let gic_handle() consume and EOI the IRQ before the
230+
* guest ever sees it. AVZ only needs put_byte for output.
231+
*/
232+
irq_bind(bcm283x_mu_dev.irq_def.irqnr, bcm283x_mu_int, NULL, NULL);
233+
#endif
234+
235+
serial_ops.enable_irq = bcm283x_mu_enable_irq;
236+
serial_ops.disable_irq = bcm283x_mu_disable_irq;
237+
238+
/* Enable RX interrupts at the controller, then at the GIC. */
239+
bcm283x_mu = (bcm283x_mu_t *) bcm283x_mu_dev.base;
240+
iowrite32(&bcm283x_mu->ier, UART_IER_RX_ENABLE);
241+
242+
serial_ops.enable_irq();
243+
244+
bcm283x_mu_dev.irq_enabled = true;
245+
112246
return 0;
113247
}
114248

so3/so3/dts/rpi4_64.dts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@
5555
serial@fe215040 {
5656
compatible = "serial,bcm283x-mu";
5757
reg = <0x0 0xfe215040 0x0 0x1000>;
58+
interrupt-parent = <&gic>;
59+
60+
/*
61+
* AUX (mini UART) — SPI 93, as the Broadcom
62+
* bcm2711-rpi-4-b.dtb gives for /soc/serial@7e215040.
63+
* Without it the console falls back to polling and Ctrl-C
64+
* cannot raise SIGINT.
65+
*/
66+
interrupts = <0 93 4>;
5867
status = "ok";
5968
};
6069

so3/so3/include/completion.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ static inline void reinit_completion(struct completion *x)
4545
}
4646

4747
void wait_for_completion(completion_t *completion);
48+
49+
/*
50+
* Same as wait_for_completion(), except that a pending signal abandons the
51+
* wait and returns -EINTR (0 on a normal completion). Use it whenever a
52+
* user-space read may block indefinitely on an external event, so that the
53+
* process stays killable while it waits.
54+
*/
55+
int wait_for_completion_interruptible(completion_t *completion);
56+
4857
void complete(completion_t *completion);
4958
void init_completion(completion_t *completion);
5059

so3/so3/include/device/arch/bcm283x_mu.h

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,38 @@
3030
#define UART_THR 0x0
3131
#define UART_LSR 0x14
3232

33-
/* Bits and regs definitions (taken from U-boot) */
33+
/*
34+
* Bits and regs definitions.
35+
*
36+
* NOTE: this layout used to be copied from U-Boot's struct bcm283x_mu_regs,
37+
* which lists iir before ier. That is wrong: the mini UART is 8250-compatible
38+
* (Linux drives it with the generic 8250 driver, and the BCM2835 peripherals
39+
* doc puts AUX_MU_IER_REG at +0x04 and AUX_MU_IIR_REG at +0x08, as the
40+
* MU_IER_REG/MU_IIR_REG offsets in mach/io.h already stated). The mistake was
41+
* harmless as long as neither field was touched — U-Boot never reads them and
42+
* this driver only used io and lsr — but it bites the moment ier is written to
43+
* enable the RX interrupt.
44+
*/
3445
typedef struct {
35-
u32 io;
36-
u32 iir;
37-
u32 ier;
38-
u32 lcr;
39-
u32 mcr;
40-
u32 lsr;
41-
u32 msr;
42-
u32 scratch;
43-
u32 cntl;
44-
u32 stat;
45-
u32 baud;
46+
u32 io; /* 0x00 */
47+
u32 ier; /* 0x04 */
48+
u32 iir; /* 0x08 */
49+
u32 lcr; /* 0x0c */
50+
u32 mcr; /* 0x10 */
51+
u32 lsr; /* 0x14 */
52+
u32 msr; /* 0x18 */
53+
u32 scratch; /* 0x1c */
54+
u32 cntl; /* 0x20 */
55+
u32 stat; /* 0x24 */
56+
u32 baud; /* 0x28 */
4657
} bcm283x_mu_t;
4758

4859
/* LSR register bits */
4960
#define UART_LSR_RX_READY (1 << 0)
5061
#define UART_LSR_TX_READY (1 << 5)
5162

63+
/* IER register bits (8250 semantics, see the note above) */
64+
#define UART_IER_RX_ENABLE (1 << 0)
65+
#define UART_IER_TX_ENABLE (1 << 1)
66+
5267
#endif /* BCM28x_MU_H */

so3/so3/include/signal.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,17 @@ typedef struct __sigaction {
101101
sigaction_t *sa;
102102
} __sigaction_t;
103103

104+
/* Forward declaration: thread.h already includes us for sigset_t. */
105+
struct tcb;
106+
107+
/*
108+
* True when <tcb> has at least one pending signal its mask does not block.
109+
* Used by the interruptible blocking primitives to decide whether to abandon
110+
* their wait; the signal itself is acted upon later, by sig_check() on the way
111+
* back to user space.
112+
*/
113+
bool signal_pending(struct tcb *tcb);
114+
104115
SYSCALL_DECLARE(rt_sigaction, int signum, const sigaction_t *action, sigaction_t *old_action, size_t sigsize);
105116
SYSCALL_DECLARE(rt_sigprocmask, int how, const sigset_t *set, sigset_t *old, size_t sigsize);
106117
SYSCALL_DECLARE(kill, int pid, int sig);

so3/so3/include/thread.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ struct tcb {
9191
bool killed;
9292

9393
#ifdef CONFIG_IPC_SIGNAL
94+
/* Set while the thread sits in an interruptible blocking primitive
95+
* (see wait_for_completion_interruptible()). sys_do_kill() wakes such
96+
* threads so they can observe the signal instead of sleeping until
97+
* their completion happens to fire; the primitive then unwinds its own
98+
* wait state and returns -EINTR. Only interruptible waiters are woken:
99+
* the other primitives park a stack-allocated queue entry in a list and
100+
* must not be resumed behind their back. */
101+
bool interruptible;
102+
94103
/* Mask for thread's disabled signals */
95104
sigset_t sig_mask;
96105
#endif

so3/so3/ipc/completion.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,60 @@ void wait_for_completion(completion_t *completion)
5252
local_irq_restore(flags);
5353
}
5454

55+
/*
56+
* Interruptible variant of wait_for_completion(): returns 0 once completed, or
57+
* -EINTR if a signal arrived first.
58+
*
59+
* The thread advertises itself as interruptible so that sys_do_kill() knows it
60+
* may be woken (see wake_interruptible_threads()); without that, a thread
61+
* parked here is off the ready list and would sleep until its completion
62+
* happens to fire, no matter how many signals are raised meanwhile.
63+
*/
64+
int wait_for_completion_interruptible(completion_t *completion)
65+
{
66+
queue_thread_t q_tcb;
67+
unsigned long flags;
68+
int ret = 0;
69+
70+
ASSERT(!__in_interrupt);
71+
ASSERT(local_irq_is_enabled());
72+
73+
flags = local_irq_save();
74+
75+
q_tcb.tcb = current();
76+
77+
if (!completion->count) {
78+
list_add_tail(&q_tcb.list, &completion->tcb_list);
79+
80+
#ifdef CONFIG_IPC_SIGNAL
81+
current()->interruptible = true;
82+
83+
while (!completion->count && !signal_pending(current()))
84+
waiting();
85+
86+
current()->interruptible = false;
87+
88+
if (!completion->count) {
89+
/* Signalled. complete() unlinks the waiter it wakes, but
90+
* nobody unlinked us — and q_tcb lives on this stack
91+
* frame, which is about to go away. Do it ourselves. */
92+
list_del(&q_tcb.list);
93+
local_irq_restore(flags);
94+
95+
return -EINTR;
96+
}
97+
#else
98+
while (!completion->count)
99+
waiting();
100+
#endif /* CONFIG_IPC_SIGNAL */
101+
}
102+
completion->count--;
103+
104+
local_irq_restore(flags);
105+
106+
return ret;
107+
}
108+
55109
/*
56110
* Wake a thread waiting on a completion.
57111
* IRQs are disabled; this function can be safely called from an interrupt context.

0 commit comments

Comments
 (0)