[{"id":3674942,"web_url":"http://patchwork.ozlabs.org/comment/3674942/","msgid":"<e012eed2-31fc-47b7-a709-79b887c9a5d1@gmail.com>","list_archive_url":null,"date":"2026-04-08T03:14:02","subject":"Re: [PATCH v2 4/5] hw/ufs: Add UFS Write Booster Support","submitter":{"id":86755,"url":"http://patchwork.ozlabs.org/api/people/86755/","name":"Jeuk Kim","email":"jeuk20.kim@gmail.com"},"content":"On 4/7/2026 7:23 PM, Jaemyung Lee wrote:\n> Add UFS Write Booster implementation which follows UFS 4.1 Spec.\n>\n> Signed-off-by: Jaemyung Lee <jaemyung.lee@samsung.com>\n> ---\n>   hw/ufs/ufs.c        | 425 ++++++++++++++++++++++++++++++++++++++++++++++++++--\n>   hw/ufs/ufs.h        |  47 ++++++\n>   include/block/ufs.h |  51 +++++++\n>   3 files changed, 508 insertions(+), 15 deletions(-)\n>\n> diff --git a/hw/ufs/ufs.c b/hw/ufs/ufs.c\n> index 5102d9bbab640faded69c93be73cbe319db68646..cfaff454e8eec84c8d171b688679a11e113fb495 100644\n> --- a/hw/ufs/ufs.c\n> +++ b/hw/ufs/ufs.c\n> @@ -996,11 +996,15 @@ static const int flag_permission[UFS_QUERY_FLAG_IDN_COUNT] = {\n>       [UFS_QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL] = UFS_QUERY_FLAG_READ,\n>       [UFS_QUERY_FLAG_IDN_BUSY_RTC] = UFS_QUERY_FLAG_READ,\n>       [UFS_QUERY_FLAG_IDN_PERMANENTLY_DISABLE_FW_UPDATE] = UFS_QUERY_FLAG_READ,\n> -    /* Write Booster is not supported */\n> -    [UFS_QUERY_FLAG_IDN_WB_EN] = UFS_QUERY_FLAG_READ,\n> -    [UFS_QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN] = UFS_QUERY_FLAG_READ,\n> +    [UFS_QUERY_FLAG_IDN_WB_EN] = UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET |\n> +                                 UFS_QUERY_FLAG_CLEAR | UFS_QUERY_FLAG_TOGGLE,\n> +    [UFS_QUERY_FLAG_IDN_WB_BUFF_FLUSH_EN] =\n> +        UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET | UFS_QUERY_FLAG_CLEAR |\n> +        UFS_QUERY_FLAG_TOGGLE,\n>       [UFS_QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8] = UFS_QUERY_FLAG_READ,\n> -    [UFS_QUERY_FLAG_IDN_UNPIN_EN] = UFS_QUERY_FLAG_READ,\n> +    [UFS_QUERY_FLAG_IDN_UNPIN_EN] = UFS_QUERY_FLAG_READ | UFS_QUERY_FLAG_SET |\n> +                                    UFS_QUERY_FLAG_CLEAR |\n> +                                    UFS_QUERY_FLAG_TOGGLE,\n>   };\n>   \n>   static inline QueryRespCode ufs_flag_check_idn_valid(uint8_t idn, int op)\n> @@ -1097,6 +1101,9 @@ static QueryRespCode ufs_write_flag_value(UfsHc *u, uint8_t idn, uint8_t value)\n>       case UFS_QUERY_FLAG_IDN_WB_BUFF_FLUSH_DURING_HIBERN8:\n>           u->flags.wb_buffer_flush_during_hibernate = value;\n>           break;\n> +    case UFS_QUERY_FLAG_IDN_UNPIN_EN:\n> +        u->flags.unpin_en = value;\n> +        break;\n>       default:\n>           return UFS_QUERY_RESULT_INVALID_VALUE;\n>       }\n> @@ -1189,16 +1196,20 @@ static const int attr_permission[UFS_QUERY_ATTR_IDN_COUNT] = {\n>       [UFS_QUERY_ATTR_IDN_HID_PROG_RATIO] = UFS_QUERY_ATTR_READ,\n>       [UFS_QUERY_ATTR_IDN_HID_STATE] = UFS_QUERY_ATTR_READ,\n>       [UFS_QUERY_ATTR_IDN_WB_BUFF_RESIZE_HINT] = UFS_QUERY_ATTR_READ,\n> -    [UFS_QUERY_ATTR_IDN_WB_BUFF_RESIZE_EN] = UFS_QUERY_ATTR_READ,\n> +    [UFS_QUERY_ATTR_IDN_WB_BUFF_RESIZE_EN] = UFS_QUERY_ATTR_WRITE,\n>       [UFS_QUERY_ATTR_IDN_WB_BUFF_RESIZE_STATUS] = UFS_QUERY_ATTR_READ,\n> -    [UFS_QUERY_ATTR_IDN_WB_BUFF_PARTIAL_FLUSH_MODE] = UFS_QUERY_ATTR_READ,\n> -    [UFS_QUERY_ATTR_IDN_MAX_FIFO_WB_PARTIAL_FLUSH_MODE] = UFS_QUERY_ATTR_READ,\n> +    [UFS_QUERY_ATTR_IDN_WB_BUFF_PARTIAL_FLUSH_MODE] =\n> +        UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE,\n> +    [UFS_QUERY_ATTR_IDN_MAX_FIFO_WB_PARTIAL_FLUSH_MODE] =\n> +        UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE,\n>       [UFS_QUERY_ATTR_IDN_CURR_FIFO_WB_PARTIAL_FLUSH_MODE] = UFS_QUERY_ATTR_READ,\n>       [UFS_QUERY_ATTR_IDN_PINNED_WB_BUFF_CURR_ALLOC_UNITS] = UFS_QUERY_ATTR_READ,\n>       [UFS_QUERY_ATTR_IDN_PINNED_WB_BUFF_AVAIL_PERCENT] = UFS_QUERY_ATTR_READ,\n>       [UFS_QUERY_ATTR_IDN_PINNED_WB_CUMM_WRITTEN_SIZE] = UFS_QUERY_ATTR_READ,\n> -    [UFS_QUERY_ATTR_IDN_PINNED_WB_NUM_ALLOC_UNITS] = UFS_QUERY_ATTR_READ,\n> -    [UFS_QUERY_ATTR_IDN_NON_PINNED_WB_MIN_NUM_ALLOC_UNITS] = UFS_QUERY_ATTR_READ,\n> +    [UFS_QUERY_ATTR_IDN_PINNED_WB_NUM_ALLOC_UNITS] =\n> +        UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE,\n> +    [UFS_QUERY_ATTR_IDN_NON_PINNED_WB_MIN_NUM_ALLOC_UNITS] =\n> +        UFS_QUERY_ATTR_READ | UFS_QUERY_ATTR_WRITE,\n>   };\n>   \n>   static inline QueryRespCode ufs_attr_check_idn_valid(uint8_t idn, int op)\n> @@ -1238,6 +1249,27 @@ static inline uint8_t ufs_read_device_temp(UfsHc *u)\n>       return 0;\n>   }\n>   \n> +static inline uint32_t ufs_wb_read_flush_status(UfsHc *u)\n> +{\n> +    if (u->attributes.wb_buffer_flush_status == UFS_WB_FLUSH_SUSPENDED ||\n> +        u->attributes.wb_buffer_flush_status == UFS_WB_FLUSH_COMPLETED ||\n> +        u->attributes.wb_buffer_flush_status == UFS_WB_FLUSH_FAILED) {\n> +        u->attributes.wb_buffer_flush_status = UFS_WB_FLUSH_IDLE;\n> +    }\n> +\n> +    return u->attributes.wb_buffer_flush_status;\n> +}\n> +\n> +static inline uint32_t ufs_wb_read_resize_status(UfsHc *u)\n> +{\n> +    if (u->attributes.wb_buffer_resize_status == UFS_WB_RESIZE_COMPLETED ||\n> +        u->attributes.wb_buffer_resize_status == UFS_WB_RESIZE_FAILED) {\n> +        u->attributes.wb_buffer_resize_status = UFS_WB_RESIZE_IDLE;\n> +    }\n> +\n> +    return u->attributes.wb_buffer_resize_status;\n> +}\n> +\n>   static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn)\n>   {\n>       switch (idn) {\n> @@ -1292,7 +1324,7 @@ static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn)\n>       case UFS_QUERY_ATTR_IDN_THROTTLING_STATUS:\n>           return u->attributes.throttling_status;\n>       case UFS_QUERY_ATTR_IDN_WB_FLUSH_STATUS:\n> -        return u->attributes.wb_buffer_flush_status;\n> +        return ufs_wb_read_flush_status(u);\n>       case UFS_QUERY_ATTR_IDN_AVAIL_WB_BUFF_SIZE:\n>           return u->attributes.available_wb_buffer_size;\n>       case UFS_QUERY_ATTR_IDN_WB_BUFF_LIFE_TIME_EST:\n> @@ -1323,10 +1355,8 @@ static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn)\n>           return u->attributes.hid_state;\n>       case UFS_QUERY_ATTR_IDN_WB_BUFF_RESIZE_HINT:\n>           return u->attributes.wb_buffer_resize_hint;\n> -    case UFS_QUERY_ATTR_IDN_WB_BUFF_RESIZE_EN:\n> -        return u->attributes.wb_buffer_resize_en;\n>       case UFS_QUERY_ATTR_IDN_WB_BUFF_RESIZE_STATUS:\n> -        return u->attributes.wb_buffer_resize_status;\n> +        return ufs_wb_read_resize_status(u);\n>       case UFS_QUERY_ATTR_IDN_WB_BUFF_PARTIAL_FLUSH_MODE:\n>           return u->attributes.wb_buffer_partial_flush_mode;\n>       case UFS_QUERY_ATTR_IDN_MAX_FIFO_WB_PARTIAL_FLUSH_MODE:\n> @@ -1347,6 +1377,32 @@ static uint32_t ufs_read_attr_value(UfsHc *u, uint8_t idn)\n>       return 0;\n>   }\n>   \n> +static void ufs_wb_resize_op(UfsHc *u, uint32_t value)\n> +{\n> +    if (u->attributes.wb_buffer_resize_status == UFS_WB_RESIZE_IN_PROGRESS) {\n> +        return;\n> +    }\n> +\n> +    u->attributes.wb_buffer_resize_en = value;\n> +    u->attributes.wb_buffer_resize_status = UFS_WB_RESIZE_IN_PROGRESS;\n> +}\n> +\n> +static void ufs_wb_pinned_max_size(UfsHc *u, uint32_t value)\n> +{\n> +    UfsWb *wb = &u->wb;\n> +\n> +    u->attributes.pinned_wb_num_alloc_units = cpu_to_be32(value);\n> +    wb->pinned_max_bytes = ufs_unit_to_byte(u, value);\n> +}\n> +\n> +static void ufs_wb_pinned_min_size(UfsHc *u, uint32_t value)\n> +{\n> +    UfsWb *wb = &u->wb;\n> +\n> +    u->attributes.non_pinned_wb_min_num_alloc_units = cpu_to_be32(value);\n> +    wb->pinned_min_bytes = ufs_unit_to_byte(u, value);\n> +}\n> +\n>   static QueryRespCode ufs_write_attr_value(UfsHc *u, uint8_t idn, uint32_t value)\n>   {\n>       switch (idn) {\n> @@ -1383,6 +1439,27 @@ static QueryRespCode ufs_write_attr_value(UfsHc *u, uint8_t idn, uint32_t value)\n>       case UFS_QUERY_ATTR_IDN_TIMESTAMP:\n>           u->attributes.timestamp = cpu_to_be64(value);\n>           break;\n> +    case UFS_QUERY_ATTR_IDN_WB_BUFF_RESIZE_EN:\n> +        if (value >= UFS_WB_RESIZE_OP_MAX) {\n> +            return UFS_QUERY_RESULT_INVALID_VALUE;\n> +        }\n> +        ufs_wb_resize_op(u, value);\n> +        break;\n> +    case UFS_QUERY_ATTR_IDN_WB_BUFF_PARTIAL_FLUSH_MODE:\n> +        if (value >= UFS_WB_FLUSH_MODE_MAX) {\n> +            return UFS_QUERY_RESULT_INVALID_VALUE;\n> +        }\n> +        u->attributes.wb_buffer_partial_flush_mode = value;\n> +        break;\n> +    case UFS_QUERY_ATTR_IDN_MAX_FIFO_WB_PARTIAL_FLUSH_MODE:\n> +        u->attributes.max_fifo_wb_partial_flush_mode = cpu_to_be32(value);\n> +        break;\n> +    case UFS_QUERY_ATTR_IDN_PINNED_WB_NUM_ALLOC_UNITS:\n> +        ufs_wb_pinned_max_size(u, value);\n> +        break;\n> +    case UFS_QUERY_ATTR_IDN_NON_PINNED_WB_MIN_NUM_ALLOC_UNITS:\n> +        ufs_wb_pinned_min_size(u, value);\n> +        break;\n>       default:\n>           g_assert_not_reached();\n>           return 0;\n> @@ -1725,6 +1802,98 @@ static void ufs_process_req(void *opaque)\n>       }\n>   }\n>   \n> +#define UFS_GROUP_NUMBER_MASK 0x1F\n> +#define UFS_WB_GROUP_NUMBER_DEFAULT 0x00 /* 00000b */\n> +#define UFS_WB_GROUP_NUMBER_PINNED 0x18 /* 11000b */\n> +static bool ufs_wb_check_write_pinned(UfsHc *u, UfsRequest *req)\n> +{\n> +    uint8_t cmd = req->req_upiu.sc.cdb[0];\n> +    uint8_t group_number = UFS_WB_GROUP_NUMBER_DEFAULT;\n> +\n> +    if (u->attributes.wb_buffer_partial_flush_mode != UFS_WB_FLUSH_PINNED) {\n> +        return false;\n> +    }\n> +\n> +    if (cmd == WRITE_16) {\n> +        group_number = req->req_upiu.sc.cdb[14] & UFS_GROUP_NUMBER_MASK;\n> +\n> +    } else if (cmd == WRITE_10) {\n> +        group_number = req->req_upiu.sc.cdb[6] & UFS_GROUP_NUMBER_MASK;\n> +    }\n> +\n> +    return (group_number == UFS_WB_GROUP_NUMBER_PINNED);\n> +}\n> +\n> +#define UFS_WB_TOTAL_WRITTEN_SHIFT 21 /* 10MB */\n> +static void ufs_wb_process_write_pinned(UfsHc *u, UfsRequest *req)\n> +{\n> +    UfsWb *wb = &u->wb;\n> +    uint64_t remain_bytes, remain_data;\n> +    uint32_t total_written;\n> +\n> +    if (!wb->pinned_curr_bytes) {\n> +        return;\n> +    }\n> +\n> +    if (wb->pinned_used_bytes >= wb->pinned_curr_bytes) {\n> +        return;\n> +    }\n> +\n> +    remain_bytes = wb->pinned_curr_bytes - wb->pinned_used_bytes;\n> +    if (remain_bytes >= req->data_len) {\n\nreq->data_len represents the requested length, not the actually \nprocessed length.\nIt seems more appropriate to use transferred_len.\n\n\n> +        wb->pinned_total_written_bytes += req->data_len;\n> +        wb->pinned_used_bytes += req->data_len;\n> +        remain_data = 0;\n> +\n> +    } else {\n> +        wb->pinned_total_written_bytes += remain_bytes;\n> +        wb->pinned_used_bytes += remain_bytes;\n> +        remain_data = req->data_len - remain_bytes;\n> +    }\n> +\n> +    total_written = wb->pinned_total_written_bytes >> UFS_WB_TOTAL_WRITTEN_SHIFT;\n> +    u->attributes.pinned_wb_cumm_written_size = cpu_to_be32(total_written);\n> +\n> +    remain_bytes = wb->curr_bytes - wb->used_bytes;\n> +    wb->used_bytes += MIN(remain_bytes, remain_data);\n> +}\n> +\n> +static void ufs_wb_process_write_normal(UfsHc *u, UfsRequest *req)\n> +{\n> +    UfsWb *wb = &u->wb;\n> +    uint64_t curr_bytes, used_bytes, remain_bytes;\n> +\n> +    if (!wb->curr_bytes) {\n> +        return;\n> +    }\n> +\n> +    curr_bytes = wb->curr_bytes - wb->pinned_curr_bytes;\n> +    used_bytes = wb->used_bytes - wb->pinned_used_bytes;\n> +\n> +    if (used_bytes >= curr_bytes) {\n> +        return;\n> +    }\n> +\n> +    remain_bytes = curr_bytes - used_bytes;\n> +    wb->used_bytes += MIN(remain_bytes, req->data_len);\n> +}\n> +\n> +static void ufs_wb_process_write_req(UfsRequest *req)\n> +{\n> +    UfsHc *u = req->hc;\n> +\n> +    if (!u->flags.wb_en || !ufs_is_write_req(req)) {\n> +        return;\n> +    }\n> +\n> +    if (ufs_wb_check_write_pinned(u, req)) {\n> +        ufs_wb_process_write_pinned(u, req);\n\nPlease remove the unnecessary blank line.\n\n\n> +\n> +    } else {\n> +        ufs_wb_process_write_normal(u, req);\n> +    }\n> +}\n> +\n>   void ufs_complete_req(UfsRequest *req, UfsReqResult req_result)\n>   {\n>       UfsHc *u = req->hc;\n> @@ -1732,6 +1901,7 @@ void ufs_complete_req(UfsRequest *req, UfsReqResult req_result)\n>   \n>       if (req_result == UFS_REQUEST_SUCCESS) {\n>           req->utrd.header.dword_2 = cpu_to_le32(UFS_OCS_SUCCESS);\n> +        ufs_wb_process_write_req(req);\n>       } else {\n>           req->utrd.header.dword_2 = cpu_to_le32(UFS_OCS_INVALID_CMD_TABLE_ATTR);\n>       }\n> @@ -1801,9 +1971,177 @@ static void ufs_sendback_req(void *opaque)\n>       ufs_irq_check(u);\n>   }\n>   \n> +static inline void ufs_wb_update_avail_buffer(UfsHc *u)\n> +{\n> +    UfsWb *wb = &u->wb;\n> +    uint64_t non_pinned_curr_bytes, non_pinned_used_bytes;\n> +    uint32_t units;\n> +\n> +    units = ufs_byte_to_unit(u, wb->pinned_curr_bytes);\n> +    u->attributes.pinned_wb_buffer_curr_alloc_units = cpu_to_be32(units);\n> +\n> +    if (!wb->pinned_curr_bytes)\n> +        u->attributes.pinned_wb_buffer_avail_percent = 0;\n> +    else\n> +        u->attributes.pinned_wb_buffer_avail_percent =\n> +            (wb->pinned_curr_bytes - wb->pinned_used_bytes) * 10 /\n> +            wb->pinned_curr_bytes;\n> +\n> +    non_pinned_curr_bytes = wb->curr_bytes - wb->pinned_curr_bytes;\n> +    non_pinned_used_bytes = wb->used_bytes - wb->pinned_used_bytes;\n> +\n> +    units = ufs_byte_to_unit(u, non_pinned_curr_bytes);\n> +    u->attributes.current_wb_buffer_size = cpu_to_be32(units);\n> +\n> +    if (!non_pinned_curr_bytes)\n> +        u->attributes.available_wb_buffer_size = 0;\n> +    else\n> +        u->attributes.available_wb_buffer_size =\n> +            (non_pinned_curr_bytes - non_pinned_used_bytes) * 10 /\n> +            non_pinned_curr_bytes;\n> +}\n> +\n> +static inline bool ufs_wb_is_flush_needed(UfsHc *u, uint64_t *no_flush_bytes)\n> +{\n> +    UfsWb *wb = &u->wb;\n> +    uint32_t max_unit;\n> +\n> +    switch (u->attributes.wb_buffer_partial_flush_mode) {\n> +    case UFS_WB_FLUSH_NONE:\n> +        *no_flush_bytes = 0;\n> +        break;\n> +    case UFS_WB_FLUSH_FIFO:\n> +        max_unit = be32_to_cpu(u->attributes.max_fifo_wb_partial_flush_mode);\n> +        *no_flush_bytes = ufs_unit_to_byte(u, max_unit);\n> +        break;\n> +    case UFS_WB_FLUSH_PINNED:\n> +        *no_flush_bytes = (u->flags.unpin_en) ? 0 : wb->pinned_used_bytes;\n> +        break;\n> +    default:\n> +        g_assert_not_reached();\n> +        break;\n> +    }\n> +\n> +    return wb->used_bytes > *no_flush_bytes;\n> +}\n> +\n> +#define UFS_WB_FLUSH_BYTES (4096 * 1024)\n> +static void ufs_wb_process_flush(UfsHc *u)\n> +{\n> +    UfsWb *wb = &u->wb;\n> +    uint64_t flush_bytes, no_flush_bytes;\n> +\n> +    switch (u->attributes.wb_buffer_flush_status) {\n> +    case UFS_WB_FLUSH_IDLE:\n> +        if (u->flags.wb_buffer_flush_en &&\n> +            ufs_wb_is_flush_needed(u, &no_flush_bytes)) {\n> +            u->attributes.wb_buffer_flush_status = UFS_WB_FLUSH_IN_PROGRESS;\n> +        }\n> +\n> +        break;\n> +    case UFS_WB_FLUSH_IN_PROGRESS:\n> +        if (!u->flags.wb_buffer_flush_en) {\n> +            u->attributes.wb_buffer_flush_status = UFS_WB_FLUSH_FAILED;\n> +\n> +        } else if (!ufs_wb_is_flush_needed(u, &no_flush_bytes)) {\n> +            u->attributes.wb_buffer_flush_status = UFS_WB_FLUSH_COMPLETED;\n> +\n> +        } else if (wb->pinned_used_bytes && u->flags.unpin_en) {\n> +            flush_bytes = MIN(wb->pinned_used_bytes, UFS_WB_FLUSH_BYTES);\n> +            wb->pinned_used_bytes -= flush_bytes;\n> +            wb->used_bytes -= flush_bytes;\n> +\n> +        } else {\n> +            flush_bytes = MIN(wb->used_bytes - no_flush_bytes, UFS_WB_FLUSH_BYTES);\n> +            wb->used_bytes -= flush_bytes;\n> +        }\n> +\n> +        break;\n> +    case UFS_WB_FLUSH_COMPLETED:\n> +        if (ufs_wb_is_flush_needed(u, &no_flush_bytes)) {\n> +            u->attributes.wb_buffer_flush_status =\n> +                (u->flags.wb_buffer_flush_en) ? UFS_WB_FLUSH_IDLE :\n> +                                                UFS_WB_FLUSH_IN_PROGRESS;\n> +        }\n> +    }\n> +}\n> +\n> +static void ufs_wb_process_resize(UfsHc *u)\n> +{\n> +    UfsWb *wb = &u->wb;\n> +\n> +    if (u->attributes.wb_buffer_resize_status != UFS_WB_RESIZE_IN_PROGRESS) {\n> +        return;\n> +    }\n> +\n> +    switch (u->attributes.wb_buffer_resize_en) {\n> +    case UFS_WB_DECREASE:\n> +        if (wb->curr_bytes <= wb->min_bytes) {\n> +            u->attributes.wb_buffer_resize_status = UFS_WB_RESIZE_FAILED;\n> +            return;\n> +\n> +        } else if (wb->curr_bytes <= wb->used_bytes) {\n> +            u->attributes.wb_buffer_resize_status = UFS_WB_RESIZE_FAILED;\n> +            return;\n> +        }\n> +\n> +        if (wb->curr_bytes - wb->used_bytes >= wb->resize_bytes) {\n> +            wb->curr_bytes -= wb->resize_bytes;\n> +        } else {\n> +            wb->curr_bytes = wb->used_bytes;\n> +        }\n> +\n> +        if (wb->curr_bytes < wb->min_bytes) {\n> +            wb->curr_bytes = wb->min_bytes;\n> +        }\n> +\n> +        break;\n> +    case UFS_WB_INCREASE:\n> +        if (wb->curr_bytes >= wb->max_bytes) {\n> +            u->attributes.wb_buffer_resize_status = UFS_WB_RESIZE_FAILED;\n> +            return;\n> +        }\n> +\n> +        wb->curr_bytes += wb->resize_bytes;\n> +        if (wb->curr_bytes >= wb->max_bytes) {\n> +            wb->curr_bytes = wb->max_bytes;\n> +        }\n> +\n> +        break;\n> +    default:\n> +        g_assert_not_reached();\n> +        break;\n> +    }\n> +\n> +    u->attributes.wb_buffer_resize_status = UFS_WB_RESIZE_COMPLETED;\n> +}\n> +\n> +static void ufs_wb_process_pinned(UfsHc *u)\n> +{\n> +    UfsWb *wb = &u->wb;\n> +    uint64_t avail_bytes;\n> +\n> +    avail_bytes = wb->curr_bytes - wb->used_bytes + wb->pinned_used_bytes;\n> +\n> +    if ((u->attributes.wb_buffer_partial_flush_mode != UFS_WB_FLUSH_PINNED) ||\n> +        (wb->curr_bytes <= wb->pinned_min_bytes)) {\n> +        wb->pinned_curr_bytes = 0;\n> +        wb->pinned_used_bytes = 0;\n> +\n> +    } else if (avail_bytes <= wb->pinned_max_bytes) {\n> +        wb->pinned_curr_bytes = avail_bytes;\n> +\n> +    } else {\n> +        wb->pinned_curr_bytes = wb->pinned_max_bytes;\n> +    }\n> +}\n> +\n>   static void ufs_process_idle(UfsHc *u)\n>   {\n> -    /* Currently do nothing */\n> +    ufs_wb_process_flush(u);\n> +    ufs_wb_process_resize(u);\n> +    ufs_wb_process_pinned(u);\n> +    ufs_wb_update_avail_buffer(u);\n>       return;\n>   }\n>   \n> @@ -1873,6 +2211,10 @@ static bool ufs_check_constraints(UfsHc *u, Error **errp)\n>           return false;\n>       }\n>   \n> +    if (u->params.wb_min_size > u->params.wb_max_size) {\n> +        error_setg(errp, \"wb-min-size must be less than or equal wb-max-size\");\n\nreturn false is missing here.\n\n> +    }\n> +\n>       return true;\n>   }\n>   \n> @@ -1911,11 +2253,42 @@ static void ufs_init_state(UfsHc *u)\n>       }\n>   }\n>   \n> +static void ufs_wb_init(UfsHc *u)\n> +{\n> +    UfsWb *wb = &u->wb;\n> +\n> +    wb->max_bytes = ufs_unit_to_byte(u, u->params.wb_max_size);\n> +    wb->min_bytes = ufs_unit_to_byte(u, u->params.wb_min_size);\n> +\n> +    wb->curr_bytes = wb->max_bytes;\n> +    wb->used_bytes = 0;\n> +    wb->resize_bytes = (wb->max_bytes - wb->min_bytes) / 10;\n> +\n> +    u->attributes.wb_buffer_flush_status = UFS_WB_FLUSH_IDLE;\n> +    u->attributes.available_wb_buffer_size = 0xA;\n> +    u->attributes.wb_buffer_life_time_est = 0x1;\n> +    u->attributes.current_wb_buffer_size = cpu_to_be32(u->params.wb_max_size);\n> +\n> +    u->attributes.wb_buffer_resize_hint = UFS_WB_HINT_KEEP;\n> +    u->attributes.wb_buffer_resize_status = UFS_WB_RESIZE_IDLE;\n> +\n> +    u->attributes.wb_buffer_partial_flush_mode = UFS_WB_FLUSH_NONE;\n> +\n> +    wb->pinned_curr_bytes = 0;\n> +    wb->pinned_used_bytes = 0;\n> +    wb->pinned_min_bytes = 0;\n> +    wb->pinned_total_written_bytes = 0;\n> +}\n> +\n>   static void ufs_init_hc(UfsHc *u)\n>   {\n>       uint32_t cap = 0;\n>       uint32_t mcqconfig = 0;\n>       uint32_t mcqcap = 0;\n> +    uint32_t ext_wb_sup = WB_RESIZE | WB_FIFO | WB_PINNED;\n> +    uint32_t ext_ufs_feat_sup = UFS_DEV_WB_SUPPORT |\n> +                                UFS_DEV_HIGH_TEMP_NOTIF |\n> +                                UFS_DEV_LOW_TEMP_NOTIF;\n>       int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL_RT);\n>   \n>       u->reg_size = pow2ceil(ufs_reg_size(u));\n> @@ -1977,8 +2350,13 @@ static void ufs_init_hc(UfsHc *u)\n>           UFS_DEV_LOW_TEMP_NOTIF;\n>       u->device_desc.queue_depth = u->params.nutrs;\n>       u->device_desc.product_revision_level = 0x04;\n> +    u->device_desc.extended_wb_support |= cpu_to_be16(ext_wb_sup);\n>       u->device_desc.extended_ufs_features_support =\n> -        cpu_to_be32(UFS_DEV_HIGH_TEMP_NOTIF | UFS_DEV_LOW_TEMP_NOTIF);\n> +        cpu_to_be32((ext_ufs_feat_sup));\n> +    u->device_desc.write_booster_buffer_preserve_user_space_en = 0x01;\n> +    u->device_desc.write_booster_buffer_type = 0x01;\n> +    u->device_desc.num_shared_write_booster_buffer_alloc_units =\n> +        cpu_to_be32(u->params.wb_max_size);\n>   \n>       memset(&u->geometry_desc, 0, sizeof(GeometryDescriptor));\n>       u->geometry_desc.length = sizeof(GeometryDescriptor);\n> @@ -1994,6 +2372,16 @@ static void ufs_init_hc(UfsHc *u)\n>           0x0; /* out-of-order data transfer is not supported */\n>       u->geometry_desc.max_context_id_number = 0x5;\n>       u->geometry_desc.supported_memory_types = cpu_to_be16(0x8001);\n> +    u->geometry_desc.write_booster_buffer_max_n_alloc_units =\n> +        cpu_to_be32(u->params.wb_max_size);\n> +    u->geometry_desc.device_max_write_booster_l_us =\n> +        u->params.wb_max_lus;\n> +    u->geometry_desc.write_booster_buffer_cap_adj_fac =\n> +        u->params.wb_cap_adj_fac;\n> +    u->geometry_desc.supported_write_booster_buffer_user_space_reduction_types =\n> +        u->params.wb_reduction;\n> +    u->geometry_desc.supported_write_booster_buffer_types =\n> +        0x1; /* lu-dedicated buffer type is not supported */\n>   \n>       memset(&u->attributes, 0, sizeof(u->attributes));\n>       u->attributes.max_data_in_size = 0x08;\n> @@ -2008,6 +2396,8 @@ static void ufs_init_hc(UfsHc *u)\n>       memset(&u->flags, 0, sizeof(u->flags));\n>       u->flags.permanently_disable_fw_update = 1;\n>   \n> +    ufs_wb_init(u);\n> +\n>       /*\n>        * The temperature value is fixed to UFS_TEMPERATURE and does not change\n>        * dynamically\n> @@ -2073,6 +2463,11 @@ static const Property ufs_props[] = {\n>       DEFINE_PROP_UINT8(\"nutmrs\", UfsHc, params.nutmrs, 8),\n>       DEFINE_PROP_BOOL(\"mcq\", UfsHc, params.mcq, false),\n>       DEFINE_PROP_UINT8(\"mcq-maxq\", UfsHc, params.mcq_maxq, 2),\n> +    DEFINE_PROP_UINT32(\"wb-max-size\", UfsHc, params.wb_max_size, 0x400),\n> +    DEFINE_PROP_UINT32(\"wb-min-size\", UfsHc, params.wb_min_size, 0x100),\n> +    DEFINE_PROP_UINT8(\"wb-max-lus\", UfsHc, params.wb_max_lus, 1),\n> +    DEFINE_PROP_UINT8(\"wb-cap-adj-fac\", UfsHc, params.wb_cap_adj_fac, 3),\n> +    DEFINE_PROP_UINT8(\"wb-reduction\", UfsHc, params.wb_reduction, 1),\n>   };\n>   \n>   static const VMStateDescription ufs_vmstate = {\n> diff --git a/hw/ufs/ufs.h b/hw/ufs/ufs.h\n> index b5f040302129f4d02732ddd20ef82eb33c41922a..42a20245128a765afc2560342135ced81dcc684c 100644\n> --- a/hw/ufs/ufs.h\n> +++ b/hw/ufs/ufs.h\n> @@ -91,6 +91,11 @@ typedef struct UfsParams {\n>       bool mcq; /* Multiple Command Queue support */\n>       uint8_t mcq_qcfgptr; /* MCQ Queue Configuration Pointer in MCQCAP */\n>       uint8_t mcq_maxq; /* MCQ Maximum number of Queues */\n> +    uint32_t wb_max_size; /* WB Maximum allocation units */\n> +    uint32_t wb_min_size; /* WB Minimum allocation units */\n> +    uint8_t wb_max_lus; /* WB Maximum number of LUs */\n> +    uint8_t wb_cap_adj_fac; /* WB Capacity Adjustment Factor */\n> +    uint8_t wb_reduction; /* WB User Space Reduction */\n>   } UfsParams;\n>   \n>   /*\n> @@ -118,6 +123,24 @@ typedef struct UfsCq {\n>       QTAILQ_HEAD(, UfsRequest) req_list;\n>   } UfsCq;\n>   \n> +/*\n> + * Extended features\n> + */\n> +typedef struct UfsWb {\n> +    struct UfsHc *u;\n> +    uint64_t max_bytes;\n> +    uint64_t min_bytes;\n> +    uint64_t curr_bytes;\n> +    uint64_t used_bytes;\n> +    uint64_t resize_bytes;\n> +\n> +    uint64_t pinned_max_bytes;\n> +    uint64_t pinned_min_bytes;\n> +    uint64_t pinned_curr_bytes;\n> +    uint64_t pinned_used_bytes;\n> +    uint64_t pinned_total_written_bytes;\n> +} UfsWb;\n> +\n>   typedef struct UfsHc {\n>       PCIDevice parent_obj;\n>       UfsBus bus;\n> @@ -147,6 +170,9 @@ typedef struct UfsHc {\n>       UfsSq *sq[UFS_MAX_MCQ_QNUM];\n>       UfsCq *cq[UFS_MAX_MCQ_QNUM];\n>   \n> +    /* Extended features */\n> +    UfsWb wb;\n> +\n>       uint8_t temperature;\n>   \n>       QEMUTimer idle_timer;\n> @@ -211,6 +237,27 @@ static inline bool ufs_mcq_cq_full(UfsHc *u, uint32_t qid)\n>       return tail == ufs_mcq_cq_head(u, qid);\n>   }\n>   \n> +static inline uint64_t ufs_unit_to_byte(UfsHc *u, uint32_t unit)\n> +{\n> +    return (uint64_t)unit * u->geometry_desc.allocation_unit_size *\n> +           be32_to_cpu(u->geometry_desc.segment_size) * BDRV_SECTOR_SIZE;\n> +}\n> +\n> +static inline uint32_t ufs_byte_to_unit(UfsHc *u, uint64_t byte)\n> +{\n> +    return byte / BDRV_SECTOR_SIZE /\n> +           be32_to_cpu(u->geometry_desc.segment_size) /\n> +           u->geometry_desc.allocation_unit_size;\n> +}\n> +\n> +static inline bool ufs_is_write_req(UfsRequest *req)\n> +{\n> +    uint8_t cmd = req->req_upiu.sc.cdb[0];\n> +\n> +    /* UFS 4.1 Specifiaction doesn't support WRITE_12 */\n> +    return (cmd == WRITE_6) || (cmd == WRITE_10) || (cmd == WRITE_16);\n> +}\n> +\n>   #define TYPE_UFS \"ufs\"\n>   #define UFS(obj) OBJECT_CHECK(UfsHc, (obj), TYPE_UFS)\n>   \n> diff --git a/include/block/ufs.h b/include/block/ufs.h\n> index 4dacfb776f947a285d86018add82115f148b7dd9..aa0dde805826d0b06ab80fb74034192ba06c2045 100644\n> --- a/include/block/ufs.h\n> +++ b/include/block/ufs.h\n> @@ -1126,11 +1126,24 @@ enum health_desc_param {\n>       UFS_HEALTH_DESC_PARAM_LIFE_TIME_EST_B = 0x4,\n>   };\n>   \n> +/* Possible values for bUFSFeaturesSupport */\n>   enum {\n>       UFS_DEV_HIGH_TEMP_NOTIF = BIT(4),\n>       UFS_DEV_LOW_TEMP_NOTIF = BIT(5),\n>   };\n>   \n> +/* Possible values for dExtendedWriteBoosterSupport */\n> +enum {\n> +    WB_RESIZE = BIT(0),\n> +    WB_FIFO = BIT(1),\n> +    WB_PINNED = BIT(2),\n> +};\n> +\n> +/* Possible values for dExtendedUFSFeaturesSupport */\n> +enum {\n> +    UFS_DEV_WB_SUPPORT = BIT(8),\n> +};\n> +\n>   /* WriteBooster buffer mode */\n>   enum {\n>       UFS_WB_BUF_MODE_LU_DEDICATED = 0x0,\n> @@ -1207,6 +1220,44 @@ enum ufs_dev_pwr_mode {\n>       UFS_DEEPSLEEP_PWR_MODE = 4,\n>   };\n>   \n> +/* UFS Write Booster */\n> +enum ufs_wb_flush_status {\n> +    UFS_WB_FLUSH_IDLE = 0,\n> +    UFS_WB_FLUSH_IN_PROGRESS = 1,\n> +    UFS_WB_FLUSH_SUSPENDED = 2,\n> +    UFS_WB_FLUSH_COMPLETED = 3,\n> +    UFS_WB_FLUSH_FAILED = 4,\n> +    UFS_WB_FLUSH_STATUS_MAX,\n> +};\n> +\n> +enum ufs_wb_flush_mode {\n> +    UFS_WB_FLUSH_NONE = 0,\n> +    UFS_WB_FLUSH_FIFO = 1,\n> +    UFS_WB_FLUSH_PINNED = 2,\n> +    UFS_WB_FLUSH_MODE_MAX,\n> +};\n> +\n> +enum ufs_wb_resize_hint {\n> +    UFS_WB_HINT_KEEP = 0,\n> +    UFS_WB_HINT_DECREASE = 1,\n> +    UFS_WB_HINT_ENCREASE = 2,\n> +    UFS_WB_RESIZE_HINT_MAX,\n> +};\n> +\n> +enum ufs_wb_resize_op {\n> +    UFS_WB_DECREASE = 0,\n> +    UFS_WB_INCREASE = 1,\n> +    UFS_WB_RESIZE_OP_MAX,\n> +};\n> +\n> +enum ufs_wb_resize_status {\n> +    UFS_WB_RESIZE_IDLE = 0,\n> +    UFS_WB_RESIZE_IN_PROGRESS = 1,\n> +    UFS_WB_RESIZE_COMPLETED = 2,\n> +    UFS_WB_RESIZE_FAILED = 3,\n> +    UFS_WB_RESIZE_STATUS_MAX,\n> +};\n> +\n>   /*\n>    * struct UtpCmdRsp - Response UPIU structure\n>    * @residual_transfer_count: Residual transfer count DW-3\n>","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 (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=kd+h/5Fd;\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=lists.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)"],"Received":["from lists.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 4frY5w2qvYz1xy1\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 09 Apr 2026 05:29:08 +1000 (AEST)","from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1wAYZl-0005bL-Hr; Wed, 08 Apr 2026 15:28:34 -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 <jeuk20.kim@gmail.com>)\n id 1wAY86-0007yl-4A\n for qemu-devel@nongnu.org; Wed, 08 Apr 2026 14:59:58 -0400","from mail-pg1-x530.google.com ([2607:f8b0:4864:20::530])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)\n (Exim 4.90_1) (envelope-from <jeuk20.kim@gmail.com>)\n id 1wAJMk-00009C-EA\n for qemu-devel@nongnu.org; Tue, 07 Apr 2026 23:14:09 -0400","by mail-pg1-x530.google.com with SMTP id\n 41be03b00d2f7-c70ea5e9e9dso2328422a12.1\n for <qemu-devel@nongnu.org>; Tue, 07 Apr 2026 20:14:05 -0700 (PDT)","from [192.168.0.22] ([211.226.54.223])\n by smtp.gmail.com with ESMTPSA id\n d2e1a72fcca58-82d59834b63sm3056853b3a.21.2026.04.07.20.14.01\n (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128);\n Tue, 07 Apr 2026 20:14:03 -0700 (PDT)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20251104; t=1775618044; x=1776222844; darn=nongnu.org;\n h=content-transfer-encoding:in-reply-to:from:references:cc:to\n :content-language:subject:user-agent:mime-version:date:message-id\n :from:to:cc:subject:date:message-id:reply-to;\n bh=Epl8R6gqkuYHpoEN/nDPcfRLfr570OoK83w0h8WjqWE=;\n b=kd+h/5FdvKDohqFWHYNWTFFVfvJ8f0oByuyimRu+693c6jDp7xzN8Uzt4M5Wse7Zk1\n CjqX1RZNE4KH45MFmQNsPVJVgDuC1Z9lhFZ9dSxrYw4Ym0lgk6rxjKbbX/cBY2XT3CKO\n lS+FqvfcmN/n1cvhBzAO7P1yaJHOWFQcsLnfvgYEudg1WKD+ncV9TA19ilgWI4487k+1\n 4hEt6mBCV0rLn3sV6sriinDX+obu+SAbvrkvnUrsO2MOo2AEg8j7GrS8OB7vIuL8vDXb\n oTu/XxvBwGadb8w6EnFmbkAONUleIRA+JkZK5/V6jLDieP4kEqjTt9hHxc7tDPHHzdDd\n fcmA==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1775618044; x=1776222844;\n h=content-transfer-encoding:in-reply-to:from:references:cc:to\n :content-language:subject:user-agent:mime-version:date:message-id\n :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id\n :reply-to;\n bh=Epl8R6gqkuYHpoEN/nDPcfRLfr570OoK83w0h8WjqWE=;\n b=GYGat/2HI2UqHZQ3ZU/btLR5KyvsqWnIdrXFxLJFJDJsnm2axbBcTYyrYCyX9v1Bme\n CuzQRiF+T8JsfqiQvxHT5FJKKwBfHT2XScb5nXtd9oJ6Lqe4gNGeKGBM7RUfSrdHet/t\n 0XV9z8YtSmAdRU1fpqcgIgAMA/Suyq2+5DOjEK8NdJVPjIojs2dxzmZS9xVlc//wYfvs\n uMjN20Chc+3mhb0yNAHBPd946XInws+xp3MsFbL66o0mQiMpVD6hQEfh1fgvFgCKlfMf\n t6+vOh0fuUoKpfba3PtFO7moh00FYpQxQQwsSXRRb5kHZ7tQ4ulRGwLkKnNad9j4lKoR\n pMhQ==","X-Forwarded-Encrypted":"i=1;\n AJvYcCXPtGX2RXDMeRC0LlSTnP+aPdKbLsCHG+SyuqOE5jtwh8mSujR4+NY0cMuk7tKq4AU/4ivxV++VHgRW@nongnu.org","X-Gm-Message-State":"AOJu0YzE0Ck6hmGwPssjPTiXumDKskHrvVlYPBROHDa2aZnzHstIp5zh\n MrThpz4aj44h9FDR0wLm9kL2kA+f39HNHhy8L6wSR9DEvsny+ZzHvmoc","X-Gm-Gg":"AeBDietqTZJb4wIZtTGH52bhpEBoZUNk/EE61EB5mSnhaEah8jS1yHHbzrCzyRsigc5\n uqpj7VjE7zxMZstJQRz/THPbp+thxHz/Bcw/jZgAmemYVdUEjN24OVRYEgeYei6PxLc4NlKdv7o\n Ljx1qCZ4zQJZQ8uokGy7ml/1KnaS2SGl/QzUn/XTR/RKAN8PfOY5QNtZToMhwohWspWBq2mdcBm\n IiKfROZt6d5TW6D6ARR1fyQrrX4TIkHil6IdiEiVvWaitfWTgDl7BoDZdjiYLfQx69SaMyIbqHw\n n3lIOTHP+0RRR86e2yIxiSRwzvg+H+eIhV8cdvd2BDnSaWlICiQ0aknNFNkRICqJZ5stQFRwtQh\n oVSZWSEezWylqhBNzNeC/8ff1K+3Pgiv4gT3sz4XQ2c4k8LB4BrtQTQNNARoqdnrhm7QQuKVTlx\n gge5fPd0d054NeNNst9bf297F+TImM8M1OiQO2z2uC4w==","X-Received":"by 2002:a05:6a20:7295:b0:39f:2a87:b791 with SMTP id\n adf61e73a8af0-39f2ee2d4bbmr19149205637.23.1775618043720;\n Tue, 07 Apr 2026 20:14:03 -0700 (PDT)","Message-ID":"<e012eed2-31fc-47b7-a709-79b887c9a5d1@gmail.com>","Date":"Wed, 8 Apr 2026 12:14:02 +0900","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v2 4/5] hw/ufs: Add UFS Write Booster Support","Content-Language":"ko","To":"Jaemyung Lee <ldc.jml@gmail.com>, qemu-devel@nongnu.org","Cc":"Jeuk Kim <jeuk20.kim@samsung.com>, Kevin Wolf <kwolf@redhat.com>,\n Hanna Reitz <hreitz@redhat.com>, qemu-block@nongnu.org,\n Fabiano Rosas <farosas@suse.de>, Laurent Vivier <lvivier@redhat.com>,\n Paolo Bonzini <pbonzini@redhat.com>, Jaemyung Lee <jaemyung.lee@samsung.com>","References":"<20260407-write_booster-v2-0-dd772bb30194@samsung.com>\n <20260407-write_booster-v2-4-dd772bb30194@samsung.com>","From":"Jeuk Kim <jeuk20.kim@gmail.com>","In-Reply-To":"<20260407-write_booster-v2-4-dd772bb30194@samsung.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","Received-SPF":"pass client-ip=2607:f8b0:4864:20::530;\n envelope-from=jeuk20.kim@gmail.com; helo=mail-pg1-x530.google.com","X-Spam_score_int":"-20","X-Spam_score":"-2.1","X-Spam_bar":"--","X-Spam_report":"(-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,\n DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001,\n RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001,\n SPF_PASS=-0.001 autolearn=unavailable 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"}}]