From f62f008347dee8ee5fb3863dd1a7b850c39d7086 Mon Sep 17 00:00:00 2001 From: Rob Meades Date: Fri, 15 Mar 2024 22:37:10 +0000 Subject: [PATCH] Linux change only: add PPP support. (#1117) Support for PPP connectivity via a cellular module is now added to the Linux platform. --- cell/api/u_cell_time.h | 5 + cell/src/u_cell_private.h | 8 + cell/src/u_cell_pwr.c | 2 + cell/src/u_cell_time.c | 7 + common/security/test/u_security_test.c | 15 +- example/sockets/README.md | 3 +- example/sockets/main_ppp_linux.c | 293 ++++++ port/api/u_port_ppp.h | 52 +- port/platform/common/automation/DATABASE.md | 2 +- port/platform/common/automation/Jenkinsfile | 2 +- .../automation/docker/builder/Dockerfile | 10 +- .../common/automation/scripts/u_run_linux.py | 62 +- port/platform/linux/README.md | 43 +- port/platform/linux/linux.cmake | 7 + port/platform/linux/src/u_port.c | 5 + port/platform/linux/src/u_port_ppp.c | 985 ++++++++++++++++++ port/platform/linux/src/u_port_ppp_private.h | 56 + port/platform/linux/test/u_linux_ppp_test.c | 580 +++++++++++ port/u_port_ppp_default.c | 7 + 19 files changed, 2094 insertions(+), 50 deletions(-) create mode 100644 example/sockets/main_ppp_linux.c create mode 100644 port/platform/linux/src/u_port_ppp.c create mode 100644 port/platform/linux/src/u_port_ppp_private.h create mode 100644 port/platform/linux/test/u_linux_ppp_test.c diff --git a/cell/api/u_cell_time.h b/cell/api/u_cell_time.h index 566b2178..554dd32e 100644 --- a/cell/api/u_cell_time.h +++ b/cell/api/u_cell_time.h @@ -251,6 +251,11 @@ typedef struct { * then it cannot also be used for CellTime and hence an error may be * returned if cellTimeOnly is set to false. * + * If you have compiled with U_CFG_PPP_ENABLE then calling this function, + * which necessarily disconnects from the network, will disconnect PPP + * and it will not be re-enabled until a new network connection is made, + * e.g. by calling uCellNetConnect(). + * * @param cellHandle the handle of the cellular instance. * @param mode the mode that CellTime should operate in, * must be one of #U_CELL_TIME_MODE_PULSE, diff --git a/cell/src/u_cell_private.h b/cell/src/u_cell_private.h index d0027aed..6614cb4d 100644 --- a/cell/src/u_cell_private.h +++ b/cell/src/u_cell_private.h @@ -628,6 +628,14 @@ int32_t uCellPrivateCFunGet(const uCellPrivateInstance_t *pInstance); * uCellPrivateCFunMode() can be called subseqently to put it * back again. * + * Note: if you are calling this with a mode that powers the + * module down (e.g. 0 or 4) then make sure that the calling + * function calls uPortPppDisconnect(), _before_ it locks the + * cellular API mutex, in order to bring any PPP connections + * down first; must be before the API mutex is locked as the + * process of bringing down a PPP connection will call into the + * cellular API. + * * @param pInstance pointer to the cellular instance. * @return the previous mode or negative error code. */ diff --git a/cell/src/u_cell_pwr.c b/cell/src/u_cell_pwr.c index b47752c9..9034fce1 100644 --- a/cell/src/u_cell_pwr.c +++ b/cell/src/u_cell_pwr.c @@ -1135,6 +1135,8 @@ static int32_t moduleConfigure(uCellPrivateInstance_t *pInstance, uPortTaskBlock(1000); } uAtClientLock(atHandle); + // This can sometimes take a little longer than the norm + uAtClientTimeoutSet(atHandle, 15000); uAtClientCommandStart(atHandle, "AT+CFUN="); uAtClientWriteInt(atHandle, pInstance->pModule->radioOffCfun); diff --git a/cell/src/u_cell_time.c b/cell/src/u_cell_time.c index ef3d026a..947a93c9 100644 --- a/cell/src/u_cell_time.c +++ b/cell/src/u_cell_time.c @@ -47,6 +47,7 @@ #include "u_port_os.h" #include "u_port_heap.h" #include "u_port_uart.h" +#include "u_port_ppp.h" #include "u_time.h" @@ -684,6 +685,12 @@ int32_t uCellTimeSyncCellEnable(uDeviceHandle_t cellHandle, if (gUCellPrivateMutex != NULL) { + // Since this function requires the normal radio + // operation of the module to be disabled, take any + // PPP connection down first (since we can't do so + // while the cellular API mutex is locked) + uPortPppDisconnect(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; diff --git a/common/security/test/u_security_test.c b/common/security/test/u_security_test.c index 208f7395..d2e84aa1 100644 --- a/common/security/test/u_security_test.c +++ b/common/security/test/u_security_test.c @@ -120,9 +120,6 @@ static uNetworkTestList_t *pStdPreamble() { uNetworkTestList_t *pList; - // In case a previous test failed - uNetworkTestCleanUp(); - U_PORT_TEST_ASSERT(uPortInit() == 0); U_PORT_TEST_ASSERT(uDeviceInit() == 0); @@ -262,6 +259,10 @@ U_PORT_TEST_FUNCTION("[security]", "securitySeal") // Close the devices once more and free the list for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { if (*pTmp->pDevHandle != NULL) { + U_TEST_PRINT_LINE("taking down %s...", + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uNetworkInterfaceDown(*pTmp->pDevHandle, + pTmp->networkType) == 0); U_TEST_PRINT_LINE("closing device %s...", gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType]); U_PORT_TEST_ASSERT(uDeviceClose(*pTmp->pDevHandle, false) == 0); @@ -400,6 +401,10 @@ U_PORT_TEST_FUNCTION("[security]", "securityPskGeneration") // Close the devices once more and free the list for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { if (*pTmp->pDevHandle != NULL) { + U_TEST_PRINT_LINE("taking down %s...", + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uNetworkInterfaceDown(*pTmp->pDevHandle, + pTmp->networkType) == 0); U_TEST_PRINT_LINE("closing device %s...", gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType]); U_PORT_TEST_ASSERT(uDeviceClose(*pTmp->pDevHandle, false) == 0); @@ -526,6 +531,10 @@ U_PORT_TEST_FUNCTION("[security]", "securityZtp") // Close the devices once more and free the list for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { if (*pTmp->pDevHandle != NULL) { + U_TEST_PRINT_LINE("taking down %s...", + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uNetworkInterfaceDown(*pTmp->pDevHandle, + pTmp->networkType) == 0); U_TEST_PRINT_LINE("closing device %s...", gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType]); U_PORT_TEST_ASSERT(uDeviceClose(*pTmp->pDevHandle, false) == 0); diff --git a/example/sockets/README.md b/example/sockets/README.md index 76a7686d..7952fee3 100644 --- a/example/sockets/README.md +++ b/example/sockets/README.md @@ -21,7 +21,6 @@ For the remainder of the \#defines (see "Using A xxx Module" below) you may eith Follow the instructions in the [port/platform/arduino](/port/platform/arduino) directory to create the Arduino library version of `ubxlib`, which will include the example here. # Using A Cellular Module - `U_CFG_TEST_CELL_MODULE_TYPE`: consult [u_cell_module_type.h](/cell/api/u_cell_module_type.h) to determine the type name for the cellular module you intend to use. For instance, to use SARA-R5 you would set `U_CFG_TEST_CELL_MODULE_TYPE` to `U_CELL_MODULE_TYPE_SARA_R5`. `U_CFG_APP_PIN_CELL_xxx`: the default values for the MCU pins connecting your cellular module to your MCU are \#defined in the file [port/platform](/port/platform)`//mcu//cfg/cfg_app_platform_specific.h`. You should check if these are correct for your board and, if not, override the values of the \#defines (where -1 means "not connected"). @@ -31,7 +30,6 @@ Follow the instructions in the [port/platform/arduino](/port/platform/arduino) d Obviously you will need a SIM in your board, an antenna connected and you may need to know the APN associated with the SIM (though accepting the network default often works). # Using A Wi-Fi Module - `U_CFG_TEST_SHORT_RANGE_MODULE_TYPE`: consult [u_short_range_module_type.h](/common/short_range/api/u_short_range_module_type.h) to determine the type name for the short range module module you intend to use. For instance, to use NINA-W13 you would set `U_CFG_TEST_SHORT_RANGE_MODULE_TYPE` to `U_SHORT_RANGE_MODULE_TYPE_NINA_W13`. `U_CFG_APP_PIN_SHORT_RANGE_xxx`: the default values for the MCU pins connecting your short range module to your MCU are \#defined in the file [port/platform](/port/platform)`//mcu//cfg/cfg_app_platform_specific.h`. You should check if these are correct for your board and, if not, override the values of the \#defines (where -1 means "not connected"). @@ -43,6 +41,7 @@ On the following platforms: - [ESP-IDF](/port/platform/esp-idf) - [Zephyr](/port/platform/zephyr) +- [Linux](/port/platform/linux) ...and with following \[cellular\] modules: diff --git a/example/sockets/main_ppp_linux.c b/example/sockets/main_ppp_linux.c new file mode 100644 index 00000000..0447e52a --- /dev/null +++ b/example/sockets/main_ppp_linux.c @@ -0,0 +1,293 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @brief This example demonstrates how to bring up a network + * connection and then perform sockets using pppd on Linux. For + * more information on how to set up pppd with this, see + * /port/platform/linux/README.md. + * + * For this example to run you must define U_CFG_PPP_ENABLE when + * building ubxlib; you could do this by including it in the U_FLAGS + * environment variable which linux.cmake looks for: + * + * export U_FLAGS=-DU_CFG_PPP_ENABLE + * + * The choice of [cellular] module is made at build time, see the + * README.md for instructions. + */ + +// This example is only for Linux +#ifdef __linux__ + +// Bring in all of the ubxlib public header files +#include "ubxlib.h" + +#ifdef U_CFG_PPP_ENABLE + +// snprintf() +#include "stdlib.h" + +// The BSD sockets interface +#include "unistd.h" +#include "sys/socket.h" +#include "netdb.h" +#include "arpa/inet.h" +#include "errno.h" +#include "net/if.h" // struct ifreq + +// Bring in the application settings +#include "u_cfg_app_platform_specific.h" + +#ifndef U_CFG_DISABLE_TEST_AUTOMATION +// This purely for internal u-blox testing +# include "u_cfg_test_platform_specific.h" +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +// Echo server URL and port number +#define MY_SERVER_NAME "ubxlib.com" +#define MY_SERVER_PORT 5055 + +// For u-blox internal testing only +#ifdef U_PORT_TEST_ASSERT +# define EXAMPLE_FINAL_STATE(x) U_PORT_TEST_ASSERT(x); +#else +# define EXAMPLE_FINAL_STATE(x) +#endif + +#ifndef U_PORT_TEST_FUNCTION +# error if you are not using the unit test framework to run this code you must ensure that the platform clocks/RTOS are set up and either define U_PORT_TEST_FUNCTION yourself or replace it as necessary. +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +#ifdef U_CFG_TEST_CELL_MODULE_TYPE +// Set U_CFG_TEST_CELL_MODULE_TYPE to your module type, +// chosen from the values in cell/api/u_cell_module_type.h +// +// Note that the pin numbers are those of the MCU: if you +// are using an MCU inside a u-blox module the IO pin numbering +// for the module is likely different that from the MCU: check +// the data sheet for the module to determine the mapping. + +// DEVICE i.e. module/chip configuration: in this case a cellular +// module connected via UART +static const uDeviceCfg_t gDeviceCfg = { + .deviceType = U_DEVICE_TYPE_CELL, + .deviceCfg = { + .cfgCell = { + .moduleType = U_CFG_TEST_CELL_MODULE_TYPE, + .pSimPinCode = NULL, /* SIM pin */ + .pinEnablePower = U_CFG_APP_PIN_CELL_ENABLE_POWER, + .pinPwrOn = U_CFG_APP_PIN_CELL_PWR_ON, + .pinVInt = U_CFG_APP_PIN_CELL_VINT, + .pinDtrPowerSaving = U_CFG_APP_PIN_CELL_DTR + }, + }, + .transportType = U_DEVICE_TRANSPORT_TYPE_UART, + .transportCfg = { + .cfgUart = { + .uart = U_CFG_APP_CELL_UART, + .baudRate = U_CELL_UART_BAUD_RATE, + .pinTxd = U_CFG_APP_PIN_CELL_TXD, + .pinRxd = U_CFG_APP_PIN_CELL_RXD, + .pinCts = U_CFG_APP_PIN_CELL_CTS, + .pinRts = U_CFG_APP_PIN_CELL_RTS, +#ifdef U_CFG_APP_UART_PREFIX + .pPrefix = U_PORT_STRINGIFY_QUOTED(U_CFG_APP_UART_PREFIX) // Relevant for Linux only +#else + .pPrefix = NULL +#endif + }, + }, +}; +// NETWORK configuration +static const uNetworkCfgCell_t gNetworkCfg = { + .type = U_NETWORK_TYPE_CELL, + .pApn = NULL, /* APN: NULL to accept default. If using a Thingstream SIM enter "tsiot" here */ + .timeoutSeconds = 240 /* Connection timeout in seconds */ + // There are four additional fields here which we do NOT set, + // we allow the compiler to set them to 0 and all will be fine. + // The fields are: + // + // - "pKeepGoingCallback": you may set this field to a function + // of the form "bool keepGoingCallback(uDeviceHandle_t devHandle)", + // e.g.: + // + // .pKeepGoingCallback = keepGoingCallback; + // + // ...and your function will be called periodically during an + // abortable network operation such as connect/disconnect; + // if it returns true the operation will continue else it + // will be aborted, allowing you immediate control. If this + // field is set, timeoutSeconds will be ignored. + // + // - "pUsername" and "pPassword": if you are required to set a + // user name and password to go with the APN value that you + // were given by your service provider, set them here. + // + // - "authenticationMode": if you MUST give a user name and + // password then you must populate this field with the + // authentication mode that should be used, see + // #uCellNetAuthenticationMode_t in u_cell_net.h, and noting + // that automatic authentication mode will NOT work with PPP. + // You ONLY NEED TO WORRY ABOUT THIS if you were given a user name + // name and password with the APN (which is thankfully not usual). + // + // - "pMccMnc": ONLY required if you wish to connect to a specific + // MCC/MNC rather than to the best available network; should point + // to the null-terminated string giving the MCC and MNC of the PLMN + // to use (for example "23410"). +}; +#else +// No module available - set some dummy values to make test system happy +static const uDeviceCfg_t gDeviceCfg = {.deviceType = U_DEVICE_TYPE_NONE}; +static const uNetworkCfgCell_t gNetworkCfg = {.type = U_NETWORK_TYPE_NONE}; +#endif + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: THE EXAMPLE + * -------------------------------------------------------------- */ + +// The entry point, main(). +U_PORT_TEST_FUNCTION("[example]", "examplePppLinuxSockets") +{ + uDeviceHandle_t devHandle = NULL; + struct hostent *pHostEnt; + struct sockaddr_in destinationAddress = {0}; + int32_t sock; + const char message[] = "The quick brown linux-fox jumps over the lazy dog."; + size_t txSize = sizeof(message); + char buffer[128]; + size_t rxSize = 0; + int32_t returnCode; + struct ifreq interface = {0}; + + // Initialise the APIs we will need + uPortInit(); + uDeviceInit(); + + // Open the device + returnCode = uDeviceOpen(&gDeviceCfg, &devHandle); + uPortLog("Opened device with return code %d.\n", returnCode); + + if (returnCode == 0) { + // Bring up the network interface + uPortLog("Bringing up the network...\n"); + if (uNetworkInterfaceUp(devHandle, U_NETWORK_TYPE_CELL, + &gNetworkCfg) == 0) { + + // Linux is now connected to the internet + // via pppd and the cellular module + + // Look up the IP address of the echo server + errno = 0; + pHostEnt = gethostbyname(MY_SERVER_NAME); + if (pHostEnt != NULL) { + uPortLog("Found %s at %s.\n", MY_SERVER_NAME, + inet_ntoa(*(struct in_addr *) pHostEnt->h_addr)); + + // Call the native BSD sockets APIs to send data + + destinationAddress.sin_addr = *(struct in_addr *) pHostEnt->h_addr; + destinationAddress.sin_family = pHostEnt->h_addrtype; + destinationAddress.sin_port = htons(MY_SERVER_PORT); + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (sock >= 0) { + // You wouldn't normally do this but, while testing, this + // Linux instance will likely also have an Ethernet connection, + // so we bind the socket to the "ppp0" interface that pppd + // will have created to ensure that we are sending data over + // the PPP/cellular connection + snprintf(interface.ifr_name, sizeof(interface.ifr_name), "ppp0"); + if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, + &interface, sizeof(interface)) == 0) { + if (connect(sock, (struct sockaddr *) &destinationAddress, sizeof(destinationAddress)) == 0) { + if (send(sock, message, txSize, 0) == txSize) { + uPortLog("Sent %d byte(s) to echo server.\n", txSize); + rxSize = recv(sock, buffer, sizeof(buffer), 0); + if (rxSize > 0) { + uPortLog("\nReceived echo back (%d byte(s)): %s\n", rxSize, buffer); + } else { + uPortLog("\nNo reply received!\n"); + } + } else { + uPortLog("Unable to send to server (errno %d)!\n", errno); + } + } else { + uPortLog("Unable to connect to server (errno %d)!\n", errno); + } + } else { + uPortLog("Unable to bind socket to interface ppp0 (errno %d)!\n", errno); + } + } else { + uPortLog("Unable to create socket (errno %d)!\n", errno); + } + + // Close the socket + uPortLog("Closing socket...\n"); + shutdown(sock, 0); + close(sock); + } else { + uPortLog("Unable to find %s (errno %d)!\n", MY_SERVER_NAME, errno); + } + + // When finished with the network layer + uPortLog("Taking down network...\n"); + uNetworkInterfaceDown(devHandle, U_NETWORK_TYPE_CELL); + } else { + uPortLog("Unable to bring up the network!\n"); + } + + // Close the device + // Note: we don't power the device down here in order + // to speed up testing; you may prefer to power it off + // by setting the second parameter to true. + uDeviceClose(devHandle, false); + + } else { + uPortLog("Unable to bring up the device!\n"); + } + + // Tidy up + uDeviceDeinit(); + uPortDeinit(); + + uPortLog("Done.\n"); + +#ifdef U_CFG_TEST_CELL_MODULE_TYPE + // For u-blox internal testing only + EXAMPLE_FINAL_STATE(rxSize == sizeof(message)); +#endif +} + +#endif // #ifdef U_CFG_PPP_ENABLE +#endif // #ifdef __linux__ + +// End of file diff --git a/port/api/u_port_ppp.h b/port/api/u_port_ppp.h index a026c8d5..e8c614bb 100644 --- a/port/api/u_port_ppp.h +++ b/port/api/u_port_ppp.h @@ -45,7 +45,8 @@ * are documented in the same place. * * Please also note that the application NEVER needs to call - * any of the functions defined here; they are purely called + * any of the functions defined here aside from possibly + * uPortPppSetLocalDeviceName(); they are purely called * from within ubxlib to connect a platform's PPP interface. */ @@ -89,6 +90,14 @@ extern "C" { # define U_PORT_PPP_DNS_SECONDARY_DEFAULT_STR "8.8.4.4" #endif +#ifndef U_PORT_PPP_LOCAL_DEVICE_NAME_LENGTH +/** The maximum length of the string passed to + * uPortPppSetLocalDeviceName(), not including the null terminator + * (what strlen() would return). + */ +# define U_PORT_PPP_LOCAL_DEVICE_NAME_LENGTH 32 +#endif + /* ---------------------------------------------------------------- * TYPES * -------------------------------------------------------------- */ @@ -241,6 +250,28 @@ void uPortPppDefaultPrivateLink(void); * FUNCTIONS * -------------------------------------------------------------- */ +/** Set the name (mostly port number) of the IP-stack-end PPP device + * on this MCU that uPortPppAttach() will connect with; this must be + * called *before* uDeviceOpen() is called on the device where you + * expect to use PPP. + * + * This is currently only applicable for the Linux platform, where + * it must be an IP address with a port number; the default if + * this function is not called will be U_PORT_PPP_LOCAL_DEVICE_NAME + * (e.g. 127.0.0.1:5000). + * + * If a PPP interface is not supported by a platform this function + * does not need to be implemented: a weakly-linked implementation + * will take over and return #U_ERROR_COMMON_NOT_SUPPORTED. + * + * @param[in] pDevice a pointer to a string giving the name + * of the device, up to + * #U_PORT_PPP_LOCAL_DEVICE_NAME_LENGTH + * characters long. + * @return zero on success else negative error code. + */ +int32_t uPortPppSetLocalDeviceName(const char *pDevice); + /** Attach a PPP interface to the bottom of the IP stack of a * platform. This is called by a ubxlib layer (e.g. cellular) * when a device is powered-up that is able to support PPP. This @@ -327,20 +358,29 @@ int32_t uPortPppAttach(void *pDevHandle, * name for PPP authentication; should * be set to NULL if no user name or * password is required. This value - * is currently IGNORED in the Zephyr + * is currently IGNORED (a) in the Zephyr, * case since the user name is hard-coded - * by Zephyr (see pap.c inside Zephyr). + * by Zephyr (see pap.c inside Zephyr) + * and (b) in the Linux case, since the + * username must be provided to pppd + * itself, there is no way for this code + * to pass it to pppd. * @param[in] pPassword pointer to a string giving the * password for PPP authentication; must * be non-NULL if pUsername is non-NULL, * ignored if pUsername is NULL. This - * value is currently IGNORED in the Zephyr + * value is currently IGNORED (a) in the Zephyr * case since the password is hard-coded - * by Zephyr (see pap.c inside Zephyr). + * by Zephyr (see pap.c inside Zephyr) and + * (b) in the Linux case, since the password + * must be provided to pppd itself, there + * is no way for this code to pass it to + * pppd. * @param authenticationMode the authentication mode, ignored if * pUsername is NULL; ignored by Zephyr * (PAP will be used if authentication is - * required). + * required) and not used in the Linux case + * for the reasons given above. * @return zero on success, else negative error * code. */ diff --git a/port/platform/common/automation/DATABASE.md b/port/platform/common/automation/DATABASE.md index b0fddbbb..bdd76da8 100644 --- a/port/platform/common/automation/DATABASE.md +++ b/port/platform/common/automation/DATABASE.md @@ -64,7 +64,7 @@ The table below defines the instances of test hardware available on the `ubxlib` | 25 | HPG Solution board (NINA-W1), live network | ESP32 | | ESP-IDF | | LARA_R6 M9 | port device network sock cell security mqtt_client gnss location geofence || U_CFG_GEOFENCE U_CFG_TEST_GNSS_POWER_SAVING_NOT_SUPPORTED U_CFG_TEST_DISABLE_MUX U_GNSS_MGA_TEST_ASSIST_NOW_AUTONOMOUS_NOT_SUPPORTED U_NETWORK_GNSS_CFG_CELL_USE_AT_ONLY U_HTTP_CLIENT_DISABLE_TEST U_CELL_CFG_TEST_USE_FIXED_TIME_SECONDS U_CFG_TEST_CELL_GEOFENCE U_CFG_MONITOR_DTR_RTS_OFF U_CELL_TEST_NO_INVALID_APN U_CELL_TEST_CFG_BANDMASK1=0x0000000000080084ULL U_CELL_NET_TEST_RAT=U_CELL_NET_RAT_LTE U_CELL_TEST_CFG_MNO_PROFILE=90 U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_CFG_APP_PIN_CELL_PWR_ON=0x800c U_CFG_APP_PIN_CELL_RESET=13 U_CELL_RESET_PIN_DRIVE_MODE=U_PORT_GPIO_DRIVE_MODE_NORMAL U_CFG_APP_PIN_CELL_VINT=0x8025 U_CFG_APP_PIN_CELL_DTR=15 U_CFG_APP_PIN_CELL_TXD=25 U_CFG_APP_PIN_CELL_RXD=26 U_CFG_APP_PIN_CELL_RTS=27 U_CFG_APP_PIN_CELL_CTS=36 U_CFG_APP_GNSS_I2C=0 U_GNSS_TEST_I2C_ADDRESS_EXTRA=0x43 U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_DEBUG_UTILS_DUMP_THREADS | | 26 | NINA-B4 | NRF52833 | ubx_evkninab4_nrf52833 | Zephyr | | M10 | port ubx_protocol gnss spartn | | U_CFG_APP_GNSS_I2C=0 U_CFG_APP_I2C_MAX_SEGMENT_SIZE=32 U_CFG_TEST_PIN_GNSS_RESET_N=30 U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 | | 27 | ESP32S3-DevKitC | ESP32S3 | | ESP-IDF | | M9 | port ubx_protocol gnss spartn | | U_CFG_TEST_GNSS_POWER_SAVING_NOT_SUPPORTED U_CFG_APP_GNSS_I2C=0 U_CFG_TEST_PIN_GNSS_RESET_N=40 U_GNSS_MGA_TEST_ASSIST_NOW_AUTONOMOUS_NOT_SUPPORTED U_CFG_TEST_PIN_A=1 U_CFG_TEST_PIN_B=9 U_CFG_TEST_PIN_C=38 U_CFG_TEST_PIN_UART_A_CTS=11 U_CFG_TEST_PIN_UART_A_RTS=47 U_CFG_TEST_PIN_UART_A_RXD=10 U_CFG_TEST_PIN_UART_A_TXD=48 U_CFG_APP_PIN_GNSS_SDA=18 U_CFG_APP_PIN_GNSS_SCL=17 U_CFG_MUTEX_DEBUG U_DEBUG_UTILS_DUMP_THREADS | -| 28 | Linux + EVK, Cat M1, uConnect | LINUX64 | | Linux | | SARA_R5 M9 NINA_W15 | port device network sock ble wifi cell short_range security mqtt_client http_client ubx_protocol gnss spartn location geofence |cell short_range gnss geodesic | U_CFG_GEOFENCE U_CFG_HEAP_MONITOR U_ASSERT_HOOK_FUNCTION_TEST_RETURN U_CFG_TEST_USE_VALGRIND U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_APP_UART_PREFIX=/dev/ttyAMA U_CFG_APP_CELL_UART=0 U_CFG_APP_PIN_CELL_PWR_ON=25 U_CELL_PWR_ON_PIN_DRIVE_MODE=U_PORT_GPIO_DRIVE_MODE_NORMAL U_CFG_APP_SHORT_RANGE_UART=1 U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=26 U_CFG_APP_PIN_SHORT_RANGE_CTS=0 U_CFG_APP_PIN_SHORT_RANGE_RTS=0 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_CFG_TEST_GNSS_SPI_SELECT_INDEX=0 U_CFG_APP_GNSS_SPI=0 U_CFG_APP_GNSS_I2C=8 U_CFG_TEST_PIN_GNSS_RESET_N=19 U_GNSS_MGA_TEST_HAS_FLASH U_CFG_TEST_UART_PREFIX=/tmp/ttyv U_CFG_TEST_UART_A=0 U_CFG_TEST_UART_B=1 U_AT_CLIENT_TEST_AT_TIMEOUT_TOLERANCE_MS=1000 U_CFG_TEST_PIN_A=17 U_CFG_TEST_PIN_B=27 U_CFG_TEST_PIN_C=22 U_CFG_MUTEX_DEBUG | +| 28 | Linux + EVK, Cat M1, uConnect | LINUX64 | | Linux | | SARA_R5 M9 NINA_W15 | port device network sock ble wifi cell short_range security mqtt_client http_client ubx_protocol gnss spartn location geofence |cell short_range gnss geodesic | U_CFG_PPP_ENABLE U_CFG_GEOFENCE U_CFG_HEAP_MONITOR U_ASSERT_HOOK_FUNCTION_TEST_RETURN U_CFG_TEST_USE_VALGRIND U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_APP_UART_PREFIX=/dev/ttyAMA U_CFG_APP_CELL_UART=0 U_CFG_APP_PIN_CELL_PWR_ON=25 U_CELL_PWR_ON_PIN_DRIVE_MODE=U_PORT_GPIO_DRIVE_MODE_NORMAL U_CFG_APP_SHORT_RANGE_UART=1 U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=26 U_CFG_APP_PIN_SHORT_RANGE_CTS=0 U_CFG_APP_PIN_SHORT_RANGE_RTS=0 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_CFG_TEST_GNSS_SPI_SELECT_INDEX=0 U_CFG_APP_GNSS_SPI=0 U_CFG_APP_GNSS_I2C=8 U_CFG_TEST_PIN_GNSS_RESET_N=19 U_GNSS_MGA_TEST_HAS_FLASH U_CFG_TEST_UART_PREFIX=/tmp/ttyv U_CFG_TEST_UART_A=0 U_CFG_TEST_UART_B=1 U_AT_CLIENT_TEST_AT_TIMEOUT_TOLERANCE_MS=1000 U_CFG_TEST_PIN_A=17 U_CFG_TEST_PIN_B=27 U_CFG_TEST_PIN_C=22 U_CFG_MUTEX_DEBUG | | 29 | HPG C214 board (NINA-W1), live network | ESP32 | | ESP-IDF | | LENA_R8 M9 | port device network sock cell security mqtt_client gnss location || U_CFG_PPP_ENABLE U_HTTP_CLIENT_DISABLE_TEST U_CELL_GPIO_DISABLE_TEST U_MQTT_CLIENT_TEST_NO_NULL_SEND U_CFG_TEST_GNSS_POWER_SAVING_NOT_SUPPORTED U_GNSS_MGA_TEST_ASSIST_NOW_AUTONOMOUS_NOT_SUPPORTED U_CELL_CFG_TEST_USE_FIXED_TIME_SECONDS U_CFG_MONITOR_DTR_RTS_OFF U_CELL_TEST_NO_INVALID_APN U_CELL_TEST_CFG_BANDMASK1=0x0000000000080084ULL U_CELL_NET_TEST_RAT=U_CELL_NET_RAT_LTE U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_CFG_APP_PIN_CELL_PWR_ON=0x801a U_CFG_APP_PIN_CELL_RESET=33 U_CELL_RESET_PIN_DRIVE_MODE=U_PORT_GPIO_DRIVE_MODE_NORMAL U_CFG_APP_PIN_CELL_VINT=0x8025 U_CFG_APP_PIN_CELL_DTR=15 U_CFG_APP_PIN_CELL_TXD=25 U_CFG_APP_PIN_CELL_RXD=34 U_CFG_APP_PIN_CELL_RTS=27 U_CFG_APP_PIN_CELL_CTS=36 U_CFG_APP_GNSS_I2C=0 U_GNSS_TEST_I2C_ADDRESS_EXTRA=0x43 U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_DEBUG_UTILS_DUMP_THREADS | | 30 | STM32F407 Discovery, NORA-W3, live network | STM32F4 | | STM32Cube | | LARA_R6 NORA_W36 | port device network sock ble wifi cell short_range security mqtt_client http_client location | cell short_range short_range_gen2 | CMSIS_V2 U_CFG_TEST_CELL_PWR_DISABLE HSE_VALUE=8000000U U_CELL_TEST_CFG_APN=iot.1nce.net U_CELL_CFG_TEST_USE_FIXED_TIME_SECONDS U_CELL_TEST_NO_INVALID_APN U_CELL_TEST_CFG_BANDMASK1=0x0000000000080084ULL U_CELL_NET_TEST_RAT=U_CELL_NET_RAT_LTE U_CELL_TEST_CFG_MNO_PROFILE=90 U_CFG_APP_PIN_C030_ENABLE_3V3=-1 U_CFG_APP_PIN_CELL_RESET=-1 U_CFG_APP_CELL_UART=2 U_CFG_APP_PIN_CELL_TXD=0x03 U_CFG_APP_PIN_CELL_RXD=0x02 U_CFG_APP_PIN_CELL_RTS=-1 U_CFG_APP_PIN_CELL_CTS=-1 U_CFG_TEST_PIN_A=-1 U_CFG_TEST_PIN_B=-1 U_CFG_TEST_PIN_C=-1 U_CFG_TEST_UART_A=-1 U_DEBUG_UTILS_DUMP_THREADS U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_BLE_TEST_CFG_REMOTE_SPS_PERIPHERAL=2462ABB6EAC6p U_CFG_APP_SHORT_RANGE_ROLE=3 | | 31 | STM32F7, Nucleo-F767ZI | STM32 | nucleo_f767zi | Zephyr | | | port | | U_DEBUG_UTILS_DUMP_THREADS | diff --git a/port/platform/common/automation/Jenkinsfile b/port/platform/common/automation/Jenkinsfile index 0f9617c1..4a690d43 100644 --- a/port/platform/common/automation/Jenkinsfile +++ b/port/platform/common/automation/Jenkinsfile @@ -426,7 +426,7 @@ def buildAndTestPipeline(instance_str, json_entry, filter) { } } finally { // Store summary-, debug- and test report file as artifacts - archiveArtifacts artifacts: "${instance_workdir}/**/${summary_file}, ${instance_workdir}/**/${test_report_file}, ${instance_workdir}/**/${debug_file}, ${instance_workdir}/**/*.hex, ${instance_workdir}/**/*.bin, ${instance_workdir}/**/*.elf, ${instance_workdir}/**/*.exe, ${instance_workdir}/**/*.map, ${instance_workdir}/*.kml, ${instance_workdir}/**/analyze_html/*", allowEmptyArchive: true + archiveArtifacts artifacts: "${instance_workdir}/**/${summary_file}, ${instance_workdir}/**/${test_report_file}, ${instance_workdir}/**/${debug_file}, ${instance_workdir}/**/*.hex, ${instance_workdir}/**/*.bin, ${instance_workdir}/**/*.elf, ${instance_workdir}/**/*.exe, ${instance_workdir}/**/*.map, ${instance_workdir}/*.kml, ${instance_workdir}/*.log, ${instance_workdir}/**/analyze_html/*", allowEmptyArchive: true if (test_report_arg != "") { // Record the test results junit(testResults: "${working_dir}/${instance_str}/${test_report_file}", allowEmptyResults: true, skipOldReports: true, skipPublishingChecks: true) diff --git a/port/platform/common/automation/docker/builder/Dockerfile b/port/platform/common/automation/docker/builder/Dockerfile index cb1509e5..864cda0d 100644 --- a/port/platform/common/automation/docker/builder/Dockerfile +++ b/port/platform/common/automation/docker/builder/Dockerfile @@ -148,12 +148,20 @@ WORKDIR /workdir RUN chmod 1777 /tmp && \ pip3 install pyserial pylint psutil pylink-square requests_toolbelt rpyc debugpy invoke==2.0.0 coloredlogs verboselogs && \ apt-get update && apt-get install -y --no-install-recommends \ - usbutils gawk iputils-ping openssh-client socat ppp \ + usbutils gawk iputils-ping openssh-client socat ppp picocom \ # Needed for OpenOCD libhidapi-hidraw0 && \ # Cleanup apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +# Set permission on pppd so that we can run it without having to be root +# and it doesn't require a serial modem to be a server capable of +# authenticating itself +RUN chown root /usr/sbin/pppd && chmod 4755 /usr/sbin/pppd && \ + sed 's/^auth/noauth/' /etc/ppp/options > options && \ + cp options /etc/ppp/options && \ + rm options + #*************************************************** # Setup environmental variables #*************************************************** diff --git a/port/platform/common/automation/scripts/u_run_linux.py b/port/platform/common/automation/scripts/u_run_linux.py index b2841c14..7dfbe968 100644 --- a/port/platform/common/automation/scripts/u_run_linux.py +++ b/port/platform/common/automation/scripts/u_run_linux.py @@ -44,6 +44,9 @@ # to the socat utility which does the redirection UART_BAUD_RATE = 115200 +# The device name that pppd will connect (i.e. U_PORT_PPP_LOCAL_SOCKET_NAME) +PPP_LOCAL_SOCKET_NAME = "127.0.0.1:5000" + # The start marker to use on a Valgrind print VALGRIND_START_MARKER = "VALGRIND SAYS:" @@ -100,7 +103,7 @@ def uart_to_device_list_create(u_flags, logger): uart_to_device["type"] = parts[0] uart_to_device["uart"] = u_cfg_test_uart_prefix + parts[1] uart_to_device_list.append(uart_to_device) - logger.info(uart_to_device["type"] + f': will be UART ' + \ + logger.info(uart_to_device["type"] + ': will be UART ' + \ uart_to_device["uart"]) elif flag.startswith("U_CFG_APP_") and "_UART=" in flag: parts = flag.split("=") @@ -116,8 +119,8 @@ def uart_to_device_list_create(u_flags, logger): for uart_to_device in uart_to_device_list: if uart_to_device["type"] == uart_type: uart_to_device["device_to"] = parts[1] - logger.info(uart_to_device["type"] + f': will be UART ' + \ - uart_to_device["uart"] + f' to ' + uart_to_device["device_to"]) + logger.info(uart_to_device["type"] + ': will be UART ' + \ + uart_to_device["uart"] + ' to ' + uart_to_device["device_to"]) break # When done, check if there were any U_CFG_APP_xxx_UART # entries without a corresponding U_CFG_APP_xxx_UART_DEV entry @@ -390,32 +393,35 @@ def run(ctx, instance, platform, board_name=DEFAULT_BOARD_NAME, build_dir=DEFAUL CONNECTION_LOCK_GUARD_TIME_SECONDS, logger=U_LOG) as locked_connection: if locked_connection: - # Create the UART loopbacks/redirections as directed by the list of defines - # - # For instance (noting NO quotation marks in the values of the defines): - # - # U_CFG_TEST_UART_PREFIX=/tmp/ttyv U_CFG_TEST_UART_A=0 - # - # ...would cause "/tmp/ttyv0" to be looped-back on itself, or: - # - # U_CFG_TEST_UART_PREFIX=/tmp/ttyv U_CFG_TEST_UART_A=0 U_CFG_TEST_UART_B=1 - # - # ...would cause "/tmp/ttyv0" to be looped-back to "/tmp/ttyv1", or: - # - # U_CFG_TEST_APP_PREFIX=/dev/tty U_CFG_APP_CELL_UART=0 U_CFG_APP_CELL_UART_DEV=2 - # - # ...would cause the cellular UART "/dev/tty0" to be redirected to "/dev/tty2" - uart_to_device_list = uart_to_device_list_create(defines, logger=U_LOG) - if uart_to_device_list: - redirect_uart_fixed(uart_to_device_list, ctx.reporter) - # Start the .exe and monitor what it spits out - try: + # Get pppd running, in case we're testing PPP + cmd = ["pppd", "socket", f"{PPP_LOCAL_SOCKET_NAME}", f"{UART_BAUD_RATE}", + "passive", "persist", "maxfail", "0", "local", "defaultroute"] + with u_utils.ExeRun(cmd, logger=U_LOG) as process: + # Create the UART loopbacks/redirections as directed by the list of defines + # + # For instance (noting NO quotation marks in the values of the defines): + # + # U_CFG_TEST_UART_PREFIX=/tmp/ttyv U_CFG_TEST_UART_A=0 + # + # ...would cause "/tmp/ttyv0" to be looped-back on itself, or: + # + # U_CFG_TEST_UART_PREFIX=/tmp/ttyv U_CFG_TEST_UART_A=0 U_CFG_TEST_UART_B=1 + # + # ...would cause "/tmp/ttyv0" to be looped-back to "/tmp/ttyv1", or: + # + # U_CFG_TEST_APP_PREFIX=/dev/tty U_CFG_APP_CELL_UART=0 U_CFG_APP_CELL_UART_DEV=2 + # + # ...would cause the cellular UART "/dev/tty0" to be redirected to "/dev/tty2" + uart_to_device_list = uart_to_device_list_create(defines, logger=U_LOG) + if uart_to_device_list: + redirect_uart_fixed(uart_to_device_list, ctx.reporter) # Start the executable and monitor what it spits out call_list = [exe_path] if "U_CFG_TEST_USE_VALGRIND" in defines: call_list = ["valgrind"] + ["--leak-check=yes"] + \ [f"--error-markers={VALGRIND_START_MARKER}"] + \ - [f"--suppressions={VALGRIND_SUPPRESSION_PATH}"] + call_list + [f"--suppressions={VALGRIND_SUPPRESSION_PATH}"] + \ + call_list # If you need Valgrind to suppress more errors, the best way # to go about that is to temporarily use the following line @@ -432,7 +438,8 @@ def run(ctx, instance, platform, board_name=DEFAULT_BOARD_NAME, build_dir=DEFAUL # has been sent SIGINT, at which point we are no longer monitoring # its output, hence no leak error will be flagged by this script # (though they will still be there in the logged output) - u_monitor.callback(valgrind_callback, VALGRIND_REGEX, valgrind_error_counter) + u_monitor.callback(valgrind_callback, VALGRIND_REGEX, + valgrind_error_counter) with u_utils.ExeRun(call_list, logger=U_LOG) as process: return_value = u_monitor.main(process, u_monitor.CONNECTION_PROCESS, @@ -451,11 +458,6 @@ def run(ctx, instance, platform, board_name=DEFAULT_BOARD_NAME, build_dir=DEFAUL # Remove the redirections for device_redirect in DEVICE_REDIRECTS: u_utils.device_redirect_stop(device_redirect) - except KeyboardInterrupt as ex: - # Remove the redirections in case of CTRL-C - for device_redirect in DEVICE_REDIRECTS: - u_utils.device_redirect_stop(device_redirect) - raise KeyboardInterrupt from ex else: ctx.reporter.event(u_report.EVENT_TYPE_INFRASTRUCTURE, u_report.EVENT_FAILED, diff --git a/port/platform/linux/README.md b/port/platform/linux/README.md index 3516a216..e2209c8f 100644 --- a/port/platform/linux/README.md +++ b/port/platform/linux/README.md @@ -4,30 +4,61 @@ These directories provide the implementation of the porting layer on native Linu # Building All software building for this platform is intended to be made using [CMake](https://cmake.org/). -There are typically two scenarios when it comes to building Linux application which includes ubxlib. +There are typically two scenarios when it comes to building Linux application which includes `ubxlib`. The first case is to build the test runner application within this repo. More information about this can be [found here](mcu/posix/runner/README.md). -On the other hand if you want to add ubxlib to an existing or new Linux application of your own you just have to add the following text your *CMakeLists.txt* file +On the other hand if you want to add `ubxlib` to an existing or new Linux application of your own you just have to add the following text your `CMakeLists.txt` file include(DIRECTORY_WHERE_YOU_HAVE_PUT_UBXLIB/port/platform/linux/linux.cmake) target_link_libraries(YOUR_APPLICATION_NAME ubxlib ${UBXLIB_REQUIRED_LINK_LIBS}) target_include_directories(YOUR_APPLICATION_NAME PUBLIC ${UBXLIB_INC} ${UBXLIB_PUBLIC_INC_PORT}) - # Visual Studio Code Both case listed above can also be made from within Visual Studio Code (on the Linux platform). -In the first case you can just open the predefined Visual Studio project file available in the root directory of this repository, *ubxlib-runner.code-workspace*. You can then select the *Build Linux runner* build target to start a build, and then the *Linux runner* debug target to start debugging. +In the first case you can just open the predefined Visual Studio project file available in the root directory of this repository, `ubxlib-runner.code-workspace`. You can then select the `Build Linux runner` build target to start a build, and then the `Linux runner` debug target to start debugging. -In the second case you have to install the [Cmake extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools). +In the second case you have to install the [CMake extension for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools). More information on how to use CMake in Visual Studio Code can be [found here](https://code.visualstudio.com/docs/cpp/CMake-linux). +# PPP-Level Integration With Cellular +If you wish to use a cellular connection directly as an IP transport with Linux, you may do so using `pppd`, installed as the package `ppp` in the usual way. The setup will look something like this: + +``` + +----------+ +------------------+ +--------------+ +-----------------+ \|/ + IP address | | socket | Your application | | | UART/USB | | | + inside o----| pppd |<-------->| using ubxlib |<----->| /dev/ttyXXX0 |<---------->| Cellular module |<------+ + Linux | | | | | | | | + +----------+ +------------------+ +--------------+ +-----------------+ + ^ ^ + \ / + ------------------------- PPP over serial ------------------------- +``` + +In words: `ubxlib` exposes a socket (by default `5000`) which `pppd` is able to connect to, `ubxlib` then connects on to the cellular module via a physical serial port, all of which means that `pppd` is able to make a PPP-over-serial connection to the PPP entity inside the cellular module, which has the connection to the public internet of the cellular network. + +For PPP connectivity to be available you need to define `U_CFG_PPP_ENABLE` when building `ubxlib`, e.g. by including it in the `U_FLAGS` environment variable which `linux.cmake` looks for. Your application code must configure `ubxlib` to know what serial port the cellular module is connected to (the `/dev/ttyXXX0` above, e.g. `/dev/ttyAMA0` for UART0 of a Raspberry Pi) and the Linux port layer here inside `ubxlib` will listen on socket `U_PORT_PPP_LOCAL_DEVICE_NAME` (i.e. `127.0.0.1:5000`) for a connection at the other end from `pppd`. + +`pppd` should be run as follows: + +``` +pppd socket 127.0.0.1:5000 115200 passive persist maxfail 0 local defaultroute +``` + +This will cause `pppd` to try connecting to `ubxlib` on port `5000`, persistently, until it is terminated. If you have permissions problems running `pppd`, take a look [here](https://tldp.org/HOWTO/PPP-HOWTO/root.html) to find the best way to resolve them: when we run `pppd` in the `ubxlilb` test system we have `pppd` `setuid` and we put `noauth` in the `/etc/ppp/options` file as the test system is secured at the perimeter, miscreants cannot get in; if you do not wish to rely on this in your scenario then you may need to configure a peer address within `pppd` and use `pppd call` or some such. If you need to use a socket other than `5000`, then in your application you may do this by calling `uPortPppSetLocalDeviceName()` with a revised socket address, e.g. `127.0.0.1:6000`, **before** you call `uDeviceOpen()`. + +IMPORTANT: if your cellular service provider requires you to enter a username and/or password for authentication then you must provide those parameters to `pppd`, since there is no way for the `ubxlib` code to supply them to `pppd`. + +Once `pppd` is running you may start your application, which will call `ubxlib` in the usual way to open the cellular device and connect it to the network: you can find an example of how to do this in [main_ppp_linux.c](/example/sockets/main_ppp_linux.c). + +You may also find [the rest of this extremely detailed HOWTO](https://tldp.org/HOWTO/PPP-HOWTO/index.html) for `pppd` useful. + # Limitations Some limitations apply on this platform: - Linux does not provide an implementation of critical sections, something which this code relies upon for cellular power saving (specifically, the process of waking up from cellular power-saving) hence cellular power saving cannot be used from a Linux build. -- On a Raspberry Pi (any flavour), the I2C HW implementation of the Broadcom chip does not correctly support clock stretching, which is required for u-blox GNSS devices, hence it is recommended that, if you are using I2C to talk to the GNSS device, you use the bit-bashing I2C drive to avoid data loss, e.g. by adding the line `dtoverlay=i2c-gpio,i2c_gpio_sda=2,i2c_gpio_scl=3,i2c_gpio_delay_us=2,bus=8` to `/boot/config.txt` and NOT uncommenting the `i2c_arm` line in the same file. +- On a Raspberry Pi (any flavour), the I2C HW implementation of the Broadcom chip does not correctly support clock stretching, which is required for u-blox GNSS devices, hence it is recommended that, if you are using I2C to talk to the GNSS device, you use the bit-bashing I2C driver to avoid data loss, e.g. by adding the line `dtoverlay=i2c-gpio,i2c_gpio_sda=2,i2c_gpio_scl=3,i2c_gpio_delay_us=2,bus=8` to `/boot/config.txt` and NOT uncommenting the `i2c_arm` line in the same file. - Use of GPIO chips above 0 are supported but NOT when the pin in question is to be an output that must be set high at initialisation; this is because the `uPortGpioConfig()` call is what tells this code to use an index other than 0 and, to set an output pin high at initialisation, `uPortGpioSet()`, has to be called _before_ `uPortGpioConfig()`. - All testing has been carried out on a 64-bit Raspberry Pi 4. \ No newline at end of file diff --git a/port/platform/linux/linux.cmake b/port/platform/linux/linux.cmake index 067d3a83..3927d6d2 100644 --- a/port/platform/linux/linux.cmake +++ b/port/platform/linux/linux.cmake @@ -60,8 +60,15 @@ set(UBXLIB_SRC_PORT ${UBXLIB_BASE}/port/platform/${UBXLIB_PLATFORM}/src/u_port_uart.c ${UBXLIB_BASE}/port/platform/${UBXLIB_PLATFORM}/src/u_port_i2c.c ${UBXLIB_BASE}/port/platform/${UBXLIB_PLATFORM}/src/u_port_spi.c + ${UBXLIB_BASE}/port/platform/${UBXLIB_PLATFORM}/src/u_port_ppp.c ${UBXLIB_BASE}/port/clib/u_port_clib_mktime64.c) +# Add the platform-specific tests and examples +list(APPEND UBXLIB_TEST_SRC + ${UBXLIB_BASE}/port/platform/${UBXLIB_PLATFORM}/test/u_linux_ppp_test.c + ${UBXLIB_BASE}/example/sockets/main_ppp_linux.c +) + # Generate a library of ubxlib add_library(ubxlib OBJECT ${UBXLIB_SRC} ${UBXLIB_SRC_PORT}) message("UBXLIB_COMPILE_OPTIONS will be \"${UBXLIB_COMPILE_OPTIONS}\"") diff --git a/port/platform/linux/src/u_port.c b/port/platform/linux/src/u_port.c index 3185b97e..c66cf3d8 100644 --- a/port/platform/linux/src/u_port.c +++ b/port/platform/linux/src/u_port.c @@ -44,6 +44,7 @@ #include "u_port_uart.h" #include "u_port_event_queue_private.h" #include "u_port_os_private.h" +#include "u_port_ppp_private.h" /* ---------------------------------------------------------------- * COMPILE-TIME MACROS @@ -94,12 +95,16 @@ int32_t uPortInit() if (errorCode == 0) { errorCode = uPortUartInit(); } + if (errorCode == 0) { + errorCode = uPortPppPrivateInit(); + } return errorCode; } // Deinitialise the porting layer. void uPortDeinit() { + uPortPppPrivateDeinit(); uPortUartDeinit(); uPortEventQueuePrivateDeinit(); uPortOsPrivateDeinit(); diff --git a/port/platform/linux/src/u_port_ppp.c b/port/platform/linux/src/u_port_ppp.c new file mode 100644 index 00000000..8a22a3bd --- /dev/null +++ b/port/platform/linux/src/u_port_ppp.c @@ -0,0 +1,985 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @file + * @brief This file allows a connection to be made from pppd to a + * PPP interface inside ubxlib. Such a PPP interface is provided by + * a cellular module. + * + * See port/platform/linux/README.md for a description of how it works. + * + * It is only compiled if U_CFG_PPP_ENABLE is defined. + */ + +#ifdef U_CFG_OVERRIDE +# include "u_cfg_override.h" // For a customer's configuration override +#endif +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. +#include "stdbool.h" +#include "string.h" // strlen(), memset() + +#include "pthread.h" // threadId +#include "sys/socket.h" +#include "netinet/in.h" +#include "arpa/inet.h" +#include "unistd.h" +#include "errno.h" +#include "sys/types.h" + +#include "u_cfg_os_platform_specific.h" // U_CFG_OS_PRIORITY_MAX +#include "u_cfg_sw.h" +#include "u_error_common.h" + +#include "u_linked_list.h" + +#include "u_sock.h" + +#include "u_port.h" +#include "u_port_os.h" +#include "u_port_debug.h" +#include "u_port_heap.h" +#include "u_port_ppp.h" +#include "u_port_ppp_private.h" + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +#ifndef U_PORT_PPP_LOCAL_DEVICE_NAME +/** The name of the device that is the PPP entity at the bottom + * of the IP stack on this MCU, i.e. the Linux-end of the PPP + * link that pppd will connect to. + */ +# define U_PORT_PPP_LOCAL_DEVICE_NAME "127.0.0.1:5000" +#endif + +#ifndef U_PORT_PPP_CONNECT_TIMEOUT_SECONDS +/** How long to wait for PPP to connect. + */ +# define U_PORT_PPP_CONNECT_TIMEOUT_SECONDS 15 +#endif + +#ifndef U_PORT_PPP_DISCONNECT_TIMEOUT_SECONDS +/** How long to wait for PPP to disconnect. + */ +# define U_PORT_PPP_DISCONNECT_TIMEOUT_SECONDS 10 +#endif + +#ifndef U_PORT_PPP_TX_LOOP_GUARD +/** How many times around the transmit loop to allow if stuff + * won't send. + */ +# define U_PORT_PPP_TX_LOOP_GUARD 1000 +#endif + +#ifndef U_PORT_PPP_TX_LOOP_DELAY_MS +/** How long to wait between transmit attempts in milliseconds + * when the data to transmit won't go all at once. + */ +# define U_PORT_PPP_TX_LOOP_DELAY_MS 10 +#endif + +#ifndef U_PORT_PPP_SOCKET_TASK_STACK_SIZE_BYTES +/** The stack size for the callback that is listening for + * the pppd connection locally and shipping data out from it. + */ +# define U_PORT_PPP_SOCKET_TASK_STACK_SIZE_BYTES (1024 * 5) +#endif + +#ifndef U_PORT_PPP_SOCKET_TASK_PRIORITY +/** The priority of the task that is listening for the pppd + * connection locally receiving data fro it, should + * be relatively high (e.g. U_CFG_OS_PRIORITY_MAX - 5, which is + * the same as the AT Client URC task). + */ +# define U_PORT_PPP_SOCKET_TASK_PRIORITY (U_CFG_OS_PRIORITY_MAX - 5) +#endif + +#ifndef U_PORT_PPP_BUFFER_CACHE_SIZE +/** pppd has no way to tell this code that the link is up, + * so we keep a small cache of the communications in both + * directions that we can monitor to see what's going on. + * IMPORTANT: this must be at least as big as + * gPppEncapsulatedIpcpPacketStart[], gLcpTerminateReqPacket[], + * gLcpTerminateAckPacket[] and gConnectionTerminatedString[] for + * this code to work. + */ +# define U_PORT_PPP_BUFFER_CACHE_SIZE 64 +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +#ifdef U_CFG_PPP_ENABLE + +/** A structure to contain a buffer, used for monitoring + * communications between the PPP entities. + */ +typedef struct { + char buffer[U_PORT_PPP_BUFFER_CACHE_SIZE]; + size_t size; +} uPortPppBufferCache_t; + +/** Define a PPP interface. + */ +typedef struct { + void *pDevHandle; + int listeningSocket; // int type since this is a native socket + int connectedSocket; + uPortTaskHandle_t socketTaskHandle; + uPortMutexHandle_t socketTaskMutex; + bool socketTaskExit; + uPortPppBufferCache_t fromModuleBufferCache; + uPortPppBufferCache_t fromPppdBufferCache; + bool dataTransferSuspended; + uPortPppConnectCallback_t *pConnectCallback; + uPortPppDisconnectCallback_t *pDisconnectCallback; + uPortPppTransmitCallback_t *pTransmitCallback; + bool pppRunning; + bool ipConnected; + bool waitingForModuleDisconnect; +} uPortPppInterface_t; + +/** Structure to hold the name of the MCU-end PPP device; + * used to ensure thread-safety between calls to + * uPortPppSetLocalDeviceName() and uPortPppAttach(). + */ +typedef struct { + char name[U_PORT_PPP_LOCAL_DEVICE_NAME_LENGTH + 1]; // +1 for terminator + pthread_t threadId; +} uPortPppLocalDevice_t; + +#endif // #ifdef U_CFG_PPP_ENABLE + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +#ifdef U_CFG_PPP_ENABLE + +/** Root of the linked list of PPP entities. + */ +static uLinkedList_t *gpPppInterfaceList = NULL; /**< A list of uPortPppInterface_t. */ + +/** Root of linked list of local device names. + */ +static uLinkedList_t *gpPppLocalDeviceNameList = NULL; /**< A list of uPortPppLocalDevice_t. */ + +/** Mutex to protect the linked list of PPP entities. + */ +static uPortMutexHandle_t gMutex = NULL; + +/** The bytes that represent the start of a PPP-encapsulated + * IPCP packet. + */ +static const char gPppEncapsulatedIpcpPacketStart[] = {0x7e, 0x80, 0x21}; + +/** The bytes that represent a normal LCP Terminate-Req. + */ +static const char gLcpTerminateReqPacket[] = {0x7e, 0xff, 0x7d, 0x23, 0xc0, 0x21, 0x7d, 0x25, + 0x7d, 0x22, 0x7d, 0x20, 0x7d, 0x30, 0x55, 0x73, + 0x65, 0x72, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x53, 0x33, 0x7e + }; + +/** The bytes that represent an LCP Terminate-Ack for + * gLcpTerminateReqPacket[]. + */ +static const char gLcpTerminateAckPacket[] = {0x7e, 0xff, 0x7d, 0x23, 0xc0, 0x21, 0x7d, 0x26, + 0x7d, 0x22, 0x7d, 0x20, 0x7d, 0x24, 0x94, 0x7d, + 0x2d, 0x7e + }; +/** The string that the cellular module sends in response + * to an gLcpTerminateReqPacket[]. + */ +static const char gConnectionTerminatedString[] = {'\r', '\n', 'N', 'O', ' ', 'C', 'A', 'R', + 'R', 'I', 'E', 'R', '\r', '\n' + }; + +#endif // #ifdef U_CFG_PPP_ENABLE + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +#ifdef U_CFG_PPP_ENABLE + +// Find the local device name set by the given thread. +static uPortPppLocalDevice_t *pFindLocalDeviceName(pthread_t threadId) +{ + uPortPppLocalDevice_t *pLocalDevice = NULL; + uLinkedList_t *pList = gpPppLocalDeviceNameList; + + while ((pList != NULL) && (pLocalDevice == NULL)) { + if (((uPortPppLocalDevice_t *) pList->p)->threadId == threadId) { + pLocalDevice = (uPortPppLocalDevice_t *) pList->p; + } else { + pList = pList->pNext; + } + } + + return pLocalDevice; +} + +// Find the PPP interface structure for the given handle. +static uPortPppInterface_t *pFindPppInterface(void *pDevHandle) +{ + uPortPppInterface_t *pPppInterface = NULL; + uLinkedList_t *pList = gpPppInterfaceList; + + while ((pList != NULL) && (pPppInterface == NULL)) { + if (((uPortPppInterface_t *) pList->p)->pDevHandle == pDevHandle) { + pPppInterface = (uPortPppInterface_t *) pList->p; + } else { + pList = pList->pNext; + } + } + + return pPppInterface; +} + +// Add the new pBuffer contents to pBufferCache and then determine +// if the buffer cache contains pBufferWanted. If it does not but +// there is a partial match then the contents of pBufferCache are +// moved down to remove the uninteresting bits, else pBufferCache +// is cleared. +// IMPORTANT: since this removes the cached buffer contents based +// on whether there is a match or not it will ONLY WORK if a given +// cache buffer is searched for one set of wanted stuff at a time. +static bool bufferContains(uPortPppBufferCache_t *pBufferCache, + const char *pBuffer, size_t size, + const char *pBufferWanted, size_t bufferLength) +{ + size_t count = 0; + size_t startOffset = 0; + size_t x; + + if (pBufferCache != NULL) { + if (pBuffer != NULL) { + // Copy as much of the new data as we can into the buffer cache + if (size > sizeof(pBufferCache->buffer) - pBufferCache->size) { + size = sizeof(pBufferCache->buffer) - pBufferCache->size; + } + memcpy(pBufferCache->buffer + pBufferCache->size, pBuffer, size); + pBufferCache->size += size; + } + if (pBufferWanted != NULL) { + // Check for a match + for (x = 0; (x < pBufferCache->size) && (count < bufferLength); x++) { + if (pBufferCache->buffer[x] == *(pBufferWanted + count)) { + count++; + } else { + count = 0; + if (pBufferCache->buffer[x] == *pBufferWanted) { + count = 1; + } else { + startOffset = x; + } + } + } + if ((count > 0) && (count < bufferLength)) { + // Partial match, move the contents of the cached buffer + // down to remove the uninteresting bits + x = pBufferCache->size - startOffset; + memmove(pBufferCache->buffer, pBufferCache->buffer + startOffset, x); + pBufferCache->size = x; + } else { + // Either a complete match or no match, clear the cache + pBufferCache->size = 0; + } + } + } + + return (count == bufferLength); +} + +// Do a select on a socket with a timeout in milliseconds. +static bool socketSelect(int socket, int32_t timeoutMs) +{ + fd_set set; + struct timeval timeout = {0}; + + FD_ZERO(&set); + FD_SET(socket, &set); + timeout.tv_usec = timeoutMs * 1000; + return (select(socket + 1, &set, NULL, NULL, &timeout) > 0); +} + +// Terminate a PPP link. +static void terminateLink(uPortPppInterface_t *pPppInterface) +{ + char buffer[128]; + int32_t dataSize; + int32_t sent; + const char *pData; + size_t retryCount = 0; + int32_t startTimeMs; + bool pppdConnected = (pPppInterface->connectedSocket >= 0); + + // First, suspend normal data transfer between the entities + pPppInterface->dataTransferSuspended = true; + + // Start by terminating the cellular side + if (pPppInterface->pTransmitCallback != NULL) { + pPppInterface->waitingForModuleDisconnect = true; + pData = gLcpTerminateReqPacket; + dataSize = sizeof(gLcpTerminateReqPacket); + while ((dataSize > 0) && (retryCount < U_PORT_PPP_TX_LOOP_GUARD)) { + sent = pPppInterface->pTransmitCallback(pPppInterface->pDevHandle, + pData, dataSize); + if (sent > 0) { + dataSize -= sent; + pData += sent; + } else { + retryCount++; + uPortTaskBlock(U_PORT_PPP_TX_LOOP_DELAY_MS); + } + } + } + + if (pPppInterface->connectedSocket >= 0) { + // While we are waiting for a response (which will be + // picked up by moduleDataCallback() by setting + // pPppInterface->waitingForModuleDisconnect to false), + // terminate pppd on the MCU-side + pData = gLcpTerminateReqPacket; + dataSize = sizeof(gLcpTerminateReqPacket); + retryCount = 0; + while ((dataSize > 0) && (retryCount < U_PORT_PPP_TX_LOOP_GUARD)) { + // Note: send() is like write() but, when passed MSG_NOSIGNAL, + // it returns an error if the far end has closed the socket, + // rather than causing Linux to throw a signal 13 (SIGPIPE) + // exception which the application would have to handle + sent = send(pPppInterface->connectedSocket, pData, dataSize, MSG_NOSIGNAL); + if (sent > 0) { + dataSize -= sent; + pData += sent; + } else { + retryCount++; + uPortTaskBlock(U_PORT_PPP_TX_LOOP_DELAY_MS); + } + } + } + + // Wait for the response from pppd on the MCU side, and + // from the cellular side (via the waitingForModuleDisconnect flag) + startTimeMs = uPortGetTickTimeMs(); + while ((pPppInterface->waitingForModuleDisconnect || pppdConnected) && + (uPortGetTickTimeMs() - startTimeMs < U_PORT_PPP_CONNECT_TIMEOUT_SECONDS * 1000)) { + // Wait for data to arrive on the connected socket + if (pppdConnected && + socketSelect(pPppInterface->connectedSocket, U_CFG_OS_YIELD_MS)) { + // Read the data + dataSize = read(pPppInterface->connectedSocket, buffer, sizeof(buffer)); + if ((dataSize > 0) && + bufferContains(&(pPppInterface->fromPppdBufferCache), + buffer, dataSize, + gLcpTerminateAckPacket, + sizeof(gLcpTerminateAckPacket))) { + pppdConnected = false; + } + } + uPortTaskBlock(250); + } + + if (!pppdConnected && !pPppInterface->waitingForModuleDisconnect) { + pPppInterface->ipConnected = false; + pPppInterface->pppRunning = false; + } + + // Give up waiting now whatever + pPppInterface->waitingForModuleDisconnect = false; +} + +// Callback for when data is received from the cellular side. +static void moduleDataCallback(void *pDevHandle, const char *pData, + size_t dataSize, void *pCallbackParam) +{ + uPortPppInterface_t *pPppInterface = (uPortPppInterface_t *) pCallbackParam; + int32_t x = dataSize; + const char *pTmp = pData; + int32_t written = 0; + size_t retryCount = 0; + + // Write the data to the connected socket, if there is one + while (!pPppInterface->dataTransferSuspended && (x > 0) && + (written >= 0) && (retryCount < U_PORT_PPP_TX_LOOP_GUARD) && + (pPppInterface->connectedSocket >= 0)) { + written = send(pPppInterface->connectedSocket, pTmp, x, MSG_NOSIGNAL); + if (written > 0) { + x -= written; + pTmp += written; + } else { + retryCount++; + uPortTaskBlock(U_PORT_PPP_TX_LOOP_DELAY_MS); + } + } + // Note: the check below is performed even when data transfer + // is suspended as we may still be expecting a disconnect + if ((dataSize > 0) && pPppInterface->waitingForModuleDisconnect) { + if (bufferContains(&(pPppInterface->fromModuleBufferCache), + pData, dataSize, gConnectionTerminatedString, + sizeof(gConnectionTerminatedString))) { + pPppInterface->waitingForModuleDisconnect = false; + } + } +} + +// Task to listen on a socket for a pppd connection and pull data from it. +static void socketTask(void *pParameters) +{ + uPortPppInterface_t *pPppInterface = (uPortPppInterface_t *) pParameters; + char buffer[1024]; // Can be this big because we have allowed enough room on the stack + char *pData; + int32_t x; + int32_t dataSize; + int32_t written; + size_t retryCount; + + // Lock the task mutex to indicate that we're running + U_PORT_MUTEX_LOCK(pPppInterface->socketTaskMutex); + + // "1" here for just one connection at a time + listen(pPppInterface->listeningSocket, 1); + + while (!pPppInterface->socketTaskExit) { + // Wait for a connection using select with a timeout, don't block + if (socketSelect(pPppInterface->listeningSocket, U_CFG_OS_YIELD_MS)) { + // Got some data on the listening socket, accept the connection + pPppInterface->connectedSocket = accept(pPppInterface->listeningSocket, NULL, NULL); + if (pPppInterface->connectedSocket >= 0) { + uPortLog("U_PORT_PPP: pppd has connected to socket.\n"); + while ((pPppInterface->connectedSocket >= 0) && + !pPppInterface->socketTaskExit) { + // Wait for data to arrive on the connected socket + if (socketSelect(pPppInterface->connectedSocket, U_CFG_OS_YIELD_MS) && + !pPppInterface->dataTransferSuspended) { + // Read the data + dataSize = read(pPppInterface->connectedSocket, buffer, sizeof(buffer)); + if (dataSize > 0) { + if ((pPppInterface->pTransmitCallback != NULL) && + !pPppInterface->dataTransferSuspended) { + // Write the data to the cellular module + retryCount = 0; + pData = buffer; + x = dataSize; + written = 0; + while (pPppInterface->pppRunning && + !pPppInterface->dataTransferSuspended && + (x > 0) && (written >= 0) && (retryCount < U_PORT_PPP_TX_LOOP_GUARD)) { + written = pPppInterface->pTransmitCallback(pPppInterface->pDevHandle, pData, x); + if (written > 0) { + x -= written; + pData += written; + } else { + retryCount++; + uPortTaskBlock(U_PORT_PPP_TX_LOOP_DELAY_MS); + } + } + if ((dataSize > 0) && !pPppInterface->ipConnected) { + // If the connection is not already flagged as IP-connected, + // check the buffer of data for the start of an encapsulated + // IPCP frame, which indicates that we are done with the LCP + // part, the only part that could fail: we are connected. + pPppInterface->ipConnected = bufferContains(&(pPppInterface->fromPppdBufferCache), + buffer, dataSize, + gPppEncapsulatedIpcpPacketStart, + sizeof(gPppEncapsulatedIpcpPacketStart)); + } + } + } else if (dataSize == 0) { + // If select() indicated there was data and yet + // reading the data gives us nothing then this + // is the socket telling us that the far-end has + // closed it + close(pPppInterface->connectedSocket); + pPppInterface->connectedSocket = -1; + } + } + } + if (pPppInterface->socketTaskExit) { + // If we have been told to exit then close + // the connected socket on the way out + close(pPppInterface->connectedSocket); + pPppInterface->connectedSocket = -1; + uPortLog("U_PORT_PPP: pppd has been disconnected from socket.\n"); + } else { + uPortLog("U_PORT_PPP: pppd has disconnected from socket.\n"); + } + } + } else { + uPortTaskBlock(250); + } + } + close(pPppInterface->listeningSocket); + uPortLog("U_PORT_PPP: no longer listening for pppd on socket.\n"); + + // Unlock the task mutex to indicate we're done + U_PORT_MUTEX_UNLOCK(pPppInterface->socketTaskMutex); + + uPortTaskDelete(NULL); +} + +// Start a listening task on the address given. +static int32_t startSocketTask(uPortPppInterface_t *pPppInterface, + const char *pAddressString) +{ + int32_t errorCode; + struct sockaddr_in socketAddress = {0}; + uSockAddress_t sockUbxlib; + int reuse = 1; + + errorCode = uSockStringToAddress(pAddressString, &sockUbxlib); + if (errorCode == 0) { + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + // Create a listening socket and bind the given address to it + pPppInterface->connectedSocket = -1; + pPppInterface->listeningSocket = socket(AF_INET, SOCK_STREAM, 0); + if (pPppInterface->listeningSocket >= 0) { + // Set SO_REUSEADDR (and, in some cases SO_REUSEPORT) so that we + // can re-bind to the socket when we come back into here + if (setsockopt(pPppInterface->listeningSocket, SOL_SOCKET, SO_REUSEADDR, + (const char *) &reuse, sizeof(reuse)) < 0) { + // This is not fatal, it just means that the OS might prevent + // us binding to the same address again if we come back into + // here too quickly + uPortLog("U_PORT_PPP: *** WARNING *** setting socket option SO_REUSEADDR" + " returned errno %d.\n", errno); + } +#ifdef SO_REUSEPORT + if (setsockopt(pPppInterface->listeningSocket, SOL_SOCKET, SO_REUSEPORT, + (const char *) &reuse, sizeof(reuse)) < 0) { + // This is not fatal, it just means that the OS might prevent + // us binding to the same address again if we come back into + // here too quickly + uPortLog("U_PORT_PPP: *** WARNING *** setting socket option SO_REUSEPORT" + " returned errno %d.\n", errno); + } +#endif + errorCode = (int32_t) U_ERROR_COMMON_INVALID_ADDRESS; + socketAddress.sin_family = AF_INET; + if (sockUbxlib.ipAddress.type == U_SOCK_ADDRESS_TYPE_V4) { + socketAddress.sin_addr.s_addr = htonl(sockUbxlib.ipAddress.address.ipv4); + } else { + // TODO: find out how this copy should work for an IPV6 address + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + } + socketAddress.sin_port = htons(sockUbxlib.port); + if (bind(pPppInterface->listeningSocket, + (struct sockaddr *) &socketAddress, + sizeof(socketAddress)) == 0) { + // Now kick off a task that will listen on that socket + // and read data from anything that attaches to it + errorCode = uPortMutexCreate(&(pPppInterface->socketTaskMutex)); + if (errorCode == 0) { + errorCode = uPortTaskCreate(socketTask, + "pppSocketTask", + U_PORT_PPP_SOCKET_TASK_STACK_SIZE_BYTES, + pPppInterface, + U_PORT_PPP_SOCKET_TASK_PRIORITY, + &(pPppInterface->socketTaskHandle)); + if (errorCode == 0) { + uPortLog("U_PORT_PPP: listening for pppd on socket %s.\n", pAddressString); + } else { + uPortMutexDelete(pPppInterface->socketTaskMutex); + close(pPppInterface->listeningSocket); + } + } else { + close(pPppInterface->listeningSocket); + } + } else { + uPortLog("U_PORT_PPP: *** WARNING *** bind() to \"%s\" returned errno %d.\n", + pAddressString, errno); + } + } + } + + return errorCode; +} + +// Stop the listening task. +static void stopSocketTask(uPortPppInterface_t *pPppInterface) +{ + // Set the flag to make the socket task exit + pPppInterface->socketTaskExit = true; + // Wait for the task to exit + U_PORT_MUTEX_LOCK(pPppInterface->socketTaskMutex); + U_PORT_MUTEX_UNLOCK(pPppInterface->socketTaskMutex); + // Free the mutex + uPortMutexDelete(pPppInterface->socketTaskMutex); + pPppInterface->socketTaskMutex = NULL; +} + +// Disconnect a PPP interface. +static void pppDisconnect(uPortPppInterface_t *pPppInterface) +{ + bool wasRunning = false; + + if (pPppInterface != NULL) { + if (pPppInterface->pppRunning) { + wasRunning = true; + // We don't have control over pppd, can't tell it to + // disconnect the PPP link, which is kinda vital, + // so instead we take control of the link and terminate + // both sides ourselves + terminateLink(pPppInterface); + } + if (pPppInterface->pDisconnectCallback != NULL) { + pPppInterface->pDisconnectCallback(pPppInterface->pDevHandle, + pPppInterface->pppRunning); + } + pPppInterface->pppRunning = false; + if (wasRunning) { + uPortLog("U_PORT_PPP: socket disconnected from module (but pppd still connected to socket).\n"); + } + } +} + +#endif // #ifdef U_CFG_PPP_ENABLE + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS THAT ARE PRIVATE TO THIS PORT LAYER + * -------------------------------------------------------------- */ + +#ifdef U_CFG_PPP_ENABLE + +// Initialise the PPP stuff. +int32_t uPortPppPrivateInit() +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + + if (gMutex == NULL) { + errorCode = uPortMutexCreate(&gMutex); + } + + return errorCode; +} + +// Deinitialise the PPP stuff. +void uPortPppPrivateDeinit() +{ + uLinkedList_t *pListNext; + uPortPppInterface_t *pPppInterface; + uPortPppLocalDevice_t *pLocalDevice; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + // Remove any local device names + while (gpPppLocalDeviceNameList != NULL) { + pLocalDevice = (uPortPppLocalDevice_t *) (gpPppLocalDeviceNameList->p); + uPortFree(pLocalDevice); + uLinkedListRemove(&gpPppLocalDeviceNameList, pLocalDevice); + } + + // Now remove all PPP interfaces + while (gpPppInterfaceList != NULL) { + pPppInterface = (uPortPppInterface_t *) gpPppInterfaceList->p; + pListNext = gpPppInterfaceList->pNext; + uLinkedListRemove(&gpPppInterfaceList, pPppInterface); + // Make sure we don't accidentally try to call the + // transmit or down callbacks since the device handle + // will have been destroyed by now + pPppInterface->pTransmitCallback = NULL; + pPppInterface->pDisconnectCallback = NULL; + pppDisconnect(pPppInterface); + stopSocketTask(pPppInterface); + uPortFree(pPppInterface); + gpPppInterfaceList = pListNext; + } + + U_PORT_MUTEX_UNLOCK(gMutex); + uPortMutexDelete(gMutex); + gMutex = NULL; + } +} + +#else + +// Initialise the PPP stuff. +int32_t uPortPppPrivateInit() +{ + return (int32_t) U_ERROR_COMMON_SUCCESS; +} + +// Deinitialise the PPP stuff. +void uPortPppPrivateDeinit() +{ +} + +#endif // #ifdef U_CFG_PPP_ENABLE + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +#ifdef U_CFG_PPP_ENABLE + +// Set the name of the device that is the MCU-end PPP entity. +int32_t uPortPppSetLocalDeviceName(const char *pDevice) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + uPortPppLocalDevice_t *pLocalDevice; + pthread_t threadId; + uPortPppLocalDevice_t *pTmp; + + if ((pDevice != NULL) && (strlen(pDevice) < sizeof(pLocalDevice->name))) { + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + pLocalDevice = pUPortMalloc(sizeof(uPortPppLocalDevice_t)); + if (pLocalDevice != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + // Remove any existing local device name for this + // thread ID + threadId = pthread_self(); + while ((pTmp = pFindLocalDeviceName(threadId)) != NULL) { + uPortFree(pTmp); + uLinkedListRemove(&gpPppLocalDeviceNameList, pTmp); + } + // Add the new one + strncpy(pLocalDevice->name, pDevice, sizeof(pLocalDevice->name)); + pLocalDevice->threadId = threadId; + if (uLinkedListAdd(&gpPppLocalDeviceNameList, (void *) pLocalDevice)) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } else { + uPortFree(pLocalDevice); + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + } + + return errorCode; +} + +// Attach a PPP interface to pppd. +int32_t uPortPppAttach(void *pDevHandle, + uPortPppConnectCallback_t *pConnectCallback, + uPortPppDisconnectCallback_t *pDisconnectCallback, + uPortPppTransmitCallback_t *pTransmitCallback) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uPortPppInterface_t *pPppInterface; + uPortPppLocalDevice_t *pLocalDevice; + const char *pName = U_PORT_PPP_LOCAL_DEVICE_NAME; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + pPppInterface = pFindPppInterface(pDevHandle); + if (pPppInterface == NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + pPppInterface = (uPortPppInterface_t *) pUPortMalloc(sizeof(*pPppInterface)); + if (pPppInterface != NULL) { + memset(pPppInterface, 0, sizeof(*pPppInterface)); + // Get the pppd-end device name and start a task + // which will open a socket listening on it and + // receive data sent by it + pLocalDevice = pFindLocalDeviceName(pthread_self()); + if (pLocalDevice != NULL) { + pName = pLocalDevice->name; + } + errorCode = startSocketTask(pPppInterface, pName); + if (errorCode == 0) { + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + pPppInterface->pDevHandle = pDevHandle; + pPppInterface->pConnectCallback = pConnectCallback; + pPppInterface->pDisconnectCallback = pDisconnectCallback; + pPppInterface->pTransmitCallback = pTransmitCallback; + if (uLinkedListAdd(&gpPppInterfaceList, pPppInterface)) { + // Everything else is done in uPortPppConnect() + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } else { + stopSocketTask(pPppInterface); + uPortFree(pPppInterface); + } + } else { + uPortFree(pPppInterface); + } + } + } + + if (errorCode < 0) { + uPortLog("U_PORT_PPP: *** WARNING *** unable to attach PPP (%d).\n", errorCode); + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Connect a PPP interface. +int32_t uPortPppConnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress, + uSockIpAddress_t *pDnsIpAddressPrimary, + uSockIpAddress_t *pDnsIpAddressSecondary, + const char *pUsername, + const char *pPassword, + uPortPppAuthenticationMode_t authenticationMode) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uPortPppInterface_t *pPppInterface; + int32_t startTimeMs; + + // There is no way for this code to provide the authentication + // parameters back to pppd, the user has to set them when + // pppd is started + (void) pUsername; + (void) pPassword; + (void) authenticationMode; + + // PPP negotiation will set these + (void) pIpAddress; + (void) pDnsIpAddressPrimary; + (void) pDnsIpAddressSecondary; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND; + pPppInterface = pFindPppInterface(pDevHandle); + if (pPppInterface != NULL) { + // In case we were previously connected and + // then disconnected + pPppInterface->dataTransferSuspended = false; + pPppInterface->ipConnected = false; + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pPppInterface->pConnectCallback != NULL) { + errorCode = pPppInterface->pConnectCallback(pDevHandle, + moduleDataCallback, + pPppInterface, NULL, + U_PORT_PPP_RECEIVE_BUFFER_BYTES, + NULL); + } + if (errorCode == 0) { + pPppInterface->pppRunning = true; + // Use a nice specific error message here, most likely to point + // people at a PPP kinda problem + errorCode = (int32_t) U_ERROR_COMMON_PROTOCOL_ERROR; + // Wait for the IP connection to succeed + startTimeMs = uPortGetTickTimeMs(); + while ((!pPppInterface->ipConnected) && + (uPortGetTickTimeMs() - startTimeMs < U_PORT_PPP_CONNECT_TIMEOUT_SECONDS * 1000)) { + uPortTaskBlock(250); + } + if (pPppInterface->ipConnected) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + uPortLog("U_PORT_PPP: socket connected to module.\n"); + } + if ((errorCode != 0) && + (pPppInterface->pDisconnectCallback != NULL)) { + // Clean up on error + pPppInterface->pDisconnectCallback(pPppInterface->pDevHandle, false); + pPppInterface->pppRunning = false; + } + } + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Reconnect a PPP interface. +int32_t uPortPppReconnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uPortPppInterface_t *pPppInterface; + + (void) pIpAddress; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND; + pPppInterface = pFindPppInterface(pDevHandle); + if ((pPppInterface != NULL) && (pPppInterface->pppRunning)) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pPppInterface->pConnectCallback != NULL) { + errorCode = pPppInterface->pConnectCallback(pDevHandle, + moduleDataCallback, + pPppInterface, NULL, + U_PORT_PPP_RECEIVE_BUFFER_BYTES, + NULL); + } + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Disconnect a PPP interface. +int32_t uPortPppDisconnect(void *pDevHandle) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uPortPppInterface_t *pPppInterface; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND; + pPppInterface = pFindPppInterface(pDevHandle); + if (pPppInterface != NULL) { + pppDisconnect(pPppInterface); + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Detach a PPP interface from pppd. +int32_t uPortPppDetach(void *pDevHandle) +{ + uPortPppInterface_t *pPppInterface; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + pPppInterface = pFindPppInterface(pDevHandle); + if (pPppInterface != NULL) { + uLinkedListRemove(&gpPppInterfaceList, pPppInterface); + pppDisconnect(pPppInterface); + stopSocketTask(pPppInterface); + uPortFree(pPppInterface); + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return (int32_t) U_ERROR_COMMON_SUCCESS; +} + +#endif // #ifdef U_CFG_PPP_ENABLE + +// End of file diff --git a/port/platform/linux/src/u_port_ppp_private.h b/port/platform/linux/src/u_port_ppp_private.h new file mode 100644 index 00000000..5bd7ea49 --- /dev/null +++ b/port/platform/linux/src/u_port_ppp_private.h @@ -0,0 +1,56 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _U_PORT_PPP_PRIVATE_H_ +#define _U_PORT_PPP_PRIVATE_H_ + +/** @file + * @brief Stuff private to the PPP part of the Linux porting layer. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * FUNCTIONS + * -------------------------------------------------------------- */ + +/** Initialise the PPP stuff. + * + * @return zero on success else negative error code. + */ +int32_t uPortPppPrivateInit(); + +/** Deinitialise the PPP stuff. + */ +void uPortPppPrivateDeinit(); + +#ifdef __cplusplus +} +#endif + +#endif // _U_PORT_PPP_PRIVATE_H_ + +// End of file diff --git a/port/platform/linux/test/u_linux_ppp_test.c b/port/platform/linux/test/u_linux_ppp_test.c new file mode 100644 index 00000000..c86a42a3 --- /dev/null +++ b/port/platform/linux/test/u_linux_ppp_test.c @@ -0,0 +1,580 @@ +/* + * Copyright 2019-2024 u-blox + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Only #includes of u_* and the C standard library are allowed here, + * no platform stuff and no OS stuff. Anything required from + * the platform/OS must be brought in through u_port* to maintain + * portability. + */ + +/** @file + * @brief Tests of Linux sockets using pppd connected to a cellular + * module via ubxlib. These tests should pass on Linux when there is a + * cellular module connected. These tests use the network API and the + * test configuration information from the network API and sockets API + & to provide the communication path. + * + * The tests are only compiled if U_CFG_PPP_ENABLE is defined. + * + * IMPORTANT: see notes in u_cfg_test_platform_specific.h for the + * naming rules that must be followed when using the U_PORT_TEST_FUNCTION() + * macro. + */ + +#ifdef U_CFG_OVERRIDE +# include "u_cfg_override.h" // For a customer's configuration override +#endif + +#ifdef U_CFG_PPP_ENABLE + +#include "stddef.h" // NULL, size_t etc. +#include "stdlib.h" // rand(), snprintf() +#include "stdint.h" // int32_t etc. +#include "stdbool.h" +#include "string.h" // strlen() +#include "unistd.h" +#include "sys/socket.h" +#include "netdb.h" // struct addrinfo +#include "arpa/inet.h" +#include "errno.h" +#include "fcntl.h" +#include "net/if.h" // struct ifreq + +#include "u_compiler.h" // U_WEAK + +#include "u_cfg_sw.h" +#include "u_cfg_app_platform_specific.h" +#include "u_cfg_test_platform_specific.h" +#include "u_cfg_os_platform_specific.h" + +#include "u_error_common.h" + +#include "u_port_clib_platform_specific.h" /* struct timeval in some cases. */ +#include "u_port.h" +#include "u_port_os.h" +#include "u_port_heap.h" +#include "u_port_debug.h" +#include "u_port_event_queue.h" + +#include "u_test_util_resource_check.h" + +#include "u_network.h" // In order to provide a comms +#include "u_network_test_shared_cfg.h" // path for the socket + +#include "u_sock_test_shared_cfg.h" + +// These for uCellPrivateModule_t +#include "u_at_client.h" +#include "u_cell_module_type.h" +#include "u_cell.h" +#include "u_cell_file.h" +#include "u_cell_net.h" // Required by u_cell_private.h +#include "u_cell_private.h" // So that we can get at some innards + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/** The string to put at the start of all prints from this test. + */ +#define U_TEST_PREFIX "U_LINUX_SOCK_TEST: " + +/** Print a whole line, with terminator, prefixed for this test file. + */ +#define U_TEST_PRINT_LINE(format, ...) uPortLog(U_TEST_PREFIX format "\n", ##__VA_ARGS__) + +#ifndef U_LINUX_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES +/** The stack size to use for the asynchronous receive task. + */ +# define U_LINUX_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES 2560 +#endif + +#ifndef U_LINUX_PPP_TEST_RECEIVE_TASK_PRIORITY +/** The priority to use for the asynchronous receive task. + */ +# define U_LINUX_PPP_TEST_RECEIVE_TASK_PRIORITY (U_CFG_OS_PRIORITY_MIN + 5) +#endif + +#ifndef U_LINUX_PPP_TEST_RECEIVE_TASK_RELAX_MS +/** How long the receive task should relax for between receive + * attempts. + */ +# define U_LINUX_PPP_TEST_RECEIVE_TASK_RELAX_MS 10 +#endif + +#ifndef U_LINUX_PPP_TEST_RECEIVE_TASK_EXIT_MS +/** How long to allow for the the receive task to exit; + * should be quite a lot longer than + * #U_LINUX_PPP_TEST_RECEIVE_TASK_EXIT_MS. + */ +# define U_LINUX_PPP_TEST_RECEIVE_TASK_EXIT_MS 100 +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/** Struct to pass to rxTask(). + */ +typedef struct { + int32_t sock; + char *pBuffer; + size_t bufferLength; + size_t bytesToSend; + size_t bytesReceived; + size_t packetsReceived; + uPortTaskHandle_t taskHandle; + bool asyncExit; +} uLinuxPppSockTestConfig_t; + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/** Some data to exchange with an echo server. + */ +static const char gSendData[] = "_____0000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0100:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0200:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0300:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0400:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0500:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0600:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0700:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0800:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0900:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1100:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1200:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1300:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1400:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1500:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1600:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1700:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1800:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1900:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____2000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789"; + +/** Data structure passed around during aynchronous receive. + */ +//lint -esym(785, gTestConfig) Suppress too few initialisers +static uLinuxPppSockTestConfig_t gTestConfig = {.taskHandle = NULL}; + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +// Do this before every test to ensure there is a usable network. +static uNetworkTestList_t *pStdPreamble() +{ + uNetworkTestList_t *pList; + + U_PORT_TEST_ASSERT(uPortInit() == 0); + U_PORT_TEST_ASSERT(uDeviceInit() == 0); + + // Add the device for each network configuration + // if not already added + pList = pUNetworkTestListAlloc(uNetworkTestHasPpp); + if (pList == NULL) { + U_TEST_PRINT_LINE("*** WARNING *** nothing to do."); + } + // Open the devices that are not already open + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + if (*pTmp->pDevHandle == NULL) { + U_TEST_PRINT_LINE("adding device %s for network %s...", + gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType], + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uDeviceOpen(pTmp->pDeviceCfg, pTmp->pDevHandle) == 0); + } + } + + // Bring up each network type + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + U_TEST_PRINT_LINE("bringing up %s...", + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uNetworkInterfaceUp(*pTmp->pDevHandle, + pTmp->networkType, + pTmp->pNetworkCfg) == 0); + } + + return pList; +} + +// Receive data echoed back to us over a socket. +static void rxTask(void *pParameter) +{ + int32_t sizeBytes; + uLinuxPppSockTestConfig_t *pTestConfig = (uLinuxPppSockTestConfig_t *) pParameter; + + U_TEST_PRINT_LINE("rxTask receiving on socket %d.", pTestConfig->sock); + // Read from the socket until there's nothing left to read + //lint -e{776} Suppress possible truncation of addition + do { + sizeBytes = recv(pTestConfig->sock, + pTestConfig->pBuffer + + pTestConfig->bytesReceived, + pTestConfig->bytesToSend - + pTestConfig->bytesReceived, 0); + if (sizeBytes > 0) { + U_TEST_PRINT_LINE("received %d byte(s) of data @%d ms.", + sizeBytes, (int32_t) uPortGetTickTimeMs()); + pTestConfig->bytesReceived += sizeBytes; + pTestConfig->packetsReceived++; + } else { + uPortTaskBlock(U_LINUX_PPP_TEST_RECEIVE_TASK_RELAX_MS); + } + } while ((pTestConfig->bytesReceived < pTestConfig->bytesToSend) && + !pTestConfig->asyncExit); + + U_TEST_PRINT_LINE("rxTask exiting."); + + uPortTaskDelete(NULL); +} + +// Make sure that size is greater than 0 and no more than limit. +static size_t fix(size_t size, size_t limit) +{ + if (size == 0) { + size = limit / 2; // better than 1 + } else if (size > limit) { + size = limit; + } + + return size; +} + +// Send an entire TCP data buffer until done. +static size_t sendTcp(int32_t sock, const char *pData, size_t sizeBytes) +{ + int32_t x; + size_t sentSizeBytes = 0; + int32_t startTimeMs; + + U_TEST_PRINT_LINE("sending %d byte(s) of TCP data...", sizeBytes); + startTimeMs = uPortGetTickTimeMs(); + while ((sentSizeBytes < sizeBytes) && + ((uPortGetTickTimeMs() - startTimeMs) < 10000)) { + x = send(sock, (const void *) pData, sizeBytes - sentSizeBytes, 0); + if (x > 0) { + sentSizeBytes += x; + pData += x; + U_TEST_PRINT_LINE("sent %d byte(s) of TCP data @%d ms.", + sentSizeBytes, (int32_t) uPortGetTickTimeMs()); + } + } + + return sentSizeBytes; +} + +// Check a buffer of what was sent against what was echoed back and +// print out useful info if they differ. +static bool checkAgainstSentData(const char *pDataSent, + size_t dataSentSizeBytes, + const char *pDataReceived, + size_t dataReceivedSizeBytes) +{ + bool success = true; + int32_t x; +#if U_CFG_ENABLE_LOGGING + int32_t y; + int32_t z; +#endif + + if (dataReceivedSizeBytes == dataSentSizeBytes) { + // Run through checking that the characters are the same + for (x = 0; ((*(pDataReceived + x) == *(pDataSent + x))) && + (x < (int32_t) dataSentSizeBytes); x++) { + } + if (x != (int32_t) dataSentSizeBytes) { +#if U_CFG_ENABLE_LOGGING + y = x - 5; + if (y < 0) { + y = 0; + } + z = 10; + if (y + z > (int32_t) dataSentSizeBytes) { + z = ((int32_t) dataSentSizeBytes) - y; + } + U_TEST_PRINT_LINE("difference at character %d (sent \"%*.*s\", received" + " \"%*.*s\").", x + 1, z, z, pDataSent + y, z, z, + pDataReceived + y); +#endif + success = false; + } + } else { + U_TEST_PRINT_LINE("%d byte(s) missing (%d byte(s) received when %d were" + " expected)).", dataSentSizeBytes - dataReceivedSizeBytes, + dataReceivedSizeBytes, dataSentSizeBytes); + success = false; + } + + return success; +} + +// Release OS resources that may have been left hanging +// by a failed test +static void osCleanup() +{ + if (gTestConfig.taskHandle != NULL) { + gTestConfig.asyncExit = true; + uPortTaskBlock(U_LINUX_PPP_TEST_RECEIVE_TASK_EXIT_MS); + gTestConfig.taskHandle = NULL; + } + uPortFree(gTestConfig.pBuffer); + gTestConfig.pBuffer = NULL; +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: WEAK IMPLEMENTATION OF CELL API FUNCTION + * -------------------------------------------------------------- */ + +// This to allow the code here to at least compile if cellular is not included. +U_WEAK const uCellPrivateModule_t *pUCellPrivateGetModule(uDeviceHandle_t cellHandle) +{ + (void) cellHandle; + return NULL; +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: TESTS + * -------------------------------------------------------------- */ + +/** Basic TCP test. + * + * Note: we used to name the tests here linuxBlah to match the + * pattern of the platform tests under ESP-IDF and Zephyr. However, + * setting U_CFG_APP_FILTER to "linux" to run just these Linux tests + * doesn't work: "linux" is implicitly defined by the toolchain to + * be 1, so any time it appears as a conditional compilation flag + * the compiler will replace it with 1. These test names begin with + * testLinux instead of just linux; setting U_CFG_APP_FILTER to + * "testLinux" _will_ work. + */ +U_PORT_TEST_FUNCTION("[testLinuxSock]", "testLinuxSockTcp") +{ + const uCellPrivateModule_t *pModule; + uNetworkTestList_t *pList; + int32_t resourceCount; + char hostIp[] = U_SOCK_TEST_ECHO_TCP_SERVER_IP_ADDRESS; + struct sockaddr_in destinationAddress; + int32_t sock; + int32_t errorCode; + int32_t x; + size_t sizeBytes = 0; + size_t offset; + int32_t startTimeMs; + struct ifreq interface = {0}; + + // Whatever called us likely initialised the + // port so deinitialise it here to obtain the + // correct initial resource count + uPortDeinit(); + + // Obtain the initial resource count + resourceCount = uTestUtilGetDynamicResourceCount(); + + // Do the standard preamble to make sure there is + // a network underneath us + pList = pStdPreamble(); + + // Repeat for all bearers that have a supported PPP interface + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + pModule = NULL; + if (pTmp->pDeviceCfg->deviceType == U_DEVICE_TYPE_CELL) { + pModule = pUCellPrivateGetModule(*pTmp->pDevHandle); + } + if ((pModule == NULL) || + U_CELL_PRIVATE_HAS(pModule, U_CELL_PRIVATE_FEATURE_PPP)) { + + U_TEST_PRINT_LINE("doing async TCP test on %s.", + gpUNetworkTestTypeName[pTmp->networkType]); + osCleanup(); + + inet_pton(AF_INET, hostIp, &destinationAddress.sin_addr); + destinationAddress.sin_family = AF_INET; + destinationAddress.sin_port = htons(U_SOCK_TEST_ECHO_TCP_SERVER_PORT); + + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + U_TEST_PRINT_LINE("opening socket() to %s:%d returned %d (errno %d).", + hostIp, U_SOCK_TEST_ECHO_TCP_SERVER_PORT, sock, errno); + U_PORT_TEST_ASSERT(sock >= 0); + + // Set socket to be non-blocking for our asynchronous receive + x = fcntl(sock, F_GETFL, 0); + U_PORT_TEST_ASSERT(x >= 0); + x &= ~O_NONBLOCK; + U_PORT_TEST_ASSERT(fcntl(sock, F_SETFL, x) == 0); + + // Bind the socket to ppp0, the PPP interface, otherwise it will + // likely send over the Ethernet port + snprintf(interface.ifr_name, sizeof(interface.ifr_name), "ppp0"); + U_PORT_TEST_ASSERT(setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, + &interface, sizeof(interface)) == 0); + + errorCode = connect(sock, (struct sockaddr *) &destinationAddress, sizeof(destinationAddress)); + U_TEST_PRINT_LINE("connect() returned %d (errno %d).", errorCode, errno); + U_PORT_TEST_ASSERT(errorCode == 0); + + memset(&gTestConfig, 0, sizeof(gTestConfig)); + gTestConfig.sock = sock; + // We're sending all of gSendData except the + // null terminator on the end + gTestConfig.bytesToSend = sizeof(gSendData) - 1; + + // Malloc a buffer to receive TCP packets into + // and put the fill value into it + gTestConfig.bufferLength = gTestConfig.bytesToSend; + gTestConfig.pBuffer = (char *) pUPortMalloc(gTestConfig.bufferLength); + U_PORT_TEST_ASSERT(gTestConfig.pBuffer != NULL); + + // Create a task to receive data + U_PORT_TEST_ASSERT(uPortTaskCreate(rxTask, "rxTask", + U_LINUX_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES, + (void *) &gTestConfig, + U_LINUX_PPP_TEST_RECEIVE_TASK_PRIORITY, + &gTestConfig.taskHandle) == 0); + + // Throw random sized segments up... + offset = 0; + x = 0; + startTimeMs = uPortGetTickTimeMs(); + while (offset < gTestConfig.bytesToSend) { + sizeBytes = (rand() % U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE) + 1; + sizeBytes = fix(sizeBytes, U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE); + if (sizeBytes < U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE) { + sizeBytes = U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE; + } + if (offset + sizeBytes > gTestConfig.bytesToSend) { + sizeBytes = gTestConfig.bytesToSend - offset; + } + U_TEST_PRINT_LINE("write number %d.", x + 1); + U_PORT_TEST_ASSERT(sendTcp(gTestConfig.sock, + gSendData + offset, + sizeBytes) == sizeBytes); + offset += sizeBytes; + x++; + } + U_TEST_PRINT_LINE("a total of %d byte(s) sent in %d write(s).", offset, x); + + // Give the data time to come back + for (x = 10; (x > 0) && + (gTestConfig.bytesReceived < gTestConfig.bytesToSend); x--) { + uPortTaskBlock(1000); + } + + U_TEST_PRINT_LINE("TCP async receive task got %d segment(s)" + " totalling %d byte(s) and the send/receive" + " process took %d milliseconds.", + gTestConfig.packetsReceived, + gTestConfig.bytesReceived, + uPortGetTickTimeMs() - startTimeMs); + + // Check that we reassembled everything correctly + U_PORT_TEST_ASSERT(checkAgainstSentData(gSendData, + gTestConfig.bytesToSend, + gTestConfig.pBuffer, + gTestConfig.bytesReceived)); + + // Let the receive task close + gTestConfig.asyncExit = true; + uPortTaskBlock(U_LINUX_PPP_TEST_RECEIVE_TASK_EXIT_MS); + gTestConfig.taskHandle = NULL; + + shutdown(sock, 0); + close(sock); + + // Free memory + uPortFree(gTestConfig.pBuffer); + gTestConfig.pBuffer = NULL; + + // Free memory from event queues + uPortEventQueueCleanUp(); + + } else { + U_TEST_PRINT_LINE("*** WARNING *** not testing PPP since device does not support it."); + } + } + + // Remove each network type + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + U_TEST_PRINT_LINE("taking down %s...", + gpUNetworkTestTypeName[pTmp->networkType]); + U_PORT_TEST_ASSERT(uNetworkInterfaceDown(*pTmp->pDevHandle, + pTmp->networkType) == 0); + } + // Remove each device + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + if (*pTmp->pDevHandle != NULL) { + U_TEST_PRINT_LINE("closing device %s...", + gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType]); + U_PORT_TEST_ASSERT(uDeviceClose(*pTmp->pDevHandle, false) == 0); + *pTmp->pDevHandle = NULL; + } + } + uNetworkTestListFree(); + + uDeviceDeinit(); + uPortDeinit(); + + // Check for resource leaks + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); + resourceCount = uTestUtilGetDynamicResourceCount() - resourceCount; + U_TEST_PRINT_LINE("we have leaked %d resources(s).", resourceCount); + U_PORT_TEST_ASSERT(resourceCount <= 0); +} + +/** Clean-up to be run at the end of this round of tests, just + * in case there were test failures which would have resulted + * in the deinitialisation being skipped. + */ +U_PORT_TEST_FUNCTION("[testLinuxSock]", "testLinuxSockCleanUp") +{ + osCleanup(); + // The network test configuration is shared between + // the network, sockets, security and location tests + // so must reset the handles here in case the + // tests of one of the other APIs are coming next. + uNetworkTestCleanUp(); + uDeviceDeinit(); + uPortDeinit(); + // Printed for information: asserting happens in the postamble + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); +} + +#endif // #ifdef U_CFG_PPP_ENABLE + +// End of file diff --git a/port/u_port_ppp_default.c b/port/u_port_ppp_default.c index dee7e698..51d45fa8 100644 --- a/port/u_port_ppp_default.c +++ b/port/u_port_ppp_default.c @@ -67,6 +67,13 @@ void uPortPppDefaultPrivateLink() * PUBLIC FUNCTIONS * -------------------------------------------------------------- */ +// Set the name of the device that is the MCU-end PPP entity. +U_WEAK int32_t uPortPppSetLocalDeviceName(const char *pDevice) +{ + (void) pDevice; + return (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; +} + // Attach a PPP interface to the bottom of the IP stack of a platform. U_WEAK int32_t uPortPppAttach(void *pDevHandle, uPortPppConnectCallback_t *pConnectCallback,