diff options
author | Federico G. Schwindt <fgsch@cvs.openbsd.org> | 2002-06-15 05:56:00 +0000 |
---|---|---|
committer | Federico G. Schwindt <fgsch@cvs.openbsd.org> | 2002-06-15 05:56:00 +0000 |
commit | a52e8989f333415b7df95e47c440c265a38b81c5 (patch) | |
tree | 61939e2ecc21989ec201eaac3769803ceba462ac /sys | |
parent | 8fbd715c239af8c9ead2a7d2c549ba8047d47e32 (diff) |
GPR400 smartcard reader driver, some stuff still missing.
jason@ commented on it.
Diffstat (limited to 'sys')
-rw-r--r-- | sys/dev/pcmcia/files.pcmcia | 7 | ||||
-rw-r--r-- | sys/dev/pcmcia/gpr.c | 439 |
2 files changed, 445 insertions, 1 deletions
diff --git a/sys/dev/pcmcia/files.pcmcia b/sys/dev/pcmcia/files.pcmcia index c5cc1b9efb3..f6d3922d319 100644 --- a/sys/dev/pcmcia/files.pcmcia +++ b/sys/dev/pcmcia/files.pcmcia @@ -1,4 +1,4 @@ -# $OpenBSD: files.pcmcia,v 1.36 2001/08/18 16:19:01 aaron Exp $ +# $OpenBSD: files.pcmcia,v 1.37 2002/06/15 05:55:59 fgsch Exp $ # $NetBSD: files.pcmcia,v 1.9 1998/06/21 18:45:41 christos Exp $ # # Config.new file and device description for machine-independent PCMCIA code. @@ -90,3 +90,8 @@ file dev/pcmcia/if_ray.c ray # Aironet 4500/4800 802.11 DS WLAN attach an at pcmcia with an_pcmcia file dev/pcmcia/if_an_pcmcia.c an_pcmcia + +# Gemplus GPR400 SmartCard reader +device gpr +attach gpr at pcmcia with gpr +file dev/pcmcia/gpr.c gpr needs-flag diff --git a/sys/dev/pcmcia/gpr.c b/sys/dev/pcmcia/gpr.c new file mode 100644 index 00000000000..1bc7cfc5af8 --- /dev/null +++ b/sys/dev/pcmcia/gpr.c @@ -0,0 +1,439 @@ +/* $OpenBSD: gpr.c,v 1.1 2002/06/15 05:55:59 fgsch Exp $ */ + +/* + * Copyright (c) 2002, Federico G. Schwindt + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of the Federico G. Schwindt nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE COPYRIGHT + * OWNER 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. + */ + +/* + * A driver for the Gemplus GPR400 SmartCard reader. + * + * The gpr400 driver written by Wolf Geldmacher <wgeldmacher@paus.ch> for + * Linux was used as documentation. + */ + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/device.h> +#include <sys/systm.h> +#include <sys/proc.h> +#include <sys/ioctl.h> + +#include <dev/pcmcia/pcmciavar.h> +#include <dev/pcmcia/pcmciareg.h> +#include <dev/pcmcia/pcmciadevs.h> + +/* Registers in I/O space (32 bytes) */ +#define GPR400_HAP_CTRL 0x00 /* Handshake and PRG Control */ +#define GPR400_RESET 0x01 /* Master reset */ +#define GPR400_IREQ 0x02 /* Interrupt request */ +#define GPR400_INTR 0x04 /* Interrupt */ + /* bits 3..8 PRG control */ +#define GPR400_PD_CTRL 0x01 /* PRG data */ +/* bytes 3..32 used for data exchange */ + +/* Registers in attribute memory (read only) */ +#define GPR400_SETUP 0x018 /* General Setup */ +#define GPR400_LOCK_MASK 0x08 /* 0: locked, 1: unlocked */ +#define GPR400_SCARD1 0x01a /* SmartCard Reg. 1 */ +#define GPR400_INS_MASK 0x08 /* 0: in the reader, 1: removed */ +#define GPR400_DET_MASK 0x80 /* 0: no inserted, 1: inserted */ +#define GPR400_SCARD2 0x01c /* SmartCard Reg. 2 */ +#define GPR400_CAC 0x01e /* Clock and Control */ + +/* TLV */ +#define GPR400_CLOSE 0x10 /* Close session */ +#define GPR400_OPEN 0x20 /* Open session */ +#define GPR400_APDU 0x30 /* APDU exchange */ +#define GPR400_POWER 0x40 /* Power down/Standby */ + /* 0: Power down, 1: Standby */ +#define GPR400_SELECT 0x50 /* Select card */ +#define GPR400_DRV0 0x00 /* Downloaded driver 0 */ +#define GPR400_ISODRV 0x02 /* ISO7816-3 driver */ +#define GPR400_CLK_MASK 0x08 /* 0: 3.68Mhz, 1: 7.36Mhz */ +#define GPR400_STATUS 0xA0 /* Reader status */ + +#define GPR400_CONT 0x04 /* Chain block */ + +#define GPR400_MEM_LEN 0x1000 + +#define GPRUNIT(x) (minor(x) & 0x0f) + +/* + * gpr device operations. + */ +#define GPR_CLOSE _IO('g', 1) +#define GPR_CMD _IO('g', 2) +#define GPR_OPEN _IO('g', 3) +#define GPR_POWER _IOW('g', 4, int) +#define GPR_RAM _IO('g', 5) +#define GPR_RESET _IO('g', 6) +#define GPR_SELECT _IO('g', 7) +#define GPR_STATUS _IO('g', 8) +#define GPR_TLV _IO('g', 9) + +#ifdef GPRDEBUG +int gprdebug; +#define DPRINTF(x) if (gprdebug) printf x +#else +#define DPRINTF(x) +#endif + +struct gpr_softc { + struct device sc_dev; + + struct pcmcia_function *sc_pf; + + bus_space_handle_t sc_ioh; + bus_space_tag_t sc_iot; + + struct pcmcia_io_handle sc_pioh; + int sc_iowin; + + bus_space_handle_t sc_memh; + bus_space_tag_t sc_memt; + + struct pcmcia_mem_handle sc_pmemh; + int sc_memwin; + bus_addr_t sc_offset; + + void * sc_ih; +}; + +int gpr_match(struct device *, void *, void *); +void gpr_attach(struct device *, struct device *, void *); +int gpr_detach(struct device *, int); +int gpr_activate(struct device *, enum devact); + +int gpr_open(dev_t, int, int, struct proc *); +int gpr_close(dev_t, int, int, struct proc *); +int gpr_ioctl(dev_t, u_long, caddr_t, int, struct proc *); +int gpr_intr(void *); + +int tlvput(struct gpr_softc *, u_long, u_int8_t *, int); + +struct cfattach gpr_ca = { + sizeof(struct gpr_softc), gpr_match, gpr_attach, gpr_detach, + gpr_activate +}; + +struct cfdriver gpr_cd = { + NULL, "gpr", DV_DULL +}; + +int +gpr_match(struct device *parent, void *match, void *aux) +{ + struct pcmcia_attach_args *pa = aux; + + if (pa->manufacturer == PCMCIA_VENDOR_GEMPLUS && + pa->product == PCMCIA_PRODUCT_GEMPLUS_GPR400) + return (1); + + return (0); +} + +void +gpr_attach(struct device *parent, struct device *self, void *aux) +{ + struct gpr_softc *sc = (void *)self; + struct pcmcia_attach_args *pa = aux; + struct pcmcia_config_entry *cfe; + + for (cfe = SIMPLEQ_FIRST(&pa->pf->cfe_head); cfe; + cfe = SIMPLEQ_NEXT(cfe, cfe_list)) { + + if (!pcmcia_io_alloc(pa->pf, cfe->iospace[0].start, + cfe->iospace[0].length, cfe->iospace[0].length, + &sc->sc_pioh)) + break; + } + + if (cfe == NULL) { + printf(": can't alloc i/o space\n"); + goto fail_io_alloc; + } + + pcmcia_function_init(pa->pf, cfe); + if (pcmcia_function_enable(pa->pf)) { + printf(": function enable failed\n"); + goto fail_enable; + } + + if (pcmcia_io_map(pa->pf, PCMCIA_WIDTH_AUTO, 0, + sc->sc_pioh.size, &sc->sc_pioh, &sc->sc_iowin)) { + printf(": can't map i/o space\n"); + goto fail_io_map; + } + + /* + * GPR400 has some registers in attribute memory as well. + */ + if (pcmcia_mem_alloc(pa->pf, GPR400_MEM_LEN, &sc->sc_pmemh)) { + printf(": can't map mem space\n"); + goto fail_mem_alloc; + } + + if (pcmcia_mem_map(pa->pf, PCMCIA_MEM_ATTR, pa->pf->ccr_base, + GPR400_MEM_LEN, &sc->sc_pmemh, &sc->sc_offset, &sc->sc_memwin)) { + printf(": can't map memory\n"); + goto fail_mem_map; + } + + sc->sc_pf = pa->pf; + sc->sc_iot = sc->sc_pioh.iot; + sc->sc_ioh = sc->sc_pioh.ioh; + sc->sc_memt = sc->sc_pmemh.memt; + sc->sc_memh = sc->sc_pmemh.memh; + + printf(" port 0x%lx/%d", sc->sc_pioh.addr, sc->sc_pioh.size); + + sc->sc_ih = pcmcia_intr_establish(pa->pf, IPL_TTY, gpr_intr, sc, + sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + printf(": couldn't establish interrupt\n"); + goto fail_intr; + } + + printf("\n"); + + return; + +fail_intr: + pcmcia_mem_unmap(pa->pf, sc->sc_memwin); +fail_mem_map: + pcmcia_mem_free(pa->pf, &sc->sc_pmemh); +fail_mem_alloc: + pcmcia_io_unmap(pa->pf, sc->sc_iowin); +fail_io_map: + pcmcia_function_disable(pa->pf); +fail_enable: + pcmcia_io_free(pa->pf, &sc->sc_pioh); +fail_io_alloc: + return; +} + +int +gpr_detach(struct device *dev, int flags) +{ + struct gpr_softc *sc = (struct gpr_softc *)dev; + + pcmcia_io_unmap(sc->sc_pf, sc->sc_iowin); + pcmcia_io_free(sc->sc_pf, &sc->sc_pioh); + + return (0); +} + +int +gpr_activate(struct device *dev, enum devact act) +{ + struct gpr_softc *sc = (struct gpr_softc *)dev; + + switch (act) { + case DVACT_ACTIVATE: + pcmcia_function_enable(sc->sc_pf); + sc->sc_ih = pcmcia_intr_establish(sc->sc_pf, IPL_TTY, + gpr_intr, sc, sc->sc_dev.dv_xname); + break; + + case DVACT_DEACTIVATE: + pcmcia_intr_disestablish(sc->sc_pf, sc->sc_ih); + pcmcia_function_disable(sc->sc_pf); + break; + } + + return (0); +} + +int +gpr_open(dev_t dev, int flags, int mode, struct proc *p) +{ + int unit = GPRUNIT(dev); + struct gpr_softc *sc; + int error; + + DPRINTF(("%s: flags %d, mode %d\n", __func__, flags, mode)); + + if (unit >= gpr_cd.cd_ndevs || + (sc = gpr_cd.cd_devs[unit]) == NULL) + return (ENXIO); + + if ((error = tlvput(sc, GPR_SELECT, "\x02", 1)) < 0) + return (error); + + return (0); +} + +int +gpr_close(dev_t dev, int flags, int mode, struct proc *p) +{ + int unit = GPRUNIT(dev); + struct gpr_softc *sc = gpr_cd.cd_devs[unit]; + int error; + + DPRINTF(("%s: flags %d, mode %d\n", __func__, flags, mode)); + + if ((error = tlvput(sc, GPR_CLOSE, "", 0)) < 0) + return (error); + + return (0); +} + +int +gpr_ioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p) +{ + int unit = GPRUNIT(dev); + struct gpr_softc *sc = gpr_cd.cd_devs[unit]; + int error; + + DPRINTF(("%s: cmd %d, flags 0x%x\n", __func__, cmd, flags)); + + switch (cmd) { + case GPR_RESET: + /* + * To reset and power up the reader, set bit 0 in the + * HAP register for at least 5us and wait for 20ms. + */ + bus_space_write_1(sc->sc_iot, sc->sc_ioh, GPR400_HAP_CTRL, + GPR400_RESET); + delay(10); + bus_space_write_1(sc->sc_iot, sc->sc_ioh, GPR400_HAP_CTRL, 0); + delay(20 * 1000); + /* FALLTHROUGH */ + + case GPR_SELECT: + if ((error = tlvput(sc, GPR400_SELECT, "\x02", 1)) < 0) + return (error); + error = 0; + break; + + case GPR_POWER: + { + u_int8_t *mode; + + if (*(int *)addr) + mode = "\x01"; /* Standby */ + else + mode = "\x00"; /* Power down */ + + if ((error = tlvput(sc, GPR400_POWER, mode, 1)) < 0) + return (error); + error = 0; + } + break; + + case GPR_CLOSE: + if ((error = tlvput(sc, GPR400_CLOSE, "", 0)) < 0) + return (error); + error = 0; + break; + + case GPR_CMD: + case GPR_OPEN: + case GPR_RAM: + case GPR_STATUS: + case GPR_TLV: + default: + error = EINVAL; + break; + }; + + return (error); +} + +int +gpr_intr(void *arg) +{ + struct gpr_softc *sc = arg; + u_int8_t val; + + DPRINTF(("%s: got interrupt\n", __func__)); + + /* Ack interrupt */ + val = bus_space_read_1(sc->sc_iot, sc->sc_ioh, GPR400_HAP_CTRL); + bus_space_write_1(sc->sc_iot, sc->sc_ioh, GPR400_HAP_CTRL, + val & ~GPR400_INTR); + + wakeup(sc); + + return (1); +} + +int +tlvput(struct gpr_softc *sc, u_long cmd, u_int8_t *data, int len) +{ + int i, resid = 1; + + DPRINTF(("%s: cmd 0x%x, data %p, len %d\n", __func__, + cmd, data, len)); + + for (i = 0; resid || i < len; i += 28) { + int j, ret; + int s; + + resid = len - i - 28; + if (resid < 0) + resid = 0; + + if (resid) + cmd |= GPR400_CONT; + else + cmd &= ~GPR400_CONT; + + DPRINTF(("%s: sending cmd 0x%x, len %d, resid %d\n", + __func__, cmd, len - resid, resid)); + + bus_space_write_1(sc->sc_iot, sc->sc_ioh, 0x02, cmd); + bus_space_write_1(sc->sc_iot, sc->sc_ioh, 0x03, len - resid); + + for (j = 0; j < len - resid; j++) { + bus_space_write_1(sc->sc_iot, sc->sc_ioh, + 0x04 + j, *data++); + } + + s = spltty(); + + /* Tell the reader to process this command. */ + bus_space_write_1(sc->sc_iot, sc->sc_ioh, GPR400_HAP_CTRL, + GPR400_IREQ); + + tsleep(sc, PCATCH, "tlvput", 0); + + splx(s); + + /* Read the status. */ + ret = bus_space_read_1(sc->sc_iot, sc->sc_ioh, 0x04); + + DPRINTF(("%s: ret %d\n", __func__, ret)); + + if (ret != 0x00 || (!resid && ret != 0xe7)) + return (EIO); + } + + return (0); +} |