# NetBSD Port > [!WARNING] > This page is very much a work in progress! ## Building I've forked a copy of the NetBSD sources. ### Kernel NetBSD contains a port called **hb68k** which is designed for any 680x0 sbc type of board and I used that to create a variant for my SBC, which I've called **SBC3**. ``` git clone https://github.com/znac049/netbsd-src.git cd netbsd-src ./build.sh -U -u -j4 -O ~/obj -m hb68k tools ./build.sh -U -u -j4 -O ~/obj -m hb68k kernel=SBC3 ./build.sh -U -u -j4 -O ~/obj -m hb68k distribution ``` - The first step builds the tools (compiler etc) that will be used to build the kernel and userspace. - The next step builds the kernel. - The final step takes a Long Time(tm) and builds the userspace, which will need to be added to the root filesystem. ### Root File System ``` ./mksbc3img.sh -k ~/obj/sys/arch/hb68k/compile/SBC3/netbsd -s 4096 sbc3.img ``` ## Device Driver Templates I asked Claude about templates for coding device drivers. Here's what it said: ### Uart The template is myuart.c. It’s a complete single-channel UART driver for hb68k: it attaches through the device tree, can be the system console, and gives you a normal tty with interrupt-driven I/O. What you change: the two CHIP-SPECIFIC sections near the top - Register map: placeholder offsets and bit definitions for data, status, control, baud divisor and interrupt enable. The real byte offset is reg << reg-shift, so register spacing comes from the device tree rather than the code. - Eight small hardware functions, the only code that touches the chip: - `rx_ready` / `getc` - `tx_ready` / `putc` - `clear_errors` - `set_intr` - `intr_pending` - `program`, which sets speed and frame format from termios flags. The example assumes a 16× divisor UART. - The `compatible` string your device-tree node will use. Everything below those sections is generic, and you shouldn’t need to touch it for a first version: console, autoconfiguration, interrupt handling, soft interrupt, and the tty and cdevsw entry points. Each part has a comment explaining its role and the locking. #### How data moves: - Receive: the hard interrupt copies bytes into a ring buffer. A soft interrupt later passes them to the tty layer, because the hard interrupt mustn’t call into it. - Transmit: the tty layer hands over a chunk of output. The transmit interrupt feeds it to the chip, then switches itself off when the chunk is done. - Console: the console uses polling and works from very early in boot. BREAK on the console drops into DDB. Setup steps, all listed at the top of the file: 1. Add the device/attach/file lines to files.hb68k. 1. Add a major number to majors.hb68k. 14 is free in your tree. 1. Add a myuart* at fdt? pass 5 line to the kernel config. 1. Add a device-tree node. 1. On the target, make the device node by hand (mknod /dev/ttyM0 c 14 0), since MAKEDEV won’t know about the driver. Deliberately left out: - modem control (carrier is treated as always present) - hardware flow control - split input/output speeds - sending BREAK The ioctl function marks where the last of these goes. Your mc68681_duart.c is a worked example of the same structure extended to a two-channel chip, with an IVR, if you want to compare. One constraint carries over from your board: the driver’s lock is a spin mutex at IPL_VM, so the chip’s interrupt level in the device tree must be 5 or lower. ``` /* $NetBSD$ */ /*- * Copyright (c) 2026 The NetBSD Foundation, Inc. * All rights reserved. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * myuart -- template driver for a simple, single-channel UART attached * via Flattened Device Tree (FDT), with console and tty support. * * ====================================================================== * HOW TO USE THIS TEMPLATE * ====================================================================== * * 1. Copy this file to e.g. sys/arch/hb68k/dev/<chip>.c and replace * "myuart" with your driver name throughout (keep it short; it * becomes the device name, e.g. "myuart0"). * * 2. Fill in the section marked "CHIP-SPECIFIC" below. Everything * else is generic NetBSD plumbing that works for most UARTs and * shouldn't need touching for a first version. * * 3. Hook it into the build: * * sys/arch/hb68k/conf/files.hb68k: * device myuart: tty * attach myuart at fdt * file arch/hb68k/dev/myuart.c myuart * * sys/arch/hb68k/conf/majors.hb68k (pick an unused number < 144): * device-major myuart char 14 myuart * * Kernel config (e.g. conf/SBC3): * myuart* at fdt? pass 5 * * 4. Describe the chip in the device tree: * * serial@XXXXXX { * compatible = "vendor,mychip"; (must match compat_data[]) * reg = <0xXXXXXX 0x10>; (base, size of register block) * reg-shift = <1>; (log2 of register spacing) * clock-frequency = <1843200>; (UART input clock, Hz) * interrupts = <3 0>; (IPL, vector; 0 = auto-vector) * }; * * To make it the console, point /chosen/stdout-path at it, e.g. * stdout-path = "serial1:9600n8"; * with an alias "serial1" for the node. The platform code must * provide a static mapping for its registers (as sbc3_machdep.c * does for the I/O window), because the console is set up before * the VM system is running. * * 5. Device nodes: MAKEDEV doesn't know about a new driver, so create * them by hand on the target (char major from step 3, minor = unit): * mknod /dev/ttyM0 c 14 0 * and add a line to /etc/ttys if you want a login prompt on it. * * ====================================================================== * HOW THE PIECES FIT TOGETHER * ====================================================================== * * Autoconfiguration myuart_match() / myuart_attach() are called when * the FDT bus finds a node whose "compatible" * matches compat_data[]. attach maps the registers, * allocates a tty and hooks up the interrupt. * * Console myuart_cnattach() runs very early (from * consinit(), before autoconfiguration) if the * node is /chosen/stdout-path. The console uses * polled I/O only: myuart_cngetc()/myuart_cnputc(). * * Hard interrupt myuart_intr() runs at the device's hardware IPL. * It must be quick: it moves received bytes into a * ring buffer, feeds the transmitter, and schedules * the soft interrupt. It must not call into the * tty layer. * * Soft interrupt myuart_soft() runs later at IPL_SOFTSERIAL. It * hands received bytes to the line discipline and, * when a transmit burst finishes, asks the tty * layer for more output. * * tty entry points open/close/read/write/ioctl/poll come from the * cdevsw table; myuartstart() and myuartparam() * are called by the tty layer to start output and * to change speed/framing. * * Locking sc_lock is a spin mutex at the device's IPL. It * protects the ring buffer, the transmit pointers, * the interrupt-enable shadow and register access * shared between the interrupt handler and the * rest of the driver. * * This template deliberately leaves out modem control (carrier is * assumed always present), hardware flow control, split speeds, and * power management. Add them later if your hardware needs them. */ #include <sys/cdefs.h> __KERNEL_RCSID(0, "$NetBSD$"); #include <sys/param.h> #include <sys/bus.h> #include <sys/conf.h> #include <sys/device.h> #include <sys/fcntl.h> #include <sys/intr.h> #include <sys/kauth.h> #include <sys/poll.h> #include <sys/proc.h> #include <sys/syslog.h> #include <sys/systm.h> #include <sys/tty.h> #include <dev/cons.h> #include <dev/fdt/fdtvar.h> #include <dev/fdt/fdt_console.h> /* * ###################################################################### * CHIP-SPECIFIC: registers * ###################################################################### * * Replace these with your chip's register map. Offsets are register * *numbers*; the real byte offset is (reg << reg-shift), so the same * code works whether the chip sits on every byte, every other byte, * or every fourth byte. * * The placeholder layout below is a typical simple UART: * DATA read: receive holding register, write: transmit holding * STATUS read-only status bits * CTRL frame format and enables * BAUD baud rate divisor (16 bits, split over two registers) * IER interrupt enable bits */ #define MYUART_REG_DATA 0 #define MYUART_REG_STATUS 1 #define MYUART_REG_CTRL 2 #define MYUART_REG_BAUDLO 3 #define MYUART_REG_BAUDHI 4 #define MYUART_REG_IER 5 /* STATUS bits */ #define MYUART_SR_RXRDY 0x01 /* a received byte is waiting */ #define MYUART_SR_TXRDY 0x02 /* transmit register is empty */ #define MYUART_SR_OVERRUN 0x10 /* receiver overrun */ #define MYUART_SR_PARITY 0x20 /* parity error on this byte */ #define MYUART_SR_FRAMING 0x40 /* framing error on this byte */ #define MYUART_SR_BREAK 0x80 /* break received */ /* CTRL bits */ #define MYUART_CTRL_ENABLE 0x01 /* enable receiver + transmitter */ #define MYUART_CTRL_8BIT 0x02 /* 8 data bits (else 7) */ #define MYUART_CTRL_PARENB 0x04 /* parity enable */ #define MYUART_CTRL_PARODD 0x08 /* odd parity (else even) */ #define MYUART_CTRL_2STOP 0x10 /* 2 stop bits (else 1) */ /* IER bits */ #define MYUART_IER_RX 0x01 /* interrupt when RXRDY */ #define MYUART_IER_TX 0x02 /* interrupt when TXRDY */ #define MYUART_DEFAULT_FREQ 1843200 /* if the DT has no clock-frequency */ #define MYUART_DEFAULT_SPEED 9600 /* if stdout-path has no speed */ /* * Everything needed to talk to the registers. Shared by the console * (which runs before the softc exists) and the attached device. */ struct myuart_regs { bus_space_tag_t ur_bst; bus_space_handle_t ur_bsh; u_int ur_shift; /* reg-shift */ u_int ur_freq; /* input clock, Hz */ }; static inline uint8_t myuart_read(const struct myuart_regs *ur, u_int reg) { return bus_space_read_1(ur->ur_bst, ur->ur_bsh, reg << ur->ur_shift); } static inline void myuart_write(const struct myuart_regs *ur, u_int reg, uint8_t val) { bus_space_write_1(ur->ur_bst, ur->ur_bsh, reg << ur->ur_shift, val); } /* * ###################################################################### * CHIP-SPECIFIC: low-level operations * ###################################################################### * * These eight small functions are the whole hardware interface. The * rest of the driver only ever touches the chip through them. */ /* Is a received byte waiting? Returns the status bits for that byte. */ static inline bool myuart_hw_rx_ready(const struct myuart_regs *ur, uint8_t *srp) { const uint8_t sr = myuart_read(ur, MYUART_REG_STATUS); *srp = sr; return (sr & MYUART_SR_RXRDY) != 0; } /* Read the received byte. Call only after rx_ready returned true. */ static inline uint8_t myuart_hw_getc(const struct myuart_regs *ur) { return myuart_read(ur, MYUART_REG_DATA); } /* Can another byte be written to the transmitter? */ static inline bool myuart_hw_tx_ready(const struct myuart_regs *ur) { return (myuart_read(ur, MYUART_REG_STATUS) & MYUART_SR_TXRDY) != 0; } /* Write one byte to the transmitter. */ static inline void myuart_hw_putc(const struct myuart_regs *ur, uint8_t c) { myuart_write(ur, MYUART_REG_DATA, c); } /* * Clear any error / break condition after it has been read. Many * chips clear errors automatically when the data register is read; * if yours does, leave this empty. */ static inline void myuart_hw_clear_errors(const struct myuart_regs *ur) { /* e.g. myuart_write(ur, MYUART_REG_CTRL, ... | RESET_ERRORS); */ } /* * Set the interrupt enables. `rx' and `tx' say which sources may * interrupt. The transmit interrupt must only be enabled while there * is something to send: most UARTs assert "transmitter empty" * continuously, so leaving it enabled would interrupt forever. */ static inline void myuart_hw_set_intr(const struct myuart_regs *ur, bool rx, bool tx) { myuart_write(ur, MYUART_REG_IER, (rx ? MYUART_IER_RX : 0) | (tx ? MYUART_IER_TX : 0)); } /* * Did this chip cause the current interrupt? Used to share an * interrupt level with other devices. If your chip has an "interrupt * pending" bit, test that instead. */ static inline bool myuart_hw_intr_pending(const struct myuart_regs *ur, bool tx_enabled) { const uint8_t sr = myuart_read(ur, MYUART_REG_STATUS); return (sr & (MYUART_SR_RXRDY | MYUART_SR_BREAK)) != 0 || (tx_enabled && (sr & MYUART_SR_TXRDY) != 0); } /* * Program speed and frame format, and enable the receiver and * transmitter. Returns 0, or EINVAL if the speed or format can't be * done. Called with interrupts for this device blocked. * * `cflag' uses the termios(4) bits: CSIZE (CS5..CS8), CSTOPB, PARENB, * PARODD. */ static int myuart_hw_program(const struct myuart_regs *ur, int speed, tcflag_t cflag) { uint8_t ctrl = MYUART_CTRL_ENABLE; u_int div; if (speed <= 0) { return EINVAL; } /* * Example for a 16x-oversampling divisor UART: * divisor = clock / (16 * speed), rounded to nearest. * Reject rates more than 3% off. */ div = (ur->ur_freq + 8 * speed) / (16 * speed); if (div == 0 || div > 0xffff) { return EINVAL; } const u_int actual = ur->ur_freq / (16 * div); const u_int err = actual > (u_int)speed ? actual - speed : speed - actual; if (err * 100 > (u_int)speed * 3) { return EINVAL; } switch (ISSET(cflag, CSIZE)) { case CS8: ctrl |= MYUART_CTRL_8BIT; break; case CS7: break; default: return EINVAL; /* this example chip only does 7/8 */ } if (ISSET(cflag, PARENB)) { ctrl |= MYUART_CTRL_PARENB; if (ISSET(cflag, PARODD)) { ctrl |= MYUART_CTRL_PARODD; } } if (ISSET(cflag, CSTOPB)) { ctrl |= MYUART_CTRL_2STOP; } myuart_write(ur, MYUART_REG_CTRL, 0); /* quiesce */ myuart_write(ur, MYUART_REG_BAUDLO, div & 0xff); myuart_write(ur, MYUART_REG_BAUDHI, (div >> 8) & 0xff); myuart_write(ur, MYUART_REG_CTRL, ctrl); return 0; } /* * ###################################################################### * Device tree matching * ###################################################################### * * List every "compatible" string this driver handles. */ static const struct device_compatible_entry compat_data[] = { { .compat = "vendor,mychip" }, DEVICE_COMPAT_EOL }; /* * ###################################################################### * Generic driver below this line * ###################################################################### */ #define MYUART_RBUF_SIZE 256 /* receive ring size, power of 2 */ struct myuart_softc { device_t sc_dev; struct myuart_regs sc_regs; kmutex_t sc_lock; /* see "Locking" above */ void *sc_ih; /* hardware interrupt */ void *sc_si; /* soft interrupt */ struct tty *sc_tty; bool sc_console; /* we are the console */ bool sc_rx_on; /* shadow of RX intr enable */ bool sc_tx_on; /* shadow of TX intr enable */ /* Receive ring: (status << 8) | data, filled by myuart_intr(). */ uint16_t sc_rbuf[MYUART_RBUF_SIZE]; u_int sc_rbput; u_int sc_rbget; u_int sc_rbovf; /* bytes dropped, ring full */ /* Transmit burst, set up by myuartstart(). */ u_char *sc_tba; /* next byte to send */ int sc_tbc; /* bytes left */ bool sc_tx_done; /* burst finished */ }; static int myuart_match(device_t, cfdata_t, void *); static void myuart_attach(device_t, device_t, void *); CFATTACH_DECL_NEW(myuart, sizeof(struct myuart_softc), myuart_match, myuart_attach, NULL, NULL); extern struct cfdriver myuart_cd; static dev_type_open(myuartopen); static dev_type_close(myuartclose); static dev_type_read(myuartread); static dev_type_write(myuartwrite); static dev_type_ioctl(myuartioctl); static dev_type_stop(myuartstop); static dev_type_tty(myuarttty); static dev_type_poll(myuartpoll); /* The character device switch; its major number comes from majors.*. */ const struct cdevsw myuart_cdevsw = { .d_open = myuartopen, .d_close = myuartclose, .d_read = myuartread, .d_write = myuartwrite, .d_ioctl = myuartioctl, .d_stop = myuartstop, .d_tty = myuarttty, .d_poll = myuartpoll, .d_mmap = nommap, .d_kqfilter = ttykqfilter, .d_discard = nodiscard, .d_flag = D_TTY }; static void myuartstart(struct tty *); static int myuartparam(struct tty *, struct termios *); static int myuart_intr(void *); static void myuart_soft(void *); /* Call with sc_lock held: update the hardware interrupt enables. */ static void myuart_update_intr(struct myuart_softc *sc) { KASSERT(mutex_owned(&sc->sc_lock)); myuart_hw_set_intr(&sc->sc_regs, sc->sc_rx_on, sc->sc_tx_on); } /* * ---------------------------------------------------------------------- * Console * ---------------------------------------------------------------------- * * The console is set up long before autoconfiguration, so it can't use * the softc. Its state lives in this static structure instead. */ static struct { struct myuart_regs regs; bus_addr_t addr; /* to recognise ourselves in attach */ int speed; tcflag_t cflag; bool active; } myuart_cons; static cnm_state_t myuart_cnm_state; /* console "magic" (break -> DDB) */ static int myuart_cngetc(dev_t dev) { uint8_t sr; int s, c; s = splhigh(); while (!myuart_hw_rx_ready(&myuart_cons.regs, &sr)) { continue; } c = myuart_hw_getc(&myuart_cons.regs); splx(s); return c; } static void myuart_cnputc(dev_t dev, int c) { int s; s = splhigh(); while (!myuart_hw_tx_ready(&myuart_cons.regs)) { continue; } myuart_hw_putc(&myuart_cons.regs, c); splx(s); } static void myuart_cnpollc(dev_t dev, int on) { /* Nothing to do: the console routines always poll. */ } static struct consdev myuart_consdev = { .cn_getc = myuart_cngetc, .cn_putc = myuart_cnputc, .cn_pollc = myuart_cnpollc, .cn_dev = NODEV, /* filled in by myuart_attach() */ .cn_pri = CN_NORMAL, }; static int myuart_console_match(int phandle) { return of_compatible_match(phandle, compat_data); } /* * Called from consinit() when /chosen/stdout-path points at one of our * nodes. `uart_freq' is the node's clock-frequency (or the platform's * fallback). */ static void myuart_cnattach(struct fdt_attach_args *faa, u_int uart_freq) { const int phandle = faa->faa_phandle; struct myuart_regs * const ur = &myuart_cons.regs; bus_addr_t addr; bus_size_t size; if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { return; } ur->ur_bst = faa->faa_bst; ur->ur_shift = fdtbus_get_reg_shift(phandle, 0); ur->ur_freq = uart_freq ? uart_freq : MYUART_DEFAULT_FREQ; /* This must hit a static mapping: UVM isn't running yet. */ if (bus_space_map(ur->ur_bst, addr, size, 0, &ur->ur_bsh) != 0) { return; } /* Speed and format from stdout-path, e.g. "serial0:9600n8". */ myuart_cons.speed = fdtbus_get_stdout_speed(); if (myuart_cons.speed < 0) { myuart_cons.speed = MYUART_DEFAULT_SPEED; } myuart_cons.cflag = fdtbus_get_stdout_flags(); myuart_hw_set_intr(ur, false, false); if (myuart_hw_program(ur, myuart_cons.speed, myuart_cons.cflag) != 0) { /* Fall back to something sane rather than staying silent. */ myuart_cons.speed = MYUART_DEFAULT_SPEED; myuart_cons.cflag = CS8 | CREAD | CLOCAL; if (myuart_hw_program(ur, myuart_cons.speed, myuart_cons.cflag) != 0) { return; } } myuart_cons.addr = addr; myuart_cons.active = true; cn_tab = &myuart_consdev; } static const struct fdt_console myuart_fdt_console = { .match = myuart_console_match, .consinit = myuart_cnattach, }; FDT_CONSOLE(myuart, &myuart_fdt_console); /* * ---------------------------------------------------------------------- * Autoconfiguration * ---------------------------------------------------------------------- */ static int myuart_match(device_t parent, cfdata_t cf, void *aux) { struct fdt_attach_args * const faa = aux; return of_compatible_match(faa->faa_phandle, compat_data); } static void myuart_attach(device_t parent, device_t self, void *aux) { struct myuart_softc * const sc = device_private(self); struct fdt_attach_args * const faa = aux; const int phandle = faa->faa_phandle; struct myuart_regs * const ur = &sc->sc_regs; char intrstr[128]; bus_addr_t addr; bus_size_t size; struct tty *tp; sc->sc_dev = self; /* --- Registers ------------------------------------------------ */ if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { aprint_error(": couldn't get registers\n"); return; } ur->ur_bst = faa->faa_bst; ur->ur_shift = fdtbus_get_reg_shift(phandle, 0); if (of_getprop_uint32(phandle, "clock-frequency", &ur->ur_freq) != 0) { ur->ur_freq = MYUART_DEFAULT_FREQ; } if (bus_space_map(ur->ur_bst, addr, size, 0, &ur->ur_bsh) != 0) { aprint_error(": couldn't map registers\n"); return; } sc->sc_console = myuart_cons.active && myuart_cons.addr == addr; /* * The lock is a spin mutex at IPL_VM, which on hb68k blocks * hardware levels 1-5. So the device's interrupt level (first * cell of "interrupts") must be 5 or lower. */ mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_VM); aprint_naive("\n"); aprint_normal(": UART, %u Hz clock%s\n", ur->ur_freq, sc->sc_console ? ", console" : ""); /* --- Quiet the chip (but leave the console's settings alone) - */ mutex_spin_enter(&sc->sc_lock); sc->sc_rx_on = sc->sc_tx_on = false; myuart_update_intr(sc); mutex_spin_exit(&sc->sc_lock); /* --- tty ------------------------------------------------------ */ tp = tty_alloc(); tp->t_oproc = myuartstart; /* tty layer: "start output" */ tp->t_param = myuartparam; /* tty layer: "change settings" */ tty_attach(tp); sc->sc_tty = tp; if (sc->sc_console) { /* Tell the console layer which /dev entry is the console. */ myuart_consdev.cn_dev = makedev( cdevsw_lookup_major(&myuart_cdevsw), device_unit(self)); aprint_normal_dev(self, "console\n"); } /* --- Interrupts ----------------------------------------------- */ sc->sc_si = softint_establish(SOFTINT_SERIAL, myuart_soft, sc); if (sc->sc_si == NULL) { aprint_error_dev(self, "couldn't establish soft interrupt\n"); return; } if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { aprint_error_dev(self, "couldn't decode interrupt\n"); return; } sc->sc_ih = fdtbus_intr_establish_xname(phandle, 0, IPL_VM, FDT_INTR_MPSAFE, myuart_intr, sc, device_xname(self)); if (sc->sc_ih == NULL) { aprint_error_dev(self, "couldn't establish interrupt at %s\n", intrstr); return; } aprint_normal_dev(self, "interrupting at %s\n", intrstr); /* * The console listens all the time, so that a BREAK (or the * configured magic sequence) can drop into DDB even when no * process has the tty open. */ if (sc->sc_console) { cn_init_magic(&myuart_cnm_state); cn_set_magic("\047\001"); /* BREAK */ mutex_spin_enter(&sc->sc_lock); sc->sc_rx_on = true; myuart_update_intr(sc); mutex_spin_exit(&sc->sc_lock); } } /* * ---------------------------------------------------------------------- * Hardware interrupt * ---------------------------------------------------------------------- * * Runs at the device's IPL with sc_lock held. Keep it short. */ static void myuart_rxintr(struct myuart_softc *sc) { struct myuart_regs * const ur = &sc->sc_regs; const bool open = ISSET(sc->sc_tty->t_state, TS_ISOPEN); uint8_t sr, c; while (myuart_hw_rx_ready(ur, &sr)) { c = myuart_hw_getc(ur); if (sr & (MYUART_SR_OVERRUN | MYUART_SR_PARITY | MYUART_SR_FRAMING | MYUART_SR_BREAK)) { myuart_hw_clear_errors(ur); } /* Console magic: BREAK (or a byte sequence) -> DDB. */ if (sc->sc_console) { if (sr & MYUART_SR_BREAK) { cn_check_magic(myuart_consdev.cn_dev, CNC_BREAK, myuart_cnm_state); } else { cn_check_magic(myuart_consdev.cn_dev, c, myuart_cnm_state); } } if (!open || (sr & MYUART_SR_BREAK)) { continue; /* nobody listening, or a BREAK */ } if (sc->sc_rbput - sc->sc_rbget >= MYUART_RBUF_SIZE) { sc->sc_rbovf++; /* ring full: drop it */ continue; } sc->sc_rbuf[sc->sc_rbput++ & (MYUART_RBUF_SIZE - 1)] = ((uint16_t)sr << 8) | c; } } static void myuart_txintr(struct myuart_softc *sc) { struct myuart_regs * const ur = &sc->sc_regs; /* Fill the transmitter (or its FIFO) as far as it will go. */ while (sc->sc_tbc > 0 && myuart_hw_tx_ready(ur)) { myuart_hw_putc(ur, *sc->sc_tba++); sc->sc_tbc--; } if (sc->sc_tbc == 0) { /* Burst done: stop TX interrupts, let the softint refill. */ sc->sc_tx_on = false; myuart_update_intr(sc); sc->sc_tx_done = true; } } static int myuart_intr(void *arg) { struct myuart_softc * const sc = arg; mutex_spin_enter(&sc->sc_lock); if (!myuart_hw_intr_pending(&sc->sc_regs, sc->sc_tx_on)) { mutex_spin_exit(&sc->sc_lock); return 0; /* not ours (shared interrupt level) */ } myuart_rxintr(sc); if (sc->sc_tx_on) { myuart_txintr(sc); } mutex_spin_exit(&sc->sc_lock); softint_schedule(sc->sc_si); return 1; } /* * ---------------------------------------------------------------------- * Soft interrupt * ---------------------------------------------------------------------- * * Runs at IPL_SOFTSERIAL, where it's safe to call the tty layer. */ static void myuart_soft(void *arg) { struct myuart_softc * const sc = arg; struct tty * const tp = sc->sc_tty; uint16_t ent; u_int ovf; bool tx_done; int c; /* Report ring-buffer overflows (not too often: use log(9)). */ mutex_spin_enter(&sc->sc_lock); ovf = sc->sc_rbovf; sc->sc_rbovf = 0; mutex_spin_exit(&sc->sc_lock); if (ovf != 0) { log(LOG_WARNING, "%s: %u receive overflow(s)\n", device_xname(sc->sc_dev), ovf); } /* Hand received bytes to the line discipline. */ for (;;) { mutex_spin_enter(&sc->sc_lock); if (sc->sc_rbget == sc->sc_rbput) { mutex_spin_exit(&sc->sc_lock); break; } ent = sc->sc_rbuf[sc->sc_rbget++ & (MYUART_RBUF_SIZE - 1)]; mutex_spin_exit(&sc->sc_lock); c = ent & 0xff; if (ent & (MYUART_SR_FRAMING << 8)) { SET(c, TTY_FE); } if (ent & (MYUART_SR_PARITY << 8)) { SET(c, TTY_PE); } if (ISSET(tp->t_state, TS_ISOPEN)) { (*tp->t_linesw->l_rint)(c, tp); } } /* If a transmit burst finished, release it and ask for more. */ mutex_spin_enter(&sc->sc_lock); tx_done = sc->sc_tx_done; sc->sc_tx_done = false; mutex_spin_exit(&sc->sc_lock); if (tx_done) { CLR(tp->t_state, TS_BUSY); if (ISSET(tp->t_state, TS_FLUSH)) { CLR(tp->t_state, TS_FLUSH); } else { ndflush(&tp->t_outq, (int)(sc->sc_tba - tp->t_outq.c_cf)); } (*tp->t_linesw->l_start)(tp); } } /* * ---------------------------------------------------------------------- * tty layer callbacks * ---------------------------------------------------------------------- */ static struct myuart_softc * myuart_lookup(dev_t dev) { struct myuart_softc *sc; sc = device_lookup_private(&myuart_cd, TTUNIT(dev)); if (sc == NULL || sc->sc_si == NULL || sc->sc_ih == NULL) { return NULL; /* no such unit, or attach failed */ } return sc; } /* * Start output: take a contiguous chunk of the tty's output queue and * let the transmit interrupt send it. */ static void myuartstart(struct tty *tp) { struct myuart_softc * const sc = myuart_lookup(tp->t_dev); int s; if (sc == NULL) { return; } s = spltty(); if (ISSET(tp->t_state, TS_BUSY | TS_TIMEOUT | TS_TTSTOP)) { goto out; /* already sending, or stopped (^S) */ } if (!ttypull(tp)) { goto out; /* nothing to send */ } mutex_spin_enter(&sc->sc_lock); sc->sc_tba = tp->t_outq.c_cf; sc->sc_tbc = ndqb(&tp->t_outq, 0); SET(tp->t_state, TS_BUSY); sc->sc_tx_on = true; /* interrupts as soon as TX is empty */ myuart_update_intr(sc); mutex_spin_exit(&sc->sc_lock); out: splx(s); } /* Stop output (e.g. on ^S or a flush). */ static void myuartstop(struct tty *tp, int flag) { struct myuart_softc * const sc = myuart_lookup(tp->t_dev); if (sc == NULL) { return; } mutex_spin_enter(&sc->sc_lock); if (ISSET(tp->t_state, TS_BUSY)) { sc->sc_tbc = 0; /* finish after the current byte */ if (!ISSET(tp->t_state, TS_TTSTOP)) { SET(tp->t_state, TS_FLUSH); } } mutex_spin_exit(&sc->sc_lock); } /* Change speed / framing (stty, tcsetattr). */ static int myuartparam(struct tty *tp, struct termios *t) { struct myuart_softc * const sc = myuart_lookup(tp->t_dev); int error; if (sc == NULL) { return ENXIO; } /* Unsupported in this template: split speeds, flow control. */ if (t->c_ispeed != 0 && t->c_ispeed != t->c_ospeed) { return EINVAL; } if (ISSET(t->c_cflag, CRTSCTS | MDMBUF)) { return EINVAL; } mutex_spin_enter(&sc->sc_lock); error = myuart_hw_program(&sc->sc_regs, t->c_ospeed, t->c_cflag); mutex_spin_exit(&sc->sc_lock); if (error) { return error; } tp->t_ispeed = 0; tp->t_ospeed = t->c_ospeed; tp->t_cflag = t->c_cflag; /* No modem control: carrier is always present. */ (void)(*tp->t_linesw->l_modem)(tp, 1); return 0; } /* * ---------------------------------------------------------------------- * cdevsw entry points * ---------------------------------------------------------------------- */ static int myuartopen(dev_t dev, int flag, int mode, struct lwp *l) { struct myuart_softc * const sc = myuart_lookup(dev); struct tty *tp; int error; if (sc == NULL) { return ENXIO; } tp = sc->sc_tty; if (kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_OPEN, tp)) { return EBUSY; } /* First open: set defaults and turn on receive interrupts. */ if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) { struct termios t; tp->t_dev = dev; t.c_ispeed = 0; if (sc->sc_console) { t.c_ospeed = myuart_cons.speed; t.c_cflag = myuart_cons.cflag; } else { t.c_ospeed = TTYDEF_SPEED; t.c_cflag = TTYDEF_CFLAG; } SET(t.c_cflag, CLOCAL); CLR(t.c_cflag, CRTSCTS | MDMBUF); tp->t_ospeed = 0; /* force myuartparam() to program */ (void)myuartparam(tp, &t); tp->t_iflag = TTYDEF_IFLAG; tp->t_oflag = TTYDEF_OFLAG; tp->t_lflag = TTYDEF_LFLAG; ttychars(tp); ttsetwater(tp); mutex_spin_enter(&sc->sc_lock); sc->sc_rbget = sc->sc_rbput = 0; sc->sc_rx_on = true; myuart_update_intr(sc); mutex_spin_exit(&sc->sc_lock); } error = ttyopen(tp, TTDIALOUT(dev), ISSET(flag, O_NONBLOCK)); if (error == 0) { error = (*tp->t_linesw->l_open)(dev, tp); } return error; } static int myuartclose(dev_t dev, int flag, int mode, struct lwp *l) { struct myuart_softc * const sc = myuart_lookup(dev); struct tty *tp; if (sc == NULL) { return 0; } tp = sc->sc_tty; if (!ISSET(tp->t_state, TS_ISOPEN)) { return 0; } (*tp->t_linesw->l_close)(tp, flag); ttyclose(tp); /* Last close: go quiet (the console keeps listening for BREAK). */ if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0 && !sc->sc_console) { mutex_spin_enter(&sc->sc_lock); sc->sc_rx_on = sc->sc_tx_on = false; myuart_update_intr(sc); mutex_spin_exit(&sc->sc_lock); } return 0; } static int myuartread(dev_t dev, struct uio *uio, int flag) { struct myuart_softc * const sc = myuart_lookup(dev); if (sc == NULL) { return ENXIO; } return (*sc->sc_tty->t_linesw->l_read)(sc->sc_tty, uio, flag); } static int myuartwrite(dev_t dev, struct uio *uio, int flag) { struct myuart_softc * const sc = myuart_lookup(dev); if (sc == NULL) { return ENXIO; } return (*sc->sc_tty->t_linesw->l_write)(sc->sc_tty, uio, flag); } static int myuartpoll(dev_t dev, int events, struct lwp *l) { struct myuart_softc * const sc = myuart_lookup(dev); if (sc == NULL) { return POLLHUP; } return (*sc->sc_tty->t_linesw->l_poll)(sc->sc_tty, events, l); } static struct tty * myuarttty(dev_t dev) { struct myuart_softc * const sc = myuart_lookup(dev); return sc != NULL ? sc->sc_tty : NULL; } static int myuartioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) { struct myuart_softc * const sc = myuart_lookup(dev); struct tty *tp; int error; if (sc == NULL) { return ENXIO; } tp = sc->sc_tty; /* Line discipline first, then the generic tty ioctls. */ error = (*tp->t_linesw->l_ioctl)(tp, cmd, data, flag, l); if (error != EPASSTHROUGH) { return error; } error = ttioctl(tp, cmd, data, flag, l); if (error != EPASSTHROUGH) { return error; } /* * Device-specific ioctls go here, e.g. TIOCSBRK / TIOCCBRK to * start and stop sending a break, or TIOCMGET / TIOCMSET for * modem control lines. */ return EPASSTHROUGH; } ``` ### Timer Confirmed: a NULL argument makes the dispatcher pass the interrupt frame. Now a compile test: The timer template is mytimer.c, built the same way as the UART one. I compiled it into an SBC3 kernel twice, once each way for the auto-reload setting, and both built cleanly. Then I took it back out of the tree. What you change: the CHIP-SPECIFIC sections - Registers: a placeholder register map for a simple down-counter (control, status, 24-bit reload). - Limits and reload behaviour: - MYTIMER_COUNT_MAX is the largest reload value. - MYTIMER_PRESCALE is any fixed divider in front of the counter. - MYTIMER_AUTO_RELOAD says whether the chip restarts itself at zero. If it doesn’t, the driver restarts it by hand on every tick. That loses a little time per tick, so the clock runs slightly slow. - Three hardware functions: - hw_stop() stops the counter and clears pending interrupts. - hw_start(count) loads the count and starts periodic interrupts. - hw_ack() clears the interrupt. Getting this wrong makes the machine appear to hang, because the CPU re-enters the handler immediately. How it fits into hb68k: a timer driver provides only mechanism. The clock code in hb68k/clock.c owns the policy and calls the driver through four functions in a small table (struct clock_if): | Function | When it’s called | What it does | | ---------------------- | -------------------------- | ------------ | | `clk_init` | once, late in boot | establishes the interrupt at IPL_SCHED | | `clk_arm(interval_us)` | once, right after init | starts periodic interrupts, e.g. every 10 000 µs for 100 Hz | | `clk_intr` | at the start of every tick | acknowledges the interrupt, and reloads if needed | | `clk_disarm` | shutdown or error | stops the timer | The file header explains: - How netbsd,hardclock / netbsd,statclock under /chosen pick each timer’s role, and what happens without them. - Why the timer belongs at IPL 6. - Which HZ values are allowed (they must divide 1 000 000). - Why the interrupt handler’s argument must be NULL. I checked this in the dispatcher: with NULL, the clock code receives the interrupt frame, which it needs. Setup is lighter than the UART’s: - Add device/attach/file to files.hb68k. - Add mytimer* at fdt? pass 3 to the config. - Add a device-tree node with clocks or clock-frequency, plus an interrupts entry. There’s no major number or /dev node, since nothing opens a timer. Left out deliberately: - A high-resolution timecounter. That needs a free-running counter, not a counter that resets every tick, so it depends on what your chip offers. - Randomising the statclock interval. hb68k’s clock code doesn’t do this yet either. ``` /* $NetBSD$ */ /*- * Copyright (c) 2026 The NetBSD Foundation, Inc. * All rights reserved. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* * mytimer -- template driver for a periodic hardware timer used as a * NetBSD/hb68k system clock, attached via Flattened Device Tree (FDT). * * ====================================================================== * HOW TO USE THIS TEMPLATE * ====================================================================== * * 1. Copy this file to e.g. sys/arch/hb68k/dev/<chip>_timer.c and * replace "mytimer" with your driver name throughout (it becomes * the device name, e.g. "mytimer0"). * * 2. Fill in the sections marked "CHIP-SPECIFIC" below. Everything * else is generic hb68k plumbing. * * 3. Hook it into the build: * * sys/arch/hb68k/conf/files.hb68k: * device mytimer * attach mytimer at fdt * file arch/hb68k/dev/mytimer.c mytimer * * Kernel config (e.g. conf/SBC3). Timers attach after the * interrupt controller (pass 2), hence pass 3: * mytimer* at fdt? pass 3 * * A timer has no /dev entry, so it needs no major number. * * 4. Describe the chip in the device tree: * * timer@XXXXXX { * compatible = "vendor,mytimer"; (must match compat_data[]) * reg = <0xXXXXXX 0x10>; (base, size of register block) * reg-shift = <1>; (log2 of register spacing) * clocks = <&osc>; (a fixed-clock node), OR * clock-frequency = <1000000>; (input clock in Hz) * interrupts = <6 0>; (IPL, vector; 0 = auto-vector) * }; * * and say which job it does under /chosen, using an alias: * * chosen { * netbsd,hardclock = "timer0"; (the main system tick) * netbsd,statclock = "timer1"; (optional, see below) * }; * aliases { * timer0 = "/obio/timer@XXXXXX"; * }; * * Without those properties, the first timer to attach becomes the * hardclock and the second (if any) the statclock. * * ====================================================================== * HOW hb68k USES A TIMER * ====================================================================== * * The machine-independent clock code in sys/arch/hb68k/hb68k/clock.c * owns the policy; a timer driver only provides mechanism, through the * five-member `struct clock_if' (machine/clockvar.h): * * clk_dev The timer's device_t. * * clk_init Called once, from cpu_initclocks(), late in boot. Must * establish the hardware interrupt with the handler it is * given (at IPL_SCHED) and return 0, or an error. * * clk_arm Start the timer interrupting periodically, every * `interval_us' microseconds (10000 for the usual * hz = 100). Called once, right after clk_init. * * clk_intr Called by the clock code at the start of every timer * interrupt. Acknowledge the interrupt here and, if the * chip doesn't reload by itself, restart the count. * * clk_disarm Stop the timer (used at shutdown / error paths). * * The driver registers itself with clock_attach() at the end of * attach. The clock code then decides which role the timer plays: * * hardclock Drives the system tick: timekeeping, scheduling, * callouts. Every system needs one. * * statclock Optional second timer for CPU-usage statistics and * profiling. With only one timer, the hardclock does both * jobs, and boot prints "No statclock; using hardclock." * * Interrupt level: the hardclock runs at IPL_SCHED, which on hb68k is * hardware level 6, so wire the timer to IPL 6 and use 6 as the first * cell of "interrupts". A lower level works, but the clock interrupt * can then be delayed or blocked by other devices and by the soft * interrupt level (1), so the system time will drift. * * hz must divide 1000000 exactly (the clock code checks this): use * 50, 100, 200, 250, 400, 500 or 1000. Set it in the kernel config * with "options HZ=..." if the default of 100 doesn't suit the * hardware. */ #include <sys/cdefs.h> __KERNEL_RCSID(0, "$NetBSD$"); #include <sys/param.h> #include <sys/bus.h> #include <sys/device.h> #include <sys/intr.h> #include <sys/systm.h> #include <machine/clockvar.h> #include <dev/fdt/fdtvar.h> /* * ###################################################################### * CHIP-SPECIFIC: registers * ###################################################################### * * Replace these with your chip's register map. Offsets are register * *numbers*; the real byte offset is (reg << reg-shift). * * The placeholder layout below is a typical simple down-counter: * CTRL enable, interrupt enable, auto-reload * STATUS "counter reached zero" flag, write 1 to clear * RELOAD0..2 24-bit reload (preload) value, low byte first */ #define MYTIMER_REG_CTRL 0 #define MYTIMER_REG_STATUS 1 #define MYTIMER_REG_RELOAD0 2 /* bits 7..0 */ #define MYTIMER_REG_RELOAD1 3 /* bits 15..8 */ #define MYTIMER_REG_RELOAD2 4 /* bits 23..16 */ /* CTRL bits */ #define MYTIMER_CTRL_RUN 0x01 /* counter running */ #define MYTIMER_CTRL_IRQEN 0x02 /* interrupt on reaching zero */ #define MYTIMER_CTRL_RELOAD 0x04 /* reload from RELOAD on zero */ /* STATUS bits */ #define MYTIMER_SR_ZERO 0x01 /* counter hit zero (write 1 = clear) */ /* * Counter limits. MAX is the largest value the reload register can * hold; PRESCALE is any fixed divider between the input clock and the * counter (1 if none). Together with the input clock these decide the * longest interval the timer can produce: MAX * PRESCALE / freq. */ #define MYTIMER_COUNT_MAX 0x00ffffffU #define MYTIMER_PRESCALE 1 /* * Set to true if the counter reloads itself when it reaches zero * (most timers can). If false, clk_intr() restarts it by hand; the * time spent getting to the interrupt handler is then lost on every * tick, and the clock runs slightly slow. */ #define MYTIMER_AUTO_RELOAD true /* * ###################################################################### * Device tree matching * ###################################################################### */ static const struct device_compatible_entry compat_data[] = { { .compat = "vendor,mytimer" }, DEVICE_COMPAT_EOL }; /* * ###################################################################### * Driver state * ###################################################################### */ struct mytimer_softc { device_t sc_dev; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; u_int sc_shift; /* reg-shift */ uint32_t sc_freq; /* counter decrement rate, Hz */ uint32_t sc_reload; /* count for one interval */ void *sc_ih; /* interrupt handle */ struct clock_if sc_clkif; /* our interface to clock.c */ }; static inline uint8_t mytimer_read(struct mytimer_softc *sc, u_int reg) { return bus_space_read_1(sc->sc_bst, sc->sc_bsh, reg << sc->sc_shift); } static inline void mytimer_write(struct mytimer_softc *sc, u_int reg, uint8_t val) { bus_space_write_1(sc->sc_bst, sc->sc_bsh, reg << sc->sc_shift, val); } /* * ###################################################################### * CHIP-SPECIFIC: low-level operations * ###################################################################### * * The only code that touches the hardware. */ /* Stop the counter and clear any pending interrupt. */ static void mytimer_hw_stop(struct mytimer_softc *sc) { mytimer_write(sc, MYTIMER_REG_CTRL, 0); mytimer_write(sc, MYTIMER_REG_STATUS, MYTIMER_SR_ZERO); } /* * Load `count' and start counting down, interrupting at zero. With * MYTIMER_AUTO_RELOAD the chip then repeats on its own. */ static void mytimer_hw_start(struct mytimer_softc *sc, uint32_t count) { mytimer_write(sc, MYTIMER_REG_RELOAD0, count & 0xff); mytimer_write(sc, MYTIMER_REG_RELOAD1, (count >> 8) & 0xff); mytimer_write(sc, MYTIMER_REG_RELOAD2, (count >> 16) & 0xff); mytimer_write(sc, MYTIMER_REG_CTRL, MYTIMER_CTRL_RUN | MYTIMER_CTRL_IRQEN | (MYTIMER_AUTO_RELOAD ? MYTIMER_CTRL_RELOAD : 0)); } /* * Acknowledge the interrupt so the chip drops its request line. If * this is forgotten, the CPU takes the interrupt again the moment the * handler returns, and the machine appears to hang. */ static void mytimer_hw_ack(struct mytimer_softc *sc) { mytimer_write(sc, MYTIMER_REG_STATUS, MYTIMER_SR_ZERO); } /* * ###################################################################### * Generic driver below this line * ###################################################################### */ /* Convert an interval in microseconds to counter ticks. */ static uint32_t mytimer_us_to_ticks(struct mytimer_softc *sc, unsigned int interval_us) { const uint64_t ticks = ((uint64_t)sc->sc_freq * interval_us + 500000) / 1000000; if (ticks == 0 || ticks > MYTIMER_COUNT_MAX) { /* * The input clock is too slow (ticks == 0) or too fast * (ticks too big) for this interval. Change the clock, * the prescaler, or HZ. */ panic("%s: can't make a %u us interval from %u Hz " "(ticks=%llu)", device_xname(sc->sc_dev), interval_us, sc->sc_freq, (unsigned long long)ticks); } return (uint32_t)ticks; } /* clock_if: establish our interrupt. `intrfunc' is clock.c's handler. */ static int mytimer_init(struct clock_if *clk, int (*intrfunc)(void *)) { struct mytimer_softc * const sc = container_of(clk, struct mytimer_softc, sc_clkif); const int phandle = devhandle_to_of(device_handle(sc->sc_dev)); char intrstr[128]; if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { aprint_error_dev(sc->sc_dev, "couldn't decode interrupt\n"); return ENXIO; } /* * The handler's argument must be NULL: clock.c's handler then * receives the interrupt frame, which hardclock() needs to tell * whether the interrupted code was in user or kernel mode. */ sc->sc_ih = fdtbus_intr_establish_xname(phandle, 0, IPL_SCHED, FDT_INTR_MPSAFE, intrfunc, NULL, device_xname(sc->sc_dev)); if (sc->sc_ih == NULL) { aprint_error_dev(sc->sc_dev, "couldn't establish interrupt at %s\n", intrstr); return ENXIO; } aprint_normal_dev(sc->sc_dev, "interrupting at %s\n", intrstr); return 0; } /* clock_if: start periodic interrupts every `interval_us'. */ static int mytimer_arm(struct clock_if *clk, unsigned int interval_us) { struct mytimer_softc * const sc = container_of(clk, struct mytimer_softc, sc_clkif); sc->sc_reload = mytimer_us_to_ticks(sc, interval_us); mytimer_hw_stop(sc); mytimer_hw_start(sc, sc->sc_reload); return 0; } /* clock_if: called at the start of every timer interrupt. */ static void mytimer_intr(struct clock_if *clk) { struct mytimer_softc * const sc = container_of(clk, struct mytimer_softc, sc_clkif); mytimer_hw_ack(sc); if (!MYTIMER_AUTO_RELOAD) { mytimer_hw_start(sc, sc->sc_reload); } } /* clock_if: stop the timer. */ static void mytimer_disarm(struct clock_if *clk) { struct mytimer_softc * const sc = container_of(clk, struct mytimer_softc, sc_clkif); mytimer_hw_stop(sc); } /* * ---------------------------------------------------------------------- * Autoconfiguration * ---------------------------------------------------------------------- */ static int mytimer_match(device_t parent, cfdata_t cf, void *aux) { struct fdt_attach_args * const faa = aux; return of_compatible_match(faa->faa_phandle, compat_data); } static void mytimer_attach(device_t parent, device_t self, void *aux) { struct mytimer_softc * const sc = device_private(self); struct fdt_attach_args * const faa = aux; const int phandle = faa->faa_phandle; bus_addr_t addr; bus_size_t size; struct clk *clk; uint32_t freq; sc->sc_dev = self; sc->sc_bst = faa->faa_bst; /* --- Registers ------------------------------------------------ */ if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { aprint_error(": couldn't get registers\n"); return; } sc->sc_shift = fdtbus_get_reg_shift(phandle, 0); if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { aprint_error(": couldn't map registers\n"); return; } /* * --- Input clock ---------------------------------------------- * Prefer a "clocks" reference to a clock node; fall back to a * plain "clock-frequency" property. */ clk = fdtbus_clock_get_index(phandle, 0); if (clk != NULL) { freq = clk_get_rate(clk); } else if (of_getprop_uint32(phandle, "clock-frequency", &freq) != 0) { aprint_error(": no \"clocks\" or \"clock-frequency\"\n"); return; } sc->sc_freq = freq / MYTIMER_PRESCALE; if (sc->sc_freq == 0) { aprint_error(": invalid clock rate %u Hz\n", freq); return; } /* Make sure the timer is quiet until the clock code arms it. */ mytimer_hw_stop(sc); aprint_naive("\n"); aprint_normal(": timer, %u Hz count rate\n", sc->sc_freq); /* --- Register with the clock code ------------------------------ */ sc->sc_clkif.clk_dev = self; sc->sc_clkif.clk_init = mytimer_init; sc->sc_clkif.clk_arm = mytimer_arm; sc->sc_clkif.clk_intr = mytimer_intr; sc->sc_clkif.clk_disarm = mytimer_disarm; clock_attach(&sc->sc_clkif); } CFATTACH_DECL_NEW(mytimer, sizeof(struct mytimer_softc), mytimer_match, mytimer_attach, NULL, NULL); ```