/*- * Copyright (c) 2017 Ilya Bakulin. All rights reserved. * Copyright (c) 2018-2019 The FreeBSD Foundation * * Portions of this software were developed by Björn Zeeb * under sponsorship from the FreeBSD Foundation. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. * * * Portions of this software may have been developed with reference to * the SD Simplified Specification. The following disclaimer may apply: * * The following conditions apply to the release of the simplified * specification ("Simplified Specification") by the SD Card Association and * the SD Group. The Simplified Specification is a subset of the complete SD * Specification which is owned by the SD Card Association and the SD * Group. This Simplified Specification is provided on a non-confidential * basis subject to the disclaimers below. Any implementation of the * Simplified Specification may require a license from the SD Card * Association, SD Group, SD-3C LLC or other third parties. * * Disclaimers: * * The information contained in the Simplified Specification is presented only * as a standard specification for SD Cards and SD Host/Ancillary products and * is provided "AS-IS" without any representations or warranties of any * kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD * Card Association for any damages, any infringements of patents or other * right of the SD Group, SD-3C LLC, the SD Card Association or any third * parties, which may result from its use. No license is granted by * implication, estoppel or otherwise under any patent or other rights of the * SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing * herein shall be construed as an obligation by the SD Group, the SD-3C LLC * or the SD Card Association to disclose or distribute any technical * information, know-how or other confidential information to any third party. */ /* * Implements the (kernel specific) SDIO parts. * This will hide all cam(4) functionality from the SDIO driver implementations * which will just be newbus/device(9) and hence look like any other driver for, * e.g., PCI. * The sdiob(4) parts effetively "translate" between the two worlds "bridging" * messages from MMCCAM to newbus and back. */ #include __FBSDID("$FreeBSD$"); #include "opt_cam.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for cam_path */ #include #include #include #include #include "sdio_if.h" #ifdef DEBUG #define DPRINTF(...) printf(__VA_ARGS__) #define DPRINTFDEV(_dev, ...) device_printf((_dev), __VA_ARGS__) #else #define DPRINTF(...) #define DPRINTFDEV(_dev, ...) #endif struct sdiob_softc { uint32_t sdio_state; #define SDIO_STATE_DEAD 0x0001 #define SDIO_STATE_INITIALIZING 0x0002 #define SDIO_STATE_READY 0x0004 uint32_t nb_state; #define NB_STATE_DEAD 0x0001 #define NB_STATE_SIM_ADDED 0x0002 #define NB_STATE_READY 0x0004 /* CAM side (including sim_dev). */ struct card_info cardinfo; struct cam_periph *periph; union ccb *ccb; struct task discover_task; /* Newbus side. */ device_t dev; /* Ourselves. */ device_t child[8]; }; /* -------------------------------------------------------------------------- */ /* * SDIO CMD52 and CM53 implementations along with wrapper functions for * read/write and a CAM periph helper function. * These are the backend implementations of the sdio_if.m framework talking * through CAM to sdhci. * Note: these functions are also called during early discovery stage when * we are not a device(9) yet. Hence they cannot always use device_printf() * to log errors and have to call CAM_DEBUG() during these early stages. */ static int sdioerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) { return (cam_periph_error(ccb, cam_flags, sense_flags)); } /* CMD52: direct byte access. */ static int sdiob_rw_direct_sc(struct sdiob_softc *sc, uint8_t fn, uint32_t addr, bool wr, uint8_t *val) { uint32_t arg, flags; int error; KASSERT((val != NULL), ("%s val passed as NULL\n", __func__)); if (sc->ccb == NULL) sc->ccb = xpt_alloc_ccb(); else memset(sc->ccb, 0, sizeof(*sc->ccb)); xpt_setup_ccb(&sc->ccb->ccb_h, sc->periph->path, CAM_PRIORITY_NONE); CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_TRACE, ("%s(fn=%d, addr=%#02x, wr=%d, *val=%#02x)\n", __func__, fn, addr, wr, *val)); flags = MMC_RSP_R5 | MMC_CMD_AC; arg = SD_IO_RW_FUNC(fn) | SD_IO_RW_ADR(addr); if (wr) arg |= SD_IO_RW_WR | SD_IO_RW_RAW | SD_IO_RW_DAT(*val); cam_fill_mmcio(&sc->ccb->mmcio, /*retries*/ 0, /*cbfcnp*/ NULL, /*flags*/ CAM_DIR_NONE, /*mmc_opcode*/ SD_IO_RW_DIRECT, /*mmc_arg*/ arg, /*mmc_flags*/ flags, /*mmc_data*/ 0, /*timeout*/ sc->cardinfo.f[fn].timeout); error = cam_periph_runccb(sc->ccb, sdioerror, CAM_FLAG_NONE, 0, NULL); if (error != 0) { if (sc->dev != NULL) device_printf(sc->dev, "%s: Failed to %s address %#10x error=%d\n", __func__, (wr) ? "write" : "read", addr, error); else CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_INFO, ("%s: Failed to %s address: %#10x error=%d\n", __func__, (wr) ? "write" : "read", addr, error)); return (error); } /* TODO: Add handling of MMC errors */ /* ccb->mmcio.cmd.error ? */ if (wr == false) *val = sc->ccb->mmcio.cmd.resp[0] & 0xff; return (0); } static int sdio_rw_direct(device_t dev, uint8_t fn, uint32_t addr, bool wr, uint8_t *val) { struct sdiob_softc *sc; int error; sc = device_get_softc(dev); cam_periph_lock(sc->periph); error = sdiob_rw_direct_sc(sc, fn, addr, wr, val); cam_periph_unlock(sc->periph); return (error); } static int sdiob_read_direct(device_t dev, uint8_t fn, uint32_t addr, uint8_t *val) { int error; uint8_t v; error = sdio_rw_direct(dev, fn, addr, false, &v); /* Be polite and do not touch the value on read error. */ if (error == 0 && val != NULL) *val = v; return (error); } static int sdiob_write_direct(device_t dev, uint8_t fn, uint32_t addr, uint8_t val) { return (sdio_rw_direct(dev, fn, addr, true, &val)); } /* * CMD53: IO_RW_EXTENDED, read and write multiple I/O registers. * Increment false gets FIFO mode (single register address). */ /* * A b_count of 0 means byte mode, b_count > 0 gets block mode. * A b_count of >= 512 would mean infinitive block transfer, which would become * b_count = 0, is not yet supported. * For b_count == 0, blksz is the len of bytes, otherwise it is the amount of * full sized blocks (you must not round the blocks up and leave the last one * partial!) * For byte mode, the maximum of blksz is the functions cur_blksize. * This function should ever only be called by sdio_rw_extended_sc()! */ static int sdiob_rw_extended_cam(struct sdiob_softc *sc, uint8_t fn, uint32_t addr, bool wr, uint8_t *buffer, bool incaddr, uint32_t b_count, uint16_t blksz) { struct mmc_data mmcd; uint32_t arg, cam_flags, flags, len; int error; if (sc->ccb == NULL) sc->ccb = xpt_alloc_ccb(); else memset(sc->ccb, 0, sizeof(*sc->ccb)); xpt_setup_ccb(&sc->ccb->ccb_h, sc->periph->path, CAM_PRIORITY_NONE); CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_TRACE, ("%s(fn=%d addr=%#0x wr=%d b_count=%u blksz=%u buf=%p incr=%d)\n", __func__, fn, addr, wr, b_count, blksz, buffer, incaddr)); KASSERT((b_count <= 511), ("%s: infinitive block transfer not yet " "supported: b_count %u blksz %u, sc %p, fn %u, addr %#10x, %s, " "buffer %p, %s\n", __func__, b_count, blksz, sc, fn, addr, wr ? "wr" : "rd", buffer, incaddr ? "incaddr" : "fifo")); /* Blksz needs to be within bounds for both byte and block mode! */ KASSERT((blksz <= sc->cardinfo.f[fn].cur_blksize), ("%s: blksz " "%u > bur_blksize %u, sc %p, fn %u, addr %#10x, %s, " "buffer %p, %s, b_count %u\n", __func__, blksz, sc->cardinfo.f[fn].cur_blksize, sc, fn, addr, wr ? "wr" : "rd", buffer, incaddr ? "incaddr" : "fifo", b_count)); if (b_count == 0) { /* Byte mode */ len = blksz; if (blksz == 512) blksz = 0; arg = SD_IOE_RW_LEN(blksz); } else { /* Block mode. */ #ifdef __notyet__ if (b_count > 511) { /* Infinitive block transfer. */ b_count = 0; } #endif len = b_count * blksz; arg = SD_IOE_RW_BLK | SD_IOE_RW_LEN(b_count); } flags = MMC_RSP_R5 | MMC_CMD_ADTC; arg |= SD_IOE_RW_FUNC(fn) | SD_IOE_RW_ADR(addr); if (incaddr) arg |= SD_IOE_RW_INCR; memset(&mmcd, 0, sizeof(mmcd)); mmcd.data = buffer; mmcd.len = len; if (arg & SD_IOE_RW_BLK) { /* XXX both should be known from elsewhere, aren't they? */ mmcd.block_size = blksz; mmcd.block_count = b_count; } if (wr) { arg |= SD_IOE_RW_WR; cam_flags = CAM_DIR_OUT; mmcd.flags = MMC_DATA_WRITE; } else { cam_flags = CAM_DIR_IN; mmcd.flags = MMC_DATA_READ; } #ifdef __notyet__ if (b_count == 0) { /* XXX-BZ TODO FIXME. Cancel I/O: CCCR -> ASx */ /* Stop cmd. */ } #endif cam_fill_mmcio(&sc->ccb->mmcio, /*retries*/ 0, /*cbfcnp*/ NULL, /*flags*/ cam_flags, /*mmc_opcode*/ SD_IO_RW_EXTENDED, /*mmc_arg*/ arg, /*mmc_flags*/ flags, /*mmc_data*/ &mmcd, /*timeout*/ sc->cardinfo.f[fn].timeout); if (arg & SD_IOE_RW_BLK) { mmcd.flags |= MMC_DATA_BLOCK_SIZE; if (b_count != 1) sc->ccb->mmcio.cmd.data->flags |= MMC_DATA_MULTI; } /* Execute. */ error = cam_periph_runccb(sc->ccb, sdioerror, CAM_FLAG_NONE, 0, NULL); if (error != 0) { if (sc->dev != NULL) device_printf(sc->dev, "%s: Failed to %s address %#10x buffer %p size %u " "%s b_count %u blksz %u error=%d\n", __func__, (wr) ? "write to" : "read from", addr, buffer, len, (incaddr) ? "incr" : "fifo", b_count, blksz, error); else CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_INFO, ("%s: Failed to %s address %#10x buffer %p size %u " "%s b_count %u blksz %u error=%d\n", __func__, (wr) ? "write to" : "read from", addr, buffer, len, (incaddr) ? "incr" : "fifo", b_count, blksz, error)); return (error); } /* TODO: Add handling of MMC errors */ /* ccb->mmcio.cmd.error ? */ error = sc->ccb->mmcio.cmd.resp[0] & 0xff; if (error != 0) { if (sc->dev != NULL) device_printf(sc->dev, "%s: Failed to %s address %#10x buffer %p size %u " "%s b_count %u blksz %u mmcio resp error=%d\n", __func__, (wr) ? "write to" : "read from", addr, buffer, len, (incaddr) ? "incr" : "fifo", b_count, blksz, error); else CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_INFO, ("%s: Failed to %s address %#10x buffer %p size %u " "%s b_count %u blksz %u mmcio resp error=%d\n", __func__, (wr) ? "write to" : "read from", addr, buffer, len, (incaddr) ? "incr" : "fifo", b_count, blksz, error)); } return (error); } static int sdiob_rw_extended_sc(struct sdiob_softc *sc, uint8_t fn, uint32_t addr, bool wr, uint32_t size, uint8_t *buffer, bool incaddr) { int error; uint32_t len; uint32_t b_count; /* * If block mode is supported and we have at least 4 bytes to write and * the size is at least one block, then start doing blk transfers. */ while (sc->cardinfo.support_multiblk && size > 4 && size >= sc->cardinfo.f[fn].cur_blksize) { b_count = size / sc->cardinfo.f[fn].cur_blksize; KASSERT(b_count >= 1, ("%s: block count too small %u size %u " "cur_blksize %u\n", __func__, b_count, size, sc->cardinfo.f[fn].cur_blksize)); #ifdef __notyet__ /* XXX support inifinite transfer with b_count = 0. */ #else if (b_count > 511) b_count = 511; #endif len = b_count * sc->cardinfo.f[fn].cur_blksize; error = sdiob_rw_extended_cam(sc, fn, addr, wr, buffer, incaddr, b_count, sc->cardinfo.f[fn].cur_blksize); if (error != 0) return (error); size -= len; buffer += len; if (incaddr) addr += len; } while (size > 0) { len = MIN(size, sc->cardinfo.f[fn].cur_blksize); error = sdiob_rw_extended_cam(sc, fn, addr, wr, buffer, incaddr, 0, len); if (error != 0) return (error); /* Prepare for next iteration. */ size -= len; buffer += len; if (incaddr) addr += len; } return (0); } static int sdiob_rw_extended(device_t dev, uint8_t fn, uint32_t addr, bool wr, uint32_t size, uint8_t *buffer, bool incaddr) { struct sdiob_softc *sc; int error; sc = device_get_softc(dev); cam_periph_lock(sc->periph); error = sdiob_rw_extended_sc(sc, fn, addr, wr, size, buffer, incaddr); cam_periph_unlock(sc->periph); return (error); } static int sdiob_read_extended(device_t dev, uint8_t fn, uint32_t addr, uint32_t size, uint8_t *buffer, bool incaddr) { return (sdiob_rw_extended(dev, fn, addr, false, size, buffer, incaddr)); } static int sdiob_write_extended(device_t dev, uint8_t fn, uint32_t addr, uint32_t size, uint8_t *buffer, bool incaddr) { return (sdiob_rw_extended(dev, fn, addr, true, size, buffer, incaddr)); } /* -------------------------------------------------------------------------- */ /* Bus interface, ivars handling. */ static int sdiob_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct sdiob_softc *sc; struct sdio_func *f; f = device_get_ivars(child); KASSERT(f != NULL, ("%s: dev %p child %p which %d, child ivars NULL\n", __func__, dev, child, which)); switch (which) { case SDIOB_IVAR_SUPPORT_MULTIBLK: sc = device_get_softc(dev); KASSERT(sc != NULL, ("%s: dev %p child %p which %d, sc NULL\n", __func__, dev, child, which)); *result = sc->cardinfo.support_multiblk; break; case SDIOB_IVAR_FUNCTION: *result = (uintptr_t)f; break; case SDIOB_IVAR_FUNCNUM: *result = f->fn; break; case SDIOB_IVAR_CLASS: *result = f->class; break; case SDIOB_IVAR_VENDOR: *result = f->vendor; break; case SDIOB_IVAR_DEVICE: *result = f->device; break; case SDIOB_IVAR_DRVDATA: *result = f->drvdata; break; default: return (ENOENT); } return (0); } static int sdiob_write_ivar(device_t dev, device_t child, int which, uintptr_t value) { struct sdio_func *f; f = device_get_ivars(child); KASSERT(f != NULL, ("%s: dev %p child %p which %d, child ivars NULL\n", __func__, dev, child, which)); switch (which) { case SDIOB_IVAR_SUPPORT_MULTIBLK: case SDIOB_IVAR_FUNCTION: case SDIOB_IVAR_FUNCNUM: case SDIOB_IVAR_CLASS: case SDIOB_IVAR_VENDOR: case SDIOB_IVAR_DEVICE: return (EINVAL); /* Disallowed. */ case SDIOB_IVAR_DRVDATA: f->drvdata = value; break; default: return (ENOENT); } return (0); } /* -------------------------------------------------------------------------- */ /* * Newbus functions for ourselves to probe/attach/detach and become a proper * device(9). Attach will also probe for child devices (another driver * implementing SDIO). */ static int sdiob_probe(device_t dev) { device_set_desc(dev, "SDIO CAM-Newbus bridge"); return (BUS_PROBE_DEFAULT); } static int sdiob_attach(device_t dev) { struct sdiob_softc *sc; int error, i; sc = device_get_softc(dev); if (sc == NULL) return (ENXIO); /* * Now that we are a dev, create one child device per function, * initialize the backpointer, so we can pass them around and * call CAM operations on the parent, and also set the function * itself as ivars, so that we can query/update them. * Do this before any child gets a chance to attach. */ for (i = 0; i < sc->cardinfo.num_funcs; i++) { sc->child[i] = device_add_child(dev, NULL, -1); if (sc->child[i] == NULL) { device_printf(dev, "%s: failed to add child\n", __func__); return (ENXIO); } sc->cardinfo.f[i].dev = sc->child[i]; /* Set the function as ivar to the child device. */ device_set_ivars(sc->child[i], &sc->cardinfo.f[i]); } /* * No one will ever attach to F0; we do the above to have a "device" * to talk to in a general way in the code. * Also do the probe/attach in a 2nd loop, so that all devices are * present as we do have drivers consuming more than one device/func * and might play "tricks" in order to do that assuming devices and * ivars are available for all. */ for (i = 1; i < sc->cardinfo.num_funcs; i++) { error = device_probe_and_attach(sc->child[i]); if (error != 0 && bootverbose) device_printf(dev, "%s: device_probe_and_attach(%p %s) " "failed %d for function %d, no child yet\n", __func__, sc->child, device_get_nameunit(sc->child[i]), error, i); } sc->nb_state = NB_STATE_READY; cam_periph_lock(sc->periph); xpt_announce_periph(sc->periph, NULL); cam_periph_unlock(sc->periph); return (0); } static int sdiob_detach(device_t dev) { /* XXX TODO? */ return (EOPNOTSUPP); } /* -------------------------------------------------------------------------- */ /* * driver(9) and device(9) "control plane". * This is what we use when we are making ourselves a device(9) in order to * provide a newbus interface again, as well as the implementation of the * SDIO interface. */ static device_method_t sdiob_methods[] = { /* Device interface. */ DEVMETHOD(device_probe, sdiob_probe), DEVMETHOD(device_attach, sdiob_attach), DEVMETHOD(device_detach, sdiob_detach), /* Bus interface. */ DEVMETHOD(bus_add_child, bus_generic_add_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), DEVMETHOD(bus_read_ivar, sdiob_read_ivar), DEVMETHOD(bus_write_ivar, sdiob_write_ivar), /* SDIO interface. */ DEVMETHOD(sdio_read_direct, sdiob_read_direct), DEVMETHOD(sdio_write_direct, sdiob_write_direct), DEVMETHOD(sdio_read_extended, sdiob_read_extended), DEVMETHOD(sdio_write_extended, sdiob_write_extended), DEVMETHOD_END }; static devclass_t sdiob_devclass; static driver_t sdiob_driver = { SDIOB_NAME_S, sdiob_methods, 0 }; /* -------------------------------------------------------------------------- */ /* * CIS related. * Read card and function information and populate the cardinfo structure. */ static int sdio_read_direct_sc(struct sdiob_softc *sc, uint8_t fn, uint32_t addr, uint8_t *val) { int error; uint8_t v; error = sdiob_rw_direct_sc(sc, fn, addr, false, &v); if (error == 0 && val != NULL) *val = v; return (error); } static int sdio_func_read_cis(struct sdiob_softc *sc, uint8_t fn, uint32_t cis_addr) { char cis1_info_buf[256]; char *cis1_info[4]; int start, i, count, ret; uint32_t addr; uint8_t ch, tuple_id, tuple_len, tuple_count, v; /* If we encounter any read errors, abort and return. */ #define ERR_OUT(ret) \ if (ret != 0) \ goto err; ret = 0; /* Use to prevent infinite loop in case of parse errors. */ tuple_count = 0; memset(cis1_info_buf, 0, 256); do { addr = cis_addr; ret = sdio_read_direct_sc(sc, 0, addr++, &tuple_id); ERR_OUT(ret); if (tuple_id == SD_IO_CISTPL_END) break; if (tuple_id == 0) { cis_addr++; continue; } ret = sdio_read_direct_sc(sc, 0, addr++, &tuple_len); ERR_OUT(ret); if (tuple_len == 0) { CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_PERIPH, ("%s: parse error: 0-length tuple %#02x\n", __func__, tuple_id)); return (EIO); } switch (tuple_id) { case SD_IO_CISTPL_VERS_1: addr += 2; for (count = 0, start = 0, i = 0; (count < 4) && ((i + 4) < 256); i++) { ret = sdio_read_direct_sc(sc, 0, addr + i, &ch); ERR_OUT(ret); DPRINTF("%s: count=%d, start=%d, i=%d, got " "(%#02x)\n", __func__, count, start, i, ch); if (ch == 0xff) break; cis1_info_buf[i] = ch; if (ch == 0) { cis1_info[count] = cis1_info_buf + start; start = i + 1; count++; } } DPRINTF("Card info: "); for (i=0; i < 4; i++) if (cis1_info[i]) DPRINTF(" %s", cis1_info[i]); DPRINTF("\n"); break; case SD_IO_CISTPL_MANFID: /* TPLMID_MANF */ ret = sdio_read_direct_sc(sc, 0, addr++, &v); ERR_OUT(ret); sc->cardinfo.f[fn].vendor = v; ret = sdio_read_direct_sc(sc, 0, addr++, &v); ERR_OUT(ret); sc->cardinfo.f[fn].vendor |= (v << 8); /* TPLMID_CARD */ ret = sdio_read_direct_sc(sc, 0, addr++, &v); ERR_OUT(ret); sc->cardinfo.f[fn].device = v; ret = sdio_read_direct_sc(sc, 0, addr, &v); ERR_OUT(ret); sc->cardinfo.f[fn].device |= (v << 8); break; case SD_IO_CISTPL_FUNCID: /* Not sure if we need to parse it? */ break; case SD_IO_CISTPL_FUNCE: if (tuple_len < 4) { printf("%s: FUNCE is too short: %d\n", __func__, tuple_len); break; } /* TPLFE_TYPE (Extended Data) */ ret = sdio_read_direct_sc(sc, 0, addr++, &v); ERR_OUT(ret); if (fn == 0) { if (v != 0x00) break; } else { if (v != 0x01) break; addr += 0x0b; } ret = sdio_read_direct_sc(sc, 0, addr, &v); ERR_OUT(ret); sc->cardinfo.f[fn].max_blksize = v; ret = sdio_read_direct_sc(sc, 0, addr+1, &v); ERR_OUT(ret); sc->cardinfo.f[fn].max_blksize |= (v << 8); break; default: CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_PERIPH, ("%s: Skipping fn %d tuple %d ID %#02x " "len %#02x\n", __func__, fn, tuple_count, tuple_id, tuple_len)); } if (tuple_len == 0xff) { /* Also marks the end of a tuple chain (E1 16.2) */ /* The tuple is valid, hence this going at the end. */ break; } cis_addr += 2 + tuple_len; tuple_count++; } while (tuple_count < 20); err: #undef ERR_OUT return (ret); } static int sdio_get_common_cis_addr(struct sdiob_softc *sc, uint32_t *addr) { int error; uint32_t a; uint8_t val; error = sdio_read_direct_sc(sc, 0, SD_IO_CCCR_CISPTR + 0, &val); if (error != 0) goto err; a = val; error = sdio_read_direct_sc(sc, 0, SD_IO_CCCR_CISPTR + 1, &val); if (error != 0) goto err; a |= (val << 8); error = sdio_read_direct_sc(sc, 0, SD_IO_CCCR_CISPTR + 2, &val); if (error != 0) goto err; a |= (val << 16); if (a < SD_IO_CIS_START || a > SD_IO_CIS_START + SD_IO_CIS_SIZE) { err: CAM_DEBUG(sc->ccb->ccb_h.path, CAM_DEBUG_PERIPH, ("%s: bad CIS address: %#04x, error %d\n", __func__, a, error)); } else if (error == 0 && addr != NULL) *addr = a; return (error); } static int sdiob_get_card_info(struct sdiob_softc *sc) { struct mmc_params *mmcp; uint32_t cis_addr, fbr_addr; int fn, error; uint8_t fn_max, val; error = sdio_get_common_cis_addr(sc, &cis_addr); if (error != 0) return (-1); memset(&sc->cardinfo, 0, sizeof(sc->cardinfo)); /* F0 must always be present. */ fn = 0; error = sdio_func_read_cis(sc, fn, cis_addr); if (error != 0) return (error); sc->cardinfo.num_funcs++; /* Read CCCR Card Capability. */ error = sdio_read_direct_sc(sc, 0, SD_IO_CCCR_CARDCAP, &val); if (error != 0) return (error); sc->cardinfo.support_multiblk = (val & CCCR_CC_SMB) ? true : false; DPRINTF("%s: F%d: Vendor %#04x product %#04x max block size %d bytes " "support_multiblk %s\n", __func__, fn, sc->cardinfo.f[fn].vendor, sc->cardinfo.f[fn].device, sc->cardinfo.f[fn].max_blksize, sc->cardinfo.support_multiblk ? "yes" : "no"); /* mmcp->sdio_func_count contains the number of functions w/o F0. */ mmcp = &sc->ccb->ccb_h.path->device->mmc_ident_data; fn_max = MIN(mmcp->sdio_func_count + 1, nitems(sc->cardinfo.f)); for (fn = 1; fn < fn_max; fn++) { fbr_addr = SD_IO_FBR_START * fn + SD_IO_FBR_CIS_OFFSET; error = sdio_read_direct_sc(sc, 0, fbr_addr++, &val); if (error != 0) break; cis_addr = val; error = sdio_read_direct_sc(sc, 0, fbr_addr++, &val); if (error != 0) break; cis_addr |= (val << 8); error = sdio_read_direct_sc(sc, 0, fbr_addr++, &val); if (error != 0) break; cis_addr |= (val << 16); error = sdio_func_read_cis(sc, fn, cis_addr); if (error != 0) break; /* Read the Standard SDIO Function Interface Code. */ fbr_addr = SD_IO_FBR_START * fn; error = sdio_read_direct_sc(sc, 0, fbr_addr++, &val); if (error != 0) break; sc->cardinfo.f[fn].class = (val & 0x0f); if (sc->cardinfo.f[fn].class == 0x0f) { error = sdio_read_direct_sc(sc, 0, fbr_addr, &val); if (error != 0) break; sc->cardinfo.f[fn].class = val; } sc->cardinfo.f[fn].fn = fn; sc->cardinfo.f[fn].cur_blksize = sc->cardinfo.f[fn].max_blksize; sc->cardinfo.f[fn].retries = 0; sc->cardinfo.f[fn].timeout = 5000; DPRINTF("%s: F%d: Class %d Vendor %#04x product %#04x " "max_blksize %d bytes\n", __func__, fn, sc->cardinfo.f[fn].class, sc->cardinfo.f[fn].vendor, sc->cardinfo.f[fn].device, sc->cardinfo.f[fn].max_blksize); if (sc->cardinfo.f[fn].vendor == 0) { DPRINTF("%s: F%d doesn't exist\n", __func__, fn); break; } sc->cardinfo.num_funcs++; } return (error); } /* -------------------------------------------------------------------------- */ /* * CAM periph registration, allocation, and detached from that a discovery * task, which goes off reads cardinfo, and then adds ourselves to our SIM's * device adding the devclass and registering the driver. This keeps the * newbus chain connected though we will talk CAM in the middle (until one * day CAM might be newbusyfied). */ static int sdio_newbus_sim_add(struct sdiob_softc *sc) { device_t pdev; devclass_t bus_devclass; int error; /* Add ourselves to our parent (SIM) device. */ /* Add ourselves to our parent. That way we can become a parent. */ KASSERT(sc->periph->sim->sim_dev != NULL, ("%s: sim_dev is NULL, sc %p " "periph %p sim %p\n", __func__, sc, sc->periph, sc->periph->sim)); if (sc->dev == NULL) sc->dev = BUS_ADD_CHILD(sc->periph->sim->sim_dev, 0, SDIOB_NAME_S, -1); if (sc->dev == NULL) return (ENXIO); device_set_softc(sc->dev, sc); /* * Don't set description here; devclass_add_driver() -> * device_probe_child() -> device_set_driver() will nuke it again. */ pdev = device_get_parent(sc->dev); KASSERT(pdev != NULL, ("%s: sc %p dev %p (%s) parent is NULL\n", __func__, sc, sc->dev, device_get_nameunit(sc->dev))); bus_devclass = device_get_devclass(pdev); if (bus_devclass == NULL) { printf("%s: Failed to get devclass from %s.\n", __func__, device_get_nameunit(pdev)); return (ENXIO); } mtx_lock(&Giant); error = devclass_add_driver(bus_devclass, &sdiob_driver, BUS_PASS_DEFAULT, &sdiob_devclass); mtx_unlock(&Giant); if (error != 0) { printf("%s: Failed to add driver to devclass: %d.\n", __func__, error); return (error); } /* Done. */ sc->nb_state = NB_STATE_SIM_ADDED; return (0); } static void sdiobdiscover(void *context, int pending) { struct cam_periph *periph; struct sdiob_softc *sc; int error; KASSERT(context != NULL, ("%s: context is NULL\n", __func__)); periph = (struct cam_periph *)context; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s\n", __func__)); /* Periph was held for us when this task was enqueued. */ if ((periph->flags & CAM_PERIPH_INVALID) != 0) { cam_periph_release(periph); return; } sc = periph->softc; sc->sdio_state = SDIO_STATE_INITIALIZING; if (sc->ccb == NULL) sc->ccb = xpt_alloc_ccb(); else memset(sc->ccb, 0, sizeof(*sc->ccb)); xpt_setup_ccb(&sc->ccb->ccb_h, periph->path, CAM_PRIORITY_NONE); /* * Read CCCR and FBR of each function, get manufacturer and device IDs, * max block size, and whatever else we deem necessary. */ cam_periph_lock(periph); error = sdiob_get_card_info(sc); if (error == 0) sc->sdio_state = SDIO_STATE_READY; else sc->sdio_state = SDIO_STATE_DEAD; cam_periph_unlock(periph); if (error) return; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s: num_func %d\n", __func__, sc->cardinfo.num_funcs)); /* * Now CAM portion of the driver has been initialized and * we know VID/PID of all the functions on the card. * Time to hook into the newbus. */ error = sdio_newbus_sim_add(sc); if (error != 0) sc->nb_state = NB_STATE_DEAD; return; } /* Called at the end of cam_periph_alloc() for us to finish allocation. */ static cam_status sdiobregister(struct cam_periph *periph, void *arg) { struct sdiob_softc *sc; int error; CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s: arg %p\n", __func__, arg)); if (arg == NULL) { printf("%s: no getdev CCB, can't register device pariph %p\n", __func__, periph); return(CAM_REQ_CMP_ERR); } if (periph->sim == NULL || periph->sim->sim_dev == NULL) { printf("%s: no sim %p or sim_dev %p\n", __func__, periph->sim, (periph->sim != NULL) ? periph->sim->sim_dev : NULL); return(CAM_REQ_CMP_ERR); } sc = (struct sdiob_softc *) malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT|M_ZERO); if (sc == NULL) { printf("%s: unable to allocate sc\n", __func__); return (CAM_REQ_CMP_ERR); } sc->sdio_state = SDIO_STATE_DEAD; sc->nb_state = NB_STATE_DEAD; TASK_INIT(&sc->discover_task, 0, sdiobdiscover, periph); /* Refcount until we are setup. Can't block. */ error = cam_periph_hold(periph, PRIBIO); if (error != 0) { printf("%s: lost periph during registration!\n", __func__); free(sc, M_DEVBUF); return(CAM_REQ_CMP_ERR); } periph->softc = sc; sc->periph = periph; cam_periph_unlock(periph); error = taskqueue_enqueue(taskqueue_thread, &sc->discover_task); cam_periph_lock(periph); /* We will continue to hold a refcount for discover_task. */ /* cam_periph_unhold(periph); */ xpt_schedule(periph, CAM_PRIORITY_XPT); return (CAM_REQ_CMP); } static void sdioboninvalidate(struct cam_periph *periph) { CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s:\n", __func__)); return; } static void sdiobcleanup(struct cam_periph *periph) { CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s:\n", __func__)); return; } static void sdiobstart(struct cam_periph *periph, union ccb *ccb) { CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("%s: ccb %p\n", __func__, ccb)); return; } static void sdiobasync(void *softc, uint32_t code, struct cam_path *path, void *arg) { struct cam_periph *periph; struct ccb_getdev *cgd; cam_status status; periph = (struct cam_periph *)softc; CAM_DEBUG(path, CAM_DEBUG_TRACE, ("%s(code=%d)\n", __func__, code)); switch (code) { case AC_FOUND_DEVICE: if (arg == NULL) break; cgd = (struct ccb_getdev *)arg; if (cgd->protocol != PROTO_MMCSD) break; /* We do not support SD memory (Combo) Cards. */ if ((path->device->mmc_ident_data.card_features & CARD_FEATURE_MEMORY)) { CAM_DEBUG(path, CAM_DEBUG_TRACE, ("Memory card, not interested\n")); break; } /* * Allocate a peripheral instance for this device which starts * the probe process. */ status = cam_periph_alloc(sdiobregister, sdioboninvalidate, sdiobcleanup, sdiobstart, SDIOB_NAME_S, CAM_PERIPH_BIO, path, sdiobasync, AC_FOUND_DEVICE, cgd); if (status != CAM_REQ_CMP && status != CAM_REQ_INPROG) CAM_DEBUG(path, CAM_DEBUG_PERIPH, ("%s: Unable to attach to new device due to " "status %#02x\n", __func__, status)); break; default: CAM_DEBUG(path, CAM_DEBUG_PERIPH, ("%s: cannot handle async code %#02x\n", __func__, code)); cam_periph_async(periph, code, path, arg); break; } } static void sdiobinit(void) { cam_status status; /* * Register for new device notification. We will be notified for all * already existing ones. */ status = xpt_register_async(AC_FOUND_DEVICE, sdiobasync, NULL, NULL); if (status != CAM_REQ_CMP) printf("%s: Failed to attach async callback, statux %#02x", __func__, status); } /* This function will allow unloading the KLD. */ static int sdiobdeinit(void) { return (EOPNOTSUPP); } static struct periph_driver sdiobdriver = { .init = sdiobinit, .driver_name = SDIOB_NAME_S, .units = TAILQ_HEAD_INITIALIZER(sdiobdriver.units), .generation = 0, .flags = 0, .deinit = sdiobdeinit, }; PERIPHDRIVER_DECLARE(SDIOB_NAME, sdiobdriver); MODULE_VERSION(SDIOB_NAME, 1);