diff options
Diffstat (limited to 'firmware/src/simtrace/iso7816_uart.c')
-rw-r--r-- | firmware/src/simtrace/iso7816_uart.c | 641 |
1 files changed, 641 insertions, 0 deletions
diff --git a/firmware/src/simtrace/iso7816_uart.c b/firmware/src/simtrace/iso7816_uart.c new file mode 100644 index 0000000..cd1a2f0 --- /dev/null +++ b/firmware/src/simtrace/iso7816_uart.c @@ -0,0 +1,641 @@ +/* Driver for AT91SAM7 USART0 in ISO7816-3 mode for passive sniffing + * (C) 2010 by Harald Welte <hwelte@hmw-consulting.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <AT91SAM7.h> +#include <lib_AT91SAM7.h> +#include <openpcd.h> + +#include <simtrace_usb.h> + +#include <os/usb_handler.h> +#include <os/dbgu.h> +#include <os/pio_irq.h> + +#include "../simtrace.h" +#include "../openpcd.h" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +static const AT91PS_USART usart = AT91C_BASE_US0; + +enum iso7816_3_state { + ISO7816_S_RESET, /* in Reset */ + ISO7816_S_WAIT_ATR, /* waiting for ATR to start */ + ISO7816_S_IN_ATR, /* while we are receiving the ATR */ + ISO7816_S_WAIT_APDU, /* waiting for start of new APDU */ + ISO7816_S_IN_APDU, /* inside a single APDU */ + ISO7816_S_IN_PTS, /* while we are inside the PTS / PSS */ +}; + +/* detailed sub-states of ISO7816_S_IN_ATR */ +enum atr_state { + ATR_S_WAIT_TS, + ATR_S_WAIT_T0, + ATR_S_WAIT_TA, + ATR_S_WAIT_TB, + ATR_S_WAIT_TC, + ATR_S_WAIT_TD, + ATR_S_WAIT_HIST, + ATR_S_WAIT_TCK, + ATR_S_DONE, +}; + +/* detailed sub-states of ISO7816_S_IN_PTS */ +enum pts_state { + PTS_S_WAIT_REQ_PTSS, + PTS_S_WAIT_REQ_PTS0, + PTS_S_WAIT_REQ_PTS1, + PTS_S_WAIT_REQ_PTS2, + PTS_S_WAIT_REQ_PTS3, + PTS_S_WAIT_REQ_PCK, + PTS_S_WAIT_RESP_PTSS = PTS_S_WAIT_REQ_PTSS | 0x10, + PTS_S_WAIT_RESP_PTS0 = PTS_S_WAIT_REQ_PTS0 | 0x10, + PTS_S_WAIT_RESP_PTS1 = PTS_S_WAIT_REQ_PTS1 | 0x10, + PTS_S_WAIT_RESP_PTS2 = PTS_S_WAIT_REQ_PTS2 | 0x10, + PTS_S_WAIT_RESP_PTS3 = PTS_S_WAIT_REQ_PTS3 | 0x10, + PTS_S_WAIT_RESP_PCK = PTS_S_WAIT_REQ_PCK | 0x10, +}; + +#define _PTSS 0 +#define _PTS0 1 +#define _PTS1 2 +#define _PTS2 3 +#define _PTS3 4 +#define _PCK 5 + +struct iso7816_3_handle { + enum iso7816_3_state state; + + u_int8_t fi; + u_int8_t di; + u_int8_t wi; + u_int32_t waiting_time; + + enum atr_state atr_state; + u_int8_t atr_idx; + u_int8_t atr_hist_len; + u_int8_t atr_last_td; + u_int8_t atr[64]; + + enum pts_state pts_state; + u_int8_t pts_req[6]; + u_int8_t pts_resp[6]; + + struct simtrace_hdr sh; + + int rctx_must_be_sent; + struct req_ctx *rctx; +}; + +struct iso7816_3_handle isoh; + + +/* Table 6 from ISO 7816-3 */ +static const u_int16_t fi_table[] = { + 0, 372, 558, 744, 1116, 1488, 1860, 0, + 0, 512, 768, 1024, 1536, 2048, 0, 0 +}; + +/* Table 7 from ISO 7816-3 */ +static const u_int8_t di_table[] = { + 0, 1, 2, 4, 8, 16, 0, 0, + 0, 0, 2, 4, 8, 16, 32, 64, +}; + +/* compute the F/D ratio based on Fi and Di values */ +static int compute_fidi_ratio(u_int8_t fi, u_int8_t di) +{ + u_int16_t f, d; + int ret; + + if (fi >= ARRAY_SIZE(fi_table) || + di >= ARRAY_SIZE(di_table)) + return -EINVAL; + + f = fi_table[fi]; + if (f == 0) + return -EINVAL; + + d = di_table[di]; + if (d == 0) + return -EINVAL; + + if (di < 8) + ret = f / d; + else + ret = f * d; + + return ret; +} + +static void refill_rctx(struct iso7816_3_handle *ih) +{ + struct req_ctx *rctx; + + rctx = req_ctx_find_get(0, RCTX_STATE_FREE, + RCTX_STATE_LIBRFID_BUSY); + if (!rctx) { + ih->rctx = NULL; + return; + } + + ih->sh.cmd = SIMTRACE_MSGT_DATA; + + /* reserve spece at start of rctx */ + rctx->tot_len = sizeof(struct simtrace_hdr); + + ih->rctx = rctx; +} + +static void send_rctx(struct iso7816_3_handle *ih) +{ + struct req_ctx *rctx = ih->rctx; + + if (!rctx) + return; + + /* copy the simtrace header */ + memcpy(rctx->data, &ih->sh, sizeof(ih->sh)); + + req_ctx_set_state(rctx, RCTX_STATE_UDP_EP2_PENDING); + + memset(&ih->sh, 0, sizeof(ih->sh)); + ih->rctx = NULL; +} + + +/* Update the ATR sub-state */ +static void set_atr_state(struct iso7816_3_handle *ih, enum atr_state new_atrs) +{ + if (new_atrs == ATR_S_WAIT_TS) { + ih->atr_idx = 0; + ih->atr_hist_len = 0; + ih->atr_last_td = 0; + memset(ih->atr, 0, sizeof(ih->atr)); + } else if (ih->atr_state == new_atrs) + return; + + //DEBUGPCR("ATR state %u -> %u", ih->atr_state, new_atrs); + ih->atr_state = new_atrs; +} + +#define ISO7816_3_INIT_WTIME 9600 +#define ISO7816_3_DEFAULT_WI 10 + +static void update_fidi(struct iso7816_3_handle *ih) +{ + int rc; + + rc = compute_fidi_ratio(ih->fi, ih->di); + if (rc > 0 && rc < 0x400) { + DEBUGPCR("computed Fi(%u) Di(%u) ratio: %d", ih->fi, ih->di, rc); + /* make sure UART uses new F/D ratio */ + usart->US_CR |= AT91C_US_RXDIS | AT91C_US_RSTRX; + usart->US_FIDI = rc & 0x3ff; + usart->US_CR |= AT91C_US_RXEN | AT91C_US_STTTO; + /* notify ETU timer about this */ + tc_etu_set_etu(rc); + } else + DEBUGPCRF("computed FiDi ratio %d unsupported", rc); +} + +/* Update the ISO 7816-3 APDU receiver state */ +static void set_state(struct iso7816_3_handle *ih, enum iso7816_3_state new_state) +{ + if (new_state == ISO7816_S_RESET) { + usart->US_CR |= AT91C_US_RXDIS | AT91C_US_RSTRX; + } else if (new_state == ISO7816_S_WAIT_ATR) { + /* Reset to initial Fi / Di ratio */ + ih->fi = 1; + ih->di = 1; + update_fidi(ih); + /* initialize todefault WI, this will be overwritten if we + * receive TC2, and it will be programmed into hardware after + * ATR is finished */ + ih->wi = ISO7816_3_DEFAULT_WI; + /* update waiting time to initial waiting time */ + ih->waiting_time = ISO7816_3_INIT_WTIME; + tc_etu_set_wtime(ih->waiting_time); + /* Set ATR sub-state to initial state */ + set_atr_state(ih, ATR_S_WAIT_TS); + /* Notice that we are just coming out of reset */ + ih->sh.flags |= SIMTRACE_FLAG_ATR; + } + + if (ih->state == new_state) + return; + + //DEBUGPCR("7816 state %u -> %u", ih->state, new_state); + ih->state = new_state; +} + +/* determine the next ATR state based on received interface byte */ +static enum atr_state next_intb_state(struct iso7816_3_handle *ih, u_int8_t ch) +{ + switch (ih->atr_state) { + case ATR_S_WAIT_TD: + case ATR_S_WAIT_T0: + ih->atr_last_td = ch; + goto from_td; + case ATR_S_WAIT_TC: + if ((ih->atr_last_td & 0x0f) == 0x02) { + /* TC2 contains WI */ + ih->wi = ch; + } + goto from_tc; + case ATR_S_WAIT_TB: + goto from_tb; + case ATR_S_WAIT_TA: + goto from_ta; + default: + DEBUGPCR("something wrong, old_state != TA"); + return ATR_S_WAIT_TCK; + } + +from_td: + if (ih->atr_last_td & 0x10) + return ATR_S_WAIT_TA; +from_ta: + if (ih->atr_last_td & 0x20) + return ATR_S_WAIT_TB; +from_tb: + if (ih->atr_last_td & 0x40) + return ATR_S_WAIT_TC; +from_tc: + if (ih->atr_last_td & 0x80) + return ATR_S_WAIT_TD; + + return ATR_S_WAIT_HIST; +} + +/* process an incomng ATR byte */ +static enum iso7816_3_state +process_byte_atr(struct iso7816_3_handle *ih, u_int8_t byte) +{ + /* add byte to ATR buffer */ + ih->atr[ih->atr_idx] = byte; + ih->atr_idx++; + + switch (ih->atr_state) { + case ATR_S_WAIT_TS: + /* FIXME: if we don't have the RST line we might get this */ + if (byte == 0) { + ih->atr_idx--; + break; + } + /* FIXME: check inverted logic */ + set_atr_state(ih, ATR_S_WAIT_T0); + break; + case ATR_S_WAIT_T0: + /* obtain the number of historical bytes */ + ih->atr_hist_len = byte & 0xf; + /* Mask out the hist-byte-length to indiicate T=0 */ + set_atr_state(ih, next_intb_state(ih, byte & 0xf0)); + break; + case ATR_S_WAIT_TA: + case ATR_S_WAIT_TB: + case ATR_S_WAIT_TC: + case ATR_S_WAIT_TD: + set_atr_state(ih, next_intb_state(ih, byte)); + break; + case ATR_S_WAIT_HIST: + ih->atr_hist_len--; + /* after all historical bytes are recieved, go to TCK */ + if (ih->atr_hist_len == 0) + set_atr_state(ih, ATR_S_WAIT_TCK); + break; + case ATR_S_WAIT_TCK: + /* FIXME: process and verify the TCK */ + set_atr_state(ih, ATR_S_DONE); + /* send off the USB context */ + ih->rctx_must_be_sent = 1; + /* update the waiting time */ + ih->waiting_time = 960 * di_table[ih->di] * ih->wi; + tc_etu_set_wtime(ih->waiting_time); + return ISO7816_S_WAIT_APDU; + } + + return ISO7816_S_IN_ATR; +} + +/* Update the ATR sub-state */ +static void set_pts_state(struct iso7816_3_handle *ih, enum pts_state new_ptss) +{ + //DEBUGPCR("PTS state %u -> %u", ih->pts_state, new_ptss); + ih->pts_state = new_ptss; +} + +/* Determine the next PTS state */ +static enum pts_state next_pts_state(struct iso7816_3_handle *ih) +{ + u_int8_t is_resp = ih->pts_state & 0x10; + u_int8_t sstate = ih->pts_state & 0x0f; + u_int8_t *pts_ptr; + + if (!is_resp) + pts_ptr = ih->pts_req; + else + pts_ptr = ih->pts_resp; + + switch (sstate) { + case PTS_S_WAIT_REQ_PTSS: + goto from_ptss; + case PTS_S_WAIT_REQ_PTS0: + goto from_pts0; + case PTS_S_WAIT_REQ_PTS1: + goto from_pts1; + case PTS_S_WAIT_REQ_PTS2: + goto from_pts2; + case PTS_S_WAIT_REQ_PTS3: + goto from_pts3; + } + + if (ih->pts_state == PTS_S_WAIT_REQ_PCK) + return PTS_S_WAIT_RESP_PTSS; + +from_ptss: + return PTS_S_WAIT_REQ_PTS0 | is_resp; +from_pts0: + if (pts_ptr[_PTS0] & (1 << 4)) + return PTS_S_WAIT_REQ_PTS1 | is_resp; +from_pts1: + if (pts_ptr[_PTS0] & (1 << 5)) + return PTS_S_WAIT_REQ_PTS2 | is_resp; +from_pts2: + if (pts_ptr[_PTS0] & (1 << 6)) + return PTS_S_WAIT_REQ_PTS3 | is_resp; +from_pts3: + return PTS_S_WAIT_REQ_PCK | is_resp; +} + +static enum iso7816_3_state +process_byte_pts(struct iso7816_3_handle *ih, u_int8_t byte) +{ + switch (ih->pts_state) { + case PTS_S_WAIT_REQ_PTSS: + ih->pts_req[_PTSS] = byte; + break; + case PTS_S_WAIT_REQ_PTS0: + ih->pts_req[_PTS0] = byte; + break; + case PTS_S_WAIT_REQ_PTS1: + ih->pts_req[_PTS1] = byte; + break; + case PTS_S_WAIT_REQ_PTS2: + ih->pts_req[_PTS2] = byte; + break; + case PTS_S_WAIT_REQ_PTS3: + ih->pts_req[_PTS3] = byte; + break; + case PTS_S_WAIT_REQ_PCK: + /* FIXME: check PCK */ + ih->pts_req[_PCK] = byte; + break; + case PTS_S_WAIT_RESP_PTSS: + ih->pts_resp[_PTSS] = byte; + break; + case PTS_S_WAIT_RESP_PTS0: + ih->pts_resp[_PTS0] = byte; + break; + case PTS_S_WAIT_RESP_PTS1: + /* This must be TA1 */ + ih->fi = byte >> 4; + ih->di = byte & 0xf; + DEBUGPCR("found Fi=%u Di=%u", ih->fi, ih->di); + ih->pts_resp[_PTS1] = byte; + break; + case PTS_S_WAIT_RESP_PTS2: + ih->pts_resp[_PTS2] = byte; + break; + case PTS_S_WAIT_RESP_PTS3: + ih->pts_resp[_PTS3] = byte; + break; + case PTS_S_WAIT_RESP_PCK: + ih->pts_resp[_PCK] = byte; + /* FIXME: check PCK */ + set_pts_state(ih, PTS_S_WAIT_REQ_PTSS); + /* update baud rate generator with Fi/Di */ + update_fidi(ih); + /* Wait for the next APDU */ + return ISO7816_S_WAIT_APDU; + } + /* calculate the next state and set it */ + set_pts_state(ih, next_pts_state(ih)); + + return ISO7816_S_IN_PTS; +} + +static void process_byte(struct iso7816_3_handle *ih, u_int8_t byte) +{ + int new_state = -1; + struct req_ctx *rctx; + + if (!ih->rctx) + refill_rctx(ih); + + switch (ih->state) { + case ISO7816_S_RESET: + break; + case ISO7816_S_WAIT_ATR: + case ISO7816_S_IN_ATR: + new_state = process_byte_atr(ih, byte); + break; + case ISO7816_S_WAIT_APDU: + if (byte == 0xff) { + new_state = process_byte_pts(ih, byte); + goto out_silent; + } + case ISO7816_S_IN_APDU: + new_state = ISO7816_S_IN_APDU; + break; + case ISO7816_S_IN_PTS: + new_state = process_byte_pts(ih, byte); + goto out_silent; + } + + /* The USB buffer could be gone in case the timer expired or code above + * this line explicitly sent it off */ + if (!ih->rctx) + refill_rctx(ih); + + rctx = ih->rctx; + if (!rctx) { + DEBUGPCR("==> Lost byte, missing rctx"); + return; + } + + /* store the byte in the USB request context */ + rctx->data[rctx->tot_len] = byte; + rctx->tot_len++; + + if (rctx->tot_len >= rctx->size || ih->rctx_must_be_sent) { + ih->rctx_must_be_sent = 0; + send_rctx(ih); + } + +out_silent: + if (new_state != -1) + set_state(ih, new_state); +} + +/* timeout of work waiting time during receive */ +void iso7816_wtime_expired(void) +{ + /* Always flush the URB at Rx timeout as this indicates end of APDU */ + if (isoh.rctx) { + isoh.sh.flags |= SIMTRACE_FLAG_WTIME_EXP; + send_rctx(&isoh); + } + if (isoh.state == ISO7816_S_IN_PTS) { + /* Timout during PTS: Card does not support PTS */ + } + set_state(&isoh, ISO7816_S_WAIT_APDU); +} + +static __ramfunc void usart_irq(void) +{ + u_int32_t csr = usart->US_CSR; + u_int8_t octet; + + //DEBUGP("USART IRQ, CSR=0x%08x\n", csr); + + if (csr & AT91C_US_RXRDY) { + /* at least one character received */ + octet = usart->US_RHR & 0xff; + //DEBUGP("%02x ", octet); + process_byte(&isoh, octet); + } + + if (csr & AT91C_US_TXRDY) { + /* nothing to transmit anymore */ + } + + if (csr & (AT91C_US_PARE|AT91C_US_FRAME|AT91C_US_OVRE)) { + /* FIXME: some error has occurrerd */ + } +} + +/* handler for the RST input pin state change */ +static void reset_pin_irq(u_int32_t pio) +{ + if (!AT91F_PIO_IsInputSet(AT91C_BASE_PIOA, pio)) { + DEBUGPCR("nRST"); + set_state(&isoh, ISO7816_S_RESET); + } else { + DEBUGPCR("RST"); + set_state(&isoh, ISO7816_S_WAIT_ATR); + } +} + +void iso_uart_dump(void) +{ + u_int32_t csr = usart->US_CSR; + + DEBUGPCR("USART CSR=0x%08x", csr); +} + +void iso_uart_rst(unsigned int state) +{ + DEBUGPCR("USART set nRST set state=%u", state); + switch (state) { + case 0: + AT91F_PIO_ClearOutput(AT91C_BASE_PIOA, SIMTRACE_PIO_nRST); + AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, SIMTRACE_PIO_nRST); + break; + case 1: + AT91F_PIO_SetOutput(AT91C_BASE_PIOA, SIMTRACE_PIO_nRST); + AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, SIMTRACE_PIO_nRST); + break; + default: + AT91F_PIO_CfgInput(AT91C_BASE_PIOA, SIMTRACE_PIO_nRST); + break; + } +} + +void iso_uart_rx_mode(void) +{ + DEBUGPCR("USART Entering Rx Mode"); + /* Enable receive interrupts */ + usart->US_IER = AT91C_US_RXRDY | AT91C_US_OVRE | AT91C_US_FRAME | + AT91C_US_PARE | AT91C_US_NACK | AT91C_US_ITERATION; + + /* call interrupt handler once to set initial state RESET / ATR */ + reset_pin_irq(SIMTRACE_PIO_nRST); +} + +void iso_uart_clk_master(unsigned int master) +{ + DEBUGPCR("USART Clock Master %u", master); + if (master) { + usart->US_MR = AT91C_US_USMODE_ISO7816_0 | AT91C_US_CLKS_CLOCK | + AT91C_US_CHRL_8_BITS | AT91C_US_NBSTOP_1_BIT | + AT91C_US_CKLO; + usart->US_BRGR = (0x0000 << 16) | 16; + } else { + usart->US_MR = AT91C_US_USMODE_ISO7816_0 | AT91C_US_CLKS_EXT | + AT91C_US_CHRL_8_BITS | AT91C_US_NBSTOP_1_BIT | + AT91C_US_CKLO; + usart->US_BRGR = (0x0000 << 16) | 0x0001; + } +} + +void iso_uart_init(void) +{ + DEBUGPCR("USART Initializing"); + + refill_rctx(&isoh); + + /* make sure we get clock from the power management controller */ + AT91F_US0_CfgPMC(); + + /* configure all 3 signals as input */ + AT91F_PIO_CfgPeriph(AT91C_BASE_PIOA, SIMTRACE_PIO_IO, SIMTRACE_PIO_CLK); + AT91F_PIO_CfgInput(AT91C_BASE_PIOA, SIMTRACE_PIO_nRST); + + AT91F_AIC_ConfigureIt(AT91C_BASE_AIC, AT91C_ID_US0, + OPENPCD_IRQ_PRIO_USART, + AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, &usart_irq); + AT91F_AIC_EnableIt(AT91C_BASE_AIC, AT91C_ID_US0); + + usart->US_CR = AT91C_US_RXDIS | AT91C_US_TXDIS | + (AT91C_US_RSTRX | AT91C_US_RSTTX); + /* FIXME: wait for some time */ + usart->US_CR = AT91C_US_RXDIS | AT91C_US_TXDIS; + + /* ISO7816 T=0 mode with external clock input */ + usart->US_MR = AT91C_US_USMODE_ISO7816_0 | AT91C_US_CLKS_EXT | + AT91C_US_CHRL_8_BITS | AT91C_US_NBSTOP_1_BIT | + AT91C_US_CKLO; + + /* Disable all interrupts */ + usart->US_IDR = 0xff; + /* Clock Divider = 1, i.e. no division of SCLK */ + usart->US_BRGR = (0x0000 << 16) | 0x0001; + /* Disable Receiver Time-out */ + usart->US_RTOR = 0; + /* Disable Transmitter Timeguard */ + usart->US_TTGR = 0; + + pio_irq_register(SIMTRACE_PIO_nRST, &reset_pin_irq); + AT91F_PIO_CfgInputFilter(AT91C_BASE_PIOA, SIMTRACE_PIO_nRST); + pio_irq_enable(SIMTRACE_PIO_nRST); +} |