cregit-Linux how code gets into the kernel

Release 4.11 drivers/watchdog/kempld_wdt.c

Directory: drivers/watchdog
/*
 * Kontron PLD watchdog driver
 *
 * Copyright (c) 2010-2013 Kontron Europe GmbH
 * Author: Michael Brunner <michael.brunner@kontron.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License 2 as published
 * by the Free Software Foundation.
 *
 * This program is distributed in the hope that 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.
 *
 * Note: From the PLD watchdog point of view timeout and pretimeout are
 *       defined differently than in the kernel.
 *       First the pretimeout stage runs out before the timeout stage gets
 *       active.
 *
 * Kernel/API:                     P-----| pretimeout
 *               |-----------------------T timeout
 * Watchdog:     |-----------------P       pretimeout_stage
 *                                 |-----T timeout_stage
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/uaccess.h>
#include <linux/watchdog.h>
#include <linux/platform_device.h>
#include <linux/mfd/kempld.h>


#define KEMPLD_WDT_STAGE_TIMEOUT(x)	(0x1b + (x) * 4)

#define KEMPLD_WDT_STAGE_CFG(x)		(0x18 + (x))

#define STAGE_CFG_GET_PRESCALER(x)	(((x) & 0x30) >> 4)

#define STAGE_CFG_SET_PRESCALER(x)	(((x) & 0x3) << 4)

#define STAGE_CFG_PRESCALER_MASK	0x30

#define STAGE_CFG_ACTION_MASK		0x7

#define STAGE_CFG_ASSERT		(1 << 3)


#define KEMPLD_WDT_MAX_STAGES		2

#define KEMPLD_WDT_KICK			0x16

#define KEMPLD_WDT_CFG			0x17

#define KEMPLD_WDT_CFG_ENABLE		0x10

#define KEMPLD_WDT_CFG_ENABLE_LOCK	0x8

#define KEMPLD_WDT_CFG_GLOBAL_LOCK	0x80

enum {
	
ACTION_NONE = 0,
	
ACTION_RESET,
	
ACTION_NMI,
	
ACTION_SMI,
	
ACTION_SCI,
	
ACTION_DELAY,
};

enum {
	
STAGE_TIMEOUT = 0,
	
STAGE_PRETIMEOUT,
};

enum {
	
PRESCALER_21 = 0,
	
PRESCALER_17,
	
PRESCALER_12,
};


static const u32 kempld_prescaler[] = {
	[PRESCALER_21] = (1 << 21) - 1,
	[PRESCALER_17] = (1 << 17) - 1,
	[PRESCALER_12] = (1 << 12) - 1,
	0,
};


struct kempld_wdt_stage {
	
unsigned int	id;
	
u32		mask;
};


struct kempld_wdt_data {
	
struct kempld_device_data	*pld;
	
struct watchdog_device		wdd;
	
unsigned int			pretimeout;
	
struct kempld_wdt_stage		stage[KEMPLD_WDT_MAX_STAGES];
#ifdef CONFIG_PM
	
u8				pm_status_store;
#endif
};


#define DEFAULT_TIMEOUT		30 
/* seconds */

#define DEFAULT_PRETIMEOUT	0


static unsigned int timeout = DEFAULT_TIMEOUT;
module_param(timeout, uint, 0);
MODULE_PARM_DESC(timeout,
	"Watchdog timeout in seconds. (>=0, default="
	__MODULE_STRING(DEFAULT_TIMEOUT) ")");


static unsigned int pretimeout = DEFAULT_PRETIMEOUT;
module_param(pretimeout, uint, 0);
MODULE_PARM_DESC(pretimeout,
	"Watchdog pretimeout in seconds. (>=0, default="
	__MODULE_STRING(DEFAULT_PRETIMEOUT) ")");


static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout,
	"Watchdog cannot be stopped once started (default="
	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");


static int kempld_wdt_set_stage_action(struct kempld_wdt_data *wdt_data, struct kempld_wdt_stage *stage, u8 action) { struct kempld_device_data *pld = wdt_data->pld; u8 stage_cfg; if (!stage || !stage->mask) return -EINVAL; kempld_get_mutex(pld); stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id)); stage_cfg &= ~STAGE_CFG_ACTION_MASK; stage_cfg |= (action & STAGE_CFG_ACTION_MASK); if (action == ACTION_RESET) stage_cfg |= STAGE_CFG_ASSERT; else stage_cfg &= ~STAGE_CFG_ASSERT; kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg); kempld_release_mutex(pld); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser115100.00%1100.00%
Total115100.00%1100.00%


