The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH] platform/chrome: cros_ec_proto: Fix deferred response payload handling
@ 2026-08-10 21:43 Rob Barnes
  2026-08-13  6:49 ` Tzung-Bi Shih
  0 siblings, 1 reply; 2+ messages in thread
From: Rob Barnes @ 2026-08-10 21:43 UTC (permalink / raw)
  To: bleung, tzungbi; +Cc: tomhughes, chrome-platform, linux-kernel

From: Rob Barnes <robbarnes@google.com>
Subject: [PATCH] platform/chrome: cros_ec_proto: Fix deferred response
payload handling

When cros_ec_wait_until_complete() succeeds after an
EC_RES_IN_PROGRESS status, it previously returned the 4-byte
transfer size of EC_CMD_GET_COMMS_STATUS. For 0-byte payload
commands (such as EC_CMD_FLASH_ERASE), userspace received 4
bytes instead of 0, causing response size validation errors.
For commands expecting non-zero response payloads, the kernel
exited without issuing EC_CMD_RESEND_RESPONSE, leaving response
buffers uninitialized.

Refactor cros_ec_wait_until_complete() to pass orig_msg, issue
EC_CMD_RESEND_RESPONSE into orig_msg->data when insize > 0, and
return 0 for 0-byte response commands. Save and restore orig_msg
fields (command, outsize, version) around the resend request to
prevent unintended caller struct mutations. Update existing KUnit
tests and add test cases for 4-byte and 16-byte response
payloads.

Signed-off-by: Rob Barnes <robbarnes@google.com>
---
 drivers/platform/chrome/cros_ec_proto.c      |  25 ++-
 drivers/platform/chrome/cros_ec_proto_test.c | 151 +++++++++++++++++-
 2 files changed, 168 insertions(+), 8 deletions(-)

diff --git a/drivers/platform/chrome/cros_ec_proto.c
b/drivers/platform/chrome/cros_ec_proto.c
index a655322b599e..45d6e9bce746 100644
--- a/drivers/platform/chrome/cros_ec_proto.c
+++ b/drivers/platform/chrome/cros_ec_proto.c
@@ -147,7 +147,7 @@ static int prepare_tx_legacy(struct cros_ec_device *ec_dev,
 	return EC_MSG_TX_PROTO_BYTES + msg->outsize;
 }

-static int cros_ec_wait_until_complete(struct cros_ec_device *ec_dev,
uint32_t *result)
+static int cros_ec_wait_until_complete(struct cros_ec_device *ec_dev,
+					struct cros_ec_command *orig_msg)
 {
 	struct {
 		struct cros_ec_command msg;
@@ -162,7 +162,7 @@ static int cros_ec_wait_until_complete(struct
cros_ec_device *ec_dev, uint32_t *
 		if (ret < 0)
 			return ret;

-		*result = msg->result;
+		orig_msg->result = msg->result;
 		if (msg->result != EC_RES_SUCCESS)
 			return ret;

@@ -170,8 +170,28 @@ static int cros_ec_wait_until_complete(struct
cros_ec_device *ec_dev, uint32_t *
 			break;
 		}

-		if (!(status->flags & EC_COMMS_STATUS_PROCESSING))
-			return ret;
+		if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) {
+			/*
+			 * If original command requested response payload, retrieve it via
+			 * EC_CMD_RESEND_RESPONSE into orig_msg->data. Otherwise return 0
+			 * for zero-payload commands.
+			 */
+			if (orig_msg->insize > 0) {
+				uint32_t orig_cmd = orig_msg->command;
+				uint32_t orig_outsize = orig_msg->outsize;
+				uint32_t orig_version = orig_msg->version;
+
+				orig_msg->command = EC_CMD_RESEND_RESPONSE;
+				orig_msg->outsize = 0;
+				orig_msg->version = 0;
+				ret = cros_ec_xfer_command(ec_dev, orig_msg);
+				orig_msg->command = orig_cmd;
+				orig_msg->outsize = orig_outsize;
+				orig_msg->version = orig_version;
+				return ret;
+			}
+			return 0;
+		}
 	}

 	if (i >= EC_COMMAND_RETRIES)
@@ -185,7 +205,7 @@ static int cros_ec_send_command(struct
cros_ec_device *ec_dev, struct cros_ec_co
 	int ret = cros_ec_xfer_command(ec_dev, msg);

 	if (msg->result == EC_RES_IN_PROGRESS)
-		ret = cros_ec_wait_until_complete(ec_dev, &msg->result);
+		ret = cros_ec_wait_until_complete(ec_dev, msg);

 	return ret;
 }
diff --git a/drivers/platform/chrome/cros_ec_proto_test.c
b/drivers/platform/chrome/cros_ec_proto_test.c
index 63e38671e95a..609ac8c5108f 100644
--- a/drivers/platform/chrome/cros_ec_proto_test.c
+++ b/drivers/platform/chrome/cros_ec_proto_test.c
@@ -1756,7 +1756,7 @@ static void
cros_ec_proto_test_cmd_xfer_protocol_v2_no_op(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, ret, -EIO);
 }

-static void cros_ec_proto_test_cmd_xfer_in_progress_normal(struct kunit *test)
+static void cros_ec_proto_test_cmd_xfer_in_progress_payload_0bytes(struct
kunit *test)
 {
 	struct cros_ec_proto_test_priv *priv = test->priv;
 	struct cros_ec_device *ec_dev = &priv->ec_dev;
@@ -1786,7 +1786,7 @@ static void
cros_ec_proto_test_cmd_xfer_in_progress_normal(struct kunit *test)
 	}

 	ret = cros_ec_cmd_xfer(ec_dev, &msg);
-	KUNIT_EXPECT_EQ(test, ret, sizeof(struct ec_response_get_comms_status));
+	KUNIT_EXPECT_EQ(test, ret, 0);

 	KUNIT_EXPECT_EQ(test, msg.result, EC_RES_SUCCESS);

@@ -1811,6 +1811,149 @@ static void
cros_ec_proto_test_cmd_xfer_in_progress_normal(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, cros_kunit_ec_pkt_xfer_mock_called, 2);
 }

+static void cros_ec_proto_test_cmd_xfer_in_progress_payload_4bytes(struct
kunit *test)
+{
+	struct cros_ec_proto_test_priv *priv = test->priv;
+	struct cros_ec_device *ec_dev = &priv->ec_dev;
+	struct ec_xfer_mock *mock;
+	u8 resp_data[4] = {0x11, 0x22, 0x33, 0x44};
+	struct {
+		struct cros_ec_command msg;
+		u8 data[4];
+	} buf;
+	struct cros_ec_command *msg = &buf.msg;
+	int ret;
+
+	memset(&buf, 0, sizeof(buf));
+	msg->version = 1;
+	msg->command = 0x1234;
+	msg->outsize = 2;
+	msg->insize = sizeof(buf.data);
+
+	ec_dev->pkt_xfer = cros_kunit_ec_pkt_xfer_mock;
+
+	/* For initial command returning EC_RES_IN_PROGRESS. */
+	{
+		mock = cros_kunit_ec_xfer_mock_addx(test, 0, EC_RES_IN_PROGRESS, 0);
+		KUNIT_ASSERT_PTR_NE(test, mock, NULL);
+	}
+
+	/* For EC_CMD_GET_COMMS_STATUS status polling. */
+	{
+		struct ec_response_get_comms_status *data;
+
+		mock = cros_kunit_ec_xfer_mock_add(test, sizeof(*data));
+		KUNIT_ASSERT_PTR_NE(test, mock, NULL);
+
+		data = (struct ec_response_get_comms_status *)mock->o_data;
+		data->flags = 0;
+	}
+
+	/* For EC_CMD_RESEND_RESPONSE returning 4 bytes payload. */
+	{
+		mock = cros_kunit_ec_xfer_mock_add(test, sizeof(resp_data));
+		KUNIT_ASSERT_PTR_NE(test, mock, NULL);
+		memcpy(mock->o_data, resp_data, sizeof(resp_data));
+	}
+
+	ret = cros_ec_cmd_xfer(ec_dev, msg);
+	KUNIT_EXPECT_EQ(test, ret, 4);
+	KUNIT_EXPECT_EQ(test, msg->result, EC_RES_SUCCESS);
+	KUNIT_EXPECT_EQ(test, msg->command, 0x1234);
+	KUNIT_EXPECT_EQ(test, msg->outsize, 2);
+	KUNIT_EXPECT_EQ(test, msg->version, 1);
+	KUNIT_EXPECT_EQ(test, memcmp(msg->data, resp_data, sizeof(resp_data)), 0);
+
+	/* Verify mock sequence */
+	{
+		mock = cros_kunit_ec_xfer_mock_next();
+		KUNIT_EXPECT_PTR_NE(test, mock, NULL);
+
+		mock = cros_kunit_ec_xfer_mock_next();
+		KUNIT_EXPECT_PTR_NE(test, mock, NULL);
+		KUNIT_EXPECT_EQ(test, mock->msg.command, EC_CMD_GET_COMMS_STATUS);
+
+		mock = cros_kunit_ec_xfer_mock_next();
+		KUNIT_EXPECT_PTR_NE(test, mock, NULL);
+		KUNIT_EXPECT_EQ(test, mock->msg.command, EC_CMD_RESEND_RESPONSE);
+		KUNIT_EXPECT_EQ(test, mock->msg.outsize, 0);
+		KUNIT_EXPECT_EQ(test, mock->msg.version, 0);
+		KUNIT_EXPECT_EQ(test, mock->msg.insize, 4);
+	}
+
+	KUNIT_EXPECT_EQ(test, cros_kunit_ec_pkt_xfer_mock_called, 3);
+}
+
+static void cros_ec_proto_test_cmd_xfer_in_progress_payload_gt4bytes(struct
kunit *test)
+{
+	struct cros_ec_proto_test_priv *priv = test->priv;
+	struct cros_ec_device *ec_dev = &priv->ec_dev;
+	struct ec_xfer_mock *mock;
+	u8 resp_data[16];
+	struct {
+		struct cros_ec_command msg;
+		u8 data[16];
+	} buf;
+	struct cros_ec_command *msg = &buf.msg;
+	int ret, i;
+
+	for (i = 0; i < sizeof(resp_data); ++i)
+		resp_data[i] = (u8)(i + 1);
+
+	memset(&buf, 0, sizeof(buf));
+	msg->version = 0;
+	msg->command = 0x5678;
+	msg->insize = sizeof(buf.data);
+
+	ec_dev->pkt_xfer = cros_kunit_ec_pkt_xfer_mock;
+
+	/* For initial command returning EC_RES_IN_PROGRESS. */
+	{
+		mock = cros_kunit_ec_xfer_mock_addx(test, 0, EC_RES_IN_PROGRESS, 0);
+		KUNIT_ASSERT_PTR_NE(test, mock, NULL);
+	}
+
+	/* For EC_CMD_GET_COMMS_STATUS status polling. */
+	{
+		struct ec_response_get_comms_status *data;
+
+		mock = cros_kunit_ec_xfer_mock_add(test, sizeof(*data));
+		KUNIT_ASSERT_PTR_NE(test, mock, NULL);
+
+		data = (struct ec_response_get_comms_status *)mock->o_data;
+		data->flags = 0;
+	}
+
+	/* For EC_CMD_RESEND_RESPONSE returning 16 bytes payload. */
+	{
+		mock = cros_kunit_ec_xfer_mock_add(test, sizeof(resp_data));
+		KUNIT_ASSERT_PTR_NE(test, mock, NULL);
+		memcpy(mock->o_data, resp_data, sizeof(resp_data));
+	}
+
+	ret = cros_ec_cmd_xfer(ec_dev, msg);
+	KUNIT_EXPECT_EQ(test, ret, 16);
+	KUNIT_EXPECT_EQ(test, msg->result, EC_RES_SUCCESS);
+	KUNIT_EXPECT_EQ(test, memcmp(msg->data, resp_data, sizeof(resp_data)), 0);
+
+	/* Verify mock sequence */
+	{
+		mock = cros_kunit_ec_xfer_mock_next();
+		KUNIT_EXPECT_PTR_NE(test, mock, NULL);
+
+		mock = cros_kunit_ec_xfer_mock_next();
+		KUNIT_EXPECT_PTR_NE(test, mock, NULL);
+		KUNIT_EXPECT_EQ(test, mock->msg.command, EC_CMD_GET_COMMS_STATUS);
+
+		mock = cros_kunit_ec_xfer_mock_next();
+		KUNIT_EXPECT_PTR_NE(test, mock, NULL);
+		KUNIT_EXPECT_EQ(test, mock->msg.command, EC_CMD_RESEND_RESPONSE);
+		KUNIT_EXPECT_EQ(test, mock->msg.insize, 16);
+	}
+
+	KUNIT_EXPECT_EQ(test, cros_kunit_ec_pkt_xfer_mock_called, 3);
+}
+
 static void cros_ec_proto_test_cmd_xfer_in_progress_retries_eagain(struct
kunit *test)
 {
 	struct cros_ec_proto_test_priv *priv = test->priv;
@@ -2715,7 +2858,9 @@ static struct kunit_case cros_ec_proto_test_cases[] = {
 	KUNIT_CASE(cros_ec_proto_test_cmd_xfer_protocol_v3_no_op),
 	KUNIT_CASE(cros_ec_proto_test_cmd_xfer_protocol_v2_normal),
 	KUNIT_CASE(cros_ec_proto_test_cmd_xfer_protocol_v2_no_op),
-	KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_normal),
+	KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_payload_0bytes),
+	KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_payload_4bytes),
+	KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_payload_gt4bytes),
 	KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_retries_eagain),
 	KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_retries_status_processing),
 	KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_xfer_error),
-- 
2.45.0

^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] platform/chrome: cros_ec_proto: Fix deferred response payload handling
  2026-08-10 21:43 [PATCH] platform/chrome: cros_ec_proto: Fix deferred response payload handling Rob Barnes
@ 2026-08-13  6:49 ` Tzung-Bi Shih
  0 siblings, 0 replies; 2+ messages in thread
