diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c index 6b286ea6f121..7c562559ad8b 100644 --- a/drivers/clk/clk-scmi.c +++ b/drivers/clk/clk-scmi.c @@ -12,7 +12,6 @@ #include #include #include -#include #define NOT_ATOMIC false #define ATOMIC true @@ -57,35 +56,17 @@ static unsigned long scmi_clk_recalc_rate(struct clk_hw *hw, static int scmi_clk_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { - u64 fmin, fmax, ftmp; + int ret; struct scmi_clk *clk = to_scmi_clk(hw); /* - * We can't figure out what rate it will be, so just return the - * rate back to the caller. scmi_clk_recalc_rate() will be called - * after the rate is set and we'll know what rate the clock is + * If we could not get a better rate scmi_clk_recalc_rate() will be + * called after the rate is set and we'll know what rate the clock is * running at then. */ - if (clk->info->rate_discrete) - return 0; - - fmin = clk->info->range.min_rate; - fmax = clk->info->range.max_rate; - if (req->rate <= fmin) { - req->rate = fmin; - - return 0; - } else if (req->rate >= fmax) { - req->rate = fmax; - - return 0; - } - - ftmp = req->rate - fmin; - ftmp += clk->info->range.step_size - 1; /* to round up */ - do_div(ftmp, clk->info->range.step_size); - - req->rate = ftmp * clk->info->range.step_size + fmin; + ret = scmi_proto_clk_ops->determine_rate(clk->ph, clk->id, &req->rate); + if (ret) + return ret; return 0; } @@ -221,7 +202,6 @@ static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk, const struct clk_ops *scmi_ops) { int ret; - unsigned long min_rate, max_rate; struct clk_init_data init = { .flags = CLK_GET_RATE_NOCACHE, @@ -236,20 +216,8 @@ static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk, if (ret) return ret; - if (sclk->info->rate_discrete) { - int num_rates = sclk->info->list.num_rates; - - if (num_rates <= 0) - return -EINVAL; - - min_rate = sclk->info->list.rates[0]; - max_rate = sclk->info->list.rates[num_rates - 1]; - } else { - min_rate = sclk->info->range.min_rate; - max_rate = sclk->info->range.max_rate; - } - - clk_hw_set_rate_range(&sclk->hw, min_rate, max_rate); + clk_hw_set_rate_range(&sclk->hw, sclk->info->min_rate, + sclk->info->max_rate); return ret; } diff --git a/drivers/clk/clk-scpi.c b/drivers/clk/clk-scpi.c index 7806569cd0d5..24cee7c9fda6 100644 --- a/drivers/clk/clk-scpi.c +++ b/drivers/clk/clk-scpi.c @@ -258,7 +258,7 @@ static void scpi_clocks_remove(struct platform_device *pdev) } for_each_available_child_of_node(np, child) - of_clk_del_provider(np); + of_clk_del_provider(child); } static int scpi_clocks_probe(struct platform_device *pdev) diff --git a/drivers/firmware/arm_scmi/base.c b/drivers/firmware/arm_scmi/base.c index cd1331c2fc40..a9cea09355ff 100644 --- a/drivers/firmware/arm_scmi/base.c +++ b/drivers/firmware/arm_scmi/base.c @@ -69,7 +69,7 @@ static int scmi_base_attributes_get(const struct scmi_protocol_handle *ph) int ret; struct scmi_xfer *t; struct scmi_msg_resp_base_attributes *attr_info; - struct scmi_revision_info *rev = ph->get_priv(ph); + struct scmi_base_info *rev = ph->get_priv(ph); ret = ph->xops->xfer_get_init(ph, PROTOCOL_ATTRIBUTES, 0, sizeof(*attr_info), &t); @@ -103,7 +103,7 @@ scmi_base_vendor_id_get(const struct scmi_protocol_handle *ph, bool sub_vendor) int ret, size; char *vendor_id; struct scmi_xfer *t; - struct scmi_revision_info *rev = ph->get_priv(ph); + struct scmi_base_info *rev = ph->get_priv(ph); if (sub_vendor) { cmd = BASE_DISCOVER_SUB_VENDOR; @@ -143,7 +143,7 @@ scmi_base_implementation_version_get(const struct scmi_protocol_handle *ph) int ret; __le32 *impl_ver; struct scmi_xfer *t; - struct scmi_revision_info *rev = ph->get_priv(ph); + struct scmi_base_info *rev = ph->get_priv(ph); ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_IMPLEMENT_VERSION, 0, sizeof(*impl_ver), &t); @@ -180,7 +180,7 @@ scmi_base_implementation_list_get(const struct scmi_protocol_handle *ph, __le32 *num_skip, *num_ret; u32 tot_num_ret = 0, loop_num_ret; struct device *dev = ph->dev; - struct scmi_revision_info *rev = ph->get_priv(ph); + struct scmi_base_info *rev = ph->get_priv(ph); ret = ph->xops->xfer_get_init(ph, BASE_DISCOVER_LIST_PROTOCOLS, sizeof(*num_skip), 0, &t); @@ -325,6 +325,8 @@ static void *scmi_base_fill_custom_report(const struct scmi_protocol_handle *ph, void *report, u32 *src_id) { int i; + u32 error_status; + size_t expected_sz; const struct scmi_base_error_notify_payld *p = payld; struct scmi_base_error_report *r = report; @@ -338,10 +340,19 @@ static void *scmi_base_fill_custom_report(const struct scmi_protocol_handle *ph, if (evt_id != SCMI_EVENT_BASE_ERROR_EVENT || sizeof(*p) < payld_sz) return NULL; + expected_sz = offsetof(typeof(*p), msg_reports); + if (payld_sz < expected_sz) + return NULL; + r->timestamp = timestamp; r->agent_id = le32_to_cpu(p->agent_id); - r->fatal = IS_FATAL_ERROR(le32_to_cpu(p->error_status)); - r->cmd_count = ERROR_CMD_COUNT(le32_to_cpu(p->error_status)); + error_status = le32_to_cpu(p->error_status); + r->fatal = IS_FATAL_ERROR(error_status); + r->cmd_count = ERROR_CMD_COUNT(error_status); + expected_sz += r->cmd_count * sizeof(p->msg_reports[0]); + if (payld_sz < expected_sz) + return NULL; + for (i = 0; i < r->cmd_count; i++) r->reports[i] = le64_to_cpu(p->msg_reports[i]); *src_id = 0; @@ -377,7 +388,7 @@ static int scmi_base_protocol_init(const struct scmi_protocol_handle *ph) u8 *prot_imp; char name[SCMI_SHORT_NAME_MAX_SIZE]; struct device *dev = ph->dev; - struct scmi_revision_info *rev = scmi_revision_area_get(ph); + struct scmi_base_info *rev = scmi_revision_area_get(ph); rev->major_ver = PROTOCOL_REV_MAJOR(ph->version); rev->minor_ver = PROTOCOL_REV_MINOR(ph->version); diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c index ab36871650a1..42e666a628c7 100644 --- a/drivers/firmware/arm_scmi/clock.c +++ b/drivers/firmware/arm_scmi/clock.c @@ -5,6 +5,7 @@ * Copyright (C) 2018-2022 ARM Ltd. */ +#include #include #include #include @@ -156,13 +157,26 @@ struct scmi_clock_rate_notify_payld { __le32 rate_high; }; +struct scmi_clock_desc { + u32 id; + unsigned int tot_rates; + struct scmi_clock_rates r; +#define RATE_MIN 0 +#define RATE_MAX 1 +#define RATE_STEP 2 + struct scmi_clock_info info; +}; + +#define to_desc(p) (container_of(p, struct scmi_clock_desc, info)) + struct clock_info { int num_clocks; int max_async_req; bool notify_rate_changed_cmd; bool notify_rate_change_requested_cmd; atomic_t cur_async_req; - struct scmi_clock_info *clk; + struct scmi_clock_desc *clkds; +#define CLOCK_INFO(c, i) (&(((c)->clkds + (i))->info)) int (*clock_config_set)(const struct scmi_protocol_handle *ph, u32 clk_id, enum clk_state state, enum scmi_clock_oem_config oem_type, @@ -184,7 +198,7 @@ scmi_clock_domain_lookup(struct clock_info *ci, u32 clk_id) if (clk_id >= ci->num_clocks) return ERR_PTR(-EINVAL); - return ci->clk + clk_id; + return CLOCK_INFO(ci, clk_id); } static int @@ -225,8 +239,7 @@ scmi_clock_protocol_attributes_get(const struct scmi_protocol_handle *ph, struct scmi_clk_ipriv { struct device *dev; - u32 clk_id; - struct scmi_clock_info *clk; + struct scmi_clock_desc *clkd; }; static void iter_clk_possible_parents_prepare_message(void *message, unsigned int desc_index, @@ -235,7 +248,7 @@ static void iter_clk_possible_parents_prepare_message(void *message, unsigned in struct scmi_msg_clock_possible_parents *msg = message; const struct scmi_clk_ipriv *p = priv; - msg->id = cpu_to_le32(p->clk_id); + msg->id = cpu_to_le32(p->clkd->id); /* Set the number of OPPs to be skipped/already read */ msg->skip_parents = cpu_to_le32(desc_index); } @@ -245,7 +258,6 @@ static int iter_clk_possible_parents_update_state(struct scmi_iterator_state *st { const struct scmi_msg_resp_clock_possible_parents *r = response; struct scmi_clk_ipriv *p = priv; - struct device *dev = ((struct scmi_clk_ipriv *)p)->dev; u32 flags; flags = le32_to_cpu(r->num_parent_flags); @@ -257,14 +269,15 @@ static int iter_clk_possible_parents_update_state(struct scmi_iterator_state *st * assume it's returned+remaining on first call. */ if (!st->max_resources) { - p->clk->num_parents = st->num_returned + st->num_remaining; - p->clk->parents = devm_kcalloc(dev, p->clk->num_parents, - sizeof(*p->clk->parents), - GFP_KERNEL); - if (!p->clk->parents) { - p->clk->num_parents = 0; + int num_parents = st->num_returned + st->num_remaining; + + p->clkd->info.parents = devm_kcalloc(p->dev, num_parents, + sizeof(*p->clkd->info.parents), + GFP_KERNEL); + if (!p->clkd->info.parents) return -ENOMEM; - } + + /* max_resources is used by the iterators to control bounds */ st->max_resources = st->num_returned + st->num_remaining; } @@ -279,29 +292,29 @@ static int iter_clk_possible_parents_process_response(const struct scmi_protocol const struct scmi_msg_resp_clock_possible_parents *r = response; struct scmi_clk_ipriv *p = priv; - u32 *parent = &p->clk->parents[st->desc_index + st->loop_idx]; + p->clkd->info.parents[st->desc_index + st->loop_idx] = + le32_to_cpu(r->possible_parents[st->loop_idx]); - *parent = le32_to_cpu(r->possible_parents[st->loop_idx]); + /* Count only effectively discovered parents */ + p->clkd->info.num_parents++; return 0; } -static int scmi_clock_possible_parents(const struct scmi_protocol_handle *ph, u32 clk_id, - struct scmi_clock_info *clk) +static int scmi_clock_possible_parents(const struct scmi_protocol_handle *ph, + u32 clk_id, struct clock_info *cinfo) { struct scmi_iterator_ops ops = { .prepare_message = iter_clk_possible_parents_prepare_message, .update_state = iter_clk_possible_parents_update_state, .process_response = iter_clk_possible_parents_process_response, }; - + struct scmi_clock_desc *clkd = &cinfo->clkds[clk_id]; struct scmi_clk_ipriv ppriv = { - .clk_id = clk_id, - .clk = clk, + .clkd = clkd, .dev = ph->dev, }; void *iter; - int ret; iter = ph->hops->iter_response_init(ph, &ops, 0, CLOCK_POSSIBLE_PARENTS_GET, @@ -310,9 +323,7 @@ static int scmi_clock_possible_parents(const struct scmi_protocol_handle *ph, u3 if (IS_ERR(iter)) return PTR_ERR(iter); - ret = ph->hops->iter_response_run(iter); - - return ret; + return ph->hops->iter_response_run(iter); } static int @@ -351,7 +362,7 @@ static int scmi_clock_attributes_get(const struct scmi_protocol_handle *ph, u32 attributes; struct scmi_xfer *t; struct scmi_msg_resp_clock_attributes *attr; - struct scmi_clock_info *clk = cinfo->clk + clk_id; + struct scmi_clock_info *clk = CLOCK_INFO(cinfo, clk_id); ret = ph->xops->xfer_get_init(ph, CLOCK_ATTRIBUTES, sizeof(clk_id), sizeof(*attr), &t); @@ -393,7 +404,7 @@ static int scmi_clock_attributes_get(const struct scmi_protocol_handle *ph, clk->rate_change_requested_notifications = true; if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) { if (SUPPORTS_PARENT_CLOCK(attributes)) - scmi_clock_possible_parents(ph, clk_id, clk); + scmi_clock_possible_parents(ph, clk_id, cinfo); if (SUPPORTS_GET_PERMISSIONS(attributes)) scmi_clock_get_permissions(ph, clk_id, clk); if (SUPPORTS_EXTENDED_CONFIG(attributes)) @@ -423,7 +434,7 @@ static void iter_clk_describe_prepare_message(void *message, struct scmi_msg_clock_describe_rates *msg = message; const struct scmi_clk_ipriv *p = priv; - msg->id = cpu_to_le32(p->clk_id); + msg->id = cpu_to_le32(p->clkd->id); /* Set the number of rates to be skipped/already read */ msg->rate_index = cpu_to_le32(desc_index); } @@ -456,20 +467,33 @@ iter_clk_describe_update_state(struct scmi_iterator_state *st, flags = le32_to_cpu(r->num_rates_flags); st->num_remaining = NUM_REMAINING(flags); st->num_returned = NUM_RETURNED(flags); - p->clk->rate_discrete = RATE_DISCRETE(flags); + p->clkd->r.rate_discrete = RATE_DISCRETE(flags); /* Warn about out of spec replies ... */ - if (!p->clk->rate_discrete && + if (!p->clkd->r.rate_discrete && (st->num_returned != 3 || st->num_remaining != 0)) { dev_warn(p->dev, "Out-of-spec CLOCK_DESCRIBE_RATES reply for %s - returned:%d remaining:%d rx_len:%zd\n", - p->clk->name, st->num_returned, st->num_remaining, + p->clkd->info.name, st->num_returned, st->num_remaining, st->rx_len); SCMI_QUIRK(clock_rates_triplet_out_of_spec, QUIRK_OUT_OF_SPEC_TRIPLET); } + if (!st->max_resources) { + unsigned int tot_rates = st->num_returned + st->num_remaining; + + p->clkd->r.rates = devm_kcalloc(p->dev, tot_rates, + sizeof(*p->clkd->r.rates), GFP_KERNEL); + if (!p->clkd->r.rates) + return -ENOMEM; + + /* max_resources is used by the iterators to control bounds */ + p->clkd->tot_rates = tot_rates; + st->max_resources = tot_rates; + } + return 0; } @@ -478,38 +502,20 @@ iter_clk_describe_process_response(const struct scmi_protocol_handle *ph, const void *response, struct scmi_iterator_state *st, void *priv) { - int ret = 0; struct scmi_clk_ipriv *p = priv; const struct scmi_msg_resp_clock_describe_rates *r = response; - if (!p->clk->rate_discrete) { - switch (st->desc_index + st->loop_idx) { - case 0: - p->clk->range.min_rate = RATE_TO_U64(r->rate[0]); - break; - case 1: - p->clk->range.max_rate = RATE_TO_U64(r->rate[1]); - break; - case 2: - p->clk->range.step_size = RATE_TO_U64(r->rate[2]); - break; - default: - ret = -EINVAL; - break; - } - } else { - u64 *rate = &p->clk->list.rates[st->desc_index + st->loop_idx]; + p->clkd->r.rates[p->clkd->r.num_rates] = RATE_TO_U64(r->rate[st->loop_idx]); - *rate = RATE_TO_U64(r->rate[st->loop_idx]); - p->clk->list.num_rates++; - } + /* Count only effectively discovered rates */ + p->clkd->r.num_rates++; - return ret; + return 0; } static int -scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, - struct scmi_clock_info *clk) +scmi_clock_describe_rates_get_full(const struct scmi_protocol_handle *ph, + struct scmi_clock_desc *clkd) { int ret; void *iter; @@ -519,12 +525,16 @@ scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, .process_response = iter_clk_describe_process_response, }; struct scmi_clk_ipriv cpriv = { - .clk_id = clk_id, - .clk = clk, + .clkd = clkd, .dev = ph->dev, }; - iter = ph->hops->iter_response_init(ph, &ops, SCMI_MAX_NUM_RATES, + /* + * Using tot_rates as max_resources parameter here so as to trigger + * the dynamic allocation only when strictly needed: when trying a + * full enumeration after a lazy one tot_rates will be non-zero. + */ + iter = ph->hops->iter_response_init(ph, &ops, clkd->tot_rates, CLOCK_DESCRIBE_RATES, sizeof(struct scmi_msg_clock_describe_rates), &cpriv); @@ -535,18 +545,99 @@ scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id, if (ret) return ret; - if (!clk->rate_discrete) { - dev_dbg(ph->dev, "Min %llu Max %llu Step %llu Hz\n", - clk->range.min_rate, clk->range.max_rate, - clk->range.step_size); - } else if (clk->list.num_rates) { - sort(clk->list.rates, clk->list.num_rates, - sizeof(clk->list.rates[0]), rate_cmp_func, NULL); + /* empty set ? */ + if (!clkd->r.num_rates) + return 0; + + if (clkd->r.rate_discrete && PROTOCOL_REV_MAJOR(ph->version) == 0x1) + sort(clkd->r.rates, clkd->r.num_rates, + sizeof(clkd->r.rates[0]), rate_cmp_func, NULL); + + return 0; +} + +static int +scmi_clock_describe_rates_get_lazy(const struct scmi_protocol_handle *ph, + struct scmi_clock_desc *clkd) +{ + struct scmi_iterator_ops ops = { + .prepare_message = iter_clk_describe_prepare_message, + .update_state = iter_clk_describe_update_state, + .process_response = iter_clk_describe_process_response, + }; + struct scmi_clk_ipriv cpriv = { + .clkd = clkd, + .dev = ph->dev, + }; + unsigned int first, last; + void *iter; + int ret; + + iter = ph->hops->iter_response_init(ph, &ops, 0, CLOCK_DESCRIBE_RATES, + sizeof(struct scmi_msg_clock_describe_rates), + &cpriv); + if (IS_ERR(iter)) + return PTR_ERR(iter); + + /* Try to grab a triplet, so that in case is NON-discrete we are done */ + first = 0; + last = 2; + ret = ph->hops->iter_response_run_bound(iter, &first, &last); + if (ret) + goto out; + + /* + * If discrete and we don't already have it, grab the last value, which + * should be the max + */ + if (clkd->r.rate_discrete && clkd->tot_rates > clkd->r.num_rates) { + first = clkd->tot_rates - 1; + last = clkd->tot_rates - 1; + ret = ph->hops->iter_response_run_bound(iter, &first, &last); } +out: + ph->hops->iter_response_bound_cleanup(iter); + return ret; } +static int +scmi_clock_describe_rates_get(const struct scmi_protocol_handle *ph, + u32 clk_id, struct clock_info *cinfo) +{ + struct scmi_clock_desc *clkd = &cinfo->clkds[clk_id]; + int ret; + + /* + * Since only after SCMI Clock v1.0 the returned rates are guaranteed to + * be discovered in ascending order, lazy enumeration cannot be use for + * SCMI Clock v1.0 protocol. + */ + if (PROTOCOL_REV_MAJOR(ph->version) > 0x1) + ret = scmi_clock_describe_rates_get_lazy(ph, clkd); + else + ret = scmi_clock_describe_rates_get_full(ph, clkd); + + if (ret) + return ret; + + clkd->info.min_rate = clkd->r.rates[RATE_MIN]; + if (!clkd->r.rate_discrete) { + clkd->info.max_rate = clkd->r.rates[RATE_MAX]; + dev_dbg(ph->dev, "Min %llu Max %llu Step %llu Hz\n", + clkd->r.rates[RATE_MIN], clkd->r.rates[RATE_MAX], + clkd->r.rates[RATE_STEP]); + } else { + clkd->info.max_rate = clkd->r.rates[clkd->r.num_rates - 1]; + dev_dbg(ph->dev, "Clock:%s Num_Rates:%u -> Min %llu Max %llu\n", + clkd->info.name, clkd->tot_rates, + clkd->info.min_rate, clkd->info.max_rate); + } + + return 0; +} + static int scmi_clock_rate_get(const struct scmi_protocol_handle *ph, u32 clk_id, u64 *value) @@ -624,6 +715,75 @@ static int scmi_clock_rate_set(const struct scmi_protocol_handle *ph, return ret; } +static int scmi_clock_determine_rate(const struct scmi_protocol_handle *ph, + u32 clk_id, unsigned long *rate) +{ + u64 fmin, fmax, ftmp; + struct scmi_clock_info *clk; + struct scmi_clock_desc *clkd; + struct clock_info *ci = ph->get_priv(ph); + + if (!rate) + return -EINVAL; + + clk = scmi_clock_domain_lookup(ci, clk_id); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + clkd = to_desc(clk); + + /* + * If we can't figure out what rate it will be, so just return the + * rate back to the caller. + */ + if (clkd->r.rate_discrete) + return 0; + + fmin = clk->min_rate; + fmax = clk->max_rate; + if (*rate <= fmin) { + *rate = fmin; + return 0; + } else if (*rate >= fmax) { + *rate = fmax; + return 0; + } + + ftmp = *rate - fmin; + ftmp += clkd->r.rates[RATE_STEP] - 1; /* to round up */ + ftmp = div64_ul(ftmp, clkd->r.rates[RATE_STEP]); + + *rate = ftmp * clkd->r.rates[RATE_STEP] + fmin; + + return 0; +} + +static const struct scmi_clock_rates * +scmi_clock_all_rates_get(const struct scmi_protocol_handle *ph, u32 clk_id) +{ + struct clock_info *ci = ph->get_priv(ph); + struct scmi_clock_desc *clkd; + struct scmi_clock_info *clk; + + clk = scmi_clock_domain_lookup(ci, clk_id); + if (IS_ERR(clk) || !clk->name[0]) + return NULL; + + clkd = to_desc(clk); + /* Needs full enumeration ? */ + if (clkd->r.rate_discrete && clkd->tot_rates != clkd->r.num_rates) { + int ret; + + /* rates[] is already allocated BUT we need to re-enumerate */ + clkd->r.num_rates = 0; + ret = scmi_clock_describe_rates_get_full(ph, clkd); + if (ret) + return NULL; + } + + return &clkd->r; +} + static int scmi_clock_config_set(const struct scmi_protocol_handle *ph, u32 clk_id, enum clk_state state, @@ -936,6 +1096,8 @@ static const struct scmi_clk_proto_ops clk_proto_ops = { .info_get = scmi_clock_info_get, .rate_get = scmi_clock_rate_get, .rate_set = scmi_clock_rate_set, + .determine_rate = scmi_clock_determine_rate, + .all_rates_get = scmi_clock_all_rates_get, .enable = scmi_clock_enable, .disable = scmi_clock_disable, .state_get = scmi_clock_state_get, @@ -1080,17 +1242,16 @@ static int scmi_clock_protocol_init(const struct scmi_protocol_handle *ph) if (ret) return ret; - cinfo->clk = devm_kcalloc(ph->dev, cinfo->num_clocks, - sizeof(*cinfo->clk), GFP_KERNEL); - if (!cinfo->clk) + cinfo->clkds = devm_kcalloc(ph->dev, cinfo->num_clocks, + sizeof(*cinfo->clkds), GFP_KERNEL); + if (!cinfo->clkds) return -ENOMEM; for (clkid = 0; clkid < cinfo->num_clocks; clkid++) { - struct scmi_clock_info *clk = cinfo->clk + clkid; - + cinfo->clkds[clkid].id = clkid; ret = scmi_clock_attributes_get(ph, clkid, cinfo); if (!ret) - scmi_clock_describe_rates_get(ph, clkid, clk); + scmi_clock_describe_rates_get(ph, clkid, cinfo); } if (PROTOCOL_REV_MAJOR(ph->version) >= 0x3) { diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 7c9617d080a0..b9723c105fc1 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -138,7 +139,7 @@ static inline void unpack_scmi_header(u32 msg_hdr, struct scmi_msg_hdr *hdr) xfer_; \ }) -struct scmi_revision_info * +struct scmi_base_info * scmi_revision_area_get(const struct scmi_protocol_handle *ph); void scmi_setup_protocol_implemented(const struct scmi_protocol_handle *ph, u8 *prot_imp); @@ -462,6 +463,28 @@ struct scmi_transport_core_operations { const struct scmi_message_operations *msg; }; +/** + * struct scmi_transport_handle - Transport instance handle + * @supplier_get: A helper to retrieve the device descriptor, identifying the + * transport driver serving this SCMI instance, which will be + * used as a supplier for the core SCMI driver: returning an + * error here causes the probe sequence to be interrupted and + * return that same error code, so that each transport can decide + * which policy to implement by choosing an appropriate error. + * @supplier_put: A helper to signal that the specified transport supplier is + * no more being used and it is made available again. + * + * Note that these helpers are needed and provided only by those transports + * whose initialization relies on some other subsystem and whose relations to + * the core SCMI driver is not tracked by firmware descriptions. + */ +struct scmi_transport_handle { + struct device __must_check *(*supplier_get) + (const struct scmi_transport_handle *th); + int (*supplier_put)(const struct scmi_transport_handle *th, + struct device *dev); +}; + /** * struct scmi_transport - A structure representing a configured transport * @@ -470,35 +493,163 @@ struct scmi_transport_core_operations { * @desc: Transport descriptor * @core_ops: A pointer to a pointer used by the core SCMI stack to make the * core transport operations accessible to the transports. + * @th: An optional pointer to the transport handle */ struct scmi_transport { struct device *supplier; struct scmi_desc desc; struct scmi_transport_core_operations **core_ops; + const struct scmi_transport_handle *th; }; +/** + * struct scmi_transport_supplier - Transport descriptor + * @mtx: A mutex to protect @available + * @available: A reference to an initialized transport device, when available. + * This reference is implicitly used to track the status of the + * supplier and it can cycle through the following 3 states: + * 1. NOT_READY - PTR_ERR(-EPROBE_DEFER): no supplier available; + * this is the transport initial state. + * 2. AVAILABLE - : a transport supplier has been + * initialized and it is available, ready to use. + * 3. BUSY _ PTR_ERR(-EBUSY): transport supplier is currently in use. + * @th: An embedded transport handle object that embeds the helpers + * implementing the above mentioned logic + * + * Note that this transport driver enforces single instance probing. + */ +struct scmi_transport_supplier { + /* Protect @available */ + struct mutex mtx; + struct device *available; + const struct scmi_transport_handle th; +}; + +#define to_sup(t) container_of(t, struct scmi_transport_supplier, th) + +/** + * scmi_transport_supplier_put - A helper to dispose of a supplier + * @th: A reference to the transport handle to use + * @supplier: A reference to the device supplier to manage, cannot be NULL + * or ERR_PTR. + * + * Note that putting a supplier will have different effect based on the + * current state of scmi_transport_supplier.available: + * - NOT_READY/BUSY: @supplier will be set as the new available device: this + * can be used to made available a supplier OR stop using one. + * - AVAILABLE: if the @supplier we are disposing of matches the currently + * available one, roll back to NOT_READY state. + * Any other attempt to override an available supplier with a + * new one is rejected, effectively enforcing one single supplier. + * + * Return: 0 on Success, errno otherwise. + */ +static inline int +scmi_transport_supplier_put(const struct scmi_transport_handle *th, + struct device *supplier) +{ + struct scmi_transport_supplier *sup = to_sup(th); + + /* Nothing to do when the provided supplier was never real */ + if (IS_ERR_OR_NULL(supplier)) + return 0; + + guard(mutex)(&sup->mtx); + switch (PTR_ERR_OR_ZERO(sup->available)) { + case -EPROBE_DEFER: + case -EBUSY: + sup->available = supplier; + break; + case 0: + /* Putting a supplier when in the AVAILABLE state causes a + * transition back to the NOT_READY state, BUT only if the + * supplier we are disposing of was exactly the device that was + * previously made readily available. + */ + if (supplier != sup->available) + return -EINVAL; + sup->available = ERR_PTR(-EPROBE_DEFER); + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * scmi_transport_supplier_get - A helper to get hold of a supplier + * @th: A reference to the transport handle to use + * + * Note that, trying to get a supplier device can return: + * - a ready to use supplier device, (subsequently made unavailable) + * - PTR_ERR(-EPROBE_DEFER): no supplier is available + * - PTR_ERR(-BUSY): supplier was already taken by a previous get + * + * This allows the probe to defer and wait when a possible device can + * be reasonably expected to appear. + * + * Return: a usable supplier device on Success or PTR_ERR on Failure. + */ +static inline struct device * +scmi_transport_supplier_get(const struct scmi_transport_handle *th) +{ + struct scmi_transport_supplier *sup = to_sup(th); + struct device *supplier; + + guard(mutex)(&sup->mtx); + supplier = sup->available; + if (!IS_ERR(sup->available)) + sup->available = ERR_PTR(-EBUSY); + + return supplier; +} + +#define DEFINE_SCMI_TRANSPORT_SUPPLIER(__supplier) \ +struct scmi_transport_supplier __supplier = { \ + .mtx = __MUTEX_INITIALIZER(__supplier.mtx), \ + .available = INIT_ERR_PTR(-EPROBE_DEFER), \ + .th.supplier_get = scmi_transport_supplier_get, \ + .th.supplier_put = scmi_transport_supplier_put, \ +} + #define DEFINE_SCMI_TRANSPORT_DRIVER(__tag, __drv, __desc, __match, __core_ops)\ static void __tag##_dev_free(void *data) \ { \ struct platform_device *spdev = data; \ + struct scmi_transport *strans; \ + \ + strans = dev_get_platdata(&spdev->dev); \ + if (strans && strans->th) \ + strans->th->supplier_put(strans->th, strans->supplier); \ \ platform_device_unregister(spdev); \ } \ \ static int __tag##_probe(struct platform_device *pdev) \ { \ - struct device *dev = &pdev->dev; \ + struct device *dev = &pdev->dev, *supplier; \ struct platform_device *spdev; \ struct scmi_transport strans; \ int ret; \ \ + supplier = dev; \ + strans.th = device_get_match_data(dev); \ + if (strans.th) { \ + supplier = strans.th->supplier_get(strans.th); \ + if (IS_ERR(supplier)) \ + return PTR_ERR(supplier); \ + } \ + \ spdev = platform_device_alloc("arm-scmi", PLATFORM_DEVID_AUTO); \ - if (!spdev) \ - return -ENOMEM; \ + if (!spdev) { \ + ret = -ENOMEM; \ + goto err_mem; \ + } \ \ device_set_of_node_from_dev(&spdev->dev, dev); \ \ - strans.supplier = dev; \ + strans.supplier = supplier; \ memcpy(&strans.desc, &(__desc), sizeof(strans.desc)); \ strans.core_ops = &(__core_ops); \ \ @@ -515,6 +666,10 @@ static int __tag##_probe(struct platform_device *pdev) \ \ err: \ platform_device_put(spdev); \ +err_mem: \ + if (strans.th) \ + strans.th->supplier_put(strans.th, supplier); \ + \ return ret; \ } \ \ diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index f167194f7cf6..3e0d975ec94c 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -17,6 +17,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -133,7 +134,7 @@ struct scmi_protocol_instance { * usage. * @protocols_mtx: A mutex to protect protocols instances initialization. * @protocols_imp: List of protocols implemented, currently maximum of - * scmi_revision_info.num_protocols elements allocated by the + * scmi_base_info.num_protocols elements allocated by the * base protocol * @active_protocols: IDR storing device_nodes for protocols actually defined * in the DT and confirmed as implemented by fw. @@ -151,7 +152,7 @@ struct scmi_info { int id; struct device *dev; const struct scmi_desc *desc; - struct scmi_revision_info version; + struct scmi_base_info version; struct scmi_handle handle; struct scmi_xfers_info tx_minfo; struct scmi_xfers_info rx_minfo; @@ -265,7 +266,7 @@ scmi_vendor_protocol_lookup(int protocol_id, char *vendor_id, } static const struct scmi_protocol * -scmi_vendor_protocol_get(int protocol_id, struct scmi_revision_info *version) +scmi_vendor_protocol_get(int protocol_id, struct scmi_base_info *version) { const struct scmi_protocol *proto; @@ -303,7 +304,7 @@ scmi_vendor_protocol_get(int protocol_id, struct scmi_revision_info *version) } static const struct scmi_protocol * -scmi_protocol_get(int protocol_id, struct scmi_revision_info *version) +scmi_protocol_get(int protocol_id, struct scmi_base_info *version) { const struct scmi_protocol *proto = NULL; @@ -1789,86 +1790,115 @@ static void *scmi_iterator_init(const struct scmi_protocol_handle *ph, size_t tx_size, void *priv) { int ret; - struct scmi_iterator *i; - i = devm_kzalloc(ph->dev, sizeof(*i), GFP_KERNEL); + struct scmi_iterator *i __free(kfree) = kzalloc(sizeof(*i), GFP_KERNEL); if (!i) return ERR_PTR(-ENOMEM); + if (!ops || !ph) + return ERR_PTR(-EINVAL); + i->ph = ph; i->ops = ops; i->priv = priv; ret = ph->xops->xfer_get_init(ph, msg_id, tx_size, 0, &i->t); - if (ret) { - devm_kfree(ph->dev, i); + if (ret) return ERR_PTR(ret); - } i->state.max_resources = max_resources; i->msg = i->t->tx.buf; i->resp = i->t->rx.buf; - return i; + return no_free_ptr(i); } -static int scmi_iterator_run(void *iter) +static int __scmi_iterator_run(void *iter, unsigned int *start, unsigned int *end) { - int ret = -EINVAL; + int ret; struct scmi_iterator_ops *iops; const struct scmi_protocol_handle *ph; struct scmi_iterator_state *st; - struct scmi_iterator *i = iter; + struct scmi_iterator *i; + unsigned int n; - if (!i || !i->ops || !i->ph) - return ret; + if (!iter) + return -EINVAL; + i = iter; iops = i->ops; ph = i->ph; st = &i->state; + /* Reinitialize state for next run */ + st->num_returned = 0; + st->num_remaining = 0; + st->desc_index = start ? *start : 0; + do { iops->prepare_message(i->msg, st->desc_index, i->priv); ret = ph->xops->do_xfer(ph, i->t); if (ret) - break; + return ret; st->rx_len = i->t->rx.len; ret = iops->update_state(st, i->resp, i->priv); if (ret) - break; + return ret; if (st->num_returned > st->max_resources - st->desc_index) { dev_err(ph->dev, "No. of resources can't exceed %d\n", st->max_resources); - ret = -EINVAL; - break; + return -EINVAL; } - for (st->loop_idx = 0; st->loop_idx < st->num_returned; - st->loop_idx++) { + if (end) + n = min(st->num_returned, *end - st->desc_index + 1); + else + n = st->num_returned; + for (st->loop_idx = 0; st->loop_idx < n; st->loop_idx++) { ret = iops->process_response(ph, i->resp, st, i->priv); if (ret) - goto out; + return ret; } - st->desc_index += st->num_returned; + st->desc_index += n; ph->xops->reset_rx_to_maxsz(ph, i->t); /* * check for both returned and remaining to avoid infinite * loop due to buggy firmware */ - } while (st->num_returned && st->num_remaining); + } while (st->num_returned && st->num_remaining && + (!end || (st->desc_index <= min(*end, st->max_resources - 1)))); -out: - /* Finalize and destroy iterator */ - ph->xops->xfer_put(ph, i->t); - devm_kfree(ph->dev, i); + return 0; +} + +static void scmi_iterator_bound_cleanup(void *iter) +{ + struct scmi_iterator *i = iter; + + i->ph->xops->xfer_put(i->ph, i->t); + kfree(i); +} + +static int scmi_iterator_run(void *iter) +{ + int ret; + + ret = __scmi_iterator_run(iter, NULL, NULL); + scmi_iterator_bound_cleanup(iter); return ret; } +static int scmi_iterator_run_bound(void *iter, unsigned int *start, + unsigned int *end) +{ + return __scmi_iterator_run(iter, start, end); +} + struct scmi_msg_get_fc_info { __le32 domain; __le32 message_id; @@ -2047,6 +2077,8 @@ static const struct scmi_proto_helpers_ops helpers_ops = { .get_max_msg_size = scmi_common_get_max_msg_size, .iter_response_init = scmi_iterator_init, .iter_response_run = scmi_iterator_run, + .iter_response_run_bound = scmi_iterator_run_bound, + .iter_response_bound_cleanup = scmi_iterator_bound_cleanup, .protocol_msg_check = scmi_protocol_msg_check, .fastchannel_init = scmi_common_fastchannel_init, .fastchannel_db_ring = scmi_common_fastchannel_db_ring, @@ -2063,7 +2095,7 @@ static const struct scmi_proto_helpers_ops helpers_ops = { * Return: A reference to the version memory area associated to the SCMI * instance underlying this protocol handle. */ -struct scmi_revision_info * +struct scmi_base_info * scmi_revision_area_get(const struct scmi_protocol_handle *ph) { const struct scmi_protocol_instance *pi = ph_to_pi(ph); @@ -2376,7 +2408,7 @@ scmi_is_protocol_implemented(const struct scmi_handle *handle, u8 prot_id) { int i; struct scmi_info *info = handle_to_scmi_info(handle); - struct scmi_revision_info *rev = handle->version; + struct scmi_base_info *rev = handle->version; if (!info->protocols_imp) return false; @@ -2552,13 +2584,11 @@ static bool scmi_is_transport_atomic(const struct scmi_handle *handle, */ static struct scmi_handle *scmi_handle_get(struct device *dev) { - struct list_head *p; struct scmi_info *info; struct scmi_handle *handle = NULL; mutex_lock(&scmi_list_mutex); - list_for_each(p, &scmi_list) { - info = list_entry(p, struct scmi_info, node); + list_for_each_entry(info, &scmi_list, node) { if (dev->parent == info->dev) { info->users++; handle = &info->handle; @@ -3171,7 +3201,7 @@ static const struct scmi_desc *scmi_transport_setup(struct device *dev) static void scmi_enable_matching_quirks(struct scmi_info *info) { - struct scmi_revision_info *rev = &info->version; + struct scmi_base_info *rev = &info->version; dev_dbg(info->dev, "Looking for quirks matching: %s/%s/0x%08X\n", rev->vendor_id, rev->sub_vendor_id, rev->impl_ver); diff --git a/drivers/firmware/arm_scmi/power.c b/drivers/firmware/arm_scmi/power.c index bb5062ab8280..28ef63a4ecc2 100644 --- a/drivers/firmware/arm_scmi/power.c +++ b/drivers/firmware/arm_scmi/power.c @@ -204,8 +204,12 @@ scmi_power_name_get(const struct scmi_protocol_handle *ph, u32 domain) { struct scmi_power_info *pi = ph->get_priv(ph); - struct power_dom_info *dom = pi->dom_info + domain; + struct power_dom_info *dom; + if (domain >= pi->num_domains) + return "unknown"; + + dom = pi->dom_info + domain; return dom->name; } diff --git a/drivers/firmware/arm_scmi/powercap.c b/drivers/firmware/arm_scmi/powercap.c index ab9733f4458b..27e3c805e927 100644 --- a/drivers/firmware/arm_scmi/powercap.c +++ b/drivers/firmware/arm_scmi/powercap.c @@ -453,10 +453,14 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph, return -EINVAL; /* Just log the last set request if acting on a disabled domain */ - if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2 && - !pi->states[domain_id].enabled) { - pi->states[domain_id].last_pcap = power_cap; - return 0; + if (PROTOCOL_REV_MAJOR(ph->version) >= 0x2) { + if (!scmi_powercap_dom_info_get(ph, domain_id)) + return -EINVAL; + + if (!pi->states[domain_id].enabled) { + pi->states[domain_id].last_pcap = power_cap; + return 0; + } } return __scmi_powercap_cap_set(ph, pi, domain_id, @@ -637,6 +641,9 @@ static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph, if (PROTOCOL_REV_MAJOR(ph->version) < 0x2) return -EINVAL; + if (!scmi_powercap_dom_info_get(ph, domain_id)) + return -EINVAL; + if (enable == pi->states[domain_id].enabled) return 0; @@ -678,6 +685,9 @@ static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph, if (PROTOCOL_REV_MAJOR(ph->version) < 0x2) return 0; + if (!scmi_powercap_dom_info_get(ph, domain_id)) + return -EINVAL; + /* * Report always real platform state; platform could have ignored * a previous disable request. Default true on any error. diff --git a/drivers/firmware/arm_scmi/protocols.h b/drivers/firmware/arm_scmi/protocols.h index f51245aca259..15ad5162e37a 100644 --- a/drivers/firmware/arm_scmi/protocols.h +++ b/drivers/firmware/arm_scmi/protocols.h @@ -259,7 +259,15 @@ struct scmi_fc_info { * multi-part responses using the custom operations * provided in @ops. * @iter_response_run: A common helper to trigger the run of a previously - * initialized iterator. + * initialized iterator. Note that unbound iterators are + * automatically cleaned up. + * @iter_response_run_bound: A common helper to trigger the run of a previously + * initialized iterator, but only within the + * specified, optional, @start and @end resource + * indexes. Note that these bound-iterators need + * explicit cleanup via @iter_response_bound_cleanup. + * @iter_response_bound_cleanup: A common helper to finally release the iterator + * for bound iterators. * @protocol_msg_check: A common helper to check is a specific protocol message * is supported. * @fastchannel_init: A common helper used to initialize FC descriptors by @@ -276,6 +284,9 @@ struct scmi_proto_helpers_ops { unsigned int max_resources, u8 msg_id, size_t tx_size, void *priv); int (*iter_response_run)(void *iter); + int (*iter_response_run_bound)(void *iter, + unsigned int *start, unsigned int *end); + void (*iter_response_bound_cleanup)(void *iter); int (*protocol_msg_check)(const struct scmi_protocol_handle *ph, u32 message_id, u32 *attributes); void (*fastchannel_init)(const struct scmi_protocol_handle *ph, diff --git a/drivers/firmware/arm_scmi/quirks.c b/drivers/firmware/arm_scmi/quirks.c index 03848283c2a0..9ed5ce4feded 100644 --- a/drivers/firmware/arm_scmi/quirks.c +++ b/drivers/firmware/arm_scmi/quirks.c @@ -219,9 +219,9 @@ static unsigned int scmi_quirk_signature(const char *vend, const char *sub_vend) static int scmi_quirk_range_parse(struct scmi_quirk *quirk) { const char *last, *first __free(kfree) = NULL; + int ret = 0; size_t len; char *sep; - int ret; quirk->start_range = 0; quirk->end_range = 0xFFFFFFFF; @@ -238,16 +238,15 @@ static int scmi_quirk_range_parse(struct scmi_quirk *quirk) if (sep) *sep = '\0'; - if (sep == first) /* -X */ - ret = kstrtouint(first + 1, 0, &quirk->end_range); - else /* X OR X- OR X-y */ + if (sep != first) /* X OR X- OR X-y */ { ret = kstrtouint(first, 0, &quirk->start_range); - if (ret) - return ret; + if (ret) + return ret; + } if (!sep) quirk->end_range = quirk->start_range; - else if (sep != last) /* x-Y */ + else if (sep != last) /* -X OR x-Y */ ret = kstrtouint(sep + 1, 0, &quirk->end_range); if (quirk->start_range > quirk->end_range) @@ -259,10 +258,8 @@ static int scmi_quirk_range_parse(struct scmi_quirk *quirk) void scmi_quirks_initialize(void) { struct scmi_quirk *quirk; - int i; - for (i = 0, quirk = scmi_quirks_table[0]; quirk; - i++, quirk = scmi_quirks_table[i]) { + for (unsigned int i = 0; (quirk = scmi_quirks_table[i]); i++) { int ret; ret = scmi_quirk_range_parse(quirk); diff --git a/drivers/firmware/arm_scmi/sensors.c b/drivers/firmware/arm_scmi/sensors.c index 882d55f987d2..b14bb1146356 100644 --- a/drivers/firmware/arm_scmi/sensors.c +++ b/drivers/firmware/arm_scmi/sensors.c @@ -793,7 +793,7 @@ static int scmi_sensor_config_get(const struct scmi_protocol_handle *ph, if (!ret) { struct scmi_sensor_info *s = si->sensors + sensor_id; - *sensor_config = get_unaligned_le64(t->rx.buf); + *sensor_config = get_unaligned_le32(t->rx.buf); s->sensor_config = *sensor_config; } @@ -1072,12 +1072,15 @@ scmi_sensor_fill_custom_report(const struct scmi_protocol_handle *ph, case SCMI_EVENT_SENSOR_UPDATE: { int i; + size_t expected_sz; struct scmi_sensor_info *s; const struct scmi_sensor_update_notify_payld *p = payld; struct scmi_sensor_update_report *r = report; struct sensors_info *sinfo = ph->get_priv(ph); - /* payld_sz is variable for this event */ + if (payld_sz < sizeof(*p)) + break; + r->sensor_id = le32_to_cpu(p->sensor_id); if (r->sensor_id >= sinfo->num_sensors) break; @@ -1091,6 +1094,11 @@ scmi_sensor_fill_custom_report(const struct scmi_protocol_handle *ph, * readings defined for this sensor or 1 for scalar sensors. */ r->readings_count = s->num_axis ?: 1; + expected_sz = sizeof(*p) + r->readings_count * + sizeof(p->readings[0]); + if (payld_sz < expected_sz) + break; + for (i = 0; i < r->readings_count; i++) scmi_parse_sensor_readings(&r->readings[i], &p->readings[i]); diff --git a/drivers/firmware/arm_scmi/transports/optee.c b/drivers/firmware/arm_scmi/transports/optee.c index 07ae18d5279d..dbe32e141748 100644 --- a/drivers/firmware/arm_scmi/transports/optee.c +++ b/drivers/firmware/arm_scmi/transports/optee.c @@ -154,6 +154,8 @@ static struct scmi_transport_core_operations *core; /* There can be only 1 SCMI service in OP-TEE we connect to */ static struct scmi_optee_agent *scmi_optee_private; +static DEFINE_SCMI_TRANSPORT_SUPPLIER(scmi_optee_supplier); + /* Open a session toward SCMI OP-TEE service with REE_KERNEL identity */ static int open_session(struct scmi_optee_agent *agent, u32 *tee_session) { @@ -522,7 +524,7 @@ static struct scmi_desc scmi_optee_desc = { }; static const struct of_device_id scmi_of_match[] = { - { .compatible = "linaro,scmi-optee" }, + { .compatible = "linaro,scmi-optee", .data = &scmi_optee_supplier.th}, { /* Sentinel */ }, }; @@ -561,18 +563,20 @@ static int scmi_optee_service_probe(struct tee_client_device *scmi_pta) if (ret) goto err; - /* Ensure agent resources are all visible before scmi_optee_private is */ + /* Ensure initialized scmi_optee_private is visible */ smp_mb(); scmi_optee_private = agent; - ret = platform_driver_register(&scmi_optee_driver); - if (ret) { - scmi_optee_private = NULL; - goto err; - } + ret = scmi_transport_supplier_put(&scmi_optee_supplier.th, agent->dev); + if (ret) + goto err_put; return 0; +err_put: + /* Ensure cleared reference is visible before resources are released */ + smp_store_mb(scmi_optee_private, NULL); + err: tee_client_close_context(tee_ctx); @@ -586,13 +590,12 @@ static void scmi_optee_service_remove(struct tee_client_device *scmi_pta) if (!scmi_optee_private) return; - platform_driver_unregister(&scmi_optee_driver); - if (!list_empty(&scmi_optee_private->channel_list)) return; /* Ensure cleared reference is visible before resources are released */ smp_store_mb(scmi_optee_private, NULL); + scmi_transport_supplier_put(&scmi_optee_supplier.th, agent->dev); tee_client_close_context(agent->tee_ctx); } @@ -616,7 +619,30 @@ static struct tee_client_driver scmi_optee_service_driver = { }, }; -module_tee_client_driver(scmi_optee_service_driver); +static int __init scmi_transport_optee_init(void) +{ + int ret; + + ret = tee_client_driver_register(&scmi_optee_service_driver); + if (ret) + return ret; + + ret = platform_driver_register(&scmi_optee_driver); + if (ret) { + tee_client_driver_unregister(&scmi_optee_service_driver); + return ret; + } + + return ret; +} +module_init(scmi_transport_optee_init); + +static void __exit scmi_transport_optee_exit(void) +{ + platform_driver_unregister(&scmi_optee_driver); + tee_client_driver_unregister(&scmi_optee_service_driver); +} +module_exit(scmi_transport_optee_exit); MODULE_AUTHOR("Etienne Carriere "); MODULE_DESCRIPTION("SCMI OPTEE Transport driver"); diff --git a/drivers/firmware/arm_scmi/transports/virtio.c b/drivers/firmware/arm_scmi/transports/virtio.c index 326c4a93e44b..3282d8271839 100644 --- a/drivers/firmware/arm_scmi/transports/virtio.c +++ b/drivers/firmware/arm_scmi/transports/virtio.c @@ -4,7 +4,7 @@ * (SCMI). * * Copyright (C) 2020-2022 OpenSynergy. - * Copyright (C) 2021-2024 ARM Ltd. + * Copyright (C) 2021-2026 ARM Ltd. */ /** @@ -116,6 +116,8 @@ static struct scmi_transport_core_operations *core; /* Only one SCMI VirtIO device can possibly exist */ static struct virtio_device *scmi_vdev; +static DEFINE_SCMI_TRANSPORT_SUPPLIER(scmi_virtio_supplier); + static void scmi_vio_channel_ready(struct scmi_vio_channel *vioch, struct scmi_chan_info *cinfo) { @@ -394,6 +396,10 @@ static bool virtio_chan_available(struct device_node *of_node, int idx) return false; } + dev_dbg(&scmi_vdev->dev, "%s Channel %sAVAILABLE on SCMI Virtio device.\n", + idx == VIRTIO_SCMI_VQ_TX ? "TX" : "RX", + (vioch && !vioch->cinfo) ? "" : "NOT "); + return vioch && !vioch->cinfo; } @@ -410,7 +416,7 @@ static int virtio_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, int i; if (!scmi_vdev) - return -EPROBE_DEFER; + return -EINVAL; vioch = &((struct scmi_vio_channel *)scmi_vdev->priv)[index]; @@ -460,6 +466,9 @@ static int virtio_chan_setup(struct scmi_chan_info *cinfo, struct device *dev, scmi_vio_channel_ready(vioch, cinfo); + dev_dbg(&scmi_vdev->dev, "%s Channel SETUP on SCMI Virtio device.\n", + tx ? "TX" : "RX"); + return 0; } @@ -801,7 +810,7 @@ static struct scmi_desc scmi_virtio_desc = { }; static const struct of_device_id scmi_of_match[] = { - { .compatible = "arm,scmi-virtio" }, + { .compatible = "arm,scmi-virtio", .data = &scmi_virtio_supplier.th}, { /* Sentinel */ }, }; @@ -864,33 +873,33 @@ static int scmi_vio_probe(struct virtio_device *vdev) sz = MSG_TOKEN_MAX; } channels[i].max_msg = sz; + dev_dbg(dev, "VQ%d initialized with max_msg: %d\n", i, sz); } vdev->priv = channels; - /* Ensure initialized scmi_vdev is visible */ smp_store_mb(scmi_vdev, vdev); /* Set device ready */ virtio_device_ready(vdev); - ret = platform_driver_register(&scmi_virtio_driver); + ret = scmi_transport_supplier_put(&scmi_virtio_supplier.th, &vdev->dev); if (ret) { + virtio_reset_device(vdev); vdev->priv = NULL; vdev->config->del_vqs(vdev); /* Ensure NULLified scmi_vdev is visible */ smp_store_mb(scmi_vdev, NULL); - return ret; } + dev_dbg(dev, "Probed and initialized SCMI Virtio device.\n"); + return 0; } static void scmi_vio_remove(struct virtio_device *vdev) { - platform_driver_unregister(&scmi_virtio_driver); - /* * Once we get here, virtio_chan_free() will have already been called by * the SCMI core for any existing channel and, as a consequence, all the @@ -900,8 +909,10 @@ static void scmi_vio_remove(struct virtio_device *vdev) */ virtio_reset_device(vdev); vdev->config->del_vqs(vdev); + /* Ensure scmi_vdev is visible as NULL */ smp_store_mb(scmi_vdev, NULL); + scmi_transport_supplier_put(&scmi_virtio_supplier.th, &vdev->dev); } static int scmi_vio_validate(struct virtio_device *vdev) @@ -936,7 +947,30 @@ static struct virtio_driver virtio_scmi_driver = { .validate = scmi_vio_validate, }; -module_virtio_driver(virtio_scmi_driver); +static int __init scmi_transport_virtio_init(void) +{ + int ret; + + ret = register_virtio_driver(&virtio_scmi_driver); + if (ret) + return ret; + + ret = platform_driver_register(&scmi_virtio_driver); + if (ret) { + unregister_virtio_driver(&virtio_scmi_driver); + return ret; + } + + return ret; +} +module_init(scmi_transport_virtio_init); + +static void __exit scmi_transport_virtio_exit(void) +{ + platform_driver_unregister(&scmi_virtio_driver); + unregister_virtio_driver(&virtio_scmi_driver); +} +module_exit(scmi_transport_virtio_exit); MODULE_AUTHOR("Igor Skalkin "); MODULE_AUTHOR("Peter Hilber "); diff --git a/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c b/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c index 0ada753367ef..637973fb45e6 100644 --- a/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c +++ b/drivers/firmware/arm_scmi/vendors/imx/imx-sm-misc.c @@ -27,6 +27,7 @@ enum scmi_imx_misc_protocol_cmd { SCMI_IMX_MISC_CTRL_GET = 0x4, SCMI_IMX_MISC_DISCOVER_BUILD_INFO = 0x6, SCMI_IMX_MISC_CTRL_NOTIFY = 0x8, + SCMI_IMX_MISC_RESET_REASON_GET = 0xA, SCMI_IMX_MISC_CFG_INFO_GET = 0xC, SCMI_IMX_MISC_SYSLOG_GET = 0xD, SCMI_IMX_MISC_BOARD_INFO = 0xE, @@ -89,6 +90,37 @@ struct scmi_imx_misc_cfg_info_out { u8 cfgname[MISC_MAX_CFGNAME]; }; +struct scmi_imx_misc_reset_reason_in { +#define MISC_REASON_FLAG_SYSTEM BIT(0) + __le32 flags; +}; + +struct scmi_imx_misc_reset_reason_out { + /* Boot reason flags */ +#define MISC_BOOT_FLAG_VLD BIT(31) +#define MISC_BOOT_FLAG_ORG_VLD BIT(28) +#define MISC_BOOT_FLAG_ORIGIN GENMASK(27, 24) +#define MISC_BOOT_FLAG_O_SHIFT 24 +#define MISC_BOOT_FLAG_ERR_VLD BIT(23) +#define MISC_BOOT_FLAG_ERR_ID GENMASK(22, 8) +#define MISC_BOOT_FLAG_E_SHIFT 8 +#define MISC_BOOT_FLAG_REASON GENMASK(7, 0) + __le32 b_flags; + /* Shutdown reason flags */ +#define MISC_SHUTDOWN_FLAG_VLD BIT(31) +#define MISC_SHUTDOWN_FLAG_EXT_LEN GENMASK(30, 29) +#define MISC_SHUTDOWN_FLAG_ORG_VLD BIT(28) +#define MISC_SHUTDOWN_FLAG_ORIGIN GENMASK(27, 24) +#define MISC_SHUTDOWN_FLAG_O_SHIFT 24 +#define MISC_SHUTDOWN_FLAG_ERR_VLD BIT(23) +#define MISC_SHUTDOWN_FLAG_ERR_ID GENMASK(22, 8) +#define MISC_SHUTDOWN_FLAG_E_SHIFT 8 +#define MISC_SHUTDOWN_FLAG_REASON GENMASK(7, 0) + __le32 s_flags; + /* Array of extended info words */ + __le32 extinfo[MISC_EXT_INFO_LEN_MAX]; +}; + struct scmi_imx_misc_syslog_in { __le32 flags; __le32 index; @@ -452,11 +484,65 @@ static int scmi_imx_misc_syslog_get(const struct scmi_protocol_handle *ph, u16 * return ph->hops->iter_response_run(iter); } +static int scmi_imx_misc_reset_reason(const struct scmi_protocol_handle *ph, bool system, + struct scmi_imx_misc_reset_reason *boot_r, + struct scmi_imx_misc_reset_reason *shut_r, + u32 *extinfo) +{ + struct scmi_imx_misc_reset_reason_in *in; + struct scmi_imx_misc_reset_reason_out *out; + struct scmi_xfer *t; + int ret; + + ret = ph->xops->xfer_get_init(ph, SCMI_IMX_MISC_RESET_REASON_GET, sizeof(*in), + sizeof(*out), &t); + if (ret) + return ret; + + in = t->tx.buf; + if (system) + in->flags = le32_encode_bits(1, MISC_REASON_FLAG_SYSTEM); + else + in->flags = cpu_to_le32(0); + + ret = ph->xops->do_xfer(ph, t); + if (!ret) { + out = t->rx.buf; + if (boot_r) { + boot_r->valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_VLD); + boot_r->orig_valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ORG_VLD); + boot_r->err_valid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ERR_VLD); + boot_r->reason = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_REASON); + boot_r->origin = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ORIGIN); + boot_r->errid = le32_get_bits(out->b_flags, MISC_BOOT_FLAG_ERR_ID); + } + + if (shut_r) { + shut_r->valid = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_VLD); + shut_r->orig_valid = le32_get_bits(out->s_flags, + MISC_SHUTDOWN_FLAG_ORG_VLD); + shut_r->err_valid = le32_get_bits(out->s_flags, + MISC_SHUTDOWN_FLAG_ERR_VLD); + shut_r->reason = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_REASON); + shut_r->origin = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_ORIGIN); + shut_r->errid = le32_get_bits(out->s_flags, MISC_SHUTDOWN_FLAG_ERR_ID); + } + + if (extinfo) + memcpy_from_le32(extinfo, out->extinfo, MISC_EXT_INFO_LEN_MAX); + } + + ph->xops->xfer_put(ph, t); + + return ret; +} + static const struct scmi_imx_misc_proto_ops scmi_imx_misc_proto_ops = { .misc_ctrl_set = scmi_imx_misc_ctrl_set, .misc_ctrl_get = scmi_imx_misc_ctrl_get, .misc_ctrl_req_notify = scmi_imx_misc_ctrl_notify, .misc_syslog = scmi_imx_misc_syslog_get, + .misc_reset_reason = scmi_imx_misc_reset_reason, }; static int scmi_imx_misc_protocol_init(const struct scmi_protocol_handle *ph) diff --git a/drivers/firmware/imx/sm-misc.c b/drivers/firmware/imx/sm-misc.c index 0a8ada329c9d..16b5ff833d21 100644 --- a/drivers/firmware/imx/sm-misc.c +++ b/drivers/firmware/imx/sm-misc.c @@ -18,6 +18,29 @@ static const struct scmi_imx_misc_proto_ops *imx_misc_ctrl_ops; static struct scmi_protocol_handle *ph; struct notifier_block scmi_imx_misc_ctrl_nb; +static const char * const rst_imx95[] = { + "cm33_lockup", "cm33_swreq", "cm7_lockup", "cm7_swreq", "fccu", + "jtag_sw", "ele", "tempsense", "wdog1", "wdog2", "wdog3", "wdog4", + "wdog5", "jtag", "cm33_exc", "bbm", "sw", "sm_err", "fusa_sreco", + "pmic", "unused", "unused", "unused", "unused", "unused", "unused", + "unused", "unused", "unused", "unused", "unused", "por", +}; + +static const char * const rst_imx94[] = { + "cm33_lockup", "cm33_swreq", "cm70_lockup", "cm70_swreq", "fccu", + "jtag_sw", "ele", "tempsense", "wdog1", "wdog2", "wdog3", "wdog4", + "wdog5", "jtag", "wdog6", "wdog7", "wdog8", "wo_netc", "cm33s_lockup", + "cm33s_swreq", "cm71_lockup", "cm71_swreq", "cm33_exc", "bbm", "sw", + "sm_err", "fusa_sreco", "pmic", "unused", "unused", "unused", "por", +}; + +static const struct of_device_id allowlist[] = { + { .compatible = "fsl,imx952", .data = rst_imx95 }, + { .compatible = "fsl,imx95", .data = rst_imx95 }, + { .compatible = "fsl,imx94", .data = rst_imx94 }, + { /* Sentinel */ } +}; + int scmi_imx_misc_ctrl_set(u32 id, u32 val) { if (!ph) @@ -75,6 +98,54 @@ static void scmi_imx_misc_put(void *p) debugfs_remove((struct dentry *)p); } +static int scmi_imx_misc_get_reason(struct scmi_device *sdev) +{ + struct scmi_imx_misc_reset_reason boot, shutdown; + const char **rst; + bool system = true; + int ret; + + if (!of_machine_device_match(allowlist)) + return 0; + + rst = (const char **)of_machine_get_match_data(allowlist); + + ret = imx_misc_ctrl_ops->misc_reset_reason(ph, system, &boot, &shutdown, NULL); + if (!ret) { + if (boot.valid) + dev_info(&sdev->dev, "%s Boot reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[boot.reason], + boot.orig_valid ? boot.origin : -1, + boot.err_valid ? boot.errid : -1); + if (shutdown.valid) + dev_info(&sdev->dev, "%s shutdown reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[shutdown.reason], + shutdown.orig_valid ? shutdown.origin : -1, + shutdown.err_valid ? shutdown.errid : -1); + } else { + dev_err(&sdev->dev, "Failed to get system reset reason: %d\n", ret); + } + + system = false; + ret = imx_misc_ctrl_ops->misc_reset_reason(ph, system, &boot, &shutdown, NULL); + if (!ret) { + if (boot.valid) + dev_info(&sdev->dev, "%s Boot reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[boot.reason], + boot.orig_valid ? boot.origin : -1, + boot.err_valid ? boot.errid : -1); + if (shutdown.valid) + dev_info(&sdev->dev, "%s shutdown reason: %s, origin: %d, errid: %d\n", + system ? "SYS" : "LM", rst[shutdown.reason], + shutdown.orig_valid ? shutdown.origin : -1, + shutdown.err_valid ? shutdown.errid : -1); + } else { + dev_err(&sdev->dev, "Failed to get lm reset reason: %d\n", ret); + } + + return 0; +} + static int scmi_imx_misc_ctrl_probe(struct scmi_device *sdev) { const struct scmi_handle *handle = sdev->handle; @@ -133,6 +204,8 @@ static int scmi_imx_misc_ctrl_probe(struct scmi_device *sdev) scmi_imx_dentry = debugfs_create_dir("scmi_imx", NULL); debugfs_create_file("syslog", 0444, scmi_imx_dentry, &sdev->dev, &syslog_fops); + scmi_imx_misc_get_reason(sdev); + return devm_add_action_or_reset(&sdev->dev, scmi_imx_misc_put, scmi_imx_dentry); } diff --git a/include/linux/scmi_imx_protocol.h b/include/linux/scmi_imx_protocol.h index 2407d7693b6b..ab867463c08c 100644 --- a/include/linux/scmi_imx_protocol.h +++ b/include/linux/scmi_imx_protocol.h @@ -52,6 +52,17 @@ struct scmi_imx_misc_ctrl_notify_report { unsigned int flags; }; + +#define MISC_EXT_INFO_LEN_MAX 4 +struct scmi_imx_misc_reset_reason { + bool valid:1; + bool orig_valid:1; + bool err_valid:1; + u32 reason; + u32 origin; + u32 errid; +}; + struct scmi_imx_misc_proto_ops { int (*misc_ctrl_set)(const struct scmi_protocol_handle *ph, u32 id, u32 num, u32 *val); @@ -61,6 +72,9 @@ struct scmi_imx_misc_proto_ops { u32 ctrl_id, u32 evt_id, u32 flags); int (*misc_syslog)(const struct scmi_protocol_handle *ph, u16 *size, void *array); + int (*misc_reset_reason)(const struct scmi_protocol_handle *ph, + bool system, struct scmi_imx_misc_reset_reason *boot_r, + struct scmi_imx_misc_reset_reason *shut_r, u32 *extinfo); }; /* See LMM_ATTRIBUTES in imx95.rst */ diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index aafaac1496b0..5ab73b1ab9aa 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -15,10 +15,9 @@ #define SCMI_MAX_STR_SIZE 64 #define SCMI_SHORT_NAME_MAX_SIZE 16 -#define SCMI_MAX_NUM_RATES 16 /** - * struct scmi_revision_info - version information structure + * struct scmi_base_info - version information structure * * @major_ver: Major ABI version. Change here implies risk of backward * compatibility break. @@ -31,7 +30,7 @@ * @vendor_id: A vendor identifier(Null terminated ASCII string) * @sub_vendor_id: A sub-vendor identifier(Null terminated ASCII string) */ -struct scmi_revision_info { +struct scmi_base_info { u16 major_ver; u16 minor_ver; u8 num_protocols; @@ -41,27 +40,23 @@ struct scmi_revision_info { char sub_vendor_id[SCMI_SHORT_NAME_MAX_SIZE]; }; +struct scmi_clock_rates { + bool rate_discrete; + unsigned int num_rates; + u64 *rates; +}; + struct scmi_clock_info { char name[SCMI_MAX_STR_SIZE]; unsigned int enable_latency; - bool rate_discrete; bool rate_changed_notifications; bool rate_change_requested_notifications; bool state_ctrl_forbidden; bool rate_ctrl_forbidden; bool parent_ctrl_forbidden; bool extended_config; - union { - struct { - int num_rates; - u64 rates[SCMI_MAX_NUM_RATES]; - } list; - struct { - u64 min_rate; - u64 max_rate; - u64 step_size; - } range; - }; + u64 min_rate; + u64 max_rate; int num_parents; u32 *parents; }; @@ -91,6 +86,11 @@ enum scmi_clock_oem_config { * @info_get: get the information of the specified clock * @rate_get: request the current clock rate of a clock * @rate_set: set the clock rate of a clock + * @determine_rate: determine the effective rate that can be supported by a + * clock calculating the closest allowed rate. + * Note that @rate is an input/output parameter used both to + * describe the requested rate and report the closest match + * @all_rates_get: get the list of all available rates for the specified clock. * @enable: enables the specified clock * @disable: disables the specified clock * @state_get: get the status of the specified clock @@ -108,6 +108,10 @@ struct scmi_clk_proto_ops { u64 *rate); int (*rate_set)(const struct scmi_protocol_handle *ph, u32 clk_id, u64 rate); + int (*determine_rate)(const struct scmi_protocol_handle *ph, u32 clk_id, + unsigned long *rate); + const struct scmi_clock_rates __must_check *(*all_rates_get) + (const struct scmi_protocol_handle *ph, u32 clk_id); int (*enable)(const struct scmi_protocol_handle *ph, u32 clk_id, bool atomic); int (*disable)(const struct scmi_protocol_handle *ph, u32 clk_id, @@ -901,7 +905,7 @@ struct scmi_notify_ops { */ struct scmi_handle { struct device *dev; - struct scmi_revision_info *version; + struct scmi_base_info *version; int __must_check (*devm_protocol_acquire)(struct scmi_device *sdev, u8 proto);