tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Intel Bluetooth driver



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));
}


Home | Main Index | Thread Index | Old Index