octeon_wdog.c revision 330897
1130812Smarcel/*-
2130812Smarcel * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3130812Smarcel *
4130812Smarcel * Copyright (c) 2009, Oleksandr Tymoshenko <gonzo@FreeBSD.org>
5130812Smarcel * Copyright (c) 2010-2011, Juli Mallett <jmallett@FreeBSD.org>
6130812Smarcel * All rights reserved.
7130812Smarcel *
8130812Smarcel * Redistribution and use in source and binary forms, with or without
9130812Smarcel * modification, are permitted provided that the following conditions
10130812Smarcel * are met:
11130812Smarcel * 1. Redistributions of source code must retain the above copyright
12130812Smarcel *    notice unmodified, this list of conditions, and the following
13130812Smarcel *    disclaimer.
14130812Smarcel * 2. Redistributions in binary form must reproduce the above copyright
15130812Smarcel *    notice, this list of conditions and the following disclaimer in the
16130812Smarcel *    documentation and/or other materials provided with the distribution.
17130812Smarcel *
18130812Smarcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19130812Smarcel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20130812Smarcel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21130812Smarcel * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22130812Smarcel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23130812Smarcel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24130812Smarcel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25130812Smarcel * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26130812Smarcel * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27130812Smarcel * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28130812Smarcel * SUCH DAMAGE.
29130812Smarcel */
30130812Smarcel
31130812Smarcel/*
32130812Smarcel * Watchdog driver for Cavium Octeon
33130812Smarcel */
34130812Smarcel
35130812Smarcel#include <sys/cdefs.h>
36130812Smarcel__FBSDID("$FreeBSD: stable/11/sys/mips/cavium/octeon_wdog.c 330897 2018-03-14 03:19:51Z eadler $");
37130812Smarcel
38130812Smarcel#include <sys/param.h>
39130812Smarcel#include <sys/systm.h>
40130812Smarcel#include <sys/watchdog.h>
41130812Smarcel#include <sys/bus.h>
42130812Smarcel#include <sys/kernel.h>
43130812Smarcel#include <sys/module.h>
44130812Smarcel#include <sys/sysctl.h>
45130812Smarcel#include <sys/rman.h>
46130812Smarcel#include <sys/smp.h>
47130812Smarcel
48130812Smarcel#include <contrib/octeon-sdk/cvmx.h>
49130812Smarcel#include <mips/cavium/octeon_irq.h>
50130812Smarcel
51130812Smarcel#define	DEFAULT_TIMER_VAL	65535
52130812Smarcel
53130812Smarcelstruct octeon_wdog_softc {
54130812Smarcel	device_t sc_dev;
55130812Smarcel	struct octeon_wdog_core_softc {
56130812Smarcel		int csc_core;
57130812Smarcel		struct resource *csc_intr;
58130812Smarcel		void *csc_intr_cookie;
59130812Smarcel	} sc_cores[MAXCPU];
60130812Smarcel	int sc_armed;
61130812Smarcel	int sc_debug;
62130812Smarcel};
63130812Smarcel
64130812Smarcelextern void octeon_wdog_nmi_handler(void);
65130812Smarcelvoid octeon_wdog_nmi(void);
66130812Smarcel
67130812Smarcelstatic void octeon_watchdog_arm_core(int);
68130812Smarcelstatic void octeon_watchdog_disarm_core(int);
69130812Smarcelstatic int octeon_wdog_attach(device_t);
70130812Smarcelstatic void octeon_wdog_identify(driver_t *, device_t);
71130812Smarcelstatic int octeon_wdog_intr(void *);
72130812Smarcelstatic int octeon_wdog_probe(device_t);
73130812Smarcelstatic void octeon_wdog_setup(struct octeon_wdog_softc *, int);
74130812Smarcelstatic void octeon_wdog_sysctl(device_t);
75130812Smarcelstatic void octeon_wdog_watchdog_fn(void *, u_int, int *);
76130812Smarcel
77130812Smarcelvoid
78130812Smarcelocteon_wdog_nmi(void)
79130812Smarcel{
80130812Smarcel	int core;
81130812Smarcel
82130812Smarcel	core = cvmx_get_core_num();
83130812Smarcel
84130812Smarcel	printf("cpu%u: NMI detected\n", core);
85130812Smarcel	printf("cpu%u: Exception PC: %p\n", core, (void *)mips_rd_excpc());
86130812Smarcel	printf("cpu%u: status %#x cause %#x\n", core, mips_rd_status(), mips_rd_cause());
87130812Smarcel
88130812Smarcel	/*
89130812Smarcel	 * This is the end
90130812Smarcel	 * Beautiful friend
91130812Smarcel	 *
92130812Smarcel	 * Just wait for Soft Reset to come and take us
93130812Smarcel	 */
94130812Smarcel	for (;;)
95130812Smarcel		continue;
96130812Smarcel}
97130812Smarcel
98130812Smarcelstatic void
99130812Smarcelocteon_watchdog_arm_core(int core)
100130812Smarcel{
101130812Smarcel	cvmx_ciu_wdogx_t ciu_wdog;
102130812Smarcel
103130812Smarcel	/* Poke it! */
104130812Smarcel	cvmx_write_csr(CVMX_CIU_PP_POKEX(core), 1);
105130812Smarcel
106130812Smarcel	/*
107130812Smarcel	 * XXX
108130812Smarcel	 * Perhaps if KDB is enabled, we should use mode=2 and drop into the
109130812Smarcel	 * debugger on NMI?
110130812Smarcel	 *
111130812Smarcel	 * XXX
112130812Smarcel	 * Timer should be calculated based on CPU frquency
113130812Smarcel	 */
114130812Smarcel	ciu_wdog.u64 = 0;
115130812Smarcel	ciu_wdog.s.len = DEFAULT_TIMER_VAL;
116130812Smarcel	ciu_wdog.s.mode = 3;
117130812Smarcel	cvmx_write_csr(CVMX_CIU_WDOGX(core), ciu_wdog.u64);
118130812Smarcel}
119130812Smarcel
120130812Smarcelstatic void
121130812Smarcelocteon_watchdog_disarm_core(int core)
122130812Smarcel{
123130812Smarcel
124130812Smarcel	cvmx_write_csr(CVMX_CIU_WDOGX(core), 0);
125130812Smarcel}
126130812Smarcel
127130812Smarcelstatic void
128130812Smarcelocteon_wdog_watchdog_fn(void *private, u_int cmd, int *error)
129130812Smarcel{
130130812Smarcel	struct octeon_wdog_softc *sc = private;
131130812Smarcel	int core;
132130812Smarcel
133130812Smarcel	cmd &= WD_INTERVAL;
134130812Smarcel	if (sc->sc_debug)
135130812Smarcel		device_printf(sc->sc_dev, "%s: cmd: %x\n", __func__, cmd);
136130812Smarcel	if (cmd > 0) {
137130812Smarcel		CPU_FOREACH(core)
138130812Smarcel			octeon_watchdog_arm_core(core);
139130812Smarcel		sc->sc_armed = 1;
140130812Smarcel		*error = 0;
141130812Smarcel	} else {
142130812Smarcel		if (sc->sc_armed) {
143130812Smarcel			CPU_FOREACH(core)
144130812Smarcel				octeon_watchdog_disarm_core(core);
145130812Smarcel			sc->sc_armed = 0;
146130812Smarcel		}
147130812Smarcel	}
148130812Smarcel}
149130812Smarcel
150130812Smarcelstatic void
151130812Smarcelocteon_wdog_sysctl(device_t dev)
152130812Smarcel{
153130812Smarcel	struct octeon_wdog_softc *sc = device_get_softc(dev);
154130812Smarcel
155130812Smarcel        struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev);
156130812Smarcel        struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev);
157130812Smarcel
158130812Smarcel        SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
159130812Smarcel                "debug", CTLFLAG_RW, &sc->sc_debug, 0,
160130812Smarcel                "enable watchdog debugging");
161130812Smarcel        SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
162130812Smarcel                "armed", CTLFLAG_RD, &sc->sc_armed, 0,
163130812Smarcel                "whether the watchdog is armed");
164130812Smarcel}
165130812Smarcel
166130812Smarcelstatic void
167130812Smarcelocteon_wdog_setup(struct octeon_wdog_softc *sc, int core)
168130812Smarcel{
169130812Smarcel	struct octeon_wdog_core_softc *csc;
170130812Smarcel	int rid, error;
171130812Smarcel
172130812Smarcel	csc = &sc->sc_cores[core];
173130812Smarcel
174130812Smarcel	csc->csc_core = core;
175130812Smarcel
176130812Smarcel	/* Interrupt part */
177130812Smarcel	rid = 0;
178130812Smarcel	csc->csc_intr = bus_alloc_resource(sc->sc_dev, SYS_RES_IRQ, &rid,
179130812Smarcel	    OCTEON_IRQ_WDOG0 + core, OCTEON_IRQ_WDOG0 + core, 1, RF_ACTIVE);
180130812Smarcel	if (csc->csc_intr == NULL)
181130812Smarcel		panic("%s: bus_alloc_resource for core %u failed",
182130812Smarcel		    __func__, core);
183130812Smarcel
184130812Smarcel	error = bus_setup_intr(sc->sc_dev, csc->csc_intr, INTR_TYPE_MISC,
185130812Smarcel	    octeon_wdog_intr, NULL, csc, &csc->csc_intr_cookie);
186130812Smarcel	if (error != 0)
187		panic("%s: bus_setup_intr for core %u: %d", __func__, core,
188		    error);
189
190	bus_bind_intr(sc->sc_dev, csc->csc_intr, core);
191	bus_describe_intr(sc->sc_dev, csc->csc_intr, csc->csc_intr_cookie,
192	    "cpu%u", core);
193
194	if (sc->sc_armed) {
195		/* Armed by default.  */
196		octeon_watchdog_arm_core(core);
197	} else {
198		/* Disarmed by default.  */
199		octeon_watchdog_disarm_core(core);
200	}
201}
202
203static int
204octeon_wdog_intr(void *arg)
205{
206	struct octeon_wdog_core_softc *csc = arg;
207
208	KASSERT(csc->csc_core == cvmx_get_core_num(),
209	    ("got watchdog interrupt for core %u on core %u.",
210	     csc->csc_core, cvmx_get_core_num()));
211
212	(void)csc;
213
214	/* Poke it! */
215	cvmx_write_csr(CVMX_CIU_PP_POKEX(cvmx_get_core_num()), 1);
216
217	return (FILTER_HANDLED);
218}
219
220static int
221octeon_wdog_probe(device_t dev)
222{
223
224	device_set_desc(dev, "Cavium Octeon watchdog timer");
225	return (0);
226}
227
228static int
229octeon_wdog_attach(device_t dev)
230{
231	struct octeon_wdog_softc *sc = device_get_softc(dev);
232	uint64_t *nmi_handler = (uint64_t*)octeon_wdog_nmi_handler;
233	int core, i;
234
235	/* Initialise */
236	sc->sc_armed = 0; /* XXX Ought to be a tunable / config option.  */
237	sc->sc_debug = 0;
238
239	sc->sc_dev = dev;
240	EVENTHANDLER_REGISTER(watchdog_list, octeon_wdog_watchdog_fn, sc, 0);
241	octeon_wdog_sysctl(dev);
242
243	for (i = 0; i < 16; i++) {
244		cvmx_write_csr(CVMX_MIO_BOOT_LOC_ADR, i * 8);
245		cvmx_write_csr(CVMX_MIO_BOOT_LOC_DAT, nmi_handler[i]);
246        }
247
248	cvmx_write_csr(CVMX_MIO_BOOT_LOC_CFGX(0), 0x81fc0000);
249
250	CPU_FOREACH(core)
251		octeon_wdog_setup(sc, core);
252	return (0);
253}
254
255static void
256octeon_wdog_identify(driver_t *drv, device_t parent)
257{
258
259	BUS_ADD_CHILD(parent, 0, "owdog", 0);
260}
261
262static device_method_t octeon_wdog_methods[] = {
263	DEVMETHOD(device_identify, octeon_wdog_identify),
264
265	DEVMETHOD(device_probe, octeon_wdog_probe),
266	DEVMETHOD(device_attach, octeon_wdog_attach),
267	{0, 0},
268};
269
270static driver_t octeon_wdog_driver = {
271	"owdog",
272	octeon_wdog_methods,
273	sizeof(struct octeon_wdog_softc),
274};
275static devclass_t octeon_wdog_devclass;
276
277DRIVER_MODULE(owdog, ciu, octeon_wdog_driver, octeon_wdog_devclass, 0, 0);
278