/* * Copyright (c) 2002 Bruce M. Simpson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Bruce M. Simpson. * 4. Neither the name of Bruce M. Simpson nor the names of co- * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bruce M. Simpson AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Bruce M. Simpson OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "card_if.h" #include "gprsc.h" struct gprsc_softc { int sc_unit; device_t sc_dev; dev_t sc_cdev; int sc_opened; struct resource *sc_io; int sc_io_rid; bus_space_tag_t sc_io_tag; bus_space_handle_t sc_io_bsh; struct resource *sc_mem; int sc_mem_rid; bus_space_tag_t sc_mem_tag; bus_space_handle_t sc_mem_bsh; struct resource *sc_irq; int sc_irq_rid; void *sc_ih; u_char sc_dying; u_char sc_sleep; }; static int gprsc_pccard_match(device_t dev); static int gprsc_pccard_probe(device_t dev); static int gprsc_pccard_attach(device_t dev); static int gprsc_pccard_detach(device_t dev); static void gprsc_pccard_intr(void *xsc); static int gprsc_alloc(struct gprsc_softc *sc); static int gprsc_alloc_mem(struct gprsc_softc *sc); static void gprsc_free(struct gprsc_softc *sc); static int gprsc_tlv_send(struct gprsc_softc *sc, u_char tag, u_int16_t len, u_char *val); static int gprsc_tlv_recv(struct gprsc_softc *sc, u_char *tag, u_int16_t *len, u_char *val); static int gprsc_reset(struct gprsc_softc *sc); static int gprsc_ioctl_open(struct gprsc_softc *sc, caddr_t cmdarg); static int gprsc_ioctl_tlv(struct gprsc_softc *sc, caddr_t cmdarg); static int gprsc_ioctl_status(struct gprsc_softc *sc, caddr_t cmdarg); static int gprsc_ioctl_ram(struct gprsc_softc *sc, caddr_t cmdarg); static int gprsc_ioctl_cmd(struct gprsc_softc *sc, caddr_t cmdarg); static const struct pccard_product gprsc_products[] = { PCMCIA_CARD(GEMPLUS, GPR400, 0), { NULL } }; static device_method_t gprsc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, pccard_compat_probe), DEVMETHOD(device_attach, pccard_compat_attach), DEVMETHOD(device_detach, gprsc_pccard_detach), /* Card interface */ DEVMETHOD(card_compat_match, gprsc_pccard_match), DEVMETHOD(card_compat_probe, gprsc_pccard_probe), DEVMETHOD(card_compat_attach, gprsc_pccard_attach), { 0, 0 } }; static driver_t gprsc_driver = { "gprsc", gprsc_methods, sizeof(struct gprsc_softc) }; static devclass_t gprsc_devclass; DRIVER_MODULE(gprsc, pccard, gprsc_driver, gprsc_devclass, 0, 0); static d_open_t gprsc_open; static d_close_t gprsc_close; static d_ioctl_t gprsc_ioctl; #define GPRSC_CDEV_MAJOR 202 #define GPRSC_UNIT(n) (minor(n)) static struct cdevsw gprsc_cdevsw = { .d_open = gprsc_open, .d_close = gprsc_close, .d_ioctl = gprsc_ioctl, .d_name = "gprsc", .d_maj = GPRSC_CDEV_MAJOR }; #if !defined(lint) static const char rcsid[] = "$FreeBSD$"; #endif /* * pccard bus child routines */ static int gprsc_pccard_match(device_t dev) { const struct pccard_product *pp; pp = pccard_product_lookup(dev, gprsc_products, sizeof(gprsc_products[0]), NULL); if (pp != NULL) { if (pp->pp_name != NULL) device_set_desc(dev, pp->pp_name); return (0); } return (EIO); } static int gprsc_pccard_probe(device_t dev) { struct gprsc_softc *sc = device_get_softc(dev); int err; if ((sc == NULL) || (sc->sc_dying)) return (ENXIO); sc->sc_dev = dev; err = gprsc_alloc(sc); if (err) { GPRPUTS(sc, "resource allocation failed"); return (err); } gprsc_free(sc); return (0); } static int gprsc_pccard_attach(device_t dev) { struct gprsc_softc *sc = device_get_softc(dev); int err; if ((sc == NULL) || (sc->sc_dying)) return (ENXIO); sc->sc_dev = dev; err = gprsc_alloc(sc); if (err) { GPRPUTS(sc, "resource allocation failed"); return (err); } err = bus_setup_intr(dev, sc->sc_irq, INTR_TYPE_TTY, gprsc_pccard_intr, sc, &sc->sc_ih); if (err) GPRPUTS(sc, "failed to setup interrupt handler"); sc->sc_cdev = make_dev(&gprsc_cdevsw, sc->sc_unit, UID_ROOT, GID_OPERATOR, S_IRUSR|S_IWUSR, "gprsc%d", sc->sc_unit); sc->sc_cdev->si_drv1 = sc; return (0); } static int gprsc_pccard_detach(device_t dev) { struct gprsc_softc *sc = device_get_softc(dev); int err; sc->sc_dying = 1; wakeup(sc); /* XXX perhaps msleep is the best way to deal */ destroy_dev(sc->sc_cdev); err = bus_teardown_intr(dev, sc->sc_irq, sc->sc_ih); if (err) GPRPUTS(sc, "failed to unregister interrupt handler"); gprsc_free(sc); return (0); } /* * resource allocation routines */ static int gprsc_alloc(struct gprsc_softc *sc) { #ifdef notyet int err; #endif sc->sc_cdev = (void *) sc->sc_io = sc->sc_irq = sc->sc_mem = NULL; sc->sc_opened = sc->sc_sleep = sc->sc_dying = 0; sc->sc_io_rid = 0; sc->sc_io = bus_alloc_resource(sc->sc_dev, SYS_RES_IOPORT, &sc->sc_io_rid, 0, ~0, GPR_IO_SIZE, RF_ACTIVE); if (!sc->sc_io) return (ENOMEM); sc->sc_irq_rid = 0; sc->sc_irq = bus_alloc_resource(sc->sc_dev, SYS_RES_IRQ, &sc->sc_irq_rid, 0, ~0, 1, RF_ACTIVE); if (!sc->sc_irq) { gprsc_free(sc); return (ENOMEM); } #ifdef notyet /* * XXX: There is meant to be a single register in the * attribute memory, but the driver never actually uses it. */ err = gprsc_alloc_mem(sc); if (err) { gprsc_free(sc); return (ENOMEM); } #endif sc->sc_io_tag = rman_get_bustag(sc->sc_io); sc->sc_io_bsh = rman_get_bushandle(sc->sc_io); sc->sc_unit = device_get_unit(sc->sc_dev); return (0); } /* * Allocate and map in the attribute memory. */ static int gprsc_alloc_mem(struct gprsc_softc *sc) { int err; sc->sc_mem_rid = GPR_AM_RID; sc->sc_mem = bus_alloc_resource(sc->sc_dev, SYS_RES_MEMORY, &sc->sc_mem_rid, 0, ~0, GPR_MEM_SIZE, RF_ACTIVE); if (!sc->sc_mem) { GPRPUTS(sc, "cannot allocate attribute memory"); return (ENOMEM); } err = CARD_SET_MEMORY_OFFSET(device_get_parent(sc->sc_dev), sc->sc_dev, sc->sc_mem_rid, 0, NULL); if (err) { GPRDPRINTF(sc, "CARD_SET_MEMORY_OFFSET returned 0x%0x", err); goto fail; } err = CARD_SET_RES_FLAGS(device_get_parent(sc->sc_dev), sc->sc_dev, SYS_RES_MEMORY, sc->sc_mem_rid, PCCARD_A_MEM_ATTR); if (err) { GPRDPRINTF(sc, "CARD_SET_RES_FLAGS returned 0x%0x", err); goto fail; } err = CARD_SET_RES_FLAGS(device_get_parent(sc->sc_dev), sc->sc_dev, SYS_RES_MEMORY, sc->sc_mem_rid, PCCARD_A_MEM_8BIT); if (err) { GPRDPRINTF(sc, "CARD_SET_RES_FLAGS returned 0x%0x", err); goto fail; } sc->sc_mem_tag = rman_get_bustag(sc->sc_mem); sc->sc_mem_bsh = rman_get_bushandle(sc->sc_mem); err = 0; fail: if (err) { GPRPUTS(sc, "cannot set attribute memory parameters"); bus_release_resource(sc->sc_dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem); sc->sc_mem = NULL; sc->sc_mem_tag = sc->sc_mem_bsh = -1; } return (err); } static void gprsc_free(struct gprsc_softc *sc) { if (sc->sc_io) bus_release_resource(sc->sc_dev, SYS_RES_IOPORT, sc->sc_io_rid, sc->sc_io); if (sc->sc_irq) bus_release_resource(sc->sc_dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq); #if 0 if (sc->sc_mem) bus_release_resource(sc->sc_dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem); #endif sc->sc_io = sc->sc_irq = sc->sc_mem = 0; sc->sc_mem_tag = sc->sc_mem_bsh = sc->sc_io_tag = sc->sc_io_bsh = -1; return; } /* * ISA IRQ handler */ static void gprsc_pccard_intr(void *xsc) { struct gprsc_softc *sc; sc = (struct gprsc_softc *) xsc; if ((sc == NULL) || (sc->sc_dying)) return; GPR_CLEAR_INTR(sc); if (sc->sc_sleep) { sc->sc_sleep = 0; wakeup(sc); } } /* * gprsc device-specific routines */ /* * gprsc_tlv_send() -- Send a command to the smartcard using the TLV buffer. * Transmission will be aborted if the process is interrupted. * Returns the reader status byte. */ static int gprsc_tlv_send(struct gprsc_softc *sc, u_char tag, u_int16_t len, u_char *val) { int i, j, chunks, remainder, err; #if 0 GPRDPRINTF(sc, "tag=%d,len=%d,val=%p", (int) tag, (int) len, val); #endif if (len > GPR_TLV_MAX_CMD_LEN) return (GPR_STATUS_LEN_ERR); /* * Fragment the command packet into GPR_TLV_MAX_CHUNK_LEN * sized chunks. */ chunks = len / GPR_TLV_MAX_CHUNK_LEN; remainder = len % GPR_TLV_MAX_CHUNK_LEN; tag |= GPR_TLV_CONT; for (i = 0; i < chunks; i++) { GPR_WRITE_TLV_TAG(sc, tag); GPR_WRITE_TLV_LEN(sc, GPR_TLV_MAX_CHUNK_LEN); for (j = 0; i < GPR_TLV_MAX_CHUNK_LEN; j++) GPR_WRITE_TLV_VAL(sc, j, *val++); GPR_START_CMD(sc); sc->sc_sleep = 1; err = tsleep(sc, PZERO, "gprsc_tlv_send", 0); err = GPR_READ_TLV_STATUS(sc); if (err != GPR_STATUS_OK) { GPRDPUTS(sc, "command error"); return (err); } } #if 0 GPRDPRINTF(sc, "sending remainder"); #endif tag &= ~(GPR_TLV_CONT); GPR_WRITE_TLV_TAG(sc, tag); GPR_WRITE_TLV_LEN(sc, remainder); for (i = 0; i < remainder; i++) GPR_WRITE_TLV_VAL(sc, i, *val++); GPR_START_CMD(sc); sc->sc_sleep = 1; err = tsleep(sc, PZERO, "gprsc_tlv_send", 0); err = GPR_READ_TLV_STATUS(sc); if ((err != GPR_STATUS_OK) && (err != GPR_STATUS_SW1_ERR)) { GPRDPUTS(sc, "command error"); } return (err); } /* * gprsc_tlv_recv() -- Receive a TLV string from the smart card reader. * Returns the reader status byte. * * Warning: This routine does not check that the buffer size is * at lease GPR_TLV_MAX_CMD_LEN bytes in length. This is the caller's * responsibility. */ static int gprsc_tlv_recv(struct gprsc_softc *sc, u_char *tag, u_int16_t *len, u_char *val) { u_char in_tag, in_len; int err, skip, i; in_tag = GPR_READ_TLV_TAG(sc); *tag = (in_tag & ~GPR_TLV_CONT); #if 0 GPRDPRINTF(sc, "tag=%d,len=%d,val=%p", (int) tag, (int) len, val); #endif /* skip the status byte */ in_len = GPR_READ_TLV_LEN(sc) & 0xFF; skip = 1; *len = in_len - skip; err = GPR_READ_TLV_STATUS(sc); if (err == GPR_STATUS_SW1_ERR) err = GPR_STATUS_OK; if (err != GPR_STATUS_OK) { GPRDPRINTF(sc, "read error: %d", err); return (err); } while (in_tag & GPR_TLV_CONT) { for (i = 0; i < in_len - skip; ++i) *val++ = GPR_READ_TLV_VAL(sc, skip+i); /* get next chunk */ GPRDPRINTF(sc, "sleeping for another chunk"); GPR_START_CMD(sc); sc->sc_sleep = 1; err = tsleep(sc, PZERO, "gprsc_tlv_recv", 0); in_tag = GPR_READ_TLV_TAG(sc); in_len = GPR_READ_TLV_LEN(sc) & 0xFF; *len += in_len; skip = 0; } /* get last chunk */ for (i = 0; i < in_len - skip; ++i) *val++ = GPR_READ_TLV_VAL(sc, skip+i); return (err); } static int gprsc_reset(struct gprsc_softc *sc) { u_char select_args[1] = { GPR_SELECT_ISO_7816_3 }; int err; /* * flag a reset as pending such that interrupts are acknowledged * from the device, but ignored, to be on the safe side. wait for * GPR_RESET_TIMO for the reset to complete. */ GPRDPUTS(sc, "reset"); GPR_RESET(sc); err = tsleep(gprsc_reset, PZERO, "gprsc_reset", GPR_RESET_TIMO); /* send select card command */ return (gprsc_tlv_send(sc, GPR_TAG_SELECT, sizeof(select_args), (u_char *) select_args)); } /* * character device routines */ static int gprsc_open(dev_t dev, int flags, int fmt, struct thread *td) { struct gprsc_softc *sc = (struct gprsc_softc *) dev->si_drv1; int err; if (sc->sc_opened) return (EBUSY); sc->sc_opened++; err = gprsc_reset(sc); if (err != GPR_STATUS_OK) return (EINVAL); return ((err == GPR_STATUS_OK) ? 0 : EINVAL); } /* * close session with card */ static int gprsc_close(dev_t dev, int flags, int fmt, struct thread *td) { struct gprsc_softc *sc = (struct gprsc_softc *) dev->si_drv1; if (!sc) return (ENXIO); (void) gprsc_tlv_send(sc, GPR_TAG_CLOSE, 0, NULL); sc->sc_opened = 0; return (0); } /* * ioctl dispatcher */ static int gprsc_ioctl(dev_t dev, u_long cmd, caddr_t cmdarg, int flags, struct thread *td) { struct gprsc_softc *sc = (struct gprsc_softc *) dev->si_drv1; if (sc->sc_dying) return (EIO); switch (cmd) { case GPR400_RESET: return (gprsc_reset(sc)); break; case GPR400_PWROFF: case GPR400_STNDBY: (void) gprsc_tlv_send(sc, GPR_TAG_POWER, 1, "\x00"); return (0); break; case GPR400_OPEN: return (gprsc_ioctl_open(sc, cmdarg)); break; case GPR400_CLOSE: return (gprsc_tlv_send(sc, GPR_TAG_CLOSE, 0, NULL)); break; case GPR400_SELECT: return (gprsc_tlv_send(sc, GPR_TAG_SELECT, 1, "\x02")); break; case GPR400_STATUS: return (gprsc_ioctl_status(sc, cmdarg)); break; case GPR400_RAM: return (gprsc_ioctl_ram(sc, cmdarg)); break; case GPR400_CMD: return (gprsc_ioctl_cmd(sc, cmdarg)); break; case GPR400_TLV: return (gprsc_ioctl_tlv(sc, cmdarg)); break; default: break; } return (EINVAL); } /* * ioctl command handlers */ static int gprsc_ioctl_open(struct gprsc_softc *sc, caddr_t cmdarg) { struct gpr400_atr *atr; struct gpr400_atr *uatr = (struct gpr400_atr *) cmdarg; int err; u_int16_t len; u_char tag; GPRDPRINTF(sc, ""); if (uatr == NULL) return (EFAULT); MALLOC(atr, struct gpr400_atr *, GPR_TLV_MAX_CMD_LEN, M_TEMP, M_WAITOK); if (atr == NULL) return (ENOMEM); len = 0; bzero(atr, GPR_TLV_MAX_CMD_LEN); err = gprsc_tlv_send(sc, GPR_TAG_OPEN, 0, NULL); if (err != GPR_STATUS_OK) return (err); err = gprsc_tlv_recv(sc, &tag, &len, (u_char *) &atr->data); if (len > sizeof(atr->data)) GPRPUTS(sc, "WARNING: long (>62 byte) ATR reply truncated!"); atr->status = err; atr->len = len; bcopy(atr, uatr, sizeof(*uatr)); return (err); } static int gprsc_ioctl_tlv(struct gprsc_softc *sc, caddr_t cmdarg) { struct gpr400_tlv *tlv = (struct gpr400_tlv *) cmdarg; int err; GPRDPRINTF(sc, ""); if (tlv == NULL) return (EFAULT); err = gprsc_tlv_send(sc, tlv->tag, tlv->length, &tlv->value[0]); if (err != GPR_STATUS_OK) return (err); tlv->tag = tlv->length = 0; err = gprsc_tlv_recv(sc, &tlv->tag, &tlv->length, &tlv->value[0]); if (err != GPR_STATUS_OK) { bcopy(&tlv->value[0], &tlv->value[1], tlv->length); tlv->value[0] = err; } return (err); } static int gprsc_ioctl_status(struct gprsc_softc *sc, caddr_t cmdarg) { struct gpr400_status *status = (struct gpr400_status *) cmdarg; u_char *buf, tag; u_int16_t len; int err; if (status == NULL) return (EFAULT); MALLOC(buf, u_char *, GPR_TLV_MAX_CMD_LEN, M_TEMP, M_WAITOK); if (buf == NULL) return (ENOMEM); (void) gprsc_tlv_send(sc, GPR_TAG_STATUS, 1, "\x00"); err = gprsc_tlv_recv(sc, &tag, &len, buf); status->status = err; status->os_version = buf[0]; status->flash_mem = buf[1]; status->manufacturer = buf[2]; (void) gprsc_tlv_send(sc, GPR_TAG_STATUS, 1, "\x01"); err = gprsc_tlv_recv(sc, &tag, &len, buf); status->rom_sum = buf[0]; status->ram_sum = buf[1]; status->flash_sum = ((len > 2) ? buf[2] : 0); (void) gprsc_tlv_send(sc, GPR_TAG_STATUS, 1, "\x02"); err = gprsc_tlv_recv(sc, &tag, &len, buf); status->reg1 = buf[0]; status->reg2 = buf[1]; status->info = buf[2]; status->card_inserted = ((buf[0] & 0x80) ? 1 : 0); FREE(buf, M_TEMP); return (0); } static int gprsc_ioctl_ram(struct gprsc_softc *sc, caddr_t cmdarg) { caddr_t membase; GPRDPRINTF(sc, ""); if (cmdarg == NULL) return (EFAULT); membase = rman_get_virtual(sc->sc_mem); if (membase == NULL) return (EFAULT); bcopy(membase, cmdarg, sizeof(struct gpr400_ram)); return (0); } /* * XXX: Possible buffer overrun/hang in this routine, being exercised by * pkcs15-init from OpenSC. * * The system locks up when a GET RESPONSE command is issued to read * from the card; not sure why. * * The offending sequence looks like this:- * cl in p1 p2 ln * 00 C0 00 00 0C */ static int gprsc_ioctl_cmd(struct gprsc_softc *sc, caddr_t cmdarg) { struct gpr400_cmd *cmd; struct gpr400_cmd *ucmd = (struct gpr400_cmd *) cmdarg; int err; u_int16_t len; u_char tag; if (ucmd == NULL) return (EFAULT); if (ucmd->dir == GPR400_CMD_DIR_TOCARD) len = GPR400_MIN_CMD_SIZE + ucmd->len; else if (ucmd->dir == GPR400_CMD_DIR_FROMCARD) len = GPR400_MIN_CMD_SIZE; else return (EINVAL); MALLOC(cmd, struct gpr400_cmd *, sizeof(*cmd), M_TEMP, M_WAITOK); if (cmd == NULL) return (EFAULT); bcopy(ucmd, cmd, sizeof(*ucmd)); GPRDPRINTF(sc, "cmd: dir=%d cla=%02x ins=%02x p1=%02x p2=%02x " "len=%02x", cmd->dir, cmd->cla, cmd->ins, cmd->p1, cmd->p2, len); err = gprsc_tlv_send(sc, GPR_TAG_APDU, len, (u_char *) cmd); GPRDPRINTF(sc, "tlv_send err=%02x", err); tag = len = 0; cmd->sw1 = cmd->sw2 = 0; cmd->status = gprsc_tlv_recv(sc, &tag, &len, cmd->data); if (len >= 2) { cmd->sw1 = cmd->data[len - 2]; cmd->sw2 = cmd->data[len - 1]; } GPRDPRINTF(sc, "answer: status=%02x tag=%02x len=%02x " "sw1=%02x sw2=%02x", cmd->status, tag, len, cmd->sw1, cmd->sw2); bcopy(cmd, ucmd, sizeof(*ucmd)); FREE(cmd, M_TEMP); return (0); }