diff options
author | Alon Levy <alevy@redhat.com> | 2012-08-26 18:47:42 +0200 |
---|---|---|
committer | Alon Levy <alevy@redhat.com> | 2012-08-26 18:48:28 +0200 |
commit | 8299050506bc4fc5aaa3be6d4eecab6e14ae0061 (patch) | |
tree | 0c7d3a8cef09abfc240a8bff0f4d08463ea159fe | |
parent | 768aa6172db18fd6493abad77f743b7fc40108ed (diff) |
ccid-card-passthru: handle partial writes to chardevqxl/pull
Signed-off-by: Alon Levy <alevy@redhat.com>
-rw-r--r-- | hw/ccid-card-passthru.c | 152 |
1 files changed, 129 insertions, 23 deletions
diff --git a/hw/ccid-card-passthru.c b/hw/ccid-card-passthru.c index bd6c77777..906afe931 100644 --- a/hw/ccid-card-passthru.c +++ b/hw/ccid-card-passthru.c @@ -42,23 +42,74 @@ uint8_t DEFAULT_ATR[] = { #define PASSTHRU_DEV_NAME "ccid-card-passthru" #define VSCARD_IN_SIZE 65536 +/* 10 Hz polling of chardev out */ +#define VSCARD_OUT_TIMER_DELTA_MS 100 + /* maximum size of ATR - from 7816-3 */ #define MAX_ATR_SIZE 40 +typedef struct Buffer { + uint32_t pos; + uint32_t size; + uint32_t max_size; + uint32_t resize; + uint8_t *data; +} Buffer; + typedef struct PassthruState PassthruState; struct PassthruState { CCIDCardState base; CharDriverState *cs; - uint8_t vscard_in_data[VSCARD_IN_SIZE]; - uint32_t vscard_in_pos; + Buffer vscard_in; uint32_t vscard_in_hdr; uint8_t atr[MAX_ATR_SIZE]; uint8_t atr_length; uint8_t debug; + uint32_t outbuffer_max; + Buffer vscard_out; + QEMUTimer *vscard_out_timer; }; /* + * return 0 on success + * return 1 if not enough place for data (none is copied) + */ +static int buffer_append(PassthruState *card, Buffer *buffer, + const uint8_t *data, uint32_t length) +{ + if (buffer->pos + length > buffer->size) { + if (buffer->resize) { + if (buffer->max_size > buffer->pos + length) { + return 1; + } + DPRINTF(card, D_VERBOSE, "%s: realloc to %d\n", __func__, + buffer->pos + length); + buffer->size = buffer->pos + length; + buffer->data = qemu_oom_check(realloc(buffer->data, buffer->size)); + } else { + return 1; + } + } + assert(buffer->pos < buffer->size); + memcpy(buffer->data + buffer->pos, data, length); + buffer->pos += length; + return 0; +} + +static void ccid_card_cs_write(PassthruState *s, + const uint8_t *buffer, uint32_t length) +{ + int written = qemu_chr_fe_write(s->cs, buffer, length); + int remaining = length - written; + + if (remaining == 0) { + return; + } + buffer_append(s, &s->vscard_out, buffer, length); +} + +/* * VSCard protocol over chardev * This code should not depend on the card type. */ @@ -72,8 +123,8 @@ static void ccid_card_vscard_send_msg(PassthruState *s, scr_msg_header.type = htonl(type); scr_msg_header.reader_id = htonl(reader_id); scr_msg_header.length = htonl(length); - qemu_chr_fe_write(s->cs, (uint8_t *)&scr_msg_header, sizeof(VSCMsgHeader)); - qemu_chr_fe_write(s->cs, payload, length); + ccid_card_cs_write(s, (uint8_t *)&scr_msg_header, sizeof(VSCMsgHeader)); + ccid_card_cs_write(s, payload, length); } static void ccid_card_vscard_send_apdu(PassthruState *s, @@ -108,8 +159,8 @@ static int ccid_card_vscard_can_read(void *opaque) { PassthruState *card = opaque; - return VSCARD_IN_SIZE >= card->vscard_in_pos ? - VSCARD_IN_SIZE - card->vscard_in_pos : 0; + return card->vscard_in.size >= card->vscard_in.pos ? + card->vscard_in.size - card->vscard_in.pos : 0; } static void ccid_card_vscard_handle_init( @@ -200,7 +251,7 @@ static void ccid_card_vscard_handle_message(PassthruState *card, static void ccid_card_vscard_drop_connection(PassthruState *card) { qemu_chr_delete(card->cs); - card->vscard_in_pos = card->vscard_in_hdr = 0; + card->vscard_in.pos = card->vscard_in_hdr = 0; } static void ccid_card_vscard_read(void *opaque, const uint8_t *buf, int size) @@ -208,31 +259,28 @@ static void ccid_card_vscard_read(void *opaque, const uint8_t *buf, int size) PassthruState *card = opaque; VSCMsgHeader *hdr; - if (card->vscard_in_pos + size > VSCARD_IN_SIZE) { + if (buffer_append(card, &card->vscard_in, buf, size)) { error_report( - "no room for data: pos %d + size %d > %d. dropping connection.", - card->vscard_in_pos, size, VSCARD_IN_SIZE); + "no room for data: pos %d + size %d > %d. dropping connection.", + card->vscard_in.pos, size, card->vscard_in.size); ccid_card_vscard_drop_connection(card); return; } - assert(card->vscard_in_pos < VSCARD_IN_SIZE); assert(card->vscard_in_hdr < VSCARD_IN_SIZE); - memcpy(card->vscard_in_data + card->vscard_in_pos, buf, size); - card->vscard_in_pos += size; - hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr); + hdr = (VSCMsgHeader *)(card->vscard_in.data + card->vscard_in_hdr); - while ((card->vscard_in_pos - card->vscard_in_hdr >= sizeof(VSCMsgHeader)) - &&(card->vscard_in_pos - card->vscard_in_hdr >= + while ((card->vscard_in.pos - card->vscard_in_hdr >= sizeof(VSCMsgHeader)) + && (card->vscard_in.pos - card->vscard_in_hdr >= sizeof(VSCMsgHeader) + ntohl(hdr->length))) { hdr->reader_id = ntohl(hdr->reader_id); hdr->length = ntohl(hdr->length); hdr->type = ntohl(hdr->type); ccid_card_vscard_handle_message(card, hdr); card->vscard_in_hdr += hdr->length + sizeof(VSCMsgHeader); - hdr = (VSCMsgHeader *)(card->vscard_in_data + card->vscard_in_hdr); + hdr = (VSCMsgHeader *)(card->vscard_in.data + card->vscard_in_hdr); } - if (card->vscard_in_hdr == card->vscard_in_pos) { - card->vscard_in_pos = card->vscard_in_hdr = 0; + if (card->vscard_in_hdr == card->vscard_in.pos) { + card->vscard_in.pos = card->vscard_in_hdr = 0; } } @@ -242,7 +290,8 @@ static void ccid_card_vscard_event(void *opaque, int event) switch (event) { case CHR_EVENT_BREAK: - card->vscard_in_pos = card->vscard_in_hdr = 0; + DPRINTF(card, D_INFO, "%s: CHR_EVENT_BREAK\n", __func__); + card->vscard_in.pos = card->vscard_in_hdr = 0; break; case CHR_EVENT_FOCUS: break; @@ -274,11 +323,50 @@ static const uint8_t *passthru_get_atr(CCIDCardState *base, uint32_t *len) return card->atr; } +static void buffer_init(Buffer *buf, uint32_t max_size, int resizable, + int alloc_up_front) +{ + buf->max_size = max_size; + buf->resize = resizable; + buf->pos = 0; + if (alloc_up_front) { + buf->size = max_size; + } else { + buf->size = 1024; + } + buf->data = qemu_oom_check(malloc(buf->size)); +} + +static void buffer_write_out(Buffer *buf, CharDriverState *cs) +{ + int written; + + written = qemu_chr_fe_write(cs, + buf->data + buf->pos, + buf->size - buf->pos); + buf->pos += written; + if (buf->pos == buf->size) { + buf->size = buf->pos = 0; + } +} + +static void ccid_card_passthru_vscard_out_timeout(void *opaque) +{ + PassthruState *card = opaque; + + buffer_write_out(&card->vscard_out, card->cs); + if (card->vscard_out.size > 0) { + qemu_mod_timer(card->vscard_out_timer, + qemu_get_clock_ms(vm_clock) + + VSCARD_OUT_TIMER_DELTA_MS); + } +} + static int passthru_initfn(CCIDCardState *base) { PassthruState *card = DO_UPCAST(PassthruState, base, base); - card->vscard_in_pos = 0; + buffer_init(&card->vscard_in, VSCARD_IN_SIZE, 0, 1); card->vscard_in_hdr = 0; if (card->cs) { DPRINTF(card, D_INFO, "initing chardev\n"); @@ -294,6 +382,9 @@ static int passthru_initfn(CCIDCardState *base) assert(sizeof(DEFAULT_ATR) <= MAX_ATR_SIZE); memcpy(card->atr, DEFAULT_ATR, sizeof(DEFAULT_ATR)); card->atr_length = sizeof(DEFAULT_ATR); + buffer_init(&card->vscard_out, card->outbuffer_max, 0, 0); + card->vscard_out_timer = qemu_new_timer_ms(vm_clock, + ccid_card_passthru_vscard_out_timeout, card); return 0; } @@ -302,16 +393,30 @@ static int passthru_exitfn(CCIDCardState *base) return 0; } +static VMStateDescription buffer_vmstate = { + .name = PASSTHRU_DEV_NAME "/buffer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(pos, Buffer), + VMSTATE_UINT32(size, Buffer), + VMSTATE_UINT32(max_size, Buffer), + VMSTATE_UINT32(resize, Buffer), + VMSTATE_VBUFFER_UINT32(data, Buffer, 1, NULL, 0, size), + VMSTATE_END_OF_LIST() + } +}; + static VMStateDescription passthru_vmstate = { .name = PASSTHRU_DEV_NAME, .version_id = 1, .minimum_version_id = 1, .fields = (VMStateField[]) { - VMSTATE_BUFFER(vscard_in_data, PassthruState), - VMSTATE_UINT32(vscard_in_pos, PassthruState), + VMSTATE_STRUCT(vscard_in, PassthruState, 1, buffer_vmstate, Buffer), VMSTATE_UINT32(vscard_in_hdr, PassthruState), VMSTATE_BUFFER(atr, PassthruState), VMSTATE_UINT8(atr_length, PassthruState), + VMSTATE_STRUCT(vscard_out, PassthruState, 1, buffer_vmstate, Buffer), VMSTATE_END_OF_LIST() } }; @@ -319,6 +424,7 @@ static VMStateDescription passthru_vmstate = { static Property passthru_card_properties[] = { DEFINE_PROP_CHR("chardev", PassthruState, cs), DEFINE_PROP_UINT8("debug", PassthruState, debug, 0), + DEFINE_PROP_UINT32("outbuffer", PassthruState, outbuffer_max, 1024*1024), DEFINE_PROP_END_OF_LIST(), }; |