--- //depot/vendor/freebsd/src/sys/dev/ipmi/ipmi.c 2008/11/14 01:55:36 +++ //depot/user/jhb/ipmi/sys/dev/ipmi/ipmi.c 2009/06/08 16:54:23 @@ -585,11 +748,104 @@ } /* - * Watchdog event handler. + * Watchdog support. */ +struct string_table { + const char *name; + u_char value; +}; + +static struct string_table intr_names[] = { + { "none", 0x00 }, + { "SMI", IPMI_SET_WD_ACTION_SMI }, + { "NMI", IPMI_SET_WD_ACTION_NMI }, + { "INTR", IPMI_SET_WD_ACTION_INTR }, + { NULL, 0x00 } +}; + +static struct string_table action_names[] = { + { "none", 0x00 }, + { "reset", IPMI_SET_WD_ACTION_RESET }, + { "off", IPMI_SET_WD_ACTION_POWER_DOWN }, + { "power cycle", IPMI_SET_WD_ACTION_POWER_CYCLE }, + { NULL, 0x00 } +}; + +static int +table_sysctl(struct sysctl_req *req, struct string_table *table, u_char *val) +{ + struct string_table *entry; + char buf[16]; + int error; + u_char v; + + /* Lookup the name and copy it out. */ + v = *val; + for (entry = table;; entry++) { + KASSERT(entry->name != NULL, ("bogus value")); + if (entry->value == v) + break; + } + error = SYSCTL_OUT(req, entry->name, strlen(entry->name) + 1); + if (error || req->newptr == NULL) + return (error); + + /* Attempt to parse the new value. */ + if (req->newlen > sizeof(buf)) + return (EINVAL); + error = SYSCTL_IN(req, buf, req->newlen); + if (buf[req->newlen - 1] != '\0') + return (EINVAL); + + /* Lookup the new value in the table. */ + for (entry = table;; entry++) { + if (entry->name == NULL) + return (EINVAL); + if (strcasecmp(table->name, buf) == 0) { + *val = entry->value; + return (0); + } + } +} + +static int +ipmi_intr_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct ipmi_softc *sc; + + sc = arg1; + return (table_sysctl(req, intr_names, &sc->ipmi_watchdog_interrupt)); +} + +static int +ipmi_action_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct ipmi_softc *sc; + + sc = arg1; + return (table_sysctl(req, action_names, &sc->ipmi_watchdog_action)); +} + +static int +ipmi_timeout_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct ipmi_softc *sc; + int error, timeout; + + sc = arg1; + timeout = sc->ipmi_watchdog_pretimo; + error = sysctl_handle_int(oidp, &timeout, 0, req); + if (error || req->newptr == NULL) + return (error); + if (timeout < 0 || timeout > 255) + return (EINVAL); + sc->ipmi_watchdog_pretimo = timeout; + return (0); +} + static void -ipmi_set_watchdog(struct ipmi_softc *sc, int sec) +ipmi_set_watchdog(struct ipmi_softc *sc, int timo) { struct ipmi_request *req; int error; @@ -597,14 +853,21 @@ req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), IPMI_SET_WDOG, 6, 0); - if (sec) { + if (timo) { req->ir_request[0] = IPMI_SET_WD_TIMER_DONT_STOP | IPMI_SET_WD_TIMER_SMS_OS; - req->ir_request[1] = IPMI_SET_WD_ACTION_RESET; - req->ir_request[2] = 0; + req->ir_request[1] = sc->ipmi_watchdog_action | + sc->ipmi_watchdog_interrupt; + if (sc->ipmi_watchdog_interrupt) { + req->ir_request[2] = sc->ipmi_watchdog_pretimo; + timo += sc->ipmi_watchdog_pretimo * 10; + if (timo > 0xffff) + timo = 0xffff; + } else + req->ir_request[2] = 0; req->ir_request[3] = 0; /* Timer use */ - req->ir_request[4] = (sec * 10) & 0xff; - req->ir_request[5] = (sec * 10) / 2550; + req->ir_request[4] = (timo) & 0xff; + req->ir_request[5] = (timo) >> 8; } else { req->ir_request[0] = IPMI_SET_WD_TIMER_SMS_OS; req->ir_request[1] = 0; @@ -617,10 +880,9 @@ error = ipmi_submit_driver_request(sc, req, 0); if (error) device_printf(sc->ipmi_dev, "Failed to set watchdog\n"); + ipmi_free_request(req); - if (error == 0 && sec) { - ipmi_free_request(req); - + if (error == 0 && timo) { req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), IPMI_RESET_WDOG, 0, 0); @@ -628,12 +890,8 @@ if (error) device_printf(sc->ipmi_dev, "Failed to reset watchdog\n"); + ipmi_free_request(req); } - - ipmi_free_request(req); - /* - dump_watchdog(sc); - */ } static void @@ -643,13 +901,122 @@ unsigned int timeout; cmd &= WD_INTERVAL; + + /* The shift below can't handle > 64. */ if (cmd > 0 && cmd <= 63) { - timeout = ((uint64_t)1 << cmd) / 1800000000; - ipmi_set_watchdog(sc, timeout); - *error = 0; - } else { - ipmi_set_watchdog(sc, 0); + + /* (1 << 16) ns < 100ms which is our interval. */ + if (cmd < 17) + timeout = 1; + else + timeout = ((uint64_t)1 << cmd) / 100000; + + /* We can only handle up to 0xffff ticks. */ + if (timeout <= 0xffff) { + ipmi_set_watchdog(sc, timeout); + *error = 0; + return; + } + } + + ipmi_set_watchdog(sc, 0); +} + +static void +ipmi_probe_watchdog(struct ipmi_softc *sc) +{ + struct ipmi_request *req; + device_t dev; + int error; + + dev = sc->ipmi_dev; + + /* First, check to see if we have a watchdog. */ + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_GET_WDOG, 0, 0); + + error = ipmi_submit_driver_request(sc, req, 0); + if (error || req->ir_compcode != 0x00) { + ipmi_free_request(req); + return; + } + ipmi_free_request(req); + + /* + * See if the watchdog supports a pre-timeout + * interrupt. We do this by trying to setup the + * watchdog with a pre-timeout interrupt and seeing if + * it works. + */ + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_SET_WDOG, 6, 0); + req->ir_request[0] = IPMI_SET_WD_TIMER_SMS_OS; + req->ir_request[1] = IPMI_SET_WD_ACTION_INTR; + req->ir_request[2] = 0; + req->ir_request[3] = 0; + req->ir_request[4] = 10; + req->ir_request[5] = 0; + error = ipmi_submit_driver_request(sc, req, 0); + if (error || req->ir_compcode != 0x00) { + ipmi_free_request(req); + goto attach; + } + ipmi_free_request(req); + + /* See if it took. */ + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_GET_WDOG, 0, 8); + error = ipmi_submit_driver_request(sc, req, 0); + if (error || req->ir_compcode != 0x00) { + ipmi_free_request(req); + goto attach; + } + if ((req->ir_reply[2] & IPMI_SET_WD_ACTION_INTR_MASK) == + IPMI_SET_WD_ACTION_INTR) { + device_printf(dev, + "Watchdog supports pre-timeout interrupt\n"); + + /* Install timer sysctls */ + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "watchdog_interrupt", + CTLTYPE_STRING | CTLFLAG_RW, sc, 0, ipmi_intr_sysctl, "A", + "Watchdog Interrupt"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "watchdog_intr_timo", + CTLTYPE_INT | CTLFLAG_RW, sc, 0, ipmi_timeout_sysctl, "I", + "Watchdog Interrupt Pre-Timeout Interval"); } + +attach: + /* Reset the watchdog back to off. */ + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_SET_WDOG, 6, 0); + req->ir_request[0] = IPMI_SET_WD_TIMER_SMS_OS; + req->ir_request[1] = 0; + req->ir_request[2] = 0; + req->ir_request[3] = 0; + req->ir_request[4] = 0; + req->ir_request[5] = 0; + ipmi_submit_driver_request(sc, req, 0); + ipmi_free_request(req); + + /* Install action sysctl */ + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "watchdog_action", CTLTYPE_STRING | CTLFLAG_RW, sc, 0, + ipmi_action_sysctl, "A", "Watchdog Action"); + + /* Default to a machine reset with no pre-timeout interrupt. */ + sc->ipmi_watchdog_pretimo = 0; + sc->ipmi_watchdog_interrupt = 0; + sc->ipmi_watchdog_action = IPMI_SET_WD_ACTION_RESET; + + /* Register the watchdog event handler. */ + sc->ipmi_watchdog_tag = EVENTHANDLER_REGISTER(watchdog_list, + ipmi_wd_event, sc, 0); + device_printf(dev, "Attached watchdog\n"); } static void @@ -739,19 +1110,8 @@ } device_printf(dev, "Number of channels %d\n", i); - /* probe for watchdog */ - req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), - IPMI_GET_WDOG, 0, 0); + /* Probe for watchdog. */ + ipmi_probe_watchdog(sc); - - ipmi_submit_driver_request(sc, req, 0); - - if (req->ir_compcode == 0x00) { - device_printf(dev, "Attached watchdog\n"); - /* register the watchdog event handler */ - sc->ipmi_watchdog_tag = EVENTHANDLER_REGISTER(watchdog_list, - ipmi_wd_event, sc, 0); - } - ipmi_free_request(req); sc->ipmi_cdev = make_dev(&ipmi_cdevsw, device_get_unit(dev), UID_ROOT, GID_OPERATOR, 0660, "ipmi%d", device_get_unit(dev)); --- //depot/vendor/freebsd/src/sys/dev/ipmi/ipmivars.h 2008/08/29 20:38:26 +++ //depot/user/jhb/ipmi/sys/dev/ipmi/ipmivars.h 2009/06/08 17:06:44 @@ -101,26 +112,29 @@ struct resource *ipmi_irq_res; void *ipmi_irq; int ipmi_detaching; int ipmi_opened; struct cdev *ipmi_cdev; TAILQ_HEAD(,ipmi_request) ipmi_pending_requests; eventhandler_tag ipmi_watchdog_tag; + u_char ipmi_watchdog_action; + u_char ipmi_watchdog_interrupt; + u_char ipmi_watchdog_pretimo; struct intr_config_hook ipmi_ich; struct mtx ipmi_lock; struct cv ipmi_request_added; struct proc *ipmi_kthread; driver_intr_t *ipmi_intr; int (*ipmi_startup)(struct ipmi_softc *); int (*ipmi_enqueue_request)(struct ipmi_softc *, struct ipmi_request *); }; #define ipmi_ssif_smbus_address _iface.ssif.smbus_address #define ipmi_ssif_smbus _iface.ssif.smbus struct ipmi_ipmb { u_char foo; }; #define KCS_MODE 0x01 #define SMIC_MODE 0x02 #define BT_MODE 0x03 --- //depot/vendor/freebsd/src/sys/sys/ipmi.h 2006/09/22 22:11:46 +++ //depot/user/jhb/ipmi/sys/sys/ipmi.h 2008/08/26 20:02:58 @@ -70,9 +70,17 @@ #define IPMI_SET_WDOG 0x24 #define IPMI_GET_WDOG 0x25 +/* Fields for watchdog commands. */ #define IPMI_SET_WD_TIMER_SMS_OS 0x04 #define IPMI_SET_WD_TIMER_DONT_STOP 0x40 +#define IPMI_SET_WD_TIMER_DONT_LOG 0x80 #define IPMI_SET_WD_ACTION_RESET 0x01 +#define IPMI_SET_WD_ACTION_POWER_DOWN 0x02 +#define IPMI_SET_WD_ACTION_POWER_CYCLE 0x04 +#define IPMI_SET_WD_ACTION_SMI 0x10 +#define IPMI_SET_WD_ACTION_NMI 0x20 +#define IPMI_SET_WD_ACTION_INTR 0x40 +#define IPMI_SET_WD_ACTION_INTR_MASK 0x70 struct ipmi_msg { unsigned char netfn;