|
|
1.1 ! root 1: /* ! 2: * CCID Card Device. Emulated card. ! 3: * ! 4: * Copyright (c) 2011 Red Hat. ! 5: * Written by Alon Levy. ! 6: * ! 7: * This code is licensed under the GNU LGPL, version 2 or later. ! 8: */ ! 9: ! 10: /* ! 11: * It can be used to provide access to the local hardware in a non exclusive ! 12: * way, or it can use certificates. It requires the usb-ccid bus. ! 13: * ! 14: * Usage 1: standard, mirror hardware reader+card: ! 15: * qemu .. -usb -device usb-ccid -device ccid-card-emulated ! 16: * ! 17: * Usage 2: use certificates, no hardware required ! 18: * one time: create the certificates: ! 19: * for i in 1 2 3; do ! 20: * certutil -d /etc/pki/nssdb -x -t "CT,CT,CT" -S -s "CN=user$i" -n user$i ! 21: * done ! 22: * qemu .. -usb -device usb-ccid \ ! 23: * -device ccid-card-emulated,cert1=user1,cert2=user2,cert3=user3 ! 24: * ! 25: * If you use a non default db for the certificates you can specify it using ! 26: * the db parameter. ! 27: */ ! 28: ! 29: #include <eventt.h> ! 30: #include <vevent.h> ! 31: #include <vreader.h> ! 32: #include <vcard_emul.h> ! 33: ! 34: #include "qemu-thread.h" ! 35: #include "qemu-char.h" ! 36: #include "monitor.h" ! 37: #include "hw/ccid.h" ! 38: ! 39: #define DPRINTF(card, lvl, fmt, ...) \ ! 40: do {\ ! 41: if (lvl <= card->debug) {\ ! 42: printf("ccid-card-emul: %s: " fmt , __func__, ## __VA_ARGS__);\ ! 43: } \ ! 44: } while (0) ! 45: ! 46: #define EMULATED_DEV_NAME "ccid-card-emulated" ! 47: ! 48: #define BACKEND_NSS_EMULATED_NAME "nss-emulated" ! 49: #define BACKEND_CERTIFICATES_NAME "certificates" ! 50: ! 51: enum { ! 52: BACKEND_NSS_EMULATED = 1, ! 53: BACKEND_CERTIFICATES ! 54: }; ! 55: ! 56: #define DEFAULT_BACKEND BACKEND_NSS_EMULATED ! 57: ! 58: typedef struct EmulatedState EmulatedState; ! 59: ! 60: enum { ! 61: EMUL_READER_INSERT = 0, ! 62: EMUL_READER_REMOVE, ! 63: EMUL_CARD_INSERT, ! 64: EMUL_CARD_REMOVE, ! 65: EMUL_GUEST_APDU, ! 66: EMUL_RESPONSE_APDU, ! 67: EMUL_ERROR, ! 68: }; ! 69: ! 70: static const char *emul_event_to_string(uint32_t emul_event) ! 71: { ! 72: switch (emul_event) { ! 73: case EMUL_READER_INSERT: ! 74: return "EMUL_READER_INSERT"; ! 75: case EMUL_READER_REMOVE: ! 76: return "EMUL_READER_REMOVE"; ! 77: case EMUL_CARD_INSERT: ! 78: return "EMUL_CARD_INSERT"; ! 79: case EMUL_CARD_REMOVE: ! 80: return "EMUL_CARD_REMOVE"; ! 81: case EMUL_GUEST_APDU: ! 82: return "EMUL_GUEST_APDU"; ! 83: case EMUL_RESPONSE_APDU: ! 84: return "EMUL_RESPONSE_APDU"; ! 85: case EMUL_ERROR: ! 86: return "EMUL_ERROR"; ! 87: } ! 88: return "UNKNOWN"; ! 89: } ! 90: ! 91: typedef struct EmulEvent { ! 92: QSIMPLEQ_ENTRY(EmulEvent) entry; ! 93: union { ! 94: struct { ! 95: uint32_t type; ! 96: } gen; ! 97: struct { ! 98: uint32_t type; ! 99: uint64_t code; ! 100: } error; ! 101: struct { ! 102: uint32_t type; ! 103: uint32_t len; ! 104: uint8_t data[]; ! 105: } data; ! 106: } p; ! 107: } EmulEvent; ! 108: ! 109: #define MAX_ATR_SIZE 40 ! 110: struct EmulatedState { ! 111: CCIDCardState base; ! 112: uint8_t debug; ! 113: char *backend_str; ! 114: uint32_t backend; ! 115: char *cert1; ! 116: char *cert2; ! 117: char *cert3; ! 118: char *db; ! 119: uint8_t atr[MAX_ATR_SIZE]; ! 120: uint8_t atr_length; ! 121: QSIMPLEQ_HEAD(event_list, EmulEvent) event_list; ! 122: QemuMutex event_list_mutex; ! 123: VReader *reader; ! 124: QSIMPLEQ_HEAD(guest_apdu_list, EmulEvent) guest_apdu_list; ! 125: QemuMutex vreader_mutex; /* and guest_apdu_list mutex */ ! 126: QemuMutex handle_apdu_mutex; ! 127: QemuCond handle_apdu_cond; ! 128: int pipe[2]; ! 129: int quit_apdu_thread; ! 130: QemuMutex apdu_thread_quit_mutex; ! 131: QemuCond apdu_thread_quit_cond; ! 132: }; ! 133: ! 134: static void emulated_apdu_from_guest(CCIDCardState *base, ! 135: const uint8_t *apdu, uint32_t len) ! 136: { ! 137: EmulatedState *card = DO_UPCAST(EmulatedState, base, base); ! 138: EmulEvent *event = (EmulEvent *)qemu_malloc(sizeof(EmulEvent) + len); ! 139: ! 140: assert(event); ! 141: event->p.data.type = EMUL_GUEST_APDU; ! 142: event->p.data.len = len; ! 143: memcpy(event->p.data.data, apdu, len); ! 144: qemu_mutex_lock(&card->vreader_mutex); ! 145: QSIMPLEQ_INSERT_TAIL(&card->guest_apdu_list, event, entry); ! 146: qemu_mutex_unlock(&card->vreader_mutex); ! 147: qemu_mutex_lock(&card->handle_apdu_mutex); ! 148: qemu_cond_signal(&card->handle_apdu_cond); ! 149: qemu_mutex_unlock(&card->handle_apdu_mutex); ! 150: } ! 151: ! 152: static const uint8_t *emulated_get_atr(CCIDCardState *base, uint32_t *len) ! 153: { ! 154: EmulatedState *card = DO_UPCAST(EmulatedState, base, base); ! 155: ! 156: *len = card->atr_length; ! 157: return card->atr; ! 158: } ! 159: ! 160: static void emulated_push_event(EmulatedState *card, EmulEvent *event) ! 161: { ! 162: qemu_mutex_lock(&card->event_list_mutex); ! 163: QSIMPLEQ_INSERT_TAIL(&(card->event_list), event, entry); ! 164: qemu_mutex_unlock(&card->event_list_mutex); ! 165: if (write(card->pipe[1], card, 1) != 1) { ! 166: DPRINTF(card, 1, "write to pipe failed\n"); ! 167: } ! 168: } ! 169: ! 170: static void emulated_push_type(EmulatedState *card, uint32_t type) ! 171: { ! 172: EmulEvent *event = (EmulEvent *)qemu_malloc(sizeof(EmulEvent)); ! 173: ! 174: assert(event); ! 175: event->p.gen.type = type; ! 176: emulated_push_event(card, event); ! 177: } ! 178: ! 179: static void emulated_push_error(EmulatedState *card, uint64_t code) ! 180: { ! 181: EmulEvent *event = (EmulEvent *)qemu_malloc(sizeof(EmulEvent)); ! 182: ! 183: assert(event); ! 184: event->p.error.type = EMUL_ERROR; ! 185: event->p.error.code = code; ! 186: emulated_push_event(card, event); ! 187: } ! 188: ! 189: static void emulated_push_data_type(EmulatedState *card, uint32_t type, ! 190: const uint8_t *data, uint32_t len) ! 191: { ! 192: EmulEvent *event = (EmulEvent *)qemu_malloc(sizeof(EmulEvent) + len); ! 193: ! 194: assert(event); ! 195: event->p.data.type = type; ! 196: event->p.data.len = len; ! 197: memcpy(event->p.data.data, data, len); ! 198: emulated_push_event(card, event); ! 199: } ! 200: ! 201: static void emulated_push_reader_insert(EmulatedState *card) ! 202: { ! 203: emulated_push_type(card, EMUL_READER_INSERT); ! 204: } ! 205: ! 206: static void emulated_push_reader_remove(EmulatedState *card) ! 207: { ! 208: emulated_push_type(card, EMUL_READER_REMOVE); ! 209: } ! 210: ! 211: static void emulated_push_card_insert(EmulatedState *card, ! 212: const uint8_t *atr, uint32_t len) ! 213: { ! 214: emulated_push_data_type(card, EMUL_CARD_INSERT, atr, len); ! 215: } ! 216: ! 217: static void emulated_push_card_remove(EmulatedState *card) ! 218: { ! 219: emulated_push_type(card, EMUL_CARD_REMOVE); ! 220: } ! 221: ! 222: static void emulated_push_response_apdu(EmulatedState *card, ! 223: const uint8_t *apdu, uint32_t len) ! 224: { ! 225: emulated_push_data_type(card, EMUL_RESPONSE_APDU, apdu, len); ! 226: } ! 227: ! 228: #define APDU_BUF_SIZE 270 ! 229: static void *handle_apdu_thread(void* arg) ! 230: { ! 231: EmulatedState *card = arg; ! 232: uint8_t recv_data[APDU_BUF_SIZE]; ! 233: int recv_len; ! 234: VReaderStatus reader_status; ! 235: EmulEvent *event; ! 236: ! 237: while (1) { ! 238: qemu_mutex_lock(&card->handle_apdu_mutex); ! 239: qemu_cond_wait(&card->handle_apdu_cond, &card->handle_apdu_mutex); ! 240: qemu_mutex_unlock(&card->handle_apdu_mutex); ! 241: if (card->quit_apdu_thread) { ! 242: card->quit_apdu_thread = 0; /* debugging */ ! 243: break; ! 244: } ! 245: qemu_mutex_lock(&card->vreader_mutex); ! 246: while (!QSIMPLEQ_EMPTY(&card->guest_apdu_list)) { ! 247: event = QSIMPLEQ_FIRST(&card->guest_apdu_list); ! 248: assert((unsigned long)event > 1000); ! 249: QSIMPLEQ_REMOVE_HEAD(&card->guest_apdu_list, entry); ! 250: if (event->p.data.type != EMUL_GUEST_APDU) { ! 251: DPRINTF(card, 1, "unexpected message in handle_apdu_thread\n"); ! 252: qemu_free(event); ! 253: continue; ! 254: } ! 255: if (card->reader == NULL) { ! 256: DPRINTF(card, 1, "reader is NULL\n"); ! 257: qemu_free(event); ! 258: continue; ! 259: } ! 260: recv_len = sizeof(recv_data); ! 261: reader_status = vreader_xfr_bytes(card->reader, ! 262: event->p.data.data, event->p.data.len, ! 263: recv_data, &recv_len); ! 264: DPRINTF(card, 2, "got back apdu of length %d\n", recv_len); ! 265: if (reader_status == VREADER_OK) { ! 266: emulated_push_response_apdu(card, recv_data, recv_len); ! 267: } else { ! 268: emulated_push_error(card, reader_status); ! 269: } ! 270: qemu_free(event); ! 271: } ! 272: qemu_mutex_unlock(&card->vreader_mutex); ! 273: } ! 274: qemu_mutex_lock(&card->apdu_thread_quit_mutex); ! 275: qemu_cond_signal(&card->apdu_thread_quit_cond); ! 276: qemu_mutex_unlock(&card->apdu_thread_quit_mutex); ! 277: return NULL; ! 278: } ! 279: ! 280: static void *event_thread(void *arg) ! 281: { ! 282: int atr_len = MAX_ATR_SIZE; ! 283: uint8_t atr[MAX_ATR_SIZE]; ! 284: VEvent *event = NULL; ! 285: EmulatedState *card = arg; ! 286: ! 287: while (1) { ! 288: const char *reader_name; ! 289: ! 290: event = vevent_wait_next_vevent(); ! 291: if (event == NULL || event->type == VEVENT_LAST) { ! 292: break; ! 293: } ! 294: if (event->type != VEVENT_READER_INSERT) { ! 295: if (card->reader == NULL && event->reader != NULL) { ! 296: /* Happens after device_add followed by card remove or insert. ! 297: * XXX: create synthetic add_reader events if vcard_emul_init ! 298: * already called, which happens if device_del and device_add ! 299: * are called */ ! 300: card->reader = vreader_reference(event->reader); ! 301: } else { ! 302: if (event->reader != card->reader) { ! 303: fprintf(stderr, ! 304: "ERROR: wrong reader: quiting event_thread\n"); ! 305: break; ! 306: } ! 307: } ! 308: } ! 309: switch (event->type) { ! 310: case VEVENT_READER_INSERT: ! 311: /* TODO: take a specific reader. i.e. track which reader ! 312: * we are seeing here, check it is the one we want (the first, ! 313: * or by a particular name), and ignore if we don't want it. ! 314: */ ! 315: reader_name = vreader_get_name(event->reader); ! 316: if (card->reader != NULL) { ! 317: DPRINTF(card, 2, "READER INSERT - replacing %s with %s\n", ! 318: vreader_get_name(card->reader), reader_name); ! 319: qemu_mutex_lock(&card->vreader_mutex); ! 320: vreader_free(card->reader); ! 321: qemu_mutex_unlock(&card->vreader_mutex); ! 322: emulated_push_reader_remove(card); ! 323: } ! 324: qemu_mutex_lock(&card->vreader_mutex); ! 325: DPRINTF(card, 2, "READER INSERT %s\n", reader_name); ! 326: card->reader = vreader_reference(event->reader); ! 327: qemu_mutex_unlock(&card->vreader_mutex); ! 328: emulated_push_reader_insert(card); ! 329: break; ! 330: case VEVENT_READER_REMOVE: ! 331: DPRINTF(card, 2, " READER REMOVE: %s\n", ! 332: vreader_get_name(event->reader)); ! 333: qemu_mutex_lock(&card->vreader_mutex); ! 334: vreader_free(card->reader); ! 335: card->reader = NULL; ! 336: qemu_mutex_unlock(&card->vreader_mutex); ! 337: emulated_push_reader_remove(card); ! 338: break; ! 339: case VEVENT_CARD_INSERT: ! 340: /* get the ATR (intended as a response to a power on from the ! 341: * reader */ ! 342: atr_len = MAX_ATR_SIZE; ! 343: vreader_power_on(event->reader, atr, &atr_len); ! 344: card->atr_length = (uint8_t)atr_len; ! 345: DPRINTF(card, 2, " CARD INSERT\n"); ! 346: emulated_push_card_insert(card, atr, atr_len); ! 347: break; ! 348: case VEVENT_CARD_REMOVE: ! 349: DPRINTF(card, 2, " CARD REMOVE\n"); ! 350: emulated_push_card_remove(card); ! 351: break; ! 352: case VEVENT_LAST: /* quit */ ! 353: vevent_delete(event); ! 354: return NULL; ! 355: break; ! 356: default: ! 357: break; ! 358: } ! 359: vevent_delete(event); ! 360: } ! 361: return NULL; ! 362: } ! 363: ! 364: static void pipe_read(void *opaque) ! 365: { ! 366: EmulatedState *card = opaque; ! 367: EmulEvent *event, *next; ! 368: char dummy; ! 369: int len; ! 370: ! 371: do { ! 372: len = read(card->pipe[0], &dummy, sizeof(dummy)); ! 373: } while (len == sizeof(dummy)); ! 374: qemu_mutex_lock(&card->event_list_mutex); ! 375: QSIMPLEQ_FOREACH_SAFE(event, &card->event_list, entry, next) { ! 376: DPRINTF(card, 2, "event %s\n", emul_event_to_string(event->p.gen.type)); ! 377: switch (event->p.gen.type) { ! 378: case EMUL_RESPONSE_APDU: ! 379: ccid_card_send_apdu_to_guest(&card->base, event->p.data.data, ! 380: event->p.data.len); ! 381: break; ! 382: case EMUL_READER_INSERT: ! 383: ccid_card_ccid_attach(&card->base); ! 384: break; ! 385: case EMUL_READER_REMOVE: ! 386: ccid_card_ccid_detach(&card->base); ! 387: break; ! 388: case EMUL_CARD_INSERT: ! 389: assert(event->p.data.len <= MAX_ATR_SIZE); ! 390: card->atr_length = event->p.data.len; ! 391: memcpy(card->atr, event->p.data.data, card->atr_length); ! 392: ccid_card_card_inserted(&card->base); ! 393: break; ! 394: case EMUL_CARD_REMOVE: ! 395: ccid_card_card_removed(&card->base); ! 396: break; ! 397: case EMUL_ERROR: ! 398: ccid_card_card_error(&card->base, event->p.error.code); ! 399: break; ! 400: default: ! 401: DPRINTF(card, 2, "unexpected event\n"); ! 402: break; ! 403: } ! 404: qemu_free(event); ! 405: } ! 406: QSIMPLEQ_INIT(&card->event_list); ! 407: qemu_mutex_unlock(&card->event_list_mutex); ! 408: } ! 409: ! 410: static int init_pipe_signaling(EmulatedState *card) ! 411: { ! 412: if (pipe(card->pipe) < 0) { ! 413: DPRINTF(card, 2, "pipe creation failed\n"); ! 414: return -1; ! 415: } ! 416: fcntl(card->pipe[0], F_SETFL, O_NONBLOCK); ! 417: fcntl(card->pipe[1], F_SETFL, O_NONBLOCK); ! 418: fcntl(card->pipe[0], F_SETOWN, getpid()); ! 419: qemu_set_fd_handler(card->pipe[0], pipe_read, NULL, card); ! 420: return 0; ! 421: } ! 422: ! 423: #define CERTIFICATES_DEFAULT_DB "/etc/pki/nssdb" ! 424: #define CERTIFICATES_ARGS_TEMPLATE\ ! 425: "db=\"%s\" use_hw=no soft=(,Virtual Reader,CAC,,%s,%s,%s)" ! 426: ! 427: static int wrap_vcard_emul_init(VCardEmulOptions *options) ! 428: { ! 429: static int called; ! 430: static int options_was_null; ! 431: ! 432: if (called) { ! 433: if ((options == NULL) != options_was_null) { ! 434: printf("%s: warning: running emulated with certificates" ! 435: " and emulated side by side is not supported\n", ! 436: __func__); ! 437: return VCARD_EMUL_FAIL; ! 438: } ! 439: vcard_emul_replay_insertion_events(); ! 440: return VCARD_EMUL_OK; ! 441: } ! 442: options_was_null = (options == NULL); ! 443: called = 1; ! 444: return vcard_emul_init(options); ! 445: } ! 446: ! 447: static int emulated_initialize_vcard_from_certificates(EmulatedState *card) ! 448: { ! 449: char emul_args[200]; ! 450: VCardEmulOptions *options = NULL; ! 451: ! 452: snprintf(emul_args, sizeof(emul_args) - 1, CERTIFICATES_ARGS_TEMPLATE, ! 453: card->db ? card->db : CERTIFICATES_DEFAULT_DB, ! 454: card->cert1, card->cert2, card->cert3); ! 455: options = vcard_emul_options(emul_args); ! 456: if (options == NULL) { ! 457: printf("%s: warning: not using certificates due to" ! 458: " initialization error\n", __func__); ! 459: } ! 460: return wrap_vcard_emul_init(options); ! 461: } ! 462: ! 463: typedef struct EnumTable { ! 464: const char *name; ! 465: uint32_t value; ! 466: } EnumTable; ! 467: ! 468: EnumTable backend_enum_table[] = { ! 469: {BACKEND_NSS_EMULATED_NAME, BACKEND_NSS_EMULATED}, ! 470: {BACKEND_CERTIFICATES_NAME, BACKEND_CERTIFICATES}, ! 471: {NULL, 0}, ! 472: }; ! 473: ! 474: static uint32_t parse_enumeration(char *str, ! 475: EnumTable *table, uint32_t not_found_value) ! 476: { ! 477: uint32_t ret = not_found_value; ! 478: ! 479: while (table->name != NULL) { ! 480: if (strcmp(table->name, str) == 0) { ! 481: ret = table->value; ! 482: break; ! 483: } ! 484: table++; ! 485: } ! 486: return ret; ! 487: } ! 488: ! 489: static int emulated_initfn(CCIDCardState *base) ! 490: { ! 491: EmulatedState *card = DO_UPCAST(EmulatedState, base, base); ! 492: QemuThread thread_id; ! 493: VCardEmulError ret; ! 494: EnumTable *ptable; ! 495: ! 496: QSIMPLEQ_INIT(&card->event_list); ! 497: QSIMPLEQ_INIT(&card->guest_apdu_list); ! 498: qemu_mutex_init(&card->event_list_mutex); ! 499: qemu_mutex_init(&card->vreader_mutex); ! 500: qemu_mutex_init(&card->handle_apdu_mutex); ! 501: qemu_cond_init(&card->handle_apdu_cond); ! 502: card->reader = NULL; ! 503: card->quit_apdu_thread = 0; ! 504: if (init_pipe_signaling(card) < 0) { ! 505: return -1; ! 506: } ! 507: card->backend = parse_enumeration(card->backend_str, backend_enum_table, 0); ! 508: if (card->backend == 0) { ! 509: printf("unknown backend, must be one of:\n"); ! 510: for (ptable = backend_enum_table; ptable->name != NULL; ++ptable) { ! 511: printf("%s\n", ptable->name); ! 512: } ! 513: return -1; ! 514: } ! 515: ! 516: /* TODO: a passthru backened that works on local machine. third card type?*/ ! 517: if (card->backend == BACKEND_CERTIFICATES) { ! 518: if (card->cert1 != NULL && card->cert2 != NULL && card->cert3 != NULL) { ! 519: ret = emulated_initialize_vcard_from_certificates(card); ! 520: } else { ! 521: printf("%s: you must provide all three certs for" ! 522: " certificates backend\n", EMULATED_DEV_NAME); ! 523: return -1; ! 524: } ! 525: } else { ! 526: if (card->backend != BACKEND_NSS_EMULATED) { ! 527: printf("%s: bad backend specified. The options are:\n%s (default)," ! 528: " %s.\n", EMULATED_DEV_NAME, BACKEND_NSS_EMULATED_NAME, ! 529: BACKEND_CERTIFICATES_NAME); ! 530: return -1; ! 531: } ! 532: if (card->cert1 != NULL || card->cert2 != NULL || card->cert3 != NULL) { ! 533: printf("%s: unexpected cert parameters to nss emulated backend\n", ! 534: EMULATED_DEV_NAME); ! 535: return -1; ! 536: } ! 537: /* default to mirroring the local hardware readers */ ! 538: ret = wrap_vcard_emul_init(NULL); ! 539: } ! 540: if (ret != VCARD_EMUL_OK) { ! 541: printf("%s: failed to initialize vcard\n", EMULATED_DEV_NAME); ! 542: return -1; ! 543: } ! 544: qemu_thread_create(&thread_id, event_thread, card); ! 545: qemu_thread_create(&thread_id, handle_apdu_thread, card); ! 546: return 0; ! 547: } ! 548: ! 549: static int emulated_exitfn(CCIDCardState *base) ! 550: { ! 551: EmulatedState *card = DO_UPCAST(EmulatedState, base, base); ! 552: VEvent *vevent = vevent_new(VEVENT_LAST, NULL, NULL); ! 553: ! 554: vevent_queue_vevent(vevent); /* stop vevent thread */ ! 555: qemu_mutex_lock(&card->apdu_thread_quit_mutex); ! 556: card->quit_apdu_thread = 1; /* stop handle_apdu thread */ ! 557: qemu_cond_signal(&card->handle_apdu_cond); ! 558: qemu_cond_wait(&card->apdu_thread_quit_cond, ! 559: &card->apdu_thread_quit_mutex); ! 560: /* handle_apdu thread stopped, can destroy all of it's mutexes */ ! 561: qemu_cond_destroy(&card->handle_apdu_cond); ! 562: qemu_cond_destroy(&card->apdu_thread_quit_cond); ! 563: qemu_mutex_destroy(&card->apdu_thread_quit_mutex); ! 564: qemu_mutex_destroy(&card->handle_apdu_mutex); ! 565: qemu_mutex_destroy(&card->vreader_mutex); ! 566: qemu_mutex_destroy(&card->event_list_mutex); ! 567: return 0; ! 568: } ! 569: ! 570: static CCIDCardInfo emulated_card_info = { ! 571: .qdev.name = EMULATED_DEV_NAME, ! 572: .qdev.desc = "emulated smartcard", ! 573: .qdev.size = sizeof(EmulatedState), ! 574: .initfn = emulated_initfn, ! 575: .exitfn = emulated_exitfn, ! 576: .get_atr = emulated_get_atr, ! 577: .apdu_from_guest = emulated_apdu_from_guest, ! 578: .qdev.unplug = qdev_simple_unplug_cb, ! 579: .qdev.props = (Property[]) { ! 580: DEFINE_PROP_STRING("backend", EmulatedState, backend_str), ! 581: DEFINE_PROP_STRING("cert1", EmulatedState, cert1), ! 582: DEFINE_PROP_STRING("cert2", EmulatedState, cert2), ! 583: DEFINE_PROP_STRING("cert3", EmulatedState, cert3), ! 584: DEFINE_PROP_STRING("db", EmulatedState, db), ! 585: DEFINE_PROP_UINT8("debug", EmulatedState, debug, 0), ! 586: DEFINE_PROP_END_OF_LIST(), ! 587: }, ! 588: }; ! 589: ! 590: static void ccid_card_emulated_register_devices(void) ! 591: { ! 592: ccid_card_qdev_register(&emulated_card_info); ! 593: } ! 594: ! 595: device_init(ccid_card_emulated_register_devices)
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.