基于 Supermicro X7SPA-H (Atom D510) 的路由器(3)
之前家里用的那个 Linksys WRT310N 路由器就有相当严重的问题(不排除是DD-WRT的问题),如果连续若干天不重启的话,无线网络的连接有时就会失败。部署新的路由器之后,设置了一个cron任务令其每天早上5:30重启:
30 5 * * * /usr/bin/ssh root@10.253.87.254 reboot > /dev/null 2>&1
当然,执行cron的那个用户需要使用ssh key来登录WRT310N,并事先将key传到WRT310N上面。
由于 FreeBSD 默认会对链路状态的变化进行记录,因此只要查看系统日志就可以知道cron是否真的执行了。
在之前的部分我们讨论过关于功耗的问题。在实际使用过程中,我发现 powerd(8) 对于 CPU 占用的计算是有问题的:它计算的是所有CPU的占用率之和,而比较合理的依据则是占用率最高的CPU的占用率。不过,对 Atom 来说 powerd 能够产生的影响有限,因此如果真在使用Atom系统的话不一定真的需要这个patch。下面这个patch可以让 powerd 更有效地 throttle CPU 的频率:
Index: usr.sbin/powerd/powerd.c
===================================================================
--- usr.sbin/powerd/powerd.c (revision 213306)
+++ usr.sbin/powerd/powerd.c (working copy)
@@ -127,6 +127,7 @@
{
static long *cp_times = NULL, *cp_times_old = NULL;
static int ncpus = 0;
+ int newload;
size_t cp_times_len;
int error, cpu, i, total;
@@ -151,7 +152,7 @@
return (error);
if (load) {
- *load = 0;
+ *load = newload = 0;
for (cpu = 0; cpu < ncpus; cpu++) {
total = 0;
for (i = 0; i < CPUSTATES; i++) {
@@ -160,8 +161,10 @@
}
if (total == 0)
continue;
- *load += 100 - (cp_times[cpu * CPUSTATES + CP_IDLE] -
+ newload = 100 - (cp_times[cpu * CPUSTATES + CP_IDLE] -
cp_times_old[cpu * CPUSTATES + CP_IDLE]) * 100 / total;
+ if (*load < newload)
+ *load = newload;
}
}
对路由器这样的设备来说,当有软件 bug 的时候,我们往往希望系统自己恢复并报告问题,而不是等待用户去用调试器现场调试。想要达到这个目的,可以使用 watchdog 机制。在这款主板上有两个 watchdog,其中系统没有自带的那个 Winbond watchdog 的驱动如下:
winbondwd.c
/*-
* Copyright (c) 2010 iXsystems, Inc.
* All rights reserved.
* Written by: Xin LI <delphij@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Winbond Watchdog Timer (WDT) driver
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <machine/resource.h>
#include <sys/watchdog.h>
#include <isa/isavar.h>
#include <dev/pci/pcivar.h>
#include <dev/winbondwd/winbondwd.h>
static devclass_t winbondwd_devclass;
#define winbondwd_read_1(sc, off) \
bus_space_read_1((sc)->wb_bst, (sc)->wb_bsh, (off))
#define winbondwd_write_1(sc, off, val) \
bus_space_write_1((sc)->wb_bst, (sc)->wb_bsh, (off), (val))
/*
* Enter Winbond Extended Functions State
*/
static __inline void
winbondwd_config_enter(struct winbondwd_softc *sc)
{
winbondwd_write_1(sc, 0, 0x87);
winbondwd_write_1(sc, 0, 0x87);
}
/*
* Leave Winbond Extended Functions State
*/
static __inline void
winbondwd_config_leave(struct winbondwd_softc *sc)
{
winbondwd_write_1(sc, 0, 0xaa);
}
static __inline unsigned char
winbondwd_read_reg(struct winbondwd_softc *sc, unsigned char reg)
{
winbondwd_write_1(sc, 0, reg);
return (winbondwd_read_1(sc, 1));
}
/*
* Write specified extended function register
*/
static __inline void
winbondwd_write_reg(struct winbondwd_softc *sc, unsigned char reg, unsigned char val)
{
winbondwd_write_1(sc, 0, reg);
winbondwd_write_1(sc, 1, val);
}
/*
* Select the watchdog device (GPIO Port 2, Logical device 8)
*/
static void
winbondwd_select(struct winbondwd_softc *sc)
{
winbondwd_config_enter(sc);
winbondwd_write_reg(sc, /* LDN bit 7:1 */ 0x7, /* GPIO Port 2 */ 0x8);
winbondwd_write_reg(sc, /* CR30 */ 0x30, /* Activate */ 0x1);
}
/*
* Deselect the watchdog device (only a config_leave is needed)
*/
static void
winbondwd_deselect(struct winbondwd_softc *sc)
{
winbondwd_config_leave(sc);
}
/*
* Show timeout
*/
static void
winbondwd_show_timeout(struct winbondwd_softc *sc)
{
const char *mode_str;
unsigned char timeout, mode;
winbondwd_select(sc);
timeout = winbondwd_read_reg(sc, 0xf6 /* Timeout CR */);
if (timeout == 0) {
sc->active = 0;
if (bootverbose)
device_printf(sc->device,
"Winbond watchdog not running\n");
} else {
sc->active = 1;
mode = winbondwd_read_reg(sc, 0xf5 /* Bit 3: count mode */);
mode_str = (mode & (1 << 2))? "minute" : "second";
device_printf(sc->device,
"Winbond watchdog will timeout after %hhu %s%s\n",
timeout, mode_str, ((timeout > 1)? "s" : ""));
}
winbondwd_deselect(sc);
}
/*
* Set timeout in seconds; 0 = disable
*/
static void
winbondwd_set_timeout(struct winbondwd_softc *sc, unsigned char timeout)
{
unsigned char mode;
/* Don't bother to disable if the watchdog is not running */
if (sc->active == 0 && timeout == 0)
return;
winbondwd_select(sc);
mode = winbondwd_read_reg(sc, 0xf5 /* Bit 3: count mode */);
mode &= ~(1 << 2); /* Choose seconds mode */
winbondwd_write_reg(sc, 0xf5, mode);
winbondwd_write_reg(sc, 0xf6 /* Timeout CR */, timeout);
winbondwd_deselect(sc);
if (bootverbose) {
if (timeout == 0)
device_printf(sc->device,
"Winbond watchdog disabled.\n");
else
device_printf(sc->device,
"Winbond watchdog timeout after %hhu seconds.\n",
timeout);
}
sc->active = (timeout == 0) ? 0 : 1;
}
/*
* Watchdog event handler - called by the framework to enable or disable
* the watchdog or change the initial timeout value.
*/
static void
winbondwd_event(void *arg, unsigned int cmd, int *error)
{
struct winbondwd_softc *sc = arg;
unsigned char rtimeout;
uint64_t timeout;
if (cmd == 0)
winbondwd_set_timeout(sc, 0);
else {
timeout = (uint64_t)1 << (cmd & WD_INTERVAL);
if (timeout < (uint64_t)0xff * 1000 * 1000 * 1000) {
rtimeout = timeout / (1000 * 1000 * 1000);
if (rtimeout == 0)
rtimeout = 0xff;
winbondwd_set_timeout(sc, rtimeout);
} else {
device_printf(sc->device,
"Value %u too big, disabling\n", cmd & WD_INTERVAL);
/* Proposed timeout can not be satisified */
winbondwd_set_timeout(sc, 0);
}
}
}
/*
* A hack to probe Winbond chip's base port.
*/
static unsigned int
winbondwd_baseport_probe(void)
{
unsigned char val;
int i;
const unsigned int baseport_candidates[] = { 0x2e, 0x4e, 0 };
for (i = 0; baseport_candidates[i] != 0; i++) {
/*
* Enter config mode.
*
* Output magic number twice to the index register
*/
outb(baseport_candidates[i], 0x87);
outb(baseport_candidates[i], 0x87);
/*
* I know this is ugly but I didn't found a way to do
* this in a cleaner manner.
*/
/* Get data from CR 0x20 (Device ID) */
outb(baseport_candidates[i], 0x20);
val = inb(baseport_candidates[i]+1);
if (bootverbose)
printf("winbondwd0: CR20 probing port 0x%x got 0x%x\n", baseport_candidates[i], val);
if (val != 0xff) {
outb(baseport_candidates[i], 0xaa);
return baseport_candidates[i];
}
}
return (unsigned int)(-1);
}
/*
* Look for Winbond device.
*/
static void
winbondwd_identify(driver_t *driver, device_t parent)
{
unsigned int baseport;
device_t dev;
if ((dev = device_find_child(parent, driver->name, 0)) == NULL) {
if (resource_int_value("winbondwd", 0, "baseport", &baseport) != 0) {
baseport = winbondwd_baseport_probe();
if (baseport == (unsigned int)(-1)) {
printf("winbondwd0: Compatible Winbond Super I/O not found.\n");
return;
}
}
dev = BUS_ADD_CHILD(parent, 0, driver->name, 0);
bus_set_resource(dev, SYS_RES_IOPORT, 0, baseport, 2);
}
if (dev == NULL)
return;
}
static int
winbondwd_probe(device_t dev)
{
/* Do not claim some ISA PnP device by accident. */
if (isa_get_logicalid(dev) != 0)
return (ENXIO);
return (0);
}
static int
winbondwd_attach(device_t dev)
{
struct winbondwd_softc *sc;
unsigned int baseport;
sc = device_get_softc(dev);
sc->device = dev;
if (resource_int_value("winbondwd", 0, "baseport", &baseport) != 0) {
baseport = winbondwd_baseport_probe();
if (baseport == (unsigned int)(-1)) {
device_printf(dev,
"No compatible Winbond Super I/O found.\n");
return (ENXIO);
}
}
/* allocate I/O register space */
sc->wb_rid = 0;
sc->wb_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->wb_rid,
baseport, baseport + 1, 2, RF_ACTIVE | RF_SHAREABLE);
if (sc->wb_res == NULL) {
device_printf(dev, "Unable to reserve Extended Function Registers\n");
goto fail;
}
sc->wb_bst = rman_get_bustag(sc->wb_res);
sc->wb_bsh = rman_get_bushandle(sc->wb_res);
/* Display the device status */
winbondwd_show_timeout(sc);
/* register the watchdog event handler */
sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, winbondwd_event, sc, 0);
return (0);
fail:
if (sc->wb_res != NULL)
bus_release_resource(dev, SYS_RES_IOPORT,
sc->wb_rid, sc->wb_res);
return (ENXIO);
}
static int
winbondwd_detach(device_t dev)
{
struct winbondwd_softc *sc;
sc = device_get_softc(dev);
/* deregister event handler */
if (sc->ev_tag != NULL)
EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag);
sc->ev_tag = NULL;
/* Disable the watchdog */
if (sc->active)
winbondwd_set_timeout(sc, 0);
/* deallocate I/O register space */
bus_release_resource(dev, SYS_RES_IOPORT, sc->wb_rid, sc->wb_res);
return (0);
}
static device_method_t winbondwd_methods[] = {
DEVMETHOD(device_identify, winbondwd_identify),
DEVMETHOD(device_probe, winbondwd_probe),
DEVMETHOD(device_attach, winbondwd_attach),
DEVMETHOD(device_detach, winbondwd_detach),
DEVMETHOD(device_shutdown, winbondwd_detach),
{0,0}
};
static driver_t winbondwd_driver = {
"winbondwd",
winbondwd_methods,
sizeof(struct winbondwd_softc),
};
DRIVER_MODULE(winbondwd, isa, winbondwd_driver, winbondwd_devclass, NULL, NULL);
winbondwd.h
/*-
* Copyright (c) 2010 iXsystems, Inc.
* All rights reserved.
* Written by: Xin LI <delphij@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef _WINBONDWD_H_
#define _WINBONDWD_H_
struct winbondwd_softc {
device_t device;
int active;
unsigned int timeout;
int wb_rid;
struct resource *wb_res;
bus_space_tag_t wb_bst;
bus_space_handle_t wb_bsh;
eventhandler_tag ev_tag;
};
#endif /* _WINBONDWD_H_ */
在调试这个驱动的时候有一个小插曲。我早期为 X8STi 主板撰写的那个驱动版本中,采用判断硬件是否存在的方法是向其可能的 EFIR 寄存器写 datasheet 指定的 unlock 魔数 (0x87) 之后立即读回并判断是否不是 0xff。尽管 datasheet 上没有明示,但是这个方法对于 X7SPA-H 主板上的 Winbond 83627DHG 不能用。因此退而求其次使用了芯片的 revision 寄存器来进行判断。这个方法并不可靠(最明显的两个问题是:a) 贸然对端口直接进行读写操作有可能导致其他问题,因为此时我们并不知道那个芯片是不是 Winbond 的,不过似乎也没有更好的办法,ACPI并没有宣示 _WDT,而且那是一个LPC设备; b) 仅仅因为能读出一个 revision 数值并不足以判断这个芯片就是 Winbond),不过我测试的没有 Winbond Super I/O 芯片的机器,包括 Dell D630,并没有引发更严重的问题。此外,这个驱动还应进行改进,以利用其以分钟为单位计时的能力。
为什么一定要支持这个 Winbond 的 watchdog 呢?原因是这款主板支持启动时的 watchdog,如果 OS 在5分钟之内不去pat主板上Winbond Super I/O上的watchdog计时器,则系统会自动重启。这个功能对于实现更安全的升级机制至关重要:使用nextboot(8)配置启动一次新内核,这个内核可能会卡在某个地方,而 watchdog 则可以让系统复位,这样在远程更新系统时就能基本消除起不来的风险了。
这个驱动在系统引导的过程中并不禁用watchdog。这样做的考虑是在watchdogd启动之前,仍然不认为OS已经完全启动。我计划增加一个 loader(8) 变量来控制这个行为。