get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.2/patches/2224333/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2224333,
    "url": "http://patchwork.ozlabs.org/api/1.2/patches/2224333/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260417-sdcard-performance-b4-v4-1-119e66be10c2@avm.de/",
    "project": {
        "id": 14,
        "url": "http://patchwork.ozlabs.org/api/1.2/projects/14/?format=api",
        "name": "QEMU Development",
        "link_name": "qemu-devel",
        "list_id": "qemu-devel.nongnu.org",
        "list_email": "qemu-devel@nongnu.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": "",
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<20260417-sdcard-performance-b4-v4-1-119e66be10c2@avm.de>",
    "list_archive_url": null,
    "date": "2026-04-17T09:51:34",
    "name": "[v4,1/7] hw/sd: Switch read/write primitive to buf+len",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "4838b0a37b771cd668071300f372b17c86016544",
    "submitter": {
        "id": 91343,
        "url": "http://patchwork.ozlabs.org/api/1.2/people/91343/?format=api",
        "name": "Christian Speich",
        "email": "c.speich@avm.de"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260417-sdcard-performance-b4-v4-1-119e66be10c2@avm.de/mbox/",
    "series": [
        {
            "id": 500297,
            "url": "http://patchwork.ozlabs.org/api/1.2/series/500297/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=500297",
            "date": "2026-04-17T09:51:34",
            "name": "hw/sd: Improve performance of read/write/erase",
            "version": 4,
            "mbox": "http://patchwork.ozlabs.org/series/500297/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2224333/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2224333/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>",
        "X-Original-To": "incoming@patchwork.ozlabs.org",
        "Delivered-To": "patchwork-incoming@legolas.ozlabs.org",
        "Authentication-Results": [
            "legolas.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=avm.de header.i=@avm.de header.a=rsa-sha256\n header.s=mail header.b=QwA1n7zj;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists1p.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)"
        ],
        "Received": [
            "from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fxr0c1g5hz1yDF\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 17 Apr 2026 19:57:52 +1000 (AEST)",
            "from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists1p.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1wDfxJ-00038H-4N; Fri, 17 Apr 2026 05:57:46 -0400",
            "from eggs.gnu.org ([2001:470:142:3::10])\n by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <c.speich@avm.de>)\n id 1wDfx9-0002jT-Di; Fri, 17 Apr 2026 05:57:35 -0400",
            "from mail.avm.de ([212.42.244.119])\n by eggs.gnu.org with esmtps (TLS1.2:DHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <c.speich@avm.de>)\n id 1wDfx5-0002zP-Ia; Fri, 17 Apr 2026 05:57:33 -0400",
            "from [172.16.0.1] (helo=mail.avm.de)\n by mail.avm.de with ESMTP (eXpurgate 4.55.2)\n (envelope-from <c.speich@avm.de>)\n id 69e20404-1e6c-7f0000032729-7f000001c504-1\n for <multiple-recipients>; Fri, 17 Apr 2026 11:57:24 +0200",
            "from mail-notes.avm.de (mail-notes.avm.de [172.16.0.1])\n by mail.avm.de (Postfix) with ESMTP;\n Fri, 17 Apr 2026 11:57:24 +0200 (CEST)",
            "from [127.0.1.1] ([172.17.89.139])\n by mail-notes.avm.de (HCL Domino Release 14.0FP4)\n with ESMTP id 2026041711572442-2974 ;\n Fri, 17 Apr 2026 11:57:24 +0200"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=avm.de; s=mail;\n t=1776419845; bh=SwBm8aaTR5UUQij4KU+WjH8fTCf9makjG0ZretA3JBo=;\n h=From:Date:Subject:References:In-Reply-To:To:Cc:From;\n b=QwA1n7zjK7ZHpeg314pX0qEd1vD4c8r5G+Id5QAATf0knC85GOBQXMBVWXgc0xbV5\n IXLfMPryFXV3TCujcl+fe/5nVGfPptznPMfp/zPCdbAh2Iomm99w5tLWOEYq4rFDYc\n iEtDeX2oviaUD8AY27bjBzUe6SzNDYfiXvSavMmQ=",
        "From": "Christian Speich <c.speich@avm.de>",
        "Date": "Fri, 17 Apr 2026 11:51:34 +0200",
        "Subject": "[PATCH v4 1/7] hw/sd: Switch read/write primitive to buf+len",
        "MIME-Version": "1.0",
        "Message-Id": "<20260417-sdcard-performance-b4-v4-1-119e66be10c2@avm.de>",
        "References": "<20260417-sdcard-performance-b4-v4-0-119e66be10c2@avm.de>",
        "In-Reply-To": "<20260417-sdcard-performance-b4-v4-0-119e66be10c2@avm.de>",
        "To": "qemu-devel@nongnu.org",
        "Cc": "=?utf-8?q?Philippe_Mathieu-Daud=C3=A9?= <philmd@linaro.org>,\n Bin Meng <bmeng.cn@gmail.com>, qemu-block@nongnu.org,\n Marcel Apfelbaum <marcel.apfelbaum@gmail.com>,\n Yanan Wang <wangyanan55@huawei.com>, Zhao Liu <zhao1.liu@intel.com>,\n Christian Speich <c.speich@avm.de>",
        "X-Mailer": "b4 0.14.2",
        "X-MIMETrack": "Itemize by SMTP Server on ANIS1/AVM(Release 14.0FP4|March 10,\n 2025) at 17.04.2026 11:57:24,\n Serialize by Router on ANIS1/AVM(Release 14.0FP4|March 10, 2025) at\n 17.04.2026 11:57:24, Serialize complete at 17.04.2026 11:57:24",
        "X-TNEFEvaluated": "1",
        "Content-Transfer-Encoding": "8bit",
        "Content-Type": "text/plain; charset=\"utf-8\"",
        "X-purgate-ID": "149429::1776419845-30F16233-6BA837FA/0/0",
        "X-purgate-type": "clean",
        "X-purgate-size": "10846",
        "X-purgate-Ad": "Categorized by eleven eXpurgate (R) https://www.eleven.de",
        "X-purgate": [
            "This mail is considered clean (visit https://www.eleven.de for\n further information)",
            "clean"
        ],
        "Received-SPF": "pass client-ip=212.42.244.119; envelope-from=c.speich@avm.de;\n helo=mail.avm.de",
        "X-Spam_score_int": "-48",
        "X-Spam_score": "-4.9",
        "X-Spam_bar": "----",
        "X-Spam_report": "(-4.9 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.54,\n DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,\n RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001,\n RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, SPF_HELO_NONE=0.001,\n SPF_PASS=-0.001 autolearn=ham autolearn_force=no",
        "X-Spam_action": "no action",
        "X-BeenThere": "qemu-devel@nongnu.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "qemu development <qemu-devel.nongnu.org>",
        "List-Unsubscribe": "<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.nongnu.org/archive/html/qemu-devel>",
        "List-Post": "<mailto:qemu-devel@nongnu.org>",
        "List-Help": "<mailto:qemu-devel-request@nongnu.org?subject=help>",
        "List-Subscribe": "<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>",
        "Errors-To": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org",
        "Sender": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org"
    },
    "content": "Currently, read/writes are broken down into individual bytes which result\nin many function calls. This is quite bad for performance and since both\nthe layer below and above work with larger buffers, it should be\ncorrected.\n\nThis patch is the first that switches the corresponding interface over to\nuse a buf+len instead of a single byte. However, for most cases the\nimplementation still only reads one byte and is then called again with\nthe remaining buffer.\n\nOptimizations taking advantage of this new interface are to follow in the\nnext commits.\n\nSigned-off-by: Christian Speich <c.speich@avm.de>\nReviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>\n---\n hw/sd/core.c       | 26 ++++++++++++++---------\n hw/sd/sd.c         | 62 +++++++++++++++++++++++++++++++++++-------------------\n include/hw/sd/sd.h | 22 +++++++++++++------\n 3 files changed, 71 insertions(+), 39 deletions(-)",
    "diff": "diff --git a/hw/sd/core.c b/hw/sd/core.c\nindex 3568a81e809fe107cfd0b5cc33b8b5761b11ce04..594c5e011ba30940a33799f9032c92494ee0ca19 100644\n--- a/hw/sd/core.c\n+++ b/hw/sd/core.c\n@@ -113,21 +113,24 @@ void sdbus_write_byte(SDBus *sdbus, uint8_t value)\n     if (card) {\n         SDCardClass *sc = SDMMC_COMMON_GET_CLASS(card);\n \n-        sc->write_byte(card, value);\n+        sc->write_data(card, &value, 1);\n     }\n }\n \n void sdbus_write_data(SDBus *sdbus, const void *buf, size_t length)\n {\n     SDState *card = get_card(sdbus);\n-    const uint8_t *data = buf;\n \n     if (card) {\n         SDCardClass *sc = SDMMC_COMMON_GET_CLASS(card);\n \n-        for (size_t i = 0; i < length; i++) {\n-            trace_sdbus_write(sdbus_name(sdbus), data[i]);\n-            sc->write_byte(card, data[i]);\n+        while (length > 0) {\n+            size_t written = sc->write_data(card, buf, length);\n+\n+            g_assert(written >= 1);\n+\n+            buf += written;\n+            length -= written;\n         }\n     }\n }\n@@ -140,7 +143,7 @@ uint8_t sdbus_read_byte(SDBus *sdbus)\n     if (card) {\n         SDCardClass *sc = SDMMC_COMMON_GET_CLASS(card);\n \n-        value = sc->read_byte(card);\n+        sc->read_data(card, &value, 1);\n     }\n     trace_sdbus_read(sdbus_name(sdbus), value);\n \n@@ -150,14 +153,17 @@ uint8_t sdbus_read_byte(SDBus *sdbus)\n void sdbus_read_data(SDBus *sdbus, void *buf, size_t length)\n {\n     SDState *card = get_card(sdbus);\n-    uint8_t *data = buf;\n \n     if (card) {\n         SDCardClass *sc = SDMMC_COMMON_GET_CLASS(card);\n \n-        for (size_t i = 0; i < length; i++) {\n-            data[i] = sc->read_byte(card);\n-            trace_sdbus_read(sdbus_name(sdbus), data[i]);\n+        while (length > 0) {\n+            size_t read = sc->read_data(card, buf, length);\n+\n+            g_assert(read >= 1);\n+\n+            buf += read;\n+            length -= read;\n         }\n     }\n }\ndiff --git a/hw/sd/sd.c b/hw/sd/sd.c\nindex 37f6e0702b0bce85915ef727ba1ec05f02f9c32c..135113add29b5ee3cb11d9d535355eba8f6cb3f7 100644\n--- a/hw/sd/sd.c\n+++ b/hw/sd/sd.c\n@@ -2638,30 +2638,37 @@ static bool sd_generic_read_byte(SDState *sd, uint8_t *value)\n     return false;\n }\n \n-static void sd_write_byte(SDState *sd, uint8_t value)\n+static size_t sd_write_data(SDState *sd, const void *buf, size_t length)\n {\n     unsigned int partition_access;\n     int i;\n+    const uint8_t *value = buf;\n \n     if (!sd->blk || !blk_is_inserted(sd->blk)) {\n-        return;\n+        return length;\n     }\n \n     if (sd->state != sd_receivingdata_state) {\n         qemu_log_mask(LOG_GUEST_ERROR,\n                       \"%s: not in Receiving-Data state\\n\", __func__);\n-        return;\n+        return length;\n     }\n \n     if (sd->card_status & (ADDRESS_ERROR | WP_VIOLATION))\n-        return;\n+        return length;\n+\n+    /*\n+     * Only read one byte at a time. We will be called again with the\n+     * remaining.\n+     */\n+    length = 1;\n \n     trace_sdcard_write_data(sd->proto->name,\n                             sd->last_cmd_name,\n-                            sd->current_cmd, sd->data_offset, value);\n+                            sd->current_cmd, sd->data_offset, value[0]);\n     switch (sd->current_cmd) {\n     case 24:  /* CMD24:  WRITE_SINGLE_BLOCK */\n-        if (sd_generic_write_byte(sd, value)) {\n+        if (sd_generic_write_byte(sd, value[0])) {\n             /* TODO: Check CRC before committing */\n             sd->state = sd_programming_state;\n             sd_blk_write(sd, sd->data_start, sd->data_offset);\n@@ -2686,7 +2693,7 @@ static void sd_write_byte(SDState *sd, uint8_t value)\n                 }\n             }\n         }\n-        sd->data[sd->data_offset++] = value;\n+        sd->data[sd->data_offset++] = value[0];\n         if (sd->data_offset >= sd->blk_len) {\n             /* TODO: Check CRC before committing */\n             sd->state = sd_programming_state;\n@@ -2716,7 +2723,7 @@ static void sd_write_byte(SDState *sd, uint8_t value)\n         break;\n \n     case 26:  /* CMD26:  PROGRAM_CID */\n-        if (sd_generic_write_byte(sd, value)) {\n+        if (sd_generic_write_byte(sd, value[0])) {\n             /* TODO: Check CRC before committing */\n             sd->state = sd_programming_state;\n             for (i = 0; i < sizeof(sd->cid); i ++)\n@@ -2734,7 +2741,7 @@ static void sd_write_byte(SDState *sd, uint8_t value)\n         break;\n \n     case 27:  /* CMD27:  PROGRAM_CSD */\n-        if (sd_generic_write_byte(sd, value)) {\n+        if (sd_generic_write_byte(sd, value[0])) {\n             /* TODO: Check CRC before committing */\n             sd->state = sd_programming_state;\n             for (i = 0; i < sizeof(sd->csd); i ++)\n@@ -2757,7 +2764,7 @@ static void sd_write_byte(SDState *sd, uint8_t value)\n         break;\n \n     case 42:  /* CMD42:  LOCK_UNLOCK */\n-        if (sd_generic_write_byte(sd, value)) {\n+        if (sd_generic_write_byte(sd, value[0])) {\n             /* TODO: Check CRC before committing */\n             sd->state = sd_programming_state;\n             sd_lock_command(sd);\n@@ -2767,36 +2774,47 @@ static void sd_write_byte(SDState *sd, uint8_t value)\n         break;\n \n     case 56:  /* CMD56:  GEN_CMD */\n-        sd_generic_write_byte(sd, value);\n+        sd_generic_write_byte(sd, value[0]);\n         break;\n \n     default:\n         g_assert_not_reached();\n     }\n+\n+    return length;\n }\n \n-static uint8_t sd_read_byte(SDState *sd)\n+static size_t sd_read_data(SDState *sd, void *buf, size_t length)\n {\n     /* TODO: Append CRCs */\n     const uint8_t dummy_byte = 0x00;\n     unsigned int partition_access;\n-    uint8_t ret;\n     uint32_t io_len;\n+    uint8_t *value = buf;\n \n     if (!sd->blk || !blk_is_inserted(sd->blk)) {\n-        return dummy_byte;\n+        memset(buf, dummy_byte, length);\n+        return length;\n     }\n \n     if (sd->state != sd_sendingdata_state) {\n         qemu_log_mask(LOG_GUEST_ERROR,\n                       \"%s: not in Sending-Data state\\n\", __func__);\n-        return dummy_byte;\n+        memset(buf, dummy_byte, length);\n+        return length;\n     }\n \n     if (sd->card_status & (ADDRESS_ERROR | WP_VIOLATION)) {\n-        return dummy_byte;\n+        memset(buf, dummy_byte, length);\n+        return length;\n     }\n \n+    /*\n+     * We will only read one byte at a time. We will be called again with the\n+     * remaining buffer.\n+     */\n+    length = 1;\n+\n     io_len = sd_blk_len(sd);\n \n     trace_sdcard_read_data(sd->proto->name,\n@@ -2814,7 +2832,7 @@ static uint8_t sd_read_byte(SDState *sd)\n     case 30: /* CMD30:  SEND_WRITE_PROT */\n     case 51: /* ACMD51: SEND_SCR */\n     case 56: /* CMD56:  GEN_CMD */\n-        sd_generic_read_byte(sd, &ret);\n+        sd_generic_read_byte(sd, value);\n         break;\n \n     case 18:  /* CMD18:  READ_MULTIPLE_BLOCK */\n@@ -2831,7 +2849,7 @@ static uint8_t sd_read_byte(SDState *sd)\n                 sd_blk_read(sd, sd->data_start, io_len);\n             }\n         }\n-        ret = sd->data[sd->data_offset ++];\n+        *value = sd->data[sd->data_offset++];\n \n         if (sd->data_offset >= io_len) {\n             sd->data_start += io_len;\n@@ -2850,10 +2868,10 @@ static uint8_t sd_read_byte(SDState *sd)\n     default:\n         qemu_log_mask(LOG_GUEST_ERROR, \"%s: DAT read illegal for command %s\\n\",\n                                        __func__, sd->last_cmd_name);\n-        return dummy_byte;\n+        *value = dummy_byte;\n     }\n \n-    return ret;\n+    return length;\n }\n \n static bool sd_receive_ready(SDState *sd)\n@@ -3173,8 +3191,8 @@ static void sdmmc_common_class_init(ObjectClass *klass, const void *data)\n     sc->get_dat_lines = sd_get_dat_lines;\n     sc->get_cmd_line = sd_get_cmd_line;\n     sc->do_command = sd_do_command;\n-    sc->write_byte = sd_write_byte;\n-    sc->read_byte = sd_read_byte;\n+    sc->write_data = sd_write_data;\n+    sc->read_data = sd_read_data;\n     sc->receive_ready = sd_receive_ready;\n     sc->data_ready = sd_data_ready;\n     sc->get_inserted = sd_get_inserted;\ndiff --git a/include/hw/sd/sd.h b/include/hw/sd/sd.h\nindex d12f24955a5ba3c1ba9ab851d75992e830c00608..2162fb584020110bcdaa7a92c2a05b6cfc041d2f 100644\n--- a/include/hw/sd/sd.h\n+++ b/include/hw/sd/sd.h\n@@ -107,22 +107,30 @@ struct SDCardClass {\n     size_t (*do_command)(SDState *sd, SDRequest *req,\n                          uint8_t *resp, size_t respsz);\n     /**\n-     * Write a byte to a SD card.\n+     * Write data to a SD card.\n      * @sd: card\n-     * @value: byte to write\n+     * @value: data to write\n+     * @len: length of data\n      *\n-     * Write a byte on the data lines of a SD card.\n+     * Write data on the data lines of a SD card. May write not all data, in\n+     * which case it should be called again. At least one byte must be consumed.\n+     *\n+     * Return: number of bytes actually written. >= 1\n      */\n-    void (*write_byte)(SDState *sd, uint8_t value);\n+    size_t (*write_data)(SDState *sd, const void *buf, size_t len);\n     /**\n      * Read a byte from a SD card.\n      * @sd: card\n+     * @buf: buffer to receive the data\n+     * @len: size of data to read\n      *\n-     * Read a byte from the data lines of a SD card.\n+     * Read data from the data lines of a SD card. This may not read all\n+     * requested data, in this case it should be called again with the remaining\n+     * buffer. At least one byte must be read.\n      *\n-     * Return: byte value read\n+     * Return: number of bytes actually read. >= 1\n      */\n-    uint8_t (*read_byte)(SDState *sd);\n+    size_t (*read_data)(SDState *sd, void* buf, size_t len);\n     bool (*receive_ready)(SDState *sd);\n     bool (*data_ready)(SDState *sd);\n     void (*set_voltage)(SDState *sd, uint16_t millivolts);\n",
    "prefixes": [
        "v4",
        "1/7"
    ]
}