gnuk/src/usb-icc.c

508 lines
12 KiB
C
Raw Normal View History

2010-08-18 03:57:45 +00:00
/*
2010-08-19 08:09:59 +00:00
* usb-icc.c -- USB CCID/ICCD protocol handling
2010-08-18 03:57:45 +00:00
*
* Copyright (C) 2010 Free Software Initiative of Japan
* Author: NIIBE Yutaka <gniibe@fsij.org>
*
* This file is a part of Gnuk, a GnuPG USB Token implementation.
*
* Gnuk 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 3 of the License, or
* (at your option) any later version.
*
* Gnuk 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, see <http://www.gnu.org/licenses/>.
*
*/
2010-09-04 04:48:26 +00:00
#include "config.h"
2010-08-18 05:21:58 +00:00
#include "ch.h"
#include "hal.h"
2010-08-19 08:09:59 +00:00
#include "gnuk.h"
2010-08-18 05:21:58 +00:00
#include "usb_lib.h"
#include "usb_desc.h"
#include "usb_mem.h"
#include "hw_config.h"
#include "usb_istr.h"
2010-08-23 05:40:33 +00:00
#define ICC_POWER_ON 0x62
#define ICC_POWER_OFF 0x63
#define ICC_SLOT_STATUS 0x65
#define ICC_XFR_BLOCK 0x6F
#define ICC_DATA_BLOCK_RET 0x80
#define ICC_SLOT_STATUS_RET 0x81
#define ICC_MSG_SEQ_OFFSET 6
#define ICC_MSG_STATUS_OFFSET 7
#define ICC_MSG_ERROR_OFFSET 8
#define ICC_MSG_CHAIN_OFFSET 9
#define ICC_MSG_DATA_OFFSET 10
#define ICC_MSG_HEADER_SIZE ICC_MSG_DATA_OFFSET
#define ICC_MAX_MSG_DATA_SIZE (USB_BUF_SIZE - ICC_MSG_HEADER_SIZE)
#define ICC_STATUS_RUN 0x00
#define ICC_STATUS_PRESENT 0x01
#define ICC_STATUS_NOTPRESENT 0x02
#define ICC_CMD_STATUS_OK 0x00
#define ICC_CMD_STATUS_ERROR 0x40
#define ICC_CMD_STATUS_TIMEEXT 0x80
#define ICC_ERROR_XFR_OVERRUN 0xFC
struct icc_header {
uint8_t msg_type;
int32_t data_len;
uint8_t slot;
uint8_t seq;
uint8_t rsvd;
uint16_t param;
} __attribute__((packed));
static struct icc_header *icc_header;
static uint8_t icc_seq;
static uint8_t *icc_data;
static int icc_data_size;
static uint8_t icc_rcv_data[USB_BUF_SIZE];
static uint8_t icc_tx_data[USB_BUF_SIZE];
static int icc_tx_size;
static int
icc_tx_ready (void)
{
if (icc_tx_size == -1)
return 1;
else
return 0;
}
2010-08-18 05:21:58 +00:00
2010-08-19 08:09:59 +00:00
Thread *icc_thread;
#define EV_RX_DATA_READY (eventmask_t)1 /* USB Rx data available */
/*
* Tx done
*/
2010-08-18 05:21:58 +00:00
void
2010-09-04 09:44:01 +00:00
EP1_IN_Callback (void)
2010-08-18 05:21:58 +00:00
{
2010-08-23 05:40:33 +00:00
if (icc_tx_size == USB_BUF_SIZE)
{
icc_tx_size = 0;
2010-09-04 09:44:01 +00:00
USB_SIL_Write (EP1_IN, icc_tx_data, icc_tx_size);
SetEPTxValid (ENDP1);
2010-08-23 05:40:33 +00:00
}
else
icc_tx_size = -1;
2010-08-18 05:21:58 +00:00
}
2010-08-19 08:09:59 +00:00
/*
2010-08-23 05:40:33 +00:00
* Upon arrival of Bulk-OUT packet,
* we setup the variables (icc_header, icc_data, and icc_data_size, icc_seq)
* and notify icc_thread
* (modify header's byte order to host order if needed)
2010-08-19 08:09:59 +00:00
*/
2010-08-18 05:21:58 +00:00
void
2010-09-04 09:44:01 +00:00
EP2_OUT_Callback (void)
2010-08-18 05:21:58 +00:00
{
2010-08-23 05:40:33 +00:00
int len;
2010-08-18 05:21:58 +00:00
2010-08-23 05:40:33 +00:00
#ifdef HOST_BIG_ENDIAN
#error "Here, you need to write code to correct byte order."
#else
/* nothing to do */
2010-08-19 08:09:59 +00:00
#endif
2010-08-18 03:57:45 +00:00
2010-09-04 09:44:01 +00:00
len = USB_SIL_Read (EP2_OUT, icc_rcv_data);
2010-08-23 05:40:33 +00:00
icc_header = (struct icc_header *)icc_rcv_data;
icc_data = &icc_rcv_data[ICC_MSG_DATA_OFFSET];
icc_data_size = len - ICC_MSG_HEADER_SIZE;
icc_seq = icc_header->seq;
2010-08-26 10:50:06 +00:00
if (icc_data_size < 0)
2010-08-23 05:40:33 +00:00
/* just ignore short invalid packet, enable Rx again */
2010-09-04 09:44:01 +00:00
SetEPRxValid (ENDP2);
2010-08-23 05:40:33 +00:00
else
/* Notify icc_thread */
chEvtSignalI (icc_thread, EV_RX_DATA_READY);
2010-08-26 10:50:06 +00:00
/*
* what if (icc_data_size != icc_header->data_len)???
*/
2010-08-23 05:40:33 +00:00
}
2010-08-19 08:09:59 +00:00
enum icc_state
{
ICC_STATE_START, /* Initial */
2010-08-23 05:40:33 +00:00
ICC_STATE_WAIT, /* Waiting APDU */
2010-08-19 08:09:59 +00:00
/* Busy1, Busy2, Busy3, Busy5 */
ICC_STATE_EXECUTE, /* Busy4 */
2010-08-23 05:40:33 +00:00
ICC_STATE_RECEIVE, /* APDU Received Partially */
ICC_STATE_SEND, /* APDU Sent Partially */
2010-08-19 08:09:59 +00:00
};
2010-08-18 03:57:45 +00:00
2010-08-19 08:09:59 +00:00
static enum icc_state icc_state;
2010-08-18 05:21:58 +00:00
2010-08-18 08:02:04 +00:00
/* Direct conversion, T=1, "FSIJ" */
2010-08-23 05:40:33 +00:00
static const char ATR[] = { '\x3B', '\x84', '\x01', 'F', 'S', 'I', 'J',
('\x84'^'F'^'S'^'I'^'J') };
2010-08-18 08:02:04 +00:00
2010-08-19 08:09:59 +00:00
/* Send back ATR (Answer To Reset) */
enum icc_state
2010-08-23 05:40:33 +00:00
icc_power_on (void)
2010-08-18 08:02:04 +00:00
{
2010-08-23 05:40:33 +00:00
int size_atr;
2010-08-18 08:02:04 +00:00
size_atr = sizeof (ATR);
2010-08-23 05:40:33 +00:00
icc_tx_data[0] = ICC_DATA_BLOCK_RET;
icc_tx_data[1] = size_atr;
icc_tx_data[2] = 0x00;
icc_tx_data[3] = 0x00;
icc_tx_data[4] = 0x00;
icc_tx_data[5] = 0x00; /* Slot */
icc_tx_data[ICC_MSG_SEQ_OFFSET] = icc_seq;
icc_tx_data[ICC_MSG_STATUS_OFFSET] = 0x00;
icc_tx_data[ICC_MSG_ERROR_OFFSET] = 0x00;
icc_tx_data[ICC_MSG_CHAIN_OFFSET] = 0x00;
memcpy (&icc_tx_data[ICC_MSG_DATA_OFFSET], ATR, size_atr);
if (!icc_tx_ready ())
{
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR0B\r\n");
2010-08-23 05:40:33 +00:00
}
else
{
icc_tx_size = ICC_MSG_DATA_OFFSET + size_atr;
2010-09-04 09:44:01 +00:00
USB_SIL_Write (EP1_IN, icc_tx_data, icc_tx_size);
SetEPTxValid (ENDP1);
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ON\r\n");
2010-08-23 05:40:33 +00:00
}
2010-08-19 08:09:59 +00:00
return ICC_STATE_WAIT;
2010-08-18 08:02:04 +00:00
}
2010-08-19 08:09:59 +00:00
static void
2010-08-23 05:40:33 +00:00
icc_send_status (void)
2010-08-18 08:02:04 +00:00
{
2010-08-23 05:40:33 +00:00
icc_tx_data[0] = ICC_SLOT_STATUS_RET;
icc_tx_data[1] = 0x00;
icc_tx_data[2] = 0x00;
icc_tx_data[3] = 0x00;
icc_tx_data[4] = 0x00;
icc_tx_data[5] = 0x00; /* Slot */
icc_tx_data[ICC_MSG_SEQ_OFFSET] = icc_seq;
2010-08-19 08:09:59 +00:00
if (icc_state == ICC_STATE_START)
2010-08-23 05:40:33 +00:00
/* 1: ICC present but not activated 2: No ICC present */
icc_tx_data[ICC_MSG_STATUS_OFFSET] = 1;
2010-08-19 08:09:59 +00:00
else
2010-08-23 05:40:33 +00:00
/* An ICC is present and active */
icc_tx_data[ICC_MSG_STATUS_OFFSET] = 0;
icc_tx_data[ICC_MSG_ERROR_OFFSET] = 0x00;
icc_tx_data[ICC_MSG_CHAIN_OFFSET] = 0x00;
2010-08-18 08:02:04 +00:00
2010-08-23 05:40:33 +00:00
if (!icc_tx_ready ())
{
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR0C\r\n");
2010-08-23 05:40:33 +00:00
}
else
{
icc_tx_size = ICC_MSG_DATA_OFFSET;
2010-09-04 09:44:01 +00:00
USB_SIL_Write (EP1_IN, icc_tx_data, icc_tx_size);
SetEPTxValid (ENDP1);
2010-08-23 05:40:33 +00:00
}
2010-09-05 16:55:29 +00:00
#ifdef DEBUG_MORE
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("St\r\n");
2010-09-05 16:55:29 +00:00
#endif
2010-08-19 08:09:59 +00:00
}
2010-08-18 08:02:04 +00:00
2010-08-19 08:09:59 +00:00
enum icc_state
2010-08-23 05:40:33 +00:00
icc_power_off (void)
2010-08-19 08:09:59 +00:00
{
2010-08-23 05:40:33 +00:00
icc_send_status ();
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("OFF\r\n");
2010-08-19 08:09:59 +00:00
return ICC_STATE_START;
2010-08-18 08:02:04 +00:00
}
2010-08-23 05:40:33 +00:00
uint8_t cmd_APDU[MAX_CMD_APDU_SIZE];
uint8_t res_APDU[MAX_RES_APDU_SIZE];
int cmd_APDU_size;
int res_APDU_size;
static uint8_t *p_cmd;
static uint8_t *p_res;
static void
icc_send_data_block (uint8_t status, uint8_t error, uint8_t chain,
uint8_t *data, int len)
{
icc_tx_data[0] = ICC_DATA_BLOCK_RET;
icc_tx_data[1] = len & 0xFF;
icc_tx_data[2] = (len >> 8)& 0xFF;
icc_tx_data[3] = (len >> 16)& 0xFF;
icc_tx_data[4] = (len >> 24)& 0xFF;
icc_tx_data[5] = 0x00; /* Slot */
icc_tx_data[ICC_MSG_SEQ_OFFSET] = icc_seq;
icc_tx_data[ICC_MSG_STATUS_OFFSET] = status;
icc_tx_data[ICC_MSG_ERROR_OFFSET] = error;
icc_tx_data[ICC_MSG_CHAIN_OFFSET] = chain;
if (len)
memcpy (&icc_tx_data[ICC_MSG_DATA_OFFSET], data, len);
if (!icc_tx_ready ())
{ /* not ready to send */
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR09\r\n");
2010-08-23 05:40:33 +00:00
}
else
{
icc_tx_size = ICC_MSG_DATA_OFFSET + len;
2010-09-04 09:44:01 +00:00
USB_SIL_Write (EP1_IN, icc_tx_data, icc_tx_size);
SetEPTxValid (ENDP1);
2010-09-05 16:55:29 +00:00
#ifdef DEBUG_MORE
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("DATA\r\n");
2010-09-05 16:55:29 +00:00
#endif
2010-08-23 05:40:33 +00:00
}
}
2010-08-19 08:09:59 +00:00
static enum icc_state
icc_handle_data (void)
2010-08-18 05:21:58 +00:00
{
2010-08-19 08:09:59 +00:00
enum icc_state next_state = icc_state;
2010-08-18 08:02:04 +00:00
2010-08-19 08:09:59 +00:00
switch (icc_state)
{
case ICC_STATE_START:
2010-08-23 05:40:33 +00:00
if (icc_header->msg_type == ICC_POWER_ON)
next_state = icc_power_on ();
else if (icc_header->msg_type == ICC_POWER_OFF)
next_state = icc_power_off ();
else if (icc_header->msg_type == ICC_SLOT_STATUS)
icc_send_status ();
2010-08-19 08:09:59 +00:00
else
{ /* XXX: error */
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR01\r\n");
2010-08-19 08:09:59 +00:00
}
break;
case ICC_STATE_WAIT:
2010-08-23 05:40:33 +00:00
if (icc_header->msg_type == ICC_POWER_ON)
/* Not in the spec., but GPG 2 */
next_state = icc_power_on ();
else if (icc_header->msg_type == ICC_POWER_OFF)
next_state = icc_power_off ();
else if (icc_header->msg_type == ICC_SLOT_STATUS)
icc_send_status ();
else if (icc_header->msg_type == ICC_XFR_BLOCK)
2010-08-18 08:02:04 +00:00
{
2010-08-23 05:40:33 +00:00
if (icc_header->param == 0)
{ /* Give this message to GPG thread */
p_cmd = cmd_APDU;
memcpy (p_cmd, icc_data, icc_data_size);
cmd_APDU_size = icc_data_size;
2010-08-19 08:09:59 +00:00
chEvtSignal (gpg_thread, (eventmask_t)1);
2010-08-23 05:40:33 +00:00
next_state = ICC_STATE_EXECUTE;
2010-09-08 05:24:12 +00:00
chEvtSignal (blinker_thread, EV_LED_ON);
2010-08-19 08:09:59 +00:00
}
2010-08-23 05:40:33 +00:00
else if (icc_header->param == 1)
2010-08-19 08:09:59 +00:00
{
2010-08-23 05:40:33 +00:00
p_cmd = cmd_APDU;
memcpy (p_cmd, icc_data, icc_data_size);
p_cmd += icc_data_size;
cmd_APDU_size = icc_data_size;
icc_send_data_block (0, 0, 0x10, NULL, 0);
2010-08-19 08:09:59 +00:00
next_state = ICC_STATE_RECEIVE;
}
else
2010-08-23 05:40:33 +00:00
{ /* XXX: error */;
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR02\r\n");
2010-08-19 08:09:59 +00:00
}
2010-08-18 08:02:04 +00:00
}
2010-08-19 08:09:59 +00:00
else
{ /* XXX: error */
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR03\r\n");
DEBUG_BYTE (icc_header->msg_type);
2010-08-23 05:40:33 +00:00
next_state = ICC_STATE_START;
2010-08-19 08:09:59 +00:00
}
break;
case ICC_STATE_EXECUTE:
2010-08-23 05:40:33 +00:00
if (icc_header->msg_type == ICC_POWER_OFF)
2010-08-19 08:09:59 +00:00
{
/* XXX: Kill GPG thread */
2010-08-23 05:40:33 +00:00
next_state = icc_power_off ();
2010-08-19 08:09:59 +00:00
}
2010-08-23 05:40:33 +00:00
else if (icc_header->msg_type == ICC_SLOT_STATUS)
icc_send_status ();
2010-08-19 08:09:59 +00:00
else
{ /* XXX: error */
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR04\r\n");
DEBUG_BYTE (icc_header->msg_type);
2010-08-23 05:40:33 +00:00
next_state = ICC_STATE_START;
2010-08-19 08:09:59 +00:00
}
break;
case ICC_STATE_RECEIVE:
2010-08-23 05:40:33 +00:00
if (icc_header->msg_type == ICC_POWER_OFF)
next_state = icc_power_off ();
else if (icc_header->msg_type == ICC_SLOT_STATUS)
icc_send_status ();
else if (icc_header->msg_type == ICC_XFR_BLOCK)
2010-08-18 08:02:04 +00:00
{
2010-08-23 05:40:33 +00:00
if (cmd_APDU_size + icc_data_size <= MAX_CMD_APDU_SIZE)
2010-08-19 08:09:59 +00:00
{
2010-08-23 05:40:33 +00:00
memcpy (p_cmd, icc_data, icc_data_size);
p_cmd += icc_data_size;
cmd_APDU_size += icc_data_size;
if (icc_header->param == 2) /* Got final block */
{ /* Give this message to GPG thread */
next_state = ICC_STATE_EXECUTE;
2010-09-08 05:24:12 +00:00
chEvtSignal (blinker_thread, EV_LED_ON);
2010-08-23 05:40:33 +00:00
cmd_APDU_size = p_cmd - cmd_APDU;
chEvtSignal (gpg_thread, (eventmask_t)1);
}
else if (icc_header->param == 3)
icc_send_data_block (0, 0, 0x10, NULL, 0);
else
{ /* XXX: error */
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR08\r\n");
2010-08-23 05:40:33 +00:00
}
}
else /* Overrun */
{
icc_send_data_block (ICC_CMD_STATUS_ERROR, ICC_ERROR_XFR_OVERRUN,
0, NULL, 0);
next_state = ICC_STATE_WAIT;
2010-08-19 08:09:59 +00:00
}
2010-08-18 08:02:04 +00:00
}
else
2010-08-19 08:09:59 +00:00
{ /* XXX: error */
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR05\r\n");
DEBUG_BYTE (icc_header->msg_type);
2010-08-23 05:40:33 +00:00
next_state = ICC_STATE_START;
2010-08-19 08:09:59 +00:00
}
break;
case ICC_STATE_SEND:
2010-08-23 05:40:33 +00:00
if (icc_header->msg_type == ICC_POWER_OFF)
next_state = icc_power_off ();
else if (icc_header->msg_type == ICC_SLOT_STATUS)
icc_send_status ();
else if (icc_header->msg_type == ICC_XFR_BLOCK)
2010-08-18 08:02:04 +00:00
{
2010-08-23 05:40:33 +00:00
if (icc_header->param == 0x10)
{
if (res_APDU_size <= ICC_MAX_MSG_DATA_SIZE)
{
icc_send_data_block (0, 0, 0x02, p_res, res_APDU_size);
next_state = ICC_STATE_WAIT;
}
else
{
icc_send_data_block (0, 0, 0x03,
p_res, ICC_MAX_MSG_DATA_SIZE);
p_res += ICC_MAX_MSG_DATA_SIZE;
res_APDU_size -= ICC_MAX_MSG_DATA_SIZE;
}
}
else
{ /* XXX: error */
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR0A\r\n");
DEBUG_BYTE (icc_header->param >> 8);
DEBUG_BYTE (icc_header->param & 0xff);
2010-08-23 05:40:33 +00:00
next_state = ICC_STATE_WAIT;
}
2010-08-19 08:09:59 +00:00
}
else
{ /* XXX: error */
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR06\r\n");
DEBUG_BYTE (icc_header->msg_type);
2010-08-23 05:40:33 +00:00
next_state = ICC_STATE_START;
2010-08-18 08:02:04 +00:00
}
2010-08-19 08:09:59 +00:00
break;
}
2010-08-18 08:02:04 +00:00
2010-09-04 09:44:01 +00:00
SetEPRxValid (ENDP2);
2010-08-19 08:09:59 +00:00
return next_state;
}
static enum icc_state
icc_handle_timeout (void)
{
enum icc_state next_state = icc_state;
2010-08-23 05:40:33 +00:00
switch (icc_state)
2010-08-19 08:09:59 +00:00
{
2010-08-23 05:40:33 +00:00
case ICC_STATE_EXECUTE:
icc_send_data_block (ICC_CMD_STATUS_TIMEEXT, 0, 0, NULL, 0);
break;
case ICC_STATE_RECEIVE:
case ICC_STATE_SEND:
case ICC_STATE_START:
case ICC_STATE_WAIT:
break;
2010-08-19 08:09:59 +00:00
}
return next_state;
}
2010-09-08 05:24:12 +00:00
#define USB_ICC_TIMEOUT MS2ST(1950)
2010-08-19 08:09:59 +00:00
msg_t
USBthread (void *arg)
{
(void)arg;
icc_thread = chThdSelf ();
chEvtClear (ALL_EVENTS);
icc_state = ICC_STATE_START;
2010-08-23 05:40:33 +00:00
icc_tx_size = -1;
2010-08-19 08:09:59 +00:00
while (1)
{
eventmask_t m;
m = chEvtWaitOneTimeout (ALL_EVENTS, USB_ICC_TIMEOUT);
if (m == EV_RX_DATA_READY)
icc_state = icc_handle_data ();
else if (m == EV_EXEC_FINISHED)
{
if (icc_state == ICC_STATE_EXECUTE)
{
2010-09-08 05:24:12 +00:00
chEvtSignal (blinker_thread, EV_LED_OFF);
2010-08-23 05:40:33 +00:00
if (res_APDU_size <= ICC_MAX_MSG_DATA_SIZE)
2010-08-19 08:09:59 +00:00
{
2010-08-23 05:40:33 +00:00
icc_send_data_block (0, 0, 0, res_APDU, res_APDU_size);
2010-08-19 08:09:59 +00:00
icc_state = ICC_STATE_WAIT;
}
else
{
2010-08-23 05:40:33 +00:00
p_res = res_APDU;
icc_send_data_block (0, 0, 0x01,
p_res, ICC_MAX_MSG_DATA_SIZE);
p_res += ICC_MAX_MSG_DATA_SIZE;
res_APDU_size -= ICC_MAX_MSG_DATA_SIZE;
2010-08-19 08:09:59 +00:00
icc_state = ICC_STATE_SEND;
}
}
else
{ /* XXX: error */
2010-09-03 15:42:36 +00:00
DEBUG_INFO ("ERR07\r\n");
2010-08-19 08:09:59 +00:00
}
}
else /* Timeout */
icc_state = icc_handle_timeout ();
2010-08-18 05:21:58 +00:00
}
return 0;
}