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.61k stars 6.5k forks source link

Driver: modem add interface enable handler for power saving #18117

Open INemesisI opened 5 years ago

INemesisI commented 5 years ago

Is your enhancement proposal related to a problem? Please describe. Currently the modem implementation connects to a network before booting the main application and stays connected forever. This is problematic for battery powered applications where we want to be offline most of the time to save power. There is currently no way of disabling/enabling the module from the main application.

Describe the solution you'd like I am proposing to use the enable function of the existing L2 layer implementation. The enable function is called whenever net_if_up() or net_if_down() is called on the interface. The main application just needs to activate or deactivate the interface to shut down or wake up the module . The L2 layer enable is already used by the ethernet implementation and solves the issue with only minimal changes. This would require to add a new OFFLOAD_L2 layer which is added to the interface in the NET_DEVICE_OFFLOAD_INIT and NET_IF_OFFLOAD_INIT macros.

Describe alternatives you've considered I considered adding the enable function to the socket_offload api. This would require adding a second enable function call check in net_if_up and net_if_down, but is also a valid solution to me.

Additional context I created a proof of concept for the L2 layer approach based on #18103: https://github.com/INemesisI/zephyr/commit/43bc52543f8ac65b1fc5544e81b61e58cd0d40ee I am however not sure where to put the initialisation of the L2 layer. IMO it should not be in the modem implementation as it is at the moment.

carlescufi commented 5 years ago

Could you please take a look at this Pull Request? https://github.com/zephyrproject-rtos/zephyr/pull/18103

INemesisI commented 5 years ago

I did, I committed on top of #18103. I was thinking of putting the L2 layer initialisation into the socket layer? @mike-scott might have a better understanding of the new code!

jukkar commented 5 years ago

I was thinking of putting the L2 layer initialisation into the socket layer?

I commented the prototype at #18103: INemesisI@43bc525 and the L2 offload init needs to go to subsys/net/l2/offload.

chris-schra commented 5 years ago

@INemesisI and @mike-scott, how do you like to handle the different sleep types? Especially - in our case - AT+CFUN=0, AT+PWRDN (Modem device completely off) vs. "NETWORK_SLEEP" (or so), eg AT+CSCLK / AT+QSCLK / AT+UPSV for sending the device to lowest power mode while staying connected to the network

mike-scott commented 5 years ago

@INemesisI @chris-makaio brings up great point. I think with the addition of the modem context layers, we can put in place 1 (or more) low power layers which is selectable by the driver.

We will need a PR to introduce a generic interface for modem power to the modem_context structure. Something like struct modem_power with a function prototype called set_power_mode. Then have several power implementations for the drivers to choose from:

The real "win" in my mind would be to integrate the modem power API with Zephyr's power management API: https://docs.zephyrproject.org/latest/reference/power_management/index.html

Then let Zephyr tell the modem when it's ok to power down for a while.

All of this will need quite a bit of research. In particular, many of the power modes only work well if you know in advance how long you want to stay asleep.

chris-schra commented 5 years ago

I think eDRX is too specific and should go to the same config struct as the APN. And as you said, time is the factor, so there might be an advantage for something similar to k_sleep logic, BUT there are too many possible use cases so frankly speaking, I would just go with the Kernel‘s PM states suspend and let the user decide according to the use case. BUT, making proper use of is_busy could check the sockets.

To sum up what I propose: DEVICE_PM_LOW_POWER_STATE: slow clock (XSCLK=1) DEVICE_PM_SUSPEND_STATE: AT+CFUN=0 DEVICE_PM_OFF_STATE: PWRDWN

chris-schra commented 5 years ago

Please also keep in mind that the modem (as it seems we‘re planning it atm) is not all about sockets. For example, one might use the SMS feature only as a trigger and no net Another example (might be too specific) is that I want to add a driver for RN2483 AT-based LoRa-modem. It could use some sort of sockets (say, faked UDP) but basically just uses the AT-command part and the shared modem driver api. And there are also WiFi modules with at command set

mike-scott commented 5 years ago

@chris-makaio Yes, I think these layers may need to be renamed/moved at some point. Several other device types can make use of the command-based helpers and possibly this implementation for power states.

My flow currently looks something like this:

This would leave the actual implementation of the power states up to each modem which might have differing commands?

mike-scott commented 5 years ago

Note that network-based devices might also need to perform network interface management when power states change to correctly reflect whether the interface is up / down for samples.

chris-schra commented 5 years ago

@mike-scott I never worked with "the other way" (not the device PM way) so I'm not sure if it leaves control over the power state transitions. I mean... well... basically AT+xSCLK=1 can be applied all of the time, maybe except times where there is a lot of UART activity. So this would logically be the low_power mode. I guess. I as a user writing my program would do:

So, clearly, everything depends on the use case. I'll try to apply it to your logic (as far as I understand it) and draw this case:

And keep in mind that the application itself might require - eg when an exception occurs - to irregularly send data to the server, outside the timed loop.

