libinput/src/quirks.c
Peter Hutterer a7e4cbc212 quirks: allow overriding of AttrEventCode and AttrInputProp
This switches the quirk from AttrEventCodeEnable/Disable to just
AttrEventCode with a +/- prefix for each entry.
This switches the quirk from AttrInputPropEnable/Disable to just
AttrInputProp with a +/- prefix for each entry.

Previously, both event codes and input props would only apply the
last-matching section entry for a device. Furthermore, an earlier Disable entry
would take precedence over a later Enable entry. For example, a set of
sections with these lines *should* enable left, right and middle:

  [first]
  AttrEventCodeEnable=BTN_LEFT;BTN_RIGHT;BTN_MIDDLE

  [second]
  AttrEventCodeDisable=BTN_RIGHT

  [third]
  AttrEventCodeEnable=BTN_LEFT;BTN_RIGHT;

Alas: the first line was effectively ignored (quirks only returned the
last-matching one, i.e. the one from "third"). And due to implementation
details in evdev.c, the Disable attribute was processed after Enable,
i.e. the device was enabled for left + right and then disabled for
right. As a result, the device only had BTN_LEFT enabled.

Fix this by changing the attribute to carry both enable/disable
information and merging the commands together.

Internally, all quirks matching a device are simply ref'd into an array
in the struct quirks. The applied value is simply the last entry in the
array corresponding to our quirk.

For AttrEventCode and AttrInputProp instead do this:
- switch them to a tuple with the code as first entry and a boolean
  enable/disable as second entry
- if the struct quirk already has an entry for either, append the more
  recent one to the existing entry (instead of creating a new entry in
  the array). This way we have all entries that match and in-order of
  precedence - i.e. we can process them left-to-right to end up
  with the right state.

Fixes: https://gitlab.freedesktop.org/libinput/libinput/-/issues/821

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2022-11-28 08:25:41 +10:00

1772 lines
41 KiB
C

