[<prev] [next>] [day] [month] [year] [list]
Message-ID: <CABtds-1=hvKzpKJK7X4xrGJcG4-14MB52YfJLP=19ihTUASmtQ@mail.gmail.com>
Date: Tue, 4 Nov 2025 12:02:43 -0500
from: Sean Rhodes <sean@...rlabs.systems>
To: linux-kernel@...r.kernel.org
Subject: [PATCH] drivers/mmc: rtsx_usb: expose CPRM ioctl interface
>From 3273869107d69ffa9a97a50d41a71cc220cbc425 Mon Sep 17 00:00:00 2001
From: Sean Rhodes <sean@...rlabs.systems>
Date: Tue, 4 Nov 2025 16:30:57 +0000
Subject: [PATCH 5/5] drivers/mmc: rtsx_usb: expose CPRM ioctl interface
Add the user-facing CPRM command/response ioctls, track response buffers
inside the host, and register a miscdevice per controller so
userspace can issue raw CPRM transactions against supported readers.
Cc: Ulf Hansson <ulf.hansson@...aro.org>
Cc: Ricky Wu <ricky_wu@...ltek.com>
Cc: Avri Altman <avri.altman@...disk.com>
Cc: Jisheng Zhang <jszhang@...nel.org>
Cc: Dan Carpenter <dan.carpenter@...aro.org>
Cc: Binbin Zhou <zhoubinbin@...ngson.cn>
Cc: linux-mmc@...r.kernel.org
Cc: linux-kernel@...r.kernel.org
Signed-off-by: Sean Rhodes <sean@...rlabs.systems>
---
drivers/mmc/host/rtsx_usb_sdmmc.c | 544 +++++++++++++++++++++++++++++-
1 file changed, 538 insertions(+), 6 deletions(-)
diff --git a/drivers/mmc/host/rtsx_usb_sdmmc.c
b/drivers/mmc/host/rtsx_usb_sdmmc.c
index 5d8c72459b3f..211297ee2f20 100644
--- a/drivers/mmc/host/rtsx_usb_sdmmc.c
+++ b/drivers/mmc/host/rtsx_usb_sdmmc.c
@@ -12,6 +12,9 @@
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/usb.h>
+#include <linux/fs.h>
+#include <linux/bitops.h>
+#include <linux/mmc/core.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sd.h>
@@ -21,6 +24,10 @@
#include <linux/pm_runtime.h>
#include <linux/workqueue.h>
#include <linux/jiffies.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/compat.h>
+#include <linux/vmalloc.h>
#include <linux/rtsx_usb.h>
#include <linux/unaligned.h>
@@ -35,6 +42,42 @@
#define RTSX_USB_SD_IDLE_POLL_INTERVAL msecs_to_jiffies(1000)
#define RTSX_USB_SD_IDLE_WAIT_MAX 10
#define RTSX_USB_SD_SEQ_WAIT_MAX 2
+#define RTSX_USB_MMC_CMD_RETRIES 3
+
+#define RTSX_USB_IOC_MAGIC 0x39
+
+struct rtsx_usb_ioc_sd_direct {
+ u8 cmnd[12];
+ __u64 buf;
+ __s32 buf_len;
+} __packed;
+
+struct rtsx_usb_ioc_sd_rsp {
+ __u64 rsp;
+ __s32 rsp_len;
+} __packed;
+
+#define RTSX_USB_IOC_SD_DIRECT _IOWR(RTSX_USB_IOC_MAGIC, 0xA0, \
+ struct rtsx_usb_ioc_sd_direct)
+#define RTSX_USB_IOC_SD_GET_RSP _IOWR(RTSX_USB_IOC_MAGIC, 0xA1, \
+ struct rtsx_usb_ioc_sd_rsp)
+
+#ifdef CONFIG_COMPAT
+struct rtsx_usb_ioc_sd_direct32 {
+ u8 cmnd[12];
+ compat_uptr_t buf;
+ __s32 buf_len;
+} __packed;
+
+struct rtsx_usb_ioc_sd_rsp32 {
+ compat_uptr_t rsp;
+ __s32 rsp_len;
+} __packed;
+#define RTSX_USB_IOC_SD_DIRECT32 _IOWR(RTSX_USB_IOC_MAGIC, 0xA0, \
+ struct rtsx_usb_ioc_sd_direct32)
+#define RTSX_USB_IOC_SD_GET_RSP32 _IOWR(RTSX_USB_IOC_MAGIC, 0xA1, \
+ struct rtsx_usb_ioc_sd_rsp32)
+#endif
struct rtsx_usb_sdmmc {
struct platform_device *pdev;
@@ -64,6 +107,14 @@ struct rtsx_usb_sdmmc {
unsigned char power_mode;
u16 ocp_stat;
+ struct mutex cprm_lock;
+ u8 cprm_rsp[16];
+ size_t cprm_rsp_len;
+ bool cprm_rsp_valid;
+ u8 cprm_rsp_type;
+ struct miscdevice cprm_miscdev;
+ char *cprm_name;
+ bool cprm_registered;
#ifdef RTSX_USB_USE_LEDS_CLASS
struct led_classdev led;
char led_name[32];
@@ -123,7 +174,7 @@ static void sdmmc_enter_idle(struct rtsx_usb_sdmmc *host)
goto out_dbg;
err = rtsx_usb_write_register(ucr, FPDCTL,
- SSC_POWER_MASK, SSC_POWER_DOWN);
+ SSC_POWER_MASK, SSC_POWER_DOWN);
if (!err)
host->idle = true;
@@ -143,7 +194,7 @@ static void sdmmc_leave_idle(struct rtsx_usb_sdmmc *host)
return;
err = rtsx_usb_write_register(ucr, FPDCTL,
- SSC_POWER_MASK, SSC_POWER_ON);
+ SSC_POWER_MASK, SSC_POWER_ON);
if (err)
goto out_dbg;
@@ -977,8 +1028,7 @@ static void rtsx_usb_sdmmc_poll_card(struct
work_struct *work)
} else if (!present) {
if (host->idle_counter < host->idle_wait_max)
host->idle_counter++;
- if (!host->idle &&
- host->idle_counter >= host->idle_wait_max)
+ if (!host->idle && host->idle_counter >= host->idle_wait_max)
sdmmc_enter_idle(host);
} else {
host->idle_counter = 0;
@@ -1015,8 +1065,7 @@ static void rtsx_usb_sdmmc_poll_card(struct
work_struct *work)
bool request_active = READ_ONCE(host->mrq) != NULL;
bool card_present = READ_ONCE(host->card_exist);
unsigned long delay = card_present || request_active ?
- RTSX_USB_SD_POLL_INTERVAL :
- RTSX_USB_SD_IDLE_POLL_INTERVAL;
+ RTSX_USB_SD_POLL_INTERVAL : RTSX_USB_SD_IDLE_POLL_INTERVAL;
host->poll_interval = delay;
queue_delayed_work(system_wq, &host->card_poll, delay);
@@ -1106,6 +1155,457 @@ static void sdmmc_request(struct mmc_host
*mmc, struct mmc_request *mrq)
mmc_request_done(mmc, mrq);
}
+static int rtsx_usb_cprm_rsp_config(u8 rsp_code, unsigned int *flags,
+ u8 *rsp_len)
+{
+ unsigned int cmd_flags;
+ u8 len;
+
+ switch (rsp_code) {
+ case 0x04: /* R0 */
+ cmd_flags = MMC_RSP_NONE;
+ len = 0;
+ break;
+ case 0x01: /* R1/R5/R6/R7 */
+ cmd_flags = MMC_RSP_R1;
+ len = 4;
+ break;
+ case 0x09: /* R1b */
+ cmd_flags = MMC_RSP_R1B;
+ len = 4;
+ break;
+ case 0x02: /* R2 */
+ cmd_flags = MMC_RSP_R2;
+ len = 16;
+ break;
+ case 0x05: /* R3/R4 */
+ cmd_flags = MMC_RSP_R3;
+ len = 4;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (flags)
+ *flags = cmd_flags;
+ if (rsp_len)
+ *rsp_len = len;
+
+ return 0;
+}
+
+static void rtsx_usb_cprm_store_resp(struct rtsx_usb_sdmmc *host,
+ struct mmc_command *cmd, u8 rsp_code,
+ u8 rsp_len)
+{
+ int i;
+
+ host->cprm_rsp_type = rsp_code;
+ host->cprm_rsp_len = rsp_len;
+ host->cprm_rsp_valid = false;
+
+ if (!rsp_len)
+ goto out_valid;
+
+ if (rsp_len == 16) {
+ for (i = 0; i < 4; i++)
+ put_unaligned_be32(cmd->resp[i], host->cprm_rsp + i * 4);
+ } else {
+ put_unaligned_be32(cmd->resp[0], host->cprm_rsp);
+ }
+
+out_valid:
+ host->cprm_rsp_valid = true;
+}
+
+static int rtsx_usb_mmc_deselect_cards(struct mmc_host *host)
+{
+ struct mmc_command cmd = { };
+
+ if (!host)
+ return -EINVAL;
+
+ cmd.opcode = MMC_SELECT_CARD;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_NONE | MMC_CMD_AC;
+
+ return mmc_wait_for_cmd(host, &cmd, RTSX_USB_MMC_CMD_RETRIES);
+}
+
+static int rtsx_usb_mmc_select_card(struct mmc_card *card)
+{
+ struct mmc_command cmd = { };
+
+ if (!card)
+ return -EINVAL;
+
+ cmd.opcode = MMC_SELECT_CARD;
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+
+ return mmc_wait_for_cmd(card->host, &cmd, RTSX_USB_MMC_CMD_RETRIES);
+}
+
+static int rtsx_usb_cprm_exec_direct(struct rtsx_usb_sdmmc *host,
+ const struct rtsx_usb_ioc_sd_direct *req)
+{
+ struct device *dev = sdmmc_dev(host);
+ struct mmc_host *mmc = host->mmc;
+ struct mmc_card *card = mmc->card;
+ struct mmc_request mrq = {};
+ struct mmc_command cmd = {};
+ struct mmc_command stop = {};
+ struct mmc_data data = {};
+ struct scatterlist sg;
+ unsigned int rsp_flags;
+ u8 rsp_len;
+ void *kbuf = NULL;
+ void __user *user_buf;
+ u32 arg, data_len;
+ unsigned int blksz = 512;
+ unsigned int blocks = 0;
+ bool restore_blklen = false;
+ bool send_stop, standby, acmd;
+ u8 dir;
+ int ret = 0;
+
+ mutex_lock(&host->cprm_lock);
+ host->cprm_rsp_valid = false;
+
+ if (!card) {
+ ret = -ENOMEDIUM;
+ goto out_unlock;
+ }
+
+ dir = (req->cmnd[0] >> 3) & 0x03;
+ send_stop = req->cmnd[0] & BIT(2);
+ standby = req->cmnd[0] & BIT(1);
+ acmd = req->cmnd[0] & BIT(0);
+
+ cmd.opcode = req->cmnd[1];
+ arg = ((u32)req->cmnd[2] << 24) |
+ ((u32)req->cmnd[3] << 16) |
+ ((u32)req->cmnd[4] << 8) |
+ (u32)req->cmnd[5];
+ cmd.arg = arg;
+ data_len = ((u32)req->cmnd[6] << 16) |
+ ((u32)req->cmnd[7] << 8) |
+ (u32)req->cmnd[8];
+ cmd.error = 0;
+
+ user_buf = u64_to_user_ptr(req->buf);
+
+ if (req->buf_len < 0) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+ switch (dir) {
+ case 0:
+ if (data_len) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+ break;
+ case 1:
+ case 2:
+ if (!data_len || req->buf_len < data_len) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+ if (!user_buf) {
+ ret = -EFAULT;
+ goto out_unlock;
+ }
+ break;
+ default:
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ ret = rtsx_usb_cprm_rsp_config(req->cmnd[9], &rsp_flags, &rsp_len);
+ if (ret)
+ goto out_unlock;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(dev);
+ goto out_unlock;
+ }
+
+ mmc_get_card(card);
+ mmc_claim_host(mmc);
+
+ if (standby) {
+ ret = rtsx_usb_mmc_deselect_cards(mmc);
+ if (ret)
+ goto out_release;
+ }
+
+ if (acmd) {
+ ret = mmc_app_cmd(mmc, card);
+ if (ret)
+ goto out_restore_select;
+ }
+
+ if (dir) {
+ if (data_len < 512) {
+ ret = mmc_set_blocklen(card, data_len);
+ if (ret)
+ goto out_restore_select;
+ blksz = data_len;
+ blocks = 1;
+ restore_blklen = true;
+ } else if (data_len % 512) {
+ ret = -EINVAL;
+ goto out_restore_select;
+ } else {
+ blocks = data_len / 512;
+ }
+
+ kbuf = kvmalloc(data_len, GFP_KERNEL);
+ if (!kbuf) {
+ ret = -ENOMEM;
+ goto out_restore_blklen;
+ }
+
+ if (dir == 2) {
+ if (copy_from_user(kbuf, user_buf, data_len)) {
+ ret = -EFAULT;
+ goto out_restore_blklen;
+ }
+ }
+
+ data.blksz = blksz;
+ data.blocks = blocks ? blocks : 1;
+ data.flags = (dir == 1) ? MMC_DATA_READ : MMC_DATA_WRITE;
+ data.sg = &sg;
+ data.sg_len = 1;
+ data.error = 0;
+ sg_init_one(&sg, kbuf, data_len);
+ mmc_set_data_timeout(&data, card);
+ mrq.data = &data;
+ cmd.flags = rsp_flags | MMC_CMD_ADTC;
+ } else {
+ cmd.flags = rsp_flags | MMC_CMD_AC;
+ }
+
+ mrq.cmd = &cmd;
+
+ if (dir && send_stop) {
+ stop.opcode = MMC_STOP_TRANSMISSION;
+ stop.arg = 0;
+ stop.flags = MMC_RSP_R1B | MMC_CMD_AC;
+ mrq.stop = &stop;
+ }
+
+ mmc_wait_for_req(mmc, &mrq);
+
+ if (cmd.error)
+ ret = cmd.error;
+ else if (mrq.data && mrq.data->error)
+ ret = mrq.data->error;
+ else if (mrq.stop && mrq.stop->error)
+ ret = mrq.stop->error;
+ else
+ ret = 0;
+
+ if (restore_blklen) {
+ int err = mmc_set_blocklen(card, 512);
+
+ if (!ret && err)
+ ret = err;
+ }
+
+ if (standby) {
+ int err = rtsx_usb_mmc_select_card(card);
+
+ if (!ret && err)
+ ret = err;
+ }
+
+ mmc_release_host(mmc);
+ mmc_put_card(card);
+ pm_runtime_put(dev);
+
+ if (ret)
+ goto out_copy_err;
+
+ if (dir == 1) {
+ if (copy_to_user(user_buf, kbuf, data_len)) {
+ ret = -EFAULT;
+ goto out_copy_err;
+ }
+ }
+
+ rtsx_usb_cprm_store_resp(host, &cmd, req->cmnd[9], rsp_len);
+
+ ret = 0;
+
+out_copy_err:
+ if (ret)
+ host->cprm_rsp_valid = false;
+ kvfree(kbuf);
+ mutex_unlock(&host->cprm_lock);
+ return ret;
+
+out_restore_blklen:
+ if (restore_blklen) {
+ int err_restore = mmc_set_blocklen(card, 512);
+
+ if (!ret && err_restore)
+ ret = err_restore;
+ }
+out_restore_select:
+ if (standby) {
+ int err_select = rtsx_usb_mmc_select_card(card);
+
+ if (!ret && err_select)
+ ret = err_select;
+ }
+out_release:
+ mmc_release_host(mmc);
+ mmc_put_card(card);
+ pm_runtime_put(dev);
+out_unlock:
+ kvfree(kbuf);
+ mutex_unlock(&host->cprm_lock);
+ return ret;
+}
+
+static int rtsx_usb_cprm_get_response(struct rtsx_usb_sdmmc *host,
+ struct rtsx_usb_ioc_sd_rsp *req)
+{
+ void __user *user_rsp = u64_to_user_ptr(req->rsp);
+ size_t to_copy;
+ int ret = 0;
+
+ mutex_lock(&host->cprm_lock);
+ if (!host->cprm_rsp_valid) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+ if (req->rsp_len < 0) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ to_copy = min_t(size_t, host->cprm_rsp_len,
+ (size_t)req->rsp_len);
+
+ if (to_copy && copy_to_user(user_rsp, host->cprm_rsp, to_copy)) {
+ ret = -EFAULT;
+ goto out_unlock;
+ }
+
+ req->rsp_len = to_copy;
+
+out_unlock:
+ mutex_unlock(&host->cprm_lock);
+ return ret;
+}
+
+static int rtsx_usb_cprm_open(struct inode *inode, struct file *file)
+{
+ struct rtsx_usb_sdmmc *host;
+
+ host = container_of(file->private_data,
+ struct rtsx_usb_sdmmc, cprm_miscdev);
+ if (!host || host->host_removal)
+ return -ENODEV;
+
+ file->private_data = host;
+ return nonseekable_open(inode, file);
+}
+
+static long rtsx_usb_cprm_unlocked_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct rtsx_usb_sdmmc *host = file->private_data;
+ struct rtsx_usb_ioc_sd_direct direct;
+ struct rtsx_usb_ioc_sd_rsp rsp;
+ void __user *argp = (void __user *)arg;
+ int ret;
+
+ if (host->host_removal)
+ return -ENODEV;
+
+ switch (cmd) {
+ case RTSX_USB_IOC_SD_DIRECT:
+ if (copy_from_user(&direct, argp, sizeof(direct)))
+ return -EFAULT;
+ ret = rtsx_usb_cprm_exec_direct(host, &direct);
+ return ret;
+ case RTSX_USB_IOC_SD_GET_RSP:
+ if (copy_from_user(&rsp, argp, sizeof(rsp)))
+ return -EFAULT;
+ ret = rtsx_usb_cprm_get_response(host, &rsp);
+ if (ret)
+ return ret;
+ if (copy_to_user(argp, &rsp, sizeof(rsp)))
+ return -EFAULT;
+ return 0;
+ default:
+ return -ENOTTY;
+ }
+}
+
+#ifdef CONFIG_COMPAT
+static long rtsx_usb_cprm_compat_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct rtsx_usb_sdmmc *host = file->private_data;
+ void __user *argp = compat_ptr(arg);
+
+ if (host->host_removal)
+ return -ENODEV;
+
+ switch (cmd) {
+ case RTSX_USB_IOC_SD_DIRECT32:
+ {
+ struct rtsx_usb_ioc_sd_direct32 direct32;
+ struct rtsx_usb_ioc_sd_direct direct;
+
+ if (copy_from_user(&direct32, argp, sizeof(direct32)))
+ return -EFAULT;
+ memcpy(direct.cmnd, direct32.cmnd, sizeof(direct.cmnd));
+ direct.buf = direct32.buf;
+ direct.buf_len = direct32.buf_len;
+ return rtsx_usb_cprm_exec_direct(host, &direct);
+ }
+ case RTSX_USB_IOC_SD_GET_RSP32:
+ {
+ struct rtsx_usb_ioc_sd_rsp32 rsp32;
+ struct rtsx_usb_ioc_sd_rsp rsp;
+ int ret;
+
+ if (copy_from_user(&rsp32, argp, sizeof(rsp32)))
+ return -EFAULT;
+ rsp.rsp = rsp32.rsp;
+ rsp.rsp_len = rsp32.rsp_len;
+ ret = rtsx_usb_cprm_get_response(host, &rsp);
+ if (ret)
+ return ret;
+ rsp32.rsp_len = rsp.rsp_len;
+ if (copy_to_user(argp, &rsp32, sizeof(rsp32)))
+ return -EFAULT;
+ return 0;
+ }
+ default:
+ return -ENOTTY;
+ }
+}
+#endif
+
+static const struct file_operations rtsx_usb_cprm_fops = {
+ .owner = THIS_MODULE,
+ .open = rtsx_usb_cprm_open,
+ .unlocked_ioctl = rtsx_usb_cprm_unlocked_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = rtsx_usb_cprm_compat_ioctl,
+#endif
+ .llseek = no_llseek,
+};
+
static int sd_set_bus_width(struct rtsx_usb_sdmmc *host,
unsigned char bus_width)
{
@@ -1600,6 +2100,12 @@ static void rtsx_usb_init_host(struct
rtsx_usb_sdmmc *host)
host->seq_read = false;
host->seq_counter = 0;
host->seq_wait_max = RTSX_USB_SD_SEQ_WAIT_MAX;
+ mutex_init(&host->cprm_lock);
+ host->cprm_rsp_len = 0;
+ host->cprm_rsp_valid = false;
+ host->cprm_rsp_type = 0;
+ host->cprm_registered = false;
+ host->cprm_name = NULL;
}
static int rtsx_usb_sdmmc_drv_probe(struct platform_device *pdev)
@@ -1658,6 +2164,27 @@ static int rtsx_usb_sdmmc_drv_probe(struct
platform_device *pdev)
return ret;
}
+ host->cprm_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s_cprm",
+ mmc_hostname(mmc));
+ if (host->cprm_name) {
+ host->cprm_miscdev.minor = MISC_DYNAMIC_MINOR;
+ host->cprm_miscdev.name = host->cprm_name;
+ host->cprm_miscdev.fops = &rtsx_usb_cprm_fops;
+ host->cprm_miscdev.parent = &pdev->dev;
+ host->cprm_miscdev.mode = 0600;
+ ret = misc_register(&host->cprm_miscdev);
+ if (ret) {
+ dev_warn(&(pdev->dev),
+ "Failed to register CPRM interface: %d\n", ret);
+ } else {
+ host->cprm_registered = true;
+ dev_set_drvdata(host->cprm_miscdev.this_device, host);
+ }
+ } else {
+ dev_warn(&(pdev->dev),
+ "Failed to allocate CPRM interface name\n");
+ }
+
queue_delayed_work(system_wq, &host->card_poll,
host->poll_interval);
@@ -1675,6 +2202,11 @@ static void rtsx_usb_sdmmc_drv_remove(struct
platform_device *pdev)
mmc = host->mmc;
host->host_removal = true;
cancel_delayed_work_sync(&host->card_poll);
+ if (host->cprm_registered) {
+ dev_set_drvdata(host->cprm_miscdev.this_device, NULL);
+ misc_deregister(&host->cprm_miscdev);
+ host->cprm_registered = false;
+ }
mutex_lock(&host->host_mutex);
if (host->mrq) {
--
2.51.0
Powered by blists - more mailing lists