From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-dl1-f41.google.com (mail-dl1-f41.google.com [74.125.82.41]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CA5E947CC86 for ; Tue, 16 Jun 2026 17:33:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.41 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781631195; cv=none; b=YX6ao4DTGF7lVJSr+LLMNaCcgve7kZp9ATYAbpa+WFBlGFkDT9zzQVJAb95+urRbd2CBUSkc/Y9yjpL/MBP931ki4jjF4C9vsPVRNnDcdvGrZXXfbmlXGDUYhlwyWwhSLojpWMyI9lnaMc2CIibbR5NxinmNoSAH6sEcjg4rK4A= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781631195; c=relaxed/simple; bh=0zTaGck00WCDmGFTrGRwlCgjeuIkm1YIgxiXoPhe/ZI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=tE7dzRFQIU0dy0tyMu7IQw9AWqMqo/dNKUAqenUlQZQOAY2vSzqsxSb7F9PnGTmslOIrTgnxruMsCBtmb5o0rldEVhxsBuYVGnsszvQHZ8taXV/bfS9VhsOTbOD1Pcc4qcvSE73dsc2ZPS4kKvIJgsPBaekZKtCamoi+1jjXgoA= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=qVUlmtKa; arc=none smtp.client-ip=74.125.82.41 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="qVUlmtKa" Received: by mail-dl1-f41.google.com with SMTP id a92af1059eb24-1384eb94d20so9148394c88.1 for ; Tue, 16 Jun 2026 10:33:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1781631193; x=1782235993; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=kZItjPd3qFLxrRcJE2CSJDnMZ4oYVdjNoHqhzSxIojQ=; b=qVUlmtKaH/pJqFJqVDv8nTDy3Ozg2RTk3xxQ1+B+I6qpZTXJLXi2aMWvELOzO4V8Ko IwALscrtv/b+oBy985Coot5HY0s4A9JGh8OfegHc74QByFA0E4z2x0AO96eqMPYV2Mnj XmBXfiImAjQjh2NjcMDrujUBY04KYGq3dzpfc45TQu6ED5oI51B/ugQQEtqSe2ug+CPv C/kLj+4gIX89OFsF/tVLyVsEksaVDGu9CjfzEUPDUdhzu2pmV8pWdiFSD9KuM0DjDbza AJrPnX53/xLC7P4EbGKscQdUn+xwcOnLJ3XjvFU4Ak9aBXCubdF9g8/7tDHM2nT05QPK Lwgg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781631193; x=1782235993; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=kZItjPd3qFLxrRcJE2CSJDnMZ4oYVdjNoHqhzSxIojQ=; b=puc15NgJOQ5RnfYBAJdHyBRlMdI2fHvAYT6K14g8qaCady3akZiuTwEEOnW0Z1spW7 sRzEw3OQHXa/qep1m9g1mS/C1heyQ5R1Ozw48P9O78xE9xUxeDK9QNr9SDD2NCaYW3XZ gtvQamEy7yZalHPWBmYtmLxlYSBcXC0HXRKtSkvgZ8MGG/N/7i/NJzzQHMeEYp8+gmza iMhFTFwu7dHld6a3HtHwI1NHyHxG4jcdGRnuy6FwBk+tB+rswPSZEPAdimeq5Xf10TyD 5MTP9dGqZbRTrqLsDY+FnvL7loOtxI2qB/9MAG/VeK7DkChapvYWpA8520afk27OmS7j NVnQ== X-Forwarded-Encrypted: i=1; AFNElJ+6qUu0GlWRrqsFpV8JWApNTfpKcfE71ehRsLaDsZ6IkFc6Oi/iCJK7Ph1V+CHfWBaHlJxn0oYGkiMPvSc=@vger.kernel.org X-Gm-Message-State: AOJu0YzE5u0o992FGnXnTltCMnCftx/pgreJN5cHpdUe1mVjR6kaW+GR 02DC2UepMdACbmhA6Ko3F73m5ei8/KOHnN64pZeVN9LSNPHt53v9SErv X-Gm-Gg: Acq92OGbfKtfR+MW7CWwP1cirKnRN5bTs9J1V4aYGpVWJa6vUWpVbPW7aLgya2A7i8U UCjn+qfqU8rzgV4cQ6Gcii+UHgO8Xsafzhrlxfewcklz5cyp7hMDIDMNy2lA1WTHvIqOw1GB29+ NriwdE5Y0p9Lqxy4NjmJhIAA07rfsnwYXbA84zTZ5G+7qZ05Tuu7tgbUn4x9O81kvB+QM3wH90X oSiEBX1wU/oxJDKOXdT38CjSfJQwlIalMLnB0FJ/tqcpdhaprh1f/wf7nkElmv09LUAmYlYgcEG mV8/eIdHURDFuLyrphY4ZgjzeXiPqYIr4wQKfkpFoIdyDznLl7pAyY42s12mWp66THMSDLq3ORn aoDImPVw8BAdK5Mi3qBGjwQzQX9liV0ofpCx6c2GrwIKrxSFkQS/5eN69lqtKYnH+jMtAazcLB5 fQD+0nby2iMbYSH+mJ/RtIf3r2KwccQHNXEqzIJxrP/nIyKoVTpNAea3waaj3fqxeaptG7 X-Received: by 2002:a05:693c:3103:b0:2ed:e15:c927 with SMTP id 5a478bee46e88-30bca0e332fmr150994eec.35.1781631192821; Tue, 16 Jun 2026 10:33:12 -0700 (PDT) Received: from fx.tailc0aff1.ts.net ([206.206.192.132]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-30ba6b7f840sm5431554eec.19.2026.06.16.10.33.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Jun 2026 10:33:12 -0700 (PDT) From: Weiming Shi To: Greg Kroah-Hartman , Jiri Slaby , Shuah Khan Cc: "Starke, Daniel" , Xiang Mei , linux-serial@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org, Weiming Shi Subject: [PATCH v2 2/2] selftests: tty: add base regression test for n_gsm line discipline Date: Tue, 16 Jun 2026 10:32:40 -0700 Message-ID: <20260616173240.3665059-3-bestswngs@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260616173240.3665059-1-bestswngs@gmail.com> References: <20260616173240.3665059-1-bestswngs@gmail.com> Precedence: bulk X-Mailing-List: linux-serial@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit n_gsm has no selftest coverage. Add a base functional regression test that drives the line discipline over a local pty pair, so no real serial hardware or modem is required, and exercises the GSM 07.10 / 3GPP TS 27.010 basic-option mux from userspace. The test attaches N_GSM to the pty master, then: - basic: brings up the mux (SETCONF, initiator side), drives the DLCI 0 control channel SABM/UA handshake and tears it down. - getconf: round-trips GSMIOC_GETCONF/GSMIOC_SETCONF and checks the configuration is preserved. - data_dlci: opens a data DLCI (DLCI 1) via the SABM/UA exchange and verifies the responder side answers, covering the control -> data DLCI path. Frames are encoded by hand against 3GPP TS 27.010 (address EA/C-R/DLCI bits, SABM/UA/UIH control fields, the reversed CRC-8 FCS) with the clause numbers referenced in the comments, so the test doubles as a small, readable description of the on-wire format. It is a functional/regression test, not a race reproducer: it gives the subsystem a green baseline to catch behavioural regressions, including in the gsm_queue() control-frame dispatch path. Wire it into the tty selftest Makefile, add CONFIG_N_GSM=y to the config fragment, and ignore the built binary. The test SKIPs cleanly when N_GSM is not built, /dev/ptmx is missing, or it lacks the capability to attach the ldisc. Signed-off-by: Weiming Shi Assisted-by: Claude:claude-opus-4-8 --- tools/testing/selftests/tty/.gitignore | 1 + tools/testing/selftests/tty/Makefile | 2 +- tools/testing/selftests/tty/config | 1 + tools/testing/selftests/tty/tty_n_gsm_test.c | 344 +++++++++++++++++++ 4 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/tty/tty_n_gsm_test.c diff --git a/tools/testing/selftests/tty/.gitignore b/tools/testing/selftests/tty/.gitignore index 2453685d2..e3fcee15e 100644 --- a/tools/testing/selftests/tty/.gitignore +++ b/tools/testing/selftests/tty/.gitignore @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only tty_tiocsti_test tty_tstamp_update +tty_n_gsm_test diff --git a/tools/testing/selftests/tty/Makefile b/tools/testing/selftests/tty/Makefile index 7f6fbe5a0..ae546d0d4 100644 --- a/tools/testing/selftests/tty/Makefile +++ b/tools/testing/selftests/tty/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 CFLAGS = -O2 -Wall -TEST_GEN_PROGS := tty_tstamp_update tty_tiocsti_test +TEST_GEN_PROGS := tty_tstamp_update tty_tiocsti_test tty_n_gsm_test LDLIBS += -lcap include ../lib.mk diff --git a/tools/testing/selftests/tty/config b/tools/testing/selftests/tty/config index c6373aba6..66a5ffc9e 100644 --- a/tools/testing/selftests/tty/config +++ b/tools/testing/selftests/tty/config @@ -1 +1,2 @@ CONFIG_LEGACY_TIOCSTI=y +CONFIG_N_GSM=y diff --git a/tools/testing/selftests/tty/tty_n_gsm_test.c b/tools/testing/selftests/tty/tty_n_gsm_test.c new file mode 100644 index 000000000..064231512 --- /dev/null +++ b/tools/testing/selftests/tty/tty_n_gsm_test.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * n_gsm line discipline test + * + * Exercise the n_gsm (GSM 07.10 mux) control paths over a pty, so the driver + * can be regression-tested without the real modem hardware. The test attaches + * the ldisc, configures the mux, opens DLCI 0, drives a control frame through + * the receive path (reaching gsm_control_reply()) and reconfigures, which + * tears the mux down and frees the DLCI. It is a functional coverage test of + * the receive and teardown paths, not a reproducer for any specific race. + * + * The frame encoding follows 3GPP TS 07.10 (a.k.a. 27.010), basic option. + * + * Requires CONFIG_N_GSM and CAP_NET_ADMIN to attach the ldisc. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" + +#ifndef N_GSM0710 +#define N_GSM0710 21 +#endif + +/* + * GSM 07.10 basic option framing. Field encodings below are from + * 3GPP TS 07.10 (a.k.a. 27.010): the basic-option flag (section 5.2.6.1), + * the address field with EA/C-R/DLCI bits (5.2.1.2, command vs response + * C/R from table 1), the control field codings (5.2.1.3, table 2), the + * length field (5.2.1.5), and the control-channel message types (5.4.6.3). + */ +#define GSM0_SOF 0xf9 /* basic-option flag, 1001 1111 */ +#define ADDR_DLCI0 0x03 /* EA=1, C/R=1, DLCI=0 (command) */ +#define ADDR_DLCI0_RSP 0x01 /* EA=1, C/R=0, DLCI=0 (response) */ +#define ADDR_DLCI1 0x07 /* EA=1, C/R=1, DLCI=1 (command) */ +#define GSM_PF 0x10 /* poll/final bit */ +#define CTRL_SABM (0x2f | GSM_PF) /* SABM command */ +#define CTRL_UA (0x63 | GSM_PF) /* UA, the SABM acknowledgment */ +#define CTRL_UIH 0xef /* UIH command/response */ +#define INIT_FCS 0xff +#define CMD_TEST_EA 0x23 /* (CMD_TEST << 1) | EA, type 5.4.6.3.4 */ + +#define MAX_FRAME 64 + +/* Reversed CRC-8 table (poly 0x07), from 3GPP TS 07.10 annex B.3.5. */ +static const unsigned char gsm_fcs8[256] = { +0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, +0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, +0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, +0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, +0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, +0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, +0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, +0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, +0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, +0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, +0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, +0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, +0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, +0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, +0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, +0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf, +}; + +static unsigned char fcs_header(const unsigned char *p, int n) +{ + unsigned char fcs = INIT_FCS; + int i; + + for (i = 0; i < n; i++) + fcs = gsm_fcs8[fcs ^ p[i]]; + return 0xff - fcs; +} + +/* + * Build a GSM0 frame: SOF addr ctrl len [data] FCS SOF. + * Returns the frame length, or -1 if it would not fit in MAX_FRAME. + */ +static int build_frame(unsigned char *out, unsigned char addr, + unsigned char ctrl, const unsigned char *data, int dlen) +{ + unsigned char hdr[3] = { addr, ctrl, (unsigned char)((dlen << 1) | 1) }; + int i = 0, j; + + if (dlen < 0 || dlen + 6 > MAX_FRAME) + return -1; + + out[i++] = GSM0_SOF; + out[i++] = addr; + out[i++] = ctrl; + out[i++] = (unsigned char)((dlen << 1) | 1); + for (j = 0; j < dlen; j++) + out[i++] = data[j]; + out[i++] = fcs_header(hdr, 3); + out[i++] = GSM0_SOF; + return i; +} + +static int gsm_setconf(int fd, int mtu) +{ + struct gsm_config c; + + memset(&c, 0, sizeof(c)); + c.adaption = 1; + c.encapsulation = 0; /* basic option framing */ + c.initiator = 0; /* responder: the peer (master side) drives DLCI 0 */ + c.mru = 64; + c.mtu = mtu; + c.i = 1; /* UIH frames */ + c.k = 2; /* window size */ + /* Short timers and a single retry so open/close handshakes and the + * teardown complete quickly within the test. + */ + c.t1 = 1; + c.t2 = 1; + c.n2 = 1; + return ioctl(fd, GSMIOC_SETCONF, &c); +} + +/* + * Open a pty pair with a raw master and the n_gsm ldisc on the slave. + * Returns 0 and fills *mfd (master) / *sfd (slave/ldisc) on success, or + * -errno otherwise. + */ +static int gsm_open(int *mfd, int *sfd) +{ + int ldisc = N_GSM0710; + char sname[128]; + struct termios tio; + int m, s, e; + + m = open("/dev/ptmx", O_RDWR | O_NOCTTY); + if (m < 0) + return -errno; + if (grantpt(m) || unlockpt(m) || ptsname_r(m, sname, sizeof(sname))) { + e = errno; + close(m); + return -e; + } + s = open(sname, O_RDWR | O_NOCTTY); + if (s < 0) { + e = errno; + close(m); + return -e; + } + if (tcgetattr(m, &tio) == 0) { + cfmakeraw(&tio); + tcsetattr(m, TCSANOW, &tio); + } + if (ioctl(s, TIOCSETD, &ldisc) < 0) { + e = errno; + close(s); + close(m); + return -e; + } + *mfd = m; + *sfd = s; + return 0; +} + +static long now_ms(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +/* + * Wait until the mux sends a frame on DLCI 0 with the given address and + * control field, e.g. the UA that acknowledges SABM (addr 0x03), or the + * CMD_TEST reply (response addr 0x01). Returns 1 if a matching frame header + * (SOF, addr, ctrl) is seen, 0 on timeout. Matching the SOF + address + + * control sequence (rather than a lone control byte) avoids false hits on + * FCS or payload bytes that happen to equal the control value. + */ +static int wait_for_frame(int mfd, unsigned char addr, unsigned char ctrl, + int timeout_ms) +{ + struct pollfd pfd = { .fd = mfd, .events = POLLIN }; + long deadline = now_ms() + timeout_ms; + unsigned char buf[256]; + int left; + + while ((left = deadline - now_ms()) > 0) { + int ret, n, i; + + ret = poll(&pfd, 1, left); + if (ret < 0) { + if (errno == EINTR) + continue; + return 0; + } + if (ret == 0 || !(pfd.revents & POLLIN)) + continue; + + n = read(mfd, buf, sizeof(buf)); + if (n <= 0) + continue; + for (i = 0; i + 2 < n; i++) { + if (buf[i] == GSM0_SOF && buf[i + 1] == addr && + buf[i + 2] == ctrl) + return 1; + } + } + return 0; +} + +FIXTURE(n_gsm) +{ + int mfd; /* pty master */ + int sfd; /* pty slave, carrying the n_gsm ldisc */ +}; + +FIXTURE_SETUP(n_gsm) +{ + int ret; + + /* So FIXTURE_TEARDOWN does not close fd 0 if setup bails early. */ + self->mfd = -1; + self->sfd = -1; + + ret = gsm_open(&self->mfd, &self->sfd); + + if (ret == -EPERM || ret == -EACCES) + SKIP(return, "need CAP_NET_ADMIN to attach n_gsm ldisc"); + if (ret == -EINVAL || ret == -ENODEV) + SKIP(return, "CONFIG_N_GSM not enabled"); + if (ret == -ENOENT) + SKIP(return, "no pty support (/dev/ptmx missing)"); + ASSERT_EQ(ret, 0) + TH_LOG("gsm_open failed: %d", ret); +} + +FIXTURE_TEARDOWN(n_gsm) +{ + if (self->sfd >= 0) + close(self->sfd); + if (self->mfd >= 0) + close(self->mfd); +} + +/* + * Configure the mux, open DLCI 0 and push a control frame through the receive + * path, then reconfigure to tear the mux down. This needs no hardware and + * verifies the n_gsm receive/teardown paths are reachable and do not crash. + */ +TEST_F(n_gsm, basic) +{ + unsigned char sabm[MAX_FRAME], cmd_test[MAX_FRAME]; + /* + * CMD_TEST control command: cmd byte = (CMD_TEST << 1) | EA, then a + * length-EA byte (0x01) meaning zero bytes of test data. + */ + unsigned char payload[2] = { CMD_TEST_EA, 0x01 }; + int slen, tlen; + + /* Activate the mux; this allocates DLCI 0. */ + ASSERT_EQ(gsm_setconf(self->sfd, 64), 0) + TH_LOG("GSMIOC_SETCONF failed: %m"); + + slen = build_frame(sabm, ADDR_DLCI0, CTRL_SABM, NULL, 0); + tlen = build_frame(cmd_test, ADDR_DLCI0, CTRL_UIH, payload, sizeof(payload)); + ASSERT_GT(slen, 0); + ASSERT_GT(tlen, 0); + + /* Open DLCI 0 and wait for the UA reply confirming it reached OPEN. */ + ASSERT_EQ(write(self->mfd, sabm, slen), slen); + ASSERT_EQ(wait_for_frame(self->mfd, ADDR_DLCI0, CTRL_UA, 1000), 1) + TH_LOG("DLCI 0 did not open (no UA reply)"); + + /* + * Drive a CMD_TEST control frame; the receive path reaches + * gsm_control_reply(), which sends a CMD_TEST reply back on the + * response address. Wait for that reply so we know the frame was + * processed, rather than sleeping. + */ + ASSERT_EQ(write(self->mfd, cmd_test, tlen), tlen); + EXPECT_EQ(wait_for_frame(self->mfd, ADDR_DLCI0_RSP, CTRL_UIH, 1000), 1) + TH_LOG("no CMD_TEST reply seen"); + + /* Reconfigure: tears the mux down and frees DLCI 0. */ + EXPECT_EQ(gsm_setconf(self->sfd, 127), 0); +} + +/* + * Configure the mux and read the configuration back with GSMIOC_GETCONF, + * checking the value round-trips. + */ +TEST_F(n_gsm, getconf) +{ + struct gsm_config c; + + ASSERT_EQ(gsm_setconf(self->sfd, 64), 0) + TH_LOG("GSMIOC_SETCONF failed: %m"); + + memset(&c, 0, sizeof(c)); + ASSERT_EQ(ioctl(self->sfd, GSMIOC_GETCONF, &c), 0) + TH_LOG("GSMIOC_GETCONF failed: %m"); + EXPECT_EQ(c.mtu, 64u); +} + +/* + * Open DLCI 0, then open a data channel (DLCI 1) with SABM and check the mux + * acknowledges it with a UA. This exercises gsm_dlci_alloc() and the data DLCI + * open path, not just the control channel. + */ +TEST_F(n_gsm, data_dlci) +{ + unsigned char sabm[MAX_FRAME]; + int slen; + + ASSERT_EQ(gsm_setconf(self->sfd, 64), 0) + TH_LOG("GSMIOC_SETCONF failed: %m"); + + slen = build_frame(sabm, ADDR_DLCI0, CTRL_SABM, NULL, 0); + ASSERT_GT(slen, 0); + ASSERT_EQ(write(self->mfd, sabm, slen), slen); + ASSERT_EQ(wait_for_frame(self->mfd, ADDR_DLCI0, CTRL_UA, 1000), 1) + TH_LOG("DLCI 0 did not open"); + + /* Open DLCI 1 (a data channel) and wait for its UA. */ + slen = build_frame(sabm, ADDR_DLCI1, CTRL_SABM, NULL, 0); + ASSERT_GT(slen, 0); + ASSERT_EQ(write(self->mfd, sabm, slen), slen); + EXPECT_EQ(wait_for_frame(self->mfd, ADDR_DLCI1, CTRL_UA, 1000), 1) + TH_LOG("DLCI 1 did not open (no UA reply)"); + + /* Tear the mux down. */ + EXPECT_EQ(gsm_setconf(self->sfd, 127), 0); +} + +TEST_HARNESS_MAIN -- 2.43.0