delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

01 Oct 2010

基于 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) 变量来控制这个行为。