blob: 3651752b054f7cd9459529af77731f96e78ef520 [file] [log] [blame]
Harald Welte7abdb512016-03-03 17:48:32 +01001#define TRACE_LEVEL 6
2
Harald Welte2a6d3af2016-02-28 19:29:14 +01003#include "board.h"
Harald Welte16055642016-03-03 11:02:45 +01004#include "simtrace.h"
5#include "ringbuffer.h"
Harald Welte2a6d3af2016-02-28 19:29:14 +01006#include "card_emu.h"
Harald Weltebd717682016-02-28 19:30:05 +01007#include "iso7816_fidi.h"
Harald Welte54cb3d02016-02-29 14:12:40 +01008#include "utils.h"
Harald Welteacae4122016-03-02 10:27:58 +01009#include "linuxlist.h"
Harald Welte9f240b62016-03-18 10:32:56 +010010#include "llist_irqsafe.h"
Harald Welteebb80ed2016-03-02 13:56:59 +010011#include "req_ctx.h"
12#include "cardemu_prot.h"
Harald Weltebd717682016-02-28 19:30:05 +010013
14#define TRACE_ENTRY() TRACE_DEBUG("%s entering\n", __func__)
Harald Welte2a6d3af2016-02-28 19:29:14 +010015
16static const Pin pins_cardsim[] = PINS_CARDSIM;
17
18/* UART pins */
19static const Pin pins_usim1[] = {PINS_USIM1};
20static const Pin pin_usim1_rst = PIN_USIM1_nRST;
21static const Pin pin_usim1_vcc = PIN_USIM1_VCC;
22
23#ifdef CARDEMU_SECOND_UART
24static const Pin pins_usim2[] = {PINS_USIM1};
25static const Pin pin_usim2_rst = PIN_USIM2_nRST;
26static const Pin pin_usim2_vcc = PIN_USIM2_VCC;
27#endif
28
Harald Welte13e82022016-03-02 15:17:53 +010029struct cardem_inst {
30 struct card_handle *ch;
31 struct llist_head usb_out_queue;
32 struct ringbuf rb;
33 struct Usart_info usart_info;
34 int usb_pending_old;
35 uint8_t ep_out;
36 uint8_t ep_in;
37 uint8_t ep_int;
Harald Welte0eaa9922016-03-04 03:03:49 +010038 const Pin pin_insert;
Harald Welte13e82022016-03-02 15:17:53 +010039};
Harald Welte2a6d3af2016-02-28 19:29:14 +010040
Harald Welte13e82022016-03-02 15:17:53 +010041static struct cardem_inst cardem_inst[] = {
Harald Welte2a6d3af2016-02-28 19:29:14 +010042 {
Harald Welte13e82022016-03-02 15:17:53 +010043 .usart_info = {
44 .base = USART1,
45 .id = ID_USART1,
46 .state = USART_RCV
47 },
48 .ep_out = PHONE_DATAOUT,
49 .ep_in = PHONE_DATAIN,
50 .ep_int = PHONE_INT,
Harald Welte0eaa9922016-03-04 03:03:49 +010051 .pin_insert = PIN_SET_USIM1_PRES,
Harald Welte2a6d3af2016-02-28 19:29:14 +010052 },
53#ifdef CARDEMU_SECOND_UART
54 {
Harald Welte13e82022016-03-02 15:17:53 +010055 .usart_info = {
56 .base = USART0,
57 .id = ID_USART0,
58 .state = USART_RCV
59 },
60 .ep_out = CARDEM_USIM2_DATAOUT,
61 .ep_in = CARDEM_USIM2_DATAIN,
62 .ep_int = CARDEM_USIM2_INT,
Harald Welte0eaa9922016-03-04 03:03:49 +010063 .pin_insert = PIN_SET_USIM2_PRES,
Harald Welte2a6d3af2016-02-28 19:29:14 +010064 },
65#endif
66};
67
68static Usart *get_usart_by_chan(uint8_t uart_chan)
69{
70 switch (uart_chan) {
71 case 0:
72 return USART1;
73#ifdef CARDEMU_SECOND_UART
74 case 1:
75 return USART0;
76#endif
77 }
78 return NULL;
79}
80
81/***********************************************************************
82 * Call-Backs from card_emu.c
83 ***********************************************************************/
84
85/* call-back from card_emu.c to enable/disable transmit and/or receive */
86void card_emu_uart_enable(uint8_t uart_chan, uint8_t rxtx)
87{
88 Usart *usart = get_usart_by_chan(uart_chan);
89 switch (rxtx) {
90 case ENABLE_TX:
Harald Welte9dbc46e2016-02-29 10:05:10 +010091 USART_DisableIt(usart, ~US_IER_TXRDY);
Harald Welte2a6d3af2016-02-28 19:29:14 +010092 USART_SetReceiverEnabled(usart, 0);
Harald Welte8a416b12016-03-01 00:42:04 +010093 usart->US_CR = US_CR_RSTSTA | US_CR_RSTIT | US_CR_RSTNACK;
94 USART_EnableIt(usart, US_IER_TXRDY);
Harald Welte2a6d3af2016-02-28 19:29:14 +010095 USART_SetTransmitterEnabled(usart, 1);
96 break;
97 case ENABLE_RX:
Harald Welte9dbc46e2016-02-29 10:05:10 +010098 USART_DisableIt(usart, ~US_IER_RXRDY);
Harald Welte8a416b12016-03-01 00:42:04 +010099 USART_SetTransmitterEnabled(usart, 0);
100 usart->US_CR = US_CR_RSTSTA | US_CR_RSTIT | US_CR_RSTNACK;
101 USART_EnableIt(usart, US_IER_RXRDY);
102 USART_SetReceiverEnabled(usart, 1);
Harald Welte2a6d3af2016-02-28 19:29:14 +0100103 break;
104 case 0:
105 default:
106 USART_SetTransmitterEnabled(usart, 0);
107 USART_SetReceiverEnabled(usart, 0);
Harald Welte9dbc46e2016-02-29 10:05:10 +0100108 USART_DisableIt(usart, 0xFFFFFFFF);
Harald Welte8a416b12016-03-01 00:42:04 +0100109 usart->US_CR = US_CR_RSTSTA | US_CR_RSTIT | US_CR_RSTNACK;
Harald Welte2a6d3af2016-02-28 19:29:14 +0100110 break;
111 }
112}
113
114/* call-back from card_emu.c to transmit a byte */
115int card_emu_uart_tx(uint8_t uart_chan, uint8_t byte)
116{
Harald Welte13e82022016-03-02 15:17:53 +0100117 Usart *usart = get_usart_by_chan(uart_chan);
118#if 0
Harald Welte2a6d3af2016-02-28 19:29:14 +0100119 Usart_info *ui = &usart_info[uart_chan];
120 ISO7816_SendChar(byte, ui);
Harald Welte13e82022016-03-02 15:17:53 +0100121#else
122 int i = 0;
123 while ((usart->US_CSR & (US_CSR_TXRDY)) == 0) {
124 if (!(i%1000000)) {
125 printf("s: %x %02X", usart->US_CSR, usart->US_RHR & 0xFF);
126 usart->US_CR = US_CR_RSTTX;
127 usart->US_CR = US_CR_RSTRX;
128 }
129 }
130 usart->US_THR = byte;
131 TRACE_ERROR("Sx%02x\r\n", byte);
132#endif
Harald Welte2a6d3af2016-02-28 19:29:14 +0100133 return 1;
134}
135
136
137/* FIXME: integrate this with actual irq handler */
Harald Weltec0bd7f02016-02-29 10:13:33 +0100138void usart_irq_rx(uint8_t uart)
Harald Welte2a6d3af2016-02-28 19:29:14 +0100139{
Harald Weltec0bd7f02016-02-29 10:13:33 +0100140 Usart *usart = get_usart_by_chan(uart);
Harald Welte13e82022016-03-02 15:17:53 +0100141 struct cardem_inst *ci = &cardem_inst[0];
Harald Weltec0bd7f02016-02-29 10:13:33 +0100142 uint32_t csr;
143 uint8_t byte = 0;
144
Harald Welte2a6d3af2016-02-28 19:29:14 +0100145#ifdef CARDEMU_SECOND_UART
Harald Weltec0bd7f02016-02-29 10:13:33 +0100146 if (uart == 1)
Harald Welte13e82022016-03-02 15:17:53 +0100147 ci = &cardem_inst[1];
Harald Welte2a6d3af2016-02-28 19:29:14 +0100148#endif
Harald Welteda15ca02016-03-17 21:14:04 +0100149 csr = usart->US_CSR & usart->US_IMR;
Harald Welte2a6d3af2016-02-28 19:29:14 +0100150
Harald Weltec0bd7f02016-02-29 10:13:33 +0100151 if (csr & US_CSR_RXRDY) {
152 byte = (usart->US_RHR) & 0xFF;
Harald Welte13e82022016-03-02 15:17:53 +0100153 rbuf_write(&ci->rb, byte);
Harald Weltec0bd7f02016-02-29 10:13:33 +0100154 }
Harald Welte2a6d3af2016-02-28 19:29:14 +0100155
Harald Welte12d4bdf2016-03-02 10:31:03 +0100156 if (csr & US_CSR_TXRDY) {
Harald Welte13e82022016-03-02 15:17:53 +0100157 if (card_emu_tx_byte(ci->ch) == 0)
Harald Welte12d4bdf2016-03-02 10:31:03 +0100158 USART_DisableIt(usart, US_IER_TXRDY);
159 }
160
Harald Welte2a6d3af2016-02-28 19:29:14 +0100161 if (csr & (US_CSR_OVRE|US_CSR_FRAME|US_CSR_PARE|
162 US_CSR_TIMEOUT|US_CSR_NACK|(1<<10))) {
Harald Weltec0bd7f02016-02-29 10:13:33 +0100163 usart->US_CR = US_CR_RSTSTA | US_CR_RSTIT | US_CR_RSTNACK;
164 TRACE_ERROR("e 0x%x st: 0x%x\n", byte, csr);
Harald Welte2a6d3af2016-02-28 19:29:14 +0100165 }
166}
167
Harald Weltebd717682016-02-28 19:30:05 +0100168/* call-back from card_emu.c to change UART baud rate */
169int card_emu_uart_update_fidi(uint8_t uart_chan, unsigned int fidi)
170{
171 int rc;
Harald Weltebd717682016-02-28 19:30:05 +0100172 Usart *usart = get_usart_by_chan(uart_chan);
173
Harald Welte99f62a62016-02-29 10:08:49 +0100174 usart->US_CR |= US_CR_RXDIS | US_CR_RSTRX;
175 usart->US_FIDI = fidi & 0x3ff;
176 usart->US_CR |= US_CR_RXEN | US_CR_STTTO;
177 return 0;
Harald Weltebd717682016-02-28 19:30:05 +0100178}
Harald Welte2a6d3af2016-02-28 19:29:14 +0100179
180/***********************************************************************
181 * Core USB / mainloop integration
182 ***********************************************************************/
183
Harald Weltec0bd7f02016-02-29 10:13:33 +0100184static void usim1_rst_irqhandler(const Pin *pPin)
185{
186 int active = PIO_Get(&pin_usim1_rst) ? 0 : 1;
Harald Welte13e82022016-03-02 15:17:53 +0100187 card_emu_io_statechg(cardem_inst[0].ch, CARD_IO_RST, active);
Harald Weltec0bd7f02016-02-29 10:13:33 +0100188}
189
190static void usim1_vcc_irqhandler(const Pin *pPin)
191{
192 int active = PIO_Get(&pin_usim1_vcc) ? 1 : 0;
Harald Welte13e82022016-03-02 15:17:53 +0100193 card_emu_io_statechg(cardem_inst[0].ch, CARD_IO_VCC, active);
Harald Weltec0bd7f02016-02-29 10:13:33 +0100194 /* FIXME do this for real */
Harald Welte13e82022016-03-02 15:17:53 +0100195 card_emu_io_statechg(cardem_inst[0].ch, CARD_IO_CLK, active);
Harald Weltec0bd7f02016-02-29 10:13:33 +0100196}
197
Harald Welte13e82022016-03-02 15:17:53 +0100198#ifdef CARDEMU_SECOND_UART
199static void usim2_rst_irqhandler(const Pin *pPin)
200{
201 int active = PIO_Get(&pin_usim2_rst) ? 0 : 1;
202 card_emu_io_statechg(cardem_inst[1].ch, CARD_IO_RST, active);
203}
204
205static void usim2_vcc_irqhandler(const Pin *pPin)
206{
207 int active = PIO_Get(&pin_usim2_vcc) ? 1 : 0;
208 card_emu_io_statechg(cardem_inst[1].ch, CARD_IO_VCC, active);
209 /* FIXME do this for real */
210 card_emu_io_statechg(cardem_inst[1].ch, CARD_IO_CLK, active);
211}
212#endif
213
Harald Welte2a6d3af2016-02-28 19:29:14 +0100214/* executed once at system boot for each config */
215void mode_cardemu_configure(void)
216{
Harald Weltebd717682016-02-28 19:30:05 +0100217 TRACE_ENTRY();
Harald Welte2a6d3af2016-02-28 19:29:14 +0100218}
219
220/* called if config is activated */
221void mode_cardemu_init(void)
222{
Harald Welte13e82022016-03-02 15:17:53 +0100223 int i;
Harald Weltebd717682016-02-28 19:30:05 +0100224
Harald Welte13e82022016-03-02 15:17:53 +0100225 TRACE_ENTRY();
Harald Weltec0bd7f02016-02-29 10:13:33 +0100226
Harald Welte2a6d3af2016-02-28 19:29:14 +0100227 PIO_Configure(pins_cardsim, PIO_LISTSIZE(pins_cardsim));
228
Harald Welte13e82022016-03-02 15:17:53 +0100229 INIT_LLIST_HEAD(&cardem_inst[0].usb_out_queue);
230 rbuf_reset(&cardem_inst[0].rb);
Harald Welte2a6d3af2016-02-28 19:29:14 +0100231 PIO_Configure(pins_usim1, PIO_LISTSIZE(pins_usim1));
Harald Welte13e82022016-03-02 15:17:53 +0100232 ISO7816_Init(&cardem_inst[0].usart_info, CLK_SLAVE);
Harald Welte2a6d3af2016-02-28 19:29:14 +0100233 NVIC_EnableIRQ(USART1_IRQn);
Harald Weltec0bd7f02016-02-29 10:13:33 +0100234 PIO_ConfigureIt(&pin_usim1_rst, usim1_rst_irqhandler);
235 PIO_EnableIt(&pin_usim1_rst);
236 PIO_ConfigureIt(&pin_usim1_vcc, usim1_vcc_irqhandler);
237 PIO_EnableIt(&pin_usim1_vcc);
Harald Welte13e82022016-03-02 15:17:53 +0100238 cardem_inst[0].ch = card_emu_init(0, 2, 0);
Harald Welte2a6d3af2016-02-28 19:29:14 +0100239
240#ifdef CARDEMU_SECOND_UART
Harald Welte13e82022016-03-02 15:17:53 +0100241 INIT_LLIST_HEAD(&cardem_inst[1].usb_out_queue);
242 rbuf_reset(&cardem_inst[1].rb);
Harald Welte2a6d3af2016-02-28 19:29:14 +0100243 PIO_Configure(pins_usim2, PIO_LISTSIZE(pins_usim2));
Harald Welte13e82022016-03-02 15:17:53 +0100244 ISO7816_Init(&cardem_inst[1].usart_info, CLK_SLAVE);
Harald Welte2a6d3af2016-02-28 19:29:14 +0100245 NVIC_EnableIRQ(USART0_IRQn);
Harald Welte13e82022016-03-02 15:17:53 +0100246 PIO_ConfigureIt(&pin_usim2_rst, usim2_rst_irqhandler);
247 PIO_EnableIt(&pin_usim2_rst);
248 PIO_ConfigureIt(&pin_usim2_vcc, usim2_vcc_irqhandler);
249 PIO_EnableIt(&pin_usim2_vcc);
250 cardem_inst[1].ch = card_emu_init(1, 0, 1);
Harald Welte2a6d3af2016-02-28 19:29:14 +0100251#endif
252}
253
254/* called if config is deactivated */
255void mode_cardemu_exit(void)
256{
Harald Weltebd717682016-02-28 19:30:05 +0100257 TRACE_ENTRY();
258
Harald Welte7abdb512016-03-03 17:48:32 +0100259 /* FIXME: stop tc_fdt */
260 /* FIXME: release all rctx, unlink them from any queue */
261
Harald Weltec0bd7f02016-02-29 10:13:33 +0100262 PIO_DisableIt(&pin_usim1_rst);
263 PIO_DisableIt(&pin_usim1_vcc);
264
Harald Welte2a6d3af2016-02-28 19:29:14 +0100265 NVIC_DisableIRQ(USART1_IRQn);
266 USART_SetTransmitterEnabled(USART1, 0);
267 USART_SetReceiverEnabled(USART1, 0);
268
269#ifdef CARDEMU_SECOND_UART
Harald Welte13e82022016-03-02 15:17:53 +0100270 PIO_DisableIt(&pin_usim2_rst);
271 PIO_DisableIt(&pin_usim2_vcc);
272
Harald Welte2a6d3af2016-02-28 19:29:14 +0100273 NVIC_DisableIRQ(USART0_IRQn);
274 USART_SetTransmitterEnabled(USART0, 0);
275 USART_SetReceiverEnabled(USART0, 0);
276#endif
277}
278
Harald Welteacae4122016-03-02 10:27:58 +0100279static int llist_count(struct llist_head *head)
280{
281 struct llist_head *list;
282 int i = 0;
283
284 llist_for_each(list, head)
285 i++;
286
287 return i;
288}
289
Harald Welteebb80ed2016-03-02 13:56:59 +0100290/* handle a single USB command as received from the USB host */
Harald Welte0eaa9922016-03-04 03:03:49 +0100291static void dispatch_usb_command(struct req_ctx *rctx, struct cardem_inst *ci)
Harald Welteebb80ed2016-03-02 13:56:59 +0100292{
293 struct cardemu_usb_msg_hdr *hdr;
Harald Welte06b27f62016-03-02 14:26:38 +0100294 struct cardemu_usb_msg_set_atr *atr;
Harald Welte0eaa9922016-03-04 03:03:49 +0100295 struct cardemu_usb_msg_cardinsert *cardins;
Harald Welteebb80ed2016-03-02 13:56:59 +0100296 struct llist_head *queue;
297
298 hdr = (struct cardemu_usb_msg_hdr *) rctx->data;
299 switch (hdr->msg_type) {
300 case CEMU_USB_MSGT_DT_TX_DATA:
Harald Welte0eaa9922016-03-04 03:03:49 +0100301 queue = card_emu_get_uart_tx_queue(ci->ch);
Harald Welteebb80ed2016-03-02 13:56:59 +0100302 req_ctx_set_state(rctx, RCTX_S_UART_TX_PENDING);
303 llist_add_tail(&rctx->list, queue);
Harald Welte0eaa9922016-03-04 03:03:49 +0100304 card_emu_have_new_uart_tx(ci->ch);
Harald Welteebb80ed2016-03-02 13:56:59 +0100305 break;
306 case CEMU_USB_MSGT_DT_SET_ATR:
Harald Welte06b27f62016-03-02 14:26:38 +0100307 atr = (struct cardemu_usb_msg_set_atr *) hdr;
Harald Welted295b922016-03-18 21:01:36 +0100308 card_emu_set_atr(ci->ch, atr->atr, atr->atr_len);
Harald Welte06b27f62016-03-02 14:26:38 +0100309 req_ctx_put(rctx);
310 break;
Harald Welte0eaa9922016-03-04 03:03:49 +0100311 case CEMU_USB_MSGT_DT_CARDINSERT:
312 cardins = (struct cardemu_usb_msg_cardinsert *) hdr;
313 if (cardins->card_insert)
314 PIO_Set(&ci->pin_insert);
315 else
316 PIO_Clear(&ci->pin_insert);
Harald Welte5820ea92016-03-16 22:12:00 +0100317 req_ctx_put(rctx);
Harald Welte0eaa9922016-03-04 03:03:49 +0100318 break;
Harald Welteebb80ed2016-03-02 13:56:59 +0100319 case CEMU_USB_MSGT_DT_GET_STATS:
320 case CEMU_USB_MSGT_DT_GET_STATUS:
321 default:
322 /* FIXME */
Harald Welte5820ea92016-03-16 22:12:00 +0100323 req_ctx_put(rctx);
Harald Welteebb80ed2016-03-02 13:56:59 +0100324 break;
325 }
326}
327
328/* iterate over the queue of incoming USB commands and dispatch/execute
329 * them */
Harald Welte9f240b62016-03-18 10:32:56 +0100330static void process_any_usb_commands(struct llist_head *main_q,
331 struct cardem_inst *ci)
Harald Welteebb80ed2016-03-02 13:56:59 +0100332{
Harald Welte9f240b62016-03-18 10:32:56 +0100333 struct llist_head *lh;
334 struct req_ctx *rctx;
335 int i;
Harald Welteebb80ed2016-03-02 13:56:59 +0100336
Harald Welte9f240b62016-03-18 10:32:56 +0100337 /* limit the number of iterations to 10, to ensure we don't get
338 * stuck here without returning to main loop processing */
339 for (i = 0; i < 10; i++) {
340 /* de-queue the list head in an irq-safe way */
341 lh = llist_head_dequeue_irqsafe(main_q);
342 if (!lh)
343 break;
344 rctx = llist_entry(lh, struct req_ctx, list);
345 /* dispatch the command with interrupts enabled */
Harald Welte0eaa9922016-03-04 03:03:49 +0100346 dispatch_usb_command(rctx, ci);
Harald Welte5820ea92016-03-16 22:12:00 +0100347 }
Harald Welteebb80ed2016-03-02 13:56:59 +0100348}
349
Harald Welte2a6d3af2016-02-28 19:29:14 +0100350/* main loop function, called repeatedly */
351void mode_cardemu_run(void)
352{
Harald Welteacae4122016-03-02 10:27:58 +0100353 struct llist_head *queue;
Harald Welte13e82022016-03-02 15:17:53 +0100354 unsigned int i;
Harald Welteacae4122016-03-02 10:27:58 +0100355
Harald Welte13e82022016-03-02 15:17:53 +0100356 for (i = 0; i < ARRAY_SIZE(cardem_inst); i++) {
357 struct cardem_inst *ci = &cardem_inst[i];
358
Harald Welte54cb3d02016-02-29 14:12:40 +0100359 /* drain the ring buffer from UART into card_emu */
Harald Weltec0bd7f02016-02-29 10:13:33 +0100360 while (1) {
361 __disable_irq();
Harald Welte13e82022016-03-02 15:17:53 +0100362 if (rbuf_is_empty(&ci->rb)) {
Harald Weltec0bd7f02016-02-29 10:13:33 +0100363 __enable_irq();
364 break;
365 }
Harald Welte13e82022016-03-02 15:17:53 +0100366 uint8_t byte = rbuf_read(&ci->rb);
Harald Weltec0bd7f02016-02-29 10:13:33 +0100367 __enable_irq();
Harald Welte13e82022016-03-02 15:17:53 +0100368 card_emu_process_rx_byte(ci->ch, byte);
Harald Welteacae4122016-03-02 10:27:58 +0100369 TRACE_ERROR("Rx%02x\r\n", byte);
Harald Weltec0bd7f02016-02-29 10:13:33 +0100370 }
Harald Welteacae4122016-03-02 10:27:58 +0100371
Harald Welte13e82022016-03-02 15:17:53 +0100372 queue = card_emu_get_usb_tx_queue(ci->ch);
Harald Welteacae4122016-03-02 10:27:58 +0100373 int usb_pending = llist_count(queue);
Harald Welte13e82022016-03-02 15:17:53 +0100374 if (usb_pending != ci->usb_pending_old) {
Harald Welteacae4122016-03-02 10:27:58 +0100375// printf("usb_pending=%d\r\n", usb_pending);
Harald Welte13e82022016-03-02 15:17:53 +0100376 ci->usb_pending_old = usb_pending;
Harald Welteacae4122016-03-02 10:27:58 +0100377 }
Harald Welte13e82022016-03-02 15:17:53 +0100378 usb_refill_to_host(queue, ci->ep_in);
Harald Welteacae4122016-03-02 10:27:58 +0100379
Harald Welteebb80ed2016-03-02 13:56:59 +0100380 /* ensure we can handle incoming USB messages from the
381 * host */
Harald Welte13e82022016-03-02 15:17:53 +0100382 queue = &ci->usb_out_queue;
383 usb_refill_from_host(queue, ci->ep_out);
Harald Welte0eaa9922016-03-04 03:03:49 +0100384 process_any_usb_commands(queue, ci);
Harald Welte2a6d3af2016-02-28 19:29:14 +0100385 }
Harald Welte2a6d3af2016-02-28 19:29:14 +0100386
Harald Welte2a6d3af2016-02-28 19:29:14 +0100387}