| /* |
| * Copyright (c) 2020, Mediatek Inc. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #pragma GCC diagnostic ignored "-Wall" |
| #pragma GCC diagnostic ignored "-Wextra" |
| #pragma GCC diagnostic ignored "-Wsign-compare" |
| #pragma GCC diagnostic ignored "-Wpointer-bool-conversion" |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <errno.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <endian.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "ufs.h" |
| #include "ufs_hmr.h" |
| #include "ufs_cmds.h" |
| |
| /* |
| * HMR Quirks: 1 - enable, 0 - disable |
| */ |
| |
| /* |
| * Big-little-endian byte order: some params |
| * may occur in order other than expected. |
| * Change byte order for the following params. |
| */ |
| #define HMR_QUIRK_REFRESH_TOTCOUNT_BYTE_ORDER 1 |
| #define HMR_QUIRK_REFRESH_PROGRESS_BYTE_ORDER 1 |
| |
| /* |
| * There is the maximum number of HMR operations in the life |
| * of the device. Having reached this number, the device will |
| * return "General Failure" and set the bRefreshStatus attribute |
| * to 0x05. |
| */ |
| #define HMR_REFRESH_MAX_TOTCOUNT 200 |
| |
| /* |
| * Output progress every N iterations. |
| */ |
| #define HMR_PROGRESS_OUTPUT_ITER 1 |
| #if HMR_PROGRESS_OUTPUT_ITER <= 0 |
| # error keep HMR_PROGRESS_OUTPUT_ITER > 0 |
| #endif |
| |
| /* |
| * The HMR mode type description string size we |
| * support to output. Can be enlarged if required. |
| */ |
| #define HMR_MODE_TYPE_MAX_SIZE 40 |
| |
| enum hmr_err_codes { |
| EHMR_OK = 0, /* success */ |
| EHMR_REFRESH_PROGRESS = 100, /* wrong refresh progress */ |
| EHMR_REFRESH_STATUS, /* wrong refresh status */ |
| EHMR_REFRESH_TOTCOUNT, /* wrong refresh total count */ |
| EHMR_FREEMEM, /* memory wasn't freed */ |
| EHMR_NOMEM, /* not enough memory */ |
| EHMR_INVAL, /* invalid argument */ |
| EHMR_NORETRY, /* cannot be retried */ |
| EHMR_REFRESH_METHOD, /* wrong refrresh method */ |
| EHMR_REFRESH_UNIT, /* wrong refrresh unit */ |
| }; |
| |
| enum hmr_refresh_status { |
| HMR_ST_IDLE = 0, |
| HMR_ST_IN_PROGRESS, |
| HMR_ST_ABORTED, |
| HMR_ST_COMPLETED, |
| HMR_ST_BUSY, |
| HMR_ST_GENERAL, |
| }; |
| |
| enum hmr_stage_skip { |
| HMR_SKIP_STATUS_CHECK = 1 << 0, |
| HMR_SKIP_METHOD_SET = 1 << 1, |
| HMR_SKIP_UNIT_SET = 1 << 2, |
| }; |
| |
| #pragma pack(push, 1) |
| struct descriptor_health_layout { |
| __u8 length; |
| __u8 type; |
| __u8 pre_eol_info; |
| __u8 dev_life_time_est_a; |
| __u8 dev_life_time_est_b; |
| __u8 vendor_prop_info[0x20]; |
| __u32 refresh_total_count; |
| __u32 refresh_progress; |
| }; |
| #pragma pack(pop) |
| |
| struct descriptor { |
| enum desc_idn idn; |
| const char *name; |
| size_t size; |
| void *layout; |
| }; |
| |
| extern int do_query_rq(int fd, |
| struct ufs_bsg_request *bsg_req, /* request struct of the sg_io_v4 */ |
| struct ufs_bsg_reply *bsg_rsp, /* response struct of the sg_io_v4 */ |
| __u8 query_req_func, /* read / write */ |
| __u8 opcode, /* opcode r/w of desc/attr/fl etc.*/ |
| __u8 idn, /* (-t) util option */ |
| __u8 index, /* (-i) util option */ |
| __u8 sel, /* (-s) util option */ |
| __u16 req_buf_len, /* request buffer size */ |
| __u16 res_buf_len, /* response buffer size */ |
| __u8 *data_buf); /* buffer with/for data */ |
| |
| extern struct desc_field_offset device_health_desc_conf_field_name[]; |
| extern struct attr_fields ufs_attrs[]; |
| extern struct flag_fields ufs_flags[]; |
| |
| static struct descriptor_health_layout desc_health_layout; |
| |
| static struct descriptor desc_health = { |
| QUERY_DESC_IDN_HEALTH, |
| "Health", |
| sizeof desc_health_layout, |
| &desc_health_layout |
| }; |
| |
| static inline void hmr_delay_retry(int sec) |
| { |
| if (sec > 0) |
| sleep(sec); |
| } |
| |
| static inline void hmr_output_message(const char *msg) |
| { |
| /* |
| * |---------------------------------------------------------| |
| * | Message format template: | |
| * |---------------------------------------------------------| |
| * |<<-6->| |<<-29-> | |
| * |HMR: | |variable string | |
| * | | |
| * |---------------------------------------------------------| |
| * |HMR: | |waiting idle status | |
| * |---------------------------------------------------------| |
| */ |
| printf("%-6s %-29s\r", "HMR:", msg); |
| fflush(stdout); |
| } |
| |
| static inline void hmr_output_progress(__u32 progress, |
| __u64 iter, int sec, const char *msg) |
| { |
| /* |
| * |---------------------------------------------------------| |
| * | Progress format template: | |
| * |---------------------------------------------------------| |
| * |<<-6->| | <-9->>| |<<-35-> | |
| * |HMR: | | hex dig| |variable string | |
| * | | |
| * |---------------------------------------------------------| |
| * | The case of up to 100% progress: | |
| * |---------------------------------------------------------| |
| * |<<-6->| | <-9->>| |<<-35-> | |
| * |HMR: | | f20a| | | |
| * | | | | | | |
| * |---------------------------------------------------------| |
| * | The case of 100% progress: | |
| * |---------------------------------------------------------| |
| * |<<-6->| | <-8->>| |<<-20-> | |
| * |HMR: | | 100|% | | |
| * |---------------------------------------------------------| |
| */ |
| |
| /* |
| * Completed - print 100%, |
| * otherwise print hex progress indicator every N iterations. |
| */ |
| if (!progress) |
| printf("%-6s %8d%% %-20s\r", "HMR:", 100, msg); |
| else if (0 == (iter % HMR_PROGRESS_OUTPUT_ITER)) |
| printf("%-6s %9x %-20s\r", "HMR:", progress, msg); |
| |
| fflush(stdout); |
| if (sec > 0) |
| sleep(sec); |
| } |
| |
| static inline void hmr_output_header(const char *unit_str, int method) |
| { |
| int count; |
| const char *method_str; |
| char mode_str[HMR_MODE_TYPE_MAX_SIZE]; |
| |
| method_str = method == HMR_METHOD_FORCE ? "force" : "selective"; |
| |
| count = snprintf(mode_str, sizeof mode_str, "method:%s, unit:%s", |
| method_str, unit_str); |
| |
| if (count >= HMR_MODE_TYPE_MAX_SIZE) |
| ; /* the output was truncated, enlarge HMR_MODE_TYPE_MAX_SIZE */ |
| |
| /* |
| * |---------------------------------------------------------| |
| * | Header format template: | |
| * |---------------------------------------------------------| |
| * |<<-6->| | <-9->>| |<<-35-> | |
| * |HMR: | | status| |mode type | |
| * | | |
| * |---------------------------------------------------------| |
| * | Examples: | |
| * |---------------------------------------------------------| |
| * |HMR: | | started| |method:selective, unit:minimum | |
| * | | | | | | |
| * |---------------------------------------------------------| |
| * |HMR: | | started| |method:force, unit:full | |
| * | | | | | | |
| * |---------------------------------------------------------| |
| */ |
| printf("%-6s %9s %-30s\n", "HMR:", "started", mode_str); |
| fflush(stdout); |
| } |
| |
| static inline void hmr_output_footer(int rc) |
| { |
| /* |
| * |---------------------------------------------------------| |
| * | Footer format template: | |
| * |---------------------------------------------------------| |
| * |<<-6->| | <-9->>| |<<-35-> | |
| * |HMR: | | status| |error msg / code | |
| * | | |
| * |---------------------------------------------------------| |
| * | Examples: | |
| * |---------------------------------------------------------| |
| * |HMR: | |completed| | OK | |
| * | | | | | | |
| * |---------------------------------------------------------| |
| * |HMR: | | stopped| |-115 | |
| * | | | | | | |
| * |---------------------------------------------------------| |
| */ |
| |
| /* |
| * Leading LF is due to CR-ending on progress print out. |
| * In case of error LF was already sent with the error msg. |
| */ |
| if (0 == rc) |
| printf("\n%-6s %9s %-30s\n", "HMR:", "completed", "OK"); |
| else |
| printf("%-6s %9s %-30d\n", "HMR:", "stopped", rc); |
| |
| fflush(stdout); |
| } |
| |
| static inline void hmr_query_error(int rc, |
| const char *job_type, /* write/read/set/clear/etc. */ |
| const char *subject, /* desc/attr/flag/etc. */ |
| int opcode, /* opcode r/w of desc/attr/fl etc. */ |
| const char *field_name, /* name of the operated field */ |
| int field_idn) /* index of the field */ |
| { |
| print_error("hmr: query command: %s %s failed: " |
| "opcode 0x%x, field-name %s, field-idn 0x%x, rc %d.\n", |
| job_type, subject, opcode, field_name, field_idn); |
| } |
| |
| static inline int hmr_attr_sanity(enum attr_idn idn) |
| { |
| if (idn < 0 || idn >= QUERY_ATTR_IDN_MAX) |
| return -EHMR_INVAL; |
| |
| return EHMR_OK; |
| } |
| |
| static inline int hmr_flag_sanity(enum flag_idn idn) |
| { |
| if (idn < 0 || idn >= QUERY_FLAG_IDN_MAX) |
| return -EHMR_INVAL; |
| |
| return EHMR_OK; |
| } |
| |
| static inline int hmr_desc_sanity(enum desc_idn idn) |
| { |
| if (idn < 0 || idn >= QUERY_DESC_IDN_MAX) |
| return -EHMR_INVAL; |
| |
| return EHMR_OK; |
| } |
| |
| static int hmr_dev_open(const char* path, int *fd) |
| { |
| int rc = 0; |
| |
| errno = 0; |
| |
| *fd = open(path, O_RDWR); |
| if (*fd < 0) { |
| rc = errno; /* save errno: errno can be changed by the print */ |
| print_error("hmr: %s: '%s'", strerror(rc), path); |
| } |
| |
| return rc == 0 ? rc : -rc; |
| } |
| |
| static int hmr_dev_close(const char* path, int fd) |
| { |
| int rc; |
| |
| errno = 0; |
| |
| rc = close(fd); |
| if (rc) { |
| rc = errno; |
| print_error("hmr: %s: '%s'", strerror(rc), path); |
| } |
| |
| return rc == 0 ? rc : -rc; |
| } |
| |
| static int hmr_attr_read(__u32 *result, |
| int fd, |
| struct ufs_bsg_request *bsg_req, |
| struct ufs_bsg_reply *bsg_rsp, |
| enum attr_idn idn) |
| { |
| int rc; |
| struct attr_fields *field; |
| |
| if (hmr_attr_sanity(idn) != EHMR_OK || !result) |
| return -EHMR_INVAL; |
| |
| field = &ufs_attrs[idn]; |
| |
| /* Query to read attribute */ |
| rc = do_query_rq(fd, |
| bsg_req, |
| bsg_rsp, |
| UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, |
| UPIU_QUERY_OPCODE_READ_ATTR, |
| idn, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0); |
| |
| if (rc) { |
| hmr_query_error(rc, "read", "attr", UPIU_QUERY_OPCODE_READ_ATTR, |
| field->name, idn); |
| goto out; |
| } |
| |
| *result = be32toh(bsg_rsp->upiu_rsp.qr.value); |
| |
| out: |
| return rc; |
| } |
| |
| static int hmr_attr_write(__u32 value, |
| int fd, |
| struct ufs_bsg_request *bsg_req, |
| struct ufs_bsg_reply *bsg_rsp, |
| enum attr_idn idn) |
| { |
| int rc; |
| struct attr_fields *field; |
| |
| if (hmr_attr_sanity(idn) != EHMR_OK) |
| return -EHMR_INVAL; |
| |
| field = &ufs_attrs[idn]; |
| |
| bsg_req->upiu_req.qr.value = htobe32(value); |
| |
| /* Query to write attribute */ |
| rc = do_query_rq(fd, |
| bsg_req, |
| bsg_rsp, |
| UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, |
| UPIU_QUERY_OPCODE_WRITE_ATTR, |
| idn, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0); |
| |
| if (rc) |
| hmr_query_error(rc, "write", "attr", UPIU_QUERY_OPCODE_WRITE_ATTR, |
| field->name, idn); |
| |
| return rc; |
| } |
| |
| static int hmr_flag_modify(int fd, |
| struct ufs_bsg_request *bsg_req, |
| struct ufs_bsg_reply *bsg_rsp, |
| enum query_opcode opcode, |
| enum flag_idn idn) |
| { |
| int rc; |
| struct flag_fields *field; |
| |
| if (opcode != UPIU_QUERY_OPCODE_SET_FLAG && |
| opcode != UPIU_QUERY_OPCODE_CLEAR_FLAG && |
| opcode != UPIU_QUERY_OPCODE_TOGGLE_FLAG) |
| return -EHMR_INVAL; |
| |
| if (hmr_flag_sanity(idn) != EHMR_OK) |
| return -EHMR_INVAL; |
| |
| field = &ufs_flags[idn]; |
| |
| /* Query to set/clear/toggle flag */ |
| rc = do_query_rq(fd, |
| bsg_req, |
| bsg_rsp, |
| UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST, |
| opcode, |
| idn, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0); |
| |
| if (rc) |
| hmr_query_error(rc, "modify", "flag", opcode, field->name, idn); |
| |
| return rc; |
| } |
| |
| static int hmr_desc_read(struct descriptor *result, |
| int fd, |
| struct ufs_bsg_request *bsg_req, |
| struct ufs_bsg_reply *bsg_rsp, |
| enum desc_idn idn) |
| { |
| int rc; |
| |
| if (hmr_desc_sanity(idn) != EHMR_OK || |
| !result || |
| !result->layout || |
| result->size <= 0) |
| return -EHMR_INVAL; |
| |
| /* Query to read descriptor */ |
| rc = do_query_rq(fd, |
| bsg_req, |
| bsg_rsp, |
| UPIU_QUERY_FUNC_STANDARD_READ_REQUEST, |
| UPIU_QUERY_OPCODE_READ_DESC, |
| idn, |
| 0, |
| 0, |
| 0, |
| result->size, |
| result->layout); |
| |
| if (rc) |
| hmr_query_error(rc, "read", "desc", UPIU_QUERY_OPCODE_READ_DESC, |
| result->name, idn); |
| |
| return rc; |
| } |
| |
| static int hmr_progress_read(__u32 *result, |
| int fd, |
| struct ufs_bsg_request *bsg_req, |
| struct ufs_bsg_reply *bsg_rsp) |
| { |
| int rc; |
| struct descriptor *desc; |
| struct descriptor_health_layout *layout; |
| |
| desc = &desc_health; |
| |
| /* Read descriptor Health */ |
| rc = hmr_desc_read(desc, |
| fd, |
| bsg_req, |
| bsg_rsp, |
| desc->idn); |
| |
| if (rc) |
| goto out; |
| |
| layout = desc->layout; |
| |
| if (!HMR_QUIRK_REFRESH_PROGRESS_BYTE_ORDER) |
| *result = be32toh(layout->refresh_progress); |
| else |
| *result = layout->refresh_progress; |
| |
| return rc; |
| |
| out: |
| print_error("hmr: read progress: failed."); |
| return rc; |
| } |
| |
| static int inline hmr_method_set(int fd, int method) |
| { |
| int rc; |
| struct ufs_bsg_request bsg_req = {0}; |
| struct ufs_bsg_reply bsg_rsp = {0}; |
| |
| /* Set attribute bRefreshMethod - force or selective */ |
| rc = hmr_attr_write(method, |
| fd, &bsg_req, &bsg_rsp, QUERY_ATTR_IDN_REFRESH_METHOD); |
| |
| return rc; |
| } |
| |
| static inline int hmr_unit_set(int fd, int unit) |
| { |
| int rc; |
| struct ufs_bsg_request bsg_req = {0}; |
| struct ufs_bsg_reply bsg_rsp = {0}; |
| |
| /* Set attribute bRefreshUnit - minimum or full */ |
| rc = hmr_attr_write(unit, |
| fd, &bsg_req, &bsg_rsp, QUERY_ATTR_IDN_REFRESH_UNIT); |
| |
| return rc; |
| } |
| |
| static inline int hmr_precondition_verify_status(int fd, |
| struct ufs_bsg_request *bsg_req, |
| struct ufs_bsg_reply *bsg_rsp) |
| { |
| int rc; |
| int cur = 0; |
| int count = 10; |
| __u32 result; |
| |
| retry: |
| /* Read refresh status */ |
| rc = hmr_attr_read(&result, |
| fd, |
| bsg_req, |
| bsg_rsp, |
| QUERY_ATTR_IDN_REFRESH_STATUS); |
| if (rc) |
| goto out; |
| |
| if (result != HMR_ST_IDLE) { |
| /* One time only */ |
| if (0 == cur) |
| hmr_output_message("waiting idle status"); |
| |
| /* Retry */ |
| if (++cur <= count) { |
| hmr_delay_retry(1); |
| goto retry; |
| } |
| |
| /* Error */ |
| print_error("hmr: precondition: " |
| "refresh status != 0x%x (0x%x)", HMR_ST_IDLE, result); |
| rc = -EHMR_REFRESH_STATUS; |
| } |
| |
| out: |
| return rc; |
| } |
| |
| static inline int hmr_precondition_validate_totcount(__u32 *result, |
| struct descriptor_health_layout *layout) |
| { |
| int rc = EHMR_OK; |
| __u32 refresh_totcount; |
| |
| if (!result || !layout) |
| return -EHMR_INVAL; |
| |
| if (!HMR_QUIRK_REFRESH_TOTCOUNT_BYTE_ORDER) |
| refresh_totcount = be32toh(layout->refresh_total_count); |
| else |
| refresh_totcount = layout->refresh_total_count; |
| |
| if (refresh_totcount >= HMR_REFRESH_MAX_TOTCOUNT) { |
| print_error("hmr: precondition: " |
| "refresh total count >= max (0x%x >= 0x%x).", |
| refresh_totcount, HMR_REFRESH_MAX_TOTCOUNT); |
| rc = -EHMR_REFRESH_TOTCOUNT; |
| goto out; |
| } |
| |
| *result = refresh_totcount; |
| |
| out: |
| return rc; |
| } |
| |
| static inline void hmr_output_wrong_run(int method, int unit) |
| { |
| print_error("hmr: precondition: device's hmr-progress is not empty. " |
| "Run is possible only with the method: %d, unit: %d", |
| method, unit); |
| } |
| |
| static int hmr_precondition_verify_run(int fd, |
| int req_method, int req_unit, enum hmr_stage_skip *stage_passer) |
| { |
| int rc; |
| __u32 dev_method; |
| __u32 dev_unit; |
| __u32 dev_progress; |
| |
| struct ufs_bsg_request bsg_req = {0}; |
| struct ufs_bsg_reply bsg_rsp = {0}; |
| |
| if (!stage_passer) |
| return -EHMR_INVAL; |
| |
| /* |
| * This code is intended to solve the case when the |
| * utility was run while HMR progress is not empty. |
| * It can happen, for example, when running the utility |
| * in one mode, then exit and run it again in some |
| * other mode, while the underline device's HMR status |
| * is still in progress. |
| * |
| * Let's make the policy simple: |
| * 1. Refresh progress is 0 -> OK. |
| * 2. Requested method equal device method |
| * and |
| * Requested unit equal device unit |
| * 1) Unit is minimum -> OK. |
| * 2) Unit is full -> OK, but have to skip the check for |
| * refresh status to be "idle", since |
| * in this mode it is "in-progress" |
| * until the HMR is completed. |
| * 3. Any other case is considered as violation. |
| */ |
| |
| /* Read progress */ |
| rc = hmr_progress_read(&dev_progress, fd, &bsg_req, &bsg_rsp); |
| if (rc) |
| goto out; |
| |
| /* Clean run, may proceed with HMR */ |
| if (0 == dev_progress) |
| goto out; |
| |
| /* Read method */ |
| rc = hmr_attr_read(&dev_method, |
| fd, |
| &bsg_req, |
| &bsg_rsp, |
| QUERY_ATTR_IDN_REFRESH_METHOD); |
| if (rc) |
| goto out; |
| |
| /* Read unit */ |
| rc = hmr_attr_read(&dev_unit, |
| fd, |
| &bsg_req, |
| &bsg_rsp, |
| QUERY_ATTR_IDN_REFRESH_UNIT); |
| if (rc) |
| goto out; |
| |
| /* Method is different, set error */ |
| if (req_method != dev_method) { |
| hmr_output_wrong_run(dev_method, dev_unit); |
| rc = -EHMR_REFRESH_UNIT; |
| goto out; |
| } |
| |
| /* Unit is different, set error */ |
| if (req_unit != dev_unit) { |
| hmr_output_wrong_run(dev_method, dev_unit); |
| rc = -EHMR_REFRESH_METHOD; |
| goto out; |
| } |
| |
| /* |
| * The progress is not empty, and method/unit are the same. |
| * |
| * 1. Unit type is full? - skip the refresh status check, |
| * since it will never be completed/idle in this mode until ends. |
| * 2. Skip set method and unit, no need - the values are already set. |
| */ |
| if (dev_unit == HMR_UNIT_FULL) |
| *stage_passer |= HMR_SKIP_STATUS_CHECK; |
| |
| *stage_passer |= (HMR_SKIP_METHOD_SET | HMR_SKIP_UNIT_SET); |
| |
| out: |
| return rc; |
| } |
| |
| static int hmr_precondition_verify(int fd, |
| struct tool_options *opt, __u32 *totcount, |
| enum hmr_stage_skip *stage_passer) |
| { |
| int rc; |
| struct descriptor *desc; |
| struct ufs_bsg_request bsg_req = {0}; |
| struct ufs_bsg_reply bsg_rsp = {0}; |
| |
| if (!opt || !totcount || !stage_passer) |
| return -EHMR_INVAL; |
| |
| /* |
| * 1. Verify the run perspective. |
| * |
| * In case of non-empty progress, make a decision |
| * if the run still can be proceeded. |
| */ |
| rc = hmr_precondition_verify_run(fd, |
| opt->hmr_method, opt->hmr_unit, stage_passer); |
| if (rc) |
| goto err; |
| |
| /* |
| * 2. Verify refresh total count. |
| * |
| * Note: the Health descriptor was already read in |
| * hmr_precondition_verify_run(), so updated |
| * refresh total count is already available. |
| */ |
| desc = &desc_health; |
| rc = hmr_precondition_validate_totcount(totcount, desc->layout); |
| if (rc) |
| goto err; |
| |
| /* Skip the refresh status check */ |
| if (*stage_passer & HMR_SKIP_STATUS_CHECK) |
| goto success; |
| |
| /* |
| * 3. Verify refresh status. |
| * |
| * Read refresh status. |
| * In case the status is not Idle - retry. |
| * |
| * We are expecting here to get the Idle status (0x00), |
| * but in some cases the status may be different: |
| * aborted (0x02), completed (0x03), etc. |
| * |
| * Usually, the statuses of this kind are saved until |
| * the first reading of the attribute is occured, then |
| * the value o the attribute should be change to Idle (0x00), |
| * but let's be generous and give it a sufficient number of |
| * retries. |
| */ |
| rc = hmr_precondition_verify_status(fd, &bsg_req, &bsg_rsp); |
| if (rc) |
| goto err; |
| |
| success: |
| return rc; |
| |
| err: |
| print_error("hmr: precondition failed."); |
| return rc; |
| } |
| |
| static inline int hmr_postcondition_verify_progress(int fd, |
| struct ufs_bsg_request *bsg_req, |
| struct ufs_bsg_reply *bsg_rsp) |
| { |
| int rc; |
| __u32 result; |
| |
| /* Read Health descriptor, and get refresh progress field */ |
| rc = hmr_progress_read(&result, fd, bsg_req, bsg_rsp); |
| if (rc) |
| goto out; |
| |
| /* Progress should be 0 on HMR completion */ |
| if (result != 0) { |
| print_error("hmr: postcondition: " |
| "refresh progress != 0x0 (0x%x).", result); |
| rc = -EHMR_REFRESH_PROGRESS; |
| goto out; |
| } |
| |
| out: |
| return rc; |
| } |
| |
| static inline int hmr_postcondition_verify_status(int fd, |
| struct ufs_bsg_request *bsg_req, |
| struct ufs_bsg_reply *bsg_rsp) |
| { |
| int rc; |
| __u32 result; |
| |
| /* Read refresh status */ |
| rc = hmr_attr_read(&result, |
| fd, |
| bsg_req, |
| bsg_rsp, |
| QUERY_ATTR_IDN_REFRESH_STATUS); |
| |
| if (rc) |
| goto out; |
| |
| /* The accepted statuses are Completed and Idle */ |
| if (result != HMR_ST_COMPLETED && result != HMR_ST_IDLE) { |
| print_error("hmr: postcondition: " |
| "refresh status != (0x%x or 0x%x) (0x%x)", |
| HMR_ST_COMPLETED, HMR_ST_IDLE, result); |
| rc = -EHMR_REFRESH_STATUS; |
| goto out; |
| } |
| |
| out: |
| return rc; |
| } |
| |
| static inline int hmr_postcondition_verify_totcount(__u32 prev_totcount, |
| struct descriptor_health_layout *layout) |
| { |
| int rc = EHMR_OK; |
| __u32 result; |
| |
| if (!layout) |
| return -EHMR_INVAL; |
| |
| if (!HMR_QUIRK_REFRESH_TOTCOUNT_BYTE_ORDER) |
| result = be32toh(layout->refresh_total_count); |
| else |
| result = layout->refresh_total_count; |
| |
| if (result <= prev_totcount) { |
| print_error("hmr: postcondition: " |
| "refresh total count 0x%x <= 0x%x.", |
| prev_totcount, result); |
| rc = -EHMR_REFRESH_TOTCOUNT; |
| } |
| |
| return rc; |
| } |
| |
| static int hmr_postcondition_verify(int fd, __u32 totcount) |
| { |
| int rc; |
| struct ufs_bsg_request bsg_req = {0}; |
| struct ufs_bsg_reply bsg_rsp = {0}; |
| struct descriptor *desc; |
| |
| desc = &desc_health; |
| |
| /* |
| * 1. Verify refresh progress. |
| * |
| * Progress equal to 0 indicates refresh was completed. |
| */ |
| rc = hmr_postcondition_verify_progress(fd, &bsg_req, &bsg_rsp); |
| if (rc) |
| goto err; |
| |
| /* |
| * 2. Check refresh status. |
| * |
| * The expected value is Completed (0x03) in first read, |
| * and Idle (0x00) in any following read (assuming no new |
| * HMR process begins). |
| * Perhaps, depending on the type of test, the first reading |
| * has already taken place. Therefore, we may consider both |
| * values to be valid. |
| */ |
| rc = hmr_postcondition_verify_status(fd, &bsg_req, &bsg_rsp); |
| if (rc) |
| goto err; |
| |
| /* |
| * 3. Verify refresh total count. |
| * |
| * Refresh total count expected to be increased |
| * upon completion. |
| * |
| * The health descriptor was already read in step 1 |
| * doing hmr_progress_read(). Just use its layout. |
| */ |
| rc = hmr_postcondition_verify_totcount(totcount, desc->layout); |
| if (rc) |
| goto err; |
| |
| /* Success */ |
| return rc; |
| |
| err: |
| print_error("hmr: postcondition failed."); |
| return rc; |
| } |
| |
| static inline int hmr_refresh_initiate_retry(int fd, |
| struct ufs_bsg_request *bsg_req, |
| struct ufs_bsg_reply *bsg_rsp, |
| int cur, |
| int count) |
| { |
| int rc; |
| __u32 result; |
| |
| if (cur > count) |
| return -EHMR_NORETRY; |
| |
| /* |
| * Read refresh status. |
| * Then retry only in case the status is Busy. |
| */ |
| rc = hmr_attr_read(&result, |
| fd, |
| bsg_req, |
| bsg_rsp, |
| QUERY_ATTR_IDN_REFRESH_STATUS); |
| |
| if (rc) |
| goto out; |
| |
| /* Other than Busy - canot be retried */ |
| if (result != HMR_ST_BUSY) { |
| rc = -EHMR_NORETRY; |
| goto out; |
| } |
| |
| out: |
| /* 0 - can be retried, otherwise cannot */ |
| return rc; |
| } |
| |
| static int hmr_refresh_initiate(int fd, |
| struct ufs_bsg_request *bsg_req, |
| struct ufs_bsg_reply *bsg_rsp) |
| { |
| int rc; |
| int err; |
| int cur = 0; |
| int count = 10; |
| |
| /* |
| * Initiate refresh. |
| * For that, set flag fRefreshEnable to 1. |
| * In case device command queues are not empty, |
| * this query request may fail. In such a case |
| * the bRefreshStatus attribite shell be set to |
| * 0x04 - HMR_ST_BUSY. This case is handled by |
| * hmr_refresh_initiate_retry(). |
| */ |
| retry: |
| rc = hmr_flag_modify(fd, bsg_req, bsg_rsp, |
| UPIU_QUERY_OPCODE_SET_FLAG, |
| QUERY_FLAG_IDN_REFRESH_ENABLE); |
| |
| /* Success */ |
| if (!rc) |
| goto out; |
| |
| /* Check query can be retried */ |
| err = hmr_refresh_initiate_retry(fd, bsg_req, bsg_rsp, ++cur, count); |
| if (!err) { |
| hmr_delay_retry(1); |
| goto retry; |
| } |
| |
| print_error("hmr: initiate unit refresh: setting flag " |
| "fRefreshEnable failed after %d retries (%d).", count, rc); |
| |
| out: |
| return rc; |
| } |
| |
| static int hmr_unit_verify_completed(int fd, |
| struct ufs_bsg_request *bsg_req, |
| struct ufs_bsg_reply *bsg_rsp) |
| { |
| __u32 result; |
| int rc; |
| int cur = 0; |
| int count = 10; |
| |
| /* |
| * Read refresh status. |
| * |
| * The expected status is Completed, which changes to Idle |
| * after it was read once. Thus, if a given attribute reads |
| * some other process, then we are in a race state and there |
| * is a possibility that the attribute value will be reset |
| * to Idle by another process. Thus, we will consider both |
| * statuses - Completed and Idle as valid at this stage. |
| */ |
| retry: |
| rc = hmr_attr_read(&result, |
| fd, |
| bsg_req, |
| bsg_rsp, |
| QUERY_ATTR_IDN_REFRESH_STATUS); |
| |
| /* Error or (Completed / Idle) */ |
| if (rc || |
| result == HMR_ST_COMPLETED || |
| result == HMR_ST_IDLE) |
| goto out; |
| |
| /* Retry */ |
| if (++cur <= count) { |
| hmr_delay_retry(1); |
| goto retry; |
| } |
| |
| /* |
| * Cannot be completed, result holds |
| * other than HMR_ST_COMPLETED or HMR_ST_IDLE status. |
| */ |
| rc = -EHMR_REFRESH_STATUS; |
| print_error("hmr: verify unit completed: getting status " |
| "completed failed after %d retries (%d).", count, result); |
| |
| out: |
| return rc; |
| } |
| |
| static int hmr_full_start(int fd, int method) |
| { |
| int rc; |
| __u32 result; |
| __u64 iter = 0; |
| |
| struct ufs_bsg_request bsg_req = {0}; |
| struct ufs_bsg_reply bsg_rsp = {0}; |
| |
| hmr_output_header("full", method); |
| |
| /* Start full refresh */ |
| rc = hmr_refresh_initiate(fd, &bsg_req, &bsg_rsp); |
| if (rc) |
| goto out; |
| |
| /* Start progress loop */ |
| while (1) { |
| /* Sample progress */ |
| rc = hmr_progress_read(&result, fd, &bsg_req, &bsg_rsp); |
| if (rc) |
| goto out; |
| |
| hmr_output_progress(result, iter++, 1, ""); |
| |
| /* Progress is 0 - completed */ |
| if (0 == result) |
| break; |
| } |
| |
| out: |
| hmr_output_footer(rc); |
| return rc; |
| } |
| |
| static int hmr_unit_start(int fd, int method) |
| { |
| int rc; |
| __u32 result; |
| __u64 iter = 0; |
| |
| struct ufs_bsg_request bsg_req = {0}; |
| struct ufs_bsg_reply bsg_rsp = {0}; |
| |
| hmr_output_header("minimum", method); |
| |
| /* Start HMR loop */ |
| while (1) { |
| /* Start unit refresh */ |
| rc = hmr_refresh_initiate(fd, &bsg_req, &bsg_rsp); |
| if (rc) |
| goto out; |
| |
| /* Verify completed */ |
| rc = hmr_unit_verify_completed(fd, &bsg_req, &bsg_rsp); |
| if (rc) |
| goto out; |
| |
| /* Read progress */ |
| rc = hmr_progress_read(&result, fd, &bsg_req, &bsg_rsp); |
| if (rc) |
| goto out; |
| |
| hmr_output_progress(result, iter++, 0, ""); |
| |
| /* Progress is 0 - completed */ |
| if (0 == result) |
| break; |
| } |
| |
| out: |
| hmr_output_footer(rc); |
| return rc; |
| } |
| |
| int do_hmr(struct tool_options *opt) |
| { |
| int rc; |
| int fd; |
| int (*hmr_job)(int, int); |
| __u32 refresh_totcount; |
| enum hmr_stage_skip stage_passer = 0; |
| |
| if (!opt || !opt->path) |
| return -EHMR_INVAL; |
| |
| /* Open dev in subject */ |
| rc = hmr_dev_open(opt->path, &fd); |
| if (rc) |
| goto out; |
| |
| /* Make verifications prior to HMR */ |
| rc = hmr_precondition_verify(fd, opt, &refresh_totcount, &stage_passer); |
| if (rc) |
| goto free; |
| |
| /* Set HMR method: force or selective */ |
| if (!(stage_passer & HMR_SKIP_METHOD_SET)) { |
| rc = hmr_method_set(fd, opt->hmr_method); |
| if (rc) |
| goto free; |
| } |
| |
| /* Set HMR unit: minimum or full */ |
| if (!(stage_passer & HMR_SKIP_UNIT_SET)) { |
| rc = hmr_unit_set(fd, opt->hmr_unit); |
| if (rc) |
| goto free; |
| } |
| |
| /* Do the HMR job */ |
| hmr_job = opt->hmr_unit == HMR_UNIT_MIN ? |
| &hmr_unit_start : &hmr_full_start; |
| |
| rc = (*hmr_job)(fd, opt->hmr_method); |
| if (rc) |
| goto free; |
| |
| /* Verify variables upon completion */ |
| rc = hmr_postcondition_verify(fd, refresh_totcount); |
| if (rc) |
| goto free; |
| |
| free: |
| rc = hmr_dev_close(opt->path, fd); |
| |
| out: |
| return rc; |
| } |
| |
| void hmr_help(char *tool_name) |
| { |
| /* General use case description */ |
| printf("\n HMR command usage:\n"); |
| printf("\n\t%s hmr [-p] <path to device> ([-x] <method> [-y] <unit>)\n", |
| tool_name); |
| |
| /* -p: mandatory, device path */ |
| printf("\n\t-p\t path - mandatory, ufs-bsg device path\n"); |
| |
| /* -x: optional, HMR method */ |
| printf("\n\t-x\t method - optional, the default is %d\n", |
| HMR_METHOD_SELECTIVE); |
| printf("\t\t\t %-3d: %-25s\n", |
| HMR_METHOD_FORCE, "force, refresh all blocks containing data"); |
| printf("\t\t\t %-3d: %-25s\n", |
| HMR_METHOD_SELECTIVE, "selective, refresh marked blocks only"); |
| |
| /* -y: optional, HMR unit */ |
| printf("\n\t-y\t unit - optional, the default is %d\n", HMR_UNIT_MIN); |
| printf("\t\t\t %-3d: %-25s\n", |
| HMR_UNIT_MIN, "minimum, perform HMR by minimum refresh units"); |
| printf("\t\t\t %-3d: %-25s\n", |
| HMR_UNIT_FULL, "full, perform a full HMR cycle in one command"); |
| } |