Arm SCMI updates for v6.5

Couple of main additions :-
 
 1. Support for multiple SMC/HVC transports for SCMI:
 
    Some platforms need to support multiple SCMI instances within
    a platform(more commonly in a VM). The same SMC/HVC FID is used with
    all the instances. The platform or the hypervisor needs a way to
    distinguish among SMC/HVC calls made from different instances.
 
    This change adds support for passing shmem channel address as the
    parameters in the SMC/HVC call. The address is split into 4KB-page
    and offset for simiplicity.
 
 2. Addition od SCMI v3.2 explicit powercap enable/disable support:
 
    SCMI v3.2 specification introduces support to disable powercapping
    as a whole on the desired zones.
 
    This change adds the needed support to the core SCMI powercap protocol,
    exposing enable/disable protocol operations and then wiring up the new
    operartions in the related powercap framework helpers.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEunHlEgbzHrJD3ZPhAEG6vDF+4pgFAmSHCbQACgkQAEG6vDF+
 4phj9hAAjYYoJ9HvIqxJ68jRRh7BFnsdeCVqsGUzopeXbiWwr+uc0hko+oP8+hjz
 R/tHvqhKuUiKC+ROOVHSx0VdU5721v2Uhvn5gtp2ZzKN/Iw7Mm/6dMlGDF790MSw
 gkxJ5dog9H04HSGG1LvQIPKKY/BNUh7xvPkwoKlCO7DyNG+WQ5yyQT0v9DiZH8gg
 j5eKX29gDZJEuzr8TlkpYHATyDlzMNVD0/pX97DedhqEqzVnms/yAa1R6zu+M0sn
 8xyR2gy0UJ0w3unT8KxEbbxdJcykYxTbyVqJIc28xtVYA1EOGkFwZLYcVvy4z0LC
 xny3PJSeL8g/pbrilbYNbYcqz9Qjb9eNU9XdDFa+Oy09skwnYr2uYje6XPBk/KFu
 cQ5xs9ChJezcIRPBq/iV4Aqe5WK05IPZ0Q0HKsuTmdOOMaCsPui5nMkkybrm3KOU
 uZ6z90Br854FI/pQqAqpwYY1Wqa1g6I4DFUwOtd04qD1V19s0CEDrx8uPtoKcSaP
 0W+BeSv56Ukvn2rYPbu0teIzbk3s00kGqHcy1YKyq1rGmZSYrBf1bfmZkIJgfN/2
 0cyzJAch4j4ggvnJQJPybWYjbmKfxE+A9hk3hjZ1mrew2wWeIsjevclu4wtNUCjc
 qT3wa8SOtjaUGTQmvEWBKeYrkMcOhx+vxSo8SjemW/X3WjYswdw=
 =uVB0
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEiK/NIGsWEZVxh/FrYKtH/8kJUicFAmSUabgACgkQYKtH/8kJ
 UieE8A//Rvcl3hDpZgNa6pCvX7dH3m2JttmBgBmGbH4mVffjqhstYknp8Fb8Faxq
 91cGvAJ0QTn0Kaox7ycHQQVxLK5u7UfTmCe6pGGIDWZqXQDj6TETZoeWmiBgnTeS
 510Id9S/CsSnjErO1D575fogSx2AohgBzIIk4r0YxZ0HcQiGteS7fAFjd4JMoheF
 ZhUUmAu8Kp//grgeujvgQ59bCP4w6dqJ7KT/wnBZCy0CACpECA+LmYS1N/bnfPB2
 rzxMo1GgUfSNEKxNx6T8UC9+LfUkitHNwfPyoe3av9GAGhozaTbGD9xll0DTj2iz
 yFyhbgyY9aCxNMAwzq6hnveZi90X/bC1hnIidH5oyQkqTNTgE1X1y7SA6+SSfBzF
 UlFafLq2xw+ENmDYOV6Lz4JREPhC2gFwjYqjn8teLQBriaCBIdtaedbGDnUVN0Ht
 Iiv4baGtrIqbwosO2o/mfCLAveY/zh5aINv3rY1bkdfdFUQqS8gDlNGTuKDN+H+3
 v80eyP8XPU2pZ7NTzSxcnz5SHSZWagVcugnzDEdeX2ZBeSKY50PzFZEY7OSU5k9o
 iYjRyChHTTab/rG51GyhthVvszU9iKSQzmGco6OnaHd/vfCeXUfNKey98zVLbjUO
 V8xJOAIZ9NNU9SzIfupfe9vjfIPOTxEqbWV3M4pvkgyKjNEddr4=
 =Ze5+
 -----END PGP SIGNATURE-----

Merge tag 'scmi-updates-6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux into soc/drivers

Arm SCMI updates for v6.5

Couple of main additions :-

1. Support for multiple SMC/HVC transports for SCMI:

   Some platforms need to support multiple SCMI instances within
   a platform(more commonly in a VM). The same SMC/HVC FID is used with
   all the instances. The platform or the hypervisor needs a way to
   distinguish among SMC/HVC calls made from different instances.

   This change adds support for passing shmem channel address as the
   parameters in the SMC/HVC call. The address is split into 4KB-page
   and offset for simiplicity.

2. Addition od SCMI v3.2 explicit powercap enable/disable support:

   SCMI v3.2 specification introduces support to disable powercapping
   as a whole on the desired zones.

   This change adds the needed support to the core SCMI powercap protocol,
   exposing enable/disable protocol operations and then wiring up the new
   operartions in the related powercap framework helpers.

* tag 'scmi-updates-6.5' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux:
  powercap: arm_scmi: Add support for disabling powercaps on a zone
  firmware: arm_scmi: Add Powercap protocol enable support
  firmware: arm_scmi: Refactor the internal powercap get/set helpers
  firmware: arm_scmi: Augment SMC/HVC to allow optional parameters
  dt-bindings: firmware: arm,scmi: support for parameter in smc/hvc call

Link: https://lore.kernel.org/r/20230612121017.4108104-1-sudeep.holla@arm.com
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Arnd Bergmann 2023-06-22 17:33:11 +02:00
commit 2d0d3a1004
6 changed files with 219 additions and 27 deletions

View File

@ -34,6 +34,10 @@ properties:
- description: SCMI compliant firmware with ARM SMC/HVC transport
items:
- const: arm,scmi-smc
- description: SCMI compliant firmware with ARM SMC/HVC transport
with shmem address(4KB-page, offset) as parameters
items:
- const: arm,scmi-smc-param
- description: SCMI compliant firmware with SCMI Virtio transport.
The virtio transport only supports a single device.
items:
@ -299,7 +303,9 @@ else:
properties:
compatible:
contains:
const: arm,scmi-smc
enum:
- arm,scmi-smc
- arm,scmi-smc-param
then:
required:
- arm,smc-id

View File

@ -2914,6 +2914,7 @@ static const struct of_device_id scmi_of_match[] = {
#endif
#ifdef CONFIG_ARM_SCMI_TRANSPORT_SMC
{ .compatible = "arm,scmi-smc", .data = &scmi_smc_desc},
{ .compatible = "arm,scmi-smc-param", .data = &scmi_smc_desc},
#endif
#ifdef CONFIG_ARM_SCMI_TRANSPORT_VIRTIO
{ .compatible = "arm,scmi-virtio", .data = &scmi_virtio_desc},

View File

@ -108,6 +108,8 @@ struct scmi_powercap_meas_changed_notify_payld {
};
struct scmi_powercap_state {
bool enabled;
u32 last_pcap;
bool meas_notif_enabled;
u64 thresholds;
#define THRESH_LOW(p, id) \
@ -313,24 +315,33 @@ static int scmi_powercap_xfer_cap_get(const struct scmi_protocol_handle *ph,
return ret;
}
static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *power_cap)
static int __scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
const struct scmi_powercap_info *dom,
u32 *power_cap)
{
struct scmi_powercap_info *dom;
struct powercap_info *pi = ph->get_priv(ph);
if (!power_cap || domain_id >= pi->num_domains)
return -EINVAL;
dom = pi->powercaps + domain_id;
if (dom->fc_info && dom->fc_info[POWERCAP_FC_CAP].get_addr) {
*power_cap = ioread32(dom->fc_info[POWERCAP_FC_CAP].get_addr);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_GET,
domain_id, *power_cap, 0);
dom->id, *power_cap, 0);
return 0;
}
return scmi_powercap_xfer_cap_get(ph, domain_id, power_cap);
return scmi_powercap_xfer_cap_get(ph, dom->id, power_cap);
}
static int scmi_powercap_cap_get(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 *power_cap)
{
const struct scmi_powercap_info *dom;
if (!power_cap)
return -EINVAL;
dom = scmi_powercap_dom_info_get(ph, domain_id);
if (!dom)
return -EINVAL;
return __scmi_powercap_cap_get(ph, dom, power_cap);
}
static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
@ -375,17 +386,20 @@ static int scmi_powercap_xfer_cap_set(const struct scmi_protocol_handle *ph,
return ret;
}
static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 power_cap,
bool ignore_dresp)
static int __scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
struct powercap_info *pi, u32 domain_id,
u32 power_cap, bool ignore_dresp)
{
int ret = -EINVAL;
const struct scmi_powercap_info *pc;
pc = scmi_powercap_dom_info_get(ph, domain_id);
if (!pc || !pc->powercap_cap_config || !power_cap ||
power_cap < pc->min_power_cap ||
power_cap > pc->max_power_cap)
return -EINVAL;
if (!pc || !pc->powercap_cap_config)
return ret;
if (power_cap &&
(power_cap < pc->min_power_cap || power_cap > pc->max_power_cap))
return ret;
if (pc->fc_info && pc->fc_info[POWERCAP_FC_CAP].set_addr) {
struct scmi_fc_info *fci = &pc->fc_info[POWERCAP_FC_CAP];
@ -394,10 +408,41 @@ static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
ph->hops->fastchannel_db_ring(fci->set_db);
trace_scmi_fc_call(SCMI_PROTOCOL_POWERCAP, POWERCAP_CAP_SET,
domain_id, power_cap, 0);
ret = 0;
} else {
ret = scmi_powercap_xfer_cap_set(ph, pc, power_cap,
ignore_dresp);
}
/* Save the last explicitly set non-zero powercap value */
if (PROTOCOL_REV_MAJOR(pi->version) >= 0x2 && !ret && power_cap)
pi->states[domain_id].last_pcap = power_cap;
return ret;
}
static int scmi_powercap_cap_set(const struct scmi_protocol_handle *ph,
u32 domain_id, u32 power_cap,
bool ignore_dresp)
{
struct powercap_info *pi = ph->get_priv(ph);
/*
* Disallow zero as a possible explicitly requested powercap:
* there are enable/disable operations for this.
*/
if (!power_cap)
return -EINVAL;
/* Just log the last set request if acting on a disabled domain */
if (PROTOCOL_REV_MAJOR(pi->version) >= 0x2 &&
!pi->states[domain_id].enabled) {
pi->states[domain_id].last_pcap = power_cap;
return 0;
}
return scmi_powercap_xfer_cap_set(ph, pc, power_cap, ignore_dresp);
return __scmi_powercap_cap_set(ph, pi, domain_id,
power_cap, ignore_dresp);
}
static int scmi_powercap_xfer_pai_get(const struct scmi_protocol_handle *ph,
@ -564,11 +609,78 @@ scmi_powercap_measurements_threshold_set(const struct scmi_protocol_handle *ph,
return ret;
}
static int scmi_powercap_cap_enable_set(const struct scmi_protocol_handle *ph,
u32 domain_id, bool enable)
{
int ret;
u32 power_cap;
struct powercap_info *pi = ph->get_priv(ph);
if (PROTOCOL_REV_MAJOR(pi->version) < 0x2)
return -EINVAL;
if (enable == pi->states[domain_id].enabled)
return 0;
if (enable) {
/* Cannot enable with a zero powercap. */
if (!pi->states[domain_id].last_pcap)
return -EINVAL;
ret = __scmi_powercap_cap_set(ph, pi, domain_id,
pi->states[domain_id].last_pcap,
true);
} else {
ret = __scmi_powercap_cap_set(ph, pi, domain_id, 0, true);
}
if (ret)
return ret;
/*
* Update our internal state to reflect final platform state: the SCMI
* server could have ignored a disable request and kept enforcing some
* powercap limit requested by other agents.
*/
ret = scmi_powercap_cap_get(ph, domain_id, &power_cap);
if (!ret)
pi->states[domain_id].enabled = !!power_cap;
return ret;
}
static int scmi_powercap_cap_enable_get(const struct scmi_protocol_handle *ph,
u32 domain_id, bool *enable)
{
int ret;
u32 power_cap;
struct powercap_info *pi = ph->get_priv(ph);
*enable = true;
if (PROTOCOL_REV_MAJOR(pi->version) < 0x2)
return 0;
/*
* Report always real platform state; platform could have ignored
* a previous disable request. Default true on any error.
*/
ret = scmi_powercap_cap_get(ph, domain_id, &power_cap);
if (!ret)
*enable = !!power_cap;
/* Update internal state with current real platform state */
pi->states[domain_id].enabled = *enable;
return 0;
}
static const struct scmi_powercap_proto_ops powercap_proto_ops = {
.num_domains_get = scmi_powercap_num_domains_get,
.info_get = scmi_powercap_dom_info_get,
.cap_get = scmi_powercap_cap_get,
.cap_set = scmi_powercap_cap_set,
.cap_enable_set = scmi_powercap_cap_enable_set,
.cap_enable_get = scmi_powercap_cap_enable_get,
.pai_get = scmi_powercap_pai_get,
.pai_set = scmi_powercap_pai_set,
.measurements_get = scmi_powercap_measurements_get,
@ -829,6 +941,11 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
if (!pinfo->powercaps)
return -ENOMEM;
pinfo->states = devm_kcalloc(ph->dev, pinfo->num_domains,
sizeof(*pinfo->states), GFP_KERNEL);
if (!pinfo->states)
return -ENOMEM;
/*
* Note that any failure in retrieving any domain attribute leads to
* the whole Powercap protocol initialization failure: this way the
@ -843,15 +960,21 @@ scmi_powercap_protocol_init(const struct scmi_protocol_handle *ph)
if (pinfo->powercaps[domain].fastchannels)
scmi_powercap_domain_init_fc(ph, domain,
&pinfo->powercaps[domain].fc_info);
/* Grab initial state when disable is supported. */
if (PROTOCOL_REV_MAJOR(version) >= 0x2) {
ret = __scmi_powercap_cap_get(ph,
&pinfo->powercaps[domain],
&pinfo->states[domain].last_pcap);
if (ret)
return ret;
pinfo->states[domain].enabled =
!!pinfo->states[domain].last_pcap;
}
}
pinfo->states = devm_kcalloc(ph->dev, pinfo->num_domains,
sizeof(*pinfo->states), GFP_KERNEL);
if (!pinfo->states)
return -ENOMEM;
pinfo->version = version;
return ph->set_priv(ph, pinfo);
}

View File

@ -20,6 +20,23 @@
#include "common.h"
/*
* The shmem address is split into 4K page and offset.
* This is to make sure the parameters fit in 32bit arguments of the
* smc/hvc call to keep it uniform across smc32/smc64 conventions.
* This however limits the shmem address to 44 bit.
*
* These optional parameters can be used to distinguish among multiple
* scmi instances that are using the same smc-id.
* The page parameter is passed in r1/x1/w1 register and the offset parameter
* is passed in r2/x2/w2 register.
*/
#define SHMEM_SIZE (SZ_4K)
#define SHMEM_SHIFT 12
#define SHMEM_PAGE(x) (_UL((x) >> SHMEM_SHIFT))
#define SHMEM_OFFSET(x) ((x) & (SHMEM_SIZE - 1))
/**
* struct scmi_smc - Structure representing a SCMI smc transport
*
@ -30,6 +47,8 @@
* @inflight: Atomic flag to protect access to Tx/Rx shared memory area.
* Used when operating in atomic mode.
* @func_id: smc/hvc call function id
* @param_page: 4K page number of the shmem channel
* @param_offset: Offset within the 4K page of the shmem channel
*/
struct scmi_smc {
@ -40,6 +59,8 @@ struct scmi_smc {
#define INFLIGHT_NONE MSG_TOKEN_MAX
atomic_t inflight;
u32 func_id;
u32 param_page;
u32 param_offset;
};
static irqreturn_t smc_msg_done_isr(int irq, void *data)
@ -137,6 +158,10 @@ static int smc_chan_setup(struct scmi_chan_info *cinfo, struct device *dev,
if (ret < 0)
return ret;
if (of_device_is_compatible(dev->of_node, "arm,scmi-smc-param")) {
scmi_info->param_page = SHMEM_PAGE(res.start);
scmi_info->param_offset = SHMEM_OFFSET(res.start);
}
/*
* If there is an interrupt named "a2p", then the service and
* completion of a message is signaled by an interrupt rather than by
@ -179,6 +204,8 @@ static int smc_send_message(struct scmi_chan_info *cinfo,
{
struct scmi_smc *scmi_info = cinfo->transport_info;
struct arm_smccc_res res;
unsigned long page = scmi_info->param_page;
unsigned long offset = scmi_info->param_offset;
/*
* Channel will be released only once response has been
@ -188,7 +215,8 @@ static int smc_send_message(struct scmi_chan_info *cinfo,
shmem_tx_prepare(scmi_info->shmem, xfer, cinfo);
arm_smccc_1_1_invoke(scmi_info->func_id, 0, 0, 0, 0, 0, 0, 0, &res);
arm_smccc_1_1_invoke(scmi_info->func_id, page, offset, 0, 0, 0, 0, 0,
&res);
/* Only SMCCC_RET_NOT_SUPPORTED is valid error code */
if (res.a0) {

View File

@ -70,10 +70,26 @@ static int scmi_powercap_get_power_uw(struct powercap_zone *pz,
return 0;
}
static int scmi_powercap_zone_enable_set(struct powercap_zone *pz, bool mode)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
return powercap_ops->cap_enable_set(spz->ph, spz->info->id, mode);
}
static int scmi_powercap_zone_enable_get(struct powercap_zone *pz, bool *mode)
{
struct scmi_powercap_zone *spz = to_scmi_powercap_zone(pz);
return powercap_ops->cap_enable_get(spz->ph, spz->info->id, mode);
}
static const struct powercap_zone_ops zone_ops = {
.get_max_power_range_uw = scmi_powercap_get_max_power_range_uw,
.get_power_uw = scmi_powercap_get_power_uw,
.release = scmi_powercap_zone_release,
.set_enable = scmi_powercap_zone_enable_set,
.get_enable = scmi_powercap_zone_enable_get,
};
static void scmi_powercap_normalize_cap(const struct scmi_powercap_zone *spz,

View File

@ -629,11 +629,25 @@ struct scmi_powercap_info {
* @num_domains_get: get the count of powercap domains provided by SCMI.
* @info_get: get the information for the specified domain.
* @cap_get: get the current CAP value for the specified domain.
* On SCMI platforms supporting powercap zone disabling, this could
* report a zero value for a zone where powercapping is disabled.
* @cap_set: set the CAP value for the specified domain to the provided value;
* if the domain supports setting the CAP with an asynchronous command
* this request will finally trigger an asynchronous transfer, but, if
* @ignore_dresp here is set to true, this call will anyway return
* immediately without waiting for the related delayed response.
* Note that the powercap requested value must NOT be zero, even if
* the platform supports disabling a powercap by setting its cap to
* zero (since SCMI v3.2): there are dedicated operations that should
* be used for that. (@cap_enable_set/get)
* @cap_enable_set: enable or disable the powercapping on the specified domain,
* if supported by the SCMI platform implementation.
* Note that, by the SCMI specification, the platform can
* silently ignore our disable request and decide to enforce
* anyway some other powercap value requested by another agent
* on the system: for this reason @cap_get and @cap_enable_get
* will always report the final platform view of the powercaps.
* @cap_enable_get: get the current CAP enable status for the specified domain.
* @pai_get: get the current PAI value for the specified domain.
* @pai_set: set the PAI value for the specified domain to the provided value.
* @measurements_get: retrieve the current average power measurements for the
@ -662,6 +676,10 @@ struct scmi_powercap_proto_ops {
u32 *power_cap);
int (*cap_set)(const struct scmi_protocol_handle *ph, u32 domain_id,
u32 power_cap, bool ignore_dresp);
int (*cap_enable_set)(const struct scmi_protocol_handle *ph,
u32 domain_id, bool enable);
int (*cap_enable_get)(const struct scmi_protocol_handle *ph,
u32 domain_id, bool *enable);
int (*pai_get)(const struct scmi_protocol_handle *ph, u32 domain_id,
u32 *pai);
int (*pai_set)(const struct scmi_protocol_handle *ph, u32 domain_id,