Hi I have an Intel Bluetooth device in my laptop (Dual Band Wireless-AC 8265), which needs firmware loaded before it can be used. I looked at it before and because the way its loaded is ugly and I'd rather not pollute the Bluetooth stack with handling it.
Basically, it identifies as a USB Bluetooth adaptor. It claims by class, subclass & protocol to be a Bluetooth device so ubt(4) will claim it, but it is not really at that point. It does not respond properly to Bluetooth HCI commands and the protocol stack that we have cannot deal with it. Other firmware requiring devices detach once the firmware is loaded and then reattach with a different ProductID but this does not. In order to find out if the device is in Bluetooth mode, you need to send/receive a couple of commands so quite a lot of setup needed in the match function which I don't really care for. So, I forced it to attach as ugen and based on Linux and FreeBSD software, I have a program now which will speak to the device and load the firmware. This only helped a bit, but now I wrote a ubtintel kernel driver, which sort of pretends to be a uhub and is configured as ubtintel* at uhub? ubt* at ubtintel? ugen* at ubtintel? and has a sysctl variable which can be set to indicate how to attach. Thus, I can disconnect (drvctl -d ugen0), set the sysctl and rescan the bus (drvctl -r ubtintel0) to attach as ubt. This does work just fine but I am unsure if its correct as I just cached the usb attach args and pass them to potential children when I want them I find the firmwares from from debian/nonfree repo so best provided via pkgsrc really, I think alongside the userland program. The ubtintel driver is attached.. is this somewhat hacky technique valid? Is there a better method for doing this? regards iain
/* $NetBSD: ubtintel.c$ */ /*- * Copyright (c) 2025 Iain Hibbert * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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/cdefs.h> __KERNEL_RCSID(0, "$NetBSD: ubtintel.c$"); #include <sys/param.h> #include <sys/device.h> #include <sys/sysctl.h> #include <sys/systm.h> #include <dev/usb/usb.h> #include <dev/usb/usbdi.h> #include <dev/usb/usbdevs.h> struct ubtintel_softc { device_t sc_dev; // its a me struct usb_attach_arg sc_uaa; // save the args, to attach device_t sc_child; // the child bool sc_firmware; // true if loaded struct sysctllog * sc_log; int sc_dying; // going away }; static int ubtintel_match(device_t, cfdata_t, void *); static void ubtintel_attach(device_t, device_t, void *); static int ubtintel_detach(device_t, int); static int ubtintel_activate(device_t, enum devact); static int ubtintel_rescan(device_t, const char *, const int *); static void ubtintel_childdet(device_t, device_t); CFATTACH_DECL2_NEW(ubtintel, sizeof(struct ubtintel_softc), ubtintel_match, ubtintel_attach, ubtintel_detach, ubtintel_activate, ubtintel_rescan, ubtintel_childdet); static int ubtintel_submatch(device_t, cfdata_t, const int *, void *); static int ubtintel_sysctl_child(SYSCTLFN_PROTO); /* * Match for specific known devices */ static const struct usb_devno ubtintel_devs[] = { { USB_VENDOR_INTEL2, 0x0025 }, // Wireless-AC 9260 { USB_VENDOR_INTEL2, 0x0026 }, { USB_VENDOR_INTEL2, 0x0029 }, // Wi-Fi 6 AX200 { USB_VENDOR_INTEL2, 0x0032 }, // Wi-Fi 6E AX210 (Gig+) { USB_VENDOR_INTEL2, 0x0033 }, { USB_VENDOR_INTEL2, 0x0035 }, { USB_VENDOR_INTEL2, 0x0036 }, { USB_VENDOR_INTEL2, 0x0037 }, { USB_VENDOR_INTEL2, 0x0038 }, { USB_VENDOR_INTEL2, 0x0039 }, { USB_VENDOR_INTEL2, 0x0a2b }, // Dual Band Wireless-AC 8265 { USB_VENDOR_INTEL2, 0x0aaa }, }; static int ubtintel_match(device_t parent, cfdata_t match, void *aux) { struct usb_attach_arg *uaa = aux; if (usb_lookup(ubtintel_devs, uaa->uaa_vendor, uaa->uaa_product) != NULL) return UMATCH_VENDOR_PRODUCT; return UMATCH_NONE; } static void ubtintel_attach(device_t parent, device_t self, void *aux) { struct ubtintel_softc *sc = device_private(self); struct usb_attach_arg *uaa = aux; const struct sysctlnode *node; char *devinfop; sc->sc_dev = self; sc->sc_uaa = *uaa; sc->sc_uaa.uaa_usegeneric = 1; // allow ugen to attach here aprint_naive("\n"); aprint_normal("\n"); devinfop = usbd_devinfo_alloc(uaa->uaa_device, 0); aprint_normal_dev(self, "%s\n", devinfop); usbd_devinfo_free(devinfop); sc->sc_child = config_found(self, &sc->sc_uaa, NULL, CFARGS(.submatch = ubtintel_submatch)); sysctl_createv(&sc->sc_log, 0, NULL, &node, 0, CTLTYPE_NODE, device_xname(sc->sc_dev), SYSCTL_DESCR("ubtintel driver information"), NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL); if (node != NULL) { sysctl_createv(&sc->sc_log, 0, NULL, NULL, CTLFLAG_READONLY, CTLTYPE_STRING, "child", SYSCTL_DESCR("child device name"), ubtintel_sysctl_child, 0, (void *)sc, 0, CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL); sysctl_createv(&sc->sc_log, 0, NULL, NULL, CTLFLAG_READWRITE, CTLTYPE_BOOL, "firmware", SYSCTL_DESCR("Firmware loaded"), NULL, 0, &sc->sc_firmware, sizeof(sc->sc_firmware), CTL_HW, node->sysctl_num, CTL_CREATE, CTL_EOL); } if (!pmf_device_register(self, NULL, NULL)) aprint_error_dev(self, "couldn't establish power handler\n"); } static int ubtintel_detach(device_t self, int flags) { struct ubtintel_softc *sc = device_private(self); sc->sc_dying = 1; pmf_device_deregister(self); sysctl_teardown(&sc->sc_log); config_detach_children(self, flags); return 0; } static int ubtintel_activate(device_t self, enum devact act) { struct ubtintel_softc *sc = device_private(self); switch (act) { case DVACT_DEACTIVATE: sc->sc_dying = 1; return 0; default: return EOPNOTSUPP; } } static int ubtintel_rescan(device_t self, const char *ifattr, const int *locs) { struct ubtintel_softc *sc = device_private(self); if (sc->sc_dying) return 0; if (sc->sc_child != NULL) return EBUSY; sc->sc_child = config_found(self, &sc->sc_uaa, NULL, CFARGS(.submatch = ubtintel_submatch, .iattr = ifattr, .locators = locs)); return 0; } static void ubtintel_childdet(device_t self, device_t child) { struct ubtintel_softc *sc = device_private(self); if (child == sc->sc_child) sc->sc_child = NULL; } static int ubtintel_submatch(device_t parent, cfdata_t cf, const int *ldesc, void *aux) { struct ubtintel_softc *sc = device_private(parent); KASSERT(cf != NULL); KASSERT(cf->cf_name != NULL); // skip matches against ubt if firmware not loaded if (strcmp(cf->cf_name, "ubt") == 0 && !sc->sc_firmware) return 0; return config_match(parent, cf, aux); } /* * provide the device name of the child */ static int ubtintel_sysctl_child(SYSCTLFN_ARGS) { struct ubtintel_softc *sc = rnode->sysctl_data; char value[DEVICE_XNAME_SIZE]; struct sysctlnode node; if (sc->sc_child != NULL) memcpy(value, device_xname(sc->sc_child), DEVICE_XNAME_SIZE); else memset(value, 0, DEVICE_XNAME_SIZE); node = *rnode; node.sysctl_data = value; node.sysctl_size = strlen(value) + 1; // strnlen() ? return sysctl_lookup(SYSCTLFN_CALL(&node)); }