git: 0f1bf1c22a0c - main - umb: Introduce the USB umb(4) network driver

From: Adrian Chadd <adrian_at_FreeBSD.org>
Date: Mon, 20 Jan 2025 23:47:40 UTC
The branch main has been updated by adrian:

URL: https://cgit.FreeBSD.org/src/commit/?id=0f1bf1c22a0c97e84a4db19197a75952487aa20b

commit 0f1bf1c22a0c97e84a4db19197a75952487aa20b
Author:     Adrian Chadd <adrian@FreeBSD.org>
AuthorDate: 2025-01-20 23:46:15 +0000
Commit:     Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2025-01-20 23:46:15 +0000

    umb: Introduce the USB umb(4) network driver
    
    This includes the port of a driver originally from OpenBSD, later
    ported to NetBSD by the author:
    
    * The umb(4) kernel driver
    * The umbctl(8) companion tool
    
    This driver supports USB network devices implementing the
    Mobile Broadband Interface Model (MBIM), often found in modern
    (internal) USB models for 4G/LTE mobile broadband access.
    
    It is currently limited to IPv4.
    
    umbctl has to be used to display or set MBIM cellular modem
    interface parameters (4G/LTE).
    
    Differential Revision:  https://reviews.freebsd.org/D48167
    Approved by:    adrian, zlei
    Sponsored by:   FreeBSD Foundation
    PR:             kern/263783
    Submitted by:   Pierre Pronchery <khorben@defora.org>
---
 sbin/Makefile                |    1 +
 sbin/umbctl/Makefile         |    8 +
 sbin/umbctl/umbctl.8         |  161 +++
 sbin/umbctl/umbctl.c         |  557 ++++++++
 share/man/man4/Makefile      |    1 +
 share/man/man4/umb.4         |  119 ++
 sys/conf/files               |    1 +
 sys/dev/usb/net/if_umb.c     | 2930 ++++++++++++++++++++++++++++++++++++++++++
 sys/dev/usb/net/if_umbreg.h  |  443 +++++++
 sys/dev/usb/net/mbim.h       |  727 +++++++++++
 sys/modules/usb/Makefile     |    2 +-
 sys/modules/usb/umb/Makefile |   33 +
 12 files changed, 4982 insertions(+), 1 deletion(-)

diff --git a/sbin/Makefile b/sbin/Makefile
index 790112b05f6f..5e5a8943c67a 100644
--- a/sbin/Makefile
+++ b/sbin/Makefile
@@ -63,6 +63,7 @@ SUBDIR=adjkerntz \
 	swapon \
 	sysctl \
 	tunefs \
+	umbctl \
 	umount
 
 .if ${MK_INET} != "no" || ${MK_INET6} != "no"