static int kempld_wdt_set_stage_timeout(struct kempld_wdt_data *wdt_data, struct kempld_wdt_stage *stage, unsigned int timeout) { struct kempld_device_data *pld = wdt_data->pld; u32 prescaler; u64 stage_timeout64; u32 stage_timeout; u32 remainder; u8 stage_cfg; #if GCC_VERSION < 40400 /* work around a bug compiling do_div() */ prescaler = READ_ONCE(kempld_prescaler[PRESCALER_21]); #else prescaler = kempld_prescaler[PRESCALER_21]; #endif if (!stage) return -EINVAL; stage_timeout64 = (u64)timeout * pld->pld_clock; remainder = do_div(stage_timeout64, prescaler); if (remainder) stage_timeout64++; if (stage_timeout64 > stage->mask) return -EINVAL; stage_timeout = stage_timeout64 & stage->mask; kempld_get_mutex(pld); stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id)); stage_cfg &= ~STAGE_CFG_PRESCALER_MASK; stage_cfg |= STAGE_CFG_SET_PRESCALER(PRESCALER_21); kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg); kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id), stage_timeout); kempld_release_mutex(pld); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser16685.57%133.33%
Arnd Bergmann2713.92%133.33%
gundberg10.52%133.33%
Total194100.00%3100.00%

/* * kempld_get_mutex must be called prior to calling this function. */
static unsigned int kempld_wdt_get_timeout(struct kempld_wdt_data *wdt_data, struct kempld_wdt_stage *stage) { struct kempld_device_data *pld = wdt_data->pld; unsigned int timeout; u64 stage_timeout; u32 prescaler; u32 remainder; u8 stage_cfg; if (!stage->mask) return 0; stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id)); stage_timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id)); prescaler = kempld_prescaler[STAGE_CFG_GET_PRESCALER(stage_cfg)]; stage_timeout = (stage_timeout & stage->mask) * prescaler; remainder = do_div(stage_timeout, pld->pld_clock); if (remainder) stage_timeout++; timeout = stage_timeout; WARN_ON_ONCE(timeout != stage_timeout); return timeout; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser134100.00%1100.00%
Total134100.00%1100.00%


static int kempld_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) { struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); struct kempld_wdt_stage *pretimeout_stage; struct kempld_wdt_stage *timeout_stage; int ret; timeout_stage = &wdt_data->stage[STAGE_TIMEOUT]; pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT]; if (pretimeout_stage->mask && wdt_data->pretimeout > 0) timeout = wdt_data->pretimeout; ret = kempld_wdt_set_stage_action(wdt_data, timeout_stage, ACTION_RESET); if (ret) return ret; ret = kempld_wdt_set_stage_timeout(wdt_data, timeout_stage, timeout); if (ret) return ret; wdd->timeout = timeout; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser121100.00%1100.00%
Total121100.00%1100.00%


static int kempld_wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int pretimeout) { struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); struct kempld_wdt_stage *pretimeout_stage; u8 action = ACTION_NONE; int ret; pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT]; if (!pretimeout_stage->mask) return -ENXIO; if (pretimeout > wdd->timeout) return -EINVAL; if (pretimeout > 0) action = ACTION_NMI; ret = kempld_wdt_set_stage_action(wdt_data, pretimeout_stage, action); if (ret) return ret; ret = kempld_wdt_set_stage_timeout(wdt_data, pretimeout_stage, wdd->timeout - pretimeout); if (ret) return ret; wdt_data->pretimeout = pretimeout; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser130100.00%1100.00%
Total130100.00%1100.00%


static void kempld_wdt_update_timeouts(struct kempld_wdt_data *wdt_data) { struct kempld_device_data *pld = wdt_data->pld; struct kempld_wdt_stage *pretimeout_stage; struct kempld_wdt_stage *timeout_stage; unsigned int pretimeout, timeout; pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT]; timeout_stage = &wdt_data->stage[STAGE_TIMEOUT]; kempld_get_mutex(pld); pretimeout = kempld_wdt_get_timeout(wdt_data, pretimeout_stage); timeout = kempld_wdt_get_timeout(wdt_data, timeout_stage); kempld_release_mutex(pld); if (pretimeout) wdt_data->pretimeout = timeout; else wdt_data->pretimeout = 0; wdt_data->wdd.timeout = pretimeout + timeout; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser111100.00%1100.00%
Total111100.00%1100.00%


