Date: Sun, 9 Jul 2017 17:02:47 +0000 (UTC) From: Warner Losh <imp@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r320845 - in head/sys: dev/mmcnull modules/mmcnull Message-ID: <201707091702.v69H2lOf000852@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: imp Date: Sun Jul 9 17:02:47 2017 New Revision: 320845 URL: https://svnweb.freebsd.org/changeset/base/320845 Log: Added mmcnull, an emulated lightweight MMC controller This emulated device attaches to the ISA bus and registers itself as HBA supporting MMC/SD cards. This allows to develop and test MMC XPT and MMC / SDIO peripheral drivers even in the VM such as bhyve. Submitted by: Ilya Babulin Added: head/sys/dev/mmcnull/ head/sys/dev/mmcnull/mmcnull.c (contents, props changed) head/sys/modules/mmcnull/ head/sys/modules/mmcnull/Makefile (contents, props changed) Added: head/sys/dev/mmcnull/mmcnull.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/dev/mmcnull/mmcnull.c Sun Jul 9 17:02:47 2017 (r320845) @@ -0,0 +1,463 @@ +/*- + * Copyright (c) 2013 Ilya Bakulin. 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. + * + * 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. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/module.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/systm.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/bus.h> +#include <sys/endian.h> +#include <sys/sysctl.h> + +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_debug.h> +#include <cam/cam_sim.h> +#include <cam/cam_xpt_sim.h> +#include <cam/scsi/scsi_all.h> + +static int is_sdio_mode = 1; + +struct mmcnull_softc { + device_t dev; + struct mtx sc_mtx; + + struct cam_devq *devq; + struct cam_sim *sim; + struct cam_path *path; + + struct callout tick; + union ccb *cur_ccb; +}; + +static void mmcnull_identify(driver_t *, device_t); +static int mmcnull_probe(device_t); +static int mmcnull_attach(device_t); +static int mmcnull_detach(device_t); +static void mmcnull_action_sd(struct cam_sim *, union ccb *); +static void mmcnull_action_sdio(struct cam_sim *, union ccb *); +static void mmcnull_intr_sd(void *xsc); +static void mmcnull_intr_sdio(void *xsc); +static void mmcnull_poll(struct cam_sim *); + +static void +mmcnull_identify(driver_t *driver, device_t parent) +{ + device_t child; + + if (resource_disabled("mmcnull", 0)) + return; + + if (device_get_unit(parent) != 0) + return; + + /* Avoid duplicates. */ + if (device_find_child(parent, "mmcnull", -1)) + return; + + child = BUS_ADD_CHILD(parent, 20, "mmcnull", 0); + if (child == NULL) { + device_printf(parent, "add MMCNULL child failed\n"); + return; + } +} + + +static int +mmcnull_probe(device_t dev) +{ + device_set_desc(dev, "Emulated MMC controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +mmcnull_attach(device_t dev) +{ + struct mmcnull_softc *sc; + sim_action_func action_func; + + sc = device_get_softc(dev); + sc->dev = dev; + + mtx_init(&sc->sc_mtx, "mmcnullmtx", NULL, MTX_DEF); + + if ((sc->devq = cam_simq_alloc(1)) == NULL) + return (ENOMEM); + + if (is_sdio_mode) + action_func = mmcnull_action_sdio; + else + action_func = mmcnull_action_sd; + sc->sim = cam_sim_alloc(action_func, mmcnull_poll, "mmcnull", sc, + device_get_unit(dev), &sc->sc_mtx, 1, 1, + sc->devq); + + if (sc->sim == NULL) { + cam_simq_free(sc->devq); + device_printf(dev, "cannot allocate CAM SIM\n"); + return (EINVAL); + } + + mtx_lock(&sc->sc_mtx); + if (xpt_bus_register(sc->sim, dev, 0) != 0) { + device_printf(dev, + "cannot register SCSI pass-through bus\n"); + cam_sim_free(sc->sim, FALSE); + cam_simq_free(sc->devq); + mtx_unlock(&sc->sc_mtx); + return (EINVAL); + } + mtx_unlock(&sc->sc_mtx); + + callout_init_mtx(&sc->tick, &sc->sc_mtx, 0); /* Callout to emulate interrupts */ + + device_printf(dev, "attached OK\n"); + + return (0); +} + +static int +mmcnull_detach(device_t dev) +{ + struct mmcnull_softc *sc; + + sc = device_get_softc(dev); + + if (sc == NULL) + return (EINVAL); + + if (sc->sim != NULL) { + mtx_lock(&sc->sc_mtx); + xpt_bus_deregister(cam_sim_path(sc->sim)); + cam_sim_free(sc->sim, FALSE); + mtx_unlock(&sc->sc_mtx); + } + + if (sc->devq != NULL) + cam_simq_free(sc->devq); + + callout_drain(&sc->tick); + mtx_destroy(&sc->sc_mtx); + + device_printf(dev, "detached OK\n"); + return (0); +} + +/* + * The interrupt handler + * This implementation calls it via callout(9) + * with the mutex already taken + */ +static void +mmcnull_intr_sd(void *xsc) { + struct mmcnull_softc *sc; + union ccb *ccb; + struct ccb_mmcio *mmcio; + + sc = (struct mmcnull_softc *) xsc; + mtx_assert(&sc->sc_mtx, MA_OWNED); + + ccb = sc->cur_ccb; + mmcio = &ccb->mmcio; + device_printf(sc->dev, "mmcnull_intr: MMC command = %d\n", + mmcio->cmd.opcode); + + switch (mmcio->cmd.opcode) { + case MMC_GO_IDLE_STATE: + device_printf(sc->dev, "Reset device\n"); + break; + case SD_SEND_IF_COND: + mmcio->cmd.resp[0] = 0x1AA; // To match mmc_xpt expectations :-) + break; + case MMC_APP_CMD: + mmcio->cmd.resp[0] = R1_APP_CMD; + break; + case SD_SEND_RELATIVE_ADDR: + case MMC_SELECT_CARD: + mmcio->cmd.resp[0] = 0x1 << 16; + break; + case ACMD_SD_SEND_OP_COND: + mmcio->cmd.resp[0] = 0xc0ff8000; + mmcio->cmd.resp[0] |= MMC_OCR_CARD_BUSY; + break; + case MMC_ALL_SEND_CID: + /* Note: this is a real CID from Wandboard int mmc */ + mmcio->cmd.resp[0] = 0x1b534d30; + mmcio->cmd.resp[1] = 0x30303030; + mmcio->cmd.resp[2] = 0x10842806; + mmcio->cmd.resp[3] = 0x5700e900; + break; + case MMC_SEND_CSD: + /* Note: this is a real CSD from Wandboard int mmc */ + mmcio->cmd.resp[0] = 0x400e0032; + mmcio->cmd.resp[1] = 0x5b590000; + mmcio->cmd.resp[2] = 0x751f7f80; + mmcio->cmd.resp[3] = 0x0a404000; + break; + case MMC_READ_SINGLE_BLOCK: + case MMC_READ_MULTIPLE_BLOCK: + strcpy(mmcio->cmd.data->data, "WTF?!"); + break; + default: + device_printf(sc->dev, "mmcnull_intr_sd: unknown command\n"); + mmcio->cmd.error = 1; + } + ccb->ccb_h.status = CAM_REQ_CMP; + + sc->cur_ccb = NULL; + xpt_done(ccb); +} + +static void +mmcnull_intr_sdio_newintr(void *xsc) { + struct mmcnull_softc *sc; + struct cam_path *dpath; + + sc = (struct mmcnull_softc *) xsc; + mtx_assert(&sc->sc_mtx, MA_OWNED); + device_printf(sc->dev, "mmcnull_intr_sdio_newintr()\n"); + + /* Our path */ + if (xpt_create_path(&dpath, NULL, cam_sim_path(sc->sim), 0, 0) != CAM_REQ_CMP) { + device_printf(sc->dev, "mmcnull_intr_sdio_newintr(): cannot create path\n"); + return; + } + xpt_async(AC_UNIT_ATTENTION, dpath, NULL); + xpt_free_path(dpath); +} + +static void +mmcnull_intr_sdio(void *xsc) { + struct mmcnull_softc *sc; + union ccb *ccb; + struct ccb_mmcio *mmcio; + + sc = (struct mmcnull_softc *) xsc; + mtx_assert(&sc->sc_mtx, MA_OWNED); + + ccb = sc->cur_ccb; + mmcio = &ccb->mmcio; + device_printf(sc->dev, "mmcnull_intr: MMC command = %d\n", + mmcio->cmd.opcode); + + switch (mmcio->cmd.opcode) { + case MMC_GO_IDLE_STATE: + device_printf(sc->dev, "Reset device\n"); + break; + case SD_SEND_IF_COND: + mmcio->cmd.resp[0] = 0x1AA; // To match mmc_xpt expectations :-) + break; + case MMC_APP_CMD: + mmcio->cmd.resp[0] = R1_APP_CMD; + break; + case IO_SEND_OP_COND: + mmcio->cmd.resp[0] = 0x12345678; + mmcio->cmd.resp[0] |= ~ R4_IO_MEM_PRESENT; + break; + case SD_SEND_RELATIVE_ADDR: + case MMC_SELECT_CARD: + mmcio->cmd.resp[0] = 0x1 << 16; + break; + case ACMD_SD_SEND_OP_COND: + /* TODO: steal valid OCR from somewhere :-) */ + mmcio->cmd.resp[0] = 0x123; + mmcio->cmd.resp[0] |= MMC_OCR_CARD_BUSY; + break; + case MMC_ALL_SEND_CID: + mmcio->cmd.resp[0] = 0x1234; + mmcio->cmd.resp[1] = 0x5678; + mmcio->cmd.resp[2] = 0x9ABC; + mmcio->cmd.resp[3] = 0xDEF0; + break; + case MMC_READ_SINGLE_BLOCK: + case MMC_READ_MULTIPLE_BLOCK: + strcpy(mmcio->cmd.data->data, "WTF?!"); + break; + case SD_IO_RW_DIRECT: + device_printf(sc->dev, "Scheduling interrupt generation...\n"); + callout_reset(&sc->tick, hz / 10, mmcnull_intr_sdio_newintr, sc); + break; + default: + device_printf(sc->dev, "mmcnull_intr_sdio: unknown command\n"); + } + ccb->ccb_h.status = CAM_REQ_CMP; + + sc->cur_ccb = NULL; + xpt_done(ccb); +} + +/* + * This is a MMC IO handler + * It extracts MMC command from CCB and sends it + * to the h/w + */ +static void +mmcnull_handle_mmcio(struct cam_sim *sim, union ccb *ccb) +{ + struct mmcnull_softc *sc; + struct ccb_mmcio *mmcio; + + sc = cam_sim_softc(sim); + mmcio = &ccb->mmcio; + ccb->ccb_h.status = CAM_REQ_INPROG; + sc->cur_ccb = ccb; + + /* Real h/w will wait for the interrupt */ + if (is_sdio_mode) + callout_reset(&sc->tick, hz / 10, mmcnull_intr_sdio, sc); + else + callout_reset(&sc->tick, hz / 10, mmcnull_intr_sd, sc); +} + +static void +mmcnull_action_sd(struct cam_sim *sim, union ccb *ccb) +{ + struct mmcnull_softc *sc; + + sc = cam_sim_softc(sim); + if (sc == NULL) { + ccb->ccb_h.status = CAM_SEL_TIMEOUT; + xpt_done(ccb); + return; + } + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + device_printf(sc->dev, "action: func_code %0x\n", ccb->ccb_h.func_code); + + switch (ccb->ccb_h.func_code) { + case XPT_PATH_INQ: + { + struct ccb_pathinq *cpi; + + cpi = &ccb->cpi; + cpi->version_num = 1; + cpi->hba_inquiry = PI_SDTR_ABLE | PI_TAG_ABLE | PI_WIDE_16; + cpi->target_sprt = 0; + cpi->hba_misc = PIM_NOBUSRESET | PIM_SEQSCAN; + cpi->hba_eng_cnt = 0; + cpi->max_target = 0; + cpi->max_lun = 0; + cpi->initiator_id = 1; + strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strncpy(cpi->hba_vid, "FreeBSD Foundation", HBA_IDLEN); + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->bus_id = cam_sim_bus(sim); + cpi->base_transfer_speed = 100; /* XXX WTF? */ + cpi->protocol = PROTO_MMCSD; + cpi->protocol_version = SCSI_REV_0; + cpi->transport = XPORT_MMCSD; + cpi->transport_version = 0; + + cpi->ccb_h.status = CAM_REQ_CMP; + break; + } + case XPT_GET_TRAN_SETTINGS: + { + struct ccb_trans_settings *cts = &ccb->cts; + struct ccb_trans_settings_mmc *mcts; + mcts = &ccb->cts.proto_specific.mmc; + + device_printf(sc->dev, "Got XPT_GET_TRAN_SETTINGS\n"); + + cts->protocol = PROTO_MMCSD; + cts->protocol_version = 0; + cts->transport = XPORT_MMCSD; + cts->transport_version = 0; + cts->xport_specific.valid = 0; + mcts->host_f_max = 12000000; + mcts->host_f_min = 200000; + mcts->host_ocr = 1; /* Fix this */ + ccb->ccb_h.status = CAM_REQ_CMP; + break; + } + case XPT_SET_TRAN_SETTINGS: + device_printf(sc->dev, "Got XPT_SET_TRAN_SETTINGS, should update IOS...\n"); + ccb->ccb_h.status = CAM_REQ_CMP; + break; + case XPT_RESET_BUS: + device_printf(sc->dev, "Got XPT_RESET_BUS, ACK it...\n"); + ccb->ccb_h.status = CAM_REQ_CMP; + break; + case XPT_MMC_IO: + /* + * Here is the HW-dependent part of + * sending the command to the underlying h/w + * At some point in the future an interrupt comes. + * Then the request will be marked as completed. + */ + device_printf(sc->dev, "Got XPT_MMC_IO\n"); + mmcnull_handle_mmcio(sim, ccb); + return; + break; + case XPT_RESET_DEV: + /* This is sent by `camcontrol reset`*/ + device_printf(sc->dev, "Got XPT_RESET_DEV\n"); + ccb->ccb_h.status = CAM_REQ_CMP; + break; + default: + device_printf(sc->dev, "Func code %d is unknown\n", ccb->ccb_h.func_code); + ccb->ccb_h.status = CAM_REQ_INVALID; + break; + } + xpt_done(ccb); + return; +} + +static void +mmcnull_action_sdio(struct cam_sim *sim, union ccb *ccb) { + mmcnull_action_sd(sim, ccb); +} + +static void +mmcnull_poll(struct cam_sim *sim) +{ + return; +} + + +static device_method_t mmcnull_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, mmcnull_identify), + DEVMETHOD(device_probe, mmcnull_probe), + DEVMETHOD(device_attach, mmcnull_attach), + DEVMETHOD(device_detach, mmcnull_detach), + DEVMETHOD_END +}; + +static driver_t mmcnull_driver = { + "mmcnull", mmcnull_methods, sizeof(struct mmcnull_softc) +}; + +static devclass_t mmcnull_devclass; + +DRIVER_MODULE(mmcnull, isa, mmcnull_driver, mmcnull_devclass, 0, 0); Added: head/sys/modules/mmcnull/Makefile ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/modules/mmcnull/Makefile Sun Jul 9 17:02:47 2017 (r320845) @@ -0,0 +1,8 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../dev/mmcnull + +KMOD= mmcnull +SRCS= mmcnull.c device_if.h bus_if.h + +.include <bsd.kmod.mk>
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201707091702.v69H2lOf000852>