diff --git a/sbin/umbctl/Makefile b/sbin/umbctl/Makefile
new file mode 100644
index 000000000000..35afb1bcfd4b
--- /dev/null
+++ b/sbin/umbctl/Makefile
@@ -0,0 +1,8 @@
+CFLAGS+= -I${SRCTOP}/sys/dev/usb/net
+
+PROG=	umbctl
+MAN=	umbctl.8
+
+BINDIR=	/sbin
+
+.include <bsd.prog.mk>
diff --git a/sbin/umbctl/umbctl.8 b/sbin/umbctl/umbctl.8
new file mode 100644
index 000000000000..55f8e315fabc
--- /dev/null
+++ b/sbin/umbctl/umbctl.8
@@ -0,0 +1,161 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2018 by Pierre Pronchery <khorben@defora.org>
+.\"
+.\" 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(S) ``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(S) 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.
+.\"
+.\" From: pppoectl.8,v 1.30 2016/09/12 05:35:20 sevan Exp $
+.\"
+.\" $NetBSD: umbctl.8,v 1.3 2020/03/22 07:45:02 khorben Exp $
+.\"
+.\" last edit-date: [Fri Dec 20 18:20:00 2024]
+.\"
+.Dd December 20, 2024
+.Dt UMBCTL 8
+.Os
+.Sh NAME
+.Nm umbctl
+.Nd display or set MBIM cellular modem interface parameters (4G/LTE)
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Ar ifname
+.Op Ar parameter Op Ar value
+.Ar ...
+.Nm
+.Op Fl v
+.Fl f Ar config-file
+.Ar ifname
+.Sh DESCRIPTION
+.Nm
+supports the following options:
+.Bl -tag -width "-f config_file"
+.It Fl f Ar config-file
+Parse
+.Ar config-file
+for
+.Ar parameter Ns Op \&= Ns Ar value
+pairs, one per line, as if they had been specified on the command line.
+This allows the password or PIN codes to be not passed as command line
+arguments.
+Comments starting with # to the end of the current line are ignored.
+.It Fl v
+Enables verbose mode.
+.El
+.Pp
+The
+.Xr umb 4
+driver may require a number of additional arguments or optional
+parameters besides the settings that can be adjusted with
+.Xr ifconfig 8 .
+These may be credentials or other tunable connectivity variables.
+The
+.Nm
+utility can be used to display the current settings, or to adjust these
+parameters as required.
+.Pp
+For whatever intent
+.Nm
+is being called, at least the parameter
+.Ar ifname
+needs to be specified, naming the interface for which the settings
+are to be performed or displayed.
+Use
+.Xr ifconfig 8
+or
+.Xr netstat 1
+to see which interfaces are available.
+.Pp
+If no other parameter is given,
+.Nm
+will just list the current status for
+.Ar ifname
+and exit.
+.Pp
+If any additional parameter is supplied, superuser privileges are
+required, and the command works in
+.Ql set
+mode.
+This is normally done quietly, unless the option
+.Fl v
+is also enabled, which will cause a final printout of the status as
+described above once all other actions have been taken.
+.Pp
+The parameters currently supported include:
+.Bl -tag -width "username=username"
+.It Ar apn Ns \&= Ns Em access-point
+Set the APN to
+.Em access-point .
+.It Ar username Ns \&= Ns Em username
+Set the username to
+.Em username .
+.It Ar password Ns \&= Ns Em password
+Set the password to
+.Em password .
+.It Ar pin Ns \&= Ns Em pin-code
+Enter the PIN
+.Em pin-code .
+.It Ar puk Ns \&= Ns Em puk-code
+Enter the PUK
+.Em puk-code .
+.It Ar roaming
+Allow data connections when roaming.
+.It Ar -roaming
+Deny data connections when roaming.
+.El
+.Sh EXAMPLES
+Display the settings for umb0:
+.Bd -literal
+# umbctl umb0
+umb0: state up, mode automatic, registration home network
+	provider "BSD-Net", dataclass LTE, signal good
+	phone number "+15554242", roaming "" (denied)
+	APN "", TX 50000000, RX 100000000
+	firmware "MBIM_FW_V1.0", hardware "MBIM_HW_V1.0"
+.Ed
+.Pp
+Configure the connection parameters for umb0 from the command line:
+.Bd -literal
+# umbctl umb0 apn operator.internet username mobile password mobile
+.Ed
+.Pp
+Configure the connection parameters for umb0 from a file:
+.Bd -literal
+# umbctl -f /dev/stdin umb0 << EOF
+pin=1234
+EOF
+.Ed
+.Sh SEE ALSO
+.Xr netstat 1 ,
+.Xr umb 4 ,
+.Xr ifconfig 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Nx 9.0 ,
+and
+.Fx 15.0 .
+.Sh AUTHORS
+The program was written by
+.An Pierre Pronchery .
diff --git a/sbin/umbctl/umbctl.c b/sbin/umbctl/umbctl.c
new file mode 100644
index 000000000000..3d57b486ad80
--- /dev/null
+++ b/sbin/umbctl/umbctl.c
@@ -0,0 +1,557 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Original copyright (c) 2018 Pierre Pronchery <khorben@defora.org> (for the
+ * NetBSD Project)
+ *
+ * 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 DEVELOPERS ``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 DEVELOPERS 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.
+ *
+ * Copyright (c) 2022 ADISTA SAS (FreeBSD updates)
+ *
+ * Updates for FreeBSD by Pierre Pronchery <pierre@defora.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the copyright holder nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
+ *
+ * $NetBSD: umbctl.c,v 1.4 2020/05/13 21:44:30 khorben Exp $
+ */
+
+#include <sys/endian.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "mbim.h"
+#include "if_umbreg.h"
+
+/* constants */
+static const struct umb_valdescr _umb_actstate[] =
+	MBIM_ACTIVATION_STATE_DESCRIPTIONS;
+
+static const struct umb_valdescr _umb_simstate[] =
+	MBIM_SIMSTATE_DESCRIPTIONS;
+
+static const struct umb_valdescr _umb_regstate[] =
+	MBIM_REGSTATE_DESCRIPTIONS;
+
+static const struct umb_valdescr _umb_pktstate[] =
+	MBIM_PKTSRV_STATE_DESCRIPTIONS;
+
+static const struct umb_valdescr _umb_dataclass[] =
+	MBIM_DATACLASS_DESCRIPTIONS;
+
+static const struct umb_valdescr _umb_state[] =
+	UMB_INTERNAL_STATE_DESCRIPTIONS;
+
+static const struct umb_valdescr _umb_pin_state[] =
+{
+	{ UMB_PIN_REQUIRED, "PIN required"},
+	{ UMB_PIN_UNLOCKED, "PIN unlocked"},
+	{ UMB_PUK_REQUIRED, "PUK required"},
+	{ 0, NULL }
+};
+
+static const struct umb_valdescr _umb_regmode[] =
+{
+	{ MBIM_REGMODE_UNKNOWN, "unknown" },
+	{ MBIM_REGMODE_AUTOMATIC, "automatic" },
+	{ MBIM_REGMODE_MANUAL, "manual" },
+	{ 0, NULL }
+};
+
+static const struct umb_valdescr _umb_ber[] =
+{
+	{ UMB_BER_EXCELLENT, "excellent" },
+	{ UMB_BER_VERYGOOD, "very good" },
+	{ UMB_BER_GOOD, "good" },
+	{ UMB_BER_OK, "ok" },
+	{ UMB_BER_MEDIUM, "medium" },
+	{ UMB_BER_BAD, "bad" },
+	{ UMB_BER_VERYBAD, "very bad" },
+	{ UMB_BER_EXTREMELYBAD, "extremely bad" },
+	{ 0, NULL }
+};
+
+
+/* prototypes */
+static int _char_to_utf16(const char * in, uint16_t * out, size_t outlen);
+static int _error(int ret, char const * format, ...);
+static int _umbctl(char const * ifname, int verbose, int argc, char * argv[]);
+static int _umbctl_file(char const * ifname, char const * filename,
+		int verbose);
+static void _umbctl_info(char const * ifname, struct umb_info * umbi);
+static int _umbctl_ioctl(char const * ifname, int fd, unsigned long request,
+		struct ifreq * ifr);
+static int _umbctl_set(char const * ifname, struct umb_parameter * umbp,
+		int argc, char * argv[]);
+static int _umbctl_socket(void);
+static int _usage(void);
+static void _utf16_to_char(uint16_t * in, int inlen, char * out, size_t outlen);
+
+
+/* functions */
+/* char_to_utf16 */
+/* this function is from OpenBSD's ifconfig(8) */
+static int _char_to_utf16(const char * in, uint16_t * out, size_t outlen)
+{
+	int	n = 0;
+	uint16_t c;
+
+	for (;;) {
+		c = *in++;
+
+		if (c == '\0') {
+			/*
+			 * NUL termination is not required, but zero out the
+			 * residual buffer
+			 */
+			memset(out, 0, outlen);
+			return n;
+		}
+		if (outlen < sizeof(*out))
+			return -1;
+
+		*out++ = htole16(c);
+		n += sizeof(*out);
+		outlen -= sizeof(*out);
+	}
+}
+
+
+/* error */
+static int _error(int ret, char const * format, ...)
+{
+	va_list ap;
+
+	fputs("umbctl: ", stderr);
+	va_start(ap, format);
+	vfprintf(stderr, format, ap);
+	va_end(ap);
+	fputs("\n", stderr);
+	return ret;
+}
+
+
+/* umbctl */
+static int _umbctl(char const * ifname, int verbose, int argc, char * argv[])
+{
+	int fd;
+	struct ifreq ifr;
+	struct umb_info umbi;
+	struct umb_parameter umbp;
+
+	if((fd = _umbctl_socket()) < 0)
+		return 2;
+	memset(&ifr, 0, sizeof(ifr));
+	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+	if(argc != 0)
+	{
+		memset(&umbp, 0, sizeof(umbp));
+		ifr.ifr_data = (caddr_t)&umbp;
+		if(_umbctl_ioctl(ifname, fd, SIOCGUMBPARAM, &ifr) != 0
+				|| _umbctl_set(ifname, &umbp, argc, argv) != 0
+				|| _umbctl_ioctl(ifname, fd, SIOCSUMBPARAM,
+					&ifr) != 0)
+		{
+			close(fd);
+			return 2;
+		}
+	}
+	if(argc == 0 || verbose > 0)
+	{
+		ifr.ifr_data = (caddr_t)&umbi;
+		if(_umbctl_ioctl(ifname, fd, SIOCGUMBINFO, &ifr) != 0)
+		{
+			close(fd);
+			return 3;
+		}
+		_umbctl_info(ifname, &umbi);
+	}
+	if(close(fd) != 0)
+		return _error(2, "%s: %s", ifname, strerror(errno));
+	return 0;
+}
+
+
+/* umbctl_file */
+static int _file_parse(char const * ifname, struct umb_parameter * umbp,
+		char const * filename);
+
+static int _umbctl_file(char const * ifname, char const * filename, int verbose)
+{
+	int fd;
+	struct ifreq ifr;
+	struct umb_parameter umbp;
+	struct umb_info umbi;
+
+	if((fd = _umbctl_socket()) < 0)
+		return 2;
+	memset(&ifr, 0, sizeof(ifr));
+	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+	ifr.ifr_data = (caddr_t)&umbp;
+	memset(&umbp, 0, sizeof(umbp));
+	if(_umbctl_ioctl(ifname, fd, SIOCGUMBPARAM, &ifr) != 0
+			|| _file_parse(ifname, &umbp, filename) != 0
+			|| _umbctl_ioctl(ifname, fd, SIOCSUMBPARAM, &ifr) != 0)
+	{
+		close(fd);
+		return 2;
+	}
+	if(verbose > 0)
+	{
+		ifr.ifr_data = (caddr_t)&umbi;
+		if(_umbctl_ioctl(ifname, fd, SIOCGUMBINFO, &ifr) != 0)
+		{
+			close(fd);
+			return 3;
+		}
+		_umbctl_info(ifname, &umbi);
+	}
+	if(close(fd) != 0)
+		return _error(2, "%s: %s", ifname, strerror(errno));
+	return 0;
+}
+
+static int _file_parse(char const * ifname, struct umb_parameter * umbp,
+		char const * filename)
+{
+	int ret = 0;
+	FILE * fp;
+	char buf[512];
+	size_t len;
+	int i;
+	int eof;
+	char * tokens[3] = { buf, NULL, NULL };
+	char * p;
+
+	if((fp = fopen(filename, "r")) == NULL)
+		return _error(2, "%s: %s", filename, strerror(errno));
+	while(fgets(buf, sizeof(buf), fp) != NULL)
+	{
+		if(buf[0] == '#')
+			continue;
+		buf[sizeof(buf) - 1] = '\0';
+		if((len = strlen(buf)) > 0)
+		{
+			if(buf[len - 1] != '\n')
+			{
+				ret = _error(2, "%s: %s", filename,
+						"Line too long");
+				while((i = fgetc(fp)) != EOF && i != '\n');
+				continue;
+			}
+			buf[len - 1] = '\0';
+		}
+		if((p = strchr(buf, '=')) != NULL)
+		{
+			tokens[1] = p + 1;
+			*p = '\0';
+		} else
+			tokens[1] = NULL;
+		ret |= _umbctl_set(ifname, umbp, (p != NULL) ? 2 : 1, tokens)
+			? 2 : 0;
+	}
+	eof = feof(fp);
+	if(fclose(fp) != 0 || !eof)
+		return _error(2, "%s: %s", filename, strerror(errno));
+	return ret;
+}
+
+
+/* umbctl_info */
+static void _umbctl_info(char const * ifname, struct umb_info * umbi)
+{
+	char provider[UMB_PROVIDERNAME_MAXLEN + 1];
+	char pn[UMB_PHONENR_MAXLEN + 1];
+	char roaming[UMB_ROAMINGTEXT_MAXLEN + 1];
+	char apn[UMB_APN_MAXLEN + 1];
+	char fwinfo[UMB_FWINFO_MAXLEN + 1];
+	char hwinfo[UMB_HWINFO_MAXLEN + 1];
+
+	_utf16_to_char(umbi->provider, UMB_PROVIDERNAME_MAXLEN,
+			provider, sizeof(provider));
+	_utf16_to_char(umbi->pn, UMB_PHONENR_MAXLEN, pn, sizeof(pn));
+	_utf16_to_char(umbi->roamingtxt, UMB_ROAMINGTEXT_MAXLEN,
+			roaming, sizeof(roaming));
+	_utf16_to_char(umbi->apn, UMB_APN_MAXLEN, apn, sizeof(apn));
+	_utf16_to_char(umbi->fwinfo, UMB_FWINFO_MAXLEN, fwinfo, sizeof(fwinfo));
+	_utf16_to_char(umbi->hwinfo, UMB_HWINFO_MAXLEN, hwinfo, sizeof(hwinfo));
+	printf("%s: state %s, mode %s, registration %s\n"
+			"\tprovider \"%s\", dataclass %s, signal %s\n"
+			"\tphone number \"%s\", roaming \"%s\" (%s)\n"
+			"\tAPN \"%s\", TX %" PRIu64 ", RX %" PRIu64 "\n"
+			"\tfirmware \"%s\", hardware \"%s\"\n",
+			ifname, umb_val2descr(_umb_state, umbi->state),
+			umb_val2descr(_umb_regmode, umbi->regmode),
+			umb_val2descr(_umb_regstate, umbi->regstate), provider,
+			umb_val2descr(_umb_dataclass, umbi->cellclass),
+			umb_val2descr(_umb_ber, umbi->ber), pn, roaming,
+			umbi->enable_roaming ? "allowed" : "denied",
+			apn, umbi->uplink_speed, umbi->downlink_speed,
+			fwinfo, hwinfo);
+}
+
+
+/* umbctl_ioctl */
+static int _umbctl_ioctl(char const * ifname, int fd, unsigned long request,
+		struct ifreq * ifr)
+{
+	if(ioctl(fd, request, ifr) != 0)
+		return _error(-1, "%s: %s", ifname, strerror(errno));
+	return 0;
+}
+
+
+/* umbctl_set */
+/* callbacks */
+static int _set_apn(char const *, struct umb_parameter *, char const *);
+static int _set_username(char const *, struct umb_parameter *, char const *);
+static int _set_password(char const *, struct umb_parameter *, char const *);
+static int _set_pin(char const *, struct umb_parameter *, char const *);
+static int _set_puk(char const *, struct umb_parameter *, char const *);
+static int _set_roaming_allow(char const *, struct umb_parameter *,
+		char const *);
+static int _set_roaming_deny(char const *, struct umb_parameter *,
+		char const *);
+
+static int _umbctl_set(char const * ifname, struct umb_parameter * umbp,
+		int argc, char * argv[])
+{
+	struct
+	{
+		char const * name;
+		int (*callback)(char const *,
+				struct umb_parameter *, char const *);
+		int parameter;
+	} callbacks[] =
+	{
+		{ "apn", _set_apn, 1 },
+		{ "username", _set_username, 1 },
+		{ "password", _set_password, 1 },
+		{ "pin", _set_pin, 1 },
+		{ "puk", _set_puk, 1 },
+		{ "roaming", _set_roaming_allow, 0 },
+		{ "-roaming", _set_roaming_deny, 0 },
+	};
+	int i;
+	size_t j;
+
+	for(i = 0; i < argc; i++)
+	{
+		for(j = 0; j < sizeof(callbacks) / sizeof(*callbacks); j++)
+			if(strcmp(argv[i], callbacks[j].name) == 0)
+			{
+				if(callbacks[j].parameter && i + 1 == argc)
+					return _error(-1, "%s: Incomplete"
+							" parameter", argv[i]);
+				if(callbacks[j].callback(ifname, umbp,
+							callbacks[j].parameter
+							? argv[i + 1] : NULL))
+					return -1;
+				if(callbacks[j].parameter)
+					i++;
+				break;
+			}
+		if(j == sizeof(callbacks) / sizeof(*callbacks))
+			return _error(-1, "%s: Unknown parameter", argv[i]);
+	}
+	return 0;
+}
+
+static int _set_apn(char const * ifname, struct umb_parameter * umbp,
+		char const * apn)
+{
+	umbp->apnlen = _char_to_utf16(apn, umbp->apn, sizeof(umbp->apn));
+	if(umbp->apnlen < 0 || (size_t)umbp->apnlen > sizeof(umbp->apn))
+		return _error(-1, "%s: %s", ifname, "APN too long");
+	return 0;
+}
+
+static int _set_username(char const * ifname, struct umb_parameter * umbp,
+		char const * username)
+{
+	umbp->usernamelen = _char_to_utf16(username, umbp->username,
+			sizeof(umbp->username));
+	if(umbp->usernamelen < 0
+			|| (size_t)umbp->usernamelen > sizeof(umbp->username))
+		return _error(-1, "%s: %s", ifname, "Username too long");
+	return 0;
+}
+
+static int _set_password(char const * ifname, struct umb_parameter * umbp,
+		char const * password)
+{
+	umbp->passwordlen = _char_to_utf16(password, umbp->password,
+			sizeof(umbp->password));
+	if(umbp->passwordlen < 0
+			|| (size_t)umbp->passwordlen > sizeof(umbp->password))
+		return _error(-1, "%s: %s", ifname, "Password too long");
+	return 0;
+}
+
+static int _set_pin(char const * ifname, struct umb_parameter * umbp,
+		char const * pin)
+{
+	umbp->is_puk = 0;
+	umbp->op = MBIM_PIN_OP_ENTER;
+	umbp->pinlen = _char_to_utf16(pin, umbp->pin, sizeof(umbp->pin));
+	if(umbp->pinlen < 0 || (size_t)umbp->pinlen
+			> sizeof(umbp->pin))
+		return _error(-1, "%s: %s", ifname, "PIN code too long");
+	return 0;
+}
+
+static int _set_puk(char const * ifname, struct umb_parameter * umbp,
+		char const * puk)
+{
+	umbp->is_puk = 1;
+	umbp->op = MBIM_PIN_OP_ENTER;
+	umbp->pinlen = _char_to_utf16(puk, umbp->pin, sizeof(umbp->pin));
+	if(umbp->pinlen < 0 || (size_t)umbp->pinlen > sizeof(umbp->pin))
+		return _error(-1, "%s: %s", ifname, "PUK code too long");
+	return 0;
+}
+
+static int _set_roaming_allow(char const * ifname, struct umb_parameter * umbp,
+		char const * unused)
+{
+	(void) ifname;
+	(void) unused;
+
+	umbp->roaming = 1;
+	return 0;
+}
+
+static int _set_roaming_deny(char const * ifname, struct umb_parameter * umbp,
+		char const * unused)
+{
+	(void) ifname;
+	(void) unused;
+
+	umbp->roaming = 0;
+	return 0;
+}
+
+
+/* umbctl_socket */
+static int _umbctl_socket(void)
+{
+	int fd;
+
+	if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
+		return _error(-1, "socket: %s", strerror(errno));
+	return fd;
+}
+
+
+/* usage */
+static int _usage(void)
+{
+	fputs("Usage: umbctl [-v] ifname [parameter [value]] [...]\n"
+"       umbctl -f config-file ifname\n",
+			stderr);
+	return 1;
+}
+
+
+/* utf16_to_char */
+static void _utf16_to_char(uint16_t * in, int inlen, char * out, size_t outlen)
+{
+	uint16_t c;
+
+	while (outlen > 0) {
+		c = inlen > 0 ? htole16(*in) : 0;
+		if (c == 0 || --outlen == 0) {
+			/* always NUL terminate result */
+			*out = '\0';
+			break;
+		}
+		*out++ = isascii(c) ? (char)c : '?';
+		in++;
+		inlen--;
+	}
+}
+
+
+/* main */
+int main(int argc, char * argv[])
+{
+	int o;
+	char const * filename = NULL;
+	int verbose = 0;
+
+	while((o = getopt(argc, argv, "f:gv")) != -1)
+		switch(o)
+		{
+			case 'f':
+				filename = optarg;
+				break;
+			case 'v':
+				verbose++;
+				break;
+			default:
+				return _usage();
+		}
+	if(optind == argc)
+		return _usage();
+	if(filename != NULL)
+	{
+		if(optind + 1 != argc)
+			return _usage();
+		return _umbctl_file(argv[optind], filename, verbose);
+	}
+	return _umbctl(argv[optind], verbose, argc - optind - 1,
+			&argv[optind + 1]);
+}
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index c03ba63c349f..9dc1c7f9bc12 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -1041,6 +1041,7 @@ MAN+=	\
 	uled.4 \
 	ulpt.4 \
 	umass.4 \
