Worlds Simplest Bootloader :: Bare Metal Programming Series 4

แชร์
ฝัง
  • เผยแพร่เมื่อ 15 เม.ย. 2023
  • In this episode of the bare metal programming series, we're taking our first steps into building a fully fledged bootloader! To do this, we need to split the application in two, build some supporting scripts, learn how to use linker scripts, and understand some of the internal CPU registers for relocating the interrupt vector table at runtime.
    =[ 🔗 Links 🔗 ]=
    🎥 Series Playlist: • Blinky To Bootloader: ...
    🗣 Discord: / discord
    ⭐️ Patreon: / lowleveljavascript
    💻 Github Repo: github.com/lowbyteproductions...

ความคิดเห็น • 40

  • @stevezhou184
    @stevezhou184 2 หลายเดือนก่อน +2

    like your video so much. bootloader is flying everywhere, but nobody explains exactly what "bootloader" is. This is problem of software, everyone coins their own concept, then use the popular NAME to name it. like your clearness

  • @gcm4312
    @gcm4312 ปีที่แล้ว

    Amazing content as usual. Thank you for sharing.

  • @CuriousCyclist
    @CuriousCyclist 4 หลายเดือนก่อน

    Thank you for taking the time to make this video. Really good educational content. 👍

  • @ivsuk
    @ivsuk 4 หลายเดือนก่อน +2

    Thank you very much. Very clear and articulate explanation. My windows toolchain with stlink debug on WSL was a bit tricky but got there in the end. Anyone struggling: multiarch-gdb symlinked to arm + usbipd.

  • @alexyoung6418
    @alexyoung6418 ปีที่แล้ว +5

    I like how you linked two otherwise separated projects together to paint a complete picture of how they are allocated in the memory and how they work together, or at least not against each other. It's worth pointing out that some applications also jump back to the bootloader to trigger the firmware update feature. Assuming the worst scenario where main application hadn't done a good cleanup by resetting the interrupt vector offset register, it's recommended to force reset that exact register in the very beginning of the bootloader execution.

    • @LowByteProductions
      @LowByteProductions  ปีที่แล้ว +1

      Great point, the bootloader should also explicitly set the vector table offset.

  • @anshulmaurya6913
    @anshulmaurya6913 4 หลายเดือนก่อน

    It's been done really really nicely - Thank You

  • @stevekoehn1675
    @stevekoehn1675 6 หลายเดือนก่อน +1

    Your thinking is so clear, your explanations so clear and logical I Get It! (I'm older and learning on my own so I need all the help I can get, ha)

  • @user-ou8ji4ll5k
    @user-ou8ji4ll5k 6 หลายเดือนก่อน +1

    Again, I realy like your project! Some improvement-suggestions: to really understand the content of the jump_to_main function (better name would be jump_to_app_main), it is essential that the listener understand that the linker will do all the main work for us. The address of the jump_fn which we are digging out of the addresses with the pointer joggling is correct BECAUSE the linker puts it there (will/has put it there) with the linker script of the app.

  • @SumitAdep
    @SumitAdep 14 วันที่ผ่านมา

    well explained

  • @nhanNguyen-wo8fy
    @nhanNguyen-wo8fy หลายเดือนก่อน

    10:30 flash memory
    15:50 fix make file
    17:10 what bootloader do
    50:40 main
    59:30 vector table offset register

  • @AbidAli-mj8cu
    @AbidAli-mj8cu 2 หลายเดือนก่อน

    At @46:51, the first 4 bytes represents Main stack pointer, which is end of the RAM (from where stack starts), 0x20000000U + 0x18000U (96KB) = 0x20018000U

  • @john999
    @john999 ปีที่แล้ว +1

    Thank you very much for your tutorial. It is so kind of you, to give us this free and easy to understand introduction into bootloaders.
    When I started to experiment with bootloaders, I was overwhelmed by the whole documentation of the chip. And of course it did not work, so I was thrown off, not knowing why it failed.
    With this start-to-finish mini-demo it is a lot easier to get going and building from that on forward.
    Would be nice if you could mention some ways of including your handmade changes of the linker script to IDEs (e.g. Eclipse). Like, where you can use the settings of the project to change the memory layout or add sections.
    Maybe this helps other to include your bootloader to their current project w/o too much editing of linker files that are handled by the autogen of the IDE.

    • @LowByteProductions
      @LowByteProductions  ปีที่แล้ว

      Thank you John. Indeed - I'll point out whenever I'm making changes to linkerscripts etc, and what those changes imply.
      As for playing well with other IDEs, I'm not sure how much help I can be 😄 I try to avoid eclipse wherever I can! I am familiar with STM32CubeIDE however, and how it can be configured to use a makefile build, which gives a lot more control. There are still autogenerated files to deal with, but typically the linkerscript is not one of the files which is not regenerated across builds. I'll add it to the list of future topics.

  • @LaSDetta
    @LaSDetta ปีที่แล้ว +1

    Really nice video! I was wondering if you could use the FILL command in the linker script to ensure that the bootloader binary is always 0x8000 bytes?

    • @LowByteProductions
      @LowByteProductions  ปีที่แล้ว +3

      Thanks Håkan! Yes this is definitely possible, but I think you'd need to change the semantics a little. To use fill, I think you'd need to define a memory region for the bootloader.
      Not a bad way of doing it at all, bit there is one advantage to the current approach that I'm not sure how to replicate using fill: its easy to wrap the bootloader.S code, and the vector_setup() function in #ifdefs, and pass a flad to the compiler like -DINCLUDE_BOOTLOADER. This way, you can conditionally include the bootloader blob in the application, or choose to omit it. Without the image, the main application would end up at 0x08000000, and no VTOR offset would be required.
      Not a deal breaker by any means, but something to consider.

  • @user-sc8wl8oo7n
    @user-sc8wl8oo7n 5 หลายเดือนก่อน

    Great content! It has helped me learn so much about bootloaders. Just wondering why do you not set the Stack Pointer to the APP before jumping to APP. I believe we need to do so to utilize all the RAM space? I also realized the reset handler by Libopencm3 doesnt include a Stack Pointer intialisation.

  • @angryman9333
    @angryman9333 ปีที่แล้ว

    you are a genius.

  • @johnhansson8646
    @johnhansson8646 3 หลายเดือนก่อน +1

    Regarding the padding of the bootloader to 32kb, couldn’t you configure that in your linker script, so that it is done for you when linking?

  • @kilwo
    @kilwo ปีที่แล้ว +4

    Just wondering why you do a C++ function call in the bootloader? If you did an assembly jump (branch I think in ARM) then you could reset the vector table offset address before the jump and the main app wouldn't need to know anything about the size of the bootloader. The main app would just work as if it was the only thing in memory and you could change the bootloader size without needing to update the main app.

    • @LowByteProductions
      @LowByteProductions  ปีที่แล้ว +1

      First of all, thanks for taking the time to comment!
      I'm not sure what you mean by a C++ function call. Where possible I'm trying to avoid inline assembly - the compiler is usually (like 99% of the time) smart enough to do the right thing when expressed in C. For example, the process for getting the address of the reset vector, casting it to a void (*)(void) function pointer and calling it, results in basically the assembly you describe; figuring out an offset, getting the reset vector address indirectly, and using a bx (branch) instruction to jump to it.
      Your point about setting the vector table offset in the bootloader is a really good suggestion - I guess that does make a lot more sense!

    • @kilwo
      @kilwo ปีที่แล้ว

      @@LowByteProductions Thanks for taking the time to make the videos! I really enjoy low level stuff. Sorry, my thoughts were not 100% clear. The C++ function call I was referring to was just the fact that you cast the pointer into a function, and then call that. The reason I was thinking this should be an unconditional jump, or equivalent, was because a function call effects the stack, where a jump doesn’t. At least in the processors I have used. Once you have changed the vector offset register, you don’t want to do a call that will push onto the stack, as it will never be returned and will waste memory. Keep up the great videos.

    • @LowByteProductions
      @LowByteProductions  ปีที่แล้ว +4

      Ah OK I see what you mean. The C++ threw me off there - I don't think there is a relation to C++ here.
      You're right that you'll end up with an unecessary stack frame, but one of the first things that happens in the reset vector for both the bootloader and the main application is setting the stack pointer to an explicit value. That has the effect of essentially removing tany stack frames that would have been there previously, and so doing a cast/call isn't a problem. And even so, no arguments or other information is pushed to the stack (at least the way this is currently compiled), as the cast is a 0 argument function. Even if the blx instruction were used (branch and link), the return address of the function is still only placed into a cpu register, not the stack.
      I really enjoy digging into the details of this kind of thing, so the discussion is very much appreciated.

    • @ZeroPlayerGame
      @ZeroPlayerGame 6 หลายเดือนก่อน

      @@LowByteProductions I actually went and checked, and I'm sorry to say, but libopencm3's reset handler does none such. All it does is copy the .data section into RAM, and run global constructors.

  • @VectorNodes
    @VectorNodes 4 หลายเดือนก่อน

    37:36 it stands for “bull shit stuff”

  • @MohammadShikha
    @MohammadShikha 10 หลายเดือนก่อน

    Hey, thanks for the video. I'm currently following along with the STM32F767ZI and the vector offset macro SCB_VTOR seems to require the final vector table address (0x08008000U) rather than an offsetting value (0x8000U) in order for interrupts (like SYS_TICK) to work. I'm not sure as to why this is the case, am I missing something obvious?

    • @LowByteProductions
      @LowByteProductions  10 หลายเดือนก่อน +3

      That shouldn't be the case - the VTOR is literally the "vector table offset register", i.e. it always specifies an offset.
      When you are moving between the bootloader and main application, you need to disable any interrupts that could occur before you're able to change the offset value.
      Just to check: you are using libopencm3 right? Because there is only one macro definition there, and it just points to a memory mapped io register.
      Edit: just checked the reference manual for cortex-m7, and indeed, the vtor specifies and offset from 0x00000000 - so yes it is effectively an absolute address. You'll have to adjust the M4 based code to account for this. I'm sure they're be other subtle detail changes too!

  • @gionag
    @gionag 4 หลายเดือนก่อน +1

    in linker, why don't use FILL to pad the bootloader ?

  • @saturdaysequalsyouth
    @saturdaysequalsyouth ปีที่แล้ว +1

    .bss is something like "block started by segment". It's probably an outdated term that was repurposed which is why it doesn't make sense.

  • @stati5tik
    @stati5tik 4 หลายเดือนก่อน

    30:30 who set the address to the reset_vector in reset_vector_entry? is this hardware specific?

    • @LowByteProductions
      @LowByteProductions  4 หลายเดือนก่อน +1

      It's per-platform, indeed. On the STM32 Cortex-M4 chips it tends to be at 0x80000000, but another vendor might map the beginning of flash elsewhere

  • @viniciusgabriellinden4724
    @viniciusgabriellinden4724 8 หลายเดือนก่อน +1

    why not something like `void_fn jump = (void_fn) (MAIN_APP_START_ADDRESS + sizeof(int)); return jump();`? more concise and does not depend on the bit width...

    • @LowByteProductions
      @LowByteProductions  8 หลายเดือนก่อน +1

      You're absolutely right, and I have a feeling you'll enjoy the next video in the series 😁

  • @dty999
    @dty999 ปีที่แล้ว +2

    I'm not a C programmer, but at 25:10, why don't you let the compiler do the pointer math for you, and use reset_vector[1] instead of adding 4 to the address?

    • @dty999
      @dty999 ปีที่แล้ว

      Or, strictly, I guess you should redefine reset_vector as vector_table, then vector_table[1] makes more sense!

    • @LowByteProductions
      @LowByteProductions  ปีที่แล้ว

      Definitely possible, and perhaps the better way to do it! Libopencm3 actually defines a structure for the vector table, so casting the address to a pointer of that structure, and referring to the reset vector by name would another option.

    • @dty999
      @dty999 ปีที่แล้ว

      Or better yet, just have something like void_fn* interrupt_table = (void_fn*)MAIN_APP_START_ADDRESS; and then you can just go interrupt_table[1](); to call it.

  • @ulysses_grant
    @ulysses_grant 2 หลายเดือนก่อน

    I am a simple man.
    I see a random channel with a video of a guy dumping game boy cartridges and I subscribe.

    • @LowByteProductions
      @LowByteProductions  2 หลายเดือนก่อน +1

      Me too buddy, me too.

    • @ulysses_grant
      @ulysses_grant 2 หลายเดือนก่อน

      ​@@LowByteProductionsJust learned about your channel. Sent the link to my brother and he just "LowByte?! This guy is a monster!" lol.
      Definitely subscribed!