Message ID | 20230615104137.20304-2-oneukum@suse.com |
---|---|
State | New |
Headers | show |
Series | [RFT] Prototype of new watchdog | expand |
Please explain what this driver is. Is that a driver for a new type of pcwd_usb ? Is that a new driver for an existing equipment ? Why a new driver then ? If it is an evolution of the existing driver, please do it in place of the existing driver so that we see what is changed and what remains. Christophe Le 15/06/2023 à 12:40, Oliver Neukum a écrit : > --- > drivers/watchdog/Kconfig | 16 ++ > drivers/watchdog/Makefile | 1 + > drivers/watchdog/pcwd_usb_nova.c | 425 +++++++++++++++++++++++++++++++ > 3 files changed, 442 insertions(+) > create mode 100644 drivers/watchdog/pcwd_usb_nova.c > > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig > index f22138709bf5..f7999fb7caaa 100644 > --- a/drivers/watchdog/Kconfig > +++ b/drivers/watchdog/Kconfig > @@ -2210,6 +2210,22 @@ config USBPCWATCHDOG > > Most people will say N. > > +config USBPCWATCHDOGNOVA > + tristate "Berkshire Products USB-PC Watchdog (new driver)" > + depends on USB > + help > + This is the driver for the Berkshire Products USB-PC Watchdog card. > + This card simply watches your kernel to make sure it doesn't freeze, > + and if it does, it reboots your computer after a certain amount of > + time. The card can also monitor the internal temperature of the PC. > + More info is available at <http://www.berkprod.com/usb_pc_watchdog.htm>. > + > + To compile this driver as a module, choose M here: the > + module will be called pcwd_usb_nova. > + > + Most people will say N. > + > + > config KEEMBAY_WATCHDOG > tristate "Intel Keem Bay SoC non-secure watchdog" > depends on ARCH_KEEMBAY || (ARM64 && COMPILE_TEST) > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile > index b4c4ccf2d703..8364e0184424 100644 > --- a/drivers/watchdog/Makefile > +++ b/drivers/watchdog/Makefile > @@ -33,6 +33,7 @@ obj-$(CONFIG_WDTPCI) += wdt_pci.o > > # USB-based Watchdog Cards > obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o > +obj-$(CONFIG_USBPCWATCHDOGNOVA) += pcwd_usb_nova.o > > # ALPHA Architecture > > diff --git a/drivers/watchdog/pcwd_usb_nova.c b/drivers/watchdog/pcwd_usb_nova.c > new file mode 100644 > index 000000000000..8a92ba8776ae > --- /dev/null > +++ b/drivers/watchdog/pcwd_usb_nova.c > @@ -0,0 +1,425 @@ > +#include <linux/module.h> > +#include <linux/types.h> > +#include <linux/errno.h> > +#include <linux/usb.h> > +#include <linux/mutex.h> > + > +#include <linux/watchdog.h> > +#include <linux/hid.h> > + > +#include <asm/byteorder.h> > +#include <asm/unaligned.h> > + > +#define MIN_BUF_LENGTH 8 > + > +/* stuff taken from the old driver */ > + > +/* according to documentation max. time to process a command for the USB > + * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */ > +#define USB_COMMAND_TIMEOUT 250 > + > +/* Watchdog's internal commands */ > +#define CMD_READ_TEMP 0x02 /* Read Temperature; > + Re-trigger Watchdog */ > +#define CMD_TRIGGER CMD_READ_TEMP > +#define CMD_GET_STATUS 0x04 /* Get Status Information */ > +#define CMD_GET_FIRMWARE_VERSION 0x08 /* Get Firmware Version */ > +#define CMD_GET_DIP_SWITCH_SETTINGS 0x0c /* Get Dip Switch Settings */ > +#define CMD_READ_WATCHDOG_TIMEOUT 0x18 /* Read Current Watchdog Time */ > +#define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 /* Write Current WatchdogTime */ > +#define CMD_ENABLE_WATCHDOG 0x30 /* Enable / Disable Watchdog */ > +#define CMD_DISABLE_WATCHDOG CMD_ENABLE_WATCHDOG > + > +/* Watchdog's Dip Switch heartbeat values */ > +static const int heartbeat_tbl[] = { > + 5, /* OFF-OFF-OFF = 5 Sec */ > + 10, /* OFF-OFF-ON = 10 Sec */ > + 30, /* OFF-ON-OFF = 30 Sec */ > + 60, /* OFF-ON-ON = 1 Min */ > + 300, /* ON-OFF-OFF = 5 Min */ > + 600, /* ON-OFF-ON = 10 Min */ > + 1800, /* ON-ON-OFF = 30 Min */ > + 3600, /* ON-ON-ON = 1 hour */ > +}; > + > +/* The vendor and product id's for the USB-PC Watchdog card */ > +#define USB_PCWD_VENDOR_ID 0x0c98 > +#define USB_PCWD_PRODUCT_ID 0x1140 > + > +/* > + * --- data structures --- > + */ > + > +struct pcwd_nova_command { > + u8 command; > + __be16 value; > + u8 reserved0; > + u8 reserved1; > + u8 reserved2; > +}__attribute__ ((__packed__)); > + > +struct pcwd_nova_wdt { > + struct watchdog_device wdd; > + struct mutex command_excl; > + struct completion response_available; > + int command_result; > + struct usb_interface *interface; > + int intr_size; > + struct pcwd_nova_command *intr_buffer; > + struct urb *intr_urb; > + struct pcwd_nova_command *command_buffer; > +}; > + > +static bool nowayout = WATCHDOG_NOWAYOUT; > +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" > + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); > + > +static const struct watchdog_info pcwd_nova_info; > +/* > + * --- helpers --- > + */ > + > +static bool ack_valid(int response) > +{ > + /* the upper byte can be garbage */ > + return (response & 0xff) != 0x00; > +} > + > +static int send_command(struct pcwd_nova_wdt *pwd, u8 command, u16 value) > +{ > + int result = -EIO; > + int transmitted; > + long left; > + struct pcwd_nova_command *cmd = pwd->command_buffer; > + struct usb_device *dev = interface_to_usbdev(pwd->interface); > + > + mutex_lock(&pwd->command_excl); > + > + cmd->command = command; > + cmd->value = cpu_to_be16(value); > + cmd->reserved0 = cmd->reserved1 = cmd->reserved2 = 0; > + > + reinit_completion(&pwd->response_available); > + transmitted = usb_control_msg( > + dev, > + usb_sndctrlpipe(dev, 0), > + HID_REQ_SET_REPORT, > + HID_DT_REPORT, > + 0x0200, > + pwd->interface->cur_altsetting->desc.bInterfaceNumber, > + cmd, > + sizeof(struct pcwd_nova_command), > + USB_CTRL_SET_TIMEOUT); > + if (transmitted != sizeof(struct pcwd_nova_command)) > + goto error; > + > + left = wait_for_completion_timeout(&pwd->response_available, > + msecs_to_jiffies(USB_COMMAND_TIMEOUT)); > + if (!left) > + goto error; > + result = pwd->command_result; > +error: > + mutex_unlock(&pwd->command_excl); > + > + return result; > +} > + > +static int usb_pcwd_stop(struct pcwd_nova_wdt *pwd) > +{ > + int retval; > + > + retval = send_command(pwd, CMD_DISABLE_WATCHDOG, 0xA5C3); > + if (retval < 0) > + goto error; > + > + if (!ack_valid(retval)) > + retval = -EIO; > + else > + retval = 0; > +error: > + return retval; > +} > + > +static int usb_pcwd_start(struct pcwd_nova_wdt *pwd) > +{ > + int retval; > + > + retval = send_command(pwd, CMD_ENABLE_WATCHDOG, 0x0000); > + if (retval < 0) > + goto error; > + > + if (!ack_valid(retval)) > + retval = -EIO; > + else > + retval = 0; > + > +error: > + return retval; > +} > + > +static int usb_pcwd_set_heartbeat(struct pcwd_nova_wdt *pwd, u16 hb) > +{ > + int retval; > + > + retval = send_command(pwd, CMD_WRITE_WATCHDOG_TIMEOUT, hb); > + > + return retval; > +} > + > +static int usb_pcwd_keepalive(struct pcwd_nova_wdt *pwd) > +{ > + int retval; > + > + retval = send_command(pwd, CMD_TRIGGER, 0x00); > + > + return retval; > +} > + > +static int usb_pcwd_get_timeleft(struct pcwd_nova_wdt *pwd) > +{ > + int retval; > + > + retval = send_command(pwd, CMD_READ_WATCHDOG_TIMEOUT, 0x00); > + > + return retval < 0 ? -EIO : retval; > +} > + > +static int evaluate_device(struct pcwd_nova_wdt *pwd) > +{ > + int rv; > + u16 heartbeat; > + > + rv = usb_pcwd_stop(pwd); > + if (rv < 0) > + goto error; > + > + /* errors intentionally ignored */ > + send_command(pwd, CMD_GET_FIRMWARE_VERSION, 0); > + > + rv = send_command(pwd, CMD_GET_DIP_SWITCH_SETTINGS, 0); > + if (rv < 0) > + goto error; > + > + heartbeat = heartbeat_tbl[(rv & 0x07)]; > + rv = usb_pcwd_set_heartbeat(pwd, heartbeat); > + if (rv < 0) > + /* retry with default */ > + rv = usb_pcwd_set_heartbeat(pwd, 0); > + > +error: > + return rv; > +} > + > +/* > + * --- watchdog operations --- > + */ > + > +static int pcwd_nova_pm_start(struct watchdog_device *wdd) > +{ > + struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd; > + int r; > + > + r = usb_pcwd_start(pwd); > + > + return r; > +} > + > +static int pcwd_nova_pm_stop(struct watchdog_device *wdd) > +{ > + struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd; > + int r; > + > + r = usb_pcwd_stop(pwd); > + > + return r; > +} > + > +static int pcwd_nova_pm_ping(struct watchdog_device *wdd) > +{ > + struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd; > + int r; > + > + r = usb_pcwd_keepalive(pwd); > + > + return r; > +} > + > +static int pcwd_nova_pm_set_heartbeat(struct watchdog_device *wdd, > + unsigned int new_timeout) > +{ > + struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd; > + int r; > + > + r = usb_pcwd_set_heartbeat(pwd, new_timeout); > + > + return r; > +} > + > +static unsigned int pcwd_nova_pm_get_timeleft(struct watchdog_device *wdd) > +{ > + struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd; > + int r; > + > + r = usb_pcwd_get_timeleft(pwd); > + > + return r; > +} > +/* > + * --- usb operations --- > + */ > + > +static void pwd_intr_callback(struct urb *u) > +{ > + struct pcwd_nova_wdt *pwd = u->context; > + struct pcwd_nova_command *result = pwd->intr_buffer; > + int status = u->status; > + > + switch (status) { > + case -ECONNRESET: > + case -ENOENT: > + case -ESHUTDOWN: > + goto death; > + case 0: > + /* get the payload */ > + pwd->command_result = be16_to_cpu(result->value); > + break; > + default: > + pwd->command_result = -EIO; > + goto resubmit; > + } > + > + complete(&pwd->response_available); > +resubmit: > + usb_submit_urb(u, GFP_ATOMIC); > + return; > +death: > + pwd->command_result = -EIO; > + complete(&pwd->response_available); > +} > + > +static int usb_pcwd_probe(struct usb_interface *interface, > + const struct usb_device_id *id) > +{ > + struct pcwd_nova_wdt *pwd; > + struct usb_endpoint_descriptor *int_in; > + int r = -EINVAL; > + int rv, size; > + > + rv = usb_find_common_endpoints(interface->cur_altsetting, > + NULL, NULL, &int_in, NULL); > + if (rv < 0) > + goto err_abort; > + > + /* keeping allocations together for error handling */ > + r = -ENOMEM; > + pwd = kzalloc(sizeof(struct pcwd_nova_wdt), GFP_KERNEL); > + if (!pwd) > + goto err_abort; > + pwd->interface = interface; > + size = max(usb_endpoint_maxp(int_in), MIN_BUF_LENGTH); > + pwd->intr_buffer = kmalloc(size, GFP_KERNEL); > + if (!pwd->intr_buffer) > + goto err_free_pwd; > + pwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL); > + if (!pwd->intr_urb) > + goto err_free_buffer; > + pwd->command_buffer = kmalloc(sizeof(struct pcwd_nova_command), GFP_KERNEL); > + if (!pwd->command_buffer) > + goto err_free_urb; > + > + mutex_init(&pwd->command_excl); > + init_completion(&pwd->response_available); > + usb_fill_int_urb(pwd->intr_urb, > + interface_to_usbdev(interface), > + usb_rcvintpipe(interface_to_usbdev(interface), > + int_in->bEndpointAddress), > + pwd->intr_buffer, > + size, > + pwd_intr_callback, > + pwd, > + int_in->bInterval); > + > + /* > + * we need to start IO now, because we need the device settings > + * that means even an unused device takes up bandwidth > + */ > + r = usb_submit_urb(pwd->intr_urb, GFP_KERNEL); > + if (r < 0) > + goto err_free_all_buffers; > + r = evaluate_device(pwd); > + if (r < 0) > + goto err_free_all_buffers; > + > + usb_set_intfdata(interface, pwd); > + > + pwd->wdd.info = &pcwd_nova_info; > + watchdog_set_drvdata(&pwd->wdd, pwd); > + watchdog_set_nowayout(&pwd->wdd, nowayout); > + watchdog_stop_on_reboot(&pwd->wdd); > + watchdog_stop_on_unregister(&pwd->wdd); > + > + r = watchdog_register_device(&pwd->wdd); > + if (r < 0) > + goto err_kill_urb; > + > + return 0; > + > +err_kill_urb: > + usb_kill_urb(pwd->intr_urb); > + usb_set_intfdata(interface, NULL); > +err_free_all_buffers: > + kfree(pwd->command_buffer); > +err_free_urb: > + usb_free_urb(pwd->intr_urb); > +err_free_buffer: > + kfree(pwd->intr_buffer); > +err_free_pwd: > + kfree(pwd); > +err_abort: > + return r; > +} > + > +static void usb_pcwd_disconnect(struct usb_interface *interface) > +{ > + struct pcwd_nova_wdt *pwd; > + > + pwd = usb_get_intfdata(interface); > + usb_kill_urb(pwd->intr_urb); > + complete(&pwd->response_available); > + watchdog_unregister_device(&pwd->wdd); > + usb_free_urb(pwd->intr_urb); > + kfree(pwd->intr_buffer); > + kfree(pwd->command_buffer); > + kfree(pwd); > +} > + > +static const struct watchdog_info pcwd_nova_info = { > + .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | > + WDIOF_SETTIMEOUT, > + .identity = "PC watchdog new driver", > +}; > + > +static const struct watchdog_ops pcwd_nova_ops = { > + .owner = THIS_MODULE, > + .start = pcwd_nova_pm_start, > + .stop = pcwd_nova_pm_stop, > + .ping = pcwd_nova_pm_ping, > + .set_timeout = pcwd_nova_pm_set_heartbeat, > + .get_timeleft = pcwd_nova_pm_get_timeleft, > +}; > + > +static const struct usb_device_id usb_pcwd_table[] = { > + { USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) }, > + { }, /* Terminating entry */ > +}; > +MODULE_DEVICE_TABLE(usb, usb_pcwd_table); > + > +static struct usb_driver usb_pcwd_drivernova = { > + "New API PCWD", > + .probe = usb_pcwd_probe, > + .disconnect = usb_pcwd_disconnect, > + .id_table = usb_pcwd_table, > +}; > + > +module_usb_driver(usb_pcwd_drivernova); > +MODULE_LICENSE("GPL");
On 6/15/23 07:29, Christophe Leroy wrote: > Please explain what this driver is. > > Is that a driver for a new type of pcwd_usb ? > Is that a new driver for an existing equipment ? Why a new driver then ? > > If it is an evolution of the existing driver, please do it in place of > the existing driver so that we see what is changed and what remains. > Agreed, this should be a patch modifying pcwd_usb.c to use the watchdog core. Any cleanups should be sent as separate patches. Thanks, Guenter
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index f22138709bf5..f7999fb7caaa 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -2210,6 +2210,22 @@ config USBPCWATCHDOG Most people will say N. +config USBPCWATCHDOGNOVA + tristate "Berkshire Products USB-PC Watchdog (new driver)" + depends on USB + help + This is the driver for the Berkshire Products USB-PC Watchdog card. + This card simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. The card can also monitor the internal temperature of the PC. + More info is available at <http://www.berkprod.com/usb_pc_watchdog.htm>. + + To compile this driver as a module, choose M here: the + module will be called pcwd_usb_nova. + + Most people will say N. + + config KEEMBAY_WATCHDOG tristate "Intel Keem Bay SoC non-secure watchdog" depends on ARCH_KEEMBAY || (ARM64 && COMPILE_TEST) diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index b4c4ccf2d703..8364e0184424 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_WDTPCI) += wdt_pci.o # USB-based Watchdog Cards obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o +obj-$(CONFIG_USBPCWATCHDOGNOVA) += pcwd_usb_nova.o # ALPHA Architecture diff --git a/drivers/watchdog/pcwd_usb_nova.c b/drivers/watchdog/pcwd_usb_nova.c new file mode 100644 index 000000000000..8a92ba8776ae --- /dev/null +++ b/drivers/watchdog/pcwd_usb_nova.c @@ -0,0 +1,425 @@ +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/usb.h> +#include <linux/mutex.h> + +#include <linux/watchdog.h> +#include <linux/hid.h> + +#include <asm/byteorder.h> +#include <asm/unaligned.h> + +#define MIN_BUF_LENGTH 8 + +/* stuff taken from the old driver */ + +/* according to documentation max. time to process a command for the USB + * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */ +#define USB_COMMAND_TIMEOUT 250 + +/* Watchdog's internal commands */ +#define CMD_READ_TEMP 0x02 /* Read Temperature; + Re-trigger Watchdog */ +#define CMD_TRIGGER CMD_READ_TEMP +#define CMD_GET_STATUS 0x04 /* Get Status Information */ +#define CMD_GET_FIRMWARE_VERSION 0x08 /* Get Firmware Version */ +#define CMD_GET_DIP_SWITCH_SETTINGS 0x0c /* Get Dip Switch Settings */ +#define CMD_READ_WATCHDOG_TIMEOUT 0x18 /* Read Current Watchdog Time */ +#define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 /* Write Current WatchdogTime */ +#define CMD_ENABLE_WATCHDOG 0x30 /* Enable / Disable Watchdog */ +#define CMD_DISABLE_WATCHDOG CMD_ENABLE_WATCHDOG + +/* Watchdog's Dip Switch heartbeat values */ +static const int heartbeat_tbl[] = { + 5, /* OFF-OFF-OFF = 5 Sec */ + 10, /* OFF-OFF-ON = 10 Sec */ + 30, /* OFF-ON-OFF = 30 Sec */ + 60, /* OFF-ON-ON = 1 Min */ + 300, /* ON-OFF-OFF = 5 Min */ + 600, /* ON-OFF-ON = 10 Min */ + 1800, /* ON-ON-OFF = 30 Min */ + 3600, /* ON-ON-ON = 1 hour */ +}; + +/* The vendor and product id's for the USB-PC Watchdog card */ +#define USB_PCWD_VENDOR_ID 0x0c98 +#define USB_PCWD_PRODUCT_ID 0x1140 + +/* + * --- data structures --- + */ + +struct pcwd_nova_command { + u8 command; + __be16 value; + u8 reserved0; + u8 reserved1; + u8 reserved2; +}__attribute__ ((__packed__)); + +struct pcwd_nova_wdt { + struct watchdog_device wdd; + struct mutex command_excl; + struct completion response_available; + int command_result; + struct usb_interface *interface; + int intr_size; + struct pcwd_nova_command *intr_buffer; + struct urb *intr_urb; + struct pcwd_nova_command *command_buffer; +}; + +static bool nowayout = WATCHDOG_NOWAYOUT; +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static const struct watchdog_info pcwd_nova_info; +/* + * --- helpers --- + */ + +static bool ack_valid(int response) +{ + /* the upper byte can be garbage */ + return (response & 0xff) != 0x00; +} + +static int send_command(struct pcwd_nova_wdt *pwd, u8 command, u16 value) +{ + int result = -EIO; + int transmitted; + long left; + struct pcwd_nova_command *cmd = pwd->command_buffer; + struct usb_device *dev = interface_to_usbdev(pwd->interface); + + mutex_lock(&pwd->command_excl); + + cmd->command = command; + cmd->value = cpu_to_be16(value); + cmd->reserved0 = cmd->reserved1 = cmd->reserved2 = 0; + + reinit_completion(&pwd->response_available); + transmitted = usb_control_msg( + dev, + usb_sndctrlpipe(dev, 0), + HID_REQ_SET_REPORT, + HID_DT_REPORT, + 0x0200, + pwd->interface->cur_altsetting->desc.bInterfaceNumber, + cmd, + sizeof(struct pcwd_nova_command), + USB_CTRL_SET_TIMEOUT); + if (transmitted != sizeof(struct pcwd_nova_command)) + goto error; + + left = wait_for_completion_timeout(&pwd->response_available, + msecs_to_jiffies(USB_COMMAND_TIMEOUT)); + if (!left) + goto error; + result = pwd->command_result; +error: + mutex_unlock(&pwd->command_excl); + + return result; +} + +static int usb_pcwd_stop(struct pcwd_nova_wdt *pwd) +{ + int retval; + + retval = send_command(pwd, CMD_DISABLE_WATCHDOG, 0xA5C3); + if (retval < 0) + goto error; + + if (!ack_valid(retval)) + retval = -EIO; + else + retval = 0; +error: + return retval; +} + +static int usb_pcwd_start(struct pcwd_nova_wdt *pwd) +{ + int retval; + + retval = send_command(pwd, CMD_ENABLE_WATCHDOG, 0x0000); + if (retval < 0) + goto error; + + if (!ack_valid(retval)) + retval = -EIO; + else + retval = 0; + +error: + return retval; +} + +static int usb_pcwd_set_heartbeat(struct pcwd_nova_wdt *pwd, u16 hb) +{ + int retval; + + retval = send_command(pwd, CMD_WRITE_WATCHDOG_TIMEOUT, hb); + + return retval; +} + +static int usb_pcwd_keepalive(struct pcwd_nova_wdt *pwd) +{ + int retval; + + retval = send_command(pwd, CMD_TRIGGER, 0x00); + + return retval; +} + +static int usb_pcwd_get_timeleft(struct pcwd_nova_wdt *pwd) +{ + int retval; + + retval = send_command(pwd, CMD_READ_WATCHDOG_TIMEOUT, 0x00); + + return retval < 0 ? -EIO : retval; +} + +static int evaluate_device(struct pcwd_nova_wdt *pwd) +{ + int rv; + u16 heartbeat; + + rv = usb_pcwd_stop(pwd); + if (rv < 0) + goto error; + + /* errors intentionally ignored */ + send_command(pwd, CMD_GET_FIRMWARE_VERSION, 0); + + rv = send_command(pwd, CMD_GET_DIP_SWITCH_SETTINGS, 0); + if (rv < 0) + goto error; + + heartbeat = heartbeat_tbl[(rv & 0x07)]; + rv = usb_pcwd_set_heartbeat(pwd, heartbeat); + if (rv < 0) + /* retry with default */ + rv = usb_pcwd_set_heartbeat(pwd, 0); + +error: + return rv; +} + +/* + * --- watchdog operations --- + */ + +static int pcwd_nova_pm_start(struct watchdog_device *wdd) +{ + struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd; + int r; + + r = usb_pcwd_start(pwd); + + return r; +} + +static int pcwd_nova_pm_stop(struct watchdog_device *wdd) +{ + struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd; + int r; + + r = usb_pcwd_stop(pwd); + + return r; +} + +static int pcwd_nova_pm_ping(struct watchdog_device *wdd) +{ + struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd; + int r; + + r = usb_pcwd_keepalive(pwd); + + return r; +} + +static int pcwd_nova_pm_set_heartbeat(struct watchdog_device *wdd, + unsigned int new_timeout) +{ + struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd; + int r; + + r = usb_pcwd_set_heartbeat(pwd, new_timeout); + + return r; +} + +static unsigned int pcwd_nova_pm_get_timeleft(struct watchdog_device *wdd) +{ + struct pcwd_nova_wdt *pwd = (struct pcwd_nova_wdt *)wdd; + int r; + + r = usb_pcwd_get_timeleft(pwd); + + return r; +} +/* + * --- usb operations --- + */ + +static void pwd_intr_callback(struct urb *u) +{ + struct pcwd_nova_wdt *pwd = u->context; + struct pcwd_nova_command *result = pwd->intr_buffer; + int status = u->status; + + switch (status) { + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + goto death; + case 0: + /* get the payload */ + pwd->command_result = be16_to_cpu(result->value); + break; + default: + pwd->command_result = -EIO; + goto resubmit; + } + + complete(&pwd->response_available); +resubmit: + usb_submit_urb(u, GFP_ATOMIC); + return; +death: + pwd->command_result = -EIO; + complete(&pwd->response_available); +} + +static int usb_pcwd_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct pcwd_nova_wdt *pwd; + struct usb_endpoint_descriptor *int_in; + int r = -EINVAL; + int rv, size; + + rv = usb_find_common_endpoints(interface->cur_altsetting, + NULL, NULL, &int_in, NULL); + if (rv < 0) + goto err_abort; + + /* keeping allocations together for error handling */ + r = -ENOMEM; + pwd = kzalloc(sizeof(struct pcwd_nova_wdt), GFP_KERNEL); + if (!pwd) + goto err_abort; + pwd->interface = interface; + size = max(usb_endpoint_maxp(int_in), MIN_BUF_LENGTH); + pwd->intr_buffer = kmalloc(size, GFP_KERNEL); + if (!pwd->intr_buffer) + goto err_free_pwd; + pwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!pwd->intr_urb) + goto err_free_buffer; + pwd->command_buffer = kmalloc(sizeof(struct pcwd_nova_command), GFP_KERNEL); + if (!pwd->command_buffer) + goto err_free_urb; + + mutex_init(&pwd->command_excl); + init_completion(&pwd->response_available); + usb_fill_int_urb(pwd->intr_urb, + interface_to_usbdev(interface), + usb_rcvintpipe(interface_to_usbdev(interface), + int_in->bEndpointAddress), + pwd->intr_buffer, + size, + pwd_intr_callback, + pwd, + int_in->bInterval); + + /* + * we need to start IO now, because we need the device settings + * that means even an unused device takes up bandwidth + */ + r = usb_submit_urb(pwd->intr_urb, GFP_KERNEL); + if (r < 0) + goto err_free_all_buffers; + r = evaluate_device(pwd); + if (r < 0) + goto err_free_all_buffers; + + usb_set_intfdata(interface, pwd); + + pwd->wdd.info = &pcwd_nova_info; + watchdog_set_drvdata(&pwd->wdd, pwd); + watchdog_set_nowayout(&pwd->wdd, nowayout); + watchdog_stop_on_reboot(&pwd->wdd); + watchdog_stop_on_unregister(&pwd->wdd); + + r = watchdog_register_device(&pwd->wdd); + if (r < 0) + goto err_kill_urb; + + return 0; + +err_kill_urb: + usb_kill_urb(pwd->intr_urb); + usb_set_intfdata(interface, NULL); +err_free_all_buffers: + kfree(pwd->command_buffer); +err_free_urb: + usb_free_urb(pwd->intr_urb); +err_free_buffer: + kfree(pwd->intr_buffer); +err_free_pwd: + kfree(pwd); +err_abort: + return r; +} + +static void usb_pcwd_disconnect(struct usb_interface *interface) +{ + struct pcwd_nova_wdt *pwd; + + pwd = usb_get_intfdata(interface); + usb_kill_urb(pwd->intr_urb); + complete(&pwd->response_available); + watchdog_unregister_device(&pwd->wdd); + usb_free_urb(pwd->intr_urb); + kfree(pwd->intr_buffer); + kfree(pwd->command_buffer); + kfree(pwd); +} + +static const struct watchdog_info pcwd_nova_info = { + .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | + WDIOF_SETTIMEOUT, + .identity = "PC watchdog new driver", +}; + +static const struct watchdog_ops pcwd_nova_ops = { + .owner = THIS_MODULE, + .start = pcwd_nova_pm_start, + .stop = pcwd_nova_pm_stop, + .ping = pcwd_nova_pm_ping, + .set_timeout = pcwd_nova_pm_set_heartbeat, + .get_timeleft = pcwd_nova_pm_get_timeleft, +}; + +static const struct usb_device_id usb_pcwd_table[] = { + { USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) }, + { }, /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, usb_pcwd_table); + +static struct usb_driver usb_pcwd_drivernova = { + "New API PCWD", + .probe = usb_pcwd_probe, + .disconnect = usb_pcwd_disconnect, + .id_table = usb_pcwd_table, +}; + +module_usb_driver(usb_pcwd_drivernova); +MODULE_LICENSE("GPL");