Software APIs
xmodem.c
1 // Copyright lowRISC contributors (OpenTitan project).
2 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
3 // SPDX-License-Identifier: Apache-2.0
4 
5 #include "sw/device/silicon_creator/lib/xmodem.h"
6 
7 #ifndef XMODEM_TESTLIB
8 #include "sw/device/silicon_creator/lib/drivers/uart.h"
9 #else
10 #include "sw/device/silicon_creator/lib/xmodem_testlib.h"
11 #endif
12 
13 /**
14  * Constants used in the XModem-CRC protocol.
15  */
16 enum {
17  kXModemCrc16 = 0x43,
18  kXModemSoh = 0x01,
19  kXModemStx = 0x02,
20  kXModemEof = 0x04,
21  kXModemAck = 0x06,
22  kXModemNak = 0x15,
23  kXModemCancel = 0x18,
24  kXModemPoly = 0x1021,
25  kXModemSendRetries = 3,
26  kXModemMaxErrors = 2,
27  kXModemShortTimeout = 100,
28  kXModemLongTimeout = 1000,
29 };
30 
31 #ifndef XMODEM_TESTLIB
32 size_t xmodem_read(void *iohandle, uint8_t *data, size_t len,
33  uint32_t timeout_ms) {
34  (void)iohandle;
35  return uart_read(data, len, timeout_ms);
36 }
37 
38 void xmodem_write(void *iohandle, const uint8_t *data, size_t len) {
39  (void)iohandle;
40  uart_write(data, len);
41 }
42 #endif
43 
44 void xmodem_putchar(void *iohandle, uint8_t ch) {
45  xmodem_write(iohandle, &ch, sizeof(ch));
46 }
47 
48 /**
49  * Calculates a CRC-16 using the XModem polynomial.
50  */
51 static uint16_t crc16(uint16_t crc, const void *buf, size_t len) {
52  const uint8_t *p = (const uint8_t *)buf;
53  for (size_t i = 0; i < len; ++i, ++p) {
54  crc ^= *p << 8;
55  for (size_t j = 0; j < 8; ++j) {
56  bool msb = (crc & 0x8000) != 0;
57  crc <<= 1;
58  if (msb)
59  crc ^= kXModemPoly;
60  }
61  }
62  return crc;
63 }
64 
65 /**
66  * Calculate an XModem CRC16 for a to-be-transmitted block.
67  */
68 static uint16_t crc16_block(const void *buf, size_t len, size_t block_sz) {
69  uint16_t crc = crc16(0, buf, len);
70  uint8_t pad = 0;
71  for (; len < block_sz; ++len) {
72  crc = crc16(crc, &pad, 1);
73  }
74  return crc;
75 }
76 
77 void xmodem_recv_start(void *iohandle) {
78  xmodem_putchar(iohandle, kXModemCrc16);
79 }
80 
81 void xmodem_ack(void *iohandle, bool ack) {
82  xmodem_putchar(iohandle, ack ? kXModemAck : kXModemNak);
83 }
84 
85 rom_error_t xmodem_recv_frame(void *iohandle, uint32_t frame, uint8_t *data,
86  size_t *rxlen, uint8_t *unknown_rx) {
87  uint8_t ch;
88  size_t n = xmodem_read(iohandle, &ch, sizeof(ch), kXModemLongTimeout);
89  if (n == 0) {
90  return kErrorXModemTimeoutStart;
91  } else if (ch == kXModemStx || ch == kXModemSoh) {
92  // Determine if we should expect a 1K or 128 byte block.
93  size_t len = ch == kXModemStx ? 1024 : 128;
94  uint8_t pkt[2];
95 
96  // Get the frame number and its inverse.
97  n = xmodem_read(iohandle, pkt, sizeof(pkt), kXModemShortTimeout);
98  if (n != sizeof(pkt)) {
99  return kErrorXModemTimeoutPacket;
100  }
101 
102  // If the frame or its inverse are incorrect, cancel.
103  bool cancel = pkt[0] != (uint8_t)frame || pkt[0] != 255 - pkt[1];
104 
105  // Receive the data. At 115200 bps, 1K should take about 89ms to
106  // receive a 1K frame. A short timeout should be enough, but we'll
107  // be generous and give more time.
108  n = xmodem_read(iohandle, data, len, kXModemShortTimeout * 3);
109  if (n != len) {
110  return kErrorXModemTimeoutData;
111  }
112 
113  // Receive the CRC-16 from the client.
114  n = xmodem_read(iohandle, pkt, sizeof(pkt), kXModemShortTimeout);
115  if (n != sizeof(pkt)) {
116  return kErrorXModemTimeoutCrc;
117  }
118  if (cancel) {
119  xmodem_cancel(iohandle);
120  return kErrorXModemCancel;
121  }
122 
123  // Compute our own CRC-16 and compare with the client's value.
124  uint16_t crc = (uint16_t)(pkt[0] << 8 | pkt[1]);
125  uint16_t val = crc16(0, data, len);
126  if (crc != val) {
127  return kErrorXModemCrc;
128  }
129  if (rxlen)
130  *rxlen = len;
131  return kErrorOk;
132  } else if (ch == kXModemEof) {
133  return kErrorXModemEndOfFile;
134  } else {
135  if (unknown_rx)
136  *unknown_rx = (uint8_t)ch;
137  return kErrorXModemUnknown;
138  }
139 }
140 
141 /**
142  * Wait for the xmodem-crc start sequence.
143  */
144 static rom_error_t xmodem_send_start(void *iohandle, uint32_t retries) {
145  uint8_t ch;
146  int cancels = 0;
147  for (uint32_t i = 0; i < retries; ++i) {
148  size_t n = xmodem_read(iohandle, &ch, sizeof(ch), kXModemLongTimeout);
149  if (n == 0)
150  continue;
151  switch (ch) {
152  case kXModemCrc16:
153  return kErrorOk;
154  case kXModemNak:
155  return kErrorXModemProtocol;
156  case kXModemCancel:
157  cancels += 1;
158  if (cancels >= 2)
159  return kErrorXModemCancel;
160  break;
161  default:
162  /* Unknown character: do nothing */
163  ;
164  }
165  }
166  return kErrorXModemTimeoutStart;
167 }
168 
169 void xmodem_cancel(void *iohandle) {
170  xmodem_putchar(iohandle, kXModemCancel);
171  xmodem_putchar(iohandle, kXModemCancel);
172 }
173 
174 static rom_error_t xmodem_send_finish(void *iohandle) {
175  xmodem_putchar(iohandle, kXModemEof);
176  uint8_t ch;
177  xmodem_read(iohandle, &ch, sizeof(ch), kXModemLongTimeout);
178  if (ch != kXModemAck) {
179  // Should have seen an ACK, but we don't really care since there is nothing
180  // we could do about it.
181  }
182  return kErrorOk;
183 }
184 
185 static rom_error_t xmodem_send_data(void *iohandle, const void *data,
186  size_t len, uint32_t max_errors) {
187  const uint8_t *p = (const uint8_t *)data;
188  uint32_t block = 0;
189  uint32_t errors = 0;
190  uint32_t cancels = 0;
191  while (len) {
192  uint32_t block_sz = len < 1024 ? 128 : 1024;
193  uint32_t chunk = len < block_sz ? len : block_sz;
194  block += 1;
195 
196  uint16_t crc = crc16_block(p, chunk, block_sz);
197  while (true) {
198  // Start an XModem-CRC frame according to size.
199  // XModem-CRC supports both 128-byte and 1K frames.
200  // Write the header: <Soh or Stx> <block> <inverse-of-block>
201  xmodem_putchar(iohandle, block_sz == 128 ? kXModemSoh : kXModemStx);
202  xmodem_putchar(iohandle, (uint8_t)block);
203  xmodem_putchar(iohandle, 255 - (uint8_t)block);
204  // Write the data.
205  xmodem_write(iohandle, p, chunk);
206  // Pad the block out to the block size.
207  for (uint32_t i = chunk; i < block_sz; ++i) {
208  xmodem_putchar(iohandle, 0);
209  }
210  // Write the CRC16 value.
211  xmodem_putchar(iohandle, crc >> 8);
212  xmodem_putchar(iohandle, crc & 0xFF);
213 
214  // Get and check the ACK.
215  uint8_t ch;
216  size_t n = xmodem_read(iohandle, &ch, sizeof(ch), kXModemLongTimeout);
217  if (n == 0)
218  return kErrorXModemTimeoutAck;
219  switch (ch) {
220  case kXModemAck:
221  goto next_block;
222  case kXModemCancel:
223  cancels += 1;
224  if (cancels >= 2)
225  return kErrorXModemCancel;
226  break;
227  case kXModemNak:
228  default:
229  errors += 1;
230  break;
231  }
232  if (errors >= max_errors) {
233  return kErrorXModemTooManyErrors;
234  }
235  }
236  next_block:
237  p += chunk;
238  len -= chunk;
239  }
240  return kErrorOk;
241 }
242 
243 rom_error_t xmodem_send(void *iohandle, const void *data, size_t len) {
244  HARDENED_RETURN_IF_ERROR(xmodem_send_start(iohandle, 30));
245  HARDENED_RETURN_IF_ERROR(
246  xmodem_send_data(iohandle, data, len, kXModemMaxErrors));
247  HARDENED_RETURN_IF_ERROR(xmodem_send_finish(iohandle));
248  return kErrorOk;
249 }