From nobody Sat Jul 25 04:30:07 2026 Received: from mail-wm2-f11.google.com (mail-wm2-f11.google.com [74.125.225.139]) (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 EC61D279DB1 for ; Sat, 18 Jul 2026 08:14:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.139 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784362478; cv=none; b=qVSQWiOJ9ean4f3l/KaCczW6wzd+UQaQXebfOCFERSL/QcmEHJuXmoNHhbt5J41SVaC3TV1sP4gLII+d2VjlIJEzK26nRG2GYYtxPSJ5//QpQdQUc5iG7vYaOfxa+mSBcqaw2vQu+3OywqrQL4JfJFl8iESvmhEtWt0sYr6Cn2w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784362478; c=relaxed/simple; bh=FPYs5SDj6qjqumMg2k6zXtsnAbrUK4LVvGwGcEzDtRQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=cZ2lZqlGlgNxGmF0Iik5KzJ8IJUmZCbx4KBbFd42yyu2FwnJVOT1WuxcgFkDIbvVu9Zp5z9Zr//MUtE+7kVlgZPb1OQ5IvQ0dQQjXjTeqefuMzh34Q7e4kE4rSE4/+NVQ4YrgHeNCa7JTsJZuk599vu9K5TCEzpdKB2nVFOvza8= 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=K/2eUj2b; arc=none smtp.client-ip=74.125.225.139 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="K/2eUj2b" Received: by mail-wm2-f11.google.com with SMTP id 5b1f17b1804b1-493f7c4152dso1799645e9.1 for ; Sat, 18 Jul 2026 01:14:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784362475; x=1784967275; 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=ZpwfRCa3QpVuvv1KidkUVHXcti0UD4UJnC6FARGoVcU=; b=K/2eUj2bXP4ZysoFqvFxXAPmr0NAr85xqQqqc2JculX5b5u3yfYxxprOX+cLOAYKMq j7KjMFf4kuESws7j/x6WuU3TXRnXFhO7fAPc1/v9Z+xxeuLrc8N1X/3ik/yuNR5/sSny c15ZZXbR/jmngmGT6bhvV2x6IcFYZNnX1Q6D4L1fbWEtN6fKmS07DOldwAXGlEaw0jnD nE3f/jwFGWJ8e0b8yVjPvJ6113m4U5JgysE27VdhQaYd6JRqxv0T7DldYcaD6vl2qO3H 9NKnkTSBRHG3Dz2Imy/3/vJwb8MNBJGb1D3dCseRKVVaaWV6Qm4peAaVm0K+O2DwqqvS fX9w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784362475; x=1784967275; 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=ZpwfRCa3QpVuvv1KidkUVHXcti0UD4UJnC6FARGoVcU=; b=VU/Er95Aeq1JspEN/k6d9xhdyu5y9dp8+fjs8n7arGABWuCvIE0LFupk6Rc7fb4fH4 soPOdi58bffHKqIFCdipur2WKuUiQA5eFl18b6/lK18j/Uorh+349/qiEVEKRGiL7rLl Wh9Pqp2dxeeDhrxP3YF24YNsZmHNwSc210LfzcNaK099TAi5FjT370QQIdbQN3SD3On3 gjd5HrgZovtmbYiFw3S3/3mW73qwd2Q3wpaDtSxBAY3fKO3/MJKzcuNfNU5SZ/DIEgjE OEijEfw38biwoUpjYNTjtvgXJ5ZKxqgfNT/fFO0uEktpgkE3JLTdPnhHb7eEsTz7I4hR CbvQ== X-Gm-Message-State: AOJu0YxYdmQwDWtIapVfM4Bf+0vyxFjvSYohtq3XhgOLIS9uKr82KjMV Nx+6wcc3eR1mODZfSeo6CMAmSgqJ6vV/wDpUwQNSIygVcG5jg2UYPufkieU20M6d X-Gm-Gg: AfdE7ckEVKYWP3ymrAo/MYTVF7iZmFOK8vBGzB/GAFvLJlLRmpYizCBFxFxCC7F2cHd ieO+P+HaxmguuNB2qEm+IE9fTUhKSdaH4ir8/7ESb436URg4cimO4GL0tdSoCeEqWkRJAnNIzPw HWvCYQEdARdyTVF7iHHXG/I4bXqnH+BcKsrW8hVrNEubh2BI4e0bmlFWdMah1ey2jhJpN+Q0nc6 ax0/myZXXe0qSFptWdH8pr9E5BMkReFR5X45OBuesgFqGBVQLGBKo36A98BBRi345Pdi8aIKP4g EBOHVlc0r1fP6xSOsu+tUNYKDmYBec5T0hdbDAzbkbnoIRPFACM1m6InP+hK5V0prgMou2ek27Q nzq+XZ86Z9YBb0gKMSkX3h/SK7CyCaC4kW4qIVaBHgOZKT/Hvq4B2UxBRmhss64qWFlWswv09St G7mCXQ4qU1NxfD X-Received: by 2002:a05:600c:3553:b0:495:4a33:2c34 with SMTP id 5b1f17b1804b1-4954a39955emr38675365e9.0.1784362474895; Sat, 18 Jul 2026 01:14:34 -0700 (PDT) Received: from meto.local ([95.70.199.165]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4954995b78dsm73234285e9.0.2026.07.18.01.14.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 18 Jul 2026 01:14:34 -0700 (PDT) From: =?UTF-8?q?Metehan=20G=C3=BCnen?= To: linux-kernel@vger.kernel.org Cc: gregkh@linuxfoundation.org, linux-acpi@vger.kernel.org, linux-gpio@vger.kernel.org, =?UTF-8?q?Metehan=20G=C3=BCnen?= Subject: [PATCH 1/4] include/uapi/linux: add gxfp_ioctl.h for GXFP5130 fingerprint sensor Date: Sat, 18 Jul 2026 11:14:28 +0300 Message-ID: <20260718081431.25456-2-metehangnen@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260718081431.25456-1-metehangnen@gmail.com> References: <20260718081431.25456-1-metehangnen@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 Define the userspace ABI for the Goodix GXFP5130 eSPI fingerprint sensor driver: - struct gxfp_tap_hdr: header prepended to every read(2) record, carries payload length, MP protocol type and a ktime_get_ns() timestamp. - struct gxfp_tx_pkt_hdr: header that userspace prepends to write(2) payloads so the driver can wrap them in the eSPI frame format. - GXFP_IOCTL_FLUSH_RXQ: flush the kernel-side RX record queue. Signed-off-by: Metehan G=C3=BCnen --- include/uapi/linux/gxfp_ioctl.h | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 include/uapi/linux/gxfp_ioctl.h diff --git a/include/uapi/linux/gxfp_ioctl.h b/include/uapi/linux/gxfp_ioct= l.h new file mode 100644 index 000000000..c9c6c2d48 --- /dev/null +++ b/include/uapi/linux/gxfp_ioctl.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __UAPI_GXFP_IOCTL_H +#define __UAPI_GXFP_IOCTL_H + +#include +#include + +#define GXFP_IOCTL_MAGIC 'G' + +/* + * 4-byte MP header + payload must fit GXFP_TX_BUFFER_SIZE(512). + */ +#define GXFP_IOCTL_TX_PAYLOAD_MAX 508u + +/* Maximum protocol payload bytes exported via read(2) records. */ +#define GXFP_IOCTL_TAP_PAYLOAD_MAX (128u * 1024u) + +/* + * Stream tap (read/poll) record header. + * Followed by `len` bytes of protocol payload. + */ +struct gxfp_tap_hdr { + __u32 len; /* protocol payload bytes */ + __u32 type; /* MP type */ + __u32 _rsvd0; + __u64 ts_ns; /* ktime_get_ns() */ + __u8 head16[16]; /* first 16 bytes of payload */ +}; + +/* + * write(2) packet header. + * Userspace writes: [gxfp_tx_pkt_hdr][payload bytes] + */ +struct gxfp_tx_pkt_hdr { + __u8 mp_flags; + __u8 _pad0; + __u16 payload_len; + __u32 flags; +}; + +#define GXFP_IOCTL_FLUSH_RXQ _IO(GXFP_IOCTL_MAGIC, 0x11) + +#endif /* __UAPI_GXFP_IOCTL_H */ --=20 2.55.0 From nobody Sat Jul 25 04:30:07 2026 Received: from mail-wm2-f2.google.com (mail-wm2-f2.google.com [74.125.225.130]) (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 A136F2DECDE for ; Sat, 18 Jul 2026 08:14:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.130 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784362488; cv=none; b=Lc/sEm43I72qygkAqO/dRaZjruLDHUUnLfY9ZNIUZQqnKV3yRuVU1sjcOmN56YenErWkTJiPs5INm0CgZho5r3s/b8kay0Dylrs9r0ixvEpaM3ak92UGJ3avRB/ZEobMoeiYZDY2SEGRMmKLeCqntkpNFYQu0lgTwTl+Rsb8mto= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784362488; c=relaxed/simple; bh=7mqqUmKOwojj90QBKxQo5976QWxtr3z75Np0e6HDPZQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=gEzZRodKRHDlkadQYxUqSheGVzmChkv9gZeJqpi0TTeWLS4uVU97LxMOi3tA0JCKpxMHQ74iaamNP8SipLwYO/35Cspmx9RNHn5xckyPWo6DT6xHQm8taCrobdLVbFeHnp1qVC81Vv3GQd9PMNA7xYozxTgk4wwR8ZaWR7crzYg= 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=DiCKcQs0; arc=none smtp.client-ip=74.125.225.130 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="DiCKcQs0" Received: by mail-wm2-f2.google.com with SMTP id 5b1f17b1804b1-49266a8770aso2702505e9.0 for ; Sat, 18 Jul 2026 01:14:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784362479; x=1784967279; 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=XndUYw6uMF7rNMbIkhITXAtIJkErIj2hiUmmdPXXLA4=; b=DiCKcQs0+4c8zrP5rZwxayGcnYSrAWP1B5JVzYSokjPm6TrCjSTG3asc6FyIKNpI5r TjFhs3HrERk/BH9RIrnSzXbmtz9suXK0/nlCCt31X0IzjSV0sfTiyPTFjmnQWJeHfvya x6vOsTbn4rUgPsbNwpPAcnID8ThOjGVCKyUu2OsSeR68/uf64wBzb5eegxDBSsTjIrEx tQiMM6ceVKxAzvRdUI35qkk5KBNJ5zAXCPcb6cQ0xZxwz2vXMYq5gvtByJmJEZZEzsEJ 2uGDPCqrKt5/KHYlb3LnMOTkYnv7eM+1Uoss5SiEWgH2+oIybT3E8NQkdqPKrxeZiVER AKIg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784362479; x=1784967279; 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=XndUYw6uMF7rNMbIkhITXAtIJkErIj2hiUmmdPXXLA4=; b=RpgrFjSysaI9TgooAPycJqZG7IDWaAWnrwimin7Wze4yBMdM3qamwbGAwBwAc/ke1I FBnxPDhJid94kWOne1prasG5FXlFn5SpGe1ucw3Wutmoz9TaZWm1WEcweaeLdFVv2i9z kly/4uALEz7PmGPRTakfHk1ycZ4stVny5TyrlH1InagIdWhru3cXRSqc+s/gFW4MuXET +7bOGZzmTq7HLbLO29y+eBnXj0F1QVYVYFChj3r2WpxxGZPFKxMJ57d/Vt6m+AFhxgFb PTzhPAZAn2qtAIc1DlzjwZvPnxteN/oQkAdIKig1brecM1mRlJI6AU9veFJ/NUUk3cAj Skcw== X-Gm-Message-State: AOJu0Yy8xMT2y1YQ1roFbg2S06w97MN5h5H5wljj1fMzJSWOfhVGWGWP We/2UcbaOsPuqJ9Nl30S3+W14FJAAyCm5Vfo3qrotmlotQSaotcKkI9YGnxAo64u X-Gm-Gg: AfdE7cmLSZPFYPDVcuKqka5tbygpqGV25XI+SpkZft/JPUnr4AblNQfsB15QLUwDfKF OarTaMzZeaUvTF+Z3XbPqHAe6w+ZUIGYH2g7r/pwA8gHypdN/h8ocW3T5+UG3VTq+PRwf4CSPzt PMEafleQ5lQlRSRo0pUS7d0hPMfkG3M3/SYQ12kjmPBWTsG3DVR2r6WL+xWfU0pKvXMu8x8ix/Q M0QE6hs3y03koPAnYkQiCm2FETLMmi530z94bwvlI8KYRkqYFkHBCgxfYVD9o60YOqtZHviWzOx wP7/eygnYZ9j7sJhCRrloAmxnUC06CNDVEBOAYEKRueI1NGwggJSl87C4jCKgq+lnIbObe22G2x Kuf+NMm0JU+Y/auWUONKxUIGLUwK+6PaMMY6lQ0tvB5EyvF2txsYTctG4wGVcGvfowpuRuXuGcQ c/CMIdRgDM84JU X-Received: by 2002:a05:600c:4fc9:b0:495:45fd:be41 with SMTP id 5b1f17b1804b1-4954a51322amr39341955e9.5.1784362477416; Sat, 18 Jul 2026 01:14:37 -0700 (PDT) Received: from meto.local ([95.70.199.165]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4954995b78dsm73234285e9.0.2026.07.18.01.14.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 18 Jul 2026 01:14:35 -0700 (PDT) From: =?UTF-8?q?Metehan=20G=C3=BCnen?= To: linux-kernel@vger.kernel.org Cc: gregkh@linuxfoundation.org, linux-acpi@vger.kernel.org, linux-gpio@vger.kernel.org, =?UTF-8?q?Metehan=20G=C3=BCnen?= Subject: [PATCH 2/4] drivers/misc: add Goodix GXFP5130 eSPI fingerprint sensor driver Date: Sat, 18 Jul 2026 11:14:29 +0300 Message-ID: <20260718081431.25456-3-metehangnen@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260718081431.25456-1-metehangnen@gmail.com> References: <20260718081431.25456-1-metehangnen@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 The GXFP5130 is a press-type fingerprint sensor by Goodix Technology. It is found in Huawei MateBook laptops (D16 2024, X Pro 2024, 14 2024) and is enumerated through ACPI with HID GXFP5130. Hardware interface: The sensor does not sit on SPI or I2C. The ACPI firmware maps a memory window (eSPI flash-channel mailbox) and three GPIOs: write-done (host pulses after writing a command), read-done (host pulses after reading each response chunk), and irq (sensor asserts to signal a response is ready). All frames use a 4-byte eSPI wrapper header followed by a Goodix MP protocol frame carrying the command body. Driver structure: transport/ eSPI framing layer (TX, synchronous RX, IRQ-driven RX) hw/ ACPI resource parsing, MMIO helpers, GPIO helpers, delay proto/ MP and Goodix command protocol encode/decode cmd/ individual command helpers (version, MCU state, reset ...) driver/ platform probe/remove, IRQ thread, UAPI misc device, debugfs trace ring buffer include/ private driver headers (gxfp_priv.h, gxfp_constants.h) Userspace interface: A misc character device /dev/gxfp is created. Only one reader at a time is allowed (CAP_SYS_ADMIN required). write(2) sends command frames; read(2)/poll(2) return response records prefixed with gxfp_tap_hdr. GXFP_IOCTL_FLUSH_RXQ discards the kernel-side queue. The libfprint library communicates with the sensor through this device. Startup: On probe the driver executes a firmware-version handshake (up to 3 retries) and falls back to a session-recovery sequence if the sensor does not acknowledge the first attempt. The IRQ line is kept masked until the handshake succeeds. Signed-off-by: Metehan G=C3=BCnen --- drivers/misc/Kconfig | 2 + drivers/misc/Makefile | 2 + drivers/misc/gxfp5130/Kconfig | 18 ++ drivers/misc/gxfp5130/Makefile | 28 ++ drivers/misc/gxfp5130/cmd/gxfp_cmd.c | 177 ++++++++++++ drivers/misc/gxfp5130/cmd/gxfp_cmd.h | 54 ++++ .../misc/gxfp5130/cmd/gxfp_cmd_mcu_config.c | 28 ++ .../misc/gxfp5130/cmd/gxfp_cmd_mcu_config.h | 12 + .../misc/gxfp5130/cmd/gxfp_cmd_mcu_state.c | 89 +++++++ .../misc/gxfp5130/cmd/gxfp_cmd_mcu_state.h | 30 +++ drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.c | 185 +++++++++++++ drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.h | 27 ++ drivers/misc/gxfp5130/cmd/gxfp_cmd_version.c | 100 +++++++ drivers/misc/gxfp5130/cmd/gxfp_cmd_version.h | 22 ++ drivers/misc/gxfp5130/driver/gxfp_irq.c | 57 ++++ drivers/misc/gxfp5130/driver/gxfp_irq.h | 9 + drivers/misc/gxfp5130/driver/gxfp_platform.c | 251 ++++++++++++++++++ drivers/misc/gxfp5130/driver/gxfp_platform.h | 12 + drivers/misc/gxfp5130/driver/gxfp_trace.c | 179 +++++++++++++ drivers/misc/gxfp5130/driver/gxfp_trace.h | 15 ++ drivers/misc/gxfp5130/driver/gxfp_uapi.h | 25 ++ drivers/misc/gxfp5130/driver/gxfp_uapi_ctrl.c | 136 ++++++++++ drivers/misc/gxfp5130/driver/gxfp_uapi_fifo.c | 245 +++++++++++++++++ drivers/misc/gxfp5130/driver/gxfp_uapi_misc.c | 123 +++++++++ drivers/misc/gxfp5130/gxfp_main.c | 127 +++++++++ drivers/misc/gxfp5130/hw/gxfp_acpi.c | 130 +++++++++ drivers/misc/gxfp5130/hw/gxfp_acpi.h | 15 ++ drivers/misc/gxfp5130/hw/gxfp_delay.c | 15 ++ drivers/misc/gxfp5130/hw/gxfp_delay.h | 7 + drivers/misc/gxfp5130/hw/gxfp_gpio.c | 64 +++++ drivers/misc/gxfp5130/hw/gxfp_gpio.h | 17 ++ drivers/misc/gxfp5130/hw/gxfp_mmio.c | 45 ++++ drivers/misc/gxfp5130/hw/gxfp_mmio.h | 10 + .../misc/gxfp5130/include/gxfp_constants.h | 38 +++ drivers/misc/gxfp5130/include/gxfp_priv.h | 55 ++++ .../misc/gxfp5130/proto/gxfp_goodix_proto.c | 84 ++++++ .../misc/gxfp5130/proto/gxfp_goodix_proto.h | 31 +++ drivers/misc/gxfp5130/proto/gxfp_mp_proto.c | 96 +++++++ drivers/misc/gxfp5130/proto/gxfp_mp_proto.h | 27 ++ .../gxfp5130/transport/gxfp_espi_common.c | 80 ++++++ .../gxfp5130/transport/gxfp_espi_common.h | 27 ++ .../gxfp5130/transport/gxfp_espi_rx_irq.c | 120 +++++++++ .../gxfp5130/transport/gxfp_espi_rx_irq.h | 15 ++ .../gxfp5130/transport/gxfp_espi_rx_sync.c | 96 +++++++ .../gxfp5130/transport/gxfp_espi_rx_sync.h | 14 + .../misc/gxfp5130/transport/gxfp_espi_tx.c | 88 ++++++ .../misc/gxfp5130/transport/gxfp_espi_tx.h | 20 ++ 47 files changed, 3047 insertions(+) create mode 100644 drivers/misc/gxfp5130/Kconfig create mode 100644 drivers/misc/gxfp5130/Makefile create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd.c create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd.h create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.c create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.h create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.c create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.h create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.c create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.h create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_version.c create mode 100644 drivers/misc/gxfp5130/cmd/gxfp_cmd_version.h create mode 100644 drivers/misc/gxfp5130/driver/gxfp_irq.c create mode 100644 drivers/misc/gxfp5130/driver/gxfp_irq.h create mode 100644 drivers/misc/gxfp5130/driver/gxfp_platform.c create mode 100644 drivers/misc/gxfp5130/driver/gxfp_platform.h create mode 100644 drivers/misc/gxfp5130/driver/gxfp_trace.c create mode 100644 drivers/misc/gxfp5130/driver/gxfp_trace.h create mode 100644 drivers/misc/gxfp5130/driver/gxfp_uapi.h create mode 100644 drivers/misc/gxfp5130/driver/gxfp_uapi_ctrl.c create mode 100644 drivers/misc/gxfp5130/driver/gxfp_uapi_fifo.c create mode 100644 drivers/misc/gxfp5130/driver/gxfp_uapi_misc.c create mode 100644 drivers/misc/gxfp5130/gxfp_main.c create mode 100644 drivers/misc/gxfp5130/hw/gxfp_acpi.c create mode 100644 drivers/misc/gxfp5130/hw/gxfp_acpi.h create mode 100644 drivers/misc/gxfp5130/hw/gxfp_delay.c create mode 100644 drivers/misc/gxfp5130/hw/gxfp_delay.h create mode 100644 drivers/misc/gxfp5130/hw/gxfp_gpio.c create mode 100644 drivers/misc/gxfp5130/hw/gxfp_gpio.h create mode 100644 drivers/misc/gxfp5130/hw/gxfp_mmio.c create mode 100644 drivers/misc/gxfp5130/hw/gxfp_mmio.h create mode 100644 drivers/misc/gxfp5130/include/gxfp_constants.h create mode 100644 drivers/misc/gxfp5130/include/gxfp_priv.h create mode 100644 drivers/misc/gxfp5130/proto/gxfp_goodix_proto.c create mode 100644 drivers/misc/gxfp5130/proto/gxfp_goodix_proto.h create mode 100644 drivers/misc/gxfp5130/proto/gxfp_mp_proto.c create mode 100644 drivers/misc/gxfp5130/proto/gxfp_mp_proto.h create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_common.c create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_common.h create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.c create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.h create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.c create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.h create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_tx.c create mode 100644 drivers/misc/gxfp5130/transport/gxfp_espi_tx.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 390256ed9..5b20aa292 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -651,4 +651,6 @@ source "drivers/misc/mchp_pci1xxxx/Kconfig" source "drivers/misc/keba/Kconfig" source "drivers/misc/amd-sbi/Kconfig" source "drivers/misc/rp1/Kconfig" +source "drivers/misc/gxfp5130/Kconfig" + endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index fed47c767..e85c68aa1 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -74,3 +74,5 @@ obj-$(CONFIG_MCHP_LAN966X_PCI) +=3D lan966x-pci.o obj-y +=3D keba/ obj-y +=3D amd-sbi/ obj-$(CONFIG_MISC_RP1) +=3D rp1/ + +obj-$(CONFIG_GXFP5130) +=3D gxfp5130/ diff --git a/drivers/misc/gxfp5130/Kconfig b/drivers/misc/gxfp5130/Kconfig new file mode 100644 index 000000000..0af2a0dec --- /dev/null +++ b/drivers/misc/gxfp5130/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +config GXFP5130 + tristate "Goodix GXFP5130 eSPI fingerprint sensor" + depends on ACPI && HAS_IOMEM && GPIOLIB + help + Driver for the Goodix GXFP5130 fingerprint sensor found in + Huawei MateBook laptops (MateBook D16 2024, MateBook X Pro 2024, + MateBook 14 2024). + + The sensor connects to the host via an eSPI-based MMIO mailbox + and is enumerated through ACPI (ACPI HID: GXFP5130). + + A character device (/dev/gxfp) is created for userspace access. + The libfprint library uses this interface for biometric enrollment + and verification via fprintd. + + If unsure, say N. To compile as a module, say M; the module + will be called gxfp. diff --git a/drivers/misc/gxfp5130/Makefile b/drivers/misc/gxfp5130/Makefile new file mode 100644 index 000000000..0bb206d23 --- /dev/null +++ b/drivers/misc/gxfp5130/Makefile @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_GXFP5130) +=3D gxfp.o + +ccflags-y +=3D -I$(src)/include + +gxfp-y :=3D \ + gxfp_main.o \ + transport/gxfp_espi_common.o \ + transport/gxfp_espi_tx.o \ + transport/gxfp_espi_rx_sync.o \ + transport/gxfp_espi_rx_irq.o \ + hw/gxfp_acpi.o \ + hw/gxfp_delay.o \ + hw/gxfp_mmio.o \ + hw/gxfp_gpio.o \ + proto/gxfp_mp_proto.o \ + proto/gxfp_goodix_proto.o \ + cmd/gxfp_cmd.o \ + cmd/gxfp_cmd_version.o \ + cmd/gxfp_cmd_mcu_state.o \ + cmd/gxfp_cmd_mcu_config.o \ + cmd/gxfp_cmd_reset.o \ + driver/gxfp_platform.o \ + driver/gxfp_irq.o \ + driver/gxfp_trace.o \ + driver/gxfp_uapi_fifo.o \ + driver/gxfp_uapi_ctrl.o \ + driver/gxfp_uapi_misc.o diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd.c b/drivers/misc/gxfp5130/c= md/gxfp_cmd.c new file mode 100644 index 000000000..90a50710f --- /dev/null +++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +#include "gxfp_cmd.h" +#include "../proto/gxfp_goodix_proto.h" +#include "../proto/gxfp_mp_proto.h" +#include "../transport/gxfp_espi_tx.h" +#include "../hw/gxfp_delay.h" +#include "gxfp_priv.h" +#include "gxfp_constants.h" +#include "../driver/gxfp_trace.h" + +int gxfp_xfer(struct gxfp_dev *gdev, const struct gxfp_xfer_req *req) +{ + __u8 *tx; + __u8 *body; + __u8 *rx; + size_t rx_len =3D 0; + unsigned int t; + int body_len; + int frame_len; + int ret; + + if (!gdev || !req) + return -EINVAL; + if (req->req_payload_len && !req->req_payload) + return -EINVAL; + if (req->resp_payload && req->resp_payload_cap =3D=3D 0) + return -EINVAL; + if (!req->resp_payload && req->resp_payload_cap !=3D 0) + return -EINVAL; + + tx =3D kmalloc(GXFP_TX_BUFFER_SIZE, GFP_KERNEL); + if (!tx) + return -ENOMEM; + body =3D kmalloc(GXFP_TX_BUFFER_SIZE, GFP_KERNEL); + if (!body) { + ret =3D -ENOMEM; + goto out_free_tx; + } + + rx =3D kmalloc(GXFP_MAILBOX_MIN_SIZE, GFP_KERNEL); + if (!rx) { + ret =3D -ENOMEM; + goto out_free_body; + } + + body_len =3D gxfp_goodix_build_frame(req->req_cmd, + req->req_payload, req->req_payload_len, + body, GXFP_TX_BUFFER_SIZE); + if (body_len < 0) { + ret =3D body_len; + goto out_free_rx; + } + + frame_len =3D gxfp_mp_build_frame(GXFP_MP_TYPE_A, + body, (size_t)body_len, + tx, GXFP_TX_BUFFER_SIZE); + if (frame_len < 0) { + ret =3D frame_len; + goto out_free_rx; + } + gxfp_trace_logf("cmd_req req=3D0x%02x expect=3D0x%02x tries=3D%u tx_mp_le= n=3D%d", + req->req_cmd, req->expect_cmd, req->tries ? req->tries : 1, frame_len); + + for (t =3D 0; t < (req->tries ? req->tries : 1); t++) { + struct gxfp_mp_frame_parsed mp; + struct gxfp_frame_parsed p; + + ret =3D gxfp_espi_xfer(gdev, tx, (size_t)frame_len, + rx, GXFP_MAILBOX_MIN_SIZE, &rx_len); + if (ret) { + gxfp_trace_logf("cmd_retry req=3D0x%02x expect=3D0x%02x try=3D%u rc=3D%= d", + req->req_cmd, req->expect_cmd, t + 1, ret); + gxfp_busy_wait_us(req->retry_delay_us); + continue; + } + + if (!gxfp_parse_mp_frame(rx, + min_t(size_t, rx_len, (size_t)GXFP_MAILBOX_MIN_SIZE), + &mp)) { + gxfp_trace_logf("cmd_bad_mp req=3D0x%02x expect=3D0x%02x try=3D%u rx_le= n=3D%zu", + req->req_cmd, req->expect_cmd, t + 1, rx_len); + gxfp_busy_wait_us(req->retry_delay_us); + continue; + } + if (!gxfp_parse_goodix_body(mp.payload, mp.payload_len, &p) || !p.valid)= { + gxfp_busy_wait_us(req->retry_delay_us); + continue; + } + if (req->require_proto_ck_ok && !p.proto_checksum_ok) { + gxfp_busy_wait_us(req->retry_delay_us); + continue; + } + if (p.cmd !=3D req->expect_cmd) { + gxfp_trace_logf("cmd_unexpected req=3D0x%02x expect=3D0x%02x got=3D0x%0= 2x try=3D%u", + req->req_cmd, req->expect_cmd, p.cmd, t + 1); + gxfp_busy_wait_us(req->retry_delay_us); + continue; + } + + if (req->resp_payload) { + if (p.payload_len > req->resp_payload_cap) { + ret =3D -EMSGSIZE; + goto out_free_rx; + } + memcpy(req->resp_payload, p.payload, p.payload_len); + } + + if (req->out_resp_payload_len) + *req->out_resp_payload_len =3D p.payload_len; + if (req->out_frame) { + *req->out_frame =3D p; + req->out_frame->payload =3D req->resp_payload; + } + ret =3D 0; + gxfp_trace_logf("cmd_ok req=3D0x%02x expect=3D0x%02x try=3D%u payload=3D= %u", + req->req_cmd, req->expect_cmd, t + 1, p.payload_len); + goto out_free_rx; + } + + ret =3D -ETIMEDOUT; + gxfp_trace_logf("cmd_timeout req=3D0x%02x expect=3D0x%02x tries=3D%u", + req->req_cmd, req->expect_cmd, req->tries ? req->tries : 1); + +out_free_rx: + kfree(rx); +out_free_body: + kfree(body); +out_free_tx: + kfree(tx); + return ret; +} + +int gxfp_cmd_xfer(struct gxfp_dev *gdev, + __u8 req_cmd, + const __u8 *req_payload, + size_t req_payload_len, + __u8 expect_cmd, + __u8 *resp_payload, + size_t resp_payload_cap, + size_t *out_resp_payload_len, + unsigned int tries, + unsigned int retry_delay_us) +{ + struct gxfp_frame_parsed p; + int ret; + + if (!resp_payload || resp_payload_cap =3D=3D 0) + return -EINVAL; + + memset(&p, 0, sizeof(p)); + ret =3D gxfp_xfer(gdev, &(const struct gxfp_xfer_req){ + .req_cmd =3D req_cmd, + .req_payload =3D req_payload, + .req_payload_len =3D req_payload_len, + .expect_cmd =3D expect_cmd, + .require_proto_ck_ok =3D true, + .out_frame =3D &p, + .resp_payload =3D resp_payload, + .resp_payload_cap =3D resp_payload_cap, + .out_resp_payload_len =3D out_resp_payload_len, + .tries =3D tries, + .retry_delay_us =3D retry_delay_us, + }); + if (ret) + return ret; + + dev_dbg(gdev->dev, + "CMD_XFER: req=3D0x%02x expect=3D0x%02x payload_len=3D%u\n", + req_cmd, expect_cmd, p.payload_len); + return 0; +} diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd.h b/drivers/misc/gxfp5130/c= md/gxfp_cmd.h new file mode 100644 index 000000000..7d7b5bba3 --- /dev/null +++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_CMD_H +#define __GXFP_CMD_H + +#include + +struct gxfp_dev; +struct gxfp_frame_parsed; + +/** + * struct gxfp_xfer_req - Parameters for a single Goodix command exchange. + * @req_cmd: Command byte to send. + * @expect_cmd: Expected command byte in the response frame. + * @require_proto_ck_ok: If true, require protocol checksum to pass. + * @tries: Number of send/receive attempts (0 treated as 1). + * @retry_delay_us: Microseconds to wait between retries. + * @req_payload: Pointer to request payload (may be NULL). + * @req_payload_len: Length of request payload in bytes. + * @out_frame: If non-NULL, receives parsed frame metadata on s= uccess. + * @resp_payload: If non-NULL, response payload is copied here. + * @resp_payload_cap: Capacity of @resp_payload buffer. + * @out_resp_payload_len: If non-NULL, receives actual response payload le= ngth. + */ +struct gxfp_xfer_req { + __u8 req_cmd; + __u8 expect_cmd; + bool require_proto_ck_ok; + unsigned int tries; + unsigned int retry_delay_us; + + const __u8 *req_payload; + size_t req_payload_len; + + struct gxfp_frame_parsed *out_frame; + + __u8 *resp_payload; + size_t resp_payload_cap; + size_t *out_resp_payload_len; +}; + +int gxfp_xfer(struct gxfp_dev *gdev, const struct gxfp_xfer_req *req); + +int gxfp_cmd_xfer(struct gxfp_dev *gdev, + __u8 req_cmd, + const __u8 *req_payload, + size_t req_payload_len, + __u8 expect_cmd, + __u8 *resp_payload, + size_t resp_payload_cap, + size_t *out_resp_payload_len, + unsigned int tries, + unsigned int retry_delay_us); + +#endif /* __GXFP_CMD_H */ diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.c b/drivers/misc= /gxfp5130/cmd/gxfp_cmd_mcu_config.c new file mode 100644 index 000000000..cc38d7d96 --- /dev/null +++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include + +#include "gxfp_priv.h" +#include "gxfp_cmd.h" +#include "gxfp_constants.h" +#include "gxfp_cmd_mcu_config.h" + +int gxfp_cmd_upload_config_mcu(struct gxfp_dev *gdev, const __u8 *cfg, siz= e_t cfg_len) +{ + __u8 ack_payload[32]; + size_t ack_len =3D 0; + + if (!gdev) + return -EINVAL; + if (!cfg || cfg_len =3D=3D 0) + return -EINVAL; + + return gxfp_cmd_xfer(gdev, + GXFP_CMD_UPLOAD_CONFIG_MCU, + cfg, cfg_len, + GXFP_CMD_ACK, + ack_payload, sizeof(ack_payload), + &ack_len, + GXFP_DEFAULT_RETRY_COUNT, + GXFP_DEFAULT_TIMEOUT_US); +} diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.h b/drivers/misc= /gxfp5130/cmd/gxfp_cmd_mcu_config.h new file mode 100644 index 000000000..9a32aa802 --- /dev/null +++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_config.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_CMD_MCU_CONFIG_H +#define __GXFP_CMD_MCU_CONFIG_H + +#include + +struct gxfp_dev; + +/* Not used or tested */ +int gxfp_cmd_upload_config_mcu(struct gxfp_dev *gdev, const __u8 *cfg, siz= e_t cfg_len); + +#endif /* __GXFP_CMD_MCU_CONFIG_H */ diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.c b/drivers/misc/= gxfp5130/cmd/gxfp_cmd_mcu_state.c new file mode 100644 index 000000000..d6a238474 --- /dev/null +++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include + +#include "gxfp_priv.h" +#include "gxfp_cmd.h" +#include "gxfp_constants.h" +#include "gxfp_cmd_mcu_state.h" + +int gxfp_cmd_parse_mcu_state_payload(const __u8 *buf, size_t len, + struct gxfp_mcu_state *out) +{ + if (!buf || !out) + return -EINVAL; + + memset(out, 0, sizeof(*out)); + if (len < GXFP_MCU_STATE_LEN) + return -EBADMSG; + + out->version =3D buf[0]; + + out->b1_raw =3D buf[1]; + out->is_pov_image_valid =3D (buf[1] >> 0) & 0x1; + out->is_tls_connected =3D (buf[1] >> 1) & 0x1; + out->is_tls_used =3D (buf[1] >> 2) & 0x1; + out->is_locked =3D (buf[1] >> 3) & 0x1; + + memcpy(out->reserved, buf + 2, sizeof(out->reserved)); + + return 0; +} + +static int gxfp_do_query_mcu_state(struct gxfp_dev *gdev, + struct gxfp_mcu_state *out) +{ + __u8 payload[GXFP_MAX_MCU_STATE_PAYLOAD]; + __u8 trigger_payload[5]; + size_t payload_len =3D 0; + int ret; + struct timespec64 ts; + __u16 ts_u16; + + if (!out) + return -EINVAL; + + memset(out, 0, sizeof(*out)); + + if (!gdev->hw.mailbox_mmio || gdev->hw.mailbox_size < GXFP_MAILBOX_MIN_SI= ZE) + return -ENODEV; + + ktime_get_real_ts64(&ts); + ts_u16 =3D (__u16)(((__u64)(ts.tv_sec % 60) * 1000ull) + ((__u64)ts.tv_ns= ec / 1000000ull)); + trigger_payload[0] =3D 0x55; + trigger_payload[1] =3D (__u8)(ts_u16 & 0xff); + trigger_payload[2] =3D (__u8)((ts_u16 >> 8) & 0xff); + trigger_payload[3] =3D 0x00; + trigger_payload[4] =3D 0x00; + dev_dbg(gdev->dev, "MCU_STATE: trigger ts=3D0x%04x payload=3D%*ph\n", + ts_u16, (int)sizeof(trigger_payload), trigger_payload); + + ret =3D gxfp_cmd_xfer(gdev, + GXFP_CMD_TRIGGER_MCU_STATE, trigger_payload, sizeof(trigger_payload), + GXFP_CMD_QUERY_MCU_STATE, + payload, sizeof(payload), &payload_len, + GXFP_DEFAULT_RETRY_COUNT, GXFP_DEFAULT_TIMEOUT_US); + if (ret) + return ret; + + ret =3D gxfp_cmd_parse_mcu_state_payload(payload, payload_len, out); + if (ret) + return ret; + + dev_dbg(gdev->dev, + "MCU_STATE: version=3D%u pov_valid=3D%u tls=3D%u locked=3D%u\n", + out->version, + out->is_pov_image_valid, + out->is_tls_connected, + out->is_locked); + return 0; +} + +int gxfp_cmd_query_mcu_state(struct gxfp_dev *gdev, struct gxfp_mcu_state = *out) +{ + return gxfp_do_query_mcu_state(gdev, out); +} diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.h b/drivers/misc/= gxfp5130/cmd/gxfp_cmd_mcu_state.h new file mode 100644 index 000000000..593b62499 --- /dev/null +++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_mcu_state.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_CMD_MCU_STATE_H +#define __GXFP_CMD_MCU_STATE_H + +#include +#include "gxfp_constants.h" + +struct gxfp_dev; + +#define GXFP_MCU_STATE_LEN 20 + +struct gxfp_mcu_state { + /* byte 0 */ + __u8 version; + + /* byte 1 */ + __u8 b1_raw; + __u8 is_pov_image_valid; + __u8 is_tls_connected; + __u8 is_tls_used; + __u8 is_locked; + + /* byte 2 - 19 */ + __u8 reserved[18]; +}; + +int gxfp_cmd_query_mcu_state(struct gxfp_dev *gdev, struct gxfp_mcu_state = *out); +int gxfp_cmd_parse_mcu_state_payload(const __u8 *buf, size_t len, struct g= xfp_mcu_state *out); + +#endif /* __GXFP_CMD_MCU_STATE_H */ diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.c b/drivers/misc/gxfp= 5130/cmd/gxfp_cmd_reset.c new file mode 100644 index 000000000..b2edba33a --- /dev/null +++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include + +#include "gxfp_priv.h" +#include "gxfp_cmd.h" +#include "../hw/gxfp_delay.h" +#include "gxfp_constants.h" + +#include "gxfp_cmd_reset.h" + +int gxfp_cmd_notify_power_state(struct gxfp_dev *gdev, + __u8 state) +{ + __u8 payload[1]; + __u8 ack_payload[8]; + size_t ack_len =3D 0; + int rc; + + if (!gdev) + return -EINVAL; + + payload[0] =3D state; + rc =3D gxfp_cmd_xfer(gdev, + GXFP_CMD_NOTIFY_POWER_STATE, + payload, sizeof(payload), + GXFP_CMD_ACK, + ack_payload, sizeof(ack_payload), &ack_len, + 1, + GXFP_DEFAULT_TIMEOUT_US); + + dev_dbg(gdev->dev, + "NOTIFY_POWER_STATE: cmd=3D0x0E state=3D%u rc=3D%d ack_len=3D%zu\n", + state, rc, ack_len); + return rc; +} + +int gxfp_cmd_sleep_cleanup(struct gxfp_dev *gdev, unsigned int post_delay_= ms) +{ + static const __u8 sleep_payload[2] =3D { 0x00, 0x00 }; + __u8 ack_payload[8]; + size_t ack_len =3D 0; + int rc; + + if (!gdev) + return -EINVAL; + + rc =3D gxfp_cmd_xfer(gdev, + GXFP_CMD_SLEEP_CLEANUP, + sleep_payload, sizeof(sleep_payload), + GXFP_CMD_ACK, + ack_payload, sizeof(ack_payload), &ack_len, + 1, + GXFP_DEFAULT_TIMEOUT_US); + dev_dbg(gdev->dev, "SLEEP/CLEANUP: cmd=3D0xD2 rc=3D%d ack_len=3D%zu\n", r= c, ack_len); + + if (post_delay_ms) + msleep(post_delay_ms); + + return rc; +} + +int gxfp_cmd_reset_device(struct gxfp_dev *gdev, + __u8 reset_flags) +{ + __u8 payload[2]; + __u8 ack_payload[8]; + size_t ack_len =3D 0; + int rc; + + if (!gdev) + return -EINVAL; + + payload[0] =3D reset_flags; + payload[1] =3D 0x14; + + rc =3D gxfp_cmd_xfer(gdev, + GXFP_CMD_RESET_DEVICE, + payload, sizeof(payload), + GXFP_CMD_ACK, + ack_payload, sizeof(ack_payload), &ack_len, + 1, + GXFP_DEFAULT_TIMEOUT_US); + dev_dbg(gdev->dev, + "RESET_DEVICE: cmd=3D0xA1 flags=3D0x%02x rc=3D%d ack_len=3D%zu\n", + reset_flags, rc, ack_len); + return rc; +} + +int gxfp_cmd_recover_session(struct gxfp_dev *gdev, bool unstick_tls) +{ + int rc =3D 0; + int r; + unsigned int tries; + + if (!gdev) + return -EINVAL; + + /* D0Exit notifies MCU power state */ + for (tries =3D 0; tries < 3; tries++) { + r =3D gxfp_cmd_notify_power_state(gdev, 1); + if (!r) + break; + if ((tries + 1) < 3) + usleep_range(10000, 11000); + } + if (r && !rc) + rc =3D r; + gxfp_busy_wait_us(GXFP_RESET_DELAY_US); + + if (unstick_tls) { + r =3D gxfp_cmd_protocol_init(gdev); + if (r && !rc) + rc =3D r; + gxfp_busy_wait_us(1000); + + r =3D gxfp_cmd_tls_unlock(gdev); + if (r && !rc) + rc =3D r; + gxfp_busy_wait_us(1000); + } + + r =3D gxfp_cmd_sleep_cleanup(gdev, 200); + if (r && !rc) + rc =3D r; + + for (tries =3D 0; tries < 6; tries++) { + r =3D gxfp_cmd_reset_device(gdev, GXFP_RESET_FLAG_MCU_SENSOR); + if (!r) + break; + if ((tries + 1) < 6) + gxfp_busy_wait_us(500000); + } + if (r && !rc) + rc =3D r; + gxfp_busy_wait_us(GXFP_RESET_DELAY_US); + + dev_dbg(gdev->dev, "RECOVER_SESSION: unstick_tls=3D%u rc=3D%d\n", unstick= _tls ? 1 : 0, rc); + return rc; +} + +int gxfp_cmd_tls_unlock(struct gxfp_dev *gdev) +{ + __u8 payload[2] =3D { 0x00, 0x00 }; + __u8 ack_payload[8]; + size_t ack_len =3D 0; + int rc; + + if (!gdev) + return -EINVAL; + + rc =3D gxfp_cmd_xfer(gdev, + GXFP_CMD_TLS_UNLOCK, + payload, sizeof(payload), + GXFP_CMD_ACK, + ack_payload, sizeof(ack_payload), &ack_len, + 1, + GXFP_DEFAULT_TIMEOUT_US); + + dev_dbg(gdev->dev, "TLS_UNLOCK: cmd=3D0xD4 rc=3D%d ack_len=3D%zu\n", rc, = ack_len); + return rc; +} + +int gxfp_cmd_protocol_init(struct gxfp_dev *gdev) +{ + __u8 payload[4] =3D { 0x00, 0x00, 0x00, 0x00 }; + __u8 ack_payload[8]; + size_t ack_len =3D 0; + int rc; + + if (!gdev) + return -EINVAL; + + rc =3D gxfp_cmd_xfer(gdev, + GXFP_CMD_PROTOCOL_INIT, + payload, sizeof(payload), + GXFP_CMD_ACK, + ack_payload, sizeof(ack_payload), &ack_len, + 1, + GXFP_DEFAULT_TIMEOUT_US); + + dev_dbg(gdev->dev, "PROTO_INIT: cmd=3D0x01 rc=3D%d ack_len=3D%zu\n", rc, = ack_len); + return rc; +} diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.h b/drivers/misc/gxfp= 5130/cmd/gxfp_cmd_reset.h new file mode 100644 index 000000000..29f7434c1 --- /dev/null +++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_reset.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_CMD_RESET_H +#define __GXFP_CMD_RESET_H + +#include + +struct gxfp_dev; + +#define GXFP_RESET_FLAG_SENSOR 0x01u +#define GXFP_RESET_FLAG_MCU 0x02u +#define GXFP_RESET_FLAG_MCU_SENSOR (GXFP_RESET_FLAG_MCU | GXFP_RESET_FLAG_= SENSOR) + +int gxfp_cmd_sleep_cleanup(struct gxfp_dev *gdev, unsigned int post_delay_= ms); + +int gxfp_cmd_notify_power_state(struct gxfp_dev *gdev, + __u8 state); + +int gxfp_cmd_reset_device(struct gxfp_dev *gdev, + __u8 reset_flags); + +int gxfp_cmd_tls_unlock(struct gxfp_dev *gdev); + +int gxfp_cmd_protocol_init(struct gxfp_dev *gdev); + +int gxfp_cmd_recover_session(struct gxfp_dev *gdev, bool unstick_tls); + +#endif /* __GXFP_CMD_RESET_H */ diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_version.c b/drivers/misc/gx= fp5130/cmd/gxfp_cmd_version.c new file mode 100644 index 000000000..7ea28e965 --- /dev/null +++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_version.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include + +#include "gxfp_priv.h" +#include "gxfp_cmd.h" +#include "../hw/gxfp_delay.h" +#include "gxfp_constants.h" +#include "gxfp_cmd_version.h" + +static bool gxfp_extract_ascii_version(const __u8 *data, char *out, size_t= out_len) +{ + size_t j; + + if (!out || out_len =3D=3D 0) + return false; + out[0] =3D '\0'; + + if (!data) + return false; + + for (j =3D 0; (j + 1) < out_len; j++) { + __u8 ch =3D data[j]; + + if (ch =3D=3D 0 || ch < 0x20 || ch > 0x7e) + break; + out[j] =3D (char)ch; + } + out[j] =3D '\0'; + + return j !=3D 0; +} + +static int gxfp_do_get_version(struct gxfp_dev *gdev, + struct gxfp_get_version_result *out) +{ + static const __u8 req_payload[] =3D { 0x01 }; + __u8 payload[GXFP_MAX_VERSION_RAW]; + size_t rx_len =3D 0; + int ret; + + memset(out, 0, sizeof(*out)); + out->status =3D (__u32)-EIO; + out->gpio_status =3D (__u32)-EIO; + out->tx_status =3D (__u32)-EIO; + out->rx_status =3D (__u32)-EIO; + + if (!gdev->hw.mailbox_mmio || gdev->hw.mailbox_size < GXFP_MAILBOX_MIN_SI= ZE) { + out->status =3D (__u32)-ENODEV; + out->gpio_status =3D (__u32)-ENODEV; + out->tx_status =3D (__u32)-ENODEV; + out->rx_status =3D (__u32)-ENODEV; + return -ENODEV; + } + + ret =3D gxfp_cmd_xfer(gdev, + GXFP_CMD_FIRMWARE_VERSION, + req_payload, + sizeof(req_payload), + GXFP_CMD_FIRMWARE_VERSION, + payload, + sizeof(payload), + &rx_len, + GXFP_DEFAULT_RETRY_COUNT, + GXFP_DEFAULT_TIMEOUT_US); + out->tx_status =3D 0; + out->gpio_status =3D 0; + out->rx_status =3D (__u32)(ret ? ret : 0); + if (ret) { + out->status =3D (__u32)ret; + return ret; + } + + out->bytes_read =3D (__u32)min_t(size_t, rx_len, sizeof(out->raw)); + memset(out->raw, 0, sizeof(out->raw)); + memcpy(out->raw, payload, out->bytes_read); + dev_dbg(gdev->dev, + "GET_VERSION: payload_len=3D%zu head16=3D%*ph\n", + rx_len, 16, out->raw); + + (void)gxfp_extract_ascii_version(payload, + out->version_ascii, + sizeof(out->version_ascii)); + if (out->version_ascii[0] =3D=3D '\0') { + out->status =3D (__u32)-ENODATA; + return 0; + } + + out->status =3D 0; + return 0; +} + +int gxfp_cmd_get_version(struct gxfp_dev *gdev, struct gxfp_get_version_re= sult *out) +{ + return gxfp_do_get_version(gdev, out); +} diff --git a/drivers/misc/gxfp5130/cmd/gxfp_cmd_version.h b/drivers/misc/gx= fp5130/cmd/gxfp_cmd_version.h new file mode 100644 index 000000000..8f619ab3a --- /dev/null +++ b/drivers/misc/gxfp5130/cmd/gxfp_cmd_version.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_CMD_VERSION_H +#define __GXFP_CMD_VERSION_H + +#include +#include "gxfp_constants.h" + +struct gxfp_dev; + +struct gxfp_get_version_result { + __u32 status; + __u32 gpio_status; + __u32 tx_status; + __u32 rx_status; + __u32 bytes_read; + char version_ascii[GXFP_MAX_VERSION_ASCII]; + __u8 raw[GXFP_MAX_VERSION_RAW]; +}; + +int gxfp_cmd_get_version(struct gxfp_dev *gdev, struct gxfp_get_version_re= sult *out); + +#endif /* __GXFP_CMD_VERSION_H */ diff --git a/drivers/misc/gxfp5130/driver/gxfp_irq.c b/drivers/misc/gxfp513= 0/driver/gxfp_irq.c new file mode 100644 index 000000000..09ad2aaaa --- /dev/null +++ b/drivers/misc/gxfp5130/driver/gxfp_irq.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +#include "gxfp_priv.h" +#include "../transport/gxfp_espi_rx_irq.h" + +#include "gxfp_irq.h" +#include "gxfp_uapi.h" +#include "gxfp_trace.h" + +#include "../hw/gxfp_gpio.h" + +irqreturn_t gxfp_irq_thread(int irq, void *data) +{ + struct gxfp_dev *gdev =3D data; + size_t rx_len =3D 0; + int ret; + bool complete =3D false; + bool push =3D false; + + if (!gdev || !gdev->irq.rx_buf || gdev->irq.rx_cap < 16) + return IRQ_NONE; + + mutex_lock(&gdev->lock); + + ret =3D gxfp_espi_irq_read_step(gdev, gdev->irq.rx_buf, gdev->irq.rx_cap, + &rx_len, &complete); + if (ret) { + gxfp_trace_logf("irq_read_step rc=3D%d", ret); + goto out_clear; + } + + if (!complete) + goto out_clear; + + if (rx_len =3D=3D 0) + goto out_clear; + + push =3D true; + +out_clear: + gxfp_gpio_set_read_done(gdev, 0); + mutex_unlock(&gdev->lock); + + if (push) + gxfp_trace_logf("irq_rx_complete len=3D%zu head=3D%*ph", rx_len, + (int)min_t(size_t, 8, rx_len), gdev->irq.rx_buf); + + if (push) + gxfp_uapi_rxq_push_packet(gdev, gdev->irq.rx_buf, rx_len); + + return IRQ_HANDLED; +} diff --git a/drivers/misc/gxfp5130/driver/gxfp_irq.h b/drivers/misc/gxfp513= 0/driver/gxfp_irq.h new file mode 100644 index 000000000..9b7fb41a1 --- /dev/null +++ b/drivers/misc/gxfp5130/driver/gxfp_irq.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_IRQ_H +#define __GXFP_IRQ_H + +#include + +irqreturn_t gxfp_irq_thread(int irq, void *data); + +#endif /* __GXFP_IRQ_H */ diff --git a/drivers/misc/gxfp5130/driver/gxfp_platform.c b/drivers/misc/gx= fp5130/driver/gxfp_platform.c new file mode 100644 index 000000000..842c9d803 --- /dev/null +++ b/drivers/misc/gxfp5130/driver/gxfp_platform.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gxfp_priv.h" +#include "../hw/gxfp_acpi.h" +#include "../hw/gxfp_gpio.h" +#include "gxfp_constants.h" +#include "gxfp_uapi.h" +#include "gxfp_irq.h" +#include "gxfp_platform.h" +#include "gxfp_trace.h" + +#define GXFP_IRQ_RX_CAP (128u * 1024u) + + +static int gxfp_platform_add_acpi_driver_gpios(struct platform_device *pde= v) +{ + struct gxfp_dev *gdev =3D platform_get_drvdata(pdev); + struct acpi_gpio_params *params; + struct acpi_gpio_mapping *map; + int irq_idx; + int done_idx[2]; + bool done_active_low; + int rc; + + if (!pdev || !gdev) + return -EINVAL; + + rc =3D gxfp_acpi_get_gpio_crs_indexes(&pdev->dev, &irq_idx, done_idx, &do= ne_active_low); + if (rc) { + dev_warn(&pdev->dev, "ACPI _CRS: cannot build driver GPIO mapping (rc=3D= %d)\n", rc); + return rc; + } + + params =3D devm_kcalloc(&pdev->dev, 3, sizeof(*params), GFP_KERNEL); + map =3D devm_kcalloc(&pdev->dev, 4, sizeof(*map), GFP_KERNEL); + if (!params || !map) + return -ENOMEM; + + params[0].crs_entry_index =3D (unsigned int)irq_idx; + params[0].line_index =3D 0; + params[0].active_low =3D false; + map[0].name =3D "irq-gpios"; + map[0].data =3D ¶ms[0]; + map[0].size =3D 1; + + params[1].crs_entry_index =3D (unsigned int)done_idx[0]; + params[1].line_index =3D 0; + params[1].active_low =3D done_active_low; + map[1].name =3D "write-done-gpios"; + map[1].data =3D ¶ms[1]; + map[1].size =3D 1; + map[1].quirks =3D 0; + + params[2].crs_entry_index =3D (unsigned int)done_idx[1]; + params[2].line_index =3D 0; + params[2].active_low =3D done_active_low; + map[2].name =3D "read-done-gpios"; + map[2].data =3D ¶ms[2]; + map[2].size =3D 1; + map[2].quirks =3D 0; + + /* map[3] is terminator */ + + rc =3D devm_acpi_dev_add_driver_gpios(&pdev->dev, map); + if (rc) { + dev_warn(&pdev->dev, "ACPI: dev_add_driver_gpios failed (rc=3D%d)\n", rc= ); + return rc; + } + + dev_dbg(&pdev->dev, + "ACPI: installed driver GPIO mapping (irq_idx=3D%d done_idx=3D%d,%d done= _active_low=3D%u)\n", + irq_idx, done_idx[0], done_idx[1], done_active_low ? 1 : 0); + return 0; +} + +int gxfp_platform_probe(struct platform_device *pdev) +{ + struct gxfp_dev *gdev; + struct resource *res; + unsigned long irq_flags; + unsigned int irq_type; + int ret; + + gdev =3D devm_kzalloc(&pdev->dev, sizeof(*gdev), GFP_KERNEL); + if (!gdev) + return -ENOMEM; + gdev->dev =3D &pdev->dev; + mutex_init(&gdev->lock); + atomic_set(&gdev->mailbox_seq16, GXFP_ESPI_SEQ16_INIT); + gdev->irq.assem_active =3D false; + gdev->irq.assem_total =3D 0; + gdev->irq.assem_copied =3D 0; + + res =3D platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing MMIO resource (mailbox window)\n"); + return -ENODEV; + } + + gdev->hw.mailbox_size =3D resource_size(res); + gdev->hw.mailbox_mmio =3D devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(gdev->hw.mailbox_mmio)) + return PTR_ERR(gdev->hw.mailbox_mmio); + + platform_set_drvdata(pdev, gdev); + (void)gxfp_trace_init(&pdev->dev); + + /* GPIOs */ + { + int gpio_rc =3D gxfp_platform_init_gpios(pdev); + + if (gpio_rc) { + if (gpio_rc =3D=3D -EPROBE_DEFER) + dev_dbg(&pdev->dev, "GPIO not ready, deferring probe\n"); + return gpio_rc; + } + } + + /* misc device */ + ret =3D gxfp_uapi_register(gdev); + if (ret) { + dev_err(&pdev->dev, "failed to register misc device (/dev/gxfp) rc=3D%d\= n", ret); + return ret; + } + + /* IRQ */ + gdev->irq.rx_cap =3D GXFP_IRQ_RX_CAP; + gdev->irq.rx_buf =3D devm_kmalloc(&pdev->dev, gdev->irq.rx_cap, GFP_KERNE= L); + if (!gdev->irq.rx_buf) { + dev_err(&pdev->dev, "IRQ: kmalloc(%zu) failed\n", gdev->irq.rx_cap); + ret =3D -ENOMEM; + goto err_ioctl; + } + + gdev->irq.rx_tmp =3D devm_kmalloc(&pdev->dev, (size_t)GXFP_READ_CHUNK_MAX= , GFP_KERNEL); + if (!gdev->irq.rx_tmp) { + ret =3D -ENOMEM; + goto err_ioctl; + } + + gdev->hw.irq =3D gpiod_to_irq(gdev->hw.gpio_irq); + if (gdev->hw.irq < 0) { + dev_err(&pdev->dev, "IRQ: gpiod_to_irq failed rc=3D%d\n", gdev->hw.irq); + ret =3D gdev->hw.irq; + goto err_ioctl; + } + + irq_type =3D irq_get_trigger_type(gdev->hw.irq); + if (irq_type =3D=3D IRQ_TYPE_NONE) { + /* + * ACPI DSDT for SPBA reports: + * GpioInt(Level, ActiveHigh, ExclusiveAndWake, ...) + */ + dev_info(&pdev->dev, "IRQ: trigger type unknown; defaulting to LEVEL_HIG= H\n"); + (void)irq_set_irq_type(gdev->hw.irq, IRQ_TYPE_LEVEL_HIGH); + irq_type =3D irq_get_trigger_type(gdev->hw.irq); + } + + irq_flags =3D IRQF_ONESHOT | IRQF_NO_AUTOEN; + ret =3D devm_request_threaded_irq(&pdev->dev, + gdev->hw.irq, + NULL, + gxfp_irq_thread, + irq_flags, + "gxfp-irq", + gdev); + if (ret) { + dev_err(&pdev->dev, "IRQ: request_threaded_irq failed (irq=3D%d, rc=3D%d= )\n", + gdev->hw.irq, ret); + gdev->hw.irq =3D 0; + goto err_ioctl; + } + dev_dbg(&pdev->dev, "IRQ: registered (irq=3D%d, flags=3D0x%lx, type=3D0x%= x)\n", + gdev->hw.irq, irq_flags, irq_type); + + dev_dbg(&pdev->dev, "gxfp probed (mailbox=3D%pa len=3D%pa)\n", + &res->start, &gdev->hw.mailbox_size); + return 0; + +err_ioctl: + gxfp_uapi_unregister(gdev); + return ret; +} + +int gxfp_platform_init_gpios(struct platform_device *pdev) +{ + struct gxfp_dev *gdev =3D platform_get_drvdata(pdev); + struct gpio_desc *d; + int added_map =3D 0; + + d =3D devm_gpiod_get(&pdev->dev, "write-done", GPIOD_OUT_LOW); + if (IS_ERR(d) && PTR_ERR(d) =3D=3D -ENOENT) { + (void)gxfp_platform_add_acpi_driver_gpios(pdev); + added_map =3D 1; + d =3D devm_gpiod_get(&pdev->dev, "write-done", GPIOD_OUT_LOW); + } + gdev->hw.gpio_write_done =3D d; + if (IS_ERR(gdev->hw.gpio_write_done)) + return PTR_ERR(gdev->hw.gpio_write_done); + + d =3D devm_gpiod_get(&pdev->dev, "read-done", GPIOD_OUT_LOW); + if (!added_map && IS_ERR(d) && PTR_ERR(d) =3D=3D -ENOENT) { + (void)gxfp_platform_add_acpi_driver_gpios(pdev); + added_map =3D 1; + d =3D devm_gpiod_get(&pdev->dev, "read-done", GPIOD_OUT_LOW); + } + gdev->hw.gpio_read_done =3D d; + if (IS_ERR(gdev->hw.gpio_read_done)) + return PTR_ERR(gdev->hw.gpio_read_done); + + d =3D devm_gpiod_get(&pdev->dev, "irq", GPIOD_IN); + if (!added_map && IS_ERR(d) && PTR_ERR(d) =3D=3D -ENOENT) { + (void)gxfp_platform_add_acpi_driver_gpios(pdev); + added_map =3D 1; + d =3D devm_gpiod_get(&pdev->dev, "irq", GPIOD_IN); + } + gdev->hw.gpio_irq =3D d; + if (IS_ERR(gdev->hw.gpio_irq)) + return PTR_ERR(gdev->hw.gpio_irq); + + if (!gdev->hw.gpio_write_done || !gdev->hw.gpio_read_done || !gdev->hw.gp= io_irq) { + dev_err(&pdev->dev, "missing GPIOs write-done/read-done/irq (write_done= =3D%p, read_done=3D%p, irq=3D%p)\n", + gdev->hw.gpio_write_done, gdev->hw.gpio_read_done, gdev->hw.gpio_irq); + return -EINVAL; + } + + gxfp_gpio_force_done_deassert(gdev->dev, gdev); + + return 0; +} + +void gxfp_platform_remove(struct platform_device *pdev) +{ + struct gxfp_dev *gdev =3D platform_get_drvdata(pdev); + + if (gdev && gdev->hw.irq > 0) { + disable_irq(gdev->hw.irq); + synchronize_irq(gdev->hw.irq); + } + gxfp_trace_exit(); + gxfp_uapi_unregister(gdev); +} diff --git a/drivers/misc/gxfp5130/driver/gxfp_platform.h b/drivers/misc/gx= fp5130/driver/gxfp_platform.h new file mode 100644 index 000000000..fa69f18e5 --- /dev/null +++ b/drivers/misc/gxfp5130/driver/gxfp_platform.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_PLATFORM_H +#define __GXFP_PLATFORM_H + +#include + +int gxfp_platform_probe(struct platform_device *pdev); +void gxfp_platform_remove(struct platform_device *pdev); + +int gxfp_platform_init_gpios(struct platform_device *pdev); + +#endif /* __GXFP_PLATFORM_H */ diff --git a/drivers/misc/gxfp5130/driver/gxfp_trace.c b/drivers/misc/gxfp5= 130/driver/gxfp_trace.c new file mode 100644 index 000000000..e51dfe422 --- /dev/null +++ b/drivers/misc/gxfp5130/driver/gxfp_trace.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gxfp_trace.h" + +#define GXFP_TRACE_RING_SIZE 1024u +#define GXFP_TRACE_LINE_MAX 192u +#define GXFP_TRACE_DEFAULT_ENABLE 0 + +static struct { + spinlock_t lock; + bool inited; + bool enabled; + u32 seq; + u32 head; + u32 count; + char lines[GXFP_TRACE_RING_SIZE][GXFP_TRACE_LINE_MAX]; + struct dentry *dir; +} gxfp_trace; + +static void gxfp_trace_push_locked(const char *line) +{ + u32 idx; + + if (!line) + return; + + idx =3D gxfp_trace.head; + strscpy(gxfp_trace.lines[idx], line, GXFP_TRACE_LINE_MAX); + gxfp_trace.head =3D (idx + 1u) % GXFP_TRACE_RING_SIZE; + if (gxfp_trace.count < GXFP_TRACE_RING_SIZE) + gxfp_trace.count++; +} + +void gxfp_trace_logf(const char *fmt, ...) +{ + unsigned long flags; + char msg[128]; + char line[GXFP_TRACE_LINE_MAX]; + va_list ap; + u64 ts; + u32 seq; + int n; + + if (!fmt || !READ_ONCE(gxfp_trace.inited) || !READ_ONCE(gxfp_trace.enable= d)) + return; + + va_start(ap, fmt); + vscnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + ts =3D ktime_get_ns(); + spin_lock_irqsave(&gxfp_trace.lock, flags); + seq =3D ++gxfp_trace.seq; + n =3D scnprintf(line, sizeof(line), "%u %llu %s", seq, ts, msg); + if (n >=3D 0) + gxfp_trace_push_locked(line); + spin_unlock_irqrestore(&gxfp_trace.lock, flags); +} + +void gxfp_trace_clear(void) +{ + unsigned long flags; + + spin_lock_irqsave(&gxfp_trace.lock, flags); + gxfp_trace.head =3D 0; + gxfp_trace.count =3D 0; + spin_unlock_irqrestore(&gxfp_trace.lock, flags); +} + +static int gxfp_trace_dump_show(struct seq_file *s, void *unused) +{ + char (*snapshot)[GXFP_TRACE_LINE_MAX]; + unsigned long flags; + u32 count; + u32 start; + u32 i; + + snapshot =3D kcalloc(GXFP_TRACE_RING_SIZE, sizeof(*snapshot), GFP_KERNEL); + if (!snapshot) + return -ENOMEM; + + spin_lock_irqsave(&gxfp_trace.lock, flags); + count =3D gxfp_trace.count; + start =3D (gxfp_trace.head + GXFP_TRACE_RING_SIZE - count) % GXFP_TRACE_R= ING_SIZE; + for (i =3D 0; i < count; i++) { + u32 idx =3D (start + i) % GXFP_TRACE_RING_SIZE; + + strscpy(snapshot[i], gxfp_trace.lines[idx], GXFP_TRACE_LINE_MAX); + } + spin_unlock_irqrestore(&gxfp_trace.lock, flags); + + seq_printf(s, "enabled=3D%u ring_size=3D%u count=3D%u\n", + READ_ONCE(gxfp_trace.enabled) ? 1 : 0, + GXFP_TRACE_RING_SIZE, + count); + seq_puts(s, "# seq ts_ns message\n"); + for (i =3D 0; i < count; i++) + seq_printf(s, "%s\n", snapshot[i]); + + kfree(snapshot); + return 0; +} + +static int gxfp_trace_dump_open(struct inode *inode, struct file *file) +{ + return single_open(file, gxfp_trace_dump_show, inode->i_private); +} + +static const struct file_operations gxfp_trace_dump_fops =3D { + .owner =3D THIS_MODULE, + .open =3D gxfp_trace_dump_open, + .read =3D seq_read, + .llseek =3D seq_lseek, + .release =3D single_release, +}; + +static ssize_t gxfp_trace_clear_write(struct file *file, + const char __user *ubuf, + size_t count, + loff_t *ppos) +{ + (void)file; + (void)ubuf; + (void)ppos; + gxfp_trace_clear(); + return count; +} + +static const struct file_operations gxfp_trace_clear_fops =3D { + .owner =3D THIS_MODULE, + .write =3D gxfp_trace_clear_write, + .llseek =3D noop_llseek, +}; + +int gxfp_trace_init(struct device *dev) +{ + (void)dev; + + if (READ_ONCE(gxfp_trace.inited)) + return 0; + + memset(&gxfp_trace, 0, sizeof(gxfp_trace)); + spin_lock_init(&gxfp_trace.lock); + gxfp_trace.enabled =3D GXFP_TRACE_DEFAULT_ENABLE; + + gxfp_trace.dir =3D debugfs_create_dir("gxfp", NULL); + if (!IS_ERR_OR_NULL(gxfp_trace.dir)) { + debugfs_create_file("trace_dump", 0400, gxfp_trace.dir, + NULL, &gxfp_trace_dump_fops); + debugfs_create_file("trace_clear", 0200, gxfp_trace.dir, + NULL, &gxfp_trace_clear_fops); + debugfs_create_bool("trace_enable", 0600, gxfp_trace.dir, &gxfp_trace.en= abled); + } + + gxfp_trace.inited =3D true; + return 0; +} + +void gxfp_trace_exit(void) +{ + if (!READ_ONCE(gxfp_trace.inited)) + return; + + debugfs_remove_recursive(gxfp_trace.dir); + gxfp_trace.dir =3D NULL; + gxfp_trace.inited =3D false; +} diff --git a/drivers/misc/gxfp5130/driver/gxfp_trace.h b/drivers/misc/gxfp5= 130/driver/gxfp_trace.h new file mode 100644 index 000000000..2041ba0b0 --- /dev/null +++ b/drivers/misc/gxfp5130/driver/gxfp_trace.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_TRACE_H +#define __GXFP_TRACE_H + +#include + +struct device; + +int gxfp_trace_init(struct device *dev); +void gxfp_trace_exit(void); +void gxfp_trace_clear(void); + +void gxfp_trace_logf(const char *fmt, ...) __printf(1, 2); + +#endif /* __GXFP_TRACE_H */ diff --git a/drivers/misc/gxfp5130/driver/gxfp_uapi.h b/drivers/misc/gxfp51= 30/driver/gxfp_uapi.h new file mode 100644 index 000000000..ecc60ee06 --- /dev/null +++ b/drivers/misc/gxfp5130/driver/gxfp_uapi.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_UAPI_DRV_H +#define __GXFP_UAPI_DRV_H + +#include +#include +#include + +struct gxfp_dev; + +void gxfp_uapi_rxq_push_packet(struct gxfp_dev *gdev, const __u8 *buf, siz= e_t len); + +void gxfp_uapi_rxq_flush_locked(struct gxfp_dev *gdev); +ssize_t gxfp_uapi_read(struct file *file, char __user *ubuf, size_t count,= loff_t *ppos); +__poll_t gxfp_uapi_poll(struct file *file, poll_table *wait); + +int gxfp_uapi_open(struct inode *inode, struct file *file); +int gxfp_uapi_release(struct inode *inode, struct file *file); +ssize_t gxfp_uapi_write(struct file *file, const char __user *ubuf, size_t= count, loff_t *ppos); +long gxfp_uapi_unlocked_ioctl(struct file *file, unsigned int cmd, unsigne= d long arg); + +int gxfp_uapi_register(struct gxfp_dev *gdev); +void gxfp_uapi_unregister(struct gxfp_dev *gdev); + +#endif /* __GXFP_UAPI_DRV_H */ diff --git a/drivers/misc/gxfp5130/driver/gxfp_uapi_ctrl.c b/drivers/misc/g= xfp5130/driver/gxfp_uapi_ctrl.c new file mode 100644 index 000000000..d6b4a888f --- /dev/null +++ b/drivers/misc/gxfp5130/driver/gxfp_uapi_ctrl.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +#include "gxfp_priv.h" +#include "../transport/gxfp_espi_tx.h" +#include "../include/uapi/linux/gxfp_ioctl.h" +#include "gxfp_uapi.h" +#include "gxfp_trace.h" + +int gxfp_uapi_open(struct inode *inode, struct file *file) +{ + struct miscdevice *m =3D file->private_data; + struct gxfp_dev *gdev =3D container_of(m, struct gxfp_dev, uapi.miscdev); + unsigned long flags; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (file->f_mode & FMODE_READ) { + spin_lock_irqsave(&gdev->uapi.rxq_lock, flags); + if (!gdev->uapi.rxq_inited) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return -ENODEV; + } + if (gdev->uapi.rxq_reader_open) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return -EBUSY; + } + gdev->uapi.rxq_reader_open =3D true; + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + } + + file->private_data =3D gdev; + gxfp_trace_logf("uapi_open ok mode=3D0x%x", file->f_mode); + return 0; +} + +int gxfp_uapi_release(struct inode *inode, struct file *file) +{ + struct gxfp_dev *gdev =3D file->private_data; + unsigned long flags; + + if (!gdev) + return 0; + + if (file->f_mode & FMODE_READ) { + spin_lock_irqsave(&gdev->uapi.rxq_lock, flags); + gdev->uapi.rxq_reader_open =3D false; + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + } + + return 0; +} + +ssize_t gxfp_uapi_write(struct file *file, const char __user *ubuf, size_t= count, loff_t *ppos) +{ + struct gxfp_dev *gdev =3D file->private_data; + struct gxfp_tx_pkt_hdr txh; + __u8 *mp_local =3D NULL; + size_t payload_len; + size_t need; + int ret; + + if (!gdev || !ubuf) + return -ENODEV; + if (count < sizeof(txh)) + return -EINVAL; + + if (copy_from_user(&txh, ubuf, sizeof(txh))) + return -EFAULT; + + payload_len =3D (size_t)txh.payload_len; + if (payload_len > GXFP_IOCTL_TX_PAYLOAD_MAX) + return -EINVAL; + + need =3D sizeof(txh) + payload_len; + if (count !=3D need) + return -EINVAL; + + mp_local =3D kmalloc(4u + payload_len, GFP_KERNEL); + if (!mp_local) + return -ENOMEM; + + mp_local[0] =3D txh.mp_flags; + mp_local[1] =3D (__u8)(payload_len & 0xff); + mp_local[2] =3D (__u8)((payload_len >> 8) & 0xff); + mp_local[3] =3D (__u8)(mp_local[0] + mp_local[1] + mp_local[2]); + if (payload_len && copy_from_user(mp_local + 4, ubuf + sizeof(txh), paylo= ad_len)) { + kfree(mp_local); + return -EFAULT; + } + + mutex_lock(&gdev->lock); + ret =3D gxfp_espi_write(gdev, mp_local, 4u + payload_len); + mutex_unlock(&gdev->lock); + gxfp_trace_logf("uapi_write rc=3D%d mp_len=3D%zu head=3D%*ph", ret, 4u + = payload_len, + (int)min_t(size_t, 8, 4u + payload_len), mp_local); + + kfree(mp_local); + if (ret) + return ret; + + return (ssize_t)count; +} + +long gxfp_uapi_unlocked_ioctl(struct file *file, unsigned int cmd, unsigne= d long arg) +{ + struct gxfp_dev *gdev =3D file->private_data; + int ret; + + if (!gdev) + return -ENODEV; + + switch (cmd) { + case GXFP_IOCTL_FLUSH_RXQ: { + unsigned long flush_flags; + + ret =3D mutex_lock_interruptible(&gdev->uapi.rxq_mutex); + if (ret) + return ret; + + spin_lock_irqsave(&gdev->uapi.rxq_lock, flush_flags); + gxfp_uapi_rxq_flush_locked(gdev); + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flush_flags); + mutex_unlock(&gdev->uapi.rxq_mutex); + gxfp_trace_logf("uapi_ioctl flush_rxq ok"); + return 0; + } + default: + return -ENOTTY; + } +} diff --git a/drivers/misc/gxfp5130/driver/gxfp_uapi_fifo.c b/drivers/misc/g= xfp5130/driver/gxfp_uapi_fifo.c new file mode 100644 index 000000000..7be5f1265 --- /dev/null +++ b/drivers/misc/gxfp5130/driver/gxfp_uapi_fifo.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gxfp_priv.h" +#include "gxfp_uapi.h" +#include "../include/uapi/linux/gxfp_ioctl.h" +#include "../proto/gxfp_mp_proto.h" + +void gxfp_uapi_rxq_flush_locked(struct gxfp_dev *gdev) +{ + if (!gdev || !gdev->uapi.rxq_inited) + return; + kfifo_reset(&gdev->uapi.rxq_fifo); +} + +static void gxfp_uapi_rxq_push(struct gxfp_dev *gdev, const __u8 *buf, siz= e_t len) +{ + unsigned long flags; + struct gxfp_tap_hdr hdr; + struct gxfp_mp_frame_parsed mp; + size_t need; + + if (!gdev || !gdev->uapi.rxq_inited || !buf || len =3D=3D 0) + return; + if (!gxfp_parse_mp_frame(buf, len, &mp) || !mp.valid) + return; + if (mp.payload_len > (size_t)U32_MAX) + return; + + memset(&hdr, 0, sizeof(hdr)); + hdr.len =3D (__u32)mp.payload_len; + hdr.type =3D (__u32)(mp.flags >> 4); + hdr.ts_ns =3D ktime_get_ns(); + memcpy(hdr.head16, mp.payload, min_t(size_t, mp.payload_len, sizeof(hdr.h= ead16))); + + spin_lock_irqsave(&gdev->uapi.rxq_lock, flags); + if (!gdev->uapi.rxq_inited) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return; + } + need =3D sizeof(hdr) + mp.payload_len; + if (kfifo_avail(&gdev->uapi.rxq_fifo) < need) { + unsigned int avail =3D kfifo_avail(&gdev->uapi.rxq_fifo); + + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + dev_warn_ratelimited(gdev->dev, + "rxq: drop need=3D%zu avail=3D%u\n", + need, avail); + return; + } + (void)kfifo_in(&gdev->uapi.rxq_fifo, (const void *)&hdr, sizeof(hdr)); + (void)kfifo_in(&gdev->uapi.rxq_fifo, mp.payload, mp.payload_len); + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + + wake_up_interruptible(&gdev->uapi.rxq_wq); +} + +void gxfp_uapi_rxq_push_packet(struct gxfp_dev *gdev, const __u8 *buf, siz= e_t len) +{ + gxfp_uapi_rxq_push(gdev, buf, len); +} + +static bool gxfp_uapi_rxq_ready(struct gxfp_dev *gdev) +{ + unsigned long flags; + bool ready; + + spin_lock_irqsave(&gdev->uapi.rxq_lock, flags); + ready =3D gdev->uapi.rxq_inited && !kfifo_is_empty(&gdev->uapi.rxq_fifo); + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + + return ready; +} + +static int gxfp_uapi_wait_nonempty(struct gxfp_dev *gdev, struct file *fil= e) +{ + unsigned long flags; + int ret; + + for (;;) { + spin_lock_irqsave(&gdev->uapi.rxq_lock, flags); + if (!gdev->uapi.rxq_inited) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return -ENODEV; + } + if (!kfifo_is_empty(&gdev->uapi.rxq_fifo)) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return 0; + } + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + ret =3D wait_event_interruptible(gdev->uapi.rxq_wq, + gxfp_uapi_rxq_ready(gdev) || !READ_ONCE(gdev->uapi.rxq_inited)); + if (ret) + return ret; + } +} + +static int gxfp_uapi_peek_need(struct gxfp_dev *gdev, size_t count, size_t= *out_need) +{ + struct gxfp_tap_hdr hdr; + unsigned long flags; + size_t need; + + if (!out_need) + return -EINVAL; + + spin_lock_irqsave(&gdev->uapi.rxq_lock, flags); + if (!gdev->uapi.rxq_inited) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return -ENODEV; + } + if (kfifo_len(&gdev->uapi.rxq_fifo) < sizeof(hdr)) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return -EIO; + } + if (kfifo_out_peek(&gdev->uapi.rxq_fifo, &hdr, sizeof(hdr)) !=3D sizeof(h= dr)) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return -EIO; + } + need =3D sizeof(hdr) + (size_t)hdr.len; + if ((size_t)kfifo_len(&gdev->uapi.rxq_fifo) < need) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return -EIO; + } + if (count < need) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return -EMSGSIZE; + } + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + + *out_need =3D need; + return 0; +} + +static int gxfp_uapi_pop_record(struct gxfp_dev *gdev, __u8 *tmp, + size_t need, unsigned int *out_popped) +{ + unsigned long flags; + unsigned int popped; + + if (!out_popped) + return -EINVAL; + + spin_lock_irqsave(&gdev->uapi.rxq_lock, flags); + if (!gdev->uapi.rxq_inited) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return -ENODEV; + } + popped =3D kfifo_out(&gdev->uapi.rxq_fifo, tmp, need); + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + + *out_popped =3D popped; + return 0; +} + +ssize_t gxfp_uapi_read(struct file *file, char __user *ubuf, size_t count,= loff_t *ppos) +{ + struct gxfp_dev *gdev =3D file->private_data; + __u8 *tmp; + size_t need; + int ret; + unsigned int popped =3D 0; + + if (!gdev || !gdev->uapi.rxq_inited) + return -ENODEV; + if (count =3D=3D 0) + return 0; + + ret =3D mutex_lock_interruptible(&gdev->uapi.rxq_mutex); + if (ret) + return ret; + + ret =3D gxfp_uapi_wait_nonempty(gdev, file); + if (ret) + goto out_unlock; + + ret =3D gxfp_uapi_peek_need(gdev, count, &need); + if (ret) + goto out_unlock; + + tmp =3D kmalloc(need, GFP_KERNEL); + if (!tmp) { + ret =3D -ENOMEM; + goto out_unlock; + } + + ret =3D gxfp_uapi_pop_record(gdev, tmp, need, &popped); + if (ret) { + kfree(tmp); + goto out_unlock; + } + + if (popped !=3D need) { + kfree(tmp); + ret =3D -EIO; + goto out_unlock; + } + + if (copy_to_user(ubuf, tmp, popped)) { + kfree(tmp); + ret =3D -EFAULT; + goto out_unlock; + } + + kfree(tmp); + ret =3D (int)popped; + +out_unlock: + mutex_unlock(&gdev->uapi.rxq_mutex); + return ret; +} + +__poll_t gxfp_uapi_poll(struct file *file, poll_table *wait) +{ + struct gxfp_dev *gdev =3D file->private_data; + __poll_t mask =3D 0; + unsigned long flags; + + if (!gdev || !gdev->uapi.rxq_inited) + return EPOLLERR; + + poll_wait(file, &gdev->uapi.rxq_wq, wait); + spin_lock_irqsave(&gdev->uapi.rxq_lock, flags); + if (!gdev->uapi.rxq_inited) { + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return EPOLLERR; + } + if (!kfifo_is_empty(&gdev->uapi.rxq_fifo)) + mask |=3D EPOLLIN | EPOLLRDNORM; + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + return mask; +} diff --git a/drivers/misc/gxfp5130/driver/gxfp_uapi_misc.c b/drivers/misc/g= xfp5130/driver/gxfp_uapi_misc.c new file mode 100644 index 000000000..c797ed432 --- /dev/null +++ b/drivers/misc/gxfp5130/driver/gxfp_uapi_misc.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include + +#include "gxfp_priv.h" +#include "../hw/gxfp_acpi.h" +#include "gxfp_uapi.h" + +#define GXFP_RXQ_FIFO_BYTES SZ_1M + +static ssize_t acpi_id_show(struct device *dev, struct device_attribute *a= ttr, char *buf) +{ + struct miscdevice *m =3D dev_get_drvdata(dev); + struct gxfp_dev *gdev; + char hid[16]; + int rc; + + if (!m) + return -ENODEV; + gdev =3D container_of(m, struct gxfp_dev, uapi.miscdev); + if (!gdev || !gdev->dev) + return -ENODEV; + rc =3D gxfp_acpi_get_hid(gdev->dev, hid, sizeof(hid)); + if (rc) + return sysfs_emit(buf, "\n"); + return sysfs_emit(buf, "%s\n", hid); +} +static DEVICE_ATTR_RO(acpi_id); + +static struct attribute *gxfp_misc_attrs[] =3D { + &dev_attr_acpi_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(gxfp_misc); + +static const struct file_operations gxfp_uapi_fops =3D { + .owner =3D THIS_MODULE, + .open =3D gxfp_uapi_open, + .release =3D gxfp_uapi_release, + .read =3D gxfp_uapi_read, + .write =3D gxfp_uapi_write, + .poll =3D gxfp_uapi_poll, + .unlocked_ioctl =3D gxfp_uapi_unlocked_ioctl, + .compat_ioctl =3D compat_ptr_ioctl, + .llseek =3D noop_llseek, +}; + +int gxfp_uapi_register(struct gxfp_dev *gdev) +{ + int ret; + int rc_fifo; + unsigned int fifo_bytes; + + if (!gdev) + return -EINVAL; + if (gdev->uapi.misc_registered) + return 0; + + gdev->uapi.miscdev.minor =3D MISC_DYNAMIC_MINOR; + gdev->uapi.miscdev.name =3D "gxfp"; + gdev->uapi.miscdev.fops =3D &gxfp_uapi_fops; + gdev->uapi.miscdev.parent =3D gdev->dev; + gdev->uapi.miscdev.groups =3D gxfp_misc_groups; + + spin_lock_init(&gdev->uapi.rxq_lock); + mutex_init(&gdev->uapi.rxq_mutex); + init_waitqueue_head(&gdev->uapi.rxq_wq); + gdev->uapi.rxq_inited =3D false; + gdev->uapi.rxq_reader_open =3D false; + fifo_bytes =3D GXFP_RXQ_FIFO_BYTES; + rc_fifo =3D kfifo_alloc(&gdev->uapi.rxq_fifo, fifo_bytes, GFP_KERNEL); + if (rc_fifo) + return rc_fifo; + gdev->uapi.rxq_inited =3D true; + + ret =3D misc_register(&gdev->uapi.miscdev); + if (ret) + goto err_fifo; + + gdev->uapi.misc_registered =3D true; + + dev_dbg(gdev->dev, "misc device registered: /dev/%s\n", gdev->uapi.miscde= v.name); + return 0; + +err_fifo: + if (gdev->uapi.rxq_inited) { + kfifo_free(&gdev->uapi.rxq_fifo); + gdev->uapi.rxq_inited =3D false; + } + return ret; +} + +void gxfp_uapi_unregister(struct gxfp_dev *gdev) +{ + unsigned long flags; + + if (!gdev) + return; + if (!gdev->uapi.misc_registered && !gdev->uapi.rxq_inited) + return; + + if (gdev->uapi.misc_registered) { + misc_deregister(&gdev->uapi.miscdev); + gdev->uapi.misc_registered =3D false; + } + + if (gdev->uapi.rxq_inited) { + mutex_lock(&gdev->uapi.rxq_mutex); + spin_lock_irqsave(&gdev->uapi.rxq_lock, flags); + gdev->uapi.rxq_inited =3D false; + gdev->uapi.rxq_reader_open =3D false; + kfifo_reset(&gdev->uapi.rxq_fifo); + spin_unlock_irqrestore(&gdev->uapi.rxq_lock, flags); + wake_up_interruptible_all(&gdev->uapi.rxq_wq); + + kfifo_free(&gdev->uapi.rxq_fifo); + mutex_unlock(&gdev->uapi.rxq_mutex); + } +} diff --git a/drivers/misc/gxfp5130/gxfp_main.c b/drivers/misc/gxfp5130/gxfp= _main.c new file mode 100644 index 000000000..863e36a88 --- /dev/null +++ b/drivers/misc/gxfp5130/gxfp_main.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include + +#include "include/gxfp_priv.h" +#include "cmd/gxfp_cmd.h" +#include "cmd/gxfp_cmd_reset.h" +#include "cmd/gxfp_cmd_version.h" +#include "cmd/gxfp_cmd_mcu_state.h" +#include "driver/gxfp_platform.h" + +#define GXFP_DRV_NAME "gxfp" + +static int gxfp_startup_sequence(struct platform_device *pdev) +{ + struct gxfp_dev *gdev =3D platform_get_drvdata(pdev); + struct gxfp_get_version_result ver; + struct gxfp_mcu_state mcu; + int rc; + int mcu_rc; + unsigned int try; + + if (!gdev) + return -ENODEV; + + msleep(150); + memset(&ver, 0, sizeof(ver)); + memset(&mcu, 0, sizeof(mcu)); + + mutex_lock(&gdev->lock); + rc =3D 0; + for (try =3D 0; try < 3; try++) { + rc =3D gxfp_cmd_get_version(gdev, &ver); + if (!rc) + break; + if ((try + 1) < 3) + msleep(20); + } + mcu_rc =3D gxfp_cmd_query_mcu_state(gdev, &mcu); + if (ver.status !=3D 0) { + dev_warn(&pdev->dev, + "INIT: get_version failed (%d); attempting recover_session then retry\= n", + (int)ver.status); + (void)gxfp_cmd_recover_session(gdev, 1); + (void)gxfp_cmd_get_version(gdev, &ver); + } + mutex_unlock(&gdev->lock); + + if (ver.status =3D=3D 0 && ver.version_ascii[0] !=3D '\0') + dev_info(&pdev->dev, "INIT: FW=3D'%s'\n", ver.version_ascii); + if (ver.status !=3D 0) + return (int)ver.status; + + return 0; +} + +static void gxfp_shutdown_sequence(struct platform_device *pdev) +{ + struct gxfp_dev *gdev; + int rc; + + if (!pdev) + return; + gdev =3D platform_get_drvdata(pdev); + if (!gdev) + return; + + mutex_lock(&gdev->lock); + (void)gxfp_cmd_notify_power_state(gdev, 1); + rc =3D gxfp_cmd_sleep_cleanup(gdev, 0); + mutex_unlock(&gdev->lock); + dev_dbg(&pdev->dev, "REMOVE: sent SLEEP/CLEANUP(0xD2) rc=3D%d\n", rc); +} + +static int gxfp_probe(struct platform_device *pdev) +{ + struct gxfp_dev *gdev; + int ret; + + ret =3D gxfp_platform_probe(pdev); + if (ret) + return ret; + + ret =3D gxfp_startup_sequence(pdev); + if (ret) { + gxfp_platform_remove(pdev); + return ret; + } + + gdev =3D platform_get_drvdata(pdev); + if (gdev && gdev->hw.irq > 0) + enable_irq(gdev->hw.irq); + + return 0; +} + +static void gxfp_remove(struct platform_device *pdev) +{ + gxfp_shutdown_sequence(pdev); + gxfp_platform_remove(pdev); +} + +static const struct acpi_device_id gxfp_acpi_match[] =3D { + { "GXFP5130", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, gxfp_acpi_match); + +static struct platform_driver gxfp_driver =3D { + .driver =3D { + .name =3D GXFP_DRV_NAME, + .acpi_match_table =3D gxfp_acpi_match, + }, + .probe =3D gxfp_probe, + .remove =3D gxfp_remove, +}; + +module_platform_driver(gxfp_driver); + +MODULE_DESCRIPTION("eSPI-Based GXFP5130 Fingerprint Sensor Driver"); +MODULE_AUTHOR("vindeu"); +MODULE_AUTHOR("Metehan G=C3=BCnen "); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/gxfp5130/hw/gxfp_acpi.c b/drivers/misc/gxfp5130/h= w/gxfp_acpi.c new file mode 100644 index 000000000..17e42fcd3 --- /dev/null +++ b/drivers/misc/gxfp5130/hw/gxfp_acpi.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +#include "gxfp_acpi.h" + +struct gxfp_acpi_res_state { + struct device *dev; + int gpio_seen; + int gpio_io_seen; + u16 gpio_io_pin[2]; + u8 gpio_io_pol[2]; + int gpio_io_crs_index[2]; + int gpio_int_seen; + int gpio_int_crs_index; +}; + +static int gxfp_acpi_res_cb(struct acpi_resource *ares, void *context) +{ + struct gxfp_acpi_res_state *st =3D context; + + if (!ares || !st || !st->dev) + return 0; + + switch (ares->type) { + case ACPI_RESOURCE_TYPE_GPIO: { + struct acpi_resource_gpio *g =3D &ares->data.gpio; + u16 pin0 =3D 0xffff; + const char *ctype =3D "gpio?"; + + st->gpio_seen++; + + if (g->pin_table_length < 1) + break; + pin0 =3D g->pin_table[0]; + + if (g->connection_type =3D=3D ACPI_RESOURCE_GPIO_TYPE_INT) { + ctype =3D "GpioInt"; + st->gpio_int_seen++; + if (st->gpio_int_seen =3D=3D 1) + st->gpio_int_crs_index =3D st->gpio_seen - 1; + } else if (g->connection_type =3D=3D ACPI_RESOURCE_GPIO_TYPE_IO) { + ctype =3D "GpioIo"; + if (st->gpio_io_seen < 2) { + st->gpio_io_pin[st->gpio_io_seen] =3D pin0; + st->gpio_io_pol[st->gpio_io_seen] =3D g->polarity; + st->gpio_io_crs_index[st->gpio_io_seen] =3D st->gpio_seen - 1; + st->gpio_io_seen++; + } + } + break; + } + default: + break; + } + + return 0; +} + +int gxfp_acpi_get_gpio_crs_indexes(struct device *dev, + int *irq_gpio_crs_index, + int done_gpio_crs_index[2], + bool *done_active_low) +{ + struct acpi_device *adev =3D ACPI_COMPANION(dev); + struct gxfp_acpi_res_state st =3D { .dev =3D dev }; + LIST_HEAD(res_list); + int rc; + bool p0, p1; + + if (!dev || !irq_gpio_crs_index || !done_gpio_crs_index || !done_active_l= ow) + return -EINVAL; + if (!adev) + return -ENODEV; + + st.gpio_int_crs_index =3D -1; + st.gpio_io_crs_index[0] =3D -1; + st.gpio_io_crs_index[1] =3D -1; + + rc =3D acpi_dev_get_resources(adev, &res_list, gxfp_acpi_res_cb, &st); + acpi_dev_free_resource_list(&res_list); + if (rc <=3D 0) + return -ENOENT; + if (st.gpio_int_seen < 1 || st.gpio_int_crs_index < 0) + return -ENOENT; + if (st.gpio_io_seen < 2 || st.gpio_io_crs_index[0] < 0 || st.gpio_io_crs_= index[1] < 0) + return -ENOENT; + + p0 =3D st.gpio_io_pol[0] ? true : false; + p1 =3D st.gpio_io_pol[1] ? true : false; + if (p0 !=3D p1) { + dev_warn(dev, "ACPI _CRS: done GPIO polarities differ (done[0]=3D%u done= [1]=3D%u)\n", + st.gpio_io_pol[0], st.gpio_io_pol[1]); + return -EINVAL; + } + + *irq_gpio_crs_index =3D st.gpio_int_crs_index; + done_gpio_crs_index[0] =3D st.gpio_io_crs_index[0]; + done_gpio_crs_index[1] =3D st.gpio_io_crs_index[1]; + *done_active_low =3D p0; + return 0; +} + +int gxfp_acpi_get_hid(struct device *dev, char *buf, size_t bufsz) +{ + struct acpi_device *adev; + const char *hid; + size_t len; + + if (!dev || !buf || bufsz < 2) + return -EINVAL; + + adev =3D ACPI_COMPANION(dev); + if (!adev) + return -ENODEV; + + hid =3D acpi_device_hid(adev); + if (!hid || hid[0] =3D=3D '\0') + return -ENOENT; + + len =3D strlen(hid); + if (len >=3D bufsz) + return -ENOSPC; + + memcpy(buf, hid, len + 1); + return 0; +} diff --git a/drivers/misc/gxfp5130/hw/gxfp_acpi.h b/drivers/misc/gxfp5130/h= w/gxfp_acpi.h new file mode 100644 index 000000000..5975a9fa0 --- /dev/null +++ b/drivers/misc/gxfp5130/hw/gxfp_acpi.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_ACPI_H +#define __GXFP_ACPI_H + +#include +#include + +int gxfp_acpi_get_gpio_crs_indexes(struct device *dev, + int *irq_gpio_crs_index, + int done_gpio_crs_index[2], + bool *done_active_low); + +int gxfp_acpi_get_hid(struct device *dev, char *buf, size_t bufsz); + +#endif /* __GXFP_ACPI_H */ diff --git a/drivers/misc/gxfp5130/hw/gxfp_delay.c b/drivers/misc/gxfp5130/= hw/gxfp_delay.c new file mode 100644 index 000000000..58c0e7396 --- /dev/null +++ b/drivers/misc/gxfp5130/hw/gxfp_delay.c @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0 +#include + +#include "gxfp_delay.h" + +void gxfp_busy_wait_us(unsigned int us) +{ + if (!us) + return; + if (us <=3D 2000) { + udelay(us); + return; + } + usleep_range(us, us + 200); +} diff --git a/drivers/misc/gxfp5130/hw/gxfp_delay.h b/drivers/misc/gxfp5130/= hw/gxfp_delay.h new file mode 100644 index 000000000..7c7f7f46a --- /dev/null +++ b/drivers/misc/gxfp5130/hw/gxfp_delay.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_DELAY_H +#define __GXFP_DELAY_H + +void gxfp_busy_wait_us(unsigned int us); + +#endif /* __GXFP_DELAY_H */ diff --git a/drivers/misc/gxfp5130/hw/gxfp_gpio.c b/drivers/misc/gxfp5130/h= w/gxfp_gpio.c new file mode 100644 index 000000000..934e8397a --- /dev/null +++ b/drivers/misc/gxfp5130/hw/gxfp_gpio.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include + +#include "gxfp_gpio.h" +#include "gxfp_delay.h" +#include "gxfp_priv.h" + +#define GXFP_GPIO_PULSE_HIGH_US 4000 +#define GXFP_GPIO_READ_DONE_CLEAR_DELAY_US 200 +#define GXFP_GPIO_READ_DONE_HIGH_US 50 +#define GXFP_GPIO_WRITE_DONE_FORCE_DEASSERT true +#define GXFP_GPIO_WRITE_DONE_PRE_US 50 + +static inline void gxfp_gpio_pulse_delay_us(unsigned int us) +{ + if (us) + gxfp_busy_wait_us(us); +} + + +void gxfp_gpio_pulse_write_done(struct gxfp_dev *gdev) +{ + if (GXFP_GPIO_WRITE_DONE_FORCE_DEASSERT && gdev && gdev->hw.gpio_write_do= ne) { + gpiod_set_value_cansleep(gdev->hw.gpio_write_done, 0); + gxfp_gpio_pulse_delay_us(GXFP_GPIO_WRITE_DONE_PRE_US); + } + gpiod_set_value_cansleep(gdev->hw.gpio_write_done, 1); + gxfp_gpio_pulse_delay_us(GXFP_GPIO_PULSE_HIGH_US); + gpiod_set_value_cansleep(gdev->hw.gpio_write_done, 0); + + gxfp_gpio_pulse_delay_us(GXFP_GPIO_READ_DONE_CLEAR_DELAY_US); +} + +void gxfp_gpio_pulse_read_done(struct gxfp_dev *gdev) +{ + gxfp_gpio_set_read_done(gdev, 1); + gxfp_gpio_pulse_delay_us(GXFP_GPIO_READ_DONE_HIGH_US); + gxfp_gpio_set_read_done(gdev, 0); +} + +void gxfp_gpio_set_read_done(struct gxfp_dev *gdev, int value) +{ + if (!gdev || !gdev->hw.gpio_read_done) + return; + + gpiod_set_value_cansleep(gdev->hw.gpio_read_done, value ? 1 : 0); + + if (!value) + gxfp_gpio_pulse_delay_us(GXFP_GPIO_READ_DONE_CLEAR_DELAY_US); +} + +void gxfp_gpio_force_done_deassert(struct device *dev, struct gxfp_dev *gd= ev) +{ + if (!dev || !gdev || !gdev->hw.gpio_write_done || !gdev->hw.gpio_read_don= e) + return; + + gpiod_set_value_cansleep(gdev->hw.gpio_write_done, 0); + gpiod_set_value_cansleep(gdev->hw.gpio_read_done, 0); +} diff --git a/drivers/misc/gxfp5130/hw/gxfp_gpio.h b/drivers/misc/gxfp5130/h= w/gxfp_gpio.h new file mode 100644 index 000000000..a36550ccf --- /dev/null +++ b/drivers/misc/gxfp5130/hw/gxfp_gpio.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_GPIO_H +#define __GXFP_GPIO_H + +#include + +struct gxfp_dev; + +void gxfp_gpio_pulse_write_done(struct gxfp_dev *gdev); + +void gxfp_gpio_set_read_done(struct gxfp_dev *gdev, int value); + +void gxfp_gpio_pulse_read_done(struct gxfp_dev *gdev); + +void gxfp_gpio_force_done_deassert(struct device *dev, struct gxfp_dev *gd= ev); + +#endif /* __GXFP_GPIO_H */ diff --git a/drivers/misc/gxfp5130/hw/gxfp_mmio.c b/drivers/misc/gxfp5130/h= w/gxfp_mmio.c new file mode 100644 index 000000000..22187cd5a --- /dev/null +++ b/drivers/misc/gxfp5130/hw/gxfp_mmio.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include + +#include "gxfp_mmio.h" + +int gxfp_mmio_write_qword_aligned(void __iomem *mmio, const __u8 *buf, siz= e_t len) +{ + size_t i, qwords; + + if (!mmio || !buf) + return -EINVAL; + if (len % 8) + return -EINVAL; + + qwords =3D len / 8; + for (i =3D 0; i < qwords; i++) { + __u64 v; + + memcpy(&v, buf + i * 8, 8); + writeq(v, mmio + i * 8); + } + wmb(); /* ensure all writes are visible to the device before returning */ + return 0; +} + +int gxfp_mmio_read_qword_aligned(__u8 *buf, void __iomem *mmio, size_t len) +{ + size_t i, qwords; + + if (!mmio || !buf) + return -EINVAL; + if (len % 8) + return -EINVAL; + + qwords =3D len / 8; + for (i =3D 0; i < qwords; i++) { + __u64 v =3D readq(mmio + i * 8); + + memcpy(buf + i * 8, &v, 8); + } + rmb(); /* ensure all reads are complete before caller processes data */ + return 0; +} diff --git a/drivers/misc/gxfp5130/hw/gxfp_mmio.h b/drivers/misc/gxfp5130/h= w/gxfp_mmio.h new file mode 100644 index 000000000..b701682a9 --- /dev/null +++ b/drivers/misc/gxfp5130/hw/gxfp_mmio.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_MMIO_H +#define __GXFP_MMIO_H + +#include + +int gxfp_mmio_write_qword_aligned(void __iomem *mmio, const __u8 *buf, siz= e_t len); +int gxfp_mmio_read_qword_aligned(__u8 *buf, void __iomem *mmio, size_t len= ); + +#endif /* __GXFP_MMIO_H */ diff --git a/drivers/misc/gxfp5130/include/gxfp_constants.h b/drivers/misc/= gxfp5130/include/gxfp_constants.h new file mode 100644 index 000000000..ff4025cd4 --- /dev/null +++ b/drivers/misc/gxfp5130/include/gxfp_constants.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_CONSTANTS_H +#define __GXFP_CONSTANTS_H + +#define GXFP_MAILBOX_MIN_SIZE 0x1000 + +#define GXFP_READ_CHUNK_MAX 0xe00 +#define GXFP_TX_BUFFER_SIZE 0x200 + +#define GXFP_CMD_FIRMWARE_VERSION 0xA8 +#define GXFP_CMD_TRIGGER_MCU_STATE 0xAF +#define GXFP_CMD_QUERY_MCU_STATE 0xAE +#define GXFP_CMD_ACK 0xB0 +#define GXFP_CMD_UPLOAD_CONFIG_MCU 0x90 +#define GXFP_CMD_NOTIFY_POWER_STATE 0x0E +#define GXFP_CMD_PROTOCOL_INIT 0x01 +#define GXFP_CMD_SLEEP_CLEANUP 0xD2 +#define GXFP_CMD_RESET_DEVICE 0xA1 +#define GXFP_CMD_TLS_UNLOCK 0xD4 + +#define GXFP_MAX_VERSION_RAW 512 +#define GXFP_MAX_VERSION_ASCII 32 +#define GXFP_MAX_MCU_STATE_PAYLOAD 256 + +#define GXFP_DEFAULT_RETRY_COUNT 8 +#define GXFP_DEFAULT_TIMEOUT_US 15000 +#define GXFP_RESET_DELAY_US 200000 + +#define GXFP_FRAME_ALIGNMENT 8 + +#define GXFP_MP_TYPE_A 0xAu +#define GXFP_MP_TYPE_B 0xBu +#define GXFP_MP_TYPE_C 0xCu + +#define GXFP_ESPI_WRAPPER_MAGIC 0xF0 +#define GXFP_ESPI_SEQ16_INIT 0x8881 + +#endif /* __GXFP_CONSTANTS_H */ diff --git a/drivers/misc/gxfp5130/include/gxfp_priv.h b/drivers/misc/gxfp5= 130/include/gxfp_priv.h new file mode 100644 index 000000000..71c6d8332 --- /dev/null +++ b/drivers/misc/gxfp5130/include/gxfp_priv.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_PRIV_H +#define __GXFP_PRIV_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gxfp_dev { + struct device *dev; + + struct mutex lock; + atomic_t mailbox_seq16; + + struct { + void __iomem *mailbox_mmio; + resource_size_t mailbox_size; + + struct gpio_desc *gpio_write_done; + struct gpio_desc *gpio_read_done; + struct gpio_desc *gpio_irq; + int irq; + } hw; + + struct { + __u8 *rx_buf; + size_t rx_cap; + __u8 *rx_tmp; + + bool assem_active; + size_t assem_total; + size_t assem_copied; + } irq; + + struct { + struct miscdevice miscdev; + bool misc_registered; + + struct mutex rxq_mutex; + spinlock_t rxq_lock; + wait_queue_head_t rxq_wq; + struct kfifo rxq_fifo; /* bytes: [tap_hdr][payload][tap_hdr][payload]...= */ + bool rxq_inited; + bool rxq_reader_open; + } uapi; + +}; + +#endif /* __GXFP_PRIV_H */ diff --git a/drivers/misc/gxfp5130/proto/gxfp_goodix_proto.c b/drivers/misc= /gxfp5130/proto/gxfp_goodix_proto.c new file mode 100644 index 000000000..03a30cd6d --- /dev/null +++ b/drivers/misc/gxfp5130/proto/gxfp_goodix_proto.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include + +#include "gxfp_goodix_proto.h" + +bool gxfp_parse_goodix_body(const __u8 *buf, size_t buf_len, + struct gxfp_frame_parsed *out) +{ + size_t need; + __u16 decl_len, payload_len; + __u8 cmd; + __u8 proto_ck; + + if (!out || !buf || buf_len < 4) + return false; + memset(out, 0, sizeof(*out)); + + cmd =3D buf[0]; + decl_len =3D (__u16)buf[1] | ((__u16)buf[2] << 8); + if (decl_len < 1) + return false; + payload_len =3D (__u16)(decl_len - 1); + + need =3D 3u + (size_t)payload_len + 1u; + if (need > buf_len) + return false; + + out->cmd =3D cmd; + out->decl_len =3D decl_len; + out->payload =3D &buf[3]; + out->payload_len =3D payload_len; + proto_ck =3D buf[3 + payload_len]; + out->proto_checksum =3D proto_ck; + out->proto_checksum_ok =3D + (gxfp_goodix_proto_checksum(cmd, decl_len, out->payload, payload_len) = =3D=3D proto_ck); + out->valid =3D true; + + return true; +} + +__u8 gxfp_goodix_proto_checksum(__u8 cmd, __u16 decl_len, + const __u8 *payload, size_t payload_len) +{ + __u32 sum =3D (__u32)cmd + (__u32)(decl_len & 0xff) + (__u32)((decl_len >= > 8) & 0xff); + size_t i; + + for (i =3D 0; i < payload_len; i++) + sum +=3D (__u32)payload[i]; + + return (__u8)(0xaa - (__u8)sum); +} + +int gxfp_goodix_build_frame(__u8 cmd, + const __u8 *payload, + size_t payload_len, + __u8 *out, + size_t out_cap) +{ + __u16 decl_len; + size_t total; + + if (!out || out_cap < 4) + return -EINVAL; + if (payload_len && !payload) + return -EINVAL; + if (payload_len > GXFP_GOODIX_PAYLOAD_U16_MAX) + return -EOVERFLOW; + + decl_len =3D (__u16)(1u + payload_len); + total =3D 1u + 2u + payload_len + 1u; + if (total > out_cap) + return -EMSGSIZE; + + out[0] =3D cmd; + out[1] =3D (__u8)(decl_len & 0xff); + out[2] =3D (__u8)((decl_len >> 8) & 0xff); + if (payload_len) + memcpy(&out[3], payload, payload_len); + out[3 + payload_len] =3D gxfp_goodix_proto_checksum(cmd, decl_len, payloa= d, payload_len); + + return (int)total; +} diff --git a/drivers/misc/gxfp5130/proto/gxfp_goodix_proto.h b/drivers/misc= /gxfp5130/proto/gxfp_goodix_proto.h new file mode 100644 index 000000000..4bbee6f79 --- /dev/null +++ b/drivers/misc/gxfp5130/proto/gxfp_goodix_proto.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_GOODIX_PROTO_H +#define __GXFP_GOODIX_PROTO_H + +#include + +#define GXFP_GOODIX_PAYLOAD_U16_MAX 0xFF00u + +struct gxfp_frame_parsed { + bool valid; + bool proto_checksum_ok; + __u8 cmd; + __u16 decl_len; + const __u8 *payload; + __u16 payload_len; + __u8 proto_checksum; +}; + +bool gxfp_parse_goodix_body(const __u8 *buf, size_t buf_len, + struct gxfp_frame_parsed *out); + +__u8 gxfp_goodix_proto_checksum(__u8 cmd, __u16 decl_len, + const __u8 *payload, size_t payload_len); + +int gxfp_goodix_build_frame(__u8 cmd, + const __u8 *payload, + size_t payload_len, + __u8 *out, + size_t out_cap); + +#endif /* __GXFP_GOODIX_PROTO_H */ diff --git a/drivers/misc/gxfp5130/proto/gxfp_mp_proto.c b/drivers/misc/gxf= p5130/proto/gxfp_mp_proto.c new file mode 100644 index 000000000..19fa44e36 --- /dev/null +++ b/drivers/misc/gxfp5130/proto/gxfp_mp_proto.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include + +#include "gxfp_mp_proto.h" +#include "gxfp_constants.h" + +bool gxfp_parse_mp_frame(const __u8 *buf, size_t buf_len, + struct gxfp_mp_frame_parsed *out) +{ + __u16 mp_len =3D 0; + size_t total; + + if (!out || !buf || buf_len < 4) + return false; + memset(out, 0, sizeof(*out)); + + if (!gxfp_check_mp_head(buf, 4, &mp_len)) + return false; + + total =3D 4u + (size_t)mp_len; + if (total > buf_len) + return false; + + out->flags =3D buf[0]; + out->payload_len =3D mp_len; + out->payload =3D &buf[4]; + out->valid =3D true; + return true; +} + +bool gxfp_check_mp_head(const __u8 *buf, size_t buf_len, __u16 *out_mp_len) +{ + __u8 flags, type, ck_expect; + __u16 mp_len; + + if (out_mp_len) + *out_mp_len =3D 0; + if (!buf || buf_len < 4) + return false; + + flags =3D buf[0]; + type =3D flags >> 4; + if (type !=3D GXFP_MP_TYPE_A && + type !=3D GXFP_MP_TYPE_B && + type !=3D GXFP_MP_TYPE_C) + return false; + + mp_len =3D (__u16)buf[1] | ((__u16)buf[2] << 8); + if (mp_len >=3D (__u16)GXFP_GOODIX_MP_LEN_MAX) + return false; + + ck_expect =3D (__u8)(flags + buf[1] + buf[2]); + if (buf[3] !=3D ck_expect) + return false; + + if (out_mp_len) + *out_mp_len =3D mp_len; + return true; +} + +int gxfp_mp_build_frame(__u8 mp_type, + const __u8 *payload, + size_t payload_len, + __u8 *out, + size_t out_cap) +{ + __u8 flags; + __u16 mp_len; + size_t total; + + if (!out || out_cap < 4) + return -EINVAL; + if (payload_len && !payload) + return -EINVAL; + if (payload_len > GXFP_GOODIX_MP_LEN_MAX) + return -EOVERFLOW; + if (mp_type !=3D GXFP_MP_TYPE_A && mp_type !=3D GXFP_MP_TYPE_B && mp_type= !=3D GXFP_MP_TYPE_C) + return -EINVAL; + + mp_len =3D (__u16)payload_len; + total =3D 4u + payload_len; + if (total > out_cap) + return -EMSGSIZE; + + flags =3D (__u8)(mp_type << 4); + out[0] =3D flags; + out[1] =3D (__u8)(mp_len & 0xff); + out[2] =3D (__u8)((mp_len >> 8) & 0xff); + out[3] =3D (__u8)(out[0] + out[1] + out[2]); + if (payload_len) + memcpy(&out[4], payload, payload_len); + + return (int)total; +} diff --git a/drivers/misc/gxfp5130/proto/gxfp_mp_proto.h b/drivers/misc/gxf= p5130/proto/gxfp_mp_proto.h new file mode 100644 index 000000000..9773c09be --- /dev/null +++ b/drivers/misc/gxfp5130/proto/gxfp_mp_proto.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_MP_PROTO_H +#define __GXFP_MP_PROTO_H + +#include + +#define GXFP_GOODIX_MP_LEN_MAX 0x6c00u + +struct gxfp_mp_frame_parsed { + bool valid; + __u8 flags; + __u16 payload_len; + const __u8 *payload; +}; + +bool gxfp_parse_mp_frame(const __u8 *buf, size_t buf_len, + struct gxfp_mp_frame_parsed *out); + +bool gxfp_check_mp_head(const __u8 *buf, size_t buf_len, __u16 *out_mp_len= ); + +int gxfp_mp_build_frame(__u8 mp_type, + const __u8 *payload, + size_t payload_len, + __u8 *out, + size_t out_cap); + +#endif /* __GXFP_MP_PROTO_H */ diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_common.c b/drivers/m= isc/gxfp5130/transport/gxfp_espi_common.c new file mode 100644 index 000000000..fd93a2d86 --- /dev/null +++ b/drivers/misc/gxfp5130/transport/gxfp_espi_common.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +#include "gxfp_espi_common.h" +#include "gxfp_priv.h" +#include "../hw/gxfp_mmio.h" +#include "../proto/gxfp_mp_proto.h" + +void gxfp_espi_irq_assem_reset(struct gxfp_dev *gdev) +{ + gdev->irq.assem_active =3D false; + gdev->irq.assem_total =3D 0; + gdev->irq.assem_copied =3D 0; +} + +int gxfp_espi_rx_read_window(struct gxfp_dev *gdev, __u8 *dst) +{ + int ret; + + ret =3D gxfp_mmio_read_qword_aligned(dst, + gdev->hw.mailbox_mmio + GXFP_ESPI_RX_OFF, + (size_t)GXFP_ESPI_CHUNK_MAX); + if (ret) + return -EIO; + + return 0; +} + +void gxfp_espi_rx_chunk_calc(size_t remain, + size_t *chunk, + size_t *pad, + size_t *do_read) +{ + *chunk =3D min_t(size_t, remain, (size_t)GXFP_ESPI_CHUNK_MAX); + *pad =3D (GXFP_ESPI_ALIGN - (*chunk & (GXFP_ESPI_ALIGN - 1))) & + (GXFP_ESPI_ALIGN - 1); + if (do_read) + *do_read =3D *chunk + *pad; +} + +int gxfp_espi_rx_start_packet(struct gxfp_dev *gdev, + __u8 *rx_buf, + size_t rx_cap, + bool validate_head, + size_t *out_total, + size_t *out_first_chunk) +{ + size_t total; + __u16 mp_len =3D 0; + int ret; + + ret =3D gxfp_espi_rx_read_window(gdev, rx_buf); + if (ret) + return ret; + + if (validate_head) { + if (!gxfp_check_mp_head(rx_buf, 4, &mp_len)) { + dev_warn_ratelimited(gdev->dev, + "ESPI RX: bad package head (gxfp_check_mp_head failed)\n"); + return -EBADMSG; + } + } else { + mp_len =3D get_unaligned_le16(rx_buf + 1); + } + + total =3D 4u + (size_t)mp_len; + if (total =3D=3D 0 || total > rx_cap) + return -EMSGSIZE; + + if (out_total) + *out_total =3D total; + if (out_first_chunk) + *out_first_chunk =3D min_t(size_t, total, (size_t)GXFP_ESPI_CHUNK_MAX); + + return 0; +} diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_common.h b/drivers/m= isc/gxfp5130/transport/gxfp_espi_common.h new file mode 100644 index 000000000..75607c478 --- /dev/null +++ b/drivers/misc/gxfp5130/transport/gxfp_espi_common.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_ESPI_COMMON_H +#define __GXFP_ESPI_COMMON_H + +#include +#include "../include/gxfp_constants.h" + +struct gxfp_dev; + +#define GXFP_ESPI_RX_OFF 0x200 +#define GXFP_ESPI_CHUNK_MAX GXFP_READ_CHUNK_MAX +#define GXFP_ESPI_ALIGN GXFP_FRAME_ALIGNMENT + +void gxfp_espi_irq_assem_reset(struct gxfp_dev *gdev); +int gxfp_espi_rx_read_window(struct gxfp_dev *gdev, __u8 *dst); +void gxfp_espi_rx_chunk_calc(size_t remain, + size_t *chunk, + size_t *pad, + size_t *do_read); +int gxfp_espi_rx_start_packet(struct gxfp_dev *gdev, + __u8 *rx_buf, + size_t rx_cap, + bool validate_head, + size_t *out_total, + size_t *out_first_chunk); + +#endif /* __GXFP_ESPI_COMMON_H */ diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.c b/drivers/m= isc/gxfp5130/transport/gxfp_espi_rx_irq.c new file mode 100644 index 000000000..43c424366 --- /dev/null +++ b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include + +#include "gxfp_espi_common.h" +#include "gxfp_espi_rx_irq.h" +#include "gxfp_priv.h" +#include "../hw/gxfp_gpio.h" + +int gxfp_espi_irq_read_step(struct gxfp_dev *gdev, + __u8 *rx_buf, + size_t rx_cap, + size_t *out_rx_len, + bool *complete) +{ + int ret; + size_t total; + size_t first_chunk; + size_t copied; + + if (complete) + *complete =3D false; + if (out_rx_len) + *out_rx_len =3D 0; + + if (!gdev || !rx_buf || rx_cap < GXFP_ESPI_CHUNK_MAX) + return -EINVAL; + if (!gdev->hw.mailbox_mmio) + return -ENODEV; + if (gdev->hw.mailbox_size < GXFP_ESPI_RX_OFF + GXFP_ESPI_CHUNK_MAX) + return -ENODEV; + + if (!gdev->irq.assem_active) { + ret =3D gxfp_espi_rx_start_packet(gdev, rx_buf, rx_cap, true, + &total, &first_chunk); + if (ret) { + gxfp_espi_irq_assem_reset(gdev); + return ret; + } + + gdev->irq.assem_total =3D total; + gdev->irq.assem_copied =3D first_chunk; + gdev->irq.assem_active =3D true; + + copied =3D gdev->irq.assem_copied; + if (copied >=3D total) { + gxfp_gpio_set_read_done(gdev, 1); + gxfp_espi_irq_assem_reset(gdev); + if (complete) + *complete =3D true; + if (out_rx_len) + *out_rx_len =3D total; + return 0; + } + + gxfp_gpio_pulse_read_done(gdev); + if (out_rx_len) + *out_rx_len =3D copied; + return 0; + } + + total =3D gdev->irq.assem_total; + copied =3D gdev->irq.assem_copied; + if (total =3D=3D 0 || copied =3D=3D 0 || copied > rx_cap) { + gxfp_espi_irq_assem_reset(gdev); + return -EINVAL; + } + + { + size_t remain =3D (copied < total) ? (total - copied) : 0; + size_t chunk; + size_t pad; + __u8 *tmp =3D gdev->irq.rx_tmp; + + gxfp_espi_rx_chunk_calc(remain, &chunk, &pad, NULL); + if (chunk =3D=3D 0) { + gxfp_espi_irq_assem_reset(gdev); + if (complete) + *complete =3D true; + if (out_rx_len) + *out_rx_len =3D total; + return 0; + } + + if (copied + chunk > rx_cap) { + gxfp_espi_irq_assem_reset(gdev); + return -EMSGSIZE; + } + + if (!tmp) { + gxfp_espi_irq_assem_reset(gdev); + return -ENODEV; + } + + ret =3D gxfp_espi_rx_read_window(gdev, tmp); + if (ret) { + gxfp_espi_irq_assem_reset(gdev); + return ret; + } + + memcpy(rx_buf + copied, tmp, chunk); + copied +=3D chunk; + gdev->irq.assem_copied =3D copied; + } + + if (copied < total) { + gxfp_gpio_pulse_read_done(gdev); + if (out_rx_len) + *out_rx_len =3D copied; + return 0; + } + + gxfp_gpio_set_read_done(gdev, 1); + gxfp_espi_irq_assem_reset(gdev); + if (complete) + *complete =3D true; + if (out_rx_len) + *out_rx_len =3D total; + return 0; +} diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.h b/drivers/m= isc/gxfp5130/transport/gxfp_espi_rx_irq.h new file mode 100644 index 000000000..98ef1fc18 --- /dev/null +++ b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_irq.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_ESPI_RX_IRQ_H +#define __GXFP_ESPI_RX_IRQ_H + +#include + +struct gxfp_dev; + +int gxfp_espi_irq_read_step(struct gxfp_dev *gdev, + __u8 *rx_buf, + size_t rx_cap, + size_t *out_rx_len, + bool *complete); + +#endif /* __GXFP_ESPI_RX_IRQ_H */ diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.c b/drivers/= misc/gxfp5130/transport/gxfp_espi_rx_sync.c new file mode 100644 index 000000000..5afb6e358 --- /dev/null +++ b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +#include "gxfp_espi_common.h" +#include "gxfp_espi_rx_sync.h" +#include "gxfp_priv.h" +#include "../hw/gxfp_delay.h" +#include "../hw/gxfp_gpio.h" +#include "gxfp_constants.h" + +static int gxfp_espi_read_assemble(struct gxfp_dev *gdev, + __u8 *dst, + size_t dst_cap, + size_t *out_len) +{ + size_t total; + size_t first_chunk; + size_t off; + int ret; + + if (!gdev || !dst || dst_cap < GXFP_ESPI_CHUNK_MAX) + return -EINVAL; + if (!gdev->hw.mailbox_mmio) + return -ENODEV; + if (gdev->hw.mailbox_size < GXFP_ESPI_RX_OFF + GXFP_ESPI_CHUNK_MAX) + return -ENODEV; + + memset(dst, 0, dst_cap); + + ret =3D gxfp_espi_rx_start_packet(gdev, dst, dst_cap, false, + &total, &first_chunk); + if (ret) + return ret; + + dev_dbg(gdev->dev, "ESPI RX: first=3D%u total=3D%zu (mp_len=3D%u) head16= =3D%*ph\n", + GXFP_ESPI_CHUNK_MAX, total, + (unsigned int)get_unaligned_le16(dst + 1), 16, dst); + off =3D first_chunk; + + while (off < total) { + size_t remain =3D total - off; + size_t chunk; + size_t pad; + __u8 *tmp =3D gdev->irq.rx_tmp; + + gxfp_espi_rx_chunk_calc(remain, &chunk, &pad, NULL); + + dev_dbg(gdev->dev, + "ESPI RX: off=3D%zu remain=3D%zu chunk=3D%zu pad=3D%zu\n", + off, remain, chunk, pad); + + if (!tmp) + return -ENODEV; + + if (off + chunk > dst_cap) + return -EMSGSIZE; + + ret =3D gxfp_espi_rx_read_window(gdev, tmp); + if (ret) + return ret; + + memcpy(dst + off, tmp, chunk); + + off +=3D chunk; + + if (off < total) { + gxfp_gpio_pulse_read_done(gdev); + gxfp_busy_wait_us(GXFP_DEFAULT_TIMEOUT_US); + } + } + + gxfp_gpio_pulse_read_done(gdev); + dev_dbg(gdev->dev, "ESPI RX: done total=3D%zu\n", total); + + if (out_len) + *out_len =3D total; + + return 0; +} + +int gxfp_espi_read(struct gxfp_dev *gdev, + __u8 *rx_buf, + size_t rx_cap, + size_t *out_rx_len) +{ + if (!gdev || !rx_buf || rx_cap =3D=3D 0) + return -EINVAL; + if (!gdev->hw.mailbox_mmio) + return -ENODEV; + + return gxfp_espi_read_assemble(gdev, rx_buf, rx_cap, out_rx_len); +} diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.h b/drivers/= misc/gxfp5130/transport/gxfp_espi_rx_sync.h new file mode 100644 index 000000000..665f82348 --- /dev/null +++ b/drivers/misc/gxfp5130/transport/gxfp_espi_rx_sync.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_ESPI_RX_SYNC_H +#define __GXFP_ESPI_RX_SYNC_H + +#include + +struct gxfp_dev; + +int gxfp_espi_read(struct gxfp_dev *gdev, + __u8 *rx_buf, + size_t rx_cap, + size_t *out_rx_len); + +#endif /* __GXFP_ESPI_RX_SYNC_H */ diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_tx.c b/drivers/misc/= gxfp5130/transport/gxfp_espi_tx.c new file mode 100644 index 000000000..05d67ed04 --- /dev/null +++ b/drivers/misc/gxfp5130/transport/gxfp_espi_tx.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include + +#include "gxfp_espi_tx.h" +#include "gxfp_espi_rx_sync.h" +#include "gxfp_espi_common.h" +#include "gxfp_priv.h" +#include "../hw/gxfp_delay.h" +#include "../hw/gxfp_gpio.h" +#include "../hw/gxfp_mmio.h" +#include "gxfp_constants.h" +#include "../driver/gxfp_trace.h" + +int gxfp_espi_xfer(struct gxfp_dev *gdev, + const __u8 *mp_frame, + size_t mp_frame_len, + __u8 *rx_buf, + size_t rx_cap, + size_t *out_rx_len) +{ + int ret; + + ret =3D gxfp_espi_write(gdev, mp_frame, mp_frame_len); + if (ret) + return ret; + + gxfp_busy_wait_us(1000); + + return gxfp_espi_read(gdev, rx_buf, rx_cap, out_rx_len); +} + +int gxfp_espi_write(struct gxfp_dev *gdev, + const __u8 *mp_frame, + size_t mp_frame_len) +{ + __u8 *tx; + __u16 seq16; + size_t pad; + size_t tx_len; + int ret; + + if (!gdev || !mp_frame || mp_frame_len =3D=3D 0) + return -EINVAL; + if (!gdev->hw.mailbox_mmio) + return -ENODEV; + + pad =3D (GXFP_ESPI_ALIGN - (mp_frame_len & (GXFP_ESPI_ALIGN - 1))) & + (GXFP_ESPI_ALIGN - 1); + tx_len =3D 8 + mp_frame_len + pad; + if (tx_len > GXFP_TX_BUFFER_SIZE) + return -EOVERFLOW; + + tx =3D kmalloc(tx_len, GFP_KERNEL); + if (!tx) + return -ENOMEM; + + memset(tx, 0, tx_len); + tx[0] =3D GXFP_ESPI_WRAPPER_MAGIC; + tx[1] =3D (__u8)(mp_frame_len & 0xff); + tx[2] =3D (__u8)((mp_frame_len >> 8) & 0xff); + tx[3] =3D (__u8)((tx[0] + tx[1] + tx[2]) & 0xff); + + seq16 =3D (__u16)(atomic_inc_return(&gdev->mailbox_seq16) & 0xFFFF); + tx[4] =3D (__u8)(seq16 & 0xff); + tx[5] =3D (__u8)((seq16 >> 8) & 0xff); + + memcpy(&tx[8], mp_frame, mp_frame_len); + + ret =3D gxfp_mmio_write_qword_aligned(gdev->hw.mailbox_mmio, tx, + ALIGN(tx_len, GXFP_ESPI_ALIGN)); + gxfp_trace_logf("espi_tx rc=3D%d seq=3D0x%04x mp_len=3D%zu mp_head=3D%*ph= ", ret, + seq16, mp_frame_len, (int)min_t(size_t, 8, mp_frame_len), mp_frame); + if (ret) { + dev_err_ratelimited(gdev->dev, + "ESPI TX: mmio_write failed rc=3D%d seq16=3D0x%04x payload_len=3D%zu\n", + ret, seq16, mp_frame_len); + kfree(tx); + return -EIO; + } + + kfree(tx); + gxfp_gpio_pulse_write_done(gdev); + return 0; +} diff --git a/drivers/misc/gxfp5130/transport/gxfp_espi_tx.h b/drivers/misc/= gxfp5130/transport/gxfp_espi_tx.h new file mode 100644 index 000000000..e1a788ab5 --- /dev/null +++ b/drivers/misc/gxfp5130/transport/gxfp_espi_tx.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __GXFP_ESPI_TX_H +#define __GXFP_ESPI_TX_H + +#include + +struct gxfp_dev; + +int gxfp_espi_xfer(struct gxfp_dev *gdev, + const __u8 *mp_frame, + size_t mp_frame_len, + __u8 *rx_buf, + size_t rx_cap, + size_t *out_rx_len); + +int gxfp_espi_write(struct gxfp_dev *gdev, + const __u8 *mp_frame, + size_t mp_frame_len); + +#endif /* __GXFP_ESPI_TX_H */ --=20 2.55.0 From nobody Sat Jul 25 04:30:07 2026 Received: from mail-wm2-f1.google.com (mail-wm2-f1.google.com [74.125.225.129]) (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 C2656382365 for ; Sat, 18 Jul 2026 08:14:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.129 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784362482; cv=none; b=d/iELRiY6JNb4j6NHD7uUC8wDHWJmB/GzZEwbcWX1ppXWOUeFxhojZFb8d78Oh2msx7DjcnKz/Q6Z5SeCBNaVWzy3VpisEUwlTFvjmY9yCk5crh9WkTgioUHhb0i3z8I1j1sm+3jCzaZpJJNXf2CMAfZqZL96ErGmWzxQciat4o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784362482; c=relaxed/simple; bh=XL9SHzqUU4I9q1TOJJm0qrvHJGljsH5Wa/3R3Q++ISc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=VJ7ptTQBlCm4gyuJCFSnLUS34PHROr+/NFdqrpgq500HQud+fZqSb2llw810bGl0Uc7jdM0HaPBGFWEPUApXO7l/QjhEEAQmKefIqKaEq2LWRPSqc/feweMOT7mAGb406r1b+17qCwC5uvWede6hOK6PaumVJDGBuNKmdYamLJQ= 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=Sz4SFOtq; arc=none smtp.client-ip=74.125.225.129 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="Sz4SFOtq" Received: by mail-wm2-f1.google.com with SMTP id 5b1f17b1804b1-4923680507aso2604245e9.0 for ; Sat, 18 Jul 2026 01:14:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784362479; x=1784967279; 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=nElWDkdLXGPOvjS1cFwIf8i11/YAz84PJLo/bNncAwI=; b=Sz4SFOtqkHCTrDt2crKY+g/VSZvBZ2SFyc8kA1x4BwDfajTnMCIiUAAH7BKPVRUGs7 JPybJoP2fd6yuY+OuiEFnf5H5o/++3CVpatsd87qyTv+ovohW03LFvCSZtyD7NcNPmiE vDW4Ti/BgwfCR8kSlq7nUQOZhU8RcOCaRrFGExyWcE0ylOnPWNgdlL/4OPKOsCH2Ofly VbxP/yWycBabZ5cgwaL+7rgn4s4rh399jIMatsSGq7Q6bbmCA+iEc2syt1Pi7l45u/GW 2GbZoQ5T7WjS9OVb3QBM/A5ABb3wv2oRlKmvYJl+7nvBRtEZ9DyNc1CngX1bdWFGStDZ OPnQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784362479; x=1784967279; 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=nElWDkdLXGPOvjS1cFwIf8i11/YAz84PJLo/bNncAwI=; b=kGE6X61Yj0g/vf+MpC+FuJP10mlyO5W4HX8GfpFs6V2bf7vOwPqtQGVuBpEH9F7gfm 0eLhlBHhp0DKWviw6SDvluFeOD/ORr/a2H1nOQ+gp2z1ayIIDP3U02WT3efFpixIy0wc dSDY1m28oiBZi+O2NGzk80+KIMtpyY3MgfNVb/xthahRDE18kxeR+nwTGE+WU9dgmpcl jzAR+HgU2xPFIX8bdeI60iqJWnBORrZaG0wyxD/37QX9Be0sxg/LCW2Kwv0RUkaKC3fM jNb/v3xxFA5hk3vdfN5NDGdIW+P/tb5FLRr49/yoazcf7icy8sdRCe+wXsQC3ymtIYSR mT7g== X-Gm-Message-State: AOJu0Yxtno+DbvlGnnZ827QKQACvAbcK+aGZUlwtrH+CN3q7QCzWmJX3 OwA0zJSsr1muSadJyt1B2y/1HriVEb0xyJy/nocxS2wNRF6Zm5e5mfbM/DjB9POe X-Gm-Gg: AfdE7cnEcJFXmpAlulUw9OkaSclhFSxAGzT6o09TOKYqVbnhguOtAklylvkv0mKBr+H j7g3jgx0KysbRvF0HPJkGfQPlpPaapYO/1qsaDtTXBqx/M+d2hYtwhl2IYASeWYLo61zbA1xFre 8b8TVzl57Bbqstts3CahhKpsdMFUmuZ7js51WRocjvAyTImH6xkNQRvBT+4AiX3h+479EKiKYBH efJN2djDcOEduiZF9I/aDJelU4XoNbzOYQK2xwhZG/tXM/eK1JRIGyQqvZSNcS6HE2Lll9rON8W gge/ZxNHCu9Ph+bJ+wTL0udnXrNV3wCmTOwSJppgK6BiSEHijWsjnJPbPvedRvLHPUwx60GriOQ f9Y1Y5nQhEA7v5ljWGAHmxaiChbVeuD3qtOaUDnOoLCgS2cURwA+EtnzmthY3Tkq/E1sryYJlqQ UQ+A== X-Received: by 2002:a05:600c:3b17:b0:495:4044:11fc with SMTP id 5b1f17b1804b1-4954a511a2fmr35340535e9.7.1784362478632; Sat, 18 Jul 2026 01:14:38 -0700 (PDT) Received: from meto.local ([95.70.199.165]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4954995b78dsm73234285e9.0.2026.07.18.01.14.37 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 18 Jul 2026 01:14:38 -0700 (PDT) From: =?UTF-8?q?Metehan=20G=C3=BCnen?= To: linux-kernel@vger.kernel.org Cc: gregkh@linuxfoundation.org, linux-acpi@vger.kernel.org, linux-gpio@vger.kernel.org, =?UTF-8?q?Metehan=20G=C3=BCnen?= Subject: [PATCH 3/4] Documentation/misc-devices: add gxfp5130.rst Date: Sat, 18 Jul 2026 11:14:30 +0300 Message-ID: <20260718081431.25456-4-metehangnen@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260718081431.25456-1-metehangnen@gmail.com> References: <20260718081431.25456-1-metehangnen@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 Document the Goodix GXFP5130 eSPI fingerprint sensor driver: hardware interface (MMIO mailbox + 3 GPIOs), userspace ABI (open/write/read/poll/ioctl), sysfs attribute, debugfs trace interface, and the table of supported laptop models. Signed-off-by: Metehan G=C3=BCnen --- Documentation/misc-devices/gxfp5130.rst | 90 +++++++++++++++++++++++++ Documentation/misc-devices/index.rst | 1 + 2 files changed, 91 insertions(+) create mode 100644 Documentation/misc-devices/gxfp5130.rst diff --git a/Documentation/misc-devices/gxfp5130.rst b/Documentation/misc-d= evices/gxfp5130.rst new file mode 100644 index 000000000..73a468a8b --- /dev/null +++ b/Documentation/misc-devices/gxfp5130.rst @@ -0,0 +1,90 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Goodix GXFP5130 Fingerprint Sensor +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Overview +-------- + +The GXFP5130 is a press-type fingerprint sensor by Goodix Technology used = in +several Huawei MateBook laptops. It connects to the host via an eSPI-based +MMIO mailbox interface and is enumerated through ACPI (HID: ``GXFP5130``). + +This driver exposes a character device (``/dev/gxfp``) that userspace +libraries such as libfprint use to perform fingerprint enrollment and +verification through fprintd. + +Hardware interface +------------------ + +The sensor communicates through a memory-mapped mailbox window provided by +the ACPI ``_CRS`` resource. Signalling uses three GPIOs: + +- **write-done**: host pulses high after writing a command frame +- **read-done**: host pulses high after reading each chunk of a response +- **irq**: sensor asserts to indicate a response is available + +All frames use a 4-byte eSPI wrapper header followed by a Goodix MP protoc= ol +frame, which in turn wraps a Goodix command/response body. + +UAPI +---- + +The character device supports the following operations: + +``open(2)`` + Requires ``CAP_SYS_ADMIN``. Only one reader is allowed at a time. + +``write(2)`` + Write a ``struct gxfp_tx_pkt_hdr`` followed by the payload bytes. + The driver wraps this in an eSPI frame and sends it to the sensor. + +``read(2)`` + Returns one response record at a time. Each record begins with a + ``struct gxfp_tap_hdr`` header followed by the response payload. + Blocks until a response is available (unless ``O_NONBLOCK``). + +``poll(2)`` + Returns ``EPOLLIN`` when a response record is available. + +``ioctl(2)`` + ``GXFP_IOCTL_FLUSH_RXQ`` =E2=80=94 discard all queued response records. + +Kernel parameters +----------------- + +None. All configuration is automatic from ACPI. + +sysfs attributes +---------------- + +``/sys/class/misc/gxfp/acpi_id`` (read-only) + ACPI HID of the enumerated device (e.g. ``GXFP5130``). + +debugfs +------- + +When ``CONFIG_DEBUG_FS`` is enabled, the driver creates +``/sys/kernel/debug/gxfp/``: + +``trace_dump`` (read-only) + Ring buffer of recent driver trace events, one per line. + Format: `` `` + +``trace_clear`` (write-only) + Writing anything clears the trace ring buffer. + +``trace_enable`` (read-write) + ``0`` to disable tracing (default), ``1`` to enable. + +Supported hardware +------------------ + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Device ACPI ID Firmware seen +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Huawei MateBook D16 2024 GXFP5130 GF_GCC_EC_20067 +Huawei MateBook X Pro 2024 GXFP5130 GF_GCC_EC_20068 +Huawei MateBook 14 2024 GXFP5130 (under investigation) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D diff --git a/Documentation/misc-devices/index.rst b/Documentation/misc-devi= ces/index.rst index f911edaec..17f78a355 100644 --- a/Documentation/misc-devices/index.rst +++ b/Documentation/misc-devices/index.rst @@ -10,6 +10,7 @@ fit into other categories. .. toctree:: :caption: Table of contents :maxdepth: 2 + gxfp5130 =20 ad525x_dpot amd-sbi --=20 2.55.0 From nobody Sat Jul 25 04:30:07 2026 Received: from mail-wr2-f1.google.com (mail-wr2-f1.google.com [74.125.225.65]) (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 59A433822BB for ; Sat, 18 Jul 2026 08:14:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.225.65 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784362485; cv=none; b=PDRCIIiBoOgxWykVYPM7ndbrGY67N8Rj5jLGrkhDJ/vwYjBcYZVoFic9UyinUMSJEoFMpY2RtL3LBoCVFi74aeuhIfNIzcHkAq3WUIsrmWHVqwe80RkE9Q2fKyoNBAfblOsnGTC0I4d3acoRx/HDPUmPJCh0L68jGjLDfzeEwtI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784362485; c=relaxed/simple; bh=26TxEaZXjVJo5tBGtmwTRSiqCE9SIgBq2GP3qLNfyYo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=M05g9d2M5P5bChnvXnRKfp5uO7H11hXRXm5iR4lItseoZNlN7C/+hXmL+rGchG3YGJmxbVNblT73+zP3xRjE2GPw3voWY/sAo7m1RoUdq8AePhLYoPfmfnw0Fi/3o+7XoToqzHAsFI9tzjClY+V6ndDrbRjnlFDD/Ojt+6vRVGA= 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=tBFAcBQw; arc=none smtp.client-ip=74.125.225.65 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="tBFAcBQw" Received: by mail-wr2-f1.google.com with SMTP id ffacd0b85a97d-46f8c3a2f24so331109f8f.0 for ; Sat, 18 Jul 2026 01:14:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784362481; x=1784967281; 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=eQ6kK9yKWQDUj7jQssc4rdEfAQwIGwXXm+Ja37pL5TU=; b=tBFAcBQw1YlEoXdB8GCekJyIePirzb+sSQz4uCwZ71m1iwXv1cVC68jLlyZaecXJeX x9RuWAzE+bzRFnObAf8H9ubdH4yuq+I0G95K34WPBdSgcJURO0qEQyUpDwK54StsLDOf zaX769FOQgjAsi+UdiUB5XDuXVyIk12p3kimFoBwgWEr/O29IEZ/i6fSDEfLEqtvGghx Zmb/LjNGijTMyYBgeu9O59aU59nNPKIB5giFxvhaqLuRWEGFM395IBTQ0Sdht2+Boxup SJDFAFIokUl9tOxaWPh1Jx+xklhIC1hq4kY6EFCVklKlOomI0F/sko9JJ7td6A36Fd1d eksw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784362481; x=1784967281; 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=eQ6kK9yKWQDUj7jQssc4rdEfAQwIGwXXm+Ja37pL5TU=; b=etLdac9WBmdGHwcoMrPOzshxJPm1NkpLYd5eANhUWVabhQp1y7br6ZiPofIgXP6Ut7 6KTDx5Ne0WDTtHst3XQEngEjxdmlFk0ie/MU3bfAz7Mp9Go4jNNq5GrEKqaBao0YJN9f yjOIHOpvg2GA6ZPKQs6fg5WQ2GdHTVYSs74zVUYxZxfUMIcUikJELgtDbVgv3ae5PW9J mfPVocL2pj/JST2gQJm+/v5eS85g8RKMonZmUg6ZqDdg0tg6xSUBQdewJuwSwj4lFjsf dkdUiKhNlTCHR/zea8OYIFz7wQGP7BCBdmESFrnkAW95fOAaavk0vOUGVjVo1AmKbnez JEGQ== X-Gm-Message-State: AOJu0YzXbg4EfCb8G8pjxGOwr8NBAHlYp8dZjriY/fz/1GqmCAOZXD8B 70hcYoZMvAOpjzYiLb0OtNtlq0OFBEHq5Z+dV1/pt102gVXmafzIs/cfEvWxRUnC X-Gm-Gg: AfdE7cky+Fxi0EbPac6yxYCL10n1u7xzfhD57rjh605jrQGOK57XM8bEfcs7blippPe ZxRQXJ+N5zH7zK+esLb/KAbNisGjHrcz33X8Xa3hl1g7czCJ09OY1/DYX4gRFNGrW1PAkqHolZN odIeF4ds0diIVxmnnLMFI/WWPQyU/nRzmoXKkzBPRtepphxKichOKrYniXvdv3h3D+tepl9mnFn mkHURcsSkgSzGGpnhtjLv0k51/Aq6s7Qpq05QHEDXX7AIJ+1wrCNl2VDMFce7qP2nSFGFgDRVAn NVxSmoWtr0Qj93C1925bY8KC1XPmnF/Gqm01jhdvTzCJncH82evAuAKK9KyC/lTzdxPZzvoz6AX CtY86mlElrmGlV6tniHg1mBm8j3FZIEFXtlzvSNHRDRM0XXi1yD1ciBdCduX/32ResED9vdTNUd v+PmKKrdEItokv X-Received: by 2002:a05:600c:548a:b0:492:6e72:eee with SMTP id 5b1f17b1804b1-4954a50b65amr34547365e9.3.1784362481213; Sat, 18 Jul 2026 01:14:41 -0700 (PDT) Received: from meto.local ([95.70.199.165]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4954995b78dsm73234285e9.0.2026.07.18.01.14.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 18 Jul 2026 01:14:40 -0700 (PDT) From: =?UTF-8?q?Metehan=20G=C3=BCnen?= To: linux-kernel@vger.kernel.org Cc: gregkh@linuxfoundation.org, linux-acpi@vger.kernel.org, linux-gpio@vger.kernel.org, =?UTF-8?q?Metehan=20G=C3=BCnen?= Subject: [PATCH 4/4] MAINTAINERS: add entry for GXFP5130 fingerprint sensor driver Date: Sat, 18 Jul 2026 11:14:31 +0300 Message-ID: <20260718081431.25456-5-metehangnen@gmail.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260718081431.25456-1-metehangnen@gmail.com> References: <20260718081431.25456-1-metehangnen@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 Add maintainer entry for the new Goodix GXFP5130 driver covering the driver source tree, UAPI header, and documentation. Signed-off-by: Metehan G=C3=BCnen --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index a674e3652..7d18b5410 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11048,6 +11048,13 @@ S: Maintained F: Documentation/devicetree/bindings/connector/gocontroll,moduline-module-= slot.yaml =20 GOODIX TOUCHSCREEN +GOODIX GXFP5130 FINGERPRINT SENSOR DRIVER +M: Metehan G=C3=BCnen +L: linux-kernel@vger.kernel.org +S: Maintained +F: Documentation/misc-devices/gxfp5130.rst +F: drivers/misc/gxfp5130/ +F: include/uapi/linux/gxfp_ioctl.h M: Hans de Goede L: linux-input@vger.kernel.org S: Maintained --=20 2.55.0