Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F158009994
D55472.id172550.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
15 KB
Referenced Files
None
Subscribers
None
D55472.id172550.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D55472: appleir: Add Apple IR receiver driver
Attached
Detach File
Event Timeline
Log In to Comment