Ugh, sorry, I hope it's a bit clear what I mean...

At the moment, I do the whole logic app-side and depending on the status of the business logic, I call device_set_power_state with one mode or the other

chris-schra commented 5 years ago

the more I think about the more I understand what you mean. If you're planning to have a least two power control modes (like the OS), one being "application" (mine), the other being "os" (yours), I absolutely +1

INemesisI commented 5 years ago

@chris-makaio thanks for pointing that out! @mike-scott your implementation idea sounds great! Much more sophisticated than the interface up/down implementation.

IMO there still needs to be a way for the application to tell the driver to connect/disconnect.

In our use case we only want to communicate a couple of times per day, but we are doing data processing every other minute. If the power mode controls the modem state it might be enabled without even being used.

INemesisI commented 5 years ago

BTW, I addressed the comments from @jukkar, If anybody is interested in the ifup/ifdown implementation until we figured out a proper power saving implementation: https://github.com/INemesisI/zephyr/commit/e5a508de291c2c21f2e49b1e94049fccbf9e1f67

mike-scott commented 5 years ago

@INemesisI @chris-makaio Your points of where to control the power states is a good one. I think we can let the sample itself determine what controls the power state.

For the "controlled directly by the sample" use-case: There are functions such as device_list_get() and device_set_power_state() described here which could be used directly: https://docs.zephyrproject.org/latest/reference/power_management/index.html#get-device-list

chris-schra commented 4 years ago

okay, I'm back on this. Any progress so far? I'm currently implementing some generic Quectel drivers based on @mike-scott 's U-Blox implementation. But I found that there is a huge power leakage due to the uart_irq_rx_enable in modem_iface_uart.c which kept the UART from sleeping (average current during "sleep" about 1mA). ATM I disable the RX IRQ on sleep, replacing it by a raw gpio_callback and the current dropped to 160uA.

I've added two functions in modem_iface_uart:

int modem_iface_uart_sleep(struct modem_iface *iface,
                              const char *dev_name)
{
    /* get UART device */
    iface->dev = device_get_binding(dev_name);
    if (!iface->dev) {
        return -ENODEV;
    }

    uart_irq_rx_disable(iface->dev);

    return 0;
}

int modem_iface_uart_wake(struct modem_iface *iface,
                           const char *dev_name)
{
    /* get UART device */
    iface->dev = device_get_binding(dev_name);
    if (!iface->dev) {
        return -ENODEV;
    }

    uart_irq_rx_enable(iface->dev);

    return 0;
}

and the driver itself:


static void modem_uart_sleep(void)
{
    LOG_DBG("Modem UART sleep");
    modem_iface_uart_sleep(&mctx.iface, MDM_UART_DEV_NAME);
    device_set_power_state(device_get_binding(MDM_UART_DEV_NAME), DEVICE_PM_LOW_POWER_STATE, NULL, NULL);
    gpio_pin_interrupt_configure( device_get_binding("GPIO_0"), 11, GPIO_INT_EDGE_TO_ACTIVE);
    gpio_add_callback(device_get_binding("GPIO_0"), &button_cb_data);

    mdata.awake = false;
}
static void modem_uart_wake(void)
{
    if(!mdata.awake)
    {
        modem_pin_config(&mctx, MDM_POWER, false);
        modem_pin_config(&mctx, MDM_WAKE, false);

        LOG_DBG("Modem UART wake");
        gpio_remove_callback(device_get_binding("GPIO_0"), &modem_raw_isr);
        device_set_power_state(device_get_binding(MDM_UART_DEV_NAME), DEVICE_PM_ACTIVE_STATE, NULL, NULL);
        modem_iface_uart_wake(&mctx.iface, MDM_UART_DEV_NAME);
    }
    mdata.awake = true;
}

I haven't found the correct way to retrieve the RX pin & ctrl yet

Any comments?

chris-schra commented 4 years ago

Anyone interested in this? I'd like to discuss my ideas before implementing it. Otherwise I'll implement and get hurt in review ;)

ghost commented 4 years ago

@chris-makaio It's hard for me to see where the discussion in this issue landed. Maybe you can recap briefly what you plan to do and what is your scope?

Btw, the pins usually go to the device tree, which when done correctly creates defines that you can use in place of "GPIO_0" and 11.

tbursztyka commented 4 years ago

@chris-makaio suspend/resume PM state are now supported in network stack, i.e.: you suspend the device via PM, the net stack will be notified and act accordingly (same when resuming). It requires that your driver implements the PM API control function. See an actual example: drivers/ethernet/eth_mcux.c:eth_mcux_device_pm_control()

chris-schra commented 4 years ago

Sorry for the confusion

@weinholtendian cellular modems, especially those for LTE-M / NB, feature a whole set of power saving mechanisms. I think we need to find a solution to use them in a transparent way. Actually, the UART stuff is wrong here, sorry for that. I just found that the current interrupt mechanism for UART's RX didn't let the UART go to sleep. And of course I know that the device pins are configure in DT but I was not interested in the specific device's pin but rather in the parent UART's RX pin. I had to figure out how to solve it with the new DT_ macros. Just for future reference I solved it likes this:

