SatCat5
cfgbus_multiserial.cc
1 // Copyright 2021-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/cfgbus_multiserial.h>
7 #include <satcat5/log.h>
8 #include <satcat5/utils.h>
9 
11 namespace log = satcat5::log;
12 namespace util = satcat5::util;
13 
14 // Define the ConfigBus register map.
15 const unsigned MultiSerial::REGADDR_IRQ = 0;
16 const unsigned MultiSerial::REGADDR_CFG = 1;
17 const unsigned MultiSerial::REGADDR_STATUS = 2;
18 const unsigned MultiSerial::REGADDR_DATA = 3;
19 
20 // Status and command codes for the multiserial control registers.
21 static const u32 MS_DVALID = (1u << 8);
22 static const u32 MS_RD_READY = (1u << 0);
23 static const u32 MS_CMD_FULL = (1u << 1);
24 static const u32 MS_BUSY = (1u << 2);
25 static const u32 MS_ERROR = (1u << 3);
26 
27 // Skip the block copy process if we're in direct mode.
28 // Otherwise, max size is limited by the hardware FIFO size.
29 static const unsigned HW_COPY_MAX =
30  SATCAT5_CFGBUS_DIRECT ? 1 : 32;
31 
32 MultiSerial::MultiSerial(
33  ConfigBus* cfg, unsigned devaddr,
34  unsigned maxpkt,
35  u8* txbuff, unsigned txsize,
36  u8* rxbuff, unsigned rxsize)
37  : cfg::Interrupt(cfg, devaddr, REGADDR_IRQ)
38  , m_ctrl(cfg->get_register(devaddr))
39  , m_tx(txbuff, txsize, maxpkt)
40  , m_rx(rxbuff, rxsize, maxpkt)
41  , m_cmd_max(maxpkt)
42  , m_cmd_cbidx(0)
43  , m_cmd_queued(0)
44  , m_new_wralloc(0)
45  , m_new_rdalloc(0)
46  , m_rdalloc(0)
47  , m_irq_wrrem(0)
48  , m_irq_rdrem(0)
49 {
50  m_rdalloc = m_rx.get_write_space();
51  m_rx.set_callback(this);
52 }
53 
54 bool MultiSerial::write_check(unsigned ncmd, unsigned nread) {
55  // Sanity check: Can we accept this command at all?
56  if (ncmd == 0) return false; // Invalid command
57  if (m_cmd_queued >= m_cmd_max) return false; // No room in Rx-buffer
58 
59  // Check free-space in command buffer:
60  // (Each command includes u8 read-length, then u16 each opcode.)
61  m_new_wralloc = 1 + 2*ncmd;
62  if (m_new_wralloc > m_tx.get_write_space()) return false;
63 
64  // Check preallocated space in reply buffer:
65  // (Each command includes u8 each reply byte, then u8 error flag.)
66  m_new_rdalloc = nread + 1;
67  if (m_new_rdalloc > m_rdalloc) return false;
68 
69  // Safe to proceed.
70  m_tx.write_u8(m_new_rdalloc);
71  return true;
72 }
73 
75  // Calculate index of the new command.
76  unsigned idx = util::modulo_add_uns(m_cmd_cbidx + m_cmd_queued, m_cmd_max);
77 
78  // Sanity-check: Predicted length should match actual.
79  unsigned actual = m_tx.get_write_partial();
80  if (m_new_wralloc != actual) {
81  log::Log(log::ERROR, "MST: Write-length mismatch")
82  .write((u16)actual).write((u16)m_new_wralloc);
83  m_tx.write_abort(); // Discard this command
84  } else {
85  m_tx.write_finalize(); // Retain this command
86  ++m_cmd_queued;
87  m_rdalloc -= m_new_rdalloc;
88  request_poll(); // Start writing if possible
89  }
90 
91  return idx;
92 }
93 
95  // Handle any pending notifications...
96  unsigned nread;
97  while (nread = m_rx.get_read_ready(), nread) {
98  // Ask child to handle callback.
99  read_done(m_cmd_cbidx);
101  // Mark this item as completed.
102  --m_cmd_queued;
103  m_rdalloc += nread;
104  m_cmd_cbidx = util::modulo_add_uns(m_cmd_cbidx + 1, m_cmd_max);
105  }
106 }
107 
109  // Schedule follow-up, but no urgent action required.
110  request_poll();
111 }
112 
114  // Read each reply byte from the hardware FIFO.
115  while (m_irq_rdrem > 1) {
116  u32 tmp = m_ctrl[REGADDR_DATA];
117  if (tmp & MS_DVALID) {
118  m_rx.write_u8((u8)(tmp & 0xFF));
119  --m_irq_rdrem;
120  } else {
121  break;
122  }
123  }
124 
125  // Did we just finish a command?
126  if (m_irq_wrrem == 0 && m_irq_rdrem == 1) {
127  // Check the hardware BUSY flag...
128  u32 status = m_ctrl[REGADDR_STATUS];
129  if (status & MS_BUSY) {
130  // Still busy? Try again later.
131  request_poll();
132  return;
133  } else {
134  // Done with command, note error flag.
135  m_irq_rdrem = 0;
136  m_rx.write_u8((status & MS_ERROR) ? 1 : 0);
139  }
140  }
141 
142  // Should we open a new command?
143  if (m_irq_wrrem == 0 && m_irq_rdrem == 0) {
144  if (m_tx.get_read_ready()) {
145  m_irq_rdrem = m_tx.read_u8();
146  m_irq_wrrem = m_tx.get_read_ready() / 2;
147  } else {
148  return; // Idle.
149  }
150  }
151 
152  // Write each opcode to the hardware FIFO.
153  while (m_irq_wrrem) {
154  // Determine how much data we can write safely.
155  unsigned num_copy = SATCAT5_CFGBUS_DIRECT ? 1 :
156  util::min_unsigned(HW_COPY_MAX, m_irq_wrrem);
157  u32 status = m_ctrl[REGADDR_STATUS];
158  if (status & MS_CMD_FULL) break;
159  if (status & MS_BUSY) num_copy = 1;
160  // Pull that data from the transmit buffer...
161  u32 temp[HW_COPY_MAX];
162  for (unsigned a = 0 ; a < num_copy ; ++a) {
163  temp[a] = (u32)m_tx.read_u16();
164  }
165  // ...and copy it to the hardware FIFO.
166  #if SATCAT5_CFGBUS_DIRECT
167  m_ctrl[REGADDR_DATA] = temp[0];
168  #else
169  m_ctrl[REGADDR_DATA].write_repeat(num_copy, temp);
170  #endif
171  m_irq_wrrem -= num_copy;
172  }
173 }
#define SATCAT5_CFGBUS_DIRECT
By default, use the general-purpose interface (see above).
Definition: cfgbus_core.h:37
Generic ConfigBus API.
Definition: cfgbus_core.h:124
Event-handler for individual ConfigBus interrupts.
Partial driver for the multipurpose serial peripheral.
void data_rcvd(satcat5::io::Readable *src) override
The data_rcvd() callback is polled whenever data is available.
satcat5::io::PacketBuffer m_tx
Buffer for hardware commands.
void poll_demand()
Deferred event handler, called after request().
satcat5::io::PacketBuffer m_rx
Buffer for reply data.
virtual void read_done(unsigned cidx)=0
Callback when each transaction is finished.
unsigned write_finish()
Enqueue transaction after calling write_check().
bool write_check(unsigned ncmd, unsigned nread)
Is there enough space in the software queue? If it returns true, write each opcode and then call writ...
void irq_event()
Interrupt service routine.
unsigned get_write_partial() const
Get number of bytes in a partial packet, or -1 on overflow.
Definition: pkt_buffer.cc:69
bool write_finalize() override
Mark end of frame and release temporary working data.
Definition: pkt_buffer.cc:105
unsigned get_write_space() const override
How many bytes can be written without blocking?
Definition: pkt_buffer.cc:52
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Definition: pkt_buffer.cc:177
void write_abort() override
If possible, abort the current partially-written packet.
Definition: pkt_buffer.cc:89
void read_finalize() override
Consume any remaining bytes in this frame, if applicable.
Definition: pkt_buffer.cc:266
Abstract API for reading byte-streams and packets.
Definition: io_readable.h:68
u8 read_u8()
One of many functions for reading integer/floating point values, see details.
Definition: io_readable.cc:44
virtual void set_callback(satcat5::io::EventListener *callback)
Update registered callback for data_rcvd() events.
Definition: io_readable.cc:39
void write_u8(u8 data)
One of many functions for writing integer/floating point values, see details.
Definition: io_writeable.cc:20
The Log class creates and formats one log message.
Definition: log.h:195
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
void request_poll()
Call this method to request polling at a later time.
Definition: polling.cc:208
Diagnostic logging to UART and/or Ethernet ports.
Miscellaneous mathematical utility functions.