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