From 2e91838bf7ffdedabdb29e091207d6531d04ef4f Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 9 Aug 2016 22:34:31 -0700 Subject: watchdog: core: Fix devres_alloc() allocation size Coverity reports: Passing argument 152UL /* sizeof (*wdd) */ to function __devres_alloc_node and then casting the return value to struct watchdog_device ** is suspicious. Allocation size needs to be sizeof(*rcwdd), not sizeof(*wdd). Fixes: 83fbae5a148c ("watchdog: Add a device managed API for ...") Cc: Neil Armstrong Signed-off-by: Guenter Roeck Acked-by: Neil Armstrong Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/watchdog_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 6abb83cd7681..74265b2f806c 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -349,7 +349,7 @@ int devm_watchdog_register_device(struct device *dev, struct watchdog_device **rcwdd; int ret; - rcwdd = devres_alloc(devm_watchdog_unregister_device, sizeof(*wdd), + rcwdd = devres_alloc(devm_watchdog_unregister_device, sizeof(*rcwdd), GFP_KERNEL); if (!rcwdd) return -ENOMEM; -- cgit From c97344f73fdc35b27c7315c5e7c5decd4ce10467 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 9 Aug 2016 22:35:58 -0700 Subject: watchdog: dw_wdt: Read clock rate only once and validate it Coverity reports: divide_by_zero: In expression readl(dw_wdt->regs + 8) / clk_get_rate(dw_wdt->clk), division by expression clk_get_rate(dw_wdt->clk) which may be zero has undefined behavior. The clock used for the watchdog timer won't change its rate, so read it only once during probe. Also validate it and abort the probe function with an error if it is 0. Cc: Douglas Anderson Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/dw_wdt.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c index 2acb51cf5504..3c6a3de13a1b 100644 --- a/drivers/watchdog/dw_wdt.c +++ b/drivers/watchdog/dw_wdt.c @@ -54,6 +54,7 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " struct dw_wdt { void __iomem *regs; struct clk *clk; + unsigned long rate; struct notifier_block restart_handler; struct watchdog_device wdd; }; @@ -72,7 +73,7 @@ static inline int dw_wdt_top_in_seconds(struct dw_wdt *dw_wdt, unsigned top) * There are 16 possible timeout values in 0..15 where the number of * cycles is 2 ^ (16 + i) and the watchdog counts down. */ - return (1U << (16 + top)) / clk_get_rate(dw_wdt->clk); + return (1U << (16 + top)) / dw_wdt->rate; } static int dw_wdt_get_top(struct dw_wdt *dw_wdt) @@ -163,7 +164,7 @@ static unsigned int dw_wdt_get_timeleft(struct watchdog_device *wdd) struct dw_wdt *dw_wdt = to_dw_wdt(wdd); return readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET) / - clk_get_rate(dw_wdt->clk); + dw_wdt->rate; } static const struct watchdog_info dw_wdt_ident = { @@ -231,6 +232,12 @@ static int dw_wdt_drv_probe(struct platform_device *pdev) if (ret) return ret; + dw_wdt->rate = clk_get_rate(dw_wdt->clk); + if (dw_wdt->rate == 0) { + ret = -EINVAL; + goto out_disable_clk; + } + wdd = &dw_wdt->wdd; wdd->info = &dw_wdt_ident; wdd->ops = &dw_wdt_ops; -- cgit From d2a0015174213cc518b2b83d234c09f548f8ac32 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 11 Aug 2016 23:23:40 +0200 Subject: watchdog: pcwd_usb: don't print error when allocating urb fails kmalloc will print enough information in case of failure. Signed-off-by: Wolfram Sang Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/pcwd_usb.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/pcwd_usb.c b/drivers/watchdog/pcwd_usb.c index 68952d9ccf83..99ebf6ea3de6 100644 --- a/drivers/watchdog/pcwd_usb.c +++ b/drivers/watchdog/pcwd_usb.c @@ -666,10 +666,8 @@ static int usb_pcwd_probe(struct usb_interface *interface, /* allocate the urb's */ usb_pcwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!usb_pcwd->intr_urb) { - pr_err("Out of memory\n"); + if (!usb_pcwd->intr_urb) goto error; - } /* initialise the intr urb's */ usb_fill_int_urb(usb_pcwd->intr_urb, udev, pipe, -- cgit From 9d6b4efc16b3e8e7414fcd6aab8fec42756ba920 Mon Sep 17 00:00:00 2001 From: Shubhrajyoti Datta Date: Fri, 12 Aug 2016 12:17:01 +0530 Subject: watchdog: xilinx: Add clock support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the clock. Currently we enable at probe and relinquish at remove. Reviewed-by: Guenter Roeck Acked-by: Sören Brinkmann Signed-off-by: Shubhrajyoti Datta Acked-by: Rob Herring Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/of_xilinx_wdt.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/of_xilinx_wdt.c b/drivers/watchdog/of_xilinx_wdt.c index b2e1b4cbbdc1..fae7fe929ea3 100644 --- a/drivers/watchdog/of_xilinx_wdt.c +++ b/drivers/watchdog/of_xilinx_wdt.c @@ -10,6 +10,7 @@ * 2 of the License, or (at your option) any later version. */ +#include #include #include #include @@ -45,6 +46,7 @@ struct xwdt_device { u32 wdt_interval; spinlock_t spinlock; struct watchdog_device xilinx_wdt_wdd; + struct clk *clk; }; static int xilinx_wdt_start(struct watchdog_device *wdd) @@ -195,16 +197,30 @@ static int xwdt_probe(struct platform_device *pdev) spin_lock_init(&xdev->spinlock); watchdog_set_drvdata(xilinx_wdt_wdd, xdev); + xdev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(xdev->clk)) { + if (PTR_ERR(xdev->clk) == -ENOENT) + xdev->clk = NULL; + else + return PTR_ERR(xdev->clk); + } + + rc = clk_prepare_enable(xdev->clk); + if (rc) { + dev_err(&pdev->dev, "unable to enable clock\n"); + return rc; + } + rc = xwdt_selftest(xdev); if (rc == XWT_TIMER_FAILED) { dev_err(&pdev->dev, "SelfTest routine error\n"); - return rc; + goto err_clk_disable; } rc = watchdog_register_device(xilinx_wdt_wdd); if (rc) { dev_err(&pdev->dev, "Cannot register watchdog (err=%d)\n", rc); - return rc; + goto err_clk_disable; } dev_info(&pdev->dev, "Xilinx Watchdog Timer at %p with timeout %ds\n", @@ -213,6 +229,10 @@ static int xwdt_probe(struct platform_device *pdev) platform_set_drvdata(pdev, xdev); return 0; +err_clk_disable: + clk_disable_unprepare(xdev->clk); + + return rc; } static int xwdt_remove(struct platform_device *pdev) @@ -220,6 +240,7 @@ static int xwdt_remove(struct platform_device *pdev) struct xwdt_device *xdev = platform_get_drvdata(pdev); watchdog_unregister_device(&xdev->xilinx_wdt_wdd); + clk_disable_unprepare(xdev->clk); return 0; } -- cgit From 217209db0204ae1fa5f30af804525863e852c35d Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Wed, 10 Aug 2016 18:18:03 +0200 Subject: watchdog: ziirave_wdt: Add support to upload the firmware. This patch adds and entry to the sysfs to start firmware upload process on the specified device with the requested firmware. The uploading of the firmware needs only to happen once per firmware upgrade, as the firmware is stored in persistent storage. If the firmware upload or the firmware verification fails then we print and error message and exit. Signed-off-by: Enric Balletbo i Serra Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/ziirave_wdt.c | 409 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 404 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/ziirave_wdt.c b/drivers/watchdog/ziirave_wdt.c index fa1efef3c96e..b4e0cea5a64e 100644 --- a/drivers/watchdog/ziirave_wdt.c +++ b/drivers/watchdog/ziirave_wdt.c @@ -18,7 +18,10 @@ * GNU General Public License for more details. */ +#include #include +#include +#include #include #include #include @@ -36,6 +39,8 @@ #define ZIIRAVE_STATE_OFF 0x1 #define ZIIRAVE_STATE_ON 0x2 +#define ZIIRAVE_FW_NAME "ziirave_wdt.fw" + static char *ziirave_reasons[] = {"power cycle", "hw watchdog", NULL, NULL, "host request", NULL, "illegal configuration", "illegal instruction", "illegal trap", @@ -50,12 +55,35 @@ static char *ziirave_reasons[] = {"power cycle", "hw watchdog", NULL, NULL, #define ZIIRAVE_WDT_PING 0x9 #define ZIIRAVE_WDT_RESET_DURATION 0xa +#define ZIIRAVE_FIRM_PKT_TOTAL_SIZE 20 +#define ZIIRAVE_FIRM_PKT_DATA_SIZE 16 +#define ZIIRAVE_FIRM_FLASH_MEMORY_START 0x1600 +#define ZIIRAVE_FIRM_FLASH_MEMORY_END 0x2bbf + +/* Received and ready for next Download packet. */ +#define ZIIRAVE_FIRM_DOWNLOAD_ACK 1 +/* Currently writing to flash. Retry Download status in a moment! */ +#define ZIIRAVE_FIRM_DOWNLOAD_BUSY 2 + +/* Wait for ACK timeout in ms */ +#define ZIIRAVE_FIRM_WAIT_FOR_ACK_TIMEOUT 50 + +/* Firmware commands */ +#define ZIIRAVE_CMD_DOWNLOAD_START 0x10 +#define ZIIRAVE_CMD_DOWNLOAD_END 0x11 +#define ZIIRAVE_CMD_DOWNLOAD_SET_READ_ADDR 0x12 +#define ZIIRAVE_CMD_DOWNLOAD_READ_BYTE 0x13 +#define ZIIRAVE_CMD_RESET_PROCESSOR 0x0b +#define ZIIRAVE_CMD_JUMP_TO_BOOTLOADER 0x0c +#define ZIIRAVE_CMD_DOWNLOAD_PACKET 0x0e + struct ziirave_wdt_rev { unsigned char major; unsigned char minor; }; struct ziirave_wdt_data { + struct mutex sysfs_mutex; struct watchdog_device wdd; struct ziirave_wdt_rev bootloader_rev; struct ziirave_wdt_rev firmware_rev; @@ -146,6 +174,293 @@ static unsigned int ziirave_wdt_get_timeleft(struct watchdog_device *wdd) return ret; } +static int ziirave_firm_wait_for_ack(struct watchdog_device *wdd) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + int ret; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(ZIIRAVE_FIRM_WAIT_FOR_ACK_TIMEOUT); + do { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + + usleep_range(5000, 10000); + + ret = i2c_smbus_read_byte(client); + if (ret < 0) { + dev_err(&client->dev, "Failed to read byte\n"); + return ret; + } + } while (ret == ZIIRAVE_FIRM_DOWNLOAD_BUSY); + + return ret == ZIIRAVE_FIRM_DOWNLOAD_ACK ? 0 : -EIO; +} + +static int ziirave_firm_set_read_addr(struct watchdog_device *wdd, u16 addr) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + u8 address[2]; + + address[0] = addr & 0xff; + address[1] = (addr >> 8) & 0xff; + + return i2c_smbus_write_block_data(client, + ZIIRAVE_CMD_DOWNLOAD_SET_READ_ADDR, + ARRAY_SIZE(address), address); +} + +static int ziirave_firm_write_block_data(struct watchdog_device *wdd, + u8 command, u8 length, const u8 *data, + bool wait_for_ack) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + int ret; + + ret = i2c_smbus_write_block_data(client, command, length, data); + if (ret) { + dev_err(&client->dev, + "Failed to send command 0x%02x: %d\n", command, ret); + return ret; + } + + if (wait_for_ack) + ret = ziirave_firm_wait_for_ack(wdd); + + return ret; +} + +static int ziirave_firm_write_byte(struct watchdog_device *wdd, u8 command, + u8 byte, bool wait_for_ack) +{ + return ziirave_firm_write_block_data(wdd, command, 1, &byte, + wait_for_ack); +} + +/* + * ziirave_firm_write_pkt() - Build and write a firmware packet + * + * A packet to send to the firmware is composed by following bytes: + * Length | Addr0 | Addr1 | Data0 .. Data15 | Checksum | + * Where, + * Length: A data byte containing the length of the data. + * Addr0: Low byte of the address. + * Addr1: High byte of the address. + * Data0 .. Data15: Array of 16 bytes of data. + * Checksum: Checksum byte to verify data integrity. + */ +static int ziirave_firm_write_pkt(struct watchdog_device *wdd, + const struct ihex_binrec *rec) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + u8 i, checksum = 0, packet[ZIIRAVE_FIRM_PKT_TOTAL_SIZE]; + int ret; + u16 addr; + + memset(packet, 0, ARRAY_SIZE(packet)); + + /* Packet length */ + packet[0] = (u8)be16_to_cpu(rec->len); + /* Packet address */ + addr = (be32_to_cpu(rec->addr) & 0xffff) >> 1; + packet[1] = addr & 0xff; + packet[2] = (addr & 0xff00) >> 8; + + /* Packet data */ + if (be16_to_cpu(rec->len) > ZIIRAVE_FIRM_PKT_DATA_SIZE) + return -EMSGSIZE; + memcpy(packet + 3, rec->data, be16_to_cpu(rec->len)); + + /* Packet checksum */ + for (i = 0; i < ZIIRAVE_FIRM_PKT_TOTAL_SIZE - 1; i++) + checksum += packet[i]; + packet[ZIIRAVE_FIRM_PKT_TOTAL_SIZE - 1] = checksum; + + ret = ziirave_firm_write_block_data(wdd, ZIIRAVE_CMD_DOWNLOAD_PACKET, + ARRAY_SIZE(packet), packet, true); + if (ret) + dev_err(&client->dev, + "Failed to write firmware packet at address 0x%04x: %d\n", + addr, ret); + + return ret; +} + +static int ziirave_firm_verify(struct watchdog_device *wdd, + const struct firmware *fw) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + const struct ihex_binrec *rec; + int i, ret; + u8 data[ZIIRAVE_FIRM_PKT_DATA_SIZE]; + u16 addr; + + for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) { + /* Zero length marks end of records */ + if (!be16_to_cpu(rec->len)) + break; + + addr = (be32_to_cpu(rec->addr) & 0xffff) >> 1; + if (addr < ZIIRAVE_FIRM_FLASH_MEMORY_START || + addr > ZIIRAVE_FIRM_FLASH_MEMORY_END) + continue; + + ret = ziirave_firm_set_read_addr(wdd, addr); + if (ret) { + dev_err(&client->dev, + "Failed to send SET_READ_ADDR command: %d\n", + ret); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(data); i++) { + ret = i2c_smbus_read_byte_data(client, + ZIIRAVE_CMD_DOWNLOAD_READ_BYTE); + if (ret < 0) { + dev_err(&client->dev, + "Failed to READ DATA: %d\n", ret); + return ret; + } + data[i] = ret; + } + + if (memcmp(data, rec->data, be16_to_cpu(rec->len))) { + dev_err(&client->dev, + "Firmware mismatch at address 0x%04x\n", addr); + return -EINVAL; + } + } + + return 0; +} + +static int ziirave_firm_upload(struct watchdog_device *wdd, + const struct firmware *fw) +{ + struct i2c_client *client = to_i2c_client(wdd->parent); + int ret, words_till_page_break; + const struct ihex_binrec *rec; + struct ihex_binrec *rec_new; + + ret = ziirave_firm_write_byte(wdd, ZIIRAVE_CMD_JUMP_TO_BOOTLOADER, 1, + false); + if (ret) + return ret; + + msleep(500); + + ret = ziirave_firm_write_byte(wdd, ZIIRAVE_CMD_DOWNLOAD_START, 1, true); + if (ret) + return ret; + + msleep(500); + + for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) { + /* Zero length marks end of records */ + if (!be16_to_cpu(rec->len)) + break; + + /* Check max data size */ + if (be16_to_cpu(rec->len) > ZIIRAVE_FIRM_PKT_DATA_SIZE) { + dev_err(&client->dev, "Firmware packet too long (%d)\n", + be16_to_cpu(rec->len)); + return -EMSGSIZE; + } + + /* Calculate words till page break */ + words_till_page_break = (64 - ((be32_to_cpu(rec->addr) >> 1) & + 0x3f)); + if ((be16_to_cpu(rec->len) >> 1) > words_till_page_break) { + /* + * Data in passes page boundary, so we need to split in + * two blocks of data. Create a packet with the first + * block of data. + */ + rec_new = kzalloc(sizeof(struct ihex_binrec) + + (words_till_page_break << 1), + GFP_KERNEL); + if (!rec_new) + return -ENOMEM; + + rec_new->len = cpu_to_be16(words_till_page_break << 1); + rec_new->addr = rec->addr; + memcpy(rec_new->data, rec->data, + be16_to_cpu(rec_new->len)); + + ret = ziirave_firm_write_pkt(wdd, rec_new); + kfree(rec_new); + if (ret) + return ret; + + /* Create a packet with the second block of data */ + rec_new = kzalloc(sizeof(struct ihex_binrec) + + be16_to_cpu(rec->len) - + (words_till_page_break << 1), + GFP_KERNEL); + if (!rec_new) + return -ENOMEM; + + /* Remaining bytes */ + rec_new->len = rec->len - + cpu_to_be16(words_till_page_break << 1); + + rec_new->addr = cpu_to_be32(be32_to_cpu(rec->addr) + + (words_till_page_break << 1)); + + memcpy(rec_new->data, + rec->data + (words_till_page_break << 1), + be16_to_cpu(rec_new->len)); + + ret = ziirave_firm_write_pkt(wdd, rec_new); + kfree(rec_new); + if (ret) + return ret; + } else { + ret = ziirave_firm_write_pkt(wdd, rec); + if (ret) + return ret; + } + } + + /* For end of download, the length field will be set to 0 */ + rec_new = kzalloc(sizeof(struct ihex_binrec) + 1, GFP_KERNEL); + if (!rec_new) + return -ENOMEM; + + ret = ziirave_firm_write_pkt(wdd, rec_new); + kfree(rec_new); + if (ret) { + dev_err(&client->dev, "Failed to send EMPTY packet: %d\n", ret); + return ret; + } + + /* This sleep seems to be required */ + msleep(20); + + /* Start firmware verification */ + ret = ziirave_firm_verify(wdd, fw); + if (ret) { + dev_err(&client->dev, + "Failed to verify firmware: %d\n", ret); + return ret; + } + + /* End download operation */ + ret = ziirave_firm_write_byte(wdd, ZIIRAVE_CMD_DOWNLOAD_END, 1, false); + if (ret) + return ret; + + /* Reset the processor */ + ret = ziirave_firm_write_byte(wdd, ZIIRAVE_CMD_RESET_PROCESSOR, 1, + false); + if (ret) + return ret; + + msleep(500); + + return 0; +} + static const struct watchdog_info ziirave_wdt_info = { .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, .identity = "Zodiac RAVE Watchdog", @@ -166,9 +481,18 @@ static ssize_t ziirave_wdt_sysfs_show_firm(struct device *dev, { struct i2c_client *client = to_i2c_client(dev->parent); struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + int ret; + + ret = mutex_lock_interruptible(&w_priv->sysfs_mutex); + if (ret) + return ret; + + ret = sprintf(buf, "02.%02u.%02u", w_priv->firmware_rev.major, + w_priv->firmware_rev.minor); - return sprintf(buf, "02.%02u.%02u", w_priv->firmware_rev.major, - w_priv->firmware_rev.minor); + mutex_unlock(&w_priv->sysfs_mutex); + + return ret; } static DEVICE_ATTR(firmware_version, S_IRUGO, ziirave_wdt_sysfs_show_firm, @@ -180,9 +504,18 @@ static ssize_t ziirave_wdt_sysfs_show_boot(struct device *dev, { struct i2c_client *client = to_i2c_client(dev->parent); struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + int ret; - return sprintf(buf, "01.%02u.%02u", w_priv->bootloader_rev.major, - w_priv->bootloader_rev.minor); + ret = mutex_lock_interruptible(&w_priv->sysfs_mutex); + if (ret) + return ret; + + ret = sprintf(buf, "01.%02u.%02u", w_priv->bootloader_rev.major, + w_priv->bootloader_rev.minor); + + mutex_unlock(&w_priv->sysfs_mutex); + + return ret; } static DEVICE_ATTR(bootloader_version, S_IRUGO, ziirave_wdt_sysfs_show_boot, @@ -194,17 +527,81 @@ static ssize_t ziirave_wdt_sysfs_show_reason(struct device *dev, { struct i2c_client *client = to_i2c_client(dev->parent); struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + int ret; + + ret = mutex_lock_interruptible(&w_priv->sysfs_mutex); + if (ret) + return ret; + + ret = sprintf(buf, "%s", ziirave_reasons[w_priv->reset_reason]); - return sprintf(buf, "%s", ziirave_reasons[w_priv->reset_reason]); + mutex_unlock(&w_priv->sysfs_mutex); + + return ret; } static DEVICE_ATTR(reset_reason, S_IRUGO, ziirave_wdt_sysfs_show_reason, NULL); +static ssize_t ziirave_wdt_sysfs_store_firm(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev->parent); + struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client); + const struct firmware *fw; + int err; + + err = request_ihex_firmware(&fw, ZIIRAVE_FW_NAME, dev); + if (err) { + dev_err(&client->dev, "Failed to request ihex firmware\n"); + return err; + } + + err = mutex_lock_interruptible(&w_priv->sysfs_mutex); + if (err) + goto release_firmware; + + err = ziirave_firm_upload(&w_priv->wdd, fw); + if (err) { + dev_err(&client->dev, "The firmware update failed: %d\n", err); + goto unlock_mutex; + } + + /* Update firmware version */ + err = ziirave_wdt_revision(client, &w_priv->firmware_rev, + ZIIRAVE_WDT_FIRM_VER_MAJOR); + if (err) { + dev_err(&client->dev, "Failed to read firmware version: %d\n", + err); + goto unlock_mutex; + } + + dev_info(&client->dev, "Firmware updated to version 02.%02u.%02u\n", + w_priv->firmware_rev.major, w_priv->firmware_rev.minor); + + /* Restore the watchdog timeout */ + err = ziirave_wdt_set_timeout(&w_priv->wdd, w_priv->wdd.timeout); + if (err) + dev_err(&client->dev, "Failed to set timeout: %d\n", err); + +unlock_mutex: + mutex_unlock(&w_priv->sysfs_mutex); + +release_firmware: + release_firmware(fw); + + return err ? err : count; +} + +static DEVICE_ATTR(update_firmware, S_IWUSR, NULL, + ziirave_wdt_sysfs_store_firm); + static struct attribute *ziirave_wdt_attrs[] = { &dev_attr_firmware_version.attr, &dev_attr_bootloader_version.attr, &dev_attr_reset_reason.attr, + &dev_attr_update_firmware.attr, NULL }; ATTRIBUTE_GROUPS(ziirave_wdt); @@ -252,6 +649,8 @@ static int ziirave_wdt_probe(struct i2c_client *client, if (!w_priv) return -ENOMEM; + mutex_init(&w_priv->sysfs_mutex); + w_priv->wdd.info = &ziirave_wdt_info; w_priv->wdd.ops = &ziirave_wdt_ops; w_priv->wdd.min_timeout = ZIIRAVE_TIMEOUT_MIN; -- cgit From 619db74a2ba076d33097f49b17c93369666ac6f5 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Mon, 15 Aug 2016 13:34:47 +0200 Subject: watchdog-asm9260: Delete owner assignment The field "owner" is set by core. Thus delete an extra initialisation. Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci Signed-off-by: Markus Elfring Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/asm9260_wdt.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/watchdog/asm9260_wdt.c b/drivers/watchdog/asm9260_wdt.c index c9686b2fdafd..d0b59ba0f661 100644 --- a/drivers/watchdog/asm9260_wdt.c +++ b/drivers/watchdog/asm9260_wdt.c @@ -389,7 +389,6 @@ MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match); static struct platform_driver asm9260_wdt_driver = { .driver = { .name = "asm9260-wdt", - .owner = THIS_MODULE, .of_match_table = asm9260_wdt_of_match, }, .probe = asm9260_wdt_probe, -- cgit From bfb1f46f69060642d54125cb777c817f3f9436e3 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Sun, 11 Sep 2016 10:59:57 +0200 Subject: watchdog: txx9wdt: Add missing clock (un)prepare calls for CCF While the custom minimal TXx9 clock implementation doesn't need or use clock (un)prepare calls (they are dummies if !CONFIG_HAVE_CLK_PREPARE), they are mandatory when using the Common Clock Framework. Hence add them, to prepare for the advent of CCF. Signed-off-by: Geert Uytterhoeven Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/txx9wdt.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/txx9wdt.c b/drivers/watchdog/txx9wdt.c index c2da880292bc..6f7a9deb27d0 100644 --- a/drivers/watchdog/txx9wdt.c +++ b/drivers/watchdog/txx9wdt.c @@ -112,7 +112,7 @@ static int __init txx9wdt_probe(struct platform_device *dev) txx9_imclk = NULL; goto exit; } - ret = clk_enable(txx9_imclk); + ret = clk_prepare_enable(txx9_imclk); if (ret) { clk_put(txx9_imclk); txx9_imclk = NULL; @@ -144,7 +144,7 @@ static int __init txx9wdt_probe(struct platform_device *dev) return 0; exit: if (txx9_imclk) { - clk_disable(txx9_imclk); + clk_disable_unprepare(txx9_imclk); clk_put(txx9_imclk); } return ret; @@ -153,7 +153,7 @@ exit: static int __exit txx9wdt_remove(struct platform_device *dev) { watchdog_unregister_device(&txx9wdt); - clk_disable(txx9_imclk); + clk_disable_unprepare(txx9_imclk); clk_put(txx9_imclk); return 0; } -- cgit From eadc4fe17d04723373ada219d382b3b413b21dad Mon Sep 17 00:00:00 2001 From: Shubhrajyoti Datta Date: Mon, 12 Sep 2016 12:53:49 +0530 Subject: watchdog: cadence_wdt: Fix the suspend resume Currently even if no users are there the suspend tries to stop the watchdog and resume starts it. so after resume the watchdog starts and resets the board. Fix the same by adding a check for users. Reviewed-by: Guenter Roeck Signed-off-by: Shubhrajyoti Datta Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/cadence_wdt.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/cadence_wdt.c b/drivers/watchdog/cadence_wdt.c index 4dda9024e229..0fd267eb9342 100644 --- a/drivers/watchdog/cadence_wdt.c +++ b/drivers/watchdog/cadence_wdt.c @@ -424,8 +424,10 @@ static int __maybe_unused cdns_wdt_suspend(struct device *dev) struct platform_device *pdev = to_platform_device(dev); struct cdns_wdt *wdt = platform_get_drvdata(pdev); - cdns_wdt_stop(&wdt->cdns_wdt_device); - clk_disable_unprepare(wdt->clk); + if (watchdog_active(&wdt->cdns_wdt_device)) { + cdns_wdt_stop(&wdt->cdns_wdt_device); + clk_disable_unprepare(wdt->clk); + } return 0; } @@ -442,12 +444,14 @@ static int __maybe_unused cdns_wdt_resume(struct device *dev) struct platform_device *pdev = to_platform_device(dev); struct cdns_wdt *wdt = platform_get_drvdata(pdev); - ret = clk_prepare_enable(wdt->clk); - if (ret) { - dev_err(dev, "unable to enable clock\n"); - return ret; + if (watchdog_active(&wdt->cdns_wdt_device)) { + ret = clk_prepare_enable(wdt->clk); + if (ret) { + dev_err(dev, "unable to enable clock\n"); + return ret; + } + cdns_wdt_start(&wdt->cdns_wdt_device); } - cdns_wdt_start(&wdt->cdns_wdt_device); return 0; } -- cgit From 6e938f6e86a54ba22324a8e4f8c0fb774cffa273 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 28 Aug 2016 22:26:26 +0200 Subject: watchdog: iTCO_wdt: constify iTCO_wdt_pm structure iTCO_wdt_pm, of type struct dev_pm_ops, is never modified, so declare it as const. Done with the help of Coccinelle. Signed-off-by: Julia Lawall Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/iTCO_wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c index 54cab189a763..06fcb6c8c917 100644 --- a/drivers/watchdog/iTCO_wdt.c +++ b/drivers/watchdog/iTCO_wdt.c @@ -629,7 +629,7 @@ static int iTCO_wdt_resume_noirq(struct device *dev) return 0; } -static struct dev_pm_ops iTCO_wdt_pm = { +static const struct dev_pm_ops iTCO_wdt_pm = { .suspend_noirq = iTCO_wdt_suspend_noirq, .resume_noirq = iTCO_wdt_resume_noirq, }; -- cgit From 7123f253f02b7370ad25d9047fd91a5ef6ae551f Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Thu, 1 Sep 2016 19:35:25 +0200 Subject: watchdog: tegra: constify watchdog_ops structures Check for watchdog_ops structures that are only stored in the ops field of a watchdog_device structure. This field is declared const, so watchdog_ops structures that have this property can be declared as const also. The semantic patch that makes this change is as follows: (http://coccinelle.lip6.fr/) // @r disable optional_qualifier@ identifier i; position p; @@ static struct watchdog_ops i@p = { ... }; @ok@ identifier r.i; struct watchdog_device e; position p; @@ e.ops = &i@p; @bad@ position p != {r.p,ok.p}; identifier r.i; struct watchdog_ops e; @@ e@i@p @depends on !bad disable optional_qualifier@ identifier r.i; @@ static +const struct watchdog_ops i = { ... }; // Signed-off-by: Julia Lawall Acked-by: Thierry Reding Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/tegra_wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/watchdog/tegra_wdt.c b/drivers/watchdog/tegra_wdt.c index 9ec57608da82..2d53c3f9394f 100644 --- a/drivers/watchdog/tegra_wdt.c +++ b/drivers/watchdog/tegra_wdt.c @@ -178,7 +178,7 @@ static const struct watchdog_info tegra_wdt_info = { .identity = "Tegra Watchdog", }; -static struct watchdog_ops tegra_wdt_ops = { +static const struct watchdog_ops tegra_wdt_ops = { .owner = THIS_MODULE, .start = tegra_wdt_start, .stop = tegra_wdt_stop, -- cgit From 85f15cfc213da88d9eb01c943be454328b104f3c Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Thu, 1 Sep 2016 19:35:26 +0200 Subject: watchdog: constify watchdog_ops structures Check for watchdog_ops structures that are only stored in the ops field of a watchdog_device structure. This field is declared const, so watchdog_ops structures that have this property can be declared as const also. The semantic patch that makes this change is as follows: (http://coccinelle.lip6.fr/) // @r disable optional_qualifier@ identifier i; position p; @@ static struct watchdog_ops i@p = { ... }; @ok@ identifier r.i; struct watchdog_device e; position p; @@ e.ops = &i@p; @bad@ position p != {r.p,ok.p}; identifier r.i; struct watchdog_ops e; @@ e@i@p @depends on !bad disable optional_qualifier@ identifier r.i; @@ static +const struct watchdog_ops i = { ... }; // Signed-off-by: Julia Lawall Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/bcm7038_wdt.c | 2 +- drivers/watchdog/cadence_wdt.c | 2 +- drivers/watchdog/kempld_wdt.c | 2 +- drivers/watchdog/rn5t618_wdt.c | 2 +- drivers/watchdog/softdog.c | 2 +- drivers/watchdog/w83627hf_wdt.c | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/bcm7038_wdt.c b/drivers/watchdog/bcm7038_wdt.c index 4245b65d645c..e238df4d75a2 100644 --- a/drivers/watchdog/bcm7038_wdt.c +++ b/drivers/watchdog/bcm7038_wdt.c @@ -107,7 +107,7 @@ static struct watchdog_info bcm7038_wdt_info = { WDIOF_MAGICCLOSE }; -static struct watchdog_ops bcm7038_wdt_ops = { +static const struct watchdog_ops bcm7038_wdt_ops = { .owner = THIS_MODULE, .start = bcm7038_wdt_start, .stop = bcm7038_wdt_stop, diff --git a/drivers/watchdog/cadence_wdt.c b/drivers/watchdog/cadence_wdt.c index 0fd267eb9342..98acef72334d 100644 --- a/drivers/watchdog/cadence_wdt.c +++ b/drivers/watchdog/cadence_wdt.c @@ -269,7 +269,7 @@ static struct watchdog_info cdns_wdt_info = { }; /* Watchdog Core Ops */ -static struct watchdog_ops cdns_wdt_ops = { +static const struct watchdog_ops cdns_wdt_ops = { .owner = THIS_MODULE, .start = cdns_wdt_start, .stop = cdns_wdt_stop, diff --git a/drivers/watchdog/kempld_wdt.c b/drivers/watchdog/kempld_wdt.c index 5bf931ce1353..8e302d0e346c 100644 --- a/drivers/watchdog/kempld_wdt.c +++ b/drivers/watchdog/kempld_wdt.c @@ -430,7 +430,7 @@ static struct watchdog_info kempld_wdt_info = { WDIOF_PRETIMEOUT }; -static struct watchdog_ops kempld_wdt_ops = { +static const struct watchdog_ops kempld_wdt_ops = { .owner = THIS_MODULE, .start = kempld_wdt_start, .stop = kempld_wdt_stop, diff --git a/drivers/watchdog/rn5t618_wdt.c b/drivers/watchdog/rn5t618_wdt.c index d1c12278cb6a..0805ee2acd7a 100644 --- a/drivers/watchdog/rn5t618_wdt.c +++ b/drivers/watchdog/rn5t618_wdt.c @@ -136,7 +136,7 @@ static struct watchdog_info rn5t618_wdt_info = { .identity = DRIVER_NAME, }; -static struct watchdog_ops rn5t618_wdt_ops = { +static const struct watchdog_ops rn5t618_wdt_ops = { .owner = THIS_MODULE, .start = rn5t618_wdt_start, .stop = rn5t618_wdt_stop, diff --git a/drivers/watchdog/softdog.c b/drivers/watchdog/softdog.c index b067edf246df..1ae469e94045 100644 --- a/drivers/watchdog/softdog.c +++ b/drivers/watchdog/softdog.c @@ -92,7 +92,7 @@ static struct watchdog_info softdog_info = { .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, }; -static struct watchdog_ops softdog_ops = { +static const struct watchdog_ops softdog_ops = { .owner = THIS_MODULE, .start = softdog_ping, .stop = softdog_stop, diff --git a/drivers/watchdog/w83627hf_wdt.c b/drivers/watchdog/w83627hf_wdt.c index 09e8003039dc..ef2ecaf53a14 100644 --- a/drivers/watchdog/w83627hf_wdt.c +++ b/drivers/watchdog/w83627hf_wdt.c @@ -302,7 +302,7 @@ static struct watchdog_info wdt_info = { .identity = "W83627HF Watchdog", }; -static struct watchdog_ops wdt_ops = { +static const struct watchdog_ops wdt_ops = { .owner = THIS_MODULE, .start = wdt_start, .stop = wdt_stop, -- cgit From df9c692b5618914d4ce7c9e3e011c5683fc16226 Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Mon, 12 Sep 2016 10:35:12 +0100 Subject: watchdog: rt2880_wdt: Remove assignment of dev pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 0254e953537c ("watchdog: Drop pointer to watchdog device from struct watchdog_device") removed the dev pointer from struct watchdog_device, but this driver was still assigning it, leading to a compilation error: drivers/watchdog/rt2880_wdt.c: In function ‘rt288x_wdt_probe’: drivers/watchdog/rt2880_wdt.c:161:16: error: ‘struct watchdog_device’ has no member named ‘dev’ rt288x_wdt_dev.dev = &pdev->dev; ^ scripts/Makefile.build:289: recipe for target 'drivers/watchdog/rt2880_wdt.o' failed Fix this by removing the assignment. Fixes: 0254e953537c ("watchdog: Drop pointer to watchdog device ...") Signed-off-by: Matt Redfearn Cc: stable@vger.kernel.org # v4.5+ Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/rt2880_wdt.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/watchdog/rt2880_wdt.c b/drivers/watchdog/rt2880_wdt.c index 1967919ae743..14b4fd428fff 100644 --- a/drivers/watchdog/rt2880_wdt.c +++ b/drivers/watchdog/rt2880_wdt.c @@ -158,7 +158,6 @@ static int rt288x_wdt_probe(struct platform_device *pdev) rt288x_wdt_freq = clk_get_rate(rt288x_wdt_clk) / RALINK_WDT_PRESCALE; - rt288x_wdt_dev.dev = &pdev->dev; rt288x_wdt_dev.bootstatus = rt288x_wdt_bootcause(); rt288x_wdt_dev.max_timeout = (0xfffful / rt288x_wdt_freq); rt288x_wdt_dev.parent = &pdev->dev; -- cgit From 8355b3f94425ac8b9683869354be935795f055ca Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 12 Sep 2016 06:16:51 -0700 Subject: watchdog: mt7621_wdt: Remove assignment of dev pointer Commit 0254e953537c ("watchdog: Drop pointer to watchdog device from struct watchdog_device") removed the dev pointer from struct watchdog_device, but this driver was still assigning it, leading to a compilation error: drivers/watchdog/mt7621_wdt.c: In function 'mt7621_wdt_probe': drivers/watchdog/mt7621_wdt.c:142:16: error: 'struct watchdog_device' has no member named 'dev' Fix this by removing the assignment. Fixes: 0254e953537c ("watchdog: Drop pointer to watchdog device ...") Signed-off-by: Guenter Roeck Cc: stable@vger.kernel.org # v4.5+ Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/mt7621_wdt.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/watchdog/mt7621_wdt.c b/drivers/watchdog/mt7621_wdt.c index 4a2290f900a8..d5735c12067d 100644 --- a/drivers/watchdog/mt7621_wdt.c +++ b/drivers/watchdog/mt7621_wdt.c @@ -139,7 +139,6 @@ static int mt7621_wdt_probe(struct platform_device *pdev) if (!IS_ERR(mt7621_wdt_reset)) reset_control_deassert(mt7621_wdt_reset); - mt7621_wdt_dev.dev = &pdev->dev; mt7621_wdt_dev.bootstatus = mt7621_wdt_bootcause(); watchdog_init_timeout(&mt7621_wdt_dev, mt7621_wdt_dev.max_timeout, -- cgit From 7dd2ce7c91bd29d2fb7436cd2a607c7d24835e82 Mon Sep 17 00:00:00 2001 From: Peter Griffin Date: Wed, 14 Sep 2016 14:27:49 +0100 Subject: watchdog: st_wdt: Remove support for obsolete platforms STiH415/6 SoC support is being removed from the kernel. This patch updates the watchdog driver to remove references to these obsolete platforms. Signed-off-by: Peter Griffin Cc: Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/st_lpc_wdt.c | 33 --------------------------------- 1 file changed, 33 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/st_lpc_wdt.c b/drivers/watchdog/st_lpc_wdt.c index 14e9badf2bfa..e6100e447dd8 100644 --- a/drivers/watchdog/st_lpc_wdt.c +++ b/drivers/watchdog/st_lpc_wdt.c @@ -52,27 +52,6 @@ struct st_wdog { bool warm_reset; }; -static struct st_wdog_syscfg stid127_syscfg = { - .reset_type_reg = 0x004, - .reset_type_mask = BIT(2), - .enable_reg = 0x000, - .enable_mask = BIT(2), -}; - -static struct st_wdog_syscfg stih415_syscfg = { - .reset_type_reg = 0x0B8, - .reset_type_mask = BIT(6), - .enable_reg = 0x0B4, - .enable_mask = BIT(7), -}; - -static struct st_wdog_syscfg stih416_syscfg = { - .reset_type_reg = 0x88C, - .reset_type_mask = BIT(6), - .enable_reg = 0x888, - .enable_mask = BIT(7), -}; - static struct st_wdog_syscfg stih407_syscfg = { .enable_reg = 0x204, .enable_mask = BIT(19), @@ -83,18 +62,6 @@ static const struct of_device_id st_wdog_match[] = { .compatible = "st,stih407-lpc", .data = &stih407_syscfg, }, - { - .compatible = "st,stih416-lpc", - .data = &stih416_syscfg, - }, - { - .compatible = "st,stih415-lpc", - .data = &stih415_syscfg, - }, - { - .compatible = "st,stid127-lpc", - .data = &stid127_syscfg, - }, {}, }; MODULE_DEVICE_TABLE(of, st_wdog_match); -- cgit From 68d4cb809ef84f9a0ea6a23c4c0dc0ae48355f78 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Wed, 31 Aug 2016 14:52:49 +0300 Subject: watchdog: imx2_wdt: use preferred BIT macro instead of open coded values This is a nonfunctional change, declare register bit values with BIT() helper macro. The issues are reported by checkpatch: CHECK: Prefer using the BIT macro #40: FILE: drivers/watchdog/imx2_wdt.c:40: +#define IMX2_WDT_WCR_WDA (1 << 5) /* -> External Reset WDOG_B */ etc. Signed-off-by: Vladimir Zapolskiy Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/imx2_wdt.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c index 62f346bb4348..d17643eb7683 100644 --- a/drivers/watchdog/imx2_wdt.c +++ b/drivers/watchdog/imx2_wdt.c @@ -37,18 +37,18 @@ #define IMX2_WDT_WCR 0x00 /* Control Register */ #define IMX2_WDT_WCR_WT (0xFF << 8) /* -> Watchdog Timeout Field */ -#define IMX2_WDT_WCR_WDA (1 << 5) /* -> External Reset WDOG_B */ -#define IMX2_WDT_WCR_SRS (1 << 4) /* -> Software Reset Signal */ -#define IMX2_WDT_WCR_WRE (1 << 3) /* -> WDOG Reset Enable */ -#define IMX2_WDT_WCR_WDE (1 << 2) /* -> Watchdog Enable */ -#define IMX2_WDT_WCR_WDZST (1 << 0) /* -> Watchdog timer Suspend */ +#define IMX2_WDT_WCR_WDA BIT(5) /* -> External Reset WDOG_B */ +#define IMX2_WDT_WCR_SRS BIT(4) /* -> Software Reset Signal */ +#define IMX2_WDT_WCR_WRE BIT(3) /* -> WDOG Reset Enable */ +#define IMX2_WDT_WCR_WDE BIT(2) /* -> Watchdog Enable */ +#define IMX2_WDT_WCR_WDZST BIT(0) /* -> Watchdog timer Suspend */ #define IMX2_WDT_WSR 0x02 /* Service Register */ #define IMX2_WDT_SEQ1 0x5555 /* -> service sequence 1 */ #define IMX2_WDT_SEQ2 0xAAAA /* -> service sequence 2 */ #define IMX2_WDT_WRSR 0x04 /* Reset Status Register */ -#define IMX2_WDT_WRSR_TOUT (1 << 1) /* -> Reset due to Timeout */ +#define IMX2_WDT_WRSR_TOUT BIT(1) /* -> Reset due to Timeout */ #define IMX2_WDT_WMCR 0x08 /* Misc Register */ -- cgit From df044e02206230c7d79a9aef96a6c087476f5533 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Wed, 31 Aug 2016 14:52:41 +0300 Subject: watchdog: add pretimeout support to the core Since the watchdog framework centrializes the IOCTL interfaces of device drivers now, SETPRETIMEOUT and GETPRETIMEOUT need to be added in the common code. Signed-off-by: Robin Gong Signed-off-by: Wolfram Sang [vzapolskiy: added conditional pretimeout sysfs attribute visibility] Signed-off-by: Vladimir Zapolskiy Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/watchdog_dev.c | 56 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 040bf8382f46..4b381a6be2ff 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -335,16 +335,45 @@ static int watchdog_set_timeout(struct watchdog_device *wdd, if (watchdog_timeout_invalid(wdd, timeout)) return -EINVAL; - if (wdd->ops->set_timeout) + if (wdd->ops->set_timeout) { err = wdd->ops->set_timeout(wdd, timeout); - else + } else { wdd->timeout = timeout; + /* Disable pretimeout if it doesn't fit the new timeout */ + if (wdd->pretimeout >= wdd->timeout) + wdd->pretimeout = 0; + } watchdog_update_worker(wdd); return err; } +/* + * watchdog_set_pretimeout: set the watchdog timer pretimeout + * @wdd: the watchdog device to set the timeout for + * @timeout: pretimeout to set in seconds + */ + +static int watchdog_set_pretimeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + int err = 0; + + if (!(wdd->info->options & WDIOF_PRETIMEOUT)) + return -EOPNOTSUPP; + + if (watchdog_pretimeout_invalid(wdd, timeout)) + return -EINVAL; + + if (wdd->ops->set_pretimeout) + err = wdd->ops->set_pretimeout(wdd, timeout); + else + wdd->pretimeout = timeout; + + return err; +} + /* * watchdog_get_timeleft: wrapper to get the time left before a reboot * @wdd: the watchdog device to get the remaining time from @@ -429,6 +458,15 @@ static ssize_t timeout_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(timeout); +static ssize_t pretimeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", wdd->pretimeout); +} +static DEVICE_ATTR_RO(pretimeout); + static ssize_t identity_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -459,6 +497,9 @@ static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr, if (attr == &dev_attr_timeleft.attr && !wdd->ops->get_timeleft) mode = 0; + else if (attr == &dev_attr_pretimeout.attr && + !(wdd->info->options & WDIOF_PRETIMEOUT)) + mode = 0; return mode; } @@ -466,6 +507,7 @@ static struct attribute *wdt_attrs[] = { &dev_attr_state.attr, &dev_attr_identity.attr, &dev_attr_timeout.attr, + &dev_attr_pretimeout.attr, &dev_attr_timeleft.attr, &dev_attr_bootstatus.attr, &dev_attr_status.attr, @@ -646,6 +688,16 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, break; err = put_user(val, p); break; + case WDIOC_SETPRETIMEOUT: + if (get_user(val, p)) { + err = -EFAULT; + break; + } + err = watchdog_set_pretimeout(wdd, val); + break; + case WDIOC_GETPRETIMEOUT: + err = put_user(wdd->pretimeout, p); + break; default: err = -ENOTTY; break; -- cgit From fc113d54e9d7ef3296cdf2eff49c8ca0a3e5a482 Mon Sep 17 00:00:00 2001 From: Brian Boylston Date: Mon, 26 Sep 2016 13:57:14 -0500 Subject: watchdog: hpwdt: add support for iLO5 iLO5 will offer the same watchdog timer as previous generations, but the PCI subsystem vendor ID will be PCI_VENDOR_ID_HP_3PAR (0x1590) instead of PCI_VENDOR_ID_HP (0x103c). Add 0x1590 to the whitelist and be more specific when ignoring the 103c,1979 device. Signed-off-by: Brian Boylston Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/hpwdt.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 8f89bd8a826a..70c7194e2810 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -39,7 +39,7 @@ #include #include -#define HPWDT_VERSION "1.3.3" +#define HPWDT_VERSION "1.4.0" #define SECS_TO_TICKS(secs) ((secs) * 1000 / 128) #define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000) #define HPWDT_MAX_TIMER TICKS_TO_SECS(65535) @@ -814,7 +814,8 @@ static int hpwdt_init_one(struct pci_dev *dev, * not run on a legacy ASM box. * So we only support the G5 ProLiant servers and higher. */ - if (dev->subsystem_vendor != PCI_VENDOR_ID_HP) { + if (dev->subsystem_vendor != PCI_VENDOR_ID_HP && + dev->subsystem_vendor != PCI_VENDOR_ID_HP_3PAR) { dev_warn(&dev->dev, "This server does not have an iLO2+ ASIC.\n"); return -ENODEV; @@ -823,7 +824,8 @@ static int hpwdt_init_one(struct pci_dev *dev, /* * Ignore all auxilary iLO devices with the following PCI ID */ - if (dev->subsystem_device == 0x1979) + if (dev->subsystem_vendor == PCI_VENDOR_ID_HP && + dev->subsystem_device == 0x1979) return -ENODEV; if (pci_enable_device(dev)) { -- cgit From ff84136cb6a4943f489ad037fe93f43be0573c23 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Fri, 7 Oct 2016 15:39:54 +0300 Subject: watchdog: add watchdog pretimeout governor framework The change adds a simple watchdog pretimeout framework infrastructure, its purpose is to allow users to select a desired handling of watchdog pretimeout events, which may be generated by some watchdog devices. A user selects a default watchdog pretimeout governor during compilation stage. Watchdogs with WDIOF_PRETIMEOUT capability now have one more device attribute in sysfs, pretimeout_governor attribute is intended to display the selected watchdog pretimeout governor. The framework has no impact at runtime on watchdog devices with no WDIOF_PRETIMEOUT capability set. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Guenter Roeck Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 7 ++ drivers/watchdog/Makefile | 5 +- drivers/watchdog/watchdog_dev.c | 23 ++++++ drivers/watchdog/watchdog_pretimeout.c | 131 +++++++++++++++++++++++++++++++++ drivers/watchdog/watchdog_pretimeout.h | 40 ++++++++++ 5 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 drivers/watchdog/watchdog_pretimeout.c create mode 100644 drivers/watchdog/watchdog_pretimeout.h (limited to 'drivers') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1bffe006ca9a..04d535ae497d 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1831,4 +1831,11 @@ config USBPCWATCHDOG Most people will say N. +comment "Watchdog Pretimeout Governors" + +config WATCHDOG_PRETIMEOUT_GOV + bool "Enable watchdog pretimeout governors" + help + The option allows to select watchdog pretimeout governors. + endif # WATCHDOG diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index c22ad3ea3539..990c36ed4716 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -3,9 +3,12 @@ # # The WatchDog Timer Driver Core. -watchdog-objs += watchdog_core.o watchdog_dev.o obj-$(CONFIG_WATCHDOG_CORE) += watchdog.o +watchdog-objs += watchdog_core.o watchdog_dev.o + +watchdog-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV) += watchdog_pretimeout.o + # Only one watchdog can succeed. We probe the ISA/PCI/USB based # watchdog-cards first, then the architecture specific watchdog # drivers and then the architecture independent "softdog" driver. diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 4b381a6be2ff..d2d0b5e37a35 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -49,6 +49,7 @@ #include /* For copy_to_user/put_user/... */ #include "watchdog_core.h" +#include "watchdog_pretimeout.h" /* * struct watchdog_core_data - watchdog core internal data @@ -488,6 +489,16 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(state); +static ssize_t pretimeout_governor_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + return watchdog_pretimeout_governor_get(wdd, buf); +} +static DEVICE_ATTR_RO(pretimeout_governor); + static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr, int n) { @@ -500,6 +511,10 @@ static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr, else if (attr == &dev_attr_pretimeout.attr && !(wdd->info->options & WDIOF_PRETIMEOUT)) mode = 0; + else if (attr == &dev_attr_pretimeout_governor.attr && + (!(wdd->info->options & WDIOF_PRETIMEOUT) || + !IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV))) + mode = 0; return mode; } @@ -512,6 +527,7 @@ static struct attribute *wdt_attrs[] = { &dev_attr_bootstatus.attr, &dev_attr_status.attr, &dev_attr_nowayout.attr, + &dev_attr_pretimeout_governor.attr, NULL, }; @@ -989,6 +1005,12 @@ int watchdog_dev_register(struct watchdog_device *wdd) return PTR_ERR(dev); } + ret = watchdog_register_pretimeout(wdd); + if (ret) { + device_destroy(&watchdog_class, devno); + watchdog_cdev_unregister(wdd); + } + return ret; } @@ -1002,6 +1024,7 @@ int watchdog_dev_register(struct watchdog_device *wdd) void watchdog_dev_unregister(struct watchdog_device *wdd) { + watchdog_unregister_pretimeout(wdd); device_destroy(&watchdog_class, wdd->wd_data->cdev.dev); watchdog_cdev_unregister(wdd); } diff --git a/drivers/watchdog/watchdog_pretimeout.c b/drivers/watchdog/watchdog_pretimeout.c new file mode 100644 index 000000000000..72612255fb55 --- /dev/null +++ b/drivers/watchdog/watchdog_pretimeout.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2015-2016 Mentor Graphics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include + +#include "watchdog_pretimeout.h" + +/* Default watchdog pretimeout governor */ +static struct watchdog_governor *default_gov; + +/* The spinlock protects default_gov, wdd->gov and pretimeout_list */ +static DEFINE_SPINLOCK(pretimeout_lock); + +/* List of watchdog devices, which can generate a pretimeout event */ +static LIST_HEAD(pretimeout_list); + +struct watchdog_pretimeout { + struct watchdog_device *wdd; + struct list_head entry; +}; + +int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf) +{ + int count = 0; + + spin_lock_irq(&pretimeout_lock); + if (wdd->gov) + count = sprintf(buf, "%s\n", wdd->gov->name); + spin_unlock_irq(&pretimeout_lock); + + return count; +} + +void watchdog_notify_pretimeout(struct watchdog_device *wdd) +{ + unsigned long flags; + + spin_lock_irqsave(&pretimeout_lock, flags); + if (!wdd->gov) { + spin_unlock_irqrestore(&pretimeout_lock, flags); + return; + } + + wdd->gov->pretimeout(wdd); + spin_unlock_irqrestore(&pretimeout_lock, flags); +} +EXPORT_SYMBOL_GPL(watchdog_notify_pretimeout); + +int watchdog_register_governor(struct watchdog_governor *gov) +{ + struct watchdog_pretimeout *p; + + if (!default_gov) { + spin_lock_irq(&pretimeout_lock); + default_gov = gov; + + list_for_each_entry(p, &pretimeout_list, entry) + if (!p->wdd->gov) + p->wdd->gov = default_gov; + spin_unlock_irq(&pretimeout_lock); + } + + return 0; +} +EXPORT_SYMBOL(watchdog_register_governor); + +void watchdog_unregister_governor(struct watchdog_governor *gov) +{ + struct watchdog_pretimeout *p; + + spin_lock_irq(&pretimeout_lock); + if (gov == default_gov) + default_gov = NULL; + + list_for_each_entry(p, &pretimeout_list, entry) + if (p->wdd->gov == gov) + p->wdd->gov = default_gov; + spin_unlock_irq(&pretimeout_lock); +} +EXPORT_SYMBOL(watchdog_unregister_governor); + +int watchdog_register_pretimeout(struct watchdog_device *wdd) +{ + struct watchdog_pretimeout *p; + + if (!(wdd->info->options & WDIOF_PRETIMEOUT)) + return 0; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + spin_lock_irq(&pretimeout_lock); + list_add(&p->entry, &pretimeout_list); + p->wdd = wdd; + wdd->gov = default_gov; + spin_unlock_irq(&pretimeout_lock); + + return 0; +} + +void watchdog_unregister_pretimeout(struct watchdog_device *wdd) +{ + struct watchdog_pretimeout *p, *t; + + if (!(wdd->info->options & WDIOF_PRETIMEOUT)) + return; + + spin_lock_irq(&pretimeout_lock); + wdd->gov = NULL; + + list_for_each_entry_safe(p, t, &pretimeout_list, entry) { + if (p->wdd == wdd) { + list_del(&p->entry); + break; + } + } + spin_unlock_irq(&pretimeout_lock); + + kfree(p); +} diff --git a/drivers/watchdog/watchdog_pretimeout.h b/drivers/watchdog/watchdog_pretimeout.h new file mode 100644 index 000000000000..c6cd9f80adb2 --- /dev/null +++ b/drivers/watchdog/watchdog_pretimeout.h @@ -0,0 +1,40 @@ +#ifndef __WATCHDOG_PRETIMEOUT_H +#define __WATCHDOG_PRETIMEOUT_H + +#define WATCHDOG_GOV_NAME_MAXLEN 20 + +struct watchdog_device; + +struct watchdog_governor { + const char name[WATCHDOG_GOV_NAME_MAXLEN]; + void (*pretimeout)(struct watchdog_device *wdd); +}; + +#if IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV) +/* Interfaces to watchdog pretimeout governors */ +int watchdog_register_governor(struct watchdog_governor *gov); +void watchdog_unregister_governor(struct watchdog_governor *gov); + +/* Interfaces to watchdog_dev.c */ +int watchdog_register_pretimeout(struct watchdog_device *wdd); +void watchdog_unregister_pretimeout(struct watchdog_device *wdd); +int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf); + +#else +static inline int watchdog_register_pretimeout(struct watchdog_device *wdd) +{ + return 0; +} + +static inline void watchdog_unregister_pretimeout(struct watchdog_device *wdd) +{ +} + +static inline int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, + char *buf) +{ + return -EINVAL; +} +#endif + +#endif -- cgit From f77710c4cda01ad9c3672fb2f97bdea9a94da92a Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Fri, 7 Oct 2016 15:39:55 +0300 Subject: watchdog: pretimeout: add noop pretimeout governor The change adds noop watchdog pretimeout governor, only an informational message is printed to the kernel log buffer when a watchdog triggers a pretimeout event. While introducing the first pretimeout governor the selected design assumes that the default pretimeout governor is selected by its name and it is always built-in, thus the default pretimeout governor can not be unregistered and the correspondent check can be removed from the watchdog_unregister_governor() function. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Guenter Roeck Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 28 ++++++++++++++++++++ drivers/watchdog/Makefile | 2 ++ drivers/watchdog/pretimeout_noop.c | 47 ++++++++++++++++++++++++++++++++++ drivers/watchdog/watchdog_pretimeout.h | 4 +++ 4 files changed, 81 insertions(+) create mode 100644 drivers/watchdog/pretimeout_noop.c (limited to 'drivers') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 04d535ae497d..4aeb8f95beb4 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1838,4 +1838,32 @@ config WATCHDOG_PRETIMEOUT_GOV help The option allows to select watchdog pretimeout governors. +if WATCHDOG_PRETIMEOUT_GOV + +choice + prompt "Default Watchdog Pretimeout Governor" + default WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC + help + This option selects a default watchdog pretimeout governor. + The governor takes its action, if a watchdog is capable + to report a pretimeout event. + +config WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP + bool "noop" + select WATCHDOG_PRETIMEOUT_GOV_NOOP + help + Use noop watchdog pretimeout governor by default. If noop + governor is selected by a user, write a short message to + the kernel log buffer and don't do any system changes. + +endchoice + +config WATCHDOG_PRETIMEOUT_GOV_NOOP + tristate "Noop watchdog pretimeout governor" + help + Noop watchdog pretimeout governor, only an informational + message is added to kernel log buffer. + +endif # WATCHDOG_PRETIMEOUT_GOV + endif # WATCHDOG diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 990c36ed4716..0502a21db1db 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -9,6 +9,8 @@ watchdog-objs += watchdog_core.o watchdog_dev.o watchdog-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV) += watchdog_pretimeout.o +obj-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV_NOOP) += pretimeout_noop.o + # Only one watchdog can succeed. We probe the ISA/PCI/USB based # watchdog-cards first, then the architecture specific watchdog # drivers and then the architecture independent "softdog" driver. diff --git a/drivers/watchdog/pretimeout_noop.c b/drivers/watchdog/pretimeout_noop.c new file mode 100644 index 000000000000..85f5299d197c --- /dev/null +++ b/drivers/watchdog/pretimeout_noop.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015-2016 Mentor Graphics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include + +#include "watchdog_pretimeout.h" + +/** + * pretimeout_noop - No operation on watchdog pretimeout event + * @wdd - watchdog_device + * + * This function prints a message about pretimeout to kernel log. + */ +static void pretimeout_noop(struct watchdog_device *wdd) +{ + pr_alert("watchdog%d: pretimeout event\n", wdd->id); +} + +static struct watchdog_governor watchdog_gov_noop = { + .name = "noop", + .pretimeout = pretimeout_noop, +}; + +static int __init watchdog_gov_noop_register(void) +{ + return watchdog_register_governor(&watchdog_gov_noop); +} + +static void __exit watchdog_gov_noop_unregister(void) +{ + watchdog_unregister_governor(&watchdog_gov_noop); +} +module_init(watchdog_gov_noop_register); +module_exit(watchdog_gov_noop_unregister); + +MODULE_AUTHOR("Vladimir Zapolskiy "); +MODULE_DESCRIPTION("Panic watchdog pretimeout governor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/watchdog_pretimeout.h b/drivers/watchdog/watchdog_pretimeout.h index c6cd9f80adb2..c4b6f88dc9c9 100644 --- a/drivers/watchdog/watchdog_pretimeout.h +++ b/drivers/watchdog/watchdog_pretimeout.h @@ -20,6 +20,10 @@ int watchdog_register_pretimeout(struct watchdog_device *wdd); void watchdog_unregister_pretimeout(struct watchdog_device *wdd); int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf); +#if IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP) +#define WATCHDOG_PRETIMEOUT_DEFAULT_GOV "noop" +#endif + #else static inline int watchdog_register_pretimeout(struct watchdog_device *wdd) { -- cgit From da0d12ff2b829a35e9921918e925d79497b82bef Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Fri, 7 Oct 2016 15:39:56 +0300 Subject: watchdog: pretimeout: add panic pretimeout governor The change adds panic watchdog pretimeout governor, on watchdog pretimeout event the kernel shall panic. In general watchdog pretimeout event means that something essentially bad is going on the system, for example a process scheduler stalls or watchdog feeder is killed due to OOM, so printing out information attendant to panic and before likely unavoidable reboot caused by a watchdog may help to determine a root cause of the issue. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Guenter Roeck Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 14 ++++++++++ drivers/watchdog/Makefile | 1 + drivers/watchdog/pretimeout_panic.c | 47 ++++++++++++++++++++++++++++++++++ drivers/watchdog/watchdog_pretimeout.c | 6 ++--- drivers/watchdog/watchdog_pretimeout.h | 2 ++ 5 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 drivers/watchdog/pretimeout_panic.c (limited to 'drivers') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 4aeb8f95beb4..cbd33321c0f8 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1856,6 +1856,14 @@ config WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP governor is selected by a user, write a short message to the kernel log buffer and don't do any system changes. +config WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC + bool "panic" + select WATCHDOG_PRETIMEOUT_GOV_PANIC + help + Use panic watchdog pretimeout governor by default, if + a watchdog pretimeout event happens, consider that + a watchdog feeder is dead and reboot is unavoidable. + endchoice config WATCHDOG_PRETIMEOUT_GOV_NOOP @@ -1864,6 +1872,12 @@ config WATCHDOG_PRETIMEOUT_GOV_NOOP Noop watchdog pretimeout governor, only an informational message is added to kernel log buffer. +config WATCHDOG_PRETIMEOUT_GOV_PANIC + tristate "Panic watchdog pretimeout governor" + help + Panic watchdog pretimeout governor, on watchdog pretimeout + event put the kernel into panic. + endif # WATCHDOG_PRETIMEOUT_GOV endif # WATCHDOG diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 0502a21db1db..1a34c6a96988 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -10,6 +10,7 @@ watchdog-objs += watchdog_core.o watchdog_dev.o watchdog-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV) += watchdog_pretimeout.o obj-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV_NOOP) += pretimeout_noop.o +obj-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV_PANIC) += pretimeout_panic.o # Only one watchdog can succeed. We probe the ISA/PCI/USB based # watchdog-cards first, then the architecture specific watchdog diff --git a/drivers/watchdog/pretimeout_panic.c b/drivers/watchdog/pretimeout_panic.c new file mode 100644 index 000000000000..0c197a1c97f4 --- /dev/null +++ b/drivers/watchdog/pretimeout_panic.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015-2016 Mentor Graphics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include + +#include "watchdog_pretimeout.h" + +/** + * pretimeout_panic - Panic on watchdog pretimeout event + * @wdd - watchdog_device + * + * Panic, watchdog has not been fed till pretimeout event. + */ +static void pretimeout_panic(struct watchdog_device *wdd) +{ + panic("watchdog pretimeout event\n"); +} + +static struct watchdog_governor watchdog_gov_panic = { + .name = "panic", + .pretimeout = pretimeout_panic, +}; + +static int __init watchdog_gov_panic_register(void) +{ + return watchdog_register_governor(&watchdog_gov_panic); +} + +static void __exit watchdog_gov_panic_unregister(void) +{ + watchdog_unregister_governor(&watchdog_gov_panic); +} +module_init(watchdog_gov_panic_register); +module_exit(watchdog_gov_panic_unregister); + +MODULE_AUTHOR("Vladimir Zapolskiy "); +MODULE_DESCRIPTION("Panic watchdog pretimeout governor"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/watchdog_pretimeout.c b/drivers/watchdog/watchdog_pretimeout.c index 72612255fb55..098c965f6c78 100644 --- a/drivers/watchdog/watchdog_pretimeout.c +++ b/drivers/watchdog/watchdog_pretimeout.c @@ -60,7 +60,8 @@ int watchdog_register_governor(struct watchdog_governor *gov) { struct watchdog_pretimeout *p; - if (!default_gov) { + if (!strncmp(gov->name, WATCHDOG_PRETIMEOUT_DEFAULT_GOV, + WATCHDOG_GOV_NAME_MAXLEN)) { spin_lock_irq(&pretimeout_lock); default_gov = gov; @@ -79,9 +80,6 @@ void watchdog_unregister_governor(struct watchdog_governor *gov) struct watchdog_pretimeout *p; spin_lock_irq(&pretimeout_lock); - if (gov == default_gov) - default_gov = NULL; - list_for_each_entry(p, &pretimeout_list, entry) if (p->wdd->gov == gov) p->wdd->gov = default_gov; diff --git a/drivers/watchdog/watchdog_pretimeout.h b/drivers/watchdog/watchdog_pretimeout.h index c4b6f88dc9c9..867492aa7ea6 100644 --- a/drivers/watchdog/watchdog_pretimeout.h +++ b/drivers/watchdog/watchdog_pretimeout.h @@ -22,6 +22,8 @@ int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf); #if IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP) #define WATCHDOG_PRETIMEOUT_DEFAULT_GOV "noop" +#elif IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC) +#define WATCHDOG_PRETIMEOUT_DEFAULT_GOV "panic" #endif #else -- cgit From 53f96cee1aff74c8ee3c5f7a25df0c01d7117eeb Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Fri, 7 Oct 2016 15:37:00 +0300 Subject: watchdog: pretimeout: add option to select a pretimeout governor in runtime The change converts watchdog device attribute "pretimeout_governor" from read-only to read-write type to allow users to select a desirable watchdog pretimeout governor in runtime, e.g. % echo -n panic > /sys/..../watchdog/watchdog0/pretimeout To get this working a list of registered pretimeout governors is created and a new helper function watchdog_pretimeout_governor_set() is exported to watchdog_dev.c. If a selected governor is gone, a watchdog device pretimeout notification is delegated to a default built-in pretimeout governor. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Guenter Roeck Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/watchdog_dev.c | 15 ++++++- drivers/watchdog/watchdog_pretimeout.c | 76 ++++++++++++++++++++++++++++++++++ drivers/watchdog/watchdog_pretimeout.h | 8 ++++ 3 files changed, 98 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index d2d0b5e37a35..3fdbe0ab1365 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -497,7 +497,20 @@ static ssize_t pretimeout_governor_show(struct device *dev, return watchdog_pretimeout_governor_get(wdd, buf); } -static DEVICE_ATTR_RO(pretimeout_governor); + +static ssize_t pretimeout_governor_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + int ret = watchdog_pretimeout_governor_set(wdd, buf); + + if (!ret) + ret = count; + + return ret; +} +static DEVICE_ATTR_RW(pretimeout_governor); static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr, int n) diff --git a/drivers/watchdog/watchdog_pretimeout.c b/drivers/watchdog/watchdog_pretimeout.c index 098c965f6c78..c9e4a0326938 100644 --- a/drivers/watchdog/watchdog_pretimeout.c +++ b/drivers/watchdog/watchdog_pretimeout.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "watchdog_pretimeout.h" @@ -29,6 +30,28 @@ struct watchdog_pretimeout { struct list_head entry; }; +/* The mutex protects governor list and serializes external interfaces */ +static DEFINE_MUTEX(governor_lock); + +/* List of the registered watchdog pretimeout governors */ +static LIST_HEAD(governor_list); + +struct governor_priv { + struct watchdog_governor *gov; + struct list_head entry; +}; + +static struct governor_priv *find_governor_by_name(const char *gov_name) +{ + struct governor_priv *priv; + + list_for_each_entry(priv, &governor_list, entry) + if (sysfs_streq(gov_name, priv->gov->name)) + return priv; + + return NULL; +} + int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf) { int count = 0; @@ -41,6 +64,28 @@ int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf) return count; } +int watchdog_pretimeout_governor_set(struct watchdog_device *wdd, + const char *buf) +{ + struct governor_priv *priv; + + mutex_lock(&governor_lock); + + priv = find_governor_by_name(buf); + if (!priv) { + mutex_unlock(&governor_lock); + return -EINVAL; + } + + spin_lock_irq(&pretimeout_lock); + wdd->gov = priv->gov; + spin_unlock_irq(&pretimeout_lock); + + mutex_unlock(&governor_lock); + + return 0; +} + void watchdog_notify_pretimeout(struct watchdog_device *wdd) { unsigned long flags; @@ -59,6 +104,22 @@ EXPORT_SYMBOL_GPL(watchdog_notify_pretimeout); int watchdog_register_governor(struct watchdog_governor *gov) { struct watchdog_pretimeout *p; + struct governor_priv *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_lock(&governor_lock); + + if (find_governor_by_name(gov->name)) { + mutex_unlock(&governor_lock); + kfree(priv); + return -EBUSY; + } + + priv->gov = gov; + list_add(&priv->entry, &governor_list); if (!strncmp(gov->name, WATCHDOG_PRETIMEOUT_DEFAULT_GOV, WATCHDOG_GOV_NAME_MAXLEN)) { @@ -71,6 +132,8 @@ int watchdog_register_governor(struct watchdog_governor *gov) spin_unlock_irq(&pretimeout_lock); } + mutex_unlock(&governor_lock); + return 0; } EXPORT_SYMBOL(watchdog_register_governor); @@ -78,12 +141,25 @@ EXPORT_SYMBOL(watchdog_register_governor); void watchdog_unregister_governor(struct watchdog_governor *gov) { struct watchdog_pretimeout *p; + struct governor_priv *priv, *t; + + mutex_lock(&governor_lock); + + list_for_each_entry_safe(priv, t, &governor_list, entry) { + if (priv->gov == gov) { + list_del(&priv->entry); + kfree(priv); + break; + } + } spin_lock_irq(&pretimeout_lock); list_for_each_entry(p, &pretimeout_list, entry) if (p->wdd->gov == gov) p->wdd->gov = default_gov; spin_unlock_irq(&pretimeout_lock); + + mutex_unlock(&governor_lock); } EXPORT_SYMBOL(watchdog_unregister_governor); diff --git a/drivers/watchdog/watchdog_pretimeout.h b/drivers/watchdog/watchdog_pretimeout.h index 867492aa7ea6..6cd6c89a411c 100644 --- a/drivers/watchdog/watchdog_pretimeout.h +++ b/drivers/watchdog/watchdog_pretimeout.h @@ -19,6 +19,8 @@ void watchdog_unregister_governor(struct watchdog_governor *gov); int watchdog_register_pretimeout(struct watchdog_device *wdd); void watchdog_unregister_pretimeout(struct watchdog_device *wdd); int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf); +int watchdog_pretimeout_governor_set(struct watchdog_device *wdd, + const char *buf); #if IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP) #define WATCHDOG_PRETIMEOUT_DEFAULT_GOV "noop" @@ -41,6 +43,12 @@ static inline int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, { return -EINVAL; } + +static inline int watchdog_pretimeout_governor_set(struct watchdog_device *wdd, + const char *buf) +{ + return -EINVAL; +} #endif #endif -- cgit From 89873a711dd20b614abb6e4038fb4b5462f4c701 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Fri, 7 Oct 2016 15:39:57 +0300 Subject: watchdog: pretimeout: add pretimeout_available_governors attribute The change adds an option to a user with CONFIG_WATCHDOG_SYSFS and CONFIG_WATCHDOG_PRETIMEOUT_GOV enabled to get information about all registered watchdog pretimeout governors by reading watchdog device attribute named "pretimeout_available_governors". Signed-off-by: Vladimir Zapolskiy Reviewed-by: Guenter Roeck Reviewed-by: Wolfram Sang Tested-by: Wolfram Sang Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/watchdog_dev.c | 11 ++++++++++- drivers/watchdog/watchdog_pretimeout.c | 15 +++++++++++++++ drivers/watchdog/watchdog_pretimeout.h | 6 ++++++ 3 files changed, 31 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 3fdbe0ab1365..32930a073a12 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -489,6 +489,13 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(state); +static ssize_t pretimeout_available_governors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return watchdog_pretimeout_available_governors_get(buf); +} +static DEVICE_ATTR_RO(pretimeout_available_governors); + static ssize_t pretimeout_governor_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -524,7 +531,8 @@ static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr, else if (attr == &dev_attr_pretimeout.attr && !(wdd->info->options & WDIOF_PRETIMEOUT)) mode = 0; - else if (attr == &dev_attr_pretimeout_governor.attr && + else if ((attr == &dev_attr_pretimeout_governor.attr || + attr == &dev_attr_pretimeout_available_governors.attr) && (!(wdd->info->options & WDIOF_PRETIMEOUT) || !IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV))) mode = 0; @@ -541,6 +549,7 @@ static struct attribute *wdt_attrs[] = { &dev_attr_status.attr, &dev_attr_nowayout.attr, &dev_attr_pretimeout_governor.attr, + &dev_attr_pretimeout_available_governors.attr, NULL, }; diff --git a/drivers/watchdog/watchdog_pretimeout.c b/drivers/watchdog/watchdog_pretimeout.c index c9e4a0326938..9db07bfb4334 100644 --- a/drivers/watchdog/watchdog_pretimeout.c +++ b/drivers/watchdog/watchdog_pretimeout.c @@ -52,6 +52,21 @@ static struct governor_priv *find_governor_by_name(const char *gov_name) return NULL; } +int watchdog_pretimeout_available_governors_get(char *buf) +{ + struct governor_priv *priv; + int count = 0; + + mutex_lock(&governor_lock); + + list_for_each_entry(priv, &governor_list, entry) + count += sprintf(buf + count, "%s\n", priv->gov->name); + + mutex_unlock(&governor_lock); + + return count; +} + int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf) { int count = 0; diff --git a/drivers/watchdog/watchdog_pretimeout.h b/drivers/watchdog/watchdog_pretimeout.h index 6cd6c89a411c..a5a32b39c56d 100644 --- a/drivers/watchdog/watchdog_pretimeout.h +++ b/drivers/watchdog/watchdog_pretimeout.h @@ -18,6 +18,7 @@ void watchdog_unregister_governor(struct watchdog_governor *gov); /* Interfaces to watchdog_dev.c */ int watchdog_register_pretimeout(struct watchdog_device *wdd); void watchdog_unregister_pretimeout(struct watchdog_device *wdd); +int watchdog_pretimeout_available_governors_get(char *buf); int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf); int watchdog_pretimeout_governor_set(struct watchdog_device *wdd, const char *buf); @@ -38,6 +39,11 @@ static inline void watchdog_unregister_pretimeout(struct watchdog_device *wdd) { } +static inline int watchdog_pretimeout_available_governors_get(char *buf) +{ + return -EINVAL; +} + static inline int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf) { -- cgit From 2accf320786210db92f36866cc71fa894f510a4a Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Fri, 7 Oct 2016 15:41:38 +0300 Subject: watchdog: softdog: implement pretimeout support Give devices which do not have hardware support for pretimeout at least a software version of it. Signed-off-by: Wolfram Sang Signed-off-by: Vladimir Zapolskiy Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/softdog.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/watchdog/softdog.c b/drivers/watchdog/softdog.c index 1ae469e94045..c7bdc986dca1 100644 --- a/drivers/watchdog/softdog.c +++ b/drivers/watchdog/softdog.c @@ -72,10 +72,27 @@ static void softdog_fire(unsigned long data) static struct timer_list softdog_ticktock = TIMER_INITIALIZER(softdog_fire, 0, 0); +static struct watchdog_device softdog_dev; + +static void softdog_pretimeout(unsigned long data) +{ + watchdog_notify_pretimeout(&softdog_dev); +} + +static struct timer_list softdog_preticktock = + TIMER_INITIALIZER(softdog_pretimeout, 0, 0); + static int softdog_ping(struct watchdog_device *w) { if (!mod_timer(&softdog_ticktock, jiffies + (w->timeout * HZ))) __module_get(THIS_MODULE); + + if (w->pretimeout) + mod_timer(&softdog_preticktock, jiffies + + (w->timeout - w->pretimeout) * HZ); + else + del_timer(&softdog_preticktock); + return 0; } @@ -84,12 +101,15 @@ static int softdog_stop(struct watchdog_device *w) if (del_timer(&softdog_ticktock)) module_put(THIS_MODULE); + del_timer(&softdog_preticktock); + return 0; } static struct watchdog_info softdog_info = { .identity = "Software Watchdog", - .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | + WDIOF_PRETIMEOUT, }; static const struct watchdog_ops softdog_ops = { -- cgit From 39487f6688a557ebfc69816d7e02f210bf8fb2a3 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Fri, 7 Oct 2016 15:41:39 +0300 Subject: watchdog: imx2_wdt: add pretimeout function support The change adds watchdog pretimeout notification handling to imx2_wdt driver, if device data contains information about a valid interrupt. It is unlikely but still possible (e.g. through a software limitation) that only a subset of watchdogs on SoC has interrupt lines, hence functionally the devices from these two groups have different capabilities, and this is reflected in different watchdog_info structs assigned to the devices. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Guenter Roeck Reviewed-by: Wolfram Sang Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/imx2_wdt.c | 48 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'drivers') diff --git a/drivers/watchdog/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c index d17643eb7683..4874b0f18650 100644 --- a/drivers/watchdog/imx2_wdt.c +++ b/drivers/watchdog/imx2_wdt.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,11 @@ #define IMX2_WDT_WRSR 0x04 /* Reset Status Register */ #define IMX2_WDT_WRSR_TOUT BIT(1) /* -> Reset due to Timeout */ +#define IMX2_WDT_WICR 0x06 /* Interrupt Control Register */ +#define IMX2_WDT_WICR_WIE BIT(15) /* -> Interrupt Enable */ +#define IMX2_WDT_WICR_WTIS BIT(14) /* -> Interrupt Status */ +#define IMX2_WDT_WICR_WICT 0xFF /* -> Interrupt Count Timeout */ + #define IMX2_WDT_WMCR 0x08 /* Misc Register */ #define IMX2_WDT_MAX_TIME 128 @@ -80,6 +86,12 @@ static const struct watchdog_info imx2_wdt_info = { .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, }; +static const struct watchdog_info imx2_wdt_pretimeout_info = { + .identity = "imx2+ watchdog", + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | + WDIOF_PRETIMEOUT, +}; + static int imx2_wdt_restart(struct watchdog_device *wdog, unsigned long action, void *data) { @@ -169,6 +181,35 @@ static int imx2_wdt_set_timeout(struct watchdog_device *wdog, return 0; } +static int imx2_wdt_set_pretimeout(struct watchdog_device *wdog, + unsigned int new_pretimeout) +{ + struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); + + if (new_pretimeout >= IMX2_WDT_MAX_TIME) + return -EINVAL; + + wdog->pretimeout = new_pretimeout; + + regmap_update_bits(wdev->regmap, IMX2_WDT_WICR, + IMX2_WDT_WICR_WIE | IMX2_WDT_WICR_WICT, + IMX2_WDT_WICR_WIE | (new_pretimeout << 1)); + return 0; +} + +static irqreturn_t imx2_wdt_isr(int irq, void *wdog_arg) +{ + struct watchdog_device *wdog = wdog_arg; + struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); + + regmap_write_bits(wdev->regmap, IMX2_WDT_WICR, + IMX2_WDT_WICR_WTIS, IMX2_WDT_WICR_WTIS); + + watchdog_notify_pretimeout(wdog); + + return IRQ_HANDLED; +} + static int imx2_wdt_start(struct watchdog_device *wdog) { struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); @@ -188,6 +229,7 @@ static const struct watchdog_ops imx2_wdt_ops = { .start = imx2_wdt_start, .ping = imx2_wdt_ping, .set_timeout = imx2_wdt_set_timeout, + .set_pretimeout = imx2_wdt_set_pretimeout, .restart = imx2_wdt_restart, }; @@ -236,6 +278,12 @@ static int __init imx2_wdt_probe(struct platform_device *pdev) wdog->max_hw_heartbeat_ms = IMX2_WDT_MAX_TIME * 1000; wdog->parent = &pdev->dev; + ret = platform_get_irq(pdev, 0); + if (ret > 0) + if (!devm_request_irq(&pdev->dev, ret, imx2_wdt_isr, 0, + dev_name(&pdev->dev), wdog)) + wdog->info = &imx2_wdt_pretimeout_info; + ret = clk_prepare_enable(wdev->clk); if (ret) return ret; -- cgit