#define BC92_UART_NODE DT_PARENT(DT_N_INST_0_quectel_bc92)
#define BC92_UART_RX_PIN DT_PROP(BC92_UART_NODE, rx_pin)
#if BC92_UART_RX_PIN > 31
#define BC92_UART_RX_CTRL DT_LABEL(DT_N_NODELABEL_gpio1)
#else
#define BC92_UART_RX_CTRL DT_LABEL(DT_N_NODELABEL_gpio0)
#endif

I know that I could and should replace the "DT_N_INST_0" by another macro, but for now it's fine. Now I can remove the uart_irx_rq and add a "normal" GPIO interrupt like so

uart_irq_rx_disable(iface->dev);
gpio_pin_interrupt_configure( device_get_binding(BC92_UART_RX_CTRL), BC92_UART_RX_PIN, GPIO_INT_EDGE_TO_ACTIVE);
gpio_add_callback(device_get_binding(BC92_UART_RX_CTRL), &button_cb_data);

UART consumption drops from > 900uA to < 10uA

@tbursztyka as discussed previously, there's a lot of complexity for low power cellular devices. I've already implemented the PM API but I'm afraid I'll have to add some more KCONFIG options to let the user control what should happen in which mode. That's the primary question: What is a low power mode for a low power cellular modem? Here's my current assignment:

DEVICE_PM_LOW_POWER_STATE: Send UART to sleep, enable device's UART sleep mode

DEVICE_PM_SUSPEND_STATE: Set devices functionality (AT+CFUN) to minimum functionality (AT+CFUN=0)

DEVICE_PM_OFF_STATE: Power down device, in case of Quectel modems that's "AT+QPOWD"

But what if PSM should be supported. For those who don't know about it, it is a finer grained control to manage the connection with the operator. While a GPRS modem has to "ping" the operator in regular intervals (about 2.5secs, typically, set by operator) which is really power consuming, PSM allows for NB/LTE-M devices to control the intervals. Like with LoRa you can tell your device when to wake up and poll the operator for new data (or push data if you want to). For now I'm working with a case similar to a LoRa Class-A node: the modem is mostly sleeping and only if my application has something to push, I wake the modem from PSM and push the data to the cloud. Most of the time we're in the <10uAs. On the other hand, there's a case similar to LoRa Class-C nodes: the device should be reachable by the network preferably the whole time. In this mode, the current consumption would be around 1.5mA, depending on the PSM polling frequency. And this polling frequency is a whole new configuration thing as the possible intervals range from a few hours to even weeks. What makes it even more complicated is that the most devices as of today don't have the possibility to be woken up early. Say, the user controls the modem to sleep for 2 hours but something happens and he wants to wake the modem earlier - no way, the most modems need a full reset (with power consuming reconnect to the network). Fortunately newer generations (Q4/2019+) support exiting PSM via a dedicated pin. That's what we do currently and it's awesome.. the whole system has 25uA in idle and if a specific sensor value changes (say, a button press) the application wakes the modem and pushes the data (ca 2s latency from button press until the server receives the data). The modem is awake for < 7s with a total consumption of 130uWh.

ghost commented 4 years ago

Is power management for the GSM modem driver in scope for this issue? (PPP and GSM 07.10 need to be reinitialized when starting up a completely powered off modem).

mtahirbutt commented 4 years ago

I am facing a similar issue to completely power off modem, but reintialize routines is not working, may be I have set correctly the modem context. is someone successful in this regard.

gordonklaus commented 2 years ago

It looks like this discussion has stalled.

I'd be glad to join the discussion and help with testing, code review, and development support, in case anyone wants to revive this issue. Any kind of modem power saving support would be valuable to me. I'm using a BG96 modem.

rerickson1 commented 2 years ago

Low power operation is working in the HL7800 driver. Making a more reusable common infrastructure is needed though. Key things that need to happen (HL7800 driver has examples of these):

  1. When modem is in reset/sleep, no IO from the host should have active voltage.
  2. Turn off host UART when modem is asleep
  3. Support for turning the modem all the way off vs holding it in reset
  4. Properly terminate socket connections (for upper stack layers) when modem goes to sleep
  5. Re-init IP connection when modem wakes up
  6. Resume configured sockets from upper stack layer where applicable
carlescufi commented 10 months ago

@bjarki-trackunit should we close this issue now that the new modem subsystem?

bjarki-andreasen commented 10 months ago

@bjarki-trackunit should we close this issue now that the new modem subsystem?

Well, not really, this is something I have planned in the near future, the current driver uses PM alone to control the modem, but we should be controlling the radio separately, I need to extend the modem_ppp module to forward the L2 enable call

Maybe assign it to me @mike-scott ?