⬢ DragonFlyBSD Kernel Audit
DF-5523 / df5521_rig.c
← back to finding ↓ download raw
/*
 * DF-5521/5522/5523/5525 rig: software I2C controller on isa.
 *
 * Registers a stateless "dfa_iic" controller driver on the isa bus and an
 * "iicbus" child on it, so that the production chain
 *
 *     dfa_iic0 (isa) -> iicbus0 -> iicsmb0 -> smbus0 -> smb0 (/dev/smb0)
 *
 * comes up with 100% production code (iicbus.ko, iicsmb.ko, smbus.ko,
 * smb.ko are the stock modules).  Only the wire is software:
 *
 *   - every START/WRITE/READ/STOP is kprintf'ed with its bytes so the
 *     iicsmb bridge's wire protocol is directly observable;
 *   - reads can sleep a configurable delay (hw.dfa.rdelay ms) to hold an
 *     SMBus transaction in flight (kldunload race window);
 *   - the READ log line shows the requested length, which is exactly the
 *     bridge's (int)*count -- i.e. /dev/smb's *uninitialized* bcount;
 *   - hw.dfa.fire_event=N injects iicbus_intr(iicbus0, N, &c) the way a
 *     real controller ISR would (pcfintr calls iicbus_intr on 8 paths).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/sysctl.h>

#include <bus/iicbus/iiconf.h>
#include <bus/iicbus/iicbus.h>

#include "iicbus_if.h"

/* iicbus_driver/iicbus_devclass are declared in <bus/iicbus/iicbus.h> */

#define DF_SLAVE_CNT 8		/* emulated SMBus slave block count byte */

struct dfa_softc {
	device_t iicbus;
	int unit;
	int rxoff;		/* slave TX stream offset within a txn */
};

static struct dfa_softc *g_sc;

static int dfa_rdelay = 0;	/* ms to sleep inside READ (race window) */

static int dfa_probe(device_t);
static int dfa_attach(device_t);
static int dfa_detach(device_t);

static int dfa_callback(device_t, int, caddr_t);
static int dfa_reset(device_t, u_char, u_char, u_char *);
static int dfa_start(device_t, u_char, int);
static int dfa_rstart(device_t, u_char, int);
static int dfa_stop(device_t);
static int dfa_write(device_t, const char *, int, int *, int);
static int dfa_read(device_t, char *, int, int *, int, int);

static device_method_t dfa_methods[] = {
	/* device interface */
	DEVMETHOD(device_probe,		dfa_probe),
	DEVMETHOD(device_attach,	dfa_attach),
	DEVMETHOD(device_detach,	dfa_detach),

	/* iicbus interface (this driver IS an i2c controller) */
	DEVMETHOD(iicbus_callback,	dfa_callback),
	DEVMETHOD(iicbus_reset,		dfa_reset),
	DEVMETHOD(iicbus_start,		dfa_start),
	DEVMETHOD(iicbus_repeated_start, dfa_rstart),
	DEVMETHOD(iicbus_stop,		dfa_stop),
	DEVMETHOD(iicbus_write,		dfa_write),
	DEVMETHOD(iicbus_read,		dfa_read),

	DEVMETHOD_END
};

static driver_t dfa_driver = {
	"dfa_iic",
	dfa_methods,
	sizeof(struct dfa_softc),
};

static devclass_t dfa_devclass;

DRIVER_MODULE(dfa_iic, isa, dfa_driver, dfa_devclass, NULL, NULL);
DRIVER_MODULE(iicbus, dfa_iic, iicbus_driver, iicbus_devclass, NULL, NULL);
MODULE_DEPEND(dfa_iic, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);

static int
dfa_probe(device_t dev)
{
	device_set_desc(dev, "df5521 software I2C controller (wire-logged)");
	return (0);
}

static int
dfa_attach(device_t dev)
{
	struct dfa_softc *sc = device_get_softc(dev);

	sc->unit = device_get_unit(dev);
	sc->iicbus = device_add_child(dev, "iicbus", -1);
	if (sc->iicbus == NULL) {
		kprintf("df5521: device_add_child(iicbus) failed\n");
		return (ENXIO);
	}
	g_sc = sc;

	/* iicbus attach adds ic/iic/iicsmb children (iicbus.c:144-146);
	 * iicsmb attach adds smbus (iicsmb.c:156); smbus attach probes the
	 * bus (all probes fail: iicsmb has no smbus_trans) and smb attaches
	 * -> /dev/smb0. */
	kprintf("df5521: dfa_iic%d attached, attaching iicbus chain\n", sc->unit);
	bus_generic_attach(dev);
	kprintf("df5521: chain up: iicbus=%s\n",
	    device_get_nameunit(sc->iicbus));
	return (0);
}

static int
dfa_detach(device_t dev)
{
	struct dfa_softc *sc = device_get_softc(dev);

	if (sc->iicbus) {
		bus_generic_detach(dev);
		device_delete_child(dev, sc->iicbus);
	}
	g_sc = NULL;
	return (0);
}

static int
dfa_callback(device_t dev, int index, caddr_t data)
{
	return (0);
}

static int
dfa_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
{
	return (0);
}

