From nobody Fri Apr 3 17:33:22 2026 Received: from mail-pj1-f42.google.com (mail-pj1-f42.google.com [209.85.216.42]) (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 78B7F33F377 for ; Thu, 2 Apr 2026 01:59:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095189; cv=none; b=QI8Kk0BjVYzkmVy18WKHBr3qqeCMX4BNCrQqKG92/+9Yk3Tug0AhVAp9ZKiV6gPhQS2OOZoOJfRfp8ENW+cqsm+PsBCb02oEYkxibyg2L966O4UkHhhfJ7pInQnN6QxIXz7OwIqmgUUOsNysZVctVuBtcvRKaXNz+stEIdzU1ec= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775095189; c=relaxed/simple; bh=JIGaiWILWYd0ZoK0ikea+P2ixn29s5bt23bI5nqme0o=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=UUhhA75GWnuymtaIbYre0pQSW0vpKqXXgKPi/3SAu2eK+aw0GyCm/iesd3UUVVmp19nw9z+PQACk5/rjwKI6QyCINpeiggu0XuSgqFZA0hDSOFKkGbJ9ssXMtYBxKcS84QGpdOafNqDtcFSyGK8L3QjUNUpDJ4EUYdminfkoNH8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org; spf=pass smtp.mailfrom=chromium.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b=ZJCYjjej; arc=none smtp.client-ip=209.85.216.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=chromium.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chromium.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="ZJCYjjej" Received: by mail-pj1-f42.google.com with SMTP id 98e67ed59e1d1-35c206f0481so293708a91.0 for ; Wed, 01 Apr 2026 18:59:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1775095185; x=1775699985; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=xs3FKlG0KKJ6apTLSx2TatZL5Y4UXFT4/1d9n+29X9c=; b=ZJCYjjejDXvGwPRc74WTF1jzxQQc0BJjPcXfiMNyIEH0Ho2ki+q2LiWOKYXr1+n4+h xjcYJ6k4cNTQJKDRVR5TE2+xXhc+j5ozEidma4tUQ7V/O4/2zlGrGo9DnwnxdroOqryW KPCB9Uo0vF14/WKFnoIJBfXrcB/ENv+K9V0MY= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775095185; x=1775699985; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=xs3FKlG0KKJ6apTLSx2TatZL5Y4UXFT4/1d9n+29X9c=; b=eZEcKXmotBTcYkFGHhCgckKlYUv/Ckfqr4kzS6RruclAmi1k7/gmwFqqxZWsa+8jLf 13Ko28LF45J1kYfIXvvEfIUygfQO1fI4ZAsOu9rqK3SqqLby4W8QNxMRnu7awmEPfEZV mITju1JxEwLcag/5+5GAzgsrVTVjw0bDQpS7cskf7NM3cTOt6prdV48A9oeiDLOI/RxP LdhYQmzXGK4ZqhxJtdpaOBD64DkGxZdTmdw7XwgQm7NWT5zDE3Vg8CuEOPyRDKiAbxTQ 5i7PMynjMyxq8AWDBE8vuHtz9k3cxrI4wQxYi9L8gp/A+ajD87g1QuKAUtyLU1N54Jfp zbKw== X-Forwarded-Encrypted: i=1; AJvYcCXwrXH7BDtJ/rPqmKDWFpIrr3AdQ6xw83q52CmVm2wPOVGlOcJTJjcWt0t+S8rCUX+ZwZot6uOhV+KSdNg=@vger.kernel.org X-Gm-Message-State: AOJu0YznmjhHxF0GPKlnNBDEVThvreg7MXQMoiVrL5NrtBKQ4q/f9Jfj jJng2/gciWnPGVCgUVyO+YmrLq9ZLNgwjSvGwzBfaFTnPvdioZpePr4aCkKEVrGYLw== X-Gm-Gg: ATEYQzz/U9VVxsqyKeC+pX4rMbchjTqIi3dyQJb/p3XK+G0ZG8OcTgTYJrk6RQfbvzZ +3o0Ytkwl+TtTc4athJDW3Ik5Kf/6qEJlTPgG3jdoHZoVESwQHWXId4DjqXHiPdJYmOaualpiZV spHZNVvmjnw11QQr6ddbSe0AhC7UsiNFLVc/AM+11pcw4ZcEuqA7v1O5OzI+Ey858faeDVRVG2G OmpXI3J7m7lP+ZHsuB+A2B7ZnETyvbYYPsoA8rItc8hWD2axRJ6eZ7sRGFvFLU8i2YKVCSVFcSA Dlqz2v5zassK1O/tZuwFeM5A4MYBXX2CF27Wbwa8RhwiVWRG0nAYw0Yh/jqZhNXg8pHhtCvLrqW wW9iy2pI+T6jlMbruNSMPI9C3465auM0knnrSa4rI8j1zpEhLXRUSoz4dIiPvw7yi8DIW2YUSJ5 9Iqrp3kYTwCSrPBOJaTLOm0PTktTK3vBjFPTmUpAyW6IdMiQkrEpuD3+sLfu1gJwUsLa+X1X3Zx KBWXAWqiNf/dIW9bycm8tgD26uWoJxjoA== X-Received: by 2002:a17:90b:2783:b0:35d:974d:8f7 with SMTP id 98e67ed59e1d1-35dd40208f3mr1231386a91.1.1775095184852; Wed, 01 Apr 2026 18:59:44 -0700 (PDT) Received: from jingyliang-input-linux.c.googlers.com (111.169.168.34.bc.googleusercontent.com. [34.168.169.111]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b27478cb4fsm11187535ad.29.2026.04.01.18.59.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Apr 2026 18:59:44 -0700 (PDT) From: Jingyuan Liang Date: Thu, 02 Apr 2026 01:59:42 +0000 Subject: [PATCH v3 05/11] HID: spi-hid: add HID SPI protocol implementation 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 Message-Id: <20260402-send-upstream-v3-5-6091c458d357@chromium.org> References: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> In-Reply-To: <20260402-send-upstream-v3-0-6091c458d357@chromium.org> To: Jiri Kosina , Benjamin Tissoires , Jonathan Corbet , Mark Brown , Steven Rostedt , Masami Hiramatsu , Mathieu Desnoyers , Dmitry Torokhov , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-input@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-spi@vger.kernel.org, linux-trace-kernel@vger.kernel.org, devicetree@vger.kernel.org, hbarnor@chromium.org, tfiga@chromium.org, Jingyuan Liang , Dmitry Antipov , Angela Czubak X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1775095180; l=23224; i=jingyliang@chromium.org; s=20260213; h=from:subject:message-id; bh=JIGaiWILWYd0ZoK0ikea+P2ixn29s5bt23bI5nqme0o=; b=nZiAqmeHu4hv26Kd9OPkd+PhIzNo3aU2JQ+48z6nkx4mCCvATf3kMdHDwjTgHhCvU5n4crQWw h5kPZL1PliPAMqxM48ZAJKgNV8K223STr2rA0L4E/SJl7JAxcvXk/5a X-Developer-Key: i=jingyliang@chromium.org; a=ed25519; pk=VTYSdqslTtYOjWWoIGgYoWupGWqNSidrggReKMgfPo4= This driver follows HID Over SPI Protocol Specification 1.0 available at https://www.microsoft.com/en-us/download/details.aspx?id=3D103325. The initial version of the driver does not support: 1) multi-fragment input reports, 2) sending GET_INPUT and COMMAND output report types and processing their respective acknowledge input reports, and 3) device sleep power state. Signed-off-by: Dmitry Antipov Signed-off-by: Angela Czubak Signed-off-by: Jingyuan Liang --- drivers/hid/spi-hid/spi-hid-core.c | 582 +++++++++++++++++++++++++++++++++= +++- 1 file changed, 572 insertions(+), 10 deletions(-) diff --git a/drivers/hid/spi-hid/spi-hid-core.c b/drivers/hid/spi-hid/spi-h= id-core.c index 4723b87346d4..00b9718ba2c3 100644 --- a/drivers/hid/spi-hid/spi-hid-core.c +++ b/drivers/hid/spi-hid/spi-hid-core.c @@ -23,11 +23,16 @@ #include #include #include +#include #include #include #include +#include #include +#include #include +#include +#include #include #include #include @@ -35,12 +40,22 @@ #include #include #include +#include +#include + +/* Protocol constants */ +#define SPI_HID_READ_APPROVAL_CONSTANT 0xff +#define SPI_HID_INPUT_HEADER_SYNC_BYTE 0x5a +#define SPI_HID_INPUT_HEADER_VERSION 0x03 +#define SPI_HID_SUPPORTED_VERSION 0x0300 =20 #define SPI_HID_OUTPUT_REPORT_CONTENT_ID_DESC_REQUEST 0x00 =20 -#define SPI_HID_RESP_TIMEOUT 1000 +#define SPI_HID_MAX_RESET_ATTEMPTS 3 +#define SPI_HID_RESP_TIMEOUT 1000 =20 /* Protocol message size constants */ +#define SPI_HID_READ_APPROVAL_LEN 5 #define SPI_HID_OUTPUT_HEADER_LEN 8 =20 /* flags */ @@ -49,6 +64,22 @@ * requests. The FW becomes ready after sending the report descriptor. */ #define SPI_HID_READY 0 +/* + * refresh_in_progress is set to true while the refresh_device worker + * thread is destroying and recreating the hidraw device. When this flag + * is set to true, the ll_close and ll_open functions will not cause + * power state changes. + */ +#define SPI_HID_REFRESH_IN_PROGRESS 1 +/* + * reset_pending indicates that the device is being reset. When this flag + * is set to true, garbage interrupts triggered during reset will be + * dropped and will not cause error handling. + */ +#define SPI_HID_RESET_PENDING 2 +#define SPI_HID_RESET_RESPONSE 3 +#define SPI_HID_CREATE_DEVICE 4 +#define SPI_HID_ERROR 5 =20 /* Raw input buffer with data from the bus */ struct spi_hid_input_buf { @@ -57,6 +88,22 @@ struct spi_hid_input_buf { u8 content[]; }; =20 +/* Processed data from input report header */ +struct spi_hid_input_header { + u8 version; + u16 report_length; + u8 last_fragment_flag; + u8 sync_const; +}; + +/* Processed data from an input report */ +struct spi_hid_input_report { + u8 report_type; + u16 content_length; + u8 content_id; + u8 *content; +}; + /* Raw output report buffer to be put on the bus */ struct spi_hid_output_buf { u8 header[SPI_HID_OUTPUT_HEADER_LEN]; @@ -114,6 +161,9 @@ struct spi_hid { struct spi_device *spi; /* spi device. */ struct hid_device *hid; /* pointer to corresponding HID dev. */ =20 + struct spi_transfer input_transfer[2]; /* Transfer buffer for read and wr= ite. */ + struct spi_message input_message; /* used to execute a sequence of spi tr= ansfers. */ + struct spihid_ops *ops; struct spi_hid_conf *conf; =20 @@ -131,10 +181,20 @@ struct spi_hid { =20 unsigned long flags; /* device flags. */ =20 - /* Control lock to make sure one output transaction at a time. */ + struct work_struct reset_work; + + /* Control lock to ensure complete output transaction. */ struct mutex output_lock; + /* Power lock to make sure one power state change at a time. */ + struct mutex power_lock; + /* I/O lock to prevent concurrent output writes during the input read. */ + struct mutex io_lock; + struct completion output_done; =20 + u8 read_approval_header[SPI_HID_READ_APPROVAL_LEN]; + u8 read_approval_body[SPI_HID_READ_APPROVAL_LEN]; + u32 report_descriptor_crc32; /* HID report descriptor crc32 checksum. */ =20 u32 regulator_error_count; @@ -146,6 +206,66 @@ struct spi_hid { =20 static struct hid_ll_driver spi_hid_ll_driver; =20 +static void spi_hid_populate_read_approvals(const struct spi_hid_conf *con= f, + u8 *header_buf, u8 *body_buf) +{ + header_buf[0] =3D conf->read_opcode; + put_unaligned_be24(conf->input_report_header_address, &header_buf[1]); + header_buf[4] =3D SPI_HID_READ_APPROVAL_CONSTANT; + + body_buf[0] =3D conf->read_opcode; + put_unaligned_be24(conf->input_report_body_address, &body_buf[1]); + body_buf[4] =3D SPI_HID_READ_APPROVAL_CONSTANT; +} + +static void spi_hid_parse_dev_desc(const struct hidspi_dev_descriptor *raw, + struct spi_hid_device_descriptor *desc) +{ + desc->hid_version =3D le16_to_cpu(raw->bcd_ver); + desc->report_descriptor_length =3D le16_to_cpu(raw->rep_desc_len); + desc->max_input_length =3D le16_to_cpu(raw->max_input_len); + desc->max_output_length =3D le16_to_cpu(raw->max_output_len); + + /* FIXME: multi-fragment not supported, field below not used */ + desc->max_fragment_length =3D le16_to_cpu(raw->max_frag_len); + + desc->vendor_id =3D le16_to_cpu(raw->vendor_id); + desc->product_id =3D le16_to_cpu(raw->product_id); + desc->version_id =3D le16_to_cpu(raw->version_id); + desc->no_output_report_ack =3D le16_to_cpu(raw->flags) & BIT(0); +} + +static void spi_hid_populate_input_header(const u8 *buf, + struct spi_hid_input_header *header) +{ + header->version =3D buf[0] & 0xf; + header->report_length =3D (get_unaligned_le16(&buf[1]) & 0x3fff) * 4; + header->last_fragment_flag =3D (buf[2] & 0x40) >> 6; + header->sync_const =3D buf[3]; +} + +static void spi_hid_populate_input_body(const u8 *buf, + struct input_report_body_header *body) +{ + body->input_report_type =3D buf[0]; + body->content_len =3D get_unaligned_le16(&buf[1]); + body->content_id =3D buf[3]; +} + +static void spi_hid_input_report_prepare(struct spi_hid_input_buf *buf, + struct spi_hid_input_report *report) +{ + struct spi_hid_input_header header; + struct input_report_body_header body; + + spi_hid_populate_input_header(buf->header, &header); + spi_hid_populate_input_body(buf->body, &body); + report->report_type =3D body.input_report_type; + report->content_length =3D body.content_len; + report->content_id =3D body.content_id; + report->content =3D buf->content; +} + static void spi_hid_populate_output_header(u8 *buf, const struct spi_hid_conf *conf, const struct spi_hid_output_report *report) @@ -157,6 +277,33 @@ static void spi_hid_populate_output_header(u8 *buf, buf[7] =3D report->content_id; } =20 +static int spi_hid_input_sync(struct spi_hid *shid, void *buf, u16 length, + bool is_header) +{ + int error; + + shid->input_transfer[0].tx_buf =3D is_header ? + shid->read_approval_header : + shid->read_approval_body; + shid->input_transfer[0].len =3D SPI_HID_READ_APPROVAL_LEN; + + shid->input_transfer[1].rx_buf =3D buf; + shid->input_transfer[1].len =3D length; + + spi_message_init_with_transfers(&shid->input_message, + shid->input_transfer, 2); + + error =3D spi_sync(shid->spi, &shid->input_message); + if (error) { + dev_err(&shid->spi->dev, "Error starting sync transfer: %d.", error); + shid->bus_error_count++; + shid->bus_last_error =3D error; + return error; + } + + return 0; +} + static int spi_hid_output(struct spi_hid *shid, const void *buf, u16 lengt= h) { int error; @@ -196,6 +343,50 @@ static void spi_hid_stop_hid(struct spi_hid *shid) hid_destroy_device(hid); } =20 +static void spi_hid_error(struct spi_hid *shid) +{ + struct device *dev =3D &shid->spi->dev; + int error; + + guard(mutex)(&shid->power_lock); + if (shid->power_state =3D=3D HIDSPI_OFF) + return; + + if (shid->reset_attempts++ >=3D SPI_HID_MAX_RESET_ATTEMPTS) { + dev_err(dev, "unresponsive device, aborting."); + spi_hid_stop_hid(shid); + shid->ops->assert_reset(shid->ops); + error =3D shid->ops->power_down(shid->ops); + if (error) { + dev_err(dev, "failed to disable regulator."); + shid->regulator_error_count++; + shid->regulator_last_error =3D error; + } + return; + } + + clear_bit(SPI_HID_READY, &shid->flags); + set_bit(SPI_HID_RESET_PENDING, &shid->flags); + + shid->ops->assert_reset(shid->ops); + + shid->power_state =3D HIDSPI_OFF; + + /* + * We want to cancel pending reset work as the device is being reset + * to recover from an error. cancel_work_sync will put us in a deadlock + * because this function is scheduled in 'reset_work' and we should + * avoid waiting for itself. + */ + cancel_work(&shid->reset_work); + + shid->ops->sleep_minimal_reset_delay(shid->ops); + + shid->power_state =3D HIDSPI_ON; + + shid->ops->deassert_reset(shid->ops); +} + static int spi_hid_send_output_report(struct spi_hid *shid, struct spi_hid_output_report *report) { @@ -206,13 +397,13 @@ static int spi_hid_send_output_report(struct spi_hid = *shid, u8 padding; int error; =20 - guard(mutex)(&shid->output_lock); if (report->content_length > shid->desc.max_output_length) { dev_err(dev, "Output report too big, content_length 0x%x.", report->content_length); return -E2BIG; } =20 + guard(mutex)(&shid->io_lock); spi_hid_populate_output_header(buf->header, shid->conf, report); =20 if (report->content_length) @@ -236,6 +427,7 @@ static int spi_hid_sync_request(struct spi_hid *shid, struct device *dev =3D &shid->spi->dev; int error; =20 + guard(mutex)(&shid->output_lock); error =3D spi_hid_send_output_report(shid, report); if (error) return error; @@ -250,6 +442,86 @@ static int spi_hid_sync_request(struct spi_hid *shid, return 0; } =20 +/* + * Handle the reset response from the FW by sending a request for the devi= ce + * descriptor. + */ +static void spi_hid_reset_response(struct spi_hid *shid) +{ + struct device *dev =3D &shid->spi->dev; + struct spi_hid_output_report report =3D { + .report_type =3D DEVICE_DESCRIPTOR, + .content_length =3D 0x0, + .content_id =3D SPI_HID_OUTPUT_REPORT_CONTENT_ID_DESC_REQUEST, + .content =3D NULL, + }; + int error; + + if (test_bit(SPI_HID_READY, &shid->flags)) { + dev_err(dev, "Spontaneous FW reset!"); + clear_bit(SPI_HID_READY, &shid->flags); + shid->dir_count++; + } + + if (shid->power_state =3D=3D HIDSPI_OFF) + return; + + error =3D spi_hid_sync_request(shid, &report); + if (error) { + dev_WARN_ONCE(dev, true, + "Failed to send device descriptor request: %d.", error); + set_bit(SPI_HID_ERROR, &shid->flags); + schedule_work(&shid->reset_work); + } +} + +static int spi_hid_input_report_handler(struct spi_hid *shid, + struct spi_hid_input_buf *buf) +{ + struct device *dev =3D &shid->spi->dev; + struct spi_hid_input_report r; + int error =3D 0; + + if (!test_bit(SPI_HID_READY, &shid->flags) || + test_bit(SPI_HID_REFRESH_IN_PROGRESS, &shid->flags) || !shid->hid) { + dev_err(dev, "HID not ready"); + return 0; + } + + spi_hid_input_report_prepare(buf, &r); + + error =3D hid_input_report(shid->hid, HID_INPUT_REPORT, + r.content - 1, r.content_length + 1, 1); + + if (error =3D=3D -ENODEV || error =3D=3D -EBUSY) { + dev_err(dev, "ignoring report --> %d.", error); + return 0; + } else if (error) { + dev_err(dev, "Bad input report: %d.", error); + } + + return error; +} + +static void spi_hid_response_handler(struct spi_hid *shid, + struct input_report_body_header *body) +{ + shid->response_length =3D body->content_len; + /* completion_done returns 0 if there are waiters, otherwise 1 */ + if (completion_done(&shid->output_done)) { + dev_err(&shid->spi->dev, "Unexpected response report."); + } else { + if (body->input_report_type =3D=3D REPORT_DESCRIPTOR_RESPONSE || + body->input_report_type =3D=3D GET_FEATURE_RESPONSE) { + memcpy(shid->response->body, shid->input->body, + sizeof(shid->input->body)); + memcpy(shid->response->content, shid->input->content, + body->content_len); + } + complete(&shid->output_done); + } +} + /* * This function returns the length of the report descriptor, or a negative * error code if something went wrong. @@ -269,6 +541,8 @@ static int spi_hid_report_descriptor_request(struct spi= _hid *shid) if (ret) { dev_err(dev, "Expected report descriptor not received: %d.", ret); + set_bit(SPI_HID_ERROR, &shid->flags); + schedule_work(&shid->reset_work); return ret; } =20 @@ -323,6 +597,205 @@ static int spi_hid_create_device(struct spi_hid *shid) return 0; } =20 +static void spi_hid_refresh_device(struct spi_hid *shid) +{ + struct device *dev =3D &shid->spi->dev; + u32 new_crc32 =3D 0; + int error =3D 0; + + error =3D spi_hid_report_descriptor_request(shid); + if (error < 0) { + dev_err(dev, + "%s: failed report descriptor request: %d", + __func__, error); + return; + } + new_crc32 =3D crc32_le(0, (unsigned char const *)shid->response->content, + (size_t)error); + + /* Same report descriptor, so no need to create a new hid device. */ + if (new_crc32 =3D=3D shid->report_descriptor_crc32) { + set_bit(SPI_HID_READY, &shid->flags); + return; + } + + shid->report_descriptor_crc32 =3D new_crc32; + + set_bit(SPI_HID_REFRESH_IN_PROGRESS, &shid->flags); + + spi_hid_stop_hid(shid); + + error =3D spi_hid_create_device(shid); + if (error) { + dev_err(dev, "%s: Failed to create hid device: %d.", __func__, error); + return; + } + + clear_bit(SPI_HID_REFRESH_IN_PROGRESS, &shid->flags); +} + +static void spi_hid_reset_work(struct work_struct *work) +{ + struct spi_hid *shid =3D + container_of(work, struct spi_hid, reset_work); + struct device *dev =3D &shid->spi->dev; + int error =3D 0; + + if (test_and_clear_bit(SPI_HID_RESET_RESPONSE, &shid->flags)) { + spi_hid_reset_response(shid); + return; + } + + if (test_and_clear_bit(SPI_HID_CREATE_DEVICE, &shid->flags)) { + guard(mutex)(&shid->power_lock); + if (shid->power_state =3D=3D HIDSPI_OFF) { + dev_err(dev, "%s: Powered off, returning", __func__); + return; + } + + if (!shid->hid) { + error =3D spi_hid_create_device(shid); + if (error) { + dev_err(dev, "%s: Failed to create hid device: %d.", + __func__, error); + return; + } + } else { + spi_hid_refresh_device(shid); + } + + return; + } + + if (test_and_clear_bit(SPI_HID_ERROR, &shid->flags)) { + spi_hid_error(shid); + return; + } +} + +static int spi_hid_process_input_report(struct spi_hid *shid, + struct spi_hid_input_buf *buf) +{ + struct spi_hid_input_header header; + struct input_report_body_header body; + struct device *dev =3D &shid->spi->dev; + struct hidspi_dev_descriptor *raw; + + spi_hid_populate_input_header(buf->header, &header); + spi_hid_populate_input_body(buf->body, &body); + + if (body.content_len > header.report_length) { + dev_err(dev, "Bad body length %d > %d.", body.content_len, + header.report_length); + return -EPROTO; + } + + switch (body.input_report_type) { + case DATA: + return spi_hid_input_report_handler(shid, buf); + case RESET_RESPONSE: + clear_bit(SPI_HID_RESET_PENDING, &shid->flags); + set_bit(SPI_HID_RESET_RESPONSE, &shid->flags); + schedule_work(&shid->reset_work); + break; + case DEVICE_DESCRIPTOR_RESPONSE: + /* Mark the completion done to avoid timeout */ + spi_hid_response_handler(shid, &body); + + /* Reset attempts at every device descriptor fetch */ + shid->reset_attempts =3D 0; + raw =3D (struct hidspi_dev_descriptor *)buf->content; + + /* Validate device descriptor length before parsing */ + if (body.content_len !=3D HIDSPI_DEVICE_DESCRIPTOR_SIZE) { + dev_err(dev, "Invalid content length %d, expected %lu.", + body.content_len, + HIDSPI_DEVICE_DESCRIPTOR_SIZE); + return -EPROTO; + } + + if (le16_to_cpu(raw->dev_desc_len) !=3D + HIDSPI_DEVICE_DESCRIPTOR_SIZE) { + dev_err(dev, + "Invalid wDeviceDescLength %d, expected %lu.", + raw->dev_desc_len, + HIDSPI_DEVICE_DESCRIPTOR_SIZE); + return -EPROTO; + } + + spi_hid_parse_dev_desc(raw, &shid->desc); + + if (shid->desc.hid_version !=3D SPI_HID_SUPPORTED_VERSION) { + dev_err(dev, + "Unsupported device descriptor version %4x.", + shid->desc.hid_version); + return -EPROTONOSUPPORT; + } + + set_bit(SPI_HID_CREATE_DEVICE, &shid->flags); + schedule_work(&shid->reset_work); + + break; + case OUTPUT_REPORT_RESPONSE: + if (shid->desc.no_output_report_ack) { + dev_err(dev, "Unexpected output report response."); + break; + } + fallthrough; + case GET_FEATURE_RESPONSE: + case SET_FEATURE_RESPONSE: + case REPORT_DESCRIPTOR_RESPONSE: + spi_hid_response_handler(shid, &body); + break; + /* + * FIXME: sending GET_INPUT and COMMAND reports not supported, thus + * throw away responses to those, they should never come. + */ + case GET_INPUT_REPORT_RESPONSE: + case COMMAND_RESPONSE: + dev_err(dev, "Not a supported report type: 0x%x.", + body.input_report_type); + break; + default: + dev_err(dev, "Unknown input report: 0x%x.", body.input_report_type); + return -EPROTO; + } + + return 0; +} + +static int spi_hid_bus_validate_header(struct spi_hid *shid, + struct spi_hid_input_header *header) +{ + struct device *dev =3D &shid->spi->dev; + + if (header->version !=3D SPI_HID_INPUT_HEADER_VERSION) { + dev_err(dev, "Unknown input report version (v 0x%x).", + header->version); + return -EINVAL; + } + + if (shid->desc.max_input_length !=3D 0 && + header->report_length > shid->desc.max_input_length) { + dev_err(dev, "Input report body size %u > max expected of %u.", + header->report_length, shid->desc.max_input_length); + return -EMSGSIZE; + } + + if (header->last_fragment_flag !=3D 1) { + dev_err(dev, "Multi-fragment reports not supported."); + return -EOPNOTSUPP; + } + + if (header->sync_const !=3D SPI_HID_INPUT_HEADER_SYNC_BYTE) { + dev_err(dev, "Invalid input report sync constant (0x%x).", + header->sync_const); + return -EINVAL; + } + + return 0; +} + static int spi_hid_get_request(struct spi_hid *shid, u8 content_id) { struct device *dev =3D &shid->spi->dev; @@ -339,6 +812,8 @@ static int spi_hid_get_request(struct spi_hid *shid, u8= content_id) dev_err(dev, "Expected get request response not received! Error %d.", error); + set_bit(SPI_HID_ERROR, &shid->flags); + schedule_work(&shid->reset_work); return error; } =20 @@ -358,9 +833,83 @@ static int spi_hid_set_request(struct spi_hid *shid, u= 8 *arg_buf, u16 arg_len, return spi_hid_sync_request(shid, &report); } =20 -/* This is a placeholder. Will be implemented in the next patch. */ static irqreturn_t spi_hid_dev_irq(int irq, void *_shid) { + struct spi_hid *shid =3D _shid; + struct device *dev =3D &shid->spi->dev; + struct spi_hid_input_header header; + int error =3D 0; + + scoped_guard(mutex, &shid->io_lock) { + error =3D spi_hid_input_sync(shid, shid->input->header, + sizeof(shid->input->header), true); + if (error) { + dev_err(dev, "Failed to transfer header: %d.", error); + goto err; + } + + if (shid->power_state =3D=3D HIDSPI_OFF) { + dev_warn(dev, "Device is off after header was received."); + goto out; + } + + if (shid->input_message.status < 0) { + dev_warn(dev, "Error reading header: %d.", + shid->input_message.status); + shid->bus_error_count++; + shid->bus_last_error =3D shid->input_message.status; + goto err; + } + + spi_hid_populate_input_header(shid->input->header, &header); + + error =3D spi_hid_bus_validate_header(shid, &header); + if (error) { + if (!test_bit(SPI_HID_RESET_PENDING, &shid->flags)) { + dev_err(dev, "Failed to validate header: %d.", error); + print_hex_dump(KERN_ERR, "spi_hid: header buffer: ", + DUMP_PREFIX_NONE, 16, 1, shid->input->header, + sizeof(shid->input->header), false); + shid->bus_error_count++; + shid->bus_last_error =3D error; + goto err; + } + goto out; + } + + error =3D spi_hid_input_sync(shid, shid->input->body, header.report_leng= th, + false); + if (error) { + dev_err(dev, "Failed to transfer body: %d.", error); + goto err; + } + + if (shid->power_state =3D=3D HIDSPI_OFF) { + dev_warn(dev, "Device is off after body was received."); + goto out; + } + + if (shid->input_message.status < 0) { + dev_warn(dev, "Error reading body: %d.", + shid->input_message.status); + shid->bus_error_count++; + shid->bus_last_error =3D shid->input_message.status; + goto err; + } + } + + error =3D spi_hid_process_input_report(shid, shid->input); + if (error) { + dev_err(dev, "Failed to process input report: %d.", error); + goto err; + } + +out: + return IRQ_HANDLED; + +err: + set_bit(SPI_HID_ERROR, &shid->flags); + schedule_work(&shid->reset_work); return IRQ_HANDLED; } =20 @@ -571,10 +1120,13 @@ static int spi_hid_ll_output_report(struct hid_devic= e *hid, __u8 *buf, return -ENODEV; } =20 - if (shid->desc.no_output_report_ack) - error =3D spi_hid_send_output_report(shid, &report); - else + if (shid->desc.no_output_report_ack) { + scoped_guard(mutex, &shid->output_lock) { + error =3D spi_hid_send_output_report(shid, &report); + } + } else { error =3D spi_hid_sync_request(shid, &report); + } =20 if (error) { dev_err(dev, "failed to send output report."); @@ -662,11 +1214,23 @@ int spi_hid_core_probe(struct spi_device *spi, struc= t spihid_ops *ops, shid->power_state =3D HIDSPI_ON; shid->ops =3D ops; shid->conf =3D conf; + set_bit(SPI_HID_RESET_PENDING, &shid->flags); =20 spi_set_drvdata(spi, shid); =20 + /* Using now populated conf let's pre-calculate the read approvals */ + spi_hid_populate_read_approvals(shid->conf, shid->read_approval_header, + shid->read_approval_body); + + mutex_init(&shid->output_lock); + mutex_init(&shid->power_lock); + mutex_init(&shid->io_lock); + init_completion(&shid->output_done); + + INIT_WORK(&shid->reset_work, spi_hid_reset_work); + /* - * we need to allocate the buffer without knowing the maximum + * We need to allocate the buffer without knowing the maximum * size of the reports. Let's use SZ_2K, then we do the * real computation later. */ @@ -706,8 +1270,6 @@ int spi_hid_core_probe(struct spi_device *spi, struct = spihid_ops *ops, dev_dbg(dev, "%s: d3 -> %s.", __func__, spi_hid_power_mode_string(shid->power_state)); =20 - spi_hid_create_device(shid); - return 0; } EXPORT_SYMBOL_GPL(spi_hid_core_probe); --=20 2.53.0.1185.g05d4b7b318-goog