Current Path : /sys/amd64/compile/hs32/modules/usr/src/sys/modules/cas/@/dev/usb/input/ |
FreeBSD hs32.drive.ne.jp 9.1-RELEASE FreeBSD 9.1-RELEASE #1: Wed Jan 14 12:18:08 JST 2015 root@hs32.drive.ne.jp:/sys/amd64/compile/hs32 amd64 |
Current File : //sys/amd64/compile/hs32/modules/usr/src/sys/modules/cas/@/dev/usb/input/atp.c |
/*- * Copyright (c) 2009 Rohit Grover * 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/cdefs.h> __FBSDID("$FreeBSD: release/9.1.0/sys/dev/usb/input/atp.c 235000 2012-05-04 15:05:30Z hselasky $"); #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/malloc.h> #include <sys/module.h> #include <sys/lock.h> #include <sys/mutex.h> #include <sys/bus.h> #include <sys/conf.h> #include <sys/fcntl.h> #include <sys/file.h> #include <sys/selinfo.h> #include <sys/poll.h> #include <sys/sysctl.h> #include <sys/uio.h> #include <dev/usb/usb.h> #include <dev/usb/usbdi.h> #include <dev/usb/usbdi_util.h> #include <dev/usb/usbhid.h> #include "usbdevs.h" #define USB_DEBUG_VAR atp_debug #include <dev/usb/usb_debug.h> #include <sys/mouse.h> #define ATP_DRIVER_NAME "atp" /* * Driver specific options: the following options may be set by * `options' statements in the kernel configuration file. */ /* The multiplier used to translate sensor reported positions to mickeys. */ #ifndef ATP_SCALE_FACTOR #define ATP_SCALE_FACTOR 48 #endif /* * This is the age (in microseconds) beyond which a touch is * considered to be a slide; and therefore a tap event isn't registered. */ #ifndef ATP_TOUCH_TIMEOUT #define ATP_TOUCH_TIMEOUT 125000 #endif /* * A double-tap followed by a single-finger slide is treated as a * special gesture. The driver responds to this gesture by assuming a * virtual button-press for the lifetime of the slide. The following * threshold is the maximum time gap (in microseconds) between the two * tap events preceding the slide for such a gesture. */ #ifndef ATP_DOUBLE_TAP_N_DRAG_THRESHOLD #define ATP_DOUBLE_TAP_N_DRAG_THRESHOLD 200000 #endif /* * The device provides us only with pressure readings from an array of * X and Y sensors; for our algorithms, we need to interpret groups * (typically pairs) of X and Y readings as being related to a single * finger stroke. We can relate X and Y readings based on their times * of incidence. The coincidence window should be at least 10000us * since it is used against values from getmicrotime(), which has a * precision of around 10ms. */ #ifndef ATP_COINCIDENCE_THRESHOLD #define ATP_COINCIDENCE_THRESHOLD 40000 /* unit: microseconds */ #if ATP_COINCIDENCE_THRESHOLD > 100000 #error "ATP_COINCIDENCE_THRESHOLD too large" #endif #endif /* #ifndef ATP_COINCIDENCE_THRESHOLD */ /* * The wait duration (in microseconds) after losing a touch contact * before zombied strokes are reaped and turned into button events. */ #define ATP_ZOMBIE_STROKE_REAP_WINDOW 50000 #if ATP_ZOMBIE_STROKE_REAP_WINDOW > 100000 #error "ATP_ZOMBIE_STROKE_REAP_WINDOW too large" #endif /* end of driver specific options */ /* Tunables */ SYSCTL_NODE(_hw_usb, OID_AUTO, atp, CTLFLAG_RW, 0, "USB atp"); #ifdef USB_DEBUG enum atp_log_level { ATP_LLEVEL_DISABLED = 0, ATP_LLEVEL_ERROR, ATP_LLEVEL_DEBUG, /* for troubleshooting */ ATP_LLEVEL_INFO, /* for diagnostics */ }; static int atp_debug = ATP_LLEVEL_ERROR; /* the default is to only log errors */ SYSCTL_INT(_hw_usb_atp, OID_AUTO, debug, CTLFLAG_RW, &atp_debug, ATP_LLEVEL_ERROR, "ATP debug level"); #endif /* USB_DEBUG */ static u_int atp_touch_timeout = ATP_TOUCH_TIMEOUT; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, touch_timeout, CTLFLAG_RW, &atp_touch_timeout, 125000, "age threshold (in micros) for a touch"); static u_int atp_double_tap_threshold = ATP_DOUBLE_TAP_N_DRAG_THRESHOLD; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, double_tap_threshold, CTLFLAG_RW, &atp_double_tap_threshold, ATP_DOUBLE_TAP_N_DRAG_THRESHOLD, "maximum time (in micros) between a double-tap"); static u_int atp_mickeys_scale_factor = ATP_SCALE_FACTOR; static int atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS); SYSCTL_PROC(_hw_usb_atp, OID_AUTO, scale_factor, CTLTYPE_UINT | CTLFLAG_RW, &atp_mickeys_scale_factor, sizeof(atp_mickeys_scale_factor), atp_sysctl_scale_factor_handler, "IU", "movement scale factor"); static u_int atp_small_movement_threshold = ATP_SCALE_FACTOR >> 3; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, small_movement, CTLFLAG_RW, &atp_small_movement_threshold, ATP_SCALE_FACTOR >> 3, "the small movement black-hole for filtering noise"); /* * The movement threshold for a stroke; this is the maximum difference * in position which will be resolved as a continuation of a stroke * component. */ static u_int atp_max_delta_mickeys = ((3 * ATP_SCALE_FACTOR) >> 1); SYSCTL_UINT(_hw_usb_atp, OID_AUTO, max_delta_mickeys, CTLFLAG_RW, &atp_max_delta_mickeys, ((3 * ATP_SCALE_FACTOR) >> 1), "max. mickeys-delta which will match against an existing stroke"); /* * Strokes which accumulate at least this amount of absolute movement * from the aggregate of their components are considered as * slides. Unit: mickeys. */ static u_int atp_slide_min_movement = (ATP_SCALE_FACTOR >> 3); SYSCTL_UINT(_hw_usb_atp, OID_AUTO, slide_min_movement, CTLFLAG_RW, &atp_slide_min_movement, (ATP_SCALE_FACTOR >> 3), "strokes with at least this amt. of movement are considered slides"); /* * The minimum age of a stroke for it to be considered mature; this * helps filter movements (noise) from immature strokes. Units: interrupts. */ static u_int atp_stroke_maturity_threshold = 2; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, stroke_maturity_threshold, CTLFLAG_RW, &atp_stroke_maturity_threshold, 2, "the minimum age of a stroke for it to be considered mature"); /* Accept pressure readings from sensors only if above this value. */ static u_int atp_sensor_noise_threshold = 2; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, sensor_noise_threshold, CTLFLAG_RW, &atp_sensor_noise_threshold, 2, "accept pressure readings from sensors only if above this value"); /* Ignore pressure spans with cumulative press. below this value. */ static u_int atp_pspan_min_cum_pressure = 10; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, pspan_min_cum_pressure, CTLFLAG_RW, &atp_pspan_min_cum_pressure, 10, "ignore pressure spans with cumulative press. below this value"); /* Maximum allowed width for pressure-spans.*/ static u_int atp_pspan_max_width = 4; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, pspan_max_width, CTLFLAG_RW, &atp_pspan_max_width, 4, "maximum allowed width (in sensors) for pressure-spans"); /* We support three payload protocols */ typedef enum { ATP_PROT_GEYSER1, ATP_PROT_GEYSER2, ATP_PROT_GEYSER3, } atp_protocol; /* Define the various flavours of devices supported by this driver. */ enum { ATP_DEV_PARAMS_0, ATP_DEV_PARAMS_PBOOK, ATP_DEV_PARAMS_PBOOK_15A, ATP_DEV_PARAMS_PBOOK_17, ATP_N_DEV_PARAMS }; struct atp_dev_params { u_int data_len; /* for sensor data */ u_int n_xsensors; u_int n_ysensors; atp_protocol prot; } atp_dev_params[ATP_N_DEV_PARAMS] = { [ATP_DEV_PARAMS_0] = { .data_len = 64, .n_xsensors = 20, .n_ysensors = 10, .prot = ATP_PROT_GEYSER3 }, [ATP_DEV_PARAMS_PBOOK] = { .data_len = 81, .n_xsensors = 16, .n_ysensors = 16, .prot = ATP_PROT_GEYSER1 }, [ATP_DEV_PARAMS_PBOOK_15A] = { .data_len = 64, .n_xsensors = 15, .n_ysensors = 9, .prot = ATP_PROT_GEYSER2 }, [ATP_DEV_PARAMS_PBOOK_17] = { .data_len = 81, .n_xsensors = 26, .n_ysensors = 16, .prot = ATP_PROT_GEYSER1 }, }; static const STRUCT_USB_HOST_ID atp_devs[] = { /* Core Duo MacBook & MacBook Pro */ { USB_VPI(USB_VENDOR_APPLE, 0x0217, ATP_DEV_PARAMS_0) }, { USB_VPI(USB_VENDOR_APPLE, 0x0218, ATP_DEV_PARAMS_0) }, { USB_VPI(USB_VENDOR_APPLE, 0x0219, ATP_DEV_PARAMS_0) }, /* Core2 Duo MacBook & MacBook Pro */ { USB_VPI(USB_VENDOR_APPLE, 0x021a, ATP_DEV_PARAMS_0) }, { USB_VPI(USB_VENDOR_APPLE, 0x021b, ATP_DEV_PARAMS_0) }, { USB_VPI(USB_VENDOR_APPLE, 0x021c, ATP_DEV_PARAMS_0) }, /* Core2 Duo MacBook3,1 */ { USB_VPI(USB_VENDOR_APPLE, 0x0229, ATP_DEV_PARAMS_0) }, { USB_VPI(USB_VENDOR_APPLE, 0x022a, ATP_DEV_PARAMS_0) }, { USB_VPI(USB_VENDOR_APPLE, 0x022b, ATP_DEV_PARAMS_0) }, /* 12 inch PowerBook and iBook */ { USB_VPI(USB_VENDOR_APPLE, 0x030a, ATP_DEV_PARAMS_PBOOK) }, { USB_VPI(USB_VENDOR_APPLE, 0x030b, ATP_DEV_PARAMS_PBOOK) }, /* 15 inch PowerBook */ { USB_VPI(USB_VENDOR_APPLE, 0x020e, ATP_DEV_PARAMS_PBOOK) }, { USB_VPI(USB_VENDOR_APPLE, 0x020f, ATP_DEV_PARAMS_PBOOK) }, { USB_VPI(USB_VENDOR_APPLE, 0x0215, ATP_DEV_PARAMS_PBOOK_15A) }, /* 17 inch PowerBook */ { USB_VPI(USB_VENDOR_APPLE, 0x020d, ATP_DEV_PARAMS_PBOOK_17) }, }; /* * The following structure captures the state of a pressure span along * an axis. Each contact with the touchpad results in separate * pressure spans along the two axes. */ typedef struct atp_pspan { u_int width; /* in units of sensors */ u_int cum; /* cumulative compression (from all sensors) */ u_int cog; /* center of gravity */ u_int loc; /* location (scaled using the mickeys factor) */ boolean_t matched; /* to track pspans as they match against strokes. */ } atp_pspan; typedef enum atp_stroke_type { ATP_STROKE_TOUCH, ATP_STROKE_SLIDE, } atp_stroke_type; #define ATP_MAX_PSPANS_PER_AXIS 3 typedef struct atp_stroke_component { /* Fields encapsulating the pressure-span. */ u_int loc; /* location (scaled) */ u_int cum_pressure; /* cumulative compression */ u_int max_cum_pressure; /* max cumulative compression */ boolean_t matched; /*to track components as they match against pspans.*/ /* Fields containing information about movement. */ int delta_mickeys; /* change in location (un-smoothened movement)*/ int pending; /* cum. of pending short movements */ int movement; /* current smoothened movement */ } atp_stroke_component; typedef enum atp_axis { X = 0, Y = 1 } atp_axis; #define ATP_MAX_STROKES (2 * ATP_MAX_PSPANS_PER_AXIS) /* * The following structure captures a finger contact with the * touchpad. A stroke comprises two p-span components and some state. */ typedef struct atp_stroke { atp_stroke_type type; struct timeval ctime; /* create time; for coincident siblings. */ u_int age; /* * Unit: interrupts; we maintain * this value in addition to * 'ctime' in order to avoid the * expensive call to microtime() * at every interrupt. */ atp_stroke_component components[2]; u_int velocity_squared; /* * Average magnitude (squared) * of recent velocity. */ u_int cum_movement; /* cum. absolute movement so far */ uint32_t flags; /* the state of this stroke */ #define ATSF_ZOMBIE 0x1 } atp_stroke; #define ATP_FIFO_BUF_SIZE 8 /* bytes */ #define ATP_FIFO_QUEUE_MAXLEN 50 /* units */ enum { ATP_INTR_DT, ATP_RESET, ATP_N_TRANSFER, }; struct atp_softc { device_t sc_dev; struct usb_device *sc_usb_device; #define MODE_LENGTH 8 char sc_mode_bytes[MODE_LENGTH]; /* device mode */ struct mtx sc_mutex; /* for synchronization */ struct usb_xfer *sc_xfer[ATP_N_TRANSFER]; struct usb_fifo_sc sc_fifo; struct atp_dev_params *sc_params; mousehw_t sc_hw; mousemode_t sc_mode; u_int sc_pollrate; mousestatus_t sc_status; u_int sc_state; #define ATP_ENABLED 0x01 #define ATP_ZOMBIES_EXIST 0x02 #define ATP_DOUBLE_TAP_DRAG 0x04 #define ATP_VALID 0x08 u_int sc_left_margin; u_int sc_right_margin; atp_stroke sc_strokes[ATP_MAX_STROKES]; u_int sc_n_strokes; int8_t *sensor_data; /* from interrupt packet */ int *base_x; /* base sensor readings */ int *base_y; int *cur_x; /* current sensor readings */ int *cur_y; int *pressure_x; /* computed pressures */ int *pressure_y; u_int sc_idlecount; /* preceding idle interrupts */ #define ATP_IDLENESS_THRESHOLD 10 struct timeval sc_reap_time; struct timeval sc_reap_ctime; /*ctime of siblings to be reaped*/ }; /* * The last byte of the sensor data contains status bits; the * following values define the meanings of these bits. */ enum atp_status_bits { ATP_STATUS_BUTTON = (uint8_t)0x01, /* The button was pressed */ ATP_STATUS_BASE_UPDATE = (uint8_t)0x04, /* Data from an untouched pad.*/ }; typedef enum interface_mode { RAW_SENSOR_MODE = (uint8_t)0x04, HID_MODE = (uint8_t)0x08 } interface_mode; /* * function prototypes */ static usb_fifo_cmd_t atp_start_read; static usb_fifo_cmd_t atp_stop_read; static usb_fifo_open_t atp_open; static usb_fifo_close_t atp_close; static usb_fifo_ioctl_t atp_ioctl; static struct usb_fifo_methods atp_fifo_methods = { .f_open = &atp_open, .f_close = &atp_close, .f_ioctl = &atp_ioctl, .f_start_read = &atp_start_read, .f_stop_read = &atp_stop_read, .basename[0] = ATP_DRIVER_NAME, }; /* device initialization and shutdown */ static usb_error_t atp_req_get_report(struct usb_device *udev, void *data); static int atp_set_device_mode(device_t dev, interface_mode mode); static void atp_reset_callback(struct usb_xfer *, usb_error_t); static int atp_enable(struct atp_softc *sc); static void atp_disable(struct atp_softc *sc); static int atp_softc_populate(struct atp_softc *); static void atp_softc_unpopulate(struct atp_softc *); /* sensor interpretation */ static __inline void atp_interpret_sensor_data(const int8_t *, u_int, atp_axis, int *, atp_protocol); static __inline void atp_get_pressures(int *, const int *, const int *, int); static void atp_detect_pspans(int *, u_int, u_int, atp_pspan *, u_int *); /* movement detection */ static boolean_t atp_match_stroke_component(atp_stroke_component *, const atp_pspan *, atp_stroke_type); static void atp_match_strokes_against_pspans(struct atp_softc *, atp_axis, atp_pspan *, u_int, u_int); static boolean_t atp_update_strokes(struct atp_softc *, atp_pspan *, u_int, atp_pspan *, u_int); static __inline void atp_add_stroke(struct atp_softc *, const atp_pspan *, const atp_pspan *); static void atp_add_new_strokes(struct atp_softc *, atp_pspan *, u_int, atp_pspan *, u_int); static void atp_advance_stroke_state(struct atp_softc *, atp_stroke *, boolean_t *); static void atp_terminate_stroke(struct atp_softc *, u_int); static __inline boolean_t atp_stroke_has_small_movement(const atp_stroke *); static __inline void atp_update_pending_mickeys(atp_stroke_component *); static void atp_compute_smoothening_scale_ratio(atp_stroke *, int *, int *); static boolean_t atp_compute_stroke_movement(atp_stroke *); /* tap detection */ static __inline void atp_setup_reap_time(struct atp_softc *, struct timeval *); static void atp_reap_zombies(struct atp_softc *, u_int *, u_int *); static void atp_convert_to_slide(struct atp_softc *, atp_stroke *); /* updating fifo */ static void atp_reset_buf(struct atp_softc *sc); static void atp_add_to_queue(struct atp_softc *, int, int, uint32_t); usb_error_t atp_req_get_report(struct usb_device *udev, void *data) { struct usb_device_request req; req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UR_GET_REPORT; USETW2(req.wValue, (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */); USETW(req.wIndex, 0); USETW(req.wLength, MODE_LENGTH); return (usbd_do_request(udev, NULL /* mutex */, &req, data)); } static int atp_set_device_mode(device_t dev, interface_mode mode) { struct atp_softc *sc; usb_device_request_t req; usb_error_t err; if ((mode != RAW_SENSOR_MODE) && (mode != HID_MODE)) return (ENXIO); sc = device_get_softc(dev); sc->sc_mode_bytes[0] = mode; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_SET_REPORT; USETW2(req.wValue, (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */); USETW(req.wIndex, 0); USETW(req.wLength, MODE_LENGTH); err = usbd_do_request(sc->sc_usb_device, NULL, &req, sc->sc_mode_bytes); if (err != USB_ERR_NORMAL_COMPLETION) return (ENXIO); return (0); } void atp_reset_callback(struct usb_xfer *xfer, usb_error_t error) { usb_device_request_t req; struct usb_page_cache *pc; struct atp_softc *sc = usbd_xfer_softc(xfer); switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: sc->sc_mode_bytes[0] = RAW_SENSOR_MODE; req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UR_SET_REPORT; USETW2(req.wValue, (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */); USETW(req.wIndex, 0); USETW(req.wLength, MODE_LENGTH); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, sc->sc_mode_bytes, MODE_LENGTH); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frame_len(xfer, 1, MODE_LENGTH); usbd_xfer_set_frames(xfer, 2); usbd_transfer_submit(xfer); break; case USB_ST_TRANSFERRED: default: break; } } static int atp_enable(struct atp_softc *sc) { /* Allocate the dynamic buffers */ if (atp_softc_populate(sc) != 0) { atp_softc_unpopulate(sc); return (ENOMEM); } /* reset status */ memset(sc->sc_strokes, 0, sizeof(sc->sc_strokes)); sc->sc_n_strokes = 0; memset(&sc->sc_status, 0, sizeof(sc->sc_status)); sc->sc_idlecount = 0; sc->sc_state |= ATP_ENABLED; DPRINTFN(ATP_LLEVEL_INFO, "enabled atp\n"); return (0); } static void atp_disable(struct atp_softc *sc) { atp_softc_unpopulate(sc); sc->sc_state &= ~(ATP_ENABLED | ATP_VALID); DPRINTFN(ATP_LLEVEL_INFO, "disabled atp\n"); } /* Allocate dynamic memory for some fields in softc. */ static int atp_softc_populate(struct atp_softc *sc) { const struct atp_dev_params *params = sc->sc_params; if (params == NULL) { DPRINTF("params uninitialized!\n"); return (ENXIO); } if (params->data_len) { sc->sensor_data = malloc(params->data_len * sizeof(int8_t), M_USB, M_WAITOK); if (sc->sensor_data == NULL) { DPRINTF("mem for sensor_data\n"); return (ENXIO); } } if (params->n_xsensors != 0) { sc->base_x = malloc(params->n_xsensors * sizeof(*(sc->base_x)), M_USB, M_WAITOK); if (sc->base_x == NULL) { DPRINTF("mem for sc->base_x\n"); return (ENXIO); } sc->cur_x = malloc(params->n_xsensors * sizeof(*(sc->cur_x)), M_USB, M_WAITOK); if (sc->cur_x == NULL) { DPRINTF("mem for sc->cur_x\n"); return (ENXIO); } sc->pressure_x = malloc(params->n_xsensors * sizeof(*(sc->pressure_x)), M_USB, M_WAITOK); if (sc->pressure_x == NULL) { DPRINTF("mem. for pressure_x\n"); return (ENXIO); } } if (params->n_ysensors != 0) { sc->base_y = malloc(params->n_ysensors * sizeof(*(sc->base_y)), M_USB, M_WAITOK); if (sc->base_y == NULL) { DPRINTF("mem for base_y\n"); return (ENXIO); } sc->cur_y = malloc(params->n_ysensors * sizeof(*(sc->cur_y)), M_USB, M_WAITOK); if (sc->cur_y == NULL) { DPRINTF("mem for cur_y\n"); return (ENXIO); } sc->pressure_y = malloc(params->n_ysensors * sizeof(*(sc->pressure_y)), M_USB, M_WAITOK); if (sc->pressure_y == NULL) { DPRINTF("mem. for pressure_y\n"); return (ENXIO); } } return (0); } /* Free dynamic memory allocated for some fields in softc. */ static void atp_softc_unpopulate(struct atp_softc *sc) { const struct atp_dev_params *params = sc->sc_params; if (params == NULL) { return; } if (params->n_xsensors != 0) { if (sc->base_x != NULL) { free(sc->base_x, M_USB); sc->base_x = NULL; } if (sc->cur_x != NULL) { free(sc->cur_x, M_USB); sc->cur_x = NULL; } if (sc->pressure_x != NULL) { free(sc->pressure_x, M_USB); sc->pressure_x = NULL; } } if (params->n_ysensors != 0) { if (sc->base_y != NULL) { free(sc->base_y, M_USB); sc->base_y = NULL; } if (sc->cur_y != NULL) { free(sc->cur_y, M_USB); sc->cur_y = NULL; } if (sc->pressure_y != NULL) { free(sc->pressure_y, M_USB); sc->pressure_y = NULL; } } if (sc->sensor_data != NULL) { free(sc->sensor_data, M_USB); sc->sensor_data = NULL; } } /* * Interpret the data from the X and Y pressure sensors. This function * is called separately for the X and Y sensor arrays. The data in the * USB packet is laid out in the following manner: * * sensor_data: * --,--,Y1,Y2,--,Y3,Y4,--,Y5,...,Y10, ... X1,X2,--,X3,X4 * indices: 0 1 2 3 4 5 6 7 8 ... 15 ... 20 21 22 23 24 * * '--' (in the above) indicates that the value is unimportant. * * Information about the above layout was obtained from the * implementation of the AppleTouch driver in Linux. * * parameters: * sensor_data * raw sensor data from the USB packet. * num * The number of elements in the array 'arr'. * axis * Axis of data to fetch * arr * The array to be initialized with the readings. * prot * The protocol to use to interpret the data */ static __inline void atp_interpret_sensor_data(const int8_t *sensor_data, u_int num, atp_axis axis, int *arr, atp_protocol prot) { u_int i; u_int di; /* index into sensor data */ switch (prot) { case ATP_PROT_GEYSER1: /* * For Geyser 1, the sensors are laid out in pairs * every 5 bytes. */ for (i = 0, di = (axis == Y) ? 1 : 2; i < 8; di += 5, i++) { arr[i] = sensor_data[di]; arr[i+8] = sensor_data[di+2]; if (axis == X && num > 16) arr[i+16] = sensor_data[di+40]; } break; case ATP_PROT_GEYSER2: case ATP_PROT_GEYSER3: for (i = 0, di = (axis == Y) ? 2 : 20; i < num; /* empty */ ) { arr[i++] = sensor_data[di++]; arr[i++] = sensor_data[di++]; di++; } break; } } static __inline void atp_get_pressures(int *p, const int *cur, const int *base, int n) { int i; for (i = 0; i < n; i++) { p[i] = cur[i] - base[i]; if (p[i] > 127) p[i] -= 256; if (p[i] < -127) p[i] += 256; if (p[i] < 0) p[i] = 0; /* * Shave off pressures below the noise-pressure * threshold; this will reduce the contribution from * lower pressure readings. */ if ((u_int)p[i] <= atp_sensor_noise_threshold) p[i] = 0; /* filter away noise */ else p[i] -= atp_sensor_noise_threshold; } } static void atp_detect_pspans(int *p, u_int num_sensors, u_int max_spans, /* max # of pspans permitted */ atp_pspan *spans, /* finger spans */ u_int *nspans_p) /* num spans detected */ { u_int i; int maxp; /* max pressure seen within a span */ u_int num_spans = 0; enum atp_pspan_state { ATP_PSPAN_INACTIVE, ATP_PSPAN_INCREASING, ATP_PSPAN_DECREASING, } state; /* state of the pressure span */ /* * The following is a simple state machine to track * the phase of the pressure span. */ memset(spans, 0, max_spans * sizeof(atp_pspan)); maxp = 0; state = ATP_PSPAN_INACTIVE; for (i = 0; i < num_sensors; i++) { if (num_spans >= max_spans) break; if (p[i] == 0) { if (state == ATP_PSPAN_INACTIVE) { /* * There is no pressure information for this * sensor, and we aren't tracking a finger. */ continue; } else { state = ATP_PSPAN_INACTIVE; maxp = 0; num_spans++; } } else { switch (state) { case ATP_PSPAN_INACTIVE: state = ATP_PSPAN_INCREASING; maxp = p[i]; break; case ATP_PSPAN_INCREASING: if (p[i] > maxp) maxp = p[i]; else if (p[i] <= (maxp >> 1)) state = ATP_PSPAN_DECREASING; break; case ATP_PSPAN_DECREASING: if (p[i] > p[i - 1]) { /* * This is the beginning of * another span; change state * to give the appearance that * we're starting from an * inactive span, and then * re-process this reading in * the next iteration. */ num_spans++; state = ATP_PSPAN_INACTIVE; maxp = 0; i--; continue; } break; } /* Update the finger span with this reading. */ spans[num_spans].width++; spans[num_spans].cum += p[i]; spans[num_spans].cog += p[i] * (i + 1); } } if (state != ATP_PSPAN_INACTIVE) num_spans++; /* close the last finger span */ /* post-process the spans */ for (i = 0; i < num_spans; i++) { /* filter away unwanted pressure spans */ if ((spans[i].cum < atp_pspan_min_cum_pressure) || (spans[i].width > atp_pspan_max_width)) { if ((i + 1) < num_spans) { memcpy(&spans[i], &spans[i + 1], (num_spans - i - 1) * sizeof(atp_pspan)); i--; } num_spans--; continue; } /* compute this span's representative location */ spans[i].loc = spans[i].cog * atp_mickeys_scale_factor / spans[i].cum; spans[i].matched = FALSE; /* not yet matched against a stroke */ } *nspans_p = num_spans; } /* * Match a pressure-span against a stroke-component. If there is a * match, update the component's state and return TRUE. */ static boolean_t atp_match_stroke_component(atp_stroke_component *component, const atp_pspan *pspan, atp_stroke_type stroke_type) { int delta_mickeys; u_int min_pressure; delta_mickeys = pspan->loc - component->loc; if ((u_int)abs(delta_mickeys) > atp_max_delta_mickeys) return (FALSE); /* the finger span is too far out; no match */ component->loc = pspan->loc; /* * A sudden and significant increase in a pspan's cumulative * pressure indicates the incidence of a new finger * contact. This usually revises the pspan's * centre-of-gravity, and hence the location of any/all * matching stroke component(s). But such a change should * *not* be interpreted as a movement. */ if (pspan->cum > ((3 * component->cum_pressure) >> 1)) delta_mickeys = 0; component->cum_pressure = pspan->cum; if (pspan->cum > component->max_cum_pressure) component->max_cum_pressure = pspan->cum; /* * Disregard the component's movement if its cumulative * pressure drops below a fraction of the maximum; this * fraction is determined based on the stroke's type. */ if (stroke_type == ATP_STROKE_TOUCH) min_pressure = (3 * component->max_cum_pressure) >> 2; else min_pressure = component->max_cum_pressure >> 2; if (component->cum_pressure < min_pressure) delta_mickeys = 0; component->delta_mickeys = delta_mickeys; return (TRUE); } static void atp_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis, atp_pspan *pspans, u_int n_pspans, u_int repeat_count) { u_int i, j; u_int repeat_index = 0; /* Determine the index of the multi-span. */ if (repeat_count) { u_int cum = 0; for (i = 0; i < n_pspans; i++) { if (pspans[i].cum > cum) { repeat_index = i; cum = pspans[i].cum; } } } for (i = 0; i < sc->sc_n_strokes; i++) { atp_stroke *stroke = &sc->sc_strokes[i]; if (stroke->components[axis].matched) continue; /* skip matched components */ for (j = 0; j < n_pspans; j++) { if (pspans[j].matched) continue; /* skip matched pspans */ if (atp_match_stroke_component( &stroke->components[axis], &pspans[j], stroke->type)) { /* There is a match. */ stroke->components[axis].matched = TRUE; /* Take care to repeat at the multi-span. */ if ((repeat_count > 0) && (j == repeat_index)) repeat_count--; else pspans[j].matched = TRUE; break; /* skip to the next stroke */ } } /* loop over pspans */ } /* loop over strokes */ } /* * Update strokes by matching against current pressure-spans. * Return TRUE if any movement is detected. */ static boolean_t atp_update_strokes(struct atp_softc *sc, atp_pspan *pspans_x, u_int n_xpspans, atp_pspan *pspans_y, u_int n_ypspans) { u_int i, j; atp_stroke *stroke; boolean_t movement = FALSE; u_int repeat_count = 0; /* Reset X and Y components of all strokes as unmatched. */ for (i = 0; i < sc->sc_n_strokes; i++) { stroke = &sc->sc_strokes[i]; stroke->components[X].matched = FALSE; stroke->components[Y].matched = FALSE; } /* * Usually, the X and Y pspans come in pairs (the common case * being a single pair). It is possible, however, that * multiple contacts resolve to a single pspan along an * axis, as illustrated in the following: * * F = finger-contact * * pspan pspan * +-----------------------+ * | . . | * | . . | * | . . | * | . . | * pspan |.........F......F | * | | * | | * | | * +-----------------------+ * * * The above case can be detected by a difference in the * number of X and Y pspans. When this happens, X and Y pspans * aren't easy to pair or match against strokes. * * When X and Y pspans differ in number, the axis with the * smaller number of pspans is regarded as having a repeating * pspan (or a multi-pspan)--in the above illustration, the * Y-axis has a repeating pspan. Our approach is to try to * match the multi-pspan repeatedly against strokes. The * difference between the number of X and Y pspans gives us a * crude repeat_count for matching multi-pspans--i.e. the * multi-pspan along the Y axis (above) has a repeat_count of 1. */ repeat_count = abs(n_xpspans - n_ypspans); atp_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans, (((repeat_count != 0) && ((n_xpspans < n_ypspans))) ? repeat_count : 0)); atp_match_strokes_against_pspans(sc, Y, pspans_y, n_ypspans, (((repeat_count != 0) && (n_ypspans < n_xpspans)) ? repeat_count : 0)); /* Update the state of strokes based on the above pspan matches. */ for (i = 0; i < sc->sc_n_strokes; i++) { stroke = &sc->sc_strokes[i]; if (stroke->components[X].matched && stroke->components[Y].matched) { atp_advance_stroke_state(sc, stroke, &movement); } else { /* * At least one component of this stroke * didn't match against current pspans; * terminate it. */ atp_terminate_stroke(sc, i); } } /* Add new strokes for pairs of unmatched pspans */ for (i = 0; i < n_xpspans; i++) { if (pspans_x[i].matched == FALSE) break; } for (j = 0; j < n_ypspans; j++) { if (pspans_y[j].matched == FALSE) break; } if ((i < n_xpspans) && (j < n_ypspans)) { #ifdef USB_DEBUG if (atp_debug >= ATP_LLEVEL_INFO) { printf("unmatched pspans:"); for (; i < n_xpspans; i++) { if (pspans_x[i].matched) continue; printf(" X:[loc:%u,cum:%u]", pspans_x[i].loc, pspans_x[i].cum); } for (; j < n_ypspans; j++) { if (pspans_y[j].matched) continue; printf(" Y:[loc:%u,cum:%u]", pspans_y[j].loc, pspans_y[j].cum); } printf("\n"); } #endif /* USB_DEBUG */ if ((n_xpspans == 1) && (n_ypspans == 1)) /* The common case of a single pair of new pspans. */ atp_add_stroke(sc, &pspans_x[0], &pspans_y[0]); else atp_add_new_strokes(sc, pspans_x, n_xpspans, pspans_y, n_ypspans); } #ifdef USB_DEBUG if (atp_debug >= ATP_LLEVEL_INFO) { for (i = 0; i < sc->sc_n_strokes; i++) { atp_stroke *stroke = &sc->sc_strokes[i]; printf(" %s%clc:%u,dm:%d,pnd:%d,cum:%d,max:%d,mv:%d%c" ",%clc:%u,dm:%d,pnd:%d,cum:%d,max:%d,mv:%d%c", (stroke->flags & ATSF_ZOMBIE) ? "zomb:" : "", (stroke->type == ATP_STROKE_TOUCH) ? '[' : '<', stroke->components[X].loc, stroke->components[X].delta_mickeys, stroke->components[X].pending, stroke->components[X].cum_pressure, stroke->components[X].max_cum_pressure, stroke->components[X].movement, (stroke->type == ATP_STROKE_TOUCH) ? ']' : '>', (stroke->type == ATP_STROKE_TOUCH) ? '[' : '<', stroke->components[Y].loc, stroke->components[Y].delta_mickeys, stroke->components[Y].pending, stroke->components[Y].cum_pressure, stroke->components[Y].max_cum_pressure, stroke->components[Y].movement, (stroke->type == ATP_STROKE_TOUCH) ? ']' : '>'); } if (sc->sc_n_strokes) printf("\n"); } #endif /* USB_DEBUG */ return (movement); } /* Initialize a stroke using a pressure-span. */ static __inline void atp_add_stroke(struct atp_softc *sc, const atp_pspan *pspan_x, const atp_pspan *pspan_y) { atp_stroke *stroke; if (sc->sc_n_strokes >= ATP_MAX_STROKES) return; stroke = &sc->sc_strokes[sc->sc_n_strokes]; memset(stroke, 0, sizeof(atp_stroke)); /* * Strokes begin as potential touches. If a stroke survives * longer than a threshold, or if it records significant * cumulative movement, then it is considered a 'slide'. */ stroke->type = ATP_STROKE_TOUCH; microtime(&stroke->ctime); stroke->age = 1; /* Unit: interrupts */ stroke->components[X].loc = pspan_x->loc; stroke->components[X].cum_pressure = pspan_x->cum; stroke->components[X].max_cum_pressure = pspan_x->cum; stroke->components[X].matched = TRUE; stroke->components[Y].loc = pspan_y->loc; stroke->components[Y].cum_pressure = pspan_y->cum; stroke->components[Y].max_cum_pressure = pspan_y->cum; stroke->components[Y].matched = TRUE; sc->sc_n_strokes++; if (sc->sc_n_strokes > 1) { /* Reset double-tap-n-drag if we have more than one strokes. */ sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; } DPRINTFN(ATP_LLEVEL_INFO, "[%u,%u], time: %u,%ld\n", stroke->components[X].loc, stroke->components[Y].loc, (unsigned int)stroke->ctime.tv_sec, (unsigned long int)stroke->ctime.tv_usec); } static void atp_add_new_strokes(struct atp_softc *sc, atp_pspan *pspans_x, u_int n_xpspans, atp_pspan *pspans_y, u_int n_ypspans) { atp_pspan spans[2][ATP_MAX_PSPANS_PER_AXIS]; u_int nspans[2]; u_int i; u_int j; /* Copy unmatched pspans into the local arrays. */ for (i = 0, nspans[X] = 0; i < n_xpspans; i++) { if (pspans_x[i].matched == FALSE) { spans[X][nspans[X]] = pspans_x[i]; nspans[X]++; } } for (j = 0, nspans[Y] = 0; j < n_ypspans; j++) { if (pspans_y[j].matched == FALSE) { spans[Y][nspans[Y]] = pspans_y[j]; nspans[Y]++; } } if (nspans[X] == nspans[Y]) { /* Create new strokes from pairs of unmatched pspans */ for (i = 0, j = 0; (i < nspans[X]) && (j < nspans[Y]); i++, j++) atp_add_stroke(sc, &spans[X][i], &spans[Y][j]); } else { u_int cum = 0; atp_axis repeat_axis; /* axis with multi-pspans */ u_int repeat_count; /* repeat count for the multi-pspan*/ u_int repeat_index = 0; /* index of the multi-span */ repeat_axis = (nspans[X] > nspans[Y]) ? Y : X; repeat_count = abs(nspans[X] - nspans[Y]); for (i = 0; i < nspans[repeat_axis]; i++) { if (spans[repeat_axis][i].cum > cum) { repeat_index = i; cum = spans[repeat_axis][i].cum; } } /* Create new strokes from pairs of unmatched pspans */ i = 0, j = 0; for (; (i < nspans[X]) && (j < nspans[Y]); i++, j++) { atp_add_stroke(sc, &spans[X][i], &spans[Y][j]); /* Take care to repeat at the multi-pspan. */ if (repeat_count > 0) { if ((repeat_axis == X) && (repeat_index == i)) { i--; /* counter loop increment */ repeat_count--; } else if ((repeat_axis == Y) && (repeat_index == j)) { j--; /* counter loop increment */ repeat_count--; } } } } } /* * Advance the state of this stroke--and update the out-parameter * 'movement' as a side-effect. */ void atp_advance_stroke_state(struct atp_softc *sc, atp_stroke *stroke, boolean_t *movement) { stroke->age++; if (stroke->age <= atp_stroke_maturity_threshold) { /* Avoid noise from immature strokes. */ stroke->components[X].delta_mickeys = 0; stroke->components[Y].delta_mickeys = 0; } /* Revitalize stroke if it had previously been marked as a zombie. */ if (stroke->flags & ATSF_ZOMBIE) stroke->flags &= ~ATSF_ZOMBIE; if (atp_compute_stroke_movement(stroke)) *movement = TRUE; if (stroke->type != ATP_STROKE_TOUCH) return; /* Convert touch strokes to slides upon detecting movement or age. */ if (stroke->cum_movement >= atp_slide_min_movement) { atp_convert_to_slide(sc, stroke); } else { /* If a touch stroke is found to be older than the * touch-timeout threshold, it should be converted to * a slide; except if there is a co-incident sibling * with a later creation time. * * When multiple fingers make contact with the * touchpad, they are likely to be separated in their * times of incidence. During a multi-finger tap, * therefore, the last finger to make * contact--i.e. the one with the latest * 'ctime'--should be used to determine how the * touch-siblings get treated; otherwise older * siblings may lapse the touch-timeout and get * converted into slides prematurely. The following * loop determines if there exists another touch * stroke with a larger 'ctime' than the current * stroke (NOTE: zombies with a larger 'ctime' are * also considered) . */ u_int i; for (i = 0; i < sc->sc_n_strokes; i++) { if ((&sc->sc_strokes[i] == stroke) || (sc->sc_strokes[i].type != ATP_STROKE_TOUCH)) continue; if (timevalcmp(&sc->sc_strokes[i].ctime, &stroke->ctime, >)) break; } if (i == sc->sc_n_strokes) { /* Found no other touch stroke with a larger 'ctime'. */ struct timeval tdiff; /* Compute the stroke's age. */ getmicrotime(&tdiff); if (timevalcmp(&tdiff, &stroke->ctime, >)) timevalsub(&tdiff, &stroke->ctime); else { /* * If we are here, it is because getmicrotime * reported the current time as being behind * the stroke's start time; getmicrotime can * be imprecise. */ tdiff.tv_sec = 0; tdiff.tv_usec = 0; } if ((tdiff.tv_sec > (atp_touch_timeout / 1000000)) || ((tdiff.tv_sec == (atp_touch_timeout / 1000000)) && (tdiff.tv_usec >= (atp_touch_timeout % 1000000)))) atp_convert_to_slide(sc, stroke); } } } /* Switch a given touch stroke to being a slide. */ void atp_convert_to_slide(struct atp_softc *sc, atp_stroke *stroke) { stroke->type = ATP_STROKE_SLIDE; /* Are we at the beginning of a double-click-n-drag? */ if ((sc->sc_n_strokes == 1) && ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) && timevalcmp(&stroke->ctime, &sc->sc_reap_time, >)) { struct timeval delta; struct timeval window = { atp_double_tap_threshold / 1000000, atp_double_tap_threshold % 1000000 }; delta = stroke->ctime; timevalsub(&delta, &sc->sc_reap_time); if (timevalcmp(&delta, &window, <=)) sc->sc_state |= ATP_DOUBLE_TAP_DRAG; } } /* * Terminate a stroke. While SLIDE strokes are dropped, TOUCH strokes * are retained as zombies so as to reap all their siblings together; * this helps establish the number of fingers involved in the tap. */ static void atp_terminate_stroke(struct atp_softc *sc, u_int index) /* index of the stroke to be terminated */ { atp_stroke *s = &sc->sc_strokes[index]; if (s->flags & ATSF_ZOMBIE) { return; } if ((s->type == ATP_STROKE_TOUCH) && (s->age > atp_stroke_maturity_threshold)) { s->flags |= ATSF_ZOMBIE; /* If no zombies exist, then prepare to reap zombies later. */ if ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) { atp_setup_reap_time(sc, &s->ctime); sc->sc_state |= ATP_ZOMBIES_EXIST; } } else { /* Drop this stroke. */ memcpy(&sc->sc_strokes[index], &sc->sc_strokes[index + 1], (sc->sc_n_strokes - index - 1) * sizeof(atp_stroke)); sc->sc_n_strokes--; /* * Reset the double-click-n-drag at the termination of * any slide stroke. */ sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; } } static __inline boolean_t atp_stroke_has_small_movement(const atp_stroke *stroke) { return (((u_int)abs(stroke->components[X].delta_mickeys) <= atp_small_movement_threshold) && ((u_int)abs(stroke->components[Y].delta_mickeys) <= atp_small_movement_threshold)); } /* * Accumulate delta_mickeys into the component's 'pending' bucket; if * the aggregate exceeds the small_movement_threshold, then retain * delta_mickeys for later. */ static __inline void atp_update_pending_mickeys(atp_stroke_component *component) { component->pending += component->delta_mickeys; if ((u_int)abs(component->pending) <= atp_small_movement_threshold) component->delta_mickeys = 0; else { /* * Penalise pending mickeys for having accumulated * over short deltas. This operation has the effect of * scaling down the cumulative contribution of short * movements. */ component->pending -= (component->delta_mickeys << 1); } } static void atp_compute_smoothening_scale_ratio(atp_stroke *stroke, int *numerator, int *denominator) { int dxdt; int dydt; u_int vel_squared; /* Square of the velocity vector's magnitude. */ u_int vel_squared_smooth; /* Table holding (10 * sqrt(x)) for x between 1 and 256. */ static uint8_t sqrt_table[256] = { 10, 14, 17, 20, 22, 24, 26, 28, 30, 31, 33, 34, 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 50, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 60, 61, 62, 63, 64, 64, 65, 66, 67, 67, 68, 69, 70, 70, 71, 72, 72, 73, 74, 74, 75, 76, 76, 77, 78, 78, 79, 80, 80, 81, 81, 82, 83, 83, 84, 84, 85, 86, 86, 87, 87, 88, 88, 89, 90, 90, 91, 91, 92, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, 98, 99, 100, 100, 100, 101, 101, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 107, 107, 108, 108, 109, 109, 110, 110, 110, 111, 111, 112, 112, 113, 113, 114, 114, 114, 115, 115, 116, 116, 117, 117, 117, 118, 118, 119, 119, 120, 120, 120, 121, 121, 122, 122, 122, 123, 123, 124, 124, 124, 125, 125, 126, 126, 126, 127, 127, 128, 128, 128, 129, 129, 130, 130, 130, 131, 131, 131, 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 140, 140, 140, 141, 141, 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 150, 150, 150, 150, 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160 }; const u_int N = sizeof(sqrt_table) / sizeof(sqrt_table[0]); dxdt = stroke->components[X].delta_mickeys; dydt = stroke->components[Y].delta_mickeys; *numerator = 0, *denominator = 0; /* default values. */ /* Compute a smoothened magnitude_squared of the stroke's velocity. */ vel_squared = dxdt * dxdt + dydt * dydt; vel_squared_smooth = (3 * stroke->velocity_squared + vel_squared) >> 2; stroke->velocity_squared = vel_squared_smooth; /* retained as history */ if ((vel_squared == 0) || (vel_squared_smooth == 0)) return; /* returning (numerator == 0) will imply zero movement*/ /* * In order to determine the overall movement scale factor, * we're actually interested in the effect of smoothening upon * the *magnitude* of velocity; i.e. we need to compute the * square-root of (vel_squared_smooth / vel_squared) in the * form of a numerator and denominator. */ /* Keep within the bounds of the square-root table. */ while ((vel_squared > N) || (vel_squared_smooth > N)) { /* Dividing uniformly by 2 won't disturb the final ratio. */ vel_squared >>= 1; vel_squared_smooth >>= 1; } *numerator = sqrt_table[vel_squared_smooth - 1]; *denominator = sqrt_table[vel_squared - 1]; } /* * Compute a smoothened value for the stroke's movement from * delta_mickeys in the X and Y components. */ static boolean_t atp_compute_stroke_movement(atp_stroke *stroke) { int num; /* numerator of scale ratio */ int denom; /* denominator of scale ratio */ /* * Short movements are added first to the 'pending' bucket, * and then acted upon only when their aggregate exceeds a * threshold. This has the effect of filtering away movement * noise. */ if (atp_stroke_has_small_movement(stroke)) { atp_update_pending_mickeys(&stroke->components[X]); atp_update_pending_mickeys(&stroke->components[Y]); } else { /* large movement */ /* clear away any pending mickeys if there are large movements*/ stroke->components[X].pending = 0; stroke->components[Y].pending = 0; } /* Get the scale ratio and smoothen movement. */ atp_compute_smoothening_scale_ratio(stroke, &num, &denom); if ((num == 0) || (denom == 0)) { stroke->components[X].movement = 0; stroke->components[Y].movement = 0; stroke->velocity_squared >>= 1; /* Erode velocity_squared. */ } else { stroke->components[X].movement = (stroke->components[X].delta_mickeys * num) / denom; stroke->components[Y].movement = (stroke->components[Y].delta_mickeys * num) / denom; stroke->cum_movement += abs(stroke->components[X].movement) + abs(stroke->components[Y].movement); } return ((stroke->components[X].movement != 0) || (stroke->components[Y].movement != 0)); } static __inline void atp_setup_reap_time(struct atp_softc *sc, struct timeval *tvp) { struct timeval reap_window = { ATP_ZOMBIE_STROKE_REAP_WINDOW / 1000000, ATP_ZOMBIE_STROKE_REAP_WINDOW % 1000000 }; microtime(&sc->sc_reap_time); timevaladd(&sc->sc_reap_time, &reap_window); sc->sc_reap_ctime = *tvp; /* ctime to reap */ } static void atp_reap_zombies(struct atp_softc *sc, u_int *n_reaped, u_int *reaped_xlocs) { u_int i; atp_stroke *stroke; *n_reaped = 0; for (i = 0; i < sc->sc_n_strokes; i++) { struct timeval tdiff; stroke = &sc->sc_strokes[i]; if ((stroke->flags & ATSF_ZOMBIE) == 0) continue; /* Compare this stroke's ctime with the ctime being reaped. */ if (timevalcmp(&stroke->ctime, &sc->sc_reap_ctime, >=)) { tdiff = stroke->ctime; timevalsub(&tdiff, &sc->sc_reap_ctime); } else { tdiff = sc->sc_reap_ctime; timevalsub(&tdiff, &stroke->ctime); } if ((tdiff.tv_sec > (ATP_COINCIDENCE_THRESHOLD / 1000000)) || ((tdiff.tv_sec == (ATP_COINCIDENCE_THRESHOLD / 1000000)) && (tdiff.tv_usec > (ATP_COINCIDENCE_THRESHOLD % 1000000)))) { continue; /* Skip non-siblings. */ } /* * Reap this sibling zombie stroke. */ if (reaped_xlocs != NULL) reaped_xlocs[*n_reaped] = stroke->components[X].loc; /* Erase the stroke from the sc. */ memcpy(&stroke[i], &stroke[i + 1], (sc->sc_n_strokes - i - 1) * sizeof(atp_stroke)); sc->sc_n_strokes--; *n_reaped += 1; --i; /* Decr. i to keep it unchanged for the next iteration */ } DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n", *n_reaped); /* There could still be zombies remaining in the system. */ for (i = 0; i < sc->sc_n_strokes; i++) { stroke = &sc->sc_strokes[i]; if (stroke->flags & ATSF_ZOMBIE) { DPRINTFN(ATP_LLEVEL_INFO, "zombies remain!\n"); atp_setup_reap_time(sc, &stroke->ctime); return; } } /* If we reach here, then no more zombies remain. */ sc->sc_state &= ~ATP_ZOMBIES_EXIST; } /* Device methods. */ static device_probe_t atp_probe; static device_attach_t atp_attach; static device_detach_t atp_detach; static usb_callback_t atp_intr; static const struct usb_config atp_config[ATP_N_TRANSFER] = { [ATP_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = { .pipe_bof = 1, .short_xfer_ok = 1, }, .bufsize = 0, /* use wMaxPacketSize */ .callback = &atp_intr, }, [ATP_RESET] = { .type = UE_CONTROL, .endpoint = 0, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request) + MODE_LENGTH, .callback = &atp_reset_callback, .interval = 0, /* no pre-delay */ }, }; static int atp_probe(device_t self) { struct usb_attach_arg *uaa = device_get_ivars(self); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if ((uaa->info.bInterfaceClass != UICLASS_HID) || (uaa->info.bInterfaceProtocol != UIPROTO_MOUSE)) return (ENXIO); return (usbd_lookup_id_by_uaa(atp_devs, sizeof(atp_devs), uaa)); } static int atp_attach(device_t dev) { struct atp_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); usb_error_t err; DPRINTFN(ATP_LLEVEL_INFO, "sc=%p\n", sc); sc->sc_dev = dev; sc->sc_usb_device = uaa->device; /* * By default the touchpad behaves like an HID device, sending * packets with reportID = 2. Such reports contain only * limited information--they encode movement deltas and button * events,--but do not include data from the pressure * sensors. The device input mode can be switched from HID * reports to raw sensor data using vendor-specific USB * control commands; but first the mode must be read. */ err = atp_req_get_report(sc->sc_usb_device, sc->sc_mode_bytes); if (err != USB_ERR_NORMAL_COMPLETION) { DPRINTF("failed to read device mode (%d)\n", err); return (ENXIO); } if (atp_set_device_mode(dev, RAW_SENSOR_MODE) != 0) { DPRINTF("failed to set mode to 'RAW_SENSOR' (%d)\n", err); return (ENXIO); } mtx_init(&sc->sc_mutex, "atpmtx", NULL, MTX_DEF | MTX_RECURSE); err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, atp_config, ATP_N_TRANSFER, sc, &sc->sc_mutex); if (err) { DPRINTF("error=%s\n", usbd_errstr(err)); goto detach; } if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_mutex, &atp_fifo_methods, &sc->sc_fifo, device_get_unit(dev), -1, uaa->info.bIfaceIndex, UID_ROOT, GID_OPERATOR, 0644)) { goto detach; } device_set_usb_desc(dev); sc->sc_params = &atp_dev_params[uaa->driver_info]; sc->sc_hw.buttons = 3; sc->sc_hw.iftype = MOUSE_IF_USB; sc->sc_hw.type = MOUSE_PAD; sc->sc_hw.model = MOUSE_MODEL_GENERIC; sc->sc_hw.hwid = 0; sc->sc_mode.protocol = MOUSE_PROTO_MSC; sc->sc_mode.rate = -1; sc->sc_mode.resolution = MOUSE_RES_UNKNOWN; sc->sc_mode.accelfactor = 0; sc->sc_mode.level = 0; sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; sc->sc_state = 0; sc->sc_left_margin = atp_mickeys_scale_factor; sc->sc_right_margin = (sc->sc_params->n_xsensors - 1) * atp_mickeys_scale_factor; return (0); detach: atp_detach(dev); return (ENOMEM); } static int atp_detach(device_t dev) { struct atp_softc *sc; sc = device_get_softc(dev); if (sc->sc_state & ATP_ENABLED) { mtx_lock(&sc->sc_mutex); atp_disable(sc); mtx_unlock(&sc->sc_mutex); } usb_fifo_detach(&sc->sc_fifo); usbd_transfer_unsetup(sc->sc_xfer, ATP_N_TRANSFER); mtx_destroy(&sc->sc_mutex); return (0); } static void atp_intr(struct usb_xfer *xfer, usb_error_t error) { struct atp_softc *sc = usbd_xfer_softc(xfer); int len; struct usb_page_cache *pc; uint8_t status_bits; atp_pspan pspans_x[ATP_MAX_PSPANS_PER_AXIS]; atp_pspan pspans_y[ATP_MAX_PSPANS_PER_AXIS]; u_int n_xpspans = 0, n_ypspans = 0; u_int reaped_xlocs[ATP_MAX_STROKES]; u_int tap_fingers = 0; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (len > (int)sc->sc_params->data_len) { DPRINTFN(ATP_LLEVEL_ERROR, "truncating large packet from %u to %u bytes\n", len, sc->sc_params->data_len); len = sc->sc_params->data_len; } if (len < (int)sc->sc_params->data_len) goto tr_setup; pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, sc->sensor_data, sc->sc_params->data_len); /* Interpret sensor data */ atp_interpret_sensor_data(sc->sensor_data, sc->sc_params->n_xsensors, X, sc->cur_x, sc->sc_params->prot); atp_interpret_sensor_data(sc->sensor_data, sc->sc_params->n_ysensors, Y, sc->cur_y, sc->sc_params->prot); /* * If this is the initial update (from an untouched * pad), we should set the base values for the sensor * data; deltas with respect to these base values can * be used as pressure readings subsequently. */ status_bits = sc->sensor_data[sc->sc_params->data_len - 1]; if ((sc->sc_params->prot == ATP_PROT_GEYSER3 && (status_bits & ATP_STATUS_BASE_UPDATE)) || !(sc->sc_state & ATP_VALID)) { memcpy(sc->base_x, sc->cur_x, sc->sc_params->n_xsensors * sizeof(*(sc->base_x))); memcpy(sc->base_y, sc->cur_y, sc->sc_params->n_ysensors * sizeof(*(sc->base_y))); sc->sc_state |= ATP_VALID; goto tr_setup; } /* Get pressure readings and detect p-spans for both axes. */ atp_get_pressures(sc->pressure_x, sc->cur_x, sc->base_x, sc->sc_params->n_xsensors); atp_detect_pspans(sc->pressure_x, sc->sc_params->n_xsensors, ATP_MAX_PSPANS_PER_AXIS, pspans_x, &n_xpspans); atp_get_pressures(sc->pressure_y, sc->cur_y, sc->base_y, sc->sc_params->n_ysensors); atp_detect_pspans(sc->pressure_y, sc->sc_params->n_ysensors, ATP_MAX_PSPANS_PER_AXIS, pspans_y, &n_ypspans); /* Update strokes with new pspans to detect movements. */ sc->sc_status.flags &= ~MOUSE_POSCHANGED; if (atp_update_strokes(sc, pspans_x, n_xpspans, pspans_y, n_ypspans)) sc->sc_status.flags |= MOUSE_POSCHANGED; /* Reap zombies if it is time. */ if (sc->sc_state & ATP_ZOMBIES_EXIST) { struct timeval now; getmicrotime(&now); if (timevalcmp(&now, &sc->sc_reap_time, >=)) atp_reap_zombies(sc, &tap_fingers, reaped_xlocs); } sc->sc_status.flags &= ~MOUSE_STDBUTTONSCHANGED; sc->sc_status.obutton = sc->sc_status.button; /* Get the state of the physical buttton. */ sc->sc_status.button = (status_bits & ATP_STATUS_BUTTON) ? MOUSE_BUTTON1DOWN : 0; if (sc->sc_status.button != 0) { /* Reset DOUBLE_TAP_N_DRAG if the button is pressed. */ sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; } else if (sc->sc_state & ATP_DOUBLE_TAP_DRAG) { /* Assume a button-press with DOUBLE_TAP_N_DRAG. */ sc->sc_status.button = MOUSE_BUTTON1DOWN; } sc->sc_status.flags |= sc->sc_status.button ^ sc->sc_status.obutton; if (sc->sc_status.flags & MOUSE_STDBUTTONSCHANGED) { DPRINTFN(ATP_LLEVEL_INFO, "button %s\n", ((sc->sc_status.button & MOUSE_BUTTON1DOWN) ? "pressed" : "released")); } else if ((sc->sc_status.obutton == 0) && (sc->sc_status.button == 0) && (tap_fingers != 0)) { /* Ignore single-finger taps at the edges. */ if ((tap_fingers == 1) && ((reaped_xlocs[0] <= sc->sc_left_margin) || (reaped_xlocs[0] > sc->sc_right_margin))) { tap_fingers = 0; } DPRINTFN(ATP_LLEVEL_INFO, "tap_fingers: %u\n", tap_fingers); } if (sc->sc_status.flags & (MOUSE_POSCHANGED | MOUSE_STDBUTTONSCHANGED)) { int dx, dy; u_int n_movements; dx = 0, dy = 0, n_movements = 0; for (u_int i = 0; i < sc->sc_n_strokes; i++) { atp_stroke *stroke = &sc->sc_strokes[i]; if ((stroke->components[X].movement) || (stroke->components[Y].movement)) { dx += stroke->components[X].movement; dy += stroke->components[Y].movement; n_movements++; } } /* * Disregard movement if multiple * strokes record motion. */ if (n_movements != 1) dx = 0, dy = 0; sc->sc_status.dx += dx; sc->sc_status.dy += dy; atp_add_to_queue(sc, dx, -dy, sc->sc_status.button); } if (tap_fingers != 0) { /* Add a pair of events (button-down and button-up). */ switch (tap_fingers) { case 1: atp_add_to_queue(sc, 0, 0, MOUSE_BUTTON1DOWN); break; case 2: atp_add_to_queue(sc, 0, 0, MOUSE_BUTTON2DOWN); break; case 3: atp_add_to_queue(sc, 0, 0, MOUSE_BUTTON3DOWN); break; default: break;/* handle taps of only up to 3 fingers */ } atp_add_to_queue(sc, 0, 0, 0); /* button release */ } /* * The device continues to trigger interrupts at a * fast rate even after touchpad activity has * stopped. Upon detecting that the device has * remained idle beyond a threshold, we reinitialize * it to silence the interrupts. */ if ((sc->sc_status.flags == 0) && (sc->sc_n_strokes == 0) && (sc->sc_status.button == 0)) { sc->sc_idlecount++; if (sc->sc_idlecount >= ATP_IDLENESS_THRESHOLD) { DPRINTFN(ATP_LLEVEL_INFO, "idle\n"); /* * Use the last frame before we go idle for * calibration on pads which do not send * calibration frames. */ if (sc->sc_params->prot < ATP_PROT_GEYSER3) { memcpy(sc->base_x, sc->cur_x, sc->sc_params->n_xsensors * sizeof(*(sc->base_x))); memcpy(sc->base_y, sc->cur_y, sc->sc_params->n_ysensors * sizeof(*(sc->base_y))); } sc->sc_idlecount = 0; usbd_transfer_start(sc->sc_xfer[ATP_RESET]); } } else { sc->sc_idlecount = 0; } case USB_ST_SETUP: tr_setup: /* check if we can put more data into the FIFO */ if (usb_fifo_put_bytes_max( sc->sc_fifo.fp[USB_FIFO_RX]) != 0) { usbd_xfer_set_frame_len(xfer, 0, sc->sc_params->data_len); usbd_transfer_submit(xfer); } break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } return; } static void atp_add_to_queue(struct atp_softc *sc, int dx, int dy, uint32_t buttons_in) { uint32_t buttons_out; uint8_t buf[8]; dx = imin(dx, 254); dx = imax(dx, -256); dy = imin(dy, 254); dy = imax(dy, -256); buttons_out = MOUSE_MSC_BUTTONS; if (buttons_in & MOUSE_BUTTON1DOWN) buttons_out &= ~MOUSE_MSC_BUTTON1UP; else if (buttons_in & MOUSE_BUTTON2DOWN) buttons_out &= ~MOUSE_MSC_BUTTON2UP; else if (buttons_in & MOUSE_BUTTON3DOWN) buttons_out &= ~MOUSE_MSC_BUTTON3UP; DPRINTFN(ATP_LLEVEL_INFO, "dx=%d, dy=%d, buttons=%x\n", dx, dy, buttons_out); /* Encode the mouse data in standard format; refer to mouse(4) */ buf[0] = sc->sc_mode.syncmask[1]; buf[0] |= buttons_out; buf[1] = dx >> 1; buf[2] = dy >> 1; buf[3] = dx - (dx >> 1); buf[4] = dy - (dy >> 1); /* Encode extra bytes for level 1 */ if (sc->sc_mode.level == 1) { buf[5] = 0; /* dz */ buf[6] = 0; /* dz - (dz / 2) */ buf[7] = MOUSE_SYS_EXTBUTTONS; /* Extra buttons all up. */ } usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf, sc->sc_mode.packetsize, 1); } static void atp_reset_buf(struct atp_softc *sc) { /* reset read queue */ usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]); } static void atp_start_read(struct usb_fifo *fifo) { struct atp_softc *sc = usb_fifo_softc(fifo); int rate; /* Check if we should override the default polling interval */ rate = sc->sc_pollrate; /* Range check rate */ if (rate > 1000) rate = 1000; /* Check for set rate */ if ((rate > 0) && (sc->sc_xfer[ATP_INTR_DT] != NULL)) { /* Stop current transfer, if any */ usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]); /* Set new interval */ usbd_xfer_set_interval(sc->sc_xfer[ATP_INTR_DT], 1000 / rate); /* Only set pollrate once */ sc->sc_pollrate = 0; } usbd_transfer_start(sc->sc_xfer[ATP_INTR_DT]); } static void atp_stop_read(struct usb_fifo *fifo) { struct atp_softc *sc = usb_fifo_softc(fifo); usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]); } static int atp_open(struct usb_fifo *fifo, int fflags) { DPRINTFN(ATP_LLEVEL_INFO, "\n"); if (fflags & FREAD) { struct atp_softc *sc = usb_fifo_softc(fifo); int rc; if (sc->sc_state & ATP_ENABLED) return (EBUSY); if (usb_fifo_alloc_buffer(fifo, ATP_FIFO_BUF_SIZE, ATP_FIFO_QUEUE_MAXLEN)) { return (ENOMEM); } rc = atp_enable(sc); if (rc != 0) { usb_fifo_free_buffer(fifo); return (rc); } } return (0); } static void atp_close(struct usb_fifo *fifo, int fflags) { if (fflags & FREAD) { struct atp_softc *sc = usb_fifo_softc(fifo); atp_disable(sc); usb_fifo_free_buffer(fifo); } } int atp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) { struct atp_softc *sc = usb_fifo_softc(fifo); mousemode_t mode; int error = 0; mtx_lock(&sc->sc_mutex); switch(cmd) { case MOUSE_GETHWINFO: *(mousehw_t *)addr = sc->sc_hw; break; case MOUSE_GETMODE: *(mousemode_t *)addr = sc->sc_mode; break; case MOUSE_SETMODE: mode = *(mousemode_t *)addr; if (mode.level == -1) /* Don't change the current setting */ ; else if ((mode.level < 0) || (mode.level > 1)) { error = EINVAL; goto done; } sc->sc_mode.level = mode.level; sc->sc_pollrate = mode.rate; sc->sc_hw.buttons = 3; if (sc->sc_mode.level == 0) { 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) { 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; } atp_reset_buf(sc); break; case MOUSE_GETLEVEL: *(int *)addr = sc->sc_mode.level; break; case MOUSE_SETLEVEL: if (*(int *)addr < 0 || *(int *)addr > 1) { error = EINVAL; goto done; } sc->sc_mode.level = *(int *)addr; sc->sc_hw.buttons = 3; if (sc->sc_mode.level == 0) { 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) { 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; } atp_reset_buf(sc); break; case MOUSE_GETSTATUS: { mousestatus_t *status = (mousestatus_t *)addr; *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; if (status->dx || status->dy || status->dz) status->flags |= MOUSE_POSCHANGED; if (status->button != status->obutton) status->flags |= MOUSE_BUTTONSCHANGED; break; } default: error = ENOTTY; } done: mtx_unlock(&sc->sc_mutex); return (error); } static int atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS) { int error; u_int tmp; u_int prev_mickeys_scale_factor; prev_mickeys_scale_factor = atp_mickeys_scale_factor; tmp = atp_mickeys_scale_factor; error = sysctl_handle_int(oidp, &tmp, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (tmp == prev_mickeys_scale_factor) return (0); /* no change */ atp_mickeys_scale_factor = tmp; DPRINTFN(ATP_LLEVEL_INFO, "%s: resetting mickeys_scale_factor to %u\n", ATP_DRIVER_NAME, tmp); /* Update dependent thresholds. */ if (atp_small_movement_threshold == (prev_mickeys_scale_factor >> 3)) atp_small_movement_threshold = atp_mickeys_scale_factor >> 3; if (atp_max_delta_mickeys == ((3 * prev_mickeys_scale_factor) >> 1)) atp_max_delta_mickeys = ((3 * atp_mickeys_scale_factor) >>1); if (atp_slide_min_movement == (prev_mickeys_scale_factor >> 3)) atp_slide_min_movement = atp_mickeys_scale_factor >> 3; return (0); } static device_method_t atp_methods[] = { /* Device interface */ DEVMETHOD(device_probe, atp_probe), DEVMETHOD(device_attach, atp_attach), DEVMETHOD(device_detach, atp_detach), { 0, 0 } }; static driver_t atp_driver = { .name = ATP_DRIVER_NAME, .methods = atp_methods, .size = sizeof(struct atp_softc) }; static devclass_t atp_devclass; DRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, 0); MODULE_DEPEND(atp, usb, 1, 1, 1); MODULE_VERSION(atp, 1);