Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User apps subsystem #73

Closed
DrZlo13 opened this issue Aug 30, 2020 · 22 comments
Closed

User apps subsystem #73

DrZlo13 opened this issue Aug 30, 2020 · 22 comments
Assignees

Comments

@DrZlo13
Copy link
Member

DrZlo13 commented Aug 30, 2020

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.

@DrZlo13 DrZlo13 changed the title User apps logic User apps subsystem Aug 30, 2020
@yiiii777
Copy link
Contributor

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
Copy link
Contributor

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:

  • open peripheral concurrently and take mutex every time you want to interact with it
  • use "mute" feature of FURI: when next app open the same periphery, access of previous app is blocked until next app ends
  • open peripheral exclusively and other app cannot open it

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

@pavel-demin
Copy link

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
Copy link

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:

  • core: 512k of FLASH for code + 64k of SRAM for data
  • apps: 128k of SRAM for both code and data

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
Copy link
Contributor

yiiii777 commented Aug 30, 2020

and then call a function that points to that address.

Change location of interrupt table

@pavel-demin
Copy link

pavel-demin commented Aug 30, 2020

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.

This was referenced Aug 31, 2020
@glitchcore
Copy link
Contributor

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
Copy link

reendael commented Aug 31, 2020

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
Copy link
Contributor

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

@glitchcore
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Member Author

DrZlo13 commented Sep 11, 2020

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

  • FURI will be updated to do only resource managment Change FURI #110
  • Part about loading-unloading apps will be fully rewrited
  • App memory defragmentation is a very expensive task, including for the normal operation of the application. We discard it.
  • Applications cannot do memory allocations, only static memory use. No malloc, no new, etc. App can implement heap managment in own memory, and use it, but core dont provide memalloc api, fragmentation of a core memory inacceptable.
  • Every application has a manifest with some parameters like a app name, maybe small description and the most important — max stack size. For app development stage we implement core mode where app can be loaded with max stack size, fully tested and it will be possible to find out real stack consumption.

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
Copy link
Contributor

After long discussion we decide:

Internal flash stored apps

  • .text and .rodata sections linked during uploading binary to flash. On flipper size uploader get binary size, allocate space in internal flash and return start address. Then PC link binary for that address and write it to flash.
  • during application load process system allocate space for application stack and run code from flash.

External apps

  • loader has filesystem-like interface to get app manifest and data
  • system allocate space for application, copy .text and .rodata sections and make reallocation and linking with core.
  • system place stack after code sections.

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
Copy link
Member Author

DrZlo13 commented Sep 17, 2020

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
Copy link
Member

skotopes commented Sep 22, 2020

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
Copy link
Member Author

DrZlo13 commented Oct 8, 2020

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
Copy link
Contributor

will return to this task later

@clasqui
Copy link

clasqui commented Aug 11, 2022

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
Copy link
Member

@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
Copy link

clasqui commented Aug 11, 2022

@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
Copy link
Member

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

@skotopes
Copy link
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants