diff --git a/boards/shields/swir_hl78xx_ev_kit/Kconfig.shield b/boards/shields/swir_hl78xx_ev_kit/Kconfig.shield new file mode 100644 index 0000000000000..e5c5d711b3426 --- /dev/null +++ b/boards/shields/swir_hl78xx_ev_kit/Kconfig.shield @@ -0,0 +1,5 @@ +# Copyright (c) 2025 Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +config SHIELD_SWIR_HL78XX_EV_KIT + def_bool $(shields_list_contains,swir_hl78xx_ev_kit) diff --git a/boards/shields/swir_hl78xx_ev_kit/doc/img/SW-Dev-RC76.3.webp b/boards/shields/swir_hl78xx_ev_kit/doc/img/SW-Dev-RC76.3.webp new file mode 100644 index 0000000000000..f2f339ed522d6 Binary files /dev/null and b/boards/shields/swir_hl78xx_ev_kit/doc/img/SW-Dev-RC76.3.webp differ diff --git a/boards/shields/swir_hl78xx_ev_kit/doc/index.rst b/boards/shields/swir_hl78xx_ev_kit/doc/index.rst new file mode 100644 index 0000000000000..cc6a9ce761f67 --- /dev/null +++ b/boards/shields/swir_hl78xx_ev_kit/doc/index.rst @@ -0,0 +1,79 @@ +.. _swir_hl78xx_ev_kit: + +HL/RC Module Evaluation Kit Shield +################################## + +Overview +******** + +Welcome to the HL78 module getting started guide. +This guide will help you set up the evaluation kit (eval kit) +for sending AT commands to the HL78 module and initiating data transmission. + +.. figure:: img/SW-Dev-RC76.3.webp + :align: center + :alt: HL/RC Module Evaluation Kit Shield Shield + + HL/RC Module Evaluation Kit Shield Shield (Credit: Sierrra Wireless) + +More information about the shield can be found at the `HL/RC Module Evaluation Kit Shield guide website`_. + +Pins Assignment of HL/RC Module Evaluation Kit Shield Shield +============================================================ ++--------------------------+----------------------------------------------------------+ +| Shield Connector Pin | Function | ++==========================+==========================================================+ +| CN403 alias | UART 1 (with CTS and RTS pins) | ++--------------------------+----------------------------------------------------------+ +| CN303 alias | SPI / UART 3 | ++--------------------------+----------------------------------------------------------+ +| CN1000 alias | GPIO Test Pins | ++--------------------------+----------------------------------------------------------+ +| GPIO6 CN1000_3 | LOW POWER MONITORING | ++--------------------------+----------------------------------------------------------+ +| VGPIO alias | Indirect indicator of hibernate mode entry/exit | ++--------------------------+----------------------------------------------------------+ +| RESET CN1000_12 | RESET SIGNAL | ++--------------------------+----------------------------------------------------------+ +| WAKE-UP CN1000_8 | SPI / UART 3 | ++--------------------------+----------------------------------------------------------+ + +Please refer to the website for more information about HL/RC Module Evaluation Kit Shield Shield setup. +.. _HL/RC Module Evaluation Kit Shield guide website: + +Checking Your Basic Configurations in PuTTY +=========================================== +Before trying to set up a wired connection between the board and a host MCU, +it's a good idea to first go through this list of basic AT commands over a +USB COM port on a PC. For reference, you can find all the AT commands for the +HL78xx modules in the Source. + +Requirements +************ + +This shield can be used with any boards which provides a configuration for +header connectors and defines node aliases for UART, SPI and USB interfaces (see +:ref:`shields` for more details). + +Programming +*********** + +Set ``--shield swir_hl78xx_ev_kit`` when you invoke ``west build``. For +example: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/modem/hello_hl78xx + :board: st/nucleo_u575zi_q + :shield: swir_hl78xx_ev_kit + :goals: build + +References +********** + +.. target-notes:: + +.. _HL/RC Module Evaluation Kit Shield guide website: + https://source.sierrawireless.com/resources/airprime/development_kits/hl78xx-hl7900-development-kit-guide/ + +.. _HL/RC Module Evaluation Kit Shield specification website: + https://info.sierrawireless.com/iot-modules-evaluation-kit#guide-for-the-hl78-series-evaluation-kit diff --git a/boards/shields/swir_hl78xx_ev_kit/shield.yml b/boards/shields/swir_hl78xx_ev_kit/shield.yml new file mode 100644 index 0000000000000..536f2bc030754 --- /dev/null +++ b/boards/shields/swir_hl78xx_ev_kit/shield.yml @@ -0,0 +1,6 @@ +shield: + name: swir_hl78xx_ev_kit + full_name: Sierra Wireless HL/RC Module Evaluation Kit + vendor: Sierra Wireless + supported_features: + - modem diff --git a/boards/shields/swir_hl78xx_ev_kit/swir_hl78xx_ev_kit.overlay b/boards/shields/swir_hl78xx_ev_kit/swir_hl78xx_ev_kit.overlay new file mode 100644 index 0000000000000..f2e7a75ae658f --- /dev/null +++ b/boards/shields/swir_hl78xx_ev_kit/swir_hl78xx_ev_kit.overlay @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + modem-uart = &usart2; + modem = &modem; + gnss = &gnss; + }; +}; + +&usart2 { + pinctrl-0 = <&usart2_tx_pa2 &usart2_rx_pa3 &usart2_rts_pd4 &usart2_cts_pd3>; + pinctrl-1 = <&analog_pa2 &analog_pa3 &analog_pd4 &analog_pd3>; + dmas = <&gpdma1 0 27 STM32_DMA_PERIPH_TX + &gpdma1 1 26 STM32_DMA_PERIPH_RX>; + dma-names = "tx", "rx"; + pinctrl-names = "default", "sleep"; + current-speed = <115200>; + status = "okay"; + hw-flow-control; + modem: hl_modem { + compatible = "swir,hl7812"; + status = "okay"; + mdm-reset-gpios = <&gpiod 5 (GPIO_ACTIVE_LOW)>; + socket_offload: socket_offload { + compatible = "swir,hl7812-offload"; + status = "okay"; + /* optional properties for future: */ + max-data-length = <512>; + }; + gnss: hl_gnss { + compatible = "swir,hl7812-gnss"; + pps-mode = "GNSS_PPS_MODE_DISABLED"; + fix-rate = <1000>; + status = "okay"; + }; + }; + +}; diff --git a/drivers/modem/CMakeLists.txt b/drivers/modem/CMakeLists.txt index adc6614dc7fe5..4891e11cc333c 100644 --- a/drivers/modem/CMakeLists.txt +++ b/drivers/modem/CMakeLists.txt @@ -35,6 +35,8 @@ if (CONFIG_MODEM_SIM7080) zephyr_library_sources(simcom-sim7080.c) endif() +add_subdirectory_ifdef(CONFIG_MODEM_HL78XX hl78xx) + zephyr_library_sources_ifdef(CONFIG_MODEM_CELLULAR modem_cellular.c) zephyr_library_sources_ifdef(CONFIG_MODEM_AT_USER_PIPE modem_at_user_pipe.c) zephyr_library_sources_ifdef(CONFIG_MODEM_AT_SHELL modem_at_shell.c) diff --git a/drivers/modem/Kconfig b/drivers/modem/Kconfig index b8e66ef427482..833bebff31784 100644 --- a/drivers/modem/Kconfig +++ b/drivers/modem/Kconfig @@ -192,7 +192,7 @@ source "drivers/modem/Kconfig.quectel-bg9x" source "drivers/modem/Kconfig.wncm14a2a" source "drivers/modem/Kconfig.cellular" source "drivers/modem/Kconfig.at_shell" - +source "drivers/modem/hl78xx/Kconfig.hl78xx" source "drivers/modem/Kconfig.hl7800" source "drivers/modem/Kconfig.simcom-sim7080" diff --git a/drivers/modem/hl78xx/CMakeLists.txt b/drivers/modem/hl78xx/CMakeLists.txt new file mode 100644 index 0000000000000..1320925476238 --- /dev/null +++ b/drivers/modem/hl78xx/CMakeLists.txt @@ -0,0 +1,28 @@ +# +# Copyright (c) 2025 Netfeasa Ltd. +# +# SPDX-License-Identifier: Apache-2.0 +# +zephyr_library() + +zephyr_library_sources( + hl78xx.c + hl78xx_sockets.c + hl78xx_cfg.c + hl78xx_chat.c + hl78xx_apis.c +) + +add_subdirectory_ifdef(CONFIG_HL78XX_EVT_MONITOR hl78xx_evt_monitor) + +zephyr_library_include_directories( + ./ + # IP headers + ${ZEPHYR_BASE}/subsys/net/ip + ${ZEPHYR_BASE}/subsys/net/lib/sockets +) + +zephyr_library_include_directories_ifdef( + CONFIG_NET_SOCKETS_SOCKOPT_TLS + ${ZEPHYR_BASE}/subsys/net/lib/tls_credentials +) diff --git a/drivers/modem/hl78xx/Kconfig.hl78xx b/drivers/modem/hl78xx/Kconfig.hl78xx new file mode 100644 index 0000000000000..bed24a07276fa --- /dev/null +++ b/drivers/modem/hl78xx/Kconfig.hl78xx @@ -0,0 +1,822 @@ +# Sierra Wireless HL78XX driver driver options + +# Copyright (c) 2025 Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +config MODEM_HL78XX + bool "HL78XX modem driver" + select MODEM_MODULES + select MODEM_CHAT + select MODEM_PIPE + select MODEM_PIPELINK + select MODEM_BACKEND_UART + select RING_BUFFER + select MODEM_SOCKET + select NET_OFFLOAD + select MODEM_CONTEXT + select EXPERIMENTAL + depends on !MODEM_CELLULAR + imply GPIO + help + Choose this setting to enable Sierra Wireless HL78XX driver LTE-CatM1/NB-IoT modem + driver. + +if MODEM_HL78XX + +choice MODEM_HL78XX_VARIANT + bool "Sierra Wireless hl78xx variant selection" + default MODEM_HL78XX_12 if DT_HAS_SWIR_HL7812_ENABLED + default MODEM_HL78XX_00 if DT_HAS_SWIR_HL7800_ENABLED + default MODEM_HL78XX_AUTODETECT_VARIANT + +config MODEM_HL78XX_12 + bool "Sierra Wireless hl7812" + help + Support for hl7812 modem + +config MODEM_HL78XX_00 + bool "Sierra Wireless hl7800" + help + Support for hl7800 modem + +config MODEM_HL78XX_AUTODETECT_VARIANT + bool "detect automatically" + help + Automatic detection of modem variant (HL7812 or HL7800) + +endchoice + +if MODEM_HL78XX_12 + +config MODEM_HL78XX_12_FW_R6 + bool "Modem firmware R6" + help + Only for HL7812, enable this setting to use NBNTN rat. + This is required for NBNTN mode. + NBNTN mode is supported with R6 firmware. + +endif # MODEM_HL78XX_12 + +config MODEM_HL78XX_UART_BUFFER_SIZES + int "The UART receive and transmit buffer sizes in bytes." + default 512 + +config MODEM_HL78XX_CHAT_BUFFER_SIZES + int "The size of the buffers used for the chat scripts in bytes." + default 512 + +config MODEM_HL78XX_USER_PIPE_BUFFER_SIZES + int "The size of the buffers used for each user pipe in bytes." + default 512 + +config MODEM_HL78XX_RECV_BUF_CNT + int "The number of allocated network buffers" + default 30 + +config MODEM_HL78XX_RECV_BUF_SIZE + int "The size of the network buffers in bytes" + default 128 + +config MODEM_HL78XX_RX_WORKQ_STACK_SIZE + int "Stack size for the Sierra Wireless HL78XX driver modem driver work queue" + default 2048 + help + This stack is used by the work queue to pass off net_pkt data + to the rest of the network stack, letting the rx thread continue + processing data. + +choice MODEM_HL78XX_ADDRESS_FAMILY + prompt "IP Address family" + default MODEM_HL78XX_ADDRESS_FAMILY_IPV4V6 + help + The address family for IP connections. + +config MODEM_HL78XX_ADDRESS_FAMILY_IPV4 + bool "IPv4" + +config MODEM_HL78XX_ADDRESS_FAMILY_IPV6 + bool "IPv6" + +config MODEM_HL78XX_ADDRESS_FAMILY_IPV4V6 + bool "IPv4v6" + +endchoice + +choice MODEM_HL78XX_BOOT_MODE + prompt "Modem Boot Type" + default MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE + help + Set Modem Functionality see, AT+CFUN + Consider reset conditions after settings, second parameter of cfun + 0 — Do not reset the MT before setting it to power level. + 1 — Reset the MT before setting it to power level. + +config MODEM_HL78XX_BOOT_IN_MINIMUM_FUNCTIONAL_MODE + bool "MINIMUM FUNCTIONAL MODE" + help + - AT+CFUN = 0,0 + — Minimum functionality, SIM powered off + - Consider reset conditions second parameter of cfun + +config MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE + bool "FULL FUNCTIONAL MODE" + help + - AT+CFUN = 1,0 + - Full functionality, starts cellular searching + - Consider reset conditions after settings, second parameter of cfun + +config MODEM_HL78XX_BOOT_IN_AIRPLANE_MODE + bool "AIRPLANE MODE" + help + - AT+CFUN = 4,0 + - Disable radio transmit and receive; SIM powered on. (i.e. "Airplane + Mode") + - Consider reset conditions after settings, second parameter of cfun +endchoice + +if MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE + +config MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING + bool "WAIT FOR ROAMING" + help + Keep the device in boot mode until have +CREG/+CEREG: 1(normal) or 5(roaming) +endif # MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE + +config MODEM_HL78XX_PERIODIC_SCRIPT_MS + int "Periodic script interval in milliseconds" + default 2000 + +choice MODEM_HL78XX_APN_SOURCE + prompt "APN SOURCE" + default MODEM_HL78XX_APN_SOURCE_NETWORK + help + Select the source for automatically detecting the APN. + You can choose between IMSI (International Mobile Subscriber Identity) + or ICCID (Integrated Circuit Card Identifier) as the reference for APN association. + +config MODEM_HL78XX_APN_SOURCE_ICCID + bool "CCID Associated APN" + help + - AT+CCID + - Multiple ICCID and APN combinations can be stored in APN PROFILE configuration + see MODEM_HL78XX_APN_PROFILES + +config MODEM_HL78XX_APN_SOURCE_IMSI + bool "CIMI Associated APN" + help + - AT+CIMI + - Multiple CIMI and APN combinations can be stored in APN PROFILE configuration + see MODEM_HL78XX_APN_PROFILES + +config MODEM_HL78XX_APN_SOURCE_KCONFIG + bool "User defined Single APN" + help + - Use the APN defined in MODEM_HL78XX_APN + - Supports only one APN + +config MODEM_HL78XX_APN_SOURCE_NETWORK + bool "Network Provided APN" + help + - AT+CGCONTRDP=1 + - Use the APN provided by the network +endchoice + +if MODEM_HL78XX_APN_SOURCE_KCONFIG + +config MODEM_HL78XX_APN + string "APN for establishing network connection" + default "xxxxxxxx" + help + This setting is used in the AT+CGDCONT command to set the APN name + for the network connection context. This value is specific to + the network provider and has to be changed. + +endif # MODEM_HL78XX_APN_SOURCE_KCONFIG + +if MODEM_HL78XX_APN_SOURCE_ICCID || MODEM_HL78XX_APN_SOURCE_IMSI + +config MODEM_HL78XX_APN_PROFILES + string "list of profiles to search when autodetecting APN" + default "hologram=23450, wm=20601, int=29505" + help + Set a comma separated list of profiles, each containing of: + = ... + = ... + +endif # MODEM_HL78XX_APN_SOURCE_ICCID || MODEM_HL78XX_APN_SOURCE_IMSI + +config MODEM_HL78XX_RSSI_WORK + bool "RSSI polling work" + default y + help + Sierra Wireless HL78XX driver device is configured to poll for RSSI + +config MODEM_HL78XX_RSSI_WORK_PERIOD + int "Configure RSSI WORK polling frequency" + depends on MODEM_HL78XX_RSSI_WORK + default 30 + help + This settings is used to configure the period of RSSI polling + +config MODEM_HL78XX_AUTORAT + bool "automatic RAT switching and set the PRL profiles" + default y + help + AT+KSRAT is provided for backwards compatibility only. AT+KSELACQ is recommended for RAT switching. + (See RAT Switching Application Note (Doc# 2174296) for details.) + +if MODEM_HL78XX_AUTORAT + +config MODEM_HL78XX_AUTORAT_OVER_WRITE_PRL + bool "Overwrite PRL profiles always at boot" + help + If you enable this option, the PRL profiles on the modem will be overwritten by the app + with the PRL profile values at boot everytime. + +config MODEM_HL78XX_AUTORAT_PRL_PROFILES + string "Configure Preferred Radio Access Technology List" + default "1,2,3" if MODEM_HL78XX_12 + default "1,2,1" if MODEM_HL78XX_00 + help + AT+KSELACQ=0,1,2,3 , MODEM HL7812,CAT-M1, NB-IoT, GSM + AT+KSELACQ=0,1,2,1 , MODEM HL7800,CAT-M1, NB-IoT, CAT-M1 + +config MODEM_HL78XX_AUTORAT_NB_BAND_CFG + string "NB-IoT band configuration (comma-separated list)" + default "1,2,3,4,5,8,12,13,20,28" + help + Specify which LTE bands (e.g., 8,20,28) to use for NB-IoT when using Auto RAT. + This string is parsed at runtime or build-time. + +config MODEM_HL78XX_AUTORAT_M1_BAND_CFG + string "Cat-M1 band configuration (comma-separated list)" + default "1,2,3,4,5,8,12,13,20,28" + help + Specify which LTE bands (e.g., 3,5,12) to use for Cat-M1 when using Auto RAT. + +endif # MODEM_HL78XX_AUTORAT + +choice MODEM_HL78XX_RAT + bool "Radio Access Technology Mode" + default MODEM_HL78XX_RAT_M1 + depends on !MODEM_HL78XX_AUTORAT + +config MODEM_HL78XX_RAT_M1 + bool "LTE-M1" + help + Enable LTE Cat-M1 mode during modem init. + In the Read response, '0' indicates CAT-M1. + +config MODEM_HL78XX_RAT_NB1 + bool "NB-IoT" + help + Enable LTE Cat-NB1 mode during modem init. + 1 — NB-IoT (HL78XX/HL7802/HL7810/HL7845/HL7812 only) + +config MODEM_HL78XX_RAT_GSM + bool "GSM" + depends on MODEM_HL78XX_12 + help + Enable GSM mode during modem init. + 2 — GSM (for HL7802/HL7812 only) + +config MODEM_HL78XX_RAT_NBNTN + bool "NB-NTN" + depends on MODEM_HL78XX_12_FW_R6 + help + Enable NBNTN mode during modem init. + 3 — NBNTN (for HL7810/HL7812 only), It does not support = 1 + +endchoice + +menuconfig MODEM_HL78XX_CONFIGURE_BANDS + bool "Configure modem bands" + depends on !MODEM_HL78XX_AUTORAT + default y if !MODEM_HL78XX_AUTORAT + help + Choose this setting to configure which LTE bands the + HL78XX modem should use at boot time. + +if MODEM_HL78XX_CONFIGURE_BANDS +if !MODEM_HL78XX_RAT_NBNTN +config MODEM_HL78XX_BAND_1 + bool "Band 1 (2000MHz)" + default y + help + Enable Band 1 (2000MHz) + +config MODEM_HL78XX_BAND_2 + bool "Band 2 (1900MHz)" + default y + help + Enable Band 2 (1900MHz) + +config MODEM_HL78XX_BAND_3 + bool "Band 3 (1800MHz)" + default y + help + Enable Band 3 (1800MHz) + +config MODEM_HL78XX_BAND_4 + bool "Band 4 (1700MHz)" + default y + help + Enable Band 4 (1700MHz) + +config MODEM_HL78XX_BAND_5 + bool "Band 5 (850MHz)" + default y + help + Enable Band 5 (850MHz) + +config MODEM_HL78XX_BAND_8 + bool "Band 8 (900MHz)" + default y + help + Enable Band 8 (900MHz) + +config MODEM_HL78XX_BAND_9 + bool "Band 9 (1900MHz)" + help + Enable Band 9 (1900MHz) + +config MODEM_HL78XX_BAND_10 + bool "Band 10 (2100MHz)" + help + Enable Band 10 (2100MHz) + +config MODEM_HL78XX_BAND_12 + bool "Band 12 (700MHz)" + default y + help + Enable Band 12 (700MHz) + +config MODEM_HL78XX_BAND_13 + bool "Band 13 (700MHz)" + default y + help + Enable Band 13 (700MHz) + +config MODEM_HL78XX_BAND_17 + bool "Band 17 (700MHz)" + help + Enable Band 17 (700MHz) + +config MODEM_HL78XX_BAND_18 + bool "Band 18 (800MHz)" + help + Enable Band 18 (800MHz) + +config MODEM_HL78XX_BAND_19 + bool "Band 19 (800MHz)" + help + Enable Band 19 (800MHz) + +config MODEM_HL78XX_BAND_20 + bool "Band 20 (800MHz)" + default y + help + Enable Band 20 (800MHz) +endif # !MODEM_HL78XX_RAT_NBNTN +config MODEM_HL78XX_BAND_23 + bool "Band 23 (2000MHz)" + default y if MODEM_HL78XX_RAT_NBNTN + help + Enable Band 23 (2000MHz) +if !MODEM_HL78XX_RAT_NBNTN +config MODEM_HL78XX_BAND_25 + bool "Band 25 (1900MHz)" + help + Enable Band 25 (1900MHz) + +config MODEM_HL78XX_BAND_26 + bool "Band 26 (800MHz)" + help + Enable Band 26 (800MHz) + +config MODEM_HL78XX_BAND_27 + bool "Band 27 (800MHz)" + help + Enable Band 27 (800MHz) + +config MODEM_HL78XX_BAND_28 + bool "Band 28 (700MHz)" + default y + help + Enable Band 28 (700MHz) + +config MODEM_HL78XX_BAND_31 + bool "Band 31 (450MHz)" + help + Enable Band 31 (450MHz) + +config MODEM_HL78XX_BAND_66 + bool "Band 66 (1800MHz)" + help + Enable Band 66 (1800MHz) + +config MODEM_HL78XX_BAND_72 + bool "Band 72 (450MHz)" + help + Enable Band 72 (450MHz) + +config MODEM_HL78XX_BAND_73 + bool "Band 73 (450MHz)" + help + Enable Band 73 (450MHz) + +config MODEM_HL78XX_BAND_85 + bool "Band 85 (700MHz)" + help + Enable Band 85 (700MHz) + +config MODEM_HL78XX_BAND_87 + bool "Band 87 (410MHz)" + help + Enable Band 87 (410MHz) + +config MODEM_HL78XX_BAND_88 + bool "Band 88 (410MHz)" + help + Enable Band 88 (410MHz) + +config MODEM_HL78XX_BAND_106 + bool "Band 106 (900MHz)" + help + Enable Band 106 (900MHz) + +config MODEM_HL78XX_BAND_107 + bool "Band 107 (1800MHz)" + help + Enable Band 107 (1800MHz) +endif # !MODEM_HL78XX_RAT_NBNTN +config MODEM_HL78XX_BAND_255 + bool "Band 255 (1500MHz)" + default y if MODEM_HL78XX_RAT_NBNTN + help + Enable Band 255 (1500MHz) + +config MODEM_HL78XX_BAND_256 + bool "Band 256 (2000MHz)" + default y if MODEM_HL78XX_RAT_NBNTN + help + Enable Band 256 (2000MHz) + +endif # MODEM_HL78XX_CONFIGURE_BAND + +# NB-IoT NTN Position Configuration +if MODEM_HL78XX_RAT_NBNTN +menuconfig MODEM_HL78XX_NBNTN_POSITIONING + bool "NB-IoT NTN Position Configuration" + depends on !MODEM_HL78XX_AUTORAT + default y if !MODEM_HL78XX_AUTORAT + help + Choose this setting to configure NB-IoT NTN Positioning parameters. + Only applicable if NB-NTN mode is selected (MODEM_HL78XX_RAT_NBNTN). + +if MODEM_HL78XX_NBNTN_POSITIONING + +choice + prompt "Position Source" + default NTN_POSITION_SOURCE_IGNSS + help + Select the source of UE (User Equipment) position for NTN TA calculation. + IGNSS: Use HL781x internal GNSS to acquire position automatically. + MANUAL: Manually enter UE position using +KNTNCMD AT command. + +config NTN_POSITION_SOURCE_IGNSS + bool "IGNSS (internal GNSS)" + help + Use HL781x internal GNSS to acquire position automatically. + +config NTN_POSITION_SOURCE_MANUAL + bool "MANUAL (manual entry)" + help + Manually enter UE position using +KNTNCMD AT command. + +endchoice + +choice + prompt "Mobility Type" + default NTN_MOBILITY_TYPE_STATIC + help + Specify the UE's mobility type. + STATIC: Position remains fixed (within 600 meters of initial fix). + DYNAMIC: Position may change, requires update for each network operation. + +config NTN_MOBILITY_TYPE_STATIC + bool "STATIC (fixed position)" + help + Position remains fixed (within 600 meters of initial fix). + +config NTN_MOBILITY_TYPE_DYNAMIC + bool "DYNAMIC (moving position)" + help + Position may change, requires update for each network operation. + +endchoice + +config NTN_STATIC_THRESHOLD + int "Static Mode Threshold (meters)" + default 600 + help + Distance between current position and initial fix that determines static/dynamic mode. + If position variation exceeds this threshold, dynamic mode is recommended. + For documentation/reference only. + +config NTN_DYNAMIC_POSREQ_UPDATE + bool "Require position update on POSREQ in Dynamic Mode" + default y + help + If enabled, the UE position must be updated after receiving unsolicited +KNTNEV: + 'POSREQ' indication, before any outgoing network operation in dynamic mode. +endif # MODEM_HL78XX_NBNTN_POSITIONING +endif # MODEM_HL78XX_RAT_NBNTN + +config MODEM_HL78XX_LOW_POWER_MODE + bool "Low power modes" + help + Choose this setting to enable a low power mode for the HL78XX modem + +if MODEM_HL78XX_LOW_POWER_MODE + +config MODEM_HL78XX_EDRX + bool "eDRX" + help + Enable LTE eDRX + +config MODEM_HL78XX_PSM + bool "PSM" + default y + help + Enable Power Save Mode (PSM) + +if MODEM_HL78XX_EDRX + +config MODEM_HL78XX_EDRX_VALUE + string "Requested eDRX timer" + default "0101" + help + Half a byte in a 4-bit format. The eDRX value refers to bit 4 to 1 + of octet 3 of the Extended DRX parameters information element. + Default value is 81.92 seconds. + +endif # MODEM_HL78XX_EDRX + +if MODEM_HL78XX_PSM + +config MODEM_HL78XX_PSM_PERIODIC_TAU + string "Requested extended periodic TAU timer" + default "10000010" + help + Requested extended periodic TAU (tracking area update) value (T3412) + to be allocated to the UE in E-UTRAN. One byte in an 8-bit format. + Default value is 1 minute. + +config MODEM_HL78XX_PSM_ACTIVE_TIME + string "Requested active time" + default "00001111" + help + Requested Active Time value (T3324) to be allocated to the UE. + One byte in an 8-bit format. Default value is 30 seconds. + +endif # MODEM_HL78XX_PSM + +choice MODEM_DEFAULT_SLEEP_LEVEL + prompt "Default Sleep Level" + default MODEM_HL78XX_SLEEP_LEVEL_HIBERNATE + help + The application can override this setting + +config MODEM_HL78XX_SLEEP_LEVEL_HIBERNATE + bool "Hibernate" + help + Lowest power consumption + IO state not retained + Application subsystem OFF + +config MODEM_HL78XX_SLEEP_LEVEL_LITE_HIBERNATE + bool "Lite Hibernate" + help + IO state retained + Application subsystem OFF + +config MODEM_HL78XX_SLEEP_LEVEL_SLEEP + bool "Sleep" + help + Highest power consumption of modem sleep states + IO state retained + Application subsystem ON + Allows sockets to remain open + +endchoice + +config MODEM_HL78XX_SLEEP_DELAY_AFTER_REBOOT + int "Delay in seconds before sleep after reboot" + default 10 + +endif # MODEM_HL78XX_LOW_POWER_MODE + +choice MODEM_HL78XX_NETWORK_REG_STATUS_REPORT_CFG + prompt "Network Registration Status Report Configuration" + default MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM_AND_CAUSE + help + · 0 — Disable network registration unsolicited result code. + · 1 — Enable network registration unsolicited result code +CEREG: + · 2 — Enable network registration and location information unsolicited result + code: + +CEREG: [,[],[],[]] + · 3 — Enable network registration, location information and EMM cause value + information unsolicited result code: + +CEREG: [,[],[],[][,, ]] + · 4 — For a UE that wants to apply PSM, enable network registration and + location information unsolicited result code: + +CEREG: [,[],[],[][,,[,[],[]]]] + · 5 — For a UE that wants to apply PSM, enable network registration, location + information and EMM cause value information unsolicited result code: + +CEREG: [,[],[],[][,[],[][,[] []]]] + +config MODEM_HL78XX_DISABLE_NETWORK_STATUS_URC_REPORT + bool "Disable network status URC report" + help + Disable network registration unsolicited result code. + +config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT + bool "Network status URC report" + help + Enable network registration unsolicited result code +CEREG: + +config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION + bool "Network status URC report with location" + help + Enable network registration and location information unsolicited result + +CEREG: [,[],[],[]] + +config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION_AND_CAUSE + bool "Network status URC report with location and cause" + help + Enable network registration, location information and EMM cause value + information unsolicited result code: + +CEREG: [,[],[],[][,, ]] + +config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM + bool "Network status URC report with PSM" + help + For a UE that wants to apply PSM, enable network registration and + location information unsolicited result code: + +CEREG: [,[],[],[][,,[,[],[]]]] + +config MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM_AND_CAUSE + bool "Network status URC report with PSM and cause" + help + For a UE that wants to apply PSM, enable network registration, location + information and EMM cause value information unsolicited result code: + +CEREG: [,[],[],[][,[],[][,[] []]]] + +endchoice + +config MODEM_HL78XX_NETWORK_REG_STATUS_REPORT_CFG_CODE + string + default "5" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM_AND_CAUSE + default "4" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_PSM + default "3" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION_AND_CAUSE + default "2" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT_WITH_LOCATION + default "1" if MODEM_HL78XX_ENABLE_NETWORK_STATUS_URC_REPORT + default "0" if MODEM_HL78XX_DISABLE_NETWORK_STATUS_URC_REPORT + help + This setting is used to configure the network registration status report + configuration code. It is used in the AT+CREG/CEREG command to set the network + registration status report configuration. + +config MODEM_MIN_ALLOWED_SIGNAL_STRENGTH + int "Minimum allowed RSRP signal strength (dBm)" + default -140 + range -140 0 + help + The average power received from a single Reference signal, + and Its typical range is around -44dbm (good) to -140dbm(bad). + Note: (Anything < - 115 dBm unusable/unreliable) + EXCELLENT_SIGNAL_STRENGTH + bool ">= -80(dBm)" + default -80 + range - 80 0 + GOOD_SIGNAL_STRENGTH + bool ">= -90(dBm)" + default -90 + range - 90 0 + MID_CELL_SIGNAL_STRENGTH + bool ">= -100(dBm)" + default -100 + range - 100 0 + CELL_EDGE_SIGNAL_STRENGTH + bool "<= -100(dBm)" + default -110 + range - 110 0 + POOR_SIGNAL_STRENGTH + bool ">= -140(dBm)" + default -140 + range - 140 0 + +config MODEM_HL78XX_ADVANCED_SOCKET_CONFIG + bool "Advanced socket configuration" + help + Enable advanced socket configuration options + +if MODEM_HL78XX_ADVANCED_SOCKET_CONFIG + +config MODEM_HL78XX_SOCKET_UDP_DISPLAY_DATA_URC + int "display data in URC" + default 0 + help + 0 — Do not display data in URC + 1 — Display data in URC automatically + 2 — Do not display data in URC and KUDPRCV command is required to dump + data. If there is no KUDPRCV command after rcv_timeout, the original data is + dropped and URC re-enabled. + +config MODEM_HL78XX_SOCKET_RESTORE_ON_BOOT + bool "Restore sockets on boot" + help + only the first session is restored + For HL780x, restore_on_boot is required to restore the first session across + eDRX/PSM hibernate cycles or reset. + • For HL781x/45, all sessions are maintained across eDRX/PSM hibernate cycles + independent of this configuration. It is only required for reset cases. + • For a restored client session (e.g. after a reset or exiting hibernation), +KTCPCNX + must be used to establish a connection before sending/receiving any data. + 0 — Do not restore sockets on boot + 1 — Restore sockets on boot + +endif # MODEM_HL78XX_ADVANCED_SOCKET_CONFIG + +config MODEM_HL78XX_NUM_SOCKETS + int "Maximum number of sockets" + default 6 + range 6 6 if !MODEM_HL78XX_ADVANCED_SOCKET_CONFIG + range 1 6 if MODEM_HL78XX_ADVANCED_SOCKET_CONFIG + help + Maximum number of sockets that can be opened at the same time + +config MODEM_HL78XX_SOCKETS_SOCKOPT_TLS + bool "TLS for sockets" + depends on NET_SOCKETS_SOCKOPT_TLS + help + This option enables TLS (Transport Layer Security) for sockets + on the HL78xx modem. + +config MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + bool "Verbose debug output in the HL78xx" + depends on MODEM_MODULES_LOG_LEVEL_DBG + help + Enabling this setting will turn on VERY heavy debugging from the + modem. Do NOT leave on for production. + This setting is depends on global log level debug. + +config MODEM_HL78XX_DEV_POWER_PULSE_DURATION + int "Duration of the power-on pulse in milliseconds." + default 1500 + help + Trigger a power-on sequence by setting a power on GPIO pin high + for a specific amount of time. + +config MODEM_HL78XX_DEV_RESET_PULSE_DURATION + int "Duration of the power-reset pulse in milliseconds." + default 100 + help + Trigger a power-reset sequence by setting a reset GPIO pin high + for a specific amount of time. + +config MODEM_HL78XX_DEV_STARTUP_TIME + int "Wait before assuming the device is ready." + default 1000 + help + The expected time (in milliseconds) the modem needs to fully power on + and become operational. + +config MODEM_HL78XX_DEV_SHUTDOWN_TIME + int "Wait before assuming the device is completely off." + default 1000 + help + The amount of time (in milliseconds) the system should wait for the modem + to fully shut down + +config MODEM_HL78XX_DEV_INIT_PRIORITY + int "Sierra Wireless HL78XX device driver init priority" + default 80 + help + Sierra Wireless HL78XX device driver initialization priority. + Do not mess with it unless you know what you are doing. + +config MODEM_HL78XX_OFFLOAD_INIT_PRIORITY + int "Sierra Wireless HL78XX offload driver init priority" + default 81 + help + Sierra Wireless HL78XX driver device driver initialization priority. + Do not mess with it unless you know what you are doing. + Make sure offload init priority higher than dev init priority + +rsource "hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor" + +endif # MODEM_HL78XX diff --git a/drivers/modem/hl78xx/hl78xx.c b/drivers/modem/hl78xx/hl78xx.c new file mode 100644 index 0000000000000..92542a447abf2 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx.c @@ -0,0 +1,1860 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "hl78xx.h" +#include "hl78xx_chat.h" +#include "hl78xx_cfg.h" + +#define MAX_SCRIPT_AT_CMD_RETRY 3 + +#define MDM_NODE DT_ALIAS(modem) +/* Check phandle target status for a specific phandle index */ +#define HAS_GPIO_IDX(node_id, prop, idx) DT_PROP_HAS_IDX(node_id, prop, idx) + +/* GPIO availability macros */ +#define HAS_RESET_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_reset_gpios, 0) +#define HAS_WAKE_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_wake_gpios, 0) +#define HAS_VGPIO_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_vgpio_gpios, 0) +#define HAS_UART_CTS_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_uart_cts_gpios, 0) +#define HAS_GPIO6_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_gpio6_gpios, 0) +#define HAS_PWR_ON_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_pwr_on_gpios, 0) +#define HAS_FAST_SHUTD_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_fast_shutd_gpios, 0) +#define HAS_UART_DSR_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_uart_dsr_gpios, 0) +#define HAS_UART_DTR_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_uart_dtr_gpios, 0) +#define HAS_GPIO8_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_gpio8_gpios, 0) +#define HAS_SIM_SWITCH_GPIO HAS_GPIO_IDX(MDM_NODE, mdm_sim_switch_gpios, 0) + +/* GPIO count macro */ +#define GPIO_CONFIG_LEN \ + (HAS_RESET_GPIO + HAS_WAKE_GPIO + HAS_VGPIO_GPIO + HAS_UART_CTS_GPIO + HAS_GPIO6_GPIO + \ + HAS_PWR_ON_GPIO + HAS_FAST_SHUTD_GPIO + HAS_UART_DSR_GPIO + HAS_UART_DTR_GPIO + \ + HAS_GPIO8_GPIO + HAS_SIM_SWITCH_GPIO) + +LOG_MODULE_REGISTER(hl78xx_dev, CONFIG_MODEM_LOG_LEVEL); +/* RX thread work queue */ +K_KERNEL_STACK_DEFINE(modem_workq_stack, CONFIG_MODEM_HL78XX_RX_WORKQ_STACK_SIZE); + +static struct k_work_q modem_workq; +hl78xx_evt_monitor_dispatcher_t event_dispatcher; + +static void hl78xx_event_handler(struct hl78xx_data *data, enum hl78xx_event evt); +static int hl78xx_on_idle_state_enter(struct hl78xx_data *data); + +struct hl78xx_state_handlers { + int (*on_enter)(struct hl78xx_data *data); + int (*on_leave)(struct hl78xx_data *data); + void (*on_event)(struct hl78xx_data *data, enum hl78xx_event evt); +}; + +/* Forward declare the table so functions earlier in this file can reference + * it. The table itself is defined later in the file (without 'static'). + */ +const static struct hl78xx_state_handlers hl78xx_state_table[]; +/** Dispatch an event to the registered event dispatcher, if any. + * + */ +static void event_dispatcher_dispatch(struct hl78xx_evt *notif) +{ + if (event_dispatcher != NULL) { + event_dispatcher(notif); + } +} +/* ------------------------------------------------------------------------- + * Utilities + * - small helpers and local utility functions + * ------------------------------------------------------------------------- + */ + +static const char *hl78xx_state_str(enum hl78xx_state state) +{ + switch (state) { + case MODEM_HL78XX_STATE_IDLE: + return "idle"; + case MODEM_HL78XX_STATE_RESET_PULSE: + return "reset pulse"; + case MODEM_HL78XX_STATE_POWER_ON_PULSE: + return "power pulse"; + case MODEM_HL78XX_STATE_AWAIT_POWER_ON: + return "await power on"; + case MODEM_HL78XX_STATE_SET_BAUDRATE: + return "set baudrate"; + case MODEM_HL78XX_STATE_RUN_INIT_SCRIPT: + return "run init script"; + case MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT: + return "init fail diagnostic script "; + case MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT: + return "run rat cfg script"; + case MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT: + return "run enable gprs script"; + case MODEM_HL78XX_STATE_AWAIT_REGISTERED: + return "await registered"; + case MODEM_HL78XX_STATE_CARRIER_ON: + return "carrier on"; + case MODEM_HL78XX_STATE_CARRIER_OFF: + return "carrier off"; + case MODEM_HL78XX_STATE_SIM_POWER_OFF: + return "sim power off"; + case MODEM_HL78XX_STATE_AIRPLANE: + return "airplane mode"; + case MODEM_HL78XX_STATE_INIT_POWER_OFF: + return "init power off"; + case MODEM_HL78XX_STATE_POWER_OFF_PULSE: + return "power off pulse"; + case MODEM_HL78XX_STATE_AWAIT_POWER_OFF: + return "await power off"; + default: + return "UNKNOWN state"; + } + + return ""; +} + +static const char *hl78xx_event_str(enum hl78xx_event event) +{ + switch (event) { + case MODEM_HL78XX_EVENT_RESUME: + return "resume"; + case MODEM_HL78XX_EVENT_SUSPEND: + return "suspend"; + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + return "script success"; + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + return "script failed"; + case MODEM_HL78XX_EVENT_SCRIPT_REQUIRE_RESTART: + return "script require restart"; + case MODEM_HL78XX_EVENT_TIMEOUT: + return "timeout"; + case MODEM_HL78XX_EVENT_REGISTERED: + return "registered"; + case MODEM_HL78XX_EVENT_DEREGISTERED: + return "deregistered"; + case MODEM_HL78XX_EVENT_BUS_OPENED: + return "bus opened"; + case MODEM_HL78XX_EVENT_BUS_CLOSED: + return "bus closed"; + case MODEM_HL78XX_EVENT_SOCKET_READY: + return "socket ready"; + default: + return "unknown event"; + } + + return ""; +} + +static bool hl78xx_gpio_is_enabled(const struct gpio_dt_spec *gpio) +{ + return (gpio->port != NULL); +} + +static void hl78xx_log_event(enum hl78xx_event evt) +{ + LOG_DBG("event %s", hl78xx_event_str(evt)); +} + +static void hl78xx_start_timer(struct hl78xx_data *data, k_timeout_t timeout) +{ + k_work_schedule(&data->timeout_work, timeout); +} + +static void hl78xx_stop_timer(struct hl78xx_data *data) +{ + k_work_cancel_delayable(&data->timeout_work); +} + +static void hl78xx_timeout_handler(struct k_work *item) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(item); + struct hl78xx_data *data = CONTAINER_OF(dwork, struct hl78xx_data, timeout_work); + + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_TIMEOUT); +} + +static void hl78xx_bus_pipe_handler(struct modem_pipe *pipe, enum modem_pipe_event event, + void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + switch (event) { + case MODEM_PIPE_EVENT_OPENED: + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_BUS_OPENED); + break; + + case MODEM_PIPE_EVENT_CLOSED: + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_BUS_CLOSED); + break; + + default: + break; + } +} + +static void hl78xx_log_state_changed(enum hl78xx_state last_state, enum hl78xx_state new_state) +{ + LOG_INF("switch from %s to %s", hl78xx_state_str(last_state), hl78xx_state_str(new_state)); +} + +static void hl78xx_event_dispatch_handler(struct k_work *item) +{ + struct hl78xx_data *data = + CONTAINER_OF(item, struct hl78xx_data, events.event_dispatch_work); + uint8_t events[sizeof(data->events.event_buf)]; + uint8_t events_cnt; + + k_mutex_lock(&data->events.event_rb_lock, K_FOREVER); + events_cnt = (uint8_t)ring_buf_get(&data->events.event_rb, events, + sizeof(data->events.event_buf)); + k_mutex_unlock(&data->events.event_rb_lock); + LOG_DBG("dequeued %d events", events_cnt); + + for (uint8_t i = 0; i < events_cnt; i++) { + hl78xx_event_handler(data, (enum hl78xx_event)events[i]); + } +} + +void hl78xx_delegate_event(struct hl78xx_data *data, enum hl78xx_event evt) +{ + k_mutex_lock(&data->events.event_rb_lock, K_FOREVER); + ring_buf_put(&data->events.event_rb, (uint8_t *)&evt, 1); + k_mutex_unlock(&data->events.event_rb_lock); + k_work_submit_to_queue(&modem_workq, &data->events.event_dispatch_work); +} +/* ------------------------------------------------------------------------- + * Chat callbacks / URC handlers + * - unsolicited response handlers and chat-related parsers + * ------------------------------------------------------------------------- + */ +void hl78xx_on_cxreg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + enum cellular_registration_status registration_status = 0; + struct hl78xx_evt event = {.type = HL78XX_LTE_REGISTRATION_STAT_UPDATE}; +#ifndef CONFIG_MODEM_HL78XX_12 + enum hl78xx_cell_rat_mode rat_mode = HL78XX_RAT_MODE_NONE; + struct hl78xx_evt rat_event; + bool rat_mode_updated = false; + int act_value = -1; +#endif /* CONFIG_MODEM_HL78XX_12 */ + if (argc < 2) { + return; + } + /* +CXREG: [,[...]] */ + if (argc > 2 && strlen(argv[1]) == 1 && strlen(argv[2]) == 1) { + /* This is a condition to distinguish received message between URC message and User + * request network status request. If the message is User message, then argv[1] and + * argv[2] will be 1 character long. If the message is URC request network status + * request, then argv[1] will be 1 character long while argv[2] will be 2 characters + * long. + */ + registration_status = ATOI(argv[2], 0, "registration_status"); +#ifndef CONFIG_MODEM_HL78XX_12 + if (argc > 4 && strlen(argv[5]) == 1) { + act_value = ATOI(argv[5], -1, "act_value"); + LOG_DBG("act_value: %d, argc: %d, argv[5]: %s", act_value, argc, argv[5]); + switch (act_value) { + case 7: + rat_mode = HL78XX_RAT_CAT_M1; + break; + case 9: + rat_mode = HL78XX_RAT_NB1; + break; + default: + rat_mode = HL78XX_RAT_MODE_NONE; + break; + } + rat_mode_updated = true; + LOG_DBG("RAT mode from response: %d", rat_mode); + } +#endif /* CONFIG_MODEM_HL78XX_12 */ + } else { + registration_status = ATOI(argv[1], 0, "registration_status"); +#ifndef CONFIG_MODEM_HL78XX_12 + if (argc > 3 && strlen(argv[4]) == 1) { + act_value = ATOI(argv[4], -1, "act_value"); + LOG_DBG("act_value: %d, argc: %d, argv[4]: %s", act_value, argc, argv[4]); + switch (act_value) { + case 7: + rat_mode = HL78XX_RAT_CAT_M1; + break; + case 9: + rat_mode = HL78XX_RAT_NB1; + break; + default: + rat_mode = HL78XX_RAT_MODE_NONE; + break; + } + rat_mode_updated = true; + LOG_DBG("RAT mode from URC: %d", rat_mode); + } +#endif /* CONFIG_MODEM_HL78XX_12 */ + } + HL78XX_LOG_DBG("%s: %d", argv[0], registration_status); + if (registration_status == data->status.registration.network_state_current) { +#ifndef CONFIG_MODEM_HL78XX_12 + /* Check if RAT mode changed even if registration status didn't */ + if (rat_mode_updated && rat_mode != -1 && + rat_mode != data->status.registration.rat_mode) { + data->status.registration.rat_mode = rat_mode; + rat_event.type = HL78XX_LTE_RAT_UPDATE; + rat_event.content.rat_mode = rat_mode; + event_dispatcher_dispatch(&rat_event); + } +#endif /* CONFIG_MODEM_HL78XX_12 */ + return; + } + /* Update the previous registration state */ + data->status.registration.network_state_previous = + data->status.registration.network_state_current; + /* Update the current registration state */ + data->status.registration.network_state_current = registration_status; + event.content.reg_status = data->status.registration.network_state_current; + + data->status.registration.is_registered_previously = + data->status.registration.is_registered_currently; +#ifndef CONFIG_MODEM_HL78XX_12 + /* Update RAT mode if parsed */ + if (rat_mode_updated && rat_mode != -1 && rat_mode != data->status.registration.rat_mode) { + data->status.registration.rat_mode = rat_mode; + rat_event.type = HL78XX_LTE_RAT_UPDATE; + rat_event.content.rat_mode = rat_mode; + event_dispatcher_dispatch(&rat_event); + } +#endif /* CONFIG_MODEM_HL78XX_12 */ + /* Update current registration flag */ + if (hl78xx_is_registered(data)) { + data->status.registration.is_registered_currently = true; + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_REGISTERED); +#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING + k_sem_give(&data->stay_in_boot_mode_sem); +#endif /* CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING */ + } else { + data->status.registration.is_registered_currently = false; + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_DEREGISTERED); + } + event_dispatcher_dispatch(&event); +} + +void hl78xx_on_ksup(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + int module_status; + struct hl78xx_evt event = {.type = HL78XX_LTE_MODEM_STARTUP}; + + if (argc != 2) { + return; + } + module_status = ATOI(argv[1], 0, "module_status"); + event.content.value = module_status; + event_dispatcher_dispatch(&event); + HL78XX_LOG_DBG("Module status: %d", module_status); +} + +void hl78xx_on_imei(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } + HL78XX_LOG_DBG("IMEI: %s %s", argv[0], argv[1]); + k_mutex_lock(&data->api_lock, K_FOREVER); + safe_strncpy((char *)data->identity.imei, argv[1], sizeof(data->identity.imei)); + k_mutex_unlock(&data->api_lock); +} + +void hl78xx_on_cgmm(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } + HL78XX_LOG_DBG("cgmm: %s %s", argv[0], argv[1]); + k_mutex_lock(&data->api_lock, K_FOREVER); + safe_strncpy((char *)data->identity.model_id, argv[1], sizeof(data->identity.model_id)); + k_mutex_unlock(&data->api_lock); +} + +void hl78xx_on_imsi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } + HL78XX_LOG_DBG("IMSI: %s %s", argv[0], argv[1]); + k_mutex_lock(&data->api_lock, K_FOREVER); + safe_strncpy((char *)data->identity.imsi, argv[1], sizeof(data->identity.imsi)); + k_mutex_unlock(&data->api_lock); +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI) + /* set the APN automatically */ + modem_detect_apn(data, argv[1]); +#endif /* CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI */ +} + +void hl78xx_on_cgmi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } + HL78XX_LOG_DBG("cgmi: %s %s", argv[0], argv[1]); + k_mutex_lock(&data->api_lock, K_FOREVER); + safe_strncpy((char *)data->identity.manufacturer, argv[1], + sizeof(data->identity.manufacturer)); + k_mutex_unlock(&data->api_lock); +} + +void hl78xx_on_cgmr(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } + HL78XX_LOG_DBG("cgmr: %s %s", argv[0], argv[1]); + k_mutex_lock(&data->api_lock, K_FOREVER); + safe_strncpy((char *)data->identity.fw_version, argv[1], sizeof(data->identity.fw_version)); + k_mutex_unlock(&data->api_lock); +} + +void hl78xx_on_iccid(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc != 2) { + return; + } + HL78XX_LOG_DBG("ICCID: %s %s", argv[0], argv[1]); + k_mutex_lock(&data->api_lock, K_FOREVER); + safe_strncpy((char *)data->identity.iccid, argv[1], sizeof(data->identity.iccid)); + k_mutex_unlock(&data->api_lock); + +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) + /* set the APN automatically */ + modem_detect_apn(data, argv[1]); +#endif /* CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID */ +} + +#if defined(CONFIG_MODEM_HL78XX_12) +void hl78xx_on_kstatev(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + enum hl78xx_cell_rat_mode rat_mode = HL78XX_RAT_MODE_NONE; + struct hl78xx_evt event = {.type = HL78XX_LTE_RAT_UPDATE}; + + if (argc != 3) { + return; + } + rat_mode = ATOI(argv[2], 0, "rat_mode"); +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + hl78xx_on_kstatev_parser(data, (enum hl78xx_info_transfer_event)ATOI(argv[1], 0, "status"), + rat_mode); +#endif /* CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG */ + if (rat_mode != data->status.registration.rat_mode) { + data->status.registration.rat_mode = rat_mode; + event.content.rat_mode = data->status.registration.rat_mode; + event_dispatcher_dispatch(&event); + } +} +#endif + +void hl78xx_on_ksrep(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc < 2) { + return; + } + data->status.ksrep = ATOI(argv[1], 0, "ksrep"); + HL78XX_LOG_DBG("KSREP: %s %s", argv[0], argv[1]); +} +void hl78xx_on_ksrat(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + struct hl78xx_evt event = {.type = HL78XX_LTE_RAT_UPDATE}; + + if (argc < 2) { + return; + } + data->status.registration.rat_mode = (uint8_t)ATOI(argv[1], 0, "rat_mode"); + event.content.rat_mode = data->status.registration.rat_mode; + event_dispatcher_dispatch(&event); + HL78XX_LOG_DBG("KSRAT: %s %s", argv[0], argv[1]); +} + +void hl78xx_on_kselacq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc < 2) { + return; + } + if (argc > 3) { + data->kselacq_data.mode = 0; + data->kselacq_data.rat1 = ATOI(argv[1], 0, "rat1"); + data->kselacq_data.rat2 = ATOI(argv[2], 0, "rat2"); + data->kselacq_data.rat3 = ATOI(argv[3], 0, "rat3"); + } else { + data->kselacq_data.mode = 0; + data->kselacq_data.rat1 = 0; + data->kselacq_data.rat2 = 0; + data->kselacq_data.rat3 = 0; + } +} + +void hl78xx_on_kbndcfg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + uint8_t rat_id; + uint8_t kbnd_bitmap_size; + + if (argc < 3) { + return; + } + + rat_id = ATOI(argv[1], 0, "rat"); + kbnd_bitmap_size = strlen(argv[2]); + HL78XX_LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[2]); + if (kbnd_bitmap_size >= MDM_BAND_HEX_STR_LEN) { + LOG_ERR("%d %s Unexpected band bitmap length of %d", __LINE__, __func__, + kbnd_bitmap_size); + return; + } + if (rat_id >= HL78XX_RAT_COUNT) { + return; + } + data->status.kbndcfg[rat_id].rat = rat_id; + strncpy(data->status.kbndcfg[rat_id].bnd_bitmap, argv[2], kbnd_bitmap_size); + data->status.kbndcfg[rat_id].bnd_bitmap[kbnd_bitmap_size] = '\0'; +} + +void hl78xx_on_csq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc < 3) { + return; + } + data->status.rssi = ATOI(argv[1], 0, "rssi"); +} + +void hl78xx_on_cesq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc < 7) { + return; + } + data->status.rsrq = ATOI(argv[5], 0, "rsrq"); + data->status.rsrp = ATOI(argv[6], 0, "rsrp"); +} + +void hl78xx_on_cfun(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc < 2) { + return; + } + data->status.phone_functionality.functionality = ATOI(argv[1], 0, "phone_func"); + data->status.phone_functionality.in_progress = false; +} + +void hl78xx_on_cops(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (argc < 3) { + return; + } + safe_strncpy((char *)data->status.network_operator.operator, argv[3], + sizeof(data->status.network_operator.operator)); + data->status.network_operator.format = ATOI(argv[2], 0, "network_operator_format"); +} + +/* ------------------------------------------------------------------------- + * Pipe & chat initialization + * - modem backend pipe setup and chat initialisation helpers + * ------------------------------------------------------------------------- + */ +static void hl78xx_init_pipe(const struct device *dev) +{ + const struct hl78xx_config *cfg = dev->config; + struct hl78xx_data *data = dev->data; + + const struct modem_backend_uart_config uart_backend_config = { + .uart = cfg->uart, + .receive_buf = data->buffers.uart_rx, + .receive_buf_size = sizeof(data->buffers.uart_rx), + .transmit_buf = data->buffers.uart_tx, + .transmit_buf_size = ARRAY_SIZE(data->buffers.uart_tx), + }; + + data->uart_pipe = modem_backend_uart_init(&data->uart_backend, &uart_backend_config); +} + +/* Initialize the modem chat subsystem using wrappers from hl78xx_chat.c */ +static int modem_init_chat(const struct device *dev) +{ + struct hl78xx_data *data = dev->data; + + const struct modem_chat_config chat_config = { + .user_data = data, + .receive_buf = data->buffers.chat_rx, + .receive_buf_size = sizeof(data->buffers.chat_rx), + .delimiter = data->buffers.delimiter, + .delimiter_size = strlen(data->buffers.delimiter), + .filter = data->buffers.filter, + .filter_size = data->buffers.filter ? strlen(data->buffers.filter) : 0, + .argv = data->buffers.argv, + .argv_size = (uint16_t)ARRAY_SIZE(data->buffers.argv), + .unsol_matches = hl78xx_get_unsol_matches(), + .unsol_matches_size = (uint16_t)hl78xx_get_unsol_matches_size(), + }; + + return modem_chat_init(&data->chat, &chat_config); +} + +/* clang-format off */ +int modem_dynamic_cmd_send( + struct hl78xx_data *data, + modem_chat_script_callback script_user_callback, + const uint8_t *cmd, uint16_t cmd_size, + const struct modem_chat_match *response_matches, uint16_t matches_size, + bool user_cmd + ) +{ + int ret = 0; + int script_ret = 0; + /* validate input parameters */ + if (data == NULL) { + LOG_ERR("%d %s Invalid parameter", __LINE__, __func__); + errno = EINVAL; + return -1; + } + struct modem_chat_script_chat dynamic_script = { + .request = cmd, + .request_size = cmd_size, + .response_matches = response_matches, + .response_matches_size = matches_size, + .timeout = 1000, + }; + struct modem_chat_script chat_script = { + .name = "dynamic_script", + .script_chats = &dynamic_script, + .script_chats_size = 1, + .abort_matches = hl78xx_get_abort_matches(), + .abort_matches_size = hl78xx_get_abort_matches_size(), + .callback = script_user_callback, + .timeout = 1000 + }; + + ret = k_mutex_lock(&data->tx_lock, K_NO_WAIT); + if (ret < 0) { + if (user_cmd == false) { + errno = -ret; + } + return -1; + } + /* run the chat script */ + script_ret = modem_chat_run_script(&data->chat, &chat_script); + if (script_ret < 0) { + LOG_ERR("%d %s Failed to run at command: %d", __LINE__, __func__, script_ret); + } else { + LOG_DBG("Chat script executed successfully."); + } + ret = k_mutex_unlock(&data->tx_lock); + if (ret < 0) { + if (user_cmd == false) { + errno = -ret; + } + /* we still return the script result if available, prioritize script_ret */ + return script_ret < 0 ? -1 : script_ret; + } + return script_ret; +} +/* clang-format on */ + +/* ------------------------------------------------------------------------- + * GPIO ISR callbacks + * - lightweight wrappers for GPIO interrupts (logging & event dispatch) + * ------------------------------------------------------------------------- + */ +void mdm_vgpio_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins) +{ + struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.vgpio_cb); + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + const struct gpio_dt_spec *spec = &config->mdm_gpio_vgpio; + + if (spec == NULL || spec->port == NULL) { + LOG_ERR("VGPIO GPIO spec is not configured properly"); + return; + } + if (!(pins & BIT(spec->pin))) { + return; /* not our pin */ + } + LOG_DBG("VGPIO ISR callback %s %d %d", spec->port->name, spec->pin, gpio_pin_get_dt(spec)); +} + +#if HAS_UART_DSR_GPIO +void mdm_uart_dsr_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins) +{ + struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.vgpio_cb); + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + const struct gpio_dt_spec *spec = &config->mdm_gpio_uart_dsr; + + if (spec == NULL || spec->port == NULL) { + LOG_ERR("DSR GPIO spec is not configured properly"); + return; + } + if (!(pins & BIT(spec->pin))) { + return; /* not our pin */ + } + LOG_DBG("DSR ISR callback %d", gpio_pin_get_dt(spec)); +} +#endif + +void mdm_gpio6_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins) +{ + struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.gpio6_cb); + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + const struct gpio_dt_spec *spec = &config->mdm_gpio_gpio6; + + if (spec == NULL || spec->port == NULL) { + LOG_ERR("GPIO6 GPIO spec is not configured properly"); + return; + } + if (!(pins & BIT(spec->pin))) { + return; /* not our pin */ + } + LOG_DBG("GPIO6 ISR callback %s %d %d", spec->port->name, spec->pin, gpio_pin_get_dt(spec)); +} + +void mdm_uart_cts_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins) +{ + struct hl78xx_data *data = CONTAINER_OF(cb, struct hl78xx_data, gpio_cbs.gpio6_cb); + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + const struct gpio_dt_spec *spec = &config->mdm_gpio_uart_cts; + + if (spec == NULL || spec->port == NULL) { + LOG_ERR("CTS GPIO spec is not configured properly"); + return; + } + if (!(pins & BIT(spec->pin))) { + return; /* not our pin */ + } + LOG_DBG("CTS ISR callback %d", gpio_pin_get_dt(spec)); +} + +bool hl78xx_is_registered(struct hl78xx_data *data) +{ + return (data->status.registration.network_state_current == + CELLULAR_REGISTRATION_REGISTERED_HOME) || + (data->status.registration.network_state_current == + CELLULAR_REGISTRATION_REGISTERED_ROAMING); +} + +/* + * hl78xx_is_registered - convenience helper + * + * Simple predicate to test if the modem reports a registered state. + */ + +static int hl78xx_on_reset_pulse_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) { + gpio_pin_set_dt(&config->mdm_gpio_wake, 0); + } + gpio_pin_set_dt(&config->mdm_gpio_reset, 1); + hl78xx_start_timer(data, K_MSEC(config->reset_pulse_duration_ms)); + return 0; +} + +/* ------------------------------------------------------------------------- + * State machine handlers + * - state enter/leave and per-state event handlers + * ------------------------------------------------------------------------- + */ + +static void hl78xx_reset_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_TIMEOUT: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + + default: + break; + } +} + +static int hl78xx_on_reset_pulse_state_leave(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + gpio_pin_set_dt(&config->mdm_gpio_reset, 0); + } + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) { + gpio_pin_set_dt(&config->mdm_gpio_wake, 1); + } + hl78xx_stop_timer(data); + return 0; +} + +static int hl78xx_on_power_on_pulse_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 1); + } + hl78xx_start_timer(data, K_MSEC(config->power_pulse_duration_ms)); + return 0; +} + +static void hl78xx_power_on_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_TIMEOUT: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + + default: + break; + } +} + +static int hl78xx_on_power_on_pulse_state_leave(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 0); + } + hl78xx_stop_timer(data); + return 0; +} + +static int hl78xx_on_await_power_on_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + hl78xx_start_timer(data, K_MSEC(config->startup_time_ms)); + return 0; +} + +static void hl78xx_await_power_on_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_TIMEOUT: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_SCRIPT); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + + default: + break; + } +} +static int hl78xx_on_run_init_script_state_enter(struct hl78xx_data *data) +{ + modem_pipe_attach(data->uart_pipe, hl78xx_bus_pipe_handler, data); + return modem_pipe_open_async(data->uart_pipe); +} + +static void hl78xx_run_init_script_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_BUS_OPENED: + modem_chat_attach(&data->chat, data->uart_pipe); + /* Run init script via chat TU wrapper (script symbols live in hl78xx_chat.c) */ + hl78xx_run_init_script_async(data); + break; + + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT); + break; + + case MODEM_HL78XX_EVENT_BUS_CLOSED: + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT); + break; + + default: + break; + } +} + +static int hl78xx_on_run_init_diagnose_script_state_enter(struct hl78xx_data *data) +{ + hl78xx_run_init_fail_script_async(data); + return 0; +} + +static void hl78xx_run_init_fail_script_event_handler(struct hl78xx_data *data, + enum hl78xx_event evt) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + if (data->status.ksrep == 0) { + hl78xx_run_enable_ksup_urc_script_async(data); + hl78xx_start_timer(data, K_MSEC(config->shutdown_time_ms)); + } else { + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE); + } + } + break; + case MODEM_HL78XX_EVENT_TIMEOUT: + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE); + break; + } + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE); + break; + } + + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + case MODEM_HL78XX_EVENT_BUS_CLOSED: + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + if (!hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) { + LOG_ERR("modem wake pin is not enabled, make sure modem low power is " + "disabled, if you are not sure enable wake up pin by adding it " + "dts!!"); + } + + if (data->status.script_fail_counter++ < MAX_SCRIPT_AT_CMD_RETRY) { + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE); + break; + } + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RESET_PULSE); + break; + } + } + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + default: + break; + } +} + +static int hl78xx_on_rat_cfg_script_state_enter(struct hl78xx_data *data) +{ + int ret = 0; + bool modem_require_restart = false; + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + enum hl78xx_cell_rat_mode rat_config_request = HL78XX_RAT_MODE_NONE; + const char *cmd_restart = (const char *)SET_AIRPLANE_MODE_CMD; + + ret = hl78xx_rat_cfg(data, &modem_require_restart, &rat_config_request); + if (ret < 0) { + goto error; + } + + ret = hl78xx_band_cfg(data, &modem_require_restart, rat_config_request); + if (ret < 0) { + goto error; + } + + if (modem_require_restart) { + ret = modem_dynamic_cmd_send(data, NULL, cmd_restart, strlen(cmd_restart), + hl78xx_get_ok_match(), 1, false); + if (ret < 0) { + goto error; + } + hl78xx_start_timer(data, + K_MSEC(config->shutdown_time_ms + config->startup_time_ms)); + return 0; + } + hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS, data); + return 0; +error: + hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_ABORT, data); + LOG_ERR("%d %s Failed to send command: %d", __LINE__, __func__, ret); + return ret; +} + +static void hl78xx_run_rat_cfg_script_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + int ret = 0; + + switch (evt) { + case MODEM_HL78XX_EVENT_TIMEOUT: + LOG_DBG("Rebooting modem to apply new RAT settings"); + ret = hl78xx_run_post_restart_script_async(data); + if (ret < 0) { + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SUSPEND); + } + break; + + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + default: + break; + } +} + +static int hl78xx_on_await_power_off_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + hl78xx_start_timer(data, K_MSEC(config->shutdown_time_ms)); + return 0; +} + +static void hl78xx_await_power_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + if (evt == MODEM_HL78XX_EVENT_TIMEOUT) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + } +} + +static int hl78xx_on_enable_gprs_state_enter(struct hl78xx_data *data) +{ + int ret = 0; + /* Apply the APN if not configured yet */ + if (data->status.apn.state == APN_STATE_REFRESH_REQUESTED) { + /* APN has been updated by the user, apply the new APN */ + HL78XX_LOG_DBG("APN refresh requested, applying new APN: \"%s\"", + data->identity.apn); + data->status.apn.state = APN_STATE_NOT_CONFIGURED; + } else { +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_KCONFIG) + snprintf(data->identity.apn, sizeof(data->identity.apn), "%s", + CONFIG_MODEM_HL78XX_APN); +#elif defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) || defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI) + /* autodetect APN from IMSI */ + /* the list of SIM profiles. Global scope, so the app can change it */ + /* AT+CCID or AT+CIMI needs to be run here if it is not ran in the init script */ + if (strlen(data->identity.apn) < 1) { + LOG_WRN("%d %s APN is left blank", __LINE__, __func__); + } +#else /* defined(CONFIG_MODEM_HL78XX_APN_SOURCE_NETWORK) */ +/* set blank string to get apn from network */ +#endif + } + ret = hl78xx_api_func_set_phone_functionality(data->dev, HL78XX_AIRPLANE, false); + if (ret) { + goto error; + } + ret = hl78xx_set_apn_internal(data, data->identity.apn, strlen(data->identity.apn)); + if (ret) { + goto error; + } +#if defined(CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE) + ret = hl78xx_api_func_set_phone_functionality(data->dev, HL78XX_FULLY_FUNCTIONAL, false); + if (ret) { + goto error; + } +#endif /* CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE */ + hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS, data); + return 0; +error: + hl78xx_chat_callback_handler(&data->chat, MODEM_CHAT_SCRIPT_RESULT_ABORT, data); + LOG_ERR("%d %s Failed to send command: %d", __LINE__, __func__, ret); + return ret; +} + +static void hl78xx_enable_gprs_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + hl78xx_start_timer(data, MODEM_HL78XX_PERIODIC_SCRIPT_TIMEOUT); + break; + + case MODEM_HL78XX_EVENT_TIMEOUT: + break; + + case MODEM_HL78XX_EVENT_REGISTERED: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_ON); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + + default: + break; + } +} + +static int hl78xx_on_await_registered_state_enter(struct hl78xx_data *data) +{ + return 0; +} + +static void hl78xx_await_registered_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + hl78xx_start_timer(data, K_SECONDS(MDM_REGISTRATION_TIMEOUT)); + break; + + case MODEM_HL78XX_EVENT_TIMEOUT: + /** + * No need to run periodic script to check registration status because URC is used + * to notify the status change. + * + * If the modem is not registered within the timeout period, it will stay in this + * state forever. + * + * @attention MDM_REGISTRATION_TIMEOUT should be long enough to allow the modem to + * register to the network, especially for the first time registration. And also + * consider the network conditions / number of bands etc.. that may affect + * the registration process. + * + * TODO: add a mechanism to exit this state and retry the registration process + * + */ + LOG_WRN("Modem failed to register to the network within %d seconds", + MDM_REGISTRATION_TIMEOUT); + + break; + + case MODEM_HL78XX_EVENT_REGISTERED: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_ON); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + + default: + break; + } +} + +static int hl78xx_on_await_registered_state_leave(struct hl78xx_data *data) +{ + hl78xx_stop_timer(data); + return 0; +} + +static int hl78xx_on_carrier_on_state_enter(struct hl78xx_data *data) +{ + notif_carrier_on(data->dev); + iface_status_work_cb(data, hl78xx_chat_callback_handler); + return 0; +} + +static void hl78xx_carrier_on_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + hl78xx_start_timer(data, K_SECONDS(2)); + + break; + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + break; + + case MODEM_HL78XX_EVENT_TIMEOUT: + dns_work_cb(data->dev, true); + break; + + case MODEM_HL78XX_EVENT_DEREGISTERED: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_REGISTERED); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + + default: + break; + } +} + +static int hl78xx_on_carrier_on_state_leave(struct hl78xx_data *data) +{ + hl78xx_stop_timer(data); + return 0; +} + +static int hl78xx_on_carrier_off_state_enter(struct hl78xx_data *data) +{ + notif_carrier_off(data->dev); + /* Check whether or not there is any sockets are connected, + * if true, wait until sockets are closed properly + */ + if (check_if_any_socket_connected(data->dev) == false) { + hl78xx_start_timer(data, K_MSEC(100)); + } else { + /* There are still active sockets, wait until they are closed */ + hl78xx_start_timer(data, K_MSEC(5000)); + } + return 0; +} + +static void hl78xx_carrier_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + case MODEM_HL78XX_EVENT_SCRIPT_FAILED: + case MODEM_HL78XX_EVENT_TIMEOUT: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT); + break; + + case MODEM_HL78XX_EVENT_DEREGISTERED: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_REGISTERED); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_INIT_POWER_OFF); + break; + + default: + break; + } +} + +static int hl78xx_on_carrier_off_state_leave(struct hl78xx_data *data) +{ + hl78xx_stop_timer(data); + return 0; +} + +/* pwroff script moved to hl78xx_chat.c */ +static int hl78xx_on_init_power_off_state_enter(struct hl78xx_data *data) +{ + /** + * Eventhough you have power switch or etc.., start the power off script first + * to gracefully disconnect from the network + * + * IMSI detach before powering down IS recommended by the AT command manual + * + */ + return hl78xx_run_pwroff_script_async(data); +} + +static void hl78xx_init_power_off_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + switch (evt) { + case MODEM_HL78XX_EVENT_SCRIPT_SUCCESS: + hl78xx_enter_state(data, MODEM_HL78XX_STATE_IDLE); + break; + + case MODEM_HL78XX_EVENT_TIMEOUT: + break; + + case MODEM_HL78XX_EVENT_DEREGISTERED: + hl78xx_stop_timer(data); + break; + + default: + break; + } +} + +static int hl78xx_on_init_power_off_state_leave(struct hl78xx_data *data) +{ + return 0; +} + +static int hl78xx_on_power_off_pulse_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 1); + } + hl78xx_start_timer(data, K_MSEC(config->power_pulse_duration_ms)); + return 0; +} + +static void hl78xx_power_off_pulse_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + if (evt == MODEM_HL78XX_EVENT_TIMEOUT) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_OFF); + } +} + +static int hl78xx_on_power_off_pulse_state_leave(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + gpio_pin_set_dt(&config->mdm_gpio_pwr_on, 0); + } + hl78xx_stop_timer(data); + return 0; +} + +static int hl78xx_on_idle_state_enter(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) { + gpio_pin_set_dt(&config->mdm_gpio_wake, 0); + } + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + gpio_pin_set_dt(&config->mdm_gpio_reset, 1); + } + modem_chat_release(&data->chat); + modem_pipe_attach(data->uart_pipe, hl78xx_bus_pipe_handler, data); + modem_pipe_close_async(data->uart_pipe); + k_sem_give(&data->suspended_sem); + return 0; +} + +static void hl78xx_idle_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + switch (evt) { + case MODEM_HL78XX_EVENT_BUS_CLOSED: + break; + case MODEM_HL78XX_EVENT_RESUME: + if (config->autostarts) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON); + break; + } + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_pwr_on)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_POWER_ON_PULSE); + break; + } + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + hl78xx_enter_state(data, MODEM_HL78XX_STATE_AWAIT_POWER_ON); + break; + } + hl78xx_enter_state(data, MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT); + break; + + case MODEM_HL78XX_EVENT_SUSPEND: + k_sem_give(&data->suspended_sem); + break; + + default: + break; + } +} + +static int hl78xx_on_idle_state_leave(struct hl78xx_data *data) +{ + const struct hl78xx_config *config = (const struct hl78xx_config *)data->dev->config; + + k_sem_take(&data->suspended_sem, K_NO_WAIT); + + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_reset)) { + gpio_pin_set_dt(&config->mdm_gpio_reset, 0); + } + if (hl78xx_gpio_is_enabled(&config->mdm_gpio_wake)) { + gpio_pin_set_dt(&config->mdm_gpio_wake, 1); + } + + return 0; +} + +static int hl78xx_on_state_enter(struct hl78xx_data *data) +{ + int ret = 0; + enum hl78xx_state s = data->status.state; + + /* Use an explicit bounds check against the last enum value so this + * code can reference the table even though the table is defined later + * in the file. MODEM_HL78XX_STATE_AWAIT_POWER_OFF is the last value in + * the `enum hl78xx_state`. + */ + if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_enter) { + ret = hl78xx_state_table[s].on_enter(data); + } + + return ret; +} + +static int hl78xx_on_state_leave(struct hl78xx_data *data) +{ + int ret = 0; + enum hl78xx_state s = data->status.state; + + if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_leave) { + ret = hl78xx_state_table[s].on_leave(data); + } + + return ret; +} + +void hl78xx_enter_state(struct hl78xx_data *data, enum hl78xx_state state) +{ + int ret; + + ret = hl78xx_on_state_leave(data); + + if (ret < 0) { + LOG_WRN("failed to leave state, error: %i", ret); + + return; + } + + data->status.state = state; + ret = hl78xx_on_state_enter(data); + + if (ret < 0) { + LOG_WRN("failed to enter state error: %i", ret); + } +} + +static void hl78xx_event_handler(struct hl78xx_data *data, enum hl78xx_event evt) +{ + enum hl78xx_state state; + enum hl78xx_state s; + + hl78xx_log_event(evt); + s = data->status.state; + state = data->status.state; + if ((int)s <= MODEM_HL78XX_STATE_AWAIT_POWER_OFF && hl78xx_state_table[s].on_event) { + hl78xx_state_table[s].on_event(data, evt); + } else { + LOG_ERR("%d %s unknown event", __LINE__, __func__); + } + if (state != s) { + hl78xx_log_state_changed(state, s); + } +} + +#ifdef CONFIG_PM_DEVICE + +/* ------------------------------------------------------------------------- + * Power management + * ------------------------------------------------------------------------- + */ + +static int hl78xx_driver_pm_action(const struct device *dev, enum pm_device_action action) +{ + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + int ret = 0; + + LOG_WRN("%d %s PM_DEVICE_ACTION: %d", __LINE__, __func__, action); + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + /* suspend the device */ + LOG_DBG("%d PM_DEVICE_ACTION_SUSPEND", __LINE__); + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SUSPEND); + ret = k_sem_take(&data->suspended_sem, K_SECONDS(30)); + break; + case PM_DEVICE_ACTION_RESUME: + LOG_DBG("%d PM_DEVICE_ACTION_RESUME", __LINE__); + /* resume the device */ + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_RESUME); + break; + case PM_DEVICE_ACTION_TURN_ON: + /* + * powered on the device, used when the power + * domain this device belongs is resumed. + */ + LOG_DBG("%d PM_DEVICE_ACTION_TURN_ON", __LINE__); + break; + case PM_DEVICE_ACTION_TURN_OFF: + /* + * power off the device, used when the power + * domain this device belongs is suspended. + */ + LOG_DBG("%d PM_DEVICE_ACTION_TURN_OFF", __LINE__); + break; + default: + return -ENOTSUP; + } + return ret; +} +#endif /* CONFIG_PM_DEVICE */ + +/* ------------------------------------------------------------------------- + * Initialization + * ------------------------------------------------------------------------- + */ +static int hl78xx_init(const struct device *dev) +{ + int ret; + const struct hl78xx_config *config = (const struct hl78xx_config *)dev->config; + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + + k_mutex_init(&data->api_lock); + k_mutex_init(&data->tx_lock); + /* Initialize work queue and event handling */ + k_work_queue_start(&modem_workq, modem_workq_stack, + K_KERNEL_STACK_SIZEOF(modem_workq_stack), K_PRIO_COOP(7), NULL); + k_work_init_delayable(&data->timeout_work, hl78xx_timeout_handler); + k_work_init(&data->events.event_dispatch_work, hl78xx_event_dispatch_handler); + ring_buf_init(&data->events.event_rb, sizeof(data->events.event_buf), + data->events.event_buf); + k_sem_init(&data->suspended_sem, 0, 1); +#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING + k_sem_init(&data->stay_in_boot_mode_sem, 0, 1); +#endif /* CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING */ + k_sem_init(&data->script_stopped_sem_tx_int, 0, 1); + k_sem_init(&data->script_stopped_sem_rx_int, 0, 1); + data->dev = dev; + /* reset to default */ + data->buffers.eof_pattern_size = strlen(data->buffers.eof_pattern); + data->buffers.termination_pattern_size = strlen(data->buffers.termination_pattern); + memset(data->identity.apn, 0, MDM_APN_MAX_LENGTH); + /* GPIO validation */ + const struct gpio_dt_spec *gpio_pins[GPIO_CONFIG_LEN] = { +#if HAS_RESET_GPIO + &config->mdm_gpio_reset, +#endif +#if HAS_WAKE_GPIO + &config->mdm_gpio_wake, +#endif +#if HAS_VGPIO_GPIO + &config->mdm_gpio_vgpio, +#endif +#if HAS_UART_CTS_GPIO + &config->mdm_gpio_uart_cts, +#endif +#if HAS_GPIO6_GPIO + &config->mdm_gpio_gpio6, +#endif +#if HAS_PWR_ON_GPIO + &config->mdm_gpio_pwr_on, +#endif +#if HAS_FAST_SHUTD_GPIO + &config->mdm_gpio_fast_shutdown, +#endif +#if HAS_UART_DSR_GPIO + &config->mdm_gpio_uart_dsr, +#endif +#if HAS_UART_DTR_GPIO + &config->mdm_gpio_uart_dtr, +#endif +#if HAS_GPIO8_GPIO + &config->mdm_gpio_gpio8, +#endif +#if HAS_SIM_SWITCH_GPIO + &config->mdm_gpio_sim_switch, +#endif + }; + for (int i = 0; i < ARRAY_SIZE(gpio_pins); i++) { + if (gpio_pins[i] == NULL || !gpio_is_ready_dt(gpio_pins[i])) { + const char *port_name = "unknown"; + + if (gpio_pins[i] != NULL && gpio_pins[i]->port != NULL) { + port_name = gpio_pins[i]->port->name; + } + LOG_ERR("GPIO port (%s) not ready!", port_name); + return -ENODEV; + } + } + /* GPIO configuration */ + struct { + const struct gpio_dt_spec *spec; + gpio_flags_t flags; + const char *name; + } gpio_config[GPIO_CONFIG_LEN] = { +#if HAS_RESET_GPIO + {&config->mdm_gpio_reset, GPIO_OUTPUT, "reset"}, +#endif +#if HAS_WAKE_GPIO + {&config->mdm_gpio_wake, GPIO_OUTPUT, "wake"}, +#endif +#if HAS_VGPIO_GPIO + {&config->mdm_gpio_vgpio, GPIO_INPUT, "VGPIO"}, +#endif +#if HAS_UART_CTS_GPIO + {&config->mdm_gpio_uart_cts, GPIO_INPUT, "CTS"}, +#endif +#if HAS_GPIO6_GPIO + {&config->mdm_gpio_gpio6, GPIO_INPUT, "GPIO6"}, +#endif +#if HAS_PWR_ON_GPIO + {&config->mdm_gpio_pwr_on, GPIO_OUTPUT, "pwr_on"}, +#endif +#if HAS_FAST_SHUTD_GPIO + {&config->mdm_gpio_fast_shutdown, GPIO_OUTPUT, "fast_shutdown"}, +#endif +#if HAS_UART_DSR_GPIO + {&config->mdm_gpio_uart_dsr, GPIO_INPUT, "DSR"}, +#endif +#if HAS_UART_DTR_GPIO + {&config->mdm_gpio_uart_dtr, GPIO_OUTPUT, "DTR"}, +#endif +#if HAS_GPIO8_GPIO + {&config->mdm_gpio_gpio8, GPIO_INPUT, "GPIO8"}, +#endif +#if HAS_SIM_SWITCH_GPIO + {&config->mdm_gpio_sim_switch, GPIO_INPUT, "SIM_SWITCH"}, +#endif + }; + for (int i = 0; i < ARRAY_SIZE(gpio_config); i++) { + ret = gpio_pin_configure_dt(gpio_config[i].spec, gpio_config[i].flags); + if (ret < 0) { + LOG_ERR("Failed to configure %s pin", gpio_config[i].name); + goto error; + } + } +#if HAS_VGPIO_GPIO + /* VGPIO interrupt setup */ + gpio_init_callback(&data->gpio_cbs.vgpio_cb, mdm_vgpio_callback_isr, + BIT(config->mdm_gpio_vgpio.pin)); + + ret = gpio_add_callback(config->mdm_gpio_vgpio.port, &data->gpio_cbs.vgpio_cb); + if (ret) { + LOG_ERR("Cannot setup VGPIO callback! (%d)", ret); + goto error; + } + ret = gpio_pin_interrupt_configure_dt(&config->mdm_gpio_vgpio, GPIO_INT_EDGE_BOTH); + if (ret) { + LOG_ERR("Error configuring VGPIO interrupt! (%d)", ret); + goto error; + } +#endif /* HAS_VGPIO_GPIO */ +#if HAS_GPIO6_GPIO + /* GPIO6 interrupt setup */ + gpio_init_callback(&data->gpio_cbs.gpio6_cb, mdm_gpio6_callback_isr, + BIT(config->mdm_gpio_gpio6.pin)); + + ret = gpio_add_callback(config->mdm_gpio_gpio6.port, &data->gpio_cbs.gpio6_cb); + if (ret) { + LOG_ERR("Cannot setup GPIO6 callback! (%d)", ret); + goto error; + } + + ret = gpio_pin_interrupt_configure_dt(&config->mdm_gpio_gpio6, GPIO_INT_EDGE_BOTH); + if (ret) { + LOG_ERR("Error configuring GPIO6 interrupt! (%d)", ret); + goto error; + } +#endif /* HAS_GPIO6_GPIO */ + /* UART pipe initialization */ + (void)hl78xx_init_pipe(dev); + + ret = modem_init_chat(dev); + if (ret < 0) { + goto error; + } + +#ifndef CONFIG_PM_DEVICE + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_RESUME); +#else + pm_device_init_suspended(dev); +#endif /* CONFIG_PM_DEVICE */ + +#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING + k_sem_take(&data->stay_in_boot_mode_sem, K_FOREVER); +#endif + return 0; +error: + return ret; +} + +int hl78xx_evt_notif_handler_set(hl78xx_evt_monitor_dispatcher_t handler) +{ + event_dispatcher = handler; + return 0; +} + +/* + * State handler table + * Maps each hl78xx_state to optional enter/leave/event handlers. NULL + * entries mean the state has no action for that phase. + */ + +/* clang-format off */ +const static struct hl78xx_state_handlers hl78xx_state_table[] = { + [MODEM_HL78XX_STATE_IDLE] = { + hl78xx_on_idle_state_enter, + hl78xx_on_idle_state_leave, + hl78xx_idle_event_handler + }, + [MODEM_HL78XX_STATE_RESET_PULSE] = { + hl78xx_on_reset_pulse_state_enter, + hl78xx_on_reset_pulse_state_leave, + hl78xx_reset_pulse_event_handler + }, + [MODEM_HL78XX_STATE_POWER_ON_PULSE] = { + hl78xx_on_power_on_pulse_state_enter, + hl78xx_on_power_on_pulse_state_leave, + hl78xx_power_on_pulse_event_handler + }, + [MODEM_HL78XX_STATE_AWAIT_POWER_ON] = { + hl78xx_on_await_power_on_state_enter, + NULL, + hl78xx_await_power_on_event_handler + }, + [MODEM_HL78XX_STATE_SET_BAUDRATE] = { + NULL, + NULL, + NULL + }, + [MODEM_HL78XX_STATE_RUN_INIT_SCRIPT] = { + hl78xx_on_run_init_script_state_enter, + NULL, + hl78xx_run_init_script_event_handler + }, + [MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT] = { + hl78xx_on_run_init_diagnose_script_state_enter, + NULL, + hl78xx_run_init_fail_script_event_handler + }, + [MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT] = { + hl78xx_on_rat_cfg_script_state_enter, + NULL, + hl78xx_run_rat_cfg_script_event_handler + }, + [MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT] = { + hl78xx_on_enable_gprs_state_enter, + NULL, + hl78xx_enable_gprs_event_handler + }, + [MODEM_HL78XX_STATE_AWAIT_REGISTERED] = { + hl78xx_on_await_registered_state_enter, + hl78xx_on_await_registered_state_leave, + hl78xx_await_registered_event_handler + }, + [MODEM_HL78XX_STATE_CARRIER_ON] = { + hl78xx_on_carrier_on_state_enter, + hl78xx_on_carrier_on_state_leave, + hl78xx_carrier_on_event_handler + }, + [MODEM_HL78XX_STATE_CARRIER_OFF] = { + hl78xx_on_carrier_off_state_enter, + hl78xx_on_carrier_off_state_leave, + hl78xx_carrier_off_event_handler + }, + [MODEM_HL78XX_STATE_SIM_POWER_OFF] = { + NULL, + NULL, + NULL + }, + [MODEM_HL78XX_STATE_AIRPLANE] = { + NULL, + NULL, + NULL + }, + [MODEM_HL78XX_STATE_INIT_POWER_OFF] = { + hl78xx_on_init_power_off_state_enter, + hl78xx_on_init_power_off_state_leave, + hl78xx_init_power_off_event_handler + }, + [MODEM_HL78XX_STATE_POWER_OFF_PULSE] = { + hl78xx_on_power_off_pulse_state_enter, + hl78xx_on_power_off_pulse_state_leave, + hl78xx_power_off_pulse_event_handler + }, + [MODEM_HL78XX_STATE_AWAIT_POWER_OFF] = { + hl78xx_on_await_power_off_state_enter, + NULL, + hl78xx_await_power_off_event_handler + }, +}; +/* clang-format on */ +static DEVICE_API(cellular, hl78xx_api) = { + .get_signal = hl78xx_api_func_get_signal, + .get_modem_info = hl78xx_api_func_get_modem_info_standard, + .get_registration_status = hl78xx_api_func_get_registration_status, + .set_apn = hl78xx_api_func_set_apn, + .set_callback = NULL, +}; +/* ------------------------------------------------------------------------- + * Device API and DT registration + * ------------------------------------------------------------------------- + */ +#define MODEM_HL78XX_DEFINE_INSTANCE(inst, power_ms, reset_ms, startup_ms, shutdown_ms, start, \ + init_script, periodic_script) \ + static const struct hl78xx_config hl78xx_cfg_##inst = { \ + .uart = DEVICE_DT_GET(DT_INST_BUS(inst)), \ + .mdm_gpio_reset = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_reset_gpios, {}), \ + .mdm_gpio_wake = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_wake_gpios, {}), \ + .mdm_gpio_pwr_on = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_pwr_on_gpios, {}), \ + .mdm_gpio_fast_shutdown = \ + GPIO_DT_SPEC_INST_GET_OR(inst, mdm_fast_shutd_gpios, {}), \ + .mdm_gpio_uart_dtr = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_dtr_gpios, {}), \ + .mdm_gpio_uart_dsr = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_dsr_gpios, {}), \ + .mdm_gpio_uart_cts = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_uart_cts_gpios, {}), \ + .mdm_gpio_vgpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_vgpio_gpios, {}), \ + .mdm_gpio_gpio6 = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_gpio6_gpios, {}), \ + .mdm_gpio_gpio8 = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_gpio8_gpios, {}), \ + .mdm_gpio_sim_switch = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_sim_select_gpios, {}), \ + .power_pulse_duration_ms = (power_ms), \ + .reset_pulse_duration_ms = (reset_ms), \ + .startup_time_ms = (startup_ms), \ + .shutdown_time_ms = (shutdown_ms), \ + .autostarts = (start), \ + .init_chat_script = (init_script), \ + .periodic_chat_script = (periodic_script), \ + }; \ + static struct hl78xx_data hl78xx_data_##inst = { \ + .buffers.delimiter = "\r\n", \ + .buffers.eof_pattern = EOF_PATTERN, \ + .buffers.termination_pattern = TERMINATION_PATTERN, \ + }; \ + \ + PM_DEVICE_DT_INST_DEFINE(inst, hl78xx_driver_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(inst, hl78xx_init, PM_DEVICE_DT_INST_GET(inst), &hl78xx_data_##inst, \ + &hl78xx_cfg_##inst, POST_KERNEL, \ + CONFIG_MODEM_HL78XX_DEV_INIT_PRIORITY, &hl78xx_api); + +#define MODEM_DEVICE_SWIR_HL78XX(inst) \ + MODEM_HL78XX_DEFINE_INSTANCE(inst, CONFIG_MODEM_HL78XX_DEV_POWER_PULSE_DURATION, \ + CONFIG_MODEM_HL78XX_DEV_RESET_PULSE_DURATION, \ + CONFIG_MODEM_HL78XX_DEV_STARTUP_TIME, \ + CONFIG_MODEM_HL78XX_DEV_SHUTDOWN_TIME, false, NULL, NULL) + +#define DT_DRV_COMPAT swir_hl7812 +DT_INST_FOREACH_STATUS_OKAY(MODEM_DEVICE_SWIR_HL78XX) +#undef DT_DRV_COMPAT + +#define DT_DRV_COMPAT swir_hl7800 +DT_INST_FOREACH_STATUS_OKAY(MODEM_DEVICE_SWIR_HL78XX) +#undef DT_DRV_COMPAT diff --git a/drivers/modem/hl78xx/hl78xx.h b/drivers/modem/hl78xx/hl78xx.h new file mode 100644 index 0000000000000..d48f5b2d88743 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx.h @@ -0,0 +1,652 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef HL78XX_H +#define HL78XX_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../modem_context.h" +#include "../modem_socket.h" +#include + +#define MDM_CMD_TIMEOUT (10) /*K_SECONDS*/ +#define MDM_DNS_TIMEOUT (70) /*K_SECONDS*/ +#define MDM_CELL_BAND_SEARCH_TIMEOUT (60) /*K_SECONDS*/ +#define MDM_CMD_CONN_TIMEOUT (120) /*K_SECONDS*/ +#define MDM_REGISTRATION_TIMEOUT (180) /*K_SECONDS*/ +#define MDM_PROMPT_CMD_DELAY (50) /*K_MSEC*/ +#define MDM_RESET_LOW_TIME (1) /*K_MSEC*/ +#define MDM_RESET_HIGH_TIME (10) /*K_MSEC*/ +#define MDM_BOOT_TIME (12) /*K_SECONDS*/ +#define MDM_DNS_ADD_TIMEOUT (100) /*K_MSEC*/ +#define MODEM_HL78XX_PERIODIC_SCRIPT_TIMEOUT K_MSEC(CONFIG_MODEM_HL78XX_PERIODIC_SCRIPT_MS) + +#define MDM_MAX_DATA_LENGTH CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES + +#define MDM_MAX_SOCKETS CONFIG_MODEM_HL78XX_NUM_SOCKETS +#define MDM_BASE_SOCKET_NUM 1 +#define MDM_BAND_BITMAP_LEN_BYTES 32 +#define MDM_BAND_HEX_STR_LEN (MDM_BAND_BITMAP_LEN_BYTES * 2 + 1) + +#define MDM_KBND_BITMAP_MAX_ARRAY_SIZE 64 + +#define ADDRESS_FAMILY_IP "IP" +#define ADDRESS_FAMILY_IP4 "IPV4" +#define ADDRESS_FAMILY_IPV6 "IPV6" +#define ADDRESS_FAMILY_IPV4V6 "IPV4V6" +#define MDM_HL78XX_SOCKET_AF_IPV4 0 +#define MDM_HL78XX_SOCKET_AF_IPV6 1 +#if defined(CONFIG_MODEM_HL78XX_ADDRESS_FAMILY_IPV4V6) +#define MODEM_HL78XX_ADDRESS_FAMILY ADDRESS_FAMILY_IPV4V6 +#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT "####:####:####:####:####:####:####:####" +#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT_LEN \ + sizeof("a01.a02.a03.a04.a05.a06.a07.a08.a09.a10.a11.a12.a13.a14.a15.a16") +#elif defined(CONFIG_MODEM_HL78XX_ADDRESS_FAMILY_IPV4) +#define MODEM_HL78XX_ADDRESS_FAMILY ADDRESS_FAMILY_IPV4 +#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT "###.###.###.###" +#define MODEM_HL78XX_ADDRESS_FAMILY_FORMAT_LEN sizeof(MODEM_HL78XX_ADDRESS_FAMILY_FORMAT) + +#else +#define MODEM_HL78XX_ADDRESS_FAMILY ADDRESS_FAMILY_IPV6 +#endif + +/* Modem Communication Patterns */ +#define EOF_PATTERN "--EOF--Pattern--" +#define TERMINATION_PATTERN "+++" +#define CONNECT_STRING "CONNECT" +#define CME_ERROR_STRING "+CME ERROR: " +#define OK_STRING "OK" + +/* RAT (Radio Access Technology) commands */ +#define SET_RAT_M1_CMD_LEGACY "AT+KSRAT=0" +#define SET_RAT_NB1_CMD_LEGACY "AT+KSRAT=1" +#define SET_RAT_GSM_CMD_LEGACY "AT+KSRAT=2" +#define SET_RAT_NBNTN_CMD_LEGACY "AT+KSRAT=3" + +#define KSRAT_QUERY "AT+KSRAT?" +#define DISABLE_RAT_AUTO "AT+KSELACQ=0,0" + +#define SET_RAT_M1_CMD "AT+KSRAT=0,1" +#define SET_RAT_NB1_CMD "AT+KSRAT=1,1" +#define SET_RAT_GMS_CMD "AT+KSRAT=2,1" +#define SET_RAT_NBNTN_CMD "AT+KSRAT=3,1" + +/* Power mode commands */ +#define SET_AIRPLANE_MODE_CMD_LEGACY "AT+CFUN=4,0" +#define SET_AIRPLANE_MODE_CMD "AT+CFUN=4,1" +#define SET_FULLFUNCTIONAL_MODE_CMD_LEGACY "AT+CFUN=1,0" +#define SET_FULLFUNCTIONAL_MODE_CMD "AT+CFUN=1,1" +#define SET_SIM_PWR_OFF_MODE_CMD "AT+CFUN=0" +#define GET_FULLFUNCTIONAL_MODE_CMD "AT+CFUN?" +#define MDM_POWER_OFF_CMD_LEGACY "AT+CPWROFF" +#define MDM_POWER_FAST_OFF_CMD_LEGACY "AT+CPWROFF=1" +/* PDP Context commands */ +#define DEACTIVATE_PDP_CONTEXT "AT+CGACT=0" +#define ACTIVATE_PDP_CONTEXT "AT+CGACT=1" + +/* Helper macros */ +#define ATOI(s_, value_, desc_) modem_atoi(s_, value_, desc_, __func__) +#define ATOD(s_, value_, desc_) modem_atod(s_, value_, desc_, __func__) + +#define HL78XX_LOG_DBG(str, ...) \ + COND_CODE_1(CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG, \ + (LOG_DBG(str, ##__VA_ARGS__)), \ + ((void)0)) + +/* Enums */ +enum hl78xx_state { + MODEM_HL78XX_STATE_IDLE = 0, + MODEM_HL78XX_STATE_RESET_PULSE, + MODEM_HL78XX_STATE_POWER_ON_PULSE, + MODEM_HL78XX_STATE_AWAIT_POWER_ON, + MODEM_HL78XX_STATE_SET_BAUDRATE, + MODEM_HL78XX_STATE_RUN_INIT_SCRIPT, + MODEM_HL78XX_STATE_RUN_INIT_FAIL_DIAGNOSTIC_SCRIPT, + MODEM_HL78XX_STATE_RUN_RAT_CONFIG_SCRIPT, + MODEM_HL78XX_STATE_RUN_ENABLE_GPRS_SCRIPT, + /* Full functionality, searching + * CFUN=1 + */ + MODEM_HL78XX_STATE_AWAIT_REGISTERED, + MODEM_HL78XX_STATE_CARRIER_ON, + /* Minimum functionality, SIM powered off, Modem Power down + * CFUN=0 + */ + MODEM_HL78XX_STATE_CARRIER_OFF, + MODEM_HL78XX_STATE_SIM_POWER_OFF, + /* Minimum functionality / Airplane mode + * Sim still powered on + * CFUN=4 + */ + MODEM_HL78XX_STATE_AIRPLANE, + MODEM_HL78XX_STATE_INIT_POWER_OFF, + MODEM_HL78XX_STATE_POWER_OFF_PULSE, + MODEM_HL78XX_STATE_AWAIT_POWER_OFF, +}; + +enum hl78xx_event { + MODEM_HL78XX_EVENT_RESUME = 0, + MODEM_HL78XX_EVENT_SUSPEND, + MODEM_HL78XX_EVENT_SCRIPT_SUCCESS, + MODEM_HL78XX_EVENT_SCRIPT_FAILED, + MODEM_HL78XX_EVENT_SCRIPT_REQUIRE_RESTART, + MODEM_HL78XX_EVENT_TIMEOUT, + MODEM_HL78XX_EVENT_REGISTERED, + MODEM_HL78XX_EVENT_DEREGISTERED, + MODEM_HL78XX_EVENT_BUS_OPENED, + MODEM_HL78XX_EVENT_BUS_CLOSED, + MODEM_HL78XX_EVENT_SOCKET_READY, +}; + +enum hl78xx_tcp_notif { + TCP_NOTIF_NETWORK_ERROR = 0, + TCP_NOTIF_NO_MORE_SOCKETS = 1, + TCP_NOTIF_MEMORY_PROBLEM = 2, + TCP_NOTIF_DNS_ERROR = 3, + TCP_NOTIF_REMOTE_DISCONNECTION = 4, + TCP_NOTIF_CONNECTION_ERROR = 5, + TCP_NOTIF_GENERIC_ERROR = 6, + TCP_NOTIF_ACCEPT_FAILED = 7, + TCP_NOTIF_SEND_MISMATCH = 8, + TCP_NOTIF_BAD_SESSION_ID = 9, + TCP_NOTIF_SESSION_ALREADY_RUNNING = 10, + TCP_NOTIF_ALL_SESSIONS_USED = 11, + TCP_NOTIF_CONNECTION_TIMEOUT = 12, + TCP_NOTIF_SSL_CONNECTION_ERROR = 13, + TCP_NOTIF_SSL_INIT_ERROR = 14, + TCP_NOTIF_SSL_CERT_ERROR = 15 +}; +/** Enum representing information transfer capability events */ +enum hl78xx_info_transfer_event { + EVENT_START_SCAN = 0, + EVENT_FAIL_SCAN, + EVENT_ENTER_CAMPED, + EVENT_CONNECTION_ESTABLISHMENT, + EVENT_START_RESCAN, + EVENT_RRC_CONNECTED, + EVENT_NO_SUITABLE_CELLS, + EVENT_ALL_REGISTRATION_FAILED +}; + +struct kselacq_syntax { + bool mode; + enum hl78xx_cell_rat_mode rat1; + enum hl78xx_cell_rat_mode rat2; + enum hl78xx_cell_rat_mode rat3; +}; + +struct kband_syntax { + uint8_t rat; + /* Max 64 digits representation format is supported + * i.e: LTE Band 256 (2000MHz) : + * 80000000 00000000 00000000 00000000 + * 00000000 00000000 00000000 00000000 + * + + * NULL terminate + */ + uint8_t bnd_bitmap[MDM_BAND_HEX_STR_LEN]; +}; + +enum apn_state_enum_t { + APN_STATE_NOT_CONFIGURED = 0, + APN_STATE_CONFIGURED, + APN_STATE_REFRESH_REQUESTED, + APN_STATE_REFRESH_IN_PROGRESS, + APN_STATE_REFRESH_COMPLETED, +}; + +struct apn_state { + enum apn_state_enum_t state; +}; +struct registration_status { + bool is_registered_currently; + bool is_registered_previously; + enum cellular_registration_status network_state_current; + enum cellular_registration_status network_state_previous; + enum hl78xx_cell_rat_mode rat_mode; +}; +/* driver data */ +struct modem_buffers { + uint8_t uart_rx[CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES]; + uint8_t uart_tx[CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES]; + uint8_t chat_rx[CONFIG_MODEM_HL78XX_CHAT_BUFFER_SIZES]; + uint8_t *delimiter; + uint8_t *filter; + uint8_t *argv[32]; + uint8_t *eof_pattern; + uint8_t eof_pattern_size; + uint8_t *termination_pattern; + uint8_t termination_pattern_size; +}; + +struct modem_identity { + uint8_t imei[MDM_IMEI_LENGTH]; + uint8_t model_id[MDM_MODEL_LENGTH]; + uint8_t imsi[MDM_IMSI_LENGTH]; + uint8_t iccid[MDM_ICCID_LENGTH]; + uint8_t manufacturer[MDM_MANUFACTURER_LENGTH]; + uint8_t fw_version[MDM_REVISION_LENGTH]; + char apn[MDM_APN_MAX_LENGTH]; +}; +struct hl78xx_phone_functionality_work { + enum hl78xx_phone_functionality functionality; + bool in_progress; +}; + +struct hl78xx_network_operator { + char operator[MDM_MODEL_LENGTH]; + uint8_t format; +}; + +struct modem_status { + struct registration_status registration; + int16_t rssi; + uint8_t ksrep; + int16_t rsrp; + int16_t rsrq; + uint16_t script_fail_counter; + int variant; + enum hl78xx_state state; + struct kband_syntax kbndcfg[HL78XX_RAT_COUNT]; + struct hl78xx_phone_functionality_work phone_functionality; + struct apn_state apn; + struct hl78xx_network_operator network_operator; +}; + +struct modem_gpio_callbacks { + struct gpio_callback vgpio_cb; + struct gpio_callback uart_dsr_cb; + struct gpio_callback gpio6_cb; + struct gpio_callback uart_cts_cb; +}; + +struct modem_event_system { + struct k_work event_dispatch_work; + uint8_t event_buf[8]; + struct ring_buf event_rb; + struct k_mutex event_rb_lock; +}; + +struct hl78xx_data { + struct modem_pipe *uart_pipe; + struct modem_backend_uart uart_backend; + struct modem_chat chat; + + struct k_mutex tx_lock; + struct k_mutex api_lock; + struct k_sem script_stopped_sem_tx_int; + struct k_sem script_stopped_sem_rx_int; + struct k_sem suspended_sem; +#ifdef CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING + struct k_sem stay_in_boot_mode_sem; +#endif /* CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING */ + + struct modem_buffers buffers; + struct modem_identity identity; + struct modem_status status; + struct modem_gpio_callbacks gpio_cbs; + struct modem_event_system events; + struct k_work_delayable timeout_work; + /* Track leftover socket data state previously stored as a TU-global. + * Moving this into the per-modem data reduces global BSS and keeps + * state colocated with the modem instance. + */ + atomic_t state_leftover; +#if defined(CONFIG_MODEM_HL78XX_RSSI_WORK) + struct k_work_delayable rssi_query_work; +#endif + + const struct device *dev; + /* GNSS device */ + const struct device *gnss_dev; + /* Offload device */ + const struct device *offload_dev; + + struct kselacq_syntax kselacq_data; +}; + +struct hl78xx_config { + const struct device *uart; + struct gpio_dt_spec mdm_gpio_reset; + struct gpio_dt_spec mdm_gpio_wake; + struct gpio_dt_spec mdm_gpio_pwr_on; + struct gpio_dt_spec mdm_gpio_vgpio; + struct gpio_dt_spec mdm_gpio_uart_cts; + struct gpio_dt_spec mdm_gpio_gpio6; + struct gpio_dt_spec mdm_gpio_fast_shutdown; + struct gpio_dt_spec mdm_gpio_uart_dtr; + struct gpio_dt_spec mdm_gpio_uart_dsr; + struct gpio_dt_spec mdm_gpio_gpio8; + struct gpio_dt_spec mdm_gpio_sim_switch; + uint16_t power_pulse_duration_ms; + uint16_t reset_pulse_duration_ms; + uint16_t startup_time_ms; + uint16_t shutdown_time_ms; + + bool autostarts; + + const struct modem_chat_script *init_chat_script; + const struct modem_chat_script *periodic_chat_script; +}; +/* socket read callback data */ +struct socket_read_data { + char *recv_buf; + size_t recv_buf_len; + struct sockaddr *recv_addr; + uint16_t recv_read_len; +}; + +/** + * @brief Check if the cellular modem is registered on the network. + * + * This function checks the modem's current registration status and + * returns true if the device is registered with a cellular network. + * + * @param data Pointer to the modem HL78xx driver data structure. + * + * @retval true if the modem is registered. + * @retval false otherwise. + */ +bool hl78xx_is_registered(struct hl78xx_data *data); + +/** + * @brief DNS resolution work callback. + * + * @param dev Pointer to the device structure. + * @param hard_reset Boolean indicating if a hard reset is required. + * Should be used internally to handle DNS resolution events. + */ +void dns_work_cb(const struct device *dev, bool hard_reset); + +/** + * @brief Callback to update and handle network interface status. + * + * This function is typically scheduled as work to check and respond to changes + * in the modem's network interface state, such as registration, link readiness, + * or disconnection events. + * + * @param data Pointer to the modem HL78xx driver data structure. + */ +void iface_status_work_cb(struct hl78xx_data *data, + modem_chat_script_callback script_user_callback); + +/** + * @brief Send a command to the modem and wait for matching response(s). + * + * This function sends a raw command to the modem and processes its response using + * the provided match patterns. It supports asynchronous notification via callback. + * + * @param data Pointer to the modem HL78xx driver data structure. + * @param script_user_callback Callback function invoked on matched responses or errors. + * @param cmd Pointer to the command buffer to send. + * @param cmd_len Length of the command in bytes. + * @param response_matches Array of expected response match patterns. + * @param matches_size Number of elements in the response_matches array. + * + * @return 0 on success, negative errno code on failure. + */ +int modem_dynamic_cmd_send(struct hl78xx_data *data, + modem_chat_script_callback script_user_callback, const uint8_t *cmd, + uint16_t cmd_len, const struct modem_chat_match *response_matches, + uint16_t matches_size, bool user_cmd); + +#define HASH_MULTIPLIER 37 +/** + * @brief Generate a 32-bit hash from a string. + * + * Useful for generating identifiers (e.g., MAC address suffix) from a given string. + * + * @param str Input string to hash. + * @param len Length of the input string. + * + * @return 32-bit hash value. + */ +static inline uint32_t hash32(const char *str, int len) +{ + uint32_t h = 0; + + for (int i = 0; i < len; ++i) { + h = (h * HASH_MULTIPLIER) + str[i]; + } + return h; +} + +/** + * @brief Generate a pseudo-random MAC address based on the modem's IMEI. + * + * This function creates a MAC address using a fixed prefix and a hash of the IMEI. + * The resulting address is consistent for the same IMEI and suitable for use + * in virtual or emulated network interfaces. + * + * @param mac_addr Pointer to a 6-byte buffer where the generated MAC address will be stored. + * @param imei Null-terminated string containing the modem's IMEI. + * + * @return Pointer to the MAC address buffer. + */ +static inline uint8_t *modem_get_mac(uint8_t *mac_addr, char *imei) +{ + uint32_t hash_value; + /* Define MAC address prefix */ + mac_addr[0] = 0x00; + mac_addr[1] = 0x10; + + /* Generate MAC address based on IMEI */ + hash_value = hash32(imei, strlen(imei)); + UNALIGNED_PUT(hash_value, (uint32_t *)(mac_addr + 2)); + + return mac_addr; +} + +/** + * @brief Convert string to long integer, but handle errors + * + * @param s: string with representation of integer number + * @param err_value: on error return this value instead + * @param desc: name the string being converted + * @param func: function where this is called (typically __func__) + * + * @retval return integer conversion on success, or err_value on error + */ +static inline int modem_atoi(const char *s, const int err_value, const char *desc, const char *func) +{ + int ret; + char *endptr; + + ret = (int)strtol(s, &endptr, 10); + if (!endptr || *endptr != '\0') { + return err_value; + } + return ret; +} + +/** + * @brief Convert a string to an double with error handling. + * + * Similar to atoi, but allows specifying an error fallback and logs errors. + * + * @param s Input string to convert. + * @param err_value Value to return on failure. + * @param desc Description of the value for logging purposes. + * @param func Function name for logging purposes. + * + * @return Converted double on success, or err_value on failure. + */ +static inline double modem_atod(const char *s, const double err_value, const char *desc, + const char *func) +{ + double ret; + char *endptr; + + ret = strtod(s, &endptr); + if (!endptr || *endptr != '\0') { + return err_value; + } + return ret; +} +/** + * @brief Small utility: safe strncpy that always NUL-terminates the destination. + * This function copies a string from src to dst, ensuring that the destination + * buffer is always NUL-terminated, even if the source string is longer than + * the destination buffer. + * @param dst Destination buffer. + * @param src Source string. + * @param dst_size Size of the destination buffer. + */ +static inline void safe_strncpy(char *dst, const char *src, size_t dst_size) +{ + size_t len = 0; + + if (dst == NULL || dst_size == 0) { + return; + } + if (src == NULL) { + dst[0] = '\0'; + return; + } + len = strlen(src); + if (len >= dst_size) { + len = dst_size - 1; + } + memcpy(dst, src, len); + dst[len] = '\0'; +} + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG +/** + * @brief Handle modem state update from +KSTATE URC (unsolicited result code). + * + * This function is called when a +KSTATE URC is received, indicating a change + * in the modem's internal state. It updates the modem driver's state machine + * accordingly. + * + * @param data Pointer to the HL78xx modem driver data structure. + * @param state Integer value representing the new modem state as reported by the URC. + */ +void hl78xx_on_kstatev_parser(struct hl78xx_data *data, int state, int rat_mode); +#endif + +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) || defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI) +/** + * @brief Automatically detect and configure the modem's APN setting. + * + * Uses internal logic to determine the correct APN based on the modem's context + * and network registration information. + * + * @param data Pointer to the modem HL78xx driver data structure. + * @param associated_number Identifier (e.g., MCCMNC or IMSI) used for APN detection. + * + * @return 0 on success, negative errno code on failure. + */ +int modem_detect_apn(struct hl78xx_data *data, const char *associated_number); +#endif +/** + * @brief Get the default band configuration in hexadecimal string format for each band. + * + * Retrieves the modem's default band configuration as a hex string, + * used for configuring or restoring band settings. + * + * @param rat The radio access technology mode for which to get the band configuration. + * @param hex_bndcfg Buffer to store the resulting hex band configuration string. + * @param size_in_bytes Size of the buffer in bytes. + * + * @retval 0 on success. + * @retval Negative errno code on failure. + */ +int hl78xx_get_band_default_config_for_rat(enum hl78xx_cell_rat_mode rat, char *hex_bndcfg, + size_t size_in_bytes); + +/** + * @brief Convert a hexadecimal string to a binary bitmap. + * + * Parses a hexadecimal string and converts it into a binary bitmap array. + * + * @param hex_str Null-terminated string containing hexadecimal data. + * @param bitmap_out Output buffer to hold the resulting binary bitmap. + * + * @retval 0 on success. + * @retval Negative errno code on failure (e.g., invalid characters, overflow). + */ +int hl78xx_hex_string_to_bitmap(const char *hex_str, uint8_t *bitmap_out); + +/** + * @brief hl78xx_api_func_get_registration_status - Brief description of the function. + * @param dev Description of dev. + * @param tech Description of tech. + * @param status Description of status. + * @return int Description of return value. + */ +int hl78xx_api_func_get_registration_status(const struct device *dev, + enum cellular_access_technology tech, + enum cellular_registration_status *status); + +/** + * @brief hl78xx_api_func_set_apn - Brief description of the function. + * @param dev Description of dev. + * @param apn Description of apn. + * @return int Description of return value. + */ +int hl78xx_api_func_set_apn(const struct device *dev, const char *apn); + +/** + * @brief hl78xx_api_func_get_modem_info_standard - Brief description of the function. + * @param dev Description of dev. + * @param type Description of type. + * @param info Description of info. + * @param size Description of size. + * @return int Description of return value. + */ +int hl78xx_api_func_get_modem_info_standard(const struct device *dev, + enum cellular_modem_info_type type, char *info, + size_t size); + +/** + * @brief hl78xx_enter_state - Brief description of the function. + * @param data Description of data. + * @param state Description of state. + */ +void hl78xx_enter_state(struct hl78xx_data *data, enum hl78xx_state state); + +/** + * @brief hl78xx_delegate_event - Brief description of the function. + * @param data Description of data. + * @param evt Description of evt. + */ +void hl78xx_delegate_event(struct hl78xx_data *data, enum hl78xx_event evt); + +/** + * @brief notif_carrier_off - Brief description of the function. + * @param dev Description of dev. + */ +void notif_carrier_off(const struct device *dev); + +/** + * @brief notif_carrier_on - Brief description of the function. + * @param dev Description of dev. + */ +void notif_carrier_on(const struct device *dev); + +/** + * @brief check_if_any_socket_connected - Brief description of the function. + * @param dev Description of dev. + * @return int Description of return value. + */ +int check_if_any_socket_connected(const struct device *dev); + +#endif /* HL78XX_H */ diff --git a/drivers/modem/hl78xx/hl78xx_apis.c b/drivers/modem/hl78xx/hl78xx_apis.c new file mode 100644 index 0000000000000..e1af412b1730c --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_apis.c @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include +#include +#include +#include +#include +#include "hl78xx.h" +#include "hl78xx_chat.h" + +LOG_MODULE_REGISTER(hl78xx_apis, CONFIG_MODEM_LOG_LEVEL); + +/* Wrapper to centralize modem_dynamic_cmd_send calls and reduce repetition. + * returns negative errno on failure or the value returned by modem_dynamic_cmd_send. + */ +static int hl78xx_send_cmd(struct hl78xx_data *data, const char *cmd, + void (*chat_cb)(struct modem_chat *, enum modem_chat_script_result, + void *), + const struct modem_chat_match *matches, uint16_t match_count) +{ + if (data == NULL || cmd == NULL) { + return -EINVAL; + } + return modem_dynamic_cmd_send(data, chat_cb, cmd, (uint16_t)strlen(cmd), matches, + match_count, true); +} + +int hl78xx_api_func_get_signal(const struct device *dev, const enum cellular_signal_type type, + int16_t *value) +{ + int ret = -ENOTSUP; + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + const char *signal_cmd_csq = "AT+CSQ"; + const char *signal_cmd_cesq = "AT+CESQ"; + + /* quick check of state under api_lock */ + k_mutex_lock(&data->api_lock, K_FOREVER); + if (data->status.state != MODEM_HL78XX_STATE_CARRIER_ON) { + k_mutex_unlock(&data->api_lock); + return -ENODATA; + } + k_mutex_unlock(&data->api_lock); + + /* Run chat script */ + switch (type) { + case CELLULAR_SIGNAL_RSSI: + ret = hl78xx_send_cmd(data, signal_cmd_csq, NULL, hl78xx_get_allow_match(), + hl78xx_get_allow_match_size()); + break; + + case CELLULAR_SIGNAL_RSRP: + case CELLULAR_SIGNAL_RSRQ: + ret = hl78xx_send_cmd(data, signal_cmd_cesq, NULL, hl78xx_get_allow_match(), + hl78xx_get_allow_match_size()); + break; + + default: + ret = -ENOTSUP; + break; + } + /* Verify chat script ran successfully */ + if (ret < 0) { + return ret; + } + /* Parse received value */ + switch (type) { + case CELLULAR_SIGNAL_RSSI: + ret = hl78xx_parse_rssi(data->status.rssi, value); + break; + + case CELLULAR_SIGNAL_RSRP: + ret = hl78xx_parse_rsrp(data->status.rsrp, value); + break; + + case CELLULAR_SIGNAL_RSRQ: + ret = hl78xx_parse_rsrq(data->status.rsrq, value); + break; + + default: + ret = -ENOTSUP; + break; + } + return ret; +} + +/** Convert hl78xx RAT mode to cellular access technology */ +enum cellular_access_technology hl78xx_rat_to_access_tech(enum hl78xx_cell_rat_mode rat_mode) +{ + switch (rat_mode) { + case HL78XX_RAT_CAT_M1: + return CELLULAR_ACCESS_TECHNOLOGY_E_UTRAN; + case HL78XX_RAT_NB1: + return CELLULAR_ACCESS_TECHNOLOGY_E_UTRAN_NB_S1; +#ifdef CONFIG_MODEM_HL78XX_12 + case HL78XX_RAT_GSM: + return CELLULAR_ACCESS_TECHNOLOGY_GSM; +#ifdef CONFIG_MODEM_HL78XX_12_FW_R6 + case HL78XX_RAT_NBNTN: + /** NBNTN might not have a direct mapping; choose closest or define new */ + return CELLULAR_ACCESS_TECHNOLOGY_NG_RAN_SAT; +#endif +#endif +#ifdef CONFIG_MODEM_HL78XX_AUTORAT + case HL78XX_RAT_MODE_AUTO: + /** AUTO mode doesn't map directly; return LTE as default or NONE */ + return CELLULAR_ACCESS_TECHNOLOGY_E_UTRAN; +#endif + case HL78XX_RAT_MODE_NONE: + default: + return -ENODATA; + } +} + +int hl78xx_api_func_get_registration_status(const struct device *dev, + enum cellular_access_technology tech, + enum cellular_registration_status *status) +{ + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + + if (status == NULL) { + return -EINVAL; + } + LOG_DBG("Requested tech: %d, current rat mode: %d REG: %d %d", tech, + data->status.registration.rat_mode, data->status.registration.network_state_current, + hl78xx_rat_to_access_tech(data->status.registration.rat_mode)); + if (tech != hl78xx_rat_to_access_tech(data->status.registration.rat_mode)) { + return -ENODATA; + } + k_mutex_lock(&data->api_lock, K_FOREVER); + *status = data->status.registration.network_state_current; + k_mutex_unlock(&data->api_lock); + return 0; +} + +int hl78xx_api_func_get_modem_info_vendor(const struct device *dev, + enum hl78xx_modem_info_type type, void *info, size_t size) +{ + int ret = 0; + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + const char *network_operator = "AT+COPS?"; + + if (info == NULL || size == 0) { + return -EINVAL; + } + /* copy identity under api lock to a local buffer then write to caller + * prevents holding lock during the return/caller access + */ + k_mutex_lock(&data->api_lock, K_FOREVER); + switch (type) { + case HL78XX_MODEM_INFO_APN: + if (data->status.apn.state != APN_STATE_CONFIGURED) { + ret = -ENODATA; + break; + } + safe_strncpy(info, (const char *)data->identity.apn, size); + break; + + case HL78XX_MODEM_INFO_CURRENT_RAT: + *(enum hl78xx_cell_rat_mode *)info = data->status.registration.rat_mode; + break; + + case HL78XX_MODEM_INFO_NETWORK_OPERATOR: + /* Network operator not currently tracked; return empty or implement tracking */ + ret = hl78xx_send_cmd(data, network_operator, NULL, hl78xx_get_allow_match(), + hl78xx_get_allow_match_size()); + if (ret < 0) { + LOG_ERR("Failed to get network operator"); + } + safe_strncpy(info, (const char *)data->status.network_operator.operator, + MIN(size, sizeof(data->status.network_operator.operator))); + break; + + default: + break; + } + k_mutex_unlock(&data->api_lock); + return ret; +} + +int hl78xx_api_func_get_modem_info_standard(const struct device *dev, + enum cellular_modem_info_type type, char *info, + size_t size) +{ + int ret = 0; + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + + if (info == NULL || size == 0) { + return -EINVAL; + } + /* copy identity under api lock to a local buffer then write to caller + * prevents holding lock during the return/caller access + */ + k_mutex_lock(&data->api_lock, K_FOREVER); + switch (type) { + case CELLULAR_MODEM_INFO_IMEI: + safe_strncpy(info, (const char *)data->identity.imei, + MIN(size, sizeof(data->identity.imei))); + break; + case CELLULAR_MODEM_INFO_SIM_IMSI: + safe_strncpy(info, (const char *)data->identity.imsi, + MIN(size, sizeof(data->identity.imsi))); + break; + case CELLULAR_MODEM_INFO_MANUFACTURER: + safe_strncpy(info, (const char *)data->identity.manufacturer, + MIN(size, sizeof(data->identity.manufacturer))); + break; + case CELLULAR_MODEM_INFO_FW_VERSION: + safe_strncpy(info, (const char *)data->identity.fw_version, + MIN(size, sizeof(data->identity.fw_version))); + break; + case CELLULAR_MODEM_INFO_MODEL_ID: + safe_strncpy(info, (const char *)data->identity.model_id, + MIN(size, sizeof(data->identity.model_id))); + break; + case CELLULAR_MODEM_INFO_SIM_ICCID: + safe_strncpy(info, (const char *)data->identity.iccid, + MIN(size, sizeof(data->identity.iccid))); + break; + default: + ret = -ENOTSUP; + break; + } + k_mutex_unlock(&data->api_lock); + return ret; +} + +int hl78xx_api_func_set_apn(const struct device *dev, const char *apn) +{ + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + /** + * Validate APN + * APN can be empty string to clear it + * to request it from network + * If the value is null or omitted, then the subscription + * value will be requested + */ + if (apn == NULL) { + return -EINVAL; + } + if (strlen(apn) >= MDM_APN_MAX_LENGTH) { + return -EINVAL; + } + /* Update in-memory APN under api lock */ + k_mutex_lock(&data->api_lock, K_FOREVER); + safe_strncpy(data->identity.apn, apn, sizeof(data->identity.apn)); + data->status.apn.state = APN_STATE_REFRESH_REQUESTED; + k_mutex_unlock(&data->api_lock); + hl78xx_enter_state(data, MODEM_HL78XX_STATE_CARRIER_OFF); + return 0; +} + +int hl78xx_api_func_set_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality functionality, + bool reset) +{ + char cmd_string[sizeof(SET_FULLFUNCTIONAL_MODE_CMD) + sizeof(int)] = {0}; + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + /* configure modem functionality with/without restart */ + snprintf(cmd_string, sizeof(cmd_string), "AT+CFUN=%d,%d", functionality, reset); + return hl78xx_send_cmd(data, cmd_string, NULL, hl78xx_get_ok_match(), 1); +} + +int hl78xx_api_func_get_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality *functionality) +{ + const char *cmd_string = GET_FULLFUNCTIONAL_MODE_CMD; + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + /* get modem phone functionality */ + return hl78xx_send_cmd(data, cmd_string, NULL, hl78xx_get_ok_match(), 1); +} + +int hl78xx_api_func_modem_dynamic_cmd_send(const struct device *dev, const char *cmd, + uint16_t cmd_size, + const struct modem_chat_match *response_matches, + uint16_t matches_size) +{ + struct hl78xx_data *data = (struct hl78xx_data *)dev->data; + + if (cmd == NULL) { + return -EINVAL; + } + /* respect provided matches_size and serialize modem access */ + return modem_dynamic_cmd_send(data, NULL, cmd, cmd_size, response_matches, matches_size, + true); +} diff --git a/drivers/modem/hl78xx/hl78xx_cfg.c b/drivers/modem/hl78xx/hl78xx_cfg.c new file mode 100644 index 0000000000000..461d066865b78 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_cfg.c @@ -0,0 +1,587 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * hl78xx_cfg.c + * + * Extracted helper implementations for RAT, band and APN configuration to + * keep the main state-machine TU small and maintainable. + */ +#include "hl78xx.h" +#include "hl78xx_cfg.h" +#include "hl78xx_chat.h" +#include + +LOG_MODULE_DECLARE(hl78xx_dev); + +#define ICCID_PREFIX_LEN 7 +#define IMSI_PREFIX_LEN 6 +#define MAX_BANDS 32 +#define MDM_APN_FULL_STRING_MAX_LEN 256 + +int hl78xx_rat_cfg(struct hl78xx_data *data, bool *modem_require_restart, + enum hl78xx_cell_rat_mode *rat_request) +{ + int ret = 0; + +#if defined(CONFIG_MODEM_HL78XX_AUTORAT) + /* Check autorat status/configs */ + if (IS_ENABLED(CONFIG_MODEM_HL78XX_AUTORAT_OVER_WRITE_PRL) || + (data->kselacq_data.rat1 == 0 && data->kselacq_data.rat2 == 0 && + data->kselacq_data.rat3 == 0)) { + char cmd_kselq[] = "AT+KSELACQ=0," CONFIG_MODEM_HL78XX_AUTORAT_PRL_PROFILES; + + ret = modem_dynamic_cmd_send(data, NULL, cmd_kselq, strlen(cmd_kselq), + hl78xx_get_ok_match(), 1, false); + if (ret < 0) { + goto error; + } else { + *modem_require_restart = true; + } + } + + *rat_request = HL78XX_RAT_MODE_AUTO; +#else + char const *cmd_ksrat_query = (const char *)KSRAT_QUERY; + char const *cmd_kselq_disable = (const char *)DISABLE_RAT_AUTO; + const char *cmd_set_rat = NULL; + /* Check if auto rat are disabled */ + if (data->kselacq_data.rat1 != 0 && data->kselacq_data.rat2 != 0 && + data->kselacq_data.rat3 != 0) { + ret = modem_dynamic_cmd_send(data, NULL, cmd_kselq_disable, + strlen(cmd_kselq_disable), hl78xx_get_ok_match(), 1, + false); + if (ret < 0) { + goto error; + } + } + /* Query current rat */ + ret = modem_dynamic_cmd_send(data, NULL, cmd_ksrat_query, strlen(cmd_ksrat_query), + hl78xx_get_ksrat_match(), 1, false); + if (ret < 0) { + goto error; + } + +#if !defined(CONFIG_MODEM_HL78XX_RAT_M1) && !defined(CONFIG_MODEM_HL78XX_RAT_NB1) && \ + !defined(CONFIG_MODEM_HL78XX_RAT_GSM) && !defined(CONFIG_MODEM_HL78XX_RAT_NBNTN) +#error "No rat has been selected." +#endif + + if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_M1)) { + cmd_set_rat = (const char *)SET_RAT_M1_CMD_LEGACY; + *rat_request = HL78XX_RAT_CAT_M1; + } else if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_NB1)) { + cmd_set_rat = (const char *)SET_RAT_NB1_CMD_LEGACY; + *rat_request = HL78XX_RAT_NB1; + } +#ifdef CONFIG_MODEM_HL78XX_12 + else if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_GSM)) { + cmd_set_rat = (const char *)SET_RAT_GSM_CMD_LEGACY; + *rat_request = HL78XX_RAT_GSM; + } +#ifdef CONFIG_MODEM_HL78XX_12_FW_R6 + else if (IS_ENABLED(CONFIG_MODEM_HL78XX_RAT_NBNTN)) { + cmd_set_rat = (const char *)SET_RAT_NBNTN_CMD_LEGACY; + *rat_request = HL78XX_RAT_NBNTN; + } +#endif +#endif + + if (cmd_set_rat == NULL || *rat_request == HL78XX_RAT_MODE_NONE) { + ret = -EINVAL; + goto error; + } + + if (*rat_request != data->status.registration.rat_mode) { + ret = modem_dynamic_cmd_send(data, NULL, cmd_set_rat, strlen(cmd_set_rat), + hl78xx_get_ok_match(), 1, false); + if (ret < 0) { + goto error; + } else { + *modem_require_restart = true; + } + } +#endif + +error: + return ret; +} + +int hl78xx_band_cfg(struct hl78xx_data *data, bool *modem_require_restart, + enum hl78xx_cell_rat_mode rat_config_request) +{ + int ret = 0; + char bnd_bitmap[MDM_BAND_HEX_STR_LEN] = {0}; + const char *modem_trimmed; + const char *expected_trimmed; + + if (rat_config_request == HL78XX_RAT_MODE_NONE) { + return -EINVAL; + } +#ifdef CONFIG_MODEM_HL78XX_AUTORAT + for (int rat = HL78XX_RAT_CAT_M1; rat <= HL78XX_RAT_NB1; rat++) { +#else + int rat = rat_config_request; + +#endif + ret = hl78xx_get_band_default_config_for_rat(rat, bnd_bitmap, + ARRAY_SIZE(bnd_bitmap)); + if (ret) { + LOG_ERR("%d %s error get band default config %d", __LINE__, __func__, ret); + goto error; + } + modem_trimmed = hl78xx_trim_leading_zeros(data->status.kbndcfg[rat].bnd_bitmap); + expected_trimmed = hl78xx_trim_leading_zeros(bnd_bitmap); + + if (strcmp(modem_trimmed, expected_trimmed) != 0) { + char cmd_bnd[80] = {0}; + + snprintf(cmd_bnd, sizeof(cmd_bnd), "AT+KBNDCFG=%d,%s", rat, bnd_bitmap); + ret = modem_dynamic_cmd_send(data, NULL, cmd_bnd, strlen(cmd_bnd), + hl78xx_get_ok_match(), 1, false); + if (ret < 0) { + goto error; + } else { + *modem_require_restart |= true; + } + } else { + LOG_DBG("The band configs (%s) matched with exist configs (%s) for rat: " + "[%d]", + modem_trimmed, expected_trimmed, rat); + } +#ifdef CONFIG_MODEM_HL78XX_AUTORAT + } +#endif +error: + return ret; +} + +int hl78xx_set_apn_internal(struct hl78xx_data *data, const char *apn, uint16_t size) +{ + int ret = 0; + char cmd_string[sizeof("AT+KCNXCFG=,\"\",\"\"") + sizeof(uint8_t) + + MODEM_HL78XX_ADDRESS_FAMILY_FORMAT_LEN + MDM_APN_MAX_LENGTH] = {0}; + int cmd_max_len = sizeof(cmd_string) - 1; + int apn_size = strlen(apn); + + if (apn == NULL || size >= MDM_APN_MAX_LENGTH) { + return -EINVAL; + } + + k_mutex_lock(&data->api_lock, K_FOREVER); + if (strncmp(data->identity.apn, apn, apn_size) != 0) { + safe_strncpy(data->identity.apn, apn, sizeof(data->identity.apn)); + } + k_mutex_unlock(&data->api_lock); + + snprintk(cmd_string, cmd_max_len, "AT+CGDCONT=1,\"%s\",\"%s\"", MODEM_HL78XX_ADDRESS_FAMILY, + apn); + + ret = modem_dynamic_cmd_send(data, NULL, cmd_string, strlen(cmd_string), + hl78xx_get_ok_match(), 1, false); + if (ret < 0) { + goto error; + } + snprintk(cmd_string, cmd_max_len, + "AT+KCNXCFG=1,\"GPRS\",\"%s\",,,\"" MODEM_HL78XX_ADDRESS_FAMILY "\"", apn); + ret = modem_dynamic_cmd_send(data, NULL, cmd_string, strlen(cmd_string), + hl78xx_get_ok_match(), 1, false); + if (ret < 0) { + goto error; + } + data->status.apn.state = APN_STATE_CONFIGURED; + return 0; +error: + LOG_ERR("Set APN to %s, result: %d", apn, ret); + return ret; +} + +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID) || defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI) +int find_apn(const char *profile, const char *associated_number, char *apn_buff, uint8_t prefix_len) +{ + char buffer[512]; + char *saveptr; + + if (prefix_len > strlen(associated_number)) { + return -1; + } + + strncpy(buffer, profile, sizeof(buffer) - 1); + buffer[sizeof(buffer) - 1] = '\0'; + + char *token = strtok_r(buffer, ",", &saveptr); + + while (token != NULL) { + char *equal_sign = strchr(token, '='); + + if (equal_sign != NULL) { + *equal_sign = '\0'; + char *p_apn = token; + char *associated_number_prefix = equal_sign + 1; + + /* Trim leading whitespace */ + while (*p_apn == ' ') { + p_apn++; + } + while (*associated_number_prefix == ' ') { + associated_number_prefix++; + } + if (strncmp(associated_number, associated_number_prefix, prefix_len) == 0) { + strncpy(apn_buff, p_apn, MDM_APN_MAX_LENGTH - 1); + apn_buff[MDM_APN_MAX_LENGTH - 1] = '\0'; + return 0; + } + } + token = strtok_r(NULL, ",", &saveptr); + } + /* No match found, clear apn_buff */ + apn_buff[0] = '\0'; + return -1; /* not found */ +} + +/* try to detect APN automatically, based on IMSI / ICCID */ +int modem_detect_apn(struct hl78xx_data *data, const char *associated_number) +{ + int rc = -1; + + if (associated_number != NULL && strlen(associated_number) >= 5) { +/* extract MMC and MNC from IMSI */ +#if defined(CONFIG_MODEM_HL78XX_APN_SOURCE_IMSI) + /* + * First 5 digits (e.g. 31026) → often sufficient to identify carrier. + * However, in some regions (like the US), MNCs can be 3 digits (e.g. 310260). + */ + char mmcmnc[7] = {0}; /* IMSI */ +#define APN_PREFIX_LEN IMSI_PREFIX_LEN +#else + /* These 7 digits are generally sufficient to identify the SIM provider. + */ + char mmcmnc[8] = {0}; /* ICCID */ +#define APN_PREFIX_LEN ICCID_PREFIX_LEN +#endif + strncpy(mmcmnc, associated_number, sizeof(mmcmnc) - 1); + mmcmnc[sizeof(mmcmnc) - 1] = '\0'; + /* try to find a matching IMSI/ICCID, and assign the APN */ + rc = find_apn(CONFIG_MODEM_HL78XX_APN_PROFILES, mmcmnc, data->identity.apn, + APN_PREFIX_LEN); + if (rc < 0) { + LOG_ERR("%d %s APN Parser error %d", __LINE__, __func__, rc); + } + } + if (rc == 0) { + LOG_INF("Assign APN: \"%s\"", data->identity.apn); + } else { + LOG_INF("No assigned APN: \"%d\"", rc); + } + return rc; +} +#endif + +void set_band_bit(uint8_t *bitmap, uint16_t band_num) +{ + uint16_t bit_pos; + uint16_t byte_index; + uint8_t bit_index; + + if (band_num < 1 || band_num > 256) { + return; /* Out of range */ + } + /* Calculate byte and bit positions */ + bit_pos = band_num - 1; + byte_index = bit_pos / 8; + bit_index = bit_pos % 8; + /* Big-endian format: band 1 in byte 31, band 256 in byte 0 */ + bitmap[byte_index] |= (1 << bit_index); +} + +#ifdef CONFIG_MODEM_HL78XX_CONFIGURE_BANDS +static uint8_t hl78xx_generate_band_bitmap(uint8_t *bitmap) +{ + memset(bitmap, 0, MDM_BAND_BITMAP_LEN_BYTES); + /* Index is reversed: Band 1 is LSB of byte 31, Band 256 is MSB of byte 0 */ +#if CONFIG_MODEM_HL78XX_BAND_1 + set_band_bit(bitmap, 1); +#endif +#if CONFIG_MODEM_HL78XX_BAND_2 + set_band_bit(bitmap, 2); +#endif +#if CONFIG_MODEM_HL78XX_BAND_3 + set_band_bit(bitmap, 3); +#endif +#if CONFIG_MODEM_HL78XX_BAND_4 + set_band_bit(bitmap, 4); +#endif +#if CONFIG_MODEM_HL78XX_BAND_5 + set_band_bit(bitmap, 5); +#endif +#if CONFIG_MODEM_HL78XX_BAND_8 + set_band_bit(bitmap, 8); +#endif +#if CONFIG_MODEM_HL78XX_BAND_9 + set_band_bit(bitmap, 9); +#endif +#if CONFIG_MODEM_HL78XX_BAND_10 + set_band_bit(bitmap, 10); +#endif +#if CONFIG_MODEM_HL78XX_BAND_12 + set_band_bit(bitmap, 12); +#endif +#if CONFIG_MODEM_HL78XX_BAND_13 + set_band_bit(bitmap, 13); +#endif +#if CONFIG_MODEM_HL78XX_BAND_17 + set_band_bit(bitmap, 17); +#endif +#if CONFIG_MODEM_HL78XX_BAND_18 + set_band_bit(bitmap, 18); +#endif +#if CONFIG_MODEM_HL78XX_BAND_19 + set_band_bit(bitmap, 19); +#endif +#if CONFIG_MODEM_HL78XX_BAND_20 + set_band_bit(bitmap, 20); +#endif +#if CONFIG_MODEM_HL78XX_BAND_23 + set_band_bit(bitmap, 23); +#endif +#if CONFIG_MODEM_HL78XX_BAND_25 + set_band_bit(bitmap, 25); +#endif +#if CONFIG_MODEM_HL78XX_BAND_26 + set_band_bit(bitmap, 26); +#endif +#if CONFIG_MODEM_HL78XX_BAND_27 + set_band_bit(bitmap, 27); +#endif +#if CONFIG_MODEM_HL78XX_BAND_28 + set_band_bit(bitmap, 28); +#endif +#if CONFIG_MODEM_HL78XX_BAND_31 + set_band_bit(bitmap, 31); +#endif +#if CONFIG_MODEM_HL78XX_BAND_66 + set_band_bit(bitmap, 66); +#endif +#if CONFIG_MODEM_HL78XX_BAND_72 + set_band_bit(bitmap, 72); +#endif +#if CONFIG_MODEM_HL78XX_BAND_73 + set_band_bit(bitmap, 73); +#endif +#if CONFIG_MODEM_HL78XX_BAND_85 + set_band_bit(bitmap, 85); +#endif +#if CONFIG_MODEM_HL78XX_BAND_87 + set_band_bit(bitmap, 87); +#endif +#if CONFIG_MODEM_HL78XX_BAND_88 + set_band_bit(bitmap, 88); +#endif +#if CONFIG_MODEM_HL78XX_BAND_106 + set_band_bit(bitmap, 106); +#endif +#if CONFIG_MODEM_HL78XX_BAND_107 + set_band_bit(bitmap, 107); +#endif +#if CONFIG_MODEM_HL78XX_BAND_255 + set_band_bit(bitmap, 255); +#endif +#if CONFIG_MODEM_HL78XX_BAND_256 + set_band_bit(bitmap, 256); +#endif + /* Add additional bands similarly... */ + return 0; +} +#endif /* CONFIG_MODEM_HL78XX_CONFIGURE_BANDS */ + +#if defined(CONFIG_MODEM_HL78XX_AUTORAT) +/** + * @brief Parse a comma-separated list of bands from a string. + * + * @param band_str The input string containing band numbers. + * @param bands Output array to store parsed band numbers. + * @param max_bands Maximum number of bands that can be stored in the output array. + * + * @return Number of bands parsed, or negative error code on failure. + */ +static int parse_band_list(const char *band_str, int *bands, size_t max_bands) +{ + char buf[128] = {0}; + char *token; + char *rest; + int count = 0; + int band = 0; + + if (!band_str || !bands || max_bands == 0) { + return -EINVAL; + } + strncpy(buf, band_str, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + rest = buf; + while ((token = strtok_r(rest, ",", &rest))) { + band = ATOI(token, -1, "band"); + if (band <= 0) { + printk("Invalid band number: %s\n", token); + continue; + } + if (count >= max_bands) { + printk("Too many bands, max is %d\n", (int)max_bands); + break; + } + bands[count++] = band; + } + return count; +} +#endif /* CONFIG_MODEM_HL78XX_AUTORAT */ + +int hl78xx_generate_bitmap_from_config(enum hl78xx_cell_rat_mode rat, uint8_t *bitmap_out) +{ + if (!bitmap_out) { + return -EINVAL; + } + memset(bitmap_out, 0, MDM_BAND_BITMAP_LEN_BYTES); +#if defined(CONFIG_MODEM_HL78XX_AUTORAT) + /* Auto-RAT: read bands from string configs */ + const char *band_str = NULL; + + switch (rat) { + case HL78XX_RAT_CAT_M1: +#ifdef CONFIG_MODEM_HL78XX_AUTORAT_M1_BAND_CFG + band_str = CONFIG_MODEM_HL78XX_AUTORAT_M1_BAND_CFG; +#endif + break; + + case HL78XX_RAT_NB1: +#ifdef CONFIG_MODEM_HL78XX_AUTORAT_NB_BAND_CFG + band_str = CONFIG_MODEM_HL78XX_AUTORAT_NB_BAND_CFG; +#endif + break; + + default: + return -EINVAL; + } + if (band_str) { + int bands[MAX_BANDS]; + int count = parse_band_list(band_str, bands, MAX_BANDS); + + if (count < 0) { + return -EINVAL; + } + for (int i = 0; i < count; i++) { + set_band_bit(bitmap_out, bands[i]); + } + return 0; + } +#else + /* Else: use standalone config */ + return hl78xx_generate_band_bitmap(bitmap_out); +#endif /* CONFIG_MODEM_HL78XX_AUTORAT */ + return -EINVAL; +} + +void hl78xx_bitmap_to_hex_string_trimmed(const uint8_t *bitmap, char *hex_str, size_t hex_str_len) +{ + int started = 0; + size_t offset = 0; + + for (int i = MDM_BAND_BITMAP_LEN_BYTES - 1; i >= 0; i--) { + if (!started && bitmap[i] == 0) { + continue; /* Skip leading zero bytes */ + } + started = 1; + if (offset + 2 >= hex_str_len) { + break; + } + offset += snprintk(&hex_str[offset], hex_str_len - offset, "%02X", bitmap[i]); + } + if (!started) { + strcpy(hex_str, "0"); + } +} + +int hl78xx_hex_string_to_bitmap(const char *hex_str, uint8_t *bitmap_out) +{ + if (strlen(hex_str) >= MDM_BAND_HEX_STR_LEN) { + LOG_ERR("Invalid hex string length: %zu", strlen(hex_str)); + return -EINVAL; + } + + for (int i = 0; i < MDM_BAND_BITMAP_LEN_BYTES; i++) { + unsigned int byte_val; + + if (sscanf(&hex_str[i * 2], "%2x", &byte_val) != 1) { + LOG_ERR("Failed to parse byte at position %d", i); + return -EINVAL; + } + bitmap_out[i] = (uint8_t)byte_val; + } + return 0; +} + +int hl78xx_get_band_default_config_for_rat(enum hl78xx_cell_rat_mode rat, char *hex_bndcfg, + size_t size_in_bytes) +{ + uint8_t bitmap[MDM_BAND_BITMAP_LEN_BYTES] = {0}; + char hex_str[MDM_BAND_HEX_STR_LEN] = {0}; + + if (size_in_bytes < MDM_BAND_HEX_STR_LEN || hex_bndcfg == NULL) { + return -EINVAL; + } + if (hl78xx_generate_bitmap_from_config(rat, bitmap) != 0) { + return -EINVAL; + } + hl78xx_bitmap_to_hex_string_trimmed(bitmap, hex_str, sizeof(hex_str)); + LOG_INF("Default band config: %s", hex_str); + strncpy(hex_bndcfg, hex_str, MDM_BAND_HEX_STR_LEN); + return 0; +} + +const char *hl78xx_trim_leading_zeros(const char *hex_str) +{ + while (*hex_str == '0' && *(hex_str + 1) != '\0') { + hex_str++; + } + return hex_str; +} + +static void strip_quotes(char *str) +{ + size_t len = strlen(str); + + if (len >= 2 && str[0] == '"' && str[len - 1] == '"') { + /* Shift string left by 1 and null-terminate earlier */ + memmove(str, str + 1, len - 2); + str[len - 2] = '\0'; + } +} + +void hl78xx_extract_essential_part_apn(const char *full_apn, char *essential_apn, size_t max_len) +{ + char apn_buf[MDM_APN_FULL_STRING_MAX_LEN] = {0}; + size_t len; + const char *mnc_ptr; + + if (full_apn == NULL || essential_apn == NULL || max_len == 0) { + return; + } + strncpy(apn_buf, full_apn, sizeof(apn_buf) - 1); + apn_buf[sizeof(apn_buf) - 1] = '\0'; + /* Remove surrounding quotes if any */ + strip_quotes(apn_buf); + mnc_ptr = strstr(apn_buf, ".mnc"); + if (mnc_ptr != NULL) { + len = mnc_ptr - apn_buf; + if (len >= max_len) { + len = max_len - 1; + } + strncpy(essential_apn, apn_buf, len); + essential_apn[len] = '\0'; + } else { + /* No ".mnc" found, copy entire string */ + strncpy(essential_apn, apn_buf, max_len - 1); + essential_apn[max_len - 1] = '\0'; + } +} diff --git a/drivers/modem/hl78xx/hl78xx_cfg.h b/drivers/modem/hl78xx/hl78xx_cfg.h new file mode 100644 index 0000000000000..5d8be2abe58d5 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_cfg.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * hl78xx_cfg.h + * + * Helper APIs for RAT, band and APN configuration extracted from hl78xx.c + * to keep the state machine file smaller and easier to read. + */ +#ifndef ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CFG_H_ +#define ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CFG_H_ + +#include +#include +#include "hl78xx.h" + +int hl78xx_rat_cfg(struct hl78xx_data *data, bool *modem_require_restart, + enum hl78xx_cell_rat_mode *rat_request); + +int hl78xx_band_cfg(struct hl78xx_data *data, bool *modem_require_restart, + enum hl78xx_cell_rat_mode rat_config_request); + +int hl78xx_set_apn_internal(struct hl78xx_data *data, const char *apn, uint16_t size); + +/** + * @brief Convert a binary bitmap to a trimmed hexadecimal string. + * + * Converts a bitmap into a hex string, removing leading zeros for a + * compact representation. Useful for modem configuration commands. + * + * @param bitmap Pointer to the input binary bitmap. + * @param hex_str Output buffer for the resulting hex string. + * @param hex_str_len Size of the output buffer in bytes. + */ +void hl78xx_bitmap_to_hex_string_trimmed(const uint8_t *bitmap, char *hex_str, size_t hex_str_len); + +/** + * @brief Trim leading zeros from a hexadecimal string. + * + * Removes any '0' characters from the beginning of the provided hex string, + * returning a pointer to the first non-zero character. + * + * @param hex_str Null-terminated hexadecimal string. + * + * @return Pointer to the first non-zero digit in the string, + * or the last zero if the string is all zeros. + */ +const char *hl78xx_trim_leading_zeros(const char *hex_str); + +/** + * @brief hl78xx_extract_essential_part_apn - Extract the essential part of the APN. + * @param full_apn Full APN string. + * @param essential_apn Buffer to store the essential part of the APN. + * @param max_len Maximum length of the essential APN buffer. + */ +void hl78xx_extract_essential_part_apn(const char *full_apn, char *essential_apn, size_t max_len); + +#endif /* ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CFG_H_ */ diff --git a/drivers/modem/hl78xx/hl78xx_chat.c b/drivers/modem/hl78xx/hl78xx_chat.c new file mode 100644 index 0000000000000..5b94791aa1d2e --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_chat.c @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + ***************************************************************************** + * hl78xx_chat.c + * + * Centralized translation unit for MODEM_CHAT_* macro-generated objects and + * chat scripts for the HL78xx driver. This file contains the MODEM_CHAT + * matches and script definitions and exposes runtime wrapper functions + * declared in hl78xx_chat.h. + * + * Contract: + * - Other translation units MUST NOT take addresses of the MODEM_CHAT_* + * symbols or use ARRAY_SIZE() on them at file scope. Use the getters + * (hl78xx_get_*) and runners (hl78xx_ run_*_script[_async]) instead. + ***************************************************************************** + */ + +#include "hl78xx.h" +#include "hl78xx_chat.h" +#include +#include + +LOG_MODULE_DECLARE(hl78xx_dev); + +/* Forward declarations of handlers implemented in hl78xx.c (extern linkage) */ +void hl78xx_on_cxreg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +/* +CGCONTRDP handler implemented in hl78xx_sockets.c - declared here so the + * chat match may reference it. This handler parses PDP context response and + * updates DNS / interface state for the driver instance. + */ +void hl78xx_on_cgdcontrdp(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +#if defined(CONFIG_MODEM_HL78XX_12) +void hl78xx_on_kstatev(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +#endif +void hl78xx_on_socknotifydata(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_ktcpnotif(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +/* Handler implemented to assign modem-provided udp socket ids */ +void hl78xx_on_kudpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc, + void *user_data); +void hl78xx_on_ktcpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc, + void *user_data); +/* Handler implemented to assign modem-provided tcp socket ids */ +void hl78xx_on_ktcpind(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +/* + * Chat script and URC match definitions - extracted from hl78xx.c + */ +void hl78xx_on_udprcv(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_kbndcfg(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_csq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_cesq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_cfun(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_cops(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_ksup(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_imei(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_cgmm(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_imsi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_cgmi(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_cgmr(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_iccid(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_ksrep(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_ksrat(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); +void hl78xx_on_kselacq(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data); + +MODEM_CHAT_MATCH_DEFINE(hl78xx_ok_match, "OK", "", NULL); +MODEM_CHAT_MATCHES_DEFINE(hl78xx_allow_match, MODEM_CHAT_MATCH("OK", "", NULL), + MODEM_CHAT_MATCH(CME_ERROR_STRING, "", NULL)); + +MODEM_CHAT_MATCHES_DEFINE(hl78xx_unsol_matches, MODEM_CHAT_MATCH("+CREG: ", ",", hl78xx_on_cxreg), + MODEM_CHAT_MATCH("+CEREG: ", ",", hl78xx_on_cxreg), +#if defined(CONFIG_MODEM_HL78XX_12) + MODEM_CHAT_MATCH("+KSTATEV: ", ",", hl78xx_on_kstatev), +#endif + MODEM_CHAT_MATCH("+KUDP_DATA: ", ",", hl78xx_on_socknotifydata), + MODEM_CHAT_MATCH("+KTCP_DATA: ", ",", hl78xx_on_socknotifydata), + MODEM_CHAT_MATCH("+KTCP_NOTIF: ", ",", hl78xx_on_ktcpnotif), +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + MODEM_CHAT_MATCH("+KUDP_RCV: ", ",", hl78xx_on_udprcv), +#endif + MODEM_CHAT_MATCH("+KBNDCFG: ", ",", hl78xx_on_kbndcfg), + MODEM_CHAT_MATCH("+CSQ: ", ",", hl78xx_on_csq), + MODEM_CHAT_MATCH("+CESQ: ", ",", hl78xx_on_cesq), + MODEM_CHAT_MATCH("+CFUN: ", "", hl78xx_on_cfun), + MODEM_CHAT_MATCH("+COPS: ", ",", hl78xx_on_cops)); + +MODEM_CHAT_MATCHES_DEFINE(hl78xx_abort_matches, MODEM_CHAT_MATCH("+CME ERROR: ", "", NULL)); +MODEM_CHAT_MATCH_DEFINE(hl78xx_at_ready_match, "+KSUP: ", "", hl78xx_on_ksup); +MODEM_CHAT_MATCH_DEFINE(hl78xx_imei_match, "", "", hl78xx_on_imei); +MODEM_CHAT_MATCH_DEFINE(hl78xx_cgmm_match, "", "", hl78xx_on_cgmm); +MODEM_CHAT_MATCH_DEFINE(hl78xx_cimi_match, "", "", hl78xx_on_imsi); +MODEM_CHAT_MATCH_DEFINE(hl78xx_cgmi_match, "", "", hl78xx_on_cgmi); +MODEM_CHAT_MATCH_DEFINE(hl78xx_cgmr_match, "", "", hl78xx_on_cgmr); +MODEM_CHAT_MATCH_DEFINE(hl78xx_iccid_match, "+CCID: ", "", hl78xx_on_iccid); +MODEM_CHAT_MATCH_DEFINE(hl78xx_ksrep_match, "+KSREP: ", ",", hl78xx_on_ksrep); +MODEM_CHAT_MATCH_DEFINE(hl78xx_ksrat_match, "+KSRAT: ", "", hl78xx_on_ksrat); +MODEM_CHAT_MATCH_DEFINE(hl78xx_kselacq_match, "+KSELACQ: ", ",", hl78xx_on_kselacq); + +/* Chat script matches / definitions */ +MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_periodic_chat_script_cmds, + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEREG?", hl78xx_ok_match)); + +MODEM_CHAT_SCRIPT_DEFINE(hl78xx_periodic_chat_script, hl78xx_periodic_chat_script_cmds, + hl78xx_abort_matches, hl78xx_chat_callback_handler, 4); + +MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_init_chat_script_cmds, + MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_at_ready_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KHWIOCFG=3,1,6", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("ATE0", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CFUN=4,0", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSLEEP=2", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CPSMS=0", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEDRXS=0", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KPATTERN=\"--EOF--Pattern--\"", + hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CCID", hl78xx_iccid_match), + MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CMEE=1", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGSN", hl78xx_imei_match), + MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMM", hl78xx_cgmm_match), + MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMI", hl78xx_cgmi_match), + MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGMR", hl78xx_cgmr_match), + MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CIMI", hl78xx_cimi_match), + MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_ok_match), +#if defined(CONFIG_MODEM_HL78XX_12) + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSTATEV=1", hl78xx_ok_match), +#endif + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGEREP=2", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSELACQ?", hl78xx_kselacq_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSRAT?", hl78xx_ksrat_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KBNDCFG?", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CGACT?", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CREG=0", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEREG=5", hl78xx_ok_match)); + +MODEM_CHAT_SCRIPT_DEFINE(hl78xx_init_chat_script, hl78xx_init_chat_script_cmds, + hl78xx_abort_matches, hl78xx_chat_callback_handler, 10); + +/* Post-restart script (moved from hl78xx.c) */ +MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_post_restart_chat_script_cmds, + MODEM_CHAT_SCRIPT_CMD_RESP("", hl78xx_at_ready_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSRAT?", hl78xx_ksrat_match), +#if defined(CONFIG_MODEM_HL78XX_12) + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSTATEV=1", hl78xx_ok_match) +#endif +); + +MODEM_CHAT_SCRIPT_DEFINE(hl78xx_post_restart_chat_script, hl78xx_post_restart_chat_script_cmds, + hl78xx_abort_matches, hl78xx_chat_callback_handler, 1000); + +/* init_fail_script moved from hl78xx.c */ +MODEM_CHAT_SCRIPT_CMDS_DEFINE(init_fail_script_cmds, + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP?", hl78xx_ksrep_match)); + +MODEM_CHAT_SCRIPT_DEFINE(init_fail_script, init_fail_script_cmds, hl78xx_abort_matches, + hl78xx_chat_callback_handler, 10); + +MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_enable_ksup_urc_cmds, + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP=1", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+KSREP?", hl78xx_ksrep_match)); + +MODEM_CHAT_SCRIPT_DEFINE(hl78xx_enable_ksup_urc_script, hl78xx_enable_ksup_urc_cmds, + hl78xx_abort_matches, hl78xx_chat_callback_handler, 4); + +/* power-off script moved from hl78xx.c */ +MODEM_CHAT_SCRIPT_CMDS_DEFINE(hl78xx_pwroff_cmds, + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CFUN=0", hl78xx_ok_match), + MODEM_CHAT_SCRIPT_CMD_RESP("AT+CPWROFF", hl78xx_ok_match)); + +MODEM_CHAT_SCRIPT_DEFINE(hl78xx_pwroff_script, hl78xx_pwroff_cmds, hl78xx_abort_matches, + hl78xx_chat_callback_handler, 4); + +/* Socket-specific matches and wrappers exposed for the sockets translation + * unit. These were extracted from hl78xx_sockets.c to centralize chat + * definitions. + */ +MODEM_CHAT_MATCHES_DEFINE(connect_matches, MODEM_CHAT_MATCH(CONNECT_STRING, "", NULL), + MODEM_CHAT_MATCH(CME_ERROR_STRING, "", NULL)); +MODEM_CHAT_MATCH_DEFINE(kudpind_match, "+KUDP_IND: ", ",", hl78xx_on_kudpsocket_create); +MODEM_CHAT_MATCH_DEFINE(ktcpind_match, "+KTCP_IND: ", ",", hl78xx_on_ktcpind); +MODEM_CHAT_MATCH_DEFINE(ktcpcfg_match, "+KTCPCFG: ", "", hl78xx_on_ktcpsocket_create); +MODEM_CHAT_MATCH_DEFINE(cgdcontrdp_match, "+CGCONTRDP: ", ",", hl78xx_on_cgdcontrdp); +MODEM_CHAT_MATCH_DEFINE(ktcp_state_match, "+KTCPSTAT: ", ",", NULL); + +const struct modem_chat_match *hl78xx_get_sockets_ok_match(void) +{ + return &hl78xx_ok_match; +} + +const struct modem_chat_match *hl78xx_get_connect_matches(void) +{ + return connect_matches; +} + +size_t hl78xx_get_connect_matches_size(void) +{ + return (size_t)ARRAY_SIZE(connect_matches); +} + +const struct modem_chat_match *hl78xx_get_sockets_allow_matches(void) +{ + return hl78xx_allow_match; +} + +size_t hl78xx_get_sockets_allow_matches_size(void) +{ + return (size_t)ARRAY_SIZE(hl78xx_allow_match); +} + +const struct modem_chat_match *hl78xx_get_kudpind_match(void) +{ + return &kudpind_match; +} + +const struct modem_chat_match *hl78xx_get_ktcpind_match(void) +{ + return &ktcpind_match; +} + +const struct modem_chat_match *hl78xx_get_ktcpcfg_match(void) +{ + return &ktcpcfg_match; +} + +const struct modem_chat_match *hl78xx_get_cgdcontrdp_match(void) +{ + return &cgdcontrdp_match; +} + +const struct modem_chat_match *hl78xx_get_ktcp_state_match(void) +{ + return &ktcp_state_match; +} + +/* modem_init_chat is implemented in hl78xx.c so it can construct the + * modem_chat_config with device-local buffer sizes (argv_size) without + * relying on ARRAY_SIZE at file scope inside this translation unit. + */ + +/* Bridge function - modem_chat callback */ +void hl78xx_chat_callback_handler(struct modem_chat *chat, enum modem_chat_script_result result, + void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + if (result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS) { + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SCRIPT_SUCCESS); + } else { + hl78xx_delegate_event(data, MODEM_HL78XX_EVENT_SCRIPT_FAILED); + } +} + +/* --- Wrapper helpers -------------------------------------------------- */ +const struct modem_chat_match *hl78xx_get_ok_match(void) +{ + return &hl78xx_ok_match; +} + +const struct modem_chat_match *hl78xx_get_abort_matches(void) +{ + return hl78xx_abort_matches; +} + +const struct modem_chat_match *hl78xx_get_unsol_matches(void) +{ + return hl78xx_unsol_matches; +} + +size_t hl78xx_get_unsol_matches_size(void) +{ + /* Return size as a runtime value to avoid constant-expression errors + * in translation units that include this header. + */ + return (size_t)(ARRAY_SIZE(hl78xx_unsol_matches)); +} + +size_t hl78xx_get_abort_matches_size(void) +{ + return (size_t)(ARRAY_SIZE(hl78xx_abort_matches)); +} + +const struct modem_chat_match *hl78xx_get_allow_match(void) +{ + return hl78xx_allow_match; +} + +size_t hl78xx_get_allow_match_size(void) +{ + return (size_t)(ARRAY_SIZE(hl78xx_allow_match)); +} + +/* Run the predefined init script for the given device */ +int hl78xx_run_init_script(struct hl78xx_data *data) +{ + if (!data) { + return -EINVAL; + } + return modem_chat_run_script(&data->chat, &hl78xx_init_chat_script); +} + +/* Run the periodic script */ +int hl78xx_run_periodic_script(struct hl78xx_data *data) +{ + if (!data) { + return -EINVAL; + } + return modem_chat_run_script(&data->chat, &hl78xx_periodic_chat_script); +} + +int hl78xx_run_init_script_async(struct hl78xx_data *data) +{ + if (!data) { + return -EINVAL; + } + return modem_chat_run_script_async(&data->chat, &hl78xx_init_chat_script); +} + +int hl78xx_run_periodic_script_async(struct hl78xx_data *data) +{ + if (!data) { + return -EINVAL; + } + return modem_chat_run_script_async(&data->chat, &hl78xx_periodic_chat_script); +} + +const struct modem_chat_match *hl78xx_get_ksrat_match(void) +{ + return &hl78xx_ksrat_match; +} + +int hl78xx_run_post_restart_script(struct hl78xx_data *data) +{ + if (!data) { + return -EINVAL; + } + return modem_chat_run_script(&data->chat, &hl78xx_post_restart_chat_script); +} + +int hl78xx_run_post_restart_script_async(struct hl78xx_data *data) +{ + if (!data) { + return -EINVAL; + } + return modem_chat_run_script_async(&data->chat, &hl78xx_post_restart_chat_script); +} + +int hl78xx_run_init_fail_script_async(struct hl78xx_data *data) +{ + if (!data) { + return -EINVAL; + } + return modem_chat_run_script_async(&data->chat, &init_fail_script); +} + +int hl78xx_run_enable_ksup_urc_script_async(struct hl78xx_data *data) +{ + if (!data) { + return -EINVAL; + } + return modem_chat_run_script_async(&data->chat, &hl78xx_enable_ksup_urc_script); +} + +int hl78xx_run_pwroff_script_async(struct hl78xx_data *data) +{ + if (!data) { + return -EINVAL; + } + return modem_chat_run_script_async(&data->chat, &hl78xx_pwroff_script); +} diff --git a/drivers/modem/hl78xx/hl78xx_chat.h b/drivers/modem/hl78xx/hl78xx_chat.h new file mode 100644 index 0000000000000..eb3a1dec8365b --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_chat.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * hl78xx_chat.h + * + * Wrapper accessors for MODEM_CHAT_* objects that live in a dedicated + * translation unit (hl78xx_chat.c). Other driver TUs should only call + * these functions instead of taking addresses or using sizeof/ARRAY_SIZE + * on the macro-generated objects. + */ +#ifndef ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CHAT_H_ +#define ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CHAT_H_ + +#include +#include + +/* Forward declare driver data type to keep this header lightweight and avoid + * circular includes. The implementation file (hl78xx_chat.c) includes + * hl78xx.h for full driver visibility. + */ +struct hl78xx_data; + +/* Chat callback bridge used by driver TUs to receive script results. */ +void hl78xx_chat_callback_handler(struct modem_chat *chat, enum modem_chat_script_result result, + void *user_data); + +/* Wrapper helpers so other translation units don't need compile-time + * visibility of the MODEM_CHAT_* macro-generated symbols. + */ +const struct modem_chat_match *hl78xx_get_ok_match(void); +const struct modem_chat_match *hl78xx_get_abort_matches(void); +const struct modem_chat_match *hl78xx_get_unsol_matches(void); +size_t hl78xx_get_unsol_matches_size(void); +size_t hl78xx_get_abort_matches_size(void); +const struct modem_chat_match *hl78xx_get_allow_match(void); +size_t hl78xx_get_allow_match_size(void); + +/* Run predefined scripts from other units */ +int hl78xx_run_init_script(struct hl78xx_data *data); +int hl78xx_run_periodic_script(struct hl78xx_data *data); +int hl78xx_run_post_restart_script(struct hl78xx_data *data); +int hl78xx_run_init_fail_script_async(struct hl78xx_data *data); +int hl78xx_run_enable_ksup_urc_script_async(struct hl78xx_data *data); +int hl78xx_run_pwroff_script_async(struct hl78xx_data *data); +int hl78xx_run_post_restart_script_async(struct hl78xx_data *data); +/* Async runners for init/periodic scripts */ +int hl78xx_run_init_script_async(struct hl78xx_data *data); +int hl78xx_run_periodic_script_async(struct hl78xx_data *data); + +/* Getter for ksrat match (moved into chat TU) */ +const struct modem_chat_match *hl78xx_get_ksrat_match(void); + +/* Socket-related chat matches used by the sockets TU */ +const struct modem_chat_match *hl78xx_get_sockets_ok_match(void); +const struct modem_chat_match *hl78xx_get_connect_matches(void); +size_t hl78xx_get_connect_matches_size(void); +const struct modem_chat_match *hl78xx_get_sockets_allow_matches(void); +size_t hl78xx_get_sockets_allow_matches_size(void); +const struct modem_chat_match *hl78xx_get_kudpind_match(void); +const struct modem_chat_match *hl78xx_get_ktcpind_match(void); +const struct modem_chat_match *hl78xx_get_ktcpcfg_match(void); +const struct modem_chat_match *hl78xx_get_cgdcontrdp_match(void); +const struct modem_chat_match *hl78xx_get_ktcp_state_match(void); + +#endif /* ZEPHYR_DRIVERS_MODEM_HL78XX_HL78XX_CHAT_H_ */ diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/CMakeLists.txt b/drivers/modem/hl78xx/hl78xx_evt_monitor/CMakeLists.txt new file mode 100644 index 0000000000000..ea00a6b9cef76 --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/CMakeLists.txt @@ -0,0 +1,10 @@ +# +# Copyright (c) 2025 Netfeasa Ltd. +# +# SPDX-License-Identifier: Apache-2.0 +# + +zephyr_library() +zephyr_library_sources(hl78xx_evt_monitor.c) +# Event monitors data must be in RAM +zephyr_linker_sources(RWDATA hl78xx_evt_monitor.ld) diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor b/drivers/modem/hl78xx/hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor new file mode 100644 index 0000000000000..e002f24ec2c1e --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/Kconfig.hl78xx_evt_monitor @@ -0,0 +1,29 @@ +# +# Copyright (c) 2025 Netfeasa Ltd. +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig HL78XX_EVT_MONITOR + bool "HL78XX AT notification monitor" + +if HL78XX_EVT_MONITOR + +config HL78XX_EVT_MONITOR_HEAP_SIZE + int "Heap size for notifications" + range 64 4096 + default 256 + +config HL78XX_EVT_MONITOR_APP_INIT_PRIORITY + int "Sierra Wireless HL78XX event monitor app init priority" + default 0 + help + Sierra Wireless HL78XX event monitor app initialization priority. + Do not mess with it unless you know what you are doing. + +module=HL78XX_EVT_MONITOR +module-dep=LOG +module-str= Event notification monitor library +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +endif # HL78XX_EVT_MONITOR diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.c b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.c new file mode 100644 index 0000000000000..7bb2e688973ab --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.c @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(hl78xx_evt_monitor, CONFIG_HL78XX_EVT_MONITOR_LOG_LEVEL); + +struct evt_notif_fifo { + void *fifo_reserved; + struct hl78xx_evt data; +}; + +static struct hl78xx_evt_monitor_entry *monitor_list_head; +static struct k_spinlock monitor_list_lock; + +static void hl78xx_evt_monitor_task(struct k_work *work); + +static K_FIFO_DEFINE(hl78xx_evt_monitor_fifo); +static K_HEAP_DEFINE(hl78xx_evt_monitor_heap, CONFIG_HL78XX_EVT_MONITOR_HEAP_SIZE); +static K_WORK_DEFINE(hl78xx_evt_monitor_work, hl78xx_evt_monitor_task); + +static bool is_paused(const struct hl78xx_evt_monitor_entry *mon) +{ + return mon->flags.paused; +} + +static bool is_direct(const struct hl78xx_evt_monitor_entry *mon) +{ + return mon->flags.direct; +} + +/* Register an event monitor */ +int hl78xx_evt_monitor_register(struct hl78xx_evt_monitor_entry *mon) +{ + k_spinlock_key_t key = k_spin_lock(&monitor_list_lock); + + mon->next = monitor_list_head; + monitor_list_head = mon; + k_spin_unlock(&monitor_list_lock, key); + return 0; +} + +/* Unregister an event monitor */ +int hl78xx_evt_monitor_unregister(struct hl78xx_evt_monitor_entry *mon) +{ + k_spinlock_key_t key = k_spin_lock(&monitor_list_lock); + struct hl78xx_evt_monitor_entry **pp = &monitor_list_head; + + while (*pp) { + if (*pp == mon) { + *pp = mon->next; + mon->next = NULL; + k_spin_unlock(&monitor_list_lock, key); + return 0; + } + pp = &(*pp)->next; + } + + k_spin_unlock(&monitor_list_lock, key); + return -ENOENT; +} +/* Dispatch EVT notifications immediately, or schedules a workqueue task to do that. + * Keep this function public so that it can be called by tests. + * This function is called from an ISR. + */ +void hl78xx_evt_monitor_dispatch(struct hl78xx_evt *notif) +{ + bool monitored; + struct evt_notif_fifo *evt_notif; + size_t sz_needed; + + __ASSERT_NO_MSG(notif != NULL); + + monitored = false; + /* Global monitors: SECTION_ITERABLE */ + STRUCT_SECTION_FOREACH(hl78xx_evt_monitor_entry, e) { + if (!is_paused(e)) { + if (is_direct(e)) { + LOG_DBG("calling direct global handler %p", + e->handler); + e->handler(notif, NULL); /* NULL context for global listeners */ + } else { + monitored = true; + } + } + } + + k_spinlock_key_t key = k_spin_lock(&monitor_list_lock); + + for (struct hl78xx_evt_monitor_entry *e = monitor_list_head; e; e = e->next) { + if (!is_paused(e)) { + if (is_direct(e)) { + LOG_DBG("calling direct instance handler %p " + "(ctx=%p)", + e->handler, e); + e->handler(notif, e); + } else { + monitored = true; + } + } + } + k_spin_unlock(&monitor_list_lock, key); + + if (!monitored) { + /* Only copy monitored notifications to save heap */ + return; + } + + sz_needed = sizeof(struct evt_notif_fifo) + sizeof(notif); + + evt_notif = k_heap_alloc(&hl78xx_evt_monitor_heap, sz_needed, K_NO_WAIT); + if (!evt_notif) { + LOG_WRN("No heap space for incoming notification: %d", notif->type); + __ASSERT(evt_notif, "No heap space for incoming notification: %d", notif->type); + return; + } + + evt_notif->data = *notif; + + k_fifo_put(&hl78xx_evt_monitor_fifo, evt_notif); + k_work_submit(&hl78xx_evt_monitor_work); +} + +static void hl78xx_evt_monitor_task(struct k_work *work) +{ + struct evt_notif_fifo *evt_notif; + + while ((evt_notif = k_fifo_get(&hl78xx_evt_monitor_fifo, K_NO_WAIT))) { + /* Dispatch notification with all monitors */ + LOG_DBG("EVT notif: %d", evt_notif->data.type); + STRUCT_SECTION_FOREACH(hl78xx_evt_monitor_entry, e) { + if (!is_paused(e) && !is_direct(e)) { + LOG_DBG("Dispatching to %p", e->handler); + e->handler(&evt_notif->data, e); + } + } + /* Instance/context monitors */ + k_spinlock_key_t key = k_spin_lock(&monitor_list_lock); + + for (struct hl78xx_evt_monitor_entry *e = monitor_list_head; e; e = e->next) { + if (!is_paused(e) && !is_direct(e)) { + e->handler(&evt_notif->data, e); + } + } + k_spin_unlock(&monitor_list_lock, key); + + k_heap_free(&hl78xx_evt_monitor_heap, evt_notif); + } +} + +static int hl78xx_evt_monitor_sys_init(void) +{ + int err = 0; + + err = hl78xx_evt_notif_handler_set(hl78xx_evt_monitor_dispatch); + if (err) { + LOG_ERR("Failed to hook the dispatch function, err %d", err); + } + + return 0; +} + +/* Initialize during SYS_INIT */ +SYS_INIT(hl78xx_evt_monitor_sys_init, APPLICATION, CONFIG_HL78XX_EVT_MONITOR_APP_INIT_PRIORITY); diff --git a/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.ld b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.ld new file mode 100644 index 0000000000000..c6c32940ba1fc --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_evt_monitor/hl78xx_evt_monitor.ld @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/*HL78XX event monitors */ +. = ALIGN(4); +_hl78xx_evt_monitor_entry_list_start = .; +KEEP(*(SORT_BY_NAME("._hl78xx_evt_monitor_entry.*"))); +_hl78xx_evt_monitor_entry_list_end = .; diff --git a/drivers/modem/hl78xx/hl78xx_sockets.c b/drivers/modem/hl78xx/hl78xx_sockets.c new file mode 100644 index 0000000000000..9840ea906a36b --- /dev/null +++ b/drivers/modem/hl78xx/hl78xx_sockets.c @@ -0,0 +1,2608 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS) +#include "tls_internal.h" +#include +#endif + +#include +#include +#include +#include "hl78xx.h" +#include "hl78xx_chat.h" +#include "hl78xx_cfg.h" + +LOG_MODULE_REGISTER(hl78xx_socket, CONFIG_MODEM_LOG_LEVEL); + +/* + * hl78xx_sockets.c + * + * Responsibilities: + * - Provide the socket offload integration for the HL78xx modem. + * - Parse modem URC/chat replies used to transfer payloads over the UART pipe. + * - Format and send AT commands for socket lifecycle (create, connect, send, recv, + * close, delete) and handle their confirmation/URC callbacks. + * - Provide TLS credential handling when enabled. + */ + +/* Helper macros and constants */ +#define MODEM_STREAM_STARTER_WORD "\r\n" CONNECT_STRING "\r\n" +#define MODEM_STREAM_END_WORD "\r\n" OK_STRING "\r\n" + +#define MODEM_SOCKET_DATA_LEFTOVER_STATE_BIT (0) +#define HL78XX_UART_PIPE_WORK_SOCKET_BUFFER_SIZE 32 +/* modem socket id is 1-based */ +#define HL78XX_TCP_STATUS_ID(x) ((x > 1 ? (x) - 1 : 0)) +/* modem socket id is 1-based */ +#define HL78XX_UDP_STATUS_ID(x) ((x > 1 ? (x) - 1 : 0)) + +#define DNS_SERVERS_COUNT \ + (0 + (IS_ENABLED(CONFIG_NET_IPV6) ? 1 : 0) + (IS_ENABLED(CONFIG_NET_IPV4) ? 1 : 0) + \ + 1 /* for NULL terminator */ \ + ) +RING_BUF_DECLARE(mdm_recv_pool, CONFIG_MODEM_HL78XX_UART_BUFFER_SIZES); + +struct hl78xx_dns_info { +#ifdef CONFIG_NET_IPV4 + char v4_string[NET_IPV4_ADDR_LEN]; + struct in_addr v4; +#endif +#ifdef CONFIG_NET_IPV6 + char v6_string[NET_IPV6_ADDR_LEN]; + struct in6_addr v6; +#endif + bool ready; +}; + +/* IPv4 information is optional and only present when IPv4 is enabled */ +#ifdef CONFIG_NET_IPV4 +struct hl78xx_ipv4_info { + struct in_addr addr; + struct in_addr subnet; + struct in_addr gateway; + struct in_addr new_addr; +}; +#endif +/* IPv6 information is optional and only present when IPv6 is enabled */ +#ifdef CONFIG_NET_IPV6 +struct hl78xx_ipv6_info { + struct in6_addr addr; + struct in6_addr subnet; + struct in6_addr gateway; + struct in6_addr new_addr; +}; +#endif +/* TLS information is optional and only present when TLS is enabled */ +struct hl78xx_tls_info { + char hostname[MDM_MAX_HOSTNAME_LEN]; + bool hostname_set; +}; + +enum hl78xx_tcp_socket_status_code { + /** Error occurred, socket is not usable */ + TCP_SOCKET_ERROR = 0, + /** Connection is up, socket can be used to send/receive data */ + TCP_SOCKET_CONNECTED, +}; + +enum hl78xx_udp_socket_status_code { + UDP_SOCKET_ERROR = 0, /* Error occurred, socket is not usable */ + /** Connection is up, socket can be used to send/receive data */ + UDP_SOCKET_CREATED, +}; +struct hl78xx_tcp_status { + enum hl78xx_tcp_socket_status_code err_code; + bool is_connected; + bool is_created; +}; +struct hl78xx_udp_status { + enum hl78xx_udp_socket_status_code err_code; + bool is_created; +}; + +struct receive_socket_data { + char buf[MDM_MAX_DATA_LENGTH + ARRAY_SIZE(MODEM_STREAM_STARTER_WORD) + + ARRAY_SIZE(MODEM_STREAM_END_WORD)]; + uint16_t len; +}; +struct hl78xx_socket_data { + struct net_if *net_iface; + uint8_t mac_addr[6]; + /* socket data */ + struct modem_socket_config socket_config; + struct modem_socket sockets[MDM_MAX_SOCKETS]; + int current_sock_fd; + int sizeof_socket_data; + int requested_socket_id; + bool socket_data_error; +#if defined(CONFIG_NET_IPV4) || defined(CONFIG_NET_IPV6) + struct hl78xx_dns_info dns; +#endif +#ifdef CONFIG_NET_IPV4 + struct hl78xx_ipv4_info ipv4; +#endif +#ifdef CONFIG_NET_IPV6 + struct hl78xx_ipv6_info ipv6; +#endif + /* rx net buffer */ + struct ring_buf *buf_pool; + uint32_t expected_buf_len; + uint32_t collected_buf_len; + struct receive_socket_data receive_buf; + /* device information */ + const struct device *modem_dev; + const struct device *offload_dev; + struct hl78xx_data *mdata_global; + /* socket state */ + struct hl78xx_tls_info tls; + struct hl78xx_tcp_status tcp_conn_status[MDM_MAX_SOCKETS]; + struct hl78xx_udp_status udp_conn_status[MDM_MAX_SOCKETS]; + /* per-socket parser state (migrated from globals) - use a small enum to + * make the parser's intent explicit and easier to read. + */ + enum { + HL78XX_PARSER_IDLE = 0, + HL78XX_PARSER_CONNECT_MATCHED, + HL78XX_PARSER_EOF_OK_MATCHED, + HL78XX_PARSER_ERROR_MATCHED, + } parser_state; + /* transient: prevents further parsing until parser_reset clears it */ + bool parser_match_found; + uint16_t parser_start_index_eof; + uint16_t parser_size_of_socketdata; + /* true once payload has been pushed into ring_buf */ + bool parser_socket_data_received; + /* set when EOF pattern was found and payload pushed */ + bool parser_eof_detected; + /* set when OK token was matched after payload */ + bool parser_ok_detected; +}; + +static struct hl78xx_socket_data *socket_data_global; + +/* ===== Utils ========================================================== + * Small, stateless utility helpers used across this file. + * Grouping here reduces cognitive load when navigating the file. + */ +static inline void hl78xx_set_socket_global(struct hl78xx_socket_data *d) +{ + socket_data_global = d; +} + +static inline struct hl78xx_socket_data *hl78xx_get_socket_global(void) +{ + return socket_data_global; +} + +/* Helper: map an internal return code into POSIX errno and set errno. + * - negative values are assumed to be negative errno semantics -> map to positive + * - positive values are assumed already POSIX errno -> pass through + * - zero or unknown -> fallback to EIO + */ +static inline void hl78xx_set_errno_from_code(int code) +{ + if (code < 0) { + errno = -code; + } else if (code > 0) { + errno = code; + } else { + errno = EIO; + } +} +/* ===== Forward declarations ========================================== + * Group commonly used static helper prototypes here so callers can be + * reordered without implicit-declaration warnings. Keep this section + * compact. When moving functions into groups, add any new prototypes + * here first. + */ +static void check_tcp_state_if_needed(struct hl78xx_socket_data *socket_data, + struct modem_socket *sock); +/* Parser helpers */ +static bool split_ipv4_and_subnet(const char *combined, char *ip_out, size_t ip_out_len, + char *subnet_out, size_t subnet_out_len); +static bool parse_ip(bool is_ipv4, const char *ip_str, void *out_addr); +static bool update_dns(struct hl78xx_socket_data *socket_data, bool is_ipv4, const char *dns_str); +static void set_iface(struct hl78xx_socket_data *socket_data, bool is_ipv4); +static void parser_reset(struct hl78xx_socket_data *socket_data); +static void found_reset(struct hl78xx_socket_data *socket_data); +static bool modem_chat_parse_end_del_start(struct hl78xx_socket_data *socket_data, + struct modem_chat *chat); +static bool modem_chat_parse_end_del_complete(struct hl78xx_socket_data *socket_data, + struct modem_chat *chat); +static bool modem_chat_match_matches_received(struct hl78xx_socket_data *socket_data, + const char *match, uint16_t match_size); + +/* Receive / parser entrypoints */ +static void socket_process_bytes(struct hl78xx_socket_data *socket_data, char byte); +static int modem_process_handler(struct hl78xx_data *data); +static void modem_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event, + void *user_data); + +/* Socket I/O helpers */ +static int on_cmd_sockread_common(int socket_id, uint16_t socket_data_length, uint16_t len, + void *user_data); +static ssize_t offload_recvfrom(void *obj, void *buf, size_t len, int flags, struct sockaddr *from, + socklen_t *fromlen); +static int prepare_send_cmd(const struct modem_socket *sock, const struct sockaddr *dst_addr, + size_t buf_len, char *cmd_buf, size_t cmd_buf_size); +static int send_data_buffer(struct hl78xx_socket_data *socket_data, const char *buf, + const size_t buf_len, int *sock_written); + +/* Socket lifecycle */ +static int create_socket(struct modem_socket *sock, const struct sockaddr *addr, + struct hl78xx_socket_data *data); +static int socket_close(struct hl78xx_socket_data *socket_data, struct modem_socket *sock); +static int socket_delete(struct hl78xx_socket_data *socket_data, struct modem_socket *sock); +static void socket_notify_data(int socket_id, int new_total, void *user_data); +/* ===== TLS prototypes (conditional) ================================== + * Forward declarations for TLS-related helpers. Grouped separately so + * TLS-specific code paths are easy to find. + */ +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS) +static int map_credentials(struct hl78xx_socket_data *socket_data, const void *optval, + socklen_t optlen); +static int hl78xx_configure_chipper_suit(struct hl78xx_socket_data *socket_data); +#endif /* CONFIG_NET_SOCKETS_SOCKOPT_TLS */ + +/* ===== Container helpers ============================================= + * Small helpers used to map between container structures and their + * member pointers (eg. `modem_socket` -> `hl78xx_socket_data`). + */ +static inline struct hl78xx_socket_data *hl78xx_socket_data_from_sock(struct modem_socket *sock) +{ + /* Robustly recover the parent `hl78xx_socket_data` for any element + * address within the `sockets[]` array. Using CONTAINER_OF with + * `sockets[0]` is not safe when `sock` points to `sockets[i]` (i>0), + * because CONTAINER_OF assumes the pointer is to the member named + * in the macro (sockets[0]). That yields a pointer offset by + * i * sizeof(sockets[0]). + * + * Strategy: for each possible index i, compute the candidate parent + * base address so that &candidate->sockets[i] == sock. If the math + * yields a candidate that looks like a valid container, return it. + */ + if (!sock) { + return NULL; + } + + const size_t elem_size = sizeof(((struct hl78xx_socket_data *)0)->sockets[0]); + const size_t sockets_off = offsetof(struct hl78xx_socket_data, sockets); + struct hl78xx_socket_data *result = NULL; + + for (int i = 0; i < MDM_MAX_SOCKETS; i++) { + struct hl78xx_socket_data *candidate = + (struct hl78xx_socket_data *)((char *)sock - + (ptrdiff_t)(sockets_off + + (size_t)i * elem_size)); + /* Quick sanity: does candidate->sockets[i] point back to sock? */ + if ((struct modem_socket *)&candidate->sockets[i] != sock) { + continue; + } + if (candidate->offload_dev && candidate->mdata_global) { + return candidate; + } + + /* Remember the first match as a fallback */ + if (!result) { + result = candidate; + } + } + return result; +} + +/* ===== Chat callbacks (grouped) ===================================== + * Group all chat/URC handlers together to make the socket TU easier to + * scan. These handlers are registered via hl78xx_chat getters in + * `hl78xx_chat.c` and forward URC context into the socket layer. + */ +void hl78xx_on_socknotifydata(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + int socket_id = -1; + int new_total = -1; + + if (argc < 2) { + return; + } + + socket_id = ATOI(argv[1], -1, "socket_id"); + new_total = ATOI(argv[2], -1, "length"); + if (socket_id < 0 || new_total < 0) { + return; + } + HL78XX_LOG_DBG("%d %d %d", __LINE__, socket_id, new_total); + /* Notify the socket layer that data is available */ + socket_notify_data(socket_id, new_total, user_data); +} + +/** +KTCP_NOTIF: , */ +void hl78xx_on_ktcpnotif(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + enum hl78xx_tcp_notif tcp_notif_received; + int socket_id = -1; + int tcp_notif = -1; + + if (!data || !socket_data) { + LOG_ERR("%s: invalid user_data", __func__); + return; + } + if (argc < 2) { + return; + } + socket_id = ATOI(argv[1], -1, "socket_id"); + tcp_notif = ATOI(argv[2], -1, "tcp_notif"); + if (tcp_notif == -1) { + return; + } + tcp_notif_received = (enum hl78xx_tcp_notif)tcp_notif; + /* Store the socket id for the notification */ + socket_data->requested_socket_id = socket_id; + switch (tcp_notif_received) { + case TCP_NOTIF_REMOTE_DISCONNECTION: + /** + * To Handle remote disconnection + * give a dummy packet size of 1 + * + */ + socket_notify_data(socket_id, 1, user_data); + break; + case TCP_NOTIF_NETWORK_ERROR: + /* Handle network error */ + break; + default: + break; + } +} + +void hl78xx_on_ktcpind(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + struct modem_socket *sock = NULL; + int socket_id = -1; + int tcp_conn_stat = -1; + + if (!data || !socket_data) { + LOG_ERR("%s: invalid user_data", __func__); + return; + } + if (argc < 3 || !argv[1] || !argv[2]) { + LOG_ERR("TCP_IND: Incomplete response"); + goto exit; + } + socket_id = ATOI(argv[1], -1, "socket_id"); + if (socket_id == -1) { + goto exit; + } + sock = modem_socket_from_id(&socket_data->socket_config, socket_id); + tcp_conn_stat = ATOI(argv[2], -1, "tcp_status"); + if (tcp_conn_stat == TCP_SOCKET_CONNECTED) { + socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].err_code = + tcp_conn_stat; + socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_connected = true; + return; + } +exit: + socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].err_code = tcp_conn_stat; + socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_connected = false; + if (socket_id != -1) { + modem_socket_put(&socket_data->socket_config, sock->sock_fd); + } +} + +/* Chat/URC handler for socket-create/indication responses + * Matches +KTCPCFG: + */ +void hl78xx_on_ktcpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc, + void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + struct modem_socket *sock = NULL; + int socket_id = -1; + + if (!data || !socket_data) { + LOG_ERR("%s: invalid user_data", __func__); + return; + } + if (argc < 2 || !argv[1]) { + LOG_ERR("%s: Incomplete response", __func__); + goto exit; + } + /* argv[0] may contain extra CSV fields; parse leading integer */ + socket_id = ATOI(argv[1], -1, "socket_id"); + if (socket_id <= 0) { + LOG_DBG("unable to parse socket id from '%s'", argv[1]); + goto exit; + } + /* Try to find a reserved/new socket slot and assign the modem-provided id. */ + sock = modem_socket_from_newid(&socket_data->socket_config); + if (!sock) { + goto exit; + } + + if (modem_socket_id_assign(&socket_data->socket_config, sock, socket_id) < 0) { + LOG_ERR("Failed to assign modem socket id %d to fd %d", socket_id, sock->sock_fd); + goto exit; + } else { + LOG_DBG("Assigned modem socket id %d to fd %d", socket_id, sock->sock_fd); + } + + socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_created = true; + return; + +exit: + socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].err_code = TCP_SOCKET_ERROR; + socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(socket_id)].is_created = false; + if (socket_id != -1 && sock) { + modem_socket_put(&socket_data->socket_config, sock->sock_fd); + } +} +/* Chat/URC handler for socket-create/indication responses + * Matches +KUDPCFG: + * +KUDP_IND: ,... (or +KTCP_IND) + */ +void hl78xx_on_kudpsocket_create(struct modem_chat *chat, char **argv, uint16_t argc, + void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + struct modem_socket *sock = NULL; + int socket_id = -1; + int udp_create_stat = -1; + + if (!data || !socket_data) { + LOG_ERR("%s: invalid user_data", __func__); + return; + } + if (argc < 2 || !argv[1]) { + LOG_ERR("%s: Incomplete response", __func__); + goto exit; + } + /* argv[0] may contain extra CSV fields; parse leading integer */ + socket_id = ATOI(argv[1], -1, "socket_id"); + if (socket_id <= 0) { + LOG_DBG("unable to parse socket id from '%s'", argv[1]); + goto exit; + } + /* Try to find a reserved/new socket slot and assign the modem-provided id. */ + sock = modem_socket_from_newid(&socket_data->socket_config); + if (!sock) { + goto exit; + } + + if (modem_socket_id_assign(&socket_data->socket_config, sock, socket_id) < 0) { + LOG_ERR("Failed to assign modem socket id %d to fd %d", socket_id, sock->sock_fd); + goto exit; + } else { + LOG_DBG("Assigned modem socket id %d to fd %d", socket_id, sock->sock_fd); + } + /* Parse connection status: 1=created, otherwise=error */ + udp_create_stat = ATOI(argv[2], 0, "udp_status"); + if (udp_create_stat == UDP_SOCKET_CREATED) { + socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].err_code = + udp_create_stat; + socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].is_created = true; + return; + } +exit: + socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].err_code = UDP_SOCKET_ERROR; + socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(socket_id)].is_created = false; + if (socket_id != -1 && sock) { + modem_socket_put(&socket_data->socket_config, sock->sock_fd); + } +} + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG +#ifdef CONFIG_MODEM_HL78XX_12 +/** + * @brief Handle modem state update from +KSTATE URC of RAT Scan Finish. + * This command is intended to report events for different important state transitions and system + * occurrences. + * Actually this eventc'state is really important functionality to understand networks + * searching phase of the modem. + * Verbose debug logging for KSTATEV events + */ +void hl78xx_on_kstatev_parser(struct hl78xx_data *data, int state, int rat_mode) +{ + switch (state) { + case EVENT_START_SCAN: + break; + case EVENT_FAIL_SCAN: + LOG_DBG("Modem failed to find a suitable network"); + break; + case EVENT_ENTER_CAMPED: + LOG_DBG("Modem entered camped state on a suitable or acceptable cell"); + break; + case EVENT_CONNECTION_ESTABLISHMENT: + LOG_DBG("Modem successfully established a connection to the network"); + break; + case EVENT_START_RESCAN: + LOG_DBG("Modem is starting a rescan for available networks"); + break; + case EVENT_RRC_CONNECTED: + LOG_DBG("Modem has established an RRC connection with the network"); + break; + case EVENT_NO_SUITABLE_CELLS: + LOG_DBG("Modem did not find any suitable cells during the scan"); + break; + case EVENT_ALL_REGISTRATION_FAILED: + LOG_DBG("Modem failed to register to any network"); + break; + default: + LOG_DBG("Unhandled KSTATEV for state %d", state); + break; + } +} +#endif +/** + * @brief This function doesn't handle incoming UDP data. + * It is just a placeholder for verbose debug logging of incoming UDP data. + * +KUDP_RCV: ,, + */ +void hl78xx_on_udprcv(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 2) { + return; + } + HL78XX_LOG_DBG("%d %d [%s] [%s] [%s]", __LINE__, argc, argv[0], argv[1], argv[2]); +} +#endif +/* Handler for +CGCONTRDP: ,,,,,,[,] + * This function is invoked by the chat layer when a CGCONTRDP URC is matched. + * It extracts the PDP context address, gateway and DNS servers and updates the + * per-instance socket_data DNS fields so dns_work_cb() can apply them. + */ +void hl78xx_on_cgdcontrdp(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + const char *addr_field = NULL; + const char *gw_field = NULL; + const char *dns_field = NULL; + const char *apn_field = NULL; + + /* Accept both comma-split argv[] or a single raw token that needs tokenizing */ + if (argc >= 7) { + apn_field = argv[3]; + addr_field = argv[4]; + gw_field = argv[5]; + dns_field = argv[6]; + } else { + LOG_ERR("Incomplete CGCONTRDP response: argc=%d", argc); + return; + } + + LOG_INF("Apn=%s", apn_field); + LOG_INF("Addr=%s", addr_field); + LOG_INF("Gw=%s", gw_field); + LOG_INF("DNS=%s", dns_field); +#ifdef CONFIG_MODEM_HL78XX_APN_SOURCE_NETWORK + if (apn_field) { + hl78xx_extract_essential_part_apn(apn_field, data->identity.apn, + sizeof(data->identity.apn)); + } +#endif + /* Handle address parsing: IPv4 replies sometimes embed subnet as extra + * octets concatenated after the IP (e.g. "10.149.122.90.255.255.255.252"). + * Split and parse into the instance IPv4 fields so the interface can be + * configured before the DNS resolver is invoked. + */ +#ifdef CONFIG_NET_IPV4 + if (addr_field && strchr(addr_field, '.') && !strchr(addr_field, ':')) { + char ip_addr[NET_IPV6_ADDR_LEN] = {0}; + char subnet_mask[NET_IPV6_ADDR_LEN] = {0}; + + if (!split_ipv4_and_subnet(addr_field, ip_addr, sizeof(ip_addr), subnet_mask, + sizeof(subnet_mask))) { + LOG_ERR("CGCONTRDP: failed to split IPv4+subnet: %s", addr_field); + return; + } + if (!parse_ip(true, ip_addr, &socket_data->ipv4.new_addr)) { + return; + } + if (!parse_ip(true, subnet_mask, &socket_data->ipv4.subnet)) { + return; + } + if (gw_field && !parse_ip(true, gw_field, &socket_data->ipv4.gateway)) { + return; + } + } +#else + ARG_UNUSED(gw_field); +#endif + +#ifdef CONFIG_NET_IPV6 + if (addr_field && strchr(addr_field, ':') && + !parse_ip(false, addr_field, &socket_data->ipv6.new_addr)) { + return; + } +#endif + /* Update DNS and configure interface */ + if (!update_dns(socket_data, +#ifdef CONFIG_NET_IPV4 + (addr_field && strchr(addr_field, '.') && !strchr(addr_field, ':')), +#else + false, +#endif + dns_field ? dns_field : "")) { + return; + } + /* Configure the interface addresses so net_if_is_up()/address selection + * will succeed before attempting to reconfigure the resolver. + */ +#ifdef CONFIG_NET_IPV4 + set_iface(socket_data, (addr_field && strchr(addr_field, '.') && !strchr(addr_field, ':'))); +#elif defined(CONFIG_NET_IPV6) + set_iface(socket_data, false); +#endif + + socket_data->dns.ready = false; + LOG_DBG("CGCONTRDP processed, dns strings: v4=%s v6=%s", +#ifdef CONFIG_NET_IPV4 + socket_data->dns.v4_string, +#else + "", +#endif +#ifdef CONFIG_NET_IPV6 + socket_data->dns.v6_string +#else + "" +#endif + ); +} +/* ===== Network / Parsing Utilities =================================== + * Helpers that operate on IP address parsing and DNS/address helpers. + */ +static bool parse_ip(bool is_ipv4, const char *ip_str, void *out_addr) +{ + int ret = net_addr_pton(is_ipv4 ? AF_INET : AF_INET6, ip_str, out_addr); + + LOG_DBG("Parsing %s address: %s -> %s", is_ipv4 ? "IPv4" : "IPv6", ip_str, + (ret < 0) ? "FAIL" : "OK"); + if (ret < 0) { + LOG_ERR("Invalid IP address: %s", ip_str); + return false; + } + return true; +} + +static bool update_dns(struct hl78xx_socket_data *socket_data, bool is_ipv4, const char *dns_str) +{ + int ret; + + /* ===== Interface helpers ============================================== + * Helpers that configure the network interface for IPv4/IPv6. + */ + LOG_DBG("Updating DNS (%s): %s", is_ipv4 ? "IPv4" : "IPv6", dns_str); +#ifdef CONFIG_NET_IPV4 + if (is_ipv4) { + ret = strncmp(dns_str, socket_data->dns.v4_string, strlen(dns_str)); + if (ret != 0) { + LOG_DBG("New IPv4 DNS differs from current, marking dns_ready = false"); + socket_data->dns.ready = false; + } + strncpy(socket_data->dns.v4_string, dns_str, sizeof(socket_data->dns.v4_string)); + socket_data->dns.v4_string[sizeof(socket_data->dns.v4_string) - 1] = '\0'; + return parse_ip(true, socket_data->dns.v4_string, &socket_data->dns.v4); + } +#else + if (is_ipv4) { + LOG_DBG("IPv4 DNS reported but IPv4 disabled in build; ignoring"); + return false; + } +#endif /* CONFIG_NET_IPV4 */ +#ifdef CONFIG_NET_IPV6 + else { + ret = strncmp(dns_str, socket_data->dns.v6_string, strlen(dns_str)); + if (ret != 0) { + LOG_DBG("New IPv6 DNS differs from current, marking dns_ready = false"); + socket_data->dns.ready = false; + } + strncpy(socket_data->dns.v6_string, dns_str, sizeof(socket_data->dns.v6_string)); + socket_data->dns.v6_string[sizeof(socket_data->dns.v6_string) - 1] = '\0'; + + if (!parse_ip(false, socket_data->dns.v6_string, &socket_data->dns.v6)) { + return false; + } + + net_addr_ntop(AF_INET6, &socket_data->dns.v6, socket_data->dns.v6_string, + sizeof(socket_data->dns.v6_string)); + LOG_DBG("Parsed IPv6 DNS: %s", socket_data->dns.v6_string); + } +#endif /* CONFIG_NET_IPV6 */ + return true; +} + +static void set_iface(struct hl78xx_socket_data *socket_data, bool is_ipv4) +{ + if (!socket_data->net_iface) { + LOG_DBG("No network interface set. Skipping iface config."); + return; + } + LOG_DBG("Setting %s interface address...", is_ipv4 ? "IPv4" : "IPv6"); + if (is_ipv4) { +#ifdef CONFIG_NET_IPV4 + if (socket_data->ipv4.addr.s_addr != 0) { + net_if_ipv4_addr_rm(socket_data->net_iface, &socket_data->ipv4.addr); + } + /* Use MANUAL so the stack treats this as a configured address and it is + * available for source address selection immediately. + */ + if (!net_if_ipv4_addr_add(socket_data->net_iface, &socket_data->ipv4.new_addr, + NET_ADDR_MANUAL, 0)) { + LOG_ERR("Failed to set IPv4 interface address."); + } + + net_if_ipv4_set_netmask_by_addr(socket_data->net_iface, &socket_data->ipv4.new_addr, + &socket_data->ipv4.subnet); + net_if_ipv4_set_gw(socket_data->net_iface, &socket_data->ipv4.gateway); + + net_ipaddr_copy(&socket_data->ipv4.addr, &socket_data->ipv4.new_addr); + LOG_DBG("IPv4 interface configuration complete."); + + (void)net_if_up(socket_data->net_iface); +#else + LOG_DBG("IPv4 disabled: skipping IPv4 interface configuration"); +#endif /* CONFIG_NET_IPV4 */ + } +#ifdef CONFIG_NET_IPV6 + else { + net_if_ipv6_addr_rm(socket_data->net_iface, &socket_data->ipv6.addr); + + if (!net_if_ipv6_addr_add(socket_data->net_iface, &socket_data->ipv6.new_addr, + NET_ADDR_MANUAL, 0)) { + LOG_ERR("Failed to set IPv6 interface address."); + } else { + LOG_DBG("IPv6 interface configuration complete."); + } + /* Ensure iface up after adding address */ + (void)net_if_up(socket_data->net_iface); + } +#endif /* CONFIG_NET_IPV6 */ +} + +static bool split_ipv4_and_subnet(const char *combined, char *ip_out, size_t ip_out_len, + char *subnet_out, size_t subnet_out_len) +{ + int dot_count = 0; + const char *ptr = combined; + const char *split = NULL; + size_t ip_len = 0; + + while (*ptr && dot_count < 4) { + if (*ptr == '.') { + dot_count++; + if (dot_count == 4) { + split = ptr; + break; + } + } + ptr++; + } + if (!split) { + LOG_ERR("Invalid IPv4 + subnet format: %s", combined); + return false; + } + + ip_len = split - combined; + if (ip_len >= ip_out_len) { + ip_len = ip_out_len - 1; + } + strncpy(ip_out, combined, ip_len); + ip_out[ip_len] = '\0'; + strncpy(subnet_out, split + 1, subnet_out_len); + subnet_out[subnet_out_len - 1] = '\0'; + LOG_DBG("Extracted IP: %s, Subnet: %s", ip_out, subnet_out); + return true; +} + +/* ===== Validation ==================================================== + * Small validation helpers used by send/recv paths. + */ +static int validate_socket(const struct modem_socket *sock, struct hl78xx_socket_data *socket_data) +{ + if (!sock) { + errno = EINVAL; + return -1; + } + + bool not_connected = (!sock->is_connected && sock->type != SOCK_DGRAM); + bool tcp_disconnected = + (sock->type == SOCK_STREAM && + !socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].is_connected); + bool udp_not_created = + (sock->type == SOCK_DGRAM && + !socket_data->udp_conn_status[HL78XX_UDP_STATUS_ID(sock->id)].is_created); + + if (not_connected || tcp_disconnected || udp_not_created) { + errno = ENOTCONN; + return -1; + } + + return 0; +} + +/* ===== Parser helpers ================================================ + * Helpers that implement the streaming parser for incoming socket payloads + * and chat end-delimiter/EOF matching logic. + */ +static void parser_reset(struct hl78xx_socket_data *socket_data) +{ + memset(&socket_data->receive_buf, 0, sizeof(socket_data->receive_buf)); + socket_data->parser_match_found = false; +} + +static void found_reset(struct hl78xx_socket_data *socket_data) +{ + if (!socket_data) { + return; + } + /* Clear all parser progress state so a new transfer can start cleanly. */ + socket_data->parser_state = HL78XX_PARSER_IDLE; + socket_data->parser_match_found = false; + socket_data->parser_socket_data_received = false; + socket_data->parser_eof_detected = false; + socket_data->parser_ok_detected = false; +} + +static bool modem_chat_parse_end_del_start(struct hl78xx_socket_data *socket_data, + struct modem_chat *chat) +{ + if (socket_data->receive_buf.len == 0) { + return false; + } + /* If the last received byte matches any of the delimiter bytes, we are + * starting the end-delimiter sequence. Use memchr to avoid an explicit + * loop and to be clearer about intent. + */ + return memchr(chat->delimiter, + socket_data->receive_buf.buf[socket_data->receive_buf.len - 1], + chat->delimiter_size) != NULL; +} + +static bool modem_chat_parse_end_del_complete(struct hl78xx_socket_data *socket_data, + struct modem_chat *chat) +{ + if (socket_data->receive_buf.len < chat->delimiter_size) { + return false; + } + + return memcmp(&socket_data->receive_buf + .buf[socket_data->receive_buf.len - chat->delimiter_size], + chat->delimiter, chat->delimiter_size) == 0; +} + +static bool modem_chat_match_matches_received(struct hl78xx_socket_data *socket_data, + const char *match, uint16_t match_size) +{ + if (socket_data->receive_buf.len < match_size) { + return false; + } + return memcmp(socket_data->receive_buf.buf, match, match_size) == 0; +} + +static bool is_receive_buffer_full(struct hl78xx_socket_data *socket_data) +{ + return socket_data->receive_buf.len >= ARRAY_SIZE(socket_data->receive_buf.buf); +} + +static void handle_expected_length_decrement(struct hl78xx_socket_data *socket_data) +{ + /* Decrement expected length if CONNECT matched and expected length > 0 */ + if (socket_data->parser_state == HL78XX_PARSER_CONNECT_MATCHED && + socket_data->expected_buf_len > 0) { + socket_data->expected_buf_len--; + } +} + +static bool is_end_delimiter_only(struct hl78xx_socket_data *socket_data) +{ + return socket_data->receive_buf.len == socket_data->mdata_global->chat.delimiter_size; +} + +static bool is_valid_eof_index(struct hl78xx_socket_data *socket_data, uint8_t size_match) +{ + socket_data->parser_start_index_eof = socket_data->receive_buf.len - size_match - 2; + return socket_data->parser_start_index_eof < ARRAY_SIZE(socket_data->receive_buf.buf); +} + +/* Handle EOF pattern: if EOF_PATTERN is found at the expected location, + * push socket payload (excluding EOF marker) into the ring buffer. + * Returns number of bytes pushed on success, 0 otherwise. + */ +static int handle_eof_pattern(struct hl78xx_socket_data *socket_data) +{ + uint8_t size_match = strlen(EOF_PATTERN); + + if (socket_data->receive_buf.len < size_match + 2) { + return 0; + } + if (!is_valid_eof_index(socket_data, size_match)) { + return 0; + } + if (strncmp(&socket_data->receive_buf.buf[socket_data->parser_start_index_eof], EOF_PATTERN, + size_match) == 0) { + int ret = ring_buf_put(socket_data->buf_pool, socket_data->receive_buf.buf, + socket_data->parser_start_index_eof); + + if (ret <= 0) { + LOG_ERR("ring_buf_put failed: %d", ret); + return 0; + } + + /* Mark that payload was successfully pushed and EOF was detected */ + socket_data->parser_socket_data_received = true; + socket_data->parser_eof_detected = true; + LOG_DBG("pushed %d bytes to ring_buf; " + "collected_buf_len(before)=%u", + ret, socket_data->collected_buf_len); + socket_data->collected_buf_len += ret; + LOG_DBG("parser_socket_data_received=1 " + "collected_buf_len(after)=%u", + socket_data->collected_buf_len); + return ret; + } + return 0; +} + +/* Helper: centralize handling when the chat end-delimiter has been fully + * received. Returns true if caller should return immediately after handling. + */ +static bool handle_delimiter_complete(struct hl78xx_socket_data *socket_data, + struct modem_chat *chat) +{ + if (!modem_chat_parse_end_del_complete(socket_data, chat)) { + return false; + } + + if (is_end_delimiter_only(socket_data)) { + parser_reset(socket_data); + return true; + } + + socket_data->parser_size_of_socketdata = socket_data->receive_buf.len; + if (socket_data->parser_state == HL78XX_PARSER_CONNECT_MATCHED && + socket_data->parser_state != HL78XX_PARSER_EOF_OK_MATCHED) { + size_t connect_len = strlen(CONNECT_STRING); + size_t connect_plus_delim = connect_len + chat->delimiter_size; + + /* Case 1: Drop the initial "CONNECT" line including its CRLF */ + if (socket_data->receive_buf.len == connect_plus_delim && + modem_chat_match_matches_received(socket_data, CONNECT_STRING, + (uint16_t)connect_len)) { + parser_reset(socket_data); + return true; + } + + /* Case 2: Try to handle EOF; only reset if EOF was actually found/pushed */ + if (handle_eof_pattern(socket_data) > 0) { + parser_reset(socket_data); + return true; + } + + /* Not the initial CONNECT+CRLF and no EOF yet -> keep accumulating */ + return false; + } + + /* For other states, treat CRLF as end-of-line and reset as before */ + parser_reset(socket_data); + return true; +} + +/* Convenience helper for matching an exact string against the receive buffer. + * This consolidates the repeated pattern of checking length and content. + */ +static inline bool modem_chat_match_exact(struct hl78xx_socket_data *socket_data, const char *match) +{ + size_t size_match = strlen(match); + + if (socket_data->receive_buf.len != size_match) { + return false; + } + return modem_chat_match_matches_received(socket_data, match, (uint16_t)size_match); +} + +static void socket_process_bytes(struct hl78xx_socket_data *socket_data, char byte) +{ + const size_t cme_size = strlen(CME_ERROR_STRING); + + if (is_receive_buffer_full(socket_data)) { + LOG_WRN("Receive buffer overrun"); + parser_reset(socket_data); + return; + } + socket_data->receive_buf.buf[socket_data->receive_buf.len++] = byte; + handle_expected_length_decrement(socket_data); + if (handle_delimiter_complete(socket_data, &socket_data->mdata_global->chat)) { + return; + } + if (modem_chat_parse_end_del_start(socket_data, &socket_data->mdata_global->chat)) { + return; + } + if (socket_data->parser_state != HL78XX_PARSER_ERROR_MATCHED && + socket_data->parser_state != HL78XX_PARSER_CONNECT_MATCHED) { + /* Exact CONNECT match: length must equal CONNECT string length */ + if (modem_chat_match_exact(socket_data, CONNECT_STRING)) { + socket_data->parser_state = HL78XX_PARSER_CONNECT_MATCHED; + LOG_DBG("CONNECT matched. Expecting %d more bytes.", + socket_data->expected_buf_len); + return; + } + /* Partial CME ERROR match: length must be at least CME string length */ + if (socket_data->receive_buf.len >= cme_size && + modem_chat_match_matches_received(socket_data, CME_ERROR_STRING, + (uint16_t)cme_size)) { + socket_data->parser_state = + HL78XX_PARSER_ERROR_MATCHED; /* prevent further parsing */ + LOG_ERR("CME ERROR received. Connection failed."); + socket_data->expected_buf_len = 0; + socket_data->collected_buf_len = 0; + parser_reset(socket_data); + socket_data->socket_data_error = true; + k_sem_give(&socket_data->mdata_global->script_stopped_sem_rx_int); + return; + } + } + if (socket_data->parser_state == HL78XX_PARSER_CONNECT_MATCHED && + socket_data->parser_state != HL78XX_PARSER_EOF_OK_MATCHED && + modem_chat_match_exact(socket_data, OK_STRING)) { + socket_data->parser_state = HL78XX_PARSER_EOF_OK_MATCHED; + /* Mark that OK was observed. Payload may have already been pushed by EOF handler. + */ + socket_data->parser_ok_detected = true; + LOG_DBG("OK matched. parser_ok_detected=%d parser_socket_data_received=%d " + "collected=%u", + socket_data->parser_ok_detected, socket_data->parser_socket_data_received, + socket_data->collected_buf_len); + } +} + +/* ===== Modem pipe handlers =========================================== + * Handlers and callbacks for modem pipe events (receive/transmit). + */ +static int modem_process_handler(struct hl78xx_data *data) +{ + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + char work_buf_local[HL78XX_UART_PIPE_WORK_SOCKET_BUFFER_SIZE] = {0}; + int recv_len = 0; + int work_len = 0; + /* If no more data is expected, set leftover state and return */ + if (socket_data->expected_buf_len == 0) { + LOG_DBG("No more data expected"); + atomic_set_bit(&socket_data->mdata_global->state_leftover, + MODEM_SOCKET_DATA_LEFTOVER_STATE_BIT); + return 0; + } + + /* Use a small stack buffer for the pipe read to avoid TU-global BSS */ + work_len = MIN(sizeof(work_buf_local), socket_data->expected_buf_len); + recv_len = + modem_pipe_receive(socket_data->mdata_global->uart_pipe, work_buf_local, work_len); + if (recv_len <= 0) { + return recv_len; + } + +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_HEXDUMP_DBG(work_buf_local, recv_len, "Received bytes:"); +#endif /* CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG */ + for (int i = 0; i < recv_len; i++) { + socket_process_bytes(socket_data, work_buf_local[i]); + } + + LOG_DBG("post-process state=%d recv_len=%d recv_buf.len=%u " + "expected=%u collected=%u socket_data_received=%d", + socket_data->parser_state, recv_len, socket_data->receive_buf.len, + socket_data->expected_buf_len, socket_data->collected_buf_len, + socket_data->parser_socket_data_received); + + /* Check if we've completed reception */ + if (socket_data->parser_eof_detected && socket_data->parser_ok_detected && + socket_data->parser_socket_data_received) { + LOG_DBG("All data received: %d bytes", socket_data->parser_size_of_socketdata); + socket_data->expected_buf_len = 0; + LOG_DBG("About to give RX semaphore (eof=%d ok=%d socket_data_received=%d " + "collected=%u)", + socket_data->parser_eof_detected, socket_data->parser_ok_detected, + socket_data->parser_socket_data_received, socket_data->collected_buf_len); + k_sem_give(&socket_data->mdata_global->script_stopped_sem_rx_int); + /* Clear parser progress after the receiver has been notified */ + found_reset(socket_data); + } + return 0; +} + +static void modem_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event, + void *user_data) +{ + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + + switch (event) { + case MODEM_PIPE_EVENT_RECEIVE_READY: + (void)modem_process_handler(data); + break; + + case MODEM_PIPE_EVENT_TRANSMIT_IDLE: + k_sem_give(&data->script_stopped_sem_tx_int); + break; + + default: + LOG_DBG("Unhandled event: %d", event); + break; + } +} + +void notif_carrier_off(const struct device *dev) +{ + struct hl78xx_data *data = dev->data; + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + + net_if_carrier_off(socket_data->net_iface); +} + +void notif_carrier_on(const struct device *dev) +{ + struct hl78xx_data *data = dev->data; + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + + net_if_carrier_on(socket_data->net_iface); +} + +void iface_status_work_cb(struct hl78xx_data *data, modem_chat_script_callback script_user_callback) +{ + + const char *cmd = "AT+CGCONTRDP=1"; + int ret = 0; + + ret = modem_dynamic_cmd_send(data, script_user_callback, cmd, strlen(cmd), + hl78xx_get_cgdcontrdp_match(), 1, false); + if (ret < 0) { + LOG_ERR("Failed to send AT+CGCONTRDP command: %d", ret); + return; + } +} + +void dns_work_cb(const struct device *dev, bool hard_reset) +{ +#if defined(CONFIG_DNS_RESOLVER) && !defined(CONFIG_DNS_SERVER_IP_ADDRESSES) + int ret; + struct hl78xx_data *data = dev->data; + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + struct dns_resolve_context *dnsCtx; + struct sockaddr temp_addr; + bool valid_address = false; + bool retry = false; + const char *const dns_servers_str[DNS_SERVERS_COUNT] = { +#ifdef CONFIG_NET_IPV6 + socket_data->dns.v6_string, +#endif +#ifdef CONFIG_NET_IPV4 + socket_data->dns.v4_string, +#endif + NULL}; + const char *dns_servers_wrapped[ARRAY_SIZE(dns_servers_str)]; + + if (hard_reset) { + LOG_DBG("Resetting DNS resolver"); + dnsCtx = dns_resolve_get_default(); + if (!dnsCtx) { + LOG_WRN("No default DNS resolver context available; skipping " + "reconfigure"); + socket_data->dns.ready = true; + return; + } + if (dnsCtx->state != DNS_RESOLVE_CONTEXT_INACTIVE) { + dns_resolve_close(dnsCtx); + } + socket_data->dns.ready = false; + } + +#ifdef CONFIG_NET_IPV6 + valid_address = net_ipaddr_parse(socket_data->dns.v6_string, + strlen(socket_data->dns.v6_string), &temp_addr); + if (!valid_address && IS_ENABLED(CONFIG_NET_IPV4)) { + /* IPv6 DNS string is not valid, replace it with IPv4 address and recheck */ +#ifdef CONFIG_NET_IPV4 + strncpy(socket_data->dns.v6_string, socket_data->dns.v4_string, + sizeof(socket_data->dns.v4_string) - 1); + valid_address = net_ipaddr_parse(socket_data->dns.v6_string, + strlen(socket_data->dns.v6_string), &temp_addr); +#endif + } +#elif defined(CONFIG_NET_IPV4) + valid_address = net_ipaddr_parse(socket_data->dns.v4_string, + strlen(socket_data->dns.v4_string), &temp_addr); +#else + /* No IP stack configured */ + valid_address = false; +#endif + if (!valid_address) { + LOG_WRN("No valid DNS address!"); + return; + } + if (!socket_data->net_iface || !net_if_is_up(socket_data->net_iface) || + socket_data->dns.ready) { + LOG_DBG("DNS already ready or net_iface problem %d %d %d", !socket_data->net_iface, + !net_if_is_up(socket_data->net_iface), socket_data->dns.ready); + return; + } + memcpy(dns_servers_wrapped, dns_servers_str, sizeof(dns_servers_wrapped)); + /* set new DNS addr in DNS resolver */ + LOG_DBG("Refresh DNS resolver"); + dnsCtx = dns_resolve_get_default(); + ret = dns_resolve_reconfigure(dnsCtx, dns_servers_wrapped, NULL, DNS_SOURCE_MANUAL); + if (ret < 0) { + LOG_ERR("dns_resolve_reconfigure fail (%d)", ret); + retry = true; + } else { + LOG_DBG("DNS ready"); + socket_data->dns.ready = true; + } + if (retry) { + LOG_WRN("DNS not ready, scheduling a retry"); + } +#endif +} + +static int on_cmd_sockread_common(int socket_id, uint16_t socket_data_length, uint16_t len, + void *user_data) +{ + struct modem_socket *sock; + struct socket_read_data *sock_data; + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + int ret = 0; + + sock = modem_socket_from_fd(&socket_data->socket_config, socket_id); + if (!sock) { + LOG_ERR("Socket not found! (%d)", socket_id); + return -EINVAL; + } + sock_data = sock->data; + if (!sock_data) { + LOG_ERR("Socket data missing! Ignoring (%d)", socket_id); + return -EINVAL; + } + if (socket_data->socket_data_error && socket_data->collected_buf_len == 0) { + errno = ECONNABORTED; + return -ECONNABORTED; + } + if ((len <= 0) || socket_data_length <= 0 || socket_data->collected_buf_len < (size_t)len) { + LOG_ERR("%d Invalid data length: %d %d %d Aborting!", __LINE__, socket_data_length, + (int)len, socket_data->collected_buf_len); + return -EAGAIN; + } + if (len < socket_data_length) { + LOG_DBG("Incomplete data received! Expected: %d, Received: %d", socket_data_length, + len); + return -EAGAIN; + } + ret = ring_buf_get(socket_data->buf_pool, sock_data->recv_buf, len); + if (ret != len) { + LOG_ERR("%d Data retrieval mismatch: expected %u, got %d", __LINE__, len, ret); + return -EAGAIN; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_HEXDUMP_DBG(sock_data->recv_buf, ret, "Received Data:"); +#endif /* CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG */ + if (sock_data->recv_buf_len < (size_t)len) { + LOG_ERR("Buffer overflow! Received: %zu vs. Available: %zu", len, + sock_data->recv_buf_len); + return -EINVAL; + } + if ((size_t)len != (size_t)socket_data_length) { + LOG_ERR("Data mismatch! Copied: %zu vs. Received: %d", len, socket_data_length); + return -EINVAL; + } + sock_data->recv_read_len = len; + /* Remove packet from list */ + modem_socket_next_packet_size(&socket_data->socket_config, sock); + modem_socket_packet_size_update(&socket_data->socket_config, sock, -socket_data_length); + socket_data->collected_buf_len = 0; + return len; +} + +int modem_handle_data_capture(size_t target_len, struct hl78xx_data *data) +{ + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + + return on_cmd_sockread_common(socket_data->current_sock_fd, socket_data->sizeof_socket_data, + target_len, data); +} + +static int extract_ip_family_and_port(const struct sockaddr *addr, int *af, uint16_t *port) +{ +#if defined(CONFIG_NET_IPV6) + if (addr->sa_family == AF_INET6) { + *port = ntohs(net_sin6(addr)->sin6_port); + *af = MDM_HL78XX_SOCKET_AF_IPV6; + } else { +#endif /* CONFIG_NET_IPV6 */ +#if defined(CONFIG_NET_IPV4) + if (addr->sa_family == AF_INET) { + *port = ntohs(net_sin(addr)->sin_port); + *af = MDM_HL78XX_SOCKET_AF_IPV4; + } else { +#endif /* CONFIG_NET_IPV4 */ + errno = EAFNOSUPPORT; + return -1; +#if defined(CONFIG_NET_IPV4) + } +#endif /* CONFIG_NET_IPV4 */ +#if defined(CONFIG_NET_IPV6) + } +#endif /* CONFIG_NET_IPV6 */ + return 0; +} + +static int format_ip_and_setup_tls(struct hl78xx_socket_data *socket_data, + const struct sockaddr *addr, char *ip_str, size_t ip_str_len, + struct modem_socket *sock) +{ + int ret = modem_context_sprint_ip_addr(addr, ip_str, ip_str_len); + + if (ret != 0) { + LOG_ERR("Failed to format IP!"); + errno = ENOMEM; + return -1; + } + if (sock->ip_proto == IPPROTO_TCP) { + /* Determine actual length of the formatted IP string (it may be + * shorter than the provided buffer size). Copy at most + * MDM_MAX_HOSTNAME_LEN - 1 bytes and ensure NUL-termination to + * avoid writing past the hostname buffer. + */ + size_t actual_len = strnlen(ip_str, ip_str_len); + size_t copy_len = MIN(actual_len, (size_t)MDM_MAX_HOSTNAME_LEN - 1); + + if (copy_len > 0) { + memcpy(socket_data->tls.hostname, ip_str, copy_len); + } + socket_data->tls.hostname[copy_len] = '\0'; + socket_data->tls.hostname_set = false; + } + return 0; +} + +static int send_tcp_or_tls_config(struct modem_socket *sock, uint16_t dst_port, int af, int mode, + struct hl78xx_socket_data *socket_data) +{ + int ret = 0; + char cmd_buf[sizeof("AT+KTCPCFG=#,#,\"" MODEM_HL78XX_ADDRESS_FAMILY_FORMAT + "\",#####,,,,#,,#") + + MDM_MAX_HOSTNAME_LEN + NET_IPV6_ADDR_LEN]; + + snprintk(cmd_buf, sizeof(cmd_buf), "AT+KTCPCFG=1,%d,\"%s\",%u,,,,%d,%s,0", mode, + socket_data->tls.hostname, dst_port, af, mode == 3 ? "0" : ""); + ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf), + hl78xx_get_ktcpcfg_match(), 1, false); + if (ret < 0 || + socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].is_created == false) { + LOG_ERR("%s ret:%d", cmd_buf, ret); + modem_socket_put(&socket_data->socket_config, sock->sock_fd); + /* Map negative internal return codes to positive errno; fall back to EIO + * when the code is non-negative but the operation failed. + */ + hl78xx_set_errno_from_code(ret); + return -1; + } + return 0; +} + +static int send_udp_config(const struct sockaddr *addr, struct hl78xx_socket_data *socket_data, + struct modem_socket *sock) +{ + int ret = 0; + char cmd_buf[64]; + uint8_t display_data_urc = 0; + +#if defined(CONFIG_MODEM_HL78XX_SOCKET_UDP_DISPLAY_DATA_URC) + display_data_urc = CONFIG_MODEM_HL78XX_SOCKET_UDP_DISPLAY_DATA_URC; +#endif + snprintk(cmd_buf, sizeof(cmd_buf), "AT+KUDPCFG=1,%u,,%d,,,%d,%d", 0, display_data_urc, + (addr->sa_family - 1), 0); + + ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf), + hl78xx_get_kudpind_match(), 1, false); + if (ret < 0) { + goto error; + } + return 0; +error: + LOG_ERR("%s ret:%d", cmd_buf, ret); + modem_socket_put(&socket_data->socket_config, sock->sock_fd); + hl78xx_set_errno_from_code(ret); + return -1; +} + +static int create_socket(struct modem_socket *sock, const struct sockaddr *addr, + struct hl78xx_socket_data *data) +{ + LOG_DBG("entry fd=%d id=%d", sock->sock_fd, sock->id); + int af; + uint16_t dst_port; + char ip_str[NET_IPV6_ADDR_LEN]; + bool is_udp; + int mode; + int ret; + /* save destination address */ + memcpy(&sock->dst, addr, sizeof(*addr)); + if (extract_ip_family_and_port(addr, &af, &dst_port) < 0) { + return -1; + } + if (format_ip_and_setup_tls(data, addr, ip_str, sizeof(ip_str), sock) < 0) { + return -1; + } + is_udp = (sock->ip_proto == IPPROTO_UDP); + if (is_udp) { + ret = send_udp_config(addr, data, sock); + LOG_DBG("send_udp_config returned %d", ret); + return ret; + } + mode = (sock->ip_proto == IPPROTO_TLS_1_2) ? 3 : 0; + /* only TCP and TLS are supported */ + if (sock->ip_proto != IPPROTO_TCP && sock->ip_proto != IPPROTO_TLS_1_2) { + LOG_ERR("Unsupported protocol: %d", sock->ip_proto); + errno = EPROTONOSUPPORT; + return -1; + } + LOG_DBG("TCP/TLS socket, calling send_tcp_or_tls_config af=%d port=%u " + "mode=%d", + af, dst_port, mode); + ret = send_tcp_or_tls_config(sock, dst_port, af, mode, data); + LOG_DBG("send_tcp_or_tls_config returned %d", ret); + return ret; +} + +static int socket_close(struct hl78xx_socket_data *socket_data, struct modem_socket *sock) +{ + char buf[sizeof("AT+KTCPCLOSE=##\r")]; + int ret = 0; + + if (sock->ip_proto == IPPROTO_UDP) { + snprintk(buf, sizeof(buf), "AT+KUDPCLOSE=%d", sock->id); + } else { + snprintk(buf, sizeof(buf), "AT+KTCPCLOSE=%d", sock->id); + } + ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, buf, strlen(buf), + hl78xx_get_sockets_allow_matches(), + hl78xx_get_sockets_allow_matches_size(), false); + if (ret < 0) { + LOG_ERR("%s ret:%d", buf, ret); + } + return ret; +} + +static int socket_delete(struct hl78xx_socket_data *socket_data, struct modem_socket *sock) +{ + char buf[sizeof("AT+KTCPDEL=##\r")]; + int ret = 0; + + if (sock->ip_proto == IPPROTO_UDP) { + /** + * snprintk(buf, sizeof(buf), "AT+KUDPDEL=%d", sock->id); + * No need to delete udp config here according to ref guide. The at UDPCLOSE + * automatically deletes the session + */ + return 0; + } + snprintk(buf, sizeof(buf), "AT+KTCPDEL=%d", sock->id); + ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, buf, strlen(buf), + hl78xx_get_sockets_allow_matches(), + hl78xx_get_sockets_allow_matches_size(), false); + if (ret < 0) { + LOG_ERR("%s ret:%d", buf, ret); + } + return ret; +} + +/* ===== Socket Offload OPS ======================================== */ + +static int offload_socket(int family, int type, int proto) +{ + int ret; + /* defer modem's socket create call to bind(); use accessor and check */ + struct hl78xx_socket_data *g = hl78xx_get_socket_global(); + + HL78XX_LOG_DBG("%d %d %d %d", __LINE__, family, type, proto); + + if (!g) { + LOG_ERR("Socket global not initialized"); + errno = ENODEV; + return -1; + } + ret = modem_socket_get(&g->socket_config, family, type, proto); + if (ret < 0) { + hl78xx_set_errno_from_code(ret); + return -1; + } + errno = 0; + return ret; +} + +static int offload_close(void *obj) +{ + struct modem_socket *sock = (struct modem_socket *)obj; + struct hl78xx_socket_data *socket_data = NULL; + + if (!sock) { + return -EINVAL; + } + /* Recover the containing instance; guard in case sock isn't from this driver */ + socket_data = hl78xx_get_socket_global(); + if (!socket_data || !socket_data->offload_dev || + socket_data->offload_dev->data != socket_data) { + LOG_WRN("parent mismatch: parent != offload_dev->data (%p != %p)", socket_data, + socket_data ? socket_data->offload_dev->data : NULL); + errno = EINVAL; + return -1; + } + /* make sure socket is allocated and assigned an id */ + if (modem_socket_id_is_assigned(&socket_data->socket_config, sock) == false) { + return 0; + } + if (validate_socket(sock, socket_data) == 0) { + socket_close(socket_data, sock); + socket_delete(socket_data, sock); + modem_socket_put(&socket_data->socket_config, sock->sock_fd); + sock->is_connected = false; + } + /* Consider here successfully socket is closed */ + return 0; +} + +static int offload_bind(void *obj, const struct sockaddr *addr, socklen_t addrlen) +{ + struct modem_socket *sock = (struct modem_socket *)obj; + struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock); + int ret = 0; + + if (!sock || !socket_data || !socket_data->offload_dev) { + errno = EINVAL; + return -1; + } + LOG_DBG("entry for socket fd=%d id=%d", ((struct modem_socket *)obj)->sock_fd, + ((struct modem_socket *)obj)->id); + /* Save bind address information */ + memcpy(&sock->src, addr, sizeof(*addr)); + /* Check if socket is allocated */ + if (modem_socket_is_allocated(&socket_data->socket_config, sock)) { + /* Trigger socket creation */ + ret = create_socket(sock, addr, socket_data); + LOG_DBG("create_socket returned %d", ret); + if (ret < 0) { + LOG_ERR("%d %s SOCKET CREATION", __LINE__, __func__); + return -1; + } + } + return 0; +} + +static int offload_connect(void *obj, const struct sockaddr *addr, socklen_t addrlen) +{ + struct modem_socket *sock = (struct modem_socket *)obj; + struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock); + int ret = 0; + char cmd_buf[sizeof("AT+KTCPCFG=#\r")]; + char ip_str[NET_IPV6_ADDR_LEN]; + + if (!addr || !socket_data || !socket_data->offload_dev) { + errno = EINVAL; + return -1; + } + if (!hl78xx_is_registered(socket_data->mdata_global)) { + errno = ENETUNREACH; + return -1; + } + /* make sure socket has been allocated */ + if (modem_socket_is_allocated(&socket_data->socket_config, sock) == false) { + LOG_ERR("Invalid socket_id(%d) from fd:%d", sock->id, sock->sock_fd); + errno = EINVAL; + return -1; + } + /* make sure we've created the socket */ + if (modem_socket_id_is_assigned(&socket_data->socket_config, sock) == false) { + LOG_DBG("%d no socket assigned", __LINE__); + if (create_socket(sock, addr, socket_data) < 0) { + return -1; + } + } + memcpy(&sock->dst, addr, sizeof(*addr)); + /* skip socket connect if UDP */ + if (sock->ip_proto == IPPROTO_UDP) { + errno = 0; + return 0; + } + ret = modem_context_sprint_ip_addr(addr, ip_str, sizeof(ip_str)); + if (ret != 0) { + hl78xx_set_errno_from_code(ret); + LOG_ERR("Error formatting IP string %d", ret); + return -1; + } + /* send connect command */ + snprintk(cmd_buf, sizeof(cmd_buf), "AT+KTCPCNX=%d", sock->id); + ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf), + hl78xx_get_ktcpind_match(), 1, false); + if (ret < 0 || + socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].is_connected == false) { + sock->is_connected = false; + LOG_ERR("%s ret:%d", cmd_buf, ret); + /* Map tcp_conn_status.err_code: + * - positive values are assumed to be direct POSIX errno values -> pass + * through + * - zero or unknown -> use conservative EIO + */ + errno = (socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)].err_code > 0) + ? socket_data->tcp_conn_status[HL78XX_TCP_STATUS_ID(sock->id)] + .err_code + : EIO; + return -1; + } + sock->is_connected = true; + errno = 0; + return 0; +} + +static bool validate_recv_args(void *buf, size_t len, int flags) +{ + if (!buf || len == 0) { + errno = EINVAL; + return false; + } + if (flags & ZSOCK_MSG_PEEK) { + errno = ENOTSUP; + return false; + } + return true; +} + +static int wait_for_data_if_needed(struct hl78xx_socket_data *socket_data, + struct modem_socket *sock, int flags) +{ + int size = modem_socket_next_packet_size(&socket_data->socket_config, sock); + + if (size > 0) { + return size; + } + if (flags & ZSOCK_MSG_DONTWAIT) { + errno = EAGAIN; + return -1; + } + if (validate_socket(sock, socket_data) == -1) { + errno = 0; + return 0; + } + + modem_socket_wait_data(&socket_data->socket_config, sock); + return modem_socket_next_packet_size(&socket_data->socket_config, sock); +} + +static void prepare_read_command(struct hl78xx_socket_data *socket_data, char *sendbuf, + size_t bufsize, struct modem_socket *sock, size_t read_size) +{ + snprintk(sendbuf, bufsize, "AT+K%sRCV=%d,%zd%s", + sock->ip_proto == IPPROTO_UDP ? "UDP" : "TCP", sock->id, read_size, + socket_data->mdata_global->chat.delimiter); +} + +/* Perform the receive transaction: release chat, attach pipe, wait for tx sem, + * transmit read command, wait for rx sem and capture data. Returns 0 on + * success or a negative code which will be mapped by caller. + */ +static int hl78xx_perform_receive_transaction(struct hl78xx_socket_data *socket_data, + const char *sendbuf) +{ + int rv; + int ret; + + modem_chat_release(&socket_data->mdata_global->chat); + modem_pipe_attach(socket_data->mdata_global->uart_pipe, modem_pipe_callback, + socket_data->mdata_global); + + rv = k_sem_take(&socket_data->mdata_global->script_stopped_sem_tx_int, K_FOREVER); + if (rv < 0) { + LOG_ERR("%s: k_sem_take(tx) returned %d", __func__, rv); + return rv; + } + + ret = modem_pipe_transmit(socket_data->mdata_global->uart_pipe, (const uint8_t *)sendbuf, + strlen(sendbuf)); + if (ret < 0) { + LOG_ERR("Error sending read command: %d", ret); + return ret; + } + rv = k_sem_take(&socket_data->mdata_global->script_stopped_sem_rx_int, K_FOREVER); + if (rv < 0) { + return rv; + } + + rv = modem_handle_data_capture(socket_data->sizeof_socket_data, socket_data->mdata_global); + if (rv < 0) { + return rv; + } + + return 0; +} + +static void setup_socket_data(struct hl78xx_socket_data *socket_data, struct modem_socket *sock, + struct socket_read_data *sock_data, void *buf, size_t len, + struct sockaddr *from, uint16_t read_size) +{ + memset(sock_data, 0, sizeof(*sock_data)); + sock_data->recv_buf = buf; + sock_data->recv_buf_len = len; + sock_data->recv_addr = from; + sock->data = sock_data; + + socket_data->sizeof_socket_data = read_size; + socket_data->requested_socket_id = sock->id; + socket_data->current_sock_fd = sock->sock_fd; + socket_data->expected_buf_len = read_size + sizeof("\r\n") - 1 + + socket_data->mdata_global->buffers.eof_pattern_size + + sizeof(MODEM_STREAM_END_WORD) - 1; + socket_data->collected_buf_len = 0; + socket_data->socket_data_error = false; +} + +static void check_tcp_state_if_needed(struct hl78xx_socket_data *socket_data, + struct modem_socket *sock) +{ + const char *check_ktcp_stat = "AT+KTCPSTAT"; + /* Only check for TCP sockets */ + if (sock->type != SOCK_STREAM) { + return; + } + if (atomic_test_and_clear_bit(&socket_data->mdata_global->state_leftover, + MODEM_SOCKET_DATA_LEFTOVER_STATE_BIT) && + sock && sock->ip_proto == IPPROTO_TCP) { + modem_dynamic_cmd_send(socket_data->mdata_global, NULL, check_ktcp_stat, + strlen(check_ktcp_stat), hl78xx_get_ktcp_state_match(), 1, + true); + } +} + +static ssize_t offload_recvfrom(void *obj, void *buf, size_t len, int flags, struct sockaddr *from, + socklen_t *fromlen) +{ + struct modem_socket *sock = (struct modem_socket *)obj; + struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock); + char sendbuf[sizeof("AT+KUDPRCV=#,##########\r\n")]; + struct socket_read_data sock_data; + int next_packet_size = 0; + uint32_t max_data_length = 0; + uint16_t read_size = 0; + int trv = 0; + int ret; + + if (!sock || !socket_data || !socket_data->offload_dev) { + errno = EINVAL; + return -1; + } + /* If modem is not registered yet, propagate EAGAIN to indicate try again + * later. However, if the socket simply isn't connected (validate_socket + * returns -1) we return 0 with errno cleared so upper layers (eg. DNS + * dispatcher) treat this as no data available rather than an error and + * avoid noisy repeated error logs. + */ + if (!hl78xx_is_registered(socket_data->mdata_global)) { + errno = EAGAIN; + return -1; + } + if (validate_socket(sock, socket_data) == -1) { + errno = 0; + return 0; + } + + if (!validate_recv_args(buf, len, flags)) { + return -1; + } + ret = k_mutex_lock(&socket_data->mdata_global->tx_lock, K_SECONDS(1)); + if (ret < 0) { + LOG_ERR("Failed to acquire TX lock: %d", ret); + hl78xx_set_errno_from_code(ret); + return -1; + } + next_packet_size = wait_for_data_if_needed(socket_data, sock, flags); + if (next_packet_size <= 0) { + ret = next_packet_size; + goto exit; + } + max_data_length = + MDM_MAX_DATA_LENGTH - (socket_data->mdata_global->buffers.eof_pattern_size + + sizeof(MODEM_STREAM_STARTER_WORD) - 1); + /* limit read size to modem max data length */ + next_packet_size = MIN(next_packet_size, max_data_length); + /* limit read size to user buffer length */ + read_size = MIN(next_packet_size, len); + /* prepare socket data for the read operation */ + setup_socket_data(socket_data, sock, &sock_data, buf, len, from, read_size); + prepare_read_command(socket_data, sendbuf, sizeof(sendbuf), sock, read_size); + HL78XX_LOG_DBG("%d socket_fd: %d, socket_id: %d, expected_data_len: %d", __LINE__, + socket_data->current_sock_fd, socket_data->requested_socket_id, + socket_data->expected_buf_len); + LOG_HEXDUMP_DBG(sendbuf, strlen(sendbuf), "sending"); + trv = hl78xx_perform_receive_transaction(socket_data, sendbuf); + if (trv < 0) { + hl78xx_set_errno_from_code(trv); + ret = -1; + goto exit; + } + if (from && fromlen) { + *fromlen = sizeof(sock->dst); + memcpy(from, &sock->dst, *fromlen); + } + errno = 0; + ret = sock_data.recv_read_len; +exit: + k_mutex_unlock(&socket_data->mdata_global->tx_lock); + modem_chat_attach(&socket_data->mdata_global->chat, socket_data->mdata_global->uart_pipe); + socket_data->expected_buf_len = 0; + check_tcp_state_if_needed(socket_data, sock); + return ret; +} + +int check_if_any_socket_connected(const struct device *dev) +{ + struct hl78xx_data *data = dev->data; + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + struct modem_socket_config *cfg = &socket_data->socket_config; + + k_sem_take(&cfg->sem_lock, K_FOREVER); + for (int i = 0; i < cfg->sockets_len; i++) { + if (cfg->sockets[i].is_connected) { + /* if there is any socket connected */ + k_sem_give(&cfg->sem_lock); + return true; + } + } + k_sem_give(&cfg->sem_lock); + return false; +} + +/* ===== Send / Receive helpers ======================================== + * Helpers used by sendto/recv paths, preparing commands and transmitting + * data over the modem pipe. + */ +static int prepare_send_cmd(const struct modem_socket *sock, const struct sockaddr *dst_addr, + size_t buf_len, char *cmd_buf, size_t cmd_buf_size) +{ + int ret = 0; + + if (sock->ip_proto == IPPROTO_UDP) { + char ip_str[NET_IPV6_ADDR_LEN]; + uint16_t dst_port = 0; + + ret = modem_context_sprint_ip_addr(dst_addr, ip_str, sizeof(ip_str)); + if (ret < 0) { + LOG_ERR("Error formatting IP string %d", ret); + return ret; + } + ret = modem_context_get_addr_port(dst_addr, &dst_port); + if (ret < 0) { + LOG_ERR("Error getting port from IP address %d", ret); + return ret; + } + snprintk(cmd_buf, cmd_buf_size, "AT+KUDPSND=%d,\"%s\",%u,%zu", sock->id, ip_str, + dst_port, buf_len); + return 0; + } + + /* Default to TCP-style send command */ + snprintk(cmd_buf, cmd_buf_size, "AT+KTCPSND=%d,%zu", sock->id, buf_len); + return 0; +} + +static int send_data_buffer(struct hl78xx_socket_data *socket_data, const char *buf, + const size_t buf_len, int *sock_written) +{ + uint32_t offset = 0; + int len = buf_len; + int ret = 0; + + if (len == 0) { + LOG_DBG("%d No data to send", __LINE__); + return 0; + } + while (len > 0) { + LOG_DBG("waiting for TX semaphore (offset=%u len=%d)", offset, len); + if (k_sem_take(&socket_data->mdata_global->script_stopped_sem_tx_int, K_FOREVER) < + 0) { + LOG_ERR("%s: k_sem_take(tx) failed", __func__); + return -1; + } + ret = modem_pipe_transmit(socket_data->mdata_global->uart_pipe, + ((const uint8_t *)buf) + offset, len); + if (ret <= 0) { + LOG_ERR("Transmit error %d", ret); + return -1; + } + offset += ret; + len -= ret; + *sock_written += ret; + } + return 0; +} + +static int validate_and_prepare(struct modem_socket *sock, const struct sockaddr **dst_addr, + size_t *buf_len, char *cmd_buf, size_t cmd_buf_len) +{ + /* Validate args and prepare send command */ + if (!sock) { + errno = EINVAL; + return -1; + } + if (sock->type != SOCK_DGRAM && !sock->is_connected) { + errno = ENOTCONN; + return -1; + } + if (!*dst_addr && sock->ip_proto == IPPROTO_UDP) { + *dst_addr = &sock->dst; + } + if (*buf_len > MDM_MAX_DATA_LENGTH) { + if (sock->type == SOCK_DGRAM) { + errno = EMSGSIZE; + return -1; + } + *buf_len = MDM_MAX_DATA_LENGTH; + } + /* Consolidated send command helper handles UDP vs TCP formatting */ + return prepare_send_cmd(sock, *dst_addr, *buf_len, cmd_buf, cmd_buf_len); +} + +static int transmit_regular_data(struct hl78xx_socket_data *socket_data, const char *buf, + size_t buf_len, int *sock_written) +{ + int ret; + + ret = send_data_buffer(socket_data, buf, buf_len, sock_written); + if (ret < 0) { + return ret; + } + ret = k_sem_take(&socket_data->mdata_global->script_stopped_sem_tx_int, K_FOREVER); + if (ret < 0) { + LOG_ERR("%s: k_sem_take(tx) returned %d", __func__, ret); + return ret; + } + return modem_pipe_transmit(socket_data->mdata_global->uart_pipe, + (uint8_t *)socket_data->mdata_global->buffers.eof_pattern, + socket_data->mdata_global->buffers.eof_pattern_size); +} + +/* send binary data via the +KUDPSND/+KTCPSND commands */ +static ssize_t send_socket_data(void *obj, struct hl78xx_socket_data *socket_data, + const struct sockaddr *dst_addr, const char *buf, size_t buf_len, + k_timeout_t timeout) +{ + struct modem_socket *sock = (struct modem_socket *)obj; + char cmd_buf[82] = {0}; /* AT+KUDPSND/KTCP=,IP,PORT,LENGTH */ + int ret; + int sock_written = 0; + + ret = validate_and_prepare(sock, &dst_addr, &buf_len, cmd_buf, sizeof(cmd_buf)); + if (ret < 0) { + return ret; + } + socket_data->socket_data_error = false; + if (k_mutex_lock(&socket_data->mdata_global->tx_lock, K_SECONDS(1)) < 0) { + return -1; + } + ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, cmd_buf, strlen(cmd_buf), + (const struct modem_chat_match *)hl78xx_get_connect_matches(), + hl78xx_get_connect_matches_size(), false); + if (ret < 0 || socket_data->socket_data_error) { + hl78xx_set_errno_from_code(ret); + ret = -1; + goto cleanup; + } + modem_pipe_attach(socket_data->mdata_global->chat.pipe, modem_pipe_callback, + socket_data->mdata_global); + ret = transmit_regular_data(socket_data, buf, buf_len, &sock_written); + if (ret < 0) { + goto cleanup; + } + modem_chat_attach(&socket_data->mdata_global->chat, socket_data->mdata_global->uart_pipe); + ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, "", 0, + hl78xx_get_sockets_ok_match(), 1, false); + if (ret < 0) { + LOG_ERR("Final confirmation failed: %d", ret); + goto cleanup; + } +cleanup: + k_mutex_unlock(&socket_data->mdata_global->tx_lock); + return (ret < 0) ? -1 : sock_written; +} + +#ifdef CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS +/* ===== TLS implementation (conditional) ================================ + * TLS credential upload and chipper settings helper implementations. + */ +static int handle_tls_sockopts(void *obj, int optname, const void *optval, socklen_t optlen) +{ + int ret; + struct modem_socket *sock = (struct modem_socket *)obj; + struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock); + + if (!sock || !socket_data || !socket_data->offload_dev) { + return -EINVAL; + } + + switch (optname) { + case TLS_SEC_TAG_LIST: + ret = map_credentials(socket_data, optval, optlen); + return ret; + + case TLS_HOSTNAME: + if (optlen >= MDM_MAX_HOSTNAME_LEN) { + return -EINVAL; + } + memset(socket_data->tls.hostname, 0, MDM_MAX_HOSTNAME_LEN); + memcpy(socket_data->tls.hostname, optval, optlen); + socket_data->tls.hostname[optlen] = '\0'; + socket_data->tls.hostname_set = true; + ret = hl78xx_configure_chipper_suit(socket_data); + if (ret < 0) { + LOG_ERR("Failed to configure chipper suit: %d", ret); + return ret; + } + LOG_DBG("TLS hostname set to: %s", socket_data->tls.hostname); + return 0; + + case TLS_PEER_VERIFY: + if (*(const uint32_t *)optval != TLS_PEER_VERIFY_REQUIRED) { + LOG_WRN("Disabling peer verification is not supported"); + } + return 0; + + case TLS_CERT_NOCOPY: + return 0; /* No-op, success */ + + default: + LOG_DBG("Unsupported TLS option: %d", optname); + return -EINVAL; + } +} + +static int offload_setsockopt(void *obj, int level, int optname, const void *optval, + socklen_t optlen) +{ + int ret = 0; + + if (!IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) { + return -EINVAL; + } + if (level == SOL_TLS) { + ret = handle_tls_sockopts(obj, optname, optval, optlen); + if (ret < 0) { + hl78xx_set_errno_from_code(ret); + return -1; + } + return 0; + } + LOG_DBG("Unsupported socket option: %d", optname); + return -EINVAL; +} +#endif /* CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS */ + +static ssize_t offload_sendto(void *obj, const void *buf, size_t len, int flags, + const struct sockaddr *to, socklen_t tolen) +{ + int ret = 0; + struct modem_socket *sock = (struct modem_socket *)obj; + struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock); + + if (!sock || !socket_data || !socket_data->offload_dev) { + errno = EINVAL; + return -1; + } + if (!hl78xx_is_registered(socket_data->mdata_global)) { + LOG_ERR("Modem currently not attached to the network!"); + return -EAGAIN; + } + /* Do some sanity checks. */ + if (!buf || len == 0) { + errno = EINVAL; + return -1; + } + /* For stream sockets (TCP) the socket must be connected. For datagram + * sockets (UDP) sendto can be used without a prior connect as long as a + * destination address is provided or the socket has a stored dst. The + * helper validate_and_prepare will supply sock->dst for UDP when needed. + */ + if (sock->type != SOCK_DGRAM && !sock->is_connected) { + errno = ENOTCONN; + return -1; + } + /* Only send up to MTU bytes. */ + if (len > MDM_MAX_DATA_LENGTH) { + len = MDM_MAX_DATA_LENGTH; + } + ret = send_socket_data(obj, socket_data, to, buf, len, K_SECONDS(MDM_CMD_TIMEOUT)); + if (ret < 0) { + /* Map internal negative return codes to positive errno values. Use EIO as + * a conservative fallback when ret is non-negative (unexpected) + */ + hl78xx_set_errno_from_code(ret); + return -1; + } + errno = 0; + return ret; +} + +static int offload_ioctl(void *obj, unsigned int request, va_list args) +{ + int ret = 0; + struct modem_socket *sock = (struct modem_socket *)obj; + struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock); + struct zsock_pollfd *pfd; + struct k_poll_event **pev; + struct k_poll_event *pev_end; + /* sanity check: does parent == parent->offload_dev->data ? */ + if (socket_data && socket_data->offload_dev && + socket_data->offload_dev->data != socket_data) { + LOG_WRN("parent mismatch: parent != offload_dev->data (%p != %p)", socket_data, + socket_data->offload_dev->data); + } + switch (request) { + case ZFD_IOCTL_POLL_PREPARE: + pfd = va_arg(args, struct zsock_pollfd *); + pev = va_arg(args, struct k_poll_event **); + pev_end = va_arg(args, struct k_poll_event *); + ret = modem_socket_poll_prepare(&socket_data->socket_config, obj, pfd, pev, + pev_end); + + if (ret == -1 && errno == ENOTSUP && (pfd->events & ZSOCK_POLLOUT) && + sock->ip_proto == IPPROTO_UDP) { + /* Not Implemented */ + /* + * You can implement this later when needed + * For now, just ignore it + */ + errno = ENOTSUP; + ret = 0; + } + return ret; + + case ZFD_IOCTL_POLL_UPDATE: + pfd = va_arg(args, struct zsock_pollfd *); + pev = va_arg(args, struct k_poll_event **); + return modem_socket_poll_update(obj, pfd, pev); + + case F_GETFL: + return 0; + + case F_SETFL: { +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + int flags = va_arg(args, int); + + LOG_DBG("F_SETFL called with flags=0x%x", flags); + ARG_UNUSED(flags); +#endif + /* You can store flags if you want, but it's safe to just ignore them. */ + return 0; + } + + default: + errno = EINVAL; + return -1; + } +} + +static ssize_t offload_read(void *obj, void *buffer, size_t count) +{ + return offload_recvfrom(obj, buffer, count, 0, NULL, 0); +} + +static ssize_t offload_write(void *obj, const void *buffer, size_t count) +{ + return offload_sendto(obj, buffer, count, 0, NULL, 0); +} + +static ssize_t offload_sendmsg(void *obj, const struct msghdr *msg, int flags) +{ + ssize_t sent = 0; + struct iovec bkp_iovec = {0}; + struct msghdr crafted_msg = {.msg_name = msg->msg_name, .msg_namelen = msg->msg_namelen}; + struct modem_socket *sock = (struct modem_socket *)obj; + struct hl78xx_socket_data *socket_data = hl78xx_socket_data_from_sock(sock); + size_t full_len = 0; + int ret; + + if (!sock || !socket_data || !socket_data->offload_dev) { + errno = EINVAL; + return -1; + } + /* Compute the full length to send and validate input */ + for (int i = 0; i < msg->msg_iovlen; i++) { + if (!msg->msg_iov[i].iov_base || msg->msg_iov[i].iov_len == 0) { + errno = EINVAL; + return -1; + } + full_len += msg->msg_iov[i].iov_len; + } + while (full_len > sent) { + int removed = 0; + int i = 0; + int bkp_iovec_idx = -1; + + crafted_msg.msg_iovlen = msg->msg_iovlen; + crafted_msg.msg_iov = &msg->msg_iov[0]; + + /* Adjust iovec to remove already sent bytes */ + while (removed < sent) { + int to_remove = sent - removed; + + if (to_remove >= msg->msg_iov[i].iov_len) { + crafted_msg.msg_iovlen -= 1; + crafted_msg.msg_iov = &msg->msg_iov[i + 1]; + removed += msg->msg_iov[i].iov_len; + } else { + bkp_iovec_idx = i; + bkp_iovec = msg->msg_iov[i]; + + msg->msg_iov[i].iov_len -= to_remove; + msg->msg_iov[i].iov_base = + ((uint8_t *)msg->msg_iov[i].iov_base) + to_remove; + + removed += to_remove; + } + i++; + } + /* send_socket_data expects a buffer pointer and its byte length. + * crafted_msg.msg_iovlen is the number of iovec entries and is + * incorrect here (was causing sends of '2' bytes when two iovecs + * were present). Use the first iovec's iov_len for the byte length. + */ + ret = send_socket_data(obj, socket_data, crafted_msg.msg_name, + crafted_msg.msg_iov->iov_base, crafted_msg.msg_iov->iov_len, + K_SECONDS(MDM_CMD_TIMEOUT)); + if (bkp_iovec_idx != -1) { + msg->msg_iov[bkp_iovec_idx] = bkp_iovec; + } + if (ret < 0) { + /* Map negative internal return code to positive errno; fall back to + * EIO + */ + hl78xx_set_errno_from_code(ret); + return -1; + } + sent += ret; + } + return sent; +} +/* clang-format off */ +static const struct socket_op_vtable offload_socket_fd_op_vtable = { + .fd_vtable = { + .read = offload_read, + .write = offload_write, + .close = offload_close, + .ioctl = offload_ioctl, + }, + .bind = offload_bind, + .connect = offload_connect, + .sendto = offload_sendto, + .recvfrom = offload_recvfrom, + .listen = NULL, + .accept = NULL, + .sendmsg = offload_sendmsg, + .getsockopt = NULL, +#if defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS) + .setsockopt = offload_setsockopt, +#else + .setsockopt = NULL, +#endif +}; +/* clang-format on */ +static int hl78xx_init_sockets(const struct device *dev) +{ + int ret; + struct hl78xx_socket_data *socket_data = (struct hl78xx_socket_data *)dev->data; + + socket_data->buf_pool = &mdm_recv_pool; + /* socket config */ + ret = modem_socket_init(&socket_data->socket_config, &socket_data->sockets[0], + ARRAY_SIZE(socket_data->sockets), MDM_BASE_SOCKET_NUM, false, + &offload_socket_fd_op_vtable); + if (ret) { + goto error; + } + return 0; +error: + return ret; +} +static void socket_notify_data(int socket_id, int new_total, void *user_data) +{ + int ret = 0; + struct modem_socket *sock; + struct hl78xx_data *data = (struct hl78xx_data *)user_data; + struct hl78xx_socket_data *socket_data = + (struct hl78xx_socket_data *)data->offload_dev->data; + + if (!data || !socket_data) { + LOG_ERR("%s: invalid user_data", __func__); + return; + } + sock = modem_socket_from_id(&socket_data->socket_config, socket_id); + if (!sock) { + return; + } + /* Update the packet size */ + ret = modem_socket_packet_size_update(&socket_data->socket_config, sock, new_total); + if (ret < 0) { + LOG_ERR("socket_id:%d left_bytes:%d err: %d", socket_id, new_total, ret); + } + if (new_total > 0) { + modem_socket_data_ready(&socket_data->socket_config, sock); + } + /* Duplicate/chat callback block removed; grouped versions live earlier */ +} + +#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS) +static int hl78xx_configure_chipper_suit(struct hl78xx_socket_data *socket_data) +{ + const char *cmd_chipper_suit = "AT+KSSLCRYPTO=0,8,1,8192,4,4,3,0"; + + return modem_dynamic_cmd_send( + socket_data->mdata_global, NULL, cmd_chipper_suit, strlen(cmd_chipper_suit), + (const struct modem_chat_match *)hl78xx_get_ok_match(), 1, false); +} +/* send binary data via the K....STORE commands */ +static ssize_t hl78xx_send_cert(struct hl78xx_socket_data *socket_data, const char *cert_data, + size_t cert_len, enum tls_credential_type cert_type) +{ + int ret; + char send_buf[sizeof("AT+KPRIVKSTORE=#,####\r\n")]; + int sock_written = 0; + + if (!socket_data || !socket_data->mdata_global) { + return -EINVAL; + } + + if (cert_len == 0 || !cert_data) { + LOG_ERR("Invalid certificate data or length"); + return -EINVAL; + } + /** Certificate length exceeds maximum allowed size */ + if (cert_len > MDM_MAX_CERT_LENGTH) { + return -EINVAL; + } + + if (cert_type == TLS_CREDENTIAL_CA_CERTIFICATE || + cert_type == TLS_CREDENTIAL_SERVER_CERTIFICATE) { + snprintk(send_buf, sizeof(send_buf), "AT+KCERTSTORE=%d,%d", (cert_type - 1), + cert_len); + + } else if (cert_type == TLS_CREDENTIAL_PRIVATE_KEY) { + snprintk(send_buf, sizeof(send_buf), "AT+KPRIVKSTORE=0,%d", cert_len); + + } else { + LOG_ERR("Unsupported certificate type: %d", cert_type); + return -EINVAL; + } + socket_data->socket_data_error = false; + if (k_mutex_lock(&socket_data->mdata_global->tx_lock, K_SECONDS(1)) < 0) { + errno = EBUSY; + return -1; + } + ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, send_buf, strlen(send_buf), + (const struct modem_chat_match *)hl78xx_get_connect_matches(), + hl78xx_get_connect_matches_size(), false); + if (ret < 0) { + LOG_ERR("Error sending AT command %d", ret); + } + if (socket_data->socket_data_error) { + ret = -ENODEV; + errno = ENODEV; + goto cleanup; + } + modem_pipe_attach(socket_data->mdata_global->chat.pipe, modem_pipe_callback, + socket_data->mdata_global); + ret = send_data_buffer(socket_data, cert_data, cert_len, &sock_written); + if (ret < 0) { + goto cleanup; + } + ret = k_sem_take(&socket_data->mdata_global->script_stopped_sem_tx_int, K_FOREVER); + if (ret < 0) { + goto cleanup; + } + ret = modem_pipe_transmit(socket_data->mdata_global->uart_pipe, + (uint8_t *)socket_data->mdata_global->buffers.eof_pattern, + socket_data->mdata_global->buffers.eof_pattern_size); + if (ret < 0) { + LOG_ERR("Error sending EOF pattern: %d", ret); + } + modem_chat_attach(&socket_data->mdata_global->chat, socket_data->mdata_global->uart_pipe); + ret = modem_dynamic_cmd_send(socket_data->mdata_global, NULL, "", 0, + (const struct modem_chat_match *)hl78xx_get_ok_match(), 1, + false); + if (ret < 0) { + LOG_ERR("Final confirmation failed: %d", ret); + goto cleanup; + } +cleanup: + k_mutex_unlock(&socket_data->mdata_global->tx_lock); + return (ret < 0) ? -1 : sock_written; +} + +static int map_credentials(struct hl78xx_socket_data *socket_data, const void *optval, + socklen_t optlen) +{ + const sec_tag_t *sec_tags = (const sec_tag_t *)optval; + int ret = 0; + int tags_len; + sec_tag_t tag; + int i; + struct tls_credential *cert; + + if ((optlen % sizeof(sec_tag_t)) != 0 || (optlen == 0)) { + return -EINVAL; + } + tags_len = optlen / sizeof(sec_tag_t); + /* For each tag, retrieve the credentials value and type: */ + for (i = 0; i < tags_len; i++) { + tag = sec_tags[i]; + cert = credential_next_get(tag, NULL); + while (cert != NULL) { + switch (cert->type) { + case TLS_CREDENTIAL_CA_CERTIFICATE: + LOG_DBG("TLS_CREDENTIAL_CA_CERTIFICATE tag: %d", tag); + break; + + case TLS_CREDENTIAL_SERVER_CERTIFICATE: + LOG_DBG("TLS_CREDENTIAL_SERVER_CERTIFICATE tag: %d", tag); + break; + + case TLS_CREDENTIAL_PRIVATE_KEY: + LOG_DBG("TLS_CREDENTIAL_PRIVATE_KEY tag: %d", tag); + break; + + case TLS_CREDENTIAL_NONE: + case TLS_CREDENTIAL_PSK: + case TLS_CREDENTIAL_PSK_ID: + default: + /* Not handled */ + return -EINVAL; + } + ret = hl78xx_send_cert(socket_data, cert->buf, cert->len, cert->type); + if (ret < 0) { + return ret; + } + cert = credential_next_get(tag, cert); + } + } + + return 0; +} +#endif + +static int hl78xx_socket_init(const struct device *dev) +{ + + struct hl78xx_socket_data *data = (struct hl78xx_socket_data *)dev->data; + + data->offload_dev = dev; + /* Ensure the parent modem device pointer was set at static init time */ + if (data->modem_dev == NULL) { + LOG_ERR("modem_dev not initialized for %s", dev->name); + return -EINVAL; + } + /* Ensure the modem device is ready before accessing its driver data */ + if (!device_is_ready(data->modem_dev)) { + LOG_ERR("modem device %s not ready", data->modem_dev->name); + return -ENODEV; + } + if (data->modem_dev->data == NULL) { + LOG_ERR("modem device %s has no driver data yet", data->modem_dev->name); + return -EAGAIN; + } + data->mdata_global = (struct hl78xx_data *)data->modem_dev->data; + data->mdata_global->offload_dev = dev; + /* Keep original single global pointer usage but set via accessor. */ + hl78xx_set_socket_global(data); + atomic_set(&data->mdata_global->state_leftover, 0); + + return 0; +} + +static void modem_net_iface_init(struct net_if *iface) +{ + const struct device *dev = net_if_get_device(iface); + struct hl78xx_socket_data *data = dev->data; + + /* startup trace */ + if (!data->mdata_global) { + LOG_WRN("mdata_global not set for net iface init on %s", dev->name); + } + net_if_set_link_addr( + iface, + modem_get_mac(data->mac_addr, + data->mdata_global ? data->mdata_global->identity.imei : NULL), + sizeof(data->mac_addr), NET_LINK_ETHERNET); + data->net_iface = iface; + hl78xx_init_sockets(dev); + net_if_socket_offload_set(iface, offload_socket); +} + +static struct offloaded_if_api api_funcs = { + .iface_api.init = modem_net_iface_init, +}; + +static bool offload_is_supported(int family, int type, int proto) +{ + bool fam_ok = false; + +#ifdef CONFIG_NET_IPV4 + if (family == AF_INET) { + fam_ok = true; + } +#endif +#ifdef CONFIG_NET_IPV6 + if (family == AF_INET6) { + fam_ok = true; + } +#endif + if (!fam_ok) { + return false; + } + if (!(type == SOCK_DGRAM || type == SOCK_STREAM)) { + return false; + } + if (proto == IPPROTO_TCP || proto == IPPROTO_UDP) { + return true; + } +#if defined(CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS) + if (proto == IPPROTO_TLS_1_2) { + return true; + } +#endif + return false; +} + +#define MODEM_HL78XX_DEFINE_OFFLOAD_INSTANCE(inst) \ + static struct hl78xx_socket_data hl78xx_socket_data_##inst = { \ + .modem_dev = DEVICE_DT_GET(DT_PARENT(DT_DRV_INST(inst))), \ + }; \ + NET_DEVICE_OFFLOAD_INIT( \ + inst, "hl78xx_dev", hl78xx_socket_init, NULL, &hl78xx_socket_data_##inst, NULL, \ + CONFIG_MODEM_HL78XX_OFFLOAD_INIT_PRIORITY, &api_funcs, MDM_MAX_DATA_LENGTH); \ + \ + NET_SOCKET_OFFLOAD_REGISTER(inst, CONFIG_NET_SOCKETS_OFFLOAD_PRIORITY, AF_UNSPEC, \ + offload_is_supported, offload_socket); + +#define MODEM_OFFLOAD_DEVICE_SWIR_HL78XX(inst) MODEM_HL78XX_DEFINE_OFFLOAD_INSTANCE(inst) + +#define DT_DRV_COMPAT swir_hl7812_offload +DT_INST_FOREACH_STATUS_OKAY(MODEM_OFFLOAD_DEVICE_SWIR_HL78XX) +#undef DT_DRV_COMPAT + +#define DT_DRV_COMPAT swir_hl7800_offload +DT_INST_FOREACH_STATUS_OKAY(MODEM_OFFLOAD_DEVICE_SWIR_HL78XX) +#undef DT_DRV_COMPAT diff --git a/dts/bindings/modem/swir,hl7812-gnss.yaml b/dts/bindings/modem/swir,hl7812-gnss.yaml new file mode 100644 index 0000000000000..5160bd981eb2e --- /dev/null +++ b/dts/bindings/modem/swir,hl7812-gnss.yaml @@ -0,0 +1,12 @@ +description: | + Binding for a modem offload child node that indicates the modem + supports socket offload functionality. This node is intended to be a + child of a modem device node (for example, `modem: hl_modem { ... };`). + + The binding is intentionally small and extensible; it documents a + presence node (compatible = "swir,hl7812-offload") and may be extended + in future with additional properties that the driver may consume. + +compatible: "swir,hl7812-gnss" + +include: swir,hl78xx-gnss.yaml diff --git a/dts/bindings/modem/swir,hl7812-offload.yaml b/dts/bindings/modem/swir,hl7812-offload.yaml new file mode 100644 index 0000000000000..5f2bedf1e552c --- /dev/null +++ b/dts/bindings/modem/swir,hl7812-offload.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025, Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +description: Sierra Wireless HL7812 Modem offload + +compatible: "swir,hl7812-offload" + +include: swir,hl78xx-offload.yaml diff --git a/dts/bindings/modem/swir,hl7812.yaml b/dts/bindings/modem/swir,hl7812.yaml new file mode 100644 index 0000000000000..a75d0c044b400 --- /dev/null +++ b/dts/bindings/modem/swir,hl7812.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025, Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +description: Sierra Wireless HL7812 Modem + +compatible: "swir,hl7812" + +include: swir,hl78xx.yaml diff --git a/dts/bindings/modem/swir,hl78xx-gnss.yaml b/dts/bindings/modem/swir,hl78xx-gnss.yaml new file mode 100644 index 0000000000000..03632e05cd377 --- /dev/null +++ b/dts/bindings/modem/swir,hl78xx-gnss.yaml @@ -0,0 +1,24 @@ +description: | + Binding for a modem child node that indicates the modem supports + GNSS functionality. This node is intended to be a child of a modem + device node (for example, `modem: hl_modem { ... };`). + + The binding is intentionally small and extensible; it documents a + presence node (compatible = "swir,hl78xx-gnss") and may be extended + in future with additional properties that the driver may consume. + +compatible: "swir,hl78xx-gnss" + +include: + - uart-device.yaml + - gnss-nmea-generic.yaml + - gnss-pps.yaml + +properties: + fix-rate: + type: int + default: 1000 + description: | + Initial fix-rate GNSS modem will be operating on. May be adjusted at + run-time through GNSS APIs. Must be greater than 50-ms. + Default is power-on setting. diff --git a/dts/bindings/modem/swir,hl78xx-offload.yaml b/dts/bindings/modem/swir,hl78xx-offload.yaml new file mode 100644 index 0000000000000..8f8c9fa7b6801 --- /dev/null +++ b/dts/bindings/modem/swir,hl78xx-offload.yaml @@ -0,0 +1,32 @@ +description: | + Binding for a modem child node that indicates the modem supports + socket offloading. This node is intended to be a child of a modem + device node (for example, `modem: hl_modem { ... };`). + + The binding is intentionally small and extensible; it documents a + presence node (compatible = "net,offload-modem-sockets") and a couple + of optional integer properties that the driver may consume. + +compatible: "swir,hl78xx-offload" + +properties: + max-data-length: + type: int + description: | + "Maximum length of a single data payload (bytes) that + the modem can send/receive in one offload operation." + enum: + - 512 + - 1024 + - 2048 + - 4096 + - 8192 + + offload-priority: + type: int + description: | + "Priority of this offload modem compared to other offload + modems in the system. Lower values indicate higher priority. + The system will prefer to use the offload modem with the + highest priority (lowest value) when multiple offload modems + are available." diff --git a/dts/bindings/modem/swir,hl78xx.yaml b/dts/bindings/modem/swir,hl78xx.yaml new file mode 100644 index 0000000000000..b4b1ceef8652b --- /dev/null +++ b/dts/bindings/modem/swir,hl78xx.yaml @@ -0,0 +1,34 @@ +# Copyright (c) 2025, Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +description: Sierra Wireless HL78XX Modem + +compatible: "swir,hl78xx" + +include: + - zephyr,cellular-modem-device.yaml + +properties: + mdm-pwr-on-gpios: + type: phandle-array + + mdm-fast-shutd-gpios: + type: phandle-array + + mdm-vgpio-gpios: + type: phandle-array + + mdm-uart-dsr-gpios: + type: phandle-array + + mdm-uart-cts-gpios: + type: phandle-array + + mdm-gpio6-gpios: + type: phandle-array + + mdm-gpio8-gpios: + type: phandle-array + + mdm-sim-switch-gpios: + type: phandle-array diff --git a/include/zephyr/drivers/modem/hl78xx_apis.h b/include/zephyr/drivers/modem/hl78xx_apis.h new file mode 100644 index 0000000000000..bffd35d0e0c7c --- /dev/null +++ b/include/zephyr/drivers/modem/hl78xx_apis.h @@ -0,0 +1,458 @@ +/* + * Copyright (c) 2025 Netfeasa Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ZEPHYR_INCLUDE_DRIVERS_HL78XX_APIS_H_ +#define ZEPHYR_INCLUDE_DRIVERS_HL78XX_APIS_H_ + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Magic constants */ +#define CSQ_RSSI_UNKNOWN (99) +#define CESQ_RSRP_UNKNOWN (255) +#define CESQ_RSRQ_UNKNOWN (255) +/* Magic numbers to units conversions */ +#define CSQ_RSSI_TO_DB(v) (-113 + (2 * (v))) +#define CESQ_RSRP_TO_DB(v) (-140 + (v)) +#define CESQ_RSRQ_TO_DB(v) (-20 + ((v) / 2)) +/** Monitor is paused. */ +#define PAUSED 1 +/** Monitor is active, default */ +#define ACTIVE 0 +#define MDM_MANUFACTURER_LENGTH 20 +#define MDM_MODEL_LENGTH 32 +#define MDM_REVISION_LENGTH 64 +#define MDM_IMEI_LENGTH 16 +#define MDM_IMSI_LENGTH 23 +#define MDM_ICCID_LENGTH 22 +#define MDM_APN_MAX_LENGTH 64 +#define MDM_MAX_CERT_LENGTH 4096 +#define MDM_MAX_HOSTNAME_LEN 128 +/** + * @brief Define an Event monitor to receive notifications in the system workqueue thread. + * + * @param name The monitor name. + * @param _handler The monitor callback. + * @param ... Optional monitor initial state (@c PAUSED or @c ACTIVE). + * The default initial state of a monitor is active. + */ +#define HL78XX_EVT_MONITOR(name, _handler, ...) \ + static STRUCT_SECTION_ITERABLE(hl78xx_evt_monitor_entry, name) = { \ + .handler = _handler, \ + .next = NULL, \ + .flags.direct = false, \ + COND_CODE_1(__VA_ARGS__, (.flags.paused = __VA_ARGS__,), ()) } + +/** Cellular radio access technologies */ +enum hl78xx_cell_rat_mode { + HL78XX_RAT_CAT_M1 = 0, + HL78XX_RAT_NB1, +#ifdef CONFIG_MODEM_HL78XX_12 + HL78XX_RAT_GSM, +#ifdef CONFIG_MODEM_HL78XX_12_FW_R6 + HL78XX_RAT_NBNTN, +#endif +#endif +#ifdef CONFIG_MODEM_HL78XX_AUTORAT + HL78XX_RAT_MODE_AUTO, +#endif + HL78XX_RAT_MODE_NONE, + HL78XX_RAT_COUNT = HL78XX_RAT_MODE_NONE +}; + +/** Phone functionality modes */ +enum hl78xx_phone_functionality { + HL78XX_SIM_POWER_OFF, + HL78XX_FULLY_FUNCTIONAL, + HL78XX_AIRPLANE = 4, +}; +/** Module status codes */ +enum hl78xx_module_status { + HL78XX_MODULE_READY = 0, + HL78XX_MODULE_WAITING_FOR_ACCESS_CODE, + HL78XX_MODULE_SIM_NOT_PRESENT, + HL78XX_MODULE_SIMLOCK, + HL78XX_MODULE_UNRECOVERABLE_ERROR, + HL78XX_MODULE_UNKNOWN_STATE, + HL78XX_MODULE_INACTIVE_SIM +}; + +/** Cellular modem info type */ +enum hl78xx_modem_info_type { + /* Access Point Name */ + HL78XX_MODEM_INFO_APN, + /* */ + HL78XX_MODEM_INFO_CURRENT_RAT, + /* */ + HL78XX_MODEM_INFO_NETWORK_OPERATOR, +}; + +/** Cellular network structure */ +struct hl78xx_network { + /** Cellular access technology */ + enum hl78xx_cell_rat_mode technology; + /** + * List of bands, as defined by the specified cellular access technology, + * to enables. All supported bands are enabled if none are provided. + */ + uint16_t *bands; + /** Size of bands */ + uint16_t size; +}; + +enum hl78xx_evt_type { + HL78XX_LTE_RAT_UPDATE, + HL78XX_LTE_REGISTRATION_STAT_UPDATE, + HL78XX_LTE_SIM_REGISTRATION, + HL78XX_LTE_MODEM_STARTUP, +}; + +struct hl78xx_evt { + enum hl78xx_evt_type type; + + union { + enum cellular_registration_status reg_status; + enum hl78xx_cell_rat_mode rat_mode; + bool status; + int value; + } content; +}; +/** API for configuring networks */ +typedef int (*hl78xx_api_configure_networks)(const struct device *dev, + const struct hl78xx_network *networks, uint8_t size); + +/** API for getting supported networks */ +typedef int (*hl78xx_api_get_supported_networks)(const struct device *dev, + const struct hl78xx_network **networks, + uint8_t *size); + +/** API for getting network signal strength */ +typedef int (*hl78xx_api_get_signal)(const struct device *dev, const enum cellular_signal_type type, + int16_t *value); + +/** API for getting modem information */ +typedef int (*hl78xx_api_get_modem_info)(const struct device *dev, + const enum cellular_modem_info_type type, char *info, + size_t size); + +/** API for getting registration status */ +typedef int (*hl78xx_api_get_registration_status)(const struct device *dev, + enum cellular_access_technology tech, + enum cellular_registration_status *status); + +/** API for setting apn */ +typedef int (*hl78xx_api_set_apn)(const struct device *dev, const char *apn, uint16_t size); + +/** API for set phone functionality */ +typedef int (*hl78xx_api_set_phone_functionality)(const struct device *dev, + enum hl78xx_phone_functionality functionality, + bool reset); + +/** API for get phone functionality */ +typedef int (*hl78xx_api_get_phone_functionality)(const struct device *dev, + enum hl78xx_phone_functionality *functionality); + +/** API for get phone functionality */ +typedef int (*hl78xx_api_send_at_cmd)(const struct device *dev, const char *cmd, uint16_t cmd_size, + const struct modem_chat_match *response_matches, + uint16_t matches_size); + +/**< Event monitor entry */ +struct hl78xx_evt_monitor_entry; /* forward declaration */ +/* Event monitor dispatcher */ +typedef void (*hl78xx_evt_monitor_dispatcher_t)(struct hl78xx_evt *notif); +/* Event monitor handler */ +typedef void (*hl78xx_evt_monitor_handler_t)(struct hl78xx_evt *notif, + struct hl78xx_evt_monitor_entry *mon); + +struct hl78xx_evt_monitor_entry { + /** Monitor callback. */ + const hl78xx_evt_monitor_handler_t handler; + /* link for runtime list */ + struct hl78xx_evt_monitor_entry *next; + struct { + uint8_t paused: 1; /* Monitor is paused. */ + uint8_t direct: 1; /* Dispatch in ISR. */ + } flags; +}; +/** + * @brief hl78xx_api_func_set_phone_functionality + * @param dev Cellular network device instance + * @param functionality phone functionality mode to set + * @param reset If true, the modem will be reset as part of applying the functionality change. + * @return 0 if successful. + */ +int hl78xx_api_func_set_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality functionality, + bool reset); +/** + * @brief hl78xx_api_func_get_phone_functionality + * @param dev Cellular network device instance + * @param functionality Pointer to store the current phone functionality mode + * @return 0 if successful. + */ +int hl78xx_api_func_get_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality *functionality); +/** + * @brief hl78xx_api_func_get_signal - Brief description of the function. + * @param dev Cellular network device instance + * @param type Type of the signal to retrieve + * @param value Pointer to store the signal value + * @return 0 if successful. + */ +int hl78xx_api_func_get_signal(const struct device *dev, const enum cellular_signal_type type, + int16_t *value); +/** + * @brief hl78xx_api_func_get_modem_info_vendor - Brief description of the function. + * @param dev Cellular network device instance + * @param type Type of the modem info to retrieve + * @param info Pointer to store the modem info + * @param size Size of the info buffer + * @return 0 if successful. + */ +int hl78xx_api_func_get_modem_info_vendor(const struct device *dev, + enum hl78xx_modem_info_type type, void *info, + size_t size); +/** + * @brief hl78xx_api_func_modem_dynamic_cmd_send - Brief description of the function. + * @param dev Cellular network device instance + * @param cmd AT command to send + * @param cmd_size Size of the AT command + * @param response_matches Expected response patterns + * @param matches_size Size of the response patterns + * @return 0 if successful. + */ +int hl78xx_api_func_modem_dynamic_cmd_send(const struct device *dev, const char *cmd, + uint16_t cmd_size, + const struct modem_chat_match *response_matches, + uint16_t matches_size); +/** + * @brief Get modem info for the device + * + * @param dev Cellular network device instance + * @param type Type of the modem info requested + * @param info Info string destination + * @param size Info string size + * + * @retval 0 if successful. + * @retval -ENOTSUP if API is not supported by cellular network device. + * @retval -ENODATA if modem does not provide info requested + * @retval Negative errno-code from chat module otherwise. + */ +static inline int hl78xx_get_modem_info(const struct device *dev, + const enum hl78xx_modem_info_type type, void *info, + size_t size) +{ + return hl78xx_api_func_get_modem_info_vendor(dev, type, info, size); +} +/** + * @brief Set the modem phone functionality mode. + * + * Configures the operational state of the modem (e.g., full, airplane, or minimum functionality). + * Optionally, the modem can be reset during this transition. + * + * @param dev Pointer to the modem device instance. + * @param functionality Desired phone functionality mode to be set. + * (e.g., full, airplane, minimum – see enum hl78xx_phone_functionality) + * @param reset If true, the modem will be reset as part of applying the functionality change. + * + * @retval 0 on success. + * @retval -EINVAL if an invalid parameter is passed. + * @retval -EIO on communication or command failure with the modem. + */ +static inline int hl78xx_set_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality functionality, + bool reset) +{ + return hl78xx_api_func_set_phone_functionality(dev, functionality, reset); +} +/** + * @brief Get the current phone functionality mode of the modem. + * + * Queries the modem to retrieve its current operational mode, such as + * full functionality, airplane mode, or minimum functionality. + * + * @param dev Pointer to the modem device instance. + * @param functionality Pointer to store the retrieved functionality mode. + * (see enum hl78xx_phone_functionality) + * + * @retval 0 on success. + * @retval -EINVAL if the input parameters are invalid. + * @retval -EIO if the modem fails to respond or returns an error. + */ +static inline int hl78xx_get_phone_functionality(const struct device *dev, + enum hl78xx_phone_functionality *functionality) +{ + return hl78xx_api_func_get_phone_functionality(dev, functionality); +} +/** + * @brief Send an AT command to the modem and wait for a matched response. + * + * Transmits the specified AT command to the modem and waits for a response that matches + * one of the expected patterns defined in the response match table. + * + * @param dev Pointer to the modem device instance. + * @param cmd Pointer to the AT command string to be sent. + * @param cmd_size Length of the AT command string in bytes. + * @param response_matches Pointer to an array of expected response patterns. + * (see struct modem_chat_match) + * @param matches_size Number of response patterns in the array. + * + * @retval 0 on successful command transmission and response match. + * @retval -EINVAL if any parameter is invalid. + * @retval -ETIMEDOUT if the modem did not respond in the expected time. + * @retval -EIO on communication failure or if response did not match. + */ +static inline int hl78xx_modem_cmd_send(const struct device *dev, const char *cmd, + uint16_t cmd_size, + const struct modem_chat_match *response_matches, + uint16_t matches_size) +{ + + return hl78xx_api_func_modem_dynamic_cmd_send(dev, cmd, cmd_size, response_matches, + matches_size); +} +/** + * @brief Convert raw RSSI value from the modem to dBm. + * + * Parses the RSSI value reported by the modem (typically from an AT command response) + * and converts it to a corresponding signal strength in dBm, as defined by 3GPP TS 27.007. + * + * @param rssi Raw RSSI value (0–31 or 99 for not known or not detectable). + * @param value Pointer to store the converted RSSI in dBm. + * + * @retval 0 on successful conversion. + * @retval -EINVAL if the RSSI value is out of valid range or unsupported. + */ +static inline int hl78xx_parse_rssi(uint8_t rssi, int16_t *value) +{ + /* AT+CSQ returns a response +CSQ: , where: + * - rssi is a integer from 0 to 31 whose values describes a signal strength + * between -113 dBm for 0 and -51dbM for 31 or unknown for 99 + * - ber is an integer from 0 to 7 that describes the error rate, it can also + * be 99 for an unknown error rate + */ + if (rssi == CSQ_RSSI_UNKNOWN) { + return -EINVAL; + } + + *value = (int16_t)CSQ_RSSI_TO_DB(rssi); + return 0; +} +/** + * @brief Convert raw RSRP value from the modem to dBm. + * + * Parses the Reference Signal Received Power (RSRP) value reported by the modem + * and converts it into a corresponding signal strength in dBm, typically based on + * 3GPP TS 36.133 specifications. + * + * @param rsrp Raw RSRP value (commonly in the range 0–97, or 255 if unknown). + * @param value Pointer to store the converted RSRP in dBm. + * + * @retval 0 on successful conversion. + * @retval -EINVAL if the RSRP value is out of range or represents an unknown value. + */ +static inline int hl78xx_parse_rsrp(uint8_t rsrp, int16_t *value) +{ + /* AT+CESQ returns a response + * +CESQ: ,,,,, where: + * rsrq is a integer from 0 to 34 whose values describes the Reference + * Signal Receive Quality between -20 dB for 0 and -3 dB for 34 + * (0.5 dB steps), or unknown for 255 + * rsrp is an integer from 0 to 97 that describes the Reference Signal + * Receive Power between -140 dBm for 0 and -44 dBm for 97 (1 dBm steps), + * or unknown for 255 + */ + if (rsrp == CESQ_RSRP_UNKNOWN) { + return -EINVAL; + } + + *value = (int16_t)CESQ_RSRP_TO_DB(rsrp); + return 0; +} +/** + * @brief Convert raw RSRQ value from the modem to dB. + * + * Parses the Reference Signal Received Quality (RSRQ) value provided by the modem + * and converts it into a signal quality measurement in decibels (dB), as specified + * by 3GPP TS 36.133. + * + * @param rsrq Raw RSRQ value (typically 0–34, or 255 if unknown). + * @param value Pointer to store the converted RSRQ in dB. + * + * @retval 0 on successful conversion. + * @retval -EINVAL if the RSRQ value is out of valid range or indicates unknown. + */ +static inline int hl78xx_parse_rsrq(uint8_t rsrq, int16_t *value) +{ + if (rsrq == CESQ_RSRQ_UNKNOWN) { + return -EINVAL; + } + + *value = (int16_t)CESQ_RSRQ_TO_DB(rsrq); + return 0; +} +/** + * @brief Pause monitor. + * + * Pause monitor @p mon from receiving notifications. + * + * @param mon The monitor to pause. + */ +static inline void hl78xx_evt_monitor_pause(struct hl78xx_evt_monitor_entry *mon) +{ + mon->flags.paused = true; +} +/** + * @brief Resume monitor. + * + * Resume forwarding notifications to monitor @p mon. + * + * @param mon The monitor to resume. + */ +static inline void hl78xx_evt_monitor_resume(struct hl78xx_evt_monitor_entry *mon) +{ + mon->flags.paused = false; +} +/** + * @brief Set the event notification handler for HL78xx modem events. + * + * Registers a callback handler to receive asynchronous event notifications + * from the HL78xx modem, such as network registration changes, GNSS updates, + * or other modem-generated events. + * + * @param handler Function pointer to the event monitor callback. + * Pass NULL to clear the existing handler. + * + * @retval 0 on success. + * @retval -EINVAL if the handler parameter is invalid. + */ +int hl78xx_evt_notif_handler_set(hl78xx_evt_monitor_dispatcher_t handler); +/** + * @brief Register an event monitor to receive HL78xx modem event notifications. + */ +int hl78xx_evt_monitor_register(struct hl78xx_evt_monitor_entry *mon); +/** + * @brief Unregister an event monitor from receiving HL78xx modem event notifications. + */ +int hl78xx_evt_monitor_unregister(struct hl78xx_evt_monitor_entry *mon); +/** + * @brief Convert HL78xx RAT mode to standard cellular API. + */ +enum cellular_access_technology hl78xx_rat_to_access_tech(enum hl78xx_cell_rat_mode rat_mode); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_HL78XX_APIS_H_ */ diff --git a/samples/drivers/modem/hello_hl78xx/.gitignore b/samples/drivers/modem/hello_hl78xx/.gitignore new file mode 100644 index 0000000000000..d422a2f743fda --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/.gitignore @@ -0,0 +1,11 @@ + +# Copyright (c) 2025 Netfeasa Ltd. +# +# SPDX-License-Identifier: Apache-2.0 +# +# editors +*.swp +*~ + +# build +/build*/ diff --git a/samples/drivers/modem/hello_hl78xx/CMakeLists.txt b/samples/drivers/modem/hello_hl78xx/CMakeLists.txt new file mode 100644 index 0000000000000..829d4bc26efe9 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/CMakeLists.txt @@ -0,0 +1,13 @@ +# Sierra Wireless HL78XX Sample CMake file + +# Copyright (c) 2025 Netfeasa +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(hello_hl78xx) + +target_sources(app PRIVATE src/main.c) + +include(${ZEPHYR_BASE}/samples/net/common/common.cmake) diff --git a/samples/drivers/modem/hello_hl78xx/Kconfig b/samples/drivers/modem/hello_hl78xx/Kconfig new file mode 100644 index 0000000000000..decc7199c7cc6 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/Kconfig @@ -0,0 +1,7 @@ +# Sierra Wireless HL78XX Sample options + +# Copyright (c) 2025 Netfeasa +# SPDX-License-Identifier: Apache-2.0 + +source "samples/net/common/Kconfig" +source "Kconfig.zephyr" diff --git a/samples/drivers/modem/hello_hl78xx/README.rst b/samples/drivers/modem/hello_hl78xx/README.rst new file mode 100644 index 0000000000000..55b4ea290f47d --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/README.rst @@ -0,0 +1,72 @@ +.. zephyr:code-sample:: hello_hl78xx + :name: Hello hl78xx modem driver + + get & set basic hl78xx modem information & functionality with HL78XX modem APIs + +Overview +******** + +A simple sample that can be used with only Sierra Wireless HL78XX series modems + +Notes +***** + +This sample uses the devicetree alias ``modem`` to identify +the modem instance to use. + +Building and Running +******************** + +This application can be built and executed on QEMU as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/modem/hello_hl78xx + :host-os: all + :goals: build flash + :compact: + +To build for another board, change "qemu_x86" above to that board's name. + +Sample Output +============= + +.. code-block:: console + + [00:00:12.840,000] hl78xx_socket: Apn="netfeasavodiot.mnc028.mcc901.gprs" + [00:00:12.840,000] hl78xx_socket: Addr=10.149.105.74.255.255.255.252 + [00:00:12.840,000] hl78xx_socket: Gw=10.149.105.73 + [00:00:12.840,000] hl78xx_socket: DNS=141.1.1.1 + [00:00:12.840,000] hl78xx_socket: Extracted IP: 10.149.105.74 + [00:00:12.840,000] hl78xx_socket: Extracted Subnet: 255.255.255.252 + [00:00:12.840,000] hl78xx_dev: switch from run enable gprs script to carrier on + [00:00:15.944,000] main: IP Up + [00:00:15.944,000] main: Connected to network + + ********************************************************** + ********* Hello HL78XX Modem Sample Application ********** + ********************************************************** + [00:00:15.980,000] main: Manufacturer: Sierra Wireless + [00:00:15.980,000] main: Firmware Version: HL7812.5.7.3.0 + [00:00:15.980,000] main: APN: netfeasavodiot + [00:00:15.980,000] main: Imei: 351144441214500 + [00:00:15.980,000] main: RAT: NB1 + [00:00:15.980,000] main: Connection status: Not Registered + [00:00:15.980,000] main: RSRP : -97 + ********************************************************** + + [00:00:15.980,000] main: Setting new APN: + [00:00:15.980,000] main: IP down + [00:00:15.980,000] main: Disconnected from network + [00:00:16.013,000] main: New APN: "" + [00:00:16.013,000] main: Test endpoint: flake.legato.io:6000 + [00:00:17.114,000] main: Resolved: 20.29.223.5:6000 + [00:00:17.114,000] main: Sample application finished. + +After startup, code performs: + +#. Modem readiness check and power-on +#. Network interface setup via Zephyr's Connection Manager +#. Modem queries (manufacturer, firmware, APN, IMEI, signal strength, etc.) +#. Network registration and signal strength checks +#. Setting and verifying a new APN +#. Sending an AT command to validate communication diff --git a/samples/drivers/modem/hello_hl78xx/boards/mg100.conf b/samples/drivers/modem/hello_hl78xx/boards/mg100.conf new file mode 100644 index 0000000000000..8faf74132ae1b --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/boards/mg100.conf @@ -0,0 +1 @@ +CONFIG_MODEM_HL7800=n diff --git a/samples/drivers/modem/hello_hl78xx/boards/mg100.overlay b/samples/drivers/modem/hello_hl78xx/boards/mg100.overlay new file mode 100644 index 0000000000000..63bfd0e5d5ffa --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/boards/mg100.overlay @@ -0,0 +1 @@ +#include "pinnacle_100_common.dtsi" diff --git a/samples/drivers/modem/hello_hl78xx/boards/nrf9160dk_nrf9160_ns.conf b/samples/drivers/modem/hello_hl78xx/boards/nrf9160dk_nrf9160_ns.conf new file mode 100644 index 0000000000000..d39bd9151a541 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/boards/nrf9160dk_nrf9160_ns.conf @@ -0,0 +1,22 @@ +CONFIG_UART_ASYNC_API=y +CONFIG_UART_1_ASYNC=y +CONFIG_UART_1_INTERRUPT_DRIVEN=n +# Enable HW RX byte counting. This especially matters at higher baud rates. +CONFIG_UART_1_NRF_HW_ASYNC=y +CONFIG_UART_1_NRF_HW_ASYNC_TIMER=1 + +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_MODEM_HL78XX_DEV_STARTUP_TIME=1000 +# Disable AT shell as SLM application has no AT mode user pipes +CONFIG_MODEM_AT_SHELL=n +# Increase log buffer size to accommodate large dumps +CONFIG_LOG_BUFFER_SIZE=65535 +CONFIG_MODEM_MODULES_LOG_LEVEL_DBG=y +CONFIG_MODEM_LOG_LEVEL_DBG=y +CONFIG_MODEM_CHAT_LOG_BUFFER_SIZE=1024 +CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG=y +# Print logs and printk() output on uart0. +CONFIG_LOG_BACKEND_UART=y +CONFIG_MODEM_LOG_LEVEL_DBG=y diff --git a/samples/drivers/modem/hello_hl78xx/boards/nrf9160dk_nrf9160_ns.overlay b/samples/drivers/modem/hello_hl78xx/boards/nrf9160dk_nrf9160_ns.overlay new file mode 100644 index 0000000000000..adc03c24afef8 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/boards/nrf9160dk_nrf9160_ns.overlay @@ -0,0 +1,45 @@ +/ { + aliases { + modem = &modem; + }; +}; + +&uart1 { + compatible = "nordic,nrf-uarte"; + current-speed = <115200>; + hw-flow-control; + status = "okay"; + + pinctrl-0 = <&uart1_default_alt>; + modem: hl_modem { + compatible = "swir,hl7812"; + status = "okay"; + mdm-reset-gpios = <&gpio0 20 (GPIO_ACTIVE_LOW)>; + socket_offload: socket_offload { + compatible = "swir,hl7812-offload"; + status = "okay"; + /* optional properties for future: */ + max-data-length = <512>; + }; + gnss: hl_gnss { + compatible = "swir,hl7812-gnss"; + pps-mode = "GNSS_PPS_MODE_DISABLED"; + fix-rate = <1000>; + status = "okay"; + }; + }; +}; + +&pinctrl { + uart1_default_alt: uart1_default_alt { + group1 { + psels = ; + bias-pull-up; + }; + group2 { + psels = , + , + ; + }; + }; +}; diff --git a/samples/drivers/modem/hello_hl78xx/boards/pinnacle_100_common.dtsi b/samples/drivers/modem/hello_hl78xx/boards/pinnacle_100_common.dtsi new file mode 100644 index 0000000000000..f0ac16dcefd29 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/boards/pinnacle_100_common.dtsi @@ -0,0 +1,36 @@ +/delete-node/ &hl7800; + +/ { + aliases { + modem-uart = &uart1; + modem = &hl7800; + gnss = &gnss; + }; +}; + +&uart1 { + hl7800: hl7800 { + compatible = "swir,hl7800"; + status = "okay"; + mdm-reset-gpios = <&gpio1 15 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; + mdm-wake-gpios = <&gpio1 13 (GPIO_OPEN_SOURCE | GPIO_ACTIVE_HIGH)>; + mdm-pwr-on-gpios = <&gpio1 2 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; + mdm-fast-shutd-gpios = <&gpio1 14 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; + mdm-vgpio-gpios = <&gpio1 11 0>; + mdm-uart-dsr-gpios = <&gpio0 25 0>; + mdm-uart-cts-gpios = <&gpio0 15 0>; + mdm-gpio6-gpios = <&gpio1 12 0>; + socket_offload: socket_offload { + compatible = "swir,hl7812-offload"; + status = "okay"; + /* optional properties for future: */ + max-data-length = <512>; + }; + gnss: hl_gnss { + compatible = "swir,hl7812-gnss"; + pps-mode = "GNSS_PPS_MODE_DISABLED"; + fix-rate = <1000>; + status = "okay"; + }; + }; +}; diff --git a/samples/drivers/modem/hello_hl78xx/boards/pinnacle_100_dvk.conf b/samples/drivers/modem/hello_hl78xx/boards/pinnacle_100_dvk.conf new file mode 100644 index 0000000000000..8faf74132ae1b --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/boards/pinnacle_100_dvk.conf @@ -0,0 +1 @@ +CONFIG_MODEM_HL7800=n diff --git a/samples/drivers/modem/hello_hl78xx/boards/pinnacle_100_dvk.overlay b/samples/drivers/modem/hello_hl78xx/boards/pinnacle_100_dvk.overlay new file mode 100644 index 0000000000000..63bfd0e5d5ffa --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/boards/pinnacle_100_dvk.overlay @@ -0,0 +1 @@ +#include "pinnacle_100_common.dtsi" diff --git a/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx-verbose-logging.conf b/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx-verbose-logging.conf new file mode 100644 index 0000000000000..8fad488237a6e --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/overlay-swir_hl78xx-verbose-logging.conf @@ -0,0 +1,8 @@ +# Logging +CONFIG_LOG_BUFFER_SIZE=85536 + +# For extra verbosity +CONFIG_MODEM_MODULES_LOG_LEVEL_DBG=y +CONFIG_MODEM_LOG_LEVEL_DBG=y +CONFIG_MODEM_CHAT_LOG_BUFFER_SIZE=1024 +CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG=y diff --git a/samples/drivers/modem/hello_hl78xx/prj.conf b/samples/drivers/modem/hello_hl78xx/prj.conf new file mode 100644 index 0000000000000..dc69dcd52f02b --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/prj.conf @@ -0,0 +1,76 @@ +# Sierra Wireless HL78XX Sample configuration + +# Copyright (c) 2025 Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +# The HL78xx driver gets its IP settings from the cell network + +#system +CONFIG_HEAP_MEM_POOL_SIZE=4096 +CONFIG_MAIN_STACK_SIZE=4096 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096 +CONFIG_POSIX_API=y + +#PM +# CONFIG_PM_DEVICE=y + +#uart +CONFIG_UART_ASYNC_API=y + +# Generic networking options +CONFIG_NETWORKING=y +CONFIG_NET_UDP=y +CONFIG_NET_TCP=y +CONFIG_NET_IPV6=n +CONFIG_NET_IPV4=y +CONFIG_NET_SOCKETS=y + +# DNS +CONFIG_DNS_RESOLVER=y +CONFIG_NET_SOCKETS_DNS_TIMEOUT=12000 + +# Wait for the network to be ready +CONFIG_NET_SAMPLE_COMMON_WAIT_DNS_SERVER_ADDITION=y + +# Network management +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y +# NB-IoT has large latency, so increase timeouts. It is ok to use this for Cat-M1 as well. +CONFIG_NET_SOCKETS_CONNECT_TIMEOUT=15000 +CONFIG_NET_CONNECTION_MANAGER=y + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=32 + +# Modem driver +CONFIG_MODEM=y + +#hl78xx modem +CONFIG_MODEM_HL78XX=y + +# Statistics +CONFIG_MODEM_STATS=y +CONFIG_SHELL=y + +#apn source +# CONFIG_MODEM_HL78XX_APN_SOURCE_KCONFIG=y +# CONFIG_MODEM_HL78XX_APN="internet" +# CONFIG_MODEM_HL78XX_APN_SOURCE_ICCID=y +# CONFIG_MODEM_HL78XX_APN_PROFILES="hologram=23450, wm=20601, vodafone=8988239, em=8988303" + +# RAT selection +CONFIG_MODEM_HL78XX_AUTORAT=n +# CONFIG_MODEM_HL78XX_AUTORAT_PRL_PROFILES="2,1,3" +# CONFIG_MODEM_HL78XX_AUTORAT_NB_BAND_CFG="3,8,20,28" +# CONFIG_MODEM_HL78XX_RAT_NB1=y + +# Stay in boot mode until registered to a network +# CONFIG_MODEM_HL78XX_STAY_IN_BOOT_MODE_FOR_ROAMING=y + +# Monitor modem events +CONFIG_HL78XX_EVT_MONITOR=y +# Logging +CONFIG_LOG=y diff --git a/samples/drivers/modem/hello_hl78xx/sample.yaml b/samples/drivers/modem/hello_hl78xx/sample.yaml new file mode 100644 index 0000000000000..625cf998cd748 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/sample.yaml @@ -0,0 +1,16 @@ +sample: + description: Sample for HL78XX modem + name: Hello HL78XX sample +common: + tags: + - modem + - hl78xx + filter: dt_alias_exists("modem") +tests: + sample.driver.modem.hello_hl78xx: + platform_allow: + - nucleo_u575zi_q + integration_platforms: + - nucleo_u575zi_q + extra_args: + - SHIELD=swir_hl78xx_ev_kit diff --git a/samples/drivers/modem/hello_hl78xx/src/main.c b/samples/drivers/modem/hello_hl78xx/src/main.c new file mode 100644 index 0000000000000..e45fe0eb80990 --- /dev/null +++ b/samples/drivers/modem/hello_hl78xx/src/main.c @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2025 Netfeasa + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* Macros used to subscribe to specific Zephyr NET management events. */ +#if defined(CONFIG_NET_SAMPLE_COMMON_WAIT_DNS_SERVER_ADDITION) +#define L4_EVENT_MASK (NET_EVENT_DNS_SERVER_ADD | NET_EVENT_L4_DISCONNECTED) +#else +#define L4_EVENT_MASK (NET_EVENT_L4_CONNECTED | NET_EVENT_L4_DISCONNECTED) +#endif +#define CONN_LAYER_EVENT_MASK (NET_EVENT_CONN_IF_FATAL_ERROR) + +#define TEST_SERVER_PORT 6000 +#define TEST_SERVER_ENDPOINT "flake.legato.io" + +LOG_MODULE_REGISTER(main, CONFIG_MODEM_LOG_LEVEL); + +static K_SEM_DEFINE(network_connected_sem, 0, 1); +const struct device *modem = DEVICE_DT_GET(DT_ALIAS(modem)); + +/* Zephyr NET management event callback structures. */ +static struct net_mgmt_event_callback l4_cb; +static struct net_mgmt_event_callback conn_cb; + +static const char *rat_get_in_string(enum hl78xx_cell_rat_mode rat) +{ + switch (rat) { + case HL78XX_RAT_CAT_M1: + return "CAT-M1"; + case HL78XX_RAT_NB1: + return "NB1"; +#ifdef CONFIG_MODEM_HL78XX_12 + case HL78XX_RAT_GSM: + return "GSM"; +#ifdef CONFIG_MODEM_HL78XX_12_FW_R6 + case HL78XX_RAT_NBNTN: + return "NTN"; +#endif /* CONFIG_MODEM_HL78XX_12_FW_R6 */ +#endif /* CONFIG_MODEM_HL78XX_12 */ + default: + return "Not ready"; + } +} +/** Convert registration status to string */ +static const char *reg_status_get_in_string(enum cellular_registration_status rat) +{ + switch (rat) { + case CELLULAR_REGISTRATION_NOT_REGISTERED: + return "Not Registered"; + case CELLULAR_REGISTRATION_REGISTERED_HOME: + return "Home Network"; + case CELLULAR_REGISTRATION_SEARCHING: + return "Network Searching"; + case CELLULAR_REGISTRATION_DENIED: + return "Registration Denied"; + case CELLULAR_REGISTRATION_UNKNOWN: + return "Out of coverage or Unknown"; + case CELLULAR_REGISTRATION_REGISTERED_ROAMING: + return "Roaming Network"; + default: + return "Not ready"; + } +} + +/** Convert module status code to string */ +/** Convert hl78xx module status enum to string */ +const char *hl78xx_module_status_to_string(enum hl78xx_module_status status) +{ + switch (status) { + case HL78XX_MODULE_READY: + return "Module is ready to receive commands. No access code required."; + case HL78XX_MODULE_WAITING_FOR_ACCESS_CODE: + return "Module is waiting for an access code."; + case HL78XX_MODULE_SIM_NOT_PRESENT: + return "SIM card is not present."; + case HL78XX_MODULE_SIMLOCK: + return "Module is in SIMlock state."; + case HL78XX_MODULE_UNRECOVERABLE_ERROR: + return "Unrecoverable error."; + case HL78XX_MODULE_UNKNOWN_STATE: + return "Unknown state."; + case HL78XX_MODULE_INACTIVE_SIM: + return "Inactive SIM."; + default: + return "Invalid module status."; + } +} + +/* Zephyr NET management event callback structures. */ +static void on_net_event_l4_disconnected(void) +{ + LOG_INF("Disconnected from network"); +} + +static void on_net_event_l4_connected(void) +{ + LOG_INF("Connected to network"); + k_sem_give(&network_connected_sem); +} + +static void connectivity_event_handler(struct net_mgmt_event_callback *cb, uint64_t event, + struct net_if *iface) +{ + if (event == NET_EVENT_CONN_IF_FATAL_ERROR) { + LOG_ERR("Fatal error received from the connectivity layer"); + return; + } +} + +static void l4_event_handler(struct net_mgmt_event_callback *cb, uint64_t event, + struct net_if *iface) +{ + switch (event) { +#if defined(CONFIG_NET_SAMPLE_COMMON_WAIT_DNS_SERVER_ADDITION) + case NET_EVENT_DNS_SERVER_ADD: +#else + case NET_EVENT_L4_CONNECTED: +#endif + LOG_INF("IP Up"); + on_net_event_l4_connected(); + break; + case NET_EVENT_L4_DISCONNECTED: + LOG_INF("IP down"); + on_net_event_l4_disconnected(); + break; + default: + break; + } +} + +static void evnt_listener(struct hl78xx_evt *event, struct hl78xx_evt_monitor_entry *context) +{ +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d HL78XX modem Event Received: %d", __LINE__, event->type); +#endif + switch (event->type) { + /* Do something */ + case HL78XX_LTE_RAT_UPDATE: + LOG_INF("%d HL78XX modem rat mode changed: %d", __LINE__, event->content.rat_mode); + break; + case HL78XX_LTE_REGISTRATION_STAT_UPDATE: + LOG_INF("%d HL78XX modem registration status: %d", __LINE__, + event->content.reg_status); + break; + case HL78XX_LTE_MODEM_STARTUP: + LOG_INF("%d HL78XX modem startup status: %s", __LINE__, + hl78xx_module_status_to_string(event->content.value)); + break; + default: + break; + } +} + +static void hl78xx_on_ok(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) +{ + if (argc < 2) { + return; + } +#ifdef CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG + LOG_DBG("%d %s %s", __LINE__, __func__, argv[0]); +#endif +} + +/** + * @brief resolve_broker_addr - Resolve the broker address and port. + * @param broker Pointer to sockaddr_in structure to store the resolved address. + */ +static int resolve_broker_addr(struct sockaddr_in *broker) +{ + int ret; + struct addrinfo *ai = NULL; + + const struct addrinfo hints = { + .ai_family = AF_INET, + .ai_socktype = SOCK_STREAM, + .ai_protocol = 0, + }; + char port_string[6] = {0}; + + snprintf(port_string, sizeof(port_string), "%d", TEST_SERVER_PORT); + ret = getaddrinfo(TEST_SERVER_ENDPOINT, port_string, &hints, &ai); + if (ret == 0) { + char addr_str[INET_ADDRSTRLEN]; + + memcpy(broker, ai->ai_addr, MIN(ai->ai_addrlen, sizeof(struct sockaddr_storage))); + + inet_ntop(AF_INET, &broker->sin_addr, addr_str, sizeof(addr_str)); + LOG_INF("Resolved: %s:%u", addr_str, htons(broker->sin_port)); + } else { + LOG_ERR("failed to resolve hostname err = %d (errno = %d)", ret, errno); + } + + freeaddrinfo(ai); + + return ret; +} + +MODEM_CHAT_MATCH_DEFINE(ok_match, "OK", "", hl78xx_on_ok); + +HL78XX_EVT_MONITOR(listener_evt, evnt_listener); + +int main(void) +{ + int ret = 0; + + if (device_is_ready(modem) == false) { + LOG_ERR("%d, %s Device %s is not ready", __LINE__, __func__, modem->name); + } +#ifdef CONFIG_PM_DEVICE + LOG_INF("Powering on modem\n"); + pm_device_action_run(modem, PM_DEVICE_ACTION_RESUME); +#endif + +#ifdef CONFIG_MODEM_HL78XX_BOOT_IN_FULLY_FUNCTIONAL_MODE + if (IS_ENABLED(CONFIG_NET_CONNECTION_MANAGER)) { + struct net_if *iface = net_if_get_default(); + + if (!iface) { + LOG_ERR("No network interface found!"); + return -ENODEV; + } + + /* Setup handler for Zephyr NET Connection Manager events. */ + net_mgmt_init_event_callback(&l4_cb, l4_event_handler, L4_EVENT_MASK); + net_mgmt_add_event_callback(&l4_cb); + + /* Setup handler for Zephyr NET Connection Manager Connectivity layer. */ + net_mgmt_init_event_callback(&conn_cb, connectivity_event_handler, + CONN_LAYER_EVENT_MASK); + net_mgmt_add_event_callback(&conn_cb); + + ret = net_if_up(iface); + + if (ret < 0 && ret != -EALREADY) { + LOG_ERR("net_if_up, error: %d", ret); + return ret; + } + + (void)conn_mgr_if_connect(iface); + + LOG_INF("Waiting for network connection..."); + k_sem_take(&network_connected_sem, K_FOREVER); + } +#endif + /* Modem information */ + char manufacturer[MDM_MANUFACTURER_LENGTH] = {0}; + char fw_ver[MDM_REVISION_LENGTH] = {0}; + char apn[MDM_APN_MAX_LENGTH] = {0}; + char operator[MDM_MODEL_LENGTH] = {0}; + char imei[MDM_IMEI_LENGTH] = {0}; + enum hl78xx_cell_rat_mode tech; + enum cellular_registration_status status; + int16_t rsrp; + const char *newapn = ""; + const char *sample_cmd = "AT"; + +#ifndef CONFIG_MODEM_HL78XX_AUTORAT +#if defined(CONFIG_MODEM_HL78XX_RAT_M1) + tech = HL78XX_RAT_CAT_M1; +#elif defined(CONFIG_MODEM_HL78XX_RAT_NB1) + tech = HL78XX_RAT_NB1; +#elif defined(CONFIG_MODEM_HL78XX_RAT_GSM) + tech = HL78XX_RAT_GSM; +#elif defined(CONFIG_MODEM_HL78XX_RAT_NBNTN) + tech = HL78XX_RAT_NBNTN; +#else +#error "No rat has been selected." +#endif +#endif /* MODEM_HL78XX_AUTORAT */ + + cellular_get_modem_info(modem, CELLULAR_MODEM_INFO_MANUFACTURER, manufacturer, + sizeof(manufacturer)); + + cellular_get_modem_info(modem, CELLULAR_MODEM_INFO_FW_VERSION, fw_ver, sizeof(fw_ver)); + + hl78xx_get_modem_info(modem, HL78XX_MODEM_INFO_APN, (char *)apn, sizeof(apn)); + + cellular_get_modem_info(modem, CELLULAR_MODEM_INFO_IMEI, imei, sizeof(imei)); +#ifdef CONFIG_MODEM_HL78XX_AUTORAT + /* In auto rat mode, get the current rat from the modem status */ + hl78xx_get_modem_info(modem, HL78XX_MODEM_INFO_CURRENT_RAT, + (enum cellular_access_technology *)&tech, sizeof(tech)); +#endif /* CONFIG_MODEM_HL78XX_AUTORAT */ + /* Get the current registration status */ + cellular_get_registration_status(modem, hl78xx_rat_to_access_tech(tech), &status); + /* Get the current signal strength */ + cellular_get_signal(modem, CELLULAR_SIGNAL_RSRP, &rsrp); + /* Get the current network operator name */ + hl78xx_get_modem_info(modem, HL78XX_MODEM_INFO_NETWORK_OPERATOR, (char *)operator, + sizeof(operator)); + + LOG_RAW("\n**********************************************************\n"); + LOG_RAW("********* Hello HL78XX Modem Sample Application **********\n"); + LOG_RAW("**********************************************************\n"); + LOG_INF("Manufacturer: %s", manufacturer); + LOG_INF("Firmware Version: %s", fw_ver); + LOG_INF("APN: \"%s\"", apn); + LOG_INF("Imei: %s", imei); + LOG_INF("RAT: %s", rat_get_in_string(tech)); + LOG_INF("Connection status: %s(%d)", reg_status_get_in_string(status), status); + LOG_INF("RSRP : %d", rsrp); + LOG_INF("Operator: %s", (strlen(operator) > 0) ? operator : "\"\""); + LOG_RAW("**********************************************************\n\n"); + + LOG_INF("Setting new APN: %s", newapn); + ret = cellular_set_apn(modem, newapn); + if (ret < 0) { + LOG_ERR("Failed to set new APN, error: %d", ret); + } + + k_sem_reset(&network_connected_sem); + LOG_INF("Waiting for network connection..."); + k_sem_take(&network_connected_sem, K_FOREVER); + + hl78xx_get_modem_info(modem, HL78XX_MODEM_INFO_APN, apn, sizeof(apn)); + + hl78xx_modem_cmd_send(modem, sample_cmd, strlen(sample_cmd), &ok_match, 1); + LOG_INF("New APN: %s", (strlen(apn) > 0) ? apn : "\"\""); + + struct sockaddr_in test_endpoint_addr; + + LOG_INF("Test endpoint: %s:%d", TEST_SERVER_ENDPOINT, TEST_SERVER_PORT); + + resolve_broker_addr(&test_endpoint_addr); + + LOG_INF("Sample application finished."); + + return 0; +} diff --git a/samples/drivers/modem/index.rst b/samples/drivers/modem/index.rst new file mode 100644 index 0000000000000..5b7a92c1018fa --- /dev/null +++ b/samples/drivers/modem/index.rst @@ -0,0 +1,5 @@ +.. zephyr:code-sample-category:: modem + :name: Modem + :show-listing: + + These samples demonstrate how to use the custom modem driver APIs. diff --git a/samples/net/cloud/aws_iot_mqtt/overlay-pinnacle_100-hl78xx.conf b/samples/net/cloud/aws_iot_mqtt/overlay-pinnacle_100-hl78xx.conf new file mode 100644 index 0000000000000..4c4dcdabd740c --- /dev/null +++ b/samples/net/cloud/aws_iot_mqtt/overlay-pinnacle_100-hl78xx.conf @@ -0,0 +1,4 @@ +CONFIG_MODEM_HL7800=n +CONFIG_UART_ASYNC_API=y +CONFIG_MODEM_HL78XX=y +CONFIG_MODEM_HL78XX_AUTORAT=n diff --git a/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx-tls.conf b/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx-tls.conf new file mode 100644 index 0000000000000..23f42c08229f6 --- /dev/null +++ b/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx-tls.conf @@ -0,0 +1,5 @@ +# socket tls +CONFIG_TLS_CREDENTIALS=y +CONFIG_TLS_MAX_CREDENTIALS_NUMBER=4 +CONFIG_MODEM_HL78XX_ADVANCED_SOCKET_CONFIG=y +CONFIG_MODEM_HL78XX_SOCKETS_SOCKOPT_TLS=y diff --git a/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx-verbose-logging.conf b/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx-verbose-logging.conf new file mode 100644 index 0000000000000..b94b8874c1a42 --- /dev/null +++ b/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx-verbose-logging.conf @@ -0,0 +1,13 @@ +# Logging +CONFIG_LOG_BUFFER_SIZE=65535 + +# For extra verbosity +CONFIG_MODEM_MODULES_LOG_LEVEL_DBG=y +CONFIG_MODEM_LOG_LEVEL_DBG=y +CONFIG_MODEM_CHAT_LOG_BUFFER_SIZE=1024 +CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG=y + +CONFIG_MQTT_LOG_LEVEL_DBG=y +CONFIG_LOG_BACKEND_NET=y +CONFIG_NET_BUF_LOG=y +CONFIG_NET_LOG=y diff --git a/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx_ev_kit.conf b/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx_ev_kit.conf new file mode 100644 index 0000000000000..2ebb4456acfcb --- /dev/null +++ b/samples/net/cloud/aws_iot_mqtt/overlay-swir_hl78xx_ev_kit.conf @@ -0,0 +1,64 @@ +# Sierra Wireless HL78XX driver options + +# Copyright (c) 2025 Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +# The HL78xx driver gets its IP settings from the cell network +CONFIG_NET_CONFIG_SETTINGS=n +CONFIG_NET_DHCPV4=n +CONFIG_DNS_SERVER_IP_ADDRESSES=n + +#PM +# CONFIG_PM_DEVICE=y + +#uart +CONFIG_UART_ASYNC_API=y + +# Generic networking options +CONFIG_NET_IPV6=n + +# SNTP +CONFIG_NET_CONFIG_SNTP_INIT_SERVER="time.google.com" + +# DNS +CONFIG_NET_SOCKETS_DNS_TIMEOUT=15000 + +# Wait for the network to be ready +CONFIG_NET_SAMPLE_COMMON_WAIT_DNS_SERVER_ADDITION=y + +# Network management +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y +CONFIG_NET_CONNECTION_MANAGER=y + +# NB-IoT has large latency, so increase timeouts. It is ok to use this for Cat-M1 as well. +CONFIG_NET_SOCKETS_CONNECT_TIMEOUT=15000 + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=32 + +# Modem driver +CONFIG_MODEM=y + +#hl78xx modem +CONFIG_MODEM_HL78XX=y + +# Statistics +CONFIG_MODEM_STATS=y +CONFIG_SHELL=y +# Don't require device to have time/date +CONFIG_MBEDTLS_HAVE_TIME_DATE=n + +#apn source +# CONFIG_MODEM_HL78XX_APN_SOURCE_KCONFIG=y +# CONFIG_MODEM_HL78XX_APN="internet" + +# RAT selection +CONFIG_MODEM_HL78XX_AUTORAT=n +# CONFIG_MODEM_HL78XX_RAT_NB1=y + +# Monitor modem events +CONFIG_HL78XX_EVT_MONITOR=y diff --git a/samples/net/cloud/aws_iot_mqtt/pinnacle_100-hl78xx.overlay b/samples/net/cloud/aws_iot_mqtt/pinnacle_100-hl78xx.overlay new file mode 100644 index 0000000000000..c5cdf23107e83 --- /dev/null +++ b/samples/net/cloud/aws_iot_mqtt/pinnacle_100-hl78xx.overlay @@ -0,0 +1,36 @@ +/delete-node/ &hl7800; + +/ { + aliases { + modem-uart = &uart1; + modem = &hl7800; + gnss = &gnss; + }; +}; + +&uart1 { + hl7800: hl7800 { + compatible = "swir,hl7800"; + status = "okay"; + mdm-reset-gpios = <&gpio1 15 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; + mdm-wake-gpios = <&gpio1 13 (GPIO_OPEN_SOURCE | GPIO_ACTIVE_HIGH)>; + mdm-pwr-on-gpios = <&gpio1 2 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; + mdm-fast-shutd-gpios = <&gpio1 14 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; + mdm-vgpio-gpios = <&gpio1 11 0>; + mdm-uart-dsr-gpios = <&gpio0 25 0>; + mdm-uart-cts-gpios = <&gpio0 15 0>; + mdm-gpio6-gpios = <&gpio1 12 0>; + socket_offload: socket_offload { + compatible = "swir,hl7812-offload"; + status = "okay"; + /* optional properties for future: */ + max-data-length = <512>; + }; + gnss: hl_gnss { + compatible = "swir,hl7812-gnss"; + pps-mode = "GNSS_PPS_MODE_DISABLED"; + fix-rate = <1000>; + status = "disabled"; + }; + }; +}; diff --git a/samples/net/common/Kconfig b/samples/net/common/Kconfig index afaab763ac58f..ceed53efa8692 100644 --- a/samples/net/common/Kconfig +++ b/samples/net/common/Kconfig @@ -6,7 +6,7 @@ config NET_SAMPLE_COMMON_WAIT_DNS_SERVER_ADDITION bool "Wait DNS server addition before considering connection to be up" - depends on MODEM_HL7800 && !DNS_SERVER_IP_ADDRESSES + depends on (MODEM_HL7800 || MODEM_HL78XX) && !DNS_SERVER_IP_ADDRESSES help Make sure we get DNS server addresses from the network before considering the connection to be up. diff --git a/samples/net/lwm2m_client/Kconfig b/samples/net/lwm2m_client/Kconfig index 9625cb1b949fe..7952bf39b1887 100644 --- a/samples/net/lwm2m_client/Kconfig +++ b/samples/net/lwm2m_client/Kconfig @@ -47,7 +47,7 @@ config NET_SAMPLE_LWM2M_SERVER config NET_SAMPLE_LWM2M_WAIT_DNS bool "Wait DNS server addition before considering connection to be up" - depends on MODEM_HL7800 && !DNS_SERVER_IP_ADDRESSES + depends on (MODEM_HL7800 || MODEM_HL78XX) && !DNS_SERVER_IP_ADDRESSES help Make sure we get DNS server addresses from the network before considering the connection to be up. diff --git a/samples/net/lwm2m_client/overlay-pinnacle_100-hl78xx.conf b/samples/net/lwm2m_client/overlay-pinnacle_100-hl78xx.conf new file mode 100644 index 0000000000000..0743f4d999207 --- /dev/null +++ b/samples/net/lwm2m_client/overlay-pinnacle_100-hl78xx.conf @@ -0,0 +1,27 @@ +CONFIG_MODEM_HL7800=n +CONFIG_UART_ASYNC_API=y +CONFIG_MODEM_HL78XX=y +CONFIG_MODEM_HL78XX_AUTORAT=n + +# The HL78xx driver gets its IP settings from the cell network +CONFIG_NET_CONFIG_SETTINGS=n +CONFIG_NET_CONNECTION_MANAGER=y +CONFIG_NET_SAMPLE_LWM2M_WAIT_DNS=y +CONFIG_DNS_RESOLVER=y + +# Generic networking options +CONFIG_NET_IPV6=n + +# NB-IoT has large latency, so increase timeouts. It is ok to use this for Cat-M1 as well. +CONFIG_NET_SOCKETS_DNS_TIMEOUT=12000 +CONFIG_NET_SOCKETS_CONNECT_TIMEOUT=13000 +CONFIG_NET_SOCKETS_DTLS_TIMEOUT=15000 +CONFIG_COAP_INIT_ACK_TIMEOUT_MS=15000 + +# Logging +# CONFIG_LOG_BUFFER_SIZE=65535 +# For extra debug +# CONFIG_MODEM_MODULES_LOG_LEVEL_DBG=y +# CONFIG_MODEM_LOG_LEVEL_DBG=y +# CONFIG_MODEM_CHAT_LOG_BUFFER_SIZE=1024 +# CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG=y diff --git a/samples/net/lwm2m_client/overlay-swir_hl78xx_ev_kit.conf b/samples/net/lwm2m_client/overlay-swir_hl78xx_ev_kit.conf new file mode 100644 index 0000000000000..d10805237afe9 --- /dev/null +++ b/samples/net/lwm2m_client/overlay-swir_hl78xx_ev_kit.conf @@ -0,0 +1,74 @@ +# Sierra Wireless HL78XX driver options + +# Copyright (c) 2025 Netfeasa Ltd. +# SPDX-License-Identifier: Apache-2.0 + +# The HL78xx driver gets its IP settings from the cell network +CONFIG_NET_CONFIG_SETTINGS=n +CONFIG_NET_DHCPV4=n +CONFIG_DNS_SERVER_IP_ADDRESSES=n + +#PM +# CONFIG_PM_DEVICE=y + +#uart +CONFIG_UART_ASYNC_API=y + +# Generic networking options +CONFIG_NET_IPV6=n + +# DNS +CONFIG_DNS_RESOLVER=y +CONFIG_NET_SOCKETS_DNS_TIMEOUT=15000 + +# POSIX API +CONFIG_POSIX_API=y +CONFIG_REQUIRES_FULL_LIBC=y + +# Wait for the network to be ready +CONFIG_NET_SAMPLE_LWM2M_WAIT_DNS=y + +# Network management +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y +CONFIG_NET_CONNECTION_MANAGER=y + +# NB-IoT has large latency, so increase timeouts. It is ok to use this for Cat-M1 as well. +CONFIG_NET_SOCKETS_CONNECT_TIMEOUT=15000 + +# Network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=32 + +# Modem driver +CONFIG_MODEM=y + +#hl78xx modem +CONFIG_MODEM_HL78XX=y + +# Statistics +CONFIG_MODEM_STATS=y +CONFIG_SHELL=y +# Don't require device to have time/date +CONFIG_MBEDTLS_HAVE_TIME_DATE=n + +#apn source +# CONFIG_MODEM_HL78XX_APN_SOURCE_KCONFIG=y +# CONFIG_MODEM_HL78XX_APN="internet" + +# RAT selection +CONFIG_MODEM_HL78XX_AUTORAT=n +# CONFIG_MODEM_HL78XX_RAT_NB1=y + +# Monitor modem events +CONFIG_HL78XX_EVT_MONITOR=y + +# Logging +CONFIG_LOG_BUFFER_SIZE=65535 +# For extra verbosity +CONFIG_MODEM_MODULES_LOG_LEVEL_DBG=y +CONFIG_MODEM_LOG_LEVEL_DBG=y +CONFIG_MODEM_CHAT_LOG_BUFFER_SIZE=1024 +CONFIG_MODEM_HL78XX_LOG_CONTEXT_VERBOSE_DEBUG=y diff --git a/samples/net/lwm2m_client/pinnacle_100-hl78xx.overlay b/samples/net/lwm2m_client/pinnacle_100-hl78xx.overlay new file mode 100644 index 0000000000000..c5cdf23107e83 --- /dev/null +++ b/samples/net/lwm2m_client/pinnacle_100-hl78xx.overlay @@ -0,0 +1,36 @@ +/delete-node/ &hl7800; + +/ { + aliases { + modem-uart = &uart1; + modem = &hl7800; + gnss = &gnss; + }; +}; + +&uart1 { + hl7800: hl7800 { + compatible = "swir,hl7800"; + status = "okay"; + mdm-reset-gpios = <&gpio1 15 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; + mdm-wake-gpios = <&gpio1 13 (GPIO_OPEN_SOURCE | GPIO_ACTIVE_HIGH)>; + mdm-pwr-on-gpios = <&gpio1 2 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; + mdm-fast-shutd-gpios = <&gpio1 14 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; + mdm-vgpio-gpios = <&gpio1 11 0>; + mdm-uart-dsr-gpios = <&gpio0 25 0>; + mdm-uart-cts-gpios = <&gpio0 15 0>; + mdm-gpio6-gpios = <&gpio1 12 0>; + socket_offload: socket_offload { + compatible = "swir,hl7812-offload"; + status = "okay"; + /* optional properties for future: */ + max-data-length = <512>; + }; + gnss: hl_gnss { + compatible = "swir,hl7812-gnss"; + pps-mode = "GNSS_PPS_MODE_DISABLED"; + fix-rate = <1000>; + status = "disabled"; + }; + }; +};