Page MenuHomeFreeBSD

D55472.id173043.diff
No OneTemporary

D55472.id173043.diff

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -44,6 +44,7 @@
alc.4 \
ale.4 \
alpm.4 \
+ appleir.4 \
altq.4 \
amdpm.4 \
${_amdsbwd.4} \
diff --git a/share/man/man4/appleir.4 b/share/man/man4/appleir.4
new file mode 100644
--- /dev/null
+++ b/share/man/man4/appleir.4
@@ -0,0 +1,88 @@
+.\" Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd February 13, 2026
+.Dt APPLEIR 4
+.Os
+.Sh NAME
+.Nm appleir
+.Nd Apple IR receiver driver
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following lines in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device appleir"
+.Cd "device hidbus"
+.Cd "device hid"
+.Cd "device evdev"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+appleir_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for Apple IR receivers found in Mac computers
+(2006-2011 era).
+It supports both Apple Remote controls and generic IR remotes using the
+NEC infrared protocol.
+.Pp
+Supported devices include:
+.Bl -bullet -compact
+.It
+Apple IR Receiver (USB product IDs 0x8240, 0x8241, 0x8242, 0x8243, 0x1440)
+.El
+.Pp
+The driver decodes proprietary Apple Remote button presses and provides
+a default keymap for common NEC protocol codes used by generic IR remotes.
+Unmapped button codes can be accessed via the raw HID device at
+.Pa /dev/hidrawX
+for custom userland remapping.
+.Pp
+The
+.Pa /dev/input/eventX
+device presents the remote control as an
+.Xr evdev 4
+input device with standard KEY_* codes suitable for media applications.
+.Sh HARDWARE
+The
+.Nm
+driver supports the following devices:
+.Pp
+.Bl -bullet -compact
+.It
+Mac Mini (2006-2011)
+.It
+iMac (2006-2011)
+.It
+MacBook and MacBook Pro (2006-2009, select models)
+.El
+.Sh FILES
+.Bl -tag -width ".Pa /dev/input/eventX" -compact
+.It Pa /dev/input/eventX
+evdev input device
+.It Pa /dev/hidrawX
+raw HID device for custom button mapping
+.El
+.Sh SEE ALSO
+.Xr evdev 4 ,
+.Xr hidbus 4 ,
+.Xr usbhid 4
+.Pp
+NEC Infrared Transmission Protocol:
+.Lk https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 16.0 .
+.Sh AUTHORS
+.An Abdelkader Boudih Aq Mt freebsd@seuros.com
+.Pp
+Based on protocol reverse-engineering by James McKenzie and others.
diff --git a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1775,6 +1775,7 @@
dev/gpio/gpiobus_if.m optional gpio
dev/gpio/gpiopps.c optional gpiopps fdt
dev/gpio/ofw_gpiobus.c optional fdt gpio
+dev/hid/appleir.c optional appleir
dev/hid/bcm5974.c optional bcm5974
dev/hid/hconf.c optional hconf
dev/hid/hcons.c optional hcons
diff --git a/sys/dev/hid/appleir.c b/sys/dev/hid/appleir.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/hid/appleir.c
@@ -0,0 +1,414 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ */
+
+/*
+ * Apple IR Remote Control Driver
+ *
+ * HID driver for Apple IR receivers (USB HID, vendor 0x05ac).
+ * Supports Apple Remote and generic IR remotes using NEC protocol.
+ *
+ * Protocol reverse-engineered by James McKenzie and others.
+ * Linux reference: drivers/hid/hid-appleir.c (Apple-only)
+ *
+ * Apple Remote Protocol (proprietary):
+ * Key down: [0x25][0x87][0xee][remote_id][key_code]
+ * Key repeat: [0x26][0x87][0xee][remote_id][key_code]
+ * Battery low: [0x25][0x87][0xe0][remote_id][0x00]
+ * Key decode: (byte4 >> 1) & 0x0F -> keymap[index]
+ * Two-packet: bit 6 of key_code (0x40) set -> store index, use on next keydown
+ *
+ * Generic IR Protocol (NEC-style):
+ * Format: [0x26][0x7f][0x80][code][~code]
+ * Checksum: code + ~code = 0xFF
+ *
+ * NO hardware key-up events -- synthesize via 125ms callout timer.
+ */
+
+#include <sys/cdefs.h>
+
+#include "opt_hid.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+
+#define HID_DEBUG_VAR appleir_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include "usbdevs.h"
+
+#ifdef HID_DEBUG
+static int appleir_debug = 0;
+
+static SYSCTL_NODE(_hw_hid, OID_AUTO, appleir, CTLFLAG_RW, 0,
+ "Apple IR Remote Control");
+SYSCTL_INT(_hw_hid_appleir, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &appleir_debug, 0, "Debug level");
+#endif
+
+/* Protocol constants */
+#define APPLEIR_REPORT_LEN 5
+#define KEY_MASK 0x0F
+#define TWO_PACKETS_MASK 0x40
+#define KEYUP_DELAY_TICKS MAX(1, hz / 8) /* 125ms */
+#define TWO_PACKET_TICKS MAX(1, hz / 4) /* 250ms */
+
+/*
+ * Apple IR keymap: 17 entries, index = (key_code >> 1) & 0x0F
+ * Based on Linux driver (hid-appleir.c) keymap.
+ */
+static const uint16_t appleir_keymap[] = {
+ KEY_RESERVED, /* 0x00 */
+ KEY_MENU, /* 0x01 - menu */
+ KEY_PLAYPAUSE, /* 0x02 - play/pause */
+ KEY_FORWARD, /* 0x03 - >> */
+ KEY_BACK, /* 0x04 - << */
+ KEY_VOLUMEUP, /* 0x05 - + */
+ KEY_VOLUMEDOWN, /* 0x06 - - */
+ KEY_RESERVED, /* 0x07 */
+ KEY_RESERVED, /* 0x08 */
+ KEY_RESERVED, /* 0x09 */
+ KEY_RESERVED, /* 0x0A */
+ KEY_RESERVED, /* 0x0B */
+ KEY_RESERVED, /* 0x0C */
+ KEY_RESERVED, /* 0x0D */
+ KEY_ENTER, /* 0x0E - middle button (two-packet) */
+ KEY_PLAYPAUSE, /* 0x0F - play/pause (two-packet) */
+ KEY_RESERVED, /* 0x10 - out of range guard */
+};
+#define APPLEIR_NKEYS (nitems(appleir_keymap))
+
+/*
+ * Generic IR keymap (NEC protocol codes).
+ * Maps raw NEC codes to evdev KEY_* codes.
+ */
+struct generic_ir_map {
+ uint8_t code; /* NEC IR code */
+ uint16_t key; /* evdev KEY_* */
+};
+
+static const struct generic_ir_map generic_keymap[] = {
+ { 0xe1, KEY_VOLUMEUP },
+ { 0xe9, KEY_VOLUMEDOWN },
+ { 0xed, KEY_CHANNELUP },
+ { 0xf3, KEY_CHANNELDOWN },
+ { 0xf5, KEY_PLAYPAUSE },
+ { 0xf9, KEY_POWER },
+ { 0xfb, KEY_MUTE },
+ { 0xfe, KEY_OK },
+};
+#define GENERIC_NKEYS (nitems(generic_keymap))
+
+static uint16_t
+generic_ir_lookup(uint8_t code)
+{
+ int i;
+
+ for (i = 0; i < GENERIC_NKEYS; i++) {
+ if (generic_keymap[i].code == code)
+ return (generic_keymap[i].key);
+ }
+ return (KEY_RESERVED);
+}
+
+struct appleir_softc {
+ device_t sc_dev;
+ struct mtx sc_mtx; /* protects below + callout */
+ struct evdev_dev *sc_evdev;
+ struct callout sc_co; /* key-up timer */
+ struct callout sc_twoco; /* two-packet timeout */
+ uint16_t sc_current_key; /* evdev keycode (0=none) */
+ int sc_prev_key_idx;/* two-packet state (0=none) */
+ bool sc_batt_warned;
+};
+
+/* Apple IR receiver device IDs */
+static const struct hid_device_id appleir_devs[] = {
+ { HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8240) },
+ { HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8241) },
+ { HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8242) },
+ { HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x8243) },
+ { HID_BVP(BUS_USB, USB_VENDOR_APPLE, 0x1440) },
+};
+
+/*
+ * Callout: synthesize key-up event (no hardware key-up from remote).
+ * Runs with sc_mtx held (callout_init_mtx).
+ */
+static void
+appleir_keyup(void *arg)
+{
+ struct appleir_softc *sc = arg;
+
+ mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+ if (sc->sc_current_key != 0) {
+ evdev_push_key(sc->sc_evdev, sc->sc_current_key, 0);
+ evdev_sync(sc->sc_evdev);
+ sc->sc_current_key = 0;
+ sc->sc_prev_key_idx = 0;
+ }
+}
+
+static void
+appleir_twopacket_timeout(void *arg)
+{
+ struct appleir_softc *sc = arg;
+
+ mtx_assert(&sc->sc_mtx, MA_OWNED);
+ sc->sc_prev_key_idx = 0;
+}
+
+/*
+ * Process 5-byte HID interrupt report.
+ * Called from hidbus interrupt context.
+ */
+static void
+appleir_intr(void *context, void *data, hid_size_t len)
+{
+ struct appleir_softc *sc = context;
+ uint8_t *buf = data;
+ uint8_t report[APPLEIR_REPORT_LEN];
+ int index;
+ uint16_t new_key;
+
+ if (len != APPLEIR_REPORT_LEN) {
+ DPRINTFN(1, "bad report len: %zu\n", (size_t)len);
+ return;
+ }
+
+ memcpy(report, buf, APPLEIR_REPORT_LEN);
+
+ mtx_lock(&sc->sc_mtx);
+
+ /* Battery low: [0x25][0x87][0xe0] -- log and ignore */
+ if (report[0] == 0x25 && report[1] == 0x87 && report[2] == 0xe0) {
+ if (!sc->sc_batt_warned) {
+ device_printf(sc->sc_dev,
+ "remote battery may be low\n");
+ sc->sc_batt_warned = true;
+ }
+ goto done;
+ }
+
+ /* Key down: [0x25][0x87][0xee][remote_id][key_code] */
+ if (report[0] == 0x25 && report[1] == 0x87 && report[2] == 0xee) {
+ /* Release previous key if held */
+ if (sc->sc_current_key != 0) {
+ evdev_push_key(sc->sc_evdev, sc->sc_current_key, 0);
+ evdev_sync(sc->sc_evdev);
+ sc->sc_current_key = 0;
+ }
+
+ if (sc->sc_prev_key_idx > 0) {
+ /* Second packet of a two-packet command */
+ index = sc->sc_prev_key_idx;
+ sc->sc_prev_key_idx = 0;
+ callout_stop(&sc->sc_twoco);
+ } else if (report[4] & TWO_PACKETS_MASK) {
+ /* First packet of a two-packet command -- wait for next */
+ sc->sc_prev_key_idx = (report[4] >> 1) & KEY_MASK;
+ callout_reset(&sc->sc_twoco, TWO_PACKET_TICKS,
+ appleir_twopacket_timeout, sc);
+ goto done;
+ } else {
+ index = (report[4] >> 1) & KEY_MASK;
+ }
+
+ new_key = (index < APPLEIR_NKEYS) ?
+ appleir_keymap[index] : KEY_RESERVED;
+ if (new_key != KEY_RESERVED) {
+ sc->sc_current_key = new_key;
+ evdev_push_key(sc->sc_evdev, new_key, 1);
+ evdev_sync(sc->sc_evdev);
+ callout_reset(&sc->sc_co, KEYUP_DELAY_TICKS,
+ appleir_keyup, sc);
+ }
+ goto done;
+ }
+
+ /* Key repeat: [0x26][0x87][0xee][remote_id][key_code] */
+ if (report[0] == 0x26 && report[1] == 0x87 && report[2] == 0xee) {
+ uint16_t repeat_key;
+ int repeat_idx;
+
+ if (sc->sc_prev_key_idx > 0)
+ goto done;
+ if (report[4] & TWO_PACKETS_MASK)
+ goto done;
+
+ repeat_idx = (report[4] >> 1) & KEY_MASK;
+ repeat_key = (repeat_idx < APPLEIR_NKEYS) ?
+ appleir_keymap[repeat_idx] : KEY_RESERVED;
+ if (repeat_key == KEY_RESERVED ||
+ repeat_key != sc->sc_current_key)
+ goto done;
+
+ evdev_push_key(sc->sc_evdev, repeat_key, 1);
+ evdev_sync(sc->sc_evdev);
+ callout_reset(&sc->sc_co, KEYUP_DELAY_TICKS,
+ appleir_keyup, sc);
+ goto done;
+ }
+
+ /* Generic IR (NEC protocol): [0x26][0x7f][0x80][code][~code] */
+ if (report[0] == 0x26 && report[1] == 0x7f && report[2] == 0x80) {
+ uint8_t code = report[3];
+ uint8_t checksum = report[4];
+
+ sc->sc_prev_key_idx = 0;
+ callout_stop(&sc->sc_twoco);
+
+ if ((uint8_t)(code + checksum) != 0xFF) {
+ DPRINTFN(1, "generic IR: bad checksum %02x+%02x\n",
+ code, checksum);
+ goto done;
+ }
+
+ new_key = generic_ir_lookup(code);
+ if (new_key == KEY_RESERVED)
+ goto done;
+
+ if (sc->sc_current_key != new_key) {
+ if (sc->sc_current_key != 0)
+ evdev_push_key(sc->sc_evdev,
+ sc->sc_current_key, 0);
+ sc->sc_current_key = new_key;
+ evdev_push_key(sc->sc_evdev, new_key, 1);
+ evdev_sync(sc->sc_evdev);
+ } else {
+ evdev_push_key(sc->sc_evdev, new_key, 1);
+ evdev_sync(sc->sc_evdev);
+ }
+ callout_reset(&sc->sc_co, KEYUP_DELAY_TICKS,
+ appleir_keyup, sc);
+ goto done;
+ }
+
+ DPRINTFN(1, "unknown report: %02x %02x %02x\n",
+ report[0], report[1], report[2]);
+
+done:
+ mtx_unlock(&sc->sc_mtx);
+}
+
+static int
+appleir_probe(device_t dev)
+{
+ int error;
+
+ error = HIDBUS_LOOKUP_DRIVER_INFO(dev, appleir_devs);
+ if (error != 0)
+ return (error);
+
+ /* Only attach to first top-level collection (TLC index 0) */
+ if (hidbus_get_index(dev) != 0)
+ return (ENXIO);
+
+ hidbus_set_desc(dev, "Apple IR Receiver");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+appleir_attach(device_t dev)
+{
+ struct appleir_softc *sc = device_get_softc(dev);
+ const struct hid_device_info *hw;
+ int i, error;
+
+ sc->sc_dev = dev;
+ hw = hid_get_device_info(dev);
+ sc->sc_current_key = 0;
+ sc->sc_prev_key_idx = 0;
+ sc->sc_batt_warned = false;
+ mtx_init(&sc->sc_mtx, "appleir", NULL, MTX_DEF);
+ callout_init_mtx(&sc->sc_co, &sc->sc_mtx, 0);
+ callout_init_mtx(&sc->sc_twoco, &sc->sc_mtx, 0);
+
+ sc->sc_evdev = evdev_alloc();
+ evdev_set_name(sc->sc_evdev, device_get_desc(dev));
+ evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev));
+ evdev_set_id(sc->sc_evdev, hw->idBus, hw->idVendor, hw->idProduct,
+ hw->idVersion);
+ evdev_set_serial(sc->sc_evdev, hw->serial);
+ evdev_support_event(sc->sc_evdev, EV_SYN);
+ evdev_support_event(sc->sc_evdev, EV_KEY);
+ evdev_support_event(sc->sc_evdev, EV_REP);
+
+ for (i = 0; i < APPLEIR_NKEYS; i++) {
+ if (appleir_keymap[i] != KEY_RESERVED)
+ evdev_support_key(sc->sc_evdev, appleir_keymap[i]);
+ }
+ for (i = 0; i < GENERIC_NKEYS; i++)
+ evdev_support_key(sc->sc_evdev, generic_keymap[i].key);
+
+ error = evdev_register_mtx(sc->sc_evdev, &sc->sc_mtx);
+ if (error != 0) {
+ device_printf(dev, "evdev_register_mtx failed: %d\n", error);
+ goto fail;
+ }
+
+ hidbus_set_intr(dev, appleir_intr, sc);
+
+ error = hid_intr_start(dev);
+ if (error != 0) {
+ device_printf(dev, "hid_intr_start failed: %d\n", error);
+ goto fail;
+ }
+
+ return (0);
+
+fail:
+ if (sc->sc_evdev != NULL)
+ evdev_free(sc->sc_evdev);
+ callout_drain(&sc->sc_co);
+ callout_drain(&sc->sc_twoco);
+ mtx_destroy(&sc->sc_mtx);
+ return (error);
+}
+
+static int
+appleir_detach(device_t dev)
+{
+ struct appleir_softc *sc = device_get_softc(dev);
+
+ hid_intr_stop(dev);
+ callout_drain(&sc->sc_co);
+ callout_drain(&sc->sc_twoco);
+ evdev_free(sc->sc_evdev);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static device_method_t appleir_methods[] = {
+ DEVMETHOD(device_probe, appleir_probe),
+ DEVMETHOD(device_attach, appleir_attach),
+ DEVMETHOD(device_detach, appleir_detach),
+ DEVMETHOD_END
+};
+
+static driver_t appleir_driver = {
+ "appleir",
+ appleir_methods,
+ sizeof(struct appleir_softc)
+};
+
+DRIVER_MODULE(appleir, hidbus, appleir_driver, NULL, NULL);
+MODULE_DEPEND(appleir, hid, 1, 1, 1);
+MODULE_DEPEND(appleir, hidbus, 1, 1, 1);
+MODULE_DEPEND(appleir, evdev, 1, 1, 1);
+MODULE_VERSION(appleir, 1);
+HID_PNP_INFO(appleir_devs);
diff --git a/sys/modules/hid/Makefile b/sys/modules/hid/Makefile
--- a/sys/modules/hid/Makefile
+++ b/sys/modules/hid/Makefile
@@ -6,6 +6,7 @@
hidraw
SUBDIR += \
+ appleir \
bcm5974 \
hconf \
hcons \
diff --git a/sys/modules/hid/appleir/Makefile b/sys/modules/hid/appleir/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/hid/appleir/Makefile
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+.PATH: ${SRCTOP}/sys/dev/hid
+
+KMOD= appleir
+SRCS= appleir.c
+SRCS+= opt_hid.h opt_kbd.h
+SRCS+= bus_if.h device_if.h usbdevs.h
+
+.include <bsd.kmod.mk>

File Metadata

Mime Type
text/plain
Expires
Thu, Apr 9, 3:37 AM (6 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
31131845
Default Alt Text
D55472.id173043.diff (14 KB)

Event Timeline