summaryrefslogtreecommitdiff
path: root/sys/arch/amd64/isa
diff options
context:
space:
mode:
Diffstat (limited to 'sys/arch/amd64/isa')
-rw-r--r--sys/arch/amd64/isa/i8042.S37
-rw-r--r--sys/arch/amd64/isa/i8042.c363
-rw-r--r--sys/arch/amd64/isa/mc1468.c281
-rw-r--r--sys/arch/amd64/isa/spkr.c53
4 files changed, 571 insertions, 163 deletions
diff --git a/sys/arch/amd64/isa/i8042.S b/sys/arch/amd64/isa/i8042.S
deleted file mode 100644
index 123d3a5..0000000
--- a/sys/arch/amd64/isa/i8042.S
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
- * 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.
- * 3. Neither the name of Hyra nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
- */
-
-#include <machine/frameasm.h>
-
- .text
- .globl i8042_kb_isr
-INTRENTRY(i8042_kb_isr, handle_kb)
-handle_kb:
- call i8042_kb_event
- retq
diff --git a/sys/arch/amd64/isa/i8042.c b/sys/arch/amd64/isa/i8042.c
index 89bebc5..095f1f4 100644
--- a/sys/arch/amd64/isa/i8042.c
+++ b/sys/arch/amd64/isa/i8042.c
@@ -33,12 +33,14 @@
#include <sys/syslog.h>
#include <sys/spinlock.h>
#include <sys/param.h>
+#include <sys/ascii.h>
#include <sys/proc.h>
#include <sys/reboot.h>
#include <sys/queue.h>
#include <dev/acpi/acpi.h>
#include <dev/timer.h>
#include <dev/cons/cons.h>
+#include <dev/dmi/dmi.h>
#include <machine/cpu.h>
#include <machine/pio.h>
#include <machine/isa/i8042var.h>
@@ -51,6 +53,13 @@
#include <string.h>
#include <assert.h>
+/* From kconf(9) */
+#if !defined(__I8042_POLL)
+#define I8042_POLL 0
+#else
+#define I8042_POLL __I8042_POLL
+#endif
+
#define KEY_REP_MAX 2
#define pr_trace(fmt, ...) kprintf("i8042: " fmt, ##__VA_ARGS__)
@@ -58,7 +67,28 @@
#define IO_NOP() inb(0x80)
-static struct spinlock data_lock;
+struct i8042_databuf {
+ uint8_t data[8];
+ size_t len;
+};
+
+/*
+ * This table allows the lookup of extended
+ * scancode bytes.
+ *
+ * XXX: Excludes the 0xE0 byte
+ */
+static struct i8042_databuf i8042_etab[] = {
+ [ I8042_XSC_ENDPR] = {
+ .data = { 0x4F },
+ .len = 1
+ },
+ [I8042_XSC_ENDRL] = {
+ .data = { 0xCF },
+ .len = 1
+ }
+};
+
static struct spinlock isr_lock;
static bool shift_key = false;
static bool capslock = false;
@@ -68,12 +98,13 @@ static struct proc polltd;
static struct timer tmr;
static bool is_init = false;
+static void i8042_ibuf_wait(void);
static int dev_send(bool aux, uint8_t data);
static int i8042_kb_getc(uint8_t sc, char *chr);
-static void i8042_drain(void);
+static void i8042_drain(struct i8042_databuf *res);
static char keytab[] = {
- '\0', '\0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
+ '\0', '\x1B', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'-', '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i',
'o', 'p', '[', ']', '\n', '\0', 'a', 's', 'd', 'f', 'g', 'h',
'j', 'k', 'l', ';', '\'', '`', '\0', '\\', 'z', 'x', 'c', 'v',
@@ -103,54 +134,56 @@ kbd_set_leds(uint8_t mask)
dev_send(false, mask);
}
-/*
- * Poll the i8042 status register
- *
- * @bits: Status bits.
- * @pollset: True to poll if set
- */
-static int
-i8042_statpoll(uint8_t bits, bool pollset)
+static void
+i8042_obuf_wait(void)
{
- size_t usec_start, usec;
- size_t elapsed_msec;
- uint8_t val;
- bool tmp;
+ uint8_t status;
- usec_start = tmr.get_time_usec();
for (;;) {
- val = inb(I8042_STATUS);
- tmp = (pollset) ? ISSET(val, bits) : !ISSET(val, bits);
- usec = tmr.get_time_usec();
- elapsed_msec = (usec - usec_start) / 1000;
-
- IO_NOP();
-
- /* If tmp is set, the register updated in time */
- if (tmp) {
- break;
+ status = inb(I8042_STATUS);
+ if (ISSET(status, I8042_OBUFF)) {
+ return;
}
+ }
+}
- /* Exit with an error if we timeout */
- if (elapsed_msec > I8042_DELAY) {
- return -ETIME;
+static void
+i8042_ibuf_wait(void)
+{
+ uint8_t status;
+
+ for (;;) {
+ status = inb(I8042_STATUS);
+ if (!ISSET(status, I8042_IBUFF)) {
+ return;
}
}
-
- return val;
}
/*
* Drain i8042 internal data registers.
+ *
+ * @res: Pointer for read data to be buffered to
+ *
+ * XXX: The 'res' argument is NULLable
*/
static void
-i8042_drain(void)
+i8042_drain(struct i8042_databuf *res)
{
- spinlock_acquire(&data_lock);
while (ISSET(inb(I8042_STATUS), I8042_OBUFF)) {
- inb(I8042_DATA);
+ if (res == NULL) {
+ inb(I8042_DATA);
+ continue;
+ }
+
+ if (res->len >= sizeof(res->data)) {
+ pr_error("data recieved from i8042 is too big\n");
+ break;
+ }
+
+ res->data[res->len++] = inb(I8042_DATA);
+ tmr.msleep(10);
}
- spinlock_release(&data_lock);
}
/*
@@ -162,34 +195,47 @@ i8042_drain(void)
static void
i8042_write(uint16_t port, uint8_t val)
{
- i8042_statpoll(I8042_IBUFF, false);
+ i8042_ibuf_wait();
outb(port, val);
}
/*
- * Read the i8042 config register
+ * Read from an i8042 register.
+ *
+ * @port: I/O port
+ */
+static uint8_t
+i8042_read(uint16_t port)
+{
+ i8042_obuf_wait();
+ return inb(port);
+}
+
+/*
+ * Read the i8042 controller configuration
+ * byte.
*/
static uint8_t
i8042_read_conf(void)
{
- i8042_drain();
+ uint8_t conf;
+
i8042_write(I8042_CMD, I8042_GET_CONFB);
- i8042_statpoll(I8042_OBUFF, true);
- return inb(I8042_DATA);
+ i8042_obuf_wait();
+ conf = i8042_read(I8042_DATA);
+ return conf;
}
/*
- * Write the i8042 config register
+ * Write a new value to the i8042 controller
+ * configuration byte.
*/
static void
-i8042_write_conf(uint8_t value)
+i8042_write_conf(uint8_t conf)
{
- i8042_drain();
- i8042_statpoll(I8042_IBUFF, false);
i8042_write(I8042_CMD, I8042_SET_CONFB);
- i8042_statpoll(I8042_IBUFF, false);
- i8042_write(I8042_DATA, value);
- i8042_drain();
+ i8042_ibuf_wait();
+ i8042_write(I8042_DATA, conf);
}
/*
@@ -205,14 +251,13 @@ dev_send(bool aux, uint8_t data)
i8042_write(I8042_CMD, I8042_PORT1_SEND);
}
- i8042_statpoll(I8042_IBUFF, false);
i8042_write(I8042_DATA, data);
- i8042_statpoll(I8042_OBUFF, true);
+ i8042_obuf_wait();
return inb(I8042_DATA);
}
-void
-i8042_kb_event(void)
+static int
+i8042_kb_event(void *sp)
{
struct cpu_info *ci;
struct cons_input input;
@@ -232,50 +277,103 @@ i8042_kb_event(void)
input.chr = c;
cons_ibuf_push(&g_root_scr, input);
done:
- ci->irq_mask &= CPU_IRQ(1);
+ ci->irq_mask &= ~CPU_IRQ(1);
spinlock_release(&isr_lock);
- lapic_eoi();
+ return 1; /* handled */
}
static void
i8042_en_intr(void)
{
+ struct intr_hand ih;
uint8_t conf;
- int vec;
-
- pr_trace("ENTER -> i8042_en_intr\n");
- i8042_write(I8042_CMD, I8042_DISABLE_PORT0);
- pr_trace("port 0 disabled\n");
- vec = intr_alloc_vector("i8042-kb", IPL_BIO);
- idt_set_desc(vec, IDT_INT_GATE, ISR(i8042_kb_isr), IST_HW_IRQ);
- ioapic_set_vec(KB_IRQ, vec);
- ioapic_irq_unmask(KB_IRQ);
- pr_trace("irq 1 -> vec[%x]\n", vec);
+ ih.func = i8042_kb_event;
+ ih.priority = IPL_BIO;
+ ih.irq = KB_IRQ;
+ intr_register("i8042-kb", &ih);
- /* Setup config bits */
+ /*
+ * Enable the clock of PS/2 port 0 and tell
+ * the controller that we are accepting
+ * interrupts.
+ */
conf = i8042_read_conf();
+ conf &= ~I8042_PORT0_CLK;
conf |= I8042_PORT0_INTR;
- conf &= ~I8042_PORT1_INTR;
i8042_write_conf(conf);
- pr_trace("conf written\n");
+}
- i8042_write(I8042_CMD, I8042_ENABLE_PORT0);
- pr_trace("port 0 enabled\n");
+/*
+ * Toggle the capslock and LED
+ */
+static void
+capslock_toggle(void)
+{
+ /*
+ * In case we are holding the caps lock button down,
+ * we don't want it to be spam toggled as that would
+ * be pretty strange looking and probably annoying.
+ */
+ if (!capslock_released) {
+ return;
+ }
+
+ capslock_released = false;
+ capslock = !capslock;
+
+ if (!capslock) {
+ kbd_set_leds(0);
+ } else {
+ kbd_set_leds(I8042_LED_CAPS);
+ }
}
+/*
+ * Dump extended data buffer
+ *
+ * @buf: Data
+ */
static void
-esckey_reboot(void)
+i8042_ext_dump(struct i8042_databuf *buf)
{
- syslock();
- kprintf(OMIT_TIMESTAMP "** Machine going down for a reboot\f");
+ if (buf == NULL) {
+ return;
+ }
+
+ for (int i = 0; i < buf->len; ++i) {
+ kprintf(OMIT_TIMESTAMP "%x", buf->data[i]);
+ }
- for (size_t i = 0; i < 3; ++i) {
- kprintf(OMIT_TIMESTAMP ".\f");
- tmr.msleep(1000);
+ kprintf(OMIT_TIMESTAMP "\n");
+}
+
+/*
+ * Used internally by i8042_kb_getc() to acquire
+ * a key from an extended scancode
+ *
+ * @buf: Scancode buf
+ * @chr: Char res
+ *
+ * Returns the extended scancode type on success,
+ * otherwise a less than zero value (see I8042_XSC_*)
+ */
+static int
+i8042_kb_getxc(struct i8042_databuf *buf, char *chr)
+{
+ size_t nelem = NELEM(i8042_etab);
+ struct i8042_databuf *buf_tmp;
+ size_t len;
+
+ for (int i = 0; i < nelem; ++i) {
+ buf_tmp = &i8042_etab[i];
+ len = buf_tmp->len;
+ if (memcmp(buf->data, buf_tmp->data, len) == 0) {
+ return i;
+ }
}
- cpu_reboot(0);
+ return -1;
}
/*
@@ -290,31 +388,16 @@ static int
i8042_kb_getc(uint8_t sc, char *chr)
{
bool release = ISSET(sc, BIT(7));
+ struct i8042_databuf buf = {0};
+ int x_type;
switch (sc) {
- /* Left alt [press] */
- case 0x38:
- esckey_reboot();
- break;
+ case 0x76:
+ *chr = ASCII_ESC;
+ return 0;
/* Caps lock [press] */
case 0x3A:
- /*
- * In case we are holding the caps lock button down,
- * we don't want it to be spam toggled as that would
- * be pretty strange looking and probably annoying.
- */
- if (!capslock_released) {
- return -EAGAIN;
- }
-
- capslock_released = false;
- capslock = !capslock;
-
- if (!capslock) {
- kbd_set_leds(0);
- } else {
- kbd_set_leds(I8042_LED_CAPS);
- }
+ capslock_toggle();
return -EAGAIN;
/* Caps lock [release] */
case 0xBA:
@@ -331,6 +414,26 @@ i8042_kb_getc(uint8_t sc, char *chr)
shift_key = false;
}
return -EAGAIN;
+ /* Extended byte */
+ case 0xE0:
+ /*
+ * Most keyboards have extended scancodes which
+ * consist of multiple bytes to represent certain
+ * special keys. We'll need to give the controller
+ * about 10 ms to refill its buffer.
+ */
+ tmr.msleep(10);
+ i8042_drain(&buf);
+ x_type = i8042_kb_getxc(&buf, chr);
+
+ /* Did we implement it? */
+ if (x_type < 0) {
+ pr_error("unknown xsc: ");
+ i8042_ext_dump(&buf);
+ return -EAGAIN;
+ }
+
+ return -1;
}
if (release) {
@@ -351,43 +454,30 @@ i8042_kb_getc(uint8_t sc, char *chr)
return 0;
}
-static void
-i8042_sync_loop(void)
-{
- /* Wake up the bus */
- outb(I8042_DATA, 0x00);
- i8042_drain();
-
- for (;;) {
- i8042_sync();
- md_pause();
- }
-}
-
/*
* Grabs a key from the keyboard, used typically
* for syncing the machine however can be used
- * to bypass IRQs in case of buggy EC.
+ * to bypass IRQs to prevent lost bytes.
*/
void
i8042_sync(void)
{
static struct spinlock lock;
struct cons_input input;
- uint8_t data;
+ uint8_t data, status;
char c;
if (spinlock_try_acquire(&lock)) {
return;
}
- if (ISSET(quirks, I8042_HOSTILE) && is_init) {
- if (i8042_statpoll(I8042_OBUFF, true) < 0) {
- /* No data ready */
+ if (is_init) {
+ status = inb(I8042_STATUS);
+ if (!ISSET(status, I8042_OBUFF)) {
goto done;
}
- data = inb(I8042_DATA);
+ data = inb(I8042_DATA);
if (i8042_kb_getc(data, &c) == 0) {
input.scancode = data;
input.chr = c;
@@ -404,9 +494,20 @@ i8042_quirk(int mask)
quirks |= mask;
}
+static void
+i8042_sync_loop(void)
+{
+ for (;;) {
+ i8042_obuf_wait();
+ i8042_sync();
+ }
+}
+
static int
i8042_init(void)
{
+ const char *prodver = NULL;
+
/* Try to request a general purpose timer */
if (req_timer(TIMER_GP, &tmr) != TMRR_SUCCESS) {
pr_error("failed to fetch general purpose timer\n");
@@ -425,6 +526,9 @@ i8042_init(void)
return -ENODEV;
}
+ i8042_write(I8042_CMD, I8042_DISABLE_PORT0);
+ i8042_write(I8042_CMD, I8042_DISABLE_PORT1);
+
/*
* On some thinkpads, e.g., the T420s, the EC implementing
* the i8042 logic likes to play cop and throw NMIs at us
@@ -432,26 +536,33 @@ i8042_init(void)
* etc... As of now, treat the i8042 like a fucking bomb
* if this bit is set.
*/
- if (strcmp(acpi_oemid(), "LENOVO") == 0) {
+ if ((prodver = dmi_prodver()) == NULL) {
+ prodver = "None";
+ }
+ if (strcmp(prodver, "ThinkPad T420s") == 0) {
quirks |= I8042_HOSTILE;
- pr_trace("lenovo device, assuming hostile\n");
+ pr_trace("ThinkPad T420s detected, assuming hostile\n");
pr_trace("disabling irq 1, polling as fallback\n");
- fork1(&polltd, 0, i8042_sync_loop, NULL);
}
- if (!ISSET(quirks, I8042_HOSTILE)) {
+ /*
+ * If the i8042 has the hostile quirk or we are
+ * configured to poll for events, spawn the polling
+ * thread.
+ */
+ if (!ISSET(quirks, I8042_HOSTILE) && !I8042_POLL) {
/* Enable interrupts */
- i8042_drain();
+ i8042_drain(NULL);
i8042_en_intr();
+ } else if (ISSET(quirks, I8042_HOSTILE) || I8042_POLL) {
+ spawn(&polltd, i8042_sync_loop, NULL, 0, NULL);
+ pr_trace("polling events\n");
}
- if (dev_send(false, 0xFF) == 0xFC) {
- pr_error("kbd self test failure\n");
- return -EIO;
- }
-
+ i8042_write(I8042_CMD, I8042_ENABLE_PORT0);
+ i8042_drain(NULL);
is_init = true;
return 0;
}
-DRIVER_EXPORT(i8042_init);
+DRIVER_EXPORT(i8042_init, "i8042");
diff --git a/sys/arch/amd64/isa/mc1468.c b/sys/arch/amd64/isa/mc1468.c
new file mode 100644
index 0000000..1f3ae1d
--- /dev/null
+++ b/sys/arch/amd64/isa/mc1468.c
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * 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.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/driver.h>
+#include <sys/device.h>
+#include <sys/syslog.h>
+#include <fs/devfs.h>
+#include <machine/pio.h>
+#include <machine/cdefs.h>
+#include <string.h>
+
+#define MC1468_REGSEL 0x70
+#define MC1468_DATA 0x71
+
+/* Register A flags */
+#define MC1468_UPDATING BIT(7)
+
+/* Register B flags */
+#define MC1468_DAYSAVE BIT(1)
+#define MC1468_CLOCK24 BIT(2)
+
+static struct cdevsw mc1468_cdevsw;
+
+static uint8_t
+bin_dabble(uint8_t bin)
+{
+ uint8_t retval = 0;
+ uint8_t nibble;
+
+ for (int i = 7; i >= 0; --i) {
+ retval <<= 1;
+ if (bin & (1 << i)) {
+ retval |= 1;
+ }
+
+ for (int j = 0; j < 2; ++j) {
+ nibble = retval & (retval >> (4 * nibble)) & 0x0F;
+ if (nibble >= 5) {
+ retval += 0x03 << (4 * nibble);
+ }
+ }
+ }
+
+ return retval;
+}
+
+/*
+ * Read a byte from an MC1468XX register.
+ */
+static uint8_t
+mc1468_read(uint8_t reg)
+{
+ outb(MC1468_REGSEL, reg);
+ return inb(MC1468_DATA);
+}
+
+/*
+ * Write a byte to the MC1468XX register.
+ */
+static void
+mc1468_write(uint8_t reg, uint8_t val)
+{
+ outb(MC1468_REGSEL, reg);
+ outb(MC1468_DATA, val);
+}
+
+/*
+ * Returns true if the MC1468XX is updating
+ * its time registers.
+ */
+static bool
+mc1468_updating(void)
+{
+ uint8_t reg_b;
+
+ reg_b = mc1468_read(0xB);
+ return ISSET(reg_b, MC1468_UPDATING) != 0;
+}
+
+/*
+ * Check if date `a' and date `b' are synced.
+ * Used to make sure a bogus date caused by a
+ * read right before an MC1468XX register
+ * update doesn't occur.
+ */
+static bool
+mc1468_date_synced(struct date *a, struct date *b)
+{
+ if (a->year != b->year)
+ return false;
+ if (a->month != b->month)
+ return false;
+ if (a->day != b->day)
+ return false;
+ if (a->sec != b->sec)
+ return false;
+ if (a->min != b->min)
+ return false;
+ if (a->hour != b->hour)
+ return false;
+
+ return true;
+}
+
+/*
+ * Sometimes the clock chip may encode the
+ * date in binary-coded-decimal. This function
+ * converts a date in BCD format to plain binary.
+ */
+static void
+mc1468_bcd_conv(struct date *dp)
+{
+ dp->year = (dp->year & 0x0F) + ((dp->year / 16) * 10);
+ dp->month = (dp->month & 0x0F) + ((dp->month / 16) * 10);
+ dp->day = (dp->day & 0x0F) + ((dp->day / 16) * 10);
+ dp->sec = (dp->sec & 0x0F) + ((dp->sec / 16) * 10);
+ dp->min = (dp->min & 0x0F) + ((dp->min / 16) * 10);
+ dp->hour = (dp->hour & 0x0F) + (((dp->hour & 0x70) / 16) * 10);
+ dp->hour |= dp->hour & 0x80;
+}
+
+/*
+ * Read the time for the clock without syncing
+ * it up.
+ *
+ * XXX: Please use mc1468_get_date() instead as
+ * this function may return inconsistent
+ * values if not used correctly.
+ */
+static void
+__mc1468_get_time(struct date *dp)
+{
+ dp->year = mc1468_read(0x09);
+ dp->month = mc1468_read(0x08);
+ dp->day = mc1468_read(0x07);
+ dp->sec = mc1468_read(0x00);
+ dp->min = mc1468_read(0x02);
+ dp->hour = mc1468_read(0x04);
+}
+
+/*
+ * Write a new time/date to the chip.
+ */
+static void
+mc1468_set_date(const struct date *dp)
+{
+ while (mc1468_updating()) {
+ md_pause();
+ }
+
+ mc1468_write(0x08, bin_dabble(dp->month));
+ mc1468_write(0x07, bin_dabble(dp->day));
+ mc1468_write(0x04, bin_dabble(dp->hour));
+ mc1468_write(0x02, bin_dabble(dp->min));
+ mc1468_write(0x00, bin_dabble(dp->sec));
+}
+
+static int
+mc1468_get_date(struct date *dp)
+{
+ struct date date_cur, date_last;
+ uint8_t reg_b = mc1468_read(0x0B);
+
+ while (mc1468_updating()) {
+ __mc1468_get_time(&date_last);
+ }
+
+ /*
+ * Get the current date and time.
+ *
+ * XXX: The date and time returned by __mc1468_get_time()
+ * may at times be out of sync, read it twice to
+ * make sure everything is synced up.
+ */
+ do {
+ while (mc1468_updating()) {
+ md_pause();
+ }
+ __mc1468_get_time(&date_last);
+ date_cur.year = date_last.year;
+ date_cur.month = date_last.month;
+ date_cur.day = date_last.day;
+ date_cur.sec = date_last.sec;
+ date_cur.min = date_last.min;
+ date_cur.hour = date_last.hour;
+ } while (!mc1468_date_synced(&date_cur, &date_last));
+
+ /* Is this in BCD? */
+ if (!ISSET(reg_b, 0x04)) {
+ mc1468_bcd_conv(&date_cur);
+ }
+
+ /* 24-hour mode? */
+ if (ISSET(reg_b, MC1468_CLOCK24)) {
+ date_cur.hour = ((date_cur.hour & 0x7F) + 12) % 24;
+ }
+
+ date_cur.year += 2000;
+ *dp = date_cur;
+ return 0;
+}
+
+static int
+mc1468_dev_read(dev_t dev, struct sio_txn *sio, int flags)
+{
+ struct date d;
+ size_t len = sizeof(d);
+
+ if (sio->len > len) {
+ sio->len = len;
+ }
+
+ mc1468_get_date(&d);
+ memcpy(sio->buf, &d, sio->len);
+ return sio->len;
+}
+
+static int
+mc1468_dev_write(dev_t dev, struct sio_txn *sio, int flags)
+{
+ struct date d;
+ size_t len = sizeof(d);
+
+ if (sio->len > len) {
+ sio->len = len;
+ }
+
+ memcpy(&d, sio->buf, sio->len);
+ mc1468_set_date(&d);
+ return sio->len;
+}
+
+static int
+mc1468_init(void)
+{
+ char devname[] = "rtc";
+ devmajor_t major;
+ dev_t dev;
+
+ major = dev_alloc_major();
+ dev = dev_alloc(major);
+ dev_register(major, dev, &mc1468_cdevsw);
+ devfs_create_entry(devname, major, dev, 0444);
+ return 0;
+}
+
+static struct cdevsw mc1468_cdevsw = {
+ .read = mc1468_dev_read,
+ .write = mc1468_dev_write,
+};
+
+DRIVER_EXPORT(mc1468_init, "mc1468");
diff --git a/sys/arch/amd64/isa/spkr.c b/sys/arch/amd64/isa/spkr.c
index b1bd2a2..c96e5f9 100644
--- a/sys/arch/amd64/isa/spkr.c
+++ b/sys/arch/amd64/isa/spkr.c
@@ -30,14 +30,60 @@
#include <sys/cdefs.h>
#include <sys/errno.h>
#include <sys/param.h>
+#include <sys/device.h>
+#include <sys/driver.h>
+#include <fs/devfs.h>
#include <dev/timer.h>
#include <machine/isa/spkr.h>
#include <machine/isa/i8254.h>
#include <machine/pio.h>
+#include <string.h>
#define DIVIDEND 1193180
#define CTRL_PORT 0x61
+static struct cdevsw beep_cdevsw;
+
+/*
+ * Write to the pcspkr
+ *
+ * Bits 15:0 - frequency (hz)
+ * Bits 31:16 - duration (msec)
+ */
+static int
+dev_write(dev_t dev, struct sio_txn *sio, int flags)
+{
+ uint32_t payload = 0;
+ uint16_t hz;
+ uint16_t duration;
+ size_t len = sizeof(payload);
+
+ if (sio->len < len) {
+ return -EINVAL;
+ }
+
+ memcpy(&payload, sio->buf, len);
+ hz = payload & 0xFFFF;
+ duration = (payload >> 16) & 0xFFFF;
+ pcspkr_tone(hz, duration);
+ return sio->len;
+}
+
+static int
+beep_init(void)
+{
+ char devname[] = "beep";
+ devmajor_t major;
+ dev_t dev;
+
+ /* Register the device here */
+ major = dev_alloc_major();
+ dev = dev_alloc(major);
+ dev_register(major, dev, &beep_cdevsw);
+ devfs_create_entry(devname, major, dev, 0666);
+ return 0;
+}
+
int
pcspkr_tone(uint16_t freq, uint32_t msec)
{
@@ -67,3 +113,10 @@ pcspkr_tone(uint16_t freq, uint32_t msec)
outb(CTRL_PORT, tmp & ~3);
return 0;
}
+
+static struct cdevsw beep_cdevsw = {
+ .read = noread,
+ .write = dev_write
+};
+
+DRIVER_EXPORT(beep_init, "pcspkr");