// SPDX-License-Identifier: GPL-2.0+/* * SSH packet transport layer. * * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #include <linux/unaligned.h>#include <linux/atomic.h>#include <linux/error-injection.h>#include <linux/jiffies.h>#include <linux/kfifo.h>#include <linux/kref.h>#include <linux/kthread.h>#include <linux/ktime.h>#include <linux/limits.h>#include <linux/list.h>#include <linux/lockdep.h>#include <linux/serdev.h>#include <linux/slab.h>#include <linux/spinlock.h>#include <linux/workqueue.h> #include <linux/surface_aggregator/serial_hub.h> #include "ssh_msgb.h"#include "ssh_packet_layer.h"#include "ssh_parser.h" #include "trace.h" /* * To simplify reasoning about the code below, we define a few concepts. The * system below is similar to a state-machine for packets, however, there are * too many states to explicitly write them down. To (somewhat) manage the * states and packets we rely on flags, reference counting, and some simple * concepts. State transitions are triggered by actions. * * >> Actions << * * - submit * - transmission start (process next item in queue) * - transmission finished (guaranteed to never be parallel to transmission * start) * - ACK received * - NAK received (this is equivalent to issuing re-submit for all pending * packets) * - timeout (this is equivalent to re-issuing a submit or canceling) * - cancel (non-pending and pending) * * >> Data Structures, Packet Ownership, General Overview << * * The code below employs two main data structures: The packet queue, * containing all packets scheduled for transmission, and the set of pending * packets, containing all packets awaiting an ACK. * * Shared ownership of a packet is controlled via reference counting. Inside * the transport system are a total of five packet owners: * * - the packet queue, * - the pending set, * - the transmitter thread, * - the receiver thread (via ACKing), and * - the timeout work item. * * Normal operation is as follows: The initial reference of the packet is * obtained by submitting the packet and queuing it. The receiver thread takes * packets from the queue. By doing this, it does not increment the refcount * but takes over the reference (removing it from the queue). If the packet is * sequenced (i.e. needs to be ACKed by the client), the transmitter thread * sets-up the timeout and adds the packet to the pending set before starting * to transmit it. As the timeout is handled by a reaper task, no additional * reference for it is needed. After the transmit is done, the reference held * by the transmitter thread is dropped. If the packet is unsequenced (i.e. * does not need an ACK), the packet is completed by the transmitter thread * before dropping that reference. * * On receival of an ACK, the receiver thread removes and obtains the * reference to the packet from the pending set. The receiver thread will then * complete the packet and drop its reference. * * On receival of a NAK, the receiver thread re-submits all currently pending * packets. * * Packet timeouts are detected by the timeout reaper. This is a task, * scheduled depending on the earliest packet timeout expiration date, * checking all currently pending packets if their timeout has expired. If the * timeout of a packet has expired, it is re-submitted and the number of tries * of this packet is incremented. If this number reaches its limit, the packet * will be completed with a failure. * * On transmission failure (such as repeated packet timeouts), the completion * callback is immediately run by on thread on which the error was detected. * * To ensure that a packet eventually leaves the system it is marked as * "locked" directly before it is going to be completed or when it is * canceled. Marking a packet as "locked" has the effect that passing and * creating new references of the packet is disallowed. This means that the * packet cannot be added to the queue, the pending set, and the timeout, or * be picked up by the transmitter thread or receiver thread. To remove a * packet from the system it has to be marked as locked and subsequently all * references from the data structures (queue, pending) have to be removed. * References held by threads will eventually be dropped automatically as * their execution progresses. * * Note that the packet completion callback is, in case of success and for a * sequenced packet, guaranteed to run on the receiver thread, thus providing * a way to reliably identify responses to the packet. The packet completion * callback is only run once and it does not indicate that the packet has * fully left the system (for this, one should rely on the release method, * triggered when the reference count of the packet reaches zero). In case of * re-submission (and with somewhat unlikely timing), it may be possible that * the packet is being re-transmitted while the completion callback runs. * Completion will occur both on success and internal error, as well as when * the packet is canceled. * * >> Flags << * * Flags are used to indicate the state and progression of a packet. Some flags * have stricter guarantees than other: * * - locked * Indicates if the packet is locked. If the packet is locked, passing and/or * creating additional references to the packet is forbidden. The packet thus * may not be queued, dequeued, or removed or added to the pending set. Note * that the packet state flags may still change (e.g. it may be marked as * ACKed, transmitted, ...). * * - completed * Indicates if the packet completion callback has been executed or is about * to be executed. This flag is used to ensure that the packet completion * callback is only run once. * * - queued * Indicates if a packet is present in the submission queue or not. This flag * must only be modified with the queue lock held, and must be coherent to the * presence of the packet in the queue. * * - pending * Indicates if a packet is present in the set of pending packets or not. * This flag must only be modified with the pending lock held, and must be * coherent to the presence of the packet in the pending set. * * - transmitting * Indicates if the packet is currently transmitting. In case of * re-transmissions, it is only safe to wait on the "transmitted" completion * after this flag has been set. The completion will be set both in success * and error case. * * - transmitted * Indicates if the packet has been transmitted. This flag is not cleared by * the system, thus it indicates the first transmission only. * * - acked * Indicates if the packet has been acknowledged by the client. There are no * other guarantees given. For example, the packet may still be canceled * and/or the completion may be triggered an error even though this bit is * set. Rely on the status provided to the completion callback instead. * * - canceled * Indicates if the packet has been canceled from the outside. There are no * other guarantees given. Specifically, the packet may be completed by * another part of the system before the cancellation attempts to complete it. * * >> General Notes << * * - To avoid deadlocks, if both queue and pending locks are required, the * pending lock must be acquired before the queue lock. * * - The packet priority must be accessed only while holding the queue lock. * * - The packet timestamp must be accessed only while holding the pending * lock. */ /* * SSH_PTL_MAX_PACKET_TRIES - Maximum transmission attempts for packet. * * Maximum number of transmission attempts per sequenced packet in case of * time-outs. Must be smaller than 16. If the packet times out after this * amount of tries, the packet will be completed with %-ETIMEDOUT as status * code. */#define SSH_PTL_MAX_PACKET_TRIES 3 /* * SSH_PTL_TX_TIMEOUT - Packet transmission timeout. * * Timeout in jiffies for packet transmission via the underlying serial * device. If transmitting the packet takes longer than this timeout, the * packet will be completed with -ETIMEDOUT. It will not be re-submitted. */#define SSH_PTL_TX_TIMEOUT HZ /* * SSH_PTL_PACKET_TIMEOUT - Packet response timeout. * * Timeout as ktime_t delta for ACKs. If we have not received an ACK in this * time-frame after starting transmission, the packet will be re-submitted. */#define SSH_PTL_PACKET_TIMEOUT ms_to_ktime(1000) /* * SSH_PTL_PACKET_TIMEOUT_RESOLUTION - Packet timeout granularity. * * Time-resolution for timeouts. Should be larger than one jiffy to avoid * direct re-scheduling of reaper work_struct. */#define SSH_PTL_PACKET_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) /* * SSH_PTL_MAX_PENDING - Maximum number of pending packets. * * Maximum number of sequenced packets concurrently waiting for an ACK. * Packets marked as blocking will not be transmitted while this limit is * reached. */#define SSH_PTL_MAX_PENDING 1 /* * SSH_PTL_RX_BUF_LEN - Evaluation-buffer size in bytes. */#define SSH_PTL_RX_BUF_LEN 4096 /* * SSH_PTL_RX_FIFO_LEN - Fifo input-buffer size in bytes. */#define SSH_PTL_RX_FIFO_LEN 4096 #ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION /** * ssh_ptl_should_drop_ack_packet() - Error injection hook to drop ACK packets. * * Useful to test detection and handling of automated re-transmits by the EC. * Specifically of packets that the EC considers not-ACKed but the driver * already considers ACKed (due to dropped ACK). In this case, the EC * re-transmits the packet-to-be-ACKed and the driver should detect it as * duplicate/already handled. Note that the driver should still send an ACK * for the re-transmitted packet. */static noinline bool ssh_ptl_should_drop_ack_packet(void){ return false;}ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_ack_packet, TRUE); /** * ssh_ptl_should_drop_nak_packet() - Error injection hook to drop NAK packets. * * Useful to test/force automated (timeout-based) re-transmit by the EC. * Specifically, packets that have not reached the driver completely/with valid * checksums. Only useful in combination with receival of (injected) bad data. */static noinline bool ssh_ptl_should_drop_nak_packet(void){ return false;}ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_nak_packet, TRUE); /** * ssh_ptl_should_drop_dsq_packet() - Error injection hook to drop sequenced * data packet. * * Useful to test re-transmit timeout of the driver. If the data packet has not * been ACKed after a certain time, the driver should re-transmit the packet up * to limited number of times defined in SSH_PTL_MAX_PACKET_TRIES. */static noinline bool ssh_ptl_should_drop_dsq_packet(void){ return false;}ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_dsq_packet, TRUE); /** * ssh_ptl_should_fail_write() - Error injection hook to make * serdev_device_write() fail. * * Hook to simulate errors in serdev_device_write when transmitting packets. */static noinline int ssh_ptl_should_fail_write(void){ return 0;}ALLOW_ERROR_INJECTION(ssh_ptl_should_fail_write, ERRNO); /** * ssh_ptl_should_corrupt_tx_data() - Error injection hook to simulate invalid * data being sent to the EC. * * Hook to simulate corrupt/invalid data being sent from host (driver) to EC. * Causes the packet data to be actively corrupted by overwriting it with * pre-defined values, such that it becomes invalid, causing the EC to respond * with a NAK packet. Useful to test handling of NAK packets received by the * driver. */static noinline bool ssh_ptl_should_corrupt_tx_data(void){ return false;}ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_tx_data, TRUE); /** * ssh_ptl_should_corrupt_rx_syn() - Error injection hook to simulate invalid * data being sent by the EC. * * Hook to simulate invalid SYN bytes, i.e. an invalid start of messages and * test handling thereof in the driver. */static noinline bool ssh_ptl_should_corrupt_rx_syn(void){ return false;}ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_syn, TRUE); /** * ssh_ptl_should_corrupt_rx_data() - Error injection hook to simulate invalid * data being sent by the EC. * * Hook to simulate invalid data/checksum of the message frame and test handling * thereof in the driver. */static noinline bool ssh_ptl_should_corrupt_rx_data(void){ return false;}ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_data, TRUE); static bool __ssh_ptl_should_drop_ack_packet(struct ssh_packet *packet){ if (likely(!ssh_ptl_should_drop_ack_packet())) return false; trace_ssam_ei_tx_drop_ack_packet(packet); ptl_info(packet->ptl, "packet error injection: dropping ACK packet %p\n", packet); return true;} static bool __ssh_ptl_should_drop_nak_packet(struct ssh_packet *packet){ if (likely(!ssh_ptl_should_drop_nak_packet())) return false; trace_ssam_ei_tx_drop_nak_packet(packet); ptl_info(packet->ptl, "packet error injection: dropping NAK packet %p\n", packet); return true;} static bool __ssh_ptl_should_drop_dsq_packet(struct ssh_packet *packet){ if (likely(!ssh_ptl_should_drop_dsq_packet())) return false; trace_ssam_ei_tx_drop_dsq_packet(packet); ptl_info(packet->ptl, "packet error injection: dropping sequenced data packet %p\n", packet); return true;} static bool ssh_ptl_should_drop_packet(struct ssh_packet *packet){ /* Ignore packets that don't carry any data (i.e. flush). */ if (!packet->data.ptr || !packet->data.len) return false; switch (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)]) { case SSH_FRAME_TYPE_ACK: return __ssh_ptl_should_drop_ack_packet(packet); case SSH_FRAME_TYPE_NAK: return __ssh_ptl_should_drop_nak_packet(packet); case SSH_FRAME_TYPE_DATA_SEQ: return __ssh_ptl_should_drop_dsq_packet(packet); default: return false; }} static int ssh_ptl_write_buf(struct ssh_ptl *ptl, struct ssh_packet *packet, const unsigned char *buf, size_t count){ int status; status = ssh_ptl_should_fail_write(); if (unlikely(status)) { trace_ssam_ei_tx_fail_write(packet, status); ptl_info(packet->ptl, "packet error injection: simulating transmit error %d, packet %p\n", status, packet); return status; } return serdev_device_write_buf(ptl->serdev, buf, count);} static void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet){ /* Ignore packets that don't carry any data (i.e. flush). */ if (!packet->data.ptr || !packet->data.len) return; /* Only allow sequenced data packets to be modified. */ if (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)] != SSH_FRAME_TYPE_DATA_SEQ) return; if (likely(!ssh_ptl_should_corrupt_tx_data())) return; trace_ssam_ei_tx_corrupt_data(packet); ptl_info(packet->ptl, "packet error injection: simulating invalid transmit data on packet %p\n", packet); /* * NB: The value 0xb3 has been chosen more or less randomly so that it * doesn't have any (major) overlap with the SYN bytes (aa 55) and is * non-trivial (i.e. non-zero, non-0xff). */ memset(packet->data.ptr, 0xb3, packet->data.len);} static void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, struct ssam_span *data){ struct ssam_span frame; /* Check if there actually is something to corrupt. */ if (!sshp_find_syn(data, &frame)) return; if (likely(!ssh_ptl_should_corrupt_rx_syn())) return; trace_ssam_ei_rx_corrupt_syn(data->len); data->ptr[1] = 0xb3; /* Set second byte of SYN to "random" value. */} static void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, struct ssam_span *frame){ size_t payload_len, message_len; struct ssh_frame *sshf; /* Ignore incomplete messages, will get handled once it's complete. */ if (frame->len < SSH_MESSAGE_LENGTH(0)) return; /* Ignore incomplete messages, part 2. */ payload_len = get_unaligned_le16(&frame->ptr[SSH_MSGOFFSET_FRAME(len)]); message_len = SSH_MESSAGE_LENGTH(payload_len); if (frame->len < message_len) return; if (likely(!ssh_ptl_should_corrupt_rx_data())) return; sshf = (struct ssh_frame *)&frame->ptr[SSH_MSGOFFSET_FRAME(type)]; trace_ssam_ei_rx_corrupt_data(sshf); /* * Flip bits in first byte of payload checksum. This is basically * equivalent to a payload/frame data error without us having to worry * about (the, arguably pretty small, probability of) accidental * checksum collisions. */ frame->ptr[frame->len - 2] = ~frame->ptr[frame->len - 2];} #else /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ static inline bool ssh_ptl_should_drop_packet(struct ssh_packet *packet){ return false;} static inline int ssh_ptl_write_buf(struct ssh_ptl *ptl, struct ssh_packet *packet, const unsigned char *buf, size_t count){ return serdev_device_write_buf(ptl->serdev, buf, count);} static inline void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet){} static inline void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, struct ssam_span *data){Lines 1–500 of 2086. Load more
// SPDX-License-Identifier: GPL-2.0+ /* * SSH packet transport layer. * * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com> */ #include <linux/unaligned.h> #include <linux/atomic.h> #include <linux/error-injection.h> #include <linux/jiffies.h> #include <linux/kfifo.h> #include <linux/kref.h> #include <linux/kthread.h> #include <linux/ktime.h> #include <linux/limits.h> #include <linux/list.h> #include <linux/lockdep.h> #include <linux/serdev.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/workqueue.h> #include <linux/surface_aggregator/serial_hub.h> #include "ssh_msgb.h" #include "ssh_packet_layer.h" #include "ssh_parser.h" #include "trace.h" /* * To simplify reasoning about the code below, we define a few concepts. The * system below is similar to a state-machine for packets, however, there are * too many states to explicitly write them down. To (somewhat) manage the * states and packets we rely on flags, reference counting, and some simple * concepts. State transitions are triggered by actions. * * >> Actions << * * - submit * - transmission start (process next item in queue) * - transmission finished (guaranteed to never be parallel to transmission * start) * - ACK received * - NAK received (this is equivalent to issuing re-submit for all pending * packets) * - timeout (this is equivalent to re-issuing a submit or canceling) * - cancel (non-pending and pending) * * >> Data Structures, Packet Ownership, General Overview << * * The code below employs two main data structures: The packet queue, * containing all packets scheduled for transmission, and the set of pending * packets, containing all packets awaiting an ACK. * * Shared ownership of a packet is controlled via reference counting. Inside * the transport system are a total of five packet owners: * * - the packet queue, * - the pending set, * - the transmitter thread, * - the receiver thread (via ACKing), and * - the timeout work item. * * Normal operation is as follows: The initial reference of the packet is * obtained by submitting the packet and queuing it. The receiver thread takes * packets from the queue. By doing this, it does not increment the refcount * but takes over the reference (removing it from the queue). If the packet is * sequenced (i.e. needs to be ACKed by the client), the transmitter thread * sets-up the timeout and adds the packet to the pending set before starting * to transmit it. As the timeout is handled by a reaper task, no additional * reference for it is needed. After the transmit is done, the reference held * by the transmitter thread is dropped. If the packet is unsequenced (i.e. * does not need an ACK), the packet is completed by the transmitter thread * before dropping that reference. * * On receival of an ACK, the receiver thread removes and obtains the * reference to the packet from the pending set. The receiver thread will then * complete the packet and drop its reference. * * On receival of a NAK, the receiver thread re-submits all currently pending * packets. * * Packet timeouts are detected by the timeout reaper. This is a task, * scheduled depending on the earliest packet timeout expiration date, * checking all currently pending packets if their timeout has expired. If the * timeout of a packet has expired, it is re-submitted and the number of tries * of this packet is incremented. If this number reaches its limit, the packet * will be completed with a failure. * * On transmission failure (such as repeated packet timeouts), the completion * callback is immediately run by on thread on which the error was detected. * * To ensure that a packet eventually leaves the system it is marked as * "locked" directly before it is going to be completed or when it is * canceled. Marking a packet as "locked" has the effect that passing and * creating new references of the packet is disallowed. This means that the * packet cannot be added to the queue, the pending set, and the timeout, or * be picked up by the transmitter thread or receiver thread. To remove a * packet from the system it has to be marked as locked and subsequently all * references from the data structures (queue, pending) have to be removed. * References held by threads will eventually be dropped automatically as * their execution progresses. * * Note that the packet completion callback is, in case of success and for a * sequenced packet, guaranteed to run on the receiver thread, thus providing * a way to reliably identify responses to the packet. The packet completion * callback is only run once and it does not indicate that the packet has * fully left the system (for this, one should rely on the release method, * triggered when the reference count of the packet reaches zero). In case of * re-submission (and with somewhat unlikely timing), it may be possible that * the packet is being re-transmitted while the completion callback runs. * Completion will occur both on success and internal error, as well as when * the packet is canceled. * * >> Flags << * * Flags are used to indicate the state and progression of a packet. Some flags * have stricter guarantees than other: * * - locked * Indicates if the packet is locked. If the packet is locked, passing and/or * creating additional references to the packet is forbidden. The packet thus * may not be queued, dequeued, or removed or added to the pending set. Note * that the packet state flags may still change (e.g. it may be marked as * ACKed, transmitted, ...). * * - completed * Indicates if the packet completion callback has been executed or is about * to be executed. This flag is used to ensure that the packet completion * callback is only run once. * * - queued * Indicates if a packet is present in the submission queue or not. This flag * must only be modified with the queue lock held, and must be coherent to the * presence of the packet in the queue. * * - pending * Indicates if a packet is present in the set of pending packets or not. * This flag must only be modified with the pending lock held, and must be * coherent to the presence of the packet in the pending set. * * - transmitting * Indicates if the packet is currently transmitting. In case of * re-transmissions, it is only safe to wait on the "transmitted" completion * after this flag has been set. The completion will be set both in success * and error case. * * - transmitted * Indicates if the packet has been transmitted. This flag is not cleared by * the system, thus it indicates the first transmission only. * * - acked * Indicates if the packet has been acknowledged by the client. There are no * other guarantees given. For example, the packet may still be canceled * and/or the completion may be triggered an error even though this bit is * set. Rely on the status provided to the completion callback instead. * * - canceled * Indicates if the packet has been canceled from the outside. There are no * other guarantees given. Specifically, the packet may be completed by * another part of the system before the cancellation attempts to complete it. * * >> General Notes << * * - To avoid deadlocks, if both queue and pending locks are required, the * pending lock must be acquired before the queue lock. * * - The packet priority must be accessed only while holding the queue lock. * * - The packet timestamp must be accessed only while holding the pending * lock. */ /* * SSH_PTL_MAX_PACKET_TRIES - Maximum transmission attempts for packet. * * Maximum number of transmission attempts per sequenced packet in case of * time-outs. Must be smaller than 16. If the packet times out after this * amount of tries, the packet will be completed with %-ETIMEDOUT as status * code. */ #define SSH_PTL_MAX_PACKET_TRIES 3 /* * SSH_PTL_TX_TIMEOUT - Packet transmission timeout. * * Timeout in jiffies for packet transmission via the underlying serial * device. If transmitting the packet takes longer than this timeout, the * packet will be completed with -ETIMEDOUT. It will not be re-submitted. */ #define SSH_PTL_TX_TIMEOUT HZ /* * SSH_PTL_PACKET_TIMEOUT - Packet response timeout. * * Timeout as ktime_t delta for ACKs. If we have not received an ACK in this * time-frame after starting transmission, the packet will be re-submitted. */ #define SSH_PTL_PACKET_TIMEOUT ms_to_ktime(1000) /* * SSH_PTL_PACKET_TIMEOUT_RESOLUTION - Packet timeout granularity. * * Time-resolution for timeouts. Should be larger than one jiffy to avoid * direct re-scheduling of reaper work_struct. */ #define SSH_PTL_PACKET_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) /* * SSH_PTL_MAX_PENDING - Maximum number of pending packets. * * Maximum number of sequenced packets concurrently waiting for an ACK. * Packets marked as blocking will not be transmitted while this limit is * reached. */ #define SSH_PTL_MAX_PENDING 1 /* * SSH_PTL_RX_BUF_LEN - Evaluation-buffer size in bytes. */ #define SSH_PTL_RX_BUF_LEN 4096 /* * SSH_PTL_RX_FIFO_LEN - Fifo input-buffer size in bytes. */ #define SSH_PTL_RX_FIFO_LEN 4096 #ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION /** * ssh_ptl_should_drop_ack_packet() - Error injection hook to drop ACK packets. * * Useful to test detection and handling of automated re-transmits by the EC. * Specifically of packets that the EC considers not-ACKed but the driver * already considers ACKed (due to dropped ACK). In this case, the EC * re-transmits the packet-to-be-ACKed and the driver should detect it as * duplicate/already handled. Note that the driver should still send an ACK * for the re-transmitted packet. */ static noinline bool ssh_ptl_should_drop_ack_packet(void) { return false; } ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_ack_packet, TRUE); /** * ssh_ptl_should_drop_nak_packet() - Error injection hook to drop NAK packets. * * Useful to test/force automated (timeout-based) re-transmit by the EC. * Specifically, packets that have not reached the driver completely/with valid * checksums. Only useful in combination with receival of (injected) bad data. */ static noinline bool ssh_ptl_should_drop_nak_packet(void) { return false; } ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_nak_packet, TRUE); /** * ssh_ptl_should_drop_dsq_packet() - Error injection hook to drop sequenced * data packet. * * Useful to test re-transmit timeout of the driver. If the data packet has not * been ACKed after a certain time, the driver should re-transmit the packet up * to limited number of times defined in SSH_PTL_MAX_PACKET_TRIES. */ static noinline bool ssh_ptl_should_drop_dsq_packet(void) { return false; } ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_dsq_packet, TRUE); /** * ssh_ptl_should_fail_write() - Error injection hook to make * serdev_device_write() fail. * * Hook to simulate errors in serdev_device_write when transmitting packets. */ static noinline int ssh_ptl_should_fail_write(void) { return 0; } ALLOW_ERROR_INJECTION(ssh_ptl_should_fail_write, ERRNO); /** * ssh_ptl_should_corrupt_tx_data() - Error injection hook to simulate invalid * data being sent to the EC. * * Hook to simulate corrupt/invalid data being sent from host (driver) to EC. * Causes the packet data to be actively corrupted by overwriting it with * pre-defined values, such that it becomes invalid, causing the EC to respond * with a NAK packet. Useful to test handling of NAK packets received by the * driver. */ static noinline bool ssh_ptl_should_corrupt_tx_data(void) { return false; } ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_tx_data, TRUE); /** * ssh_ptl_should_corrupt_rx_syn() - Error injection hook to simulate invalid * data being sent by the EC. * * Hook to simulate invalid SYN bytes, i.e. an invalid start of messages and * test handling thereof in the driver. */ static noinline bool ssh_ptl_should_corrupt_rx_syn(void) { return false; } ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_syn, TRUE); /** * ssh_ptl_should_corrupt_rx_data() - Error injection hook to simulate invalid * data being sent by the EC. * * Hook to simulate invalid data/checksum of the message frame and test handling * thereof in the driver. */ static noinline bool ssh_ptl_should_corrupt_rx_data(void) { return false; } ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_data, TRUE); static bool __ssh_ptl_should_drop_ack_packet(struct ssh_packet *packet) { if (likely(!ssh_ptl_should_drop_ack_packet())) return false; trace_ssam_ei_tx_drop_ack_packet(packet); ptl_info(packet->ptl, "packet error injection: dropping ACK packet %p\n", packet); return true; } static bool __ssh_ptl_should_drop_nak_packet(struct ssh_packet *packet) { if (likely(!ssh_ptl_should_drop_nak_packet())) return false; trace_ssam_ei_tx_drop_nak_packet(packet); ptl_info(packet->ptl, "packet error injection: dropping NAK packet %p\n", packet); return true; } static bool __ssh_ptl_should_drop_dsq_packet(struct ssh_packet *packet) { if (likely(!ssh_ptl_should_drop_dsq_packet())) return false; trace_ssam_ei_tx_drop_dsq_packet(packet); ptl_info(packet->ptl, "packet error injection: dropping sequenced data packet %p\n", packet); return true; } static bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) { /* Ignore packets that don't carry any data (i.e. flush). */ if (!packet->data.ptr || !packet->data.len) return false; switch (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)]) { case SSH_FRAME_TYPE_ACK: return __ssh_ptl_should_drop_ack_packet(packet); case SSH_FRAME_TYPE_NAK: return __ssh_ptl_should_drop_nak_packet(packet); case SSH_FRAME_TYPE_DATA_SEQ: return __ssh_ptl_should_drop_dsq_packet(packet); default: return false; } } static int ssh_ptl_write_buf(struct ssh_ptl *ptl, struct ssh_packet *packet, const unsigned char *buf, size_t count) { int status; status = ssh_ptl_should_fail_write(); if (unlikely(status)) { trace_ssam_ei_tx_fail_write(packet, status); ptl_info(packet->ptl, "packet error injection: simulating transmit error %d, packet %p\n", status, packet); return status; } return serdev_device_write_buf(ptl->serdev, buf, count); } static void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) { /* Ignore packets that don't carry any data (i.e. flush). */ if (!packet->data.ptr || !packet->data.len) return; /* Only allow sequenced data packets to be modified. */ if (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)] != SSH_FRAME_TYPE_DATA_SEQ) return; if (likely(!ssh_ptl_should_corrupt_tx_data())) return; trace_ssam_ei_tx_corrupt_data(packet); ptl_info(packet->ptl, "packet error injection: simulating invalid transmit data on packet %p\n", packet); /* * NB: The value 0xb3 has been chosen more or less randomly so that it * doesn't have any (major) overlap with the SYN bytes (aa 55) and is * non-trivial (i.e. non-zero, non-0xff). */ memset(packet->data.ptr, 0xb3, packet->data.len); } static void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, struct ssam_span *data) { struct ssam_span frame; /* Check if there actually is something to corrupt. */ if (!sshp_find_syn(data, &frame)) return; if (likely(!ssh_ptl_should_corrupt_rx_syn())) return; trace_ssam_ei_rx_corrupt_syn(data->len); data->ptr[1] = 0xb3; /* Set second byte of SYN to "random" value. */ } static void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, struct ssam_span *frame) { size_t payload_len, message_len; struct ssh_frame *sshf; /* Ignore incomplete messages, will get handled once it's complete. */ if (frame->len < SSH_MESSAGE_LENGTH(0)) return; /* Ignore incomplete messages, part 2. */ payload_len = get_unaligned_le16(&frame->ptr[SSH_MSGOFFSET_FRAME(len)]); message_len = SSH_MESSAGE_LENGTH(payload_len); if (frame->len < message_len) return; if (likely(!ssh_ptl_should_corrupt_rx_data())) return; sshf = (struct ssh_frame *)&frame->ptr[SSH_MSGOFFSET_FRAME(type)]; trace_ssam_ei_rx_corrupt_data(sshf); /* * Flip bits in first byte of payload checksum. This is basically * equivalent to a payload/frame data error without us having to worry * about (the, arguably pretty small, probability of) accidental * checksum collisions. */ frame->ptr[frame->len - 2] = ~frame->ptr[frame->len - 2]; } #else /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ static inline bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) { return false; } static inline int ssh_ptl_write_buf(struct ssh_ptl *ptl, struct ssh_packet *packet, const unsigned char *buf, size_t count) { return serdev_device_write_buf(ptl->serdev, buf, count); } static inline void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) { } static inline void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, struct ssam_span *data) {