blob: 22bdea9b79c4392eb64d25a75c9d6077f5494da8 [file] [log] [blame]
Kévin Redon69b92d92019-01-24 16:39:20 +01001/*
Kévin Redon78d2f442019-01-24 18:45:59 +01002 * Copyright (C) 2019 sysmocom -s.f.m.c. GmbH, Author: Kevin Redon <kredon@sysmocom.de>
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17*/
Kévin Redon69b92d92019-01-24 16:39:20 +010018
Harald Welte1b9a5b82019-02-24 23:04:45 +010019#include <stdlib.h>
Harald Welte5a8af4d2019-05-12 15:57:20 +020020#include <inttypes.h>
Harald Welte1b9a5b82019-02-24 23:04:45 +010021#include <stdio.h>
Kévin Redon072951b2019-05-02 15:17:46 +020022#include <math.h>
Harald Weltef53f2262019-02-24 11:01:08 +010023#include <parts.h>
Harald Welte65101be2019-04-18 18:30:49 +020024#include <errno.h>
Harald Weltec7a58ba2019-04-18 17:59:19 +020025
26#include <osmocom/core/utils.h>
27
Harald Weltef53f2262019-02-24 11:01:08 +010028#include <hal_cache.h>
Harald Welte93f628a2019-02-24 14:32:30 +010029#include <hri_port_e54.h>
Harald Weltef53f2262019-02-24 11:01:08 +010030
Kévin Redon69b92d92019-01-24 16:39:20 +010031#include "atmel_start.h"
32#include "atmel_start_pins.h"
Kévin Redon072951b2019-05-02 15:17:46 +020033#include "config/hpl_gclk_config.h"
Kévin Redon69b92d92019-01-24 16:39:20 +010034
Harald Weltec3f170d2019-02-24 09:06:59 +010035#include "i2c_bitbang.h"
36#include "octsim_i2c.h"
37#include "ncn8025.h"
Kévin Redon0f050722019-05-02 15:56:25 +020038#include "iso7816_3.h"
Harald Weltec3f170d2019-02-24 09:06:59 +010039
Harald Welteff9f4ce2019-02-24 22:51:09 +010040#include "command.h"
41
Kévin Redonc89bb8c2019-04-17 01:20:23 +020042// TODO put declaration in more global file
43// TODO for now SIM7 is not present because used for debug
44static struct usart_async_descriptor* SIM_peripheral_descriptors[] = {&SIM0, &SIM1, &SIM2, &SIM3, &SIM4, &SIM5, &SIM6, NULL};
45
Kévin Redon096c5052019-05-09 15:01:17 +020046/** number of bytes transmitted on the SIM peripheral */
47static volatile bool SIM_tx_count[8];
48
Kévin Redonc89bb8c2019-04-17 01:20:23 +020049static void SIM_rx_cb(const struct usart_async_descriptor *const io_descr)
50{
51}
Kévin Redon78d2f442019-01-24 18:45:59 +010052
Kévin Redon096c5052019-05-09 15:01:17 +020053/** called when the transmission is complete
54 * e.g. this is when the byte has been sent and there is no data to transmit anymore
55 */
56static void SIM_tx_cb(const struct usart_async_descriptor *const io_descr)
57{
58 // find slotnr for corresponding USART
59 uint8_t slotnr;
60 for (slotnr = 0; slotnr < ARRAY_SIZE(SIM_peripheral_descriptors) && SIM_peripheral_descriptors[slotnr] != io_descr; slotnr++);
61
62 // set flag
63 if (slotnr < ARRAY_SIZE(SIM_peripheral_descriptors)) {
64 SIM_tx_count[slotnr] = true;
65 }
66}
67
Kévin Redon072951b2019-05-02 15:17:46 +020068/** possible clock sources for the SERCOM peripheral
69 * warning: the definition must match the GCLK configuration
70 */
71static const uint8_t sercom_glck_sources[] = {GCLK_PCHCTRL_GEN_GCLK2_Val, GCLK_PCHCTRL_GEN_GCLK4_Val, GCLK_PCHCTRL_GEN_GCLK6_Val};
72
73/** possible clock frequencies in MHz for the SERCOM peripheral
74 * warning: the definition must match the GCLK configuration
75 */
76static const double sercom_glck_freqs[] = {100E6 / CONF_GCLK_GEN_2_DIV, 100E6 / CONF_GCLK_GEN_4_DIV, 120E6 / CONF_GCLK_GEN_6_DIV};
77
78/** the GCLK ID for the SERCOM SIM peripherals
79 * @note: used as index for PCHCTRL
80 */
81static const uint8_t SIM_peripheral_GCLK_ID[] = {SERCOM0_GCLK_ID_CORE, SERCOM1_GCLK_ID_CORE, SERCOM2_GCLK_ID_CORE, SERCOM3_GCLK_ID_CORE, SERCOM4_GCLK_ID_CORE, SERCOM5_GCLK_ID_CORE, SERCOM6_GCLK_ID_CORE, SERCOM7_GCLK_ID_CORE};
82
Harald Weltec7a58ba2019-04-18 17:59:19 +020083static void ccid_app_init(void);
84
Harald Weltec3f170d2019-02-24 09:06:59 +010085static void board_init()
86{
87 int i;
88
89 for (i = 0; i < 4; i++)
90 i2c_init(&i2c[i]);
91
Harald Welte255da5e2019-04-16 18:19:53 +020092 for (i = 0; i < 8; i++)
Harald Weltec3f170d2019-02-24 09:06:59 +010093 ncn8025_init(i);
Harald Weltef53f2262019-02-24 11:01:08 +010094
95 cache_init();
96 cache_enable(CMCC);
Harald Welte93f628a2019-02-24 14:32:30 +010097
98 /* increase drive strength of 20Mhz SIM clock output to 8mA
99 * (there are 8 inputs + traces to drive!) */
100 hri_port_set_PINCFG_DRVSTR_bit(PORT, 0, 11);
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200101
102 // enable SIM interfaces
103 for (uint8_t i = 0; i < ARRAY_SIZE(SIM_peripheral_descriptors); i++) {
104 if (NULL == SIM_peripheral_descriptors[i]) {
105 continue;
106 }
107 usart_async_register_callback(SIM_peripheral_descriptors[i], USART_ASYNC_RXC_CB, SIM_rx_cb); // required for RX to work, even if the callback does nothing
Kévin Redon096c5052019-05-09 15:01:17 +0200108 usart_async_register_callback(SIM_peripheral_descriptors[i], USART_ASYNC_TXC_CB, SIM_tx_cb); // to count the number of bytes transmitted since we are using it asynchronously
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200109 usart_async_enable(SIM_peripheral_descriptors[i]);
110 }
Harald Weltec7a58ba2019-04-18 17:59:19 +0200111
112 ccid_app_init();
Harald Weltec3f170d2019-02-24 09:06:59 +0100113}
114
Harald Weltec7a58ba2019-04-18 17:59:19 +0200115/***********************************************************************
116 * CCID Driver integration
117 ***********************************************************************/
118
119#include <osmocom/core/linuxlist.h>
120#include <osmocom/core/msgb.h>
121#include "linuxlist_atomic.h"
122#include "ccid_df.h"
123
124struct usb_ep_q {
125 const char *name;
126 /* msgb queue of pending to-be-transmitted (IN/IRQ) or completed received (OUT)
127 * USB transfers */
128 struct llist_head list;
129 /* currently ongoing/processed msgb (USB transmit or receive */
130 struct msgb *in_progress;
131};
132
133struct ccid_state {
134 /* msgb queue of free msgs */
135 struct llist_head free_q;
136
137 /* msgb queue of pending to-be-transmitted (IN EP) */
138 struct usb_ep_q in_ep;
139 /* msgb queue of pending to-be-transmitted (IRQ EP) */
140 struct usb_ep_q irq_ep;
141 /* msgb queue of completed received (OUT EP) */
142 struct usb_ep_q out_ep;
Harald Welte5a8af4d2019-05-12 15:57:20 +0200143
144 /* bit-mask of card-insert status, as determined from NCN8025 IRQ output */
145 uint8_t card_insert_mask;
Harald Weltec7a58ba2019-04-18 17:59:19 +0200146};
147static struct ccid_state g_ccid_s;
148
149static void ccid_out_read_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred);
150static void ccid_in_write_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred);
151static void ccid_irq_write_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred);
152
153static void usb_ep_q_init(struct usb_ep_q *ep_q, const char *name)
154{
155 ep_q->name = name;
156 INIT_LLIST_HEAD(&ep_q->list);
157 ep_q->in_progress = NULL;
158}
159
160static void ccid_app_init(void)
161{
162 /* initialize data structures */
163 INIT_LLIST_HEAD(&g_ccid_s.free_q);
164 usb_ep_q_init(&g_ccid_s.in_ep, "IN");
165 usb_ep_q_init(&g_ccid_s.irq_ep, "IRQ");
166 usb_ep_q_init(&g_ccid_s.out_ep, "OUT");
167
168 /* OUT endpoint read complete callback (irq context) */
169 ccid_df_register_callback(CCID_DF_CB_READ_OUT, (FUNC_PTR)&ccid_out_read_compl);
170 /* IN endpoint write complete callback (irq context) */
171 ccid_df_register_callback(CCID_DF_CB_WRITE_IN, (FUNC_PTR)&ccid_in_write_compl);
172 /* IRQ endpoint write complete callback (irq context) */
173 ccid_df_register_callback(CCID_DF_CB_WRITE_IRQ, (FUNC_PTR)&ccid_irq_write_compl);
174}
175
176/* irqsafe version of msgb_enqueue */
177struct msgb *msgb_dequeue_irqsafe(struct llist_head *q)
178{
179 struct msgb *msg;
180 CRITICAL_SECTION_ENTER()
181 msg = msgb_dequeue(q);
182 CRITICAL_SECTION_LEAVE()
183 return msg;
184}
185
Harald Welte5a8af4d2019-05-12 15:57:20 +0200186void msgb_enqueue_irqsafe(struct llist_head *q, struct msgb *msg)
187{
188 CRITICAL_SECTION_ENTER()
189 msgb_enqueue(q, msg);
190 CRITICAL_SECTION_LEAVE()
191}
192
Harald Weltec7a58ba2019-04-18 17:59:19 +0200193/* submit the next pending (if any) message for the IN EP */
194static int submit_next_in(void)
195{
196 struct usb_ep_q *ep_q = &g_ccid_s.in_ep;
197 struct msgb *msg;
198 int rc;
199
200 OSMO_ASSERT(!ep_q->in_progress);
201 msg = msgb_dequeue_irqsafe(&ep_q->list);
202 if (!msg)
203 return 0;
204
205 ep_q->in_progress = msg;
206 rc = ccid_df_write_in(msgb_data(msg), msgb_length(msg));
207 if (rc != ERR_NONE) {
208 printf("EP %s failed: %d\r\n", ep_q->name, rc);
209 return -1;
210 }
211 return 1;
212
213}
214
215/* submit the next pending (if any) message for the IRQ EP */
216static int submit_next_irq(void)
217{
218 struct usb_ep_q *ep_q = &g_ccid_s.irq_ep;
219 struct msgb *msg;
220 int rc;
221
222 OSMO_ASSERT(!ep_q->in_progress);
223 msg = msgb_dequeue_irqsafe(&ep_q->list);
224 if (!msg)
225 return 0;
226
227 ep_q->in_progress = msg;
228 rc = ccid_df_write_irq(msgb_data(msg), msgb_length(msg));
229 /* may return HALTED/ERROR/DISABLED/BUSY/ERR_PARAM/ERR_FUNC/ERR_DENIED */
230 if (rc != ERR_NONE) {
231 printf("EP %s failed: %d\r\n", ep_q->name, rc);
232 return -1;
233 }
234 return 1;
235}
236
237static int submit_next_out(void)
238{
239 struct usb_ep_q *ep_q = &g_ccid_s.out_ep;
240 struct msgb *msg;
241 int rc;
242
243 OSMO_ASSERT(!ep_q->in_progress);
244 msg = msgb_dequeue_irqsafe(&g_ccid_s.free_q);
245 if (!msg)
246 return -1;
247 ep_q->in_progress = msg;
248
249 rc = ccid_df_read_out(msgb_data(msg), msgb_tailroom(msg));
250 if (rc != ERR_NONE) {
251 /* re-add to the list of free msgb's */
252 llist_add_tail_at(&g_ccid_s.free_q, &msg->list);
253 return 0;
254 }
255 return 1;
256}
257
258/* OUT endpoint read complete callback (irq context) */
259static void ccid_out_read_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred)
260{
261 struct msgb *msg = g_ccid_s.out_ep.in_progress;
262
263 /* add just-received msg to tail of endpoint queue */
264 OSMO_ASSERT(msg);
265 /* update msgb with the amount of data received */
266 msgb_put(msg, transferred);
267 /* append to list of pending-to-be-handed messages */
268 llist_add_tail_at(&msg->list, &g_ccid_s.out_ep.list);
269
270 /* submit another [free] msgb to receive the next transfer */
271 submit_next_out();
272}
273
274/* IN endpoint write complete callback (irq context) */
275static void ccid_in_write_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred)
276{
277 struct msgb *msg = g_ccid_s.in_ep.in_progress;
278
279 OSMO_ASSERT(msg);
280 /* return the message back to the queue of free message buffers */
281 llist_add_tail_at(&msg->list, &g_ccid_s.free_q);
282 g_ccid_s.in_ep.in_progress = NULL;
283
284 /* submit the next pending to-be-transmitted msgb (if any) */
285 submit_next_in();
286}
287
288/* IRQ endpoint write complete callback (irq context) */
289static void ccid_irq_write_compl(const uint8_t ep, enum usb_xfer_code code, uint32_t transferred)
290{
291 struct msgb *msg = g_ccid_s.irq_ep.in_progress;
292
293 OSMO_ASSERT(msg);
294 /* return the message back to the queue of free message buffers */
295 llist_add_tail_at(&msg->list, &g_ccid_s.free_q);
296 g_ccid_s.irq_ep.in_progress = NULL;
297
298 /* submit the next pending to-be-transmitted msgb (if any) */
299 submit_next_irq();
300}
301
Harald Welte5a8af4d2019-05-12 15:57:20 +0200302#include "ccid_proto.h"
303struct msgb *ccid_gen_notify_slot_status(const uint8_t *bitmask, uint8_t bm_len)
304{
305 //struct msgb *msg = ccid_msgb_alloc();
306 struct msgb *msg = msgb_alloc(64, "IRQ");
307 struct ccid_rdr_to_pc_notify_slot_change *nsc = msgb_put(msg, sizeof(*nsc) + bm_len);
308 nsc->bMessageType = RDR_to_PC_NotifySlotChange;
309 memcpy(&nsc->bmSlotCCState, bitmask, bm_len);
310
311 return msg;
312}
313
314/* check if any card detect state has changed */
315static void poll_card_detect(void)
316{
317 uint8_t new_mask = 0;
318 struct msgb *msg;
319 unsigned int i;
320
321 for (i = 0; i < 8; i++) {
322 bool irq_level = ncn8025_interrupt_level(i);
323 if (irq_level)
324 new_mask &= ~(1 << i);
325 else
326 new_mask |= (1 << i);
327 }
328
329 /* notify the user/host about any changes */
330 if (g_ccid_s.card_insert_mask != new_mask) {
331 printf("CARD_DET 0x%02x -> 0x%02x\r\n",
332 g_ccid_s.card_insert_mask, new_mask);
333 msg = ccid_gen_notify_slot_status(&new_mask, 1);
334 msgb_enqueue_irqsafe(&g_ccid_s.irq_ep.list, msg);
335
336 g_ccid_s.card_insert_mask = new_mask;
337 }
338}
339
340
Harald Weltec7a58ba2019-04-18 17:59:19 +0200341
342/***********************************************************************
343 * Command Line interface
344 ***********************************************************************/
345
Harald Welte1b9a5b82019-02-24 23:04:45 +0100346static int validate_slotnr(int argc, char **argv, int idx)
347{
348 int slotnr;
349 if (argc < idx+1) {
350 printf("You have to specify the slot number (0..7)\r\n");
351 return -1;
352 }
353 slotnr = atoi(argv[idx]);
354 if (slotnr < 0 || slotnr > 7) {
355 printf("You have to specify the slot number (0..7)\r\n");
356 return -1;
357 }
358 return slotnr;
359}
360
Kévin Redon072951b2019-05-02 15:17:46 +0200361/** change baud rate of card slot
362 * @param[in] slotnr slot number for which the baud rate should be set
363 * @param[in] baudrate baud rate in bps to set
364 * @return if the baud rate has been set, else a parameter is out of range
365 */
366static bool slot_set_baudrate(uint8_t slotnr, uint32_t baudrate)
367{
368 ASSERT(slotnr < ARRAY_SIZE(SIM_peripheral_descriptors));
369
370 // calculate the error corresponding to the clock sources
371 uint16_t bauds[ARRAY_SIZE(sercom_glck_freqs)];
372 double errors[ARRAY_SIZE(sercom_glck_freqs)];
373 for (uint8_t i = 0; i < ARRAY_SIZE(sercom_glck_freqs); i++) {
374 double freq = sercom_glck_freqs[i]; // remember possible SERCOM frequency
375 uint32_t min = freq / (2 * (255 + 1)); // calculate the minimum baud rate for this frequency
376 uint32_t max = freq / (2 * (0 + 1)); // calculate the maximum baud rate for this frequency
377 if (baudrate < min || baudrate > max) { // baud rate it out of supported range
378 errors[i] = NAN;
379 } else {
380 uint16_t baud = round(freq / (2 * baudrate) - 1);
381 bauds[i] = baud;
382 double actual = freq / (2 * (baud + 1));
383 errors[i] = fabs(1.0 - (actual / baudrate));
384 }
385 }
386
387 // find the smallest error
388 uint8_t best = ARRAY_SIZE(sercom_glck_freqs);
389 for (uint8_t i = 0; i < ARRAY_SIZE(sercom_glck_freqs); i++) {
390 if (isnan(errors[i])) {
391 continue;
392 }
393 if (best >= ARRAY_SIZE(sercom_glck_freqs)) {
394 best = i;
395 } else if (errors[i] < errors[best]) {
396 best = i;
397 }
398 }
399 if (best >= ARRAY_SIZE(sercom_glck_freqs)) { // found no clock supporting this baud rate
400 return false;
401 }
402
403 // set clock and baud rate
404 struct usart_async_descriptor* slot = SIM_peripheral_descriptors[slotnr]; // get slot
405 if (NULL == slot) {
406 return false;
407 }
408 printf("(%u) switching SERCOM clock to GCLK%u (freq = %lu kHz) and baud rate to %lu bps (baud = %u)\r\n", slotnr, (best + 1) * 2, (uint32_t)(round(sercom_glck_freqs[best] / 1000)), baudrate, bauds[best]);
409 while (!usart_async_is_tx_empty(slot)); // wait for transmission to complete (WARNING no timeout)
410 usart_async_disable(slot); // disable SERCOM peripheral
411 hri_gclk_clear_PCHCTRL_reg(GCLK, SIM_peripheral_GCLK_ID[slotnr], (1 << GCLK_PCHCTRL_CHEN_Pos)); // disable clock for this peripheral
412 while (hri_gclk_get_PCHCTRL_reg(GCLK, SIM_peripheral_GCLK_ID[slotnr], (1 << GCLK_PCHCTRL_CHEN_Pos))); // wait until clock is really disabled
413 // it does not seem we need to completely disable the peripheral using hri_mclk_clear_APBDMASK_SERCOMn_bit
414 hri_gclk_write_PCHCTRL_reg(GCLK, SIM_peripheral_GCLK_ID[slotnr], sercom_glck_sources[best] | (1 << GCLK_PCHCTRL_CHEN_Pos)); // set peripheral core clock and re-enable it
415 usart_async_set_baud_rate(slot, bauds[best]); // set the new baud rate
416 usart_async_enable(slot); // re-enable SERCOM peripheral
417
418 return true;
419}
420
Kévin Redon0f050722019-05-02 15:56:25 +0200421/** change ISO baud rate of card slot
422 * @param[in] slotnr slot number for which the baud rate should be set
423 * @param[in] clkdiv can clock divider
424 * @param[in] f clock rate conversion integer F
425 * @param[in] d baud rate adjustment factor D
426 * @return if the baud rate has been set, else a parameter is out of range
427 */
428static bool slot_set_isorate(uint8_t slotnr, enum ncn8025_sim_clkdiv clkdiv, uint16_t f, uint8_t d)
429{
430 // input checks
431 ASSERT(slotnr < ARRAY_SIZE(SIM_peripheral_descriptors));
432 if (clkdiv != SIM_CLKDIV_1 && clkdiv != SIM_CLKDIV_2 && clkdiv != SIM_CLKDIV_4 && clkdiv != SIM_CLKDIV_8) {
433 return false;
434 }
435 if (!iso7816_3_valid_f(f)) {
436 return false;
437 }
438 if (!iso7816_3_valid_d(d)) {
439 return false;
440 }
441
442 // set clockdiv
443 struct ncn8025_settings settings;
444 ncn8025_get(slotnr, &settings);
445 if (settings.clkdiv != clkdiv) {
446 settings.clkdiv = clkdiv;
447 ncn8025_set(slotnr, &settings);
448 }
449
450 // calculate desired frequency
451 uint32_t freq = 20000000UL; // maximum frequency
452 switch (clkdiv) {
453 case SIM_CLKDIV_1:
454 freq /= 1;
455 break;
456 case SIM_CLKDIV_2:
457 freq /= 2;
458 break;
459 case SIM_CLKDIV_4:
460 freq /= 4;
461 break;
462 case SIM_CLKDIV_8:
463 freq /= 8;
464 break;
465 }
466
467 // set baud rate
468 uint32_t baudrate = (freq * d) / f; // calculate actual baud rate
Kévin Redon5188e9f2019-05-09 17:34:55 +0200469 return slot_set_baudrate(slotnr, baudrate); // set baud rate
470}
471
472/** write data to card
473 * @param[in] slotnr slot number on which to send data
474 * @param[in] data data to be transmitted
475 * @param[in] length length of data to be transmitted
476 * @return error code
477 */
478static int slot_card_write(uint8_t slotnr, const uint8_t* data, uint16_t length)
479{
480 // input checks
481 ASSERT(slotnr < ARRAY_SIZE(SIM_peripheral_descriptors));
482 if (0 == length || NULL == data) {
483 return ERR_INVALID_ARG;
484 }
485
486 struct usart_async_descriptor* sim = SIM_peripheral_descriptors[slotnr];
487 ((Sercom *)sim->device.hw)->USART.CTRLB.bit.RXEN = 0; // disable receive (to avoid the echo back)
488 SIM_tx_count[slotnr] = false; // reset TX complete
489 for (uint16_t i = 0; i < length; i++) { // transmit data
490 while(!usart_async_is_tx_empty(sim)); // wait for previous byte to be transmitted (WARNING blocking)
491 if (1 != io_write(&sim->io, &data[i], 1)) { // put but in transmit buffer
492 return ERR_IO;
493 }
494 }
495 while (!SIM_tx_count[slotnr]); // wait until transmission is complete (WARNING blocking)
496 ((Sercom *)sim->device.hw)->USART.CTRLB.bit.RXEN = 1; // enable receive again
497
498 return ERR_NONE;
499}
500
501/** read data from card
502 * @param[in] slotnr slot number on which to send data
503 * @param[out] data buffer for read data to be stored
504 * @param[in] length length of data to be read
505 * @param[in] wt Waiting Time in ETU
506 * @return error code
507 * TODO fix WT/ETU duration
508 */
509static int slot_card_read(uint8_t slotnr, uint8_t* data, uint16_t length, uint32_t wt)
510{
511 // input checks
512 ASSERT(slotnr < ARRAY_SIZE(SIM_peripheral_descriptors));
513 if (0 == length || NULL == data) {
514 return ERR_INVALID_ARG;
515 }
516
517 struct usart_async_descriptor* sim = SIM_peripheral_descriptors[slotnr];
518
519 ((Sercom *)sim->device.hw)->USART.CTRLB.bit.RXEN = 1; // ensure RX is enabled
520 uint32_t timeout = wt; // reset waiting time
521 for (uint16_t i = 0; i < length; i++) { // read all data
522 while (timeout && !usart_async_is_rx_not_empty(sim)) { // verify if data is present
523 delay_us(149); // wait for 1 ETU (372 / 1 / 2.5 MHz = 148.8 us)
524 timeout--;
525 }
526 if (0 == timeout) { // timeout reached
527 return ERR_TIMEOUT;
528 }
529 timeout = wt; // reset waiting time
530 if (1 != io_read(&sim->io, &data[i], 1)) { // read one byte
531 return ERR_IO;
532 }
533 }
534
535 return ERR_NONE;
536}
537
538/** transfer TPDU
539 * @param[in] slotnr slot number on which to transfer the TPDU
540 * @param[in] header TPDU header to send
541 * @param[io] data TPDU data to transfer
542 * @param[in] data_length length of TPDU data to transfer
543 * @param[in] write if the data should be written (true) or read (false)
544 * TODO fix WT
545 * TODO the data length can be deduce from the header
546 */
547static int slot_tpdu_xfer(uint8_t slotnr, const uint8_t* header, uint8_t* data, uint16_t data_length, bool write)
548{
549 // input checks
550 ASSERT(slotnr < ARRAY_SIZE(SIM_peripheral_descriptors));
551 if (NULL == header || (data_length > 0 && NULL == data)) {
552 return ERR_INVALID_ARG;
553 }
554
555 int rc;
556 struct usart_async_descriptor* sim = SIM_peripheral_descriptors[slotnr]; // get USART peripheral
557 usart_async_flush_rx_buffer(sim); // flush RX buffer to start from scratch
558
559 // send command header
560 printf("(%d) TPDU: ", slotnr);
561 for (uint8_t i = 0; i < 5; i++) {
562 printf("%02x ", header[i]);
563 }
564 rc = slot_card_write(slotnr, header, 5); // transmit header
565 if (ERR_NONE != rc) {
566 printf("error in command header transmit (errno = %d)\r\n", rc);
567 return rc;
568 }
569
570 // read procedure byte, and handle data
571 uint8_t pb = 0x60; // wait more procedure byte
572 uint16_t data_i = 0; // progress in the data transfer
573 while (0x60 == pb) { // wait for SW
574 rc = slot_card_read(slotnr, &pb, 1, ISO7816_3_DEFAULT_WT);
575 if (ERR_NONE != rc) {
576 printf("error while receiving PB/SW1 (errno = %d)\r\n", rc);
577 return rc;
578 }
579 printf("%02x ", pb);
580 if (0x60 == pb) { // NULL byte
581 // just wait more time
582 } else if ((0x60 == (pb & 0xf0)) || (0x90 == (pb & 0xf0))) { // SW1 byte
583 // left the rest of the code handle it
584 } else if (header[1] == pb) { // ACK byte
585 // transfer rest of the data
586 if (data_i >= data_length) {
587 printf("error no more data to transfer\r\n");
588 return ERR_INVALID_DATA;
589 }
590 if (write) { // transmit remaining command data
591 rc = slot_card_write(slotnr, &data[data_i], data_length - data_i); // transmit command data
592 if (ERR_NONE != rc) {
593 printf("error in command data transmit (errno = %d)\r\n", rc);
594 return rc;
595 }
596 } else { // receive remaining command data
597 rc = slot_card_read(slotnr, &data[data_i], data_length - data_i, ISO7816_3_DEFAULT_WT);
598 if (ERR_NONE != rc) {
599 printf("error in command data receive (errno = %d)\r\n", rc);
600 return rc;
601 }
602 }
603 for (uint16_t i = data_i; i < data_length; i++) {
604 printf("%02x ", data[i]);
605 }
606 data_i = data_length; // remember we transferred the data
607 pb = 0x60; // wait for SW1
608 } else if (header[1] == (pb ^ 0xff)) { // ACK byte
609 // transfer only one byte
610 if (data_i >= data_length) {
611 printf("error no more data to transfer\r\n");
612 return ERR_INVALID_DATA;
613 }
614 if (write) { // transmit command data byte
615 rc = slot_card_write(slotnr, &data[data_i], 1); // transmit command data
616 if (ERR_NONE != rc) {
617 printf("error in command data transmit (errno = %d)\r\n", rc);
618 return rc;
619 }
620 } else { // receive command data byte
621 rc = slot_card_read(slotnr, &data[data_i], 1, ISO7816_3_DEFAULT_WT);
622 if (ERR_NONE != rc) {
623 printf("error in command data receive (errno = %d)\r\n", rc);
624 return rc;
625 }
626 }
627 printf("%02x ", data[data_i]);
628 data_i += 1; // remember we transferred one data byte
629 pb = 0x60; // wait for SW1
630 } else { // invalid byte
631 return ERR_INVALID_DATA;
632 }
633 }
634
635 // read SW2
636 uint8_t sw2;
637 rc = slot_card_read(slotnr, &sw2, 1, ISO7816_3_DEFAULT_WT);
638 if (ERR_NONE != rc) {
639 printf("error in receiving SW2 (errno = %d)\r\n", rc);
640 return rc;
641 }
642 printf("%02x", sw2);
643
644 printf("\r\n");
645 return ERR_NONE;
Kévin Redon0f050722019-05-02 15:56:25 +0200646}
647
Harald Welte1b9a5b82019-02-24 23:04:45 +0100648DEFUN(sim_status, cmd_sim_status, "sim-status", "Get state of specified NCN8025")
649{
650 struct ncn8025_settings settings;
651 int slotnr = validate_slotnr(argc, argv, 1);
652 if (slotnr < 0)
653 return;
654 ncn8025_get(slotnr, &settings);
655 printf("SIM%d: ", slotnr);
656 ncn8025_dump(&settings);
657 printf("\r\n");
658}
659
660DEFUN(sim_power, cmd_sim_power, "sim-power", "Enable/disable SIM card power")
661{
662 struct ncn8025_settings settings;
663 int slotnr = validate_slotnr(argc, argv, 1);
664 int enable;
665
666 if (slotnr < 0)
667 return;
668
669 if (argc < 3) {
670 printf("You have to specify 0=disable or 1=enable\r\n");
671 return;
672 }
673 enable = atoi(argv[2]);
674 ncn8025_get(slotnr, &settings);
675 if (enable)
676 settings.cmdvcc = true;
677 else
678 settings.cmdvcc = false;
679 ncn8025_set(slotnr, &settings);
680}
681
682DEFUN(sim_reset, cmd_sim_reset, "sim-reset", "Enable/disable SIM reset")
683{
684 struct ncn8025_settings settings;
685 int slotnr = validate_slotnr(argc, argv, 1);
686 int enable;
687
688 if (slotnr < 0)
689 return;
690
691 if (argc < 3) {
692 printf("You have to specify 0=disable or 1=enable\r\n");
693 return;
694 }
695 enable = atoi(argv[2]);
696 ncn8025_get(slotnr, &settings);
697 if (enable)
698 settings.rstin = true;
699 else
700 settings.rstin = false;
701 ncn8025_set(slotnr, &settings);
702}
703
704DEFUN(sim_clkdiv, cmd_sim_clkdiv, "sim-clkdiv", "Set SIM clock divider (1,2,4,8)")
705{
706 struct ncn8025_settings settings;
707 int slotnr = validate_slotnr(argc, argv, 1);
708 int clkdiv;
709
710 if (slotnr < 0)
711 return;
712
713 if (argc < 3) {
714 printf("You have to specify a valid divider (1,2,4,8)\r\n");
715 return;
716 }
717 clkdiv = atoi(argv[2]);
718 if (clkdiv != 1 && clkdiv != 2 && clkdiv != 4 && clkdiv != 8) {
719 printf("You have to specify a valid divider (1,2,4,8)\r\n");
720 return;
721 }
722 ncn8025_get(slotnr, &settings);
723 switch (clkdiv) {
724 case 1:
725 settings.clkdiv = SIM_CLKDIV_1;
726 break;
727 case 2:
728 settings.clkdiv = SIM_CLKDIV_2;
729 break;
730 case 4:
731 settings.clkdiv = SIM_CLKDIV_4;
732 break;
733 case 8:
734 settings.clkdiv = SIM_CLKDIV_8;
735 break;
736 }
737 ncn8025_set(slotnr, &settings);
738}
739
740DEFUN(sim_voltage, cmd_sim_voltage, "sim-voltage", "Set SIM voltage (5/3/1.8)")
741{
742 struct ncn8025_settings settings;
743 int slotnr = validate_slotnr(argc, argv, 1);
744
745 if (slotnr < 0)
746 return;
747
748 if (argc < 3) {
749 printf("You have to specify a valid voltage (5/3/1.8)\r\n");
750 return;
751 }
752 ncn8025_get(slotnr, &settings);
753 if (!strcmp(argv[2], "5"))
754 settings.vsel = SIM_VOLT_5V0;
755 else if (!strcmp(argv[2], "3"))
756 settings.vsel = SIM_VOLT_3V0;
757 else if (!strcmp(argv[2], "1.8"))
758 settings.vsel = SIM_VOLT_1V8;
759 else {
760 printf("You have to specify a valid voltage (5/3/1.8)\r\n");
761 return;
762 }
763 ncn8025_set(slotnr, &settings);
764}
765
766DEFUN(sim_led, cmd_sim_led, "sim-led", "Set SIM LED (1=on, 0=off)")
767{
768 struct ncn8025_settings settings;
769 int slotnr = validate_slotnr(argc, argv, 1);
770
771 if (slotnr < 0)
772 return;
773
774 if (argc < 3) {
775 printf("You have to specify 0=disable or 1=enable\r\n");
776 return;
777 }
778 ncn8025_get(slotnr, &settings);
779 if (atoi(argv[2]))
780 settings.led = true;
781 else
782 settings.led = false;
783 ncn8025_set(slotnr, &settings);
784}
785
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200786DEFUN(sim_atr, cmd_sim_atr, "sim-atr", "Read ATR from SIM card")
787{
788 struct ncn8025_settings settings;
789 int slotnr = validate_slotnr(argc, argv, 1);
Harald Welte1b9a5b82019-02-24 23:04:45 +0100790
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200791 if (slotnr < 0 || slotnr >= ARRAY_SIZE(SIM_peripheral_descriptors) || NULL == SIM_peripheral_descriptors[slotnr]) {
792 return;
793 }
794
795 // check if card is present (and read current settings)
796 ncn8025_get(slotnr, &settings);
797 if (!settings.simpres) {
Kévin Redon096c5052019-05-09 15:01:17 +0200798 printf("(%d) error: no card present\r\n", slotnr);
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200799 return;
800 }
801
802 // switch card off (assert reset and disable power)
803 // note: ISO/IEC 7816-3:2006 section 6.4 provides the deactivation sequence, but not the minimum corresponding times
804 settings.rstin = true;
805 settings.cmdvcc = false;
Harald Weltedcf57832019-04-17 17:29:41 +0200806 settings.led = true;
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200807 ncn8025_set(slotnr, &settings);
808
809 // TODO wait some time for card to be completely deactivated
810 usart_async_flush_rx_buffer(SIM_peripheral_descriptors[slotnr]); // flush RX buffer to start from scratch
811
Kévin Redon0f050722019-05-02 15:56:25 +0200812
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200813 // set clock to lowest frequency (20 MHz / 8 = 2.5 MHz)
814 // note: according to ISO/IEC 7816-3:2006 section 5.2.3 the minimum value is 1 MHz, and maximum is 5 MHz during activation
815 settings.clkdiv = SIM_CLKDIV_8;
Kévin Redon0f050722019-05-02 15:56:25 +0200816 // set USART baud rate to match the interface (f = 2.5 MHz) and card default settings (Fd = 372, Dd = 1)
817 slot_set_isorate(slotnr, settings.clkdiv, ISO7816_3_DEFAULT_FD, ISO7816_3_DEFAULT_DD);
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200818 // set card voltage to 3.0 V (the most supported)
819 // note: according to ISO/IEC 7816-3:2006 no voltage should damage the card, and you should cycle from low to high
820 settings.vsel = SIM_VOLT_3V0;
821 // provide power (the NCN8025 should perform the activation according to spec)
822 // note: activation sequence is documented in ISO/IEC 7816-3:2006 section 6.2
823 settings.cmdvcc = true;
824 ncn8025_set(slotnr, &settings);
825
826 // wait for Tb=400 cycles before re-asserting reset
827 delay_us(400 * 10000 / 2500); // 400 cycles * 1000 for us, 2.5 MHz / 1000 for us
828
829 // de-assert reset to switch card back on
830 settings.rstin = false;
831 ncn8025_set(slotnr, &settings);
832
833 // wait for Tc=40000 cycles for transmission to start
834 uint32_t cycles = 40000;
835 while (cycles && !usart_async_is_rx_not_empty(SIM_peripheral_descriptors[slotnr])) {
836 delay_us(10);
837 cycles -= 25; // 10 us = 25 cycles at 2.5 MHz
838 }
839 if (!usart_async_is_rx_not_empty(SIM_peripheral_descriptors[slotnr])) {
840 delay_us(12 * 372 / 1 / 2); // wait more than one byte (approximate freq down to 2 MHz)
841 }
842 // verify if one byte has been received
843 if (!usart_async_is_rx_not_empty(SIM_peripheral_descriptors[slotnr])) {
Kévin Redon096c5052019-05-09 15:01:17 +0200844 printf("(%d) error: card not responsive\r\n", slotnr);
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200845 return;
846 }
847
848 // read ATR (just do it until there is no traffic anymore)
Kévin Redon096c5052019-05-09 15:01:17 +0200849 // TODO the ATR should be parsed to read the right number of bytes, instead we just wait until to end of WT
Harald Welte07725812019-04-17 17:30:28 +0200850 printf("(%d) ATR: ", slotnr);
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200851 uint8_t atr_byte;
852 while (usart_async_is_rx_not_empty(SIM_peripheral_descriptors[slotnr])) {
853 if (1 == io_read(&SIM_peripheral_descriptors[slotnr]->io, &atr_byte, 1)) {
854 printf("%02x ", atr_byte);
855 }
Kévin Redon096c5052019-05-09 15:01:17 +0200856 uint16_t wt = ISO7816_3_DEFAULT_WT; // waiting time in ETU
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200857 while (wt && !usart_async_is_rx_not_empty(SIM_peripheral_descriptors[slotnr])) {
858 delay_us(149); // wait for 1 ETU (372 / 1 / 2.5 MHz = 148.8 us)
859 wt--;
860 }
861 }
862 printf("\r\n");
Harald Weltedcf57832019-04-17 17:29:41 +0200863
Kévin Redon096c5052019-05-09 15:01:17 +0200864 /* disable LED */
865 settings.led = false;
866 ncn8025_set(slotnr, &settings);
867}
868
869DEFUN(sim_iccid, cmd_sim_iccid, "sim-iccid", "Read ICCID from SIM card")
870{
871 struct ncn8025_settings settings;
872 int slotnr = validate_slotnr(argc, argv, 1);
873
874 if (slotnr < 0 || slotnr >= ARRAY_SIZE(SIM_peripheral_descriptors) || NULL == SIM_peripheral_descriptors[slotnr]) {
875 return;
876 }
877
878 // read current settings and check if card is present and powered
879 ncn8025_get(slotnr, &settings);
880 if (!settings.simpres) {
881 printf("(%d) error: no card present\r\n", slotnr);
882 return;
883 }
884 if (!settings.cmdvcc) {
885 printf("(%d) error: card not powered\r\n", slotnr);
886 return;
887 }
888 if (settings.rstin) {
889 printf("(%d) error: card under reset\r\n", slotnr);
890 return;
891 }
892
893 // enable LED
894 if (!settings.led) {
895 settings.led = true;
896 ncn8025_set(slotnr, &settings);
897 }
898
Kévin Redon5188e9f2019-05-09 17:34:55 +0200899 // select MF
900 printf("(%d) SELECT MF\r\n", slotnr);
Kévin Redon36efc6d2019-05-09 18:03:20 +0200901 const uint8_t select_header[] = {0xa0, 0xa4, 0x00, 0x00, 0x02}; // see TS 102.221 sec. 11.1.1
902 const uint8_t select_data_mf[] = {0x3f, 0x00}; // see TS 102.221 sec. 13.1
903 int rc = slot_tpdu_xfer(slotnr, select_header, (uint8_t*)select_data_mf, ARRAY_SIZE(select_data_mf), true); // transfer TPDU
Kévin Redon5188e9f2019-05-09 17:34:55 +0200904 if (ERR_NONE != rc) {
905 printf("error while SELECT MF (errno = %d)\r\n", rc);
Kévin Redon096c5052019-05-09 15:01:17 +0200906 }
Kévin Redon36efc6d2019-05-09 18:03:20 +0200907 // ignore response data
908
909 // select EF_ICCID
910 printf("(%d) SELECT EF_ICCID\r\n", slotnr);
911 const uint8_t select_data_ef_iccid[] = {0x2f, 0xe2}; // see TS 102.221 sec. 13.2
912 rc = slot_tpdu_xfer(slotnr, select_header, (uint8_t*)select_data_ef_iccid, ARRAY_SIZE(select_data_ef_iccid), true); // transfer TPDU
913 if (ERR_NONE != rc) {
914 printf("error while SELECT EF_ICCID (errno = %d)\r\n", rc);
915 }
916 // ignore response data
917
918 // read EF_ICCID
919 printf("(%d) READ EF_ICCID\r\n", slotnr);
920 uint8_t iccid[10];
921 uint8_t read_binary[] = {0xa0, 0xb0, 0x00, 0x00, ARRAY_SIZE(iccid)}; // see TS 102.221 sec. 11.1.3
922 rc = slot_tpdu_xfer(slotnr, read_binary, iccid, ARRAY_SIZE(iccid), false); // transfer TPDU
923 if (ERR_NONE != rc) {
924 printf("error while READ ICCID (errno = %d)\r\n", rc);
925 }
926 // ignore response data
927
928 printf("(%d) ICCID: ", slotnr);
929 for (uint8_t i = 0; i < ARRAY_SIZE(iccid); i++) {
930 uint8_t nibble = iccid[i] & 0xf;
931 if (0xf == nibble) {
932 break;
933 }
934 printf("%x", nibble);
935 nibble = iccid[i] >> 4;
936 if (0xf == nibble) {
937 break;
938 }
939 printf("%x", nibble);
940 }
941 printf("\r\n");
Kévin Redon096c5052019-05-09 15:01:17 +0200942
943 // disable LED
Harald Weltedcf57832019-04-17 17:29:41 +0200944 settings.led = false;
945 ncn8025_set(slotnr, &settings);
Kévin Redonc89bb8c2019-04-17 01:20:23 +0200946}
Harald Welte1b9a5b82019-02-24 23:04:45 +0100947
Harald Welte67b2aba2019-04-16 20:47:22 +0200948extern void testmode_init(void);
Harald Weltebdf1b352019-05-17 10:21:45 +0200949extern void libosmo_emb_init(void);
Harald Welte1b9a5b82019-02-24 23:04:45 +0100950
Harald Welte8049d662019-04-17 21:19:18 +0200951#include "talloc.h"
Harald Weltebdf1b352019-05-17 10:21:45 +0200952#include "logging.h"
Harald Welte3304ca22019-04-17 22:08:57 +0200953#include <osmocom/core/msgb.h>
Harald Welte8049d662019-04-17 21:19:18 +0200954void *g_tall_ctx;
955
956DEFUN(_talloc_report, cmd_talloc_report, "talloc-report", "Generate a talloc report")
957{
958 talloc_report_full(g_tall_ctx, stdout);
959}
960
961DEFUN(talloc_test, cmd_talloc_test, "talloc-test", "Test the talloc allocator")
962{
963 for (int i = 0; i < 10; i++)
964 talloc_named_const(g_tall_ctx, 10, "sibling");
965}
966
967DEFUN(v_talloc_free, cmd_talloc_free, "talloc-free", "Release all memory")
968{
969 talloc_free(g_tall_ctx);
970 g_tall_ctx = NULL;
971}
972
Harald Welte3304ca22019-04-17 22:08:57 +0200973/* dependency of libosmocore. FIXME: Implement it bsed on jiffies and/or RTC! */
974#include <sys/time.h>
975int _gettimeofday(struct timeval *tv, void *tz)
976{
977 tv->tv_sec = 0;
978 tv->tv_usec = 0;
979 return 0;
980}
981
Harald Welte65101be2019-04-18 18:30:49 +0200982/* Section 9.6 of SAMD5x/E5x Family Data Sheet */
983static int get_chip_unique_serial(uint8_t *out, size_t len)
984{
985 uint32_t *out32 = (uint32_t *)out;
986 if (len < 16)
987 return -EINVAL;
988
989 out32[0] = *(uint32_t *)0x008061fc;
990 out32[1] = *(uint32_t *)0x00806010;
991 out32[2] = *(uint32_t *)0x00806014;
992 out32[3] = *(uint32_t *)0x00806018;
993
994 return 0;
995}
996
997/* same as get_chip_unique_serial but in hex-string format */
998static int get_chip_unique_serial_str(char *out, size_t len)
999{
1000 uint8_t buf[16];
1001 int rc;
1002
1003 if (len < 16*2 + 1)
1004 return -EINVAL;
1005
1006 rc = get_chip_unique_serial(buf, sizeof(buf));
1007 if (rc < 0)
1008 return rc;
1009 osmo_hexdump_buf(out, len, buf, sizeof(buf), NULL, false);
1010 return 0;
1011}
1012
Kévin Redon69b92d92019-01-24 16:39:20 +01001013int main(void)
1014{
Harald Welte65101be2019-04-18 18:30:49 +02001015 char sernr_buf[16*2+1];
1016
Kévin Redon69b92d92019-01-24 16:39:20 +01001017 atmel_start_init();
Harald Welte65101be2019-04-18 18:30:49 +02001018 get_chip_unique_serial_str(sernr_buf, sizeof(sernr_buf));
Kévin Redon78d2f442019-01-24 18:45:59 +01001019
Kévin Redon8e538002019-01-30 11:19:19 +01001020 usb_start();
1021
Harald Weltec3f170d2019-02-24 09:06:59 +01001022 board_init();
Harald Welteff9f4ce2019-02-24 22:51:09 +01001023 command_init("sysmoOCTSIM> ");
Harald Welte1b9a5b82019-02-24 23:04:45 +01001024 command_register(&cmd_sim_status);
1025 command_register(&cmd_sim_power);
1026 command_register(&cmd_sim_reset);
1027 command_register(&cmd_sim_clkdiv);
1028 command_register(&cmd_sim_voltage);
1029 command_register(&cmd_sim_led);
Kévin Redonc89bb8c2019-04-17 01:20:23 +02001030 command_register(&cmd_sim_atr);
Kévin Redon096c5052019-05-09 15:01:17 +02001031 command_register(&cmd_sim_iccid);
Harald Welte67b2aba2019-04-16 20:47:22 +02001032 testmode_init();
Harald Welte8049d662019-04-17 21:19:18 +02001033 command_register(&cmd_talloc_test);
1034 command_register(&cmd_talloc_report);
1035 command_register(&cmd_talloc_free);
Harald Weltec3f170d2019-02-24 09:06:59 +01001036
Harald Welte059d1b02019-05-17 10:53:53 +02001037 printf("\r\n\r\nsysmocom sysmoOCTSIM firmware %s\r\n", GIT_VERSION);
Harald Welte65101be2019-04-18 18:30:49 +02001038 printf("Chip-Id %s\r\n", sernr_buf);
Harald Weltee7aa5342019-04-16 21:11:14 +02001039
Harald Welte8049d662019-04-17 21:19:18 +02001040 talloc_enable_null_tracking();
1041 g_tall_ctx = talloc_named_const(NULL, 0, "global");
1042 printf("g_tall_ctx=%p\r\n", g_tall_ctx);
Harald Weltebdf1b352019-05-17 10:21:45 +02001043
1044 libosmo_emb_init();
1045
1046 LOGP(DUSB, LOGL_ERROR, "foobar usb\n");
Harald Welte8049d662019-04-17 21:19:18 +02001047
Harald Weltee7aa5342019-04-16 21:11:14 +02001048 command_print_prompt();
Kévin Redon8e538002019-01-30 11:19:19 +01001049 while (true) { // main loop
Harald Welteff9f4ce2019-02-24 22:51:09 +01001050 command_try_recv();
Harald Welte5a8af4d2019-05-12 15:57:20 +02001051 poll_card_detect();
Kévin Redon8e538002019-01-30 11:19:19 +01001052 }
Kévin Redon69b92d92019-01-24 16:39:20 +01001053}