static int kempld_wdt_start(struct watchdog_device *wdd) { struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); struct kempld_device_data *pld = wdt_data->pld; u8 status; int ret; ret = kempld_wdt_set_timeout(wdd, wdd->timeout); if (ret) return ret; kempld_get_mutex(pld); status = kempld_read8(pld, KEMPLD_WDT_CFG); status |= KEMPLD_WDT_CFG_ENABLE; kempld_write8(pld, KEMPLD_WDT_CFG, status); status = kempld_read8(pld, KEMPLD_WDT_CFG); kempld_release_mutex(pld); /* Check if the watchdog was enabled */ if (!(status & KEMPLD_WDT_CFG_ENABLE)) return -EACCES; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser112100.00%1100.00%
Total112100.00%1100.00%


static int kempld_wdt_stop(struct watchdog_device *wdd) { struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); struct kempld_device_data *pld = wdt_data->pld; u8 status; kempld_get_mutex(pld); status = kempld_read8(pld, KEMPLD_WDT_CFG); status &= ~KEMPLD_WDT_CFG_ENABLE; kempld_write8(pld, KEMPLD_WDT_CFG, status); status = kempld_read8(pld, KEMPLD_WDT_CFG); kempld_release_mutex(pld); /* Check if the watchdog was disabled */ if (status & KEMPLD_WDT_CFG_ENABLE) return -EACCES; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser89100.00%1100.00%
Total89100.00%1100.00%


static int kempld_wdt_keepalive(struct watchdog_device *wdd) { struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); struct kempld_device_data *pld = wdt_data->pld; kempld_get_mutex(pld); kempld_write8(pld, KEMPLD_WDT_KICK, 'K'); kempld_release_mutex(pld); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser52100.00%1100.00%
Total52100.00%1100.00%


static long kempld_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd, unsigned long arg) { struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); void __user *argp = (void __user *)arg; int ret = -ENOIOCTLCMD; int __user *p = argp; int new_value; switch (cmd) { case WDIOC_SETPRETIMEOUT: if (get_user(new_value, p)) return -EFAULT; ret = kempld_wdt_set_pretimeout(wdd, new_value); if (ret) return ret; ret = kempld_wdt_keepalive(wdd); break; case WDIOC_GETPRETIMEOUT: ret = put_user(wdt_data->pretimeout, (int __user *)arg); break; } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser12599.21%150.00%
Jingoo Han10.79%150.00%
Total126100.00%2100.00%


static int kempld_wdt_probe_stages(struct watchdog_device *wdd) { struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd); struct kempld_device_data *pld = wdt_data->pld; struct kempld_wdt_stage *pretimeout_stage; struct kempld_wdt_stage *timeout_stage; u8 index, data, data_orig; u32 mask; int i, j; pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT]; timeout_stage = &wdt_data->stage[STAGE_TIMEOUT]; pretimeout_stage->mask = 0; timeout_stage->mask = 0; for (i = 0; i < 3; i++) { index = KEMPLD_WDT_STAGE_TIMEOUT(i); mask = 0; kempld_get_mutex(pld); /* Probe each byte individually. */ for (j = 0; j < 4; j++) { data_orig = kempld_read8(pld, index + j); kempld_write8(pld, index + j, 0x00); data = kempld_read8(pld, index + j); /* A failed write means this byte is reserved */ if (data != 0x00) break; kempld_write8(pld, index + j, data_orig); mask |= 0xff << (j * 8); } kempld_release_mutex(pld); /* Assign available stages to timeout and pretimeout */ if (!timeout_stage->mask) { timeout_stage->mask = mask; timeout_stage->id = i; } else { if (pld->feature_mask & KEMPLD_FEATURE_BIT_NMI) { pretimeout_stage->mask = timeout_stage->mask; timeout_stage->mask = mask; pretimeout_stage->id = timeout_stage->id; timeout_stage->id = i; } break; } } if (!timeout_stage->mask) return -ENODEV; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser279100.00%1100.00%
Total279100.00%1100.00%

