Skip to content

Commit

Permalink
hw/char/stm32l4x5_usart: Enable serial read and write
Browse files Browse the repository at this point in the history
Implement the ability to read and write characters to the
usart using the serial port.

The character transmission is based on the
cmsdk-apb-uart implementation.

Signed-off-by: Arnaud Minier <arnaud.minier@telecom-paris.fr>
Signed-off-by: Inès Varhol <ines.varhol@telecom-paris.fr>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Message-id: 20240329174402.60382-3-arnaud.minier@telecom-paris.fr
[PMM: fixed a few checkpatch nits]
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
  • Loading branch information
Reallnas authored and pm215 committed Apr 25, 2024
1 parent 4fb37ae commit 87b77e6
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 0 deletions.
143 changes: 143 additions & 0 deletions hw/char/stm32l4x5_usart.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,123 @@ REG32(RDR, 0x24)
REG32(TDR, 0x28)
FIELD(TDR, TDR, 0, 9)

static void stm32l4x5_update_irq(Stm32l4x5UsartBaseState *s)
{
if (((s->isr & R_ISR_WUF_MASK) && (s->cr3 & R_CR3_WUFIE_MASK)) ||
((s->isr & R_ISR_CMF_MASK) && (s->cr1 & R_CR1_CMIE_MASK)) ||
((s->isr & R_ISR_ABRF_MASK) && (s->cr1 & R_CR1_RXNEIE_MASK)) ||
((s->isr & R_ISR_EOBF_MASK) && (s->cr1 & R_CR1_EOBIE_MASK)) ||
((s->isr & R_ISR_RTOF_MASK) && (s->cr1 & R_CR1_RTOIE_MASK)) ||
((s->isr & R_ISR_CTSIF_MASK) && (s->cr3 & R_CR3_CTSIE_MASK)) ||
((s->isr & R_ISR_LBDF_MASK) && (s->cr2 & R_CR2_LBDIE_MASK)) ||
((s->isr & R_ISR_TXE_MASK) && (s->cr1 & R_CR1_TXEIE_MASK)) ||
((s->isr & R_ISR_TC_MASK) && (s->cr1 & R_CR1_TCIE_MASK)) ||
((s->isr & R_ISR_RXNE_MASK) && (s->cr1 & R_CR1_RXNEIE_MASK)) ||
((s->isr & R_ISR_IDLE_MASK) && (s->cr1 & R_CR1_IDLEIE_MASK)) ||
((s->isr & R_ISR_ORE_MASK) &&
((s->cr1 & R_CR1_RXNEIE_MASK) || (s->cr3 & R_CR3_EIE_MASK))) ||
/* TODO: Handle NF ? */
((s->isr & R_ISR_FE_MASK) && (s->cr3 & R_CR3_EIE_MASK)) ||
((s->isr & R_ISR_PE_MASK) && (s->cr1 & R_CR1_PEIE_MASK))) {
qemu_irq_raise(s->irq);
trace_stm32l4x5_usart_irq_raised(s->isr);
} else {
qemu_irq_lower(s->irq);
trace_stm32l4x5_usart_irq_lowered();
}
}

static int stm32l4x5_usart_base_can_receive(void *opaque)
{
Stm32l4x5UsartBaseState *s = opaque;

if (!(s->isr & R_ISR_RXNE_MASK)) {
return 1;
}

return 0;
}

static void stm32l4x5_usart_base_receive(void *opaque, const uint8_t *buf,
int size)
{
Stm32l4x5UsartBaseState *s = opaque;

if (!((s->cr1 & R_CR1_UE_MASK) && (s->cr1 & R_CR1_RE_MASK))) {
trace_stm32l4x5_usart_receiver_not_enabled(
FIELD_EX32(s->cr1, CR1, UE), FIELD_EX32(s->cr1, CR1, RE));
return;
}

/* Check if overrun detection is enabled and if there is an overrun */
if (!(s->cr3 & R_CR3_OVRDIS_MASK) && (s->isr & R_ISR_RXNE_MASK)) {
/*
* A character has been received while
* the previous has not been read = Overrun.
*/
s->isr |= R_ISR_ORE_MASK;
trace_stm32l4x5_usart_overrun_detected(s->rdr, *buf);
} else {
/* No overrun */
s->rdr = *buf;
s->isr |= R_ISR_RXNE_MASK;
trace_stm32l4x5_usart_rx(s->rdr);
}

stm32l4x5_update_irq(s);
}

/*
* Try to send tx data, and arrange to be called back later if
* we can't (ie the char backend is busy/blocking).
*/
static gboolean usart_transmit(void *do_not_use, GIOCondition cond,
void *opaque)
{
Stm32l4x5UsartBaseState *s = STM32L4X5_USART_BASE(opaque);
int ret;
/* TODO: Handle 9 bits transmission */
uint8_t ch = s->tdr;

s->watch_tag = 0;

if (!(s->cr1 & R_CR1_TE_MASK) || (s->isr & R_ISR_TXE_MASK)) {
return G_SOURCE_REMOVE;
}

ret = qemu_chr_fe_write(&s->chr, &ch, 1);
if (ret <= 0) {
s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
usart_transmit, s);
if (!s->watch_tag) {
/*
* Most common reason to be here is "no chardev backend":
* just insta-drain the buffer, so the serial output
* goes into a void, rather than blocking the guest.
*/
goto buffer_drained;
}
/* Transmit pending */
trace_stm32l4x5_usart_tx_pending();
return G_SOURCE_REMOVE;
}

