summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorFederico G. Schwindt <fgsch@cvs.openbsd.org>2002-06-15 05:56:00 +0000
committerFederico G. Schwindt <fgsch@cvs.openbsd.org>2002-06-15 05:56:00 +0000
commita52e8989f333415b7df95e47c440c265a38b81c5 (patch)
tree61939e2ecc21989ec201eaac3769803ceba462ac /sys
parent8fbd715c239af8c9ead2a7d2c549ba8047d47e32 (diff)
GPR400 smartcard reader driver, some stuff still missing.
jason@ commented on it.
Diffstat (limited to 'sys')
-rw-r--r--sys/dev/pcmcia/files.pcmcia7
-rw-r--r--sys/dev/pcmcia/gpr.c439
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);
+}