diff --git a/sbin/Makefile b/sbin/Makefile --- a/sbin/Makefile +++ b/sbin/Makefile @@ -63,6 +63,7 @@ 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 --- /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 diff --git a/sbin/umbctl/umbctl.8 b/sbin/umbctl/umbctl.8 new file mode 100644 --- /dev/null +++ b/sbin/umbctl/umbctl.8 @@ -0,0 +1,161 @@ +.\"- +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2018 by Pierre Pronchery +.\" +.\" 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 --- /dev/null +++ b/sbin/umbctl/umbctl.c @@ -0,0 +1,557 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Original copyright (c) 2018 Pierre Pronchery (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 + * + * 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 +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -1045,6 +1045,7 @@ 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 --- /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 --- a/sys/conf/files +++ b/sys/conf/files @@ -3307,6 +3307,7 @@ 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 --- /dev/null +++ b/sys/dev/usb/net/if_umb.c @@ -0,0 +1,2931 @@ +/*- + * 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 + * + * 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: if_umb.c,v 1.5 2018/09/20 09:45:16 khorben Exp $ + * $OpenBSD: if_umb.c,v 1.18 2018/02/19 08:59:52 mpi Exp $ + */ + +/* + * Mobile Broadband Interface Model specification: + * http://www.usb.org/developers/docs/devclass_docs/MBIM10Errata1_073013.zip + * Compliance testing guide + * http://www.usb.org/developers/docs/devclass_docs/MBIM-Compliance-1.0.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "usb_if.h" + +#include "mbim.h" +#include "if_umbreg.h" + +MALLOC_DECLARE(M_MBIM_CID_CONNECT); +MALLOC_DEFINE(M_MBIM_CID_CONNECT, "mbim_cid_connect", + "Connection parameters for MBIM"); + +#ifdef UMB_DEBUG +#define DPRINTF(x...) \ + do { if (umb_debug) log(LOG_DEBUG, x); } while (0) + +#define DPRINTFN(n, x...) \ + do { if (umb_debug >= (n)) log(LOG_DEBUG, x); } while (0) + +#define DDUMPN(n, b, l) \ + do { \ + if (umb_debug >= (n)) \ + umb_dump((b), (l)); \ + } while (0) + +const int umb_debug = 1; +static char *umb_uuid2str(uint8_t [MBIM_UUID_LEN]); +static void umb_dump(void *, int); + +#else +#define DPRINTF(x...) do { } while (0) +#define DPRINTFN(n, x...) do { } while (0) +#define DDUMPN(n, b, l) do { } while (0) +#endif + +#define DEVNAM(sc) device_get_nameunit((sc)->sc_dev) + +/* + * State change timeout + */ +#define UMB_STATE_CHANGE_TIMEOUT 30 + +/* + * State change flags + */ +#define UMB_NS_DONT_DROP 0x0001 /* do not drop below current state */ +#define UMB_NS_DONT_RAISE 0x0002 /* do not raise below current state */ + +/* + * Diagnostic macros + */ +const struct umb_valdescr umb_regstates[] = MBIM_REGSTATE_DESCRIPTIONS; +const struct umb_valdescr umb_dataclasses[] = MBIM_DATACLASS_DESCRIPTIONS; +const struct umb_valdescr umb_simstate[] = MBIM_SIMSTATE_DESCRIPTIONS; +const struct umb_valdescr umb_messages[] = MBIM_MESSAGES_DESCRIPTIONS; +const struct umb_valdescr umb_status[] = MBIM_STATUS_DESCRIPTIONS; +const struct umb_valdescr umb_cids[] = MBIM_CID_DESCRIPTIONS; +const struct umb_valdescr umb_pktstate[] = MBIM_PKTSRV_STATE_DESCRIPTIONS; +const struct umb_valdescr umb_actstate[] = MBIM_ACTIVATION_STATE_DESCRIPTIONS; +const struct umb_valdescr umb_error[] = MBIM_ERROR_DESCRIPTIONS; +const struct umb_valdescr umb_pintype[] = MBIM_PINTYPE_DESCRIPTIONS; +const struct umb_valdescr umb_istate[] = UMB_INTERNAL_STATE_DESCRIPTIONS; + +#define umb_regstate(c) umb_val2descr(umb_regstates, (c)) +#define umb_dataclass(c) umb_val2descr(umb_dataclasses, (c)) +#define umb_simstate(s) umb_val2descr(umb_simstate, (s)) +#define umb_request2str(m) umb_val2descr(umb_messages, (m)) +#define umb_status2str(s) umb_val2descr(umb_status, (s)) +#define umb_cid2str(c) umb_val2descr(umb_cids, (c)) +#define umb_packet_state(s) umb_val2descr(umb_pktstate, (s)) +#define umb_activation(s) umb_val2descr(umb_actstate, (s)) +#define umb_error2str(e) umb_val2descr(umb_error, (e)) +#define umb_pin_type(t) umb_val2descr(umb_pintype, (t)) +#define umb_istate(s) umb_val2descr(umb_istate, (s)) + +static device_probe_t umb_probe; +static device_attach_t umb_attach; +static device_detach_t umb_detach; +static device_suspend_t umb_suspend; +static device_resume_t umb_resume; +static void umb_attach_task(struct usb_proc_msg *); +static usb_handle_request_t umb_handle_request; +static int umb_deactivate(device_t); +static void umb_ncm_setup(struct umb_softc *, struct usb_config *); +static void umb_close_bulkpipes(struct umb_softc *); +static int umb_ioctl(struct ifnet *, u_long, caddr_t); +static void umb_init(void *); +#ifdef DEV_NETMAP +static void umb_input(struct ifnet *, struct mbuf *); +#endif +static int umb_output(struct ifnet *, struct mbuf *, + const struct sockaddr *, struct route *); +static void umb_start(struct ifnet *); +static void umb_start_task(struct usb_proc_msg *); +#if 0 +static void umb_watchdog(struct ifnet *); +#endif +static void umb_statechg_timeout(void *); + +static int umb_mediachange(struct ifnet *); +static void umb_mediastatus(struct ifnet *, struct ifmediareq *); + +static void umb_add_task(struct umb_softc *sc, usb_proc_callback_t, + struct usb_proc_msg *, struct usb_proc_msg *, int); +static void umb_newstate(struct umb_softc *, enum umb_state, int); +static void umb_state_task(struct usb_proc_msg *); +static void umb_up(struct umb_softc *); +static void umb_down(struct umb_softc *, int); + +static void umb_get_response_task(struct usb_proc_msg *); + +static void umb_decode_response(struct umb_softc *, void *, int); +static void umb_handle_indicate_status_msg(struct umb_softc *, void *, + int); +static void umb_handle_opendone_msg(struct umb_softc *, void *, int); +static void umb_handle_closedone_msg(struct umb_softc *, void *, int); +static int umb_decode_register_state(struct umb_softc *, void *, int); +static int umb_decode_devices_caps(struct umb_softc *, void *, int); +static int umb_decode_subscriber_status(struct umb_softc *, void *, int); +static int umb_decode_radio_state(struct umb_softc *, void *, int); +static int umb_decode_pin(struct umb_softc *, void *, int); +static int umb_decode_packet_service(struct umb_softc *, void *, int); +static int umb_decode_signal_state(struct umb_softc *, void *, int); +static int umb_decode_connect_info(struct umb_softc *, void *, int); +static int umb_decode_ip_configuration(struct umb_softc *, void *, int); +static void umb_rx(struct umb_softc *); +static usb_callback_t umb_rxeof; +static void umb_rxflush(struct umb_softc *); +static int umb_encap(struct umb_softc *, struct mbuf *, struct usb_xfer *); +static usb_callback_t umb_txeof; +static void umb_txflush(struct umb_softc *); +static void umb_decap(struct umb_softc *, struct usb_xfer *, int); + +static usb_error_t umb_send_encap_command(struct umb_softc *, void *, int); +static int umb_get_encap_response(struct umb_softc *, void *, int *); +static void umb_ctrl_msg(struct umb_softc *, uint32_t, void *, int); + +static void umb_open(struct umb_softc *); +static void umb_close(struct umb_softc *); + +static int umb_setpin(struct umb_softc *, int, int, void *, int, void *, + int); +static void umb_setdataclass(struct umb_softc *); +static void umb_radio(struct umb_softc *, int); +static void umb_allocate_cid(struct umb_softc *); +static void umb_send_fcc_auth(struct umb_softc *); +static void umb_packet_service(struct umb_softc *, int); +static void umb_connect(struct umb_softc *); +static void umb_disconnect(struct umb_softc *); +static void umb_send_connect(struct umb_softc *, int); + +static void umb_qry_ipconfig(struct umb_softc *); +static void umb_cmd(struct umb_softc *, int, int, const void *, int); +static void umb_cmd1(struct umb_softc *, int, int, const void *, int, uint8_t *); +static void umb_command_done(struct umb_softc *, void *, int); +static void umb_decode_cid(struct umb_softc *, uint32_t, void *, int); +static void umb_decode_qmi(struct umb_softc *, uint8_t *, int); + +static usb_callback_t umb_intr; + +static char *umb_ntop(struct sockaddr *); + +static const int umb_xfer_tout = USB_DEFAULT_TIMEOUT; + +static uint8_t umb_uuid_basic_connect[] = MBIM_UUID_BASIC_CONNECT; +static uint8_t umb_uuid_context_internet[] = MBIM_UUID_CONTEXT_INTERNET; +static uint8_t umb_uuid_qmi_mbim[] = MBIM_UUID_QMI_MBIM; +static uint32_t umb_session_id = 0; + +static const struct usb_config umb_config[UMB_N_TRANSFER] = { + [UMB_INTR_RX] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .if_index = 1, + .callback = umb_intr, + .bufsize = sizeof (struct usb_cdc_notification), + .flags = {.pipe_bof = 1,.short_xfer_ok = 1}, + .usb_mode = USB_MODE_HOST, + }, + [UMB_BULK_RX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .if_index = 0, + .callback = umb_rxeof, + .bufsize = 8 * 1024, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.ext_buffer = 1}, + .usb_mode = USB_MODE_HOST, + }, + [UMB_BULK_TX] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .if_index = 0, + .callback = umb_txeof, + .bufsize = 8 * 1024, + .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1}, + .timeout = umb_xfer_tout, + .usb_mode = USB_MODE_HOST, + }, +}; + +static device_method_t umb_methods[] = { + /* USB interface */ + DEVMETHOD(usb_handle_request, umb_handle_request), + + /* Device interface */ + DEVMETHOD(device_probe, umb_probe), + DEVMETHOD(device_attach, umb_attach), + DEVMETHOD(device_detach, umb_detach), + DEVMETHOD(device_suspend, umb_suspend), + DEVMETHOD(device_resume, umb_resume), + + DEVMETHOD_END +}; + +static driver_t umb_driver = { + .name = "umb", + .methods = umb_methods, + .size = sizeof (struct umb_softc), +}; + +MALLOC_DEFINE(M_USB_UMB, "USB UMB", "USB MBIM driver"); + +const int umb_delay = 4000; + +/* + * These devices require an "FCC Authentication" command. + */ +#ifndef USB_VENDOR_SIERRA +# define USB_VENDOR_SIERRA 0x1199 +#endif +#ifndef USB_PRODUCT_SIERRA_EM7455 +# define USB_PRODUCT_SIERRA_EM7455 0x9079 +#endif +const struct usb_device_id umb_fccauth_devs[] = { + { + .match_flag_vendor = 1, + .match_flag_product = 1, + .idVendor = USB_VENDOR_SIERRA, + .idProduct = USB_PRODUCT_SIERRA_EM7455 + } +}; + +static const uint8_t umb_qmi_alloc_cid[] = { + 0x01, + 0x0f, 0x00, /* len */ + 0x00, /* QMUX flags */ + 0x00, /* service "ctl" */ + 0x00, /* CID */ + 0x00, /* QMI flags */ + 0x01, /* transaction */ + 0x22, 0x00, /* msg "Allocate CID" */ + 0x04, 0x00, /* TLV len */ + 0x01, 0x01, 0x00, 0x02 /* TLV */ +}; + +static const uint8_t umb_qmi_fcc_auth[] = { + 0x01, + 0x0c, 0x00, /* len */ + 0x00, /* QMUX flags */ + 0x02, /* service "dms" */ +#define UMB_QMI_CID_OFFS 5 + 0x00, /* CID (filled in later) */ + 0x00, /* QMI flags */ + 0x01, 0x00, /* transaction */ + 0x5f, 0x55, /* msg "Send FCC Authentication" */ + 0x00, 0x00 /* TLV len */ +}; + +static int +umb_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + usb_interface_descriptor_t *id; + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + if ((id = usbd_get_interface_descriptor(uaa->iface)) == NULL) + return (ENXIO); + + /* + * If this function implements NCM, check if alternate setting + * 1 implements MBIM. + */ + if (id->bInterfaceClass == UICLASS_CDC && + id->bInterfaceSubClass == + UISUBCLASS_NETWORK_CONTROL_MODEL) { + id = usbd_get_interface_descriptor( + usbd_get_iface(uaa->device, + uaa->info.bIfaceIndex + 1)); + if (id == NULL || id->bAlternateSetting != 1) + return (ENXIO); + } + +#ifndef UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL +# define UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL 14 +#endif + if (id->bInterfaceClass == UICLASS_CDC && + id->bInterfaceSubClass == + UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL && + id->bInterfaceProtocol == 0) + return (BUS_PROBE_SPECIFIC); + + return (ENXIO); +} + +static int +umb_attach(device_t dev) +{ + struct umb_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usb_config config[UMB_N_TRANSFER]; + int v; + const struct usb_cdc_union_descriptor *ud; + const struct mbim_descriptor *md; + int i; + usb_interface_descriptor_t *id; + struct usb_interface *iface; + int data_ifaceno = -1; + usb_error_t error; + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + + memcpy(config, umb_config, sizeof (config)); + + device_set_usb_desc(dev); + + sc->sc_ctrl_ifaceno = uaa->info.bIfaceNum; + + mtx_init(&sc->sc_mutex, device_get_nameunit(dev), NULL, MTX_DEF); + + /* + * Some MBIM hardware does not provide the mandatory CDC Union + * Descriptor, so we also look at matching Interface + * Association Descriptors to find out the MBIM Data Interface + * number. + */ + sc->sc_ver_maj = sc->sc_ver_min = -1; + sc->sc_maxpktlen = MBIM_MAXSEGSZ_MINVAL; + id = usbd_get_interface_descriptor(uaa->iface); + + ud = usbd_find_descriptor(sc->sc_udev, id, uaa->info.bIfaceIndex, + UDESC_CS_INTERFACE, 0xff, UDESCSUB_CDC_UNION, 0xff); + if (ud != NULL) { + data_ifaceno = ud->bSlaveInterface[0]; + } + + md = usbd_find_descriptor(sc->sc_udev, id, uaa->info.bIfaceIndex, + UDESC_CS_INTERFACE, 0xff, UDESCSUB_MBIM, 0xff); + if (md != NULL) { + v = UGETW(md->bcdMBIMVersion); + sc->sc_ver_maj = MBIM_VER_MAJOR(v); + sc->sc_ver_min = MBIM_VER_MINOR(v); + sc->sc_ctrl_len = UGETW(md->wMaxControlMessage); + /* Never trust a USB device! Could try to exploit us */ + if (sc->sc_ctrl_len < MBIM_CTRLMSG_MINLEN || + sc->sc_ctrl_len > MBIM_CTRLMSG_MAXLEN) { + DPRINTF("control message len %d out of " + "bounds [%d .. %d]\n", + sc->sc_ctrl_len, MBIM_CTRLMSG_MINLEN, + MBIM_CTRLMSG_MAXLEN); + /* continue anyway */ + } + sc->sc_maxpktlen = UGETW(md->wMaxSegmentSize); + DPRINTFN(2, "ctrl_len=%d, maxpktlen=%d, cap=0x%x\n", + sc->sc_ctrl_len, sc->sc_maxpktlen, + md->bmNetworkCapabilities); + } + if (sc->sc_ver_maj < 0) { + device_printf(dev, "error: missing MBIM descriptor\n"); + goto fail; + } + + device_printf(dev, "version %d.%d\n", sc->sc_ver_maj, + sc->sc_ver_min); + + if (usbd_lookup_id_by_uaa(umb_fccauth_devs, sizeof (umb_fccauth_devs), + uaa)) { + sc->sc_flags |= UMBFLG_FCC_AUTH_REQUIRED; + sc->sc_cid = -1; + } + + for (i = 0; i < sc->sc_udev->ifaces_max; i++) { + iface = usbd_get_iface(sc->sc_udev, i); + id = usbd_get_interface_descriptor(iface); + if (id == NULL) + break; + + if (id->bInterfaceNumber == data_ifaceno) { + sc->sc_data_iface = iface; + sc->sc_ifaces_index[0] = i; + sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex; + break; + } + } + if (sc->sc_data_iface == NULL) { + device_printf(dev, "error: no data interface found\n"); + goto fail; + } + + /* + * If this is a combined NCM/MBIM function, switch to + * alternate setting one to enable MBIM. + */ + id = usbd_get_interface_descriptor(uaa->iface); + if (id != NULL && id->bInterfaceClass == UICLASS_CDC && + id->bInterfaceSubClass == UISUBCLASS_NETWORK_CONTROL_MODEL) { + device_printf(sc->sc_dev, "combined NCM/MBIM\n"); + error = usbd_req_set_alt_interface_no(sc->sc_udev, + NULL, uaa->info.bIfaceIndex, 1); + if (error != USB_ERR_NORMAL_COMPLETION) { + device_printf(dev, "error: Could not switch to" + " alternate setting for MBIM\n"); + goto fail; + } + sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex + 1; + } + + if (usb_proc_create(&sc->sc_taskqueue, &sc->sc_mutex, + device_get_nameunit(sc->sc_dev), + USB_PRI_MED) != 0) + goto fail; + + DPRINTFN(2, "ctrl-ifno#%d: data-ifno#%d\n", sc->sc_ctrl_ifaceno, + data_ifaceno); + + usb_callout_init_mtx(&sc->sc_statechg_timer, &sc->sc_mutex, 0); + + umb_ncm_setup(sc, config); + DPRINTFN(2, "%s: rx/tx size %d/%d\n", DEVNAM(sc), + sc->sc_rx_bufsz, sc->sc_tx_bufsz); + + sc->sc_rx_buf = malloc(sc->sc_rx_bufsz, M_DEVBUF, M_WAITOK); + sc->sc_tx_buf = malloc(sc->sc_tx_bufsz, M_DEVBUF, M_WAITOK); + + for (i = 0; i != 32; i++) { + error = usbd_set_alt_interface_index(sc->sc_udev, + sc->sc_ifaces_index[0], i); + if (error) + break; + + error = usbd_transfer_setup(sc->sc_udev, sc->sc_ifaces_index, + sc->sc_xfer, config, UMB_N_TRANSFER, + sc, &sc->sc_mutex); + if (error == USB_ERR_NORMAL_COMPLETION) + break; + } + if (error || (i == 32)) { + device_printf(sc->sc_dev, "error: failed to setup xfers\n"); + goto fail; + } + + sc->sc_resp_buf = malloc(sc->sc_ctrl_len, M_DEVBUF, M_WAITOK); + sc->sc_ctrl_msg = malloc(sc->sc_ctrl_len, M_DEVBUF, M_WAITOK); + + sc->sc_info.regstate = MBIM_REGSTATE_UNKNOWN; + sc->sc_info.pin_attempts_left = UMB_VALUE_UNKNOWN; + sc->sc_info.rssi = UMB_VALUE_UNKNOWN; + sc->sc_info.ber = UMB_VALUE_UNKNOWN; + + /* defer attaching the interface */ + mtx_lock(&sc->sc_mutex); + umb_add_task(sc, umb_attach_task, + &sc->sc_proc_attach_task[0].hdr, + &sc->sc_proc_attach_task[1].hdr, 0); + mtx_unlock(&sc->sc_mutex); + + return (0); + +fail: + umb_detach(sc->sc_dev); + return (ENXIO); +} + +static void +umb_attach_task(struct usb_proc_msg *msg) +{ + struct umb_task *task = (struct umb_task *)msg; + struct umb_softc *sc = task->sc; + struct ifnet *ifp; + + mtx_unlock(&sc->sc_mutex); + + CURVNET_SET_QUIET(vnet0); + + /* initialize the interface */ + sc->sc_if = ifp = if_alloc(IFT_MBIM); + if_initname(ifp, "umb", device_get_unit(sc->sc_dev)); + + if_setsoftc(ifp, sc); + if_setflags(ifp, IFF_SIMPLEX | IFF_MULTICAST | IFF_POINTOPOINT); + if_setioctlfn(ifp, umb_ioctl); +#ifdef DEV_NETMAP + if_setinputfn(ifp, umb_input); +#endif + if_setoutputfn(ifp, umb_output); + if_setstartfn(ifp, umb_start); + if_setinitfn(ifp, umb_init); + +#if 0 + ifp->if_watchdog = umb_watchdog; +#endif + ifp->if_link_state = LINK_STATE_DOWN; + ifmedia_init(&sc->sc_im, 0, umb_mediachange, umb_mediastatus); + ifmedia_add(&sc->sc_im, IFM_NONE | IFM_AUTO, 0, NULL); + + ifp->if_addrlen = 0; + ifp->if_hdrlen = sizeof (struct ncm_header16) + + sizeof (struct ncm_pointer16); + /* XXX hard-coded atm */ + if_setmtu(ifp, MIN(2048, sc->sc_maxpktlen)); + if_setsendqlen(ifp, ifqmaxlen); + if_setsendqready(ifp); + + /* attach the interface */ + if_attach(ifp); + bpfattach(ifp, DLT_RAW, 0); + + sc->sc_attached = 1; + + CURVNET_RESTORE(); + + umb_init(sc); + mtx_lock(&sc->sc_mutex); +} + +static int +umb_detach(device_t dev) +{ + struct umb_softc *sc = device_get_softc(dev); + struct ifnet *ifp = GET_IFP(sc); + + usb_proc_drain(&sc->sc_taskqueue); + + mtx_lock(&sc->sc_mutex); + if (ifp != NULL && (ifp->if_drv_flags & IFF_DRV_RUNNING)) + umb_down(sc, 1); + umb_close(sc); + mtx_unlock(&sc->sc_mutex); + + usbd_transfer_unsetup(sc->sc_xfer, UMB_N_TRANSFER); + + free(sc->sc_tx_buf, M_DEVBUF); + free(sc->sc_rx_buf, M_DEVBUF); + + usb_callout_drain(&sc->sc_statechg_timer); + + usb_proc_free(&sc->sc_taskqueue); + + mtx_destroy(&sc->sc_mutex); + + free(sc->sc_ctrl_msg, M_DEVBUF); + free(sc->sc_resp_buf, M_DEVBUF); + + if (ifp != NULL && ifp->if_softc) { + ifmedia_removeall(&sc->sc_im); + } + if (sc->sc_attached) { + bpfdetach(ifp); + if_detach(ifp); + if_free(ifp); + sc->sc_if = NULL; + } + + return 0; +} + +static void +umb_ncm_setup(struct umb_softc *sc, struct usb_config * config) +{ + usb_device_request_t req; + struct ncm_ntb_parameters np; + usb_error_t error; + + /* Query NTB tranfers sizes */ + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = NCM_GET_NTB_PARAMETERS; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ctrl_ifaceno); + USETW(req.wLength, sizeof (np)); + mtx_lock(&sc->sc_mutex); + error = usbd_do_request(sc->sc_udev, &sc->sc_mutex, &req, &np); + mtx_unlock(&sc->sc_mutex); + if (error == USB_ERR_NORMAL_COMPLETION && + UGETW(np.wLength) == sizeof (np)) { + config[UMB_BULK_RX].bufsize = UGETDW(np.dwNtbInMaxSize); + config[UMB_BULK_TX].bufsize = UGETDW(np.dwNtbOutMaxSize); + } + sc->sc_rx_bufsz = config[UMB_BULK_RX].bufsize; + sc->sc_tx_bufsz = config[UMB_BULK_TX].bufsize; +} + +static int +umb_handle_request(device_t dev, + const void *preq, void **pptr, uint16_t *plen, + uint16_t offset, uint8_t *pstate) +{ + /* FIXME really implement */ + + return (ENXIO); +} + +static int +umb_suspend(device_t dev) +{ + device_printf(dev, "Suspending\n"); + return (0); +} + +static int +umb_resume(device_t dev) +{ + device_printf(dev, "Resuming\n"); + return (0); +} + +static int +umb_deactivate(device_t dev) +{ + struct umb_softc *sc = device_get_softc(dev); + struct ifnet *ifp = GET_IFP(sc); + + if (ifp != NULL) { + if_dead(ifp); + } + sc->sc_dying = 1; + return 0; +} + +static void +umb_close_bulkpipes(struct umb_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + umb_rxflush(sc); + umb_txflush(sc); + + usbd_transfer_stop(sc->sc_xfer[UMB_BULK_RX]); + usbd_transfer_stop(sc->sc_xfer[UMB_BULK_TX]); +} + +static int +umb_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct umb_softc *sc = ifp->if_softc; + struct in_ifaddr *ia = (struct in_ifaddr *)data; + struct ifreq *ifr = (struct ifreq *)data; + int error = 0; + struct umb_parameter mp; + + if (sc->sc_dying) + return EIO; + + switch (cmd) { + case SIOCSIFADDR: + switch (ia->ia_ifa.ifa_addr->sa_family) { + case AF_INET: + break; +#ifdef INET6 + case AF_INET6: + break; +#endif /* INET6 */ + default: + error = EAFNOSUPPORT; + break; + } + break; + case SIOCSIFFLAGS: + mtx_lock(&sc->sc_mutex); + umb_add_task(sc, umb_state_task, + &sc->sc_proc_state_task[0].hdr, + &sc->sc_proc_state_task[1].hdr, 1); + mtx_unlock(&sc->sc_mutex); + break; + case SIOCGUMBINFO: + error = copyout(&sc->sc_info, ifr->ifr_ifru.ifru_data, + sizeof (sc->sc_info)); + break; + case SIOCSUMBPARAM: + error = priv_check(curthread, PRIV_NET_SETIFPHYS); + if (error) + break; + + if ((error = copyin(ifr->ifr_ifru.ifru_data, &mp, sizeof (mp))) != 0) + break; + + if ((error = umb_setpin(sc, mp.op, mp.is_puk, mp.pin, mp.pinlen, + mp.newpin, mp.newpinlen)) != 0) + break; + + if (mp.apnlen < 0 || mp.apnlen > sizeof (sc->sc_info.apn)) { + error = EINVAL; + break; + } + sc->sc_roaming = mp.roaming ? 1 : 0; + memset(sc->sc_info.apn, 0, sizeof (sc->sc_info.apn)); + memcpy(sc->sc_info.apn, mp.apn, mp.apnlen); + sc->sc_info.apnlen = mp.apnlen; + memset(sc->sc_info.username, 0, sizeof (sc->sc_info.username)); + memcpy(sc->sc_info.username, mp.username, mp.usernamelen); + sc->sc_info.usernamelen = mp.usernamelen; + memset(sc->sc_info.password, 0, sizeof (sc->sc_info.password)); + memcpy(sc->sc_info.password, mp.password, mp.passwordlen); + sc->sc_info.passwordlen = mp.passwordlen; + sc->sc_info.preferredclasses = mp.preferredclasses; + umb_setdataclass(sc); + break; + case SIOCGUMBPARAM: + memset(&mp, 0, sizeof (mp)); + memcpy(mp.apn, sc->sc_info.apn, sc->sc_info.apnlen); + mp.apnlen = sc->sc_info.apnlen; + mp.roaming = sc->sc_roaming; + mp.preferredclasses = sc->sc_info.preferredclasses; + error = copyout(&mp, ifr->ifr_ifru.ifru_data, sizeof (mp)); + break; + case SIOCSIFMTU: + /* Does this include the NCM headers and tail? */ + if (ifr->ifr_mtu > ifp->if_mtu) { + error = EINVAL; + break; + } + ifp->if_mtu = ifr->ifr_mtu; + break; + case SIOCAIFADDR: + case SIOCSIFDSTADDR: + case SIOCADDMULTI: + case SIOCDELMULTI: + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &sc->sc_im, cmd); + break; + default: + error = EINVAL; + break; + } + return (error); +} + +static void +umb_init(void *arg) +{ + struct umb_softc *sc = arg; + + mtx_lock(&sc->sc_mutex); + umb_add_task(sc, umb_start_task, + &sc->sc_proc_start_task[0].hdr, + &sc->sc_proc_start_task[1].hdr, 0); + mtx_unlock(&sc->sc_mutex); +} + +static void +umb_input(struct ifnet *ifp, struct mbuf *m) +{ + struct mbuf *mn; + struct epoch_tracker et; + + while (m) { + mn = m->m_nextpkt; + m->m_nextpkt = NULL; + + NET_EPOCH_ENTER(et); + BPF_MTAP(ifp, m); + + CURVNET_SET_QUIET(if_getvnet(ifp)); + + netisr_dispatch(NETISR_IP, m); + m = mn; + + CURVNET_RESTORE(); + NET_EPOCH_EXIT(et); + } +} + +static int +umb_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst, + struct route *rtp) +{ + int error; + + DPRINTFN(10, "%s: enter\n", __func__); + + switch (dst->sa_family) { +#ifdef INET6 + case AF_INET6: + /* fall through */ +#endif + case AF_INET: + break; + + /* silently drop dhclient packets */ + case AF_UNSPEC: + m_freem(m); + return (0); + + /* drop other packet types */ + default: + m_freem(m); + return (EAFNOSUPPORT); + } + + /* + * Queue message on interface, and start output if interface + * not yet active. + */ + error = (ifp->if_transmit)(ifp, m); + if (error) { + if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); + return (ENOBUFS); + } + + return (0); +} + +static void +umb_start(struct ifnet *ifp) +{ + struct umb_softc *sc = ifp->if_softc; + + if (sc->sc_dying || !(ifp->if_drv_flags & IFF_DRV_RUNNING)) + return; + + mtx_lock(&sc->sc_mutex); + usbd_transfer_start(sc->sc_xfer[UMB_BULK_TX]); + mtx_unlock(&sc->sc_mutex); +} + +static void +umb_start_task(struct usb_proc_msg *msg) +{ + struct umb_task *task = (struct umb_task *)msg; + struct umb_softc *sc = task->sc; + struct ifnet *ifp = GET_IFP(sc); + + DPRINTF("%s()\n", __func__); + + mtx_assert(&sc->sc_mutex, MA_OWNED); + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + /* start interrupt transfer */ + usbd_transfer_start(sc->sc_xfer[UMB_INTR_RX]); + + umb_open(sc); +} + +#if 0 +static void +umb_watchdog(struct ifnet *ifp) +{ + struct umb_softc *sc = ifp->if_softc; + + if (sc->sc_dying) + return; + + if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); + device_printf(sc->sc_dev, "watchdog timeout\n"); + usbd_transfer_drain(sc->sc_xfer[UMB_BULK_TX]); + return; +} +#endif + +static void +umb_statechg_timeout(void *arg) +{ + struct umb_softc *sc = arg; + struct ifnet *ifp = GET_IFP(sc); + + mtx_assert(&sc->sc_mutex, MA_OWNED); + + if (sc->sc_info.regstate != MBIM_REGSTATE_ROAMING || sc->sc_roaming) + if (ifp->if_flags & IFF_DEBUG) + log(LOG_DEBUG, "%s: state change timeout\n", + DEVNAM(sc)); + + umb_add_task(sc, umb_state_task, + &sc->sc_proc_state_task[0].hdr, + &sc->sc_proc_state_task[1].hdr, 0); +} + +static int +umb_mediachange(struct ifnet * ifp) +{ + return 0; +} + +static void +umb_mediastatus(struct ifnet * ifp, struct ifmediareq * imr) +{ + switch (ifp->if_link_state) { + case LINK_STATE_UP: + imr->ifm_status = IFM_AVALID | IFM_ACTIVE; + break; + case LINK_STATE_DOWN: + imr->ifm_status = IFM_AVALID; + break; + default: + imr->ifm_status = 0; + break; + } +} + +static void +umb_add_task(struct umb_softc *sc, usb_proc_callback_t callback, + struct usb_proc_msg *t0, struct usb_proc_msg *t1, int sync) +{ + struct umb_task * task; + + mtx_assert(&sc->sc_mutex, MA_OWNED); + + if (usb_proc_is_gone(&sc->sc_taskqueue)) { + return; + } + + task = usb_proc_msignal(&sc->sc_taskqueue, t0, t1); + + task->hdr.pm_callback = callback; + task->sc = sc; + + if (sync) { + usb_proc_mwait(&sc->sc_taskqueue, t0, t1); + } +} + +static void +umb_newstate(struct umb_softc *sc, enum umb_state newstate, int flags) +{ + struct ifnet *ifp = GET_IFP(sc); + + if (newstate == sc->sc_state) + return; + if (((flags & UMB_NS_DONT_DROP) && newstate < sc->sc_state) || + ((flags & UMB_NS_DONT_RAISE) && newstate > sc->sc_state)) + return; + if (ifp->if_flags & IFF_DEBUG) + log(LOG_DEBUG, "%s: state going %s from '%s' to '%s'\n", + DEVNAM(sc), newstate > sc->sc_state ? "up" : "down", + umb_istate(sc->sc_state), umb_istate(newstate)); + sc->sc_state = newstate; + umb_add_task(sc, umb_state_task, + &sc->sc_proc_state_task[0].hdr, + &sc->sc_proc_state_task[1].hdr, 0); +} + +static void +umb_state_task(struct usb_proc_msg *msg) +{ + struct umb_task *task = (struct umb_task *)msg; + struct umb_softc *sc = task->sc; + struct ifnet *ifp = GET_IFP(sc); + struct ifreq ifr; + int state; + + DPRINTF("%s()\n", __func__); + + if (sc->sc_info.regstate == MBIM_REGSTATE_ROAMING && !sc->sc_roaming) { + /* + * Query the registration state until we're with the home + * network again. + */ + umb_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_QRY, NULL, 0); + return; + } + + if (ifp->if_flags & IFF_UP) + umb_up(sc); + else + umb_down(sc, 0); + + state = (sc->sc_state == UMB_S_UP) ? LINK_STATE_UP : LINK_STATE_DOWN; + if (ifp->if_link_state != state) { + if (ifp->if_flags & IFF_DEBUG) + log(LOG_DEBUG, "%s: link state changed from %s to %s\n", + DEVNAM(sc), + (ifp->if_link_state == LINK_STATE_UP) + ? "up" : "down", + (state == LINK_STATE_UP) ? "up" : "down"); + ifp->if_link_state = state; + if (state != LINK_STATE_UP) { + /* + * Purge any existing addresses + */ + memset(sc->sc_info.ipv4dns, 0, + sizeof (sc->sc_info.ipv4dns)); + mtx_unlock(&sc->sc_mutex); + CURVNET_SET_QUIET(if_getvnet(ifp)); + if (in_control(NULL, SIOCGIFADDR, (caddr_t)&ifr, ifp, + curthread) == 0 && + satosin(&ifr.ifr_addr)->sin_addr.s_addr != + INADDR_ANY) { + in_control(NULL, SIOCDIFADDR, (caddr_t)&ifr, + ifp, curthread); + } + CURVNET_RESTORE(); + mtx_lock(&sc->sc_mutex); + } + if_link_state_change(ifp, state); + } +} + +static void +umb_up(struct umb_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + + switch (sc->sc_state) { + case UMB_S_DOWN: + DPRINTF("init: opening ...\n"); + umb_open(sc); + break; + case UMB_S_OPEN: + if (sc->sc_flags & UMBFLG_FCC_AUTH_REQUIRED) { + if (sc->sc_cid == -1) { + DPRINTF("init: allocating CID ...\n"); + umb_allocate_cid(sc); + break; + } else + umb_newstate(sc, UMB_S_CID, UMB_NS_DONT_DROP); + } else { + DPRINTF("init: turning radio on ...\n"); + umb_radio(sc, 1); + break; + } + /*FALLTHROUGH*/ + case UMB_S_CID: + DPRINTF("init: sending FCC auth ...\n"); + umb_send_fcc_auth(sc); + break; + case UMB_S_RADIO: + DPRINTF("init: checking SIM state ...\n"); + umb_cmd(sc, MBIM_CID_SUBSCRIBER_READY_STATUS, MBIM_CMDOP_QRY, + NULL, 0); + break; + case UMB_S_SIMREADY: + DPRINTF("init: attaching ...\n"); + umb_packet_service(sc, 1); + break; + case UMB_S_ATTACHED: + sc->sc_tx_seq = 0; + DPRINTF("init: connecting ...\n"); + umb_connect(sc); + break; + case UMB_S_CONNECTED: + DPRINTF("init: getting IP config ...\n"); + umb_qry_ipconfig(sc); + break; + case UMB_S_UP: + DPRINTF("init: reached state UP\n"); + if (!(ifp->if_flags & IFF_DRV_RUNNING)) { + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + umb_rx(sc); + } + break; + } + if (sc->sc_state < UMB_S_UP) + usb_callout_reset(&sc->sc_statechg_timer, + UMB_STATE_CHANGE_TIMEOUT * hz, umb_statechg_timeout, sc); + else { + usb_callout_stop(&sc->sc_statechg_timer); + } + return; +} + +static void +umb_down(struct umb_softc *sc, int force) +{ + umb_close_bulkpipes(sc); + + switch (sc->sc_state) { + case UMB_S_UP: + case UMB_S_CONNECTED: + DPRINTF("stop: disconnecting ...\n"); + umb_disconnect(sc); + if (!force) + break; + /*FALLTHROUGH*/ + case UMB_S_ATTACHED: + DPRINTF("stop: detaching ...\n"); + umb_packet_service(sc, 0); + if (!force) + break; + /*FALLTHROUGH*/ + case UMB_S_SIMREADY: + case UMB_S_RADIO: + DPRINTF("stop: turning radio off ...\n"); + umb_radio(sc, 0); + if (!force) + break; + /*FALLTHROUGH*/ + case UMB_S_CID: + case UMB_S_OPEN: + case UMB_S_DOWN: + /* Do not close the device */ + DPRINTF("stop: reached state DOWN\n"); + break; + } + if (force) + sc->sc_state = UMB_S_OPEN; + + if (sc->sc_state > UMB_S_OPEN) + usb_callout_reset(&sc->sc_statechg_timer, + UMB_STATE_CHANGE_TIMEOUT * hz, umb_statechg_timeout, sc); + else + usb_callout_stop(&sc->sc_statechg_timer); +} + +static void +umb_get_response_task(struct usb_proc_msg *msg) +{ + struct umb_task *task = (struct umb_task *)msg; + struct umb_softc *sc = task->sc; + int len; + + DPRINTF("%s()\n", __func__); + /* + * Function is required to send on RESPONSE_AVAILABLE notification for + * each encapsulated response that is to be processed by the host. + * But of course, we can receive multiple notifications before the + * response task is run. + */ + while (sc->sc_nresp > 0) { + --sc->sc_nresp; + len = sc->sc_ctrl_len; + if (umb_get_encap_response(sc, sc->sc_resp_buf, &len)) + umb_decode_response(sc, sc->sc_resp_buf, len); + } +} + +static void +umb_decode_response(struct umb_softc *sc, void *response, int len) +{ + struct mbim_msghdr *hdr = response; + struct mbim_fragmented_msg_hdr *fraghdr; + uint32_t type; + + DPRINTFN(3, "got response: len %d\n", len); + DDUMPN(4, response, len); + + if (len < sizeof (*hdr) || le32toh(hdr->len) != len) { + /* + * We should probably cancel a transaction, but since the + * message is too short, we cannot decode the transaction + * id (tid) and hence don't know, whom to cancel. Must wait + * for the timeout. + */ + DPRINTF("received short response (len %d)\n", + len); + return; + } + + /* + * XXX FIXME: if message is fragmented, store it until last frag + * is received and then re-assemble all fragments. + */ + type = le32toh(hdr->type); + switch (type) { + case MBIM_INDICATE_STATUS_MSG: + case MBIM_COMMAND_DONE: + fraghdr = response; + if (le32toh(fraghdr->frag.nfrag) != 1) { + DPRINTF("discarding fragmented messages\n"); + return; + } + break; + default: + break; + } + + DPRINTF("<- rcv %s (tid %u)\n", umb_request2str(type), + le32toh(hdr->tid)); + switch (type) { + case MBIM_FUNCTION_ERROR_MSG: + case MBIM_HOST_ERROR_MSG: + { + struct mbim_f2h_hosterr *e; + int err; + + if (len >= sizeof (*e)) { + e = response; + err = le32toh(e->err); + + DPRINTF("%s message, error %s (tid %u)\n", + umb_request2str(type), + umb_error2str(err), le32toh(hdr->tid)); + if (err == MBIM_ERROR_NOT_OPENED) + umb_newstate(sc, UMB_S_DOWN, 0); + } + break; + } + case MBIM_INDICATE_STATUS_MSG: + umb_handle_indicate_status_msg(sc, response, len); + break; + case MBIM_OPEN_DONE: + umb_handle_opendone_msg(sc, response, len); + break; + case MBIM_CLOSE_DONE: + umb_handle_closedone_msg(sc, response, len); + break; + case MBIM_COMMAND_DONE: + umb_command_done(sc, response, len); + break; + default: + DPRINTF("discard message %s\n", + umb_request2str(type)); + break; + } +} + +static void +umb_handle_indicate_status_msg(struct umb_softc *sc, void *data, int len) +{ + struct mbim_f2h_indicate_status *m = data; + uint32_t infolen; + uint32_t cid; + + if (len < sizeof (*m)) { + DPRINTF("discard short %s message\n", + umb_request2str(le32toh(m->hdr.type))); + return; + } + if (memcmp(m->devid, umb_uuid_basic_connect, sizeof (m->devid))) { + DPRINTF("discard %s message for other UUID '%s'\n", + umb_request2str(le32toh(m->hdr.type)), + umb_uuid2str(m->devid)); + return; + } + infolen = le32toh(m->infolen); + if (len < sizeof (*m) + infolen) { + DPRINTF("discard truncated %s message (want %d, got %d)\n", + umb_request2str(le32toh(m->hdr.type)), + (int)sizeof (*m) + infolen, len); + return; + } + + cid = le32toh(m->cid); + DPRINTF("indicate %s status\n", umb_cid2str(cid)); + umb_decode_cid(sc, cid, m->info, infolen); +} + +static void +umb_handle_opendone_msg(struct umb_softc *sc, void *data, int len) +{ + struct mbim_f2h_openclosedone *resp = data; + struct ifnet *ifp = GET_IFP(sc); + uint32_t status; + + status = le32toh(resp->status); + if (status == MBIM_STATUS_SUCCESS) { + if (sc->sc_maxsessions == 0) { + umb_cmd(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_QRY, NULL, + 0); + umb_cmd(sc, MBIM_CID_PIN, MBIM_CMDOP_QRY, NULL, 0); + umb_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_QRY, + NULL, 0); + } + umb_newstate(sc, UMB_S_OPEN, UMB_NS_DONT_DROP); + } else if (ifp->if_flags & IFF_DEBUG) + log(LOG_ERR, "%s: open error: %s\n", DEVNAM(sc), + umb_status2str(status)); + return; +} + +static void +umb_handle_closedone_msg(struct umb_softc *sc, void *data, int len) +{ + struct mbim_f2h_openclosedone *resp = data; + uint32_t status; + + status = le32toh(resp->status); + if (status == MBIM_STATUS_SUCCESS) + umb_newstate(sc, UMB_S_DOWN, 0); + else + DPRINTF("close error: %s\n", + umb_status2str(status)); + return; +} + +static inline void +umb_getinfobuf(char *in, int inlen, uint32_t offs, uint32_t sz, + void *out, size_t outlen) +{ + offs = le32toh(offs); + sz = le32toh(sz); + if (inlen >= offs + sz) { + memset(out, 0, outlen); + memcpy(out, in + offs, MIN(sz, outlen)); + } +} + +static inline int +umb_padding(void *data, int len, size_t sz) +{ + char *p = data; + int np = 0; + + while (len < sz && (len % 4) != 0) { + *p++ = '\0'; + len++; + np++; + } + return np; +} + +static inline int +umb_addstr(void *buf, size_t bufsz, int *offs, void *str, int slen, + uint32_t *offsmember, uint32_t *sizemember) +{ + if (*offs + slen > bufsz) + return 0; + + *sizemember = htole32((uint32_t)slen); + if (slen && str) { + *offsmember = htole32((uint32_t)*offs); + memcpy((char *)buf + *offs, str, slen); + *offs += slen; + *offs += umb_padding(buf, *offs, bufsz); + } else + *offsmember = htole32(0); + return 1; +} + +static void +umb_in_len2mask(struct in_addr *mask, int len) +{ + int i; + u_char *p; + + p = (u_char *)mask; + memset(mask, 0, sizeof (*mask)); + for (i = 0; i < len / 8; i++) + p[i] = 0xff; + if (len % 8) + p[i] = (0xff00 >> (len % 8)) & 0xff; +} + +static int +umb_decode_register_state(struct umb_softc *sc, void *data, int len) +{ + struct mbim_cid_registration_state_info *rs = data; + struct ifnet *ifp = GET_IFP(sc); + + if (len < sizeof (*rs)) + return 0; + sc->sc_info.nwerror = le32toh(rs->nwerror); + sc->sc_info.regstate = le32toh(rs->regstate); + sc->sc_info.regmode = le32toh(rs->regmode); + sc->sc_info.cellclass = le32toh(rs->curcellclass); + + /* XXX should we remember the provider_id? */ + umb_getinfobuf(data, len, rs->provname_offs, rs->provname_size, + sc->sc_info.provider, sizeof (sc->sc_info.provider)); + umb_getinfobuf(data, len, rs->roamingtxt_offs, rs->roamingtxt_size, + sc->sc_info.roamingtxt, sizeof (sc->sc_info.roamingtxt)); + + DPRINTFN(2, "%s, availclass 0x%x, class 0x%x, regmode %d\n", + umb_regstate(sc->sc_info.regstate), + le32toh(rs->availclasses), sc->sc_info.cellclass, + sc->sc_info.regmode); + + if (sc->sc_info.regstate == MBIM_REGSTATE_ROAMING && + !sc->sc_roaming && + sc->sc_info.activation == MBIM_ACTIVATION_STATE_ACTIVATED) { + if (ifp->if_flags & IFF_DEBUG) + log(LOG_INFO, + "%s: disconnecting from roaming network\n", + DEVNAM(sc)); + umb_disconnect(sc); + } + return 1; +} + +static int +umb_decode_devices_caps(struct umb_softc *sc, void *data, int len) +{ + struct mbim_cid_device_caps *dc = data; + + if (len < sizeof (*dc)) + return 0; + sc->sc_maxsessions = le32toh(dc->max_sessions); + sc->sc_info.supportedclasses = le32toh(dc->dataclass); + umb_getinfobuf(data, len, dc->devid_offs, dc->devid_size, + sc->sc_info.devid, sizeof (sc->sc_info.devid)); + umb_getinfobuf(data, len, dc->fwinfo_offs, dc->fwinfo_size, + sc->sc_info.fwinfo, sizeof (sc->sc_info.fwinfo)); + umb_getinfobuf(data, len, dc->hwinfo_offs, dc->hwinfo_size, + sc->sc_info.hwinfo, sizeof (sc->sc_info.hwinfo)); + DPRINTFN(2, "max sessions %d, supported classes 0x%x\n", + sc->sc_maxsessions, sc->sc_info.supportedclasses); + return 1; +} + +static int +umb_decode_subscriber_status(struct umb_softc *sc, void *data, int len) +{ + struct mbim_cid_subscriber_ready_info *si = data; + struct ifnet *ifp = GET_IFP(sc); + int npn; + + if (len < sizeof (*si)) + return 0; + sc->sc_info.sim_state = le32toh(si->ready); + + umb_getinfobuf(data, len, si->sid_offs, si->sid_size, + sc->sc_info.sid, sizeof (sc->sc_info.sid)); + umb_getinfobuf(data, len, si->icc_offs, si->icc_size, + sc->sc_info.iccid, sizeof (sc->sc_info.iccid)); + + npn = le32toh(si->no_pn); + if (npn > 0) + umb_getinfobuf(data, len, si->pn[0].offs, si->pn[0].size, + sc->sc_info.pn, sizeof (sc->sc_info.pn)); + else + memset(sc->sc_info.pn, 0, sizeof (sc->sc_info.pn)); + + if (sc->sc_info.sim_state == MBIM_SIMSTATE_LOCKED) + sc->sc_info.pin_state = UMB_PIN_REQUIRED; + if (ifp->if_flags & IFF_DEBUG) + log(LOG_INFO, "%s: SIM %s\n", DEVNAM(sc), + umb_simstate(sc->sc_info.sim_state)); + if (sc->sc_info.sim_state == MBIM_SIMSTATE_INITIALIZED) + umb_newstate(sc, UMB_S_SIMREADY, UMB_NS_DONT_DROP); + return 1; +} + +static int +umb_decode_radio_state(struct umb_softc *sc, void *data, int len) +{ + struct mbim_cid_radio_state_info *rs = data; + struct ifnet *ifp = GET_IFP(sc); + + if (len < sizeof (*rs)) + return 0; + + sc->sc_info.hw_radio_on = + (le32toh(rs->hw_state) == MBIM_RADIO_STATE_ON) ? 1 : 0; + sc->sc_info.sw_radio_on = + (le32toh(rs->sw_state) == MBIM_RADIO_STATE_ON) ? 1 : 0; + if (!sc->sc_info.hw_radio_on) { + device_printf(sc->sc_dev, "radio is disabled by hardware switch\n"); + /* + * XXX do we need a time to poll the state of the rfkill switch + * or will the device send an unsolicited notification + * in case the state changes? + */ + umb_newstate(sc, UMB_S_OPEN, 0); + } else if (!sc->sc_info.sw_radio_on) { + if (ifp->if_flags & IFF_DEBUG) + log(LOG_INFO, "%s: radio is off\n", DEVNAM(sc)); + umb_newstate(sc, UMB_S_OPEN, 0); + } else + umb_newstate(sc, UMB_S_RADIO, UMB_NS_DONT_DROP); + return 1; +} + +static int +umb_decode_pin(struct umb_softc *sc, void *data, int len) +{ + struct mbim_cid_pin_info *pi = data; + struct ifnet *ifp = GET_IFP(sc); + uint32_t attempts_left; + + if (len < sizeof (*pi)) + return 0; + + attempts_left = le32toh(pi->remaining_attempts); + if (attempts_left != 0xffffffff) + sc->sc_info.pin_attempts_left = attempts_left; + + switch (le32toh(pi->state)) { + case MBIM_PIN_STATE_UNLOCKED: + sc->sc_info.pin_state = UMB_PIN_UNLOCKED; + break; + case MBIM_PIN_STATE_LOCKED: + switch (le32toh(pi->type)) { + case MBIM_PIN_TYPE_PIN1: + sc->sc_info.pin_state = UMB_PIN_REQUIRED; + break; + case MBIM_PIN_TYPE_PUK1: + sc->sc_info.pin_state = UMB_PUK_REQUIRED; + break; + case MBIM_PIN_TYPE_PIN2: + case MBIM_PIN_TYPE_PUK2: + /* Assume that PIN1 was accepted */ + sc->sc_info.pin_state = UMB_PIN_UNLOCKED; + break; + } + break; + } + if (ifp->if_flags & IFF_DEBUG) + log(LOG_INFO, "%s: %s state %s (%d attempts left)\n", + DEVNAM(sc), umb_pin_type(le32toh(pi->type)), + (le32toh(pi->state) == MBIM_PIN_STATE_UNLOCKED) ? + "unlocked" : "locked", + le32toh(pi->remaining_attempts)); + + /* + * In case the PIN was set after IFF_UP, retrigger the state machine + */ + umb_add_task(sc, umb_state_task, + &sc->sc_proc_state_task[0].hdr, + &sc->sc_proc_state_task[1].hdr, 0); + return 1; +} + +static int +umb_decode_packet_service(struct umb_softc *sc, void *data, int len) +{ + struct mbim_cid_packet_service_info *psi = data; + int state, highestclass; + uint64_t up_speed, down_speed; + struct ifnet *ifp = GET_IFP(sc); + + if (len < sizeof (*psi)) + return 0; + + sc->sc_info.nwerror = le32toh(psi->nwerror); + state = le32toh(psi->state); + highestclass = le32toh(psi->highest_dataclass); + up_speed = le64toh(psi->uplink_speed); + down_speed = le64toh(psi->downlink_speed); + if (sc->sc_info.packetstate != state || + sc->sc_info.uplink_speed != up_speed || + sc->sc_info.downlink_speed != down_speed) { + if (ifp->if_flags & IFF_DEBUG) { + log(LOG_INFO, "%s: packet service ", DEVNAM(sc)); + if (sc->sc_info.packetstate != state) + log(LOG_INFO, "changed from %s to ", + umb_packet_state(sc->sc_info.packetstate)); + log(LOG_INFO, "%s, class %s, speed: %" PRIu64 " up / %" PRIu64 " down\n", + umb_packet_state(state), + umb_dataclass(highestclass), up_speed, down_speed); + } + } + sc->sc_info.packetstate = state; + sc->sc_info.highestclass = highestclass; + sc->sc_info.uplink_speed = up_speed; + sc->sc_info.downlink_speed = down_speed; + + if (sc->sc_info.regmode == MBIM_REGMODE_AUTOMATIC) { + /* + * For devices using automatic registration mode, just proceed, + * once registration has completed. + */ + if (ifp->if_flags & IFF_UP) { + switch (sc->sc_info.regstate) { + case MBIM_REGSTATE_HOME: + case MBIM_REGSTATE_ROAMING: + case MBIM_REGSTATE_PARTNER: + umb_newstate(sc, UMB_S_ATTACHED, + UMB_NS_DONT_DROP); + break; + default: + break; + } + } else + umb_newstate(sc, UMB_S_SIMREADY, UMB_NS_DONT_RAISE); + } else switch (sc->sc_info.packetstate) { + case MBIM_PKTSERVICE_STATE_ATTACHED: + umb_newstate(sc, UMB_S_ATTACHED, UMB_NS_DONT_DROP); + break; + case MBIM_PKTSERVICE_STATE_DETACHED: + umb_newstate(sc, UMB_S_SIMREADY, UMB_NS_DONT_RAISE); + break; + } + return 1; +} + +static int +umb_decode_signal_state(struct umb_softc *sc, void *data, int len) +{ + struct mbim_cid_signal_state *ss = data; + struct ifnet *ifp = GET_IFP(sc); + int rssi; + + if (len < sizeof (*ss)) + return 0; + + if (le32toh(ss->rssi) == 99) + rssi = UMB_VALUE_UNKNOWN; + else { + rssi = -113 + 2 * le32toh(ss->rssi); + if ((ifp->if_flags & IFF_DEBUG) && sc->sc_info.rssi != rssi && + sc->sc_state >= UMB_S_CONNECTED) + log(LOG_INFO, "%s: rssi %d dBm\n", DEVNAM(sc), rssi); + } + sc->sc_info.rssi = rssi; + sc->sc_info.ber = le32toh(ss->err_rate); + if (sc->sc_info.ber == -99) + sc->sc_info.ber = UMB_VALUE_UNKNOWN; + return 1; +} + +static int +umb_decode_connect_info(struct umb_softc *sc, void *data, int len) +{ + struct mbim_cid_connect_info *ci = data; + struct ifnet *ifp = GET_IFP(sc); + int act; + + if (len < sizeof (*ci)) + return 0; + + if (le32toh(ci->sessionid) != umb_session_id) { + DPRINTF("discard connection info for session %u\n", + le32toh(ci->sessionid)); + return 1; + } + if (memcmp(ci->context, umb_uuid_context_internet, + sizeof (ci->context))) { + DPRINTF("discard connection info for other context\n"); + return 1; + } + act = le32toh(ci->activation); + if (sc->sc_info.activation != act) { + if (ifp->if_flags & IFF_DEBUG) + log(LOG_INFO, "%s: connection %s\n", DEVNAM(sc), + umb_activation(act)); + if ((ifp->if_flags & IFF_DEBUG) && + le32toh(ci->iptype) != MBIM_CONTEXT_IPTYPE_DEFAULT && + le32toh(ci->iptype) != MBIM_CONTEXT_IPTYPE_IPV4) + log(LOG_DEBUG, "%s: got iptype %d connection\n", + DEVNAM(sc), le32toh(ci->iptype)); + + sc->sc_info.activation = act; + sc->sc_info.nwerror = le32toh(ci->nwerror); + + if (sc->sc_info.activation == MBIM_ACTIVATION_STATE_ACTIVATED) + umb_newstate(sc, UMB_S_CONNECTED, UMB_NS_DONT_DROP); + else if (sc->sc_info.activation == + MBIM_ACTIVATION_STATE_DEACTIVATED) + umb_newstate(sc, UMB_S_ATTACHED, 0); + /* else: other states are purely transitional */ + } + return 1; +} + +static int +umb_add_inet_config(struct umb_softc *sc, struct in_addr ip, u_int prefixlen, + struct in_addr gw) +{ + struct ifnet *ifp = GET_IFP(sc); + struct in_aliasreq ifra; + struct sockaddr_in *sin; + int rv; + + memset(&ifra, 0, sizeof (ifra)); + sin = (struct sockaddr_in *)&ifra.ifra_addr; + sin->sin_family = AF_INET; + sin->sin_len = sizeof (*sin); + sin->sin_addr = ip; + + sin = (struct sockaddr_in *)&ifra.ifra_dstaddr; + sin->sin_family = AF_INET; + sin->sin_len = sizeof (*sin); + sin->sin_addr = gw; + + sin = (struct sockaddr_in *)&ifra.ifra_mask; + sin->sin_family = AF_INET; + sin->sin_len = sizeof (*sin); + umb_in_len2mask(&sin->sin_addr, prefixlen); + + mtx_unlock(&sc->sc_mutex); + CURVNET_SET_QUIET(if_getvnet(ifp)); + rv = in_control(NULL, SIOCAIFADDR, (caddr_t)&ifra, ifp, curthread); + CURVNET_RESTORE(); + mtx_lock(&sc->sc_mutex); + if (rv != 0) { + device_printf(sc->sc_dev, "unable to set IPv4 address, error %d\n", + rv); + return rv; + } + + if (ifp->if_flags & IFF_DEBUG) + log(LOG_INFO, "%s: IPv4 addr %s, mask %s, " + "gateway %s\n", DEVNAM(sc), + umb_ntop(sintosa(&ifra.ifra_addr)), + umb_ntop(sintosa(&ifra.ifra_mask)), + umb_ntop(sintosa(&ifra.ifra_dstaddr))); + + return 0; +} + +static int +umb_decode_ip_configuration(struct umb_softc *sc, void *data, int len) +{ + struct mbim_cid_ip_configuration_info *ic = data; + struct ifnet *ifp = GET_IFP(sc); + uint32_t avail_v4; + uint32_t val; + int n, i; + int off; + struct mbim_cid_ipv4_element ipv4elem; + struct in_addr addr, gw; + int state = -1; + int rv; + + if (len < sizeof (*ic)) + return 0; + if (le32toh(ic->sessionid) != umb_session_id) { + DPRINTF("ignore IP configuration for session id %d\n", + le32toh(ic->sessionid)); + return 0; + } + + /* + * IPv4 configuration + */ + avail_v4 = le32toh(ic->ipv4_available); + if ((avail_v4 & (MBIM_IPCONF_HAS_ADDRINFO | MBIM_IPCONF_HAS_GWINFO)) == + (MBIM_IPCONF_HAS_ADDRINFO | MBIM_IPCONF_HAS_GWINFO)) { + n = le32toh(ic->ipv4_naddr); + off = le32toh(ic->ipv4_addroffs); + + if (n == 0 || off + sizeof (ipv4elem) > len) + goto tryv6; + if (n != 1 && ifp->if_flags & IFF_DEBUG) + log(LOG_INFO, "%s: more than one IPv4 addr: %d\n", + DEVNAM(sc), n); + + /* Only pick the first one */ + memcpy(&ipv4elem, (char *)data + off, sizeof (ipv4elem)); + ipv4elem.prefixlen = le32toh(ipv4elem.prefixlen); + addr.s_addr = ipv4elem.addr; + + off = le32toh(ic->ipv4_gwoffs); + if (off + sizeof (gw) > len) + goto done; + memcpy(&gw, (char *)data + off, sizeof (gw)); + + rv = umb_add_inet_config(sc, addr, ipv4elem.prefixlen, gw); + if (rv == 0) + state = UMB_S_UP; + } + + memset(sc->sc_info.ipv4dns, 0, sizeof (sc->sc_info.ipv4dns)); + if (avail_v4 & MBIM_IPCONF_HAS_DNSINFO) { + n = le32toh(ic->ipv4_ndnssrv); + off = le32toh(ic->ipv4_dnssrvoffs); + i = 0; + while (n-- > 0) { + if (off + sizeof (addr) > len) + break; + memcpy(&addr, (char *)data + off, sizeof(addr)); + if (i < UMB_MAX_DNSSRV) + sc->sc_info.ipv4dns[i++] = addr; + off += sizeof(addr); + } + } + + if ((avail_v4 & MBIM_IPCONF_HAS_MTUINFO)) { + val = le32toh(ic->ipv4_mtu); + if (ifp->if_mtu != val && val <= sc->sc_maxpktlen) { + ifp->if_mtu = val; + if (ifp->if_mtu > val) + ifp->if_mtu = val; + if (ifp->if_flags & IFF_DEBUG) + log(LOG_INFO, "%s: MTU %d\n", DEVNAM(sc), val); + } + } + + avail_v4 = le32toh(ic->ipv6_available); + if ((ifp->if_flags & IFF_DEBUG) && avail_v4 & MBIM_IPCONF_HAS_ADDRINFO) { + /* XXX FIXME: IPv6 configuration missing */ + log(LOG_INFO, "%s: ignoring IPv6 configuration\n", DEVNAM(sc)); + } + if (state != -1) + umb_newstate(sc, state, 0); + +tryv6: +done: + return 1; +} + +static void +umb_rx(struct umb_softc *sc) +{ + mtx_assert(&sc->sc_mutex, MA_OWNED); + + usbd_transfer_start(sc->sc_xfer[UMB_BULK_RX]); +} + +static void +umb_rxeof(struct usb_xfer *xfer, usb_error_t status) +{ + struct umb_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = GET_IFP(sc); + int actlen; + int aframes; + int i; + + DPRINTF("%s(%u): state=%u\n", __func__, status, USB_GET_STATE(xfer)); + + mtx_assert(&sc->sc_mutex, MA_OWNED); + + usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF("received %u bytes in %u frames\n", actlen, aframes); + + if (actlen == 0) { + if (sc->sc_rx_nerr >= 4) + /* throttle transfers */ + usbd_xfer_set_interval(xfer, 500); + else + sc->sc_rx_nerr++; + } + else { + /* disable throttling */ + usbd_xfer_set_interval(xfer, 0); + sc->sc_rx_nerr = 0; + } + + for(i = 0; i < aframes; i++) { + umb_decap(sc, xfer, i); + } + + /* fall through */ + case USB_ST_SETUP: + usbd_xfer_set_frame_data(xfer, 0, sc->sc_rx_buf, + sc->sc_rx_bufsz); + usbd_xfer_set_frames(xfer, 1); + usbd_transfer_submit(xfer); + + umb_rxflush(sc); + break; + default: + DPRINTF("rx error: %s\n", usbd_errstr(status)); + + /* disable throttling */ + usbd_xfer_set_interval(xfer, 0); + + if (status != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + usbd_xfer_set_frames(xfer, 0); + usbd_transfer_submit(xfer); + if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); + } + else if (++sc->sc_rx_nerr > 100) { + log(LOG_ERR, "%s: too many rx errors, disabling\n", + DEVNAM(sc)); + umb_deactivate(sc->sc_dev); + } + break; + } +} + +static void +umb_rxflush(struct umb_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + struct mbuf *m; + + mtx_assert(&sc->sc_mutex, MA_OWNED); + + for (;;) { + _IF_DEQUEUE(&sc->sc_rx_queue, m); + if (m == NULL) + break; + + /* + * The USB xfer has been resubmitted so its safe to unlock now. + */ + mtx_unlock(&sc->sc_mutex); + CURVNET_SET_QUIET(if_getvnet(ifp)); + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ifp->if_input(ifp, m); + else + m_freem(m); + CURVNET_RESTORE(); + mtx_lock(&sc->sc_mutex); + } +} + +static int +umb_encap(struct umb_softc *sc, struct mbuf *m, struct usb_xfer *xfer) +{ + struct ncm_header16 *hdr; + struct ncm_pointer16 *ptr; + int len; + + KASSERT(sc->sc_tx_m == NULL, + ("Assertion failed in umb_encap()")); + + /* All size constraints have been validated by the caller! */ + hdr = (struct ncm_header16 *)sc->sc_tx_buf; + ptr = (struct ncm_pointer16 *)(hdr + 1); + + USETDW(hdr->dwSignature, NCM_HDR16_SIG); + USETW(hdr->wHeaderLength, sizeof (*hdr)); + USETW(hdr->wSequence, sc->sc_tx_seq); + sc->sc_tx_seq++; + USETW(hdr->wNdpIndex, sizeof (*hdr)); + + len = m->m_pkthdr.len; + USETDW(ptr->dwSignature, MBIM_NCM_NTH16_SIG(umb_session_id)); + USETW(ptr->wLength, sizeof (*ptr)); + USETW(ptr->wNextNdpIndex, 0); + USETW(ptr->dgram[0].wDatagramIndex, MBIM_HDR16_LEN); + USETW(ptr->dgram[0].wDatagramLen, len); + USETW(ptr->dgram[1].wDatagramIndex, 0); + USETW(ptr->dgram[1].wDatagramLen, 0); + + KASSERT(len + MBIM_HDR16_LEN <= sc->sc_tx_bufsz, + ("Assertion failed in umb_encap()")); + m_copydata(m, 0, len, (char *)(ptr + 1)); + sc->sc_tx_m = m; + len += MBIM_HDR16_LEN; + USETW(hdr->wBlockLength, len); + + usbd_xfer_set_frame_data(xfer, 0, sc->sc_tx_buf, len); + usbd_xfer_set_interval(xfer, 0); + usbd_xfer_set_frames(xfer, 1); + + DPRINTFN(3, "%s: encap %d bytes\n", DEVNAM(sc), len); + DDUMPN(5, sc->sc_tx_buf, len); + return 0; +} + +static void +umb_txeof(struct usb_xfer *xfer, usb_error_t status) +{ + struct umb_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = GET_IFP(sc); + struct mbuf *m; + + DPRINTF("%s(%u) state=%u\n", __func__, status, USB_GET_STATE(xfer)); + + mtx_assert(&sc->sc_mutex, MA_OWNED); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); + + umb_txflush(sc); + + /* fall through */ + case USB_ST_SETUP: +tr_setup: + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + break; + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + if (umb_encap(sc, m, xfer)) { + if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); + umb_txflush(sc); + break; + } + + BPF_MTAP(ifp, m); + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + usbd_transfer_submit(xfer); + + break; + + default: + umb_txflush(sc); + + /* count output errors */ + if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); + DPRINTF("tx error: %s\n", + usbd_errstr(status)); + + if (status != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +static void +umb_txflush(struct umb_softc *sc) +{ + mtx_assert(&sc->sc_mutex, MA_OWNED); + + if (sc->sc_tx_m != NULL) { + m_freem(sc->sc_tx_m); + sc->sc_tx_m = NULL; + } +} + +static void +umb_decap(struct umb_softc *sc, struct usb_xfer *xfer, int frame) +{ + struct ifnet *ifp = GET_IFP(sc); + char *buf; + int len; + char *dp; + struct ncm_header16 *hdr16; + struct ncm_header32 *hdr32; + struct ncm_pointer16 *ptr16; + struct ncm_pointer16_dgram *dgram16; + struct ncm_pointer32_dgram *dgram32; + uint32_t hsig, psig; + int hlen, blen; + int ptrlen, ptroff, dgentryoff; + uint32_t doff, dlen; + struct mbuf *m; + + usbd_xfer_frame_data(xfer, frame, (void **)&buf, &len); + DPRINTFN(4, "recv %d bytes\n", len); + DDUMPN(5, buf, len); + if (len < sizeof (*hdr16)) + goto toosmall; + + hdr16 = (struct ncm_header16 *)buf; + hsig = UGETDW(hdr16->dwSignature); + hlen = UGETW(hdr16->wHeaderLength); + if (len < hlen) + goto toosmall; + if (len > sc->sc_rx_bufsz) { + DPRINTF("packet too large (%d)\n", len); + goto fail; + } + switch (hsig) { + case NCM_HDR16_SIG: + blen = UGETW(hdr16->wBlockLength); + ptroff = UGETW(hdr16->wNdpIndex); + if (hlen != sizeof (*hdr16)) { + DPRINTF("%s: bad header len %d for NTH16 (exp %zu)\n", + DEVNAM(sc), hlen, sizeof (*hdr16)); + goto fail; + } + break; + case NCM_HDR32_SIG: + hdr32 = (struct ncm_header32 *)hdr16; + blen = UGETDW(hdr32->dwBlockLength); + ptroff = UGETDW(hdr32->dwNdpIndex); + if (hlen != sizeof (*hdr32)) { + DPRINTF("%s: bad header len %d for NTH32 (exp %zu)\n", + DEVNAM(sc), hlen, sizeof (*hdr32)); + goto fail; + } + break; + default: + DPRINTF("%s: unsupported NCM header signature (0x%08x)\n", + DEVNAM(sc), hsig); + goto fail; + } + if (len < blen) { + DPRINTF("%s: bad NTB len (%d) for %d bytes of data\n", + DEVNAM(sc), blen, len); + goto fail; + } + + ptr16 = (struct ncm_pointer16 *)(buf + ptroff); + psig = UGETDW(ptr16->dwSignature); + ptrlen = UGETW(ptr16->wLength); + if (len < ptrlen + ptroff) + goto toosmall; + if (!MBIM_NCM_NTH16_ISISG(psig) && !MBIM_NCM_NTH32_ISISG(psig)) { + DPRINTF("%s: unsupported NCM pointer signature (0x%08x)\n", + DEVNAM(sc), psig); + goto fail; + } + + switch (hsig) { + case NCM_HDR16_SIG: + dgentryoff = offsetof(struct ncm_pointer16, dgram); + break; + case NCM_HDR32_SIG: + dgentryoff = offsetof(struct ncm_pointer32, dgram); + break; + default: + goto fail; + } + + while (dgentryoff < ptrlen) { + switch (hsig) { + case NCM_HDR16_SIG: + if (ptroff + dgentryoff < sizeof (*dgram16)) + goto done; + dgram16 = (struct ncm_pointer16_dgram *) + (buf + ptroff + dgentryoff); + dgentryoff += sizeof (*dgram16); + dlen = UGETW(dgram16->wDatagramLen); + doff = UGETW(dgram16->wDatagramIndex); + break; + case NCM_HDR32_SIG: + if (ptroff + dgentryoff < sizeof (*dgram32)) + goto done; + dgram32 = (struct ncm_pointer32_dgram *) + (buf + ptroff + dgentryoff); + dgentryoff += sizeof (*dgram32); + dlen = UGETDW(dgram32->dwDatagramLen); + doff = UGETDW(dgram32->dwDatagramIndex); + break; + default: + if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); + goto done; + } + + /* Terminating zero entry */ + if (dlen == 0 || doff == 0) + break; + if (len < dlen + doff) { + /* Skip giant datagram but continue processing */ + DPRINTF("%s: datagram too large (%d @ off %d)\n", + DEVNAM(sc), dlen, doff); + continue; + } + + dp = buf + doff; + DPRINTFN(3, "%s: decap %d bytes\n", DEVNAM(sc), dlen); + m = m_devget(dp, dlen, 0, ifp, NULL); + if (m == NULL) { + if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1); + continue; + } + + /* enqueue for later when the lock can be released */ + _IF_ENQUEUE(&sc->sc_rx_queue, m); + + if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1); + + } +done: + sc->sc_rx_nerr = 0; + return; +toosmall: + DPRINTF("%s: packet too small (%d)\n", DEVNAM(sc), len); +fail: + if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); +} + +static usb_error_t +umb_send_encap_command(struct umb_softc *sc, void *data, int len) +{ + usb_device_request_t req; + + if (len > sc->sc_ctrl_len) + return USB_ERR_INVAL; + + /* XXX FIXME: if (total len > sc->sc_ctrl_len) => must fragment */ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ctrl_ifaceno); + USETW(req.wLength, len); + mtx_unlock(&sc->sc_mutex); + DELAY(umb_delay); + mtx_lock(&sc->sc_mutex); + return usbd_do_request_flags(sc->sc_udev, &sc->sc_mutex, &req, data, 0, + NULL, umb_xfer_tout); +} + +static int +umb_get_encap_response(struct umb_softc *sc, void *buf, int *len) +{ + usb_device_request_t req; + usb_error_t err; + uint16_t l = *len; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ctrl_ifaceno); + USETW(req.wLength, l); + /* XXX FIXME: re-assemble fragments */ + + mtx_unlock(&sc->sc_mutex); + DELAY(umb_delay); + mtx_lock(&sc->sc_mutex); + err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mutex, &req, buf, + USB_SHORT_XFER_OK, &l, umb_xfer_tout); + if (err == USB_ERR_NORMAL_COMPLETION) { + *len = l; + return 1; + } + DPRINTF("ctrl recv: %s\n", usbd_errstr(err)); + return 0; +} + +static void +umb_ctrl_msg(struct umb_softc *sc, uint32_t req, void *data, int len) +{ + struct ifnet *ifp = GET_IFP(sc); + uint32_t tid; + struct mbim_msghdr *hdr = data; + usb_error_t err; + + if (sc->sc_dying) + return; + if (len < sizeof (*hdr)) + return; + tid = ++sc->sc_tid; + + hdr->type = htole32(req); + hdr->len = htole32(len); + hdr->tid = htole32(tid); + +#ifdef UMB_DEBUG + if (umb_debug) { + const char *op, *str; + if (req == MBIM_COMMAND_MSG) { + struct mbim_h2f_cmd *c = data; + if (le32toh(c->op) == MBIM_CMDOP_SET) + op = "set"; + else + op = "qry"; + str = umb_cid2str(le32toh(c->cid)); + } else { + op = "snd"; + str = umb_request2str(req); + } + DPRINTF("-> %s %s (tid %u)\n", op, str, tid); + } +#endif + err = umb_send_encap_command(sc, data, len); + if (err != USB_ERR_NORMAL_COMPLETION) { + if (ifp->if_flags & IFF_DEBUG) + log(LOG_ERR, "%s: send %s msg (tid %u) failed: %s\n", + DEVNAM(sc), umb_request2str(req), tid, + usbd_errstr(err)); + + /* will affect other transactions, too */ + usbd_transfer_stop(sc->sc_xfer[UMB_INTR_RX]); + } else { + DPRINTFN(2, "sent %s (tid %u)\n", + umb_request2str(req), tid); + DDUMPN(3, data, len); + } + return; +} + +static void +umb_open(struct umb_softc *sc) +{ + struct mbim_h2f_openmsg msg; + + memset(&msg, 0, sizeof (msg)); + msg.maxlen = htole32(sc->sc_ctrl_len); + umb_ctrl_msg(sc, MBIM_OPEN_MSG, &msg, sizeof (msg)); + return; +} + +static void +umb_close(struct umb_softc *sc) +{ + struct mbim_h2f_closemsg msg; + + memset(&msg, 0, sizeof (msg)); + umb_ctrl_msg(sc, MBIM_CLOSE_MSG, &msg, sizeof (msg)); +} + +static int +umb_setpin(struct umb_softc *sc, int op, int is_puk, void *pin, int pinlen, + void *newpin, int newpinlen) +{ + struct mbim_cid_pin cp; + int off; + + if (pinlen == 0) + return 0; + if (pinlen < 0 || pinlen > MBIM_PIN_MAXLEN || + newpinlen < 0 || newpinlen > MBIM_PIN_MAXLEN || + op < 0 || op > MBIM_PIN_OP_CHANGE || + (is_puk && op != MBIM_PIN_OP_ENTER)) + return EINVAL; + + memset(&cp, 0, sizeof (cp)); + cp.type = htole32(is_puk ? MBIM_PIN_TYPE_PUK1 : MBIM_PIN_TYPE_PIN1); + + off = offsetof(struct mbim_cid_pin, data); + if (!umb_addstr(&cp, sizeof (cp), &off, pin, pinlen, + &cp.pin_offs, &cp.pin_size)) + return EINVAL; + + cp.op = htole32(op); + if (newpinlen) { + if (!umb_addstr(&cp, sizeof (cp), &off, newpin, newpinlen, + &cp.newpin_offs, &cp.newpin_size)) + return EINVAL; + } else { + if ((op == MBIM_PIN_OP_CHANGE) || is_puk) + return EINVAL; + if (!umb_addstr(&cp, sizeof (cp), &off, NULL, 0, + &cp.newpin_offs, &cp.newpin_size)) + return EINVAL; + } + mtx_lock(&sc->sc_mutex); + umb_cmd(sc, MBIM_CID_PIN, MBIM_CMDOP_SET, &cp, off); + mtx_unlock(&sc->sc_mutex); + return 0; +} + +static void +umb_setdataclass(struct umb_softc *sc) +{ + struct mbim_cid_registration_state rs; + uint32_t classes; + + if (sc->sc_info.supportedclasses == MBIM_DATACLASS_NONE) + return; + + memset(&rs, 0, sizeof (rs)); + rs.regaction = htole32(MBIM_REGACTION_AUTOMATIC); + classes = sc->sc_info.supportedclasses; + if (sc->sc_info.preferredclasses != MBIM_DATACLASS_NONE) + classes &= sc->sc_info.preferredclasses; + rs.data_class = htole32(classes); + mtx_lock(&sc->sc_mutex); + umb_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_SET, &rs, sizeof (rs)); + mtx_unlock(&sc->sc_mutex); +} + +static void +umb_radio(struct umb_softc *sc, int on) +{ + struct mbim_cid_radio_state s; + + DPRINTF("set radio %s\n", on ? "on" : "off"); + memset(&s, 0, sizeof (s)); + s.state = htole32(on ? MBIM_RADIO_STATE_ON : MBIM_RADIO_STATE_OFF); + umb_cmd(sc, MBIM_CID_RADIO_STATE, MBIM_CMDOP_SET, &s, sizeof (s)); +} + +static void +umb_allocate_cid(struct umb_softc *sc) +{ + umb_cmd1(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_SET, + umb_qmi_alloc_cid, sizeof (umb_qmi_alloc_cid), umb_uuid_qmi_mbim); +} + +static void +umb_send_fcc_auth(struct umb_softc *sc) +{ + uint8_t fccauth[sizeof (umb_qmi_fcc_auth)]; + + if (sc->sc_cid == -1) { + DPRINTF("missing CID, cannot send FCC auth\n"); + umb_allocate_cid(sc); + return; + } + memcpy(fccauth, umb_qmi_fcc_auth, sizeof (fccauth)); + fccauth[UMB_QMI_CID_OFFS] = sc->sc_cid; + umb_cmd1(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_SET, + fccauth, sizeof (fccauth), umb_uuid_qmi_mbim); +} + +static void +umb_packet_service(struct umb_softc *sc, int attach) +{ + struct mbim_cid_packet_service s; + + DPRINTF("%s packet service\n", + attach ? "attach" : "detach"); + memset(&s, 0, sizeof (s)); + s.action = htole32(attach ? + MBIM_PKTSERVICE_ACTION_ATTACH : MBIM_PKTSERVICE_ACTION_DETACH); + umb_cmd(sc, MBIM_CID_PACKET_SERVICE, MBIM_CMDOP_SET, &s, sizeof (s)); +} + +static void +umb_connect(struct umb_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + + if (sc->sc_info.regstate == MBIM_REGSTATE_ROAMING && !sc->sc_roaming) { + log(LOG_INFO, "%s: connection disabled in roaming network\n", + DEVNAM(sc)); + return; + } + if (ifp->if_flags & IFF_DEBUG) + log(LOG_DEBUG, "%s: connecting ...\n", DEVNAM(sc)); + umb_send_connect(sc, MBIM_CONNECT_ACTIVATE); +} + +static void +umb_disconnect(struct umb_softc *sc) +{ + struct ifnet *ifp = GET_IFP(sc); + + if (ifp->if_flags & IFF_DEBUG) + log(LOG_DEBUG, "%s: disconnecting ...\n", DEVNAM(sc)); + umb_send_connect(sc, MBIM_CONNECT_DEACTIVATE); +} + +static void +umb_send_connect(struct umb_softc *sc, int command) +{ + struct mbim_cid_connect *c; + int off; + + /* Too large for the stack */ + mtx_unlock(&sc->sc_mutex); + c = malloc(sizeof (*c), M_MBIM_CID_CONNECT, M_WAITOK | M_ZERO); + mtx_lock(&sc->sc_mutex); + c->sessionid = htole32(umb_session_id); + c->command = htole32(command); + off = offsetof(struct mbim_cid_connect, data); + if (!umb_addstr(c, sizeof (*c), &off, sc->sc_info.apn, + sc->sc_info.apnlen, &c->access_offs, &c->access_size)) + goto done; + if (!umb_addstr(c, sizeof (*c), &off, sc->sc_info.username, + sc->sc_info.usernamelen, &c->user_offs, &c->user_size)) + goto done; + if (!umb_addstr(c, sizeof (*c), &off, sc->sc_info.password, + sc->sc_info.passwordlen, &c->passwd_offs, &c->passwd_size)) + goto done; + c->authprot = htole32(MBIM_AUTHPROT_NONE); + c->compression = htole32(MBIM_COMPRESSION_NONE); + c->iptype = htole32(MBIM_CONTEXT_IPTYPE_IPV4); + memcpy(c->context, umb_uuid_context_internet, sizeof (c->context)); + umb_cmd(sc, MBIM_CID_CONNECT, MBIM_CMDOP_SET, c, off); +done: + free(c, M_MBIM_CID_CONNECT); + return; +} + +static void +umb_qry_ipconfig(struct umb_softc *sc) +{ + struct mbim_cid_ip_configuration_info ipc; + + memset(&ipc, 0, sizeof (ipc)); + ipc.sessionid = htole32(umb_session_id); + umb_cmd(sc, MBIM_CID_IP_CONFIGURATION, MBIM_CMDOP_QRY, + &ipc, sizeof (ipc)); +} + +static void +umb_cmd(struct umb_softc *sc, int cid, int op, const void *data, int len) +{ + umb_cmd1(sc, cid, op, data, len, umb_uuid_basic_connect); +} + +static void +umb_cmd1(struct umb_softc *sc, int cid, int op, const void *data, int len, + uint8_t *uuid) +{ + struct mbim_h2f_cmd *cmd; + int totlen; + + /* XXX FIXME support sending fragments */ + if (sizeof (*cmd) + len > sc->sc_ctrl_len) { + DPRINTF("set %s msg too long: cannot send\n", + umb_cid2str(cid)); + return; + } + cmd = sc->sc_ctrl_msg; + memset(cmd, 0, sizeof (*cmd)); + cmd->frag.nfrag = htole32(1); + memcpy(cmd->devid, uuid, sizeof (cmd->devid)); + cmd->cid = htole32(cid); + cmd->op = htole32(op); + cmd->infolen = htole32(len); + totlen = sizeof (*cmd); + if (len > 0) { + memcpy(cmd + 1, data, len); + totlen += len; + } + umb_ctrl_msg(sc, MBIM_COMMAND_MSG, cmd, totlen); +} + +static void +umb_command_done(struct umb_softc *sc, void *data, int len) +{ + struct mbim_f2h_cmddone *cmd = data; + struct ifnet *ifp = GET_IFP(sc); + uint32_t status; + uint32_t cid; + uint32_t infolen; + int qmimsg = 0; + + if (len < sizeof (*cmd)) { + DPRINTF("discard short %s message\n", + umb_request2str(le32toh(cmd->hdr.type))); + return; + } + cid = le32toh(cmd->cid); + if (memcmp(cmd->devid, umb_uuid_basic_connect, sizeof (cmd->devid))) { + if (memcmp(cmd->devid, umb_uuid_qmi_mbim, + sizeof (cmd->devid))) { + DPRINTF("discard %s message for other UUID '%s'\n", + umb_request2str(le32toh(cmd->hdr.type)), + umb_uuid2str(cmd->devid)); + return; + } else + qmimsg = 1; + } + + status = le32toh(cmd->status); + switch (status) { + case MBIM_STATUS_SUCCESS: + break; + case MBIM_STATUS_NOT_INITIALIZED: + if (ifp->if_flags & IFF_DEBUG) + log(LOG_ERR, "%s: SIM not initialized (PIN missing)\n", + DEVNAM(sc)); + return; + case MBIM_STATUS_PIN_REQUIRED: + sc->sc_info.pin_state = UMB_PIN_REQUIRED; + /*FALLTHROUGH*/ + default: + if (ifp->if_flags & IFF_DEBUG) + log(LOG_ERR, "%s: set/qry %s failed: %s\n", DEVNAM(sc), + umb_cid2str(cid), umb_status2str(status)); + return; + } + + infolen = le32toh(cmd->infolen); + if (len < sizeof (*cmd) + infolen) { + DPRINTF("discard truncated %s message (want %d, got %d)\n", + umb_cid2str(cid), + (int)sizeof (*cmd) + infolen, len); + return; + } + if (qmimsg) { + if (sc->sc_flags & UMBFLG_FCC_AUTH_REQUIRED) + umb_decode_qmi(sc, cmd->info, infolen); + } else { + DPRINTFN(2, "set/qry %s done\n", + umb_cid2str(cid)); + umb_decode_cid(sc, cid, cmd->info, infolen); + } +} + +static void +umb_decode_cid(struct umb_softc *sc, uint32_t cid, void *data, int len) +{ + int ok = 1; + + switch (cid) { + case MBIM_CID_DEVICE_CAPS: + ok = umb_decode_devices_caps(sc, data, len); + break; + case MBIM_CID_SUBSCRIBER_READY_STATUS: + ok = umb_decode_subscriber_status(sc, data, len); + break; + case MBIM_CID_RADIO_STATE: + ok = umb_decode_radio_state(sc, data, len); + break; + case MBIM_CID_PIN: + ok = umb_decode_pin(sc, data, len); + break; + case MBIM_CID_REGISTER_STATE: + ok = umb_decode_register_state(sc, data, len); + break; + case MBIM_CID_PACKET_SERVICE: + ok = umb_decode_packet_service(sc, data, len); + break; + case MBIM_CID_SIGNAL_STATE: + ok = umb_decode_signal_state(sc, data, len); + break; + case MBIM_CID_CONNECT: + ok = umb_decode_connect_info(sc, data, len); + break; + case MBIM_CID_IP_CONFIGURATION: + ok = umb_decode_ip_configuration(sc, data, len); + break; + default: + /* + * Note: the above list is incomplete and only contains + * mandatory CIDs from the BASIC_CONNECT set. + * So alternate values are not unusual. + */ + DPRINTFN(4, "ignore %s\n", umb_cid2str(cid)); + break; + } + if (!ok) + DPRINTF("discard %s with bad info length %d\n", + umb_cid2str(cid), len); + return; +} + +static void +umb_decode_qmi(struct umb_softc *sc, uint8_t *data, int len) +{ + uint8_t srv; + uint16_t msg, tlvlen; + uint32_t val; + +#define UMB_QMI_QMUXLEN 6 + if (len < UMB_QMI_QMUXLEN) + goto tooshort; + + srv = data[4]; + data += UMB_QMI_QMUXLEN; + len -= UMB_QMI_QMUXLEN; + +#define UMB_GET16(p) ((uint16_t)*p | (uint16_t)*(p + 1) << 8) +#define UMB_GET32(p) ((uint32_t)*p | (uint32_t)*(p + 1) << 8 | \ + (uint32_t)*(p + 2) << 16 |(uint32_t)*(p + 3) << 24) + switch (srv) { + case 0: /* ctl */ +#define UMB_QMI_CTLLEN 6 + if (len < UMB_QMI_CTLLEN) + goto tooshort; + msg = UMB_GET16(&data[2]); + tlvlen = UMB_GET16(&data[4]); + data += UMB_QMI_CTLLEN; + len -= UMB_QMI_CTLLEN; + break; + case 2: /* dms */ +#define UMB_QMI_DMSLEN 7 + if (len < UMB_QMI_DMSLEN) + goto tooshort; + msg = UMB_GET16(&data[3]); + tlvlen = UMB_GET16(&data[5]); + data += UMB_QMI_DMSLEN; + len -= UMB_QMI_DMSLEN; + break; + default: + DPRINTF("discard QMI message for unknown service type %d\n", + srv); + return; + } + + if (len < tlvlen) + goto tooshort; + +#define UMB_QMI_TLVLEN 3 + while (len > 0) { + if (len < UMB_QMI_TLVLEN) + goto tooshort; + tlvlen = UMB_GET16(&data[1]); + if (len < UMB_QMI_TLVLEN + tlvlen) + goto tooshort; + switch (data[0]) { + case 1: /* allocation info */ + if (msg == 0x0022) { /* Allocate CID */ + if (tlvlen != 2 || data[3] != 2) /* dms */ + break; + sc->sc_cid = data[4]; + DPRINTF("QMI CID %d allocated\n", + sc->sc_cid); + umb_newstate(sc, UMB_S_CID, UMB_NS_DONT_DROP); + } + break; + case 2: /* response */ + if (tlvlen != sizeof (val)) + break; + val = UMB_GET32(&data[3]); + switch (msg) { + case 0x0022: /* Allocate CID */ + if (val != 0) { + log(LOG_ERR, "%s: allocation of QMI CID" + " failed, error 0x%x\n", DEVNAM(sc), + val); + /* XXX how to proceed? */ + return; + } + break; + case 0x555f: /* Send FCC Authentication */ + if (val == 0) + DPRINTF("%s: send FCC " + "Authentication succeeded\n", + DEVNAM(sc)); + else if (val == 0x001a0001) + DPRINTF("%s: FCC Authentication " + "not required\n", DEVNAM(sc)); + else + log(LOG_INFO, "%s: send FCC " + "Authentication failed, " + "error 0x%x\n", DEVNAM(sc), val); + + /* FCC Auth is needed only once after power-on*/ + sc->sc_flags &= ~UMBFLG_FCC_AUTH_REQUIRED; + + /* Try to proceed anyway */ + DPRINTF("init: turning radio on ...\n"); + umb_radio(sc, 1); + break; + default: + break; + } + break; + default: + break; + } + data += UMB_QMI_TLVLEN + tlvlen; + len -= UMB_QMI_TLVLEN + tlvlen; + } + return; + +tooshort: + DPRINTF("discard short QMI message\n"); + return; +} + +static void +umb_intr(struct usb_xfer *xfer, usb_error_t status) +{ + struct umb_softc *sc = usbd_xfer_softc(xfer); + struct usb_cdc_notification notification; + struct usb_page_cache *pc; + struct ifnet *ifp = GET_IFP(sc); + int total_len; + + mtx_assert(&sc->sc_mutex, MA_OWNED); + + /* FIXME use actlen or total_len? */ + usbd_xfer_status(xfer, &total_len, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF("Received %d bytes\n", total_len); + + if (total_len < UCDC_NOTIFICATION_LENGTH) { + DPRINTF("short notification (%d<%d)\n", + total_len, UCDC_NOTIFICATION_LENGTH); + return; + } + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, ¬ification, sizeof (notification)); + + if (notification.bmRequestType != UCDC_NOTIFICATION) { + DPRINTF("unexpected notification (type=0x%02x)\n", + notification.bmRequestType); + return; + } + + switch (notification.bNotification) { + case UCDC_N_NETWORK_CONNECTION: + if (ifp->if_flags & IFF_DEBUG) + log(LOG_DEBUG, "%s: network %sconnected\n", + DEVNAM(sc), + UGETW(notification.wValue) + ? "" : "dis"); + break; + case UCDC_N_RESPONSE_AVAILABLE: + DPRINTFN(2, "umb_intr: response available\n"); + ++sc->sc_nresp; + umb_add_task(sc, umb_get_response_task, + &sc->sc_proc_get_response_task[0].hdr, + &sc->sc_proc_get_response_task[1].hdr, + 0); + break; + case UCDC_N_CONNECTION_SPEED_CHANGE: + DPRINTFN(2, "umb_intr: connection speed changed\n"); + break; + default: + DPRINTF("unexpected notification (0x%02x)\n", + notification.bNotification); + break; + } + /* fallthrough */ + case USB_ST_SETUP: +tr_setup: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + default: + if (status != USB_ERR_CANCELLED) { + /* start clear stall */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + break; + } +} + +/* + * Diagnostic routines + */ +static char * +umb_ntop(struct sockaddr *sa) +{ +#define NUMBUFS 4 + static char astr[NUMBUFS][INET_ADDRSTRLEN]; + static unsigned nbuf = 0; + char *s; + + s = astr[nbuf++]; + if (nbuf >= NUMBUFS) + nbuf = 0; + + switch (sa->sa_family) { + case AF_INET: + default: + inet_ntop(AF_INET, &satosin(sa)->sin_addr, s, sizeof (astr[0])); + break; + case AF_INET6: + inet_ntop(AF_INET6, &satosin6(sa)->sin6_addr, s, + sizeof (astr[0])); + break; + } + return s; +} + +#ifdef UMB_DEBUG +static char * +umb_uuid2str(uint8_t uuid[MBIM_UUID_LEN]) +{ + static char uuidstr[2 * MBIM_UUID_LEN + 5]; + +#define UUID_BFMT "%02X" +#define UUID_SEP "-" + snprintf(uuidstr, sizeof (uuidstr), + UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_SEP + UUID_BFMT UUID_BFMT UUID_SEP + UUID_BFMT UUID_BFMT UUID_SEP + UUID_BFMT UUID_BFMT UUID_SEP + UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT, + uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], + uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]); + return uuidstr; +} + +static void +umb_dump(void *buf, int len) +{ + int i = 0; + uint8_t *c = buf; + + if (len == 0) + return; + while (i < len) { + if ((i % 16) == 0) { + if (i > 0) + log(LOG_DEBUG, "\n"); + log(LOG_DEBUG, "%4d: ", i); + } + log(LOG_DEBUG, " %02x", *c); + c++; + i++; + } + log(LOG_DEBUG, "\n"); +} +#endif /* UMB_DEBUG */ + +DRIVER_MODULE(umb, uhub, umb_driver, NULL, NULL); +MODULE_DEPEND(umb, usb, 1, 1, 1); diff --git a/sys/dev/usb/net/if_umbreg.h b/sys/dev/usb/net/if_umbreg.h new file mode 100644 --- /dev/null +++ b/sys/dev/usb/net/if_umbreg.h @@ -0,0 +1,443 @@ +/*- + * 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 + * + * 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. + * + * $OpenBSD: if_umb.h,v 1.4 2017/04/18 13:27:55 gerhard Exp $ + */ + +/* + * Mobile Broadband Interface Model + * http://www.usb.org/developers/docs/devclass_docs/MBIM-Compliance-1.0.pdf + */ + +struct umb_valdescr { + int val; + char const *descr; +}; + +static const char * +umb_val2descr(const struct umb_valdescr *vdp, int val) +{ + static char sval[32]; + + while (vdp->descr != NULL) { + if (vdp->val == val) + return vdp->descr; + vdp++; + } + snprintf(sval, sizeof (sval), "#%d", val); + return sval; +} + +#define MBIM_REGSTATE_DESCRIPTIONS { \ + { MBIM_REGSTATE_UNKNOWN, "unknown" }, \ + { MBIM_REGSTATE_DEREGISTERED, "not registered" }, \ + { MBIM_REGSTATE_SEARCHING, "searching" }, \ + { MBIM_REGSTATE_HOME, "home network" }, \ + { MBIM_REGSTATE_ROAMING, "roaming network" }, \ + { MBIM_REGSTATE_PARTNER, "partner network" }, \ + { MBIM_REGSTATE_DENIED, "access denied" }, \ + { 0, NULL } } + +#define MBIM_DATACLASS_DESCRIPTIONS { \ + { MBIM_DATACLASS_NONE, "none" }, \ + { MBIM_DATACLASS_GPRS, "GPRS" }, \ + { MBIM_DATACLASS_EDGE, "EDGE" }, \ + { MBIM_DATACLASS_UMTS, "UMTS" }, \ + { MBIM_DATACLASS_HSDPA, "HSDPA" }, \ + { MBIM_DATACLASS_HSUPA, "HSUPA" }, \ + { MBIM_DATACLASS_HSDPA|MBIM_DATACLASS_HSUPA, "HSPA" }, \ + { MBIM_DATACLASS_LTE, "LTE" }, \ + { MBIM_DATACLASS_1XRTT, "CDMA2000" }, \ + { MBIM_DATACLASS_1XEVDO, "CDMA2000" }, \ + { MBIM_DATACLASS_1XEVDO_REV_A, "CDMA2000" }, \ + { MBIM_DATACLASS_1XEVDV, "CDMA2000" }, \ + { MBIM_DATACLASS_3XRTT, "CDMA2000" }, \ + { MBIM_DATACLASS_1XEVDO_REV_B, "CDMA2000" }, \ + { MBIM_DATACLASS_UMB, "CDMA2000" }, \ + { MBIM_DATACLASS_CUSTOM, "custom" }, \ + { 0, NULL } } + +#define MBIM_1TO1_DESCRIPTION(m) { (m), #m } +#define MBIM_MESSAGES_DESCRIPTIONS { \ + MBIM_1TO1_DESCRIPTION(MBIM_OPEN_MSG), \ + MBIM_1TO1_DESCRIPTION(MBIM_CLOSE_MSG), \ + MBIM_1TO1_DESCRIPTION(MBIM_COMMAND_MSG), \ + MBIM_1TO1_DESCRIPTION(MBIM_HOST_ERROR_MSG), \ + MBIM_1TO1_DESCRIPTION(MBIM_OPEN_DONE), \ + MBIM_1TO1_DESCRIPTION(MBIM_CLOSE_DONE), \ + MBIM_1TO1_DESCRIPTION(MBIM_COMMAND_DONE), \ + MBIM_1TO1_DESCRIPTION(MBIM_FUNCTION_ERROR_MSG), \ + MBIM_1TO1_DESCRIPTION(MBIM_INDICATE_STATUS_MSG), \ + { 0, NULL } } + +#define MBIM_STATUS_DESCRIPTION(m) { MBIM_STATUS_ ## m, #m } +#define MBIM_STATUS_DESCRIPTIONS { \ + MBIM_STATUS_DESCRIPTION(SUCCESS), \ + MBIM_STATUS_DESCRIPTION(BUSY), \ + MBIM_STATUS_DESCRIPTION(FAILURE), \ + MBIM_STATUS_DESCRIPTION(SIM_NOT_INSERTED), \ + MBIM_STATUS_DESCRIPTION(BAD_SIM), \ + MBIM_STATUS_DESCRIPTION(PIN_REQUIRED), \ + MBIM_STATUS_DESCRIPTION(PIN_DISABLED), \ + MBIM_STATUS_DESCRIPTION(NOT_REGISTERED), \ + MBIM_STATUS_DESCRIPTION(PROVIDERS_NOT_FOUND), \ + MBIM_STATUS_DESCRIPTION(NO_DEVICE_SUPPORT), \ + MBIM_STATUS_DESCRIPTION(PROVIDER_NOT_VISIBLE), \ + MBIM_STATUS_DESCRIPTION(DATA_CLASS_NOT_AVAILABLE), \ + MBIM_STATUS_DESCRIPTION(PACKET_SERVICE_DETACHED), \ + MBIM_STATUS_DESCRIPTION(MAX_ACTIVATED_CONTEXTS), \ + MBIM_STATUS_DESCRIPTION(NOT_INITIALIZED), \ + MBIM_STATUS_DESCRIPTION(VOICE_CALL_IN_PROGRESS), \ + MBIM_STATUS_DESCRIPTION(CONTEXT_NOT_ACTIVATED), \ + MBIM_STATUS_DESCRIPTION(SERVICE_NOT_ACTIVATED), \ + MBIM_STATUS_DESCRIPTION(INVALID_ACCESS_STRING), \ + MBIM_STATUS_DESCRIPTION(INVALID_USER_NAME_PWD), \ + MBIM_STATUS_DESCRIPTION(RADIO_POWER_OFF), \ + MBIM_STATUS_DESCRIPTION(INVALID_PARAMETERS), \ + MBIM_STATUS_DESCRIPTION(READ_FAILURE), \ + MBIM_STATUS_DESCRIPTION(WRITE_FAILURE), \ + MBIM_STATUS_DESCRIPTION(NO_PHONEBOOK), \ + MBIM_STATUS_DESCRIPTION(PARAMETER_TOO_LONG), \ + MBIM_STATUS_DESCRIPTION(STK_BUSY), \ + MBIM_STATUS_DESCRIPTION(OPERATION_NOT_ALLOWED), \ + MBIM_STATUS_DESCRIPTION(MEMORY_FAILURE), \ + MBIM_STATUS_DESCRIPTION(INVALID_MEMORY_INDEX), \ + MBIM_STATUS_DESCRIPTION(MEMORY_FULL), \ + MBIM_STATUS_DESCRIPTION(FILTER_NOT_SUPPORTED), \ + MBIM_STATUS_DESCRIPTION(DSS_INSTANCE_LIMIT), \ + MBIM_STATUS_DESCRIPTION(INVALID_DEVICE_SERVICE_OPERATION), \ + MBIM_STATUS_DESCRIPTION(AUTH_INCORRECT_AUTN), \ + MBIM_STATUS_DESCRIPTION(AUTH_SYNC_FAILURE), \ + MBIM_STATUS_DESCRIPTION(AUTH_AMF_NOT_SET), \ + MBIM_STATUS_DESCRIPTION(CONTEXT_NOT_SUPPORTED), \ + MBIM_STATUS_DESCRIPTION(SMS_UNKNOWN_SMSC_ADDRESS), \ + MBIM_STATUS_DESCRIPTION(SMS_NETWORK_TIMEOUT), \ + MBIM_STATUS_DESCRIPTION(SMS_LANG_NOT_SUPPORTED), \ + MBIM_STATUS_DESCRIPTION(SMS_ENCODING_NOT_SUPPORTED), \ + MBIM_STATUS_DESCRIPTION(SMS_FORMAT_NOT_SUPPORTED), \ + { 0, NULL } } + +#define MBIM_ERROR_DESCRIPTION(m) { MBIM_ERROR_ ## m, #m } +#define MBIM_ERROR_DESCRIPTIONS { \ + MBIM_ERROR_DESCRIPTION(TIMEOUT_FRAGMENT), \ + MBIM_ERROR_DESCRIPTION(FRAGMENT_OUT_OF_SEQUENCE), \ + MBIM_ERROR_DESCRIPTION(LENGTH_MISMATCH), \ + MBIM_ERROR_DESCRIPTION(DUPLICATED_TID), \ + MBIM_ERROR_DESCRIPTION(NOT_OPENED), \ + MBIM_ERROR_DESCRIPTION(UNKNOWN), \ + MBIM_ERROR_DESCRIPTION(CANCEL), \ + MBIM_ERROR_DESCRIPTION(MAX_TRANSFER), \ + { 0, NULL } } + +#define MBIM_CID_DESCRIPTIONS { \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_DEVICE_CAPS), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_SUBSCRIBER_READY_STATUS), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_RADIO_STATE), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_PIN), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_PIN_LIST), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_HOME_PROVIDER), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_PREFERRED_PROVIDERS), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_VISIBLE_PROVIDERS), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_REGISTER_STATE), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_PACKET_SERVICE), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_SIGNAL_STATE), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_CONNECT), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_PROVISIONED_CONTEXTS), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_SERVICE_ACTIVATION), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_IP_CONFIGURATION), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_DEVICE_SERVICES), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_DEVICE_SERVICE_SUBSCRIBE_LIST), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_PACKET_STATISTICS), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_NETWORK_IDLE_HINT), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_EMERGENCY_MODE), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_IP_PACKET_FILTERS), \ + MBIM_1TO1_DESCRIPTION(MBIM_CID_MULTICARRIER_PROVIDERS), \ + { 0, NULL } } + +#define MBIM_SIMSTATE_DESCRIPTIONS { \ + { MBIM_SIMSTATE_NOTINITIALIZED, "not initialized" }, \ + { MBIM_SIMSTATE_INITIALIZED, "initialized" }, \ + { MBIM_SIMSTATE_NOTINSERTED, "not inserted" }, \ + { MBIM_SIMSTATE_BADSIM, "bad type" }, \ + { MBIM_SIMSTATE_FAILURE, "failed" }, \ + { MBIM_SIMSTATE_NOTACTIVATED, "not activated" }, \ + { MBIM_SIMSTATE_LOCKED, "locked" }, \ + { 0, NULL } } + +#define MBIM_PINTYPE_DESCRIPTIONS { \ + { MBIM_PIN_TYPE_NONE, "none" }, \ + { MBIM_PIN_TYPE_CUSTOM, "custom" }, \ + { MBIM_PIN_TYPE_PIN1, "PIN1" }, \ + { MBIM_PIN_TYPE_PIN2, "PIN2" }, \ + { MBIM_PIN_TYPE_DEV_SIM_PIN, "device PIN" }, \ + { MBIM_PIN_TYPE_DEV_FIRST_SIM_PIN, "device 1st PIN" }, \ + { MBIM_PIN_TYPE_NETWORK_PIN, "network PIN" }, \ + { MBIM_PIN_TYPE_NETWORK_SUBSET_PIN, "network subset PIN" }, \ + { MBIM_PIN_TYPE_SERVICE_PROVIDER_PIN, "provider PIN" }, \ + { MBIM_PIN_TYPE_CORPORATE_PIN, "corporate PIN" }, \ + { MBIM_PIN_TYPE_SUBSIDY_LOCK, "subsidy lock" }, \ + { MBIM_PIN_TYPE_PUK1, "PUK" }, \ + { MBIM_PIN_TYPE_PUK2, "PUK2" }, \ + { MBIM_PIN_TYPE_DEV_FIRST_SIM_PUK, "device 1st PUK" }, \ + { MBIM_PIN_TYPE_NETWORK_PUK, "network PUK" }, \ + { MBIM_PIN_TYPE_NETWORK_SUBSET_PUK, "network subset PUK" }, \ + { MBIM_PIN_TYPE_SERVICE_PROVIDER_PUK, "provider PUK" }, \ + { MBIM_PIN_TYPE_CORPORATE_PUK, "corporate PUK" }, \ + { 0, NULL } } + +#define MBIM_PKTSRV_STATE_DESCRIPTIONS { \ + { MBIM_PKTSERVICE_STATE_UNKNOWN, "unknown" }, \ + { MBIM_PKTSERVICE_STATE_ATTACHING, "attaching" }, \ + { MBIM_PKTSERVICE_STATE_ATTACHED, "attached" }, \ + { MBIM_PKTSERVICE_STATE_DETACHING, "detaching" }, \ + { MBIM_PKTSERVICE_STATE_DETACHED, "detached" }, \ + { 0, NULL } } + +#define MBIM_ACTIVATION_STATE_DESCRIPTIONS { \ + { MBIM_ACTIVATION_STATE_UNKNOWN, "unknown" }, \ + { MBIM_ACTIVATION_STATE_ACTIVATED, "activated" }, \ + { MBIM_ACTIVATION_STATE_ACTIVATING, "activating" }, \ + { MBIM_ACTIVATION_STATE_DEACTIVATED, "deactivated" }, \ + { MBIM_ACTIVATION_STATE_DEACTIVATING, "deactivating" }, \ + { 0, NULL } } + +/* + * Driver internal state + */ +enum umb_state { + UMB_S_DOWN = 0, /* interface down */ + UMB_S_OPEN, /* MBIM device has been opened */ + UMB_S_CID, /* QMI client id allocated */ + UMB_S_RADIO, /* radio is on */ + UMB_S_SIMREADY, /* SIM is ready */ + UMB_S_ATTACHED, /* packet service is attached */ + UMB_S_CONNECTED, /* connected to provider */ + UMB_S_UP, /* have IP configuration */ +}; + +#define UMB_INTERNAL_STATE_DESCRIPTIONS { \ + { UMB_S_DOWN, "down" }, \ + { UMB_S_OPEN, "open" }, \ + { UMB_S_CID, "CID allocated" }, \ + { UMB_S_RADIO, "radio on" }, \ + { UMB_S_SIMREADY, "SIM is ready" }, \ + { UMB_S_ATTACHED, "attached" }, \ + { UMB_S_CONNECTED, "connected" }, \ + { UMB_S_UP, "up" }, \ + { 0, NULL } } + +/* + * UMB parameters (SIOC[GS]UMBPARAM ioctls) + */ +struct umb_parameter { + int op; + int is_puk; + uint16_t pin[MBIM_PIN_MAXLEN]; + int pinlen; + + uint16_t newpin[MBIM_PIN_MAXLEN]; + int newpinlen; + +#define UMB_APN_MAXLEN 100 + uint16_t apn[UMB_APN_MAXLEN]; + int apnlen; + +#define UMB_USERNAME_MAXLEN 205 + uint16_t username[UMB_USERNAME_MAXLEN]; + int usernamelen; + +#define UMB_PASSWORD_MAXLEN 205 + uint16_t password[UMB_PASSWORD_MAXLEN]; + int passwordlen; + + int roaming; + uint32_t preferredclasses; +}; + +/* + * UMB device status info (SIOCGUMBINFO ioctl) + */ +struct umb_info { + enum umb_state state; + int enable_roaming; +#define UMB_PIN_REQUIRED 0 +#define UMB_PIN_UNLOCKED 1 +#define UMB_PUK_REQUIRED 2 + int pin_state; + int pin_attempts_left; + int activation; + int sim_state; + int regstate; + int regmode; + int nwerror; + int packetstate; + uint32_t supportedclasses; /* what the hw supports */ + uint32_t preferredclasses; /* what the user prefers */ + uint32_t highestclass; /* what the network offers */ + uint32_t cellclass; +#define UMB_PROVIDERNAME_MAXLEN 20 + uint16_t provider[UMB_PROVIDERNAME_MAXLEN]; +#define UMB_PHONENR_MAXLEN 22 + uint16_t pn[UMB_PHONENR_MAXLEN]; +#define UMB_SUBSCRIBERID_MAXLEN 15 + uint16_t sid[UMB_SUBSCRIBERID_MAXLEN]; +#define UMB_ICCID_MAXLEN 20 + uint16_t iccid[UMB_ICCID_MAXLEN]; +#define UMB_ROAMINGTEXT_MAXLEN 63 + uint16_t roamingtxt[UMB_ROAMINGTEXT_MAXLEN]; + +#define UMB_DEVID_MAXLEN 18 + uint16_t devid[UMB_DEVID_MAXLEN]; +#define UMB_FWINFO_MAXLEN 30 + uint16_t fwinfo[UMB_FWINFO_MAXLEN]; +#define UMB_HWINFO_MAXLEN 30 + uint16_t hwinfo[UMB_HWINFO_MAXLEN]; + + uint16_t apn[UMB_APN_MAXLEN]; + int apnlen; + + uint16_t username[UMB_USERNAME_MAXLEN]; + int usernamelen; + + uint16_t password[UMB_PASSWORD_MAXLEN]; + int passwordlen; + +#define UMB_VALUE_UNKNOWN -999 + int rssi; +#define UMB_BER_EXCELLENT 0 +#define UMB_BER_VERYGOOD 1 +#define UMB_BER_GOOD 2 +#define UMB_BER_OK 3 +#define UMB_BER_MEDIUM 4 +#define UMB_BER_BAD 5 +#define UMB_BER_VERYBAD 6 +#define UMB_BER_EXTREMELYBAD 7 + int ber; + + int hw_radio_on; + int sw_radio_on; + + uint64_t uplink_speed; + uint64_t downlink_speed; + +#define UMB_MAX_DNSSRV 2 + struct in_addr ipv4dns[UMB_MAX_DNSSRV]; +}; + +#if !defined(ifr_mtu) +#define ifr_mtu ifr_ifru.ifru_metric +#endif + +#ifdef _KERNEL +/* + * UMB device + */ +enum { + UMB_INTR_RX, + UMB_BULK_RX, + UMB_BULK_TX, + UMB_N_TRANSFER, +}; + +struct umb_task { + struct usb_proc_msg hdr; + struct umb_softc *sc; +}; + +struct umb_softc { + device_t sc_dev; + struct ifnet *sc_if; +#define GET_IFP(sc) ((sc)->sc_if) + struct ifmedia sc_im; + struct usb_device *sc_udev; + struct usb_xfer *sc_xfer[UMB_N_TRANSFER]; + uint8_t sc_ifaces_index[2]; + + int sc_ver_maj; + int sc_ver_min; + int sc_ctrl_len; + int sc_maxpktlen; + int sc_maxsessions; + +#define UMBFLG_FCC_AUTH_REQUIRED 0x0001 +#define UMBFLG_NO_INET6 0x0002 + uint32_t sc_flags; + int sc_cid; + + struct usb_process sc_taskqueue; + struct umb_task sc_proc_attach_task[2]; + struct umb_task sc_proc_start_task[2]; + struct umb_task sc_proc_state_task[2]; + struct umb_task sc_proc_get_response_task[2]; + + int sc_nresp; + struct mtx sc_mutex; + struct usb_callout sc_statechg_timer; + char sc_dying; + char sc_attached; + + uint8_t sc_ctrl_ifaceno; + struct usb_interface *sc_data_iface; + + void *sc_resp_buf; + void *sc_ctrl_msg; + + void *sc_rx_buf; + uint32_t sc_rx_bufsz; + unsigned sc_rx_nerr; + struct ifqueue sc_rx_queue; + + void *sc_tx_buf; + struct mbuf *sc_tx_m; + uint32_t sc_tx_bufsz; + uint32_t sc_tx_seq; + + uint32_t sc_tid; + +#define sc_state sc_info.state +#define sc_roaming sc_info.enable_roaming + struct umb_info sc_info; +}; +#endif /* _KERNEL */ diff --git a/sys/dev/usb/net/mbim.h b/sys/dev/usb/net/mbim.h new file mode 100644 --- /dev/null +++ b/sys/dev/usb/net/mbim.h @@ -0,0 +1,727 @@ +/*- + * 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 + * + * 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. + * + * $OpenBSD: mbim.h,v 1.4 2017/04/18 13:27:55 gerhard Exp $ + */ + +/* + * Mobile Broadband Interface Model + * http://www.usb.org/developers/docs/devclass_docs/MBIM-Compliance-1.0.pdf + */ + +#ifndef _MBIM_H_ +#define _MBIM_H_ + +#define UDESCSUB_MBIM 27 +#define MBIM_INTERFACE_ALTSETTING 1 + +#define MBIM_RESET_FUNCTION 0x05 + +/* + * Registration state (MBIM_REGISTER_STATE) + */ +#define MBIM_REGSTATE_UNKNOWN 0 +#define MBIM_REGSTATE_DEREGISTERED 1 +#define MBIM_REGSTATE_SEARCHING 2 +#define MBIM_REGSTATE_HOME 3 +#define MBIM_REGSTATE_ROAMING 4 +#define MBIM_REGSTATE_PARTNER 5 +#define MBIM_REGSTATE_DENIED 6 + +/* + * Data classes mask (MBIM_DATA_CLASS) + */ +#define MBIM_DATACLASS_NONE 0x00000000 +#define MBIM_DATACLASS_GPRS 0x00000001 +#define MBIM_DATACLASS_EDGE 0x00000002 +#define MBIM_DATACLASS_UMTS 0x00000004 +#define MBIM_DATACLASS_HSDPA 0x00000008 +#define MBIM_DATACLASS_HSUPA 0x00000010 +#define MBIM_DATACLASS_LTE 0x00000020 +#define MBIM_DATACLASS_1XRTT 0x00010000 +#define MBIM_DATACLASS_1XEVDO 0x00020000 +#define MBIM_DATACLASS_1XEVDO_REV_A 0x00040000 +#define MBIM_DATACLASS_1XEVDV 0x00080000 +#define MBIM_DATACLASS_3XRTT 0x00100000 +#define MBIM_DATACLASS_1XEVDO_REV_B 0x00200000 +#define MBIM_DATACLASS_UMB 0x00400000 +#define MBIM_DATACLASS_CUSTOM 0x80000000 + +/* + * Cell classes mask (MBIM_CELLULAR_CLASS) + */ +#define MBIM_CELLCLASS_GSM 0x00000001 +#define MBIM_CELLCLASS_CDMA 0x00000002 + +/* + * UUIDs + */ +#define MBIM_UUID_LEN 16 + +#define MBIM_UUID_BASIC_CONNECT { \ + 0xa2, 0x89, 0xcc, 0x33, 0xbc, 0xbb, 0x8b, 0x4f, \ + 0xb6, 0xb0, 0x13, 0x3e, 0xc2, 0xaa, 0xe6, 0xdf \ + } + +#define MBIM_UUID_CONTEXT_INTERNET { \ + 0x7e, 0x5e, 0x2a, 0x7e, 0x4e, 0x6f, 0x72, 0x72, \ + 0x73, 0x6b, 0x65, 0x6e, 0x7e, 0x5e, 0x2a, 0x7e \ + } + +#define MBIM_UUID_CONTEXT_VPN { \ + 0x9b, 0x9f, 0x7b, 0xbe, 0x89, 0x52, 0x44, 0xb7, \ + 0x83, 0xac, 0xca, 0x41, 0x31, 0x8d, 0xf7, 0xa0 \ + } + +#define MBIM_UUID_QMI_MBIM { \ + 0xd1, 0xa3, 0x0b, 0xc2, 0xf9, 0x7a, 0x6e, 0x43, \ + 0xbf, 0x65, 0xc7, 0xe2, 0x4f, 0xb0, 0xf0, 0xd3 \ + } + +#define MBIM_CTRLMSG_MINLEN 64 +#define MBIM_CTRLMSG_MAXLEN (4 * 1204) + +#define MBIM_MAXSEGSZ_MINVAL (2 * 1024) + +/* + * Control messages (host to function) + */ +#define MBIM_OPEN_MSG 1U +#define MBIM_CLOSE_MSG 2U +#define MBIM_COMMAND_MSG 3U +#define MBIM_HOST_ERROR_MSG 4U + +/* + * Control messages (function to host) + */ +#define MBIM_OPEN_DONE 0x80000001U +#define MBIM_CLOSE_DONE 0x80000002U +#define MBIM_COMMAND_DONE 0x80000003U +#define MBIM_FUNCTION_ERROR_MSG 0x80000004U +#define MBIM_INDICATE_STATUS_MSG 0x80000007U + +/* + * Generic status codes + */ +#define MBIM_STATUS_SUCCESS 0 +#define MBIM_STATUS_BUSY 1 +#define MBIM_STATUS_FAILURE 2 +#define MBIM_STATUS_SIM_NOT_INSERTED 3 +#define MBIM_STATUS_BAD_SIM 4 +#define MBIM_STATUS_PIN_REQUIRED 5 +#define MBIM_STATUS_PIN_DISABLED 6 +#define MBIM_STATUS_NOT_REGISTERED 7 +#define MBIM_STATUS_PROVIDERS_NOT_FOUND 8 +#define MBIM_STATUS_NO_DEVICE_SUPPORT 9 +#define MBIM_STATUS_PROVIDER_NOT_VISIBLE 10 +#define MBIM_STATUS_DATA_CLASS_NOT_AVAILABLE 11 +#define MBIM_STATUS_PACKET_SERVICE_DETACHED 12 +#define MBIM_STATUS_MAX_ACTIVATED_CONTEXTS 13 +#define MBIM_STATUS_NOT_INITIALIZED 14 +#define MBIM_STATUS_VOICE_CALL_IN_PROGRESS 15 +#define MBIM_STATUS_CONTEXT_NOT_ACTIVATED 16 +#define MBIM_STATUS_SERVICE_NOT_ACTIVATED 17 +#define MBIM_STATUS_INVALID_ACCESS_STRING 18 +#define MBIM_STATUS_INVALID_USER_NAME_PWD 19 +#define MBIM_STATUS_RADIO_POWER_OFF 20 +#define MBIM_STATUS_INVALID_PARAMETERS 21 +#define MBIM_STATUS_READ_FAILURE 22 +#define MBIM_STATUS_WRITE_FAILURE 23 +#define MBIM_STATUS_NO_PHONEBOOK 25 +#define MBIM_STATUS_PARAMETER_TOO_LONG 26 +#define MBIM_STATUS_STK_BUSY 27 +#define MBIM_STATUS_OPERATION_NOT_ALLOWED 28 +#define MBIM_STATUS_MEMORY_FAILURE 29 +#define MBIM_STATUS_INVALID_MEMORY_INDEX 30 +#define MBIM_STATUS_MEMORY_FULL 31 +#define MBIM_STATUS_FILTER_NOT_SUPPORTED 32 +#define MBIM_STATUS_DSS_INSTANCE_LIMIT 33 +#define MBIM_STATUS_INVALID_DEVICE_SERVICE_OPERATION 34 +#define MBIM_STATUS_AUTH_INCORRECT_AUTN 35 +#define MBIM_STATUS_AUTH_SYNC_FAILURE 36 +#define MBIM_STATUS_AUTH_AMF_NOT_SET 37 +#define MBIM_STATUS_CONTEXT_NOT_SUPPORTED 38 +#define MBIM_STATUS_SMS_UNKNOWN_SMSC_ADDRESS 100 +#define MBIM_STATUS_SMS_NETWORK_TIMEOUT 101 +#define MBIM_STATUS_SMS_LANG_NOT_SUPPORTED 102 +#define MBIM_STATUS_SMS_ENCODING_NOT_SUPPORTED 103 +#define MBIM_STATUS_SMS_FORMAT_NOT_SUPPORTED 104 + +/* + * Message formats + */ +struct mbim_msghdr { + /* Msg header */ + uint32_t type; /* message type */ + uint32_t len; /* message length */ + uint32_t tid; /* transaction id */ +} __packed; + +struct mbim_fraghdr { + uint32_t nfrag; /* total # of fragments */ + uint32_t currfrag; /* current fragment */ +} __packed; + +struct mbim_fragmented_msg_hdr { + struct mbim_msghdr hdr; + struct mbim_fraghdr frag; +} __packed; + +struct mbim_h2f_openmsg { + struct mbim_msghdr hdr; + uint32_t maxlen; +} __packed; + +struct mbim_h2f_closemsg { + struct mbim_msghdr hdr; +} __packed; + +struct mbim_h2f_cmd { + struct mbim_msghdr hdr; + struct mbim_fraghdr frag; + uint8_t devid[MBIM_UUID_LEN]; + uint32_t cid; /* command id */ +#define MBIM_CMDOP_QRY 0 +#define MBIM_CMDOP_SET 1 + uint32_t op; + uint32_t infolen; + uint8_t info[]; +} __packed; + +struct mbim_f2h_indicate_status { + struct mbim_msghdr hdr; + struct mbim_fraghdr frag; + uint8_t devid[MBIM_UUID_LEN]; + uint32_t cid; /* command id */ + uint32_t infolen; + uint8_t info[]; +} __packed; + +struct mbim_f2h_hosterr { + struct mbim_msghdr hdr; + +#define MBIM_ERROR_TIMEOUT_FRAGMENT 1 +#define MBIM_ERROR_FRAGMENT_OUT_OF_SEQUENCE 2 +#define MBIM_ERROR_LENGTH_MISMATCH 3 +#define MBIM_ERROR_DUPLICATED_TID 4 +#define MBIM_ERROR_NOT_OPENED 5 +#define MBIM_ERROR_UNKNOWN 6 +#define MBIM_ERROR_CANCEL 7 +#define MBIM_ERROR_MAX_TRANSFER 8 + uint32_t err; +} __packed; + +struct mbim_f2h_openclosedone { + struct mbim_msghdr hdr; + int32_t status; +} __packed; + +struct mbim_f2h_cmddone { + struct mbim_msghdr hdr; + struct mbim_fraghdr frag; + uint8_t devid[MBIM_UUID_LEN]; + uint32_t cid; /* command id */ + int32_t status; + uint32_t infolen; + uint8_t info[]; +} __packed; + +/* + * Messages and commands for MBIM_UUID_BASIC_CONNECT + */ +#define MBIM_CID_DEVICE_CAPS 1 +#define MBIM_CID_SUBSCRIBER_READY_STATUS 2 +#define MBIM_CID_RADIO_STATE 3 +#define MBIM_CID_PIN 4 +#define MBIM_CID_PIN_LIST 5 +#define MBIM_CID_HOME_PROVIDER 6 +#define MBIM_CID_PREFERRED_PROVIDERS 7 +#define MBIM_CID_VISIBLE_PROVIDERS 8 +#define MBIM_CID_REGISTER_STATE 9 +#define MBIM_CID_PACKET_SERVICE 10 +#define MBIM_CID_SIGNAL_STATE 11 +#define MBIM_CID_CONNECT 12 +#define MBIM_CID_PROVISIONED_CONTEXTS 13 +#define MBIM_CID_SERVICE_ACTIVATION 14 +#define MBIM_CID_IP_CONFIGURATION 15 +#define MBIM_CID_DEVICE_SERVICES 16 +#define MBIM_CID_DEVICE_SERVICE_SUBSCRIBE_LIST 19 +#define MBIM_CID_PACKET_STATISTICS 20 +#define MBIM_CID_NETWORK_IDLE_HINT 21 +#define MBIM_CID_EMERGENCY_MODE 22 +#define MBIM_CID_IP_PACKET_FILTERS 23 +#define MBIM_CID_MULTICARRIER_PROVIDERS 24 + +struct mbim_cid_subscriber_ready_info { +#define MBIM_SIMSTATE_NOTINITIALIZED 0 +#define MBIM_SIMSTATE_INITIALIZED 1 +#define MBIM_SIMSTATE_NOTINSERTED 2 +#define MBIM_SIMSTATE_BADSIM 3 +#define MBIM_SIMSTATE_FAILURE 4 +#define MBIM_SIMSTATE_NOTACTIVATED 5 +#define MBIM_SIMSTATE_LOCKED 6 + uint32_t ready; + + uint32_t sid_offs; + uint32_t sid_size; + + uint32_t icc_offs; + uint32_t icc_size; + +#define MBIM_SIMUNIQEID_NONE 0 +#define MBIM_SIMUNIQEID_PROTECT 1 + uint32_t info; + + uint32_t no_pn; + struct { + uint32_t offs; + uint32_t size; + } + pn[]; +} __packed; + +struct mbim_cid_radio_state { +#define MBIM_RADIO_STATE_OFF 0 +#define MBIM_RADIO_STATE_ON 1 + uint32_t state; +} __packed; + +struct mbim_cid_radio_state_info { + uint32_t hw_state; + uint32_t sw_state; +} __packed; + +struct mbim_cid_pin { +#define MBIM_PIN_TYPE_NONE 0 +#define MBIM_PIN_TYPE_CUSTOM 1 +#define MBIM_PIN_TYPE_PIN1 2 +#define MBIM_PIN_TYPE_PIN2 3 +#define MBIM_PIN_TYPE_DEV_SIM_PIN 4 +#define MBIM_PIN_TYPE_DEV_FIRST_SIM_PIN 5 +#define MBIM_PIN_TYPE_NETWORK_PIN 6 +#define MBIM_PIN_TYPE_NETWORK_SUBSET_PIN 7 +#define MBIM_PIN_TYPE_SERVICE_PROVIDER_PIN 8 +#define MBIM_PIN_TYPE_CORPORATE_PIN 9 +#define MBIM_PIN_TYPE_SUBSIDY_LOCK 10 +#define MBIM_PIN_TYPE_PUK1 11 +#define MBIM_PIN_TYPE_PUK2 12 +#define MBIM_PIN_TYPE_DEV_FIRST_SIM_PUK 13 +#define MBIM_PIN_TYPE_NETWORK_PUK 14 +#define MBIM_PIN_TYPE_NETWORK_SUBSET_PUK 15 +#define MBIM_PIN_TYPE_SERVICE_PROVIDER_PUK 16 +#define MBIM_PIN_TYPE_CORPORATE_PUK 17 + uint32_t type; + +#define MBIM_PIN_OP_ENTER 0 +#define MBIM_PIN_OP_ENABLE 1 +#define MBIM_PIN_OP_DISABLE 2 +#define MBIM_PIN_OP_CHANGE 3 + uint32_t op; + uint32_t pin_offs; + uint32_t pin_size; + uint32_t newpin_offs; + uint32_t newpin_size; +#define MBIM_PIN_MAXLEN 32 + uint8_t data[2 * MBIM_PIN_MAXLEN]; +} __packed; + +struct mbim_cid_pin_info { + uint32_t type; + +#define MBIM_PIN_STATE_UNLOCKED 0 +#define MBIM_PIN_STATE_LOCKED 1 + uint32_t state; + uint32_t remaining_attempts; +} __packed; + +struct mbim_cid_pin_list_info { + struct mbim_pin_desc { + +#define MBIM_PINMODE_NOTSUPPORTED 0 +#define MBIM_PINMODE_ENABLED 1 +#define MBIM_PINMODE_DISABLED 2 + uint32_t mode; + +#define MBIM_PINFORMAT_UNKNOWN 0 +#define MBIM_PINFORMAT_NUMERIC 1 +#define MBIM_PINFORMAT_ALPHANUMERIC 2 + uint32_t format; + + uint32_t minlen; + uint32_t maxlen; + } + pin1, + pin2, + dev_sim_pin, + first_dev_sim_pin, + net_pin, + net_sub_pin, + svp_pin, + corp_pin, + subsidy_lock, + custom; +} __packed; + +struct mbim_cid_device_caps { +#define MBIM_DEVTYPE_UNKNOWN 0 +#define MBIM_DEVTYPE_EMBEDDED 1 +#define MBIM_DEVTYPE_REMOVABLE 2 +#define MBIM_DEVTYPE_REMOTE 3 + uint32_t devtype; + + uint32_t cellclass; /* values: MBIM_CELLULAR_CLASS */ + uint32_t voiceclass; + uint32_t simclass; + uint32_t dataclass; /* values: MBIM_DATA_CLASS */ + uint32_t smscaps; + uint32_t cntrlcaps; + uint32_t max_sessions; + + uint32_t custdataclass_offs; + uint32_t custdataclass_size; + + uint32_t devid_offs; + uint32_t devid_size; + + uint32_t fwinfo_offs; + uint32_t fwinfo_size; + + uint32_t hwinfo_offs; + uint32_t hwinfo_size; + + uint32_t data[]; +} __packed; + +struct mbim_cid_registration_state { + uint32_t provid_offs; + uint32_t provid_size; + +#define MBIM_REGACTION_AUTOMATIC 0 +#define MBIM_REGACTION_MANUAL 1 + uint32_t regaction; + uint32_t data_class; + + uint32_t data[]; +} __packed; + +struct mbim_cid_registration_state_info { + uint32_t nwerror; + + uint32_t regstate; /* values: MBIM_REGISTER_STATE */ + +#define MBIM_REGMODE_UNKNOWN 0 +#define MBIM_REGMODE_AUTOMATIC 1 +#define MBIM_REGMODE_MANUAL 2 + uint32_t regmode; + + uint32_t availclasses; /* values: MBIM_DATA_CLASS */ + uint32_t curcellclass; /* values: MBIM_CELLULAR_CLASS */ + + uint32_t provid_offs; + uint32_t provid_size; + + uint32_t provname_offs; + uint32_t provname_size; + + uint32_t roamingtxt_offs; + uint32_t roamingtxt_size; + +#define MBIM_REGFLAGS_NONE 0 +#define MBIM_REGFLAGS_MANUAL_NOT_AVAILABLE 1 +#define MBIM_REGFLAGS_PACKETSERVICE_AUTOATTACH 2 + uint32_t regflag; + + uint32_t data[]; +} __packed; + +struct mbim_cid_packet_service { +#define MBIM_PKTSERVICE_ACTION_ATTACH 0 +#define MBIM_PKTSERVICE_ACTION_DETACH 1 + uint32_t action; +} __packed; + +struct mbim_cid_packet_service_info { + uint32_t nwerror; + +#define MBIM_PKTSERVICE_STATE_UNKNOWN 0 +#define MBIM_PKTSERVICE_STATE_ATTACHING 1 +#define MBIM_PKTSERVICE_STATE_ATTACHED 2 +#define MBIM_PKTSERVICE_STATE_DETACHING 3 +#define MBIM_PKTSERVICE_STATE_DETACHED 4 + uint32_t state; + + uint32_t highest_dataclass; + uint64_t uplink_speed; + uint64_t downlink_speed; +} __packed; + +struct mbim_cid_signal_state { + uint32_t rssi; + uint32_t err_rate; + uint32_t ss_intvl; + uint32_t rssi_thr; + uint32_t err_thr; +} __packed; + +struct mbim_cid_connect { + uint32_t sessionid; + +#define MBIM_CONNECT_DEACTIVATE 0 +#define MBIM_CONNECT_ACTIVATE 1 + uint32_t command; + +#define MBIM_ACCESS_MAXLEN 200 + uint32_t access_offs; + uint32_t access_size; + +#define MBIM_USER_MAXLEN 510 + uint32_t user_offs; + uint32_t user_size; + +#define MBIM_PASSWD_MAXLEN 510 + uint32_t passwd_offs; + uint32_t passwd_size; + +#define MBIM_COMPRESSION_NONE 0 +#define MBIM_COMPRESSION_ENABLE 1 + uint32_t compression; + +#define MBIM_AUTHPROT_NONE 0 +#define MBIM_AUTHPROT_PAP 1 +#define MBIM_AUTHPROT_CHAP 2 +#define MBIM_AUTHPROT_MSCHAP 3 + uint32_t authprot; + +#define MBIM_CONTEXT_IPTYPE_DEFAULT 0 +#define MBIM_CONTEXT_IPTYPE_IPV4 1 +#define MBIM_CONTEXT_IPTYPE_IPV6 2 +#define MBIM_CONTEXT_IPTYPE_IPV4V6 3 +#define MBIM_CONTEXT_IPTYPE_IPV4ANDV6 4 + uint32_t iptype; + + uint8_t context[MBIM_UUID_LEN]; + + uint8_t data[MBIM_ACCESS_MAXLEN + MBIM_USER_MAXLEN + + MBIM_PASSWD_MAXLEN]; + +} __packed; + +struct mbim_cid_connect_info { + uint32_t sessionid; + +#define MBIM_ACTIVATION_STATE_UNKNOWN 0 +#define MBIM_ACTIVATION_STATE_ACTIVATED 1 +#define MBIM_ACTIVATION_STATE_ACTIVATING 2 +#define MBIM_ACTIVATION_STATE_DEACTIVATED 3 +#define MBIM_ACTIVATION_STATE_DEACTIVATING 4 + uint32_t activation; + + uint32_t voice; + uint32_t iptype; + uint8_t context[MBIM_UUID_LEN]; + uint32_t nwerror; +} __packed; + +struct mbim_cid_ipv4_element { + uint32_t prefixlen; + uint32_t addr; +} __packed; + +struct mbim_cid_ipv6_element { + uint32_t prefixlen; + uint8_t addr[16]; +} __packed; + +struct mbim_cid_ip_configuration_info { + uint32_t sessionid; + +#define MBIM_IPCONF_HAS_ADDRINFO 0x0001 +#define MBIM_IPCONF_HAS_GWINFO 0x0002 +#define MBIM_IPCONF_HAS_DNSINFO 0x0004 +#define MBIM_IPCONF_HAS_MTUINFO 0x0008 + uint32_t ipv4_available; + uint32_t ipv6_available; + + uint32_t ipv4_naddr; + uint32_t ipv4_addroffs; + uint32_t ipv6_naddr; + uint32_t ipv6_addroffs; + + uint32_t ipv4_gwoffs; + uint32_t ipv6_gwoffs; + + uint32_t ipv4_ndnssrv; + uint32_t ipv4_dnssrvoffs; + uint32_t ipv6_ndnssrv; + uint32_t ipv6_dnssrvoffs; + + uint32_t ipv4_mtu; + uint32_t ipv6_mtu; + + uint32_t data[]; +} __packed; + +struct mbim_cid_packet_statistics_info { + uint32_t in_discards; + uint32_t in_errors; + uint64_t in_octets; + uint64_t in_packets; + uint64_t out_octets; + uint64_t out_packets; + uint32_t out_errors; + uint32_t out_discards; +} __packed; + + +#ifdef _KERNEL + +struct mbim_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; +#define MBIM_VER_MAJOR(v) (((v) >> 8) & 0x0f) +#define MBIM_VER_MINOR(v) ((v) & 0x0f) + uWord bcdMBIMVersion; + uWord wMaxControlMessage; + uByte bNumberFilters; + uByte bMaxFilterSize; + uWord wMaxSegmentSize; + uByte bmNetworkCapabilities; +} __packed; + +/* + * NCM Parameters + */ +#define NCM_GET_NTB_PARAMETERS 0x80 + +struct ncm_ntb_parameters { + uWord wLength; + uWord bmNtbFormatsSupported; +#define NCM_FORMAT_NTB16 0x0001 +#define NCM_FORMAT_NTB32 0x0002 + uDWord dwNtbInMaxSize; + uWord wNdpInDivisor; + uWord wNdpInPayloadRemainder; + uWord wNdpInAlignment; + uWord wReserved1; + uDWord dwNtbOutMaxSize; + uWord wNdpOutDivisor; + uWord wNdpOutPayloadRemainder; + uWord wNdpOutAlignment; + uWord wNtbOutMaxDatagrams; +} __packed; + +/* + * NCM Encoding + */ +#define MBIM_HDR16_LEN \ + (sizeof(struct ncm_header16) + sizeof(struct ncm_pointer16)) +#define MBIM_HDR32_LEN \ + (sizeof(struct ncm_header32) + sizeof(struct ncm_pointer32)) + +struct ncm_header16 { +#define NCM_HDR16_SIG 0x484d434e + uDWord dwSignature; + uWord wHeaderLength; + uWord wSequence; + uWord wBlockLength; + uWord wNdpIndex; +} __packed; + +struct ncm_header32 { +#define NCM_HDR32_SIG 0x686d636e + uDWord dwSignature; + uWord wHeaderLength; + uWord wSequence; + uDWord dwBlockLength; + uDWord dwNdpIndex; +} __packed; + + +#define MBIM_NCM_NTH_SIDSHIFT 24 +#define MBIM_NCM_NTH_GETSID(s) (((s) > MBIM_NCM_NTH_SIDSHIFT) & 0xff) + +struct ncm_pointer16_dgram { + uWord wDatagramIndex; + uWord wDatagramLen; +} __packed; + +struct ncm_pointer16 { +#define MBIM_NCM_NTH16_IPS 0x00535049 +#define MBIM_NCM_NTH16_ISISG(s) (((s) & 0x00ffffff) == MBIM_NCM_NTH16_IPS) +#define MBIM_NCM_NTH16_SIG(s) \ + ((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH16_IPS) + uDWord dwSignature; + uWord wLength; + uWord wNextNdpIndex; + + /* Minimum is two datagrams, but can be more */ + struct ncm_pointer16_dgram dgram[2]; +} __packed; + +struct ncm_pointer32_dgram { + uDWord dwDatagramIndex; + uDWord dwDatagramLen; +} __packed; + +struct ncm_pointer32 { +#define MBIM_NCM_NTH32_IPS 0x00737069 +#define MBIM_NCM_NTH32_ISISG(s) \ + (((s) & 0x00ffffff) == MBIM_NCM_NTH32_IPS) +#define MBIM_NCM_NTH32_SIG(s) \ + ((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH32_IPS) + uDWord dwSignature; + uWord wLength; + uWord wReserved6; + uDWord dwNextNdpIndex; + uDWord dwReserved12; + + /* Minimum is two datagrams, but can be more */ + struct ncm_pointer32_dgram dgram[2]; +} __packed; + +#endif /* _KERNEL */ + +#endif /* _MBIM_H_ */ diff --git a/sys/modules/usb/Makefile b/sys/modules/usb/Makefile --- a/sys/modules/usb/Makefile +++ b/sys/modules/usb/Makefile @@ -49,7 +49,7 @@ SUBDIR += atp cfumass uhid uhid_snes ukbd ums udbp uep wmt wsp ugold uled \ usbhid SUBDIR += ucom u3g uark ubsa ubser uchcom ucycom ufoma uftdi ugensa uipaq ulpt \ - umct umcs umodem umoscom uplcom uslcom uvisor uvscom + umb umct umcs umodem umoscom uplcom uslcom uvisor uvscom SUBDIR += i2ctinyusb SUBDIR += cp2112 SUBDIR += udl diff --git a/sys/modules/usb/umb/Makefile b/sys/modules/usb/umb/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/usb/umb/Makefile @@ -0,0 +1,33 @@ +# Copyright (c) 2008 Hans Petter Selasky. 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 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 AUTHOR 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. +# + +S= ${SRCTOP}/sys + +.PATH: $S/dev/usb/net + +KMOD= umb +SRCS= opt_bus.h opt_usb.h device_if.h bus_if.h usb_if.h usbdevs.h \ + if_umb.c + +.include diff --git a/sys/net/if_types.h b/sys/net/if_types.h --- a/sys/net/if_types.h +++ b/sys/net/if_types.h @@ -254,6 +254,7 @@ IFT_PFLOG = 0xf6, /* PF packet filter logging */ IFT_PFSYNC = 0xf7, /* PF packet filter synchronization */ IFT_WIREGUARD = 0xf8, /* WireGuard tunnel */ + IFT_MBIM = 0xf9, /* Mobile Broadband Interface Model */ } ifType; /* diff --git a/sys/sys/sockio.h b/sys/sys/sockio.h --- a/sys/sys/sockio.h +++ b/sys/sys/sockio.h @@ -147,4 +147,8 @@ #define SIOCSIFCAPNV _IOW('i', 155, struct ifreq) /* set IF features */ #define SIOCGIFCAPNV _IOWR('i', 156, struct ifreq) /* get IF features */ +#define SIOCGUMBINFO _IOWR('i', 157, struct ifreq) /* get MBIM info */ +#define SIOCSUMBPARAM _IOW('i', 158, struct ifreq) /* set MBIM param */ +#define SIOCGUMBPARAM _IOWR('i', 159, struct ifreq) /* get MBIM param */ + #endif /* !_SYS_SOCKIO_H_ */