flipperdevices / flipperzero-firmware

Flipper Zero firmware source code
https://flipperzero.one
GNU General Public License v3.0
12.66k stars 2.7k forks source link

User apps subsystem #73

Closed DrZlo13 closed 4 years ago

DrZlo13 commented 4 years ago

Problem

Flipper zero must run user applications.

Constrains

Static storage: solved, recently we get a sd card. Executable storage: we have only 128k ram. Flash storage is not an option because according to the datasheet inner mcu flash has only 10k rewrites. Speed: user apps must run native code, because some protocols require tight gpio timings, eg.

By speed constrains we can't use any of script-driven languages for apps. By exec storage constrains we can't design all systems like a user-app.

Solution

1) Divide apps by two groups. First — "embedded" apps, its a "basic functionality" like a iButton emulator app, and this group embedded into firmware. Second group — real "user" app. It limited in, says, 100k of ram+rom (i think 28k of ram for core is enough), because it will be loaded and executed in ram. 2) Load and exec only one user app at a time. Sounds bad, but I don't see any real cases for running two user applications at the same time. It free us of dynamic ram manager and ram defragmentation problem. 3) Load user apps from .elf with dynamic linking on load, so we get more flexibility in core api. Also we can get more ram in future, so in theory loading two and more user apps is not a problem (besides defragmentation).

My role

I can design and implement dynamic linking because I have a lot of experience in this area.

yiiii777 commented 4 years ago

At least, we need some "resident" apps, as drivers for screen, battery management, keyboard input. I expect many community versions of that modules, so we need make API and split it from core. We must keep size of core as small, as possible. This lowering count of core hacks and unofficial binary firmwares.

Also, I already think about sharing peripherals: splitting screen and splitting BLE for 2 or even more apps.

glitchcore commented 4 years ago

I already think about sharing peripherals

When I designed FURI I planned use it for including shared access to peripherial.

For example, you can place timer object pointer or GPIO to FURI record and then you have flexible scenario how to make shared access:

You also can subscribe to state change and listen when peripheral object block and freed.

pavel-demin commented 4 years ago

I also think that having loadable user applications would improve the overall user experience.

Tock OS already supports dynamically loading applications and it was listed in #17 as one of possible OS for Flipper Zero. Some information on how it is implemented in Tock OS can be found at the following links: https://github.com/tock/tock/blob/master/doc/Compilation.md#position-independent-code https://www.tockos.org/blog/2016/dynamic-loading https://reviews.llvm.org/D23195

So it could be an additional argument for Tock OS.

pavel-demin commented 4 years ago

A few more comments.

we have only 128k ram

At the moment, it looks like Flipper Zero will use STM32WB55. We will therefore have a little more SRAM for applications (192k).

It limited in, says, 100k of ram+rom (i think 28k of ram for core is enough), because it will be loaded and executed in ram.

With STM32WB55, we could have something like the following:

Load user apps from .elf with dynamic linking on load, so we get more flexibility in core api.

I think dynamic linking is not strictly necessary if we are using some simplifications. For example, user applications can be copied from micro SD card to fixed address in SRAM, then run from SRAM. That way, we can just copy a .bin file to, say, 0x20010000, and then call a function that points to that address.

yiiii777 commented 4 years ago

and then call a function that points to that address.

Change location of interrupt table

pavel-demin commented 4 years ago

Change location of interrupt table

Maybe. It depends on whether user applications should handle interrupts or not.

If the core system provides a very high level library (like for example Arduino or Linux), then user applications will only call the very high level functions provided by the core system and all interrupts will be handled by the core system.

In this case, the interrupt table entries would point to low-level core system functions and the interrupt table could remain at the initial address.

glitchcore commented 4 years ago

If the core system provides a very high level library (like for example Arduino or Linux), then user applications will only call the very high level functions provided by the core system and all interrupts will be handled by the core system.

+1

For example, user applications can be copied from micro SD card to fixed address in SRAM, then run from SRAM. That way, we can just copy a .bin file to, say, 0x20010000,

This concept not allows to run more than one app at once and we can get some problems if we will want to change memory layout.

reendael commented 4 years ago

This might have been suggested / dismissed already, but one possible solution for insufficient storage would be using external memory (RAM or ROM) with a flexible memory controller (aka FMC). Unfortunately, it seems like this peripheral is not available in STM32WB55 (but is available in L476). If WB55 as the final MCU is not locked, this could be an option.

Basically, it allows extending the MCU memory space and address external memory as if it is internal.

glitchcore commented 4 years ago

