Skip to content

Commit

Permalink
firmware/raspberrypi: Add a get_throttled sysfs file
Browse files Browse the repository at this point in the history
Under-voltage due to inadequate power supplies is a recurring problem for
new Raspberry Pi users. There are visual indications that an
under-voltage situation is occuring like blinking power led and a
lightning icon on the desktop (not shown when using the vc4 driver), but
for new users it's not obvious that this signifies a critical situation.

This patch provides a twofold improvement to the situation:

Firstly it logs under-voltage events to the kernel log. This provides
information also for headless installations.

Secondly it provides a sysfs file to read the value. This improves on
'vcgencmd' by providing change notification. Userspace can poll on the
file and be notified of changes to the value.
A script can poll the file and use dbus notification to put a windows on
the desktop with information about the severity with a recommendation to
change the power supply. A link to more information can also be provided.
Only changes to the sticky bits are reported (cleared between readings).

Signed-off-by: Noralf Trønnes <noralf@tronnes.org>

Prevent voltage low warnings from filling log

Although the correct fix for low voltage warnings is to
improve the power supply, the current implementation
of the detection can fill the log if the warning
happens freqently. This replaces the logging with
slightly custom ratelimited logging.

Signed-off-by: James Hughes <james.hughes@raspberrypi.org>

Reduce log spam when mailbox call not implemented

This changes the logging message when a mailbox call
fails to the dev_dbg level. In addition, it fixes the
low voltage detection logging code so that if the
mailbox call doies fails, it logs at error level
and flags so the call is no longer attempted.

Signed-off-by: James Hughes <james.hughes@raspberrypi.org>
  • Loading branch information
notro authored and popcornmix committed Oct 1, 2018
1 parent fb710b6 commit e460b56
Showing 1 changed file with 160 additions and 1 deletion.
161 changes: 160 additions & 1 deletion drivers/firmware/raspberrypi.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <soc/bcm2835/raspberrypi-firmware.h>

#define MBOX_MSG(chan, data28) (((data28) & ~0xf) | ((chan) & 0xf))
Expand All @@ -25,11 +26,46 @@

static struct platform_device *rpi_hwmon;

#define UNDERVOLTAGE_BIT BIT(0)


/*
* This section defines some rate limited logging that prevent
* repeated messages at much lower Hz than the default kernel settings.
* It's usually 5s, this is 5 minutes.
* Burst 3 means you may get three messages 'quickly', before
* the ratelimiting kicks in.
*/
#define LOCAL_RATELIMIT_INTERVAL (5 * 60 * HZ)
#define LOCAL_RATELIMIT_BURST 3

