mirror of https://gerrit.osmocom.org/simtrace2
695 lines
17 KiB
C
695 lines
17 KiB
C
/* ISO7816-3 state machine for the card side */
|
|
/* (C) 2010-2015 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 <stdint.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "utils.h"
|
|
#include "trace.h"
|
|
#include "iso7816_fidi.h"
|
|
#include "tc_etu.h"
|
|
#include "card_emu.h"
|
|
#include "req_ctx.h"
|
|
#include "cardemu_prot.h"
|
|
|
|
|
|
#define NUM_SLOTS 2
|
|
|
|
#define ISO7816_3_INIT_WTIME 9600
|
|
#define ISO7816_3_DEFAULT_WI 10
|
|
#define ISO7816_3_ATR_LEN_MAX (1+32) /* TS plus 32 chars */
|
|
|
|
#define ISO7816_3_PB_NULL 0x60
|
|
|
|
enum iso7816_3_card_state {
|
|
ISO_S_WAIT_POWER, /* waiting for power being applied */
|
|
ISO_S_WAIT_CLK, /* waiting for clock being applied */
|
|
ISO_S_WAIT_RST, /* waiting for reset being released */
|
|
ISO_S_WAIT_ATR, /* waiting for start of ATR */
|
|
ISO_S_IN_ATR, /* transmitting ATR to reader */
|
|
ISO_S_IN_PTS, /* transmitting ATR to reader */
|
|
ISO_S_WAIT_TPDU, /* waiting for data from reader */
|
|
ISO_S_IN_TPDU, /* inside a TPDU */
|
|
};
|
|
|
|
/* detailed sub-states of ISO_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
|
|
|
|
enum tpdu_state {
|
|
TPDU_S_WAIT_CLA,
|
|
TPDU_S_WAIT_INS,
|
|
TPDU_S_WAIT_P1,
|
|
TPDU_S_WAIT_P2,
|
|
TPDU_S_WAIT_P3,
|
|
TPDU_S_WAIT_PB, /* waiting for Tx of procedure byte */
|
|
TPDU_S_WAIT_RX, /* waiitng for more data from reader */
|
|
TPDU_S_WAIT_TX, /* waiting for more data to reader */
|
|
};
|
|
|
|
#define _CLA 0
|
|
#define _INS 1
|
|
#define _P1 2
|
|
#define _P2 3
|
|
#define _P3 4
|
|
|
|
struct card_handle {
|
|
enum iso7816_3_card_state state;
|
|
|
|
/* signal levels */
|
|
uint8_t vcc_active; /* 1 = on, 0 = off */
|
|
uint8_t in_reset; /* 1 = RST low, 0 = RST high */
|
|
uint8_t clocked; /* 1 = active, 0 = inactive */
|
|
|
|
/* timing */
|
|
uint8_t fi;
|
|
uint8_t di;
|
|
uint8_t wi;
|
|
|
|
uint8_t tc_chan; /* TC channel number */
|
|
uint8_t uart_chan; /* UART channel */
|
|
|
|
uint32_t waiting_time; /* in clocks */
|
|
|
|
/* ATR state machine */
|
|
struct {
|
|
uint8_t idx;
|
|
uint8_t len;
|
|
//uint8_t hist_len;
|
|
//uint8_t last_td;
|
|
uint8_t atr[ISO7816_3_ATR_LEN_MAX];
|
|
} atr;
|
|
|
|
/* PPS / PTS support */
|
|
struct {
|
|
enum pts_state state;
|
|
uint8_t req[6];
|
|
uint8_t resp[6];
|
|
} pts;
|
|
|
|
/* TPDU */
|
|
struct {
|
|
enum tpdu_state state;
|
|
uint8_t hdr[5];
|
|
} tpdu;
|
|
|
|
struct req_ctx *uart_rx_ctx;
|
|
struct req_ctx *uart_tx_ctx;
|
|
|
|
struct {
|
|
uint32_t tx_bytes;
|
|
uint32_t rx_bytes;
|
|
uint32_t pps;
|
|
} stats;
|
|
};
|
|
|
|
static int update_fidi(struct card_handle *ch)
|
|
{
|
|
int rc;
|
|
|
|
rc = compute_fidi_ratio(ch->fi, ch->di);
|
|
if (rc > 0 && rc < 0x400) {
|
|
TRACE_DEBUG("computed Fi(%u) Di(%u) ratio: %d\n",
|
|
ch->fi, ch->di, rc);
|
|
/* make sure UART uses new F/D ratio */
|
|
card_emu_uart_update_fidi(ch->uart_chan, rc);
|
|
/* notify ETU timer about this */
|
|
tc_etu_set_etu(ch->tc_chan, rc);
|
|
} else
|
|
TRACE_DEBUG("computed FiDi ration %d unsupported\n", rc);
|
|
}
|
|
|
|
/* Update the ISO 7816-3 TPDU receiver state */
|
|
static void card_set_state(struct card_handle *ch,
|
|
enum iso7816_3_card_state new_state)
|
|
{
|
|
switch (new_state) {
|
|
case ISO_S_WAIT_POWER:
|
|
case ISO_S_WAIT_CLK:
|
|
case ISO_S_WAIT_RST:
|
|
/* disable Rx and Tx of UART */
|
|
card_emu_uart_enable(ch->uart_chan, 0);
|
|
break;
|
|
case ISO_S_WAIT_ATR:
|
|
/* Reset to initial Fi / Di ratio */
|
|
ch->fi = 1;
|
|
ch->di = 1;
|
|
update_fidi(ch);
|
|
/* initialize todefault WI, this will be overwritten if we
|
|
* receive TC2, and it will be programmed into hardware after
|
|
* ATR is finished */
|
|
ch->wi = ISO7816_3_DEFAULT_WI;
|
|
/* update waiting time to initial waiting time */
|
|
ch->waiting_time = ISO7816_3_INIT_WTIME;
|
|
tc_etu_set_wtime(ch->tc_chan, ch->waiting_time);
|
|
/* Set ATR sub-state to initial state */
|
|
ch->atr.idx = 0;
|
|
//set_atr_state(ch, ATR_S_WAIT_TS);
|
|
/* Notice that we are just coming out of reset */
|
|
//ch->sh.flags |= SIMTRACE_FLAG_ATR;
|
|
card_emu_uart_enable(ch->uart_chan, ENABLE_TX);
|
|
break;
|
|
break;
|
|
case ISO_S_WAIT_TPDU:
|
|
/* enable the receiver, disable transmitter */
|
|
card_emu_uart_enable(ch->uart_chan, ENABLE_RX);
|
|
break;
|
|
case ISO_S_IN_ATR:
|
|
case ISO_S_IN_PTS:
|
|
case ISO_S_IN_TPDU:
|
|
/* do nothing */
|
|
break;
|
|
}
|
|
|
|
if (ch->state == new_state)
|
|
return;
|
|
|
|
TRACE_DEBUG("7816 card state %u -> %u\n", ch->state, new_state);
|
|
ch->state = new_state;
|
|
}
|
|
|
|
|
|
/**********************************************************************
|
|
* PTS / PPS handling
|
|
**********************************************************************/
|
|
|
|
/* Update the ATR sub-state */
|
|
static void set_pts_state(struct card_handle *ch, enum pts_state new_ptss)
|
|
{
|
|
TRACE_DEBUG("7816 PTS state %u -> %u\n", ch->pts.state, new_ptss);
|
|
ch->pts.state = new_ptss;
|
|
}
|
|
|
|
/* Determine the next PTS state */
|
|
static enum pts_state next_pts_state(struct card_handle *ch)
|
|
{
|
|
uint8_t is_resp = ch->pts.state & 0x10;
|
|
uint8_t sstate = ch->pts.state & 0x0f;
|
|
uint8_t *pts_ptr;
|
|
|
|
if (!is_resp)
|
|
pts_ptr = ch->pts.req;
|
|
else
|
|
pts_ptr = ch->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 (ch->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_card_state
|
|
process_byte_pts(struct card_handle *ch, uint8_t byte)
|
|
{
|
|
switch (ch->pts.state) {
|
|
case PTS_S_WAIT_REQ_PTSS:
|
|
ch->pts.req[_PTSS] = byte;
|
|
break;
|
|
case PTS_S_WAIT_REQ_PTS0:
|
|
ch->pts.req[_PTS0] = byte;
|
|
break;
|
|
case PTS_S_WAIT_REQ_PTS1:
|
|
ch->pts.req[_PTS1] = byte;
|
|
break;
|
|
case PTS_S_WAIT_REQ_PTS2:
|
|
ch->pts.req[_PTS2] = byte;
|
|
break;
|
|
case PTS_S_WAIT_REQ_PTS3:
|
|
ch->pts.req[_PTS3] = byte;
|
|
break;
|
|
case PTS_S_WAIT_REQ_PCK:
|
|
ch->pts.req[_PCK] = byte;
|
|
/* FIXME: check PCK */
|
|
memcpy(ch->pts.resp, ch->pts.req, sizeof(ch->pts.resp));
|
|
break;
|
|
}
|
|
/* calculate the next state and set it */
|
|
set_pts_state(ch, next_pts_state(ch));
|
|
|
|
return ISO_S_IN_PTS;
|
|
}
|
|
|
|
/* return a single byte to be transmitted to the reader */
|
|
static int get_byte_pps(struct card_handle *ch, uint8_t *byte)
|
|
{
|
|
/* FIXME */
|
|
#if 0
|
|
switch (ch->pts.state) {
|
|
case PTS_S_WAIT_RESP_PTSS:
|
|
ch->pts.resp[_PTSS] = byte;
|
|
break;
|
|
case PTS_S_WAIT_RESP_PTS0:
|
|
ch->pts.resp[_PTS0] = byte;
|
|
break;
|
|
case PTS_S_WAIT_RESP_PTS1:
|
|
/* This must be TA1 */
|
|
ch->fi = byte >> 4;
|
|
ch->di = byte & 0xf;
|
|
TRACE_DEBUG("found Fi=%u Di=%u\n", ch->fi, ch->di);
|
|
ch->sh.flags |= SIMTRACE_FLAG_PPS_FIDI;
|
|
ch->pts.resp[_PTS1] = byte;
|
|
break;
|
|
case PTS_S_WAIT_RESP_PTS2:
|
|
ch->pts.resp[_PTS2] = byte;
|
|
break;
|
|
case PTS_S_WAIT_RESP_PTS3:
|
|
ch->pts.resp[_PTS3] = byte;
|
|
break;
|
|
case PTS_S_WAIT_RESP_PCK:
|
|
ch->pts.resp[_PCK] = byte;
|
|
/* FIXME: check PCK */
|
|
set_pts_state(ch, PTS_S_WAIT_REQ_PTSS);
|
|
/* update baud rate generator with Fi/Di */
|
|
update_fidi(ch);
|
|
/* Wait for the next TPDU */
|
|
card_set_state(ch, ISO_S_WAIT_TPDU);
|
|
}
|
|
#endif
|
|
/* calculate the next state and set it */
|
|
set_pts_state(ch, next_pts_state(ch));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**********************************************************************
|
|
* TPDU handling
|
|
**********************************************************************/
|
|
|
|
/* add a just-received TPDU byte (from reader) to USB buffer */
|
|
static void add_tpdu_byte(struct card_handle *ch, uint8_t byte)
|
|
{
|
|
struct req_ctx *rctx;
|
|
struct cardemu_usb_msg_rx_data *rd;
|
|
|
|
/* ensure we have a buffer */
|
|
if (!ch->uart_rx_ctx) {
|
|
ch->uart_rx_ctx = req_ctx_find_get(1, RCTX_S_FREE, RCTX_S_UART_RX_BUSY);
|
|
if (!ch->uart_rx_ctx)
|
|
return;
|
|
rd = (struct cardemu_usb_msg_rx_data *) ch->uart_rx_ctx->data;
|
|
cardemu_hdr_set(&rd->hdr, CEMU_USB_MSGT_DO_RX_DATA);
|
|
rctx->tot_len = sizeof(*rd);
|
|
rctx->idx = 0;
|
|
} else
|
|
rctx = ch->uart_rx_ctx;
|
|
|
|
rd = (struct cardemu_usb_msg_rx_data *) rctx->data;
|
|
|
|
rd->data[rctx->idx++] = byte;
|
|
rctx->tot_len++;
|
|
|
|
/* check if the buffer is full. If so, send it */
|
|
if (rctx->tot_len >= rctx->size) {
|
|
/* store length of data payload fild in header */
|
|
rd->hdr.data_len = rctx->idx;
|
|
req_ctx_set_state(rctx, RCTX_S_USB_TX_PENDING);
|
|
ch->uart_rx_ctx = NULL;
|
|
/* FIXME: call into USB code to see if this buffer can
|
|
* be transmitted now */
|
|
}
|
|
}
|
|
|
|
static void set_tpdu_state(struct card_handle *ch, enum tpdu_state new_ts)
|
|
{
|
|
TRACE_DEBUG("7816 TPDU state %u -> %u\n", ch->tpdu.state, new_ts);
|
|
switch (new_ts) {
|
|
case TPDU_S_WAIT_CLA:
|
|
card_emu_uart_enable(ch->uart_chan, ENABLE_RX);
|
|
break;
|
|
case TPDU_S_WAIT_PB:
|
|
/* we just completed the TPDU header from reader to card
|
|
* and now need to disable the receiver, enable the
|
|
* transmitter and transmit the procedure byte */
|
|
card_emu_uart_enable(ch->uart_chan, ENABLE_TX);
|
|
break;
|
|
}
|
|
ch->tpdu.state = new_ts;
|
|
}
|
|
|
|
static enum tpdu_state next_tpdu_state(struct card_handle *ch)
|
|
{
|
|
switch (ch->tpdu.state) {
|
|
case TPDU_S_WAIT_CLA:
|
|
return TPDU_S_WAIT_INS;
|
|
case TPDU_S_WAIT_INS:
|
|
return TPDU_S_WAIT_P1;
|
|
case TPDU_S_WAIT_P1:
|
|
return TPDU_S_WAIT_P2;
|
|
case TPDU_S_WAIT_P2:
|
|
return TPDU_S_WAIT_P3;
|
|
case TPDU_S_WAIT_P3:
|
|
return TPDU_S_WAIT_PB;
|
|
/* simply stay in Rx or Tx by default */
|
|
case TPDU_S_WAIT_PB:
|
|
return TPDU_S_WAIT_PB;
|
|
case TPDU_S_WAIT_RX:
|
|
return TPDU_S_WAIT_RX;
|
|
case TPDU_S_WAIT_TX:
|
|
return TPDU_S_WAIT_TX;
|
|
}
|
|
}
|
|
|
|
static void send_tpdu_header(struct card_handle *ch)
|
|
{
|
|
struct req_ctx *rctx;
|
|
struct cardemu_usb_msg_rx_data *rd;
|
|
|
|
/* if we already/still have a context, send it off */
|
|
if (ch->uart_rx_ctx && rctx->idx) {
|
|
ch->uart_rx_ctx = NULL;
|
|
}
|
|
|
|
/* ensure we have a new buffer */
|
|
ch->uart_rx_ctx = req_ctx_find_get(1, RCTX_S_FREE, RCTX_S_UART_RX_BUSY);
|
|
if (!ch->uart_rx_ctx)
|
|
return;
|
|
rctx = ch->uart_rx_ctx;
|
|
rd = (struct cardemu_usb_msg_rx_data *) rctx->data;
|
|
|
|
/* initializ header */
|
|
cardemu_hdr_set(&rd->hdr, CEMU_USB_MSGT_DO_RX_DATA);
|
|
rd->flags = CEMU_DATA_F_TPDU_HDR;
|
|
rctx->tot_len = sizeof(*rd) + sizeof(ch->tpdu.hdr);
|
|
rctx->idx = 0;
|
|
|
|
/* copy TPDU header to data field */
|
|
memcpy(rd->data, ch->tpdu.hdr, sizeof(ch->tpdu.hdr));
|
|
rd->hdr.data_len = sizeof(ch->tpdu.hdr);
|
|
|
|
req_ctx_set_state(rctx, RCTX_S_USB_TX_PENDING);
|
|
}
|
|
|
|
static enum iso7816_3_card_state
|
|
process_byte_tpdu(struct card_handle *ch, uint8_t byte)
|
|
{
|
|
switch (ch->tpdu.state) {
|
|
case TPDU_S_WAIT_CLA:
|
|
ch->tpdu.hdr[_CLA] = byte;
|
|
break;
|
|
case TPDU_S_WAIT_INS:
|
|
ch->tpdu.hdr[_INS] = byte;
|
|
break;
|
|
case TPDU_S_WAIT_P1:
|
|
ch->tpdu.hdr[_P1] = byte;
|
|
break;
|
|
case TPDU_S_WAIT_P2:
|
|
ch->tpdu.hdr[_P2] = byte;
|
|
break;
|
|
case TPDU_S_WAIT_P3:
|
|
ch->tpdu.hdr[_P3] = byte;
|
|
/* FIXME: start timer to transmit further 0x60 */
|
|
/* send the TPDU header as part of a procedure byte
|
|
* request to the USB host */
|
|
send_tpdu_header(ch);
|
|
break;
|
|
case TPDU_S_WAIT_RX:
|
|
add_tpdu_byte(ch, byte);
|
|
break;
|
|
default:
|
|
TRACE_DEBUG("process_byte_tpdu() in invalid state %u\n",
|
|
ch->tpdu.state);
|
|
}
|
|
set_tpdu_state(ch, next_tpdu_state(ch));
|
|
|
|
/* ensure we stay in TPDU ISO state */
|
|
return ISO_S_IN_TPDU;
|
|
}
|
|
|
|
/* return a single byte to be transmitted to the reader */
|
|
static int get_byte_tpdu(struct card_handle *ch, uint8_t *byte)
|
|
{
|
|
struct req_ctx *rctx;
|
|
struct cardemu_usb_msg_tx_data *td;
|
|
|
|
/* ensure we are aware of any data that might be pending for
|
|
* transmit */
|
|
if (!ch->uart_tx_ctx) {
|
|
ch->uart_tx_ctx = req_ctx_find_get(1, RCTX_S_UART_TX_PENDING,
|
|
RCTX_S_UART_TX_BUSY);
|
|
if (!ch->uart_tx_ctx)
|
|
return 0;
|
|
|
|
/* start with index zero */
|
|
ch->uart_tx_ctx->idx = 0;
|
|
|
|
}
|
|
rctx = ch->uart_tx_ctx;
|
|
td = (struct cardemu_usb_msg_tx_data *) rctx->data;
|
|
|
|
#if 0
|
|
/* this must happen _after_ the byte has been transmittd */
|
|
switch (ch->tpdu.state) {
|
|
case TPDU_S_WAIT_PB:
|
|
if (td->flags & CEMU_DATA_F_PB_AND_TX)
|
|
set_tpdu_state(ch, TPDU_S_WAIT_TX);
|
|
else if (td->flags & CEMU_DATA_F_PB_AND_RX)
|
|
set_tpdu_state(ch, TPDU_S_WAIT_RX);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
/* take the next pending byte out of the rctx */
|
|
*byte = td->data[rctx->idx++];
|
|
|
|
/* check if the buffer has now been fully transmitted */
|
|
if ((rctx->idx >= td->hdr.data_len) ||
|
|
(rctx->idx + sizeof(*td) - sizeof(td->hdr) >= rctx->tot_len)) {
|
|
req_ctx_set_state(rctx, RCTX_S_FREE);
|
|
ch->uart_tx_ctx = NULL;
|
|
/* FIXME: call into USB code to chec if we need to
|
|
* submit a free buffer to accept furthe data on bulk
|
|
* out endpoint */
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**********************************************************************
|
|
* Public API
|
|
**********************************************************************/
|
|
|
|
/* process a single byte received from the reader */
|
|
void card_emu_process_rx_byte(struct card_handle *ch, uint8_t byte)
|
|
{
|
|
int new_state = -1;
|
|
|
|
ch->stats.rx_bytes++;
|
|
|
|
switch (ch->state) {
|
|
case ISO_S_WAIT_POWER:
|
|
case ISO_S_WAIT_CLK:
|
|
case ISO_S_WAIT_RST:
|
|
case ISO_S_WAIT_ATR:
|
|
/* we shouldn't receive any data from the reader yet! */
|
|
break;
|
|
case ISO_S_WAIT_TPDU:
|
|
if (byte == 0xff) {
|
|
new_state = process_byte_pts(ch, byte);
|
|
ch->stats.pps++;
|
|
goto out_silent;
|
|
}
|
|
/* fall-through */
|
|
case ISO_S_IN_TPDU:
|
|
new_state = process_byte_tpdu(ch, byte);
|
|
break;
|
|
case ISO_S_IN_PTS:
|
|
new_state = process_byte_pts(ch, byte);
|
|
goto out_silent;
|
|
}
|
|
|
|
out_silent:
|
|
if (new_state != -1)
|
|
card_set_state(ch, new_state);
|
|
}
|
|
|
|
/* return a single byte to be transmitted to the reader */
|
|
int card_emu_get_tx_byte(struct card_handle *ch, uint8_t *byte)
|
|
{
|
|
int rc = 0;
|
|
|
|
switch (ch->state) {
|
|
case ISO_S_IN_ATR:
|
|
if (ch->atr.idx < ch->atr.len) {
|
|
*byte = ch->atr.atr[ch->atr.idx++];
|
|
rc = 1;
|
|
/* detect end of ATR */
|
|
if (ch->atr.idx >= ch->atr.len)
|
|
card_set_state(ch, ISO_S_WAIT_TPDU);
|
|
}
|
|
break;
|
|
case ISO_S_IN_PTS:
|
|
rc = get_byte_pps(ch, byte);
|
|
break;
|
|
case ISO_S_IN_TPDU:
|
|
rc = get_byte_tpdu(ch, byte);
|
|
break;
|
|
}
|
|
|
|
if (rc)
|
|
ch->stats.tx_bytes++;
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* hardware driver informs us that a card I/O signal has changed */
|
|
void card_emu_io_statechg(struct card_handle *ch, enum card_io io, int active)
|
|
{
|
|
switch (io) {
|
|
case CARD_IO_VCC:
|
|
if (active == 0)
|
|
card_set_state(ch, ISO_S_WAIT_POWER);
|
|
else if (active == 1 && ch->vcc_active == 0)
|
|
card_set_state(ch, ISO_S_WAIT_CLK);
|
|
ch->vcc_active = active;
|
|
break;
|
|
case CARD_IO_CLK:
|
|
if (active == 1 && ch->state == ISO_S_WAIT_CLK)
|
|
card_set_state(ch, ISO_S_WAIT_RST);
|
|
ch->clocked = active;
|
|
break;
|
|
case CARD_IO_RST:
|
|
if (active == 0 && ch->in_reset &&
|
|
ch->vcc_active && ch->clocked) {
|
|
/* FIXME: wait 400 clocks */
|
|
//card_set_state(ch, ISO_S_WAIT_ATR);
|
|
card_set_state(ch, ISO_S_IN_ATR);
|
|
}
|
|
ch->in_reset = active;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* User sets a new ATR to be returned during next card reset */
|
|
int card_emu_set_atr(struct card_handle *ch, const uint8_t *atr, uint8_t len)
|
|
{
|
|
if (len > sizeof(ch->atr.atr))
|
|
return -1;
|
|
|
|
memcpy(ch->atr.atr, atr, len);
|
|
ch->atr.len = len;
|
|
ch->atr.idx = 0;
|
|
|
|
/* FIXME: race condition with trasmitting ATR to reader? */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* hardware driver informs us that one (more) ETU has expired */
|
|
void tc_etu_wtime_half_expired(void *handle)
|
|
{
|
|
struct card_handle *ch = handle;
|
|
/* transmit NULL procedure byte well before waiting time expires */
|
|
card_emu_uart_tx(ch->uart_chan, ISO7816_3_PB_NULL);
|
|
}
|
|
|
|
/* hardware driver informs us that one (more) ETU has expired */
|
|
void tc_etu_wtime_expired(void *handle)
|
|
{
|
|
}
|
|
|
|
/* shortest ATR found in smartcard_list.txt */
|
|
static const uint8_t default_atr[] = { 0x3B, 0x02, 0x14, 0x50 };
|
|
|
|
static struct card_handle card_handles[NUM_SLOTS];
|
|
|
|
struct card_handle *card_emu_init(uint8_t slot_num, uint8_t tc_chan, uint8_t uart_chan)
|
|
{
|
|
struct card_handle *ch;
|
|
|
|
if (slot_num >= ARRAY_SIZE(card_handles))
|
|
return NULL;
|
|
|
|
ch = &card_handles[slot_num];
|
|
|
|
memset(ch, 0, sizeof(*ch));
|
|
|
|
/* initialize the card_handle with reasonabe defaults */
|
|
ch->state = ISO_S_WAIT_POWER;
|
|
ch->vcc_active = 0;
|
|
ch->in_reset = 1;
|
|
ch->clocked = 0;
|
|
|
|
ch->fi = 0;
|
|
ch->di = 1;
|
|
ch->wi = ISO7816_3_DEFAULT_WI;
|
|
|
|
ch->tc_chan = tc_chan;
|
|
ch->uart_chan = uart_chan;
|
|
ch->waiting_time = ISO7816_3_INIT_WTIME;
|
|
|
|
ch->atr.idx = 0;
|
|
ch->atr.len = sizeof(default_atr);
|
|
memcpy(ch->atr.atr, default_atr, ch->atr.len);
|
|
|
|
ch->pts.state = PTS_S_WAIT_REQ_PTSS;
|
|
ch->tpdu.state = TPDU_S_WAIT_CLA;
|
|
|
|
tc_etu_init(ch->tc_chan, ch);
|
|
|
|
return ch;
|
|
}
|