static int
dfa_start(device_t dev, u_char slave, int timeout)
{
	struct dfa_softc *sc = device_get_softc(dev);

	sc->rxoff = 0;		/* slave restarts its TX stream per txn */
	kprintf("df5521-wire: START slave=0x%02x %s\n", slave,
	    (slave & LSB) ? "READ" : "WRITE");
	return (0);	/* always ACK */
}

static int
dfa_rstart(device_t dev, u_char slave, int timeout)
{
	kprintf("df5521-wire: REPEATED_START slave=0x%02x %s\n", slave,
	    (slave & LSB) ? "READ" : "WRITE");
	return (0);
}

static int
dfa_stop(device_t dev)
{
	kprintf("df5521-wire: STOP\n");
	return (0);
}

static int
dfa_write(device_t dev, const char *buf, int len, int *bytes, int timeout)
{
	int i;

	kprintf("df5521-wire: WRITE len=%d bytes:", len);
	for (i = 0; i < len && i < 64; i++)
		kprintf(" %02x", (u_char)buf[i]);
	if (len > 64)
		kprintf(" ...");
	kprintf("\n");
	*bytes = len;
	return (0);
}

static int
dfa_read(device_t dev, char *buf, int len, int *bytes, int last, int delay)
{
	struct dfa_softc *sc = device_get_softc(dev);
	int i;

	/* len here == the bridge's (int)*count == /dev/smb's `bcount`
	 * (smb.c:217 is NEVER initialized before smbus_bread) */
	kprintf("df5521-wire: READ req_len=%d  <== bridge *count\n", len);

	if (dfa_rdelay > 0)
		tsleep(&dfa_rdelay, 0, "dfard", dfa_rdelay * hz / 1000);

	/* emulated spec-compliant SMBus block slave: the byte stream within
	 * one transaction is [count][data...] and CONTINUES across reads */
	for (i = 0; i < len; i++, sc->rxoff++)
		buf[i] = (sc->rxoff == 0) ? DF_SLAVE_CNT : (char)(0xa0 + sc->rxoff - 1);
	*bytes = len;

	kprintf("df5521-wire: READ gave %d bytes: %02x %02x %02x %02x ...\n",
	    len, (u_char)buf[0], (u_char)buf[1], (u_char)buf[2],
	    (u_char)buf[3]);
	return (0);
}

/*
 * sysctls
 */
SYSCTL_NODE(_hw, OID_AUTO, dfa, CTLFLAG_RW, 0, "df5521 rig");

SYSCTL_INT(_hw_dfa, OID_AUTO, rdelay, CTLFLAG_RW, &dfa_rdelay, 0,
    "ms to sleep inside controller READ (in-flight window)");

static int
dfa_fire_event_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;

	error = SYSCTL_OUT(req, &v, sizeof(v));
	if (error != 0 || req->newptr == NULL)
		return (error);
	error = SYSCTL_IN(req, &v, sizeof(v));
	if (error != 0)
		return (error);
	if (g_sc && g_sc->iicbus) {
		char c = IIC_EBUSERR;

		kprintf("df5521: firing iicbus_intr(%s, event=0x%x, &c)\n",
		    device_get_nameunit(g_sc->iicbus), v);
		iicbus_intr(g_sc->iicbus, v, &c);
		kprintf("df5521: iicbus_intr returned (no fault)\n");
	} else {
		kprintf("df5521: no iicbus child\n");
	}
	return (0);
}
SYSCTL_PROC(_hw_dfa, OID_AUTO, fire_event, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfa_fire_event_sysctl, "I", "fire iicbus_intr with event value");

/*
 * Instantiate the controller the way isahint(4) does for real ISA devices
 * (same newbus API sequence the DF-5490 rig used for pcf).
 */
static int
dfa_modevent(module_t m, int what, void *arg)
{
	devclass_t dc;
	device_t isa, child;

	switch (what) {
	case MOD_LOAD:
		dc = devclass_find("isa");
		if (dc == NULL) {
			kprintf("df5521: no isa devclass\n");
			return (ENXIO);
		}
		isa = devclass_get_device(dc, 1);
		if (isa == NULL)
			isa = devclass_get_device(dc, 0);
		if (isa == NULL) {
			kprintf("df5521: no isa bus device\n");
			return (ENXIO);
		}
		child = BUS_ADD_CHILD(isa, isa, 90, "dfa_iic", -1);
		if (child == NULL) {
			kprintf("df5521: BUS_ADD_CHILD failed\n");
			return (ENXIO);
		}
		kprintf("df5521: added %s, probing+attaching\n",
		    device_get_nameunit(child));
		if (device_probe_and_attach(child) != 0)
			kprintf("df5521: device_probe_and_attach FAILED\n");
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t dfa_mod = { "df5521_rig", dfa_modevent, NULL };
/* SI_ORDER_MIDDLE+1: run AFTER the DRIVER_MODULE handlers (SI_ORDER_MIDDLE)
 * so dfa_iic-on-isa and iicbus-on-dfa_iic are registered when we instantiate. */
DECLARE_MODULE(df5521_rig, dfa_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE + 1);
MODULE_VERSION(df5521_rig, 1);