Open INemesisI opened 5 years ago
Could you please take a look at this Pull Request? https://github.com/zephyrproject-rtos/zephyr/pull/18103
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!
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
.
@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
@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:
modem_power_hardoff.c
which would be similar to the 1st option @chris-makaio describes where the modem is completely offThe 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.
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
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
@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:
ublox-sara-r4.c
) initializes some private data for a power layer (currently I'll call it modem_power.c
) -- specifically setting a function pointer for set_power_mode()
.modem_power.c
registers itself with Zephyr's Device Power Management infrastructure (https://docs.zephyrproject.org/latest/reference/power_management/index.html#device-power-management-infrastructure) to receive the PM state change notifications.modem_power.c
receives a PM state change from Zephyr's Device Power Management infrastructure, it is sent to the set_power_mode
callback handled by the modem driver.This would leave the actual implementation of the power states up to each modem which might have differing commands?
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.
@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
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
@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.
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
@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
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?
Anyone interested in this? I'd like to discuss my ideas before implementing it. Otherwise I'll implement and get hurt in review ;)
@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.
@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()
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.
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).
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.
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.
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):
@bjarki-trackunit should we close this issue now that the new modem subsystem?
@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 ?
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.