diff --git a/sys/dev/i2c/ihidev.c b/sys/dev/i2c/ihidev.c index 0575f9811934..1a622fd242c8 100644 --- a/sys/dev/i2c/ihidev.c +++ b/sys/dev/i2c/ihidev.c @@ -60,7 +60,7 @@ __KERNEL_RCSID(0, "$NetBSD: ihidev.c,v 1.20 2021/08/07 16:19:11 thorpej Exp $"); #include #include #include - +#include #include #include @@ -110,14 +110,14 @@ static int ihidev_detach(device_t, int); CFATTACH_DECL_NEW(ihidev, sizeof(struct ihidev_softc), ihidev_match, ihidev_attach, ihidev_detach, NULL); -static bool ihiddev_intr_init(struct ihidev_softc *); -static void ihiddev_intr_fini(struct ihidev_softc *); +static bool ihidev_intr_init(struct ihidev_softc *); +static void ihidev_intr_fini(struct ihidev_softc *); static bool ihidev_suspend(device_t, const pmf_qual_t *); static bool ihidev_resume(device_t, const pmf_qual_t *); static int ihidev_hid_command(struct ihidev_softc *, int, void *, bool); static int ihidev_intr(void *); -static void ihidev_softintr(void *); +static void ihidev_work(struct work *, void *); static int ihidev_reset(struct ihidev_softc *, bool); static int ihidev_hid_desc_parse(struct ihidev_softc *); @@ -160,14 +160,14 @@ ihidev_attach(device_t parent, device_t self, void *aux) sc->sc_dev = self; sc->sc_tag = ia->ia_tag; sc->sc_addr = ia->ia_addr; - mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_VM); + mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE); sc->sc_phandle = ia->ia_cookie; if (ia->ia_cookietype != I2C_COOKIE_ACPI) { aprint_error(": unsupported device tree type\n"); return; } - if (! ihidev_acpi_get_info(sc)) { + if (!ihidev_acpi_get_info(sc)) { return; } @@ -208,7 +208,7 @@ ihidev_attach(device_t parent, device_t self, void *aux) repsz)); } sc->sc_ibuf = kmem_zalloc(sc->sc_isize, KM_SLEEP); - if (! ihiddev_intr_init(sc)) { + if (!ihidev_intr_init(sc)) { return; } @@ -245,7 +245,8 @@ ihidev_attach(device_t parent, device_t self, void *aux) } /* power down until we're opened */ - if (ihidev_hid_command(sc, I2C_HID_CMD_SET_POWER, &I2C_HID_POWER_OFF, false)) { + if (ihidev_hid_command(sc, I2C_HID_CMD_SET_POWER, &I2C_HID_POWER_OFF, + false)) { aprint_error_dev(sc->sc_dev, "failed to power down\n"); return; } @@ -257,13 +258,19 @@ static int ihidev_detach(device_t self, int flags) { struct ihidev_softc *sc = device_private(self); + int error; + + error = config_detach_children(self, flags); + if (error) + return error; + + pmf_device_deregister(self); + ihidev_intr_fini(sc); + + if (ihidev_hid_command(sc, I2C_HID_CMD_SET_POWER, &I2C_HID_POWER_OFF, + true)) + aprint_error_dev(sc->sc_dev, "failed to power down\n"); - mutex_enter(&sc->sc_intr_lock); - ihiddev_intr_fini(sc); - if (ihidev_hid_command(sc, I2C_HID_CMD_SET_POWER, - &I2C_HID_POWER_OFF, true)) - aprint_error_dev(sc->sc_dev, "failed to power down\n"); - mutex_exit(&sc->sc_intr_lock); if (sc->sc_ibuf != NULL) { kmem_free(sc->sc_ibuf, sc->sc_isize); sc->sc_ibuf = NULL; @@ -272,8 +279,9 @@ ihidev_detach(device_t self, int flags) if (sc->sc_report != NULL) kmem_free(sc->sc_report, sc->sc_reportlen); - pmf_device_deregister(self); - return (0); + mutex_destroy(&sc->sc_lock); + + return 0; } static bool @@ -281,14 +289,14 @@ ihidev_suspend(device_t self, const pmf_qual_t *q) { struct ihidev_softc *sc = device_private(self); - mutex_enter(&sc->sc_intr_lock); + mutex_enter(&sc->sc_lock); if (sc->sc_refcnt > 0) { printf("ihidev power off\n"); if (ihidev_hid_command(sc, I2C_HID_CMD_SET_POWER, &I2C_HID_POWER_OFF, true)) aprint_error_dev(sc->sc_dev, "failed to power down\n"); } - mutex_exit(&sc->sc_intr_lock); + mutex_exit(&sc->sc_lock); return true; } @@ -297,12 +305,12 @@ ihidev_resume(device_t self, const pmf_qual_t *q) { struct ihidev_softc *sc = device_private(self); - mutex_enter(&sc->sc_intr_lock); + mutex_enter(&sc->sc_lock); if (sc->sc_refcnt > 0) { printf("ihidev power reset\n"); ihidev_reset(sc, true); } - mutex_exit(&sc->sc_intr_lock); + mutex_exit(&sc->sc_lock); return true; } @@ -408,6 +416,13 @@ ihidev_hid_command(struct ihidev_softc *sc, int hidcmd, void *arg, bool poll) */ report_len += report_id_len; tmprep = kmem_zalloc(report_len, KM_NOSLEEP); + if (tmprep == NULL) { + /* XXX pool or preallocate? */ + DPRINTF(("%s: out of memory\n", + device_xname(sc->sc_dev))); + res = ENOMEM; + break; + } /* type 3 id 8: 22 00 38 02 23 00 */ res = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, @@ -499,6 +514,10 @@ ihidev_hid_command(struct ihidev_softc *sc, int hidcmd, void *arg, bool poll) cmd[dataoff] = rreq->id; finalcmd = kmem_zalloc(cmdlen + rreq->len, KM_NOSLEEP); + if (finalcmd == NULL) { + res = ENOMEM; + break; + } memcpy(finalcmd, cmd, cmdlen); memcpy(finalcmd + cmdlen, rreq->data, rreq->len); @@ -635,7 +654,7 @@ ihidev_hid_desc_parse(struct ihidev_softc *sc) } sc->sc_reportlen = le16toh(sc->hid_desc.wReportDescLength); - sc->sc_report = kmem_zalloc(sc->sc_reportlen, KM_NOSLEEP); + sc->sc_report = kmem_zalloc(sc->sc_reportlen, KM_SLEEP); if (ihidev_hid_command(sc, I2C_HID_REPORT_DESCR, 0, false)) { aprint_error_dev(sc->sc_dev, "failed fetching HID report\n"); @@ -646,7 +665,7 @@ ihidev_hid_desc_parse(struct ihidev_softc *sc) } static bool -ihiddev_intr_init(struct ihidev_softc *sc) +ihidev_intr_init(struct ihidev_softc *sc) { #if NACPICA > 0 ACPI_HANDLE hdl = (void *)(uintptr_t)sc->sc_phandle; @@ -682,12 +701,13 @@ ihiddev_intr_init(struct ihidev_softc *sc) aprint_normal_dev(sc->sc_dev, "interrupting at %s\n", acpi_intr_string(sc->sc_ih, buf, sizeof(buf))); - sc->sc_sih = softint_establish(SOFTINT_SERIAL, ihidev_softintr, sc); - if (sc->sc_sih == NULL) { + if (workqueue_create(&sc->sc_wq, device_xname(sc->sc_dev), ihidev_work, + sc, PRI_NONE, IPL_TTY, WQ_MPSAFE)) { aprint_error_dev(sc->sc_dev, - "can't establish soft interrupt\n"); + "can't establish workqueue\n"); return false; } + sc->sc_work_pending = 0; return true; #else @@ -697,14 +717,14 @@ ihiddev_intr_init(struct ihidev_softc *sc) } static void -ihiddev_intr_fini(struct ihidev_softc *sc) +ihidev_intr_fini(struct ihidev_softc *sc) { #if NACPICA > 0 if (sc->sc_ih != NULL) { acpi_intr_disestablish(sc->sc_ih); } - if (sc->sc_sih != NULL) { - softint_disestablish(sc->sc_sih); + if (sc->sc_wq != NULL) { + workqueue_destroy(sc->sc_wq); } #endif } @@ -736,23 +756,19 @@ ihidev_intr(void *arg) { struct ihidev_softc * const sc = arg; - mutex_enter(&sc->sc_intr_lock); - /* - * Schedule our soft interrupt handler. If we're using a level- - * triggered interrupt, we have to mask it off while we wait - * for service. + * Schedule our work. If we're using a level-triggered + * interrupt, we have to mask it off while we wait for service. */ - softint_schedule(sc->sc_sih); + if (atomic_swap_uint(&sc->sc_work_pending, 1) == 0) + workqueue_enqueue(sc->sc_wq, &sc->sc_work, NULL); ihidev_intr_mask(sc); - mutex_exit(&sc->sc_intr_lock); - return 1; } static void -ihidev_softintr(void *arg) +ihidev_work(struct work *wk, void *arg) { struct ihidev_softc * const sc = arg; struct ihidev *scd; @@ -761,13 +777,14 @@ ihidev_softintr(void *arg) u_char *p; u_int rep = 0; - mutex_enter(&sc->sc_intr_lock); + atomic_store_relaxed(&sc->sc_work_pending, 0); + + mutex_enter(&sc->sc_lock); + iic_acquire_bus(sc->sc_tag, 0); res = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, NULL, 0, sc->sc_ibuf, sc->sc_isize, 0); iic_release_bus(sc->sc_tag, 0); - mutex_exit(&sc->sc_intr_lock); - if (res != 0) goto out; @@ -807,6 +824,8 @@ ihidev_softintr(void *arg) scd->sc_intr(scd, p, psize); out: + mutex_exit(&sc->sc_lock); + /* * If our interrupt is level-triggered, re-enable it now. */ @@ -837,7 +856,7 @@ ihidev_print(void *aux, const char *pnp) if (iha->reportid == IHIDEV_CLAIM_ALLREPORTID) return (QUIET); - + if (pnp) aprint_normal("hid at %s", pnp); @@ -863,22 +882,29 @@ int ihidev_open(struct ihidev *scd) { struct ihidev_softc *sc = scd->sc_parent; + int error; DPRINTF(("%s: %s: state=%d refcnt=%d\n", sc->sc_dev.dv_xname, __func__, scd->sc_state, sc->sc_refcnt)); - if (scd->sc_state & IHIDEV_OPEN) - return (EBUSY); + mutex_enter(&sc->sc_lock); + + if (scd->sc_state & IHIDEV_OPEN) { + error = EBUSY; + goto out; scd->sc_state |= IHIDEV_OPEN; - if (sc->sc_refcnt++ || sc->sc_isize == 0) - return (0); + if (sc->sc_refcnt++ || sc->sc_isize == 0) { + error = 0; + goto out; + } /* power on */ ihidev_reset(sc, false); + error = 0; - return (0); +out: mutex_exit(&sc->sc_lock); } void @@ -889,17 +915,22 @@ ihidev_close(struct ihidev *scd) DPRINTF(("%s: %s: state=%d refcnt=%d\n", sc->sc_dev.dv_xname, __func__, scd->sc_state, sc->sc_refcnt)); + mutex_enter(&sc->sc_lock); + + /* XXX make this an assertion */ if (!(scd->sc_state & IHIDEV_OPEN)) - return; + goto out; scd->sc_state &= ~IHIDEV_OPEN; if (--sc->sc_refcnt) - return; + goto out; if (ihidev_hid_command(sc, I2C_HID_CMD_SET_POWER, &I2C_HID_POWER_OFF, false)) aprint_error_dev(sc->sc_dev, "failed to power down\n"); + +out: mutex_exit(&sc->sc_lock); } void @@ -974,7 +1005,7 @@ ihidev_set_report(struct device *dev, int type, int id, void *data, static bool ihidev_acpi_get_info(struct ihidev_softc *sc) -{ +{ ACPI_HANDLE hdl = (void *)(uintptr_t)sc->sc_phandle; ACPI_STATUS status; ACPI_INTEGER val; diff --git a/sys/dev/i2c/ihidev.h b/sys/dev/i2c/ihidev.h index a46e6c1fdf49..a00bb27db394 100644 --- a/sys/dev/i2c/ihidev.h +++ b/sys/dev/i2c/ihidev.h @@ -30,6 +30,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#ifndef _DEV_I2C_IHIDEV_H_ +#define _DEV_I2C_IHIDEV_H_ + +/* ihidevreg.h */ + /* * HID-over-i2c driver * @@ -48,6 +53,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include + /* from usbdi.h: Match codes. */ /* First five codes is for a whole device. */ #define IMATCH_VENDOR_PRODUCT_REV 14 @@ -93,15 +100,27 @@ struct i2c_hid_desc { uint32_t reserved; } __packed; +/* ihidevvar.h */ + +#include + +#include +#include +#include + +#include + struct ihidev_softc { device_t sc_dev; i2c_tag_t sc_tag; i2c_addr_t sc_addr; uint64_t sc_phandle; + kmutex_t sc_lock; void * sc_ih; - void * sc_sih; - kmutex_t sc_intr_lock; + struct workqueue *sc_wq; + struct work sc_work; + volatile unsigned sc_work_pending; int sc_intr_type; u_int sc_hid_desc_addr; @@ -160,3 +179,4 @@ int ihidev_set_report(device_t, int, int, void *, int); int ihidev_get_report(device_t, int, int, void *, int); int ihidev_report_type_conv(int); +#endif /* _DEV_I2C_IHIDEV_H_ */