zephyrproject-rtos / zephyr

Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
https://docs.zephyrproject.org
Apache License 2.0
10.74k stars 6.56k forks source link

drivers: modem: bg9x: not supporting UDP #49208

Closed mjm987 closed 1 year ago

mjm987 commented 2 years ago

The IoT LWPA modem driver "quectel-bg9x" does not support UDP but only TCP. Thus low power and low payload protocols as CoAP and LwM2M are not suppported.

fabiobaltieri commented 2 years ago

@rerickson1 do you know if anyone can look into this?

rerickson1 commented 2 years ago

I don't have the hardware to test.

@bjarki-trackunit or @warasilapm know anyone with bg9x hardware?

warasilapm commented 2 years ago

I don't have the hardware to test.

@bjarki-trackunit or @warasilapm know anyone with bg9x hardware?

I do not have access to that modem.

bjarki-andreasen commented 2 years ago

I can make it happen :) I have a dev board with the modem, I will add it to this PR https://github.com/zephyrproject-rtos/zephyr/pull/48530, i have set aside tomorrow to get it reviewed an merged, adding and testing UDP is no issue :)

bjarki-andreasen commented 2 years ago

Adding UDP support to the BG9x driver is futile, I suggest another solution that will work now (and way better):

I suggest you use the PPP driver instead, this uses zephyrs IP stack and a protocol which is faster and more flexible than the socket implementation in the bg9x driver. To use it, simply add the following to your projects KConfig

CONFIG_GPIO=y

# GSM modem support
CONFIG_MODEM=y
CONFIG_MODEM_GSM_PPP=y
CONFIG_MODEM_CONTEXT=y
CONFIG_GSM_PPP_AUTOSTART=n
CONFIG_PPP_NET_IF_NO_AUTO_START=n
CONFIG_MODEM_GSM_QUECTEL=y
CONFIG_MODEM_GSM_APN="your apn"
CONFIG_MODEM_CELL_INFO=y
CONFIG_MODEM_SIM_NUMBERS=n
CONFIG_GSM_MUX=y
CONFIG_MAIN_STACK_SIZE=4096

# PPP networking support
CONFIG_NETWORKING=y
CONFIG_NET_NATIVE=y
CONFIG_NET_DRIVERS=y
CONFIG_NET_PPP=y
CONFIG_NET_L2_PPP=y
CONFIG_NET_L2_PPP_TIMEOUT=10000

# IPv4 enables PPP IPCP support
CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=n

CONFIG_NET_UDP=y
CONFIG_NET_SOCKETS=y

# DNS
CONFIG_DNS_RESOLVER=y
CONFIG_DNS_RESOLVER_MAX_SERVERS=1
CONFIG_DNS_SERVER_IP_ADDRESSES=y
CONFIG_DNS_SERVER1="8.8.8.8"

# Network management events
CONFIG_NET_CONNECTION_MANAGER=y

# Network management events
CONFIG_NET_CONNECTION_MANAGER=y

and remember to update the CONFIG_MODEM_GSM_APN="your apn"

and set the modem compatible to "zephyr,gsm_ppp"

/* BG95 */
&usart2 {
    pinctrl-0 = <&usart2_tx_pa2 &usart2_rx_pa3 &usart2_rts_pa1 &usart2_cts_pa0>;
    pinctrl-names = "default";
    current-speed = <115200>;
    status = "okay";
    hw-flow-control;

    gsm: gsm-modem {
        compatible = "zephyr,gsm_ppp";
        label = "gsm_ppp";
        status = "okay";
    };
};

then, you can use this sample application main.c


#include <zephyr/zephyr.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/modem/gsm_ppp.h>
#include <zephyr/net/socket.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/net/net_conn_mgr.h>
#include <zephyr/net/net_event.h>
#include <zephyr/net/net_mgmt.h>

#define HTTP_PORT 80
#define HTTP_IP ("142.250.74.110")
#define HTTP_HOST "www.google.com"
#define HTTP_PATH "/"

#define SSTRLEN(s) (sizeof(s) - 1)
#define CHECK(r) { if (r == -1) { printf("Error: " #r "\n"); exit(1); } }

#define REQUEST "GET " HTTP_PATH " HTTP/1.0\r\nHost: " HTTP_HOST "\r\n\r\n"

//static const struct gpio_dt_spec mdm_power = GPIO_DT_SPEC_GET(DT_PATH(zephyr_user), mdm_power_gpios);
//static const struct device *modem = DEVICE_DT_GET(DT_ALIAS(modem));
static char recv_buf[1024];

static struct net_mgmt_event_callback mgmt_cb;
static bool network_connected = false;