static const struct watchdog_info kempld_wdt_info = { .identity = "KEMPLD Watchdog", .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_PRETIMEOUT }; static const struct watchdog_ops kempld_wdt_ops = { .owner = THIS_MODULE, .start = kempld_wdt_start, .stop = kempld_wdt_stop, .ping = kempld_wdt_keepalive, .set_timeout = kempld_wdt_set_timeout, .ioctl = kempld_wdt_ioctl, };
static int kempld_wdt_probe(struct platform_device *pdev) { struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent); struct kempld_wdt_data *wdt_data; struct device *dev = &pdev->dev; struct watchdog_device *wdd; u8 status; int ret = 0; wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL); if (!wdt_data) return -ENOMEM; wdt_data->pld = pld; wdd = &wdt_data->wdd; wdd->parent = dev; kempld_get_mutex(pld); status = kempld_read8(pld, KEMPLD_WDT_CFG); kempld_release_mutex(pld); /* Enable nowayout if watchdog is already locked */ if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK | KEMPLD_WDT_CFG_GLOBAL_LOCK)) { if (!nowayout) dev_warn(dev, "Forcing nowayout - watchdog lock enabled!\n"); nowayout = true; } wdd->info = &kempld_wdt_info; wdd->ops = &kempld_wdt_ops; watchdog_set_drvdata(wdd, wdt_data); watchdog_set_nowayout(wdd, nowayout); ret = kempld_wdt_probe_stages(wdd); if (ret) return ret; kempld_wdt_set_timeout(wdd, timeout); kempld_wdt_set_pretimeout(wdd, pretimeout); /* Check if watchdog is already enabled */ if (status & KEMPLD_WDT_CFG_ENABLE) { /* Get current watchdog settings */ kempld_wdt_update_timeouts(wdt_data); dev_info(dev, "Watchdog was already enabled\n"); } platform_set_drvdata(pdev, wdt_data); ret = watchdog_register_device(wdd); if (ret) return ret; dev_info(dev, "Watchdog registered with %ds timeout\n", wdd->timeout); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser257100.00%1100.00%
Total257100.00%1100.00%


static void kempld_wdt_shutdown(struct platform_device *pdev) { struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev); kempld_wdt_stop(&wdt_data->wdd); }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser29100.00%1100.00%
Total29100.00%1100.00%


static int kempld_wdt_remove(struct platform_device *pdev) { struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev); struct watchdog_device *wdd = &wdt_data->wdd; int ret = 0; if (!nowayout) ret = kempld_wdt_stop(wdd); watchdog_unregister_device(wdd); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser56100.00%1100.00%
Total56100.00%1100.00%

#ifdef CONFIG_PM /* Disable watchdog if it is active during suspend */
static int kempld_wdt_suspend(struct platform_device *pdev, pm_message_t message) { struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev); struct kempld_device_data *pld = wdt_data->pld; struct watchdog_device *wdd = &wdt_data->wdd; kempld_get_mutex(pld); wdt_data->pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG); kempld_release_mutex(pld); kempld_wdt_update_timeouts(wdt_data); if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE) return kempld_wdt_stop(wdd); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser86100.00%1100.00%
Total86100.00%1100.00%

/* Enable watchdog and configure it if necessary */
static int kempld_wdt_resume(struct platform_device *pdev) { struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev); struct watchdog_device *wdd = &wdt_data->wdd; /* * If watchdog was stopped before suspend be sure it gets disabled * again, for the case BIOS has enabled it during resume */ if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE) return kempld_wdt_start(wdd); else return kempld_wdt_stop(wdd); }

Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser53100.00%1100.00%
Total53100.00%1100.00%

#else #define kempld_wdt_suspend NULL #define kempld_wdt_resume NULL #endif static struct platform_driver kempld_wdt_driver = { .driver = { .name = "kempld-wdt", }, .probe = kempld_wdt_probe, .remove = kempld_wdt_remove, .shutdown = kempld_wdt_shutdown, .suspend = kempld_wdt_suspend, .resume = kempld_wdt_resume, }; module_platform_driver(kempld_wdt_driver); MODULE_DESCRIPTION("KEM PLD Watchdog Driver"); MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>"); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
Kevin Strasser235698.62%114.29%
Arnd Bergmann271.13%114.29%
Jingoo Han30.13%228.57%
gundberg10.04%114.29%
Bhumika Goyal10.04%114.29%
Julia Lawall10.04%114.29%
Total2389100.00%7100.00%
Directory: drivers/watchdog
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.