Page MenuHomeFreeBSD

D59559.diff
No OneTemporary

D59559.diff

diff --git a/sys/dev/ufshci/ufshci.h b/sys/dev/ufshci/ufshci.h
--- a/sys/dev/ufshci/ufshci.h
+++ b/sys/dev/ufshci/ufshci.h
@@ -366,6 +366,9 @@
#define UFSHCI_MAX_UPIU_SIZE 512
#define UFSHCI_UPIU_ALIGNMENT 8 /* UPIU requires 64-bit alignment. */
+/* UFS Spec 4.1, section 10.6.1: Total EHS Length counts 32 byte units. */
+#define UFSHCI_EHS_UNIT_SIZE 32
+
struct ufshci_upiu {
/* dword 0-2 */
struct ufshci_upiu_header header;
@@ -517,6 +520,9 @@
size_t desc_size;
};
+/* The data segment of a query UPIU, where a descriptor is carried. */
+#define UFSHCI_QUERY_DATA_SEGMENT_SIZE 256
+
struct ufshci_query_request_upiu {
/* dword 0-2 */
struct ufshci_upiu_header header;
@@ -543,7 +549,7 @@
/* dword 7 */
uint32_t reserved3;
- uint8_t command_data[256];
+ uint8_t command_data[UFSHCI_QUERY_DATA_SEGMENT_SIZE];
} __packed __aligned(4);
_Static_assert(sizeof(struct ufshci_query_request_upiu) == 288,
@@ -603,7 +609,7 @@
/* dword 7 */
uint8_t reserved4[4];
- uint8_t command_data[256];
+ uint8_t command_data[UFSHCI_QUERY_DATA_SEGMENT_SIZE];
} __packed __aligned(4);
_Static_assert(sizeof(struct ufshci_query_response_upiu) == 288,
diff --git a/sys/dev/ufshci/ufshci_ioctl.h b/sys/dev/ufshci/ufshci_ioctl.h
--- a/sys/dev/ufshci/ufshci_ioctl.h
+++ b/sys/dev/ufshci/ufshci_ioctl.h
@@ -31,23 +31,29 @@
* a command UPIU uses it.
*/
struct ufshci_pt_command {
+ /* The driver overwrites task_tag. */
struct ufshci_upiu req_upiu; /* [in] */
struct ufshci_upiu resp_upiu; /* [out] */
void *buf; /* [in] PRDT payload, may be NULL */
uint32_t len; /* [in] length of buf */
uint32_t flags; /* [in] UFSHCI_PT_FLAG_* */
- uint32_t timeout_ms; /* [in] 0 means the driver default */
- uint32_t xfer_len; /* [out] bytes actually moved */
- uint8_t ocs; /* [out] overall command status */
+ uint32_t timeout_ms; /* [in] reserved, must be 0 */
+ uint32_t xfer_len; /* [out] bytes the request carried */
+ uint8_t ocs; /* [out] reserved, always 0 for now */
uint8_t reserved[7];
};
struct ufshci_pt_uic_command {
struct ufshci_uic_cmd cmd; /* [in/out] opcode and argument1..3 */
- uint32_t timeout_ms; /* [in] */
+ uint32_t timeout_ms; /* [in] reserved, must be 0 */
uint32_t result; /* [out] UICCMDARG2 result code */
};
+/*
+ * A zero return means the command reached the device, not that the device
+ * accepted it. The response field in the response UPIU header carries
+ * that. On an errno no output field is filled.
+ */
#define UFSHCI_PASSTHROUGH_CMD _IOWR('u', 0, struct ufshci_pt_command)
#define UFSHCI_PASSTHROUGH_UIC _IOWR('u', 1, struct ufshci_pt_uic_command)
diff --git a/sys/dev/ufshci/ufshci_ioctl.c b/sys/dev/ufshci/ufshci_ioctl.c
--- a/sys/dev/ufshci/ufshci_ioctl.c
+++ b/sys/dev/ufshci/ufshci_ioctl.c
@@ -8,9 +8,12 @@
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/ioccom.h>
+#include <sys/malloc.h>
+#include <sys/systm.h>
#include "ufshci_private.h"
#include "ufshci_ioctl.h"
+#include "ufshci_reg.h"
static d_ioctl_t ufshci_ioctl;
@@ -20,11 +23,244 @@
.d_name = "ufshci",
};
+/*
+ * Work out how much of the UPIU the controller has to read and how much
+ * it may write back.
+ */
+static int
+ufshci_passthrough_upiu_sizes(const struct ufshci_pt_command *pt,
+ size_t *req_size, size_t *resp_size, bool *is_admin)
+{
+ const struct ufshci_upiu_header *header = &pt->req_upiu.header;
+ size_t ehs_bytes = header->ehs_length * UFSHCI_EHS_UNIT_SIZE;
+ size_t max_data_len;
+
+ switch (header->trans_code) {
+ case UFSHCI_UPIU_TRANSACTION_CODE_QUERY_REQUEST:
+ /* Only a command UPIU has room for an EHS. */
+ if (header->ehs_length != 0)
+ return (EINVAL);
+ *req_size = sizeof(struct ufshci_query_request_upiu);
+ *resp_size = sizeof(struct ufshci_query_response_upiu);
+ *is_admin = true;
+ max_data_len = UFSHCI_QUERY_DATA_SEGMENT_SIZE;
+ break;
+ case UFSHCI_UPIU_TRANSACTION_CODE_NOP_OUT:
+ if (header->ehs_length != 0)
+ return (EINVAL);
+ *req_size = sizeof(struct ufshci_nop_out_upiu);
+ *resp_size = sizeof(struct ufshci_nop_in_upiu);
+ *is_admin = true;
+ max_data_len = 0;
+ break;
+ case UFSHCI_UPIU_TRANSACTION_CODE_COMMAND:
+ *req_size = sizeof(struct ufshci_cmd_command_upiu) + ehs_bytes;
+ /* The response carries an EHS too, for advanced RPMB. */
+ *resp_size = sizeof(struct ufshci_cmd_response_upiu) +
+ ehs_bytes;
+ *is_admin = false;
+ /* A command UPIU moves its payload through the PRDT. */
+ max_data_len = 0;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ /*
+ * The descriptor has no request length, so the controller takes it
+ * from the UPIU header. A header that declares more than the UPIU
+ * has room for would read past the command descriptor.
+ */
+ if (be16toh(header->data_segment_length) > max_data_len)
+ return (EINVAL);
+
+ return (0);
+}
+
+static int
+ufshci_passthrough_cmd(struct ufshci_controller *ctrlr,
+ struct ufshci_pt_command *pt)
+{
+ struct ufshci_completion_poll_status status;
+ struct ufshci_request *req;
+ size_t req_size, resp_size;
+ void *buf = NULL;
+ bool is_admin;
+ int error;
+
+ /*
+ * An aborted request is completed by hand and may leave cpl
+ * unfilled, so stack bytes would go back to userland.
+ */
+ memset(&status, 0, sizeof(status));
+
+ if (pt->timeout_ms != 0)
+ return (EINVAL);
+
+ /*
+ * The response copy below is shorter than this field, so without
+ * clearing it the tail would hold the caller's own input.
+ */
+ memset(&pt->resp_upiu, 0, sizeof(pt->resp_upiu));
+
+ /*
+ * The ABI cap is fixed. What a controller can actually map is not,
+ * so check both.
+ */
+ if (pt->len > UFSHCI_PT_MAX_XFER || pt->len > ctrlr->max_xfer_size)
+ return (EINVAL);
+ if (pt->len != 0 && pt->buf == NULL)
+ return (EINVAL);
+
+ /* A buffer with no direction would build a PRDT nothing reads. */
+ if (pt->len != 0 && (pt->flags &
+ (UFSHCI_PT_FLAG_DATA_IN | UFSHCI_PT_FLAG_DATA_OUT)) == 0)
+ return (EINVAL);
+
+ if ((pt->flags & (UFSHCI_PT_FLAG_DATA_IN | UFSHCI_PT_FLAG_DATA_OUT)) ==
+ (UFSHCI_PT_FLAG_DATA_IN | UFSHCI_PT_FLAG_DATA_OUT))
+ return (EINVAL);
+
+ error = ufshci_passthrough_upiu_sizes(pt, &req_size, &resp_size,
+ &is_admin);
+ if (error)
+ return (error);
+ if (req_size > sizeof(struct ufshci_upiu))
+ return (EINVAL);
+ /*
+ * The completion union is smaller than a UPIU. An EHS must not
+ * push the response past it.
+ */
+ if (resp_size > sizeof(status.cpl.response_upiu))
+ return (EINVAL);
+ /*
+ * Without EHSLUTRDS the controller cannot carry the EHS, so the
+ * device would answer a partial request.
+ */
+ if (pt->req_upiu.header.ehs_length != 0 &&
+ UFSHCIV(UFSHCI_CAP_REG_EHSLUTRDS, ctrlr->cap) == 0)
+ return (EOPNOTSUPP);
+
+ if (pt->len != 0) {
+ buf = malloc(pt->len, M_UFSHCI, M_WAITOK | M_ZERO);
+ if (pt->flags & UFSHCI_PT_FLAG_DATA_OUT) {
+ error = copyin(pt->buf, buf, pt->len);
+ if (error)
+ goto out;
+ }
+ }
+
+ req = ufshci_allocate_request_vaddr(buf, pt->len, M_WAITOK,
+ ufshci_completion_poll_cb, &status);
+
+ memcpy(&req->request_upiu, &pt->req_upiu, sizeof(req->request_upiu));
+ req->request_size = req_size;
+ req->response_size = resp_size;
+ req->is_admin = is_admin;
+
+ if (pt->flags & UFSHCI_PT_FLAG_DATA_OUT)
+ req->data_direction = UFSHCI_DATA_DIRECTION_FROM_SYS_TO_TGT;
+ else if (pt->flags & UFSHCI_PT_FLAG_DATA_IN)
+ req->data_direction = UFSHCI_DATA_DIRECTION_FROM_TGT_TO_SYS;
+ else
+ req->data_direction = UFSHCI_DATA_DIRECTION_NO_DATA_TRANSFER;
+
+ error = ufshci_ctrlr_submit_transfer_request(ctrlr, req);
+ if (error) {
+ ufshci_free_request(req);
+ goto out;
+ }
+
+ ufshci_completion_poll(&status);
+
+ memcpy(&pt->resp_upiu, &status.cpl.response_upiu,
+ min(sizeof(pt->resp_upiu), sizeof(status.cpl.response_upiu)));
+ pt->xfer_len = pt->len;
+ /* The completion does not carry the overall command status yet. */
+ pt->ocs = 0;
+
+ /*
+ * A refused command still answered, so let the caller read the
+ * response. A failure that left the response zeroed had no answer.
+ */
+ if (status.error && pt->resp_upiu.header.response == 0) {
+ error = EIO;
+ goto out;
+ }
+
+ if (pt->len != 0 && (pt->flags & UFSHCI_PT_FLAG_DATA_IN))
+ error = copyout(buf, pt->buf, pt->len);
+
+out:
+ free(buf, M_UFSHCI);
+ return (error);
+}
+
+static int
+ufshci_passthrough_uic_cmd(struct ufshci_controller *ctrlr,
+ struct ufshci_pt_uic_command *pt)
+{
+ uint32_t return_value = 0;
+ int error;
+
+ if (pt->timeout_ms != 0)
+ return (EINVAL);
+
+ /*
+ * Only the four attribute commands. The rest can drop the link or
+ * power the device off.
+ */
+ switch (pt->cmd.opcode) {
+ case UFSHCI_DME_GET:
+ case UFSHCI_DME_SET:
+ case UFSHCI_DME_PEER_GET:
+ case UFSHCI_DME_PEER_SET:
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ /*
+ * On a path that never reads the register, a value the caller left
+ * here would read as an answer.
+ */
+ pt->cmd.argument2 &= ~(UFSHCI_UICCMDARG2_REG_ERROR_CODE_MASK <<
+ UFSHCI_UICCMDARG2_REG_ERROR_CODE_SHIFT);
+
+ error = ufshci_uic_send_cmd(ctrlr, &pt->cmd, &return_value);
+
+ pt->result = UFSHCIV(UFSHCI_UICCMDARG2_REG_ERROR_CODE,
+ pt->cmd.argument2);
+ if (pt->result != 0) {
+ /*
+ * A refusal is a result, not a transport failure. There is no
+ * attribute value to go with it, and the caller's own input
+ * would read like one.
+ */
+ pt->cmd.argument3 = 0;
+ return (0);
+ }
+ if (error)
+ return (error);
+
+ pt->cmd.argument3 = return_value;
+
+ return (0);
+}
+
static int
ufshci_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg, int flag,
struct thread *td)
{
+ struct ufshci_controller *ctrlr = cdev->si_drv1;
+
switch (cmd) {
+ case UFSHCI_PASSTHROUGH_CMD:
+ return (ufshci_passthrough_cmd(ctrlr,
+ (struct ufshci_pt_command *)arg));
+ case UFSHCI_PASSTHROUGH_UIC:
+ return (ufshci_passthrough_uic_cmd(ctrlr,
+ (struct ufshci_pt_uic_command *)arg));
default:
return (ENOTTY);
}
diff --git a/sys/dev/ufshci/ufshci_private.h b/sys/dev/ufshci/ufshci_private.h
--- a/sys/dev/ufshci/ufshci_private.h
+++ b/sys/dev/ufshci/ufshci_private.h
@@ -547,6 +547,8 @@
int ufshci_uic_power_mode_ready(struct ufshci_controller *ctrlr);
int ufshci_uic_hibernation_ready(struct ufshci_controller *ctrlr);
int ufshci_uic_cmd_ready(struct ufshci_controller *ctrlr);
+int ufshci_uic_send_cmd(struct ufshci_controller *ctrlr,
+ struct ufshci_uic_cmd *uic_cmd, uint32_t *return_value);
int ufshci_uic_send_dme_link_startup(struct ufshci_controller *ctrlr);
int ufshci_uic_send_dme_get(struct ufshci_controller *ctrlr, uint16_t attribute,
uint32_t *return_value);
diff --git a/sys/dev/ufshci/ufshci_uic_cmd.c b/sys/dev/ufshci/ufshci_uic_cmd.c
--- a/sys/dev/ufshci/ufshci_uic_cmd.c
+++ b/sys/dev/ufshci/ufshci_uic_cmd.c
@@ -165,7 +165,7 @@
return (0);
}
-static int
+int
ufshci_uic_send_cmd(struct ufshci_controller *ctrlr,
struct ufshci_uic_cmd *uic_cmd, uint32_t *return_value)
{
@@ -190,8 +190,9 @@
/* The result registers stay valid only until the next command. */
if (error == 0) {
+ uic_cmd->argument2 = ufshci_mmio_read_4(ctrlr, ucmdarg2);
config_result_code = UFSHCIV(UFSHCI_UICCMDARG2_REG_ERROR_CODE,
- ufshci_mmio_read_4(ctrlr, ucmdarg2));
+ uic_cmd->argument2);
result_value = ufshci_mmio_read_4(ctrlr, ucmdarg3);
}

File Metadata

Mime Type
text/plain
Expires
Sun, Sep 13, 5:00 AM (4 h, 44 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38719976
Default Alt Text
D59559.diff (11 KB)

Event Timeline