static void event_handler(struct net_mgmt_event_callback *unus, uint32_t mgmt_event, struct net_if * unus1) {

    if (mgmt_event == NET_EVENT_L4_CONNECTED) {
        network_connected = true;
    }

    if (mgmt_event == NET_EVENT_L4_DISCONNECTED) {
        network_connected = false;
    }
}

int main(void)
{
    volatile int ret;

    net_mgmt_init_event_callback(&mgmt_cb, event_handler, NET_EVENT_L4_CONNECTED | NET_EVENT_L4_DISCONNECTED);
    net_mgmt_add_event_callback(&mgmt_cb);

    /* Put your GPIO toggles here to turn on modem if not manually turned on */

    gsm_ppp_start(modem);

    while(network_connected == false) {
        k_msleep(500);
    }

    // Variables
    struct zsock_addrinfo address;
    static struct sockaddr sock_address;
    volatile int sock_fd;
    volatile int err;

    // Setup info
    address.ai_family = AF_INET;
    address.ai_protocol = IPPROTO_UDP;
    address.ai_socktype = SOCK_DGRAM;
    address.ai_addr = &sock_address;
    address.ai_addrlen = sizeof(sock_address);

    // Create socket address
    struct sockaddr_in * sockaddr_cast_ipv4 = net_sin(&sock_address);
    sockaddr_cast_ipv4->sin_family = AF_INET;
    sockaddr_cast_ipv4->sin_port = htons(HTTP_PORT);
    ret = net_addr_pton(AF_INET, HTTP_IP, &sockaddr_cast_ipv4->sin_addr);

    // Open socket
    sock_fd = zsock_socket(AF_INET, address.ai_socktype, address.ai_protocol);

    // Connect
    ret = zsock_connect(sock_fd, address.ai_addr, address.ai_addrlen);
    err = errno;

    // Send
    ret = zsock_send(sock_fd, REQUEST, SSTRLEN(REQUEST), 0);
    err = errno;

    // Disconnect
    ret = zsock_close(sock_fd);
    err = errno;

    gsm_ppp_stop(modem);

    return 0;
}

We are working on a completely rewritten driver for the quectel BG95 and BG96 devices based on PPP and which implement power management, but these will not be ready within the next months. With the above solution, you have a stable driver, you just need to manage powering it on and off manually. (which is not possible with the current BG9x driver currently anyway)

Hope this is a satisfactory alternative

warasilapm commented 2 years ago

Out of curiosity, why is adding UDP support futile, other than the PPP solution already exists?

warasilapm commented 2 years ago

Additionally, it may be possible to move the sara driver in this direction going forward as well, assuming the PPP works correctly. Are you using the standard PPP driver included in Zephyr?

bjarki-andreasen commented 2 years ago

Out of curiosity, why is adding UDP support futile, other than the PPP solution already exists?

The BG9x driver is at a stage closer to proof of concept than a production ready driver. Adding functionality to it would simply add more points of failure to something that is already unstable.

