Page MenuHomeFreeBSD

D55472.id172550.diff
No OneTemporary

D55472.id172550.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,439 @@
+/*-
+ * 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).
+ */
+
+#include <sys/cdefs.h>
+
+/*
+ * Apple IR Remote Control Driver
+ *
+ * HID driver for Apple IR receivers (USB HID, vendor 0x05ac).
+ * Supports BOTH Apple remotes AND generic IR remotes (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][key_code]
+ * 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, discovered on FreeBSD):
+ * Format: [0x26][0x7f][0x80][code][~code]
+ * Checksum: code + ~code = 0xFF
+ * Key repeat: Same packet repeats while held
+ *
+ * NO hardware key-up events — synthesize via 125ms callout timer.
+ */
+
+#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 (hz / 8) /* 125ms */
+
+/*
+ * 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 }, /* Common NEC code */
+ { 0xe9, KEY_VOLUMEDOWN }, /* Common NEC code */
+ { 0xed, KEY_CHANNELUP }, /* Common NEC code */
+ { 0xf3, KEY_CHANNELDOWN }, /* Common NEC code */
+ { 0xf5, KEY_PLAYPAUSE }, /* Common NEC code */
+ { 0xf9, KEY_POWER }, /* Common NEC code */
+ { 0xfb, KEY_MUTE }, /* Common NEC code */
+ { 0xfe, KEY_OK }, /* Common NEC code */
+};
+#define GENERIC_NKEYS (nitems(generic_keymap))
+
+/*
+ * Lookup generic IR code in keymap.
+ * Returns evdev KEY_* code, or KEY_RESERVED if not found.
+ */
+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 */
+ uint16_t sc_current_key; /* evdev keycode (0=none) */
+ int sc_prev_key_idx;/* two-packet state (0=none) */
+};
+
+/* 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) },
+};
+
+/*
+ * Decode key index from raw HID byte.
+ * Returns:
+ * >= 0: direct key index into keymap
+ * < 0: two-packet command (store -index, wait for next keydown)
+ */
+static int
+appleir_get_key(uint8_t data)
+{
+ int key = (data >> 1) & KEY_MASK;
+
+ if (data & TWO_PACKETS_MASK)
+ key = -key; /* first of two-packet command */
+
+ return (key);
+}
+
+/*
+ * 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) {
+ DPRINTFN(5, "key up: 0x%04x\n", sc->sc_current_key);
+ 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;
+ }
+}
+
+/*
+ * 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, new_key;
+ bool battery_low = false;
+
+ if (len != APPLEIR_REPORT_LEN) {
+ DPRINTFN(1, "bad report len: %zu\n", (size_t)len);
+ return;
+ }
+
+ DPRINTFN(10, "raw: %02x %02x %02x %02x %02x\n",
+ report[0], report[1], report[2], report[3], report[4]);
+
+ /* Copy to local buffer to avoid corrupting shared data */
+ memcpy(report, buf, APPLEIR_REPORT_LEN);
+
+ /* Battery low: [0x25][0x87][0xe0] — normalize and process as keydown */
+ if (report[0] == 0x25 && report[1] == 0x87 && report[2] == 0xe0) {
+ battery_low = true;
+ report[2] = 0xee; /* Convert to keydown format */
+ }
+
+ mtx_lock(&sc->sc_mtx);
+
+ /* Key down: [0x25][0x87][0xee][remote_id][key_code] */
+ if (report[0] == 0x25 && report[1] == 0x87 && report[2] == 0xee) {
+ if (battery_low)
+ device_printf(sc->sc_dev, "remote battery may be low\n");
+ /* 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;
+ }
+
+ /* Decode key index */
+ if (sc->sc_prev_key_idx > 0)
+ index = sc->sc_prev_key_idx; /* two-packet */
+ else
+ index = appleir_get_key(report[4]);
+
+ if (index >= 0) {
+ /* Normal key press */
+ if (index < APPLEIR_NKEYS)
+ new_key = appleir_keymap[index];
+ else
+ new_key = KEY_RESERVED;
+
+ if (new_key != KEY_RESERVED) {
+ DPRINTFN(5, "key down: idx=%d key=0x%04x\n",
+ index, new_key);
+ 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);
+ }
+ sc->sc_prev_key_idx = 0;
+ } else {
+ /* Store index for next packet */
+ sc->sc_prev_key_idx = -index;
+ DPRINTFN(5, "two-packet: stored idx=%d\n",
+ sc->sc_prev_key_idx);
+ }
+ goto done;
+ }
+
+ /* Apple key repeat: [0x26][0x87][0xee][remote_id][key_code] */
+ if (report[0] == 0x26 && report[1] == 0x87 && report[2] == 0xee) {
+ if (sc->sc_current_key != 0) {
+ DPRINTFN(10, "Apple repeat: 0x%04x\n",
+ sc->sc_current_key);
+ evdev_push_key(sc->sc_evdev, sc->sc_current_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];
+
+ /* Clear any stale Apple two-packet state */
+ sc->sc_prev_key_idx = 0;
+
+ /* Validate checksum (code + ~code = 0xFF) */
+ if ((uint8_t)(code + checksum) != 0xFF) {
+ DPRINTFN(1, "generic IR: bad checksum %02x+%02x\n",
+ code, checksum);
+ goto done;
+ }
+
+ /* Lookup code in generic keymap */
+ new_key = generic_ir_lookup(code);
+ if (new_key == KEY_RESERVED) {
+ DPRINTFN(5, "generic IR: unmapped code 0x%02x\n",
+ code);
+ goto done;
+ }
+
+ /* Handle key press or repeat */
+ if (sc->sc_current_key != new_key) {
+ /* New key — release old, press new */
+ 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);
+ DPRINTFN(5, "generic IR: code=0x%02x key=0x%04x\n",
+ code, new_key);
+ } else {
+ /* Same key — repeat */
+ DPRINTFN(10, "generic IR repeat: 0x%04x\n",
+ 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;
+ }
+
+ /* Unknown report */
+ 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 (Universal)");
+ 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);
+ mtx_init(&sc->sc_mtx, "appleir", NULL, MTX_DEF);
+ callout_init_mtx(&sc->sc_co, &sc->sc_mtx, 0);
+
+ /* Create evdev device */
+ 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);
+
+ /* Register all non-reserved Apple keys */
+ for (i = 0; i < APPLEIR_NKEYS; i++) {
+ if (appleir_keymap[i] != KEY_RESERVED)
+ evdev_support_key(sc->sc_evdev, appleir_keymap[i]);
+ }
+
+ /* Register all generic IR keys */
+ 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;
+ }
+
+ /* Set interrupt handler */
+ hidbus_set_intr(dev, appleir_intr, sc);
+
+ /* Start interrupt transfers */
+ 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);
+ 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);
+ 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, May 28, 11:16 AM (21 m, 5 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
33584563
Default Alt Text
D55472.id172550.diff (15 KB)

Event Timeline