From nobody Wed Aug 26 20:24:15 2026 Received: from mail-wm1-f44.google.com (mail-wm1-f44.google.com [209.85.128.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C61B839794B for ; Sun, 16 Aug 2026 17:56:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786903015; cv=none; b=OVS6YvmmqIvVOLOoaiLBJ7TS0CHYdW3I7uarN5pW774wDdVVHLg0RW/OQwfg1RR0zcXOlrpM33Y2FTKheN9xRNAfGbdW5EoZU9L0UtQm67qi4gzvY5cqq1bxVNGlZ3CHC3GlUMaUNqA+TxU6kPtn2k9UcE5DqwX0oC06lF7NIsI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786903015; c=relaxed/simple; bh=2sJ9UkiZ4PwtKhvfsiySzSng86bG2+QAAAoU2o9PYKA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Ct9VTaHyaGU1AwrGqaNzafaYTBegnAUuAfkbh8nZbC53jqF0kSVhOmLrbL5J2CFi2H9J93V+PmHNrFuztOd8sfYx9wIFIZ0/UkJib5bR4KFcHuiVT5q19f1H+NF/ePzXUQYSabQhKq291zEsHanKvRQq1meZz7aSglUMF7AaxI0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=cB0aMRjx; arc=none smtp.client-ip=209.85.128.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="cB0aMRjx" Received: by mail-wm1-f44.google.com with SMTP id 5b1f17b1804b1-49558ce01afso20042435e9.1 for ; Sun, 16 Aug 2026 10:56:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1786903011; x=1787507811; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=BzvfZZG0rVCuUGYTaNjWlipGE2d2PfOY1pgAigY5Ii0=; b=cB0aMRjx1xuxop7Vfd/MCk2+LEAYjTS3UviLS8orBUTgFBci/IVUF4LajBLbqrv1qv MWVG404DpRJUYjJopMt/t8ZQSexJ3aBUNvYsPFJ/MwSBf6W7M6+MZzVuOOAo4UdNMLTL FGmrX/uoPhMJWVlOkwZRt/PPNUxqGqofJXgasa9nIaPaNOZ3u2Xy7o6jlmTybI19spLC PK0TbUZQkA3FBz2WONbKg3i+veak4F/v6UTYv6AXg8csOow6o2WCEuPveCFbyu6Bkvu0 PzttqIZnQnl4hsylPkcO750uqGbZb4qJerYt2SclTMdRdQ0oA25HRJJW28r5BC77lr45 fUBA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1786903011; x=1787507811; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=BzvfZZG0rVCuUGYTaNjWlipGE2d2PfOY1pgAigY5Ii0=; b=ViNIQjGghn2z44dEMwB99GIs7LLtgSm4cXaJvtT34FrNJ5gLRoSWtml4HY9F1ggDJZ B+8bvP8229O3p6DT6as66dP/YwnbrmP3E6Wbmsw/BU1FGvc5UeAeQJmSKJBE9z17BAk8 3ojUSMRSp5K9Se07dusk651IbXE+jR1ja1DldCs+v1fV+HleceI4Hc2+qWv0m9cvhPnz OBcRHmOnLegADowUd3vTslnzuYzoa8T2Bovz8ikI6DAPRuVEFi5AnhxcZ589FamhrZfJ HExM2bsPIYfwZ4QH8yy+HIPI7bUmRFfBhHb+4k5DuKxmZ45n79GX5BZtbTmkCHCmzdob jQsw== X-Forwarded-Encrypted: i=1; AHgh+RrFtjoAoe8MCJ+ErUWR9ey4gE6an9XdzJlszb4UpXTKKPkeHCZOJo82TlnysaxbZSN5EXzOW+oUXRPxswI=@vger.kernel.org X-Gm-Message-State: AOJu0Yw05EdTMQ20t5ZerDkgTLcMIrmmWKj5fraEuV3qx5Xvq7qYZQNp MPURA12/fQkK49/4QOgettATH0QuRRbevo9tEOeVU5dsOv8a8qJeOOVK X-Gm-Gg: AR+sD11cQhLTBXWZLYWiOUfOsZC9dFIPBzfnkPB09kLSGfgLo91IVMRx+es+kLx1ISL QAulHQZTSREHoIinFGKw6m2osiTvwZA/DEPOxqgqbztplFB5a0tdvxmWUjOsDMZ+6KfFKh1vEwg 3W0bF96xFDEqDBBDh+7W9v7/EqYVdMWjpWh0TS3YQELuSAcc9Brb4UduTDSabyjp58QHG/eeC9D WG4Tt9gy8wreDEAWhnQ8EICEC1wEy2mSLTAyVxFpY28p0DmMV2NhOBvNwY3yjIBlAo/u1YWF02X Z8kcm9XO1GKHp5unrkt2SXwZ7gkVBrjp41A6aMl9ZFkYmrMWfrULiKV1ZHZuotiwfbUfY3uPVsO jGNmwPV7J4CIY5DFW4KiC5eBQzp0VfsM0xWAASyircvJHOCfEpMeTMYxT7KcB0x5MKuIPItsi8h OGcjHSPUJzNlPgA0M0s49h7RNiRdvElvM0ut+XpFkoMSpzp9hVx5EDrkUWzfO7XDWCvg== X-Received: by 2002:a05:600c:4754:b0:499:7e2b:8e5 with SMTP id 5b1f17b1804b1-4999a2f9ec3mr14154735e9.5.1786903010584; Sun, 16 Aug 2026 10:56:50 -0700 (PDT) Received: from 192.168.1.19 ([2a02:ff0:220:b213:dfd1:35e6:957c:b8c2]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49996188217sm157065445e9.13.2026.08.16.10.56.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 16 Aug 2026 10:56:50 -0700 (PDT) From: =?UTF-8?q?Ertu=C4=9Frul=20Top=C3=A7u?= To: hansg@kernel.org, ilpo.jarvinen@linux.intel.com Cc: platform-driver-x86@vger.kernel.org, linux-kernel@vger.kernel.org, oe-kbuild-all@lists.linux.dev, =?UTF-8?q?Ertu=C4=9Frul=20Top=C3=A7u?= Subject: [PATCH v3] platform/x86: Add Goodix fingerprint EC mailbox transport Date: Sun, 16 Aug 2026 22:49:55 +0300 Message-ID: <20260816194955.468107-1-ertugtopcu0@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260803074454.49474-1-ertugtopcu0@gmail.com> References: <20260803074454.49474-1-ertugtopcu0@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Some Goodix fingerprint sensors are connected through an ACPI-described embedded-controller shared-memory mailbox rather than directly through USB or SPI. Add a transport driver for the GXFP5130 platform interface. The driver owns the MMIO mailbox, GPIO handshakes and IRQ. It exposes opaque mailbox records through /dev/gxfp. Keep sensor initialization, TLS, capture and image processing in userspace. Restrict the raw transport to CAP_SYS_RAWIO and document the UAPI. Handle device removal and system suspend without invalidating open file descriptors. Signed-off-by: Ertu=C4=9Frul Top=C3=A7u --- Notes: Changes in v3: - Include the non-atomic low/high 64-bit MMIO helpers so readq() and writeq() are available on 32-bit builds. - Verify the driver with the kernel test robot's i386 W=3D1 configurati= on. Changes in v2: - Treat IRQ 0 as valid and always register a successfully resolved IRQ. - Replace mailbox handshake busy waits with fsleep(). - Remove unverified Nuvoton-specific wording, stale comments and unused= model data. .../ABI/testing/dev-goodix-ec-mailbox | 22 + MAINTAINERS | 8 + drivers/platform/x86/Kconfig | 2 + drivers/platform/x86/Makefile | 3 + .../platform/x86/goodix-ec-mailbox/Kconfig | 15 + .../platform/x86/goodix-ec-mailbox/Makefile | 2 + .../x86/goodix-ec-mailbox/goodix_ec_mailbox.h | 113 +++ .../x86/goodix-ec-mailbox/goodix_ec_main.c | 715 ++++++++++++++++++ .../x86/goodix-ec-mailbox/goodix_ec_uapi.c | 360 +++++++++ include/uapi/linux/goodix_ec.h | 37 + 10 files changed, 1277 insertions(+) create mode 100644 Documentation/ABI/testing/dev-goodix-ec-mailbox create mode 100644 drivers/platform/x86/goodix-ec-mailbox/Kconfig create mode 100644 drivers/platform/x86/goodix-ec-mailbox/Makefile create mode 100644 drivers/platform/x86/goodix-ec-mailbox/goodix_ec_mailbo= x.h create mode 100644 drivers/platform/x86/goodix-ec-mailbox/goodix_ec_main.c create mode 100644 drivers/platform/x86/goodix-ec-mailbox/goodix_ec_uapi.c create mode 100644 include/uapi/linux/goodix_ec.h diff --git a/Documentation/ABI/testing/dev-goodix-ec-mailbox b/Documentatio= n/ABI/testing/dev-goodix-ec-mailbox new file mode 100644 index 000000000000..11ca80a41ca7 --- /dev/null +++ b/Documentation/ABI/testing/dev-goodix-ec-mailbox @@ -0,0 +1,22 @@ +What: /dev/gxfp +Date: July 2026 +KernelVersion: TBD +Contact: Ertugrul Topcu +Description: + Binary userspace interface for the ACPI Goodix EC mailbox + fingerprint transport. Only callers with CAP_SYS_RAWIO may open + the device. At most one reader may be open at a time. + + A write consists of struct goodix_ec_tx_header followed by exactly + payload_len opaque MP payload bytes. reserved and flags must be zero. + + A read returns one struct goodix_ec_record_header followed by exactly + len bytes. mp_type is the normalized MP type and timestamp_ns is a + CLOCK_MONOTONIC timestamp captured when the record entered the RX + queue. Records are never split across reads. + + poll(2) reports POLLIN while a complete record is queued. Device + removal reports POLLERR | POLLHUP. System suspend reports POLLERR. + + GOODIX_EC_IOCTL_FLUSH_RX discards all queued receive records. +Users: libfprint Goodix GXFP5130 userspace driver diff --git a/MAINTAINERS b/MAINTAINERS index 92a2167f1eb8..0c62071bbc1b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11109,6 +11109,14 @@ M: Maud Spierings S: Maintained F: Documentation/devicetree/bindings/connector/gocontroll,moduline-module-= slot.yaml =20 +GOODIX EC MAILBOX FINGERPRINT TRANSPORT DRIVER +M: Ertugrul Topcu +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/dev-goodix-ec-mailbox +F: drivers/platform/x86/goodix-ec-mailbox/ +F: include/uapi/linux/goodix_ec.h + GOODIX TOUCHSCREEN M: Hans de Goede L: linux-input@vger.kernel.org diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 957034f39e4e..57d1311f8670 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -440,6 +440,8 @@ config FUJITSU_TABLET =20 If you have a Fujitsu convertible or slate, say Y or M here. =20 +source "drivers/platform/x86/goodix-ec-mailbox/Kconfig" + config GPD_POCKET_FAN tristate "GPD Pocket Fan Controller support" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 872ac3842391..650ab67f7902 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -54,6 +54,9 @@ obj-$(CONFIG_AMILO_RFKILL) +=3D amilo-rfkill.o obj-$(CONFIG_FUJITSU_LAPTOP) +=3D fujitsu-laptop.o obj-$(CONFIG_FUJITSU_TABLET) +=3D fujitsu-tablet.o =20 +# Goodix +obj-$(CONFIG_GOODIX_EC_MAILBOX) +=3D goodix-ec-mailbox/ + # GPD obj-$(CONFIG_GPD_POCKET_FAN) +=3D gpd-pocket-fan.o =20 diff --git a/drivers/platform/x86/goodix-ec-mailbox/Kconfig b/drivers/platf= orm/x86/goodix-ec-mailbox/Kconfig new file mode 100644 index 000000000000..6f43b1323fa4 --- /dev/null +++ b/drivers/platform/x86/goodix-ec-mailbox/Kconfig @@ -0,0 +1,15 @@ +config GOODIX_EC_MAILBOX + tristate "Goodix fingerprint EC mailbox transport" + depends on X86 + depends on ACPI + depends on GPIOLIB + help + Enable transport support for Goodix fingerprint sensors connected + through an ACPI-described embedded-controller shared-memory mailbox. + + The driver exposes the opaque mailbox transport through /dev/gxfp. + Sensor configuration, TLS, capture and image processing remain in + userspace. + + To compile this driver as a module, choose M here. The module will be + called goodix_ec_mailbox. diff --git a/drivers/platform/x86/goodix-ec-mailbox/Makefile b/drivers/plat= form/x86/goodix-ec-mailbox/Makefile new file mode 100644 index 000000000000..52e5516eefb0 --- /dev/null +++ b/drivers/platform/x86/goodix-ec-mailbox/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_GOODIX_EC_MAILBOX) +=3D goodix_ec_mailbox.o +goodix_ec_mailbox-y :=3D goodix_ec_main.o goodix_ec_uapi.o diff --git a/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_mailbox.h b/d= rivers/platform/x86/goodix-ec-mailbox/goodix_ec_mailbox.h new file mode 100644 index 000000000000..6f412c46159b --- /dev/null +++ b/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_mailbox.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _GOODIX_EC_H_ +#define _GOODIX_EC_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GOODIX_EC_DRIVER_NAME "goodix-ec-mailbox" + +/* Shared-memory mailbox layout. */ +#define GOODIX_EC_MMIO_SIZE 0x1000 +#define GOODIX_EC_TX_OFFSET 0x000 +#define GOODIX_EC_TX_SIZE 0x0200 +#define GOODIX_EC_RX_OFFSET 0x0200 +#define GOODIX_EC_RX_SIZE 0x0e00 +#define GOODIX_EC_PACKET_ALIGNMENT 8 + +/* EC mailbox framing. */ +#define GOODIX_EC_PACKET_TYPE 0xf0 +#define GOODIX_EC_SEQUENCE_SEED 0x8881 + +/* MP framing. The payload is opaque to the kernel. */ +#define GOODIX_MP_HEADER_SIZE 4 + +/* Mailbox handshake timing. */ +#define GOODIX_WRITE_DONE_PRE_US 50 +#define GOODIX_WRITE_DONE_HIGH_US 4000 +#define GOODIX_WRITE_DONE_POST_US 200 +#define GOODIX_READ_DONE_HIGH_US 50 +#define GOODIX_READ_DONE_POST_US 200 +#define GOODIX_SYNC_RX_DELAY_US 1000 + +struct device; + +struct goodix_ec_header { + u8 type; + __le16 payload_len; + u8 checksum; + __le16 sequence; + u8 reserved[2]; +} __packed; + +struct goodix_mp_header { + u8 type; + __le16 payload_len; + u8 checksum; +} __packed; + +struct goodix_device; + +struct goodix_model_data { + const char *name; +}; + +struct goodix_device { + struct device *dev; + struct kref refcount; + bool disconnected; + bool suspended; + bool irq_enabled; + + void __iomem *mailbox; + resource_size_t mailbox_phys; + resource_size_t mailbox_size; + + struct gpio_desc *write_done_gpio; + struct gpio_desc *read_done_gpio; + struct gpio_desc *irq_gpio; + int irq; + + u8 *tx_buf; + u8 *rx_buf; + + u8 *rx_reassembly; + size_t rx_reassembly_len; + size_t rx_reassembly_received; + u8 rx_reassembly_mp_type; + bool rx_reassembly_active; + + u16 tx_sequence; + /* Serializes mailbox TX with threaded-IRQ RX access. */ + struct mutex transfer_lock; + + struct miscdevice miscdev; + struct kfifo rx_fifo; + /* Protects the RX FIFO and reader ownership state. */ + spinlock_t rx_fifo_lock; + /* Serializes record-oriented read operations. */ + struct mutex rx_read_lock; + wait_queue_head_t rx_wait; + bool rx_fifo_ready; + bool rx_reader_open; + bool misc_registered; + + const struct goodix_model_data *model; +}; + +int goodix_ec_sync_send(struct goodix_device *gdev, + const u8 *tx, size_t tx_len); +bool goodix_ec_device_get(struct goodix_device *gdev); +void goodix_ec_device_put(struct goodix_device *gdev); +int goodix_ec_uapi_register(struct goodix_device *gdev); +void goodix_ec_uapi_unregister(struct goodix_device *gdev); + +#endif /* _GOODIX_EC_H_ */ diff --git a/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_main.c b/driv= ers/platform/x86/goodix-ec-mailbox/goodix_ec_main.c new file mode 100644 index 000000000000..67e73aa3c6df --- /dev/null +++ b/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_main.c @@ -0,0 +1,715 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Goodix fingerprint sensors behind an EC shared-memory mailbox. + * + * The tested GXFP5130 platform exposes the fingerprint transport through + * ACPI-described MMIO and GPIO handshakes. The host exchanges opaque MP + * payloads with the device through this mailbox. + * + * Sensor protocol policy deliberately stays in userspace. This driver does + * not know about Goodix commands, checksums, TLS, configuration, capture,= or + * sensor power states. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "goodix_ec_mailbox.h" +#include + +#define GXFP5130_ACPI_HID "GXFP5130" + +static void goodix_ec_device_release(struct kref *refcount) +{ + struct goodix_device *gdev =3D + container_of(refcount, struct goodix_device, refcount); + + kfree(gdev); +} + +bool goodix_ec_device_get(struct goodix_device *gdev) +{ + return kref_get_unless_zero(&gdev->refcount); +} + +void goodix_ec_device_put(struct goodix_device *gdev) +{ + kref_put(&gdev->refcount, goodix_ec_device_release); +} + +static void goodix_ec_device_put_action(void *data) +{ + goodix_ec_device_put(data); +} + +/* EC mailbox transport. */ + +struct goodix_ec_acpi_gpio_state { + unsigned int gpio_count; + int irq_index; + int done_index[2]; + u8 done_polarity[2]; + unsigned int done_count; +}; + +static int goodix_ec_acpi_gpio_resource(struct acpi_resource *resource, + void *context) +{ + struct goodix_ec_acpi_gpio_state *state =3D context; + struct acpi_resource_gpio *gpio; + unsigned int index; + + if (resource->type !=3D ACPI_RESOURCE_TYPE_GPIO) + return 0; + + gpio =3D &resource->data.gpio; + index =3D state->gpio_count++; + if (!gpio->pin_table_length) + return 0; + + if (gpio->connection_type =3D=3D ACPI_RESOURCE_GPIO_TYPE_INT) { + if (state->irq_index < 0) + state->irq_index =3D index; + } else if (gpio->connection_type =3D=3D ACPI_RESOURCE_GPIO_TYPE_IO && + state->done_count < ARRAY_SIZE(state->done_index)) { + state->done_index[state->done_count] =3D index; + state->done_polarity[state->done_count] =3D gpio->polarity; + state->done_count++; + } + + return 0; +} + +static int goodix_ec_add_gpio_mappings(struct device *dev) +{ + struct goodix_ec_acpi_gpio_state state =3D { + .irq_index =3D -1, + .done_index =3D { -1, -1 }, + }; + struct acpi_gpio_mapping *mappings; + struct acpi_gpio_params *params; + struct acpi_device *adev =3D ACPI_COMPANION(dev); + LIST_HEAD(resources); + bool active_low; + int ret; + + if (!adev) + return -ENODEV; + + ret =3D acpi_dev_get_resources(adev, &resources, + goodix_ec_acpi_gpio_resource, &state); + acpi_dev_free_resource_list(&resources); + if (ret <=3D 0) + return ret < 0 ? ret : -ENOENT; + if (state.irq_index < 0 || state.done_count !=3D 2) + return dev_err_probe(dev, -ENOENT, + "ACPI _CRS does not describe one IRQ and two done GPIOs\n"); + if (state.done_polarity[0] !=3D state.done_polarity[1]) + return dev_err_probe(dev, -EINVAL, + "ACPI done GPIO polarities differ\n"); + + active_low =3D state.done_polarity[0] !=3D 0; + params =3D devm_kcalloc(dev, 3, sizeof(*params), GFP_KERNEL); + mappings =3D devm_kcalloc(dev, 4, sizeof(*mappings), GFP_KERNEL); + if (!params || !mappings) + return -ENOMEM; + + params[0].crs_entry_index =3D state.irq_index; + params[0].line_index =3D 0; + params[0].active_low =3D false; + mappings[0].name =3D "irq-gpios"; + mappings[0].data =3D ¶ms[0]; + mappings[0].size =3D 1; + + params[1].crs_entry_index =3D state.done_index[0]; + params[1].line_index =3D 0; + params[1].active_low =3D active_low; + mappings[1].name =3D "write-done-gpios"; + mappings[1].data =3D ¶ms[1]; + mappings[1].size =3D 1; + + params[2].crs_entry_index =3D state.done_index[1]; + params[2].line_index =3D 0; + params[2].active_low =3D active_low; + mappings[2].name =3D "read-done-gpios"; + mappings[2].data =3D ¶ms[2]; + mappings[2].size =3D 1; + + ret =3D devm_acpi_dev_add_driver_gpios(dev, mappings); + if (ret) + return dev_err_probe(dev, ret, + "failed to install ACPI GPIO mappings\n"); + + dev_dbg(dev, + "ACPI GPIO mappings: irq=3D%d write-done=3D%d read-done=3D%d active-low= =3D%u\n", + state.irq_index, state.done_index[0], state.done_index[1], + active_low); + return 0; +} + +static int goodix_ec_get_gpio(struct goodix_device *gdev, + struct gpio_desc **gpio, + const char *name, + enum gpiod_flags flags) +{ + *gpio =3D devm_gpiod_get(gdev->dev, name, flags); + if (IS_ERR(*gpio)) + return dev_err_probe(gdev->dev, PTR_ERR(*gpio), + "failed to acquire %s GPIO\n", name); + + if (!*gpio) + return dev_err_probe(gdev->dev, -ENODEV, + "%s GPIO missing\n", name); + + return 0; +} + +static void goodix_ec_mmio_write_tx(struct goodix_device *gdev, size_t len) +{ + size_t offset; + + for (offset =3D 0; offset < len; offset +=3D sizeof(u64)) { + u64 value =3D get_unaligned_le64(gdev->tx_buf + offset); + + writeq(value, gdev->mailbox + GOODIX_EC_TX_OFFSET + offset); + } + + /* Publish every qword before asserting the write-done doorbell. */ + wmb(); +} + +static void goodix_ec_mmio_read_rx(struct goodix_device *gdev) +{ + size_t offset; + + for (offset =3D 0; offset < GOODIX_EC_RX_SIZE; offset +=3D sizeof(u64)) { + u64 value =3D readq(gdev->mailbox + GOODIX_EC_RX_OFFSET + offset); + + put_unaligned_le64(value, gdev->rx_buf + offset); + } + + /* Complete the mailbox snapshot before parsing its headers. */ + rmb(); +} + +static int goodix_ec_pulse_write_done(struct goodix_device *gdev) +{ + if (!gdev->write_done_gpio) + return -ENODEV; + + gpiod_set_value_cansleep(gdev->write_done_gpio, 0); + fsleep(GOODIX_WRITE_DONE_PRE_US); + + gpiod_set_value_cansleep(gdev->write_done_gpio, 1); + fsleep(GOODIX_WRITE_DONE_HIGH_US); + + gpiod_set_value_cansleep(gdev->write_done_gpio, 0); + fsleep(GOODIX_WRITE_DONE_POST_US); + + return 0; +} + +static int goodix_ec_pulse_read_done(struct goodix_device *gdev) +{ + if (!gdev->read_done_gpio) + return -ENODEV; + + /* RX must be copied before acknowledging that the host consumed it. */ + gpiod_set_value_cansleep(gdev->read_done_gpio, 1); + fsleep(GOODIX_READ_DONE_HIGH_US); + + gpiod_set_value_cansleep(gdev->read_done_gpio, 0); + fsleep(GOODIX_READ_DONE_POST_US); + + return 0; +} + +static int goodix_ec_build_packet(struct goodix_device *gdev, + const u8 *payload, size_t payload_len, + size_t *packet_len) +{ + struct goodix_ec_header *header; + size_t total_len; + + if (!gdev || !payload || !payload_len || !packet_len) + return -EINVAL; + + if (payload_len > U16_MAX) + return -EOVERFLOW; + + total_len =3D ALIGN(sizeof(*header) + payload_len, + GOODIX_EC_PACKET_ALIGNMENT); + if (total_len > GOODIX_EC_TX_SIZE) + return -EMSGSIZE; + + memset(gdev->tx_buf, 0, total_len); + + header =3D (struct goodix_ec_header *)gdev->tx_buf; + header->type =3D GOODIX_EC_PACKET_TYPE; + header->payload_len =3D cpu_to_le16(payload_len); + header->checksum =3D header->type + (payload_len & 0xff) + + ((payload_len >> 8) & 0xff); + header->sequence =3D cpu_to_le16(++gdev->tx_sequence); + + memcpy(gdev->tx_buf + sizeof(*header), payload, payload_len); + *packet_len =3D total_len; + + return 0; +} + +int goodix_ec_sync_send(struct goodix_device *gdev, + const u8 *tx, size_t tx_len) +{ + size_t packet_len; + int ret; + + ret =3D goodix_ec_build_packet(gdev, tx, tx_len, &packet_len); + if (ret) + return ret; + + goodix_ec_mmio_write_tx(gdev, packet_len); + + ret =3D goodix_ec_pulse_write_done(gdev); + if (ret) + return ret; + + fsleep(GOODIX_SYNC_RX_DELAY_US); + return 0; +} + +/* MP packet transport. */ +static void goodix_ec_rx_push(struct goodix_device *gdev, u8 mp_type, + const u8 *payload, size_t payload_len) +{ + struct goodix_ec_record_header header; + unsigned long flags; + size_t required; + + if (!gdev || !payload || !payload_len || + payload_len > GOODIX_EC_UAPI_RX_MAX) + return; + + memset(&header, 0, sizeof(header)); + header.len =3D payload_len; + header.mp_type =3D mp_type >> 4; + header.timestamp_ns =3D ktime_get_ns(); + required =3D sizeof(header) + payload_len; + + spin_lock_irqsave(&gdev->rx_fifo_lock, flags); + if (!gdev->rx_fifo_ready || kfifo_avail(&gdev->rx_fifo) < required) { + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + dev_warn_ratelimited(gdev->dev, + "RX queue full; dropping %zu-byte packet\n", + payload_len); + return; + } + + kfifo_in(&gdev->rx_fifo, &header, sizeof(header)); + kfifo_in(&gdev->rx_fifo, payload, payload_len); + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + + wake_up_interruptible(&gdev->rx_wait); +} + +static irqreturn_t goodix_ec_irq_thread(int irq, void *data) +{ + struct goodix_device *gdev =3D data; + size_t chunk_len; + size_t remaining; + u16 declared_len; + u8 mp_type; + int ret =3D 0; + int ack_ret; + + mutex_lock(&gdev->transfer_lock); + + goodix_ec_mmio_read_rx(gdev); + + /* + * Continuation IRQs contain raw payload bytes without another + * MP header. + */ + if (gdev->rx_reassembly_active) { + remaining =3D gdev->rx_reassembly_len - + gdev->rx_reassembly_received; + + chunk_len =3D min_t(size_t, + remaining, + GOODIX_EC_RX_SIZE); + + memcpy(gdev->rx_reassembly + + gdev->rx_reassembly_received, + gdev->rx_buf, + chunk_len); + + gdev->rx_reassembly_received +=3D chunk_len; + + ack_ret =3D goodix_ec_pulse_read_done(gdev); + if (ack_ret) + ret =3D ack_ret; + + if (!ret && + gdev->rx_reassembly_received =3D=3D + gdev->rx_reassembly_len) { + dev_dbg(gdev->dev, + "RX reassembly complete: MP=3D0x%02x payload=3D%zu bytes\n", + gdev->rx_reassembly_mp_type, + gdev->rx_reassembly_len); + + goodix_ec_rx_push(gdev, + gdev->rx_reassembly_mp_type, + gdev->rx_reassembly, + gdev->rx_reassembly_len); + + kfree(gdev->rx_reassembly); + gdev->rx_reassembly =3D NULL; + gdev->rx_reassembly_len =3D 0; + gdev->rx_reassembly_received =3D 0; + gdev->rx_reassembly_mp_type =3D 0; + gdev->rx_reassembly_active =3D false; + } + + mutex_unlock(&gdev->transfer_lock); + + if (ret) + dev_warn_ratelimited(gdev->dev, + "IRQ %d continuation ACK failed: %d\n", + irq, ret); + + return IRQ_HANDLED; + } + + mp_type =3D gdev->rx_buf[0]; + + if ((mp_type >> 4) !=3D 0x0a && + (mp_type >> 4) !=3D 0x0b && + (mp_type >> 4) !=3D 0x0c) { + ret =3D -EBADMSG; + goto acknowledge; + } + + if (gdev->rx_buf[3] !=3D + gdev->rx_buf[0] + + gdev->rx_buf[1] + + gdev->rx_buf[2]) { + ret =3D -EBADMSG; + goto acknowledge; + } + + declared_len =3D get_unaligned_le16(gdev->rx_buf + 1); + + /* The complete payload fits in one mailbox transaction. */ + if (declared_len <=3D + GOODIX_EC_RX_SIZE - + sizeof(struct goodix_mp_header)) { + goodix_ec_rx_push(gdev, + mp_type, + gdev->rx_buf + + sizeof(struct goodix_mp_header), + declared_len); + + dev_dbg(gdev->dev, + "IRQ %d RX queued: MP=3D0x%02x payload=3D%u bytes\n", + irq, + mp_type >> 4, + declared_len); + + goto acknowledge; + } + + /* Reassemble payloads delivered across multiple IRQs. */ + gdev->rx_reassembly =3D kmalloc(declared_len, GFP_KERNEL); + if (!gdev->rx_reassembly) { + ret =3D -ENOMEM; + goto acknowledge; + } + + chunk_len =3D GOODIX_EC_RX_SIZE - + sizeof(struct goodix_mp_header); + + memcpy(gdev->rx_reassembly, + gdev->rx_buf + + sizeof(struct goodix_mp_header), + chunk_len); + + gdev->rx_reassembly_len =3D declared_len; + gdev->rx_reassembly_received =3D chunk_len; + gdev->rx_reassembly_mp_type =3D mp_type; + gdev->rx_reassembly_active =3D true; + + dev_dbg(gdev->dev, + "RX reassembly started: MP=3D0x%02x total=3D%u first=3D%zu remaining=3D%= zu\n", + mp_type >> 4, + declared_len, + chunk_len, + (size_t)declared_len - chunk_len); + +acknowledge: + ack_ret =3D goodix_ec_pulse_read_done(gdev); + if (!ret && ack_ret) + ret =3D ack_ret; + + mutex_unlock(&gdev->transfer_lock); + + if (ret) + dev_warn_ratelimited(gdev->dev, + "IRQ %d RX handling failed: %d\n", + irq, ret); + + return IRQ_HANDLED; +} + +static const struct goodix_model_data gxfp5130_model =3D { + .name =3D "GXFP5130", +}; + +/* ACPI platform driver and model selection. */ + +static int goodix_ec_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + const struct acpi_device_id *match; + struct goodix_device *gdev; + struct resource *resource; + resource_size_t mailbox_size; + unsigned long irq_flags; + unsigned int irq_type; + int ret; + + match =3D acpi_match_device(dev->driver->acpi_match_table, dev); + if (!match || !match->driver_data) + return -ENODEV; + + gdev =3D kzalloc_obj(*gdev); + if (!gdev) + return -ENOMEM; + kref_init(&gdev->refcount); + ret =3D devm_add_action_or_reset(dev, goodix_ec_device_put_action, gdev); + if (ret) + return ret; + + gdev->dev =3D dev; + gdev->model =3D (const struct goodix_model_data *)match->driver_data; + gdev->tx_sequence =3D GOODIX_EC_SEQUENCE_SEED; + mutex_init(&gdev->transfer_lock); + platform_set_drvdata(pdev, gdev); + + gdev->mailbox =3D devm_platform_get_and_ioremap_resource(pdev, 0, + &resource); + if (IS_ERR(gdev->mailbox)) + return dev_err_probe(dev, PTR_ERR(gdev->mailbox), + "failed to map EC mailbox MMIO resource\n"); + + mailbox_size =3D resource_size(resource); + if (mailbox_size < GOODIX_EC_MMIO_SIZE) + return dev_err_probe(dev, -EINVAL, + "EC mailbox resource too small: %#llx\n", + (unsigned long long)mailbox_size); + + gdev->mailbox_phys =3D resource->start; + gdev->mailbox_size =3D mailbox_size; + + gdev->tx_buf =3D devm_kzalloc(dev, GOODIX_EC_TX_SIZE, GFP_KERNEL); + if (!gdev->tx_buf) + return -ENOMEM; + + gdev->rx_buf =3D devm_kzalloc(dev, GOODIX_EC_RX_SIZE, GFP_KERNEL); + if (!gdev->rx_buf) + return -ENOMEM; + + ret =3D goodix_ec_add_gpio_mappings(dev); + if (ret) + return ret; + + ret =3D goodix_ec_get_gpio(gdev, &gdev->write_done_gpio, + "write-done", GPIOD_OUT_LOW); + if (ret) + return ret; + + ret =3D goodix_ec_get_gpio(gdev, &gdev->read_done_gpio, + "read-done", GPIOD_OUT_LOW); + if (ret) + return ret; + + ret =3D goodix_ec_get_gpio(gdev, &gdev->irq_gpio, + "irq", GPIOD_IN); + if (ret) + return ret; + + gdev->irq =3D gpiod_to_irq(gdev->irq_gpio); + if (gdev->irq < 0) + return dev_err_probe(dev, gdev->irq, + "failed to map IRQ GPIO to an IRQ\n"); + + irq_type =3D irq_get_trigger_type(gdev->irq); + if (irq_type =3D=3D IRQ_TYPE_NONE) { + ret =3D irq_set_irq_type(gdev->irq, IRQ_TYPE_LEVEL_HIGH); + if (ret) + return dev_err_probe(dev, ret, + "failed to set level-high IRQ trigger\n"); + irq_type =3D IRQ_TYPE_LEVEL_HIGH; + } + + dev_info(dev, "bound %s: mailbox=3D%pa size=3D%#llx irq=3D%d\n", + gdev->model->name, &gdev->mailbox_phys, + (unsigned long long)gdev->mailbox_size, gdev->irq); + + irq_flags =3D IRQF_ONESHOT | IRQF_NO_AUTOEN; + ret =3D devm_request_threaded_irq(dev, + gdev->irq, + NULL, + goodix_ec_irq_thread, + irq_flags, + dev_name(dev), + gdev); + if (ret) + return dev_err_probe(dev, ret, + "failed to request IRQ %d\n", + gdev->irq); + + dev_dbg(dev, "IRQ %d registered: flags=3D%#lx trigger=3D%#x\n", + gdev->irq, irq_flags, irq_type); + + ret =3D goodix_ec_uapi_register(gdev); + if (ret) + return dev_err_probe(dev, ret, + "failed to register userspace interface\n"); + + enable_irq(gdev->irq); + gdev->irq_enabled =3D true; + dev_info(dev, "IRQ %d armed\n", gdev->irq); + + dev_info(dev, "EC mailbox transport ready\n"); + return 0; +} + +static void goodix_ec_remove(struct platform_device *pdev) +{ + struct goodix_device *gdev =3D platform_get_drvdata(pdev); + + if (!gdev) + return; + + WRITE_ONCE(gdev->disconnected, true); + if (gdev->irq_enabled) { + disable_irq(gdev->irq); + synchronize_irq(gdev->irq); + gdev->irq_enabled =3D false; + } + + goodix_ec_uapi_unregister(gdev); + mutex_lock(&gdev->transfer_lock); + kfree(gdev->rx_reassembly); + gdev->rx_reassembly =3D NULL; + gdev->rx_reassembly_len =3D 0; + gdev->rx_reassembly_received =3D 0; + gdev->rx_reassembly_active =3D false; + + if (gdev->write_done_gpio) + gpiod_set_value_cansleep(gdev->write_done_gpio, 0); + if (gdev->read_done_gpio) + gpiod_set_value_cansleep(gdev->read_done_gpio, 0); + mutex_unlock(&gdev->transfer_lock); + + dev_info(gdev->dev, "%s detached\n", gdev->model->name); +} + +static int goodix_ec_suspend(struct device *dev) +{ + struct goodix_device *gdev =3D dev_get_drvdata(dev); + unsigned long flags; + + if (!gdev || READ_ONCE(gdev->disconnected)) + return 0; + + WRITE_ONCE(gdev->suspended, true); + wake_up_interruptible_all(&gdev->rx_wait); + if (gdev->irq_enabled) { + disable_irq(gdev->irq); + synchronize_irq(gdev->irq); + gdev->irq_enabled =3D false; + } + + mutex_lock(&gdev->transfer_lock); + gpiod_set_value_cansleep(gdev->write_done_gpio, 0); + gpiod_set_value_cansleep(gdev->read_done_gpio, 0); + kfree(gdev->rx_reassembly); + gdev->rx_reassembly =3D NULL; + gdev->rx_reassembly_len =3D 0; + gdev->rx_reassembly_received =3D 0; + gdev->rx_reassembly_active =3D false; + mutex_unlock(&gdev->transfer_lock); + + spin_lock_irqsave(&gdev->rx_fifo_lock, flags); + if (gdev->rx_fifo_ready) + kfifo_reset(&gdev->rx_fifo); + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + return 0; +} + +static int goodix_ec_resume(struct device *dev) +{ + struct goodix_device *gdev =3D dev_get_drvdata(dev); + + if (!gdev || READ_ONCE(gdev->disconnected)) + return 0; + + gpiod_set_value_cansleep(gdev->write_done_gpio, 0); + gpiod_set_value_cansleep(gdev->read_done_gpio, 0); + if (!gdev->irq_enabled) { + enable_irq(gdev->irq); + gdev->irq_enabled =3D true; + } + /* Permit new writes only after the receive path is armed. */ + WRITE_ONCE(gdev->suspended, false); + wake_up_interruptible_all(&gdev->rx_wait); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(goodix_ec_pm_ops, goodix_ec_suspend, + goodix_ec_resume); + +static const struct acpi_device_id goodix_ec_acpi_match[] =3D { + { + .id =3D GXFP5130_ACPI_HID, + .driver_data =3D (kernel_ulong_t)&gxfp5130_model, + }, + { } +}; +MODULE_DEVICE_TABLE(acpi, goodix_ec_acpi_match); + +static struct platform_driver goodix_ec_driver =3D { + .probe =3D goodix_ec_probe, + .remove =3D goodix_ec_remove, + .driver =3D { + .name =3D GOODIX_EC_DRIVER_NAME, + .acpi_match_table =3D ACPI_PTR(goodix_ec_acpi_match), + .pm =3D pm_sleep_ptr(&goodix_ec_pm_ops), + }, +}; +module_platform_driver(goodix_ec_driver); + +MODULE_AUTHOR("Ertugrul Topcu "); +MODULE_DESCRIPTION("Goodix fingerprint sensors over an EC mailbox"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_uapi.c b/driv= ers/platform/x86/goodix-ec-mailbox/goodix_ec_uapi.c new file mode 100644 index 000000000000..d2f075acb481 --- /dev/null +++ b/drivers/platform/x86/goodix-ec-mailbox/goodix_ec_uapi.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "goodix_ec_mailbox.h" +#include + +#define GOODIX_EC_RX_FIFO_BYTES (256u * 1024u) + +static bool goodix_ec_rx_ready(struct goodix_device *gdev) +{ + unsigned long flags; + bool ready; + + spin_lock_irqsave(&gdev->rx_fifo_lock, flags); + ready =3D gdev->rx_fifo_ready && !kfifo_is_empty(&gdev->rx_fifo); + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + + return ready; +} + +static int goodix_ec_uapi_open(struct inode *inode, struct file *file) +{ + struct miscdevice *misc =3D file->private_data; + struct goodix_device *gdev =3D + container_of(misc, struct goodix_device, miscdev); + unsigned long flags; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + if (!goodix_ec_device_get(gdev)) + return -ENODEV; + if (READ_ONCE(gdev->disconnected)) { + goodix_ec_device_put(gdev); + return -ENODEV; + } + + if (file->f_mode & FMODE_READ) { + spin_lock_irqsave(&gdev->rx_fifo_lock, flags); + if (!gdev->rx_fifo_ready) { + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + goodix_ec_device_put(gdev); + return -ENODEV; + } + + if (gdev->rx_reader_open) { + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + goodix_ec_device_put(gdev); + return -EBUSY; + } + + gdev->rx_reader_open =3D true; + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + } + + file->private_data =3D gdev; + return 0; +} + +static int goodix_ec_uapi_release(struct inode *inode, struct file *file) +{ + struct goodix_device *gdev =3D file->private_data; + unsigned long flags; + + if (gdev && (file->f_mode & FMODE_READ)) { + spin_lock_irqsave(&gdev->rx_fifo_lock, flags); + gdev->rx_reader_open =3D false; + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + } + if (gdev) + goodix_ec_device_put(gdev); + + return 0; +} + +static ssize_t goodix_ec_uapi_write(struct file *file, + const char __user *user_buffer, + size_t count, loff_t *position) +{ + struct goodix_device *gdev =3D file->private_data; + struct goodix_ec_tx_header header; + u8 *mp_packet; + size_t required; + int ret; + + if (!gdev || !user_buffer || READ_ONCE(gdev->disconnected)) + return -ENODEV; + + if (count < sizeof(header)) + return -EINVAL; + + if (copy_from_user(&header, user_buffer, sizeof(header))) + return -EFAULT; + + if (header.payload_len > GOODIX_EC_UAPI_TX_MAX) + return -EMSGSIZE; + if (header.reserved || header.flags) + return -EINVAL; + + required =3D sizeof(header) + header.payload_len; + if (count !=3D required) + return -EINVAL; + + mp_packet =3D kmalloc(GOODIX_MP_HEADER_SIZE + + header.payload_len, GFP_KERNEL); + if (!mp_packet) + return -ENOMEM; + + mp_packet[0] =3D header.mp_flags; + put_unaligned_le16(header.payload_len, mp_packet + 1); + mp_packet[3] =3D mp_packet[0] + mp_packet[1] + mp_packet[2]; + + if (header.payload_len && + copy_from_user(mp_packet + sizeof(struct goodix_mp_header), + user_buffer + sizeof(header), + header.payload_len)) { + kfree(mp_packet); + return -EFAULT; + } + + mutex_lock(&gdev->transfer_lock); + if (READ_ONCE(gdev->disconnected)) + ret =3D -ENODEV; + else if (READ_ONCE(gdev->suspended)) + ret =3D -EHOSTDOWN; + else + ret =3D goodix_ec_sync_send(gdev, mp_packet, + sizeof(struct goodix_mp_header) + + header.payload_len); + mutex_unlock(&gdev->transfer_lock); + + kfree(mp_packet); + + if (ret) + return ret; + + return count; +} + +static ssize_t goodix_ec_uapi_read(struct file *file, char __user *user_bu= ffer, + size_t count, loff_t *position) +{ + struct goodix_device *gdev =3D file->private_data; + struct goodix_ec_record_header header; + unsigned long flags; + u8 *record; + size_t required; + int ret; + + if (!gdev || !user_buffer || READ_ONCE(gdev->disconnected)) + return -ENODEV; + + ret =3D mutex_lock_interruptible(&gdev->rx_read_lock); + if (ret) + return ret; + + for (;;) { + spin_lock_irqsave(&gdev->rx_fifo_lock, flags); + + if (!gdev->rx_fifo_ready) { + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + ret =3D -ENODEV; + goto out_unlock; + } + if (READ_ONCE(gdev->suspended)) { + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + ret =3D -EHOSTDOWN; + goto out_unlock; + } + + if (kfifo_len(&gdev->rx_fifo) >=3D sizeof(header)) + break; + + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + + if (file->f_flags & O_NONBLOCK) { + ret =3D -EAGAIN; + goto out_unlock; + } + + ret =3D wait_event_interruptible(gdev->rx_wait, + goodix_ec_rx_ready(gdev) || + !READ_ONCE(gdev->rx_fifo_ready) || + READ_ONCE(gdev->suspended)); + if (ret) + goto out_unlock; + } + + if (kfifo_out_peek(&gdev->rx_fifo, &header, + sizeof(header)) !=3D sizeof(header)) { + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + ret =3D -EIO; + goto out_unlock; + } + + required =3D sizeof(header) + header.len; + if (kfifo_len(&gdev->rx_fifo) < required) { + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + ret =3D -EIO; + goto out_unlock; + } + + if (count < required) { + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + ret =3D -EMSGSIZE; + goto out_unlock; + } + + record =3D kmalloc(required, GFP_ATOMIC); + if (!record) { + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + ret =3D -ENOMEM; + goto out_unlock; + } + + if (kfifo_out(&gdev->rx_fifo, record, required) !=3D required) { + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + kfree(record); + ret =3D -EIO; + goto out_unlock; + } + + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + + if (copy_to_user(user_buffer, record, required)) { + kfree(record); + ret =3D -EFAULT; + goto out_unlock; + } + + kfree(record); + ret =3D required; + +out_unlock: + mutex_unlock(&gdev->rx_read_lock); + return ret; +} + +static __poll_t goodix_ec_uapi_poll(struct file *file, poll_table *wait) +{ + struct goodix_device *gdev =3D file->private_data; + unsigned long flags; + __poll_t mask =3D 0; + + if (!gdev || READ_ONCE(gdev->disconnected)) + return EPOLLERR; + + poll_wait(file, &gdev->rx_wait, wait); + + spin_lock_irqsave(&gdev->rx_fifo_lock, flags); + if (!gdev->rx_fifo_ready || READ_ONCE(gdev->disconnected)) + mask =3D EPOLLERR | EPOLLHUP; + else if (READ_ONCE(gdev->suspended)) + mask =3D EPOLLERR; + else if (!kfifo_is_empty(&gdev->rx_fifo)) + mask =3D EPOLLIN | EPOLLRDNORM; + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + + return mask; +} + +static long goodix_ec_uapi_ioctl(struct file *file, + unsigned int command, + unsigned long argument) +{ + struct goodix_device *gdev =3D file->private_data; + unsigned long flags; + + if (!gdev || READ_ONCE(gdev->disconnected)) + return -ENODEV; + + switch (command) { + case GOODIX_EC_IOCTL_FLUSH_RX: + spin_lock_irqsave(&gdev->rx_fifo_lock, flags); + if (gdev->rx_fifo_ready) + kfifo_reset(&gdev->rx_fifo); + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + return 0; + default: + return -ENOTTY; + } +} + +static const struct file_operations goodix_ec_uapi_fops =3D { + .owner =3D THIS_MODULE, + .open =3D goodix_ec_uapi_open, + .release =3D goodix_ec_uapi_release, + .read =3D goodix_ec_uapi_read, + .write =3D goodix_ec_uapi_write, + .poll =3D goodix_ec_uapi_poll, + .unlocked_ioctl =3D goodix_ec_uapi_ioctl, + .compat_ioctl =3D compat_ptr_ioctl, + .llseek =3D noop_llseek, +}; + +int goodix_ec_uapi_register(struct goodix_device *gdev) +{ + int ret; + + spin_lock_init(&gdev->rx_fifo_lock); + mutex_init(&gdev->rx_read_lock); + init_waitqueue_head(&gdev->rx_wait); + + ret =3D kfifo_alloc(&gdev->rx_fifo, GOODIX_EC_RX_FIFO_BYTES, GFP_KERNEL); + if (ret) + return ret; + + gdev->rx_fifo_ready =3D true; + gdev->rx_reader_open =3D false; + + gdev->miscdev.minor =3D MISC_DYNAMIC_MINOR; + gdev->miscdev.name =3D "gxfp"; + gdev->miscdev.fops =3D &goodix_ec_uapi_fops; + gdev->miscdev.parent =3D gdev->dev; + + ret =3D misc_register(&gdev->miscdev); + if (ret) { + gdev->rx_fifo_ready =3D false; + kfifo_free(&gdev->rx_fifo); + return ret; + } + + gdev->misc_registered =3D true; + dev_info(gdev->dev, "userspace interface registered: /dev/gxfp\n"); + return 0; +} + +void goodix_ec_uapi_unregister(struct goodix_device *gdev) +{ + unsigned long flags; + + if (!gdev) + return; + + if (gdev->misc_registered) { + misc_deregister(&gdev->miscdev); + gdev->misc_registered =3D false; + } + + if (!gdev->rx_fifo_ready) + return; + + spin_lock_irqsave(&gdev->rx_fifo_lock, flags); + gdev->rx_fifo_ready =3D false; + gdev->rx_reader_open =3D false; + kfifo_reset(&gdev->rx_fifo); + spin_unlock_irqrestore(&gdev->rx_fifo_lock, flags); + + wake_up_interruptible_all(&gdev->rx_wait); + kfifo_free(&gdev->rx_fifo); +} diff --git a/include/uapi/linux/goodix_ec.h b/include/uapi/linux/goodix_ec.h new file mode 100644 index 000000000000..ff8d0d505ae7 --- /dev/null +++ b/include/uapi/linux/goodix_ec.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_GOODIX_EC_H_ +#define _UAPI_GOODIX_EC_H_ + +#include +#include + +#define GOODIX_EC_UAPI_MAGIC 'G' +#define GOODIX_EC_UAPI_TX_MAX 500u +#define GOODIX_EC_UAPI_RX_MAX (128u * 1024u) + +/* + * read(2) returns one record: + * struct goodix_ec_record_header + * followed by len bytes of MP payload (normally one Goodix frame). + */ +struct goodix_ec_record_header { + __u32 len; + __u32 mp_type; + __u64 timestamp_ns; +}; + +/* + * write(2) accepts: + * struct goodix_ec_tx_header + * followed by payload_len bytes used as the MP payload. + */ +struct goodix_ec_tx_header { + __u8 mp_flags; + __u8 reserved; + __u16 payload_len; + __u32 flags; +}; + +#define GOODIX_EC_IOCTL_FLUSH_RX _IO(GOODIX_EC_UAPI_MAGIC, 0x11) + +#endif /* _UAPI_GOODIX_EC_H_ */ base-commit: 0d33d21e47d9dc66f91e44da3fc9220c74d93df7 --=20 2.55.0