More importantly for future drivers, the BG9x driver is using socket offload, which means that the entire IP stack is on the modem, so zephyrs well tested implementations based on mbedtls are not available, like MQTT, TLS etc. The modem does support these features (in theory... from experience, the implementations on less used features like FTP(S) are often broken, and since the source code is proprietary, we can't fix it), but the BG9x driver does not even attempt to implement them, getsockopt and setsockopt are simply not implemented.

There is one argument for using the socket_offload, which is to save processing power and other resources, since the entire IP stack is on the modem. So being able to choose the implementation would in theory be a good option for the end user, but, having a single portable library for PPP and implementing it in the drivers is simpler and allows for higher code quality (through reuse and testing)

More relevant info: There is work being done on creating a new device driver model which will allow for multiple interfaces pr device, so we properly can implement extra features like SMS, proper info regarding cell towers for cellular modems etc. For now, modems are "just" network interfaces...

warasilapm commented 2 years ago

I agree that some of the modem drivers (namely those that use socket offload primarily) are not exactly production ready, especially the BG9x.

That said, if you didn't already know, you can use the TLS_NATIVE socket options to achieve TLS and other features using socket offload. You can even set the native tls socket priority higher than the offloaded socket priority to achieve this by default for TLS enabled sockets. I'm not sure why the socket offload situation would prevent you from using the MQTT implementation in Zephyr though with TLS_NATIVE in use.

Not that I disagree with using PPP if available and the host can support the full IP stack. I'd personally prefer to do this on our products using the SARA-R4, which is a very similar application to what you're looking at with the BG9x with GNSS. We may end up writing very similar end drivers down the road.

rerickson1 commented 2 years ago

I would also like to look at what it would take to make the HL7800 work with PPP.

bjarki-andreasen commented 2 years ago

TLS_NATIVE

TLS_NATIVE can work with UDP and TCP support only then, which is good, because that means the socket_offload would only need to support those. The throughput is slower using an offloaded UDP or TCP stack since the communication between modem and driver has a way bigger overhead (open socket -> wait for ok -> connect socket -> wait for connect -> send chunk of 1500 bytes or so -> wait for ok -> repeat) whereas PPP, which is just a L2 interface multiplexed over the uart line has no such limitation.

bjarki-andreasen commented 2 years ago

The plan which i think we should get started on is to leave the drivers that currently exists as they are for now, then create a portable library like the modem_cmd_handler, named modem_ppp, and pull out all portable code from the gsm_ppp.c driver to it, then we can write new drivers and update the old ones, to use this new modem_ppp libary with the modem_cmd_handler and modem_iface_uart libraries.

This should allow us to mass create stable drivers for many different modems, and focus primarily on power management and secondary features (and quirks) in the individual modem drivers.

warasilapm commented 2 years ago

I think we agree there are many benefits.

The gsm_ppp.c is the portable modem, if my understanding is correct. Its documentation illustrates how to multiplex AT commands to the driver.

What prevents the current PPP driver from being used portably with the components for specific modems? Is it necessary to wrap the PPP implementation in the modem specific code and is there an obstacle to doing that? The intention seems to be the exact opposite. That doesn't mean the intention makes sense long term.

bjarki-andreasen commented 2 years ago

I think we agree there are many benefits.

The gsm_ppp.c is the portable modem, if my understanding is correct. Its documentation illustrates how to multiplex AT commands to the driver.

What prevents the current PPP driver from being used portably with the components for specific modems? Is it necessary to wrap the PPP implementation in the modem specific code and is there an obstacle to doing that? The intention seems to be the exact opposite. That doesn't mean the intention makes sense long term.

Power management and modem specific features. PPP only sets up a channel for network communication. CONFIG_MODEM_GSM_SIMCOM and CONFIG_MODEM_GSM_QUECTEL illustrate that there are some settings that are modem specific regarding setting up the communcations channel as well. (typically called chat scripts, here are ones for the quectel modems https://github.com/Quectel-Community/meta-quectel-community/tree/master/recipes-quectel-community/quectel-ppp/quectel-ppp-0.1)

The BG95 driver would contain the ppp functionality, which would set it up the networking. It would also set up the modem specific PPP configurations, control gpios like mdm_power and mdm_reset etc. expose more features like SMS, built in GNSS, possible GPIO muxes etc.

bjarki-andreasen commented 2 years ago

The portable modem_ppp would be similar to the linux ppp daemon (pppd) which sets up networking, using modem specific configurations through chat scripts https://github.com/Quectel-Community/meta-quectel-community/tree/master/recipes-quectel-community/quectel-ppp/quectel-ppp-0.1

warasilapm commented 2 years ago

The portable modem_ppp would be similar to the linux ppp daemon (pppd) which sets up networking, using modem specific configurations through chat scripts https://github.com/Quectel-Community/meta-quectel-community/tree/master/recipes-quectel-community/quectel-ppp/quectel-ppp-0.1

Right, that's actually my reasoning here. The PPP driver right now is meant to fulfil that same role. Apparently, the KConfigs fill the role of those scripts, in the sense that they select application code.

I suppose whether we refactor gsm_ppp.c to support more configuration without writing code or entirely replace it by way of creating a new modem_ppp.c is not really important. Either way I agree we should shore up what we have already. There's a lot to figure out for this new an exciting proposal. I'll try and find some time to work through your PR waiting on the sara driver ASAP.

@rerickson1 is this something to put on a roadmap? Is there a committee for the modem drivers?

warasilapm commented 2 years ago

If my understanding is correct, another benefit of moving away from socket_offload() is that it allows the network to be brought "up" after launching the application rather than having to wait for registration/connection like the SARA-R4 driver does now.

bjarki-andreasen commented 2 years ago

If my understanding is correct, another benefit of moving away from socket_offload() is that it allows the network to be brought "up" after launching the application rather than having to wait for registration/connection like the SARA-R4 driver does now.

Yes, that is one of the best features IMO, having the driver init just set up the hardware and command handlers etc.

mjm987 commented 2 years ago

Doesn't PPP encapsulate the UDP packages which are decoded/removed on the remote side by a PPP daemon? In this case it isn't suitable for using with CoAP, isn't it?

Further, extended LPWA modem/network power features as RAI couldn't be used.

bjarki-andreasen commented 2 years ago

@mjm987

PPP is a data link layer, https://en.wikipedia.org/wiki/OSI_model, it simply allows for bytes to be sent, untreated, from one node to another, the IP stack formats the data (or packets) that must be sent from one node to another. PPP is just above the physical layer, which could be ethernet, or GPRS, or some LPWA technology. PPP creates what could best be described as a RAW tunnel, or socket, on which bytes are sent.

This flow shows how data is sent from application to destination using PPP to the modem Application -> IP stack -> (CoAP / MQTT / FTP etc.) -> (TCP / UDP) -> modem -> PPP -> (LPWA, Ethernet, GPRS etc.) -> Destination

Using UDP/TCP stack inside the modem looks like this: Application -> IP stack -> (CoAP / MQTT / FTP etc.) -> modem -> (TCP / UDP) -> (LPWA, Ethernet, GPRS etc.) -> Destination

On Linux, the PPP daemon just sets up the raw socket, which CURL or WGET can then use to send data to a destination. CURL and WGET manage the transfer protocol, like MQTT, HTTPS etc.

mjm987 commented 2 years ago

Ok, I agree it should be possible but there are still some problem when using PPP:

  1. The server side needs modification by using a PPP Deaemon
  2. Additional 6..8 bytes overhead which consumes some power
  3. No influence about the connection according provider selection, select the lwpa thechnology (LTE-M or NB-IoT) baseband channels to use, power control (eDRX, RAI) etc. etc.

What I would prefer is a simple generic modem API where I could register the modem response codes, and execute AT command with timeouts which returns a device state as return code.

Is there any way to use the modem command handler directly from an application?

bjarki-andreasen commented 2 years ago

Ok, I agree it should be possible but there are still some problem when using PPP:

1. The server side needs modification by using a PPP Deaemon

2. Additional 6..8 bytes overhead which consumes some power

3. No influence about the connection according provider selection, select the lwpa thechnology (LTE-M or NB-IoT) baseband channels to use, power control (eDRX, RAI) etc. etc.

What I would prefer is a simple generic modem API where I could register the modem response codes, and execute AT command with timeouts which returns a device state as return code.

Is there any way to use the modem command handler directly from an application?

  1. The server will not see PPP, nor will it see the physical layer (LTE-M etc.) The Destination in my comment is a cellular tower. The server will see UDP or TCP, unless you are a network provider. PPP is used to communicate between the microcontroller and the modem, not the modem and the server, I incorrectly left PPP in the second flow above and have removed it.
  2. The PPP encapsulation is only used between microcontroller and modem, modem will send the appropriate bytes plus encapsulation based on physical layer protocol (LTE-M etc.)
  3. For now, if you need to configure used tech like LTE-M at compile time, clone the ppp_gsm driver out-of-tree, and update the setup commands to configure the modem to your liking. If you want to change this during runtime as well, update the out-of-tree driver to support the actions you need, and add a header which exposes the new functions you have added (see zephyr/include/drivers/modem/gsm_ppp.h). You can of course write your own driver application side, and directly access the UART, but then you will have to re implemenent the functionality of the PPP driver, its more efficient to just update the driver to perform the AT commands you need.

In the future, we will be able to expose configurations like the technology used during runtime using the new mutli API device model. One API i want to create for modems and other co processors is a DTM or direct-access mode which exposes a stream like API to the modem (read/write), to perform things like firmware updating, direct test commands, or other complex actions not easily supported by generic APIs. This could also be used in cases where the modem driver is part of the application which is often the case due to currently poor API support.

mjm987 commented 2 years ago

Thx for explanation, i got it now ;-) I tested your code with th a little help from the actual gsm-ppp sample and it generally it works! But unfortunately not very reliable: if the modem connection is unreliable or if the modem goes in automatic power down mode, sending data is no more possible. It seems the network stack locks up without error message. Even if the modem is woken-up by gpio or suspend/resume the modem via shell command or adding timeouts by socketopt SO_SNDTIMEO / SO_RCVTIMEO did not help.

I guess for now I will proceed by using the modem command handler directly in my application to have full control about timeouts and errors.

But neverthelss many thx for helping understand the basics and your effort!

bjarki-andreasen commented 2 years ago

@warasilapm Have you been able to find time to work through the issues with this PR and the SARA-R4 update?

github-actions[bot] commented 1 year ago

This issue has been marked as stale because it has been open (more than) 60 days with no activity. Remove the stale label or add a comment saying that you would like to have the label removed otherwise this issue will automatically be closed in 14 days. Note, that you can always re-open a closed issue at any time.