From: Tzung-Bi Shih @ 2026-08-13  6:49 UTC (permalink / raw)
  To: Rob Barnes; +Cc: bleung, tomhughes, chrome-platform, linux-kernel

On Mon, Aug 10, 2026 at 02:43:29PM -0700, Rob Barnes wrote:
> Subject: [PATCH] platform/chrome: cros_ec_proto: Fix deferred response
> payload handling

The subject shouldn't be here.

> ... Save and restore orig_msg
> fields (command, outsize, version) around the resend request to
> prevent unintended caller struct mutations. Update existing KUnit
> tests and add test cases for 4-byte and 16-byte response
> payloads.

They are somehow redundant; can be removed.

> -static int cros_ec_wait_until_complete(struct cros_ec_device *ec_dev,
> uint32_t *result)

./scripts/checkpatch.pl --strict:
    ERROR: patch seems to be corrupt (line wrapped?)
resulting in the patch can't be applied (accompanying with other corrupts).

> +static int cros_ec_wait_until_complete(struct cros_ec_device *ec_dev,
> +					struct cros_ec_command *orig_msg)

./scripts/checkpatch.pl --strict:
    CHECK: Alignment should match open parenthesis

> @@ -170,8 +170,28 @@ static int cros_ec_wait_until_complete(struct
> cros_ec_device *ec_dev, uint32_t *
>  			break;
>  		}
> 
> -		if (!(status->flags & EC_COMMS_STATUS_PROCESSING))
> -			return ret;
> +		if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) {
> +			/*
> +			 * If original command requested response payload, retrieve it via
> +			 * EC_CMD_RESEND_RESPONSE into orig_msg->data. Otherwise return 0
> +			 * for zero-payload commands.
> +			 */

How about exiting earlier if `orig_msg->insize` is 0?

> +			if (orig_msg->insize > 0) {
> +				uint32_t orig_cmd = orig_msg->command;
> +				uint32_t orig_outsize = orig_msg->outsize;
> +				uint32_t orig_version = orig_msg->version;

./scripts/checkpatch.pl --strict:
    CHECK: Prefer kernel type 'u32' over 'uint32_t'

> diff --git a/drivers/platform/chrome/cros_ec_proto_test.c
...
> -static void cros_ec_proto_test_cmd_xfer_in_progress_normal(struct kunit *test)
> +static void cros_ec_proto_test_cmd_xfer_in_progress_payload_0bytes(struct
                                                                    ^
							       0byte?

> +static void cros_ec_proto_test_cmd_xfer_in_progress_payload_4bytes(struct
> kunit *test)
> +{
...
> +	memset(&buf, 0, sizeof(buf));
> +	msg->version = 1;
> +	msg->command = 0x1234;
> +	msg->outsize = 2;
> +	msg->insize = sizeof(buf.data);
...
> +	ret = cros_ec_cmd_xfer(ec_dev, msg);
> +	KUNIT_EXPECT_EQ(test, ret, 4);
> +	KUNIT_EXPECT_EQ(test, msg->result, EC_RES_SUCCESS);
> +	KUNIT_EXPECT_EQ(test, msg->command, 0x1234);
> +	KUNIT_EXPECT_EQ(test, msg->outsize, 2);
> +	KUNIT_EXPECT_EQ(test, msg->version, 1);

If it wants to check the header is immutable, it should also check `insize`.

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-08-13  6:49 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10 21:43 [PATCH] platform/chrome: cros_ec_proto: Fix deferred response payload handling Rob Barnes
2026-08-13  6:49 ` Tzung-Bi Shih

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox