From 00126945902d2335c3b1f1c12db4ae2afeb4da0f Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Fri, 29 Sep 2017 23:43:54 +0200 Subject: firmware: arm_scpi: remove usage of drvdata and don't reset scpi_info to null There's no benefit using drvdata as variable scpi_info is global. Setting scpi_info to NULL in scpi_remove isn't needed too. If arm_scpi is built-in, then this code is never used. And if arm_scpi is built as a module and some other module calls get_scpi_ops() then due to this dependency scpi_remove is called only after the other module has been removed. Last but not least, users usually store the result of get_scpi_ops(), therefore setting scpi_info to NULL wouldn't really help. Signed-off-by: Heiner Kallweit [sudeep.holla: reworded the commit log a bit] Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index 7da9f1b83ebe..a7f686e9dc1c 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -862,8 +862,6 @@ static int scpi_init_versions(struct scpi_drvinfo *info) static ssize_t protocol_version_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev); - return sprintf(buf, "%d.%d\n", PROTOCOL_REV_MAJOR(scpi_info->protocol_version), PROTOCOL_REV_MINOR(scpi_info->protocol_version)); @@ -873,8 +871,6 @@ static DEVICE_ATTR_RO(protocol_version); static ssize_t firmware_version_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev); - return sprintf(buf, "%d.%d.%d\n", FW_REV_MAJOR(scpi_info->firmware_version), FW_REV_MINOR(scpi_info->firmware_version), @@ -905,21 +901,17 @@ static int scpi_remove(struct platform_device *pdev) { int i; struct device *dev = &pdev->dev; - struct scpi_drvinfo *info = platform_get_drvdata(pdev); - - scpi_info = NULL; /* stop exporting SCPI ops through get_scpi_ops */ of_platform_depopulate(dev); sysfs_remove_groups(&dev->kobj, versions_groups); - scpi_free_channels(dev, info->channels, info->num_chans); - platform_set_drvdata(pdev, NULL); + scpi_free_channels(dev, scpi_info->channels, scpi_info->num_chans); - for (i = 0; i < MAX_DVFS_DOMAINS && info->dvfs[i]; i++) { - kfree(info->dvfs[i]->opps); - kfree(info->dvfs[i]); + for (i = 0; i < MAX_DVFS_DOMAINS && scpi_info->dvfs[i]; i++) { + kfree(scpi_info->dvfs[i]->opps); + kfree(scpi_info->dvfs[i]); } - devm_kfree(dev, info->channels); - devm_kfree(dev, info); + devm_kfree(dev, scpi_info->channels); + devm_kfree(dev, scpi_info); return 0; } @@ -1027,8 +1019,6 @@ err: scpi_info->num_chans = count; scpi_info->commands = scpi_std_commands; - platform_set_drvdata(pdev, scpi_info); - if (scpi_info->is_legacy) { /* Replace with legacy variants */ scpi_ops.clk_set_val = legacy_scpi_clk_set_val; -- cgit v1.2.3-55-g7522 From cfe8a9c9aaf47af3d9e6fe4c4b03f4a88844746d Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Fri, 29 Sep 2017 23:44:01 +0200 Subject: firmware: arm_scpi: remove two unneeded devm_kfree's in scpi_remove Both memory areas are free'd anyway when the device is destroyed, so we don't have to do it manually. Signed-off-by: Heiner Kallweit Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index a7f686e9dc1c..b99af7af64ff 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -910,8 +910,6 @@ static int scpi_remove(struct platform_device *pdev) kfree(scpi_info->dvfs[i]->opps); kfree(scpi_info->dvfs[i]); } - devm_kfree(dev, scpi_info->channels); - devm_kfree(dev, scpi_info); return 0; } -- cgit v1.2.3-55-g7522 From 931cf0c53e6999375eac65f3650a656b88634b9d Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Fri, 29 Sep 2017 23:44:05 +0200 Subject: firmware: arm_scpi: pre-populate dvfs info in scpi_probe Pre-populating the dvfs info data in scpi_probe allows to make all memory allocations device-managed. This helps to simplify scpi_remove and eventually to get rid of scpi_remove completely. Signed-off-by: Heiner Kallweit [sudeep.holla: changed to continue probe even if scpi_dvfs_populate_info fails] Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 48 ++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 22 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index b99af7af64ff..97a22a73c2d2 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -644,35 +644,35 @@ static int opp_cmp_func(const void *opp1, const void *opp2) } static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) +{ + if (domain >= MAX_DVFS_DOMAINS) + return ERR_PTR(-EINVAL); + + return scpi_info->dvfs[domain] ?: ERR_PTR(-EINVAL); +} + +static int scpi_dvfs_populate_info(struct device *dev, u8 domain) { struct scpi_dvfs_info *info; struct scpi_opp *opp; struct dvfs_info buf; int ret, i; - if (domain >= MAX_DVFS_DOMAINS) - return ERR_PTR(-EINVAL); - - if (scpi_info->dvfs[domain]) /* data already populated */ - return scpi_info->dvfs[domain]; - ret = scpi_send_message(CMD_GET_DVFS_INFO, &domain, sizeof(domain), &buf, sizeof(buf)); if (ret) - return ERR_PTR(ret); + return ret; - info = kmalloc(sizeof(*info), GFP_KERNEL); + info = devm_kmalloc(dev, sizeof(*info), GFP_KERNEL); if (!info) - return ERR_PTR(-ENOMEM); + return -ENOMEM; info->count = DVFS_OPP_COUNT(buf.header); info->latency = DVFS_LATENCY(buf.header) * 1000; /* uS to nS */ - info->opps = kcalloc(info->count, sizeof(*opp), GFP_KERNEL); - if (!info->opps) { - kfree(info); - return ERR_PTR(-ENOMEM); - } + info->opps = devm_kcalloc(dev, info->count, sizeof(*opp), GFP_KERNEL); + if (!info->opps) + return -ENOMEM; for (i = 0, opp = info->opps; i < info->count; i++, opp++) { opp->freq = le32_to_cpu(buf.opps[i].freq); @@ -682,7 +682,15 @@ static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) sort(info->opps, info->count, sizeof(*opp), opp_cmp_func, NULL); scpi_info->dvfs[domain] = info; - return info; + return 0; +} + +static void scpi_dvfs_populate(struct device *dev) +{ + int domain; + + for (domain = 0; domain < MAX_DVFS_DOMAINS; domain++) + scpi_dvfs_populate_info(dev, domain); } static int scpi_dev_domain_id(struct device *dev) @@ -899,18 +907,12 @@ scpi_free_channels(struct device *dev, struct scpi_chan *pchan, int count) static int scpi_remove(struct platform_device *pdev) { - int i; struct device *dev = &pdev->dev; of_platform_depopulate(dev); sysfs_remove_groups(&dev->kobj, versions_groups); scpi_free_channels(dev, scpi_info->channels, scpi_info->num_chans); - for (i = 0; i < MAX_DVFS_DOMAINS && scpi_info->dvfs[i]; i++) { - kfree(scpi_info->dvfs[i]->opps); - kfree(scpi_info->dvfs[i]); - } - return 0; } @@ -1016,6 +1018,7 @@ err: scpi_info->channels = scpi_chan; scpi_info->num_chans = count; scpi_info->commands = scpi_std_commands; + scpi_info->scpi_ops = &scpi_ops; if (scpi_info->is_legacy) { /* Replace with legacy variants */ @@ -1035,13 +1038,14 @@ err: return ret; } + scpi_dvfs_populate(dev); + _dev_info(dev, "SCP Protocol %d.%d Firmware %d.%d.%d version\n", PROTOCOL_REV_MAJOR(scpi_info->protocol_version), PROTOCOL_REV_MINOR(scpi_info->protocol_version), FW_REV_MAJOR(scpi_info->firmware_version), FW_REV_MINOR(scpi_info->firmware_version), FW_REV_PATCH(scpi_info->firmware_version)); - scpi_info->scpi_ops = &scpi_ops; ret = sysfs_create_groups(&dev->kobj, versions_groups); if (ret) -- cgit v1.2.3-55-g7522 From 27c54cd3142184b7798e082d06c17135cafb3f60 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Fri, 29 Sep 2017 23:44:09 +0200 Subject: firmware: arm_scpi: make freeing mbox channels device-managed Make freeing the mbox channels device-managed, thus further simplifying scpi_remove and and one further step to get rid of scpi_remove. Signed-off-by: Heiner Kallweit Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index 97a22a73c2d2..ee19b365c289 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -893,16 +893,13 @@ static struct attribute *versions_attrs[] = { }; ATTRIBUTE_GROUPS(versions); -static void -scpi_free_channels(struct device *dev, struct scpi_chan *pchan, int count) +static void scpi_free_channels(void *data) { + struct scpi_drvinfo *info = data; int i; - for (i = 0; i < count && pchan->chan; i++, pchan++) { - mbox_free_channel(pchan->chan); - devm_kfree(dev, pchan->xfers); - devm_iounmap(dev, pchan->rx_payload); - } + for (i = 0; i < info->num_chans; i++) + mbox_free_channel(info->channels[i].chan); } static int scpi_remove(struct platform_device *pdev) @@ -911,7 +908,6 @@ static int scpi_remove(struct platform_device *pdev) of_platform_depopulate(dev); sysfs_remove_groups(&dev->kobj, versions_groups); - scpi_free_channels(dev, scpi_info->channels, scpi_info->num_chans); return 0; } @@ -944,7 +940,6 @@ static int scpi_probe(struct platform_device *pdev) { int count, idx, ret; struct resource res; - struct scpi_chan *scpi_chan; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; @@ -961,13 +956,19 @@ static int scpi_probe(struct platform_device *pdev) return -ENODEV; } - scpi_chan = devm_kcalloc(dev, count, sizeof(*scpi_chan), GFP_KERNEL); - if (!scpi_chan) + scpi_info->channels = devm_kcalloc(dev, count, sizeof(struct scpi_chan), + GFP_KERNEL); + if (!scpi_info->channels) return -ENOMEM; - for (idx = 0; idx < count; idx++) { + ret = devm_add_action(dev, scpi_free_channels, scpi_info); + if (ret) + return ret; + + for (; scpi_info->num_chans < count; scpi_info->num_chans++) { resource_size_t size; - struct scpi_chan *pchan = scpi_chan + idx; + int idx = scpi_info->num_chans; + struct scpi_chan *pchan = scpi_info->channels + idx; struct mbox_client *cl = &pchan->cl; struct device_node *shmem = of_parse_phandle(np, "shmem", idx); @@ -975,15 +976,14 @@ static int scpi_probe(struct platform_device *pdev) of_node_put(shmem); if (ret) { dev_err(dev, "failed to get SCPI payload mem resource\n"); - goto err; + return ret; } size = resource_size(&res); pchan->rx_payload = devm_ioremap(dev, res.start, size); if (!pchan->rx_payload) { dev_err(dev, "failed to ioremap SCPI payload\n"); - ret = -EADDRNOTAVAIL; - goto err; + return -EADDRNOTAVAIL; } pchan->tx_payload = pchan->rx_payload + (size >> 1); @@ -1009,14 +1009,9 @@ static int scpi_probe(struct platform_device *pdev) dev_err(dev, "failed to get channel%d err %d\n", idx, ret); } -err: - scpi_free_channels(dev, scpi_chan, idx); - scpi_info = NULL; return ret; } - scpi_info->channels = scpi_chan; - scpi_info->num_chans = count; scpi_info->commands = scpi_std_commands; scpi_info->scpi_ops = &scpi_ops; -- cgit v1.2.3-55-g7522 From 95998c7a6cf76a24a553bec7f6fafdef6b4d3374 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Fri, 29 Sep 2017 23:44:15 +0200 Subject: firmware: arm_scpi: remove scpi_remove sysfs_create_groups and of_platform_populate can be replaced with the device-managed versions what allows us to remove scpi_remove. Signed-off-by: Heiner Kallweit Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index ee19b365c289..f713a64c1052 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -902,16 +902,6 @@ static void scpi_free_channels(void *data) mbox_free_channel(info->channels[i].chan); } -static int scpi_remove(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - - of_platform_depopulate(dev); - sysfs_remove_groups(&dev->kobj, versions_groups); - - return 0; -} - #define MAX_SCPI_XFERS 10 static int scpi_alloc_xfer_list(struct device *dev, struct scpi_chan *ch) { @@ -1029,7 +1019,6 @@ static int scpi_probe(struct platform_device *pdev) ret = scpi_init_versions(scpi_info); if (ret) { dev_err(dev, "incorrect or no SCP firmware found\n"); - scpi_remove(pdev); return ret; } @@ -1042,11 +1031,11 @@ static int scpi_probe(struct platform_device *pdev) FW_REV_MINOR(scpi_info->firmware_version), FW_REV_PATCH(scpi_info->firmware_version)); - ret = sysfs_create_groups(&dev->kobj, versions_groups); + ret = devm_device_add_groups(dev, versions_groups); if (ret) dev_err(dev, "unable to create sysfs version group\n"); - return of_platform_populate(dev->of_node, NULL, NULL, dev); + return devm_of_platform_populate(dev); } static const struct of_device_id scpi_of_match[] = { @@ -1063,7 +1052,6 @@ static struct platform_driver scpi_driver = { .of_match_table = scpi_of_match, }, .probe = scpi_probe, - .remove = scpi_remove, }; module_platform_driver(scpi_driver); -- cgit v1.2.3-55-g7522 From d9e324ea138b43c2cfb6b01e3ae02a84be55aef4 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 4 Oct 2017 20:56:44 +0200 Subject: firmware: arm_scpi: improve struct dvfs_info to make code better readable Making the header subfields members of struct dvfs_info allows to make the code better readable and avoids some macro magic. In addition remove a useless statement using info->latency. Signed-off-by: Heiner Kallweit Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index f713a64c1052..412f1c4cf616 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -72,8 +72,6 @@ #define MAX_DVFS_DOMAINS 8 #define MAX_DVFS_OPPS 16 -#define DVFS_LATENCY(hdr) (le32_to_cpu(hdr) >> 16) -#define DVFS_OPP_COUNT(hdr) ((le32_to_cpu(hdr) >> 8) & 0xff) #define PROTOCOL_REV_MINOR_BITS 16 #define PROTOCOL_REV_MINOR_MASK ((1U << PROTOCOL_REV_MINOR_BITS) - 1) @@ -328,7 +326,9 @@ struct legacy_clk_set_value { } __packed; struct dvfs_info { - __le32 header; + u8 domain; + u8 opp_count; + __le16 latency; struct { __le32 freq; __le32 m_volt; @@ -667,8 +667,8 @@ static int scpi_dvfs_populate_info(struct device *dev, u8 domain) if (!info) return -ENOMEM; - info->count = DVFS_OPP_COUNT(buf.header); - info->latency = DVFS_LATENCY(buf.header) * 1000; /* uS to nS */ + info->count = buf.opp_count; + info->latency = le16_to_cpu(buf.latency) * 1000; /* uS to nS */ info->opps = devm_kcalloc(dev, info->count, sizeof(*opp), GFP_KERNEL); if (!info->opps) @@ -721,9 +721,6 @@ static int scpi_dvfs_get_transition_latency(struct device *dev) if (IS_ERR(info)) return PTR_ERR(info); - if (!info->latency) - return 0; - return info->latency; } -- cgit v1.2.3-55-g7522 From 4864dca72d3fb2586ffee0c92bd13d02cedfee2f Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 4 Oct 2017 21:00:39 +0200 Subject: firmware: arm_scpi: improve handling of protocol and firmware version subfields By using FIELD_GET and proper masks we can avoid quite some shifting and masking macro magic and make the code better readable. Signed-off-by: Heiner Kallweit Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index 412f1c4cf616..d305b926c6cd 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -28,6 +28,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -73,18 +74,12 @@ #define MAX_DVFS_DOMAINS 8 #define MAX_DVFS_OPPS 16 -#define PROTOCOL_REV_MINOR_BITS 16 -#define PROTOCOL_REV_MINOR_MASK ((1U << PROTOCOL_REV_MINOR_BITS) - 1) -#define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS) -#define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK) +#define PROTO_REV_MAJOR_MASK GENMASK(31, 16) +#define PROTO_REV_MINOR_MASK GENMASK(15, 0) -#define FW_REV_MAJOR_BITS 24 -#define FW_REV_MINOR_BITS 16 -#define FW_REV_PATCH_MASK ((1U << FW_REV_MINOR_BITS) - 1) -#define FW_REV_MINOR_MASK ((1U << FW_REV_MAJOR_BITS) - 1) -#define FW_REV_MAJOR(x) ((x) >> FW_REV_MAJOR_BITS) -#define FW_REV_MINOR(x) (((x) & FW_REV_MINOR_MASK) >> FW_REV_MINOR_BITS) -#define FW_REV_PATCH(x) ((x) & FW_REV_PATCH_MASK) +#define FW_REV_MAJOR_MASK GENMASK(31, 24) +#define FW_REV_MINOR_MASK GENMASK(23, 16) +#define FW_REV_PATCH_MASK GENMASK(15, 0) #define MAX_RX_TIMEOUT (msecs_to_jiffies(30)) @@ -867,19 +862,19 @@ static int scpi_init_versions(struct scpi_drvinfo *info) static ssize_t protocol_version_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%d.%d\n", - PROTOCOL_REV_MAJOR(scpi_info->protocol_version), - PROTOCOL_REV_MINOR(scpi_info->protocol_version)); + return sprintf(buf, "%lu.%lu\n", + FIELD_GET(PROTO_REV_MAJOR_MASK, scpi_info->protocol_version), + FIELD_GET(PROTO_REV_MINOR_MASK, scpi_info->protocol_version)); } static DEVICE_ATTR_RO(protocol_version); static ssize_t firmware_version_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%d.%d.%d\n", - FW_REV_MAJOR(scpi_info->firmware_version), - FW_REV_MINOR(scpi_info->firmware_version), - FW_REV_PATCH(scpi_info->firmware_version)); + return sprintf(buf, "%lu.%lu.%lu\n", + FIELD_GET(FW_REV_MAJOR_MASK, scpi_info->firmware_version), + FIELD_GET(FW_REV_MINOR_MASK, scpi_info->firmware_version), + FIELD_GET(FW_REV_PATCH_MASK, scpi_info->firmware_version)); } static DEVICE_ATTR_RO(firmware_version); @@ -1021,12 +1016,12 @@ static int scpi_probe(struct platform_device *pdev) scpi_dvfs_populate(dev); - _dev_info(dev, "SCP Protocol %d.%d Firmware %d.%d.%d version\n", - PROTOCOL_REV_MAJOR(scpi_info->protocol_version), - PROTOCOL_REV_MINOR(scpi_info->protocol_version), - FW_REV_MAJOR(scpi_info->firmware_version), - FW_REV_MINOR(scpi_info->firmware_version), - FW_REV_PATCH(scpi_info->firmware_version)); + _dev_info(dev, "SCP Protocol %lu.%lu Firmware %lu.%lu.%lu version\n", + FIELD_GET(PROTO_REV_MAJOR_MASK, scpi_info->protocol_version), + FIELD_GET(PROTO_REV_MINOR_MASK, scpi_info->protocol_version), + FIELD_GET(FW_REV_MAJOR_MASK, scpi_info->firmware_version), + FIELD_GET(FW_REV_MINOR_MASK, scpi_info->firmware_version), + FIELD_GET(FW_REV_PATCH_MASK, scpi_info->firmware_version)); ret = devm_device_add_groups(dev, versions_groups); if (ret) -- cgit v1.2.3-55-g7522 From 5c7ae6467df557f25daf42e78202531bc44c83df Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 4 Oct 2017 21:05:17 +0200 Subject: firmware: arm_scpi: improve struct sensor_value lo_val and hi_val together in this order are a little endian 64 bit value. Therefore we can simplify struct sensor_value and the code by defining it as a __le64 value and by using le64_to_cpu. Signed-off-by: Heiner Kallweit Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index d305b926c6cd..c923d1ebfa3e 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -347,9 +347,8 @@ struct _scpi_sensor_info { }; struct sensor_value { - __le32 lo_val; - __le32 hi_val; -} __packed; + __le64 val; +}; struct dev_pstate_set { __le16 dev_id; @@ -785,11 +784,10 @@ static int scpi_sensor_get_value(u16 sensor, u64 *val) return ret; if (scpi_info->is_legacy) - /* only 32-bits supported, hi_val can be junk */ - *val = le32_to_cpu(buf.lo_val); + /* only 32-bits supported, upper 32 bits can be junk */ + *val = le32_to_cpup((__le32 *)&buf.val); else - *val = (u64)le32_to_cpu(buf.hi_val) << 32 | - le32_to_cpu(buf.lo_val); + *val = le64_to_cpu(buf.val); return 0; } -- cgit v1.2.3-55-g7522 From 1b36633ea9c80f91fab505002dd7548febc51812 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Thu, 5 Oct 2017 11:31:36 +0100 Subject: firmware: arm_scpi: drop unnecessary type cast to scpi_shared_mem This patch drops the only present type cast of the SCPI payload pointer to scpi_shared_mem inorder to align with other occurrences, IOW for consistency. Reviewed-by: Heiner Kallweit Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index c923d1ebfa3e..a888970f1347 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -453,7 +453,7 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg) unsigned long flags; struct scpi_xfer *t = msg; struct scpi_chan *ch = container_of(c, struct scpi_chan, cl); - struct scpi_shared_mem *mem = (struct scpi_shared_mem *)ch->tx_payload; + struct scpi_shared_mem *mem = ch->tx_payload; if (t->tx_buf) { if (scpi_info->is_legacy) -- cgit v1.2.3-55-g7522 From 48bee74a354b211c4e4cb62e6a7b7986749747cf Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Thu, 5 Oct 2017 11:40:49 +0100 Subject: firmware: arm_scpi: remove all single element structures Both clk_get_value and sensor_value structures contains a single element and hence needs no packing making the whole structure defination unnecessary. This patch gets rid of both those unnecessary structures. Reviewed-by: Heiner Kallweit Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index a888970f1347..f0c37a4ecddf 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -304,10 +304,6 @@ struct clk_get_info { u8 name[20]; } __packed; -struct clk_get_value { - __le32 rate; -} __packed; - struct clk_set_value { __le16 id; __le16 reserved; @@ -346,10 +342,6 @@ struct _scpi_sensor_info { char name[20]; }; -struct sensor_value { - __le64 val; -}; - struct dev_pstate_set { __le16 dev_id; u8 pstate; @@ -577,13 +569,13 @@ scpi_clk_get_range(u16 clk_id, unsigned long *min, unsigned long *max) static unsigned long scpi_clk_get_val(u16 clk_id) { int ret; - struct clk_get_value clk; + __le32 rate; __le16 le_clk_id = cpu_to_le16(clk_id); ret = scpi_send_message(CMD_GET_CLOCK_VALUE, &le_clk_id, - sizeof(le_clk_id), &clk, sizeof(clk)); + sizeof(le_clk_id), &rate, sizeof(rate)); - return ret ? ret : le32_to_cpu(clk.rate); + return ret ? ret : le32_to_cpu(rate); } static int scpi_clk_set_val(u16 clk_id, unsigned long rate) @@ -775,19 +767,19 @@ static int scpi_sensor_get_info(u16 sensor_id, struct scpi_sensor_info *info) static int scpi_sensor_get_value(u16 sensor, u64 *val) { __le16 id = cpu_to_le16(sensor); - struct sensor_value buf; + __le64 value; int ret; ret = scpi_send_message(CMD_SENSOR_VALUE, &id, sizeof(id), - &buf, sizeof(buf)); + &value, sizeof(value)); if (ret) return ret; if (scpi_info->is_legacy) /* only 32-bits supported, upper 32 bits can be junk */ - *val = le32_to_cpup((__le32 *)&buf.val); + *val = le32_to_cpup((__le32 *)&value); else - *val = le64_to_cpu(buf.val); + *val = le64_to_cpu(value); return 0; } -- cgit v1.2.3-55-g7522 From 430594c1c7f5051f0d99ed9d08d086d20587cdd1 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Fri, 6 Oct 2017 20:59:05 +0200 Subject: firmware: arm_scpi: silence sparse warnings At several positions in the code sparse complains about incorrect access to __iomem annotated memory. Fix this and make sparse happy. Signed-off-by: Heiner Kallweit Signed-off-by: Sudeep Holla --- drivers/firmware/arm_scpi.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index f0c37a4ecddf..dfb373c8ba2a 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -405,19 +405,20 @@ static void scpi_process_cmd(struct scpi_chan *ch, u32 cmd) unsigned int len; if (scpi_info->is_legacy) { - struct legacy_scpi_shared_mem *mem = ch->rx_payload; + struct legacy_scpi_shared_mem __iomem *mem = + ch->rx_payload; /* RX Length is not replied by the legacy Firmware */ len = match->rx_len; - match->status = le32_to_cpu(mem->status); + match->status = ioread32(&mem->status); memcpy_fromio(match->rx_buf, mem->payload, len); } else { - struct scpi_shared_mem *mem = ch->rx_payload; + struct scpi_shared_mem __iomem *mem = ch->rx_payload; len = min(match->rx_len, CMD_SIZE(cmd)); - match->status = le32_to_cpu(mem->status); + match->status = ioread32(&mem->status); memcpy_fromio(match->rx_buf, mem->payload, len); } @@ -431,11 +432,11 @@ static void scpi_process_cmd(struct scpi_chan *ch, u32 cmd) static void scpi_handle_remote_msg(struct mbox_client *c, void *msg) { struct scpi_chan *ch = container_of(c, struct scpi_chan, cl); - struct scpi_shared_mem *mem = ch->rx_payload; + struct scpi_shared_mem __iomem *mem = ch->rx_payload; u32 cmd = 0; if (!scpi_info->is_legacy) - cmd = le32_to_cpu(mem->command); + cmd = ioread32(&mem->command); scpi_process_cmd(ch, cmd); } @@ -445,7 +446,7 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg) unsigned long flags; struct scpi_xfer *t = msg; struct scpi_chan *ch = container_of(c, struct scpi_chan, cl); - struct scpi_shared_mem *mem = ch->tx_payload; + struct scpi_shared_mem __iomem *mem = ch->tx_payload; if (t->tx_buf) { if (scpi_info->is_legacy) @@ -464,7 +465,7 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg) } if (!scpi_info->is_legacy) - mem->command = cpu_to_le32(t->cmd); + iowrite32(t->cmd, &mem->command); } static struct scpi_xfer *get_scpi_xfer(struct scpi_chan *ch) -- cgit v1.2.3-55-g7522 From 595f3a9de5815d38198136491fa50a8ee1700b7d Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Mon, 9 Oct 2017 09:54:14 -0700 Subject: ti_sci: Use %pS printk format for direct addresses Use the %pS printk format for printing symbols from direct addresses. This is important for the ia64, ppc64 and parisc64 architectures, while on other architectures there is no difference between %pS and %pF. Fix it for consistency across the kernel. Signed-off-by: Helge Deller Acked-by: Nishanth Menon Signed-off-by: Santosh Shilimkar --- drivers/firmware/ti_sci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index 00cfed3c3e1a..23b12d99ddfe 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c @@ -439,7 +439,7 @@ static inline int ti_sci_do_xfer(struct ti_sci_info *info, /* And we wait for the response. */ timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms); if (!wait_for_completion_timeout(&xfer->done, timeout)) { - dev_err(dev, "Mbox timedout in resp(caller: %pF)\n", + dev_err(dev, "Mbox timedout in resp(caller: %pS)\n", (void *)_RET_IP_); ret = -ETIMEDOUT; } -- cgit v1.2.3-55-g7522 From 4e659dbe2d02a56ca0df25c77e099760252a329c Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 14 Aug 2017 15:46:17 -0700 Subject: firmware: qcom: scm: Expose secure IO service The secure IO service provides operations for reading and writing secure memory from non-secure mode, expose this API through SCM. Reviewed-by: Stephen Boyd Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- drivers/firmware/qcom_scm-32.c | 18 ++++++++++++++++++ drivers/firmware/qcom_scm-64.c | 31 +++++++++++++++++++++++++++++++ drivers/firmware/qcom_scm.c | 12 ++++++++++++ drivers/firmware/qcom_scm.h | 6 ++++++ include/linux/qcom_scm.h | 4 ++++ 5 files changed, 71 insertions(+) (limited to 'drivers/firmware') diff --git a/drivers/firmware/qcom_scm-32.c b/drivers/firmware/qcom_scm-32.c index 93e3b96b6dfa..11fdb1584823 100644 --- a/drivers/firmware/qcom_scm-32.c +++ b/drivers/firmware/qcom_scm-32.c @@ -596,3 +596,21 @@ int __qcom_scm_iommu_secure_ptbl_init(struct device *dev, u64 addr, u32 size, { return -ENODEV; } + +int __qcom_scm_io_readl(struct device *dev, phys_addr_t addr, + unsigned int *val) +{ + int ret; + + ret = qcom_scm_call_atomic1(QCOM_SCM_SVC_IO, QCOM_SCM_IO_READ, addr); + if (ret >= 0) + *val = ret; + + return ret < 0 ? ret : 0; +} + +int __qcom_scm_io_writel(struct device *dev, phys_addr_t addr, unsigned int val) +{ + return qcom_scm_call_atomic2(QCOM_SCM_SVC_IO, QCOM_SCM_IO_WRITE, + addr, val); +} diff --git a/drivers/firmware/qcom_scm-64.c b/drivers/firmware/qcom_scm-64.c index 6e6d561708e2..bf50fb59852e 100644 --- a/drivers/firmware/qcom_scm-64.c +++ b/drivers/firmware/qcom_scm-64.c @@ -439,3 +439,34 @@ int __qcom_scm_iommu_secure_ptbl_init(struct device *dev, u64 addr, u32 size, return ret; } + +int __qcom_scm_io_readl(struct device *dev, phys_addr_t addr, + unsigned int *val) +{ + struct qcom_scm_desc desc = {0}; + struct arm_smccc_res res; + int ret; + + desc.args[0] = addr; + desc.arginfo = QCOM_SCM_ARGS(1); + + ret = qcom_scm_call(dev, QCOM_SCM_SVC_IO, QCOM_SCM_IO_READ, + &desc, &res); + if (ret >= 0) + *val = res.a1; + + return ret < 0 ? ret : 0; +} + +int __qcom_scm_io_writel(struct device *dev, phys_addr_t addr, unsigned int val) +{ + struct qcom_scm_desc desc = {0}; + struct arm_smccc_res res; + + desc.args[0] = addr; + desc.args[1] = val; + desc.arginfo = QCOM_SCM_ARGS(2); + + return qcom_scm_call(dev, QCOM_SCM_SVC_IO, QCOM_SCM_IO_WRITE, + &desc, &res); +} diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c index bb16510d75ba..e18d63935648 100644 --- a/drivers/firmware/qcom_scm.c +++ b/drivers/firmware/qcom_scm.c @@ -333,6 +333,18 @@ int qcom_scm_iommu_secure_ptbl_init(u64 addr, u32 size, u32 spare) } EXPORT_SYMBOL(qcom_scm_iommu_secure_ptbl_init); +int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val) +{ + return __qcom_scm_io_readl(__scm->dev, addr, val); +} +EXPORT_SYMBOL(qcom_scm_io_readl); + +int qcom_scm_io_writel(phys_addr_t addr, unsigned int val) +{ + return __qcom_scm_io_writel(__scm->dev, addr, val); +} +EXPORT_SYMBOL(qcom_scm_io_writel); + /** * qcom_scm_is_available() - Checks if SCM is available */ diff --git a/drivers/firmware/qcom_scm.h b/drivers/firmware/qcom_scm.h index 9bea691f30fb..a60e4b9b1394 100644 --- a/drivers/firmware/qcom_scm.h +++ b/drivers/firmware/qcom_scm.h @@ -30,6 +30,12 @@ extern int __qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus); #define QCOM_SCM_CMD_CORE_HOTPLUGGED 0x10 extern void __qcom_scm_cpu_power_down(u32 flags); +#define QCOM_SCM_SVC_IO 0x5 +#define QCOM_SCM_IO_READ 0x1 +#define QCOM_SCM_IO_WRITE 0x2 +extern int __qcom_scm_io_readl(struct device *dev, phys_addr_t addr, unsigned int *val); +extern int __qcom_scm_io_writel(struct device *dev, phys_addr_t addr, unsigned int val); + #define QCOM_SCM_SVC_INFO 0x6 #define QCOM_IS_CALL_AVAIL_CMD 0x1 extern int __qcom_scm_is_call_available(struct device *dev, u32 svc_id, diff --git a/include/linux/qcom_scm.h b/include/linux/qcom_scm.h index e5380471c2cd..e8357f570695 100644 --- a/include/linux/qcom_scm.h +++ b/include/linux/qcom_scm.h @@ -43,6 +43,8 @@ extern int qcom_scm_set_remote_state(u32 state, u32 id); extern int qcom_scm_restore_sec_cfg(u32 device_id, u32 spare); extern int qcom_scm_iommu_secure_ptbl_size(u32 spare, size_t *size); extern int qcom_scm_iommu_secure_ptbl_init(u64 addr, u32 size, u32 spare); +extern int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val); +extern int qcom_scm_io_writel(phys_addr_t addr, unsigned int val); #else static inline int qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus) @@ -73,5 +75,7 @@ qcom_scm_set_remote_state(u32 state,u32 id) { return -ENODEV; } static inline int qcom_scm_restore_sec_cfg(u32 device_id, u32 spare) { return -ENODEV; } static inline int qcom_scm_iommu_secure_ptbl_size(u32 spare, size_t *size) { return -ENODEV; } static inline int qcom_scm_iommu_secure_ptbl_init(u64 addr, u32 size, u32 spare) { return -ENODEV; } +static inline int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val) { return -ENODEV; } +static inline int qcom_scm_io_writel(phys_addr_t addr, unsigned int val) { return -ENODEV; } #endif #endif -- cgit v1.2.3-55-g7522 From 8c1b7dc9ba2294c6dbd1870a3d2e534bfda3047a Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Mon, 14 Aug 2017 15:46:18 -0700 Subject: firmware: qcom: scm: Expose download-mode control In order to aid post-mortem debugging the Qualcomm platforms provide a "memory download mode", where the boot loader will provide an interface for custom tools to "download" the content of RAM to a host machine. The mode is triggered by writing a magic value somewhere in RAM, that is read in the boot code path after a warm-restart. Two mechanism for setting this magic value are supported in modern platforms; a direct SCM call to enable the mode or through a secure io write of a magic value. In order for a normal reboot not to trigger "download mode" the magic must be cleared during a clean reboot. Download mode has to be enabled by including qcom_scm.download_mode=1 on the command line. Reviewed-by: Stephen Boyd Signed-off-by: Bjorn Andersson Signed-off-by: Andy Gross --- .../devicetree/bindings/firmware/qcom,scm.txt | 2 + drivers/firmware/Kconfig | 11 ++++ drivers/firmware/qcom_scm-32.c | 6 ++ drivers/firmware/qcom_scm-64.c | 13 ++++ drivers/firmware/qcom_scm.c | 75 ++++++++++++++++++++++ drivers/firmware/qcom_scm.h | 2 + 6 files changed, 109 insertions(+) (limited to 'drivers/firmware') diff --git a/Documentation/devicetree/bindings/firmware/qcom,scm.txt b/Documentation/devicetree/bindings/firmware/qcom,scm.txt index 20f26fbce875..7b40054be0d8 100644 --- a/Documentation/devicetree/bindings/firmware/qcom,scm.txt +++ b/Documentation/devicetree/bindings/firmware/qcom,scm.txt @@ -18,6 +18,8 @@ Required properties: * Core, iface, and bus clocks required for "qcom,scm" - clock-names: Must contain "core" for the core clock, "iface" for the interface clock and "bus" for the bus clock per the requirements of the compatible. +- qcom,dload-mode: phandle to the TCSR hardware block and offset of the + download mode control register (optional) Example for MSM8916: diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 6e4ed5a9c6fd..fa87a055905e 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -215,6 +215,17 @@ config QCOM_SCM_64 def_bool y depends on QCOM_SCM && ARM64 +config QCOM_SCM_DOWNLOAD_MODE_DEFAULT + bool "Qualcomm download mode enabled by default" + depends on QCOM_SCM + help + A device with "download mode" enabled will upon an unexpected + warm-restart enter a special debug mode that allows the user to + "download" memory content over USB for offline postmortem analysis. + The feature can be enabled/disabled on the kernel command line. + + Say Y here to enable "download mode" by default. + config TI_SCI_PROTOCOL tristate "TI System Control Interface (TISCI) Message Protocol" depends on TI_MESSAGE_MANAGER diff --git a/drivers/firmware/qcom_scm-32.c b/drivers/firmware/qcom_scm-32.c index 11fdb1584823..68b2033bc30e 100644 --- a/drivers/firmware/qcom_scm-32.c +++ b/drivers/firmware/qcom_scm-32.c @@ -561,6 +561,12 @@ int __qcom_scm_pas_mss_reset(struct device *dev, bool reset) return ret ? : le32_to_cpu(out); } +int __qcom_scm_set_dload_mode(struct device *dev, bool enable) +{ + return qcom_scm_call_atomic2(QCOM_SCM_SVC_BOOT, QCOM_SCM_SET_DLOAD_MODE, + enable ? QCOM_SCM_SET_DLOAD_MODE : 0, 0); +} + int __qcom_scm_set_remote_state(struct device *dev, u32 state, u32 id) { struct { diff --git a/drivers/firmware/qcom_scm-64.c b/drivers/firmware/qcom_scm-64.c index bf50fb59852e..3fea6f563ca9 100644 --- a/drivers/firmware/qcom_scm-64.c +++ b/drivers/firmware/qcom_scm-64.c @@ -440,6 +440,19 @@ int __qcom_scm_iommu_secure_ptbl_init(struct device *dev, u64 addr, u32 size, return ret; } +int __qcom_scm_set_dload_mode(struct device *dev, bool enable) +{ + struct qcom_scm_desc desc = {0}; + struct arm_smccc_res res; + + desc.args[0] = QCOM_SCM_SET_DLOAD_MODE; + desc.args[1] = enable ? QCOM_SCM_SET_DLOAD_MODE : 0; + desc.arginfo = QCOM_SCM_ARGS(2); + + return qcom_scm_call(dev, QCOM_SCM_SVC_BOOT, QCOM_SCM_SET_DLOAD_MODE, + &desc, &res); +} + int __qcom_scm_io_readl(struct device *dev, phys_addr_t addr, unsigned int *val) { diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c index e18d63935648..9064e559a01f 100644 --- a/drivers/firmware/qcom_scm.c +++ b/drivers/firmware/qcom_scm.c @@ -19,15 +19,20 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include "qcom_scm.h" +static bool download_mode = IS_ENABLED(CONFIG_QCOM_SCM_DOWNLOAD_MODE_DEFAULT); +module_param(download_mode, bool, 0); + #define SCM_HAS_CORE_CLK BIT(0) #define SCM_HAS_IFACE_CLK BIT(1) #define SCM_HAS_BUS_CLK BIT(2) @@ -38,6 +43,8 @@ struct qcom_scm { struct clk *iface_clk; struct clk *bus_clk; struct reset_controller_dev reset; + + u64 dload_mode_addr; }; static struct qcom_scm *__scm; @@ -345,6 +352,54 @@ int qcom_scm_io_writel(phys_addr_t addr, unsigned int val) } EXPORT_SYMBOL(qcom_scm_io_writel); +static void qcom_scm_set_download_mode(bool enable) +{ + bool avail; + int ret = 0; + + avail = __qcom_scm_is_call_available(__scm->dev, + QCOM_SCM_SVC_BOOT, + QCOM_SCM_SET_DLOAD_MODE); + if (avail) { + ret = __qcom_scm_set_dload_mode(__scm->dev, enable); + } else if (__scm->dload_mode_addr) { + ret = __qcom_scm_io_writel(__scm->dev, __scm->dload_mode_addr, + enable ? QCOM_SCM_SET_DLOAD_MODE : 0); + } else { + dev_err(__scm->dev, + "No available mechanism for setting download mode\n"); + } + + if (ret) + dev_err(__scm->dev, "failed to set download mode: %d\n", ret); +} + +static int qcom_scm_find_dload_address(struct device *dev, u64 *addr) +{ + struct device_node *tcsr; + struct device_node *np = dev->of_node; + struct resource res; + u32 offset; + int ret; + + tcsr = of_parse_phandle(np, "qcom,dload-mode", 0); + if (!tcsr) + return 0; + + ret = of_address_to_resource(tcsr, 0, &res); + of_node_put(tcsr); + if (ret) + return ret; + + ret = of_property_read_u32_index(np, "qcom,dload-mode", 1, &offset); + if (ret < 0) + return ret; + + *addr = res.start + offset; + + return 0; +} + /** * qcom_scm_is_available() - Checks if SCM is available */ @@ -370,6 +425,10 @@ static int qcom_scm_probe(struct platform_device *pdev) if (!scm) return -ENOMEM; + ret = qcom_scm_find_dload_address(&pdev->dev, &scm->dload_mode_addr); + if (ret < 0) + return ret; + clks = (unsigned long)of_device_get_match_data(&pdev->dev); if (clks & SCM_HAS_CORE_CLK) { scm->core_clk = devm_clk_get(&pdev->dev, "core"); @@ -418,9 +477,24 @@ static int qcom_scm_probe(struct platform_device *pdev) __qcom_scm_init(); + /* + * If requested enable "download mode", from this point on warmboot + * will cause the the boot stages to enter download mode, unless + * disabled below by a clean shutdown/reboot. + */ + if (download_mode) + qcom_scm_set_download_mode(true); + return 0; } +static void qcom_scm_shutdown(struct platform_device *pdev) +{ + /* Clean shutdown, disable download mode to allow normal restart */ + if (download_mode) + qcom_scm_set_download_mode(false); +} + static const struct of_device_id qcom_scm_dt_match[] = { { .compatible = "qcom,scm-apq8064", /* FIXME: This should have .data = (void *) SCM_HAS_CORE_CLK */ @@ -448,6 +522,7 @@ static struct platform_driver qcom_scm_driver = { .of_match_table = qcom_scm_dt_match, }, .probe = qcom_scm_probe, + .shutdown = qcom_scm_shutdown, }; static int __init qcom_scm_init(void) diff --git a/drivers/firmware/qcom_scm.h b/drivers/firmware/qcom_scm.h index a60e4b9b1394..83f171c23943 100644 --- a/drivers/firmware/qcom_scm.h +++ b/drivers/firmware/qcom_scm.h @@ -14,9 +14,11 @@ #define QCOM_SCM_SVC_BOOT 0x1 #define QCOM_SCM_BOOT_ADDR 0x1 +#define QCOM_SCM_SET_DLOAD_MODE 0x10 #define QCOM_SCM_BOOT_ADDR_MC 0x11 #define QCOM_SCM_SET_REMOTE_STATE 0xa extern int __qcom_scm_set_remote_state(struct device *dev, u32 state, u32 id); +extern int __qcom_scm_set_dload_mode(struct device *dev, bool enable); #define QCOM_SCM_FLAG_HLOS 0x01 #define QCOM_SCM_FLAG_COLDBOOT_MC 0x02 -- cgit v1.2.3-55-g7522 From 370d010f0ef09db7ab157a6b5d6d9a737b148f2a Mon Sep 17 00:00:00 2001 From: Timo Alho Date: Thu, 7 Sep 2017 12:31:01 +0300 Subject: firmware: tegra: Propagate error code to caller Response messages from Tegra BPMP firmware contain an error return code as the first word of payload. The error code is used to indicate incorrectly formatted request message or use of non-existing resource (clk, reset, powergate) identifier. Current implementation of tegra_bpmp_transfer() ignores this code and does not pass it to caller. Fix this by adding an extra struct member to tegra_bpmp_message and populate that with return code. Signed-off-by: Timo Alho Acked-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/firmware/tegra/bpmp.c | 22 ++++++++++++++++------ include/soc/tegra/bpmp.h | 1 + 2 files changed, 17 insertions(+), 6 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index 73ca55b7b7ec..33683b5a0d48 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -194,16 +194,24 @@ static int tegra_bpmp_wait_master_free(struct tegra_bpmp_channel *channel) } static ssize_t __tegra_bpmp_channel_read(struct tegra_bpmp_channel *channel, - void *data, size_t size) + void *data, size_t size, int *ret) { + int err; + if (data && size > 0) memcpy(data, channel->ib->data, size); - return tegra_ivc_read_advance(channel->ivc); + err = tegra_ivc_read_advance(channel->ivc); + if (err < 0) + return err; + + *ret = channel->ib->code; + + return 0; } static ssize_t tegra_bpmp_channel_read(struct tegra_bpmp_channel *channel, - void *data, size_t size) + void *data, size_t size, int *ret) { struct tegra_bpmp *bpmp = channel->bpmp; unsigned long flags; @@ -217,7 +225,7 @@ static ssize_t tegra_bpmp_channel_read(struct tegra_bpmp_channel *channel, } spin_lock_irqsave(&bpmp->lock, flags); - err = __tegra_bpmp_channel_read(channel, data, size); + err = __tegra_bpmp_channel_read(channel, data, size, ret); clear_bit(index, bpmp->threaded.allocated); spin_unlock_irqrestore(&bpmp->lock, flags); @@ -337,7 +345,8 @@ int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp, if (err < 0) return err; - return __tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size); + return __tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size, + &msg->rx.ret); } EXPORT_SYMBOL_GPL(tegra_bpmp_transfer_atomic); @@ -371,7 +380,8 @@ int tegra_bpmp_transfer(struct tegra_bpmp *bpmp, if (err == 0) return -ETIMEDOUT; - return tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size); + return tegra_bpmp_channel_read(channel, msg->rx.data, msg->rx.size, + &msg->rx.ret); } EXPORT_SYMBOL_GPL(tegra_bpmp_transfer); diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h index 9ba65222bd3f..57519f4c126a 100644 --- a/include/soc/tegra/bpmp.h +++ b/include/soc/tegra/bpmp.h @@ -110,6 +110,7 @@ struct tegra_bpmp_message { struct { void *data; size_t size; + int ret; } rx; }; -- cgit v1.2.3-55-g7522 From 2e1e09ed423781d209c1fea773b1c3e614550ea5 Mon Sep 17 00:00:00 2001 From: Mikko Perttunen Date: Mon, 24 Jul 2017 19:29:16 +0300 Subject: firmware: tegra: Expose tegra_bpmp_mrq_return() Expose and export the tegra_bpmp_mrq_return() function for use by drivers outside the core BPMP driver. This function is used to reply to messages originating from the BPMP, which is required in the thermal driver. Signed-off-by: Mikko Perttunen Signed-off-by: Thierry Reding --- drivers/firmware/tegra/bpmp.c | 5 +++-- include/soc/tegra/bpmp.h | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index 33683b5a0d48..aa4bbbd7bdb4 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -397,8 +397,8 @@ static struct tegra_bpmp_mrq *tegra_bpmp_find_mrq(struct tegra_bpmp *bpmp, return NULL; } -static void tegra_bpmp_mrq_return(struct tegra_bpmp_channel *channel, - int code, const void *data, size_t size) +void tegra_bpmp_mrq_return(struct tegra_bpmp_channel *channel, int code, + const void *data, size_t size) { unsigned long flags = channel->ib->flags; struct tegra_bpmp *bpmp = channel->bpmp; @@ -436,6 +436,7 @@ static void tegra_bpmp_mrq_return(struct tegra_bpmp_channel *channel, mbox_client_txdone(bpmp->mbox.channel, 0); } } +EXPORT_SYMBOL_GPL(tegra_bpmp_mrq_return); static void tegra_bpmp_handle_mrq(struct tegra_bpmp *bpmp, unsigned int mrq, diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h index 57519f4c126a..b382ed01af09 100644 --- a/include/soc/tegra/bpmp.h +++ b/include/soc/tegra/bpmp.h @@ -118,6 +118,8 @@ int tegra_bpmp_transfer_atomic(struct tegra_bpmp *bpmp, struct tegra_bpmp_message *msg); int tegra_bpmp_transfer(struct tegra_bpmp *bpmp, struct tegra_bpmp_message *msg); +void tegra_bpmp_mrq_return(struct tegra_bpmp_channel *channel, int code, + const void *data, size_t size); int tegra_bpmp_request_mrq(struct tegra_bpmp *bpmp, unsigned int mrq, tegra_bpmp_mrq_handler_t handler, void *data); -- cgit v1.2.3-55-g7522 From f2381f652266fabfb7a8f5c4b2a05de36cad3a73 Mon Sep 17 00:00:00 2001 From: Timo Alho Date: Tue, 3 Oct 2017 09:12:13 +0300 Subject: firmware: tegra: Add BPMP debugfs support Tegra power management firmware running on the co-processor (BPMP) implements a simple pseudo file system akin to debugfs. The file system can be used for debugging purposes to examine and change the status of selected resources controlled by the firmware (such as clocks, resets, voltages, powergates, ...). Add support to "mirror" the firmware's file system to debugfs. At boot, query firmware for a list of all possible files and create corresponding debugfs entries. Read/write of individual files is implemented by sending a Message ReQuest (MRQ) that passes the full file path name and data to firmware via DRAM. Signed-off-by: Timo Alho Reviewed-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/firmware/tegra/Makefile | 4 +- drivers/firmware/tegra/bpmp-debugfs.c | 444 ++++++++++++++++++++++++++++++++++ drivers/firmware/tegra/bpmp.c | 4 + include/soc/tegra/bpmp.h | 14 ++ 4 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/tegra/bpmp-debugfs.c (limited to 'drivers/firmware') diff --git a/drivers/firmware/tegra/Makefile b/drivers/firmware/tegra/Makefile index e34a2f79e1ad..1b826dcca719 100644 --- a/drivers/firmware/tegra/Makefile +++ b/drivers/firmware/tegra/Makefile @@ -1,2 +1,4 @@ -obj-$(CONFIG_TEGRA_BPMP) += bpmp.o +tegra-bpmp-y = bpmp.o +tegra-bpmp-$(CONFIG_DEBUG_FS) += bpmp-debugfs.o +obj-$(CONFIG_TEGRA_BPMP) += tegra-bpmp.o obj-$(CONFIG_TEGRA_IVC) += ivc.o diff --git a/drivers/firmware/tegra/bpmp-debugfs.c b/drivers/firmware/tegra/bpmp-debugfs.c new file mode 100644 index 000000000000..f7f6a0a5cb07 --- /dev/null +++ b/drivers/firmware/tegra/bpmp-debugfs.c @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ +#include +#include +#include + +#include +#include + +struct seqbuf { + char *buf; + size_t pos; + size_t size; +}; + +static void seqbuf_init(struct seqbuf *seqbuf, void *buf, size_t size) +{ + seqbuf->buf = buf; + seqbuf->size = size; + seqbuf->pos = 0; +} + +static size_t seqbuf_avail(struct seqbuf *seqbuf) +{ + return seqbuf->pos < seqbuf->size ? seqbuf->size - seqbuf->pos : 0; +} + +static size_t seqbuf_status(struct seqbuf *seqbuf) +{ + return seqbuf->pos <= seqbuf->size ? 0 : -EOVERFLOW; +} + +static int seqbuf_eof(struct seqbuf *seqbuf) +{ + return seqbuf->pos >= seqbuf->size; +} + +static int seqbuf_read(struct seqbuf *seqbuf, void *buf, size_t nbyte) +{ + nbyte = min(nbyte, seqbuf_avail(seqbuf)); + memcpy(buf, seqbuf->buf + seqbuf->pos, nbyte); + seqbuf->pos += nbyte; + return seqbuf_status(seqbuf); +} + +static int seqbuf_read_u32(struct seqbuf *seqbuf, uint32_t *v) +{ + int err; + + err = seqbuf_read(seqbuf, v, 4); + *v = le32_to_cpu(*v); + return err; +} + +static int seqbuf_read_str(struct seqbuf *seqbuf, const char **str) +{ + *str = seqbuf->buf + seqbuf->pos; + seqbuf->pos += strnlen(*str, seqbuf_avail(seqbuf)); + seqbuf->pos++; + return seqbuf_status(seqbuf); +} + +static void seqbuf_seek(struct seqbuf *seqbuf, ssize_t offset) +{ + seqbuf->pos += offset; +} + +/* map filename in Linux debugfs to corresponding entry in BPMP */ +static const char *get_filename(struct tegra_bpmp *bpmp, + const struct file *file, char *buf, int size) +{ + char root_path_buf[512]; + const char *root_path; + const char *filename; + size_t root_len; + + root_path = dentry_path(bpmp->debugfs_mirror, root_path_buf, + sizeof(root_path_buf)); + if (IS_ERR(root_path)) + return NULL; + + root_len = strlen(root_path); + + filename = dentry_path(file->f_path.dentry, buf, size); + if (IS_ERR(filename)) + return NULL; + + if (strlen(filename) < root_len || + strncmp(filename, root_path, root_len)) + return NULL; + + filename += root_len; + + return filename; +} + +static int mrq_debugfs_read(struct tegra_bpmp *bpmp, + dma_addr_t name, size_t sz_name, + dma_addr_t data, size_t sz_data, + size_t *nbytes) +{ + struct mrq_debugfs_request req = { + .cmd = cpu_to_le32(CMD_DEBUGFS_READ), + .fop = { + .fnameaddr = cpu_to_le32((uint32_t)name), + .fnamelen = cpu_to_le32((uint32_t)sz_name), + .dataaddr = cpu_to_le32((uint32_t)data), + .datalen = cpu_to_le32((uint32_t)sz_data), + }, + }; + struct mrq_debugfs_response resp; + struct tegra_bpmp_message msg = { + .mrq = MRQ_DEBUGFS, + .tx = { + .data = &req, + .size = sizeof(req), + }, + .rx = { + .data = &resp, + .size = sizeof(resp), + }, + }; + int err; + + err = tegra_bpmp_transfer(bpmp, &msg); + if (err < 0) + return err; + + *nbytes = (size_t)resp.fop.nbytes; + + return 0; +} + +static int mrq_debugfs_write(struct tegra_bpmp *bpmp, + dma_addr_t name, size_t sz_name, + dma_addr_t data, size_t sz_data) +{ + const struct mrq_debugfs_request req = { + .cmd = cpu_to_le32(CMD_DEBUGFS_WRITE), + .fop = { + .fnameaddr = cpu_to_le32((uint32_t)name), + .fnamelen = cpu_to_le32((uint32_t)sz_name), + .dataaddr = cpu_to_le32((uint32_t)data), + .datalen = cpu_to_le32((uint32_t)sz_data), + }, + }; + struct tegra_bpmp_message msg = { + .mrq = MRQ_DEBUGFS, + .tx = { + .data = &req, + .size = sizeof(req), + }, + }; + + return tegra_bpmp_transfer(bpmp, &msg); +} + +static int mrq_debugfs_dumpdir(struct tegra_bpmp *bpmp, dma_addr_t addr, + size_t size, size_t *nbytes) +{ + const struct mrq_debugfs_request req = { + .cmd = cpu_to_le32(CMD_DEBUGFS_DUMPDIR), + .dumpdir = { + .dataaddr = cpu_to_le32((uint32_t)addr), + .datalen = cpu_to_le32((uint32_t)size), + }, + }; + struct mrq_debugfs_response resp; + struct tegra_bpmp_message msg = { + .mrq = MRQ_DEBUGFS, + .tx = { + .data = &req, + .size = sizeof(req), + }, + .rx = { + .data = &resp, + .size = sizeof(resp), + }, + }; + int err; + + err = tegra_bpmp_transfer(bpmp, &msg); + if (err < 0) + return err; + + *nbytes = (size_t)resp.dumpdir.nbytes; + + return 0; +} + +static int debugfs_show(struct seq_file *m, void *p) +{ + struct file *file = m->private; + struct inode *inode = file_inode(file); + struct tegra_bpmp *bpmp = inode->i_private; + const size_t datasize = m->size; + const size_t namesize = SZ_256; + void *datavirt, *namevirt; + dma_addr_t dataphys, namephys; + char buf[256]; + const char *filename; + size_t len, nbytes; + int ret; + + filename = get_filename(bpmp, file, buf, sizeof(buf)); + if (!filename) + return -ENOENT; + + namevirt = dma_alloc_coherent(bpmp->dev, namesize, &namephys, + GFP_KERNEL | GFP_DMA32); + if (!namevirt) + return -ENOMEM; + + datavirt = dma_alloc_coherent(bpmp->dev, datasize, &dataphys, + GFP_KERNEL | GFP_DMA32); + if (!datavirt) { + ret = -ENOMEM; + goto free_namebuf; + } + + len = strlen(filename); + strncpy(namevirt, filename, namesize); + + ret = mrq_debugfs_read(bpmp, namephys, len, dataphys, datasize, + &nbytes); + + if (!ret) + seq_write(m, datavirt, nbytes); + + dma_free_coherent(bpmp->dev, datasize, datavirt, dataphys); +free_namebuf: + dma_free_coherent(bpmp->dev, namesize, namevirt, namephys); + + return ret; +} + +static int debugfs_open(struct inode *inode, struct file *file) +{ + return single_open_size(file, debugfs_show, file, SZ_128K); +} + +static ssize_t debugfs_store(struct file *file, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct inode *inode = file_inode(file); + struct tegra_bpmp *bpmp = inode->i_private; + const size_t datasize = count; + const size_t namesize = SZ_256; + void *datavirt, *namevirt; + dma_addr_t dataphys, namephys; + char fnamebuf[256]; + const char *filename; + size_t len; + int ret; + + filename = get_filename(bpmp, file, fnamebuf, sizeof(fnamebuf)); + if (!filename) + return -ENOENT; + + namevirt = dma_alloc_coherent(bpmp->dev, namesize, &namephys, + GFP_KERNEL | GFP_DMA32); + if (!namevirt) + return -ENOMEM; + + datavirt = dma_alloc_coherent(bpmp->dev, datasize, &dataphys, + GFP_KERNEL | GFP_DMA32); + if (!datavirt) { + ret = -ENOMEM; + goto free_namebuf; + } + + len = strlen(filename); + strncpy(namevirt, filename, namesize); + + if (copy_from_user(datavirt, buf, count)) { + ret = -EFAULT; + goto free_databuf; + } + + ret = mrq_debugfs_write(bpmp, namephys, len, dataphys, + count); + +free_databuf: + dma_free_coherent(bpmp->dev, datasize, datavirt, dataphys); +free_namebuf: + dma_free_coherent(bpmp->dev, namesize, namevirt, namephys); + + return ret ?: count; +} + +static const struct file_operations debugfs_fops = { + .open = debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .write = debugfs_store, + .release = single_release, +}; + +static int bpmp_populate_dir(struct tegra_bpmp *bpmp, struct seqbuf *seqbuf, + struct dentry *parent, uint32_t depth) +{ + int err; + uint32_t d, t; + const char *name; + struct dentry *dentry; + + while (!seqbuf_eof(seqbuf)) { + err = seqbuf_read_u32(seqbuf, &d); + if (err < 0) + return err; + + if (d < depth) { + seqbuf_seek(seqbuf, -4); + /* go up a level */ + return 0; + } else if (d != depth) { + /* malformed data received from BPMP */ + return -EIO; + } + + err = seqbuf_read_u32(seqbuf, &t); + if (err < 0) + return err; + err = seqbuf_read_str(seqbuf, &name); + if (err < 0) + return err; + + if (t & DEBUGFS_S_ISDIR) { + dentry = debugfs_create_dir(name, parent); + if (!dentry) + return -ENOMEM; + err = bpmp_populate_dir(bpmp, seqbuf, dentry, depth+1); + if (err < 0) + return err; + } else { + umode_t mode; + + mode = t & DEBUGFS_S_IRUSR ? S_IRUSR : 0; + mode |= t & DEBUGFS_S_IWUSR ? S_IWUSR : 0; + dentry = debugfs_create_file(name, mode, + parent, bpmp, + &debugfs_fops); + if (!dentry) + return -ENOMEM; + } + } + + return 0; +} + +static int create_debugfs_mirror(struct tegra_bpmp *bpmp, void *buf, + size_t bufsize, struct dentry *root) +{ + struct seqbuf seqbuf; + int err; + + bpmp->debugfs_mirror = debugfs_create_dir("debug", root); + if (!bpmp->debugfs_mirror) + return -ENOMEM; + + seqbuf_init(&seqbuf, buf, bufsize); + err = bpmp_populate_dir(bpmp, &seqbuf, bpmp->debugfs_mirror, 0); + if (err < 0) { + debugfs_remove_recursive(bpmp->debugfs_mirror); + bpmp->debugfs_mirror = NULL; + } + + return err; +} + +static int mrq_is_supported(struct tegra_bpmp *bpmp, unsigned int mrq) +{ + struct mrq_query_abi_request req = { .mrq = cpu_to_le32(mrq) }; + struct mrq_query_abi_response resp; + struct tegra_bpmp_message msg = { + .mrq = MRQ_QUERY_ABI, + .tx = { + .data = &req, + .size = sizeof(req), + }, + .rx = { + .data = &resp, + .size = sizeof(resp), + }, + }; + int ret; + + ret = tegra_bpmp_transfer(bpmp, &msg); + if (ret < 0) { + /* something went wrong; assume not supported */ + dev_warn(bpmp->dev, "tegra_bpmp_transfer failed (%d)\n", ret); + return 0; + } + + return resp.status ? 0 : 1; +} + +int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp) +{ + dma_addr_t phys; + void *virt; + const size_t sz = SZ_256K; + size_t nbytes; + int ret; + struct dentry *root; + + if (!mrq_is_supported(bpmp, MRQ_DEBUGFS)) + return 0; + + root = debugfs_create_dir("bpmp", NULL); + if (!root) + return -ENOMEM; + + virt = dma_alloc_coherent(bpmp->dev, sz, &phys, + GFP_KERNEL | GFP_DMA32); + if (!virt) { + ret = -ENOMEM; + goto out; + } + + ret = mrq_debugfs_dumpdir(bpmp, phys, sz, &nbytes); + if (ret < 0) + goto free; + + ret = create_debugfs_mirror(bpmp, virt, nbytes, root); +free: + dma_free_coherent(bpmp->dev, sz, virt, phys); +out: + if (ret < 0) + debugfs_remove(root); + + return ret; +} diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c index aa4bbbd7bdb4..a7f461f2e650 100644 --- a/drivers/firmware/tegra/bpmp.c +++ b/drivers/firmware/tegra/bpmp.c @@ -835,6 +835,10 @@ static int tegra_bpmp_probe(struct platform_device *pdev) if (err < 0) goto free_mrq; + err = tegra_bpmp_init_debugfs(bpmp); + if (err < 0) + dev_err(&pdev->dev, "debugfs initialization failed: %d\n", err); + return 0; free_mrq: diff --git a/include/soc/tegra/bpmp.h b/include/soc/tegra/bpmp.h index 1cf210504814..aeae4466dd25 100644 --- a/include/soc/tegra/bpmp.h +++ b/include/soc/tegra/bpmp.h @@ -94,6 +94,10 @@ struct tegra_bpmp { struct reset_controller_dev rstc; struct genpd_onecell_data genpd; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_mirror; +#endif }; struct tegra_bpmp_message { @@ -189,4 +193,14 @@ static inline int tegra_bpmp_init_powergates(struct tegra_bpmp *bpmp) } #endif +#if IS_ENABLED(CONFIG_DEBUG_FS) +int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp); +#else +static inline int tegra_bpmp_init_debugfs(struct tegra_bpmp *bpmp) +{ + return 0; +} +#endif + + #endif /* __SOC_TEGRA_BPMP_H */ -- cgit v1.2.3-55-g7522 From d82bd359972a7fe71a778396cf287bc9f9f3b981 Mon Sep 17 00:00:00 2001 From: Avaneesh Kumar Dwivedi Date: Tue, 24 Oct 2017 21:22:24 +0530 Subject: firmware: scm: Add new SCM call API for switching memory ownership Two different processors on a SOC need to switch memory ownership during load/unload. To enable this, second level memory map table need to be updated, which is done by secure layer. This patch adds the interface for making secure monitor call for memory ownership switching request. Acked-by: Andy Gross Signed-off-by: Avaneesh Kumar Dwivedi [bjorn: Minor style and kerneldoc updates] Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom_scm-32.c | 7 ++++ drivers/firmware/qcom_scm-64.c | 27 ++++++++++++ drivers/firmware/qcom_scm.c | 95 ++++++++++++++++++++++++++++++++++++++++++ drivers/firmware/qcom_scm.h | 5 +++ include/linux/qcom_scm.h | 16 +++++++ 5 files changed, 150 insertions(+) (limited to 'drivers/firmware') diff --git a/drivers/firmware/qcom_scm-32.c b/drivers/firmware/qcom_scm-32.c index 93e3b96b6dfa..60d96bcf6694 100644 --- a/drivers/firmware/qcom_scm-32.c +++ b/drivers/firmware/qcom_scm-32.c @@ -579,6 +579,13 @@ int __qcom_scm_set_remote_state(struct device *dev, u32 state, u32 id) return ret ? : le32_to_cpu(scm_ret); } +int __qcom_scm_assign_mem(struct device *dev, phys_addr_t mem_region, + size_t mem_sz, phys_addr_t src, size_t src_sz, + phys_addr_t dest, size_t dest_sz) +{ + return -ENODEV; +} + int __qcom_scm_restore_sec_cfg(struct device *dev, u32 device_id, u32 spare) { diff --git a/drivers/firmware/qcom_scm-64.c b/drivers/firmware/qcom_scm-64.c index 6e6d561708e2..ea5b1e680f67 100644 --- a/drivers/firmware/qcom_scm-64.c +++ b/drivers/firmware/qcom_scm-64.c @@ -382,6 +382,33 @@ int __qcom_scm_set_remote_state(struct device *dev, u32 state, u32 id) return ret ? : res.a1; } +int __qcom_scm_assign_mem(struct device *dev, phys_addr_t mem_region, + size_t mem_sz, phys_addr_t src, size_t src_sz, + phys_addr_t dest, size_t dest_sz) +{ + int ret; + struct qcom_scm_desc desc = {0}; + struct arm_smccc_res res; + + desc.args[0] = mem_region; + desc.args[1] = mem_sz; + desc.args[2] = src; + desc.args[3] = src_sz; + desc.args[4] = dest; + desc.args[5] = dest_sz; + desc.args[6] = 0; + + desc.arginfo = QCOM_SCM_ARGS(7, QCOM_SCM_RO, QCOM_SCM_VAL, + QCOM_SCM_RO, QCOM_SCM_VAL, QCOM_SCM_RO, + QCOM_SCM_VAL, QCOM_SCM_VAL); + + ret = qcom_scm_call(dev, QCOM_SCM_SVC_MP, + QCOM_MEM_PROT_ASSIGN_ID, + &desc, &res); + + return ret ? : res.a1; +} + int __qcom_scm_restore_sec_cfg(struct device *dev, u32 device_id, u32 spare) { struct qcom_scm_desc desc = {0}; diff --git a/drivers/firmware/qcom_scm.c b/drivers/firmware/qcom_scm.c index bb16510d75ba..334eaa341828 100644 --- a/drivers/firmware/qcom_scm.c +++ b/drivers/firmware/qcom_scm.c @@ -40,6 +40,19 @@ struct qcom_scm { struct reset_controller_dev reset; }; +struct qcom_scm_current_perm_info { + __le32 vmid; + __le32 perm; + __le64 ctx; + __le32 ctx_size; + __le32 unused; +}; + +struct qcom_scm_mem_map_info { + __le64 mem_addr; + __le64 mem_size; +}; + static struct qcom_scm *__scm; static int qcom_scm_clk_enable(void) @@ -348,6 +361,88 @@ int qcom_scm_set_remote_state(u32 state, u32 id) } EXPORT_SYMBOL(qcom_scm_set_remote_state); +/** + * qcom_scm_assign_mem() - Make a secure call to reassign memory ownership + * @mem_addr: mem region whose ownership need to be reassigned + * @mem_sz: size of the region. + * @srcvm: vmid for current set of owners, each set bit in + * flag indicate a unique owner + * @newvm: array having new owners and corrsponding permission + * flags + * @dest_cnt: number of owners in next set. + * + * Return negative errno on failure, 0 on success, with @srcvm updated. + */ +int qcom_scm_assign_mem(phys_addr_t mem_addr, size_t mem_sz, + unsigned int *srcvm, + struct qcom_scm_vmperm *newvm, int dest_cnt) +{ + struct qcom_scm_current_perm_info *destvm; + struct qcom_scm_mem_map_info *mem_to_map; + phys_addr_t mem_to_map_phys; + phys_addr_t dest_phys; + phys_addr_t ptr_phys; + size_t mem_to_map_sz; + size_t dest_sz; + size_t src_sz; + size_t ptr_sz; + int next_vm; + __le32 *src; + void *ptr; + int ret; + int len; + int i; + + src_sz = hweight_long(*srcvm) * sizeof(*src); + mem_to_map_sz = sizeof(*mem_to_map); + dest_sz = dest_cnt * sizeof(*destvm); + ptr_sz = ALIGN(src_sz, SZ_64) + ALIGN(mem_to_map_sz, SZ_64) + + ALIGN(dest_sz, SZ_64); + + ptr = dma_alloc_coherent(__scm->dev, ptr_sz, &ptr_phys, GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + /* Fill source vmid detail */ + src = ptr; + len = hweight_long(*srcvm); + for (i = 0; i < len; i++) { + src[i] = cpu_to_le32(ffs(*srcvm) - 1); + *srcvm ^= 1 << (ffs(*srcvm) - 1); + } + + /* Fill details of mem buff to map */ + mem_to_map = ptr + ALIGN(src_sz, SZ_64); + mem_to_map_phys = ptr_phys + ALIGN(src_sz, SZ_64); + mem_to_map[0].mem_addr = cpu_to_le64(mem_addr); + mem_to_map[0].mem_size = cpu_to_le64(mem_sz); + + next_vm = 0; + /* Fill details of next vmid detail */ + destvm = ptr + ALIGN(mem_to_map_sz, SZ_64) + ALIGN(src_sz, SZ_64); + dest_phys = ptr_phys + ALIGN(mem_to_map_sz, SZ_64) + ALIGN(src_sz, SZ_64); + for (i = 0; i < dest_cnt; i++) { + destvm[i].vmid = cpu_to_le32(newvm[i].vmid); + destvm[i].perm = cpu_to_le32(newvm[i].perm); + destvm[i].ctx = 0; + destvm[i].ctx_size = 0; + next_vm |= BIT(newvm[i].vmid); + } + + ret = __qcom_scm_assign_mem(__scm->dev, mem_to_map_phys, mem_to_map_sz, + ptr_phys, src_sz, dest_phys, dest_sz); + dma_free_coherent(__scm->dev, ALIGN(ptr_sz, SZ_64), ptr, ptr_phys); + if (ret) { + dev_err(__scm->dev, + "Assign memory protection call failed %d.\n", ret); + return -EINVAL; + } + + *srcvm = next_vm; + return 0; +} +EXPORT_SYMBOL(qcom_scm_assign_mem); + static int qcom_scm_probe(struct platform_device *pdev) { struct qcom_scm *scm; diff --git a/drivers/firmware/qcom_scm.h b/drivers/firmware/qcom_scm.h index 9bea691f30fb..fe54b7ba4db4 100644 --- a/drivers/firmware/qcom_scm.h +++ b/drivers/firmware/qcom_scm.h @@ -95,5 +95,10 @@ extern int __qcom_scm_iommu_secure_ptbl_size(struct device *dev, u32 spare, size_t *size); extern int __qcom_scm_iommu_secure_ptbl_init(struct device *dev, u64 addr, u32 size, u32 spare); +#define QCOM_MEM_PROT_ASSIGN_ID 0x16 +extern int __qcom_scm_assign_mem(struct device *dev, + phys_addr_t mem_region, size_t mem_sz, + phys_addr_t src, size_t src_sz, + phys_addr_t dest, size_t dest_sz); #endif diff --git a/include/linux/qcom_scm.h b/include/linux/qcom_scm.h index e5380471c2cd..6f8da9e182f9 100644 --- a/include/linux/qcom_scm.h +++ b/include/linux/qcom_scm.h @@ -23,6 +23,19 @@ struct qcom_scm_hdcp_req { u32 val; }; +struct qcom_scm_vmperm { + int vmid; + int perm; +}; + +#define QCOM_SCM_VMID_HLOS 0x3 +#define QCOM_SCM_VMID_MSS_MSA 0xF +#define QCOM_SCM_PERM_READ 0x4 +#define QCOM_SCM_PERM_WRITE 0x2 +#define QCOM_SCM_PERM_EXEC 0x1 +#define QCOM_SCM_PERM_RW (QCOM_SCM_PERM_READ | QCOM_SCM_PERM_WRITE) +#define QCOM_SCM_PERM_RWX (QCOM_SCM_PERM_RW | QCOM_SCM_PERM_EXEC) + #if IS_ENABLED(CONFIG_QCOM_SCM) extern int qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus); extern int qcom_scm_set_warm_boot_addr(void *entry, const cpumask_t *cpus); @@ -37,6 +50,9 @@ extern int qcom_scm_pas_mem_setup(u32 peripheral, phys_addr_t addr, phys_addr_t size); extern int qcom_scm_pas_auth_and_reset(u32 peripheral); extern int qcom_scm_pas_shutdown(u32 peripheral); +extern int qcom_scm_assign_mem(phys_addr_t mem_addr, size_t mem_sz, + unsigned int *src, struct qcom_scm_vmperm *newvm, + int dest_cnt); extern void qcom_scm_cpu_power_down(u32 flags); extern u32 qcom_scm_get_version(void); extern int qcom_scm_set_remote_state(u32 state, u32 id); -- cgit v1.2.3-55-g7522 From 51d3290c3ad6ba691bd66df8ead6e7c19dc995f1 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Tue, 24 Oct 2017 16:02:33 +0100 Subject: drivers/firmware: psci_checker: Add missing destroy_timer_on_stack() The PSCI checker suspend_test_thread() function (ie executed for the suspend test) requires an on-stack timer to carry out the test it executes; it sets it up through the setup_timer_on_stack() API. setup_timer_on_stack() requires its counterpart destroy_timer_on_stack() to be called when the timer is disposed of but the PSCI checker code is currently missing that call, leaving the timer object in an incosistent state when the PSCI checker stops the thread executing the suspend test. Add the missing destroy_timer_on_stack() call to fix the omission. Fixes: ea8b1c4a6019 ("drivers: psci: PSCI checker module") Signed-off-by: Lorenzo Pieralisi Reported-by: Kees Cook Cc: Kees Cook Cc: Mark Rutland Signed-off-by: Arnd Bergmann --- drivers/firmware/psci_checker.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/firmware') diff --git a/drivers/firmware/psci_checker.c b/drivers/firmware/psci_checker.c index 6523ce962865..56cf825ed779 100644 --- a/drivers/firmware/psci_checker.c +++ b/drivers/firmware/psci_checker.c @@ -340,6 +340,7 @@ static int suspend_test_thread(void *arg) * later. */ del_timer(&wakeup_timer); + destroy_timer_on_stack(&wakeup_timer); if (atomic_dec_return_relaxed(&nb_active_threads) == 0) complete(&suspend_threads_done); -- cgit v1.2.3-55-g7522 From c1d0c3f623ada808904dec676da0126f5b800630 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Mon, 13 Nov 2017 20:29:54 +0100 Subject: fw_cfg: fix the command line module name Signed-off-by: Marc-André Lureau Acked-by: Gabriel Somlo Signed-off-by: Michael S. Tsirkin --- drivers/firmware/qemu_fw_cfg.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/qemu_fw_cfg.c b/drivers/firmware/qemu_fw_cfg.c index 0e2011636fbb..5cfe39f7a45f 100644 --- a/drivers/firmware/qemu_fw_cfg.c +++ b/drivers/firmware/qemu_fw_cfg.c @@ -10,9 +10,9 @@ * and select subsets of aarch64), a Device Tree node (on arm), or using * a kernel module (or command line) parameter with the following syntax: * - * [fw_cfg.]ioport=@[::] + * [qemu_fw_cfg.]ioport=@[::] * or - * [fw_cfg.]mmio=@[::] + * [qemu_fw_cfg.]mmio=@[::] * * where: * := size of ioport or mmio range @@ -21,9 +21,9 @@ * := (optional) offset of data register * * e.g.: - * fw_cfg.ioport=2@0x510:0:1 (the default on x86) + * qemu_fw_cfg.ioport=2@0x510:0:1 (the default on x86) * or - * fw_cfg.mmio=0xA@0x9020000:8:0 (the default on arm) + * qemu_fw_cfg.mmio=0xA@0x9020000:8:0 (the default on arm) */ #include -- cgit v1.2.3-55-g7522 From ff07a23fec3f9ea28d22852097260b38903891c4 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sun, 22 Oct 2017 17:51:09 -0700 Subject: drivers/firmware: psci: Convert timers to use timer_setup() In preparation for unconditionally passing the struct timer_list pointer to all timer callbacks, switch to using the new timer_setup() and from_timer() to pass the timer pointer explicitly. Also adds missing call to destroy_timer_on_stack(). Cc: Mark Rutland Cc: Lorenzo Pieralisi Cc: linux-arm-kernel@lists.infradead.org Signed-off-by: Kees Cook --- drivers/firmware/psci_checker.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/psci_checker.c b/drivers/firmware/psci_checker.c index 56cf825ed779..f3f4f810e5df 100644 --- a/drivers/firmware/psci_checker.c +++ b/drivers/firmware/psci_checker.c @@ -220,7 +220,7 @@ out_free_cpus: return err; } -static void dummy_callback(unsigned long ignored) {} +static void dummy_callback(struct timer_list *unused) {} static int suspend_cpu(int index, bool broadcast) { @@ -287,7 +287,7 @@ static int suspend_test_thread(void *arg) pr_info("CPU %d entering suspend cycles, states 1 through %d\n", cpu, drv->state_count - 1); - setup_timer_on_stack(&wakeup_timer, dummy_callback, 0); + timer_setup_on_stack(&wakeup_timer, dummy_callback, 0); for (i = 0; i < NUM_SUSPEND_CYCLE; ++i) { int index; /* -- cgit v1.2.3-55-g7522 From 811d7e0215fb738fb9a9f0bcb1276516ad161ed1 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 15 Nov 2017 13:00:43 -0800 Subject: firmware: vpd: Destroy vpd sections in remove function vpd sections are initialized during probe and thus should be destroyed in the remove function. Fixes: 049a59db34eb ("firmware: Google VPD sysfs driver") Cc: stable Signed-off-by: Guenter Roeck Reviewed-by: Dmitry Torokhov Tested-by: Randy Dunlap Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/vpd.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c index 35e553b3b190..84217172297b 100644 --- a/drivers/firmware/google/vpd.c +++ b/drivers/firmware/google/vpd.c @@ -298,8 +298,17 @@ static int vpd_probe(struct platform_device *pdev) return vpd_sections_init(entry.cbmem_addr); } +static int vpd_remove(struct platform_device *pdev) +{ + vpd_section_destroy(&ro_vpd); + vpd_section_destroy(&rw_vpd); + + return 0; +} + static struct platform_driver vpd_driver = { .probe = vpd_probe, + .remove = vpd_remove, .driver = { .name = "vpd", }, @@ -324,8 +333,6 @@ static int __init vpd_platform_init(void) static void __exit vpd_platform_exit(void) { - vpd_section_destroy(&ro_vpd); - vpd_section_destroy(&rw_vpd); kobject_put(vpd_kobj); } -- cgit v1.2.3-55-g7522 From e4b28b3c3a405b251fa25db58abe1512814a680a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 15 Nov 2017 13:00:44 -0800 Subject: firmware: vpd: Tie firmware kobject to device lifetime It doesn't make sense to have /sys/firmware/vpd if the device is not instantiated, so tie its lifetime to the device. Fixes: 049a59db34eb ("firmware: Google VPD sysfs driver") Signed-off-by: Guenter Roeck Cc: stable Reviewed-by: Dmitry Torokhov Tested-by: Randy Dunlap Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/vpd.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c index 84217172297b..942e358efa60 100644 --- a/drivers/firmware/google/vpd.c +++ b/drivers/firmware/google/vpd.c @@ -295,7 +295,17 @@ static int vpd_probe(struct platform_device *pdev) if (ret) return ret; - return vpd_sections_init(entry.cbmem_addr); + vpd_kobj = kobject_create_and_add("vpd", firmware_kobj); + if (!vpd_kobj) + return -ENOMEM; + + ret = vpd_sections_init(entry.cbmem_addr); + if (ret) { + kobject_put(vpd_kobj); + return ret; + } + + return 0; } static int vpd_remove(struct platform_device *pdev) @@ -303,6 +313,8 @@ static int vpd_remove(struct platform_device *pdev) vpd_section_destroy(&ro_vpd); vpd_section_destroy(&rw_vpd); + kobject_put(vpd_kobj); + return 0; } @@ -322,10 +334,6 @@ static int __init vpd_platform_init(void) if (IS_ERR(pdev)) return PTR_ERR(pdev); - vpd_kobj = kobject_create_and_add("vpd", firmware_kobj); - if (!vpd_kobj) - return -ENOMEM; - platform_driver_register(&vpd_driver); return 0; @@ -333,7 +341,6 @@ static int __init vpd_platform_init(void) static void __exit vpd_platform_exit(void) { - kobject_put(vpd_kobj); } module_init(vpd_platform_init); -- cgit v1.2.3-55-g7522 From 0631fb8b027f5968c2f5031f0b3ff7be3e4bebcc Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 15 Nov 2017 13:00:45 -0800 Subject: firmware: vpd: Fix platform driver and device registration/unregistration The driver exit function needs to unregister both platform device and driver. Also, during registration, register driver first and perform error checks. Fixes: 049a59db34eb ("firmware: Google VPD sysfs driver") Signed-off-by: Guenter Roeck Cc: stable Tested-by: Randy Dunlap Reviewed-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/firmware/google/vpd.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/google/vpd.c index 942e358efa60..e4b40f2b4627 100644 --- a/drivers/firmware/google/vpd.c +++ b/drivers/firmware/google/vpd.c @@ -326,21 +326,29 @@ static struct platform_driver vpd_driver = { }, }; +static struct platform_device *vpd_pdev; + static int __init vpd_platform_init(void) { - struct platform_device *pdev; + int ret; - pdev = platform_device_register_simple("vpd", -1, NULL, 0); - if (IS_ERR(pdev)) - return PTR_ERR(pdev); + ret = platform_driver_register(&vpd_driver); + if (ret) + return ret; - platform_driver_register(&vpd_driver); + vpd_pdev = platform_device_register_simple("vpd", -1, NULL, 0); + if (IS_ERR(vpd_pdev)) { + platform_driver_unregister(&vpd_driver); + return PTR_ERR(vpd_pdev); + } return 0; } static void __exit vpd_platform_exit(void) { + platform_device_unregister(vpd_pdev); + platform_driver_unregister(&vpd_driver); } module_init(vpd_platform_init); -- cgit v1.2.3-55-g7522 From 23f1b8d938c861ee0bbb786162f7ce0685f722ec Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Mon, 20 Nov 2017 10:55:15 +0100 Subject: fw_cfg: fix driver remove On driver remove(), all objects created during probe() should be removed, but sysfs qemu_fw_cfg/rev file was left. Also reorder functions to match probe() error cleanup code. Cc: stable@vger.kernel.org Signed-off-by: Marc-André Lureau Signed-off-by: Michael S. Tsirkin --- drivers/firmware/qemu_fw_cfg.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/qemu_fw_cfg.c b/drivers/firmware/qemu_fw_cfg.c index 5cfe39f7a45f..deb483064f53 100644 --- a/drivers/firmware/qemu_fw_cfg.c +++ b/drivers/firmware/qemu_fw_cfg.c @@ -582,9 +582,10 @@ static int fw_cfg_sysfs_remove(struct platform_device *pdev) { pr_debug("fw_cfg: unloading.\n"); fw_cfg_sysfs_cache_cleanup(); + sysfs_remove_file(fw_cfg_top_ko, &fw_cfg_rev_attr.attr); + fw_cfg_io_cleanup(); fw_cfg_kset_unregister_recursive(fw_cfg_fname_kset); fw_cfg_kobj_cleanup(fw_cfg_sel_ko); - fw_cfg_io_cleanup(); return 0; } -- cgit v1.2.3-55-g7522 From 81faa5566864e97a5578c4e08c07edf04239abcc Mon Sep 17 00:00:00 2001 From: Olof Johansson Date: Sun, 3 Dec 2017 19:28:33 -0800 Subject: firmware: arm_scpi: Revert updates made during v4.15 merge window Revert "Merge tag 'scpi-updates-4.15' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux into next/drivers" Paraphrased from email from Kevin Hilman: Revert ARM SCPI changes since v4.14. Untested changes caused regressions in SCPI and CPUfreq/DVFS failures on most Amlogic SoCs. Changes reverted for v4.15 so they can be better reviewed and tested. These ARM SCPI changes caused SCPI regressions resulting in CPUfreq failures on most Amlogic SoCs (found by kernelci.org.) Unfortunately, this was not caught in linux-next due to other bugs/panics on these platforms masking this problem so we've only found it since we've fixed the other issues. Since we're already in the -rc cycle, I'd prefer to revert to a known working state (that of v4.14) rather than finding/reverting a subset, which would just lead to another untested state. These changes can then have some time to be better reviewed and tested and resubmitted for v4.16. Kevin Hilman has tested this revert on the affected Amlogic SoCs and verified that we're back to the previous (working) condition. This reverts commit 6710acf2596a29f7351e8165d981645f403e0025, reversing changes made to 4b367f2e8854da34d14bd154ff4432fb49f69b36. Reported-by: Kevin Hilman Signed-off-by: Olof Johansson --- drivers/firmware/arm_scpi.c | 216 ++++++++++++++++++++++++++------------------ 1 file changed, 129 insertions(+), 87 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/arm_scpi.c b/drivers/firmware/arm_scpi.c index dfb373c8ba2a..7da9f1b83ebe 100644 --- a/drivers/firmware/arm_scpi.c +++ b/drivers/firmware/arm_scpi.c @@ -28,7 +28,6 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include -#include #include #include #include @@ -73,13 +72,21 @@ #define MAX_DVFS_DOMAINS 8 #define MAX_DVFS_OPPS 16 - -#define PROTO_REV_MAJOR_MASK GENMASK(31, 16) -#define PROTO_REV_MINOR_MASK GENMASK(15, 0) - -#define FW_REV_MAJOR_MASK GENMASK(31, 24) -#define FW_REV_MINOR_MASK GENMASK(23, 16) -#define FW_REV_PATCH_MASK GENMASK(15, 0) +#define DVFS_LATENCY(hdr) (le32_to_cpu(hdr) >> 16) +#define DVFS_OPP_COUNT(hdr) ((le32_to_cpu(hdr) >> 8) & 0xff) + +#define PROTOCOL_REV_MINOR_BITS 16 +#define PROTOCOL_REV_MINOR_MASK ((1U << PROTOCOL_REV_MINOR_BITS) - 1) +#define PROTOCOL_REV_MAJOR(x) ((x) >> PROTOCOL_REV_MINOR_BITS) +#define PROTOCOL_REV_MINOR(x) ((x) & PROTOCOL_REV_MINOR_MASK) + +#define FW_REV_MAJOR_BITS 24 +#define FW_REV_MINOR_BITS 16 +#define FW_REV_PATCH_MASK ((1U << FW_REV_MINOR_BITS) - 1) +#define FW_REV_MINOR_MASK ((1U << FW_REV_MAJOR_BITS) - 1) +#define FW_REV_MAJOR(x) ((x) >> FW_REV_MAJOR_BITS) +#define FW_REV_MINOR(x) (((x) & FW_REV_MINOR_MASK) >> FW_REV_MINOR_BITS) +#define FW_REV_PATCH(x) ((x) & FW_REV_PATCH_MASK) #define MAX_RX_TIMEOUT (msecs_to_jiffies(30)) @@ -304,6 +311,10 @@ struct clk_get_info { u8 name[20]; } __packed; +struct clk_get_value { + __le32 rate; +} __packed; + struct clk_set_value { __le16 id; __le16 reserved; @@ -317,9 +328,7 @@ struct legacy_clk_set_value { } __packed; struct dvfs_info { - u8 domain; - u8 opp_count; - __le16 latency; + __le32 header; struct { __le32 freq; __le32 m_volt; @@ -342,6 +351,11 @@ struct _scpi_sensor_info { char name[20]; }; +struct sensor_value { + __le32 lo_val; + __le32 hi_val; +} __packed; + struct dev_pstate_set { __le16 dev_id; u8 pstate; @@ -405,20 +419,19 @@ static void scpi_process_cmd(struct scpi_chan *ch, u32 cmd) unsigned int len; if (scpi_info->is_legacy) { - struct legacy_scpi_shared_mem __iomem *mem = - ch->rx_payload; + struct legacy_scpi_shared_mem *mem = ch->rx_payload; /* RX Length is not replied by the legacy Firmware */ len = match->rx_len; - match->status = ioread32(&mem->status); + match->status = le32_to_cpu(mem->status); memcpy_fromio(match->rx_buf, mem->payload, len); } else { - struct scpi_shared_mem __iomem *mem = ch->rx_payload; + struct scpi_shared_mem *mem = ch->rx_payload; len = min(match->rx_len, CMD_SIZE(cmd)); - match->status = ioread32(&mem->status); + match->status = le32_to_cpu(mem->status); memcpy_fromio(match->rx_buf, mem->payload, len); } @@ -432,11 +445,11 @@ static void scpi_process_cmd(struct scpi_chan *ch, u32 cmd) static void scpi_handle_remote_msg(struct mbox_client *c, void *msg) { struct scpi_chan *ch = container_of(c, struct scpi_chan, cl); - struct scpi_shared_mem __iomem *mem = ch->rx_payload; + struct scpi_shared_mem *mem = ch->rx_payload; u32 cmd = 0; if (!scpi_info->is_legacy) - cmd = ioread32(&mem->command); + cmd = le32_to_cpu(mem->command); scpi_process_cmd(ch, cmd); } @@ -446,7 +459,7 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg) unsigned long flags; struct scpi_xfer *t = msg; struct scpi_chan *ch = container_of(c, struct scpi_chan, cl); - struct scpi_shared_mem __iomem *mem = ch->tx_payload; + struct scpi_shared_mem *mem = (struct scpi_shared_mem *)ch->tx_payload; if (t->tx_buf) { if (scpi_info->is_legacy) @@ -465,7 +478,7 @@ static void scpi_tx_prepare(struct mbox_client *c, void *msg) } if (!scpi_info->is_legacy) - iowrite32(t->cmd, &mem->command); + mem->command = cpu_to_le32(t->cmd); } static struct scpi_xfer *get_scpi_xfer(struct scpi_chan *ch) @@ -570,13 +583,13 @@ scpi_clk_get_range(u16 clk_id, unsigned long *min, unsigned long *max) static unsigned long scpi_clk_get_val(u16 clk_id) { int ret; - __le32 rate; + struct clk_get_value clk; __le16 le_clk_id = cpu_to_le16(clk_id); ret = scpi_send_message(CMD_GET_CLOCK_VALUE, &le_clk_id, - sizeof(le_clk_id), &rate, sizeof(rate)); + sizeof(le_clk_id), &clk, sizeof(clk)); - return ret ? ret : le32_to_cpu(rate); + return ret ? ret : le32_to_cpu(clk.rate); } static int scpi_clk_set_val(u16 clk_id, unsigned long rate) @@ -631,35 +644,35 @@ static int opp_cmp_func(const void *opp1, const void *opp2) } static struct scpi_dvfs_info *scpi_dvfs_get_info(u8 domain) -{ - if (domain >= MAX_DVFS_DOMAINS) - return ERR_PTR(-EINVAL); - - return scpi_info->dvfs[domain] ?: ERR_PTR(-EINVAL); -} - -static int scpi_dvfs_populate_info(struct device *dev, u8 domain) { struct scpi_dvfs_info *info; struct scpi_opp *opp; struct dvfs_info buf; int ret, i; + if (domain >= MAX_DVFS_DOMAINS) + return ERR_PTR(-EINVAL); + + if (scpi_info->dvfs[domain]) /* data already populated */ + return scpi_info->dvfs[domain]; + ret = scpi_send_message(CMD_GET_DVFS_INFO, &domain, sizeof(domain), &buf, sizeof(buf)); if (ret) - return ret; + return ERR_PTR(ret); - info = devm_kmalloc(dev, sizeof(*info), GFP_KERNEL); + info = kmalloc(sizeof(*info), GFP_KERNEL); if (!info) - return -ENOMEM; + return ERR_PTR(-ENOMEM); - info->count = buf.opp_count; - info->latency = le16_to_cpu(buf.latency) * 1000; /* uS to nS */ + info->count = DVFS_OPP_COUNT(buf.header); + info->latency = DVFS_LATENCY(buf.header) * 1000; /* uS to nS */ - info->opps = devm_kcalloc(dev, info->count, sizeof(*opp), GFP_KERNEL); - if (!info->opps) - return -ENOMEM; + info->opps = kcalloc(info->count, sizeof(*opp), GFP_KERNEL); + if (!info->opps) { + kfree(info); + return ERR_PTR(-ENOMEM); + } for (i = 0, opp = info->opps; i < info->count; i++, opp++) { opp->freq = le32_to_cpu(buf.opps[i].freq); @@ -669,15 +682,7 @@ static int scpi_dvfs_populate_info(struct device *dev, u8 domain) sort(info->opps, info->count, sizeof(*opp), opp_cmp_func, NULL); scpi_info->dvfs[domain] = info; - return 0; -} - -static void scpi_dvfs_populate(struct device *dev) -{ - int domain; - - for (domain = 0; domain < MAX_DVFS_DOMAINS; domain++) - scpi_dvfs_populate_info(dev, domain); + return info; } static int scpi_dev_domain_id(struct device *dev) @@ -708,6 +713,9 @@ static int scpi_dvfs_get_transition_latency(struct device *dev) if (IS_ERR(info)) return PTR_ERR(info); + if (!info->latency) + return 0; + return info->latency; } @@ -768,19 +776,20 @@ static int scpi_sensor_get_info(u16 sensor_id, struct scpi_sensor_info *info) static int scpi_sensor_get_value(u16 sensor, u64 *val) { __le16 id = cpu_to_le16(sensor); - __le64 value; + struct sensor_value buf; int ret; ret = scpi_send_message(CMD_SENSOR_VALUE, &id, sizeof(id), - &value, sizeof(value)); + &buf, sizeof(buf)); if (ret) return ret; if (scpi_info->is_legacy) - /* only 32-bits supported, upper 32 bits can be junk */ - *val = le32_to_cpup((__le32 *)&value); + /* only 32-bits supported, hi_val can be junk */ + *val = le32_to_cpu(buf.lo_val); else - *val = le64_to_cpu(value); + *val = (u64)le32_to_cpu(buf.hi_val) << 32 | + le32_to_cpu(buf.lo_val); return 0; } @@ -853,19 +862,23 @@ static int scpi_init_versions(struct scpi_drvinfo *info) static ssize_t protocol_version_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%lu.%lu\n", - FIELD_GET(PROTO_REV_MAJOR_MASK, scpi_info->protocol_version), - FIELD_GET(PROTO_REV_MINOR_MASK, scpi_info->protocol_version)); + struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev); + + return sprintf(buf, "%d.%d\n", + PROTOCOL_REV_MAJOR(scpi_info->protocol_version), + PROTOCOL_REV_MINOR(scpi_info->protocol_version)); } static DEVICE_ATTR_RO(protocol_version); static ssize_t firmware_version_show(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "%lu.%lu.%lu\n", - FIELD_GET(FW_REV_MAJOR_MASK, scpi_info->firmware_version), - FIELD_GET(FW_REV_MINOR_MASK, scpi_info->firmware_version), - FIELD_GET(FW_REV_PATCH_MASK, scpi_info->firmware_version)); + struct scpi_drvinfo *scpi_info = dev_get_drvdata(dev); + + return sprintf(buf, "%d.%d.%d\n", + FW_REV_MAJOR(scpi_info->firmware_version), + FW_REV_MINOR(scpi_info->firmware_version), + FW_REV_PATCH(scpi_info->firmware_version)); } static DEVICE_ATTR_RO(firmware_version); @@ -876,13 +889,39 @@ static struct attribute *versions_attrs[] = { }; ATTRIBUTE_GROUPS(versions); -static void scpi_free_channels(void *data) +static void +scpi_free_channels(struct device *dev, struct scpi_chan *pchan, int count) { - struct scpi_drvinfo *info = data; int i; - for (i = 0; i < info->num_chans; i++) - mbox_free_channel(info->channels[i].chan); + for (i = 0; i < count && pchan->chan; i++, pchan++) { + mbox_free_channel(pchan->chan); + devm_kfree(dev, pchan->xfers); + devm_iounmap(dev, pchan->rx_payload); + } +} + +static int scpi_remove(struct platform_device *pdev) +{ + int i; + struct device *dev = &pdev->dev; + struct scpi_drvinfo *info = platform_get_drvdata(pdev); + + scpi_info = NULL; /* stop exporting SCPI ops through get_scpi_ops */ + + of_platform_depopulate(dev); + sysfs_remove_groups(&dev->kobj, versions_groups); + scpi_free_channels(dev, info->channels, info->num_chans); + platform_set_drvdata(pdev, NULL); + + for (i = 0; i < MAX_DVFS_DOMAINS && info->dvfs[i]; i++) { + kfree(info->dvfs[i]->opps); + kfree(info->dvfs[i]); + } + devm_kfree(dev, info->channels); + devm_kfree(dev, info); + + return 0; } #define MAX_SCPI_XFERS 10 @@ -913,6 +952,7 @@ static int scpi_probe(struct platform_device *pdev) { int count, idx, ret; struct resource res; + struct scpi_chan *scpi_chan; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; @@ -929,19 +969,13 @@ static int scpi_probe(struct platform_device *pdev) return -ENODEV; } - scpi_info->channels = devm_kcalloc(dev, count, sizeof(struct scpi_chan), - GFP_KERNEL); - if (!scpi_info->channels) + scpi_chan = devm_kcalloc(dev, count, sizeof(*scpi_chan), GFP_KERNEL); + if (!scpi_chan) return -ENOMEM; - ret = devm_add_action(dev, scpi_free_channels, scpi_info); - if (ret) - return ret; - - for (; scpi_info->num_chans < count; scpi_info->num_chans++) { + for (idx = 0; idx < count; idx++) { resource_size_t size; - int idx = scpi_info->num_chans; - struct scpi_chan *pchan = scpi_info->channels + idx; + struct scpi_chan *pchan = scpi_chan + idx; struct mbox_client *cl = &pchan->cl; struct device_node *shmem = of_parse_phandle(np, "shmem", idx); @@ -949,14 +983,15 @@ static int scpi_probe(struct platform_device *pdev) of_node_put(shmem); if (ret) { dev_err(dev, "failed to get SCPI payload mem resource\n"); - return ret; + goto err; } size = resource_size(&res); pchan->rx_payload = devm_ioremap(dev, res.start, size); if (!pchan->rx_payload) { dev_err(dev, "failed to ioremap SCPI payload\n"); - return -EADDRNOTAVAIL; + ret = -EADDRNOTAVAIL; + goto err; } pchan->tx_payload = pchan->rx_payload + (size >> 1); @@ -982,11 +1017,17 @@ static int scpi_probe(struct platform_device *pdev) dev_err(dev, "failed to get channel%d err %d\n", idx, ret); } +err: + scpi_free_channels(dev, scpi_chan, idx); + scpi_info = NULL; return ret; } + scpi_info->channels = scpi_chan; + scpi_info->num_chans = count; scpi_info->commands = scpi_std_commands; - scpi_info->scpi_ops = &scpi_ops; + + platform_set_drvdata(pdev, scpi_info); if (scpi_info->is_legacy) { /* Replace with legacy variants */ @@ -1002,23 +1043,23 @@ static int scpi_probe(struct platform_device *pdev) ret = scpi_init_versions(scpi_info); if (ret) { dev_err(dev, "incorrect or no SCP firmware found\n"); + scpi_remove(pdev); return ret; } - scpi_dvfs_populate(dev); - - _dev_info(dev, "SCP Protocol %lu.%lu Firmware %lu.%lu.%lu version\n", - FIELD_GET(PROTO_REV_MAJOR_MASK, scpi_info->protocol_version), - FIELD_GET(PROTO_REV_MINOR_MASK, scpi_info->protocol_version), - FIELD_GET(FW_REV_MAJOR_MASK, scpi_info->firmware_version), - FIELD_GET(FW_REV_MINOR_MASK, scpi_info->firmware_version), - FIELD_GET(FW_REV_PATCH_MASK, scpi_info->firmware_version)); + _dev_info(dev, "SCP Protocol %d.%d Firmware %d.%d.%d version\n", + PROTOCOL_REV_MAJOR(scpi_info->protocol_version), + PROTOCOL_REV_MINOR(scpi_info->protocol_version), + FW_REV_MAJOR(scpi_info->firmware_version), + FW_REV_MINOR(scpi_info->firmware_version), + FW_REV_PATCH(scpi_info->firmware_version)); + scpi_info->scpi_ops = &scpi_ops; - ret = devm_device_add_groups(dev, versions_groups); + ret = sysfs_create_groups(&dev->kobj, versions_groups); if (ret) dev_err(dev, "unable to create sysfs version group\n"); - return devm_of_platform_populate(dev); + return of_platform_populate(dev->of_node, NULL, NULL, dev); } static const struct of_device_id scpi_of_match[] = { @@ -1035,6 +1076,7 @@ static struct platform_driver scpi_driver = { .of_match_table = scpi_of_match, }, .probe = scpi_probe, + .remove = scpi_remove, }; module_platform_driver(scpi_driver); -- cgit v1.2.3-55-g7522 From af97a77bc01ce49a466f9d4c0125479e2e2230b6 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 6 Dec 2017 09:50:08 +0000 Subject: efi: Move some sysfs files to be read-only by root Thanks to the scripts/leaking_addresses.pl script, it was found that some EFI values should not be readable by non-root users. So make them root-only, and to do that, add a __ATTR_RO_MODE() macro to make this easier, and use it in other places at the same time. Reported-by: Linus Torvalds Tested-by: Dave Young Signed-off-by: Greg Kroah-Hartman Signed-off-by: Ard Biesheuvel Cc: H. Peter Anvin Cc: Matt Fleming Cc: Peter Zijlstra Cc: Thomas Gleixner Cc: linux-efi@vger.kernel.org Cc: stable Link: http://lkml.kernel.org/r/20171206095010.24170-2-ard.biesheuvel@linaro.org Signed-off-by: Ingo Molnar --- drivers/firmware/efi/efi.c | 3 +-- drivers/firmware/efi/esrt.c | 15 ++++++--------- drivers/firmware/efi/runtime-map.c | 10 +++++----- include/linux/sysfs.h | 6 ++++++ 4 files changed, 18 insertions(+), 16 deletions(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index f70febf680c3..c3eefa126e3b 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -143,8 +143,7 @@ static ssize_t systab_show(struct kobject *kobj, return str - buf; } -static struct kobj_attribute efi_attr_systab = - __ATTR(systab, 0400, systab_show, NULL); +static struct kobj_attribute efi_attr_systab = __ATTR_RO_MODE(systab, 0400); #define EFI_FIELD(var) efi.var diff --git a/drivers/firmware/efi/esrt.c b/drivers/firmware/efi/esrt.c index bd7ed3c1148a..7aae2483fcb9 100644 --- a/drivers/firmware/efi/esrt.c +++ b/drivers/firmware/efi/esrt.c @@ -106,7 +106,7 @@ static const struct sysfs_ops esre_attr_ops = { }; /* Generic ESRT Entry ("ESRE") support. */ -static ssize_t esre_fw_class_show(struct esre_entry *entry, char *buf) +static ssize_t fw_class_show(struct esre_entry *entry, char *buf) { char *str = buf; @@ -117,18 +117,16 @@ static ssize_t esre_fw_class_show(struct esre_entry *entry, char *buf) return str - buf; } -static struct esre_attribute esre_fw_class = __ATTR(fw_class, 0400, - esre_fw_class_show, NULL); +static struct esre_attribute esre_fw_class = __ATTR_RO_MODE(fw_class, 0400); #define esre_attr_decl(name, size, fmt) \ -static ssize_t esre_##name##_show(struct esre_entry *entry, char *buf) \ +static ssize_t name##_show(struct esre_entry *entry, char *buf) \ { \ return sprintf(buf, fmt "\n", \ le##size##_to_cpu(entry->esre.esre1->name)); \ } \ \ -static struct esre_attribute esre_##name = __ATTR(name, 0400, \ - esre_##name##_show, NULL) +static struct esre_attribute esre_##name = __ATTR_RO_MODE(name, 0400) esre_attr_decl(fw_type, 32, "%u"); esre_attr_decl(fw_version, 32, "%u"); @@ -193,14 +191,13 @@ static int esre_create_sysfs_entry(void *esre, int entry_num) /* support for displaying ESRT fields at the top level */ #define esrt_attr_decl(name, size, fmt) \ -static ssize_t esrt_##name##_show(struct kobject *kobj, \ +static ssize_t name##_show(struct kobject *kobj, \ struct kobj_attribute *attr, char *buf)\ { \ return sprintf(buf, fmt "\n", le##size##_to_cpu(esrt->name)); \ } \ \ -static struct kobj_attribute esrt_##name = __ATTR(name, 0400, \ - esrt_##name##_show, NULL) +static struct kobj_attribute esrt_##name = __ATTR_RO_MODE(name, 0400) esrt_attr_decl(fw_resource_count, 32, "%u"); esrt_attr_decl(fw_resource_count_max, 32, "%u"); diff --git a/drivers/firmware/efi/runtime-map.c b/drivers/firmware/efi/runtime-map.c index 8e64b77aeac9..f377609ff141 100644 --- a/drivers/firmware/efi/runtime-map.c +++ b/drivers/firmware/efi/runtime-map.c @@ -63,11 +63,11 @@ static ssize_t map_attr_show(struct kobject *kobj, struct attribute *attr, return map_attr->show(entry, buf); } -static struct map_attribute map_type_attr = __ATTR_RO(type); -static struct map_attribute map_phys_addr_attr = __ATTR_RO(phys_addr); -static struct map_attribute map_virt_addr_attr = __ATTR_RO(virt_addr); -static struct map_attribute map_num_pages_attr = __ATTR_RO(num_pages); -static struct map_attribute map_attribute_attr = __ATTR_RO(attribute); +static struct map_attribute map_type_attr = __ATTR_RO_MODE(type, 0400); +static struct map_attribute map_phys_addr_attr = __ATTR_RO_MODE(phys_addr, 0400); +static struct map_attribute map_virt_addr_attr = __ATTR_RO_MODE(virt_addr, 0400); +static struct map_attribute map_num_pages_attr = __ATTR_RO_MODE(num_pages, 0400); +static struct map_attribute map_attribute_attr = __ATTR_RO_MODE(attribute, 0400); /* * These are default attributes that are added for every memmap entry. diff --git a/include/linux/sysfs.h b/include/linux/sysfs.h index e32dfe098e82..40839c02d28c 100644 --- a/include/linux/sysfs.h +++ b/include/linux/sysfs.h @@ -117,6 +117,12 @@ struct attribute_group { .show = _name##_show, \ } +#define __ATTR_RO_MODE(_name, _mode) { \ + .attr = { .name = __stringify(_name), \ + .mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \ + .show = _name##_show, \ +} + #define __ATTR_WO(_name) { \ .attr = { .name = __stringify(_name), .mode = S_IWUSR }, \ .store = _name##_store, \ -- cgit v1.2.3-55-g7522 From 89c5a2d34bda58319e3075e8e7dd727ea25a435c Mon Sep 17 00:00:00 2001 From: Pan Bian Date: Wed, 6 Dec 2017 09:50:09 +0000 Subject: efi/esrt: Use memunmap() instead of kfree() to free the remapping The remapping result of memremap() should be freed with memunmap(), not kfree(). Signed-off-by: Pan Bian Signed-off-by: Ard Biesheuvel Cc: Cc: H. Peter Anvin Cc: Linus Torvalds Cc: Matt Fleming Cc: Peter Zijlstra Cc: Thomas Gleixner Cc: linux-efi@vger.kernel.org Link: http://lkml.kernel.org/r/20171206095010.24170-3-ard.biesheuvel@linaro.org Signed-off-by: Ingo Molnar --- drivers/firmware/efi/esrt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/esrt.c b/drivers/firmware/efi/esrt.c index 7aae2483fcb9..c47e0c6ec00f 100644 --- a/drivers/firmware/efi/esrt.c +++ b/drivers/firmware/efi/esrt.c @@ -428,7 +428,7 @@ err_remove_group: err_remove_esrt: kobject_put(esrt_kobj); err: - kfree(esrt); + memunmap(esrt); esrt = NULL; return error; } -- cgit v1.2.3-55-g7522 From 0b02e448a2ebb46eb9be4f1bdfc87112bd420cbf Mon Sep 17 00:00:00 2001 From: Dave Young Date: Wed, 6 Dec 2017 09:50:10 +0000 Subject: efi: Add comment to avoid future expanding of sysfs systab /sys/firmware/efi/systab shows several different values, it breaks sysfs one file one value design. But since there are already userspace tools depend on it eg. kexec-tools so add code comment to alert future expanding of this file. Signed-off-by: Dave Young Signed-off-by: Ard Biesheuvel Reviewed-by: Greg Kroah-Hartman Cc: H. Peter Anvin Cc: Linus Torvalds Cc: Matt Fleming Cc: Peter Zijlstra Cc: Thomas Gleixner Cc: linux-efi@vger.kernel.org Link: http://lkml.kernel.org/r/20171206095010.24170-4-ard.biesheuvel@linaro.org Signed-off-by: Ingo Molnar --- drivers/firmware/efi/efi.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/firmware') diff --git a/drivers/firmware/efi/efi.c b/drivers/firmware/efi/efi.c index c3eefa126e3b..557a47829d03 100644 --- a/drivers/firmware/efi/efi.c +++ b/drivers/firmware/efi/efi.c @@ -109,6 +109,8 @@ struct kobject *efi_kobj; /* * Let's not leave out systab information that snuck into * the efivars driver + * Note, do not add more fields in systab sysfs file as it breaks sysfs + * one value per file rule! */ static ssize_t systab_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) -- cgit v1.2.3-55-g7522