/*- * Copyright (c) 2014 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. */ /* * Some tables, structures, definitions and constant values for the * touchpad protocol has been copied from Linux's * "drivers/input/mouse/bcm5974.c" which has the following copyright * holders under GPLv2. All device specific code in this driver has * been written from scratch. The decoding algorithm is based on * output from FreeBSD's usbdump. * * Copyright (C) 2008 Henrik Rydberg (rydberg@euromail.se) * Copyright (C) 2008 Scott Shawcroft (scott.shawcroft@gmail.com) * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) * Copyright (C) 2005 Johannes Berg (johannes@sipsolutions.net) * Copyright (C) 2005 Stelian Pop (stelian@popies.net) * Copyright (C) 2005 Frank Arnold (frank@scirocco-5v-turbo.de) * Copyright (C) 2005 Peter Osterlund (petero2@telia.com) * Copyright (C) 2005 Michael Hanselmann (linux-kernel@hansmi.ch) * Copyright (C) 2006 Nicolas Boichat (nicolas@boichat.ch) */ /* * Author's note: 'atp' supports two distinct families of Apple trackpad * products: the older Fountain/Geyser and the latest Wellspring trackpads. * The first version made its appearance with FreeBSD 8 and worked only with * the Fountain/Geyser hardware. A fork of this driver for Wellspring was * contributed by Huang Wen Hui. This driver unifies the Wellspring effort * and also improves upon the original work. * * I'm grateful to Stephan Scheunig, Angela Naegele, and Nokia IT-support * for helping me with access to hardware. Thanks also go to Nokia for * giving me an opportunity to do this work. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR atp_debug #include #include #define ATP_DRIVER_NAME "atp" /* * Driver specific options: the following options may be set by * `options' statements in the kernel configuration file. */ /* The divisor used to translate sensor reported positions to mickeys. */ #ifndef ATP_SCALE_FACTOR #define ATP_SCALE_FACTOR 16 #endif /* Threshold for small movement noise (in mickeys) */ #ifndef ATP_SMALL_MOVEMENT_THRESHOLD #define ATP_SMALL_MOVEMENT_THRESHOLD 30 #endif /* Threshold of instantaneous deltas beyond which movement is considered fast.*/ #ifndef ATP_FAST_MOVEMENT_TRESHOLD #define ATP_FAST_MOVEMENT_TRESHOLD 150 #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 #ifndef ATP_IDLENESS_THRESHOLD #define ATP_IDLENESS_THRESHOLD 10 #endif #ifndef FG_SENSOR_NOISE_THRESHOLD #define FG_SENSOR_NOISE_THRESHOLD 2 #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 wait duration in ticks after losing a touch contact before * zombied strokes are reaped and turned into button events. */ #define ATP_ZOMBIE_STROKE_REAP_INTERVAL (hz / 20) /* 50 ms */ /* The multiplier used to translate sensor reported positions to mickeys. */ #define FG_SCALE_FACTOR 380 /* * The movement threshold for a stroke; this is the maximum difference * in position which will be resolved as a continuation of a stroke * component. */ #define FG_MAX_DELTA_MICKEYS ((3 * (FG_SCALE_FACTOR)) >> 1) /* Distance-squared threshold for matching a finger with a known stroke */ #ifndef WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ #define WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ 1000000 #endif /* Ignore pressure spans with cumulative press. below this value. */ #define FG_PSPAN_MIN_CUM_PRESSURE 10 /* Maximum allowed width for pressure-spans.*/ #define FG_PSPAN_MAX_WIDTH 4 /* end of driver specific options */ /* Tunables */ static 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_RWTUN, &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_RWTUN, &atp_touch_timeout, 125000, "age threshold in microseconds 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_RWTUN, &atp_double_tap_threshold, ATP_DOUBLE_TAP_N_DRAG_THRESHOLD, "maximum time in microseconds to allow association between a double-tap and " "drag gesture"); 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_RWTUN, &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_SMALL_MOVEMENT_THRESHOLD; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, small_movement, CTLFLAG_RWTUN, &atp_small_movement_threshold, ATP_SMALL_MOVEMENT_THRESHOLD, "the small movement black-hole for filtering noise"); static u_int atp_tap_minimum = 1; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, tap_minimum, CTLFLAG_RWTUN, &atp_tap_minimum, 1, "Minimum number of taps before detection"); /* * 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 = 2 * ATP_SMALL_MOVEMENT_THRESHOLD; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, slide_min_movement, CTLFLAG_RWTUN, &atp_slide_min_movement, 2 * ATP_SMALL_MOVEMENT_THRESHOLD, "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 = 4; SYSCTL_UINT(_hw_usb_atp, OID_AUTO, stroke_maturity_threshold, CTLFLAG_RWTUN, &atp_stroke_maturity_threshold, 4, "the minimum age of a stroke for it to be considered mature"); typedef enum atp_trackpad_family { TRACKPAD_FAMILY_FOUNTAIN_GEYSER, TRACKPAD_FAMILY_WELLSPRING, TRACKPAD_FAMILY_MAX /* keep this at the tail end of the enumeration */ } trackpad_family_t; enum fountain_geyser_product { FOUNTAIN, GEYSER1, GEYSER1_17inch, GEYSER2, GEYSER3, GEYSER4, FOUNTAIN_GEYSER_PRODUCT_MAX /* keep this at the end */ }; enum wellspring_product { WELLSPRING1, WELLSPRING2, WELLSPRING3, WELLSPRING4, WELLSPRING4A, WELLSPRING5, WELLSPRING6A, WELLSPRING6, WELLSPRING5A, WELLSPRING7, WELLSPRING7A, WELLSPRING8, WELLSPRING_PRODUCT_MAX /* keep this at the end of the enumeration */ }; /* trackpad header types */ enum fountain_geyser_trackpad_type { FG_TRACKPAD_TYPE_GEYSER1, FG_TRACKPAD_TYPE_GEYSER2, FG_TRACKPAD_TYPE_GEYSER3, FG_TRACKPAD_TYPE_GEYSER4, }; enum wellspring_trackpad_type { WSP_TRACKPAD_TYPE1, /* plain trackpad */ WSP_TRACKPAD_TYPE2, /* button integrated in trackpad */ WSP_TRACKPAD_TYPE3 /* additional header fields since June 2013 */ }; /* * Trackpad family and product and family are encoded together in the * driver_info value associated with a trackpad product. */ #define N_PROD_BITS 8 /* Number of bits used to encode product */ #define ENCODE_DRIVER_INFO(FAMILY, PROD) \ (((FAMILY) << N_PROD_BITS) | (PROD)) #define DECODE_FAMILY_FROM_DRIVER_INFO(INFO) ((INFO) >> N_PROD_BITS) #define DECODE_PRODUCT_FROM_DRIVER_INFO(INFO) \ ((INFO) & ((1 << N_PROD_BITS) - 1)) #define FG_DRIVER_INFO(PRODUCT) \ ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_FOUNTAIN_GEYSER, PRODUCT) #define WELLSPRING_DRIVER_INFO(PRODUCT) \ ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_WELLSPRING, PRODUCT) /* * 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 fg_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. */ } fg_pspan; #define FG_MAX_PSPANS_PER_AXIS 3 #define FG_MAX_STROKES (2 * FG_MAX_PSPANS_PER_AXIS) #define WELLSPRING_INTERFACE_INDEX 1 /* trackpad finger data offsets, le16-aligned */ #define WSP_TYPE1_FINGER_DATA_OFFSET (13 * 2) #define WSP_TYPE2_FINGER_DATA_OFFSET (15 * 2) #define WSP_TYPE3_FINGER_DATA_OFFSET (19 * 2) /* trackpad button data offsets */ #define WSP_TYPE2_BUTTON_DATA_OFFSET 15 #define WSP_TYPE3_BUTTON_DATA_OFFSET 23 /* list of device capability bits */ #define HAS_INTEGRATED_BUTTON 1 /* trackpad finger structure - little endian */ struct wsp_finger_sensor_data { int16_t origin; /* zero when switching track finger */ int16_t abs_x; /* absolute x coordinate */ int16_t abs_y; /* absolute y coordinate */ int16_t rel_x; /* relative x coordinate */ int16_t rel_y; /* relative y coordinate */ int16_t tool_major; /* tool area, major axis */ int16_t tool_minor; /* tool area, minor axis */ int16_t orientation; /* 16384 when point, else 15 bit angle */ int16_t touch_major; /* touch area, major axis */ int16_t touch_minor; /* touch area, minor axis */ int16_t unused[3]; /* zeros */ int16_t multi; /* one finger: varies, more fingers: constant */ } __packed; typedef struct wsp_finger { /* to track fingers as they match against strokes. */ boolean_t matched; /* location (scaled using the mickeys factor) */ int x; int y; } wsp_finger_t; #define WSP_MAX_FINGERS 16 #define WSP_SIZEOF_FINGER_SENSOR_DATA sizeof(struct wsp_finger_sensor_data) #define WSP_SIZEOF_ALL_FINGER_DATA (WSP_MAX_FINGERS * \ WSP_SIZEOF_FINGER_SENSOR_DATA) #define WSP_MAX_FINGER_ORIENTATION 16384 #define ATP_SENSOR_DATA_BUF_MAX 1024 #if (ATP_SENSOR_DATA_BUF_MAX < ((WSP_MAX_FINGERS * 14 * 2) + \ WSP_TYPE3_FINGER_DATA_OFFSET)) /* note: 14 * 2 in the above is based on sizeof(struct wsp_finger_sensor_data)*/ #error "ATP_SENSOR_DATA_BUF_MAX is too small" #endif #define ATP_MAX_STROKES MAX(WSP_MAX_FINGERS, FG_MAX_STROKES) #define FG_MAX_XSENSORS 26 #define FG_MAX_YSENSORS 16 /* device-specific configuration */ struct fg_dev_params { u_int data_len; /* for sensor data */ u_int n_xsensors; u_int n_ysensors; enum fountain_geyser_trackpad_type prot; }; struct wsp_dev_params { uint8_t caps; /* device capability bitmask */ uint8_t tp_type; /* type of trackpad interface */ uint8_t finger_data_offset; /* offset to trackpad finger data */ }; static const struct fg_dev_params fg_dev_params[FOUNTAIN_GEYSER_PRODUCT_MAX] = { [FOUNTAIN] = { .data_len = 81, .n_xsensors = 16, .n_ysensors = 16, .prot = FG_TRACKPAD_TYPE_GEYSER1 }, [GEYSER1] = { .data_len = 81, .n_xsensors = 16, .n_ysensors = 16, .prot = FG_TRACKPAD_TYPE_GEYSER1 }, [GEYSER1_17inch] = { .data_len = 81, .n_xsensors = 26, .n_ysensors = 16, .prot = FG_TRACKPAD_TYPE_GEYSER1 }, [GEYSER2] = { .data_len = 64, .n_xsensors = 15, .n_ysensors = 9, .prot = FG_TRACKPAD_TYPE_GEYSER2 }, [GEYSER3] = { .data_len = 64, .n_xsensors = 20, .n_ysensors = 10, .prot = FG_TRACKPAD_TYPE_GEYSER3 }, [GEYSER4] = { .data_len = 64, .n_xsensors = 20, .n_ysensors = 10, .prot = FG_TRACKPAD_TYPE_GEYSER4 } }; static const STRUCT_USB_HOST_ID fg_devs[] = { /* PowerBooks Feb 2005, iBooks G4 */ { USB_VPI(USB_VENDOR_APPLE, 0x020e, FG_DRIVER_INFO(FOUNTAIN)) }, { USB_VPI(USB_VENDOR_APPLE, 0x020f, FG_DRIVER_INFO(FOUNTAIN)) }, { USB_VPI(USB_VENDOR_APPLE, 0x0210, FG_DRIVER_INFO(FOUNTAIN)) }, { USB_VPI(USB_VENDOR_APPLE, 0x030a, FG_DRIVER_INFO(FOUNTAIN)) }, { USB_VPI(USB_VENDOR_APPLE, 0x030b, FG_DRIVER_INFO(GEYSER1)) }, /* PowerBooks Oct 2005 */ { USB_VPI(USB_VENDOR_APPLE, 0x0214, FG_DRIVER_INFO(GEYSER2)) }, { USB_VPI(USB_VENDOR_APPLE, 0x0215, FG_DRIVER_INFO(GEYSER2)) }, { USB_VPI(USB_VENDOR_APPLE, 0x0216, FG_DRIVER_INFO(GEYSER2)) }, /* Core Duo MacBook & MacBook Pro */ { USB_VPI(USB_VENDOR_APPLE, 0x0217, FG_DRIVER_INFO(GEYSER3)) }, { USB_VPI(USB_VENDOR_APPLE, 0x0218, FG_DRIVER_INFO(GEYSER3)) }, { USB_VPI(USB_VENDOR_APPLE, 0x0219, FG_DRIVER_INFO(GEYSER3)) }, /* Core2 Duo MacBook & MacBook Pro */ { USB_VPI(USB_VENDOR_APPLE, 0x021a, FG_DRIVER_INFO(GEYSER4)) }, { USB_VPI(USB_VENDOR_APPLE, 0x021b, FG_DRIVER_INFO(GEYSER4)) }, { USB_VPI(USB_VENDOR_APPLE, 0x021c, FG_DRIVER_INFO(GEYSER4)) }, /* Core2 Duo MacBook3,1 */ { USB_VPI(USB_VENDOR_APPLE, 0x0229, FG_DRIVER_INFO(GEYSER4)) }, { USB_VPI(USB_VENDOR_APPLE, 0x022a, FG_DRIVER_INFO(GEYSER4)) }, { USB_VPI(USB_VENDOR_APPLE, 0x022b, FG_DRIVER_INFO(GEYSER4)) }, /* 17 inch PowerBook */ { USB_VPI(USB_VENDOR_APPLE, 0x020d, FG_DRIVER_INFO(GEYSER1_17inch)) }, }; static const struct wsp_dev_params wsp_dev_params[WELLSPRING_PRODUCT_MAX] = { [WELLSPRING1] = { .caps = 0, .tp_type = WSP_TRACKPAD_TYPE1, .finger_data_offset = WSP_TYPE1_FINGER_DATA_OFFSET, }, [WELLSPRING2] = { .caps = 0, .tp_type = WSP_TRACKPAD_TYPE1, .finger_data_offset = WSP_TYPE1_FINGER_DATA_OFFSET, }, [WELLSPRING3] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING4] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING4A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING5] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING6] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING5A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING6A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING7] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING7A] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE2, .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET, }, [WELLSPRING8] = { .caps = HAS_INTEGRATED_BUTTON, .tp_type = WSP_TRACKPAD_TYPE3, .finger_data_offset = WSP_TYPE3_FINGER_DATA_OFFSET, }, }; #define ATP_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) } /* TODO: STRUCT_USB_HOST_ID */ static const struct usb_device_id wsp_devs[] = { /* MacbookAir1.1 */ ATP_DEV(APPLE, WELLSPRING_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING1)), ATP_DEV(APPLE, WELLSPRING_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING1)), ATP_DEV(APPLE, WELLSPRING_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING1)), /* MacbookProPenryn, aka wellspring2 */ ATP_DEV(APPLE, WELLSPRING2_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING2)), ATP_DEV(APPLE, WELLSPRING2_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING2)), ATP_DEV(APPLE, WELLSPRING2_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING2)), /* Macbook5,1 (unibody), aka wellspring3 */ ATP_DEV(APPLE, WELLSPRING3_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING3)), ATP_DEV(APPLE, WELLSPRING3_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING3)), ATP_DEV(APPLE, WELLSPRING3_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING3)), /* MacbookAir3,2 (unibody), aka wellspring4 */ ATP_DEV(APPLE, WELLSPRING4_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4)), ATP_DEV(APPLE, WELLSPRING4_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING4)), ATP_DEV(APPLE, WELLSPRING4_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING4)), /* MacbookAir3,1 (unibody), aka wellspring4 */ ATP_DEV(APPLE, WELLSPRING4A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4A)), ATP_DEV(APPLE, WELLSPRING4A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING4A)), ATP_DEV(APPLE, WELLSPRING4A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING4A)), /* Macbook8 (unibody, March 2011) */ ATP_DEV(APPLE, WELLSPRING5_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5)), ATP_DEV(APPLE, WELLSPRING5_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING5)), ATP_DEV(APPLE, WELLSPRING5_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING5)), /* MacbookAir4,1 (unibody, July 2011) */ ATP_DEV(APPLE, WELLSPRING6A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6A)), ATP_DEV(APPLE, WELLSPRING6A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING6A)), ATP_DEV(APPLE, WELLSPRING6A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING6A)), /* MacbookAir4,2 (unibody, July 2011) */ ATP_DEV(APPLE, WELLSPRING6_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6)), ATP_DEV(APPLE, WELLSPRING6_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING6)), ATP_DEV(APPLE, WELLSPRING6_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING6)), /* Macbook8,2 (unibody) */ ATP_DEV(APPLE, WELLSPRING5A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5A)), ATP_DEV(APPLE, WELLSPRING5A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING5A)), ATP_DEV(APPLE, WELLSPRING5A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING5A)), /* MacbookPro10,1 (unibody, June 2012) */ /* MacbookPro11,? (unibody, June 2013) */ ATP_DEV(APPLE, WELLSPRING7_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7)), ATP_DEV(APPLE, WELLSPRING7_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING7)), ATP_DEV(APPLE, WELLSPRING7_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING7)), /* MacbookPro10,2 (unibody, October 2012) */ ATP_DEV(APPLE, WELLSPRING7A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7A)), ATP_DEV(APPLE, WELLSPRING7A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING7A)), ATP_DEV(APPLE, WELLSPRING7A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING7A)), /* MacbookAir6,2 (unibody, June 2013) */ ATP_DEV(APPLE, WELLSPRING8_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING8)), ATP_DEV(APPLE, WELLSPRING8_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING8)), ATP_DEV(APPLE, WELLSPRING8_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING8)), }; typedef enum atp_stroke_type { ATP_STROKE_TOUCH, ATP_STROKE_SLIDE, } atp_stroke_type; typedef enum atp_axis { X = 0, Y = 1, NUM_AXES } atp_axis; #define ATP_FIFO_BUF_SIZE 8 /* bytes */ #define ATP_FIFO_QUEUE_MAXLEN 50 /* units */ enum { ATP_INTR_DT, ATP_RESET, ATP_N_TRANSFER, }; typedef struct fg_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.*/ int delta_mickeys; /* change in location (un-smoothened movement)*/ } fg_stroke_component_t; /* * The following structure captures a finger contact with the * touchpad. A stroke comprises two p-span components and some state. */ typedef struct atp_stroke { TAILQ_ENTRY(atp_stroke) entry; atp_stroke_type type; uint32_t flags; /* the state of this stroke */ #define ATSF_ZOMBIE 0x1 boolean_t matched; /* to track match against fingers.*/ struct timeval ctime; /* create time; for coincident siblings. */ /* * Unit: interrupts; we maintain this value in * addition to 'ctime' in order to avoid the * expensive call to microtime() at every * interrupt. */ uint32_t age; /* Location */ int x; int y; /* Fields containing information about movement. */ int instantaneous_dx; /* curr. change in X location (un-smoothened) */ int instantaneous_dy; /* curr. change in Y location (un-smoothened) */ int pending_dx; /* cum. of pending short movements */ int pending_dy; /* cum. of pending short movements */ int movement_dx; /* interpreted smoothened movement */ int movement_dy; /* interpreted smoothened movement */ int cum_movement_x; /* cum. horizontal movement */ int cum_movement_y; /* cum. vertical movement */ /* * The following member is relevant only for fountain-geyser trackpads. * For these, there is the need to track pressure-spans and cumulative * pressures for stroke components. */ fg_stroke_component_t components[NUM_AXES]; } atp_stroke_t; struct atp_softc; /* forward declaration */ typedef void (*sensor_data_interpreter_t)(struct atp_softc *sc, u_int len); struct atp_softc { device_t sc_dev; struct usb_device *sc_usb_device; struct mtx sc_mutex; /* for synchronization */ struct usb_fifo_sc sc_fifo; #define MODE_LENGTH 8 char sc_mode_bytes[MODE_LENGTH]; /* device mode */ trackpad_family_t sc_family; const void *sc_params; /* device configuration */ sensor_data_interpreter_t sensor_data_interpreter; mousehw_t sc_hw; mousemode_t sc_mode; 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 struct usb_xfer *sc_xfer[ATP_N_TRANSFER]; u_int sc_pollrate; int sc_fflags; atp_stroke_t sc_strokes_data[ATP_MAX_STROKES]; TAILQ_HEAD(,atp_stroke) sc_stroke_free; TAILQ_HEAD(,atp_stroke) sc_stroke_used; u_int sc_n_strokes; struct callout sc_callout; /* * button status. Set to non-zero if the mouse-button is physically * pressed. This state variable is exposed through softc to allow * reap_sibling_zombies to avoid registering taps while the trackpad * button is pressed. */ uint8_t sc_ibtn; /* * Time when touch zombies were last reaped; useful for detecting * double-touch-n-drag. */ struct timeval sc_touch_reap_time; u_int sc_idlecount; /* Regarding the data transferred from t-pad in USB INTR packets. */ u_int sc_expected_sensor_data_len; uint8_t sc_sensor_data[ATP_SENSOR_DATA_BUF_MAX] __aligned(4); int sc_cur_x[FG_MAX_XSENSORS]; /* current sensor readings */ int sc_cur_y[FG_MAX_YSENSORS]; int sc_base_x[FG_MAX_XSENSORS]; /* base sensor readings */ int sc_base_y[FG_MAX_YSENSORS]; int sc_pressure_x[FG_MAX_XSENSORS]; /* computed pressures */ int sc_pressure_y[FG_MAX_YSENSORS]; fg_pspan sc_pspans_x[FG_MAX_PSPANS_PER_AXIS]; fg_pspan sc_pspans_y[FG_MAX_PSPANS_PER_AXIS]; }; /* * The last byte of the fountain-geyser sensor data contains status bits; the * following values define the meanings of these bits. * (only Geyser 3/4) */ enum geyser34_status_bits { FG_STATUS_BUTTON = (uint8_t)0x01, /* The button was pressed */ FG_STATUS_BASE_UPDATE = (uint8_t)0x04, /* Data from an untouched pad.*/ }; typedef enum interface_mode { RAW_SENSOR_MODE = (uint8_t)0x01, 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_set_device_mode(struct atp_softc *, interface_mode); static void atp_reset_callback(struct usb_xfer *, usb_error_t); static int atp_enable(struct atp_softc *); static void atp_disable(struct atp_softc *); /* sensor interpretation */ static void fg_interpret_sensor_data(struct atp_softc *, u_int); static void fg_extract_sensor_data(const int8_t *, u_int, atp_axis, int *, enum fountain_geyser_trackpad_type); static void fg_get_pressures(int *, const int *, const int *, int); static void fg_detect_pspans(int *, u_int, u_int, fg_pspan *, u_int *); static void wsp_interpret_sensor_data(struct atp_softc *, u_int); /* movement detection */ static boolean_t fg_match_stroke_component(fg_stroke_component_t *, const fg_pspan *, atp_stroke_type); static void fg_match_strokes_against_pspans(struct atp_softc *, atp_axis, fg_pspan *, u_int, u_int); static boolean_t wsp_match_strokes_against_fingers(struct atp_softc *, wsp_finger_t *, u_int); static boolean_t fg_update_strokes(struct atp_softc *, fg_pspan *, u_int, fg_pspan *, u_int); static boolean_t wsp_update_strokes(struct atp_softc *, wsp_finger_t [WSP_MAX_FINGERS], u_int); static void fg_add_stroke(struct atp_softc *, const fg_pspan *, const fg_pspan *); static void fg_add_new_strokes(struct atp_softc *, fg_pspan *, u_int, fg_pspan *, u_int); static void wsp_add_stroke(struct atp_softc *, const wsp_finger_t *); static void atp_advance_stroke_state(struct atp_softc *, atp_stroke_t *, boolean_t *); static boolean_t atp_stroke_has_small_movement(const atp_stroke_t *); static void atp_update_pending_mickeys(atp_stroke_t *); static boolean_t atp_compute_stroke_movement(atp_stroke_t *); static void atp_terminate_stroke(struct atp_softc *, atp_stroke_t *); /* tap detection */ static boolean_t atp_is_horizontal_scroll(const atp_stroke_t *); static boolean_t atp_is_vertical_scroll(const atp_stroke_t *); static void atp_reap_sibling_zombies(void *); static void atp_convert_to_slide(struct atp_softc *, atp_stroke_t *); /* updating fifo */ static void atp_reset_buf(struct atp_softc *); static void atp_add_to_queue(struct atp_softc *, int, int, int, uint32_t); /* 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_xfer_config[ATP_N_TRANSFER] = { [ATP_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = { .pipe_bof = 1, /* block pipe on failure */ .short_xfer_ok = 1, }, .bufsize = ATP_SENSOR_DATA_BUF_MAX, .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 atp_stroke_t * atp_alloc_stroke(struct atp_softc *sc) { atp_stroke_t *pstroke; pstroke = TAILQ_FIRST(&sc->sc_stroke_free); if (pstroke == NULL) goto done; TAILQ_REMOVE(&sc->sc_stroke_free, pstroke, entry); memset(pstroke, 0, sizeof(*pstroke)); TAILQ_INSERT_TAIL(&sc->sc_stroke_used, pstroke, entry); sc->sc_n_strokes++; done: return (pstroke); } static void atp_free_stroke(struct atp_softc *sc, atp_stroke_t *pstroke) { if (pstroke == NULL) return; sc->sc_n_strokes--; TAILQ_REMOVE(&sc->sc_stroke_used, pstroke, entry); TAILQ_INSERT_TAIL(&sc->sc_stroke_free, pstroke, entry); } static void atp_init_stroke_pool(struct atp_softc *sc) { u_int x; TAILQ_INIT(&sc->sc_stroke_free); TAILQ_INIT(&sc->sc_stroke_used); sc->sc_n_strokes = 0; memset(&sc->sc_strokes_data, 0, sizeof(sc->sc_strokes_data)); for (x = 0; x != ATP_MAX_STROKES; x++) { TAILQ_INSERT_TAIL(&sc->sc_stroke_free, &sc->sc_strokes_data[x], entry); } } static usb_error_t atp_set_device_mode(struct atp_softc *sc, interface_mode newMode) { uint8_t mode_value; usb_error_t err; if ((newMode != RAW_SENSOR_MODE) && (newMode != HID_MODE)) return (USB_ERR_INVAL); if ((newMode == RAW_SENSOR_MODE) && (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER)) mode_value = (uint8_t)0x04; else mode_value = newMode; err = usbd_req_get_report(sc->sc_usb_device, NULL /* mutex */, sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */, 0x03 /* type */, 0x00 /* id */); if (err != USB_ERR_NORMAL_COMPLETION) { DPRINTF("Failed to read device mode (%d)\n", err); return (err); } if (sc->sc_mode_bytes[0] == mode_value) return (err); /* * XXX Need to wait at least 250ms for hardware to get * ready. The device mode handling appears to be handled * asynchronously and we should not issue these commands too * quickly. */ pause("WHW", hz / 4); sc->sc_mode_bytes[0] = mode_value; return (usbd_req_set_report(sc->sc_usb_device, NULL /* mutex */, sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */, 0x03 /* type */, 0x00 /* id */)); } static 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); uint8_t mode_value; if (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER) mode_value = 0x04; else mode_value = RAW_SENSOR_MODE; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: sc->sc_mode_bytes[0] = mode_value; 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) { if (sc->sc_state & ATP_ENABLED) return (0); /* reset status */ memset(&sc->sc_status, 0, sizeof(sc->sc_status)); atp_init_stroke_pool(sc); sc->sc_state |= ATP_ENABLED; DPRINTFN(ATP_LLEVEL_INFO, "enabled atp\n"); return (0); } static void atp_disable(struct atp_softc *sc) { sc->sc_state &= ~(ATP_ENABLED | ATP_VALID); DPRINTFN(ATP_LLEVEL_INFO, "disabled atp\n"); } static void fg_interpret_sensor_data(struct atp_softc *sc, u_int data_len) { u_int n_xpspans = 0; u_int n_ypspans = 0; uint8_t status_bits; const struct fg_dev_params *params = (const struct fg_dev_params *)sc->sc_params; fg_extract_sensor_data(sc->sc_sensor_data, params->n_xsensors, X, sc->sc_cur_x, params->prot); fg_extract_sensor_data(sc->sc_sensor_data, params->n_ysensors, Y, sc->sc_cur_y, 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->sc_sensor_data[params->data_len - 1]; if (((params->prot == FG_TRACKPAD_TYPE_GEYSER3) || (params->prot == FG_TRACKPAD_TYPE_GEYSER4)) && ((sc->sc_state & ATP_VALID) == 0)) { if (status_bits & FG_STATUS_BASE_UPDATE) { memcpy(sc->sc_base_x, sc->sc_cur_x, params->n_xsensors * sizeof(*sc->sc_base_x)); memcpy(sc->sc_base_y, sc->sc_cur_y, params->n_ysensors * sizeof(*sc->sc_base_y)); sc->sc_state |= ATP_VALID; return; } } /* Get pressure readings and detect p-spans for both axes. */ fg_get_pressures(sc->sc_pressure_x, sc->sc_cur_x, sc->sc_base_x, params->n_xsensors); fg_detect_pspans(sc->sc_pressure_x, params->n_xsensors, FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_x, &n_xpspans); fg_get_pressures(sc->sc_pressure_y, sc->sc_cur_y, sc->sc_base_y, params->n_ysensors); fg_detect_pspans(sc->sc_pressure_y, params->n_ysensors, FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_y, &n_ypspans); /* Update strokes with new pspans to detect movements. */ if (fg_update_strokes(sc, sc->sc_pspans_x, n_xpspans, sc->sc_pspans_y, n_ypspans)) sc->sc_status.flags |= MOUSE_POSCHANGED; sc->sc_ibtn = (status_bits & FG_STATUS_BUTTON) ? MOUSE_BUTTON1DOWN : 0; sc->sc_status.button = sc->sc_ibtn; /* * The Fountain/Geyser 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_idlecount++; if (sc->sc_idlecount >= ATP_IDLENESS_THRESHOLD) { /* * Use the last frame before we go idle for * calibration on pads which do not send * calibration frames. */ const struct fg_dev_params *params = (const struct fg_dev_params *)sc->sc_params; DPRINTFN(ATP_LLEVEL_INFO, "idle\n"); if (params->prot < FG_TRACKPAD_TYPE_GEYSER3) { memcpy(sc->sc_base_x, sc->sc_cur_x, params->n_xsensors * sizeof(*(sc->sc_base_x))); memcpy(sc->sc_base_y, sc->sc_cur_y, params->n_ysensors * sizeof(*(sc->sc_base_y))); } sc->sc_idlecount = 0; usbd_transfer_start(sc->sc_xfer[ATP_RESET]); } } else { sc->sc_idlecount = 0; } } /* * 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 void fg_extract_sensor_data(const int8_t *sensor_data, u_int num, atp_axis axis, int *arr, enum fountain_geyser_trackpad_type prot) { u_int i; u_int di; /* index into sensor data */ switch (prot) { case FG_TRACKPAD_TYPE_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 FG_TRACKPAD_TYPE_GEYSER2: for (i = 0, di = (axis == Y) ? 1 : 19; i < num; /* empty */ ) { arr[i++] = sensor_data[di++]; arr[i++] = sensor_data[di++]; di++; } break; case FG_TRACKPAD_TYPE_GEYSER3: case FG_TRACKPAD_TYPE_GEYSER4: for (i = 0, di = (axis == Y) ? 2 : 20; i < num; /* empty */ ) { arr[i++] = sensor_data[di++]; arr[i++] = sensor_data[di++]; di++; } break; default: break; } } static void fg_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] <= FG_SENSOR_NOISE_THRESHOLD) p[i] = 0; /* filter away noise */ else p[i] -= FG_SENSOR_NOISE_THRESHOLD; } } static void fg_detect_pspans(int *p, u_int num_sensors, u_int max_spans, /* max # of pspans permitted */ fg_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 fg_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(fg_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 < FG_PSPAN_MIN_CUM_PRESSURE) || (spans[i].width > FG_PSPAN_MAX_WIDTH)) { if ((i + 1) < num_spans) { memcpy(&spans[i], &spans[i + 1], (num_spans - i - 1) * sizeof(fg_pspan)); i--; } num_spans--; continue; } /* compute this span's representative location */ spans[i].loc = spans[i].cog * FG_SCALE_FACTOR / spans[i].cum; spans[i].matched = false; /* not yet matched against a stroke */ } *nspans_p = num_spans; } static void wsp_interpret_sensor_data(struct atp_softc *sc, u_int data_len) { const struct wsp_dev_params *params = sc->sc_params; wsp_finger_t fingers[WSP_MAX_FINGERS]; struct wsp_finger_sensor_data *source_fingerp; u_int n_source_fingers; u_int n_fingers; u_int i; /* validate sensor data length */ if ((data_len < params->finger_data_offset) || ((data_len - params->finger_data_offset) % WSP_SIZEOF_FINGER_SENSOR_DATA) != 0) return; /* compute number of source fingers */ n_source_fingers = (data_len - params->finger_data_offset) / WSP_SIZEOF_FINGER_SENSOR_DATA; if (n_source_fingers > WSP_MAX_FINGERS) n_source_fingers = WSP_MAX_FINGERS; /* iterate over the source data collecting useful fingers */ n_fingers = 0; source_fingerp = (struct wsp_finger_sensor_data *)(sc->sc_sensor_data + params->finger_data_offset); for (i = 0; i < n_source_fingers; i++, source_fingerp++) { /* swap endianness, if any */ if (le16toh(0x1234) != 0x1234) { source_fingerp->origin = le16toh((uint16_t)source_fingerp->origin); source_fingerp->abs_x = le16toh((uint16_t)source_fingerp->abs_x); source_fingerp->abs_y = le16toh((uint16_t)source_fingerp->abs_y); source_fingerp->rel_x = le16toh((uint16_t)source_fingerp->rel_x); source_fingerp->rel_y = le16toh((uint16_t)source_fingerp->rel_y); source_fingerp->tool_major = le16toh((uint16_t)source_fingerp->tool_major); source_fingerp->tool_minor = le16toh((uint16_t)source_fingerp->tool_minor); source_fingerp->orientation = le16toh((uint16_t)source_fingerp->orientation); source_fingerp->touch_major = le16toh((uint16_t)source_fingerp->touch_major); source_fingerp->touch_minor = le16toh((uint16_t)source_fingerp->touch_minor); source_fingerp->multi = le16toh((uint16_t)source_fingerp->multi); } /* check for minium threshold */ if (source_fingerp->touch_major == 0) continue; fingers[n_fingers].matched = false; fingers[n_fingers].x = source_fingerp->abs_x; fingers[n_fingers].y = -source_fingerp->abs_y; n_fingers++; } if ((sc->sc_n_strokes == 0) && (n_fingers == 0)) return; if (wsp_update_strokes(sc, fingers, n_fingers)) sc->sc_status.flags |= MOUSE_POSCHANGED; switch(params->tp_type) { case WSP_TRACKPAD_TYPE2: sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE2_BUTTON_DATA_OFFSET]; break; case WSP_TRACKPAD_TYPE3: sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE3_BUTTON_DATA_OFFSET]; break; default: break; } sc->sc_status.button = sc->sc_ibtn ? MOUSE_BUTTON1DOWN : 0; } /* * Match a pressure-span against a stroke-component. If there is a * match, update the component's state and return true. */ static boolean_t fg_match_stroke_component(fg_stroke_component_t *component, const fg_pspan *pspan, atp_stroke_type stroke_type) { int delta_mickeys; u_int min_pressure; delta_mickeys = pspan->loc - component->loc; if (abs(delta_mickeys) > (int)FG_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 fg_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis, fg_pspan *pspans, u_int n_pspans, u_int repeat_count) { atp_stroke_t *strokep; u_int repeat_index = 0; u_int i; /* Determine the index of the multi-span. */ if (repeat_count) { for (i = 0; i < n_pspans; i++) { if (pspans[i].cum > pspans[repeat_index].cum) repeat_index = i; } } TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { if (strokep->components[axis].matched) continue; /* skip matched components */ for (i = 0; i < n_pspans; i++) { if (pspans[i].matched) continue; /* skip matched pspans */ if (fg_match_stroke_component( &strokep->components[axis], &pspans[i], strokep->type)) { /* There is a match. */ strokep->components[axis].matched = true; /* Take care to repeat at the multi-span. */ if ((repeat_count > 0) && (i == repeat_index)) repeat_count--; else pspans[i].matched = true; break; /* skip to the next strokep */ } } /* loop over pspans */ } /* loop over strokes */ } static boolean_t wsp_match_strokes_against_fingers(struct atp_softc *sc, wsp_finger_t *fingers, u_int n_fingers) { boolean_t movement = false; atp_stroke_t *strokep; u_int i; /* reset the matched status for all strokes */ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) strokep->matched = false; for (i = 0; i != n_fingers; i++) { u_int least_distance_sq = WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ; atp_stroke_t *strokep_best = NULL; TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { int instantaneous_dx; int instantaneous_dy; u_int d_squared; if (strokep->matched) continue; instantaneous_dx = fingers[i].x - strokep->x; instantaneous_dy = fingers[i].y - strokep->y; /* skip strokes which are far away */ d_squared = (instantaneous_dx * instantaneous_dx) + (instantaneous_dy * instantaneous_dy); if (d_squared < least_distance_sq) { least_distance_sq = d_squared; strokep_best = strokep; } } strokep = strokep_best; if (strokep != NULL) { fingers[i].matched = true; strokep->matched = true; strokep->instantaneous_dx = fingers[i].x - strokep->x; strokep->instantaneous_dy = fingers[i].y - strokep->y; strokep->x = fingers[i].x; strokep->y = fingers[i].y; atp_advance_stroke_state(sc, strokep, &movement); } } return (movement); } /* * Update strokes by matching against current pressure-spans. * Return true if any movement is detected. */ static boolean_t fg_update_strokes(struct atp_softc *sc, fg_pspan *pspans_x, u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans) { atp_stroke_t *strokep; atp_stroke_t *strokep_next; boolean_t movement = false; u_int repeat_count = 0; u_int i; u_int j; /* Reset X and Y components of all strokes as unmatched. */ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { strokep->components[X].matched = false; strokep->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); fg_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans, (((repeat_count != 0) && ((n_xpspans < n_ypspans))) ? repeat_count : 0)); fg_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. */ TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) { if (strokep->components[X].matched && strokep->components[Y].matched) { strokep->matched = true; strokep->instantaneous_dx = strokep->components[X].delta_mickeys; strokep->instantaneous_dy = strokep->components[Y].delta_mickeys; atp_advance_stroke_state(sc, strokep, &movement); } else { /* * At least one component of this stroke * didn't match against current pspans; * terminate it. */ atp_terminate_stroke(sc, strokep); } } /* 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. */ fg_add_stroke(sc, &pspans_x[0], &pspans_y[0]); else fg_add_new_strokes(sc, pspans_x, n_xpspans, pspans_y, n_ypspans); } #ifdef USB_DEBUG if (atp_debug >= ATP_LLEVEL_INFO) { TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { printf(" %s%clc:%u,dm:%d,cum:%d,max:%d,%c" ",%clc:%u,dm:%d,cum:%d,max:%d,%c", (strokep->flags & ATSF_ZOMBIE) ? "zomb:" : "", (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<', strokep->components[X].loc, strokep->components[X].delta_mickeys, strokep->components[X].cum_pressure, strokep->components[X].max_cum_pressure, (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>', (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<', strokep->components[Y].loc, strokep->components[Y].delta_mickeys, strokep->components[Y].cum_pressure, strokep->components[Y].max_cum_pressure, (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>'); } if (TAILQ_FIRST(&sc->sc_stroke_used) != NULL) printf("\n"); } #endif /* USB_DEBUG */ return (movement); } /* * Update strokes by matching against current pressure-spans. * Return true if any movement is detected. */ static boolean_t wsp_update_strokes(struct atp_softc *sc, wsp_finger_t *fingers, u_int n_fingers) { boolean_t movement = false; atp_stroke_t *strokep_next; atp_stroke_t *strokep; u_int i; if (sc->sc_n_strokes > 0) { movement = wsp_match_strokes_against_fingers( sc, fingers, n_fingers); /* handle zombie strokes */ TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) { if (strokep->matched) continue; atp_terminate_stroke(sc, strokep); } } /* initialize unmatched fingers as strokes */ for (i = 0; i != n_fingers; i++) { if (fingers[i].matched) continue; wsp_add_stroke(sc, fingers + i); } return (movement); } /* Initialize a stroke using a pressure-span. */ static void fg_add_stroke(struct atp_softc *sc, const fg_pspan *pspan_x, const fg_pspan *pspan_y) { atp_stroke_t *strokep; strokep = atp_alloc_stroke(sc); if (strokep == NULL) return; /* * 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'. */ strokep->type = ATP_STROKE_TOUCH; strokep->matched = false; microtime(&strokep->ctime); strokep->age = 1; /* number of interrupts */ strokep->x = pspan_x->loc; strokep->y = pspan_y->loc; strokep->components[X].loc = pspan_x->loc; strokep->components[X].cum_pressure = pspan_x->cum; strokep->components[X].max_cum_pressure = pspan_x->cum; strokep->components[X].matched = true; strokep->components[Y].loc = pspan_y->loc; strokep->components[Y].cum_pressure = pspan_y->cum; strokep->components[Y].max_cum_pressure = pspan_y->cum; strokep->components[Y].matched = true; 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", strokep->components[X].loc, strokep->components[Y].loc, (u_int)strokep->ctime.tv_sec, (unsigned long int)strokep->ctime.tv_usec); } static void fg_add_new_strokes(struct atp_softc *sc, fg_pspan *pspans_x, u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans) { fg_pspan spans[2][FG_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++) fg_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++) { fg_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--; } } } } } /* Initialize a stroke from an unmatched finger. */ static void wsp_add_stroke(struct atp_softc *sc, const wsp_finger_t *fingerp) { atp_stroke_t *strokep; strokep = atp_alloc_stroke(sc); if (strokep == NULL) return; /* * 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'. */ strokep->type = ATP_STROKE_TOUCH; strokep->matched = true; microtime(&strokep->ctime); strokep->age = 1; /* number of interrupts */ strokep->x = fingerp->x; strokep->y = fingerp->y; /* Reset double-tap-n-drag if we have more than one strokes. */ if (sc->sc_n_strokes > 1) sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; DPRINTFN(ATP_LLEVEL_INFO, "[%d,%d]\n", strokep->x, strokep->y); } static void atp_advance_stroke_state(struct atp_softc *sc, atp_stroke_t *strokep, boolean_t *movementp) { /* Revitalize stroke if it had previously been marked as a zombie. */ if (strokep->flags & ATSF_ZOMBIE) strokep->flags &= ~ATSF_ZOMBIE; strokep->age++; if (strokep->age <= atp_stroke_maturity_threshold) { /* Avoid noise from immature strokes. */ strokep->instantaneous_dx = 0; strokep->instantaneous_dy = 0; } if (atp_compute_stroke_movement(strokep)) *movementp = true; if (strokep->type != ATP_STROKE_TOUCH) return; /* Convert touch strokes to slides upon detecting movement or age. */ if ((abs(strokep->cum_movement_x) > atp_slide_min_movement) || (abs(strokep->cum_movement_y) > atp_slide_min_movement)) atp_convert_to_slide(sc, strokep); else { /* Compute the stroke's age. */ struct timeval tdiff; getmicrotime(&tdiff); if (timevalcmp(&tdiff, &strokep->ctime, >)) { timevalsub(&tdiff, &strokep->ctime); 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, strokep); } } } static boolean_t atp_stroke_has_small_movement(const atp_stroke_t *strokep) { return (((u_int)abs(strokep->instantaneous_dx) <= atp_small_movement_threshold) && ((u_int)abs(strokep->instantaneous_dy) <= atp_small_movement_threshold)); } /* * Accumulate instantaneous changes into the stroke's 'pending' bucket; if * the aggregate exceeds the small_movement_threshold, then retain * instantaneous changes for later. */ static void atp_update_pending_mickeys(atp_stroke_t *strokep) { /* accumulate instantaneous movement */ strokep->pending_dx += strokep->instantaneous_dx; strokep->pending_dy += strokep->instantaneous_dy; #define UPDATE_INSTANTANEOUS_AND_PENDING(I, P) \ if (abs((P)) <= atp_small_movement_threshold) \ (I) = 0; /* clobber small movement */ \ else { \ if ((I) > 0) { \ /* \ * Round up instantaneous movement to the nearest \ * ceiling. This helps preserve small mickey \ * movements from being lost in following scaling \ * operation. \ */ \ (I) = (((I) + (atp_mickeys_scale_factor - 1)) / \ atp_mickeys_scale_factor) * \ atp_mickeys_scale_factor; \ \ /* \ * Deduct the rounded mickeys from pending mickeys. \ * Note: we multiply by 2 to offset the previous \ * accumulation of instantaneous movement into \ * pending. \ */ \ (P) -= ((I) << 1); \ \ /* truncate pending to 0 if it becomes negative. */ \ (P) = imax((P), 0); \ } else { \ /* \ * Round down instantaneous movement to the nearest \ * ceiling. This helps preserve small mickey \ * movements from being lost in following scaling \ * operation. \ */ \ (I) = (((I) - (atp_mickeys_scale_factor - 1)) / \ atp_mickeys_scale_factor) * \ atp_mickeys_scale_factor; \ \ /* \ * Deduct the rounded mickeys from pending mickeys. \ * Note: we multiply by 2 to offset the previous \ * accumulation of instantaneous movement into \ * pending. \ */ \ (P) -= ((I) << 1); \ \ /* truncate pending to 0 if it becomes positive. */ \ (P) = imin((P), 0); \ } \ } UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dx, strokep->pending_dx); UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dy, strokep->pending_dy); } /* * Compute a smoothened value for the stroke's movement from * instantaneous changes in the X and Y components. */ static boolean_t atp_compute_stroke_movement(atp_stroke_t *strokep) { /* * 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(strokep)) atp_update_pending_mickeys(strokep); else { /* large movement */ /* clear away any pending mickeys if there are large movements*/ strokep->pending_dx = 0; strokep->pending_dy = 0; } /* scale movement */ strokep->movement_dx = (strokep->instantaneous_dx) / (int)atp_mickeys_scale_factor; strokep->movement_dy = (strokep->instantaneous_dy) / (int)atp_mickeys_scale_factor; if ((abs(strokep->instantaneous_dx) >= ATP_FAST_MOVEMENT_TRESHOLD) || (abs(strokep->instantaneous_dy) >= ATP_FAST_MOVEMENT_TRESHOLD)) { strokep->movement_dx <<= 1; strokep->movement_dy <<= 1; } strokep->cum_movement_x += strokep->movement_dx; strokep->cum_movement_y += strokep->movement_dy; return ((strokep->movement_dx != 0) || (strokep->movement_dy != 0)); } /* * Terminate a stroke. Aside from immature strokes, a slide or touch is * retained as a zombies so as to reap all their termination siblings * together; this helps establish the number of fingers involved at the * end of a multi-touch gesture. */ static void atp_terminate_stroke(struct atp_softc *sc, atp_stroke_t *strokep) { if (strokep->flags & ATSF_ZOMBIE) return; /* Drop immature strokes rightaway. */ if (strokep->age <= atp_stroke_maturity_threshold) { atp_free_stroke(sc, strokep); return; } strokep->flags |= ATSF_ZOMBIE; sc->sc_state |= ATP_ZOMBIES_EXIST; callout_reset(&sc->sc_callout, ATP_ZOMBIE_STROKE_REAP_INTERVAL, atp_reap_sibling_zombies, sc); /* * Reset the double-click-n-drag at the termination of any * slide stroke. */ if (strokep->type == ATP_STROKE_SLIDE) sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG; } static boolean_t atp_is_horizontal_scroll(const atp_stroke_t *strokep) { if (abs(strokep->cum_movement_x) < atp_slide_min_movement) return (false); if (strokep->cum_movement_y == 0) return (true); return (abs(strokep->cum_movement_x / strokep->cum_movement_y) >= 4); } static boolean_t atp_is_vertical_scroll(const atp_stroke_t *strokep) { if (abs(strokep->cum_movement_y) < atp_slide_min_movement) return (false); if (strokep->cum_movement_x == 0) return (true); return (abs(strokep->cum_movement_y / strokep->cum_movement_x) >= 4); } static void atp_reap_sibling_zombies(void *arg) { struct atp_softc *sc = (struct atp_softc *)arg; u_int8_t n_touches_reaped = 0; u_int8_t n_slides_reaped = 0; u_int8_t n_horizontal_scrolls = 0; u_int8_t n_vertical_scrolls = 0; int horizontal_scroll = 0; int vertical_scroll = 0; atp_stroke_t *strokep; atp_stroke_t *strokep_next; DPRINTFN(ATP_LLEVEL_INFO, "\n"); TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) { if ((strokep->flags & ATSF_ZOMBIE) == 0) continue; if (strokep->type == ATP_STROKE_TOUCH) { n_touches_reaped++; } else { n_slides_reaped++; if (atp_is_horizontal_scroll(strokep)) { n_horizontal_scrolls++; horizontal_scroll += strokep->cum_movement_x; } else if (atp_is_vertical_scroll(strokep)) { n_vertical_scrolls++; vertical_scroll += strokep->cum_movement_y; } } atp_free_stroke(sc, strokep); } DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n", n_touches_reaped + n_slides_reaped); sc->sc_state &= ~ATP_ZOMBIES_EXIST; /* No further processing necessary if physical button is depressed. */ if (sc->sc_ibtn != 0) return; if ((n_touches_reaped == 0) && (n_slides_reaped == 0)) return; /* Add a pair of virtual button events (button-down and button-up) if * the physical button isn't pressed. */ if (n_touches_reaped != 0) { if (n_touches_reaped < atp_tap_minimum) return; switch (n_touches_reaped) { case 1: atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON1DOWN); microtime(&sc->sc_touch_reap_time); /* remember this time */ break; case 2: atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON3DOWN); break; case 3: atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON2DOWN); break; default: /* we handle taps of only up to 3 fingers */ return; } atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */ } else if ((n_slides_reaped == 2) && (n_horizontal_scrolls == 2)) { if (horizontal_scroll < 0) atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON4DOWN); else atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON5DOWN); atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */ } } /* Switch a given touch stroke to being a slide. */ static void atp_convert_to_slide(struct atp_softc *sc, atp_stroke_t *strokep) { strokep->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(&strokep->ctime, &sc->sc_touch_reap_time, >)) { struct timeval delta; struct timeval window = { atp_double_tap_threshold / 1000000, atp_double_tap_threshold % 1000000 }; delta = strokep->ctime; timevalsub(&delta, &sc->sc_touch_reap_time); if (timevalcmp(&delta, &window, <=)) sc->sc_state |= ATP_DOUBLE_TAP_DRAG; } } 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_add_to_queue(struct atp_softc *sc, int dx, int dy, int dz, 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); dz = imin(dz, 126); dz = imax(dz, -128); 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] = dz >> 1; buf[6] = dz - (dz >> 1); buf[7] = (((~buttons_in) >> 3) & MOUSE_SYS_EXTBUTTONS); } usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf, sc->sc_mode.packetsize, 1); } 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) return (ENXIO); /* * Note: for some reason, the check * (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) doesn't hold true * for wellspring trackpads, so we've removed it from the common path. */ if ((usbd_lookup_id_by_uaa(fg_devs, sizeof(fg_devs), uaa)) == 0) return ((uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) ? 0 : ENXIO); if ((usbd_lookup_id_by_uaa(wsp_devs, sizeof(wsp_devs), uaa)) == 0) if (uaa->info.bIfaceIndex == WELLSPRING_INTERFACE_INDEX) return (0); return (ENXIO); } 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; void *descriptor_ptr = NULL; uint16_t descriptor_len; unsigned long di; DPRINTFN(ATP_LLEVEL_INFO, "sc=%p\n", sc); sc->sc_dev = dev; sc->sc_usb_device = uaa->device; /* Get HID descriptor */ if (usbd_req_get_hid_desc(uaa->device, NULL, &descriptor_ptr, &descriptor_len, M_TEMP, uaa->info.bIfaceIndex) != USB_ERR_NORMAL_COMPLETION) return (ENXIO); /* Get HID report descriptor length */ sc->sc_expected_sensor_data_len = hid_report_size(descriptor_ptr, descriptor_len, hid_input, NULL); free(descriptor_ptr, M_TEMP); if ((sc->sc_expected_sensor_data_len <= 0) || (sc->sc_expected_sensor_data_len > ATP_SENSOR_DATA_BUF_MAX)) { DPRINTF("atp_attach: datalength invalid or too large: %d\n", sc->sc_expected_sensor_data_len); return (ENXIO); } /* * 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. */ if ((err = atp_set_device_mode(sc, 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); di = USB_GET_DRIVER_INFO(uaa); sc->sc_family = DECODE_FAMILY_FROM_DRIVER_INFO(di); switch(sc->sc_family) { case TRACKPAD_FAMILY_FOUNTAIN_GEYSER: sc->sc_params = &fg_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)]; sc->sensor_data_interpreter = fg_interpret_sensor_data; break; case TRACKPAD_FAMILY_WELLSPRING: sc->sc_params = &wsp_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)]; sc->sensor_data_interpreter = wsp_interpret_sensor_data; break; default: goto detach; } err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->sc_xfer, atp_xfer_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_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.packetsize = MOUSE_MSC_PACKETSIZE; sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK; sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC; sc->sc_mode.accelfactor = 0; sc->sc_mode.level = 0; sc->sc_state = 0; sc->sc_ibtn = 0; callout_init_mtx(&sc->sc_callout, &sc->sc_mutex, 0); return (0); detach: atp_detach(dev); return (ENOMEM); } static int atp_detach(device_t dev) { struct atp_softc *sc; sc = device_get_softc(dev); atp_set_device_mode(sc, HID_MODE); mtx_lock(&sc->sc_mutex); callout_drain(&sc->sc_callout); if (sc->sc_state & ATP_ENABLED) 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); struct usb_page_cache *pc; int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, sc->sc_sensor_data, len); if (len < sc->sc_expected_sensor_data_len) { /* make sure we don't process old data */ memset(sc->sc_sensor_data + len, 0, sc->sc_expected_sensor_data_len - len); } sc->sc_status.flags &= ~(MOUSE_STDBUTTONSCHANGED | MOUSE_POSCHANGED); sc->sc_status.obutton = sc->sc_status.button; (sc->sensor_data_interpreter)(sc, len); 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")); } if (sc->sc_status.flags & (MOUSE_POSCHANGED | MOUSE_STDBUTTONSCHANGED)) { atp_stroke_t *strokep; u_int8_t n_movements = 0; int dx = 0; int dy = 0; int dz = 0; TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { if (strokep->flags & ATSF_ZOMBIE) continue; dx += strokep->movement_dx; dy += strokep->movement_dy; if (strokep->movement_dx || strokep->movement_dy) n_movements++; } /* average movement if multiple strokes record motion.*/ if (n_movements > 1) { dx /= (int)n_movements; dy /= (int)n_movements; } /* detect multi-finger vertical scrolls */ if (n_movements >= 2) { boolean_t all_vertical_scrolls = true; TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) { if (strokep->flags & ATSF_ZOMBIE) continue; if (!atp_is_vertical_scroll(strokep)) all_vertical_scrolls = false; } if (all_vertical_scrolls) { dz = dy; dy = dx = 0; } } sc->sc_status.dx += dx; sc->sc_status.dy += dy; sc->sc_status.dz += dz; atp_add_to_queue(sc, dx, -dy, -dz, sc->sc_status.button); } 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_expected_sensor_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; } } 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) { struct atp_softc *sc = usb_fifo_softc(fifo); /* check for duplicate open, should not happen */ if (sc->sc_fflags & fflags) return (EBUSY); /* check for first open */ if (sc->sc_fflags == 0) { int rc; if ((rc = atp_enable(sc)) != 0) return (rc); } if (fflags & FREAD) { if (usb_fifo_alloc_buffer(fifo, ATP_FIFO_BUF_SIZE, ATP_FIFO_QUEUE_MAXLEN)) { return (ENOMEM); } } sc->sc_fflags |= (fflags & (FREAD | FWRITE)); return (0); } static void atp_close(struct usb_fifo *fifo, int fflags) { struct atp_softc *sc = usb_fifo_softc(fifo); if (fflags & FREAD) usb_fifo_free_buffer(fifo); sc->sc_fflags &= ~(fflags & (FREAD | FWRITE)); if (sc->sc_fflags == 0) { atp_disable(sc); } } static 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; break; } 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; break; } 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; break; } mtx_unlock(&sc->sc_mutex); return (error); } static int atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS) { int error; u_int tmp; tmp = atp_mickeys_scale_factor; error = sysctl_handle_int(oidp, &tmp, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (tmp == atp_mickeys_scale_factor) return (0); /* no change */ if ((tmp == 0) || (tmp > (10 * ATP_SCALE_FACTOR))) return (EINVAL); atp_mickeys_scale_factor = tmp; DPRINTFN(ATP_LLEVEL_INFO, "%s: resetting mickeys_scale_factor to %u\n", ATP_DRIVER_NAME, tmp); return (0); } static devclass_t atp_devclass; static device_method_t atp_methods[] = { DEVMETHOD(device_probe, atp_probe), DEVMETHOD(device_attach, atp_attach), DEVMETHOD(device_detach, atp_detach), DEVMETHOD_END }; static driver_t atp_driver = { .name = ATP_DRIVER_NAME, .methods = atp_methods, .size = sizeof(struct atp_softc) }; DRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, 0); MODULE_DEPEND(atp, usb, 1, 1, 1); MODULE_VERSION(atp, 1);