Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/mmcnull/mmcnull.c
- This file was added.
/*- | |||||
* 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> | |||||
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(struct cam_sim *, union ccb *); | |||||
static void mmcnull_poll(struct cam_sim *); | |||||
static void | |||||
mmcnull_callout(void *arg) | |||||
{ | |||||
struct mmcnull_softc *sc; | |||||
sc = (struct mmcnull_softc *)arg; | |||||
callout_reset(&sc->tick, 1, mmcnull_callout, sc); | |||||
} | |||||
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; | |||||
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); | |||||
sc->sim = cam_sim_alloc(mmcnull_action, 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(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->mmc_command); | |||||
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 */ | |||||
callout_reset(&sc->tick, hz * 10, mmcnull_intr, sc); | |||||
} | |||||
static void | |||||
mmcnull_action(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, "Deglitch Networks", 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; | |||||
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; | |||||
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; | |||||
default: | |||||
ccb->ccb_h.status = CAM_REQ_INVALID; | |||||
break; | |||||
} | |||||
xpt_done(ccb); | |||||
return; | |||||
} | |||||
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, cpu, mmcnull_driver, mmcnull_devclass, 0, 0); |