buffer_drained:
/* Character successfully sent */
trace_stm32l4x5_usart_tx(ch);
s->isr |= R_ISR_TC_MASK | R_ISR_TXE_MASK;
stm32l4x5_update_irq(s);
return G_SOURCE_REMOVE;
}

static void usart_cancel_transmit(Stm32l4x5UsartBaseState *s)
{
if (s->watch_tag) {
g_source_remove(s->watch_tag);
s->watch_tag = 0;
}
}

static void stm32l4x5_usart_base_reset_hold(Object *obj, ResetType type)
{
Stm32l4x5UsartBaseState *s = STM32L4X5_USART_BASE(obj);
Expand All @@ -167,6 +284,22 @@ static void stm32l4x5_usart_base_reset_hold(Object *obj, ResetType type)
s->isr = 0x020000C0;
s->rdr = 0x00000000;
s->tdr = 0x00000000;

usart_cancel_transmit(s);
stm32l4x5_update_irq(s);
}

static void usart_update_rqr(Stm32l4x5UsartBaseState *s, uint32_t value)
{
/* TXFRQ */
/* Reset RXNE flag */
if (value & R_RQR_RXFRQ_MASK) {
s->isr &= ~R_ISR_RXNE_MASK;
}
/* MMRQ */
/* SBKRQ */
/* ABRRQ */
stm32l4x5_update_irq(s);
}

static uint64_t stm32l4x5_usart_base_read(void *opaque, hwaddr addr,
Expand Down Expand Up @@ -209,6 +342,7 @@ static uint64_t stm32l4x5_usart_base_read(void *opaque, hwaddr addr,
retvalue = FIELD_EX32(s->rdr, RDR, RDR);
/* Reset RXNE flag */
s->isr &= ~R_ISR_RXNE_MASK;
stm32l4x5_update_irq(s);
break;
case A_TDR:
retvalue = FIELD_EX32(s->tdr, TDR, TDR);
Expand All @@ -235,6 +369,7 @@ static void stm32l4x5_usart_base_write(void *opaque, hwaddr addr,
switch (addr) {
case A_CR1:
s->cr1 = value;
stm32l4x5_update_irq(s);
return;
case A_CR2:
s->cr2 = value;
Expand All @@ -252,6 +387,7 @@ static void stm32l4x5_usart_base_write(void *opaque, hwaddr addr,
s->rtor = value;
return;
case A_RQR:
usart_update_rqr(s, value);
return;
case A_ISR:
qemu_log_mask(LOG_GUEST_ERROR,
Expand All @@ -260,13 +396,16 @@ static void stm32l4x5_usart_base_write(void *opaque, hwaddr addr,
case A_ICR:
/* Clear the status flags */
s->isr &= ~value;
stm32l4x5_update_irq(s);
return;
case A_RDR:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: RDR is read only !\n", __func__);
return;
case A_TDR:
s->tdr = value;
s->isr &= ~R_ISR_TXE_MASK;
usart_transmit(NULL, G_IO_OUT, s);
return;
default:
qemu_log_mask(LOG_GUEST_ERROR,
Expand Down Expand Up @@ -336,6 +475,10 @@ static void stm32l4x5_usart_base_realize(DeviceState *dev, Error **errp)
error_setg(errp, "USART clock must be wired up by SoC code");
return;
}

qemu_chr_fe_set_handlers(&s->chr, stm32l4x5_usart_base_can_receive,
stm32l4x5_usart_base_receive, NULL, NULL,
s, NULL, true);
}

static void stm32l4x5_usart_base_class_init(ObjectClass *klass, void *data)
Expand Down
7 changes: 7 additions & 0 deletions hw/char/trace-events
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ sh_serial_write(char *id, unsigned size, uint64_t offs, uint64_t val) "%s size %
# stm32l4x5_usart.c
stm32l4x5_usart_read(uint64_t addr, uint32_t data) "USART: Read <0x%" PRIx64 "> -> 0x%" PRIx32 ""
stm32l4x5_usart_write(uint64_t addr, uint32_t data) "USART: Write <0x%" PRIx64 "> <- 0x%" PRIx32 ""
stm32l4x5_usart_rx(uint8_t c) "USART: got character 0x%x from backend"
stm32l4x5_usart_tx(uint8_t c) "USART: character 0x%x sent to backend"
stm32l4x5_usart_tx_pending(void) "USART: character send to backend pending"
stm32l4x5_usart_irq_raised(uint32_t reg) "USART: IRQ raised: 0x%08"PRIx32
stm32l4x5_usart_irq_lowered(void) "USART: IRQ lowered"
stm32l4x5_usart_overrun_detected(uint8_t current, uint8_t received) "USART: Overrun detected, RDR='0x%x', received 0x%x"
stm32l4x5_usart_receiver_not_enabled(uint8_t ue_bit, uint8_t re_bit) "USART: Receiver not enabled, UE=0x%x, RE=0x%x"

# xen_console.c
xen_console_connect(unsigned int idx, unsigned int ring_ref, unsigned int port, unsigned int limit) "idx %u ring_ref %u port %u limit %u"
Expand Down
1 change: 1 addition & 0 deletions include/hw/char/stm32l4x5_usart.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ struct Stm32l4x5UsartBaseState {
Clock *clk;
CharBackend chr;
qemu_irq irq;
guint watch_tag;
};

struct Stm32l4x5UsartBaseClass {
Expand Down

0 comments on commit 87b77e6

Please sign in to comment.