#ifdef CONFIG_PRINTK
#define printk_ratelimited_local(fmt, ...) \
({ \
static DEFINE_RATELIMIT_STATE(_rs, \
LOCAL_RATELIMIT_INTERVAL, \
LOCAL_RATELIMIT_BURST); \
\
if (__ratelimit(&_rs)) \
printk(fmt, ##__VA_ARGS__); \
})
#else
#define printk_ratelimited_local(fmt, ...) \
no_printk(fmt, ##__VA_ARGS__)
#endif

#define pr_crit_ratelimited_local(fmt, ...) \
printk_ratelimited_local(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_info_ratelimited_local(fmt, ...) \
printk_ratelimited_local(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)


struct rpi_firmware {
struct mbox_client cl;
struct mbox_chan *chan; /* The property channel. */
struct completion c;
u32 enabled;
struct delayed_work get_throttled_poll_work;
};

static struct platform_device *g_pdev;
Expand Down Expand Up @@ -119,7 +155,7 @@ int rpi_firmware_property_list(struct rpi_firmware *fw,
* error, if there were multiple tags in the request.
* But single-tag is the most common, so go with it.
*/
dev_err(fw->cl.dev, "Request 0x%08x returned status 0x%08x\n",
dev_dbg(fw->cl.dev, "Request 0x%08x returned status 0x%08x\n",
buf[2], buf[1]);
ret = -EINVAL;
}
Expand Down Expand Up @@ -174,6 +210,118 @@ int rpi_firmware_property(struct rpi_firmware *fw,
}
EXPORT_SYMBOL_GPL(rpi_firmware_property);

static int rpi_firmware_get_throttled(struct rpi_firmware *fw, u32 *value)
{
static int old_firmware;
static ktime_t old_timestamp;
static u32 old_value;
u32 new_sticky, old_sticky, new_uv, old_uv;
ktime_t new_timestamp;
s64 elapsed_ms;
int ret;

if (!fw)
return -EBUSY;

if (old_firmware)
return -EINVAL;

/*
* We can't run faster than the sticky shift (100ms) since we get
* flipping in the sticky bits that are cleared.
* This happens on polling, so just return the previous value.
*/
new_timestamp = ktime_get();
elapsed_ms = ktime_ms_delta(new_timestamp, old_timestamp);
if (elapsed_ms < 150) {
*value = old_value;
return 0;
}
old_timestamp = new_timestamp;

/* Clear sticky bits */
*value = 0xffff;

ret = rpi_firmware_property(fw, RPI_FIRMWARE_GET_THROTTLED,
value, sizeof(*value));

if (ret) {
/* If the mailbox call fails once, then it will continue to
* fail in the future, so no point in continuing to call it
* Usual failure reason is older firmware
*/
old_firmware = 1;
dev_err(fw->cl.dev, "Get Throttled mailbox call failed");

return ret;
}

new_sticky = *value >> 16;
old_sticky = old_value >> 16;
old_value = *value;

/* Only notify about changes in the sticky bits */
if (new_sticky == old_sticky)
return 0;

new_uv = new_sticky & UNDERVOLTAGE_BIT;
old_uv = old_sticky & UNDERVOLTAGE_BIT;

if (new_uv != old_uv) {
if (new_uv)
pr_crit_ratelimited_local(
"Under-voltage detected! (0x%08x)\n",
*value);
else
pr_info_ratelimited_local(
"Voltage normalised (0x%08x)\n",
*value);
}

sysfs_notify(&fw->cl.dev->kobj, NULL, "get_throttled");

return 0;
}

static void get_throttled_poll(struct work_struct *work)
{
struct rpi_firmware *fw = container_of(work, struct rpi_firmware,
get_throttled_poll_work.work);
u32 dummy;
int ret;

ret = rpi_firmware_get_throttled(fw, &dummy);

/* Only reschedule if we are getting valid responses */
if (!ret)
schedule_delayed_work(&fw->get_throttled_poll_work, 2 * HZ);
}

static ssize_t get_throttled_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct rpi_firmware *fw = dev_get_drvdata(dev);
u32 value;
int ret;

ret = rpi_firmware_get_throttled(fw, &value);
if (ret)
return ret;

return sprintf(buf, "%x\n", value);
}

static DEVICE_ATTR_RO(get_throttled);

static struct attribute *rpi_firmware_dev_attrs[] = {
&dev_attr_get_throttled.attr,
NULL,
};

static const struct attribute_group rpi_firmware_dev_group = {
.attrs = rpi_firmware_dev_attrs,
};

static void
rpi_firmware_print_firmware_revision(struct rpi_firmware *fw)
{
Expand Down Expand Up @@ -212,6 +360,11 @@ static int rpi_firmware_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rpi_firmware *fw;
int ret;

ret = devm_device_add_group(dev, &rpi_firmware_dev_group);
if (ret)
return ret;

fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL);
if (!fw)
Expand All @@ -230,22 +383,28 @@ static int rpi_firmware_probe(struct platform_device *pdev)
}

init_completion(&fw->c);
INIT_DELAYED_WORK(&fw->get_throttled_poll_work, get_throttled_poll);

platform_set_drvdata(pdev, fw);
g_pdev = pdev;

rpi_firmware_print_firmware_revision(fw);
rpi_register_hwmon_driver(dev, fw);

schedule_delayed_work(&fw->get_throttled_poll_work, 0);

return 0;
}

static int rpi_firmware_remove(struct platform_device *pdev)
{
struct rpi_firmware *fw = platform_get_drvdata(pdev);

cancel_delayed_work_sync(&fw->get_throttled_poll_work);

platform_device_unregister(rpi_hwmon);
rpi_hwmon = NULL;

mbox_free_channel(fw->chan);
g_pdev = NULL;

Expand Down

0 comments on commit e460b56

Please sign in to comment.