External RAM are so cool but Flipper never have it :(

glitchcore commented 4 years ago

We have massive discussion with @DrZlo13 some days ago and I have vision by current project state:

  1. For resident applications (which start on Flipper startup and never stops) we allocate memory for code and stack memory one after another (like stack).
  2. Remain amount of RAM (for example, we have 128k free memory after startup and start resident apps) divides by same blocks (for example, we have N=8 blocks of S=16k).
  3. Right now it is so hard to estimate how many applications will be runned on flipper and how much RAM they need for and I suggest do nothing with it, make prototype and get usage statistics, maybe we change N and S values above.
glitchcore commented 4 years ago

Also I want say about "library" applications — if you want to provide some "shared code":

  1. Every app has mainfest with dependency (other "library" app)
  2. Loader run this dependency library app before load main app
  3. Library app load as usual app, initialize/call constructors. Then create FURI records with pointer to its functions/resources
  4. Library app thread go to sleep state
  5. Main app starts, open library records and call library functions/use resources.
DrZlo13 commented 4 years ago

We researched mmu-less os development (have analyzed tock os, thread x, classic mac os, palm os) and get some conclusions:

With all of the above, we have the following core mechanic:

  1. On loading, core will init peripheral drivers and will start daemons, (user application also can be a daemon). Difference between daemon and application — daemon loaded to main os heap and cannot be unloaded (but has ability to be stopped an started).
  2. After that, OS analyze heap consumption and allocate memory block in heap (subheap or appheap) for user applications and have ability to show appheap distribution on display.
  3. User application will be loaded to appheap (by dynamic linking between core and app), and after that OS start a new thread with stack size from app manifest (and that stack also holded in appheap memory).
  4. If appheap dont have sufficient memory — core ask user to unload some of loaded apps, and show on display which application takes up space and where (so user is a our memory defragmentation algorytm :) ).
  5. On app unloading firstly we stop application (frees up all his resources, FURI manages this), and after that we can unload app and his stack from appheap.
  6. User application memory "protected" by double word memory barriers (pattern in memory before application memory, between app stack and app memory and after application stack [| APP_CODE | APP_STACK |]). Those barriers simply will be checked time from time for corruption.

I would like to draw your attention to the fact that in this solution it is not necessary to have block division of the appheap, everything is completely solved by ordinary memory allocation.

To understand how to organize applications api, I started work in the wip-app-loader branch, where I try to implement drivers, protocols and applications. I have dynamic applications linking in another project, but at this moment this doesn't used, since first of all it would be nice to have an understanding of the general work with FURI. To get understanding, for example, I, now implemented the protocol for FLIPPER CLI #93 via FURI api (which also provided me with more convenient work with application files on a flash card), and in the near future I plan to start rewriting the application loader and implementing a linker.

glitchcore commented 4 years ago

After long discussion we decide:

Internal flash stored apps

External apps

Also we have no good solutions for work with static and global variables and forbid using it. Maybe thread-local will be good solution.

DrZlo13 commented 4 years ago

Problem with statics seems solved. We can compile user app with flags "-msingle-pic-base -mpic-register = r9", so address of data/bss section will be obtained from register R9. Also core must be compiled with "-ffixed-r9" flag, so that compiler will not use that register.

Next steps is to prove solution. We need look how OS save fixed-register in stack (every user app has own R9 register value) and we need to make sure that manipulations with R9 will be used only with static variables.

skotopes commented 4 years ago

How about SDK for external apps?

When main firmware is built we already know address table for public API, we can provide headers, this table and compilation flags so application can be built without main firmware.

DrZlo13 commented 4 years ago

The solution to the problem of rebasing the .data section does not look so easy. single-pic-base and pic-register require compilation with the PIC flag, which leads to the implementation of the PIC linker. So far we have returned to the concept of a monolithic firmware kernel, but in the future this issue we will studied more fully.

glitchcore commented 4 years ago

will return to this task later

clasqui commented 2 years ago

Has this discussion been continued somewhere else, with the upcoming release 1.0.0? I read somwhere this is still an expected feature for the first stable release. Or is this still blocked? Can't find a newer issue here, neither a related post on the forum.

skotopes commented 2 years ago

@clasqui this feature will be a part of v1.0.0. We are currently working on external application and SDK. Please check our roadmap.

clasqui commented 2 years ago

@skotopes Nice, thank you for the clarification. Is there somewhere where I can follow the discussion on the implementation and the development of this feature? I mean, a post, a discord channel, an issue, a branch...

skotopes commented 2 years ago

Yes, we do Q&A sessions on discord and update roadmap from time to time.

skotopes commented 2 years ago

Also https://github.com/flipperdevices/flipper-questions-and-answers