/*
* Copyright © 2018 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
/* This has the hallmarks of a library to make it re-usable from the tests
* and from the list-quirks tool. It doesn't have all of the features from a
* library you'd expect though
*/
#undef NDEBUG /* You don't get to disable asserts here */
#include <assert.h>
#include <stdlib.h>
#include <libudev.h>
#include <dirent.h>
#include <fnmatch.h>
#include <libgen.h>
#ifdef __FreeBSD__
#include <kenv.h>
#endif
#include "libinput-versionsort.h"
#include "libinput-util.h"
#include "quirks.h"
/* Custom logging so we can have detailed output for the tool but minimal
* logging for libinput itself. */
#define qlog_debug(ctx_, ...) quirk_log_msg((ctx_), QLOG_NOISE, __VA_ARGS__)
#define qlog_info(ctx_, ...) quirk_log_msg((ctx_), QLOG_INFO, __VA_ARGS__)
#define qlog_error(ctx_, ...) quirk_log_msg((ctx_), QLOG_ERROR, __VA_ARGS__)
#define qlog_parser(ctx_, ...) quirk_log_msg((ctx_), QLOG_PARSER_ERROR, __VA_ARGS__)
enum property_type {
PT_UINT,
PT_INT,
PT_STRING,
PT_BOOL,
PT_DIMENSION,
PT_RANGE,
PT_DOUBLE,
PT_TUPLES,
PT_UINT_ARRAY,
};
struct quirk_array {
union {
uint32_t u[32];
} data;
size_t nelements;
};
/**
* Generic value holder for the property types we support. The type
* identifies which value in the union is defined and we expect callers to
* already know which type yields which value.
*/
struct property {
size_t refcount;
struct list link; /* struct sections.properties */
enum quirk id;
enum property_type type;
union {
bool b;
uint32_t u;
int32_t i;
char *s;
double d;
struct quirk_dimensions dim;
struct quirk_range range;
struct quirk_tuples tuples;
struct quirk_array array;
} value;
};
enum match_flags {
M_NAME = bit(0),
M_BUS = bit(1),
M_VID = bit(2),
M_PID = bit(3),
M_DMI = bit(4),
M_UDEV_TYPE = bit(5),
M_DT = bit(6),
M_VERSION = bit(7),
M_LAST = M_VERSION,
};
enum bustype {
BT_UNKNOWN,
BT_USB,
BT_BLUETOOTH,
BT_PS2,
BT_RMI,
BT_I2C,
BT_SPI,
};
enum udev_type {
UDEV_MOUSE = bit(1),
UDEV_POINTINGSTICK = bit(2),
UDEV_TOUCHPAD = bit(3),
UDEV_TABLET = bit(4),
UDEV_TABLET_PAD = bit(5),
UDEV_JOYSTICK = bit(6),
UDEV_KEYBOARD = bit(7),
};
/**
* Contains the combined set of matches for one section or the values for
* one device.
*
* bits defines which fields are set, the rest is zero.
*/
struct match {
uint32_t bits;
char *name;
enum bustype bus;
uint32_t vendor;
uint32_t product;
uint32_t version;
char *dmi; /* dmi modalias with preceding "dmi:" */
/* We can have more than one type set, so this is a bitfield */
uint32_t udev_type;
char *dt; /* device tree compatible (first) string */
};
/**
* Represents one section in the .quirks file.
*/
struct section {
struct list link;
bool has_match; /* to check for empty sections */
bool has_property; /* to check for empty sections */
char *name; /* the [Section Name] */
struct match match;
struct list properties;
};
/**
* The struct returned to the caller. It contains the
* properties for a given device.
*/
struct quirks {
size_t refcount;
struct list link; /* struct quirks_context.quirks */
/* These are not ref'd, just a collection of pointers */
struct property **properties;
size_t nproperties;
/* Special properties for AttrEventCode and AttrInputCode, these are
* owned by us, not the section */
struct list floating_properties;
};
/**
* Quirk matching context, initialized once with quirks_init_subsystem()
*/
struct quirks_context {
size_t refcount;
libinput_log_handler log_handler;
enum quirks_log_type log_type;
struct libinput *libinput; /* for logging */
char *dmi;
char *dt;
struct list sections;
/* list of quirks handed to libinput, just for bookkeeping */
struct list quirks;
};
LIBINPUT_ATTRIBUTE_PRINTF(3, 0)
static inline void
quirk_log_msg_va(struct quirks_context *ctx,
enum quirks_log_priorities priority,
const char *format,
va_list args)
{
switch (priority) {
/* We don't use this if we're logging through libinput */
default:
case QLOG_NOISE:
case QLOG_PARSER_ERROR:
if (ctx->log_type == QLOG_LIBINPUT_LOGGING)
return;
break;
case QLOG_DEBUG: /* These map straight to libinput priorities */
case QLOG_INFO:
case QLOG_ERROR:
break;
}
ctx->log_handler(ctx->libinput,
(enum libinput_log_priority)priority,
format,
args);
}
LIBINPUT_ATTRIBUTE_PRINTF(3, 4)
static inline void
quirk_log_msg(struct quirks_context *ctx,
enum quirks_log_priorities priority,
const char *format,
...)
{
va_list args;
va_start(args, format);
quirk_log_msg_va(ctx, priority, format, args);
va_end(args);
}
const char *
quirk_get_name(enum quirk q)
{
switch(q) {
case QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD: return "ModelALPSSerialTouchpad";
case QUIRK_MODEL_APPLE_TOUCHPAD: return "ModelAppleTouchpad";
case QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON: return "ModelAppleTouchpadOneButton";
case QUIRK_MODEL_BOUNCING_KEYS: return "ModelBouncingKeys";
case QUIRK_MODEL_CHROMEBOOK: return "ModelChromebook";
case QUIRK_MODEL_CLEVO_W740SU: return "ModelClevoW740SU";
case QUIRK_MODEL_DELL_CANVAS_TOTEM: return "ModelDellCanvasTotem";
case QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD: return "ModelHPPavilionDM4Touchpad";
case QUIRK_MODEL_HP_ZBOOK_STUDIO_G3: return "ModelHPZBookStudioG3";
case QUIRK_MODEL_INVERT_HORIZONTAL_SCROLLING: return "ModelInvertHorizontalScrolling";
case QUIRK_MODEL_LENOVO_SCROLLPOINT: return "ModelLenovoScrollPoint";
case QUIRK_MODEL_LENOVO_T450_TOUCHPAD: return "ModelLenovoT450Touchpad";
case QUIRK_MODEL_LENOVO_X1GEN6_TOUCHPAD: return "ModelLenovoX1Gen6Touchpad";
case QUIRK_MODEL_LENOVO_X230: return "ModelLenovoX230";
case QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD: return "ModelSynapticsSerialTouchpad";
case QUIRK_MODEL_SYSTEM76_BONOBO: return "ModelSystem76Bonobo";
case QUIRK_MODEL_SYSTEM76_GALAGO: return "ModelSystem76Galago";
case QUIRK_MODEL_SYSTEM76_KUDU: return "ModelSystem76Kudu";
case QUIRK_MODEL_TABLET_MODE_NO_SUSPEND: return "ModelTabletModeNoSuspend";
case QUIRK_MODEL_TABLET_MODE_SWITCH_UNRELIABLE: return "ModelTabletModeSwitchUnreliable";
case QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER: return "ModelTouchpadVisibleMarker";
case QUIRK_MODEL_TRACKBALL: return "ModelTrackball";
case QUIRK_MODEL_WACOM_TOUCHPAD: return "ModelWacomTouchpad";
case QUIRK_ATTR_SIZE_HINT: return "AttrSizeHint";
case QUIRK_ATTR_TOUCH_SIZE_RANGE: return "AttrTouchSizeRange";
case QUIRK_ATTR_PALM_SIZE_THRESHOLD: return "AttrPalmSizeThreshold";
case QUIRK_ATTR_LID_SWITCH_RELIABILITY: return "AttrLidSwitchReliability";
case QUIRK_ATTR_KEYBOARD_INTEGRATION: return "AttrKeyboardIntegration";
case QUIRK_ATTR_TRACKPOINT_INTEGRATION: return "AttrPointingStickIntegration";
case QUIRK_ATTR_TPKBCOMBO_LAYOUT: return "AttrTPKComboLayout";
case QUIRK_ATTR_PRESSURE_RANGE: return "AttrPressureRange";
case QUIRK_ATTR_PALM_PRESSURE_THRESHOLD: return "AttrPalmPressureThreshold";
case QUIRK_ATTR_RESOLUTION_HINT: return "AttrResolutionHint";
case QUIRK_ATTR_TRACKPOINT_MULTIPLIER: return "AttrTrackpointMultiplier";
case QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD: return "AttrThumbPressureThreshold";
case QUIRK_ATTR_USE_VELOCITY_AVERAGING: return "AttrUseVelocityAveraging";
case QUIRK_ATTR_TABLET_SMOOTHING: return "AttrTabletSmoothing";
case QUIRK_ATTR_THUMB_SIZE_THRESHOLD: return "AttrThumbSizeThreshold";
case QUIRK_ATTR_MSC_TIMESTAMP: return "AttrMscTimestamp";
case QUIRK_ATTR_EVENT_CODE: return "AttrEventCode";
case QUIRK_ATTR_INPUT_PROP: return "AttrInputProp";
default:
abort();
}
}
static inline const char *
matchflagname(enum match_flags f)
{
switch(f) {
case M_NAME: return "MatchName"; break;
case M_BUS: return "MatchBus"; break;
case M_VID: return "MatchVendor"; break;
case M_PID: return "MatchProduct"; break;
case M_VERSION: return "MatchVersion"; break;
case M_DMI: return "MatchDMIModalias"; break;
case M_UDEV_TYPE: return "MatchUdevType"; break;
case M_DT: return "MatchDeviceTree"; break;
default:
abort();
}
}
static inline struct property *
property_new(void)
{
struct property *p;
p = zalloc(sizeof *p);
p->refcount = 1;
list_init(&p->link);
return p;
}
static inline struct property *
property_ref(struct property *p)
{
assert(p->refcount > 0);
p->refcount++;
return p;
}
static inline struct property *
property_unref(struct property *p)
{
/* Note: we don't cleanup here, that is a separate call so we
can abort if we haven't cleaned up correctly. */
assert(p->refcount > 0);
p->refcount--;
return NULL;
}
/* Separate call so we can verify that the caller unrefs the property
* before shutting down the subsystem.
*/
static inline void
property_cleanup(struct property *p)
{
/* If we get here, the quirks must've been removed already */
property_unref(p);
assert(p->refcount == 0);
list_remove(&p->link);
if (p->type == PT_STRING)
free(p->value.s);
free(p);
}
/**
* Return the system DMI info in modalias format.
*/
#ifdef __linux__
static inline char *
init_dmi_linux(void)
{
struct udev *udev;
struct udev_device *udev_device;
const char *modalias = NULL;
char *copy = NULL;
const char *syspath = "/sys/devices/virtual/dmi/id";
udev = udev_new();
if (!udev)
return NULL;
udev_device = udev_device_new_from_syspath(udev, syspath);
if (udev_device)
modalias = udev_device_get_property_value(udev_device,
"MODALIAS");
/* Not sure whether this could ever really fail, if so we should
* open the sysfs file directly. But then udev wouldn't have failed,
* so... */
if (!modalias)
modalias = "dmi:*";
copy = safe_strdup(modalias);
udev_device_unref(udev_device);
udev_unref(udev);
return copy;
}
#endif
#ifdef __FreeBSD__
static inline char *
init_dmi_freebsd(void)
{
#define LEN (KENV_MVALLEN + 1)
char *modalias;
char bios_vendor[LEN], bios_version[LEN], bios_date[LEN];
char sys_vendor[LEN], product_name[LEN], product_version[LEN];
char board_vendor[LEN], board_name[LEN], board_version[LEN];
char chassis_vendor[LEN], chassis_type[LEN], chassis_version[LEN];
int chassis_type_num = 0x2;
kenv(KENV_GET, "smbios.bios.vendor", bios_vendor, LEN);
kenv(KENV_GET, "smbios.bios.version", bios_version, LEN);
kenv(KENV_GET, "smbios.bios.reldate", bios_date, LEN);
kenv(KENV_GET, "smbios.system.maker", sys_vendor, LEN);
kenv(KENV_GET, "smbios.system.product", product_name, LEN);
kenv(KENV_GET, "smbios.system.version", product_version, LEN);
kenv(KENV_GET, "smbios.planar.maker", board_vendor, LEN);
kenv(KENV_GET, "smbios.planar.product", board_name, LEN);
kenv(KENV_GET, "smbios.planar.version", board_version, LEN);
kenv(KENV_GET, "smbios.chassis.vendor", chassis_vendor, LEN);
kenv(KENV_GET, "smbios.chassis.type", chassis_type, LEN);
kenv(KENV_GET, "smbios.chassis.version", chassis_version, LEN);
#undef LEN
if (strcmp(chassis_type, "Desktop") == 0)
chassis_type_num = 0x3;
else if (strcmp(chassis_type, "Portable") == 0)
chassis_type_num = 0x8;
else if (strcmp(chassis_type, "Laptop") == 0)
chassis_type_num = 0x9;
else if (strcmp(chassis_type, "Notebook") == 0)
chassis_type_num = 0xA;
else if (strcmp(chassis_type, "Tablet") == 0)
chassis_type_num = 0x1E;
else if (strcmp(chassis_type, "Convertible") == 0)
chassis_type_num = 0x1F;
else if (strcmp(chassis_type, "Detachable") == 0)
chassis_type_num = 0x20;
xasprintf(&modalias,
"dmi:bvn%s:bvr%s:bd%s:svn%s:pn%s:pvr%s:rvn%s:rn%s:rvr%s:cvn%s:ct%d:cvr%s:",
bios_vendor, bios_version, bios_date, sys_vendor, product_name,
product_version, board_vendor, board_name, board_version, chassis_vendor,
chassis_type_num, chassis_version);
return modalias;
}
#endif
static inline char *
init_dmi(void)
{
if (getenv("LIBINPUT_RUNNING_TEST_SUITE"))
return safe_strdup("dmi:");
#if defined(__linux__)
return init_dmi_linux();
#elif defined(__FreeBSD__)
return init_dmi_freebsd();
#else
return NULL;
#endif
}
/**
* Return the dt compatible string
*/
static inline char *
init_dt(void)
{
char compatible[1024];
char *copy = NULL;
const char *syspath = "/sys/firmware/devicetree/base/compatible";
FILE *fp;
if (getenv("LIBINPUT_RUNNING_TEST_SUITE"))
return safe_strdup("");
fp = fopen(syspath, "r");
if (!fp)
return NULL;
/* devicetree/base/compatible has multiple null-terminated entries
but we only care about the first one here, so strdup is enough */
if (fgets(compatible, sizeof(compatible), fp)) {
copy = safe_strdup(compatible);
}
fclose(fp);
return copy;
}
static inline struct section *
section_new(const char *path, const char *name)
{
struct section *s = zalloc(sizeof(*s));
char *path_dup = safe_strdup(path);
xasprintf(&s->name, "%s (%s)", name, basename(path_dup));
free(path_dup);
list_init(&s->link);
list_init(&s->properties);
return s;
}
static inline void
section_destroy(struct section *s)
{
struct property *p;
free(s->name);
free(s->match.name);
free(s->match.dmi);
free(s->match.dt);
list_for_each_safe(p, &s->properties, link)
property_cleanup(p);
assert(list_empty(&s->properties));
list_remove(&s->link);
free(s);
}
static inline bool
parse_hex(const char *value, unsigned int *parsed)
{
return strneq(value, "0x", 2) &&
safe_atou_base(value, parsed, 16) &&
strspn(value, "0123456789xABCDEF") == strlen(value) &&
*parsed <= 0xFFFF;
}
/**
* Parse a MatchFooBar=banana line.
*
* @param section The section struct to be filled in
* @param key The MatchFooBar part of the line
* @param value The banana part of the line.
*
* @return true on success, false otherwise.
*/
static bool
parse_match(struct quirks_context *ctx,
struct section *s,
const char *key,
const char *value)
{
int rc = false;
#define check_set_bit(s_, bit_) { \
if ((s_)->match.bits & (bit_)) goto out; \
(s_)->match.bits |= (bit_); \
}
assert(strlen(value) >= 1);
if (streq(key, "MatchName")) {
check_set_bit(s, M_NAME);
s->match.name = safe_strdup(value);
} else if (streq(key, "MatchBus")) {
check_set_bit(s, M_BUS);
if (streq(value, "usb"))
s->match.bus = BT_USB;
else if (streq(value, "bluetooth"))
s->match.bus = BT_BLUETOOTH;
else if (streq(value, "ps2"))
s->match.bus = BT_PS2;
else if (streq(value, "rmi"))
s->match.bus = BT_RMI;
else if (streq(value, "i2c"))
s->match.bus = BT_I2C;
else if (streq(value, "spi"))
s->match.bus = BT_SPI;
else
goto out;
} else if (streq(key, "MatchVendor")) {
unsigned int vendor;
check_set_bit(s, M_VID);
if (!parse_hex(value, &vendor))
goto out;
s->match.vendor = vendor;
} else if (streq(key, "MatchProduct")) {
unsigned int product;
check_set_bit(s, M_PID);
if (!parse_hex(value, &product))
goto out;
s->match.product = product;
} else if (streq(key, "MatchVersion")) {
unsigned int version;
check_set_bit(s, M_VERSION);
if (!parse_hex(value, &version))
goto out;
s->match.version = version;
} else if (streq(key, "MatchDMIModalias")) {
check_set_bit(s, M_DMI);
if (!strneq(value, "dmi:", 4)) {
qlog_parser(ctx,
"%s: MatchDMIModalias must start with 'dmi:'\n",
s->name);
goto out;
}
s->match.dmi = safe_strdup(value);
} else if (streq(key, "MatchUdevType")) {
check_set_bit(s, M_UDEV_TYPE);
if (streq(value, "touchpad"))
s->match.udev_type = UDEV_TOUCHPAD;
else if (streq(value, "mouse"))
s->match.udev_type = UDEV_MOUSE;
else if (streq(value, "pointingstick"))
s->match.udev_type = UDEV_POINTINGSTICK;
else if (streq(value, "keyboard"))
s->match.udev_type = UDEV_KEYBOARD;
else if (streq(value, "joystick"))
s->match.udev_type = UDEV_JOYSTICK;
else if (streq(value, "tablet"))
s->match.udev_type = UDEV_TABLET;
else if (streq(value, "tablet-pad"))
s->match.udev_type = UDEV_TABLET_PAD;
else
goto out;
} else if (streq(key, "MatchDeviceTree")) {
check_set_bit(s, M_DT);
s->match.dt = safe_strdup(value);
} else {
qlog_error(ctx, "Unknown match key '%s'\n", key);
goto out;
}
#undef check_set_bit
s->has_match = true;
rc = true;
out:
return rc;
}
/**
* Parse a ModelFooBar=1 line.
*
* @param section The section struct to be filled in
* @param key The ModelFooBar part of the line
* @param value The value after the =, must be 1 or 0.
*
* @return true on success, false otherwise.
*/
static bool
parse_model(struct quirks_context *ctx,
struct section *s,
const char *key,
const char *value)
{
bool b;
enum quirk q = QUIRK_MODEL_ALPS_SERIAL_TOUCHPAD;
assert(strneq(key, "Model", 5));
if (!parse_boolean_property(value, &b))
return false;
do {
if (streq(key, quirk_get_name(q))) {
struct property *p = property_new();
p->id = q,
p->type = PT_BOOL;
p->value.b = b;
list_append(&s->properties, &p->link);
s->has_property = true;
return true;
}
} while (++q < _QUIRK_LAST_MODEL_QUIRK_);
qlog_error(ctx, "Unknown key %s in %s\n", key, s->name);
return false;
}
/**
* Parse a AttrFooBar=banana line.
*
* @param section The section struct to be filled in
* @param key The AttrFooBar part of the line
* @param value The banana part of the line.
*
* Value parsing depends on the attribute type.
*
* @return true on success, false otherwise.
*/
static inline bool
parse_attr(struct quirks_context *ctx,
struct section *s,
const char *key,
const char *value)
{
struct property *p = property_new();
bool rc = false;
struct quirk_dimensions dim;
struct quirk_range range;
unsigned int v;
bool b;
double d;
if (streq(key, quirk_get_name(QUIRK_ATTR_SIZE_HINT))) {
p->id = QUIRK_ATTR_SIZE_HINT;
if (!parse_dimension_property(value, &dim.x, &dim.y))
goto out;
p->type = PT_DIMENSION;
p->value.dim = dim;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_TOUCH_SIZE_RANGE))) {
p->id = QUIRK_ATTR_TOUCH_SIZE_RANGE;
if (!parse_range_property(value, &range.upper, &range.lower))
goto out;
p->type = PT_RANGE;
p->value.range = range;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_PALM_SIZE_THRESHOLD))) {
p->id = QUIRK_ATTR_PALM_SIZE_THRESHOLD;
if (!safe_atou(value, &v))
goto out;
p->type = PT_UINT;
p->value.u = v;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_LID_SWITCH_RELIABILITY))) {
p->id = QUIRK_ATTR_LID_SWITCH_RELIABILITY;
if (!streq(value, "reliable") &&
!streq(value, "write_open") &&
!streq(value, "unreliable"))
goto out;
p->type = PT_STRING;
p->value.s = safe_strdup(value);
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_KEYBOARD_INTEGRATION))) {
p->id = QUIRK_ATTR_KEYBOARD_INTEGRATION;
if (!streq(value, "internal") && !streq(value, "external"))
goto out;
p->type = PT_STRING;
p->value.s = safe_strdup(value);
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_TRACKPOINT_INTEGRATION))) {
p->id = QUIRK_ATTR_TRACKPOINT_INTEGRATION;
if (!streq(value, "internal") && !streq(value, "external"))
goto out;
p->type = PT_STRING;
p->value.s = safe_strdup(value);
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_TPKBCOMBO_LAYOUT))) {
p->id = QUIRK_ATTR_TPKBCOMBO_LAYOUT;
if (!streq(value, "below"))
goto out;
p->type = PT_STRING;
p->value.s = safe_strdup(value);
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_PRESSURE_RANGE))) {
p->id = QUIRK_ATTR_PRESSURE_RANGE;
if (!parse_range_property(value, &range.upper, &range.lower))
goto out;
p->type = PT_RANGE;
p->value.range = range;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_PALM_PRESSURE_THRESHOLD))) {
p->id = QUIRK_ATTR_PALM_PRESSURE_THRESHOLD;
if (!safe_atou(value, &v))
goto out;
p->type = PT_UINT;
p->value.u = v;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_RESOLUTION_HINT))) {
p->id = QUIRK_ATTR_RESOLUTION_HINT;
if (!parse_dimension_property(value, &dim.x, &dim.y))
goto out;
p->type = PT_DIMENSION;
p->value.dim = dim;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_TRACKPOINT_MULTIPLIER))) {
p->id = QUIRK_ATTR_TRACKPOINT_MULTIPLIER;
if (!safe_atod(value, &d))
goto out;
p->type = PT_DOUBLE;
p->value.d = d;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_USE_VELOCITY_AVERAGING))) {
p->id = QUIRK_ATTR_USE_VELOCITY_AVERAGING;
if (!parse_boolean_property(value, &b))
goto out;
p->type = PT_BOOL;
p->value.b = b;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_TABLET_SMOOTHING))) {
p->id = QUIRK_ATTR_TABLET_SMOOTHING;
if (!parse_boolean_property(value, &b))
goto out;
p->type = PT_BOOL;
p->value.b = b;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD))) {
p->id = QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD;
if (!safe_atou(value, &v))
goto out;
p->type = PT_UINT;
p->value.u = v;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_THUMB_SIZE_THRESHOLD))) {
p->id = QUIRK_ATTR_THUMB_SIZE_THRESHOLD;
if (!safe_atou(value, &v))
goto out;
p->type = PT_UINT;
p->value.u = v;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_MSC_TIMESTAMP))) {
p->id = QUIRK_ATTR_MSC_TIMESTAMP;
if (!streq(value, "watch"))
goto out;
p->type = PT_STRING;
p->value.s = safe_strdup(value);
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_EVENT_CODE))) {
struct input_event events[32];
size_t nevents = ARRAY_LENGTH(events);
p->id = QUIRK_ATTR_EVENT_CODE;
if (!parse_evcode_property(value, events, &nevents) ||
nevents == 0)
goto out;
for (size_t i = 0; i < nevents; i++) {
p->value.tuples.tuples[i].first = events[i].type;
p->value.tuples.tuples[i].second = events[i].code;
p->value.tuples.tuples[i].third = events[i].value;
}
p->value.tuples.ntuples = nevents;
p->type = PT_TUPLES;
rc = true;
} else if (streq(key, quirk_get_name(QUIRK_ATTR_INPUT_PROP))) {
struct input_prop props[INPUT_PROP_CNT];
size_t nprops = ARRAY_LENGTH(props);
p->id = QUIRK_ATTR_INPUT_PROP;
if (!parse_input_prop_property(value, props, &nprops) ||
nprops == 0)
goto out;
for (size_t i = 0; i < nprops; i++) {
p->value.tuples.tuples[i].first = props[i].prop;
p->value.tuples.tuples[i].second = props[i].enabled;
}
p->value.tuples.ntuples = nprops;
p->type = PT_TUPLES;
rc = true;
} else {
qlog_error(ctx, "Unknown key %s in %s\n", key, s->name);
}
out:
if (rc) {
list_append(&s->properties, &p->link);
s->has_property = true;
} else {
property_cleanup(p);
}
return rc;
}
/**
* Parse a single line, expected to be in the format Key=value. Anything
* else will be rejected with a failure.
*
* Our data files can only have Match, Model and Attr, so let's check for
* those too.
*/
static bool
parse_value_line(struct quirks_context *ctx, struct section *s, const char *line)
{
bool rc = false;
size_t nelem;
char **strv = strv_from_string(line, "=", &nelem);
if (!strv || nelem != 2)
goto out;
const char *key = strv[0];
const char *value = strv[1];
if (strlen(key) == 0 || strlen(value) == 0)
goto out;
/* Whatever the value is, it's not supposed to be in quotes */
if (value[0] == '"' || value[0] == '\'')
goto out;
if (strneq(key, "Match", 5))
rc = parse_match(ctx, s, key, value);
else if (strneq(key, "Model", 5))
rc = parse_model(ctx, s, key, value);
else if (strneq(key, "Attr", 4))
rc = parse_attr(ctx, s, key, value);
else
qlog_error(ctx, "Unknown value prefix %s\n", line);
out:
strv_free(strv);
return rc;
}
static inline bool
parse_file(struct quirks_context *ctx, const char *path)
{
enum state {
STATE_SECTION,
STATE_MATCH,
STATE_MATCH_OR_VALUE,
STATE_VALUE_OR_SECTION,
STATE_ANY,
};
FILE *fp;
char line[512];
bool rc = false;
enum state state = STATE_SECTION;
struct section *section = NULL;
int lineno = -1;
qlog_debug(ctx, "%s\n", path);
/* Not using open_restricted here, if we can't access
* our own data files, our installation is screwed up.
*/
fp = fopen(path, "r");
if (!fp) {
/* If the file doesn't exist that's fine. Only way this can
* happen is for the custom override file, all others are
* provided by scandir so they do exist. Short of races we
* don't care about. */
if (errno == ENOENT)
return true;
qlog_error(ctx, "%s: failed to open file\n", path);
goto out;
}
while (fgets(line, sizeof(line), fp)) {
char *comment;
lineno++;
comment = strstr(line, "#");
if (comment) {
/* comment points to # but we need to remove the
* preceding whitespaces too */
comment--;
while (comment >= line) {
if (*comment != ' ' && *comment != '\t')
break;
comment--;
}
*(comment + 1) = '\0';
} else { /* strip the trailing newline */
comment = strstr(line, "\n");
if (comment)
*comment = '\0';
}
if (strlen(line) == 0)
continue;
/* We don't use quotes for strings, so we really don't want
* erroneous trailing whitespaces */
switch (line[strlen(line) - 1]) {
case ' ':
case '\t':
qlog_parser(ctx,
"%s:%d: Trailing whitespace '%s'\n",
path, lineno, line);
goto out;
}
switch (line[0]) {
case '\0':
case '\n':
case '#':
break;
/* white space not allowed */
case ' ':
case '\t':
qlog_parser(ctx, "%s:%d: Preceding whitespace '%s'\n",
path, lineno, line);
goto out;
/* section title */
case '[':
if (line[strlen(line) - 1] != ']') {
qlog_parser(ctx, "%s:%d: Closing ] missing '%s'\n",
path, lineno, line);
goto out;
}
if (state != STATE_SECTION &&
state != STATE_VALUE_OR_SECTION) {
qlog_parser(ctx, "%s:%d: expected section before %s\n",
path, lineno, line);
goto out;
}
if (section &&
(!section->has_match || !section->has_property)) {
qlog_parser(ctx, "%s:%d: previous section %s was empty\n",
path, lineno, section->name);
goto out; /* Previous section was empty */
}
state = STATE_MATCH;
section = section_new(path, line);
list_append(&ctx->sections, &section->link);
break;
default:
/* entries must start with A-Z */
if (line[0] < 'A' || line[0] > 'Z') {
qlog_parser(ctx, "%s:%d: Unexpected line %s\n",
path, lineno, line);
goto out;
}
switch (state) {
case STATE_SECTION:
qlog_parser(ctx, "%s:%d: expected [Section], got %s\n",
path, lineno, line);
goto out;
case STATE_MATCH:
if (!strneq(line, "Match", 5)) {
qlog_parser(ctx, "%s:%d: expected MatchFoo=bar, have %s\n",
path, lineno, line);
goto out;
}
state = STATE_MATCH_OR_VALUE;
break;
case STATE_MATCH_OR_VALUE:
if (!strneq(line, "Match", 5))
state = STATE_VALUE_OR_SECTION;
break;
case STATE_VALUE_OR_SECTION:
if (strneq(line, "Match", 5)) {
qlog_parser(ctx, "%s:%d: expected value or [Section], have %s\n",
path, lineno, line);
goto out;
}
break;
case STATE_ANY:
break;
}
if (!parse_value_line(ctx, section, line)) {
qlog_parser(ctx, "%s:%d: failed to parse %s\n",
path, lineno, line);
goto out;
}
break;
}
}
if (!section) {
qlog_parser(ctx, "%s: is an empty file\n", path);
goto out;
}
if ((!section->has_match || !section->has_property)) {
qlog_parser(ctx, "%s:%d: previous section %s was empty\n",
path, lineno, section->name);
goto out; /* Previous section was empty */
}
rc = true;
out:
if (fp)
fclose(fp);
return rc;
}
static int
is_data_file(const struct dirent *dir) {
return strendswith(dir->d_name, ".quirks");
}
static inline bool
parse_files(struct quirks_context *ctx, const char *data_path)
{
struct dirent **namelist;
int ndev = -1;
int idx = 0;
ndev = scandir(data_path, &namelist, is_data_file, versionsort);
if (ndev <= 0) {
qlog_error(ctx,
"%s: failed to find data files\n",
data_path);
return false;
}
for (idx = 0; idx < ndev; idx++) {
char path[PATH_MAX];
snprintf(path,
sizeof(path),
"%s/%s",
data_path,
namelist[idx]->d_name);
if (!parse_file(ctx, path))
break;
}
for (int i = 0; i < ndev; i++)
free(namelist[i]);
free(namelist);
return idx == ndev;
}
struct quirks_context *
quirks_init_subsystem(const char *data_path,
const char *override_file,
libinput_log_handler log_handler,
struct libinput *libinput,
enum quirks_log_type log_type)
{
struct quirks_context *ctx = zalloc(sizeof *ctx);
assert(data_path);
ctx->refcount = 1;
ctx->log_handler = log_handler;
ctx->log_type = log_type;
ctx->libinput = libinput;
list_init(&ctx->quirks);
list_init(&ctx->sections);
qlog_debug(ctx, "%s is data root\n", data_path);
ctx->dmi = init_dmi();
ctx->dt = init_dt();
if (!ctx->dmi && !ctx->dt)
goto error;
if (!parse_files(ctx, data_path))
goto error;
if (override_file && !parse_file(ctx, override_file))
goto error;
return ctx;
error:
quirks_context_unref(ctx);
return NULL;
}
struct quirks_context *
quirks_context_ref(struct quirks_context *ctx)
{
assert(ctx->refcount > 0);
ctx->refcount++;
return ctx;
}
struct quirks_context *
quirks_context_unref(struct quirks_context *ctx)
{
struct section *s;
if (!ctx)
return NULL;
assert(ctx->refcount >= 1);
ctx->refcount--;
if (ctx->refcount > 0)
return NULL;
/* Caller needs to clean up before calling this */
assert(list_empty(&ctx->quirks));
list_for_each_safe(s, &ctx->sections, link) {
section_destroy(s);
}
free(ctx->dmi);
free(ctx->dt);
free(ctx);
return NULL;
}
static struct quirks *
quirks_new(void)
{
struct quirks *q;
q = zalloc(sizeof *q);
q->refcount = 1;
q->nproperties = 0;
list_init(&q->link);
list_init(&q->floating_properties);
return q;
}
struct quirks *
quirks_unref(struct quirks *q)
{
if (!q)
return NULL;
/* We don't really refcount, but might
* as well have the API in place */
assert(q->refcount == 1);
for (size_t i = 0; i < q->nproperties; i++) {
property_unref(q->properties[i]);
}
/* Floating properties are owned by our quirks context, need to be
* cleaned up here */
struct property *p;
list_for_each_safe(p, &q->floating_properties, link) {
property_cleanup(p);
}
list_remove(&q->link);
free(q->properties);
free(q);
return NULL;
}
/**
* Searches for the udev property on this device and its parent devices.
*
* @return the value of the property or NULL
*/
static const char *
udev_prop(struct udev_device *device, const char *prop)
{
struct udev_device *d = device;
const char *value = NULL;
do {
value = udev_device_get_property_value(d, prop);
d = udev_device_get_parent(d);
} while (value == NULL && d != NULL);
return value;
}
static inline void
match_fill_name(struct match *m,
struct udev_device *device)
{
const char *str = udev_prop(device, "NAME");
size_t slen;
if (!str)
return;
/* udev NAME is in quotes, strip them */
if (str[0] == '"')
str++;
m->name = safe_strdup(str);
slen = strlen(m->name);
if (slen > 1 &&
m->name[slen - 1] == '"')
m->name[slen - 1] = '\0';
m->bits |= M_NAME;
}
static inline void
match_fill_bus_vid_pid(struct match *m,
struct udev_device *device)
{
const char *str;
unsigned int product, vendor, bus, version;
str = udev_prop(device, "PRODUCT");
if (!str)
return;
/* ID_VENDOR_ID/ID_PRODUCT_ID/ID_BUS aren't filled in for virtual
* devices so we have to resort to PRODUCT */
if (sscanf(str, "%x/%x/%x/%x", &bus, &vendor, &product, &version) != 4)
return;
m->product = product;
m->vendor = vendor;
m->version = version;
m->bits |= M_PID|M_VID|M_VERSION;
switch (bus) {
case BUS_USB:
m->bus = BT_USB;
m->bits |= M_BUS;
break;
case BUS_BLUETOOTH:
m->bus = BT_BLUETOOTH;
m->bits |= M_BUS;
break;
case BUS_I8042:
m->bus = BT_PS2;
m->bits |= M_BUS;
break;
case BUS_RMI:
m->bus = BT_RMI;
m->bits |= M_BUS;
break;
case BUS_I2C:
m->bus = BT_I2C;
m->bits |= M_BUS;
break;
case BUS_SPI:
m->bus = BT_SPI;
m->bits |= M_BUS;
break;
default:
break;
}
}
static inline void
match_fill_udev_type(struct match *m,
struct udev_device *device)
{
struct ut_map {
const char *prop;
enum udev_type flag;
} mappings[] = {
{ "ID_INPUT_MOUSE", UDEV_MOUSE },
{ "ID_INPUT_POINTINGSTICK", UDEV_POINTINGSTICK },
{ "ID_INPUT_TOUCHPAD", UDEV_TOUCHPAD },
{ "ID_INPUT_TABLET", UDEV_TABLET },
{ "ID_INPUT_TABLET_PAD", UDEV_TABLET_PAD },
{ "ID_INPUT_JOYSTICK", UDEV_JOYSTICK },
{ "ID_INPUT_KEYBOARD", UDEV_KEYBOARD },
{ "ID_INPUT_KEY", UDEV_KEYBOARD },
};
ARRAY_FOR_EACH(mappings, map) {
if (udev_prop(device, map->prop))
m->udev_type |= map->flag;
}
m->bits |= M_UDEV_TYPE;
}
static inline void
match_fill_dmi_dt(struct match *m, char *dmi, char *dt)
{
if (dmi) {
m->dmi = dmi;
m->bits |= M_DMI;
}
if (dt) {
m->dt = dt;
m->bits |= M_DT;
}
}
static struct match *
match_new(struct udev_device *device,
char *dmi, char *dt)
{
struct match *m = zalloc(sizeof *m);
match_fill_name(m, device);
match_fill_bus_vid_pid(m, device);
match_fill_dmi_dt(m, dmi, dt);
match_fill_udev_type(m, device);
return m;
}
static void
match_free(struct match *m)
{
/* dmi and dt are global */
free(m->name);
free(m);
}
static void
quirk_merge_event_codes(struct quirks_context *ctx,
struct quirks *q,
const struct property *property)
{
for (size_t i = 0; i < q->nproperties; i++) {
struct property *p = q->properties[i];
if (p->id != property->id)
continue;
/* We have a duplicated property, merge in with ours */
size_t offset = p->value.tuples.ntuples;
size_t max = ARRAY_LENGTH(p->value.tuples.tuples);
for (size_t j = 0; j < property->value.tuples.ntuples; j++) {
if (offset + j >= max)
break;
p->value.tuples.tuples[offset + j] = property->value.tuples.tuples[j];
p->value.tuples.ntuples++;
}
return;
}
/* First time we add AttrEventCode: create a new property.
* Unlike the other properties, this one isn't part of a section, it belongs
* to the quirks */
struct property *newprop = property_new();
newprop->id = property->id;
newprop->type = property->type;
newprop->value.tuples = property->value.tuples;
/* Caller responsible for pre-allocating space */
q->properties[q->nproperties++] = property_ref(newprop);
list_append(&q->floating_properties, &newprop->link);
}
static void
quirk_apply_section(struct quirks_context *ctx,
struct quirks *q,
const struct section *s)
{
struct property *p;
size_t nprops = 0;
void *tmp;
list_for_each(p, &s->properties, link) {
nprops++;
}
nprops += q->nproperties;
tmp = realloc(q->properties, nprops * sizeof(p));
if (!tmp)
return;
q->properties = tmp;
list_for_each(p, &s->properties, link) {
qlog_debug(ctx, "property added: %s from %s\n",
quirk_get_name(p->id), s->name);
/* All quirks but AttrEventCode and AttrInputProp
* simply overwrite each other, so we can just append the
* matching property and, later when checking the quirk, pick
* the last one in the array.
*
* The event codes/input props are special because they're lists
* that may *partially* override each other, e.g. a section may
* enable BTN_LEFT and BTN_RIGHT but a later section may disable
* only BTN_RIGHT. This should result in BTN_LEFT force-enabled
* and BTN_RIGHT force-disabled.
*
* To hack around this, those are the only ones where only ever
* have one struct property in the list (not owned by a section)
* and we simply merge any extra sections onto that.
*/
if (p->id == QUIRK_ATTR_EVENT_CODE ||
p->id == QUIRK_ATTR_INPUT_PROP)
quirk_merge_event_codes(ctx, q, p);
else
q->properties[q->nproperties++] = property_ref(p);
}
}
static bool
quirk_match_section(struct quirks_context *ctx,
struct quirks *q,
struct section *s,
struct match *m,
struct udev_device *device)
{
uint32_t matched_flags = 0x0;
for (uint32_t flag = 0x1; flag <= M_LAST; flag <<= 1) {
uint32_t prev_matched_flags = matched_flags;
/* section doesn't have this bit set, continue */
if ((s->match.bits & flag) == 0)
continue;
/* Couldn't fill in this bit for the match, so we
* do not match on it */
if ((m->bits & flag) == 0) {
qlog_debug(ctx,
"%s wants %s but we don't have that\n",
s->name, matchflagname(flag));
continue;
}
/* now check the actual matching bit */
switch (flag) {
case M_NAME:
if (fnmatch(s->match.name, m->name, 0) == 0)
matched_flags |= flag;
break;
case M_BUS:
if (m->bus == s->match.bus)
matched_flags |= flag;
break;
case M_VID:
if (m->vendor == s->match.vendor)
matched_flags |= flag;
break;
case M_PID:
if (m->product == s->match.product)
matched_flags |= flag;
break;
case M_VERSION:
if (m->version == s->match.version)
matched_flags |= flag;
break;
case M_DMI:
if (fnmatch(s->match.dmi, m->dmi, 0) == 0)
matched_flags |= flag;
break;
case M_DT:
if (fnmatch(s->match.dt, m->dt, 0) == 0)
matched_flags |= flag;
break;
case M_UDEV_TYPE:
if (s->match.udev_type & m->udev_type)
matched_flags |= flag;
break;
default:
abort();
}
if (prev_matched_flags != matched_flags) {
qlog_debug(ctx,
"%s matches for %s\n",
s->name,
matchflagname(flag));
}
}
if (s->match.bits == matched_flags) {
qlog_debug(ctx, "%s is full match\n", s->name);
quirk_apply_section(ctx, q, s);
}
return true;
}
struct quirks *
quirks_fetch_for_device(struct quirks_context *ctx,
struct udev_device *udev_device)
{
struct quirks *q = NULL;
struct section *s;
struct match *m;
if (!ctx)
return NULL;
qlog_debug(ctx, "%s: fetching quirks\n",
udev_device_get_devnode(udev_device));
q = quirks_new();
m = match_new(udev_device, ctx->dmi, ctx->dt);
list_for_each(s, &ctx->sections, link) {
quirk_match_section(ctx, q, s, m, udev_device);
}
match_free(m);
if (q->nproperties == 0) {
quirks_unref(q);
return NULL;
}
list_insert(&ctx->quirks, &q->link);
return q;
}
static inline struct property *
quirk_find_prop(struct quirks *q, enum quirk which)
{
/* Run backwards to only handle the last one assigned */
for (ssize_t i = q->nproperties - 1; i >= 0; i--) {
struct property *p = q->properties[i];
if (p->id == which)
return p;
}
return NULL;
}
bool
quirks_has_quirk(struct quirks *q, enum quirk which)
{
return quirk_find_prop(q, which) != NULL;
}
bool
quirks_get_int32(struct quirks *q, enum quirk which, int32_t *val)
{
struct property *p;
if (!q)
return false;
p = quirk_find_prop(q, which);
if (!p)
return false;
assert(p->type == PT_INT);
*val = p->value.i;
return true;
}
bool
quirks_get_uint32(struct quirks *q, enum quirk which, uint32_t *val)
{
struct property *p;
if (!q)
return false;
p = quirk_find_prop(q, which);
if (!p)
return false;
assert(p->type == PT_UINT);
*val = p->value.u;
return true;
}
bool
quirks_get_double(struct quirks *q, enum quirk which, double *val)
{
struct property *p;
if (!q)
return false;
p = quirk_find_prop(q, which);
if (!p)
return false;
assert(p->type == PT_DOUBLE);
*val = p->value.d;
return true;
}
bool
quirks_get_string(struct quirks *q, enum quirk which, char **val)
{
struct property *p;
if (!q)
return false;
p = quirk_find_prop(q, which);
if (!p)
return false;
assert(p->type == PT_STRING);
*val = p->value.s;
return true;
}
bool
quirks_get_bool(struct quirks *q, enum quirk which, bool *val)
{
struct property *p;
if (!q)
return false;
p = quirk_find_prop(q, which);
if (!p)
return false;
assert(p->type == PT_BOOL);
*val = p->value.b;
return true;
}
bool
quirks_get_dimensions(struct quirks *q,
enum quirk which,
struct quirk_dimensions *val)
{
struct property *p;
if (!q)
return false;
p = quirk_find_prop(q, which);
if (!p)
return false;
assert(p->type == PT_DIMENSION);
*val = p->value.dim;
return true;
}
bool
quirks_get_range(struct quirks *q,
enum quirk which,
struct quirk_range *val)
{
struct property *p;
if (!q)
return false;
p = quirk_find_prop(q, which);
if (!p)
return false;
assert(p->type == PT_RANGE);
*val = p->value.range;
return true;
}
bool
quirks_get_tuples(struct quirks *q,
enum quirk which,
const struct quirk_tuples **tuples)
{
struct property *p;
if (!q)
return false;
p = quirk_find_prop(q, which);
if (!p)
return false;
assert(p->type == PT_TUPLES);
*tuples = &p->value.tuples;
return true;
}
bool
quirks_get_uint32_array(struct quirks *q,
enum quirk which,
const uint32_t **array,
size_t *nelements)
{
struct property *p;
if (!q)
return false;
p = quirk_find_prop(q, which);
if (!p)
return false;
assert(p->type == PT_UINT_ARRAY);
*array = p->value.array.data.u;
*nelements = p->value.array.nelements;
return true;
}