Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F102170513
D16698.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
46 KB
Referenced Files
None
Subscribers
None
D16698.id.diff
View Options
Index: sys/conf/files
===================================================================
--- sys/conf/files
+++ sys/conf/files
@@ -1853,6 +1853,7 @@
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"
Index: sys/dev/iicbus/input/acpi_iichid.c
===================================================================
--- /dev/null
+++ sys/dev/iicbus/input/acpi_iichid.c
@@ -0,0 +1,549 @@
+/*-
+ * Copyright (c) 2019 Marc Priggemeyer <marc.priggemeyer@gmail.com>
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/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)
+ 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);
Index: sys/dev/iicbus/input/iichid.h
===================================================================
--- /dev/null
+++ sys/dev/iicbus/input/iichid.h
@@ -0,0 +1,153 @@
+/* $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>
+
+/* 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;
+ int power_state;
+
+ uint8_t *input_buf;
+ int input_size;
+};
+
+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_ */
Index: sys/dev/iicbus/input/iichid.c
===================================================================
--- /dev/null
+++ sys/dev/iicbus/input/iichid.c
@@ -0,0 +1,957 @@
+/*-
+ * Copyright (c) 2019 Marc Priggemeyer <marc.priggemeyer@gmail.com>
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/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"
+
+static device_probe_t iichid_probe;
+static device_attach_t iichid_attach;
+static device_detach_t iichid_detach;
+static device_suspend_t iichid_suspend;
+static device_resume_t iichid_resume;
+
+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(device_suspend, iichid_suspend),
+ DEVMETHOD(device_resume, iichid_resume),
+
+ 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),
+};
+
+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_write_register(device_t dev, uint8_t* cmd, int cmdlen)
+{
+ uint16_t addr = iicbus_get_addr(dev);
+ struct iic_msg msgs[] = {
+ { addr << 1, IIC_M_WR, cmdlen, cmd },
+ };
+
+ 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
+iichid_set_power(device_t dev, bool sleep)
+{
+ struct iichid_softc* sc;
+ sc = device_get_softc(dev);
+
+ mtx_assert(&sc->lock, MA_OWNED);
+
+ uint16_t cmdreg = htole16(sc->desc.wCommandRegister);
+
+ uint8_t power_state = 0; //default on
+ if( sleep )
+ power_state = 1; //sleep
+
+ uint8_t cmd[] = {cmdreg & 0xff, cmdreg >> 8, power_state, 0x08}; //0x08, 4 reserved bits plus opcode (4 bit)
+ int cmdlen = 4;
+
+ mtx_unlock(&sc->lock);
+
+ int error = iichid_write_register(dev, cmd, cmdlen);
+
+ mtx_lock(&sc->lock);
+
+ return error;
+}
+
+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;
+}
+
+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");
+ }
+}
+
+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;
+ 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;
+ }
+
+ 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);
+ }
+ }
+
+// 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
+sysctl_power_state_handler(SYSCTL_HANDLER_ARGS)
+{
+ int err, value;
+ struct iichid_softc *sc;
+
+ sc = arg1;
+
+ mtx_lock(&sc->lock);
+
+ value = sc->power_state;
+ err = sysctl_handle_int(oidp, &value, 0, req);
+
+ if (err != 0 || req->newptr == NULL || value == sc->power_state)
+ {
+ mtx_unlock(&sc->lock);
+ return (err);
+ }
+
+ sc->power_state = value;
+ bool sleep = false;
+ if( sc->power_state != 0 )
+ sleep = true;
+
+ err = iichid_set_power(sc->dev, sleep);
+
+ if (err != 0)
+ {
+ device_printf(sc->dev, "Could not set power_state, error code: %d\n", err);
+ } else {
+ device_printf(sc->dev, "Successfully set power_state to %d\n", sc->power_state);
+ }
+
+ 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;
+
+ sc->dev = dev;
+ sc->detaching = false;
+ sc->input_buf = NULL;
+ sc->isopen = 0;
+ sc->invert = 0;
+ sc->power_state = 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");
+
+ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
+ OID_AUTO, "power_state", CTLTYPE_INT | CTLFLAG_RWTUN,
+ sc, 0,
+ sysctl_power_state_handler, "I", "set to 1 for sleep");
+
+ // 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);
+ }
+
+// 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);
+ }
+ }
+
+ if (sc->cdev)
+ destroy_dev(sc->cdev);
+
+ if (sc->input_buf)
+ free(sc->input_buf, M_DEVBUF);
+ }
+ return (0);
+}
+
+static int
+iichid_suspend(device_t dev)
+{
+ int err;
+ struct iichid_softc *sc = device_get_softc(dev);
+ mtx_lock(&sc->lock);
+
+ device_printf(dev, "Suspend called, setting device to power_state 1\n");
+
+ err = iichid_set_power(sc->dev, true);
+
+ if (err != 0)
+ {
+ device_printf(dev, "Could not set power_state, error code: %d\n", err);
+ } else {
+ device_printf(dev, "Successfully set power_state\n");
+ }
+
+ mtx_unlock(&sc->lock);
+
+ return (err);
+}
+
+static
+int
+iichid_resume(device_t dev)
+{
+ int err;
+ struct iichid_softc *sc = device_get_softc(dev);
+ mtx_lock(&sc->lock);
+
+ device_printf(dev, "Suspend called, setting device to power_state 0\n");
+
+ err = iichid_set_power(sc->dev, false);
+
+ if (err != 0)
+ {
+ device_printf(dev, "Could not set power_state, error code: %d\n", err);
+ } else {
+ device_printf(dev, "Successfully set power_state\n");
+ }
+
+ mtx_unlock(&sc->lock);
+
+ return (err);
+}
+
+
+DRIVER_MODULE(iichid, iicbus, iichid_driver, iichid_devclass, NULL, 0);
+MODULE_DEPEND(iichid, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
+MODULE_VERSION(iichid, 1);
Index: sys/modules/acpi/Makefile
===================================================================
--- sys/modules/acpi/Makefile
+++ sys/modules/acpi/Makefile
@@ -1,7 +1,7 @@
# $FreeBSD$
SUBDIR= acpi_asus acpi_asus_wmi acpi_dock acpi_fujitsu acpi_hp \
- acpi_ibm acpi_panasonic acpi_sony acpi_toshiba \
- acpi_video acpi_wmi aibs
+ acpi_ibm acpi_iichid acpi_panasonic acpi_sony \
+ acpi_toshiba acpi_video acpi_wmi aibs
.include <bsd.subdir.mk>
Index: sys/modules/acpi/acpi_iichid/Makefile
===================================================================
--- /dev/null
+++ 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>
Index: sys/modules/i2c/Makefile
===================================================================
--- sys/modules/i2c/Makefile
+++ sys/modules/i2c/Makefile
@@ -12,6 +12,7 @@
iic \
iicbb \
iicbus \
+ iichid \
iicsmb \
isl \
isl12xx \
Index: sys/modules/i2c/iichid/Makefile
===================================================================
--- /dev/null
+++ 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>
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 9, 11:39 AM (20 h, 25 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
14558961
Default Alt Text
D16698.id.diff (46 KB)
Attached To
Mode
D16698: First draft HID over I2C support (Mouse only)
Attached
Detach File
Event Timeline
Log In to Comment