+	umb.4 \
 	umcs.4 \
 	umct.4 \
 	umodem.4 \
diff --git a/share/man/man4/umb.4 b/share/man/man4/umb.4
new file mode 100644
index 000000000000..b0d517095933
--- /dev/null
+++ b/share/man/man4/umb.4
@@ -0,0 +1,119 @@
+.\"-
+.\" SPDX-License-Identifier: 0BSD
+.\"
+.\" Copyright (c) 2016 genua mbH
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.\" $NetBSD: umb.4,v 1.4 2019/08/30 09:22:17 wiz Exp $
+.\"
+.Dd August 24, 2019
+.Dt UMB 4
+.Os
+.Sh NAME
+.Nm umb
+.Nd USB Mobile Broadband Interface Model (MBIM)
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following lines in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device usb"
+.Cd "device umb"
+.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
+umb_load="YES"
+.Ed
+.Pp
+If neither of the above is done, the driver will automatically be loaded
+by devd(8) when the device is connected.
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for USB MBIM devices.
+.Pp
+MBIM devices establish connections via cellular networks such as
+GPRS, UMTS, and LTE.
+They appear as a regular point-to-point network interface,
+transporting raw IP frames.
+.Pp
+Required configuration parameters like PIN and APN have to be set
+with
+.Xr umbctl 8 .
+Once the SIM card has been unlocked with the correct PIN, it
+will remain in this state until the MBIM device is power-cycled.
+In case the device is connected to an "always-on" USB port,
+it may be possible to connect to a provider without entering the
+PIN again even if the system was rebooted.
+.Sh HARDWARE
+The
+.Nm
+driver should support any USB device implementing MBIM, including
+the following cellular modems:
+.Pp
+.Bl -bullet -compact
+.It
+Ericsson H5321gw and N5321gw
+.It
+Fibocom L831-EAU
+.It
+Medion Mobile S4222 (MediaTek OEM)
+.It
+Sierra Wireless EM7345
+.It
+Sierra Wireless EM7455
+.It
+Sierra Wireless EM8805
+.It
+Sierra Wireless MC8305
+.El
+.Sh SEE ALSO
+.Xr intro 4 ,
+.Xr netintro 4 ,
+.Xr usb 4 ,
+.Xr ifconfig 8 ,
+.Xr umbctl 8
+.Rs
+.%T "Universal Serial Bus Communications Class Subclass Specification for Mobile Broadband Interface Model"
+.%U http://www.usb.org/developers/docs/devclass_docs/MBIM10Errata1_073013.zip
+.Re
+.Sh HISTORY
+The
+.Nm
+device driver first appeared in
+.Ox 6.0 ,
+.Nx 9.0 ,
+and
+.Fx 15.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Gerhard Roth Aq Mt gerhard@openbsd.org
+and ported from
+.Ox
+by
+.An Pierre Pronchery Aq Mt khorben@defora.org .
+.Sh CAVEATS
+The
+.Nm
+driver does not support IPv6.
+.Pp
+Devices which fail to provide a conforming MBIM implementation will
+probably be attached as some other driver, such as
+.Xr u3g 4 .
diff --git a/sys/conf/files b/sys/conf/files
index a02174f3d954..5fd7b887581a 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3293,6 +3293,7 @@ dev/usb/net/if_muge.c		optional muge
 dev/usb/net/if_rue.c		optional rue
 dev/usb/net/if_smsc.c		optional smsc
 dev/usb/net/if_udav.c		optional udav
+dev/usb/net/if_umb.c		optional umb
 dev/usb/net/if_ure.c		optional ure
 dev/usb/net/if_usie.c		optional usie
 dev/usb/net/if_urndis.c		optional urndis
diff --git a/sys/dev/usb/net/if_umb.c b/sys/dev/usb/net/if_umb.c
new file mode 100644
index 000000000000..9b2b504cfa6b
--- /dev/null
+++ b/sys/dev/usb/net/if_umb.c
@@ -0,0 +1,2930 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Original copyright (c) 2016 genua mbH (OpenBSD version)
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Copyright (c) 2022 ADISTA SAS (re-write for FreeBSD)
+ *
+ * Re-write for FreeBSD by Pierre Pronchery <pierre@defora.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
*** 4136 LINES SKIPPED ***