From 8549e3eb5caffff4972b8d7a6880b02f0bd4ee2a Mon Sep 17 00:00:00 2001 From: RobMeades Date: Sun, 29 Oct 2023 16:52:36 +0000 Subject: [PATCH] Cellular feature, ESP-IDF only: PPP-level integration. This commit introduces the concept of a PPP interface to ubxlib, currently only for the ESP-IDF platform and with a cellular transport. This allows the native application protocols of the platform to be used with cellular. The PPP interface is defined as a new port API, which includes a default implementation that does nothing so that no existing ports are adversely affected by it. This port API is, internally, called from the cellular code, such that when the cellular network is up the PPP interface of the port layer is automatically activated, connecting into the PPP layer at the bottom of the IP stack of the platform (LWIP in the ESP-IDF case). This is currently only supported with SARA-R5 and SARA-R422 cellular modules. U_CFG_PPP_ENABLE must be define when building ubxlib to enable PPP. Of course, for this to integrate with the platform there will be additional build requirements related to the platform itself (enabling the right components of the platform etc.): see the README.md in the ESP-IDF platform directory for details. An example is added to the sockets examples showing how to use native ESP-IDF sockets with PPP. --- cell/README.md | 11 +- cell/api/u_cell_mux.h | 6 +- cell/api/u_cell_net.h | 4 + cell/src/u_cell.c | 3 + cell/src/u_cell_mux.c | 536 ++++++++----- cell/src/u_cell_mux_private.c | 100 +-- cell/src/u_cell_mux_private.h | 199 ++++- cell/src/u_cell_net.c | 112 ++- cell/src/u_cell_ppp.c | 589 ++++++++++++++ cell/src/u_cell_ppp_private.h | 66 ++ cell/src/u_cell_ppp_shared.h | 200 +++++ cell/src/u_cell_private.c | 20 +- cell/src/u_cell_private.h | 4 +- cell/src/u_cell_pwr.c | 238 ++++-- cell/src/u_cell_pwr_private.h | 48 ++ cell/src/u_cell_stub_gnss.c | 51 ++ cell/test/u_cell_ppp_test.c | 501 ++++++++++++ cell/test/u_cell_test_private.c | 10 +- .../network/test/u_network_test_shared_cfg.c | 10 + .../network/test/u_network_test_shared_cfg.h | 12 + common/sock/test/u_sock_test.c | 14 - common/sock/test/u_sock_test_shared_cfg.h | 14 + example/sockets/README.md | 12 + example/sockets/main_ppp_espidf.c | 305 +++++++ gnss/api/u_gnss.h | 10 + gnss/src/u_gnss.c | 26 + gnss/src/u_gnss_shared.h | 54 ++ port/api/u_port_ppp.h | 391 +++++++++ port/platform/arduino/source.txt | 4 + port/platform/arduino/source_test.txt | 2 + port/platform/common/automation/DATABASE.md | 5 +- .../common/automation/ubxlib_h_excludes.txt | 5 +- port/platform/esp-idf/README.md | 15 +- port/platform/esp-idf/app/u_main.c | 23 +- port/platform/esp-idf/mcu/esp32/README.md | 2 +- .../esp32/cfg/u_cfg_app_platform_specific.h | 6 +- .../esp32/components/ubxlib/CMakeLists.txt | 9 +- .../mcu/esp32/runner/sdkconfig.defaults | 12 +- .../esp32/runner/ubxlib_runner/CMakeLists.txt | 2 +- .../runner/ubxlib_runner/test/CMakeLists.txt | 2 +- port/platform/esp-idf/src/u_port.c | 16 + port/platform/esp-idf/src/u_port_ppp.c | 741 ++++++++++++++++++ .../platform/esp-idf/src/u_port_ppp_private.h | 56 ++ .../platform/esp-idf/test/u_espidf_ppp_test.c | 548 +++++++++++++ port/platform/linux/test/u_linux_ppp_test.c | 190 +++++ port/platform/platformio/inc_src.txt | 1 + port/platform/windows/README.md | 5 +- .../windows/test/u_windows_ppp_test.c | 190 +++++ port/platform/zephyr/CMakeLists.txt | 2 + port/platform/zephyr/README.md | 1 + port/platform/zephyr/test/u_zephyr_ppp_test.c | 199 +++++ port/u_port_ppp_default.c | 125 +++ port/ubxlib.cmake | 3 + port/ubxlib.mk | 3 + 54 files changed, 5272 insertions(+), 441 deletions(-) create mode 100644 cell/src/u_cell_ppp.c create mode 100644 cell/src/u_cell_ppp_private.h create mode 100644 cell/src/u_cell_ppp_shared.h create mode 100644 cell/src/u_cell_stub_gnss.c create mode 100644 cell/test/u_cell_ppp_test.c create mode 100644 example/sockets/main_ppp_espidf.c create mode 100644 gnss/src/u_gnss_shared.h create mode 100644 port/api/u_port_ppp.h create mode 100644 port/platform/esp-idf/src/u_port_ppp.c create mode 100644 port/platform/esp-idf/src/u_port_ppp_private.h create mode 100644 port/platform/esp-idf/test/u_espidf_ppp_test.c create mode 100644 port/platform/linux/test/u_linux_ppp_test.c create mode 100644 port/platform/windows/test/u_windows_ppp_test.c create mode 100644 port/platform/zephyr/test/u_zephyr_ppp_test.c create mode 100644 port/u_port_ppp_default.c diff --git a/cell/README.md b/cell/README.md index e75ffc278..ce893d927 100644 --- a/cell/README.md +++ b/cell/README.md @@ -126,4 +126,13 @@ int app_start() { while(1); } -``` \ No newline at end of file +``` + +# PPP-Level Integration With A Platform +PPP-level integration between the bottom of a platform's IP stack and cellular is supported on some platforms and some module types, currently only ESP-IDF with SARA-R5 or SARA-R422. This allows the native clients of the platform (e.g. MQTT etc.) to be used in your application with a cellular transport beneath them. + +To enable this integration you must define `U_CFG_PPP_ENABLE` for your build. Other switches/components/whatevers may also be required on the platform side: see the README.md in the relevant platform directory for details. + +To use the integration, just make a cellular connection with `ubxlib` in the usual way and the connection will be available to the platform. + +Note: if you are required to supply a username and password for your connection then, when using PPP, you must call `uCellNetSetAuthenticationMode()` to set the authentication mode explicitly; automatic authentication mode will not work with PPP. \ No newline at end of file diff --git a/cell/api/u_cell_mux.h b/cell/api/u_cell_mux.h index 9072493c5..c143d3de6 100644 --- a/cell/api/u_cell_mux.h +++ b/cell/api/u_cell_mux.h @@ -53,10 +53,10 @@ extern "C" { #define U_CELL_MUX_CHANNEL_ID_GNSS 0xFF #ifndef U_CELL_MUX_MAX_CHANNELS -/** Enough room for the control channel, an AT channel and a - * GNSS serial channel. +/** Enough room for the control channel, an AT channel, a + * GNSS serial channel and potentially a PPP data channel. */ -# define U_CELL_MUX_MAX_CHANNELS 3 +# define U_CELL_MUX_MAX_CHANNELS 4 #endif /* ---------------------------------------------------------------- diff --git a/cell/api/u_cell_net.h b/cell/api/u_cell_net.h index 72025ca47..d157d721f 100644 --- a/cell/api/u_cell_net.h +++ b/cell/api/u_cell_net.h @@ -270,6 +270,10 @@ typedef enum { } uCellNetRegDomain_t; /** The possible authentication modes for the network connection. + * + * Note: there is also a #uPortPppAuthenticationMode_t enumeration + * which is set to match this one. If you make a change here you + * may need to make a change there also. */ typedef enum { U_CELL_NET_AUTHENTICATION_MODE_NONE = 0, /**< \deprecated please use #U_CELL_NET_AUTHENTICATION_MODE_NOT_SET. */ diff --git a/cell/src/u_cell.c b/cell/src/u_cell.c index 72240eb61..463dd8076 100644 --- a/cell/src/u_cell.c +++ b/cell/src/u_cell.c @@ -60,6 +60,7 @@ #include "u_cell_private.h" // don't change it #include "u_cell_mux.h" #include "u_cell_mux_private.h" +#include "u_cell_ppp_private.h" // The headers below necessary to work around an Espressif linker problem, see uCellInit() #include "u_sock.h" @@ -143,6 +144,8 @@ static void removeCellInstance(uCellPrivateInstance_t *pInstance) uPortFree(pInstance->pFotaContext); // Free any HTTP context uCellPrivateHttpRemoveContext(pInstance); + // Free any PPP context + uCellPppPrivateRemoveContext(pInstance); // Free any CMUX context uCellMuxPrivateRemoveContext(pInstance); // Free any CellTime context diff --git a/cell/src/u_cell_mux.c b/cell/src/u_cell_mux.c index 08a578e9c..57a43a7d2 100644 --- a/cell/src/u_cell_mux.c +++ b/cell/src/u_cell_mux.c @@ -77,6 +77,8 @@ #include "u_device_shared.h" +#include "u_gnss_shared.h" // For uGnssUpdateAtHandle() + #include "u_cell_module_type.h" #include "u_cell_file.h" #include "u_cell.h" // Order is @@ -138,7 +140,7 @@ #ifndef U_CELL_MUX_CALLBACK_QUEUE_LENGTH /** The maximum length of the common callback queue for the serial devices. - * Each item in the queue will be sizeof(uCellMuxEvenTrampoline_t) bytes big. + * Each item in the queue will be sizeof(uCellMuxEventTrampoline_t) bytes big. */ # define U_CELL_MUX_CALLBACK_QUEUE_LENGTH 20 #endif @@ -162,7 +164,7 @@ typedef struct { uCellMuxPrivateContext_t *pContext; int32_t channel; uint32_t eventBitMap; -} uCellMuxEvenTrampoline_t; +} uCellMuxEventTrampoline_t; /* ---------------------------------------------------------------- * STATIC VARIABLES @@ -192,7 +194,7 @@ static const char gMuxCldCommandFrame[] = {0xf9, 0x03, 0xff, 0x05, 0xc3, 0x01, 0 // Event handler, common to all virtual serial ports. static void eventHandler(void *pParam, size_t paramLength) { - uCellMuxEvenTrampoline_t *pEventTrampoline = (uCellMuxEvenTrampoline_t *) pParam; + uCellMuxEventTrampoline_t *pEventTrampoline = (uCellMuxEventTrampoline_t *) pParam; uCellMuxPrivateContext_t *pContext = pEventTrampoline->pContext; uDeviceSerial_t *pDeviceSerial; uCellMuxPrivateChannelContext_t *pChannelContext; @@ -228,7 +230,7 @@ static int32_t sendEvent(uCellMuxPrivateContext_t *pContext, { int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; uCellMuxPrivateEventCallback_t *pEventCallback; - uCellMuxEvenTrampoline_t trampolineData; + uCellMuxEventTrampoline_t trampolineData; uint32_t eventCallbackFilter; int64_t startTime = uPortGetTickTimeMs(); @@ -406,11 +408,15 @@ static int32_t serialWriteInnards(struct uDeviceSerial_t *pDeviceSerial, lengthWritten); for (size_t x = 0; x < lengthWritten; x++) { char y = *(pBufferEncoded + x); +#ifndef U_CELL_MUX_HEX_DEBUG if (isprint((int32_t) y)) { uPortLog("%c", y); } else { +#endif uPortLog("[%02x]", y); +#ifndef U_CELL_MUX_HEX_DEBUG } +#endif } uPortLog(".\n"); } @@ -692,11 +698,33 @@ static int32_t serialRead(struct uDeviceSerial_t *pDeviceSerial, if (U_CELL_MUX_IS_OPEN(pChannelContext->state)) { pTraffic = &(pChannelContext->traffic); sizeOrErrorCode = serialReadInnards(pTraffic, pBuffer, sizeBytes); -#ifdef U_CELL_MUX_ENABLE_DEBUG +#if defined(U_CELL_MUX_ENABLE_DEBUG) || defined(U_CELL_MUX_ENABLE_USER_RX_DEBUG) if (sizeOrErrorCode > 0) { uPortLog("U_CELL_CMUX_%d: app read %d byte(s).\n", pChannelContext->channel, sizeOrErrorCode); } +#endif +#ifdef U_CELL_MUX_ENABLE_USER_RX_DEBUG + // Don't normally need this however it may be useful when + // debugging the behaviour of a destination that is out of + // reach, e.g. inside the IP stack of a platform, channeled + // via PPP + if (sizeOrErrorCode > 0) { + uPortLog("U_CELL_CMUX_%d: ", pChannelContext->channel); + for (int32_t x = 0; x < sizeOrErrorCode; x++) { + char y = *((char *) pBuffer + x); +#ifndef U_CELL_MUX_HEX_DEBUG + if (isprint((int32_t) y)) { + uPortLog("%c", y); + } else { +#endif + uPortLog("[%02x]", y); +#ifndef U_CELL_MUX_HEX_DEBUG + } +#endif + } + uPortLog(".\n"); + } #endif if (pTraffic->rxIsFlowControlledOff && (((pTraffic->rxBufferSizeBytes - serialGetReceiveSizeInnards(pDeviceSerial)) * 100) / @@ -1521,16 +1549,7 @@ static void cmuxReceiveCallback(const uAtClientStreamHandle_t *pStream, } /* ---------------------------------------------------------------- - * PUBLIC FUNCTIONS: WORKAROUND FOR LINKER ISSUE - * -------------------------------------------------------------- */ - -void uCellMuxPrivateLink() -{ - //dummy -} - -/* ---------------------------------------------------------------- - * PUBLIC FUNCTIONS + * PUBLIC FUNCTIONS THAT ARE PRIVATE TO CELLULAR * -------------------------------------------------------------- */ // Enable multiplexer mode. This involves a few steps: @@ -1541,10 +1560,9 @@ void uCellMuxPrivateLink() // AT client on CMUX channel 1, the AT channel, copy the current // state there and begin using it. // 4. If not successful, unwind. -int32_t uCellMuxEnable(uDeviceHandle_t cellHandle) +int32_t uCellMuxPrivateEnable(uCellPrivateInstance_t *pInstance) { - int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; - uCellPrivateInstance_t *pInstance; + int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; uAtClientHandle_t atHandle; uAtClientStreamHandle_t stream = U_AT_CLIENT_STREAM_HANDLE_DEFAULTS; uCellMuxPrivateContext_t *pContext; @@ -1552,170 +1570,350 @@ int32_t uCellMuxEnable(uDeviceHandle_t cellHandle) int32_t cmeeMode = 2; char tempBuffer[32]; - if (gUCellPrivateMutex != NULL) { - - U_PORT_MUTEX_LOCK(gUCellPrivateMutex); - - errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; - pInstance = pUCellPrivateGetInstance(cellHandle); - if (pInstance != NULL) { - errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; - if (U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_CMUX)) { - errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; - if (pInstance->pMuxContext == NULL) { - errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; - // Allocate memory for our CMUX context; this will be - // deallocated only when the cellular instance is removed - pInstance->pMuxContext = pUPortMalloc(sizeof(uCellMuxPrivateContext_t)); - if (pInstance->pMuxContext != NULL) { - pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; - memset(pContext, 0, sizeof(*pContext)); - // To save memory, we use a single event queue for all callbacks - // from the CMUX channels, re-using the AT client sizes - pContext->eventQueueHandle = uPortEventQueueOpen(eventHandler, - "cmuxCallbacks", - sizeof(uCellMuxEvenTrampoline_t), - U_CELL_MUX_CALLBACK_TASK_STACK_SIZE_BYTES, - U_CELL_MUX_CALLBACK_TASK_PRIORITY, - U_CELL_MUX_CALLBACK_QUEUE_LENGTH); - if (pContext->eventQueueHandle >= 0) { - if (uRingBufferCreateWithReadHandle(&(pContext->ringBuffer), - pContext->linearBuffer, - sizeof(pContext->linearBuffer), 1) == 0) { - uRingBufferSetReadRequiresHandle(&(pContext->ringBuffer), true); - pContext->readHandle = uRingBufferTakeReadHandle(&(pContext->ringBuffer)); - } else { - // Clean up on error - uPortEventQueueClose(pContext->eventQueueHandle); - uPortFree(pInstance->pMuxContext); - pInstance->pMuxContext = NULL; - } + if (pInstance != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + if (U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_CMUX)) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pInstance->pMuxContext == NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + // Allocate memory for our CMUX context; this will be + // deallocated only when the cellular instance is removed + pInstance->pMuxContext = pUPortMalloc(sizeof(uCellMuxPrivateContext_t)); + if (pInstance->pMuxContext != NULL) { + pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; + memset(pContext, 0, sizeof(*pContext)); + // To save memory, we use a single event queue for all callbacks + // from the CMUX channels, re-using the AT client sizes + pContext->eventQueueHandle = uPortEventQueueOpen(eventHandler, + "cmuxCallbacks", + sizeof(uCellMuxEventTrampoline_t), + U_CELL_MUX_CALLBACK_TASK_STACK_SIZE_BYTES, + U_CELL_MUX_CALLBACK_TASK_PRIORITY, + U_CELL_MUX_CALLBACK_QUEUE_LENGTH); + if (pContext->eventQueueHandle >= 0) { + if (uRingBufferCreateWithReadHandle(&(pContext->ringBuffer), + pContext->linearBuffer, + sizeof(pContext->linearBuffer), 1) == 0) { + uRingBufferSetReadRequiresHandle(&(pContext->ringBuffer), true); + pContext->readHandle = uRingBufferTakeReadHandle(&(pContext->ringBuffer)); } else { // Clean up on error + uPortEventQueueClose(pContext->eventQueueHandle); uPortFree(pInstance->pMuxContext); pInstance->pMuxContext = NULL; } + } else { + // Clean up on error + uPortFree(pInstance->pMuxContext); + pInstance->pMuxContext = NULL; } } - if (pInstance->pMuxContext != NULL) { - pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; - errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; - if (pContext->savedAtHandle == NULL) { - // Initialise the other parts of [an existing] context - pContext->pInstance = pInstance; - pContext->channelGnss = getChannelGnss(pInstance); - pContext->holdingBufferIndex = 0; - // Initiate CMUX - atHandle = pInstance->atHandle; - uAtClientLock(atHandle); - uAtClientStreamGetExt(atHandle, &stream); - uRingBufferFlushHandle(&(pContext->ringBuffer), pContext->readHandle); - pContext->underlyingStreamHandle = stream.handle.int32; - uAtClientCommandStart(atHandle, "AT+CMUX="); - // Only basic mode and only UIH frames are supported by any - // of the cellular modules we support - uAtClientWriteInt(atHandle, 0); - uAtClientWriteInt(atHandle, 0); - // As advised in the u-blox multiplexer document, port - // speed is left empty for max compatibility - uAtClientWriteString(atHandle, "", false); - // Set the information field length - uAtClientWriteInt(atHandle, - U_CELL_MUX_PRIVATE_INFORMATION_LENGTH_MAX_BYTES); - // Everything else is left at defaults for max compatibility - uAtClientCommandStopReadResponse(atHandle); - // Not unlocking here, just check for errors - errorCode = uAtClientErrorGet(atHandle); + } + if (pInstance->pMuxContext != NULL) { + pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pContext->savedAtHandle == NULL) { + // Initialise the other parts of [an existing] context + pContext->pInstance = pInstance; + pContext->channelGnss = getChannelGnss(pInstance); + pContext->holdingBufferIndex = 0; + // Initiate CMUX + atHandle = pInstance->atHandle; + uAtClientLock(atHandle); + uAtClientStreamGetExt(atHandle, &stream); + uRingBufferFlushHandle(&(pContext->ringBuffer), pContext->readHandle); + pContext->underlyingStreamHandle = stream.handle.int32; + uAtClientCommandStart(atHandle, "AT+CMUX="); + // Only basic mode and only UIH frames are supported by any + // of the cellular modules we support + uAtClientWriteInt(atHandle, 0); + uAtClientWriteInt(atHandle, 0); + // As advised in the u-blox multiplexer document, port + // speed is left empty for max compatibility + uAtClientWriteString(atHandle, "", false); + // Set the information field length + uAtClientWriteInt(atHandle, + U_CELL_MUX_PRIVATE_INFORMATION_LENGTH_MAX_BYTES); + // Everything else is left at defaults for max compatibility + uAtClientCommandStopReadResponse(atHandle); + // Not unlocking here, just check for errors + errorCode = uAtClientErrorGet(atHandle); + if (errorCode == 0) { + // Leave the AT client locked to stop it reacting to stuff coming + // back over the UART, which will shortly become the MUX + // control channel and not an AT interface at all. + // Replace the URC handler of the existing AT client + // with our own so that we get the received data + // and can decode it + uAtClientUrcHandlerHijackExt(atHandle, cmuxReceiveCallback, pContext); + // Give the module a moment for the MUX switcheroo + uPortTaskBlock(U_CELL_MUX_PRIVATE_ENABLE_DISABLE_DELAY_MS); + // Open the control channel, channel 0; for this we need no + // data buffer, since it does not carry user data + pContext->savedAtHandle = atHandle; + errorCode = openChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL, 0); if (errorCode == 0) { - // Leave the AT client locked to stop it reacting to stuff coming - // back over the UART, which will shortly become the MUX - // control channel and not an AT interface at all. - // Replace the URC handler of the existing AT client - // with our own so that we get the received data - // and can decode it - uAtClientUrcHandlerHijackExt(atHandle, cmuxReceiveCallback, pContext); - // Give the module a moment for the MUX switcheroo - uPortTaskBlock(U_CELL_MUX_PRIVATE_ENABLE_DISABLE_DELAY_MS); - // Open the control channel, channel 0; for this we need no - // data buffer, since it does not carry user data - pContext->savedAtHandle = atHandle; - errorCode = openChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL, 0); - if (errorCode == 0) { #ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX_0: control channel open.\n"); + uPortLog("U_CELL_CMUX_0: control channel open.\n"); #endif - // Channel 0 is up, now we need channel 1, on which - // we will need a data buffer for the information field carrying the - // user data (i.e. AT commands) - errorCode = openChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT, - U_CELL_MUX_PRIVATE_VIRTUAL_SERIAL_BUFFER_LENGTH_BYTES); - if (errorCode == 0) { + // Channel 0 is up, now we need channel 1, on which + // we will need a data buffer for the information field carrying the + // user data (i.e. AT commands) + errorCode = openChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT, + U_CELL_MUX_PRIVATE_VIRTUAL_SERIAL_BUFFER_LENGTH_BYTES); + if (errorCode == 0) { #ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX_1: AT channel open, flushing stored URCs...\n"); + uPortLog("U_CELL_CMUX_1: AT channel open, flushing stored URCs...\n"); #endif - pDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT); - // Some modules (e.g. SARA-R422) can have stored up loads of URCs - // which they like to emit over the new mux channel; flush these - // out here - uPortTaskBlock(500); - do { - uPortTaskBlock(10); - } while (pDeviceSerial->read(pDeviceSerial, tempBuffer, sizeof(tempBuffer)) > 0); - // Create a copy of the current AT client on this serial port - stream.handle.pDeviceSerial = pDeviceSerial; - stream.type = U_AT_CLIENT_STREAM_TYPE_VIRTUAL_SERIAL; - atHandle = uAtClientAddExt(&stream, NULL, U_CELL_AT_BUFFER_LENGTH_BYTES); - if (atHandle != NULL) { + pDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT); + // Some modules (e.g. SARA-R422) can have stored up loads of URCs + // which they like to emit over the new mux channel; flush these + // out here + uPortTaskBlock(500); + do { + uPortTaskBlock(10); + } while (pDeviceSerial->read(pDeviceSerial, tempBuffer, sizeof(tempBuffer)) > 0); + // Create a copy of the current AT client on this serial port + stream.handle.pDeviceSerial = pDeviceSerial; + stream.type = U_AT_CLIENT_STREAM_TYPE_VIRTUAL_SERIAL; + atHandle = uAtClientAddExt(&stream, NULL, U_CELL_AT_BUFFER_LENGTH_BYTES); + if (atHandle != NULL) { #ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX: AT client added.\n"); + uPortLog("U_CELL_CMUX: AT client added.\n"); #endif - errorCode = uCellMuxPrivateCopyAtClient(pContext->savedAtHandle, - atHandle); - if (errorCode == 0) { + errorCode = uCellMuxPrivateCopyAtClient(pContext->savedAtHandle, + atHandle); + if (errorCode == 0) { #ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX: existing AT client copied, CMUX is running.\n"); + uPortLog("U_CELL_CMUX: existing AT client copied, CMUX is running.\n"); #endif - // Now that we have everything, we set the AT handle - // of our instance to the new AT handle, leaving the - // old AT handle locked - pInstance->atHandle = atHandle; - // The setting of echo-off and AT+CMEE is port-specific, - // so we need to set those here for the new port + // Now that we have everything, we set the AT handle + // of our instance to the new AT handle, leaving the + // old AT handle locked + pInstance->atHandle = atHandle; + // The setting of echo-off and AT+CMEE is port-specific, + // so we need to set those here for the new port #ifdef U_CFG_CELL_ENABLE_NUMERIC_ERROR - cmeeMode = 1; + cmeeMode = 1; #endif - uAtClientLock(atHandle); - uAtClientCommandStart(atHandle, "ATE0"); - uAtClientCommandStopReadResponse(atHandle); - uAtClientCommandStart(atHandle, "AT+CMEE="); - uAtClientWriteInt(atHandle, cmeeMode); - uAtClientCommandStopReadResponse(atHandle); - errorCode = uAtClientUnlock(atHandle); - } else { - // Recover on error - uAtClientRemove(atHandle); - atHandle = pContext->savedAtHandle; + uAtClientLock(atHandle); + uAtClientCommandStart(atHandle, "ATE0"); + uAtClientCommandStopReadResponse(atHandle); + uAtClientCommandStart(atHandle, "AT+CMEE="); + uAtClientWriteInt(atHandle, cmeeMode); + uAtClientCommandStopReadResponse(atHandle); + errorCode = uAtClientUnlock(atHandle); + if (errorCode == 0) { + // Let GNSS update any AT handles it may hold + uGnssUpdateAtHandle(pContext->savedAtHandle, atHandle); } } else { // Recover on error + uAtClientRemove(atHandle); atHandle = pContext->savedAtHandle; } + } else { + // Recover on error + atHandle = pContext->savedAtHandle; } } } - if (errorCode < 0) { - // Clean up and unlock the AT client on error - uCellMuxPrivateCloseChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT); - // Closing the control channel will take us out of CMUX mode - uCellMuxPrivateCloseChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL); - uAtClientUrcHandlerHijackExt(atHandle, NULL, NULL); - pContext->savedAtHandle = NULL; - uAtClientUnlock(atHandle); - } + } + if (errorCode < 0) { + // Clean up and unlock the AT client on error + uCellMuxPrivateCloseChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_AT); + // Closing the control channel will take us out of CMUX mode + uCellMuxPrivateCloseChannel(pContext, U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL); + uAtClientUrcHandlerHijackExt(atHandle, NULL, NULL); + pContext->savedAtHandle = NULL; + uAtClientUnlock(atHandle); } } } } + } + + return errorCode; +} + +// Determine if the multiplexer is currently enabled. +bool uCellMuxPrivateIsEnabled(uCellPrivateInstance_t *pInstance) +{ + bool isEnabled = false; + uCellMuxPrivateContext_t *pContext; + + if ((pInstance != NULL) && (pInstance->pMuxContext != NULL)) { + pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; + isEnabled = (pContext->savedAtHandle != NULL); + } + + return isEnabled; +} + +// Add a multiplexer channel. +int32_t uCellMuxPrivateAddChannel(uCellPrivateInstance_t *pInstance, + int32_t channel, + uDeviceSerial_t **ppDeviceSerial) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + uCellMuxPrivateContext_t *pContext; + + if ((pInstance != NULL) && (ppDeviceSerial != NULL) && + (channel != U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL) && + (channel != U_CELL_MUX_PRIVATE_CHANNEL_ID_AT) && + ((channel <= U_CELL_MUX_PRIVATE_ADDRESS_MAX) || + (channel == U_CELL_MUX_CHANNEL_ID_GNSS))) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + if (pInstance->pMuxContext != NULL) { + pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; + if (pContext->savedAtHandle != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + if (channel == U_CELL_MUX_CHANNEL_ID_GNSS) { + channel = pContext->channelGnss; + } + if (channel >= 0) { + errorCode = openChannel(pContext, channel, + U_CELL_MUX_PRIVATE_VIRTUAL_SERIAL_BUFFER_LENGTH_BYTES); + if (errorCode == 0) { +#ifdef U_CELL_MUX_ENABLE_DEBUG + uPortLog("U_CELL_CMUX_%d: channel added.\n", channel); +#endif + *ppDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, channel); + } + } + } + } + } + + return errorCode; +} + +// Disable multiplexer mode. This involves a few steps: +// +// 1. Send DISC on the virtual serial interface of any currently +// open channels and close the virtual serial interfaces; +// do channel 0, the control interface, last and it will +// end CMUX mode. +// 2. Move AT client operations back to the original AT client. +// 3. DO NOT free memory; only uCellMuxPrivateRemoveContext() does +// that, to ensure thread-safety. +int32_t uCellMuxPrivateDisable(uCellPrivateInstance_t *pInstance) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + uAtClientHandle_t atHandle; + uCellMuxPrivateContext_t *pContext; + uCellMuxPrivateChannelContext_t *pChannelContext; + + if (pInstance != NULL) { + atHandle = pInstance->atHandle; + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pInstance->pMuxContext != NULL) { + pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; + // Start from the top, so that we do channel 0, which + // will always be at index 0, last + for (int32_t x = (sizeof(pContext->pDeviceSerial) / sizeof(pContext->pDeviceSerial[0])) - 1; + x >= 0; x--) { + pChannelContext = (uCellMuxPrivateChannelContext_t *) pUInterfaceContext( + pContext->pDeviceSerial[x]); + if (pChannelContext != NULL) { + uCellMuxPrivateCloseChannel(pContext, pChannelContext->channel); + } + } + if (pContext->savedAtHandle != NULL) { + // Copy the settings of the AT handler on channel 1 + // back into the original one, in case they have changed + errorCode = uCellMuxPrivateCopyAtClient(atHandle, pContext->savedAtHandle); + // While we set the error code above, there's not a whole lot + // we can do if this fails, so continue anyway; close the + // AT handler that was on channel 1 + uAtClientIgnoreAsync(atHandle); + uAtClientRemove(atHandle); + // Unhijack the old AT handler and unlock it + atHandle = pContext->savedAtHandle; + uAtClientUrcHandlerHijackExt(atHandle, NULL, NULL); + uAtClientUnlock(atHandle); + // Let GNSS update any AT handles it may hold + uGnssUpdateAtHandle(pInstance->atHandle, atHandle); + pInstance->atHandle = atHandle; + pContext->savedAtHandle = NULL; +#ifdef U_CELL_MUX_ENABLE_DEBUG + uPortLog("U_CELL_CMUX: closed.\n"); +#endif + } + // Give the module a moment for the MUX switcheroo + uPortTaskBlock(U_CELL_MUX_PRIVATE_ENABLE_DISABLE_DELAY_MS); + } + } + + return (int32_t) errorCode; +} + +// Get the serial device for the given channel. +uDeviceSerial_t *pUCellMuxPrivateGetDeviceSerial(uCellMuxPrivateContext_t *pContext, + uint8_t channel) +{ + uDeviceSerial_t *pDeviceSerial = NULL; + uCellMuxPrivateChannelContext_t *pChannelContext; + + if ((pContext != NULL) && (channel <= U_CELL_MUX_PRIVATE_CHANNEL_ID_MAX)) { + for (size_t x = 0; + (x < sizeof(pContext->pDeviceSerial) / sizeof(pContext->pDeviceSerial[0])) && + (pDeviceSerial == NULL); x++) { + if (pContext->pDeviceSerial[x] != NULL) { + pChannelContext = (uCellMuxPrivateChannelContext_t *) pUInterfaceContext( + pContext->pDeviceSerial[x]); + if ((pChannelContext != NULL) && !pChannelContext->markedForDeletion && + (pChannelContext->channel == channel)) { + pDeviceSerial = pContext->pDeviceSerial[x]; + } + } + } + } + + return pDeviceSerial; +} + +// Close a CMUX channel. +void uCellMuxPrivateCloseChannel(uCellMuxPrivateContext_t *pContext, uint8_t channel) +{ + uDeviceSerial_t *pDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, channel); + + if (pDeviceSerial != NULL) { + pDeviceSerial->close(pDeviceSerial); +#ifdef U_CELL_MUX_ENABLE_DEBUG + uPortLog("U_CELL_CMUX_%d: channel closed.\n", channel); +#endif + } +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: WORKAROUND FOR LINKER ISSUE + * -------------------------------------------------------------- */ + +void uCellMuxPrivateLink() +{ + //dummy +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +// Enable multiplexer mode. +int32_t uCellMuxEnable(uDeviceHandle_t cellHandle) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uCellPrivateInstance_t *pInstance; + + if (gUCellPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); + + errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + pInstance = pUCellPrivateGetInstance(cellHandle); + if (pInstance != NULL) { + errorCode = uCellMuxPrivateEnable(pInstance); + } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); } @@ -1728,16 +1926,14 @@ bool uCellMuxIsEnabled(uDeviceHandle_t cellHandle) { bool isEnabled = false; uCellPrivateInstance_t *pInstance; - uCellMuxPrivateContext_t *pContext; if (gUCellPrivateMutex != NULL) { U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); - if ((pInstance != NULL) && (pInstance->pMuxContext != NULL)) { - pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; - isEnabled = (pContext->savedAtHandle != NULL); + if (pInstance != NULL) { + isEnabled = uCellMuxPrivateIsEnabled(pInstance); } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); @@ -1753,7 +1949,6 @@ int32_t uCellMuxAddChannel(uDeviceHandle_t cellHandle, { int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; uCellPrivateInstance_t *pInstance; - uCellMuxPrivateContext_t *pContext; if (gUCellPrivateMutex != NULL) { @@ -1761,31 +1956,8 @@ int32_t uCellMuxAddChannel(uDeviceHandle_t cellHandle, errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; pInstance = pUCellPrivateGetInstance(cellHandle); - if ((pInstance != NULL) && (ppDeviceSerial != NULL) && - (channel != U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL) && - (channel != U_CELL_MUX_PRIVATE_CHANNEL_ID_AT) && - ((channel <= U_CELL_MUX_PRIVATE_ADDRESS_MAX) || - (channel == U_CELL_MUX_CHANNEL_ID_GNSS))) { - errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; - if (pInstance->pMuxContext != NULL) { - pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; - if (pContext->savedAtHandle != NULL) { - errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; - if (channel == U_CELL_MUX_CHANNEL_ID_GNSS) { - channel = pContext->channelGnss; - } - if (channel >= 0) { - errorCode = openChannel(pContext, channel, - U_CELL_MUX_PRIVATE_VIRTUAL_SERIAL_BUFFER_LENGTH_BYTES); - if (errorCode == 0) { -#ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX_%d: channel added.\n", channel); -#endif - *ppDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, channel); - } - } - } - } + if (pInstance != NULL) { + errorCode = uCellMuxPrivateAddChannel(pInstance, channel, ppDeviceSerial); } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); diff --git a/cell/src/u_cell_mux_private.c b/cell/src/u_cell_mux_private.c index 9696acea9..5841bd7ed 100644 --- a/cell/src/u_cell_mux_private.c +++ b/cell/src/u_cell_mux_private.c @@ -388,31 +388,6 @@ int32_t uCellMuxPrivateParseCmux(uParseHandle_t parseHandle, void *pUserParam) * PUBLIC FUNCTIONS: MISC * -------------------------------------------------------------- */ -// Get the serial device for the given channel. -uDeviceSerial_t *pUCellMuxPrivateGetDeviceSerial(uCellMuxPrivateContext_t *pContext, - uint8_t channel) -{ - uDeviceSerial_t *pDeviceSerial = NULL; - uCellMuxPrivateChannelContext_t *pChannelContext; - - if ((pContext != NULL) && (channel <= U_CELL_MUX_PRIVATE_CHANNEL_ID_MAX)) { - for (size_t x = 0; - (x < sizeof(pContext->pDeviceSerial) / sizeof(pContext->pDeviceSerial[0])) && - (pDeviceSerial == NULL); x++) { - if (pContext->pDeviceSerial[x] != NULL) { - pChannelContext = (uCellMuxPrivateChannelContext_t *) pUInterfaceContext( - pContext->pDeviceSerial[x]); - if ((pChannelContext != NULL) && !pChannelContext->markedForDeletion && - (pChannelContext->channel == channel)) { - pDeviceSerial = pContext->pDeviceSerial[x]; - } - } - } - } - - return pDeviceSerial; -} - // Copy the settings of one AT client into another AT client. int32_t uCellMuxPrivateCopyAtClient(uAtClientHandle_t atHandleSource, uAtClientHandle_t atHandleDestination) @@ -503,77 +478,6 @@ int32_t uCellMuxPrivateCopyAtClient(uAtClientHandle_t atHandleSource, return errorCode; } -// Close a CMUX channel. -void uCellMuxPrivateCloseChannel(uCellMuxPrivateContext_t *pContext, uint8_t channel) -{ - uDeviceSerial_t *pDeviceSerial = pUCellMuxPrivateGetDeviceSerial(pContext, channel); - - if (pDeviceSerial != NULL) { - pDeviceSerial->close(pDeviceSerial); -#ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX_%d: channel closed.\n", channel); -#endif - } -} - -// Disable multiplexer mode. This involves a few steps: -// -// 1. Send DISC on the virtual serial interface of any currently -// open channels and close the virtual serial interfaces; -// do channel 0, the control interface, last and it will -// end CMUX mode. -// 2. Move AT client operations back to the original AT client. -// 3. DO NOT fee memory; only uCellMuxPrivateRemoveContext() does -// that, to ensure thread-safety. -int32_t uCellMuxPrivateDisable(uCellPrivateInstance_t *pInstance) -{ - int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; - uAtClientHandle_t atHandle; - uCellMuxPrivateContext_t *pContext; - uCellMuxPrivateChannelContext_t *pChannelContext; - - if (pInstance != NULL) { - atHandle = pInstance->atHandle; - errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; - if (pInstance->pMuxContext != NULL) { - pContext = (uCellMuxPrivateContext_t *) pInstance->pMuxContext; - // Start from the top, so that we do channel 0, which - // will always be at index 0, last - for (int32_t x = (sizeof(pContext->pDeviceSerial) / sizeof(pContext->pDeviceSerial[0])) - 1; - x >= 0; x--) { - pChannelContext = (uCellMuxPrivateChannelContext_t *) pUInterfaceContext( - pContext->pDeviceSerial[x]); - if (pChannelContext != NULL) { - uCellMuxPrivateCloseChannel(pContext, pChannelContext->channel); - } - } - if (pContext->savedAtHandle != NULL) { - // Copy the settings of the AT handler on channel 1 - // back into the original one, in case they have changed - errorCode = uCellMuxPrivateCopyAtClient(atHandle, pContext->savedAtHandle); - // While we set the error code above, there's not a whole lot - // we can do if this fails, so continue anyway; close the - // AT handler that was on channel 1 - uAtClientIgnoreAsync(atHandle); - uAtClientRemove(atHandle); - // Unhijack the old AT handler and unlock it - atHandle = pContext->savedAtHandle; - uAtClientUrcHandlerHijackExt(atHandle, NULL, NULL); - uAtClientUnlock(atHandle); - pInstance->atHandle = atHandle; - pContext->savedAtHandle = NULL; -#ifdef U_CELL_MUX_ENABLE_DEBUG - uPortLog("U_CELL_CMUX: closed.\n"); -#endif - } - // Give the module a moment for the MUX switcheroo - uPortTaskBlock(U_CELL_MUX_PRIVATE_ENABLE_DISABLE_DELAY_MS); - } - } - - return (int32_t) errorCode; -} - // Remove the CMUX context for the given cellular instance. void uCellMuxPrivateRemoveContext(uCellPrivateInstance_t *pInstance) { @@ -605,4 +509,8 @@ void uCellMuxPrivateRemoveContext(uCellPrivateInstance_t *pInstance) } } +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: THERE ARE MORE uCellMuxPrivateXxx() FUNCTIONS IN U_CELL_MUX.C + * -------------------------------------------------------------- */ + // End of file diff --git a/cell/src/u_cell_mux_private.h b/cell/src/u_cell_mux_private.h index 28348c2a8..bff517243 100644 --- a/cell/src/u_cell_mux_private.h +++ b/cell/src/u_cell_mux_private.h @@ -203,6 +203,7 @@ typedef enum { typedef enum { U_CELL_MUX_PRIVATE_CHANNEL_ID_CONTROL = 0, /**< fixed by 3GPP 27.010. */ U_CELL_MUX_PRIVATE_CHANNEL_ID_AT = 1, /**< AT channel, common to all u-blox cellular modules. */ + U_CELL_MUX_PRIVATE_CHANNEL_ID_PPP = 2, /**< PPP data channel, common to all u-blox cellular modules. */ U_CELL_MUX_PRIVATE_CHANNEL_ID_MAX = U_CELL_MUX_PRIVATE_ADDRESS_MAX } uCellMuxPrivateChannelId_t; @@ -308,7 +309,170 @@ typedef struct { } uCellMuxPrivateChannelContext_t; /* ---------------------------------------------------------------- - * FUNCTIONS: 3GPP 27.010 CMUX ENCODE/DECODE + * FUNCTIONS: PRIVATE TO CELLULAR (SEE U_CELL_MUX.C) + * -------------------------------------------------------------- */ + +/** Enable multiplexer mode. Puts the cellular module's AT + * interface into multiplexer (3GPP 27.010 CMUX) mode. This + * is useful when you want to access a GNSS module that is + * connected via, or embedded inside, a cellular module as if it + * were connected directly to this MCU via a serial interface (see + * uCellMuxAddChannel()). Note that this function _internally_ + * opens and uses a CMUX channel for the AT interface, you do not + * have to do that. The AT handle that was originally passed to + * uCellAdd() will remain locked, the handle of the new one that is + * created for use internally can be obtained by calling + * uCellAtClientHandleGet(); uCellAtClientHandleGet() will always + * return the AT handle currently in use. + * + * Whether multiplexer mode is supported or not depends on the cellular + * module and the interface in use: for instance a USB interface to + * a module does not support multiplexer mode. + * + * The module must be powered on for this to work. Returns success + * without doing anything if multiplexer mode is already enabled. + * Multiplexer mode does not survive a power-cycle, either deliberate + * (with uCellPwrOff(), uCellPwrReboot(), etc.) or accidental, and + * cannot be used with 3GPP power saving (since it will also be + * reset during module deep sleep). + * + * Note: if you have passed the AT handle to a GNSS instance (e.g. + * via uGnssAdd()) it will stop working when multiplexer mode is + * enabled (because the AT handle will have been changed), hence you + * should enable multiplexer mode _before_ calling uGnssAdd() + * (and, likewise, remove any GNSS instance before disabling + * multiplexer mode). However, if you have enabled multiplexer + * mode it is much better to call uCellMuxAddChannel() with + * #U_CELL_MUX_CHANNEL_ID_GNSS and then you can pass the + * #uDeviceSerial_t handle that returns to uGnssAdd() (with the + * transport type #U_GNSS_TRANSPORT_VIRTUAL_SERIAL) and you will + * have streamed position. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pInstance a pointer to the cellular instance. + * @return zero on success or negative error code + * on failure. + */ +int32_t uCellMuxPrivateEnable(uCellPrivateInstance_t *pInstance); + +/** Determine if the multiplexer is currently enabled. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pInstance a pointer to the cellular instance. + * @return true if the multiplexer is enabled, + * else false. + */ +bool uCellMuxPrivateIsEnabled(uCellPrivateInstance_t *pInstance); + +/** Add a multiplexer channel; may be called after uCellMuxEnable() + * has returned success in order to, for instance, create a virtual + * serial port to a GNSS chip inside a SARA-R422M8S or SARA-R510M8S + * module. The virtual serial port handle returned in *ppDeviceSerial + * can be used in #uDeviceCfg_t to open the GNSS device using the + * uDevice API, or it can be passed to uGnssAdd() (with the transport + * type #U_GNSS_TRANSPORT_VIRTUAL_SERIAL) if you prefer to use the + * uGnss API the hard way. + * + * If the channel is already open, this function returns success + * without doing anything. An error is returned if uCellMuxEnable() + * has not been called. + * + * Note: there is a known issue with SARA-R5 modules where, if a GNSS + * multiplexer channel is opened, closed, and then re-opened the GNSS + * chip will be unresponsive. For that case, please open the GNSS + * multiplexer channel once at start of day. + * + * UART POWER SAVING: when UART power saving is enabled in the module + * any constraints arising will also apply to a multiplexer channel; + * specifically, if a DTR pin is not used to wake-up the module, i.e. + * the module supports and is using the "wake up on TX activity" mode + * of UART power saving then, though the AT interface will continue + * to work correctly (as it knows to expect loss of the first few + * characters of an AT string), the other multiplexer channels have + * the same restriction and have no such automated protection. Hence + * if you (a) expect to use a multiplexer channel to communicate with + * a GNSS chip in a cellular module and (b) are not able to use a DTR + * pin to wake the module up from power-saving, then you should call + * uCellPwrDisableUartSleep() to disable UART sleep while you run the + * multiplexer channel (and uCellPwrEnableUartSleep() to re-enable it + * afterwards). + * + * NOTES ON DEVICE SERIAL OPERATION: the operation of *pDeviceSerial + * is constrained in certain ways, since what you have is not a real + * serial port, it is a virtual serial port which has hijacked some + * of the functionality of the physical serial port that was + * previously running, see notes below, but particularly flow control, + * or not taking data out of one or more multiplexed serial ports fast + * enough, can have an adverse effect on other multiplexed serial ports. + * This is difficult to avoid since they are on the same transport. Hence + * it is important to service your multiplexed serial ports often or, + * alternatively, you may call serialDiscardOnFlowControl() with true + * on any serial port where you are happy for any overruns to be + * discarded (e.g. the GNSS one), so that it cannot possibly interfere + * with others (e.g. the AT command one). + * + * The stack size and priority of any event serial callbacks are not + * respected: what you end up with is #U_CELL_MUX_CALLBACK_TASK_PRIORITY + * and #U_CELL_MUX_CALLBACK_TASK_STACK_SIZE_BYTES since a common + * event queue is used for all serial devices. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pInstance a pointer to the cellular instance. + * @param channel the channel number to open; channel + * numbers are module-specific, however + * the value #U_CELL_MUX_CHANNEL_ID_GNSS + * can be used, in all cases, to open a + * channel to an embedded GNSS chip. + * Note that channel zero is reserved + * for management operations and channel + * one is the existing AT interface; + * neither value can be used here. + * @param[out] ppDeviceSerial a pointer to a place to put the + * handle of the virtual serial port + * that is the multiplexer channel. + * @return zero on success or negative error + * code on failure. + */ +int32_t uCellMuxPrivateAddChannel(uCellPrivateInstance_t *pInstance, + int32_t channel, + uDeviceSerial_t **ppDeviceSerial); + +/** Disable CMUX on the given cellular instance. This does NOT free + * memory to ensure thread safety; only uCellMuxPrivateRemoveContext() + * frees memory. Note that this may cause the atHandle in pInstance to + * change, so if you have a local copy of it you will need to refresh + * it once this function returns. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pInstance a pointer to the cellular instance. + */ +int32_t uCellMuxPrivateDisable(uCellPrivateInstance_t *pInstance); + +/** Get the serial device for the given channel. + * + * @param[in] pContext the mux context. + * @param channel the channel number. + * @return the serial device. + */ +uDeviceSerial_t *pUCellMuxPrivateGetDeviceSerial(uCellMuxPrivateContext_t *pContext, + uint8_t channel); + +/** Close a CMUX channel. This does NOT free memory to ensure + * thread safety; only uCellMuxPrivateRemoveContext() frees memory. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pContext the mux context. + * @param channel the channel number to close. + */ +void uCellMuxPrivateCloseChannel(uCellMuxPrivateContext_t *pContext, uint8_t channel); + +/* ---------------------------------------------------------------- + * FUNCTIONS: 3GPP 27.010 CMUX ENCODE/DECODE (SEE U_CELL_MUX_PRIVATE.C) * -------------------------------------------------------------- */ /** Encode a 3GPP 27.010 mux frame. @@ -364,18 +528,9 @@ int32_t uCellMuxPrivateEncode(uint8_t address, uCellMuxPrivateFrameType_t type, int32_t uCellMuxPrivateParseCmux(uParseHandle_t parseHandle, void *pUserParam); /* ---------------------------------------------------------------- - * FUNCTIONS: MISC + * FUNCTIONS: MISC (SEE U_CELL_MUX_PRIVATE.C) * -------------------------------------------------------------- */ -/** Get the serial device for the given channel. - * - * @param[in] pContext the mux context. - * @param channel the channel number. - * @return the serial device. - */ -uDeviceSerial_t *pUCellMuxPrivateGetDeviceSerial(uCellMuxPrivateContext_t *pContext, - uint8_t channel); - /** Copy the settings of one AT client into another AT client. * * @param atHandleSource the source AT client. @@ -385,28 +540,6 @@ uDeviceSerial_t *pUCellMuxPrivateGetDeviceSerial(uCellMuxPrivateContext_t *pCont int32_t uCellMuxPrivateCopyAtClient(uAtClientHandle_t atHandleSource, uAtClientHandle_t atHandleDestination); -/** Close a CMUX channel. This does NOT free memory to ensure - * thread safety; only uCellMuxPrivateRemoveContext() frees memory. - * - * Note: gUCellPrivateMutex should be locked before this is called. - * - * @param[in] pContext the mux context. - * @param channel the channel number to close. - */ -void uCellMuxPrivateCloseChannel(uCellMuxPrivateContext_t *pContext, uint8_t channel); - -/** Disable CMUX on the given cellular instance. This does NOT free - * memory to ensure thread safety; only uCellMuxPrivateRemoveContext() - * frees memory. Note that this may cause the atHandle in pInstance to - * change, so if you have a local copy of it you will need to refresh - * it once this function returns. - * - * Note: gUCellPrivateMutex should be locked before this is called. - * - * @param[in] pInstance a pointer to the cellular instance. - */ -int32_t uCellMuxPrivateDisable(uCellPrivateInstance_t *pInstance); - /** Remove the CMUX context for the given cellular instance. If CMUX * is active it will be disabled first. Note that this may cause the * atHandle in pInstance to change, so if you have a local copy of it diff --git a/cell/src/u_cell_net.c b/cell/src/u_cell_net.c index b2a6e2fa1..60c1ea1f8 100644 --- a/cell/src/u_cell_net.c +++ b/cell/src/u_cell_net.c @@ -53,6 +53,8 @@ #include "u_at_client.h" +#include "u_sock.h" + #include "u_cell_module_type.h" #include "u_cell_file.h" #include "u_cell.h" // Order is @@ -61,6 +63,7 @@ #include "u_cell_info.h" #include "u_cell_apn_db.h" #include "u_cell_mno_db.h" +#include "u_cell_ppp_shared.h" #include "u_cell_pwr_private.h" @@ -262,10 +265,22 @@ static void activateContextCallback(uAtClientHandle_t atHandle, void *pParameter) { uCellPrivateInstance_t *pInstance = (uCellPrivateInstance_t *) pParameter; + uDeviceHandle_t cellHandle = pInstance->cellHandle; + char buffer[U_CELL_NET_IP_ADDRESS_SIZE]; + uSockAddress_t address; + uSockIpAddress_t *pIpAddress = NULL; (void) atHandle; activateContext(pInstance, U_CELL_NET_CONTEXT_ID, U_CELL_NET_PROFILE_ID); + + if (U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_PPP)) { + if ((uCellNetGetIpAddressStr(cellHandle, buffer) > 0) && + (uSockStringToAddress(buffer, &address) > 0)) { + pIpAddress = &address.ipAddress; + } + uPortPppReconnect(cellHandle, pIpAddress); + } } // Set the current network status. @@ -1405,7 +1420,7 @@ static int32_t setAuthenticationMode(const uCellPrivateInstance_t *pInstance, const char *pPassword, uCellNetAuthenticationMode_t overrideAuthenticationMode) { - int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + int32_t errorCodeOrAuthenticationMode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; uAtClientHandle_t atHandle = pInstance->atHandle; uCellNetAuthenticationMode_t authenticationMode = pInstance->authenticationMode; @@ -1457,10 +1472,14 @@ static int32_t setAuthenticationMode(const uCellPrivateInstance_t *pInstance, } } uAtClientCommandStopReadResponse(atHandle); - errorCode = uAtClientUnlock(atHandle); + errorCodeOrAuthenticationMode = uAtClientUnlock(atHandle); } - return errorCode; + if (errorCodeOrAuthenticationMode == 0) { + errorCodeOrAuthenticationMode = authenticationMode; + } + + return errorCodeOrAuthenticationMode; } // Get the APN currently in use 3GPP commands, required @@ -2190,6 +2209,40 @@ static int32_t parseDeepScanLine(uAtClientHandle_t atHandle, return errorCodeOrNumber; } +// Make the PPP connection +static void connectPpp(uDeviceHandle_t cellHandle, + const char *pUsername, + const char *pPassword, + uCellNetAuthenticationMode_t authenticationMode) +{ + char buffer1[U_CELL_NET_IP_ADDRESS_SIZE]; + char buffer2[U_CELL_NET_IP_ADDRESS_SIZE]; + uSockAddress_t address; + uSockIpAddress_t *pIpAddress = NULL; + uSockAddress_t dnsAddressPrimary; + uSockIpAddress_t *pDnsIpAddressPrimary = NULL; + uSockAddress_t dnsAddressSecondary; + uSockIpAddress_t *pDnsIpAddressSecondary = NULL; + + if ((uCellNetGetIpAddressStr(cellHandle, buffer1) > 0) && + (uSockStringToAddress(buffer1, &address) > 0)) { + pIpAddress = &address.ipAddress; + } + if (uCellNetGetDnsStr(cellHandle, false, buffer1, buffer2) > 0) { + if (uSockStringToAddress(buffer1, &dnsAddressPrimary) > 0) { + pDnsIpAddressPrimary = &dnsAddressPrimary.ipAddress; + } + if (uSockStringToAddress(buffer2, &dnsAddressSecondary) > 0) { + pDnsIpAddressSecondary = &dnsAddressSecondary.ipAddress; + } + } + // uPortPppAuthenticationMode_t matches uCellNetAuthenticationMode_t + // so this is fine + uPortPppConnect(cellHandle, pIpAddress, pDnsIpAddressPrimary, + pDnsIpAddressSecondary, pUsername, pPassword, + (uPortPppAuthenticationMode_t) authenticationMode); +} + /* ---------------------------------------------------------------- * PUBLIC FUNCTIONS * -------------------------------------------------------------- */ @@ -2206,15 +2259,27 @@ int32_t uCellNetConnect(uDeviceHandle_t cellHandle, char buffer[15]; // At least 15 characters for the IMSI const char *pApnConfig = NULL; uCellNetAuthenticationMode_t overrideAuthenticationMode = U_CELL_NET_AUTHENTICATION_MODE_NOT_SET; + uCellNetAuthenticationMode_t authenticationModeUsed = overrideAuthenticationMode; + bool hasPpp = false; if (gUCellPrivateMutex != NULL) { + // It is possible that the user already has a PDP context + // up and wants to change it, in which case + // handleExistingContext() will close it. However, the + // PPP connection _must_ be taken down before this happens + // and that won't be able to act while the cellular API + // mutex is locked, so we alays take the PPP connection + // down first + uPortPppDisconnect(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; if ((pInstance != NULL) && ((pUsername == NULL) || (pPassword != NULL))) { + hasPpp = U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_PPP); errorCode = (int32_t) U_CELL_ERROR_NOT_CONNECTED; if (uCellPrivateIsRegistered(pInstance)) { @@ -2301,6 +2366,10 @@ int32_t uCellNetConnect(uDeviceHandle_t cellHandle, pUsername, pPassword, overrideAuthenticationMode); + if (errorCode >= 0) { + authenticationModeUsed = errorCode; + errorCode = 0; + } } } if (errorCode == 0) { @@ -2410,6 +2479,11 @@ int32_t uCellNetConnect(uDeviceHandle_t cellHandle, } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + + if ((errorCode == 0) && hasPpp) { + // Any PPP connection the platform may have attached is now up + connectPpp(cellHandle, pUsername, pPassword, authenticationModeUsed); + } } return errorCode; @@ -2507,15 +2581,27 @@ int32_t uCellNetActivate(uDeviceHandle_t cellHandle, char imsi[15]; const char *pApnConfig = NULL; uCellNetAuthenticationMode_t overrideAuthenticationMode = U_CELL_NET_AUTHENTICATION_MODE_NOT_SET; + uCellNetAuthenticationMode_t authenticationModeUsed = overrideAuthenticationMode; + bool hasPpp = false; if (gUCellPrivateMutex != NULL) { + // It is possible that the user already has a PDP context + // up and wants to change it, in which case + // handleExistingContext() will close it. However, the + // PPP connection _must_ be taken down before this happens + // and that won't be able to act while the cellular API + // mutex is locked, so we alays take the PPP connection + // down first + uPortPppDisconnect(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; if ((pInstance != NULL) && ((pUsername == NULL) || (pPassword != NULL))) { + hasPpp = U_CELL_PRIVATE_HAS(pInstance->pModule, U_CELL_PRIVATE_FEATURE_PPP); errorCode = (int32_t) U_CELL_ERROR_NOT_REGISTERED; if (uCellPrivateIsRegistered(pInstance)) { @@ -2579,6 +2665,10 @@ int32_t uCellNetActivate(uDeviceHandle_t cellHandle, pUsername, pPassword, overrideAuthenticationMode); + if (errorCode >= 0) { + authenticationModeUsed = errorCode; + errorCode = 0; + } } if (errorCode == 0) { if (!uCellPrivateIsRegistered(pInstance)) { @@ -2637,6 +2727,11 @@ int32_t uCellNetActivate(uDeviceHandle_t cellHandle, } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + + if ((errorCode == 0) && hasPpp) { + // Any PPP connection the platform may have attached is now up + connectPpp(cellHandle, pUsername, pPassword, authenticationModeUsed); + } } return errorCode; @@ -2652,6 +2747,10 @@ int32_t uCellNetDeactivate(uDeviceHandle_t cellHandle, if (gUCellPrivateMutex != NULL) { + // Let the platform, know that the PPP connection, + // is going down + uPortPppDisconnect(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); @@ -2674,7 +2773,8 @@ int32_t uCellNetDeactivate(uDeviceHandle_t cellHandle, errorCode = deactivate(pInstance, U_CELL_NET_CONTEXT_ID); } } - if (errorCode != 0) { + if (errorCode == 0) { + } else { uPortLog("U_CELL_NET: unable to deactivate context.\n"); } } @@ -2697,6 +2797,10 @@ int32_t uCellNetDisconnect(uDeviceHandle_t cellHandle, if (gUCellPrivateMutex != NULL) { + // Let the platform, know that the PPP connection, + // is going down + uPortPppDisconnect(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); diff --git a/cell/src/u_cell_ppp.c b/cell/src/u_cell_ppp.c new file mode 100644 index 000000000..88a941e3e --- /dev/null +++ b/cell/src/u_cell_ppp.c @@ -0,0 +1,589 @@ +/* + * Copyright 2019-2023 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 Implementation of the PPP interface for cellular. + */ + +#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" // memset(), strlen() +#include "stdio.h" // snprintf() +#include "ctype.h" // isprint() + +#include "u_cfg_sw.h" +#include "u_error_common.h" + +#include "u_port_clib_platform_specific.h" /* integer stdio, must be included + before the other port files if + any print or scan function is used. */ +#include "u_port.h" +#include "u_port_os.h" +#include "u_port_heap.h" +#include "u_port_debug.h" +#include "u_port_uart.h" +#include "u_port_event_queue.h" +#include "u_port_ppp.h" + +#include "u_interface.h" +#include "u_ringbuffer.h" + +#include "u_at_client.h" + +#include "u_device_shared.h" + +#include "u_cell_module_type.h" +#include "u_cell_file.h" +#include "u_cell.h" // Order is +#include "u_cell_net.h" // important here +#include "u_cell_private.h" // don't change it + +#include "u_cell_pwr.h" +#include "u_cell_pwr_private.h" +#include "u_cell_mux.h" +#include "u_cell_mux_private.h" + +#include "u_cell_ppp_shared.h" + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +#ifndef U_CELL_PPP_DIAL_STRING +/** The string that performs a PPP dialed-up, intended to have the. + * %d and %s replaced with the PDP context ID and the AT command + * send delimiter respectively. + */ +# define U_CELL_PPP_DIAL_STRING "ATD*99***%d#%s" +#endif + +#ifndef U_CELL_PPP_DIAL_RESPONSE_STRING +/** The string that indicates PPP has connected, sent by the module + * in response to #U_CELL_PPP_DIAL_STRING. + * Note: deliberately omits the end as some modules (e.g. SARA-R4) + * respond with things like "\r\n CONNECT 150000000\r\n". + */ +# define U_CELL_PPP_DIAL_RESPONSE_STRING "\r\nCONNECT" +#endif + +#ifndef U_CELL_PPP_HANG_UP_STRING +/** The string to send to hang-up the PPP interface. + */ +# define U_CELL_PPP_HANG_UP_STRING "~+++" +#endif + +#ifndef U_CELL_PPP_HANG_UP_RESPONSE_STRING +/** The string that indicates a successful hang-up of the PPP + * interface, sent by the module in response to + * #U_CELL_PPP_HANG_UP_STRING. + */ +# define U_CELL_PPP_HANG_UP_RESPONSE_STRING "\r\nNO CARRIER\r\n" +#endif + +#ifndef U_CELL_PPP_D2_STRING +/** The &D2 string, to be sent to set the hang-up action to really + * mean hang-up, rather than return to AT command mode, where the + * %s is intended to be replaced by the AT command delimiter. + */ +# define U_CELL_PPP_D2_STRING "AT&D2%s" +#endif + +#ifndef U_CELL_PPP_OK_STRING +/** The "OK string on the PPP interface when operated in command + * mode (i.e. an AT interface), sent in response to + * #U_CELL_PPP_D2_STRING. + */ +# define U_CELL_PPP_OK_STRING "\r\nOK\r\n" +#endif + +#ifndef U_CELL_PPP_ERROR_STRING +/** The "ERROR" string on the PPP interface when operated in + * command mode (i.e. an AT interface), may be sent at any time. + */ +# define U_CELL_PPP_ERROR_STRING "\r\nERROR\r\n" +#endif + +#ifndef U_CELL_PPP_ERROR_STRING_LENGTH +/** The length of #U_CELL_PPP_ERROR_STRING. + */ +# define U_CELL_PPP_ERROR_STRING_LENGTH 9 +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/** The context data for PPP operation. + */ +typedef struct { + uDeviceHandle_t cellHandle; + uDeviceSerial_t *pDeviceSerial; + uPortPppReceiveCallback_t *pReceiveCallback; + void *pReceiveCallbackParam; + char *pReceiveBuffer; + size_t receiveBufferSize; + bool receiveBufferIsMalloced; + bool muxAlreadyEnabled; + bool uartSleepWakeOnDataWasEnabled; +} uCellPppContext_t; + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +// Check if the given buffer contains the given string; returns +// the number of characters matched: if this is non-zero but less +// than the length of pStr then the caller should keep that many +// characters in the buffer and call this again with any additions. +static int32_t containsString(const char *pBuffer, size_t size, const char *pStr) +{ + size_t count = 0; + size_t y = 0; + + if (pStr != NULL) { + y = strlen(pStr); + for (size_t x = 0; (x < size) && (count < y); x++) { + if (*pBuffer == *(pStr + count)) { + count++; + } else { + count = 0; + if (*pBuffer == *pStr) { + count = 1; + } + } + pBuffer++; + } + } + + return count; +} + +// Print out a buffer of sent or received characters nicely. +static void printBuffer(const char *pBuffer, size_t size) +{ + for (size_t x = 0; x < size; x++) { + if (!isprint((int32_t) *pBuffer)) { + // Print the hex + uPortLog("[%02x]", (unsigned char) *pBuffer); + } else { + // Print the ASCII character + uPortLog("%c", *pBuffer); + } + pBuffer++; + } +} + +// A very minimal AT send/receive function to avoid having +// to use the full AT parser on the PPP channel. +static int32_t sendExpect(uCellPppContext_t *pContext, + const char *pSendString, + const char *pExpectedResponseString, + bool (*pKeepGoingCallback) (uDeviceHandle_t cellHandle), + size_t timeoutSeconds) +{ + int32_t errorCode = 0; + uDeviceSerial_t *pDeviceSerial = pContext->pDeviceSerial; + uDeviceHandle_t cellHandle = pContext->cellHandle; + char buffer[64]; + int32_t timeoutMs = timeoutSeconds * 1000; + int32_t startTimeMs; + int32_t expectedResponseLength; + int32_t x = 0; + int32_t y = 0; + + if (pSendString != NULL) { + x = strlen(pSendString); + errorCode = pDeviceSerial->write(pDeviceSerial, pSendString, x); + } + if (errorCode == x) { + if (x > 0) { + uPortLog("U_CELL_PPP: sent "); + printBuffer(pSendString, x); + uPortLog("\n"); + } + if (pExpectedResponseString != NULL) { + expectedResponseLength = strlen(pExpectedResponseString); + // Wait for a response to come back + errorCode = (int32_t) U_ERROR_COMMON_TIMEOUT; + startTimeMs = uPortGetTickTimeMs(); + while ((errorCode == (int32_t) U_ERROR_COMMON_TIMEOUT) && + (uPortGetTickTimeMs() - startTimeMs < timeoutMs) && + ((pKeepGoingCallback == NULL) || (pKeepGoingCallback(cellHandle)))) { + x = pDeviceSerial->read(pDeviceSerial, buffer + y, sizeof(buffer) - y); + if (x > 0) { + x += y; + uPortLog("U_CELL_PPP: received "); + printBuffer(buffer, x); + uPortLog("\n"); + y = containsString(buffer, x, pExpectedResponseString); + if (y == expectedResponseLength) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } else if (y == 0) { + y = containsString(buffer, x, U_CELL_PPP_ERROR_STRING); + if (y == U_CELL_PPP_ERROR_STRING_LENGTH) { + errorCode = (int32_t) U_ERROR_COMMON_DEVICE_ERROR; + } + } + // Keep y characters, in case there was a partial match, + // moved down to the start of the buffer + memmove(buffer, buffer + y, sizeof(buffer) - y); + } else { + // Wait a little while for more to arrive + uPortTaskBlock(100); + } + } + } + } else { + errorCode = (int32_t) U_ERROR_COMMON_DEVICE_ERROR; + } + + return errorCode; +} + +// Make the PPP connection over the AT interface. +static int32_t connectPpp(uCellPppContext_t *pContext, + bool (*pKeepGoingCallback) (uDeviceHandle_t cellHandle)) +{ + int32_t errorCode; + char buffer[16]; // Enough room for "ATD*99***1#\r" + + // Before we do anything else, send AT&D2: this is necessary + // to ensure that sending "~+++" deactivates PPP, rather than + // just returning to command mode, and it HAS to be done on + // this channel as the one that was done at start of day + // on the original AT channel does not apply here (i.e. all + // the AT&x commmands are AT-channel-specific) + snprintf(buffer, sizeof(buffer), U_CELL_PPP_D2_STRING, U_AT_CLIENT_COMMAND_DELIMITER); + errorCode = sendExpect(pContext, buffer, U_CELL_PPP_OK_STRING, + pKeepGoingCallback, U_AT_CLIENT_DEFAULT_TIMEOUT_MS / 1000); + if (errorCode == 0) { + snprintf(buffer, sizeof(buffer), U_CELL_PPP_DIAL_STRING, + U_CELL_NET_CONTEXT_ID, U_AT_CLIENT_COMMAND_DELIMITER); + errorCode = sendExpect(pContext, buffer, U_CELL_PPP_DIAL_RESPONSE_STRING, + pKeepGoingCallback, U_CELL_PPP_DIAL_TIMEOUT_SECONDS); + } + + return errorCode; +} + +// Close the PPP interface. +void closePpp(uCellPrivateInstance_t *pInstance, + bool pppTerminateRequired) +{ + uCellPppContext_t *pContext; + uDeviceSerial_t *pDeviceSerial; + size_t y; + const char *pStr; + + if (pInstance != NULL) { + pContext = (uCellPppContext_t *) pInstance->pPppContext; + // Note: we don't free the context or any allocated receive + // buffer to ensure thread-safety of the callback + if (pContext != NULL) { + if (pContext->pDeviceSerial != NULL) { + pDeviceSerial = pContext->pDeviceSerial; + if (pppTerminateRequired) { + // Send the sequence hang-up sequence, "~+++", as + // individual characters (so that the module can + // distinguish them from data) on the channel, + // to ensure that it is hung-up, and wait for + // the "NO CARRIER" response + y = strlen(U_CELL_PPP_HANG_UP_STRING); + pStr = U_CELL_PPP_HANG_UP_STRING; + for (size_t x = 0; x < y; x++, pStr++) { + pDeviceSerial->write(pDeviceSerial, pStr, 1); + } + sendExpect(pContext, NULL, + U_CELL_PPP_HANG_UP_RESPONSE_STRING, NULL, + U_CELL_PPP_HANG_UP_TIMEOUT_SECONDS); + } + // Remove the multiplexer channel + uCellMuxPrivateCloseChannel((uCellMuxPrivateContext_t *) pInstance->pMuxContext, + U_CELL_MUX_PRIVATE_CHANNEL_ID_PPP); + pContext->pDeviceSerial = NULL; + } + if (!pContext->muxAlreadyEnabled) { + // Disable the multiplexer if one was in use + // and it was us who started it + uCellMuxPrivateDisable(pInstance); + } + // Re-enable UART sleep if we had switched it off + if (pContext->uartSleepWakeOnDataWasEnabled) { + uCellPwrPrivateEnableUartSleep(pInstance); + } + } + } +} + +// Callback for data received over the PPP CMUX channel. +static void callback(uDeviceSerial_t *pDeviceSerial, + uint32_t eventBitmask, void *pParameters) +{ + uCellPppContext_t *pContext = (uCellPppContext_t *) pParameters; + int32_t bytesRead; + + if ((eventBitmask & U_PORT_UART_EVENT_BITMASK_DATA_RECEIVED) && + (pContext != NULL) && (pContext->pReceiveBuffer != NULL)) { + bytesRead = pDeviceSerial->read(pDeviceSerial, + pContext->pReceiveBuffer, + pContext->receiveBufferSize); + if ((bytesRead > 0) && (pContext->pReceiveCallback != NULL)) { + pContext->pReceiveCallback(pContext->cellHandle, + pContext->pReceiveBuffer, bytesRead, + pContext->pReceiveCallbackParam); + } + } +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS THAT ARE PRIVATE TO CELLULAR + * -------------------------------------------------------------- */ + +// Free context. +void uCellPppPrivateRemoveContext(uCellPrivateInstance_t *pInstance) +{ + uCellPppContext_t *pContext; + + if ((pInstance != NULL) && (pInstance->pPppContext != NULL)) { + closePpp(pInstance, false); + pContext = (uCellPppContext_t *) pInstance->pPppContext; + if (pContext->receiveBufferIsMalloced) { + // The receive buffer was malloc'ed, free it now + uPortFree(pContext->pReceiveBuffer); + } + uPortFree(pContext); + pInstance->pPppContext = NULL; + } +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +// Open the PPP interface of a cellular module. +int32_t uCellPppOpen(uDeviceHandle_t cellHandle, + uPortPppReceiveCallback_t *pReceiveCallback, + void *pReceiveCallbackParam, + char *pReceiveData, size_t receiveDataSize, + bool (*pKeepGoingCallback) (uDeviceHandle_t cellHandle)) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uCellPrivateInstance_t *pInstance; + uCellPppContext_t *pContext; + uDeviceSerial_t *pDeviceSerial; + bool pppTerminateRequired = false; + + if (gUCellPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); + + errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + pInstance = pUCellPrivateGetInstance(cellHandle); + if (pInstance != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + if (U_CELL_PRIVATE_HAS(pInstance->pModule, + U_CELL_PRIVATE_FEATURE_PPP)) { + // No point even trying if we're not on the network + errorCode = (int32_t) U_CELL_ERROR_NOT_REGISTERED; + if (uCellPrivateIsRegistered(pInstance)) { + pContext = (uCellPppContext_t *) pInstance->pPppContext; + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pContext == NULL) { + // Allocate memory for the context + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + pContext = (uCellPppContext_t *) pUPortMalloc(sizeof(*pContext)); + if (pContext != NULL) { + memset(pContext, 0, sizeof(*pContext)); + pContext->cellHandle = cellHandle; + } + } + if ((pContext != NULL) && (pContext->pDeviceSerial == NULL)) { + // Have a context and the serial device for PPP is not set up + if (pContext->receiveBufferIsMalloced) { + // There is a previously malloc'ed buffer: free it now + uPortFree(pContext->pReceiveBuffer); + pContext->receiveBufferIsMalloced = false; + } + pContext->pReceiveBuffer = pReceiveData; + pContext->receiveBufferSize = receiveDataSize; + if ((pReceiveCallback != NULL) && (pContext->pReceiveBuffer == NULL)) { + // Allocate memory for the receive data buffer + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + pContext->pReceiveBuffer = (char *) pUPortMalloc(receiveDataSize); + if (pContext->pReceiveBuffer != NULL) { + pContext->receiveBufferIsMalloced = true; + } + } + if ((pContext != NULL) && + ((pReceiveCallback == NULL) || (pContext->pReceiveBuffer != NULL))) { + // Have a context and either don't need a receive buffer or we have one + pInstance->pPppContext = pContext; + pContext->pReceiveCallback = pReceiveCallback; + pContext->pReceiveCallbackParam = pReceiveCallbackParam; + // Determine if CMUX and "wake-up on UART data line" UART power saving + // are already enabled + pContext->muxAlreadyEnabled = uCellMuxPrivateIsEnabled(pInstance); + pContext->uartSleepWakeOnDataWasEnabled = uCellPwrPrivateUartSleepIsEnabled(pInstance); + if (uCellPwrPrivateGetDtrPowerSavingPin(pInstance) >= 0) { + pContext->uartSleepWakeOnDataWasEnabled = false; + } + // Enable CMUX + errorCode = uCellMuxPrivateEnable(pInstance); + if (errorCode == 0) { + // Add the PPP channel + errorCode = uCellMuxPrivateAddChannel(pInstance, + U_CELL_MUX_PRIVATE_CHANNEL_ID_PPP, + &(pContext->pDeviceSerial)); + } + if (errorCode == 0) { + // If we're on wake-up-on-data UART power saving and CMUX, switch + // UART power saving off, just in case + errorCode = uCellPwrPrivateDisableUartSleep(pInstance); + } + if (errorCode == 0) { + pDeviceSerial = pContext->pDeviceSerial; + // We now have a second serial interface to + // the module: do a PPP dial-up on it. Could + // attach an AT handler to it but that would be + // an overhead in terms of RAM that we can do + // without, instead just send the dial-up string + // and wait for the response + uPortTaskBlock(1000); + errorCode = connectPpp(pContext, pKeepGoingCallback); + if ((errorCode == 0) && (pReceiveCallback != NULL)) { + pppTerminateRequired = (errorCode == 0); + // Note: the priority and stack size parameters + // to eventCallbackSet() are ignored, hence use of -1 + errorCode = pDeviceSerial->eventCallbackSet(pDeviceSerial, + U_DEVICE_SERIAL_EVENT_BITMASK_DATA_RECEIVED, + callback, pContext, + -1, -1); + } + } + if (errorCode < 0) { + // Tidy up on error + closePpp(pInstance, pppTerminateRequired); + } + } + } + } + } + } + + U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + } + + return errorCode; +} + +// Close the PPP interface of a cellular module. +int32_t uCellPppClose(uDeviceHandle_t cellHandle, + bool pppTerminateRequired) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uCellPrivateInstance_t *pInstance; + + if (gUCellPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); + + errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + pInstance = pUCellPrivateGetInstance(cellHandle); + if (pInstance != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + if (U_CELL_PRIVATE_HAS(pInstance->pModule, + U_CELL_PRIVATE_FEATURE_PPP)) { + closePpp(pInstance, pppTerminateRequired); + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } + } + + U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + } + + return errorCode; +} + +// Transmit a buffer of data over the PPP interface. +int32_t uCellPppTransmit(uDeviceHandle_t cellHandle, + const char *pData, size_t dataSize) +{ + int32_t errorCodeOrBytesSent = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; + uCellPrivateInstance_t *pInstance; + uCellPppContext_t *pContext; + uDeviceSerial_t *pDeviceSerial; + + if (gUCellPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); + + errorCodeOrBytesSent = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + pInstance = pUCellPrivateGetInstance(cellHandle); + if (pInstance != NULL) { + errorCodeOrBytesSent = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + if (U_CELL_PRIVATE_HAS(pInstance->pModule, + U_CELL_PRIVATE_FEATURE_PPP)) { + errorCodeOrBytesSent = (int32_t) U_ERROR_COMMON_NOT_FOUND; + pContext = (uCellPppContext_t *) pInstance->pPppContext; + if ((pContext != NULL) && (pContext->pDeviceSerial != NULL)) { + pDeviceSerial = pContext->pDeviceSerial; + errorCodeOrBytesSent = pDeviceSerial->write(pDeviceSerial, + pData, dataSize); + } + } + } + + U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + } + + return errorCodeOrBytesSent; +} + +// Free memory. +void uCellPppFree(uDeviceHandle_t cellHandle) +{ + uCellPrivateInstance_t *pInstance; + + if (gUCellPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); + + pInstance = pUCellPrivateGetInstance(cellHandle); + if ((pInstance != NULL) && (U_CELL_PRIVATE_HAS(pInstance->pModule, + U_CELL_PRIVATE_FEATURE_PPP))) { + uCellPppPrivateRemoveContext(pInstance); + } + + U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); + } +} + +// End of file diff --git a/cell/src/u_cell_ppp_private.h b/cell/src/u_cell_ppp_private.h new file mode 100644 index 000000000..b8e2b4eba --- /dev/null +++ b/cell/src/u_cell_ppp_private.h @@ -0,0 +1,66 @@ +/* + * Copyright 2019-2023 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_CELL_PPP_PRIVATE_H_ +#define _U_CELL_PPP_PRIVATE_H_ + +/* Only header files representing a direct and unavoidable + * dependency between the API of this module and the API + * of another module should be included here; otherwise + * please keep #includes to your .c files. */ + +/** @file + * @brief This header file defines functions that are private to + * cellular and associated with PPP. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * FUNCTIONS + * -------------------------------------------------------------- */ + +/** Remove the PPP context for the given cellular instance. If PPP + * is active it will be disabled first. Note that this may cause the + * atHandle in pInstance to change, so if you have a local copy of it + * you will need to refresh it once this function returns. + * + * This should be called _before_ uCellMuxPrivateRemoveContext(), + * since it cleans up CMUX stuff of its own. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param[in] pInstance a pointer to the cellular instance. + */ +void uCellPppPrivateRemoveContext(uCellPrivateInstance_t *pInstance); + +#ifdef __cplusplus +} +#endif + +#endif // _U_CELL_PPP_PRIVATE_H_ + +// End of file diff --git a/cell/src/u_cell_ppp_shared.h b/cell/src/u_cell_ppp_shared.h new file mode 100644 index 000000000..15b777cd4 --- /dev/null +++ b/cell/src/u_cell_ppp_shared.h @@ -0,0 +1,200 @@ +/* + * Copyright 2019-2023 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_CELL_PPP_SHARED_H_ +#define _U_CELL_PPP_SHARED_H_ + +/* Only header files representing a direct and unavoidable + * dependency between the API of this module and the API + * of another module should be included here; otherwise + * please keep #includes to your .c files. */ + +#include "u_port_ppp.h" + +/** @file + * @brief This header file defines functions that expose the PPP + * transport for cellular. They are not intended for direct customer + * use, they are shared internally with the port layer which then + * integrates with the bottom-end of the IP stack of a platform. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +#ifndef U_CELL_PPP_DIAL_TIMEOUT_SECONDS +/** The time in seconds to wait for a PPP dial-up to succeed. + */ +# define U_CELL_PPP_DIAL_TIMEOUT_SECONDS 240 +#endif + +#ifndef U_CELL_PPP_HANG_UP_TIMEOUT_SECONDS +/** How long to wait for PPP to disconnect, that is to return + * "NO CARRIER" after the hang-up sequence "~+++" has been sent. + */ +# define U_CELL_PPP_HANG_UP_TIMEOUT_SECONDS 30 +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * FUNCTIONS + * -------------------------------------------------------------- */ + +/** Open the PPP interface of a cellular module; only works with + * modules where CMUX is supported (so, for example, does not work + * on LARA-R6). The cellular network connection should already have + * been brought up using uCellNetConnect() or uCellNetActivate(); all + * this does is open the PPP data interface. If the PPP interface is + * already open this function will do nothing and return success; + * please call uCellPppClose() first if you would like to change + * the buffering arrangements, the callback or its parameter. + * + * Note: this will invoke multiplexer mode in the cellular device + * and hence will only work on interfaces that support multiplexer + * mode (for example the USB interface of a cellular device does not + * support multiplexer mode). Also, since multiplexer mode is a + * frame-oriented protocol it will be broken if there a character + * is lost on the interface and hence, on a UART interface, it is + * HIGHLY RECOMMENDED that the UART flow control lines are + * connected. + * + * Note: this function will allocate memory that is not released, + * for thread-safety reasons, until the cellular device is closed. + * If you need the heap memory back before then, see uCellPppFree(). + * + * Implementation note: follows the function signature of + * #uPortPppConnectCallback_t. + * + * @param cellHandle the handle of the cellular instance. + * @param[in] pReceiveCallback the data reception callback; may be + * NULL if only data transmission is + * required. + * @param[in,out] pReceiveCallbackParam a parameter that will be passed to + * pReceiveCallback as its last parameter; + * may be NULL, ignored if pReceiveCallback + * is NULL. + * @param[in] pReceiveData a pointer to a buffer for received + * data; may be NULL, in which case, if + * pReceiveCallback is non-NULL, this code + * will provide a receive buffer. + * @param receiveDataSize the amount of space at pReceiveData in + * bytes or, if pReceiveData is NULL, + * the receive buffer size that should + * be allocated by this function; + * #U_PORT_PPP_RECEIVE_BUFFER_BYTES is + * a sensible value. + * @param[in] pKeepGoingCallback a callback function that governs how + * long to wait for the PPP connection to + * open. This function is called once a + * second while waiting for the "CONNECT" + * response; the PPP open attempt will + * only continue while it returns true. + * This allows the caller to terminate + * the connection attempt at their + * convenience. May be NULL, in which case + * the connection attempt will eventually + * time out on failure. + * @return zero on success, else negative error + * code. + */ +int32_t uCellPppOpen(uDeviceHandle_t cellHandle, + uPortPppReceiveCallback_t *pReceiveCallback, + void *pReceiveCallbackParam, + char *pReceiveData, size_t receiveDataSize, + bool (*pKeepGoingCallback) (uDeviceHandle_t cellHandle)); + +/** Close the PPP interface of a cellular module. This does not + * deactivate the cellular connection, the caller must do that + * afterwards with a call to uCellNetDisconnect() or + * uCellNetDeactivate(). When this function has returned the + * pReceiveCallback function passed to uCellPppOpen() will no longer be + * called and any pReceiveData buffer passed to uCellPppOpen() + * will no longer be written-to. If no PPP connection is open this + * function will do nothing and return success. + * + * Note: in the case of SARA-R5, and potentially other modules, + * it is vital that the PPP session is closed cleanly by the + * application before this function is called; not doing so may + * lead to the PPP entity being unresponsive in subsequent calls + * to uCellPppOpen(). Should that occur, the sure way out is + * to reboot the module, e.g. with uCellPwrReboot(), before + * calling uCellPppOpen() again. + * + * Implementation note: follows the function signature of + * #uPortPppDisconnectCallback_t. + * + * @param cellHandle the handle of the cellular instance. + * @param pppTerminateRequired set this to true if the PPP connection + * should be terminated first or leave + * as false if the PPP connection + * has already been terminated by + * the peer; be sure to get this right + * for the SARA-R5 case. + * @return zero on success, else negative error + * code. + */ +int32_t uCellPppClose(uDeviceHandle_t cellHandle, + bool pppTerminateRequired); + +/** Transmit data over the PPP interface of the cellular module. + * This may be integrated into a higher layer, e.g. the PPP + * interface at the bottom of an IP stack, to permit it to send + * PPP frames over a cellular transport. uCellPppOpen() must have + * been called for transmission to succeed. + * + * Implementation note: follows the function signature of + * #uPortPppTransmitCallback_t. + * + * @param cellHandle the handle of the cellular instance. + * @param[in] pData a pointer to the data to transmit; can only + * be NULL if dataSize is zero. + * @param dataSize the number of bytes of data at pData. + * @return on success the number bytes transmitted, + * which may be less than dataSize, else + * negative error code. + */ +int32_t uCellPppTransmit(uDeviceHandle_t cellHandle, + const char *pData, size_t dataSize); + +/** uCellPppClose() does not free memory in order to ensure + * thread-safety; memory is only free'ed when the cellular instance + * is closed. However, if you can't wait, you really need that + * memory back, and you are absolutely sure that there is no chance + * of an asynchronous receive occurring, you may call this function + * to regain heap. Note that this only does the memory-freeing part, + * not the closing down part, i.e. you must have called + * uCellPppClose() and, to really ensure thread-safety, also called + * uCellNetDisconnect() or uCellNetDeactivate(), for it to have + * any effect. + * + * @param cellHandle the handle of the cellular instance. + */ +void uCellPppFree(uDeviceHandle_t cellHandle); + +#ifdef __cplusplus +} +#endif + +#endif // _U_CELL_PPP_SHARED_H_ + +// End of file diff --git a/cell/src/u_cell_private.c b/cell/src/u_cell_private.c index 90ba04e5d..d19ca30ce 100644 --- a/cell/src/u_cell_private.c +++ b/cell/src/u_cell_private.c @@ -117,6 +117,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_AUTHENTICATION_MODE_AUTOMATIC) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 6, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -146,6 +148,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -180,6 +184,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -204,6 +210,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_CMUX) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -252,7 +260,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_AUTHENTICATION_MODE_AUTOMATIC) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | - (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) | + (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_PPP) /* features */ ), 4, /* Default CMUX channel for GNSS */ 16 /* AT+CFUN reboot command */ @@ -280,6 +289,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -323,7 +334,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_SNR_REPORTED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | - (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) | + (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_PPP) /* features */ ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -357,6 +369,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_LWM2M) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_UCGED) | (1ULL << (int32_t) U_CELL_PRIVATE_FEATURE_HTTP) /* features */ + // PPP is supported by the module but we do not test its integration with + // ubxlib and hence it is not marked as supported ), 3, /* Default CMUX channel for GNSS */ 15 /* AT+CFUN reboot command */ @@ -388,6 +402,8 @@ const uCellPrivateModule_t gUCellPrivateModuleList[] = { // save the response to file (in fact it leaves any previous response file there, // unchanged) and returns error rather than success. This makes it impossible to // support proper HTTP operation. + // Similarly, LENA-R8 supports PPP but it fails LCP negotiation at start of day + // and hence it is not marked as supported ), -1, /* Default CMUX channel for GNSS */ 16 /* AT+CFUN reboot command */ diff --git a/cell/src/u_cell_private.h b/cell/src/u_cell_private.h index 0fefb9033..0240e7f20 100644 --- a/cell/src/u_cell_private.h +++ b/cell/src/u_cell_private.h @@ -242,7 +242,8 @@ typedef enum { U_CELL_PRIVATE_FEATURE_AUTHENTICATION_MODE_AUTOMATIC, U_CELL_PRIVATE_FEATURE_LWM2M, U_CELL_PRIVATE_FEATURE_UCGED, - U_CELL_PRIVATE_FEATURE_HTTP + U_CELL_PRIVATE_FEATURE_HTTP, + U_CELL_PRIVATE_FEATURE_PPP } uCellPrivateFeature_t; /** The characteristics that may differ between cellular modules. @@ -485,6 +486,7 @@ typedef struct uCellPrivateInstance_t { void *pCellTimeContext; /**< Hook for CellTime context. */ void *pCellTimeCellSyncContext; /**< Hook for CellTime cell synchronisation context. */ void *pFenceContext; /**< Storage for a uGeofenceContext_t. */ + void *pPppContext; /**< Hook for a PPP connection context. */ struct uCellPrivateInstance_t *pNext; } uCellPrivateInstance_t; diff --git a/cell/src/u_cell_pwr.c b/cell/src/u_cell_pwr.c index 2a94e6caa..b123ad297 100644 --- a/cell/src/u_cell_pwr.c +++ b/cell/src/u_cell_pwr.c @@ -58,6 +58,7 @@ #include "u_cell_cfg.h" #include "u_cell_mux.h" #include "u_cell_mux_private.h" +#include "u_cell_ppp_shared.h" #include "u_cell_pwr.h" #include "u_cell_pwr_private.h" @@ -1401,6 +1402,12 @@ int32_t uCellPwrPrivateOn(uCellPrivateInstance_t *pInstance, quickPowerOff(pInstance, pKeepGoingCallback); } + if ((errorCode == 0) && U_CELL_PRIVATE_HAS(pInstance->pModule, + U_CELL_PRIVATE_FEATURE_PPP)) { + // A PPP connection may now be opened by a platform + uPortPppAttach(pInstance->cellHandle, uCellPppOpen, uCellPppClose, uCellPppTransmit); + } + // If we were successful, were asleep at the start and there is // a wake-up callback then call it if (asleepAtStart && (errorCode == 0) && (pSleepContext != NULL) && @@ -1708,6 +1715,134 @@ int32_t uCellPwrPrivateGetEDrx(const uCellPrivateInstance_t *pInstance, return errorCode; } +// Get the DTR power-saving pin. +int32_t uCellPwrPrivateGetDtrPowerSavingPin(const uCellPrivateInstance_t *pInstance) +{ + int32_t errorCodeOrPin = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + + if (pInstance != NULL) { + errorCodeOrPin = (int32_t) U_ERROR_COMMON_NOT_FOUND; + if (pInstance->pinDtrPowerSaving >= 0) { + errorCodeOrPin = pInstance->pinDtrPowerSaving; + } + } + + return errorCodeOrPin; +} + +// Disable 32 kHz sleep. +int32_t uCellPwrPrivateDisableUartSleep(uCellPrivateInstance_t *pInstance) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + uCellPrivateUartSleepCache_t *pUartSleepCache; + uAtClientHandle_t atHandle; + + if (pInstance != NULL) { + pUartSleepCache = &(pInstance->uartSleepCache); + // If a wake-up handler has been set then the module supports + // UART sleep, if it has not then it doesn't and we can say so + atHandle = pInstance->atHandle; + // If a sleep handler is not set then sleep is already + // disabled, so that's fine + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (uAtClientWakeUpHandlerIsSet(atHandle)) { + // Read and stash the current UART sleep parameters + uAtClientLock(atHandle); + uAtClientCommandStart(atHandle, "AT+UPSV?"); + uAtClientCommandStop(atHandle); + uAtClientResponseStart(atHandle, "+UPSV:"); + pUartSleepCache->mode = uAtClientReadInt(atHandle); + if ((pUartSleepCache->mode == 1) || + ((pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_LENA_R8) && + (pUartSleepCache->mode == 4))) { + // Mode 1 has a time attached, as does mode 4 but only if this + // is LENA-R8 + pUartSleepCache->sleepTime = uAtClientReadInt(atHandle); + } + uAtClientResponseStop(atHandle); + errorCode = uAtClientUnlock(atHandle); + if (errorCode == 0) { + // Now switch off sleep and remove the handler, + // so that everyone knows sleep is gone + uAtClientLock(atHandle); + uAtClientCommandStart(atHandle, "AT+UPSV="); + uAtClientWriteInt(atHandle, 0); + uAtClientCommandStopReadResponse(atHandle); + errorCode = uAtClientUnlock(atHandle); + if (errorCode == 0) { + uAtClientSetWakeUpHandler(atHandle, NULL, NULL, 0); + } + } + } + } + + return errorCode; +} + +// Enable 32 kHz sleep. +int32_t uCellPwrPrivateEnableUartSleep(uCellPrivateInstance_t *pInstance) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + uCellPrivateUartSleepCache_t *pUartSleepCache; + uAtClientHandle_t atHandle; + + if (pInstance != NULL) { + pUartSleepCache = &(pInstance->uartSleepCache); + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + atHandle = pInstance->atHandle; + if (uAtClientWakeUpHandlerIsSet(atHandle)) { + // If the sleep handler is set the sleep is already + // enabled, there is nothing to do + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } else { + // If no sleep handler is set then either sleep + // is not supported or it has been disabled: + // if it has been disabled then the cache + // will contain the previous mode so check it + if (pUartSleepCache->mode > 0) { + // There is a cached mode, put it back again +#ifndef U_CFG_CELL_DISABLE_UART_POWER_SAVING + uAtClientLock(atHandle); + uAtClientCommandStart(atHandle, "AT+UPSV="); + uAtClientWriteInt(atHandle, pUartSleepCache->mode); + if (pUartSleepCache->mode == 1) { + // Mode 1 has a time + uAtClientWriteInt(atHandle, pUartSleepCache->sleepTime); + } + uAtClientCommandStopReadResponse(atHandle); + errorCode = uAtClientUnlock(atHandle); + if (errorCode == 0) { + // Empty the cache so that we know sleep + // has been re-enabled + pUartSleepCache->mode = 0; + pUartSleepCache->sleepTime = 0; + uAtClientSetWakeUpHandler(atHandle, uCellPrivateWakeUpCallback, pInstance, + (U_CELL_POWER_SAVING_UART_INACTIVITY_TIMEOUT_SECONDS * 1000) - + U_CELL_POWER_SAVING_UART_WAKEUP_MARGIN_MILLISECONDS); + } else { + // Return a clearer error code than "AT error" + errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; + } +#endif + } + } + } + + return errorCode; +} + +// Determine whether UART, AKA 32 kHz, sleep is enabled or not. +bool uCellPwrPrivateUartSleepIsEnabled(const uCellPrivateInstance_t *pInstance) +{ + bool isEnabled = false; + + if (pInstance != NULL) { + isEnabled = uAtClientWakeUpHandlerIsSet(pInstance->atHandle); + } + + return isEnabled; +} + /* ---------------------------------------------------------------- * PUBLIC FUNCTIONS * -------------------------------------------------------------- */ @@ -1797,6 +1932,9 @@ int32_t uCellPwrOff(uDeviceHandle_t cellHandle, if (gUCellPrivateMutex != NULL) { + // Detach any PPP connection + uPortPppDetach(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); @@ -1822,6 +1960,9 @@ int32_t uCellPwrOffHard(uDeviceHandle_t cellHandle, bool trulyHard, if (gUCellPrivateMutex != NULL) { + // Detach any PPP connection + uPortPppDetach(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); @@ -1914,6 +2055,9 @@ int32_t uCellPwrReboot(uDeviceHandle_t cellHandle, if (gUCellPrivateMutex != NULL) { + // Detach any PPP connection + uPortPppDetach(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); @@ -2054,6 +2198,9 @@ int32_t uCellPwrResetHard(uDeviceHandle_t cellHandle, int32_t pinReset) if (gUCellPrivateMutex != NULL) { + // Detach any PPP connection + uPortPppDetach(cellHandle); + U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); @@ -2794,51 +2941,14 @@ int32_t uCellPwrDisableUartSleep(uDeviceHandle_t cellHandle) { int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; uCellPrivateInstance_t *pInstance; - uCellPrivateUartSleepCache_t *pUartSleepCache; - uAtClientHandle_t atHandle; if (gUCellPrivateMutex != NULL) { U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); - if ((pInstance != NULL) && (pInstance->pModule != NULL)) { - pUartSleepCache = &(pInstance->uartSleepCache); - // If a wake-up handler has been set then the module supports - // UART sleep, if it has not then it doesn't and we can say so - atHandle = pInstance->atHandle; - // If a sleep handler is not set then sleep is already - // disabled, so that's fine - errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; - if (uAtClientWakeUpHandlerIsSet(atHandle)) { - // Read and stash the current UART sleep parameters - uAtClientLock(atHandle); - uAtClientCommandStart(atHandle, "AT+UPSV?"); - uAtClientCommandStop(atHandle); - uAtClientResponseStart(atHandle, "+UPSV:"); - pUartSleepCache->mode = uAtClientReadInt(atHandle); - if ((pUartSleepCache->mode == 1) || - ((pInstance->pModule->moduleType == U_CELL_MODULE_TYPE_LENA_R8) && - (pUartSleepCache->mode == 4))) { - // Mode 1 has a time attached, as does mode 4 but only if this - // is LENA-R8 - pUartSleepCache->sleepTime = uAtClientReadInt(atHandle); - } - uAtClientResponseStop(atHandle); - errorCode = uAtClientUnlock(atHandle); - if (errorCode == 0) { - // Now switch off sleep and remove the handler, - // so that everyone knows sleep is gone - uAtClientLock(atHandle); - uAtClientCommandStart(atHandle, "AT+UPSV="); - uAtClientWriteInt(atHandle, 0); - uAtClientCommandStopReadResponse(atHandle); - errorCode = uAtClientUnlock(atHandle); - if (errorCode == 0) { - uAtClientSetWakeUpHandler(atHandle, NULL, NULL, 0); - } - } - } + if (pInstance != NULL) { + errorCode = uCellPwrPrivateDisableUartSleep(pInstance); } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); @@ -2852,54 +2962,14 @@ int32_t uCellPwrEnableUartSleep(uDeviceHandle_t cellHandle) { int32_t errorCode = (int32_t) U_ERROR_COMMON_NOT_INITIALISED; uCellPrivateInstance_t *pInstance; - uCellPrivateUartSleepCache_t *pUartSleepCache; - uAtClientHandle_t atHandle; if (gUCellPrivateMutex != NULL) { U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); - if ((pInstance != NULL) && (pInstance->pModule != NULL)) { - pUartSleepCache = &(pInstance->uartSleepCache); - errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; - atHandle = pInstance->atHandle; - if (uAtClientWakeUpHandlerIsSet(atHandle)) { - // If the sleep handler is set the sleep is already - // enabled, there is nothing to do - errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; - } else { - // If no sleep handler is set then either sleep - // is not supported or it has been disabled: - // if it has been disabled then the cache - // will contain the previous mode so check it - if (pUartSleepCache->mode > 0) { - // There is a cached mode, put it back again -#ifndef U_CFG_CELL_DISABLE_UART_POWER_SAVING - uAtClientLock(atHandle); - uAtClientCommandStart(atHandle, "AT+UPSV="); - uAtClientWriteInt(atHandle, pUartSleepCache->mode); - if (pUartSleepCache->mode == 1) { - // Mode 1 has a time - uAtClientWriteInt(atHandle, pUartSleepCache->sleepTime); - } - uAtClientCommandStopReadResponse(atHandle); - errorCode = uAtClientUnlock(atHandle); - if (errorCode == 0) { - // Empty the cache so that we know sleep - // has been re-enabled - pUartSleepCache->mode = 0; - pUartSleepCache->sleepTime = 0; - uAtClientSetWakeUpHandler(atHandle, uCellPrivateWakeUpCallback, pInstance, - (U_CELL_POWER_SAVING_UART_INACTIVITY_TIMEOUT_SECONDS * 1000) - - U_CELL_POWER_SAVING_UART_WAKEUP_MARGIN_MILLISECONDS); - } else { - // Return a clearer error code than "AT error" - errorCode = (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; - } -#endif - } - } + if (pInstance != NULL) { + errorCode = uCellPwrPrivateEnableUartSleep(pInstance); } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); @@ -2908,7 +2978,6 @@ int32_t uCellPwrEnableUartSleep(uDeviceHandle_t cellHandle) return errorCode; } - // Determine whether UART, AKA 32 kHz, sleep is enabled or not. bool uCellPwrUartSleepIsEnabled(uDeviceHandle_t cellHandle) { @@ -2920,8 +2989,8 @@ bool uCellPwrUartSleepIsEnabled(uDeviceHandle_t cellHandle) U_PORT_MUTEX_LOCK(gUCellPrivateMutex); pInstance = pUCellPrivateGetInstance(cellHandle); - if ((pInstance != NULL) && (pInstance->pModule != NULL)) { - isEnabled = uAtClientWakeUpHandlerIsSet(pInstance->atHandle); + if (pInstance != NULL) { + isEnabled = uCellPwrPrivateUartSleepIsEnabled(pInstance); } U_PORT_MUTEX_UNLOCK(gUCellPrivateMutex); @@ -2930,5 +2999,4 @@ bool uCellPwrUartSleepIsEnabled(uDeviceHandle_t cellHandle) return isEnabled; } - // End of file diff --git a/cell/src/u_cell_pwr_private.h b/cell/src/u_cell_pwr_private.h index e66da1dce..9acce70aa 100644 --- a/cell/src/u_cell_pwr_private.h +++ b/cell/src/u_cell_pwr_private.h @@ -40,6 +40,7 @@ extern "C" { /** Power the cellular module on or wakeit from deep sleep. If this * function returns success then the cellular module is ready to * receive configuration commands and register with the cellular network. + * * Note: gUCellPrivateMutex should be locked before this is called. * * @param pInstance a pointer to the instance. @@ -101,6 +102,7 @@ int32_t uCellPwrPrivatePeriodicWakeupStrToSeconds(const char *pStr, int32_t *pSeconds); /** Get the 3GPP power saving settings. + * * Note: gUCellPrivateMutex should be locked before this is called. * * @param pInstance a pointer to the cellular instance. @@ -129,6 +131,7 @@ int32_t uCellPwrPrivateGet3gppPowerSaving(uCellPrivateInstance_t *pInstance, int32_t *pPeriodicWakeupSeconds); /** Get the E-DRX settings for the given RAT. + * * Note: gUCellPrivateMutex should be locked before this is called. * * @param pInstance a pointer to the cellular instance. @@ -159,6 +162,51 @@ int32_t uCellPwrPrivateGetEDrx(const uCellPrivateInstance_t *pInstance, int32_t *pEDrxSeconds, int32_t *pPagingWindowSeconds); +/** Get the DTR power-saving pin. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param pInstance a pointer to the cellular instance. + * @return the pin of this MCU that is connected to + * the DTR line of the cellular module, as + * set by uCellPwrSetDtrPowerSavingPin(), + * or negative error code. + */ +int32_t uCellPwrPrivateGetDtrPowerSavingPin(const uCellPrivateInstance_t *pInstance); + +/** Disable UART, AKA 32 kHz, sleep. 32 kHz sleep is always + * enabled where supported by the module; call this function + * to disable 32 kHz sleep. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param pInstance a pointer to the cellular instance. + * @return zero on success or negative error code on + * failure. + */ +int32_t uCellPwrPrivateDisableUartSleep(uCellPrivateInstance_t *pInstance); + +/** Enable UART, AKA 32 kHz sleep. 32 kHz sleep is always enabled + * where supported - you only need to call this if you have + * previously called uCellPwrDisableUartSleep(). + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param pInstance a pointer to the cellular instance. + * @return zero on success or negative error code on + * failure. + */ +int32_t uCellPwrPrivateEnableUartSleep(uCellPrivateInstance_t *pInstance); + +/** Determine whether UART, AKA 32 kHz, sleep is enabled or not. + * + * Note: gUCellPrivateMutex should be locked before this is called. + * + * @param pInstance a pointer to the cellular instance. + * @return true if UART sleep is enabled, else false. + */ +bool uCellPwrPrivateUartSleepIsEnabled(const uCellPrivateInstance_t *pInstance); + #ifdef __cplusplus } #endif diff --git a/cell/src/u_cell_stub_gnss.c b/cell/src/u_cell_stub_gnss.c new file mode 100644 index 000000000..8da246821 --- /dev/null +++ b/cell/src/u_cell_stub_gnss.c @@ -0,0 +1,51 @@ +/* + * Copyright 2019-2023 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 Stubs to allow the cellular API to be compiled without GNSS; + * if you call a GNSS API function from the source code here you must + * also include a weak stub for it which will return an error when + * GNSS is not included in the build. + */ + +#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 "u_compiler.h" // U_WEAK + +#include "u_gnss_shared.h" + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +U_WEAK void uGnssUpdateAtHandle(void *pAtOld, void *pAtNew) +{ + (void) pAtOld; + (void) pAtNew; +} + +// End of file diff --git a/cell/test/u_cell_ppp_test.c b/cell/test/u_cell_ppp_test.c new file mode 100644 index 000000000..5080a6577 --- /dev/null +++ b/cell/test/u_cell_ppp_test.c @@ -0,0 +1,501 @@ +/* + * Copyright 2019-2023 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 for the cellular PPP API: these should pass on all + * platforms where CMUX is also supported. They are only compiled + * if U_CFG_TEST_CELL_MODULE_TYPE is defined and U_CFG_TEST_DISABLE_MUX + * is NOT defined. + */ + +#if defined(U_CFG_TEST_CELL_MODULE_TYPE) && !defined(U_CFG_TEST_DISABLE_MUX) + +# 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" // memset() + +#include "u_cfg_sw.h" +#include "u_cfg_os_platform_specific.h" +#include "u_cfg_app_platform_specific.h" +#include "u_cfg_test_platform_specific.h" + +#include "u_error_common.h" + +#include "u_port.h" +#include "u_port_os.h" // Required by u_cell_private.h +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE +# include "u_port_i2c.h" +# include "u_port_spi.h" +#endif +#include "u_port_heap.h" +#include "u_port_debug.h" + +#include "u_test_util_resource_check.h" + +#include "u_at_client.h" + +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE +# include "u_network.h" +# include "u_network_test_shared_cfg.h" +# include "u_location.h" +#endif + +#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 +#include "u_cell_info.h" // For uCellInfoGetModelStr() +#if U_CFG_APP_PIN_CELL_PWR_ON < 0 +# include "u_cell_pwr.h" +#endif +#ifdef U_CELL_TEST_MUX_ALWAYS +# include "u_cell_mux.h" +#endif + +#include "u_cell_ppp_shared.h" + +#include "u_cell_test_cfg.h" +#include "u_cell_test_private.h" + +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE +# include "u_gnss_module_type.h" +# include "u_gnss_type.h" +# include "u_gnss.h" // uGnssSetUbxMessagePrint() +# include "u_gnss_pos.h" +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/** The string to put at the start of all prints from this test. + */ +#define U_TEST_PREFIX "U_CELL_PPP_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_CELL_PPP_TEST_TIMEOUT_SECONDS +/** How long to wait for uCellPppOpen() to connect. + */ +# define U_CELL_PPP_TEST_TIMEOUT_SECONDS 60 +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/** Used for keepGoingCallback() timeout. + */ +static int32_t gStopTimeMs; + +/** Handle. + */ +static uCellTestPrivate_t gHandles = U_CELL_TEST_PRIVATE_DEFAULTS; + +/** A small buffer to check that static buffers don't blow things + * up. + */ +static char gBuffer[16]; + +/* ---------------------------------------------------------------- +* STATIC FUNCTIONS +* -------------------------------------------------------------- */ + +// Callback function for the cellular connection process. +static bool keepGoingCallback(uDeviceHandle_t unused) +{ + bool keepGoing = true; + + (void) unused; + + if (uPortGetTickTimeMs() > gStopTimeMs) { + keepGoing = false; + } + + return keepGoing; +} + +// Callback for received data; doesn't do much. +static void receiveDataCallback(uDeviceHandle_t cellHandle, + const char *pData, + size_t dataSize, + void *pCallbackParam) +{ + (void) cellHandle; + (void) pData; + (void) dataSize; + (void) pCallbackParam; +} + +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE + +// Network-API level bring up, used when addressing the GNSS chip inside a cellular module +static uNetworkTestList_t *pStdPreamble() +{ + uNetworkTestList_t *pList; + + // Add the device for each network configuration + // if not already added + pList = pUNetworkTestListAlloc(uNetworkTestIsDeviceCell); + 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; +} + +// Convert a lat/long into a whole number and a bit-after-the-decimal-point +// that can be printed by a version of printf() that does not support +// floating point operations, returning the prefix (either "+" or "-"). +// The result should be printed with printf() format specifiers +// %c%d.%07d, e.g. something like: +// +// int32_t whole; +// int32_t fraction; +// +// printf("%c%d.%07d/%c%d.%07d", latLongToBits(latitudeX1e7, &whole, &fraction), +// whole, fraction, +// latLongToBits(longitudeX1e7, &whole, &fraction), +// whole, fraction); +static char latLongToBits(int32_t thingX1e7, + int32_t *pWhole, + int32_t *pFraction) +{ + char prefix = '+'; + + // Deal with the sign + if (thingX1e7 < 0) { + thingX1e7 = -thingX1e7; + prefix = '-'; + } + *pWhole = thingX1e7 / 10000000; + *pFraction = thingX1e7 % 10000000; + + return prefix; +} + +// Print lat/long location as a clickable link. +static void printLocation(int32_t latitudeX1e7, int32_t longitudeX1e7) +{ + char prefixLat; + char prefixLong; + int32_t wholeLat; + int32_t wholeLong; + int32_t fractionLat; + int32_t fractionLong; + + prefixLat = latLongToBits(latitudeX1e7, &wholeLat, &fractionLat); + prefixLong = latLongToBits(longitudeX1e7, &wholeLong, &fractionLong); + uPortLog("I am here: https://maps.google.com/?q=%c%d.%07d,%c%d.%07d\n", + prefixLat, wholeLat, fractionLat, prefixLong, wholeLong, + fractionLong); +} + +#endif // #ifdef U_CFG_TEST_GNSS_MODULE_TYPE + +/* ---------------------------------------------------------------- +* PUBLIC FUNCTIONS +* -------------------------------------------------------------- */ + +/** A very basic test of PPP operation indeed; most of the real + * testing is done in the platform tests. + */ +U_PORT_TEST_FUNCTION("[cellPpp]", "cellPppBasic") +{ + int32_t resourceCount; + uDeviceHandle_t cellHandle; + const uCellPrivateModule_t *pModule; + char buffer[64]; + int32_t x; + + // In case a previous test failed + uCellTestPrivateCleanup(&gHandles); + + // Obtain the initial resource count + resourceCount = uTestUtilGetDynamicResourceCount(); + + // Do the standard preamble + U_PORT_TEST_ASSERT(uCellTestPrivatePreamble(U_CFG_TEST_CELL_MODULE_TYPE, + &gHandles, true) == 0); + cellHandle = gHandles.cellHandle; + + // Get the private module data as we need it for testing + pModule = pUCellPrivateGetModule(cellHandle); + U_PORT_TEST_ASSERT(pModule != NULL); + //lint -esym(613, pModule) Suppress possible use of NULL pointer + // for pModule from now on + + // Only run if PPP operation is supported + if (U_CELL_PRIVATE_HAS(pModule, U_CELL_PRIVATE_FEATURE_PPP)) { + U_TEST_PRINT_LINE("testing PPP, first with no connection."); + // First check before having connected: should return error + gStopTimeMs = uPortGetTickTimeMs() + (U_CELL_PPP_TEST_TIMEOUT_SECONDS * 1000); + x = uCellPppOpen(cellHandle, NULL, NULL, gBuffer, + sizeof(gBuffer), keepGoingCallback); + U_TEST_PRINT_LINE("uCellPppOpen() returned %d.", x); + U_PORT_TEST_ASSERT(x < 0); + x = uCellPppTransmit(cellHandle, "dummy", 5); + U_TEST_PRINT_LINE("uCellPppTransmit() returned %d.", x); + U_PORT_TEST_ASSERT(x < 0); + + U_TEST_PRINT_LINE("now with a connection."); + // Now connect + gStopTimeMs = uPortGetTickTimeMs() + + (U_CELL_TEST_CFG_CONNECT_TIMEOUT_SECONDS * 1000); + x = uCellNetConnect(cellHandle, NULL, +#ifdef U_CELL_TEST_CFG_APN + U_PORT_STRINGIFY_QUOTED(U_CELL_TEST_CFG_APN), +#else + NULL, +#endif +#ifdef U_CELL_TEST_CFG_USERNAME + U_PORT_STRINGIFY_QUOTED(U_CELL_TEST_CFG_USERNAME), +#else + NULL, +#endif +#ifdef U_CELL_TEST_CFG_PASSWORD + U_PORT_STRINGIFY_QUOTED(U_CELL_TEST_CFG_PASSWORD), +#else + NULL, +#endif + keepGoingCallback); + U_PORT_TEST_ASSERT (x == 0); + + gStopTimeMs = uPortGetTickTimeMs() + (U_CELL_PPP_TEST_TIMEOUT_SECONDS * 1000); + x = uCellPppOpen(cellHandle, NULL, NULL, gBuffer, + sizeof(gBuffer), keepGoingCallback); + U_TEST_PRINT_LINE("uCellPppOpen() returned %d.", x); + U_PORT_TEST_ASSERT(x == 0); + + x = uCellPppTransmit(cellHandle, "dummy", 5); + U_TEST_PRINT_LINE("uCellPppTransmit() returned %d.", x); + U_PORT_TEST_ASSERT(x == 5); + + // Check that we can still do normal AT things + memset(buffer, 0, sizeof(buffer)); + x = uCellInfoGetModelStr(cellHandle, buffer, sizeof(buffer)); + U_PORT_TEST_ASSERT((x > 0) && (x < sizeof(buffer) - 1) && + (x == strlen(buffer))); + + U_TEST_PRINT_LINE("Closing PPP (there will be a delay)..."); + U_PORT_TEST_ASSERT(uCellPppClose(cellHandle, true) == 0); + + // Disconnect + U_PORT_TEST_ASSERT(uCellNetDisconnect(cellHandle, NULL) == 0); + + } else { + U_TEST_PRINT_LINE("PPP is not supported, not testing it."); + U_PORT_TEST_ASSERT(uCellPppOpen(cellHandle, receiveDataCallback, + NULL, NULL, + U_PORT_PPP_RECEIVE_BUFFER_BYTES, + keepGoingCallback) < 0); + U_PORT_TEST_ASSERT(uCellPppTransmit(cellHandle, "dummy", 5) < 0); + U_PORT_TEST_ASSERT(uCellPppClose(cellHandle, false) < 0); + uCellPppFree(cellHandle); + } + + // Do the standard postamble, also powering the module down + // as otherwise SARA-R5 can get upset since CMUX was running + uCellTestPrivatePostamble(&gHandles, true); + + // 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); +} + +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE + +/** Test that GNSS access can run at the same time as PPP. + */ +U_PORT_TEST_FUNCTION("[cellPpp]", "cellPppWithGnss") +{ + uNetworkTestList_t *pList; + const uCellPrivateModule_t *pModule; + uDeviceHandle_t cellHandle = NULL; + uDeviceHandle_t gnssHandle = NULL; + int32_t resourceCount; + int32_t x; + uLocation_t location; + + // In case a previous test failed + uCellTestPrivateCleanup(&gHandles); + uNetworkTestCleanUp(); + + // Whatever called us likely initialised the + // port so deinitialise it here to obtain the + // correct initial heap size + uPortDeinit(); + + // Obtain the initial resource count + resourceCount = uTestUtilGetDynamicResourceCount(); + + U_PORT_TEST_ASSERT(uPortInit() == 0); + // Don't check these for success as not all platforms support I2C or SPI + uPortI2cInit(); + uPortSpiInit(); + U_PORT_TEST_ASSERT(uDeviceInit() == 0); + + // Do the preamble to get all the networks up + pList = pStdPreamble(); + + // Find the cellular device and the GNSS network in the list + for (uNetworkTestList_t *pTmp = pList; (pTmp != NULL) && (gnssHandle == NULL); pTmp = pTmp->pNext) { + if (pTmp->pDeviceCfg->deviceType == U_DEVICE_TYPE_CELL) { + cellHandle = *pTmp->pDevHandle; + if (pTmp->networkType == U_NETWORK_TYPE_GNSS) { + gnssHandle = *pTmp->pDevHandle; + U_TEST_PRINT_LINE("selected GNSS network via cellular device."); + } + } + } + + if (gnssHandle != NULL) { + U_PORT_TEST_ASSERT(cellHandle != NULL); + + // So that we can see what we're doing + uGnssSetUbxMessagePrint(gnssHandle, true); + + // Get the private module data as we need it for testing + pModule = pUCellPrivateGetModule(cellHandle); + U_PORT_TEST_ASSERT(pModule != NULL); + //lint -esym(613, pModule) Suppress possible use of NULL pointer + // for pModule from now on + + // Only run if PPP operation is supported + if (U_CELL_PRIVATE_HAS(pModule, U_CELL_PRIVATE_FEATURE_PPP)) { + U_TEST_PRINT_LINE("testing PPP and GNSS at the same time."); + x = uCellPppOpen(cellHandle, NULL, NULL, gBuffer, sizeof(gBuffer), NULL); + U_TEST_PRINT_LINE("uCellPppOpen() returned %d.", x); + U_PORT_TEST_ASSERT(x == 0); + + // Now get location + x = uLocationGet(gnssHandle, U_LOCATION_TYPE_GNSS, + NULL, NULL, &location, NULL); + U_TEST_PRINT_LINE("uLocationGet() returned %d.", x); + U_PORT_TEST_ASSERT(x == 0); + printLocation(location.latitudeX1e7, location.longitudeX1e7); + + x = uCellPppTransmit(cellHandle, "dummy", 5); + U_TEST_PRINT_LINE("uCellPppTransmit() returned %d.", x); + U_PORT_TEST_ASSERT(x == 5); + + U_TEST_PRINT_LINE("Closing PPP (there will be a delay)..."); + U_PORT_TEST_ASSERT(uCellPppClose(cellHandle, true) == 0); + } + + // Call PPP free this time + uCellPppFree(cellHandle); + + } else { + U_TEST_PRINT_LINE("*** WARNING *** not testing GNSS at the same time" + " as PPP since no GNSS device is attached via cellular."); + } + + // 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 and powering off device %s...", + gpUNetworkTestDeviceTypeName[pTmp->pDeviceCfg->deviceType]); + x = uDeviceClose(*pTmp->pDevHandle, true); + if (x != 0) { + // Device has not responded to power off request, just + // release resources + x = uDeviceClose(*pTmp->pDevHandle, false); + } + U_PORT_TEST_ASSERT(x == 0); + *pTmp->pDevHandle = NULL; + } + } + uNetworkTestListFree(); + + uDeviceDeinit(); + uPortSpiDeinit(); + uPortI2cDeinit(); + 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); +} + +#endif // #ifdef U_CFG_TEST_GNSS_MODULE_TYPE + +/** 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("[cellPpp]", "cellPppCleanUp") +{ + uCellTestPrivateCleanup(&gHandles); +#ifdef U_CFG_TEST_GNSS_MODULE_TYPE + // 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(); +#endif + uPortDeinit(); + // Printed for information: asserting happens in the postamble + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); +} + +#endif // #if defined(U_CFG_TEST_CELL_MODULE_TYPE) && !defined(U_CFG_TEST_DISABLE_MUX) + +// End of file diff --git a/cell/test/u_cell_test_private.c b/cell/test/u_cell_test_private.c index de8f0e69c..81e495f31 100644 --- a/cell/test/u_cell_test_private.c +++ b/cell/test/u_cell_test_private.c @@ -53,9 +53,7 @@ #include "u_cell_pwr.h" #include "u_cell_cfg.h" #include "u_cell_info.h" -#ifdef U_CELL_TEST_MUX_ALWAYS -# include "u_cell_mux.h" -#endif +#include "u_cell_mux.h" #include "u_cell_test_cfg.h" #include "u_cell_test_private.h" @@ -264,6 +262,12 @@ int32_t uCellTestPrivatePreamble(uCellModuleType_t moduleType, // Power up U_TEST_PRINT_LINE("powering on..."); errorCode = uCellPwrOn(cellHandle, U_CELL_TEST_CFG_SIM_PIN, NULL); + if (errorCode < 0) { + // If powering-on fails, try sending the CMUX abort sequence in + // case the module is stuck in CMUX mode, and powering-on again + uCellMuxModuleAbort(cellHandle); + errorCode = uCellPwrOn(cellHandle, U_CELL_TEST_CFG_SIM_PIN, NULL); + } if (errorCode == 0) { // Note: if this is a SARA-R422 module, which supports only // 1.8V SIMs, the SIM cards we happen to use in the ubxlib test farm diff --git a/common/network/test/u_network_test_shared_cfg.c b/common/network/test/u_network_test_shared_cfg.c index 7d712c45b..6cb9b7eea 100644 --- a/common/network/test/u_network_test_shared_cfg.c +++ b/common/network/test/u_network_test_shared_cfg.c @@ -675,4 +675,14 @@ bool uNetworkTestHasStatusCallback(uDeviceType_t deviceType, (networkType == U_NETWORK_TYPE_CELL); } +// Return true if the configuration supports a PPP connection. +bool uNetworkTestHasPpp(uDeviceType_t deviceType, + uNetworkType_t networkType, + int32_t moduleType) +{ + (void) deviceType; + (void) moduleType; + return (networkType == U_NETWORK_TYPE_CELL); +} + // End of file diff --git a/common/network/test/u_network_test_shared_cfg.h b/common/network/test/u_network_test_shared_cfg.h index ce7166ad2..c69269fb0 100644 --- a/common/network/test/u_network_test_shared_cfg.h +++ b/common/network/test/u_network_test_shared_cfg.h @@ -260,6 +260,18 @@ bool uNetworkTestHasStatusCallback(uDeviceType_t deviceType, uNetworkType_t networkType, int32_t moduleType); +/** Return true if the combination supports a PPP connection. + * + * @param deviceType the device type. + * @param networkType the network type. + * @param moduleType the module type. + * @return true if a PPP connection is supported, + * else false. + */ +bool uNetworkTestHasPpp(uDeviceType_t deviceType, + uNetworkType_t networkType, + int32_t moduleType); + #endif // _U_NETWORK_TEST_CFG_H_ // End of file diff --git a/common/sock/test/u_sock_test.c b/common/sock/test/u_sock_test.c index d1391b2e7..9f58d9fb5 100644 --- a/common/sock/test/u_sock_test.c +++ b/common/sock/test/u_sock_test.c @@ -129,20 +129,6 @@ # define U_SOCK_TEST_MAX_UDP_PACKET_SIZE 500 #endif -#ifndef U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE -/** The maximum TCP read/write size to use during testing. - */ -# define U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE 1024 -#endif - -#ifndef U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE -/** Sending just one byte doesn't always cause all - * modules to actually send the data in a reasonable - * time so set a sensible minimum here for testing. - */ -# define U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE 128 -#endif - #ifndef U_SOCK_TEST_NON_BLOCKING_TIME_MS /** Expected return time for non-blocking operation *in ms during testing. diff --git a/common/sock/test/u_sock_test_shared_cfg.h b/common/sock/test/u_sock_test_shared_cfg.h index 3d7c821d0..b5a84dc0d 100644 --- a/common/sock/test/u_sock_test_shared_cfg.h +++ b/common/sock/test/u_sock_test_shared_cfg.h @@ -119,6 +119,20 @@ # define U_SOCK_TEST_UDP_RETRIES 10 #endif +#ifndef U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE +/** The maximum TCP read/write size to use during testing. + */ +# define U_SOCK_TEST_MAX_TCP_READ_WRITE_SIZE 1024 +#endif + +#ifndef U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE +/** Sending just one byte doesn't always cause all + * modules to actually send the data in a reasonable + * time so set a sensible minimum here for testing. + */ +# define U_SOCK_TEST_MIN_TCP_READ_WRITE_SIZE 128 +#endif + #ifndef U_SOCK_TEST_TCP_CLOSE_SECONDS /** Time to wait for a TCP socket to close, necessary * in the case where the underlying implementation diff --git a/example/sockets/README.md b/example/sockets/README.md index dc7ab26d7..2e3646185 100644 --- a/example/sockets/README.md +++ b/example/sockets/README.md @@ -37,3 +37,15 @@ Obviously you will need a SIM in your board, an antenna connected and you may ne `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"). `U_CFG_APP_SHORT_RANGE_UART`: this sets the internal HW UART block that your chosen MCU will use to talk to the Wi-Fi module. The default is usually acceptable but if you wish to change it then consult the file [port/platform](/port/platform)`/mcu//cfg/cfg_hw_platform_specific.h` for other options. + +# Using PPP +On the following platforms: + +- [ESP-IDF](/port/platform/esp-idf) + +...and with following \[cellular\] modules: + +- SARA-R5 +- SARA-R422 + +...it is possible to make a PPP connection between the module and the bottom of the platform's own IP stack, allowing the native applications of that platform (e.g. MQTT) to connect through the module. Set-up for `ubxlib` is as above, plus you must define `U_CFG_PPP_ENABLE` when building `ubxlib`. Unfortunately there will _always_ be additional platform-specific setup: for this, refer to the README.md of the relevant platform directory or also look at the top of the example `.c` file. \ No newline at end of file diff --git a/example/sockets/main_ppp_espidf.c b/example/sockets/main_ppp_espidf.c new file mode 100644 index 000000000..c6d75db69 --- /dev/null +++ b/example/sockets/main_ppp_espidf.c @@ -0,0 +1,305 @@ +/* + * Copyright 2019-2023 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 operations using the native + * IP stack of the ESP-IDF platform. + * + * For this example to run you must define U_CFG_PPP_ENABLE when + * building ubxlib and you must switch on the following in your + * sdkconfig file: + * + * CONFIG_LWIP_PPP_SUPPORT + * CONFIG_ESP_NETIF_TCPIP_LWIP + * CONFIG_LWIP_PPP_PAP_SUPPORT + * + * If your network operator requires a user name and password + * along with the APN **AND** requires CHAP authentication, then + * you must also switch on CONFIG_LWIP_PPP_CHAP_SUPPORT. + * + * If you are minimising the components built into your main + * application then you may need to add the ESP-IDF component + * "esp_netif" to your component list. + * + * The choice of [cellular] module is made at build time, see the + * README.md for instructions. + */ + +// This example is only for the ESP-IDF platform +#ifdef __XTENSA__ + +// Bring in all of the ubxlib public header files +#include "ubxlib.h" + +// Bring in the ESP-IDF CONFIG_ #defines +#include "sdkconfig.h" + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// The BSD sockets interface of ESP-IDF +#include "unistd.h" +#include "sys/socket.h" +#include "netdb.h" +#include "arpa/inet.h" + +#ifndef U_PORT_TEST_FUNCTION +// The ESP-IDF network interface that allows us to connect PPP +# include "esp_event.h" +# include "esp_netif.h" +#endif + +// 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 see + // #uCellNetAuthenticationMode_t in u_cell_net.h;, 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). +}; +#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(): before this is called the system +// clocks must have been started and the RTOS must be running; +// we are in task space. +U_PORT_TEST_FUNCTION("[example]", "examplePppEspIdfSockets") +{ + uDeviceHandle_t devHandle = NULL; + struct hostent *pHostEnt; + struct sockaddr_in destinationAddress; + int32_t sock; + const char message[] = "The quick brown espidf-fox jumps over the lazy dog."; + size_t txSize = sizeof(message); + char buffer[128]; + size_t rxSize = 0; + int32_t returnCode; + +#ifndef U_PORT_TEST_FUNCTION + // ESP-IDF requires the application to initialise + // its network interface and default event loop + // This is under the switch #ifndef U_PORT_TEST_FUNCTION + // only because, when we run this for internal + // testing, the initialisation is done elsewhere + esp_netif_init(); + esp_event_loop_create_default(); +#endif + + // 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) { + + // ESP-IDF's IP stack is now connected to the + // internet via the cellular module + + // Look up the IP address of the echo server + // using the ESP-IDF platform's gethostbyname() + 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 of the + // ESP-IDF platform to send data + + // You could equally use any of the ESP-IDF + // native protocol entities (MQTT, HTTP, etc.) + + 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) { + 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!\n"); + } + } else { + uPortLog("Unable to connect to server!\n"); + } + } else { + uPortLog("Unable to create socket!\n"); + } + + // Close the socket + uPortLog("Closing socket...\n"); + shutdown(sock, 0); + close(sock); + } else { + uPortLog("Unable to find %s!\n", MY_SERVER_NAME); + } + + // 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 // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) +#endif // #ifdef __XTENSA__ + +// End of file diff --git a/gnss/api/u_gnss.h b/gnss/api/u_gnss.h index 4f6efa402..9b515737f 100644 --- a/gnss/api/u_gnss.h +++ b/gnss/api/u_gnss.h @@ -180,6 +180,16 @@ void uGnssRemove(uDeviceHandle_t gnssHandle); /** Get the type and handle of the transport used by the given * GNSS instance. * + * Note: where the transport is over AT (i.e. the case where AT+UGUBX + * messages are being used to talk to a GNSS chip that is inside or + * connected via a GNSS chip, e.g. if U_NETWORK_GNSS_CFG_CELL_USE_AT_ONLY + * is defined, or CMUX is not supported, not the normal case) it is + * possible for the AT handle to change underneath, so an AT handle + * returned by this function will be locked and therefore unusable. + * This will occur if a PPP session is opened to the cellular device. + * Should a PPP session be opened this function should be called again + * to obtain the correct AT handle. + * * @param gnssHandle the handle of the GNSS instance. * @param[out] pTransportType a place to put the transport type, * may be NULL. diff --git a/gnss/src/u_gnss.c b/gnss/src/u_gnss.c index f13a8d8d4..23fc10538 100644 --- a/gnss/src/u_gnss.c +++ b/gnss/src/u_gnss.c @@ -197,6 +197,32 @@ static void deleteGnssInstance(uGnssPrivateInstance_t *pInstance) } } +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS THAT ARE SHARED WITHIN UBXLIB ONLY + * -------------------------------------------------------------- */ + +// Update an AT handle that any GNSS instance may be using. +void uGnssUpdateAtHandle(void *pAtOld, void *pAtNew) +{ + uGnssPrivateInstance_t *pInstance; + + if (gUGnssPrivateMutex != NULL) { + + U_PORT_MUTEX_LOCK(gUGnssPrivateMutex); + + pInstance = gpUGnssPrivateInstanceList; + while (pInstance != NULL) { + if ((pInstance->transportType == U_GNSS_TRANSPORT_AT) && + (pInstance->transportHandle.pAt == pAtOld)) { + pInstance->transportHandle.pAt = pAtNew; + } + pInstance = pInstance->pNext; + } + + U_PORT_MUTEX_UNLOCK(gUGnssPrivateMutex); + } +} + /* ---------------------------------------------------------------- * PUBLIC FUNCTIONS * -------------------------------------------------------------- */ diff --git a/gnss/src/u_gnss_shared.h b/gnss/src/u_gnss_shared.h new file mode 100644 index 000000000..dbca0b2ea --- /dev/null +++ b/gnss/src/u_gnss_shared.h @@ -0,0 +1,54 @@ +/* + * Copyright 2019-2023 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_GNSS_SHARED_H_ +#define _U_GNSS_SHARED_H_ + +/* Only header files representing a direct and unavoidable + * dependency between the API of this module and the API + * of another module should be included here; otherwise + * please keep #includes to your .c files. */ + +/** @file + * @brief This header file defines a few functions that are not public + * but are shared with the rest of ubxlib. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * FUNCTIONS + * -------------------------------------------------------------- */ + +/** Update the AT handles of any GNSS devices that were using pAtOld + * to be pAtNew; useful if the AT handle is changed dynamically, + * for example when CMUX is invoked with a cellular module via which + * a GNSS device is connected. + * + * @param[in] pAtOld the old AT handle. + * @param[in] pAtNew the new AT handle. + */ +void uGnssUpdateAtHandle(void *pAtOld, void *pAtNew); + +#ifdef __cplusplus +} +#endif + +#endif // _U_GNSS_SHARED_H_ + +// End of file diff --git a/port/api/u_port_ppp.h b/port/api/u_port_ppp.h new file mode 100644 index 000000000..e22eff907 --- /dev/null +++ b/port/api/u_port_ppp.h @@ -0,0 +1,391 @@ +/* + * Copyright 2019-2023 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_H_ +#define _U_PORT_PPP_H_ + +/* Only header files representing a direct and unavoidable + * dependency between the API of this module and the API + * of another module should be included here; otherwise + * please keep #includes to your .c files. */ + +#include "u_sock.h" + +/** \addtogroup __port __Port + * @{ + */ + +/** @file + * @brief This header file defines functions that allow a PPP + * interface of ubxlib to be connected into the IP stack of + * a platform. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/** Suggested size of receive buffer to request if pReceiveData + * passed to #uPortPppConnectCallback_t is NULL. + */ +#define U_PORT_PPP_RECEIVE_BUFFER_BYTES 1024 + +#ifndef U_PORT_PPP_SHUTDOWN_TIMEOUT_SECONDS +/** How long to wait for the IP stack that PPP is attached to to + * shut any connections that may be running over PPP down. + */ +# define U_PORT_PPP_SHUTDOWN_TIMEOUT_SECONDS 10 +#endif + +#ifndef U_PORT_PPP_DNS_PRIMARY_DEFAULT_STR +/** The primary DNS address to use if it is not possible to + * read the primary DNS address from the module. Use NULL + * to provide no default. + */ +# define U_PORT_PPP_DNS_PRIMARY_DEFAULT_STR "8.8.8.8" +#endif + +#ifndef U_PORT_PPP_DNS_SECONDARY_DEFAULT_STR +/** The secondary DNS address to use if it is not possible to + * read the secondary DNS address from the module. Use NULL + * to provide no default. + */ +# define U_PORT_PPP_DNS_SECONDARY_DEFAULT_STR "8.8.4.4" +#endif + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/** The possible authentication modes for the PPP connection. + * + * Note: there is also a #uCellNetAuthenticationMode_t enumeration + * which is set to match this one. If you make a change here you + * may need to make a change there also. + */ +typedef enum { + U_PORT_PPP_AUTHENTICATION_MODE_NONE = 0, + U_PORT_PPP_AUTHENTICATION_MODE_PAP = 1, + U_PORT_PPP_AUTHENTICATION_MODE_CHAP = 2, + U_PORT_PPP_AUTHENTICATION_MODE_MAX_NUM +} uPortPppAuthenticationMode_t; + +/** Callback to receive a buffer of data from the PPP interface of + * a module. This function may be hooked into the PPP API at the + * bottom-end of a platform's IP stack to permit it to receive the + * contents of PPP frames arriving from a module. Any data at pData + * should be handled by this function before it returns as it may be + * overwritten afterwards. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the [for + * example celular] instance that called + * the callback; this is a void * + * rather than a #uDeviceHandle_t here + * in order to avoid dragging in all + * of the uDevice types into the port + * layer. + * @param[in] pData a pointer to the received data; + * will not be NULL. + * @param dataSize the number of bytes of data at pData. + * @param[in,out] pCallbackParam the callback parameter that was + * passed to uPortPppAttach(). + */ +typedef void (uPortPppReceiveCallback_t)(void *pDevHandle, + const char *pData, + size_t dataSize, + void *pCallbackParam); + +/** Callback that opens the PPP interface of a module. If the PPP + * interface is already open this function should do nothing and + * return success; uPortPppDetach() should be called first if you + * would like to change the buffering arrangements, the callback + * or its parameter. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device + * on which the PPP channel is to be + * opened; this is a void * + * rather than a #uDeviceHandle_t here + * in order to avoid dragging in all + * of the uDevice types into the port + * layer. + * @param[in] pReceiveCallback the data reception callback; may be + * NULL if only data transmission is + * required. + * @param[in,out] pReceiveCallbackParam a parameter that will be passed to + * pReceiveCallback as its last parameter; + * may be NULL, ignored if pReceiveCallback + * is NULL. + * @param[in] pReceiveData a pointer to a buffer for received + * data; may be NULL, in which case, if + * pReceiveCallback is non-NULL, this code + * will provide a receive buffer. + * @param receiveDataSize the amount of space at pReceiveData in + * bytes or, if pReceiveData is NULL, the + * receive buffer size that should be + * allocated by this function; + * #U_PORT_PPP_RECEIVE_BUFFER_BYTES is + * a sensible value. + * @param[in] pKeepGoingCallback a callback function that governs how + * long to wait for the PPP connection to + * open. This function will be called + * once a second while waiting for the + * PPP connection to complete; the PPP + * open attempt will only continue while + * it returns true. This allows the caller + * to terminate the connection attempt at + * their convenience. May be NULL, in + * which case the connection attempt will + * eventually time out on failure. + * @return zero on success, else negative error + * code. + */ +typedef int32_t (uPortPppConnectCallback_t) (void *pDevHandle, + uPortPppReceiveCallback_t *pReceiveCallback, + void *pReceiveCallbackParam, + char *pReceiveData, size_t receiveDataSize, + bool (*pKeepGoingCallback) (void *pDevHandle)); + +/** Callback that closes the PPP interface of a module. When this + * function has returned the pReceiveCallback function passed to + * #uPortPppConnectCallback_t will no longer be called and any + * pReceiveData buffer passed to #uPortPppConnectCallback_t will + * no longer be written-to. If no PPP connection is open this + * function will do nothing and return success. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device on which + * the PPP channel is to be closed; this is a + * void * rather than a #uDeviceHandle_t here + * in order to avoid dragging in all of the + * uDevice types into the port layer. + * @param pppTerminateRequired set this to true if the PPP connection + * should be terminated first or leave + * as false if the PPP connection + * has already been terminated by + * the peer. + * @return zero on success, else negative error code. + */ +typedef int32_t (uPortPppDisconnectCallback_t) (void *pDevHandle, + bool pppTerminateRequired); + +/** Callback to transmit data over the PPP interface of a module. + * This may be integrated into a higher layer, e.g. the PPP + * interface at the bottom of an IP stack of a platform, to permit + * it to send PPP frames over a module. #uPortPppConnectCallback_t must + * have returned successfully for transmission to succeed. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device on which + * the PPP channel is to be transmitted; this + * is a void * rather than a #uDeviceHandle_t + * here in order to avoid dragging in all of + * the uDevice types into the port layer. + * @param[in] pData a pointer to the data to transmit; can only + * be NULL if dataSize is zero. + * @param dataSize the number of bytes of data at pData. + * @return on success the number bytes transmitted, + * which may be less than dataSize, else + * negative error code. + */ +typedef int32_t (uPortPppTransmitCallback_t) (void *pDevHandle, + const char *pData, + size_t dataSize); + +/* ---------------------------------------------------------------- + * FUNCTIONS: WORKAROUND FOR LINKER ISSUE + * -------------------------------------------------------------- */ + +/** Workaround for Espressif linker missing out files that + * only contain functions which also have weak alternatives + * (see https://www.esp32.com/viewtopic.php?f=13&t=8418&p=35899). + * + * You can ignore this function. + */ +void uPortPppDefaultPrivateLink(void); + +/* ---------------------------------------------------------------- + * FUNCTIONS + * -------------------------------------------------------------- */ + +/** 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 + * function performs all of the logical connection with the platform + * but it does NOT call any of the callback functions passed in, the + * ones that interact with the [e.g. cellular] device; those are + * simply stored for use when uPortPppConnect(), uPortPppReconnect(), + * uPortPppDisconnect() or uPortPppDetach() are called. + * + * If the PPP interface is already attached this function will do + * nothing and return success; to ensure that any new parameters + * are adopted, uPortPppDetach() should be called first. + * + * If a PPP interface is not supported by the platform this function + * does not need to be implemented: a weakly-linked implementation + * will take over and return #U_ERROR_COMMON_NOT_SUPPORTED. + * + * Note: this only attaches the PPP interface logically, the + * interface cannot be used until uPortPppConnect() is called. + * + * @param[in] pDevHandle the #uDeviceHandle_t of the device + * that is offering the PPP interface; + * this is a void * rather than a + * #uDeviceHandle_t here in order to + * avoid dragging in all of the uDevice + * types into the port layer. + * @param[in] pConnectCallback a callback that will open the PPP + * interface on the device; may be + * NULL if the PPP interface is transmit + * only and is always open. + * @param[in] pDisconnectCallback a callback that will close the PPP + * interface on the device; may be + * NULL if the PPP interface cannot + * be closed. + * @param[in] pTransmitCallback a callback that the platform may call + * to send PPP data over the PPP + * interface; may be NULL if is it not + * possible to transmit data over the + * PPP interface. + * @return zero on success, else negative error code. + */ +int32_t uPortPppAttach(void *pDevHandle, + uPortPppConnectCallback_t *pConnectCallback, + uPortPppDisconnectCallback_t *pDisconnectCallback, + uPortPppTransmitCallback_t *pTransmitCallback); + +/** Indicate that a PPP interface that was previously attached with + * a call to uPortPppAttach() is now connected. Internally + * #uPortPppConnectCallback_t will be called. + * + * If a PPP interface is not supported by the 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] pDevHandle the #uDeviceHandle_t of the device + * that is offering the PPP interface; + * this is a void * rather than a + * #uDeviceHandle_t here in order to + * avoid dragging in all of the uDevice + * types into the port layer. + * @param[in] pIpAddress the IP address, if already assigned, + * NULL if not. + * @param[in] pDnsIpAddressPrimary the primary DNS address, if already + * known, NULL if not; currently only + * IPV4 addresses are supported. + * @param[in] pDnsIpAddressSecondary the secondary DNS address, if already + * known, NULL if not; currently only + * IPV4 addresses are supported. + * @param[in] pUsername pointer to a string giving the user + * name for PPP authentication; should + * be set to NULL if no user name or + * password is required. + * @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. + * @param authenticationMode the authentication mode, ignored if + * pUsername is NULL. + * @return zero on success, else negative error + * code. + */ +int32_t uPortPppConnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress, + uSockIpAddress_t *pDnsIpAddressPrimary, + uSockIpAddress_t *pDnsIpAddressSecondary, + const char *pUsername, + const char *pPassword, + uPortPppAuthenticationMode_t authenticationMode); + +/** Reconnect a PPP interface after it was lost due to, for instance, + * a radio interface service loss. Internally #uPortPppConnectCallback_t + * will be called. + * + * If a PPP interface is not supported by the 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] pDevHandle the #uDeviceHandle_t of the device + * that is offering the PPP interface; + * this is a void * rather than a + * #uDeviceHandle_t here in order to + * avoid dragging in all of the uDevice + * types into the port layer. + * @param[in] pIpAddress the IP address, if already assigned, + * NULL if not. + * @return zero on success, else negative error + * code. + */ +int32_t uPortPppReconnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress); + +/** Indicate that a PPP interface that was previously attached with + * a call to uPortPppAttach() is going to be disconnected. This + * must be called by a ubxlib layer (e.g. cellular) that previous + * called uPortPppConnect() _before_ that connection is brought down. + * Internally it will cause #uPortPppDisconnectCallback_t to be called. + * + * When this function has returned, pReceiveCallback passed + * to #uPortPppConnectCallback_t will no longer be called and any + * pReceiveData buffer passed to #uPortPppConnectCallback_t will no + * longer be written-to. + * + * If no PPP connection is open this function will do nothing. + * + * If a PPP interface is not supported by the 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] pDevHandle the #uDeviceHandle_t of the device + * that offered the PPP interface; this + * is a void * rather than a #uDeviceHandle_t + * in order to avoid dragging in all of the + * uDevice types into the port layer. + * @return zero on success, else negative error code. + */ +int32_t uPortPppDisconnect(void *pDevHandle); + +/** Detach a PPP interface from the bottom of a platform's IP stack. + * #uPortPppDisconnectCallback_t will be called first. + * + * When this function has returned none of the callbacks passed to + * uPortPppAttach() will be called any more. + * + * If no PPP connection is open this function will do nothing. + * + * If a PPP interface is not supported by the 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] pDevHandle the #uDeviceHandle_t of the device that + * originally called uPortPppAttach(); this + * is a void * rather than a #uDeviceHandle_t + * here in order to avoid dragging in all of + * the uDevice types into the port layer. + * @return zero on success, else negative error code. + */ +int32_t uPortPppDetach(void *pDevHandle); + +#ifdef __cplusplus +} +#endif + +/** @}*/ + +#endif // _U_PORT_PPP_H_ + +// End of file diff --git a/port/platform/arduino/source.txt b/port/platform/arduino/source.txt index b4c007563..47ce1ae54 100644 --- a/port/platform/arduino/source.txt +++ b/port/platform/arduino/source.txt @@ -32,6 +32,8 @@ cell/src/u_cell_geofence.c cell/src/u_cell_private.c cell/src/u_cell_mux_private.c cell/src/u_cell_mno_db.c +cell/src/u_cell_ppp.c +cell/src/u_cell_stub_gnss.c gnss/src/u_gnss.c gnss/src/u_gnss_pwr.c gnss/src/u_gnss_cfg.c @@ -121,6 +123,7 @@ common/geofence/src/u_geofence.c common/geofence/src/dummy/u_geofence_geodesic.c port/u_port_heap.c port/u_port_resource.c +port/u_port_ppp_default.c port/platform/common/event_queue/u_port_event_queue.c port/platform/common/mbedtls/u_port_crypto.c port/clib/u_port_clib_mktime64.c @@ -132,6 +135,7 @@ port/platform/esp-idf/src/u_port_gpio.c port/platform/esp-idf/src/u_port_uart.c port/platform/esp-idf/src/u_port_i2c.c port/platform/esp-idf/src/u_port_spi.c +port/platform/esp-idf/src/u_port_ppp.c port/platform/esp-idf/src/u_port_private.c port/platform/common/mutex_debug/u_mutex_debug.c port/platform/common/log_ram/u_log_ram.c diff --git a/port/platform/arduino/source_test.txt b/port/platform/arduino/source_test.txt index 3f7918a54..a7cd491ed 100644 --- a/port/platform/arduino/source_test.txt +++ b/port/platform/arduino/source_test.txt @@ -42,6 +42,7 @@ cell/test/u_cell_gpio_test.c cell/test/u_cell_fota_test.c cell/test/u_cell_mux_test.c cell/test/u_cell_geofence_test.c +cell/test/u_cell_ppp_test.c cell/test/u_cell_test_preamble.c cell/test/u_cell_test_private.c cell/test/u_cell_mux_private_test.c @@ -96,6 +97,7 @@ port/platform/common/test/u_preamble_test.c port/platform/common/test/u_postamble_test.c port/platform/common/test/u_cleanup_test.c port/platform/common/test_util/u_test_util_resource_check.c +port/platform/esp-idf/test/u_espidf_ppp_test.c # Note: it is deliberate that u_runner.c is here but # port/platform/common/runner is in "include.txt" # and NOT just in "include_test.txt": the header file diff --git a/port/platform/common/automation/DATABASE.md b/port/platform/common/automation/DATABASE.md index 87288a52a..d24acb885 100644 --- a/port/platform/common/automation/DATABASE.md +++ b/port/platform/common/automation/DATABASE.md @@ -40,7 +40,7 @@ The table below defines the instances of test hardware available on the `ubxlib` | 11.0 | ESP32-DevKitC | ESP32 | | ESP-IDF | | M9 | port at_client ubx_protocol gnss spartn | | U_CFG_APP_GNSS_SPI=2 U_CFG_TEST_PIN_GNSS_RESET_N=25 U_GNSS_MGA_TEST_HAS_FLASH U_CFG_MUTEX_DEBUG U_CFG_TEST_UART_B=1 U_CFG_TEST_PIN_UART_A_CTS=-1 U_CFG_TEST_PIN_UART_A_RTS=-1 U_CFG_TEST_PIN_UART_A_RXD=26 U_CFG_TEST_PIN_UART_B_TXD=27 U_CFG_TEST_PIN_UART_B_RXD=14 U_DEBUG_UTILS_DUMP_THREADS | | 11.1 | ESP32-DevKitC | ESP32 | esp32:esp32:esp32doit-devkit-v1 | Arduino | ESP-IDF | M9 | port at_client ubx_protocol gnss spartn | | U_CFG_APP_GNSS_SPI=2 U_CFG_TEST_PIN_GNSS_RESET_N=25 U_GNSS_MGA_TEST_HAS_FLASH U_CFG_TEST_UART_B=1 U_CFG_TEST_PIN_UART_A_CTS=-1 U_CFG_TEST_PIN_UART_A_RTS=-1 U_CFG_TEST_PIN_UART_A_RXD=26 U_CFG_TEST_PIN_UART_B_TXD=27 U_CFG_TEST_PIN_UART_B_RXD=14 | | 11.2 | ESP32-DevKitC | ESP32 | esp-wrover-kit | platformio | arduino | M9 | port device network | | U_CFG_APP_FILTER=port.example U_CFG_APP_GNSS_SPI=2 U_GNSS_MGA_TEST_HAS_FLASH U_CFG_TEST_PIN_GNSS_RESET_N=25 | -| 12.0 | ESP32-DevKitC + EVK, Cat M1, uConnect | ESP32 | | ESP-IDF | | SARA_R5 M8 NINA_W15 | port device network sock ble wifi cell short_range security mqtt_client http_client gnss location || U_CELL_TEST_MUX_ALWAYS U_CFG_TEST_SECURITY_DISABLE U_CFG_TEST_CELL_PWR_DISABLE U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=2 U_CFG_APP_PIN_SHORT_RANGE_CTS=22 U_CFG_APP_PIN_SHORT_RANGE_RTS=23 U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_SARA_R5_M8_WORKAROUND U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_APP_PIN_CELL_TXD=21 U_CFG_APP_PIN_CELL_RXD=19 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_DEBUG_UTILS_DUMP_THREADS | +| 12.0 | ESP32-DevKitC + EVK, Cat M1, uConnect | ESP32 | | ESP-IDF | | SARA_R5 M8 NINA_W15 | port device network sock ble wifi cell short_range security mqtt_client http_client gnss location || U_CFG_PPP_ENABLE U_CELL_TEST_MUX_ALWAYS U_CFG_TEST_SECURITY_DISABLE U_CFG_TEST_CELL_PWR_DISABLE U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=2 U_CFG_APP_PIN_SHORT_RANGE_CTS=22 U_CFG_APP_PIN_SHORT_RANGE_RTS=23 U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_SARA_R5_M8_WORKAROUND U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_APP_PIN_CELL_TXD=21 U_CFG_APP_PIN_CELL_RXD=19 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_DEBUG_UTILS_DUMP_THREADS | | 12.2.0| ESP32-DevKitC + EVK, Cat M1, uConnect | ESP32 | esp-wrover-kit | platformio | espidf | SARA_R5 M8 NINA_W15 | port device network | | U_CFG_APP_FILTER=port.example.gnssInfo U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=2 U_CFG_APP_PIN_SHORT_RANGE_CTS=22 U_CFG_APP_PIN_SHORT_RANGE_RTS=23 U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_SARA_R5_M8_WORKAROUND U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_APP_PIN_CELL_TXD=21 U_CFG_APP_PIN_CELL_RXD=19 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p | | 12.2.1| ESP32-DevKitC + EVK, Cat M1 | ESP32 | esp-wrover-kit | platformio | espidf | SARA_R5 | port device network | cell | U_CFG_APP_FILTER=cellInfo U_CFG_TEST_SECURITY_DISABLE U_CFG_CELL_DISABLE_UART_POWER_SAVING U_CFG_APP_PIN_SHORT_RANGE_CTS=22 U_CFG_APP_PIN_SHORT_RANGE_RTS=23 U_CFG_SARA_R5_M8_WORKAROUND U_CFG_APP_CELL_PIN_GNSS_POWER=-1 U_CFG_APP_CELL_PIN_GNSS_DATA_READY=-1 U_CFG_APP_PIN_CELL_TXD=21 U_CFG_APP_PIN_CELL_RXD=19 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 | | 12.2.2| ESP32-DevKitC + EVK, Cat M1, uConnect | ESP32 | esp-wrover-kit | platformio | espidf | NINA_W15 | port device network | short_range | U_CFG_APP_FILTER=wifiStation U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=2 U_CFG_APP_PIN_SHORT_RANGE_CTS=22 U_CFG_APP_PIN_SHORT_RANGE_RTS=23 U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p | @@ -56,7 +56,8 @@ The table below defines the instances of test hardware available on the `ubxlib` | 19 | C030-R5 board (STM32F437), Cat M1, uConnect| STM32F4 | | STM32Cube | | SARA_R5 NINA_W15 M8 | port device network sock ble wifi cell short_range security mqtt_client http_client ubx_protocol spartn gnss location || U_CFG_TEST_CELL_PWR_DISABLE U_CFG_APP_PIN_SHORT_RANGE_RESET_TO_DEFAULTS=0x42 U_CFG_SARA_R5_M8_WORKAROUND 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_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=2462ABB6CC42p U_DEBUG_UTILS_DUMP_THREADS | | 20 | WHRE board (NINA-W1), Cat M1 | ESP32 | | ESP-IDF | | SARA_R412M_02B | cell mqtt_client | | U_CFG_APP_FILTER=cellMqtt.mqttClient.exampleMqtt.cellMuxMqtt.cellCfgGreeting.cellInfo.cellCfgTime 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 | | 21 | WHRE board (NINA-W1), Cat M1 | ESP32 | | ESP-IDF | | SARA_R410M_02B | cell mqtt_client | | U_CFG_APP_FILTER=cellMqtt.mqttClient.exampleMqtt.cellMuxMqtt.cellCfgGreeting.cellInfo.cellCfgTime 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 | -| 22 | NINA-W1 + EVK, Cat M1 | ESP32 | esp32:esp32:nina_w10 | Arduino | ESP-IDF | SARA_R422 M8 | port device network sock security cell mqtt_client http_client gnss location || U_CFG_TEST_CELL_PWR_DISABLE U_GNSS_TEST_DISABLE_ACTIVE_ANTENNA_DISABLE U_CFG_MONITOR_DTR_RTS_OFF U_CFG_1V8_SIM_WORKAROUND U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_PWR_ON=5 U_CFG_APP_PIN_CELL_TXD=14 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 | +| 22.0 | NINA-W1 + EVK, Cat M1 | ESP32 | esp32:esp32:nina_w10 | Arduino | ESP-IDF | SARA_R422 M8 | port device network sock security cell mqtt_client http_client gnss location || U_CFG_PPP_ENABLE U_CFG_TEST_CELL_PWR_DISABLE U_GNSS_TEST_DISABLE_ACTIVE_ANTENNA_DISABLE U_CFG_MONITOR_DTR_RTS_OFF U_CFG_1V8_SIM_WORKAROUND U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_PWR_ON=5 U_CFG_APP_PIN_CELL_TXD=14 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 | +| 22.1 | EVK, Cat M1 | ESP32 | | ESP-IDF | | SARA_R422 | port | | U_CFG_APP_FILTER=espidf U_CFG_PPP_ENABLE U_CFG_TEST_CELL_PWR_DISABLE U_CFG_MONITOR_DTR_RTS_OFF U_CFG_1V8_SIM_WORKAROUND U_CFG_APP_PIN_CELL_ENABLE_POWER=-1 U_CFG_APP_PIN_CELL_VINT=-1 U_CFG_APP_PIN_CELL_PWR_ON=5 U_CFG_APP_PIN_CELL_TXD=14 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 | | 23 | Windows + EVK, Cat M1, uConnect | WIN32 | | WINDOWS | MSVC | SARA_R5 M8 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_AT_CLIENT_PRINT_WITH_TIMESTAMP U_CFG_HEAP_MONITOR U_ASSERT_HOOK_FUNCTION_TEST_RETURN U_CFG_TEST_DISABLE_GREETING_CALLBACK U_CFG_MUTEX_DEBUG U_NETWORK_GNSS_CFG_CELL_USE_AT_ONLY U_GNSS_MSG_TEST_MESSAGE_RECEIVE_NON_BLOCKING_PRINT U_CFG_TEST_NET_STATUS_CELL=RF_SWITCH_A U_CFG_TEST_NET_STATUS_SHORT_RANGE=PWR_SWITCH_A U_BLE_TEST_CFG_REMOTE_SPS_CENTRAL=6009C390E4DAp U_CFG_TEST_UART_A=100 U_CFG_APP_SHORT_RANGE_UART=101 U_CFG_APP_CELL_UART=102 U_CFG_QUEUE_DEBUG | | 24 | Linux/Posix under Zephyr | LINUX32 | native_posix | Zephyr | | | port gnss geofence | gnss geodesic | U_CFG_APP_FILTER=port.gnssFenceStandalone U_CFG_GEOFENCE U_CFG_GNSS_FENCE_USE_GEODESIC U_CFG_HEAP_MONITOR U_ASSERT_HOOK_FUNCTION_TEST_RETURN U_CFG_MUTEX_DEBUG U_CFG_TEST_UART_A=0 U_CFG_TEST_UART_B=1 | | 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 | diff --git a/port/platform/common/automation/ubxlib_h_excludes.txt b/port/platform/common/automation/ubxlib_h_excludes.txt index ddda49e77..ce9a3011f 100644 --- a/port/platform/common/automation/ubxlib_h_excludes.txt +++ b/port/platform/common/automation/ubxlib_h_excludes.txt @@ -10,4 +10,7 @@ u_lib_internal.h lib_fibonacci.h # Contains no user-callable functions -u_geofence_geodesic.h \ No newline at end of file +u_geofence_geodesic.h + +# Called only internally within ubxlib +u_port_ppp.h \ No newline at end of file diff --git a/port/platform/esp-idf/README.md b/port/platform/esp-idf/README.md index bdb02fcf0..ca54dbfe2 100644 --- a/port/platform/esp-idf/README.md +++ b/port/platform/esp-idf/README.md @@ -4,4 +4,17 @@ These directories provide the implementation of the porting layer on the Espress - [app](app): contains the code that runs the application (both examples and unit tests) on the ESP-IDF platform. - [src](src): contains the implementation of the porting layers for ESP-IDF. - [mcu](mcu): contains the configuration and build files for the MCUs supported on the ESP-IDF platform. -- [u_cfg_os_platform_specific.h](u_cfg_os_platform_specific.h): task priorities and stack sizes for the platform, built into this code. \ No newline at end of file +- [u_cfg_os_platform_specific.h](u_cfg_os_platform_specific.h): task priorities and stack sizes for the platform, built into this code. +- [test](test): contains tests that use ESP-IDF application APIs to check out the integration of `ubxlib` into ESP-IDF, e.g. at PPP level beneath LWIP. + +# PPP-Level Integration With Cellular +`ubxlib` depends on very few ESP-IDF components (see the directories beneath this one for how to perform a build) but note that, if you wish to include a PPP-level integration at the base of the ESP-IDF LWIP stack, allowing use of native ESP-IDF clients (e.g. MQTT) with a cellular connection, then you must define `U_CFG_PPP_ENABLE` when building `ubxlib` and you must switch on the following in your `sdkconfig` file: + +- `CONFIG_LWIP_PPP_SUPPORT` +- `CONFIG_ESP_NETIF_TCPIP_LWIP` +- `CONFIG_LWIP_PPP_PAP_SUPPORT` +- if your network operator requires a user name and password along with the APN **AND** requires CHAP authentication, then also switch on `CONFIG_LWIP_PPP_CHAP_SUPPORT` + +If you are minimising the components built into your main application then you should add the ESP-IDF component `esp_netif` to the list. If you fail to do this your code will either fail to link because `_g_esp_netif_netstack_default_ppp` could not be found, or the initial PPP LCP negotiation phase with the module will fail. + +You must also call `esp_netif_init(()` and `esp_event_loop_create_default()` at start of day from your application code, otherwise ESP-IDF will not work correctly or will go "bang" somewhere in the IP stack at the point that a cellular connection is made. \ No newline at end of file diff --git a/port/platform/esp-idf/app/u_main.c b/port/platform/esp-idf/app/u_main.c index 36f3371c6..ca724427a 100644 --- a/port/platform/esp-idf/app/u_main.c +++ b/port/platform/esp-idf/app/u_main.c @@ -40,6 +40,15 @@ #include "u_debug_utils.h" +// Bring in the ESP-IDF CONFIG_ #defines +#include "sdkconfig.h" + +#ifdef CONFIG_LWIP_PPP_SUPPORT +# include "esp_log.h" +# include "esp_event.h" +# include "esp_netif.h" +#endif + /* ---------------------------------------------------------------- * COMPILE-TIME MACROS * -------------------------------------------------------------- */ @@ -77,6 +86,12 @@ static void appTask(void *pParam) U_MUTEX_DEBUG_WATCHDOG_TIMEOUT_SECONDS); #endif +#ifdef CONFIG_LWIP_PPP_SUPPORT + esp_netif_init(); + esp_event_loop_create_default(); + esp_log_level_set("*", ESP_LOG_VERBOSE); +#endif + #ifdef U_RUNNER_TOP_STR // If U_RUNNER_TOP_STR is defined we must be running inside the // test automation system (since the definition is added by @@ -88,7 +103,7 @@ static void appTask(void *pParam) uPortTaskBlock(5000); uPortInit(); -#if U_CFG_APP_PIN_CELL_RESET >= 0 +# if U_CFG_APP_PIN_CELL_RESET >= 0 // Set reset high (i.e. not reset) if it is connected (this for the // HPG Solution board) we use in the ubxlib test farm gpioConfig.pin = U_CFG_APP_PIN_CELL_RESET; @@ -96,7 +111,7 @@ static void appTask(void *pParam) gpioConfig.direction = U_PORT_GPIO_DIRECTION_OUTPUT; uPortGpioConfig(&gpioConfig); uPortGpioSet(U_CFG_APP_PIN_CELL_RESET, 1); -#endif +# endif UNITY_BEGIN(); @@ -120,6 +135,10 @@ static void appTask(void *pParam) uPortDeinit(); +# ifdef CONFIG_LWIP_PPP_SUPPORT + esp_netif_deinit(); +# endif + while (1) {} #else // If U_RUNNER_TOP_STR is not defined we must be running outside diff --git a/port/platform/esp-idf/mcu/esp32/README.md b/port/platform/esp-idf/mcu/esp32/README.md index 1e9f3dd3c..a7986398c 100644 --- a/port/platform/esp-idf/mcu/esp32/README.md +++ b/port/platform/esp-idf/mcu/esp32/README.md @@ -26,7 +26,7 @@ set U_FLAGS=-DMY_FLAG -DU_CFG_APP_PIN_CELL_ENABLE_POWER=-1 With this done, `cd` to your chosen build directory beneath this one to build and download your code. # Integration With Your Application -To use this port in your ESP32 application you need to include the [port/platform/esp-idf/mcu/esp32/components](components) directory in your `EXTRA_COMPONENT_DIRS` and add `ubxlib` to `COMPONENTS`. As an example you can have a look at [runner/CMakeLists.txt](runner/CMakeLists.txt). An `sdkconfig` configuration that allows the complete `ubxlib` test suite to be run can be found in [runner/sdkconfig.defaults](runner/sdkconfig.defaults) but the `ubxlib` core code requires no particular configuration beyond the default (just UART console output defaulting to HW block 0 for debugging); stack/heap should simply be configured as you require for your application. +To use this port in your ESP32 application you need to include the [port/platform/esp-idf/mcu/esp32/components](components) directory in your `EXTRA_COMPONENT_DIRS` and add `ubxlib` to `COMPONENTS`. As an example you can have a look at [runner/CMakeLists.txt](runner/CMakeLists.txt). An `sdkconfig` configuration that allows the complete `ubxlib` test suite to be run can be found in [runner/sdkconfig.defaults](runner/sdkconfig.defaults) but the `ubxlib` core code requires only UART console output (defaulting to HW block 0) for debugging, and `CONFIG_LWIP_PPP_SUPPORT` plus at least `CONFIG_LWIP_PPP_PAP_SUPPORT` for PPP connectivity; stack/heap should be configured as you require for your application. If you aren't already familiar with the ESP-IDF build environment, here's a step-by-step example, based on the approach ESP-IDF suggests and assuming you want to use, for instance, a sockets connection with a u-blox cellular module: diff --git a/port/platform/esp-idf/mcu/esp32/cfg/u_cfg_app_platform_specific.h b/port/platform/esp-idf/mcu/esp32/cfg/u_cfg_app_platform_specific.h index 3777f9606..2fb81d605 100644 --- a/port/platform/esp-idf/mcu/esp32/cfg/u_cfg_app_platform_specific.h +++ b/port/platform/esp-idf/mcu/esp32/cfg/u_cfg_app_platform_specific.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef _U_PORT_APP_PLATFORM_SPECIFIC_H_ -#define _U_PORT_APP_PLATFORM_SPECIFIC_H_ +#ifndef _U_CFG_APP_PLATFORM_SPECIFIC_H_ +#define _U_CFG_APP_PLATFORM_SPECIFIC_H_ /** @file * @brief This header file contains configuration information for @@ -334,6 +334,6 @@ # define U_CFG_APP_CELL_PIN_GNSS_DATA_READY 24 // AKA GPIO3 #endif -#endif // _U_PORT_APP_PLATFORM_SPECIFIC_H_ +#endif // _U_CFG_APP_PLATFORM_SPECIFIC_H_ // End of file diff --git a/port/platform/esp-idf/mcu/esp32/components/ubxlib/CMakeLists.txt b/port/platform/esp-idf/mcu/esp32/components/ubxlib/CMakeLists.txt index 0bf56df4a..b1f3b8aa4 100644 --- a/port/platform/esp-idf/mcu/esp32/components/ubxlib/CMakeLists.txt +++ b/port/platform/esp-idf/mcu/esp32/components/ubxlib/CMakeLists.txt @@ -68,6 +68,7 @@ set(COMPONENT_SRCS ${PLATFORM_DIR}/src/u_port_uart.c ${PLATFORM_DIR}/src/u_port_i2c.c ${PLATFORM_DIR}/src/u_port_spi.c + ${PLATFORM_DIR}/src/u_port_ppp.c ${PLATFORM_DIR}/src/u_port_private.c ${PLATFORM_DIR}/../../clib/u_port_clib_mktime64.c ${PLATFORM_DIR}/../../u_port_timezone.c @@ -78,6 +79,12 @@ set(COMPONENT_PRIV_INCLUDEDIRS ${UBXLIB_PRIVATE_INC} ) +# Add the platform-specific tests and examples +list(APPEND UBXLIB_TEST_SRC + ${PLATFORM_DIR}/test/u_espidf_ppp_test.c + ${UBXLIB_BASE}/example/sockets/main_ppp_espidf.c +) + # Export these variables to parent so they can be picked up by ubxlib_runner set(UBXLIB_TEST_SRC ${UBXLIB_TEST_SRC} PARENT_SCOPE) set(UBXLIB_TEST_INC ${UBXLIB_TEST_INC} PARENT_SCOPE) @@ -87,7 +94,7 @@ set(UBXLIB_BASE ${UBXLIB_BASE} PARENT_SCOPE) # For crypto functions and, from ESP-IDF v5, for drivers, timers # and the debug helper in esp_system -set(COMPONENT_REQUIRES "driver" "esp_timer" "mbedtls" "esp_system" ${UBXLIB_EXTRA_LIBS}) +set(COMPONENT_REQUIRES "driver" "esp_timer" "mbedtls" "esp_system" "esp_netif" ${UBXLIB_EXTRA_LIBS}) message("COMPONENT_REQUIRES for component UBXLIB is ${COMPONENT_REQUIRES}") diff --git a/port/platform/esp-idf/mcu/esp32/runner/sdkconfig.defaults b/port/platform/esp-idf/mcu/esp32/runner/sdkconfig.defaults index f699253a0..aca6fdac5 100644 --- a/port/platform/esp-idf/mcu/esp32/runner/sdkconfig.defaults +++ b/port/platform/esp-idf/mcu/esp32/runner/sdkconfig.defaults @@ -3,7 +3,7 @@ # Note: the IDF_TARGET is not specified here: it will default to esp32 # and can be overridden by setting the environment variable IDF_TARGET -# This should be sufficient that the user is left with 5 kbytes of +# This is sufficient that the user is left with 5 kbytes of # main task stack free for themselves CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 @@ -15,6 +15,11 @@ CONFIG_ESP_CONSOLE_UART_NUM=0 # ubxlib needs no more than the NANO stuff CONFIG_NEWLIB_NANO_FORMAT=y +# Needed to include PPP interface for integration into the bottom of LWIP +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_PAP_SUPPORT=y +CONFIG_LWIP_PPP_CHAP_SUPPORT=y + # We set this to "n" as otherwise the PlatformIO builds # fail because some AES functions are not brought-in # to their pre-built ESP-IDF library; normally it would @@ -32,3 +37,8 @@ CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y CONFIG_HEAP_TRACING_STANDALONE=y CONFIG_HEAP_TRACING=y +# Uncomment these as required when debugging +#CONFIG_LOG_DEFAULT_LEVEL_VERBOSE=y +#CONFIG_LWIP_PPP_DEBUG_ON=y +#CONFIG_LWIP_DEBUG=y + diff --git a/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/CMakeLists.txt b/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/CMakeLists.txt index d0269911a..563f685c9 100644 --- a/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/CMakeLists.txt +++ b/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/CMakeLists.txt @@ -4,5 +4,5 @@ # stuff in the test sub-directory as the tests. set(COMPONENT_SRCS "") set(COMPONENT_ADD_INCLUDEDIRS "test") -set(COMPONENT_REQUIRES "driver" "esptool_py" "unity") +set(COMPONENT_REQUIRES "driver" "esptool_py" "unity" "esp_netif") register_component() \ No newline at end of file diff --git a/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/test/CMakeLists.txt b/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/test/CMakeLists.txt index 96ff50d70..ebdfb6af9 100644 --- a/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/test/CMakeLists.txt +++ b/port/platform/esp-idf/mcu/esp32/runner/ubxlib_runner/test/CMakeLists.txt @@ -30,7 +30,7 @@ if (DEFINED ENV{U_UBXLIB_AUTO}) list(APPEND COMPONENT_SRCS ${UBXLIB_BASE}/port/platform/common/runner/u_runner.c) endif() -set(COMPONENT_REQUIRES "driver" "esptool_py" "unity" ${GEODESIC_COMPONENT}) +set(COMPONENT_REQUIRES "driver" "esptool_py" "unity" "esp_netif" ${GEODESIC_COMPONENT}) message("COMPONENT_REQUIRES for component UBXLIB_RUNNER TEST is ${COMPONENT_REQUIRES}") diff --git a/port/platform/esp-idf/src/u_port.c b/port/platform/esp-idf/src/u_port.c index dc975408d..fa477b0d2 100644 --- a/port/platform/esp-idf/src/u_port.c +++ b/port/platform/esp-idf/src/u_port.c @@ -32,6 +32,8 @@ #include "u_port_heap.h" #include "u_port_uart.h" #include "u_port_event_queue_private.h" +#include "u_port_ppp.h" +#include "u_port_ppp_private.h" #include "u_port_private.h" #include "freertos/FreeRTOS.h" // For xPortGetFreeHeapSize() @@ -108,6 +110,16 @@ int32_t uPortInit() { int32_t errorCode = 0; + // Workaround for Espressif linker missing out files that + // only contain functions which also have weak alternatives + // (see https://www.esp32.com/viewtopic.php?f=13&t=8418&p=35899). + // Basically any file that might end up containing only functions + // that also have WEAK linked counterparts will be lost, so we need + // to add a dummy function in those files and call it from somewhere + // that will always be present in the build, which for the port + // layer we choose to be here + uPortPppDefaultPrivateLink(); + if (!gInitialised) { errorCode = uPortHeapMonitorInit(NULL, NULL, NULL); if (errorCode == 0) { @@ -116,6 +128,9 @@ int32_t uPortInit() errorCode = uPortPrivateInit(); if (errorCode == 0) { errorCode = uPortUartInit(); + if (errorCode == 0) { + errorCode = uPortPppPrivateInit(); + } } } } @@ -129,6 +144,7 @@ int32_t uPortInit() void uPortDeinit() { if (gInitialised) { + uPortPppPrivateDeinit(); uPortUartDeinit(); uPortPrivateDeinit(); uPortEventQueuePrivateDeinit(); diff --git a/port/platform/esp-idf/src/u_port_ppp.c b/port/platform/esp-idf/src/u_port_ppp.c new file mode 100644 index 000000000..3e1eb9fbc --- /dev/null +++ b/port/platform/esp-idf/src/u_port_ppp.c @@ -0,0 +1,741 @@ +/* + * Copyright 2019-2023 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 makes a connection from the bottom of ESP NETIF, i.e. + * the bottom of the IP stack inside ESP-IDF, to a PPP interface inside + * ubxlib. Such a PPP interface is provided by a cellular module. + * + * It is only compiled if CONFIG_LWIP_PPP_SUPPORT is set in your + * sdkconfig.h and 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" // memset() + +#include "u_cfg_sw.h" +#include "u_error_common.h" + +#include "u_linked_list.h" + +#include "u_sock.h" // uSockStringToAddress() + +#include "u_port.h" +#include "u_port_os.h" +#include "u_port_heap.h" +#include "u_port_ppp.h" +#include "u_port_debug.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +// Bring in the ESP-IDF CONFIG_ #defines +#include "sdkconfig.h" + +#ifdef CONFIG_LWIP_PPP_SUPPORT +#include "esp_event.h" +#include "esp_netif_ip_addr.h" +#include "esp_netif.h" +#include "esp_netif_defaults.h" +#include "esp_netif_ppp.h" +#endif + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +#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 100 +#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 + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// Forward declaration. +struct uPortPppInterface_t; + +/** Define a NETIF driver for ESP-IDF, used to provide a PPP + * connection to the bottom of the ESP-IDF IP stack. + */ +typedef struct { + esp_netif_driver_base_t base; + struct uPortPppInterface_t *pPppInterface; + uSockIpAddress_t *pIpAddress; + uSockIpAddress_t *pDnsIpAddressPrimary; + uSockIpAddress_t *pDnsIpAddressSecondary; + const char *pUsername; + const char *pPassword; + uPortPppAuthenticationMode_t authenticationMode; +} uPortPppNetifDriver_t; + +/** Define a PPP interface. + */ +typedef struct uPortPppInterface_t { + void *pDevHandle; + uPortSemaphoreHandle_t semaphoreExit; /**< This is created set to + 0 when the interface is + created and is given + when the eventPppChanged() + is informed that the PPP + interface has been taken + down by the attached IP + stack. */ + uPortPppConnectCallback_t *pConnectCallback; + uPortPppDisconnectCallback_t *pDisconnectCallback; + uPortPppTransmitCallback_t *pTransmitCallback; + bool pppRunning; + bool ipConnected; + uPortPppNetifDriver_t netifDriver; +} uPortPppInterface_t; + +#endif // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +/** Root of the linked list of PPP entities. + */ +static uLinkedList_t *gpPppInterfaceList = NULL; /**< A linked list of uPortPppInterface_t. */ + +/** Mutex to protect the linked list of PPP entities. + */ +static uPortMutexHandle_t gMutex = NULL; + +#endif // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// Find the PPP interface structure for the given handle. +static uPortPppInterface_t *pFindPppInterface(void *pDevHandle) +{ + uPortPppInterface_t *pPppInterface = NULL; + uLinkedList_t *pList; + + 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; +} + +/** Convert an IP address of ours to ESP-IDF format. + */ +static int32_t convertIpAddress(uSockIpAddress_t *pIn, esp_ip_addr_t *pOut) +{ + int32_t espError = ESP_ERR_INVALID_ARG; + + memset(pOut, 0, sizeof(*pOut)); + switch (pIn->type) { + case U_SOCK_ADDRESS_TYPE_V4: + pOut->type = ESP_IPADDR_TYPE_V4; + pOut->u_addr.ip4.addr = ESP_IP4TOADDR(((pIn->address.ipv4 >> 24) && 0xFF), + ((pIn->address.ipv4 >> 16) && 0xFF), + ((pIn->address.ipv4 >> 8) && 0xFF), + (pIn->address.ipv4 && 0xFF)); + espError = ESP_OK; + break; + case U_SOCK_ADDRESS_TYPE_V6: + pOut->type = ESP_IPADDR_TYPE_V6; + for (size_t x = 0; x < sizeof(pOut->u_addr.ip6.addr) / sizeof(pOut->u_addr.ip6.addr[0]); x++) { + pOut->u_addr.ip6.addr[x] = ESP_IP4TOADDR(((pIn->address.ipv6[x] >> 24) && 0xFF), + ((pIn->address.ipv6[x] >> 16) && 0xFF), + ((pIn->address.ipv6[x] >> 8) && 0xFF), + (pIn->address.ipv6[x] && 0xFF)); + } + espError = ESP_OK; + break; + default: + break; + } + + return espError; +} + +// Switch off DHCP and tell the IP stack what our IP address is. +static esp_err_t setIpAddress(esp_netif_t *pEspNetif, uSockIpAddress_t *pIpAddress) +{ + esp_err_t espError = ESP_ERR_INVALID_ARG; + esp_ip_addr_t espIpAddress = {0}; + esp_netif_ip_info_t ipInfo = {0}; + + switch (pIpAddress->type) { + case U_SOCK_ADDRESS_TYPE_V4: + ipInfo.netmask.addr = ESP_IP4TOADDR(0xFF, 0xFF, 0xFF, 0); + // TODO ipInfo.gw + espError = convertIpAddress(pIpAddress, &espIpAddress); + break; + case U_SOCK_ADDRESS_TYPE_V6: + espError = ESP_ERR_NOT_SUPPORTED; + break; + default: + break; + } + if (espError == ESP_OK) { + memcpy(&ipInfo.ip, &espIpAddress.u_addr.ip4, sizeof(ipInfo.ip)); + espError = esp_netif_dhcpc_stop(pEspNetif); + if (espError == ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { + espError = ESP_OK; + } + if (espError == ESP_OK) { + espError = esp_netif_set_ip_info(pEspNetif, &ipInfo); + } + } + + return espError; +} + +// Set a DNS address. +static esp_err_t setDnsAddress(esp_netif_t *pEspNetif, esp_netif_dns_type_t type, + uSockIpAddress_t *pIpAddress) +{ + esp_err_t espError = ESP_ERR_INVALID_ARG; + esp_netif_dns_info_t dnsInfo = {0}; + + switch (pIpAddress->type) { + case U_SOCK_ADDRESS_TYPE_V4: + espError = convertIpAddress(pIpAddress, &dnsInfo.ip); + break; + case U_SOCK_ADDRESS_TYPE_V6: + espError = ESP_ERR_NOT_SUPPORTED; + break; + default: + break; + } + if (espError == ESP_OK) { + espError = esp_netif_set_dns_info(pEspNetif, type, &dnsInfo); + } + + return espError; +} + +/** This function is provided as a callback to the NETIF layer of + * ESP-IDF in the structure esp_netif_driver_ifconfig_t, see + * postAttachStart(). + */ +static esp_err_t espNetifTransmit(void *pHandle, + void *pData, size_t length) +{ + int32_t errorCode = (int32_t) U_ERROR_COMMON_IGNORED;; + uPortPppNetifDriver_t *pDriver = (uPortPppNetifDriver_t *) pHandle; + struct uPortPppInterface_t *pPppInterface = pDriver->pPppInterface; + size_t guard = 0; + size_t sent = 0; + + if ((pPppInterface->pTransmitCallback != NULL) && (pPppInterface->pppRunning)) { + errorCode = 0; + while ((length > 0) && (errorCode >= 0) && (guard < U_PORT_PPP_TX_LOOP_GUARD)) { + errorCode = pPppInterface->pTransmitCallback(pPppInterface->pDevHandle, pData + sent, + length - sent); + if (errorCode > 0) { + length -= errorCode; + sent += errorCode; + } else { + vTaskDelay(U_PORT_PPP_TX_LOOP_DELAY_MS / portTICK_PERIOD_MS); + } + guard++; + } + } + + return (length == 0) ? ESP_OK : (esp_err_t) errorCode; +} + +/** This function is provided as a callback to the NETIF layer of + * ESP-IDF in the structure esp_netif_driver_ifconfig_t, see + * postAttachStart(). + */ +static void espNetifFreeRxBuffer(void *pHandle, void *pBuffer) +{ + // Not used + (void) pHandle; + (void) pBuffer; +} + +/** This function is provided as a callback to the NETIF layer of + * ESP-IDF in the structure #uPortPppNetifDriver_t. + */ +static esp_err_t postAttachStart(esp_netif_t *pEspNetif, void *pArgs) +{ + esp_err_t espError; + uPortPppNetifDriver_t *pDriver = (uPortPppNetifDriver_t *) pArgs; + const esp_netif_driver_ifconfig_t driverIfconfig = { + .handle = pDriver, + .driver_free_rx_buffer = espNetifFreeRxBuffer, + .transmit = espNetifTransmit + }; + esp_netif_ppp_config_t pppConfig = {0}; + + pDriver->base.netif = pEspNetif; + + espError = esp_netif_set_driver_config(pEspNetif, &driverIfconfig); + if (espError == ESP_OK) { + // Switch on events so that we can tell when the IP stack + // has finished with the PPP connection + + // This pattern borrowed from + // https://github.com/espressif/esp-protocols/blob/master/components/esp_modem/src/esp_modem_netif.cpp + pppConfig.ppp_phase_event_enabled = true; + pppConfig.ppp_error_event_enabled = false; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) + esp_netif_ppp_get_params(pEspNetif, &pppConfig); +#endif // ESP-IDF >= v4.4 + if (!pppConfig.ppp_error_event_enabled) { + pppConfig.ppp_error_event_enabled = true; + espError = esp_netif_ppp_set_params(pEspNetif, &pppConfig); + } + } + + if ((espError == ESP_OK) && (pDriver->pIpAddress != NULL)) { + espError = setIpAddress(pEspNetif, pDriver->pIpAddress); + } + + if (espError == ESP_OK) { + if (pDriver->pDnsIpAddressPrimary != NULL) { + espError = setDnsAddress(pEspNetif, ESP_NETIF_DNS_MAIN, pDriver->pDnsIpAddressPrimary); + } else { + uSockAddress_t address; + if (uSockStringToAddress(U_PORT_PPP_DNS_PRIMARY_DEFAULT_STR, &address) > 0) { + espError = setDnsAddress(pEspNetif, ESP_NETIF_DNS_MAIN, &address.ipAddress); + } + } + } + // Note: secondary DNS address not supported by ESP-IDF for PPP + +#if defined(CONFIG_LWIP_PPP_PAP_SUPPORT) || defined(CONFIG_LWIP_PPP_CHAP_SUPPORT) + if (espError == ESP_OK) { + // Choose at least PAP since otherwise LCP negotiation will fail + esp_netif_auth_type_t authenticationType = NETIF_PPP_AUTHTYPE_PAP; + if ((pDriver->pUsername != NULL) && (pDriver->pPassword != NULL)) { + // The enumeration used by ESP-IDF matches uPortPppAuthenticationMode_t + authenticationType = (esp_netif_auth_type_t) pDriver->authenticationMode; + } + // Set the username/password fields to an empty string otherwise the + // authentication mode will not be accepted + if (pDriver->pUsername == NULL) { + pDriver->pUsername = ""; + } + if (pDriver->pPassword == NULL) { + pDriver->pPassword = ""; + } + espError = esp_netif_ppp_set_auth(pEspNetif, authenticationType, + pDriver->pUsername, pDriver->pPassword); + } +#endif + + return espError; +} + +// Callback for received data. +static void receiveCallback(void *pDevHandle, const char *pData, + size_t dataSize, void *pCallbackParam) +{ + uPortPppNetifDriver_t *pDriver = (uPortPppNetifDriver_t *) pCallbackParam; + esp_netif_t *pEspNetif; + + (void) pDevHandle; + + pEspNetif = pDriver->base.netif; + if (pEspNetif != NULL) { + esp_netif_receive(pEspNetif, (void *) pData, dataSize, NULL); + } +} + +// Callback for IP state change events from the attached IP stack. +static void eventIpChanged(void *pArgs, esp_event_base_t eventBase, + int32_t eventId, void *pEventData) +{ + uPortPppNetifDriver_t *pDriver = (uPortPppNetifDriver_t *) pArgs; + struct uPortPppInterface_t *pPppInterface = pDriver->pPppInterface; + + switch (eventId) { + case IP_EVENT_PPP_GOT_IP: + pPppInterface->ipConnected = true; + break; + case IP_EVENT_PPP_LOST_IP: + pPppInterface->ipConnected = false; + break; + default: + break; + } +} + +// Callback for PPP state change events from the attached IP stack. +static void eventPppChanged(void *pArgs, esp_event_base_t eventBase, + int32_t eventId, void *pEventData) +{ + uPortPppNetifDriver_t *pDriver = (uPortPppNetifDriver_t *) pArgs; + struct uPortPppInterface_t *pPppInterface = pDriver->pPppInterface; + + uPortLog("U_PORT_PPP: received event %d.\n", eventId); + if ((eventId > NETIF_PPP_ERRORNONE) && (eventId < NETIF_PP_PHASE_OFFSET)) { + // This means that the IP stack is finished with us + uPortSemaphoreGive(pPppInterface->semaphoreExit); + pPppInterface->ipConnected = false; + } +} + +// Detach a PPP interface from the bottom of ESP NETIF. +static void pppDetach(uPortPppInterface_t *pPppInterface) +{ + if ((pPppInterface != NULL) && (pPppInterface->netifDriver.base.netif != NULL)) { + if (pPppInterface->ipConnected) { + esp_netif_action_disconnected(pPppInterface->netifDriver.base.netif, NULL, 0, NULL); + } + esp_netif_action_stop(pPppInterface->netifDriver.base.netif, NULL, 0, NULL); + // Wait for the IP stack to let us go + uPortLog("U_PORT_PPP: waiting to be released.\n"); + uPortSemaphoreTryTake(pPppInterface->semaphoreExit, + U_PORT_PPP_SHUTDOWN_TIMEOUT_SECONDS * 1000); + uPortLog("U_PORT_PPP: released.\n"); + if (pPppInterface->pDisconnectCallback != NULL) { + // Disconnect PPP and, if IP is still connected, also + // get it to try to terminate the PPP link + pPppInterface->pDisconnectCallback(pPppInterface->pDevHandle, + pPppInterface->ipConnected); + } + esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, eventIpChanged); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_LOST_IP, esp_netif_action_disconnected); + esp_event_handler_unregister(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, eventPppChanged); + pPppInterface->pppRunning = false; + pPppInterface->ipConnected = false; + esp_netif_destroy(pPppInterface->netifDriver.base.netif); + pPppInterface->netifDriver.base.netif = NULL; + } +} + +#endif // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS THAT ARE PRIVATE TO THIS PORT LAYER + * -------------------------------------------------------------- */ + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(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; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + while (gpPppInterfaceList != NULL) { + pPppInterface = (uPortPppInterface_t *) gpPppInterfaceList->p; + pListNext = gpPppInterfaceList->pNext; + uLinkedListRemove(&gpPppInterfaceList, pPppInterface); + // Make sure we don't accidentally try to call the + // down callback since the device handle will have + // been destroyed by now + pPppInterface->pDisconnectCallback = NULL; + pppDetach(pPppInterface); + uPortSemaphoreDelete(pPppInterface->semaphoreExit); + 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 // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// Attach a PPP interface to the bottom of ESP NETIF. +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; + + 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)); + errorCode = uPortSemaphoreCreate(&(pPppInterface->semaphoreExit), 0, 1); + if (errorCode == 0) { + pPppInterface->pDevHandle = pDevHandle; + pPppInterface->pConnectCallback = pConnectCallback; + pPppInterface->pDisconnectCallback = pDisconnectCallback; + pPppInterface->pTransmitCallback = pTransmitCallback; + if (uLinkedListAdd(&gpPppInterfaceList, pPppInterface)) { + // On this platform we don't do anything more + // at this point, everything else is done + // in uPortPppConnect() + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } else { + uPortSemaphoreDelete(pPppInterface->semaphoreExit); + uPortFree(pPppInterface); + } + } else { + uPortFree(pPppInterface); + } + } + } + + 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; + esp_netif_config_t espNetifConfigPpp = ESP_NETIF_DEFAULT_PPP(); + esp_netif_t *pEspNetif = NULL; + size_t guardCount = 0; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_INVALID_PARAMETER; + if ((pUsername == NULL) && (pPassword == NULL)) { + authenticationMode = U_PORT_PPP_AUTHENTICATION_MODE_NONE; + } + if (authenticationMode < U_PORT_PPP_AUTHENTICATION_MODE_MAX_NUM) { + errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND; + pPppInterface = pFindPppInterface(pDevHandle); + if (pPppInterface != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_NO_MEMORY; + pEspNetif = esp_netif_new(&espNetifConfigPpp); + if (pEspNetif != NULL) { + // Connect PPP to ESP-IDF NETIF: this + // will call postAttachStart() which + // will populate + // pPppInterface->netifDriver.base.netif + pPppInterface->netifDriver.base.post_attach = postAttachStart; + pPppInterface->netifDriver.pPppInterface = pPppInterface; + pPppInterface->netifDriver.pIpAddress = pIpAddress; + pPppInterface->netifDriver.pDnsIpAddressPrimary = pDnsIpAddressPrimary; + pPppInterface->netifDriver.pDnsIpAddressSecondary = pDnsIpAddressSecondary; + pPppInterface->netifDriver.pUsername = pUsername; + pPppInterface->netifDriver.pPassword = pPassword; + pPppInterface->netifDriver.authenticationMode = authenticationMode; + errorCode = (int32_t) U_ERROR_COMMON_PLATFORM; + if ((esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, eventPppChanged, + &(pPppInterface->netifDriver)) == ESP_OK) && + (esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected, + pEspNetif) == ESP_OK) && + (esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_LOST_IP, esp_netif_action_disconnected, + pEspNetif) == ESP_OK) && + (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, eventIpChanged, + &(pPppInterface->netifDriver)) == ESP_OK) && + (esp_netif_attach(pEspNetif, &(pPppInterface->netifDriver)) == ESP_OK)) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pPppInterface->pConnectCallback != NULL) { + errorCode = pPppInterface->pConnectCallback(pDevHandle, receiveCallback, + &(pPppInterface->netifDriver), + NULL, + U_PORT_PPP_RECEIVE_BUFFER_BYTES, + NULL); + } + if (errorCode == 0) { + pPppInterface->pppRunning = true; + esp_netif_action_start(pEspNetif, NULL, 0, NULL); + while (!pPppInterface->ipConnected && (guardCount < 50)) { + // Wait a few seconds for PPP to connect so that + // the user gets a connection the moment we exit + uPortTaskBlock(100); + guardCount++; + } + } + } + if ((errorCode != 0) && (pEspNetif != NULL)) { + // Clean up on error + esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, eventIpChanged); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected); + esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_LOST_IP, esp_netif_action_disconnected); + esp_event_handler_unregister(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, eventPppChanged); + esp_netif_destroy(pEspNetif); + pPppInterface->netifDriver.base.netif = NULL; + } + } + } + } + + 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; + esp_netif_t *pEspNetif = NULL; + + if (gMutex != NULL) { + + U_PORT_MUTEX_LOCK(gMutex); + + errorCode = (int32_t) U_ERROR_COMMON_NOT_FOUND; + pPppInterface = pFindPppInterface(pDevHandle); + if (pPppInterface != NULL) { + errorCode = (int32_t) U_ERROR_COMMON_PLATFORM; + pEspNetif = pPppInterface->netifDriver.base.netif; + if ((pEspNetif != NULL) && (setIpAddress(pEspNetif, pIpAddress) == ESP_OK)) { + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + if (pPppInterface->pConnectCallback != NULL) { + errorCode = pPppInterface->pConnectCallback(pDevHandle, receiveCallback, + &(pPppInterface->netifDriver), + 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) { + // No different from detach, it's going doooooown... + pppDetach(pPppInterface); + errorCode = (int32_t) U_ERROR_COMMON_SUCCESS; + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return errorCode; +} + +// Detach a PPP interface from the bottom of ESP NETIF. +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); + pppDetach(pPppInterface); + uPortSemaphoreDelete(pPppInterface->semaphoreExit); + uPortFree(pPppInterface); + } + + U_PORT_MUTEX_UNLOCK(gMutex); + } + + return (int32_t) U_ERROR_COMMON_SUCCESS; +} + +#endif // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// End of file diff --git a/port/platform/esp-idf/src/u_port_ppp_private.h b/port/platform/esp-idf/src/u_port_ppp_private.h new file mode 100644 index 000000000..bef2429a2 --- /dev/null +++ b/port/platform/esp-idf/src/u_port_ppp_private.h @@ -0,0 +1,56 @@ +/* + * Copyright 2019-2023 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 ESP32 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/esp-idf/test/u_espidf_ppp_test.c b/port/platform/esp-idf/test/u_espidf_ppp_test.c new file mode 100644 index 000000000..4efae23a0 --- /dev/null +++ b/port/platform/esp-idf/test/u_espidf_ppp_test.c @@ -0,0 +1,548 @@ +/* + * Copyright 2019-2023 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 that use ESP-IDF's native sockets API to test the + * PPP-level integration of ubxlib as a transport of LWIP. These tests + * should pass on ESP32 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 CONFIG_LWIP_PPP_SUPPORT and + * U_CFG_PPP_ENABLE are 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 + +// Bring in the ESP-IDF CONFIG_ #defines +#include "sdkconfig.h" + +#if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +#include "stddef.h" // NULL, size_t etc. +#include "stdlib.h" // rand() +#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 "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_ESPIDF_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_ESPIDF_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES +/** The stack size to use for the asynchronous receive task. + */ +# define U_ESPIDF_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES 2560 +#endif + +#ifndef U_ESPIDF_PPP_TEST_RECEIVE_TASK_PRIORITY +/** The priority to use for the asynchronous receive task. + */ +# define U_ESPIDF_PPP_TEST_RECEIVE_TASK_PRIORITY (U_CFG_OS_PRIORITY_MIN + 5) +#endif + +#ifndef U_ESPIDF_PPP_TEST_RECEIVE_TASK_RELAX_MS +/** How long the receive task should relax for between receive + * attempts. + */ +# define U_ESPIDF_PPP_TEST_RECEIVE_TASK_RELAX_MS 10 +#endif + +#ifndef U_ESPIDF_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_ESPIDF_PPP_TEST_RECEIVE_TASK_EXIT_MS. + */ +# define U_ESPIDF_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; +} uEspidfPppSockTestConfig_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 uEspidfPppSockTestConfig_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; + uEspidfPppSockTestConfig_t *pTestConfig = (uEspidfPppSockTestConfig_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_ESPIDF_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_ESPIDF_PPP_TEST_RECEIVE_TASK_EXIT_MS); + gTestConfig.taskHandle = NULL; + } + uPortFree(gTestConfig.pBuffer); + gTestConfig.pBuffer = NULL; +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: TESTS + * -------------------------------------------------------------- */ + +/** Basic TCP test. + */ +U_PORT_TEST_FUNCTION("[espidfSock]", "espidfSockTcp") +{ + 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; + + // 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.", + hostIp, U_SOCK_TEST_ECHO_TCP_SERVER_PORT, sock); + 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); + + errorCode = connect(sock, (struct sockaddr *) &destinationAddress, sizeof(destinationAddress)); + U_TEST_PRINT_LINE("connect() returned %d.", errorCode); + 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_ESPIDF_PPP_TEST_RECEIVE_TASK_STACK_SIZE_BYTES, + (void *) &gTestConfig, + U_ESPIDF_PPP_TEST_RECEIVE_TASK_PRIORITY, + &gTestConfig.taskHandle) == 0); + + // Throw random sized segments up... + offset = 0; + x = 0; + 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).", gTestConfig.packetsReceived, + gTestConfig.bytesReceived); + + // 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_ESPIDF_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("[espidfSock]", "espidfSockCleanUp") +{ + 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 // #if defined(CONFIG_LWIP_PPP_SUPPORT) && defined(U_CFG_PPP_ENABLE) + +// 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 000000000..a294adbeb --- /dev/null +++ b/port/platform/linux/test/u_linux_ppp_test.c @@ -0,0 +1,190 @@ +/* + * Copyright 2019-2023 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 that native Linux sockets API to test the + * PPP-level integration of ubxlib into Linux. 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. + * + * 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 + +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. +#include "stdbool.h" + +#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_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 + +/* ---------------------------------------------------------------- + * 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__) + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * 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(uNetworkTestHasSock); + 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; +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: TESTS + * -------------------------------------------------------------- */ + +/** Basic TCP test. + */ +U_PORT_TEST_FUNCTION("[linuxSock]", "linuxSockTcp") +{ + uNetworkTestList_t *pList; + int32_t resourceCount; + uDeviceHandle_t devHandle; + + // Whatever called us likely initialised the + // port so deinitialise it here to obtain the + // correct initial heap size + 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 + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + devHandle = *pTmp->pDevHandle; + + U_TEST_PRINT_LINE("doing basic TCP test on %s.", + gpUNetworkTestTypeName[pTmp->networkType]); + + // TODO + (void) devHandle; + } + + // 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); + } + + // To speed things up, do not close the device + uNetworkTestListFree(); + + // 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("[linuxSock]", "linuxSockCleanUp") +{ + // 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(); + uPortDeinit(); + // Printed for information: asserting happens in the postamble + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); +} + +// End of file diff --git a/port/platform/platformio/inc_src.txt b/port/platform/platformio/inc_src.txt index d6e5d878b..7ffbbc8ce 100644 --- a/port/platform/platformio/inc_src.txt +++ b/port/platform/platformio/inc_src.txt @@ -81,6 +81,7 @@ port/clib/u_port_clib_mktime64.c port/u_port_timezone.c port/u_port_heap.c port/u_port_resource.c +port/u_port_ppp_default.c port/platform/common/mutex_debug/u_mutex_debug.c gnss/src/lib_mga/u_lib_mga.c common/network/src/u_network.c diff --git a/port/platform/windows/README.md b/port/platform/windows/README.md index 800fd18e7..115e98f8c 100644 --- a/port/platform/windows/README.md +++ b/port/platform/windows/README.md @@ -7,7 +7,6 @@ These directories provide the implementation of the porting layer on Windows. I - [src](src): contains the implementation of the porting layers for Windows. - [mcu/win32](mcu/win32): contains the configuration and build files for Windows 32-bit. - [u_cfg_os_platform_specific.h](u_cfg_os_platform_specific.h): task priorities and stack sizes for the platform, built into this code. +- [test](test): contains tests that use Windows APIs to check out the integration of `ubxlib` into Windows, e.g. at PPP level. -Windows is a great environment for rapid development and debug visibility but note that both **stack checking** and **heap checking** cannot be done under Windows. - -Note that if you wish to run stuff such as Valgrind, which is only supported on Linux, then you can do so by running [Zephyr on Linux](..\zephyr). \ No newline at end of file +Windows is a great environment for rapid development and debug visibility but note that both **stack checking** and **heap checking** cannot be done under Windows. \ No newline at end of file diff --git a/port/platform/windows/test/u_windows_ppp_test.c b/port/platform/windows/test/u_windows_ppp_test.c new file mode 100644 index 000000000..7013f3db6 --- /dev/null +++ b/port/platform/windows/test/u_windows_ppp_test.c @@ -0,0 +1,190 @@ +/* + * Copyright 2019-2023 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 that native Windows sockets API to test the + * PPP-level integration of ubxlib into Windows. These tests should + * pass on Windows 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. + * + * 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 + +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. +#include "stdbool.h" + +#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_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 + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/** The string to put at the start of all prints from this test. + */ +#define U_TEST_PREFIX "U_WINDOWS_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__) + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * 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(uNetworkTestHasSock); + 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; +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: TESTS + * -------------------------------------------------------------- */ + +/** Basic TCP test. + */ +U_PORT_TEST_FUNCTION("[windowsSock]", "windowsSockTcp") +{ + uNetworkTestList_t *pList; + int32_t resourceCount; + uDeviceHandle_t devHandle; + + // Whatever called us likely initialised the + // port so deinitialise it here to obtain the + // correct initial heap size + 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 + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + devHandle = *pTmp->pDevHandle; + + U_TEST_PRINT_LINE("doing basic TCP test on %s.", + gpUNetworkTestTypeName[pTmp->networkType]); + + // TODO + (void) devHandle; + } + + // 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); + } + + // To speed things up, do not close the device + uNetworkTestListFree(); + + // 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("[windowsSock]", "windowsSockCleanUp") +{ + // 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(); + uPortDeinit(); + // Printed for information: asserting happens in the postamble + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); +} + +// End of file diff --git a/port/platform/zephyr/CMakeLists.txt b/port/platform/zephyr/CMakeLists.txt index 335a22683..ac5993a6a 100644 --- a/port/platform/zephyr/CMakeLists.txt +++ b/port/platform/zephyr/CMakeLists.txt @@ -90,11 +90,13 @@ if (CONFIG_UBXLIB_TEST) target_sources(app PRIVATE ${UBXLIB_TEST_SRC} + test/u_zephyr_ppp_test.c ) zephyr_include_directories( ${UBXLIB_PRIVATE_INC} ${UBXLIB_TEST_INC} + test ) # we need to build https://github.com/ThrowTheSwitch/unity diff --git a/port/platform/zephyr/README.md b/port/platform/zephyr/README.md index 7a93e2df9..685a5909a 100644 --- a/port/platform/zephyr/README.md +++ b/port/platform/zephyr/README.md @@ -12,6 +12,7 @@ Note: the directory structure here differs from that in the other platform direc - [runner](runner): contains the test application configuration and build files for the Nordic MCUs supported on the Zephyr platform. - [runner_linux](runner_linux): contains the test application configuration and build files for Linux/Posix on the Zephyr platform. - [boards](boards): contains custom u-blox boards that are not \[yet\] in the Zephyr repo. +- [test](test): contains tests that use Zephyr application APIs to check out the integration of `ubxlib` into Zephyr, e.g. at PPP level. # SDK Installation (NRF Connect) `ubxlib` is tested with the version of Zephyr that comes with `nRFConnect SDK version 2.2.0` which is the recommended version; it is intended to build with all versions nRFConnect SDK from 1.6.1 up til 2.2.0. diff --git a/port/platform/zephyr/test/u_zephyr_ppp_test.c b/port/platform/zephyr/test/u_zephyr_ppp_test.c new file mode 100644 index 000000000..0c6948e6e --- /dev/null +++ b/port/platform/zephyr/test/u_zephyr_ppp_test.c @@ -0,0 +1,199 @@ +/* + * Copyright 2019-2023 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 that use Zephyr's native sockets API to test the + * PPP-level integration of ubxlib into Zephyr. These tests should + * pass on Zephyr 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. + * + * 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 + +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. +#include "stdbool.h" + +#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.h" +#include "u_port_os.h" +#include "u_port_heap.h" +#include "u_port_debug.h" + +#include "u_test_util_resource_check.h" + +#include "u_network.h" // In order to provide a comms path +#include "u_network_test_shared_cfg.h" + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/** The string to put at the start of all prints from this test. + */ +#define U_TEST_PREFIX "U_ZEPHYR_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__) + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * 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(uNetworkTestHasSock); + 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; +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: TESTS + * -------------------------------------------------------------- */ + +/** Basic TCP test. + */ +U_PORT_TEST_FUNCTION("[zephyrSock]", "zephyrSockTcp") +{ + uNetworkTestList_t *pList; + int32_t resourceCount; + uDeviceHandle_t devHandle; + + // Whatever called us likely initialised the + // port so deinitialise it here to obtain the + // correct initial heap size + 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 + for (uNetworkTestList_t *pTmp = pList; pTmp != NULL; pTmp = pTmp->pNext) { + devHandle = *pTmp->pDevHandle; + + U_TEST_PRINT_LINE("doing basic TCP test on %s.", + gpUNetworkTestTypeName[pTmp->networkType]); + + // TODO + (void) devHandle; + } + + // 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("[zephyrSock]", "zephyrSockCleanUp") +{ + // 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(); + uPortDeinit(); + // Printed for information: asserting happens in the postamble + uTestUtilResourceCheck(U_TEST_PREFIX, NULL, true); +} + +// End of file diff --git a/port/u_port_ppp_default.c b/port/u_port_ppp_default.c new file mode 100644 index 000000000..dccb5a8ea --- /dev/null +++ b/port/u_port_ppp_default.c @@ -0,0 +1,125 @@ +/* + * Copyright 2019-2023 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 Default implementations of uPortPppAttach(), uPortPppConnect(), + * uPortPppDisconnect() and uPortPppDetach() which simply return + * #U_ERROR_COMMON_NOT_SUPPORTED. + */ + +#ifdef U_CFG_OVERRIDE +# include "u_cfg_override.h" // For a customer's configuration override +#endif + +/* ---------------------------------------------------------------- + * INCLUDE FILES + * -------------------------------------------------------------- */ + +#include "stddef.h" // NULL, size_t etc. +#include "stdint.h" // int32_t etc. +#include "stdbool.h" + +#include "u_compiler.h" // U_WEAK + +#include "u_error_common.h" + +#include "u_port_ppp.h" + +/* ---------------------------------------------------------------- + * COMPILE-TIME MACROS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * TYPES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * VARIABLES + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * STATIC FUNCTIONS + * -------------------------------------------------------------- */ + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS: WORKAROUND FOR LINKER ISSUE + * -------------------------------------------------------------- */ + +void uPortPppDefaultPrivateLink() +{ + //dummy +} + +/* ---------------------------------------------------------------- + * PUBLIC FUNCTIONS + * -------------------------------------------------------------- */ + +// Attach a PPP interface to the bottom of the IP stack of a platform. +U_WEAK int32_t uPortPppAttach(void *pDevHandle, + uPortPppConnectCallback_t *pConnectCallback, + uPortPppDisconnectCallback_t *pDisconnectCallback, + uPortPppTransmitCallback_t *pTransmitCallback) +{ + (void) pDevHandle; + (void) pConnectCallback; + (void) pDisconnectCallback; + (void) pTransmitCallback; + return (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; +} + +// Bring a previously attached PPP interface up. +U_WEAK int32_t uPortPppConnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress, + uSockIpAddress_t *pDnsIpAddressPrimary, + uSockIpAddress_t *pDnsIpAddressSecondary, + const char *pUsername, + const char *pPassword, + uPortPppAuthenticationMode_t authenticationMode) +{ + (void) pDevHandle; + (void) pIpAddress; + (void) pDnsIpAddressPrimary; + (void) pDnsIpAddressSecondary; + (void) pUsername; + (void) pPassword; + (void) authenticationMode; + return (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; +} + +// Reconnect a PPP interface after it was lost. +U_WEAK int32_t uPortPppReconnect(void *pDevHandle, + uSockIpAddress_t *pIpAddress) +{ + (void) pDevHandle; + (void) pIpAddress; + return (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; +} + +// Take a previously attached PPP interface down. +U_WEAK int32_t uPortPppDisconnect(void *pDevHandle) +{ + (void) pDevHandle; + return (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; +} + +// Detach a PPP interface from the bottom of a platform's IP stack. +U_WEAK int32_t uPortPppDetach(void *pDevHandle) +{ + (void) pDevHandle; + return (int32_t) U_ERROR_COMMON_NOT_SUPPORTED; +} + +// End of file diff --git a/port/ubxlib.cmake b/port/ubxlib.cmake index b4e9fd61d..22306ced1 100644 --- a/port/ubxlib.cmake +++ b/port/ubxlib.cmake @@ -151,6 +151,9 @@ list(APPEND UBXLIB_SRC ${UBXLIB_BASE}/port/u_port_timezone.c) # Default uPortXxxResource implementation list(APPEND UBXLIB_SRC ${UBXLIB_BASE}/port/u_port_resource.c) +# Default uPortPppAttach()/uPortPppDetach() implementation +list(APPEND UBXLIB_SRC ${UBXLIB_BASE}/port/u_port_ppp_default.c) + # Optional features # short range diff --git a/port/ubxlib.mk b/port/ubxlib.mk index 7be58cda4..1f1427b1d 100644 --- a/port/ubxlib.mk +++ b/port/ubxlib.mk @@ -70,6 +70,9 @@ UBXLIB_SRC += ${UBXLIB_BASE}/port/u_port_timezone.c # Default uPortXxxResource implementation UBXLIB_SRC += ${UBXLIB_BASE}/port/u_port_resource.c +# Default uPortPppAttach()/uPortPppDetach() implementation +UBXLIB_SRC += ${UBXLIB_BASE}/port/u_port_ppp_default.c + # Optional short range related files and directories ifneq ($(filter short_range,$(UBXLIB_FEATURES)),) UBXLIB_MODULE_DIRS += \