tech-kern archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
Re: [PATCH] Driver for Elantech I2C touchpad
> Date: Sat, 15 Jul 2023 03:48:54 +0200
> From: "Vladimir 'phcoder' Serbinenko" <phcoder%gmail.com@localhost>
>
> I've submitted a similar patch for OpenBSD recently and it got merged. It
> adds support for Elantech I2C touchpad used on many laptops. Tested on my
> Chromebook Elemi
Cool! This looks great. I don't have any hardware to test, but I can
review the code -- nothing major. Some higher-level questions first:
- Is this device interface substantively different from the ihidev(4)
interface? I notice it uses the same struct i2c_hid_desc; is that a
red herring?
- Looks like this is missing a pmf handler. Does the device require
any action to suspend/resume? If so, your attach routine should do:
pmf_device_register(self, ietp_suspend, ietp_resume);
(Technically pmf_device_register returns a boolean success status,
but in reality it always returns true, and I plan to eliminate the
return value at some point soon; doing it this way will reduce the
churn.)
And your detach routine should do:
pmf_device_deregister(self);
The ietp_suspend and ietp_resume functions are guaranteed not to be
called concurrently with attach or detach. Other than that, they
may need to be synchronized with any other routines that issue i2c
commands, probably with a mutex at interrupt level IPL_NONE.
You can test suspend/resume of just this driver with drvctl(8): use
`drvctl -S ietp0' to suspend, `drvctl -Q ietp0' to resume.
If no action is needed, your attach routine should still do:
pmf_device_register(self, NULL, NULL);
> --- a/sys/dev/i2c/i2c.c
> +++ b/sys/dev/i2c/i2c.c
> @@ -646,7 +646,7 @@ iic_use_direct_match(const struct i2c_attach_args *ia, const cfdata_t cf,
>
> if (ia->ia_ncompat > 0 && ia->ia_compat != NULL) {
> *match_resultp = iic_compatible_match(ia, compats);
> - return true;
> + return *match_resultp != 0;
> }
Why did you make this change?
I don't think this can be right -- and it needs to be considered
separately from one driver import, since the function is used by
zillions of i2c drivers throughout the tree.
> +#include <sys/param.h>
> +#include <sys/systm.h>
> +#include <sys/device.h>
> +#include <sys/malloc.h>
> +#include <sys/stdint.h>
> +#include <sys/kmem.h>
Sort includes like this:
#include <sys/param.h> // special, always goes first
#include <sys/device.h> // lexicographic
#include <sys/kmem.h>
#include <sys/malloc.h>
#include <sys/stdint.h>
#include <sys/systm.h>
> +#define IETP_TOUCH_LMB (1 << 0)
> +#define IETP_TOUCH_RMB (1 << 1)
> +#define IETP_TOUCH_MMB (1 << 2)
Use __BIT instead of shifts here: __BIT(0), __BIT(1), __BIT(2).
> +static int ietp_ioctl(void *dev, u_long cmd, void *data, int flag,
> + struct lwp *l);
Tiny nit: four-space continuation lines, not tab + 3space.
> +static const struct wsmouse_accessops ietp_mouse_access = {
> + ietp_enable,
> + ietp_ioctl,
> + ietp_disable
> +};
Use C99 designated initializers here: `.enable = ietp_enable', &c.
> +ietp_match(device_t parent, cfdata_t match, void *aux)
> +{
> ...
> + if (iic_use_direct_match(ia, match, compat_data, &match_result)) {
> + return I2C_MATCH_DIRECT_COMPATIBLE;
> + }
Why does this return I2C_MATCH_DIRECT_COMPATIBLE rather than
match_result?
The usual idea of iic_use_direct_match is that it returns true if it
has an answer, and match_result is the answer (except that you seem to
have patched that logic away, but I'm not clear on why).
> + return (dpi * 10 /254);
Tiny nit: space on both sides of the operator.
> +ietp_attach(device_t parent, device_t self, void *aux)
> +{
> ...
> + ietp_fetch_descriptor(sc);
Check return value here?
> + sc->sc_ih = acpi_intr_establish(sc->sc_dev, sc->sc_phandle, IPL_TTY, false,
> + ietp_intr, sc, device_xname(sc->sc_dev));
Tiny nits:
- break line before 80 columns
- maybe write `/*mpsafe*/false' for clarity (not your fault, why is
this a boolean and not a named flag?)
- four-space continuation lines
> + printf("%s: failed reading product ID\n", device_xname(sc->sc_dev));
Use aprint_error_dev(sc->sc_dev, "failed to read product ID\n")
instead of printf with device_xname here and everywhere in the attach
routine for this kind of error message.
However, after attach, use device_printf rather than aprint_*_dev.
> +ietp_detach(device_t self, int flags)
> +{
This should start with:
error = config_detach_children(self, flags);
if (error)
return error;
Otherwise, there's nothing to disconnect wsmouse(4) when the device is
detached. You can test the detach path with drvctl(4):
- `drvctl -d ietp0' to detach ietp0
- `drvctl -r iic0' to rescan iic0 (not sure if this will work, but
it's worth a shot!)
> + return (0);
Tiny nit: `return 0', no parentheses.
> +ietp_activate(device_t self, enum devact act)
> +{
> + struct ietp_softc *sc = device_private(self);
> +
> + DPRINTF(("%s(%d)\n", __func__, act));
> +
> + switch (act) {
> + case DVACT_DEACTIVATE:
> + sc->sc_dying = 1;
> + if (ietp_set_power(sc, I2C_HID_POWER_OFF))
> + printf("%s: failed to power down\n",
> + device_xname(sc->sc_dev));
The ietp_set_power call should go in ietp_detach (after
config_detach_children), not in ietp_activate. I don't think
sc->sc_dying is actually needed; more on that in a bit. So I think
the ietp_activate callback can go away.
(The activate callback is a bit of a legacy relic that is really only
relevant in the pcmcia/cardbus driver API, but it remains confusing
for everyone; I think we should just get rid of it.)
> +ietp_iic_set_absolute_mode(struct ietp_softc *sc, bool enable)
> +{
> ...
> + printf("%s: failed writing poweron command\n", device_xname(sc->sc_dev));
device_printf(sc->sc_dev, ...)
> +ietp_iic_read_reg(struct ietp_softc *sc, uint16_t reg, size_t len, void *val)
> +{
> + uint8_t cmd[] = {
> + reg & 0xff,
> + reg >> 8,
> + };
> +
> + return iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
> + &cmd, 2, val, len, 0);
Avoid magic constants -- write __arraycount(cmd) instead of 2 here,
and in ietp_iic_write_reg.
> +parse_input(struct ietp_softc *sc, u_char *report, int len)
> +{
> ...
> + s = spltty();
> ...
> + wsmouse_input(sc->sc_wsmousedev, buttons,
> ...
> + splx(s);
New drivers generally shouldn't use spltty/splx for synchronization.
Instead, you should:
1. Put `kmutex_t sc_intr_lock;' in struct ietp_softc, initialized with
mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_TTY) on attach and
destroyed with mutex_destroy(&sc->sc_intr_lock) on detach.
2. Create a childdetached function for the driver that does this:
mutex_enter(&sc->sc_intr_lock);
if (child == sc->sc_wsmousedev)
sc->sc_wsmousedev = NULL;
mutex_exit(&sc->sc_intr_lock);
3. In the interrupt handler, use the lock and wsmousedev like so:
mutex_enter(&sc->sc_intr_lock);
if (sc->sc_wsmousedev == NULL)
goto out;
...
wsmouse_input(sc->sc_wsmousedev ...);
...
out: mutex_exit(&sc->sc_intr_lock);
With this structure, there is no need for a separate sc->sc_dying
variable. And it can be marked MP-safe as soon as wsmouse(4) is.
...Actually, let's just make wsmouse(4) work safely even if not
called with the kernel lock held so you can mark it MP-safe now.
> +ietp_enable(void *dev)
> +{
> ...
> + if (sc->sc_refcnt++ || sc->sc_isize == 0)
> + return (0);
No need for reference-counting here. wsmouse will not call the
struct wsmouse_accessops::enable function more than once before
disable.
> --- /dev/null
> +++ b/sys/dev/i2c/ietp.h
Use include guards:
#ifndef DEV_I2C_IETP_H
#define DEV_I2C_IETP_H
...
#endif /* DEV_I2C_IETP_H */
> +#include "ihidev.h" // For i2c_hid_desc
#include <dev/i2c/ihidev.h>
You'll also need, for uintN_t, device_t, and i2c_tag_t:
#include <sys/types.h> // (comes first, right after sys/param.h if both)
#include <sys/device_if.h>
#include <dev/i2c/i2cvar.h>
Home |
Main Index |
Thread Index |
Old Index