Skip to content

Instantly share code, notes, and snippets.

@johalun
Created September 27, 2019 16:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johalun/b726e3458508d5d67116c91c52d33bc8 to your computer and use it in GitHub Desktop.
Save johalun/b726e3458508d5d67116c91c52d33bc8 to your computer and use it in GitHub Desktop.
iichid.diff
diff --git a/sys/conf/files b/sys/conf/files
index 7eaa38aa6f50..8077d6a80783 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1787,6 +1787,7 @@ dev/iicbus/iicbb.c optional iicbb
dev/iicbus/iicbb_if.m optional iicbb
dev/iicbus/iicbus.c optional iicbus
dev/iicbus/iicbus_if.m optional iicbus
+dev/iicubs/iichid.c optional iichid acpi iicbus
dev/iicbus/iiconf.c optional iicbus
dev/iicbus/iicsmb.c optional iicsmb \
dependency "iicbus_if.h"
diff --git a/sys/dev/iicbus/input/acpi_iichid.c b/sys/dev/iicbus/input/acpi_iichid.c
new file mode 100644
index 000000000000..bd779b14a2b8
--- /dev/null
+++ b/sys/dev/iicbus/input/acpi_iichid.c
@@ -0,0 +1,522 @@
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+
+#include <sys/malloc.h>
+
+#include <dev/iicbus/input/iichid.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iiconf.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+
+#include <dev/acpica/acpivar.h>
+
+static device_probe_t acpi_iichid_probe;
+static device_attach_t acpi_iichid_attach;
+static device_detach_t acpi_iichid_detach;
+
+static devclass_t acpi_iichid_devclass;
+
+static device_method_t acpi_iichid_methods[] = {
+ /* device interface */
+ DEVMETHOD(device_probe, acpi_iichid_probe),
+ DEVMETHOD(device_attach, acpi_iichid_attach),
+ DEVMETHOD(device_detach, acpi_iichid_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t acpi_iichid_driver = {
+ .name = "acpi_iichid",
+ .methods = acpi_iichid_methods,
+ .size = sizeof(struct acpi_iichid_softc),
+};
+
+static char *acpi_iichid_ids[] = {
+ "PNP0C50",
+ "ACPI0C50",
+ NULL
+};
+
+static ACPI_STATUS
+acpi_iichid_walk_handler(ACPI_RESOURCE *res, void *context)
+{
+ struct acpi_iichid_softc *sc;
+
+ sc = context;
+
+ switch(res->Type) {
+ case ACPI_RESOURCE_TYPE_SERIAL_BUS:
+ //device_printf(sc->dev, " - serial bus: ");
+ if (res->Data.CommonSerialBus.Type != ACPI_RESOURCE_SERIAL_TYPE_I2C) {
+ device_printf(sc->dev, "wrong bus type, should be %d is %d\n", ACPI_RESOURCE_SERIAL_TYPE_I2C, res->Data.CommonSerialBus.Type);
+ return (AE_TYPE);
+ } else {
+ //device_printf(sc->dev, "0x%x on %s\n", le16toh(res->Data.I2cSerialBus.SlaveAddress), res->Data.CommonSerialBus.ResourceSource.StringPtr);
+ sc->hw.device_addr = res->Data.I2cSerialBus.SlaveAddress;
+ }
+ break;
+ case ACPI_RESOURCE_TYPE_EXTENDED_IRQ:
+ if (res->Data.ExtendedIrq.InterruptCount > 0) {
+ device_printf(sc->dev, " - irq: %d\n", (int)res->Data.ExtendedIrq.Interrupts[0]);
+ sc->irq = res->Data.ExtendedIrq.Interrupts[0];
+ }
+
+ break;
+ case ACPI_RESOURCE_TYPE_END_TAG:
+ //device_printf(sc->dev, " - end parsing\n");
+ break;
+
+ default:
+ device_printf(sc->dev, "unexpected type %d while parsing Current Resource Settings (_CSR)\n", res->Type);
+ break;
+ }
+
+ return AE_OK;
+}
+
+static int
+acpi_iichid_probe(device_t dev)
+{
+ if (acpi_disabled("iichid") || ACPI_ID_PROBE(device_get_parent(dev), dev, acpi_iichid_ids, NULL) == 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "HID over I2C (ACPI)");
+
+ return (BUS_PROBE_VENDOR);
+}
+
+static void
+periodic_or_intr(void *context)
+{
+ struct acpi_iichid_softc *sc;
+
+ sc = context;
+ taskqueue_enqueue(sc->taskqueue, &sc->event_task);
+}
+
+static void
+event_task(void *context, int pending)
+{
+ struct acpi_iichid_softc *sc;
+
+ sc = context;
+
+ mtx_lock(&sc->lock);
+
+ struct iichid_softc *dsc;
+ dsc = sc->iichid_sc;
+
+ mtx_unlock(&sc->lock);
+
+ dsc->event_handler(dsc, pending);
+
+ mtx_lock(&sc->lock);
+ if (sc->callout_setup && sc->sampling_rate > 0 && !callout_pending(&sc->periodic_callout) )
+ callout_reset(&sc->periodic_callout, hz / sc->sampling_rate, periodic_or_intr, sc);
+ mtx_unlock(&sc->lock);
+}
+
+static int
+acpi_iichid_setup_callout(device_t dev, struct acpi_iichid_softc *sc)
+{
+ if (sc->sampling_rate < 0)
+ {
+ device_printf(dev, "sampling_rate is below 0, can't setup callout\n");
+ return (EINVAL);
+ }
+
+ callout_init(&sc->periodic_callout, 1);
+ sc->callout_setup=true;
+ device_printf(dev, "successfully setup callout");
+ return (0);
+}
+
+static int
+acpi_iichid_reset_callout(device_t dev, struct acpi_iichid_softc *sc)
+{
+ if (sc->sampling_rate <= 0)
+ {
+ device_printf(dev, "sampling_rate is below or equal to 0, can't reset callout\n");
+ return (EINVAL);
+ }
+
+ if (sc->callout_setup)
+ callout_reset(&sc->periodic_callout, hz / sc->sampling_rate, periodic_or_intr, sc);
+ else
+ return (EINVAL);
+ return (0);
+}
+
+static void
+acpi_iichid_teardown_callout(device_t dev, struct acpi_iichid_softc *sc)
+{
+ callout_drain(&sc->periodic_callout);
+ sc->callout_setup=false;
+ device_printf(dev, "tore callout down\n");
+}
+
+static int
+acpi_iichid_setup_interrupt(device_t dev, struct acpi_iichid_softc *sc)
+{
+ sc->irq_rid = 0;
+ sc->irq_cookie = 0;
+ sc->irq_res = 0;
+ sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid, RF_ACTIVE);
+
+ if( sc->irq_res != NULL )
+ {
+ device_printf(dev, "allocated irq at 0x%lx and rid %d\n", (uint64_t)sc->irq_res, sc->irq_rid);
+ int error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_TTY | INTR_MPSAFE, NULL, periodic_or_intr, sc, &sc->irq_cookie);
+ if (error != 0)
+ {
+ device_printf(dev, "Could not setup interrupt handler\n");
+ bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res);
+ return error;
+ } else
+ device_printf(dev, "successfully setup interrupt\n");
+
+ } else {
+ device_printf(dev, "could not allocate IRQ resource\n");
+ }
+
+ return (0);
+}
+
+static void
+acpi_iichid_teardown_interrupt(device_t dev, struct acpi_iichid_softc *sc)
+{
+ if (sc->irq_cookie)
+ {
+ bus_teardown_intr(dev, sc->irq_res, sc->irq_cookie);
+ }
+
+ if (sc->irq_res)
+ {
+ bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res);
+ }
+ sc->irq_rid = 0;
+ sc->irq_cookie = 0;
+ sc->irq_res = 0;
+}
+
+static int
+sysctl_sampling_rate_handler(SYSCTL_HANDLER_ARGS)
+{
+ int err, value, oldval;
+ struct acpi_iichid_softc *sc;
+
+ sc = arg1;
+
+ mtx_lock(&sc->lock);
+
+ value = sc->sampling_rate;
+ oldval = sc->sampling_rate;
+ err = sysctl_handle_int(oidp, &value, 0, req);
+
+ if (err != 0 || req->newptr == NULL || value == sc->sampling_rate)
+ {
+ mtx_unlock(&sc->lock);
+ return (err);
+ }
+
+ sc->sampling_rate = value;
+
+ if( oldval < 0 && value >= 0 )
+ {
+ acpi_iichid_teardown_interrupt(sc->dev, sc);
+ acpi_iichid_setup_callout(sc->dev, sc);
+ } else if ( oldval >=0 && value < 0)
+ {
+ acpi_iichid_teardown_callout(sc->dev, sc);
+ acpi_iichid_setup_interrupt(sc->dev, sc);
+ }
+
+ if( value > 0 )
+ acpi_iichid_reset_callout(sc->dev, sc);
+
+ device_printf(sc->dev, "new sampling_rate value: %d\n", value);
+
+ mtx_unlock(&sc->lock);
+
+ return (0);
+}
+
+static int
+acpi_iichid_attach(device_t dev)
+{
+ struct acpi_iichid_softc *sc;
+ sc = device_get_softc(dev);
+
+ mtx_init(&sc->lock, "HID over I2C (ACPI) lock", NULL, MTX_DEF);
+
+ sc->dev = dev;
+
+ sc->irq = 0;
+ sc->irq_rid = 0;
+ sc->irq_res = 0;
+ sc->irq_cookie = 0;
+ sc->sampling_rate = -1;
+ sc->taskqueue = 0;
+ sc->iichid_sc = 0;
+ sc->callout_setup = false;
+
+ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
+ OID_AUTO, "sampling_rate", CTLTYPE_INT | CTLFLAG_RWTUN,
+ sc, 0,
+ sysctl_sampling_rate_handler, "I", "sampling rate in num/second");
+
+ //get ACPI handles for current device and its parent
+ ACPI_HANDLE ahnd = acpi_get_handle(dev),
+ phnd = NULL;
+
+ if (!ahnd) {
+ device_printf(dev, "Could not retrieve ACPI handle\n");
+ mtx_destroy(&sc->lock);
+ return (ENXIO);
+ }
+
+ //besides the ACPI parent handle, get the newbus parent
+ device_t parent;
+
+ if (ACPI_SUCCESS(AcpiGetParent(ahnd, &phnd)) && (parent = acpi_get_device(phnd)) && device_is_attached(parent))
+ {
+ //device_printf(dev, "my parent is a %s and its ACPI path is %s\n", device_get_driver(parent)->name, acpi_name(phnd));
+ } else {
+ device_printf(dev, "could not retrieve parent device or parent is not attached (driver loaded?)");
+ mtx_destroy(&sc->lock);
+ return (ENXIO);
+ }
+
+
+ //function (_DSM) to be evaluated to retrieve the address of the configuration register of the hi device
+ /* 3cdff6f7-4267-4555-ad05-b30a3d8938de */
+ static uint8_t acpi_iichid_dsm_guid[] = {
+ 0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45,
+ 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE,
+ };
+
+ //prepare 4 arguments
+ ACPI_OBJECT obj[4];
+ ACPI_OBJECT_LIST acpi_arg;
+
+ ACPI_BUFFER acpi_buf;
+
+ acpi_buf.Pointer = NULL;
+ acpi_buf.Length = ACPI_ALLOCATE_BUFFER;
+
+ acpi_arg.Pointer = &obj[0];
+ acpi_arg.Count = 4;
+
+ obj[0].Type = ACPI_TYPE_BUFFER;
+ obj[0].Buffer.Length = sizeof(acpi_iichid_dsm_guid);
+ obj[0].Buffer.Pointer = &acpi_iichid_dsm_guid[0];
+
+ obj[1].Type = ACPI_TYPE_INTEGER;
+ obj[1].Integer.Value = 1;
+
+ obj[2].Type = ACPI_TYPE_INTEGER;
+ obj[2].Integer.Value = 1;
+
+ obj[3].Type = ACPI_TYPE_PACKAGE;
+ obj[3].Package.Count = 0;
+
+ //evaluate
+ ACPI_STATUS status = ACPI_EVALUATE_OBJECT(device_get_parent(dev), dev, "_DSM", &acpi_arg, &acpi_buf);
+
+ if (ACPI_FAILURE(status)) {
+ device_printf(dev, "error evaluating _DSM\n");
+ if (acpi_buf.Pointer != NULL)
+ AcpiOsFree(acpi_buf.Pointer);
+ mtx_destroy(&sc->lock);
+ return (ENXIO);
+ }
+
+ //the result will contain the register address (int type)
+ ACPI_OBJECT *result = (ACPI_OBJECT*)acpi_buf.Pointer;
+ if( result->Type != ACPI_TYPE_INTEGER ) {
+ device_printf(dev, "_DSM should return descriptor register address as integer\n");
+ AcpiOsFree(result);
+ mtx_destroy(&sc->lock);
+ return (ENXIO);
+ }
+
+ //take it (much work done for one byte -.-)
+ device_printf(dev, "descriptor register address is %lx\n", result->Integer.Value);
+ sc->hw.config_reg = result->Integer.Value;
+
+ //cleanup
+ AcpiOsFree(result);
+
+ //_CSR holds more data (device address and irq) and only needs a callback to evaluate its data
+ status = AcpiWalkResources(ahnd, "_CRS", acpi_iichid_walk_handler, sc);
+
+ if (ACPI_FAILURE(status)) {
+ device_printf(dev, "could not evaluate _CRS\n");
+ mtx_destroy(&sc->lock);
+ return (ENXIO);
+ }
+
+ //get the full ACPI pathname of dev's parent
+ acpi_buf.Pointer = NULL;
+ acpi_buf.Length = ACPI_ALLOCATE_BUFFER;
+ AcpiGetName(phnd, ACPI_FULL_PATHNAME, &acpi_buf);
+
+ device_printf(dev, "parent device is \"%s\"\n", (const char*)acpi_buf.Pointer);
+ AcpiOsFree(acpi_buf.Pointer);
+
+ //padev will hold the newbus device handle of this (dev) devices ACPI parent, which is not necessarily the same as this (dev) devices parent.
+ //I.e. both parent devices might even be on different branches of the device tree. The ACPI parent is most likely a iicbus
+ //device (e.g. ig4's ig4iic_pci0), while the newbus parent of dev will in most cases acpi0.
+ device_t padev = acpi_get_device(phnd); //ACPI parent, the one we are interested in because it is a iicbus
+#if 0
+ device_t pbdev = device_get_parent(dev); //newbus parent, just for reference
+
+ device_printf(dev, "parent devices: 0x%lx (ACPI, %s) and 0x%lx (Newbus, %s)\n", (uint64_t)padev, device_get_name(padev), (uint64_t)pbdev, device_get_name(padev));
+#endif
+ //look below padev whether there already is a iichid device that can be reused or create a new one
+ if (padev)
+ {
+ //the should be a iicbus device, nevertheless no KASSERT here since the system will continue to function
+ // only iichid device won't operate
+ device_t iicbus_dev = device_find_child(padev, "iicbus", -1);
+ if (iicbus_dev)
+ {
+ device_t *children;
+ int ccount;
+
+ device_t dnew = NULL;
+
+ //get a list of all children below iicbus and check if parameters match
+ if (device_get_children(iicbus_dev, &children, &ccount) == 0)
+ {
+ for(int i=0; i<ccount; i++)
+ {
+ driver_t *drv = device_get_driver(children[i]);
+ if (!drv)
+ continue;
+
+ if (strcmp(drv->name, "iichid") == 0)
+ {
+ struct iichid_softc *dsc = (struct iichid_softc*)device_get_softc(children[i]);
+ if ( dsc->hw.device_addr == sc->hw.device_addr
+ && dsc->hw.config_reg == sc->hw.config_reg )
+ {
+ //reuse this child, there shouldn't be more than one
+ //if there are more devices matching that is observable in dmesg
+ dnew = children[i];
+ device_printf(dev, "device %s ADDR 0x%x REG 0x%x already present on %s\n", device_get_nameunit(children[i]), dsc->hw.device_addr, dsc->hw.config_reg, device_get_nameunit(iicbus_dev));
+ }
+ }
+ }
+ free(children, M_TEMP);
+ }
+
+ //no iichid device found to be reused, so one is created and parameters are set
+ if ( dnew == NULL )
+ {
+ //add child of type iichid below iicbus
+ dnew = BUS_ADD_CHILD(iicbus_dev, 0, "iichid", -1);
+ if (dnew)
+ {
+ //config register and device address via resource_list/ivars
+ bus_set_resource(dnew, SYS_RES_IOPORT, 0, sc->hw.config_reg, 2);
+ BUS_WRITE_IVAR(iicbus_dev, dnew, IICBUS_IVAR_ADDR, sc->hw.device_addr);
+
+ //try and attach:
+ if (device_probe_and_attach(dnew) == 0)
+ {
+ // success? print status and device configuration
+ struct iichid_softc *dsc = (struct iichid_softc*)device_get_softc(dnew);
+ device_printf(dev, "added %s ADDR 0x%x REG 0x%x to %s\n", device_get_nameunit(dnew), dsc->hw.device_addr, dsc->hw.config_reg, device_get_nameunit(iicbus_dev));
+ } else {
+ //failure? remove child, print error and leave
+ device_printf(dev, "probe or attach failed for %s! (ADDR: 0x%x, REG: 0x%x)\n", device_get_nameunit(dnew), sc->hw.device_addr, sc->hw.config_reg);
+ device_delete_child(iicbus_dev, dnew);
+ mtx_destroy(&sc->lock);
+ return (ENXIO);
+ }
+ } else {
+ device_printf(dev, "could not attach iichid device to %s! (ADDR: 0x%x, REG: 0x%x)\n", device_get_nameunit(iicbus_dev), sc->hw.device_addr, sc->hw.config_reg);
+ mtx_destroy(&sc->lock);
+ return (ENXIO);
+ }
+ }
+
+ if ( dnew != NULL )
+ {
+ struct iichid_softc *dsc = (struct iichid_softc*)device_get_softc(dnew);
+ if (dsc)
+ {
+ sc->iichid_sc = dsc;
+ TASK_INIT(&sc->event_task, 0, event_task, sc);
+
+ sc->taskqueue = taskqueue_create("iichid_tq", M_NOWAIT | M_ZERO, taskqueue_thread_enqueue, &sc->taskqueue);
+ if( sc->taskqueue == NULL )
+ {
+ return (ENXIO);
+ }else{
+ taskqueue_start_threads(&sc->taskqueue, 1, PI_TTY, "%s taskq", device_get_nameunit(sc->dev));
+ }
+
+ int error;
+ if (sc->sampling_rate >= 0)
+ {
+ error = acpi_iichid_setup_callout(dev, sc);
+ if (error != 0)
+ {
+ device_printf(dev, "please consider setting the sampling_rate sysctl to -1");
+ }
+ } else {
+ error = acpi_iichid_setup_interrupt(dev, sc);
+ if (error != 0)
+ {
+ device_printf(dev, "please consider setting the sampling_rate sysctl greater than 0.");
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+
+ return (0); /* success */
+}
+
+static int
+acpi_iichid_detach(device_t dev)
+{
+ //we leave the added devices below iicbus instances intact, since this module is only needed to parameterize
+ // them. Afterwards they function without this
+ struct acpi_iichid_softc *sc;
+ sc = device_get_softc(dev);
+
+ mtx_lock(&sc->lock);
+
+ if (sc->taskqueue)
+ {
+ taskqueue_block(sc->taskqueue);
+ taskqueue_drain(sc->taskqueue, &sc->event_task);
+ taskqueue_free(sc->taskqueue);
+ }
+
+ acpi_iichid_teardown_callout(dev, sc);
+ acpi_iichid_teardown_interrupt(dev, sc);
+
+ mtx_unlock(&sc->lock);
+ mtx_destroy(&sc->lock);
+ return (0);
+}
+
+DRIVER_MODULE(acpi_iichid, acpi, acpi_iichid_driver, acpi_iichid_devclass, NULL, 0);
+MODULE_DEPEND(acpi_iichid, acpi, 1, 1, 1);
+MODULE_DEPEND(acpi_iichid, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
+MODULE_DEPEND(acpi_iichid, iichid, 1, 1, 1);
+MODULE_VERSION(acpi_iichid, 1);
diff --git a/sys/dev/iicbus/input/iichid.c b/sys/dev/iicbus/input/iichid.c
new file mode 100644
index 000000000000..fe0e3fd96f4f
--- /dev/null
+++ b/sys/dev/iicbus/input/iichid.c
@@ -0,0 +1,916 @@
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/sbuf.h>
+#include <sys/endian.h>
+#include <sys/rman.h>
+#include <sys/uio.h>
+#include <machine/resource.h>
+
+#include <sys/ioccom.h>
+#include <sys/filio.h>
+#include <sys/tty.h>
+#include <sys/mouse.h>
+#include <dev/iicbus/input/iichid.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iiconf.h>
+
+#include "iicbus_if.h"
+#include "opt_evdev.h"
+
+#ifdef EVDEV_SUPPORT
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+#endif
+
+static device_probe_t iichid_probe;
+static device_attach_t iichid_attach;
+static device_detach_t iichid_detach;
+
+static devclass_t iichid_devclass;
+
+static device_method_t iichid_methods[] = {
+ DEVMETHOD(device_probe, iichid_probe),
+ DEVMETHOD(device_attach, iichid_attach),
+ DEVMETHOD(device_detach, iichid_detach),
+
+ DEVMETHOD_END
+};
+
+static d_open_t iichid_open;
+static d_close_t iichid_close;
+static d_read_t iichid_read;
+static d_write_t iichid_write;
+static d_ioctl_t iichid_ioctl;
+
+static struct cdevsw iichid_cdevsw = {
+ .d_version = D_VERSION,
+ .d_open = iichid_open,
+ .d_close = iichid_close,
+ .d_read = iichid_read,
+ .d_write = iichid_write,
+ .d_ioctl = iichid_ioctl,
+ .d_name = "iichid"
+};
+
+static driver_t iichid_driver = {
+ .name = "iichid",
+ .methods = iichid_methods,
+ .size = sizeof(struct iichid_softc),
+};
+
+
+#ifdef EVDEV_SUPPORT
+static evdev_open_t iichid_ev_open;
+static evdev_close_t iichid_ev_close;
+static void iichid_evdev_push(struct iichid_softc *, int32_t, int32_t,
+ int32_t, int32_t, int32_t);
+
+static const struct evdev_methods iichid_evdev_methods = {
+ .ev_open = &iichid_ev_open,
+ .ev_close = &iichid_ev_close,
+};
+#endif
+
+static int
+iichid_fetch_buffer(device_t dev, uint8_t* cmd, int cmdlen, uint8_t *buf, int buflen)
+{
+ uint16_t addr = iicbus_get_addr(dev);
+ struct iic_msg msgs[] = {
+ { addr << 1, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd },
+ { addr << 1, IIC_M_RD, buflen, buf },
+ };
+
+ return (iicbus_transfer(dev, msgs, nitems(msgs)));
+}
+
+static int
+iichid_fetch_report(device_t dev, struct i2c_hid_desc* hid_desc, uint8_t *data, int len, int *actual_len)
+{
+ struct iichid_softc* sc;
+ sc = device_get_softc(dev);
+
+ mtx_assert(&sc->lock, MA_OWNED);
+
+ *actual_len = 0;
+
+ uint16_t dtareg = htole16(hid_desc->wInputRegister);
+
+ uint8_t cmd[] = {dtareg & 0xff, dtareg >> 8};
+ int cmdlen = 2;
+ uint8_t buf[len];
+
+ mtx_unlock(&sc->lock);
+
+ int error = iichid_fetch_buffer(dev, cmd, cmdlen, buf, len);
+
+ mtx_lock(&sc->lock);
+
+ memcpy(data, buf, len);
+
+ if (error != 0)
+ {
+ device_printf(dev, "could not retrieve input report (%d)\n", error);
+ return error;
+ }
+
+ *actual_len = data[0] | data[1] << 8;
+
+ return 0;
+}
+
+static int fetch_hid_descriptor(device_t dev)
+{
+ struct iichid_softc *sc = device_get_softc(dev);
+
+ uint16_t cr = sc->hw.config_reg;
+ return (iichid_fetch_buffer(dev, (uint8_t*)&cr, sizeof(cr), (uint8_t*)&sc->desc, sizeof(struct i2c_hid_desc)));
+}
+
+static int fetch_report_descriptor(device_t dev, uint8_t **buf, int *len)
+{
+ struct iichid_softc *sc = device_get_softc(dev);
+
+ if (sc->desc.wHIDDescLength != 30)
+ return -1;
+
+ *buf = malloc(sc->desc.wReportDescLength, M_TEMP, M_NOWAIT | M_ZERO);
+ *len = sc->desc.wReportDescLength;
+
+ uint16_t rdr = sc->desc.wReportDescRegister;
+
+ int error = (iichid_fetch_buffer(dev, (uint8_t*)&rdr, sizeof(rdr), *buf, sc->desc.wReportDescLength));
+
+ return error;
+}
+
+static void
+ms_hid_parse(device_t dev, const uint8_t *buf, uint16_t len, struct ms_info *info, uint8_t index)
+{
+ uint32_t flags;
+ uint8_t i;
+ uint8_t j;
+
+ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
+ hid_input, index, &info->sc_loc_x, &flags, &info->sc_iid_x)) {
+
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= MS_FLAG_X_AXIS;
+ }
+ }
+ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
+ hid_input, index, &info->sc_loc_y, &flags, &info->sc_iid_y)) {
+
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= MS_FLAG_Y_AXIS;
+ }
+ }
+ /* Try the wheel first as the Z activator since it's tradition. */
+ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+ HUG_WHEEL), hid_input, index, &info->sc_loc_z, &flags,
+ &info->sc_iid_z) ||
+ hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+ HUG_TWHEEL), hid_input, index, &info->sc_loc_z, &flags,
+ &info->sc_iid_z)) {
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= MS_FLAG_Z_AXIS;
+ }
+ /*
+ * We might have both a wheel and Z direction, if so put
+ * put the Z on the W coordinate.
+ */
+ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+ HUG_Z), hid_input, index, &info->sc_loc_w, &flags,
+ &info->sc_iid_w)) {
+
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= MS_FLAG_W_AXIS;
+ }
+ }
+ } else if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+ HUG_Z), hid_input, index, &info->sc_loc_z, &flags,
+ &info->sc_iid_z)) {
+
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= MS_FLAG_Z_AXIS;
+ }
+ }
+ /*
+ * The Microsoft Wireless Intellimouse 2.0 reports it's wheel
+ * using 0x0048, which is HUG_TWHEEL, and seems to expect you
+ * to know that the byte after the wheel is the tilt axis.
+ * There are no other HID axis descriptors other than X,Y and
+ * TWHEEL
+ */
+ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+ HUG_TWHEEL), hid_input, index, &info->sc_loc_t,
+ &flags, &info->sc_iid_t)) {
+
+ info->sc_loc_t.pos += 8;
+
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= MS_FLAG_T_AXIS;
+ }
+ } else if (hid_locate(buf, len, HID_USAGE2(HUP_CONSUMER,
+ HUC_AC_PAN), hid_input, index, &info->sc_loc_t,
+ &flags, &info->sc_iid_t)) {
+
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS)
+ info->sc_flags |= MS_FLAG_T_AXIS;
+ }
+ /* figure out the number of buttons */
+
+ for (i = 0; i < MS_BUTTON_MAX; i++) {
+ if (!hid_locate(buf, len, HID_USAGE2(HUP_BUTTON, (i + 1)),
+ hid_input, index, &info->sc_loc_btn[i], NULL,
+ &info->sc_iid_btn[i])) {
+ break;
+ }
+ }
+
+ /* detect other buttons */
+
+ for (j = 0; (i < MS_BUTTON_MAX) && (j < 2); i++, j++) {
+ if (!hid_locate(buf, len, HID_USAGE2(HUP_MICROSOFT, (j + 1)),
+ hid_input, index, &info->sc_loc_btn[i], NULL,
+ &info->sc_iid_btn[i])) {
+ break;
+ }
+ }
+
+ info->sc_buttons = i;
+
+ if (info->sc_flags == 0)
+ return;
+
+ /* announce information about the mouse */
+ device_printf(dev, "%d buttons and [%s%s%s%s%s] coordinates ID=%u\n",
+ (info->sc_buttons),
+ (info->sc_flags & MS_FLAG_X_AXIS) ? "X" : "",
+ (info->sc_flags & MS_FLAG_Y_AXIS) ? "Y" : "",
+ (info->sc_flags & MS_FLAG_Z_AXIS) ? "Z" : "",
+ (info->sc_flags & MS_FLAG_T_AXIS) ? "T" : "",
+ (info->sc_flags & MS_FLAG_W_AXIS) ? "W" : "",
+ info->sc_iid_x);
+}
+
+static int
+iichid_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
+{
+ struct iichid_softc *sc = dev->si_drv1;
+
+ mtx_lock(&sc->lock);
+ if (sc->isopen)
+ {
+ mtx_unlock(&sc->lock);
+ return (EBUSY);
+ }
+
+ sc->isopen = true;
+ sc->bytesread = sc->sc_mode.packetsize;
+
+
+ while (!STAILQ_EMPTY(&sc->ms_queue))
+ {
+ struct ms_tx_entry *u = STAILQ_FIRST(&sc->ms_queue);
+ STAILQ_REMOVE_HEAD(&sc->ms_queue, next);
+ STAILQ_INSERT_HEAD(&sc->ms_unused_blocks, u, next);
+ }
+
+ mtx_unlock(&sc->lock);
+
+ return 0;
+}
+
+static int
+iichid_close(struct cdev *dev, int fflags, int devtype, struct thread *td)
+{
+ struct iichid_softc *sc = dev->si_drv1;
+
+ cv_broadcastpri(&sc->cv, 0);
+
+ mtx_lock(&sc->lock);
+ sc->isopen=false;
+ mtx_unlock(&sc->lock);
+
+ return 0;
+}
+
+#ifdef EVDEV_SUPPORT
+static int
+iichid_ev_open(struct evdev_dev *evdev)
+{
+ struct iichid_softc *sc = evdev_get_softc(evdev);
+
+ mtx_assert(&sc->lock, MA_OWNED);
+ sc->sc_evflags = MS_EVDEV_OPENED;
+
+ return (0);
+}
+
+static int
+iichid_ev_close(struct evdev_dev *evdev)
+{
+ struct iichid_softc *sc = evdev_get_softc(evdev);
+
+ mtx_assert(&sc->lock, MA_OWNED);
+ sc->sc_evflags = 0;
+
+ return (0);
+}
+#endif
+
+static int
+iichid_write(struct cdev *dev, struct uio *uio, int ioflags)
+{
+ return 0;
+}
+
+static int
+iichid_read(struct cdev *dev, struct uio* uio, int ioflags)
+{
+ struct iichid_softc *sc = dev->si_drv1;
+
+ mtx_lock(&sc->lock);
+
+ while (!sc->detaching && STAILQ_EMPTY(&sc->ms_queue) && sc->bytesread == sc->sc_mode.packetsize)
+ {
+ int error = cv_wait_sig(&sc->cv, &sc->lock);
+ if (error != 0)
+ {
+ mtx_unlock(&sc->lock);
+ return error;
+ }
+ }
+
+ if (sc->detaching)
+ {
+ mtx_unlock(&sc->lock);
+ return ENXIO;
+ }
+
+ if (sc->bytesread == sc->sc_mode.packetsize && !STAILQ_EMPTY(&sc->ms_queue))
+ {
+ struct ms_tx_entry *u = STAILQ_FIRST(&sc->ms_queue);
+ STAILQ_REMOVE_HEAD(&sc->ms_queue, next);
+ memcpy(&sc->rbuf, u, sizeof(struct ms_tx_entry));
+ sc->bytesread = 0;
+
+ STAILQ_INSERT_TAIL(&sc->ms_unused_blocks, u, next);
+ }
+ mtx_unlock(&sc->lock);
+
+ int error = uiomove(sc->rbuf.buf+sc->bytesread, 1, uio);
+ sc->bytesread++;
+ if (error != 0)
+ device_printf(sc->dev, "I could not be read from");
+
+ return 0;
+}
+
+static int
+iichid_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
+{
+ struct iichid_softc *sc = dev->si_drv1;
+ int error = 0;
+ mousemode_t mode;
+
+ mtx_lock(&sc->lock);
+
+ switch (cmd) {
+ case MOUSE_SETMODE:
+ mode = *(mousemode_t*)data;
+
+ if (mode.level == -1) {
+ /* don't change the current setting */
+ } else if ((mode.level < 0) || (mode.level > 1)) {
+ error = EINVAL;
+ break;
+ } else {
+ sc->sc_mode.level = mode.level;
+ }
+
+ if (sc->sc_mode.level == 0) {
+ if (sc->sc_hw.buttons > MOUSE_MSC_MAXBUTTON)
+ sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON;
+
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+ } else if (sc->sc_mode.level == 1) {
+ if (sc->sc_hw.buttons > MOUSE_SYS_MAXBUTTON)
+ sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON;
+
+ sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+ sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+ }
+ break;
+ case MOUSE_SETLEVEL:
+ if (*(int *)data < 0 || *(int *)data > 1) {
+ error = EINVAL;
+ break;
+ }
+ sc->sc_mode.level = *(int *)data;
+
+ if (sc->sc_mode.level == 0) {
+ if (sc->sc_hw.buttons > MOUSE_MSC_MAXBUTTON)
+ sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON;
+
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+ } else if (sc->sc_mode.level == 1) {
+ if (sc->sc_hw.buttons > MOUSE_SYS_MAXBUTTON)
+ sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON;
+
+ sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+ sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+ }
+ break;
+ case MOUSE_GETHWINFO:
+ *(mousehw_t *)data = sc->sc_hw;
+ break;
+
+ case MOUSE_GETMODE:
+ *(mousemode_t *)data = sc->sc_mode;
+ break;
+
+ case MOUSE_GETLEVEL:
+ *(int *)data = sc->sc_mode.level;
+ break;
+
+ case MOUSE_GETSTATUS:{
+ mousestatus_t *status = (mousestatus_t *)data;
+
+ *status = sc->sc_status;
+ sc->sc_status.obutton = sc->sc_status.button;
+ sc->sc_status.button = 0;
+ sc->sc_status.dx = 0;
+ sc->sc_status.dy = 0;
+ sc->sc_status.dz = 0;
+ /* sc->sc_status.dt = 0; */
+
+ if (status->dx || status->dy || status->dz /* || status->dt */ ) {
+ status->flags |= MOUSE_POSCHANGED;
+ }
+ if (status->button != status->obutton) {
+ status->flags |= MOUSE_BUTTONSCHANGED;
+ }
+ break;
+ }
+ default:
+ error = ENOTTY;
+ break;
+ }
+
+ mtx_unlock(&sc->lock);
+ return (error);
+}
+
+static void
+ms_put_queue(struct iichid_softc *sc, int32_t dx, int32_t dy,
+ int32_t dz, int32_t dt, int32_t buttons)
+{
+ if (dx > 254)
+ dx = 254;
+ if (dx < -256)
+ dx = -256;
+ if (dy > 254)
+ dy = 254;
+ if (dy < -256)
+ dy = -256;
+ if (dz > 126)
+ dz = 126;
+ if (dz < -128)
+ dz = -128;
+ if (dt > 126)
+ dt = 126;
+ if (dt < -128)
+ dt = -128;
+
+ if (sc->invert != 0)
+ dz = -dz;
+ if (!STAILQ_EMPTY(&sc->ms_unused_blocks))
+ {
+ struct ms_tx_entry *u = STAILQ_FIRST(&sc->ms_unused_blocks);
+ STAILQ_REMOVE_HEAD(&sc->ms_unused_blocks, next);
+
+ u->buf[0] = MOUSE_MSC_SYNC;
+ u->buf[0] |= (~buttons) & MOUSE_MSC_BUTTONS;
+ u->buf[1] = dx >> 1;
+ u->buf[2] = dy >> 1;
+ u->buf[3] = dx - (dx >> 1);
+ u->buf[4] = dy - (dy >> 1);
+
+ if (sc->sc_mode.level == 1) {
+ u->buf[5] = dz >> 1;
+ u->buf[6] = dz - (dz >> 1);
+ u->buf[7] = (((~buttons) >> 3) & MOUSE_SYS_EXTBUTTONS);
+ }
+
+ STAILQ_INSERT_TAIL(&sc->ms_queue, u, next);
+ } else {
+ device_printf(sc->dev, "no blocks available\n");
+ }
+}
+
+#ifdef EVDEV_SUPPORT
+static void
+iichid_evdev_push(struct iichid_softc *sc, int32_t dx, int32_t dy,
+ int32_t dz, int32_t dt, int32_t buttons)
+{
+ if (evdev_rcpt_mask & EVDEV_RCPT_HW_MOUSE) {
+ /* Push evdev event */
+ if (sc->invert != 0)
+ dz = -dz;
+ evdev_push_rel(sc->sc_evdev, REL_X, dx);
+ evdev_push_rel(sc->sc_evdev, REL_Y, -dy);
+ evdev_push_rel(sc->sc_evdev, REL_WHEEL, -dz);
+ evdev_push_rel(sc->sc_evdev, REL_HWHEEL, dt);
+ evdev_push_mouse_btn(sc->sc_evdev,
+ (buttons & ~MOUSE_STDBUTTONS) |
+ (buttons & (1 << 2) ? MOUSE_BUTTON1DOWN : 0) |
+ (buttons & (1 << 1) ? MOUSE_BUTTON2DOWN : 0) |
+ (buttons & (1 << 0) ? MOUSE_BUTTON3DOWN : 0));
+ evdev_sync(sc->sc_evdev);
+ }
+}
+#endif
+
+static void
+iichid_event(void* context, int pending)
+{
+ struct iichid_softc *sc = context;
+
+ mtx_lock(&sc->lock);
+
+ int actual = 0;
+ int error = iichid_fetch_report(sc->dev, &sc->desc, sc->input_buf, sc->input_size, &actual);
+
+ if (error != 0)
+ {
+ device_printf(sc->dev, "an error occured\n");
+ mtx_unlock(&sc->lock);
+ return;
+ }
+
+ if (actual <= 0)
+ {
+ device_printf(sc->dev, "no data received\n");
+ mtx_unlock(&sc->lock);
+ return;
+ }
+
+ int32_t dw = 0;
+ int32_t dx = 0;
+ int32_t dy = 0;
+ int32_t dz = 0;
+ int32_t dt = 0;
+ int32_t buttons = 0;
+ int32_t buttons_found = 0;
+#ifdef EVDEV_SUPPORT
+ int32_t buttons_reported = 0;
+#endif
+ uint8_t id = 0;
+
+ uint8_t *buf = sc->input_buf;
+ buf++; buf++;
+ int len = actual;
+
+ if (sc->sc_iid)
+ {
+ id = *buf;
+ buf++;
+ len--;
+ }
+
+// device_printf(sc->dev, "id: %d\n", id);
+
+ for(int i=0; i<MS_INFO_MAX; i++)
+ {
+ struct ms_info *info = &sc->info[i];
+ if ((info->sc_flags & MS_FLAG_W_AXIS) &&
+ (id == info->sc_iid_w))
+ dw += hid_get_data(buf, len, &info->sc_loc_w);
+
+ if ((info->sc_flags & MS_FLAG_X_AXIS) &&
+ (id == info->sc_iid_x))
+ dx += hid_get_data(buf, len, &info->sc_loc_x);
+
+ if ((info->sc_flags & MS_FLAG_Y_AXIS) &&
+ (id == info->sc_iid_y))
+ dy -= hid_get_data(buf, len, &info->sc_loc_y);
+
+ if ((info->sc_flags & MS_FLAG_Z_AXIS) &&
+ (id == info->sc_iid_z)) {
+ int32_t temp;
+ temp = hid_get_data(buf, len, &info->sc_loc_z);
+ dz -= temp;
+ }
+
+ if ((info->sc_flags & MS_FLAG_T_AXIS) &&
+ (id == info->sc_iid_t)) {
+ dt -= hid_get_data(buf, len, &info->sc_loc_t);
+ /* T-axis is translated into button presses */
+ buttons_found |= (1UL << 5) | (1UL << 6);
+ }
+
+ for (i = 0; i < info->sc_buttons; i++) {
+ uint32_t mask;
+ mask = 1UL << MS_BUT(i);
+ /* check for correct button ID */
+ if (id != info->sc_iid_btn[i])
+ continue;
+ /* check for button pressed */
+ if (hid_get_data(buf, len, &info->sc_loc_btn[i]))
+ buttons |= mask;
+ /* register button mask */
+ buttons_found |= mask;
+ }
+
+#ifdef EVDEV_SUPPORT
+ buttons_reported = buttons;
+#endif
+
+ buttons |= sc->sc_status.button & ~buttons_found;
+
+ if (dx || dy || dz || dt || dw ||
+ (buttons != sc->sc_status.button)) {
+
+ /* translate T-axis into button presses until further */
+ if (dt > 0) {
+ ms_put_queue(sc, 0, 0, 0, 0, buttons);
+ buttons |= 1UL << 5;
+ } else if (dt < 0) {
+ ms_put_queue(sc, 0, 0, 0, 0, buttons);
+ buttons |= 1UL << 6;
+ }
+
+ sc->sc_status.button = buttons;
+ sc->sc_status.dx += dx;
+ sc->sc_status.dy += dy;
+ sc->sc_status.dz += dz;
+
+ //device_printf(sc->dev, "dx: %d, dy: %d, dz: %d, dt: %d, dw: %d, btn: 0x%2x\n", dx, dy, dz, dt, dw, buttons);
+
+ ms_put_queue(sc, dx, dy, dz, dt, buttons);
+#ifdef EVDEV_SUPPORT
+ iichid_evdev_push(sc, dx, dy, dz, dt, buttons_reported);
+#endif
+ }
+ }
+
+// device_printf(sc->dev, "read: %d/%d\n", actual, sc->desc.wMaxInputLength);
+ mtx_unlock(&sc->lock);
+
+ cv_signal(&sc->cv);
+}
+
+static int
+iichid_probe(device_t dev)
+{
+ device_t pdev = device_get_parent(dev);
+
+ if (!pdev)
+ return (ENXIO);
+
+ driver_t *pdrv = device_get_driver(pdev);
+
+ if (!pdrv)
+ return (ENXIO);
+
+ if (strcmp(pdrv->name, "iicbus") != 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "HID over I2C");
+
+ return (BUS_PROBE_VENDOR);
+}
+
+static int
+sysctl_invert_handler(SYSCTL_HANDLER_ARGS)
+{
+ int err, value;
+ struct iichid_softc *sc;
+
+ sc = arg1;
+
+ mtx_lock(&sc->lock);
+
+ value = sc->invert;
+ err = sysctl_handle_int(oidp, &value, 0, req);
+
+ if (err != 0 || req->newptr == NULL || value == sc->invert)
+ {
+ mtx_unlock(&sc->lock);
+ return (err);
+ }
+
+ sc->invert = value;
+
+ mtx_unlock(&sc->lock);
+
+ return (0);
+}
+
+static int
+iichid_attach(device_t dev)
+{
+ struct iichid_softc *sc = device_get_softc(dev);
+
+ uintptr_t addr = 0, cr = 0;
+ int error;
+#ifdef EVDEV_SUPPORT
+ struct ms_info *info;
+#endif
+ sc->dev = dev;
+ sc->detaching = false;
+ sc->input_buf = NULL;
+ sc->isopen = 0;
+ sc->invert = 0;
+
+ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
+ OID_AUTO, "invert_scroll", CTLTYPE_INT | CTLFLAG_RWTUN,
+ sc, 0,
+ sysctl_invert_handler, "I", "invert mouse axis");
+
+ // the config register is passed as resources, while the device address will always have a place in the iicbus child's (ivars) heart
+ bus_get_resource(dev, SYS_RES_IOPORT, 0, (rman_res_t*)&cr, NULL);
+ BUS_READ_IVAR(device_get_parent(dev), dev, IICBUS_IVAR_ADDR, &addr);
+
+ // store the value in device's softc to have easy access
+ // values only have 1 byte length, still make the casts explicit
+ sc->hw.device_addr = (uint8_t)addr;
+ sc->hw.config_reg = (uint16_t)cr;
+
+ sc->event_handler = iichid_event;
+
+ sc->cdev = make_dev(&iichid_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "ims%d", device_get_unit(dev));
+ sc->cdev->si_drv1 = sc;
+
+ device_printf(dev, "ADDR 0x%x REG 0x%x\n", sc->hw.device_addr, sc->hw.config_reg);
+
+ if ( (error = fetch_hid_descriptor(dev)) != 0 )
+ {
+ iichid_detach(dev);
+ device_printf(dev, "could not retrieve HID descriptor from device: %d\n", error);
+ }
+
+ uint8_t *rdesc;
+ int len;
+
+ if ( (error = fetch_report_descriptor(dev, &rdesc, &len)) != 0 )
+ {
+ iichid_detach(dev);
+ device_printf(dev, "could not retrieve report descriptor from device: %d\n", error);
+ }
+
+ sc->input_size = sc->desc.wMaxInputLength;
+ sc->input_buf = malloc(sc->desc.wMaxInputLength, M_DEVBUF, M_NOWAIT | M_ZERO);
+
+ for (int i = 0; i < MS_INFO_MAX; i++) {
+ ms_hid_parse(dev, rdesc, len, &sc->info[0], 0);
+ }
+
+ int isize = hid_report_size(rdesc, len, hid_input, &sc->sc_iid);
+
+
+ if (isize+2 != sc->desc.wMaxInputLength)
+ device_printf(dev, "determined (len=%d) and described (len=%d) input report lengths mismatch\n", isize+2, sc->desc.wMaxInputLength);
+
+ mtx_init(&sc->lock, "iichid spin-lock", NULL, MTX_DEF);
+ cv_init(&sc->cv, "iichid cv");
+
+ sc->sc_hw.buttons = -1;
+ sc->sc_hw.iftype = MOUSE_IF_SYSMOUSE;
+ sc->sc_hw.type = MOUSE_MOUSE;
+ sc->sc_hw.model = MOUSE_MODEL_GENERIC;
+ sc->sc_hw.hwid = sc->desc.wVendorID;
+
+ sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+ sc->sc_mode.rate = -1;
+ sc->sc_mode.resolution = -1;
+ sc->sc_mode.accelfactor = 1;
+ sc->sc_mode.level = 0;
+ sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+
+ STAILQ_INIT(&sc->ms_queue);
+ STAILQ_INIT(&sc->ms_unused_blocks);
+ for(int i=0; i<MS_BUFQ_MAXLEN; i++)
+ {
+ struct ms_tx_entry *u = malloc(sizeof(struct ms_tx_entry), M_DEVBUF, M_NOWAIT | M_ZERO);
+ STAILQ_INSERT_TAIL(&sc->ms_unused_blocks, u, next);
+ }
+
+#ifdef EVDEV_SUPPORT
+ sc->sc_evdev = evdev_alloc();
+ evdev_set_name(sc->sc_evdev, device_get_desc(dev));
+ evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev));
+ evdev_set_id(sc->sc_evdev, BUS_I2C, sc->desc.wVendorID,
+ sc->desc.wProductID, 0);
+ evdev_set_serial(sc->sc_evdev, "");
+ evdev_set_methods(sc->sc_evdev, sc, &iichid_evdev_methods);
+ evdev_support_prop(sc->sc_evdev, INPUT_PROP_POINTER);
+ evdev_support_event(sc->sc_evdev, EV_SYN);
+ evdev_support_event(sc->sc_evdev, EV_REL);
+ evdev_support_event(sc->sc_evdev, EV_KEY);
+
+ info = &sc->info[0];
+
+ if (info->sc_flags & MS_FLAG_X_AXIS)
+ evdev_support_rel(sc->sc_evdev, REL_X);
+
+ if (info->sc_flags & MS_FLAG_Y_AXIS)
+ evdev_support_rel(sc->sc_evdev, REL_Y);
+
+ if (info->sc_flags & MS_FLAG_Z_AXIS)
+ evdev_support_rel(sc->sc_evdev, REL_WHEEL);
+
+ if (info->sc_flags & MS_FLAG_T_AXIS)
+ evdev_support_rel(sc->sc_evdev, REL_HWHEEL);
+
+ for (int i = 0; i < info->sc_buttons; i++)
+ evdev_support_key(sc->sc_evdev, BTN_MOUSE + i);
+
+ error = evdev_register_mtx(sc->sc_evdev, &sc->lock);
+ if (error) {
+ iichid_detach(dev);
+ device_printf(dev, "could not register evdev mutex for device. error: %d\n", error);
+ }
+#endif
+
+// device_printf(dev, "len: %d\nbcdVer: %d\nreport len: %d\ninput len: %d\nvid: 0x%x\npid: 0x%x\n", hid_desc.wHIDDescLength, hid_desc.bcdVersion, hid_desc.wReportDescLength, hid_desc.wMaxInputLength, hid_desc.wVendorID, hid_desc.wProductID);
+
+ return (0); /* success */
+}
+
+static int
+iichid_detach(device_t dev)
+{
+ struct iichid_softc *sc = device_get_softc(dev);
+ if (sc)
+ {
+ if (sc->isopen)
+ return (EBUSY);
+
+ mtx_lock(&sc->lock);
+ sc->detaching = true;
+ mtx_unlock(&sc->lock);
+ cv_broadcastpri(&sc->cv,0);
+ if (mtx_initialized(&sc->lock))
+ {
+ mtx_destroy(&sc->lock);
+ }
+
+ struct ms_tx_buf* queues[2] = {&sc->ms_queue, &sc->ms_unused_blocks};
+ for(int i=0; i<2; i++)
+ {
+ while (!STAILQ_EMPTY(queues[i]))
+ {
+ struct ms_tx_entry *u = STAILQ_FIRST(queues[i]);
+ STAILQ_REMOVE_HEAD(queues[i], next);
+ free(u, M_DEVBUF);
+ }
+ }
+
+#ifdef EVDEV_SUPPORT
+ evdev_free(sc->sc_evdev);
+#endif
+
+ if (sc->cdev)
+ destroy_dev(sc->cdev);
+
+ if (sc->input_buf)
+ free(sc->input_buf, M_DEVBUF);
+ }
+ return (0);
+}
+
+DRIVER_MODULE(iichid, iicbus, iichid_driver, iichid_devclass, NULL, 0);
+MODULE_DEPEND(iichid, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
+MODULE_VERSION(iichid, 1);
diff --git a/sys/dev/iicbus/input/iichid.h b/sys/dev/iicbus/input/iichid.h
new file mode 100644
index 000000000000..3f05e4864f51
--- /dev/null
+++ b/sys/dev/iicbus/input/iichid.h
@@ -0,0 +1,160 @@
+/* $OpenBSD: iichid.h,v 1.4 2016/01/31 18:24:35 jcs Exp $ */
+/*
+ * HID-over-i2c driver
+ *
+ * Copyright (c) 2015, 2016 joshua stein <jcs@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _IIC_HID_H_
+#define _IIC_HID_H_
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbhid.h>
+#include <sys/mouse.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/taskqueue.h>
+
+#include "opt_evdev.h"
+
+/* 5.1.1 - HID Descriptor Format */
+struct i2c_hid_desc {
+ uint16_t wHIDDescLength;
+ uint16_t bcdVersion;
+ uint16_t wReportDescLength;
+ uint16_t wReportDescRegister;
+ uint16_t wInputRegister;
+ uint16_t wMaxInputLength;
+ uint16_t wOutputRegister;
+ uint16_t wMaxOutputLength;
+ uint16_t wCommandRegister;
+ uint16_t wDataRegister;
+ uint16_t wVendorID;
+ uint16_t wProductID;
+ uint16_t wVersionID;
+ uint32_t reserved;
+} __packed;
+
+#define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE)
+#define MOUSE_FLAGS (HIO_RELATIVE)
+
+#define MS_BUF_SIZE 8 /* bytes */
+#define MS_BUFQ_MAXLEN 100 /* units */
+#define MS_BUTTON_MAX 31 /* exclusive, must be less than 32 */
+#define MS_BUT(i) ((i) < 3 ? (((i) + 2) % 3) : (i))
+#define MS_INFO_MAX 2 /* maximum number of HID sets */
+
+struct ms_info {
+ struct hid_location sc_loc_w;
+ struct hid_location sc_loc_x;
+ struct hid_location sc_loc_y;
+ struct hid_location sc_loc_z;
+ struct hid_location sc_loc_t;
+ struct hid_location sc_loc_btn[MS_BUTTON_MAX];
+
+ uint32_t sc_flags;
+#define MS_FLAG_X_AXIS 0x0001
+#define MS_FLAG_Y_AXIS 0x0002
+#define MS_FLAG_Z_AXIS 0x0004
+#define MS_FLAG_T_AXIS 0x0008
+#define MS_FLAG_SBU 0x0010 /* spurious button up events */
+#define MS_FLAG_REVZ 0x0020 /* Z-axis is reversed */
+#define MS_FLAG_W_AXIS 0x0040
+
+ uint8_t sc_iid_w;
+ uint8_t sc_iid_x;
+ uint8_t sc_iid_y;
+ uint8_t sc_iid_z;
+ uint8_t sc_iid_t;
+ uint8_t sc_iid_btn[MS_BUTTON_MAX];
+ uint8_t sc_buttons;
+};
+
+struct iichid_hw {
+ uint8_t device_addr;
+ uint16_t config_reg;
+};
+
+struct ms_tx_entry {
+ STAILQ_ENTRY(ms_tx_entry) next;
+ uint8_t buf[MS_BUF_SIZE];
+};
+
+STAILQ_HEAD(ms_tx_buf, ms_tx_entry);
+
+struct iichid_softc {
+ device_t dev;
+ struct cdev *cdev;
+ bool isopen;
+ struct ms_tx_entry rbuf;
+ uint8_t bytesread;
+
+ struct ms_tx_buf ms_unused_blocks;
+ struct ms_tx_buf ms_queue;
+
+ struct iichid_hw hw;
+
+ task_fn_t *event_handler;
+
+ struct i2c_hid_desc desc;
+ struct ms_info info[MS_INFO_MAX];
+ uint8_t sc_iid;
+ mousehw_t sc_hw;
+ mousestatus_t sc_status;
+ mousemode_t sc_mode;
+ struct mtx lock;
+
+ struct cv cv;
+ bool detaching;
+
+ int invert;
+
+ uint8_t *input_buf;
+ int input_size;
+
+#ifdef EVDEV_SUPPORT
+ int sc_evflags;
+#define MS_EVDEV_OPENED 1
+ struct evdev_dev *sc_evdev;
+#endif
+};
+
+struct acpi_iichid_softc {
+ device_t dev;
+ struct cdev *sc_devnode;
+
+ struct iichid_hw hw;
+
+ uint16_t irq;
+ int irq_rid;
+ struct resource* irq_res;
+ void* irq_cookie;
+ struct iichid_softc* iichid_sc;
+
+ int sampling_rate;
+ struct callout periodic_callout;
+ bool callout_setup;
+
+ struct taskqueue* taskqueue;
+ struct task event_task;
+
+ struct mtx lock;
+};
+
+int acpi_iichid_get_report(device_t dev, struct i2c_hid_desc* hid_desc, enum hid_kind type, int id, void *data, int len);
+
+
+#endif /* _IIC_HID_H_ */
diff --git a/sys/modules/acpi/acpi_iichid/Makefile b/sys/modules/acpi/acpi_iichid/Makefile
new file mode 100644
index 000000000000..2c9914de1e18
--- /dev/null
+++ b/sys/modules/acpi/acpi_iichid/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.PATH: ${SRCTOP}/sys/dev/iicbus/input
+KMOD = acpi_iichid
+SRCS = acpi_iichid.c device_if.h bus_if.h iicbus_if.h vnode_if.h opt_acpi.h acpi_if.h opt_usb.h
+
+.include <bsd.kmod.mk>
diff --git a/sys/modules/i2c/Makefile b/sys/modules/i2c/Makefile
index aec95c7eb3e4..020616a8a3ab 100644
--- a/sys/modules/i2c/Makefile
+++ b/sys/modules/i2c/Makefile
@@ -13,6 +13,7 @@ SUBDIR = \
iic \
iicbb \
iicbus \
+ iichid \
iicsmb \
isl \
isl12xx \
diff --git a/sys/modules/i2c/iichid/Makefile b/sys/modules/i2c/iichid/Makefile
new file mode 100644
index 000000000000..31b56e984331
--- /dev/null
+++ b/sys/modules/i2c/iichid/Makefile
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.PATH: ${SRCTOP}/sys/dev/iicbus/input
+KMOD = iichid
+SRCS = iichid.c device_if.h bus_if.h iicbus_if.h vnode_if.h opt_usb.h
+
+.include <bsd.kmod.mk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment