summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Stein <jcs@cvs.openbsd.org>2016-01-20 01:19:29 +0000
committerJoshua Stein <jcs@cvs.openbsd.org>2016-01-20 01:19:29 +0000
commit931b99bb793b391a061ef120612a93eed0984b70 (patch)
treea8d8cefb0ca3f572555d01478bb475668d89de26
parent465ebb74831fe1deef11460bd218afecab82c343 (diff)
add the ability to set and get reports
establish interrupt before probing for devices to handle each report id sign an int, found by jsg
-rw-r--r--sys/dev/i2c/ihidev.c381
-rw-r--r--sys/dev/i2c/ihidev.h21
2 files changed, 314 insertions, 88 deletions
diff --git a/sys/dev/i2c/ihidev.c b/sys/dev/i2c/ihidev.c
index 23637e93be1..fc796a2ae07 100644
--- a/sys/dev/i2c/ihidev.c
+++ b/sys/dev/i2c/ihidev.c
@@ -1,8 +1,8 @@
-/* $OpenBSD: ihidev.c,v 1.7 2016/01/14 21:31:27 kettenis Exp $ */
+/* $OpenBSD: ihidev.c,v 1.8 2016/01/20 01:19:28 jcs Exp $ */
/*
* HID-over-i2c driver
*
- * http://download.microsoft.com/download/7/d/d/7dd44bb7-2a7a-4505-ac1c-7227d3d96d5b/hid-over-i2c-protocol-spec-v1-0.docx
+ * https://msdn.microsoft.com/en-us/library/windows/hardware/dn642101%28v=vs.85%29.aspx
*
* Copyright (c) 2015, 2016 joshua stein <jcs@openbsd.org>
*
@@ -60,15 +60,6 @@ enum {
static int I2C_HID_POWER_ON = 0x0;
static int I2C_HID_POWER_OFF = 0x1;
-union i2c_hid_cmd {
- uint8_t data[0];
- struct cmd {
- uint16_t reg;
- uint8_t reportTypeId;
- uint8_t opcode;
- } __packed c;
-};
-
int ihidev_match(struct device *, void *, void *);
void ihidev_attach(struct device *, struct device *, void *);
int ihidev_detach(struct device *, int);
@@ -147,19 +138,7 @@ ihidev_attach(struct device *parent, struct device *self, void *aux)
return;
}
- iha.iaa = ia;
- iha.parent = sc;
- iha.reportid = IHIDEV_CLAIM_ALLREPORTID;
-
- /* Look for a driver claiming all report IDs first. */
- dev = config_found_sm((struct device *)sc, &iha, NULL,
- ihidev_submatch);
- if (dev != NULL) {
- for (repid = 0; repid < sc->sc_nrepid; repid++)
- sc->sc_subdevs[repid] = (struct ihidev *)dev;
- return;
- }
-
+ /* find largest report size and allocate memory for input buffer */
sc->sc_isize = letoh16(sc->hid_desc.wMaxInputLength);
for (repid = 0; repid < sc->sc_nrepid; repid++) {
repsz = hid_report_size(sc->sc_report, sc->sc_reportlen,
@@ -173,7 +152,34 @@ ihidev_attach(struct device *parent, struct device *self, void *aux)
DPRINTF(("%s: repid %d size %d\n", sc->sc_dev.dv_xname, repid,
repsz));
+ }
+ sc->sc_ibuf = malloc(sc->sc_isize, M_DEVBUF, M_NOWAIT | M_ZERO);
+ /* register interrupt with system */
+ if (ia->ia_int > 0) {
+ /* XXX: don't assume this uses acpi_intr_establish */
+ sc->sc_ih = acpi_intr_establish(ia->ia_int, ia->ia_int_flags,
+ IPL_BIO, ihidev_intr, sc, sc->sc_dev.dv_xname);
+ if (sc->sc_ih == NULL) {
+ printf(", failed establishing intr\n");
+ return;
+ }
+ }
+
+ iha.iaa = ia;
+ iha.parent = sc;
+
+ /* Look for a driver claiming all report IDs first. */
+ iha.reportid = IHIDEV_CLAIM_ALLREPORTID;
+ dev = config_found_sm((struct device *)sc, &iha, NULL,
+ ihidev_submatch);
+ if (dev != NULL) {
+ for (repid = 0; repid < sc->sc_nrepid; repid++)
+ sc->sc_subdevs[repid] = (struct ihidev *)dev;
+ return;
+ }
+
+ for (repid = 0; repid < sc->sc_nrepid; repid++) {
if (hid_report_size(sc->sc_report, sc->sc_reportlen, hid_input,
repid) == 0 &&
hid_report_size(sc->sc_report, sc->sc_reportlen,
@@ -187,18 +193,6 @@ ihidev_attach(struct device *parent, struct device *self, void *aux)
ihidev_submatch);
sc->sc_subdevs[repid] = (struct ihidev *)dev;
}
- sc->sc_ibuf = malloc(sc->sc_isize, M_DEVBUF, M_WAITOK);
-
- /* register interrupt with system */
- if (ia->ia_int > 0) {
- /* XXX: don't assume this uses acpi_intr_establish */
- sc->sc_ih = acpi_intr_establish(ia->ia_int, ia->ia_int_flags,
- IPL_BIO, ihidev_intr, sc, sc->sc_dev.dv_xname);
- if (sc->sc_ih == NULL) {
- printf(", failed establishing intr\n");
- return;
- }
- }
/* power down until we're opened */
if (ihidev_hid_command(sc, I2C_HID_CMD_SET_POWER, &I2C_HID_POWER_OFF)) {
@@ -239,17 +233,19 @@ ihidev_hid_command(struct ihidev_softc *sc, int hidcmd, void *arg)
case I2C_HID_CMD_DESCR: {
/*
* 5.2.2 - HID Descriptor Retrieval
- * register is passed from the controller, and is probably just
- * the address of the device
+ * register is passed from the controller
*/
- uint8_t cmdbuf[] = { htole16(sc->sc_hid_desc_addr), 0x0 };
+ uint8_t cmd[] = {
+ htole16(sc->sc_hid_desc_addr),
+ 0,
+ };
DPRINTF(("%s: HID command I2C_HID_CMD_DESCR at 0x%x\n",
sc->sc_dev.dv_xname, htole16(sc->sc_hid_desc_addr)));
/* 20 00 */
res = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
- &cmdbuf, sizeof(cmdbuf), &sc->hid_desc_buf,
+ &cmd, sizeof(cmd), &sc->hid_desc_buf,
sizeof(struct i2c_hid_desc), 0);
DPRINTF(("%s: HID descriptor:", sc->sc_dev.dv_xname));
@@ -260,57 +256,218 @@ ihidev_hid_command(struct ihidev_softc *sc, int hidcmd, void *arg)
break;
}
case I2C_HID_CMD_RESET: {
- uint8_t cmdbuf[4] = { 0 };
- union i2c_hid_cmd *cmd = (union i2c_hid_cmd *)cmdbuf;
+ uint8_t cmd[] = {
+ sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
+ wCommandRegister)],
+ sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
+ wCommandRegister) + 1],
+ 0,
+ I2C_HID_CMD_RESET,
+ };
DPRINTF(("%s: HID command I2C_HID_CMD_RESET\n",
sc->sc_dev.dv_xname));
- cmd->data[0] = sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
- wCommandRegister)];
- cmd->data[1] = sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
- wCommandRegister) + 1];
- cmd->c.opcode = I2C_HID_CMD_RESET;
-
/* 22 00 00 01 */
res = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
- &cmdbuf, sizeof(cmdbuf), NULL, 0, 0);
+ &cmd, sizeof(cmd), NULL, 0, 0);
break;
}
+ case I2C_HID_CMD_GET_REPORT: {
+ struct i2c_hid_report_request *rreq =
+ (struct i2c_hid_report_request *)arg;
+
+ uint8_t cmd[] = {
+ sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
+ wCommandRegister)],
+ sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
+ wCommandRegister) + 1],
+ 0,
+ I2C_HID_CMD_GET_REPORT,
+ 0, 0, 0,
+ };
+ int cmdlen = 7;
+ int dataoff = 4;
+ int report_id = rreq->id;
+ int report_id_len = 1;
+ int report_len = rreq->len + 2;
+ int d;
+ uint8_t *tmprep;
+
+ DPRINTF(("%s: HID command I2C_HID_CMD_GET_REPORT %d "
+ "(type %d, len %d)\n", sc->sc_dev.dv_xname, report_id,
+ rreq->type, rreq->len));
+
+ /*
+ * 7.2.2.4 - "The protocol is optimized for Report < 15. If a
+ * report ID >= 15 is necessary, then the Report ID in the Low
+ * Byte must be set to 1111 and a Third Byte is appended to the
+ * protocol. This Third Byte contains the entire/actual report
+ * ID."
+ */
+ if (report_id >= 15) {
+ cmd[dataoff++] = report_id;
+ report_id = 15;
+ report_id_len = 2;
+ } else
+ cmdlen--;
+
+ cmd[2] = report_id | rreq->type << 4;
+
+ cmd[dataoff++] = sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
+ wDataRegister)] & 0xff;
+ cmd[dataoff] = sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
+ wDataRegister)] >> 8;
+
+ /*
+ * 7.2.2.2 - Response will be a 2-byte length value, the report
+ * id with length determined above, and then the report.
+ * Allocate rreq->len + 2 + 2 bytes, read into that temporary
+ * buffer, and then copy only the report back out to
+ * rreq->data.
+ */
+ report_len += report_id_len;
+ tmprep = malloc(report_len, M_DEVBUF, M_NOWAIT | M_ZERO);
+
+ /* type 3 id 8: 22 00 38 02 23 00 */
+ res = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
+ &cmd, cmdlen, tmprep, report_len, 0);
+
+ d = tmprep[0] | tmprep[1] << 8;
+ if (d != report_len)
+ DPRINTF(("%s: response size %d != expected length %d\n",
+ sc->sc_dev.dv_xname, d, report_len));
+
+ if (report_id_len == 2)
+ d = tmprep[2] | tmprep[3] << 8;
+ else
+ d = tmprep[2];
+
+ if (d != rreq->id) {
+ DPRINTF(("%s: response report id %d != %d\n",
+ sc->sc_dev.dv_xname, d, rreq->id));
+ iic_release_bus(sc->sc_tag, 0);
+ return (1);
+ }
+
+ DPRINTF(("%s: response:", sc->sc_dev.dv_xname));
+ for (i = 0; i < report_len; i++)
+ DPRINTF((" %.2x", tmprep[i]));
+ DPRINTF(("\n"));
+
+ memcpy(rreq->data, tmprep + 2 + report_id_len, rreq->len);
+ free(tmprep, M_DEVBUF, report_len);
+
+ break;
+ }
+ case I2C_HID_CMD_SET_REPORT: {
+ struct i2c_hid_report_request *rreq =
+ (struct i2c_hid_report_request *)arg;
+
+ uint8_t cmd[] = {
+ sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
+ wCommandRegister)],
+ sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
+ wCommandRegister) + 1],
+ 0,
+ I2C_HID_CMD_SET_REPORT,
+ 0, 0, 0, 0, 0, 0,
+ };
+ int cmdlen = 10;
+ int report_id = rreq->id;
+ int report_len = 2 + (report_id ? 1 : 0) + rreq->len;
+ int dataoff;
+ uint8_t *finalcmd;
+
+ DPRINTF(("%s: HID command I2C_HID_CMD_SET_REPORT %d "
+ "(type %d, len %d):", sc->sc_dev.dv_xname, report_id,
+ rreq->type, rreq->len));
+ for (i = 0; i < rreq->len; i++)
+ DPRINTF((" %.2x", ((uint8_t *)rreq->data)[i]));
+ DPRINTF(("\n"));
+
+ /*
+ * 7.2.2.4 - "The protocol is optimized for Report < 15. If a
+ * report ID >= 15 is necessary, then the Report ID in the Low
+ * Byte must be set to 1111 and a Third Byte is appended to the
+ * protocol. This Third Byte contains the entire/actual report
+ * ID."
+ */
+ dataoff = 4;
+ if (report_id >= 15) {
+ cmd[dataoff++] = report_id;
+ report_id = 15;
+ } else
+ cmdlen--;
+
+ cmd[2] = report_id | rreq->type << 4;
+
+ if (rreq->type == I2C_HID_REPORT_TYPE_FEATURE) {
+ cmd[dataoff++] = sc->hid_desc_buf[offsetof(
+ struct i2c_hid_desc, wDataRegister)] & 0xff;
+ cmd[dataoff++] = sc->hid_desc_buf[offsetof(
+ struct i2c_hid_desc, wDataRegister)] >> 8;
+ } else {
+ cmd[dataoff++] = sc->hid_desc_buf[offsetof(
+ struct i2c_hid_desc, wOutputRegister)] & 0xff;
+ cmd[dataoff++] = sc->hid_desc_buf[offsetof(
+ struct i2c_hid_desc, wOutputRegister)] >> 8;
+ }
+
+ cmd[dataoff++] = report_len & 0xff;
+ cmd[dataoff++] = report_len >> 8;
+ cmd[dataoff] = rreq->id;
+
+ finalcmd = malloc(cmdlen + rreq->len, M_DEVBUF,
+ M_NOWAIT | M_ZERO);
+
+ memcpy(finalcmd, cmd, cmdlen);
+ memcpy(finalcmd + cmdlen, rreq->data, rreq->len);
+
+ /* type 3 id 4: 22 00 34 03 23 00 04 00 04 03 */
+ res = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
+ finalcmd, cmdlen + rreq->len, NULL, 0, 0);
+
+ free(finalcmd, M_DEVBUF, cmdlen + rreq->len);
+
+ break;
+ }
+
case I2C_HID_CMD_SET_POWER: {
- uint8_t cmdbuf[4] = { 0 };
- union i2c_hid_cmd *cmd = (union i2c_hid_cmd *)cmdbuf;
int power = *(int *)arg;
+ uint8_t cmd[] = {
+ sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
+ wCommandRegister)],
+ sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
+ wCommandRegister) + 1],
+ power,
+ I2C_HID_CMD_SET_POWER,
+ };
DPRINTF(("%s: HID command I2C_HID_CMD_SET_POWER(%d)\n",
sc->sc_dev.dv_xname, power));
- cmd->data[0] = sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
- wCommandRegister)];
- cmd->data[1] = sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
- wCommandRegister) + 1];
- cmd->c.opcode = I2C_HID_CMD_SET_POWER;
- cmd->c.reportTypeId = power;
-
/* 22 00 00 08 */
res = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
- &cmdbuf, sizeof(cmdbuf), NULL, 0, 0);
+ &cmd, sizeof(cmd), NULL, 0, 0);
break;
}
case I2C_HID_REPORT_DESCR: {
- uint8_t cmdbuf[] = {
- sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
- wReportDescRegister)], 0 };
+ uint8_t cmd[] = {
+ sc->hid_desc_buf[offsetof(struct i2c_hid_desc,
+ wReportDescRegister)],
+ 0,
+ };
DPRINTF(("%s: HID command I2C_HID_REPORT_DESCR at 0x%x with "
- "size %d\n", sc->sc_dev.dv_xname, cmdbuf[0],
+ "size %d\n", sc->sc_dev.dv_xname, cmd[0],
sc->sc_reportlen));
/* 20 00 */
res = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
- &cmdbuf, sizeof(cmdbuf), sc->sc_report, sc->sc_reportlen, 0);
+ &cmd, sizeof(cmd), sc->sc_report, sc->sc_reportlen, 0);
DPRINTF(("%s: HID report descriptor:", sc->sc_dev.dv_xname));
for (i = 0; i < sc->sc_reportlen; i++)
@@ -436,36 +593,35 @@ ihidev_intr(void *arg)
iic_release_bus(sc->sc_tag, I2C_F_POLL);
- DPRINTF(("%s: ihidev_intr: hid input:", sc->sc_dev.dv_xname));
- for (i = 0; i < sc->sc_isize; i++)
- DPRINTF((" %.2x", sc->sc_ibuf[i]));
- DPRINTF(("\n"));
-
+ /*
+ * 6.1.1 - First two bytes are the packet length, which must be less
+ * than or equal to wMaxInputLength
+ */
psize = sc->sc_ibuf[0] | sc->sc_ibuf[1] << 8;
- if (!psize) {
- DPRINTF(("%s: %s: invalid packet size\n", sc->sc_dev.dv_xname,
- __func__));
- return (1);
- }
-
- if (psize > sc->sc_isize) {
- DPRINTF(("%s: %s: truncated packet (%u > %u)\n",
+ if (!psize || psize > sc->sc_isize) {
+ DPRINTF(("%s: %s: invalid packet size (%d vs. %d)\n",
sc->sc_dev.dv_xname, __func__, psize, sc->sc_isize));
return (1);
}
- /* report id is 3rd byte */
+ /* 3rd byte is the report id */
p = sc->sc_ibuf + 2;
psize -= 2;
if (sc->sc_nrepid != 1)
- rep = *p++; psize--;
+ rep = *p++, psize--;
if (rep >= sc->sc_nrepid) {
- printf("%s: %s: bad repid %d\n", sc->sc_dev.dv_xname, __func__,
- rep);
+ printf("%s: %s: bad report id %d\n", sc->sc_dev.dv_xname,
+ __func__, rep);
return (1);
}
+ DPRINTF(("%s: ihidev_intr: hid input (rep %d):", sc->sc_dev.dv_xname,
+ rep));
+ for (i = 0; i < sc->sc_isize; i++)
+ DPRINTF((" %.2x", sc->sc_ibuf[i]));
+ DPRINTF(("\n"));
+
scd = sc->sc_subdevs[rep];
if (scd == NULL || !(scd->sc_state & IHIDEV_OPEN))
return (1);
@@ -574,3 +730,66 @@ ihidev_get_report_desc(struct ihidev_softc *sc, void **desc, int *size)
*desc = sc->sc_report;
*size = sc->sc_reportlen;
}
+
+/* convert hid_* constants used throughout HID code to i2c HID equivalents */
+int
+ihidev_report_type_conv(int hid_type_id)
+{
+ switch (hid_type_id) {
+ case hid_input:
+ return I2C_HID_REPORT_TYPE_INPUT;
+ case hid_output:
+ return I2C_HID_REPORT_TYPE_OUTPUT;
+ case hid_feature:
+ return I2C_HID_REPORT_TYPE_FEATURE;
+ default:
+ return -1;
+ }
+}
+
+int
+ihidev_get_report(struct device *dev, int type, int id, void *data, int len)
+{
+ struct ihidev_softc *sc = (struct ihidev_softc *)dev;
+ struct i2c_hid_report_request rreq;
+ int ctype;
+
+ if ((ctype = ihidev_report_type_conv(type)) < 0)
+ return (1);
+
+ rreq.type = ctype;
+ rreq.id = id;
+ rreq.data = data;
+ rreq.len = len;
+
+ if (ihidev_hid_command(sc, I2C_HID_CMD_GET_REPORT, &rreq)) {
+ printf("%s: failed fetching report\n", sc->sc_dev.dv_xname);
+ return (1);
+ }
+
+ return 0;
+}
+
+int
+ihidev_set_report(struct device *dev, int type, int id, void *data,
+ int len)
+{
+ struct ihidev_softc *sc = (struct ihidev_softc *)dev;
+ struct i2c_hid_report_request rreq;
+ int ctype;
+
+ if ((ctype = ihidev_report_type_conv(type)) < 0)
+ return (1);
+
+ rreq.type = ctype;
+ rreq.id = id;
+ rreq.data = data;
+ rreq.len = len;
+
+ if (ihidev_hid_command(sc, I2C_HID_CMD_SET_REPORT, &rreq)) {
+ printf("%s: failed setting report\n", sc->sc_dev.dv_xname);
+ return (1);
+ }
+
+ return 0;
+}
diff --git a/sys/dev/i2c/ihidev.h b/sys/dev/i2c/ihidev.h
index 47c0e153c11..a10259fa7f4 100644
--- a/sys/dev/i2c/ihidev.h
+++ b/sys/dev/i2c/ihidev.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: ihidev.h,v 1.2 2016/01/13 10:25:31 kettenis Exp $ */
+/* $OpenBSD: ihidev.h,v 1.3 2016/01/20 01:19:28 jcs Exp $ */
/*
* HID-over-i2c driver
*
@@ -80,7 +80,7 @@ struct ihidev_softc {
uint8_t *sc_report;
int sc_reportlen;
- u_int sc_nrepid;
+ int sc_nrepid;
struct ihidev **sc_subdevs;
u_int sc_isize;
@@ -109,13 +109,20 @@ struct ihidev_attach_arg {
#define IHIDEV_CLAIM_ALLREPORTID 255
};
+struct i2c_hid_report_request {
+ u_int id;
+ u_int type;
+#define I2C_HID_REPORT_TYPE_INPUT 0x1
+#define I2C_HID_REPORT_TYPE_OUTPUT 0x2
+#define I2C_HID_REPORT_TYPE_FEATURE 0x3
+ void *data;
+ u_int len;
+};
+
void ihidev_get_report_desc(struct ihidev_softc *, void **, int *);
int ihidev_open(struct ihidev *);
void ihidev_close(struct ihidev *);
int ihidev_ioctl(struct ihidev *, u_long, caddr_t, int, struct proc *);
-int ihidev_set_report(struct ihidev_softc *, int, int, void *, int);
-int ihidev_set_report_async(struct ihidev_softc *, int, int, void *, int);
-int ihidev_get_report(struct ihidev_softc *, int, int, void *, int);
-int ihidev_get_report_async(struct ihidev_softc *, int, int, void *, int,
- void *, void (*)(void *, int, void *, int));
+int ihidev_set_report(struct device *, int, int, void *, int);
+int ihidev_get_report(struct device *, int, int, void *, int);