├── .github └── workflows │ └── action.yml ├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── build.zig ├── config.json ├── docs └── figures │ └── arch.png ├── src ├── app_main.zig ├── chip │ ├── cortex-m.zig │ ├── link.lds │ ├── link_app.lds │ ├── mmio.zig │ ├── start.s │ ├── stm32f4 │ │ ├── clock.zig │ │ ├── flash.zig │ │ ├── pin.zig │ │ └── regs.zig │ ├── stm32h7 │ │ ├── clock.zig │ │ ├── flash.zig │ │ ├── pin.zig │ │ └── regs.zig │ └── stm32l4 │ │ ├── clock.zig │ │ ├── flash.zig │ │ ├── pin.zig │ │ └── regs.zig ├── default_config.zig ├── hal │ ├── clock.zig │ ├── flash.zig │ ├── hal.zig │ ├── pin.zig │ ├── spi.zig │ └── uart.zig ├── main.zig └── platform │ ├── fal │ ├── fal.zig │ ├── flash.zig │ └── partition.zig │ ├── ota │ └── ota.zig │ ├── sfud │ ├── sfud.zig │ ├── sfud_flash.zig │ └── sfud_sfdp.zig │ ├── sys.zig │ └── sys │ └── config.zig └── zboot.zig /.github/workflows/action.yml: -------------------------------------------------------------------------------- 1 | name: Build Check 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the master branch 5 | on: 6 | # Runs at 16:00 UTC (BeiJing 00:00) on the 1st of every month 7 | schedule: 8 | - cron: '0 16 1 * *' 9 | push: 10 | paths-ignore: 11 | - docs/** 12 | - '**/README.md' 13 | pull_request: 14 | paths-ignore: 15 | - docs/** 16 | - '**/README.md' 17 | 18 | permissions: 19 | contents: read # to fetch code (actions/checkout) 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | name: Build Check 25 | steps: 26 | - uses: actions/checkout@v3 27 | - uses: mlugg/setup-zig@v1.2.1 28 | - name: Build 29 | shell: bash 30 | run: zig build 31 | - name: Build for Linux 32 | if: ${{ success()}} 33 | shell: bash 34 | run: | 35 | zig build-exe ./zboot.zig -target x86_64-linux-gnu -O ReleaseSmall 36 | cp ./zboot ./zig-out/zboot-linux 37 | - name: Build for Windows 38 | if: ${{ success()}} 39 | shell: bash 40 | run: | 41 | zig build-exe ./zboot.zig -target x86_64-windows-gnu -O ReleaseSmall 42 | cp ./zboot.exe ./zig-out/zboot-windows.exe 43 | - name: Upload artifact 44 | if: ${{ success()}} 45 | uses: actions/upload-artifact@v4 46 | with: 47 | # Upload entire repository 48 | path: 'zig-out' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /zig-out 2 | /.zig-cache 3 | /*.o 4 | /*.obj 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "f407", 9 | "cwd": "${workspaceFolder}", 10 | "executable": "zig-out\\bin\\stm32-zboot.elf", 11 | "request": "launch", 12 | "type": "cortex-debug", 13 | "runToEntryPoint": "Reset_handler", 14 | "targetId": "stm32f407ve", 15 | "cmsisPack": "Keil.STM32F4xx_DFP.2.14.0-small.pack", 16 | "servertype": "pyocd", 17 | "armToolchainPath": "/opt/gcc-arm-none-eabi-10-2020-q4-major/bin", 18 | "gdbPath": "/opt/gcc-arm-none-eabi-10-2020-q4-major/bin/arm-none-eabi-gdb", 19 | }, 20 | { 21 | "name": "f407-app", 22 | "cwd": "${workspaceFolder}", 23 | "executable": "zig-out\\bin\\stm32-app.elf", 24 | "request": "launch", 25 | "type": "cortex-debug", 26 | "runToEntryPoint": "Reset_handler", 27 | "targetId": "stm32f407ve", 28 | "cmsisPack": "Keil.STM32F4xx_DFP.2.14.0-small.pack", 29 | "servertype": "pyocd", 30 | "armToolchainPath": "/opt/gcc-arm-none-eabi-10-2020-q4-major/bin", 31 | "gdbPath": "/opt/gcc-arm-none-eabi-10-2020-q4-major/bin/arm-none-eabi-gdb", 32 | }, 33 | ] 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stm32-zboot 2 | Universal stm32 boot written using zig,Compatible with [RT-Thread stm32-Bootloader](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/application-note/system/rtboot/an0028-rtboot?id=%e7%ae%80%e4%bb%8b). 3 | 4 | ![Arch](docs/figures/arch.png) 5 | 6 | ## Roadmap 7 | 8 | ### M0: F4 series basic boot (rom < 8k) 9 | 10 | - [x] Serial port log output 11 | - [x] jump to app 12 | - [x] chip-flash manage 13 | - [x] flash partition abstraction layer(Compatible with FAL) 14 | - [x] The whole package upgrade 15 | 16 | ### M1: Normal boot (rom < 16k) 17 | 18 | - [x] Support L4 series 19 | - [x] Firmware crc check 20 | - [x] Firmware decompression(fastlz) 21 | - [x] Support spi nor flash 22 | - [x] Basic zboot Tool 23 | 24 | ### M2: More series boot (rom < 32k) 25 | 26 | - [x] Support H7 series (no xip) 27 | - [ ] Support H7 series (XIP Flash) 28 | - [ ] Support differential upgrade 29 | - [x] zboot Tool 30 | 31 | ## How to build 32 | 33 | ``` 34 | zig build 35 | ``` 36 | > Depends on zig-0.13.0 37 | 38 | ## How to use zboot 39 | 40 | ### Generate stm32-zboot.bin 41 | 42 | 1. Run `zboot boot` to generate stm32-zboot.bin and config.json in the current directory. 43 | 2. Edit `config.json` to adapt to your hardware. 44 | 3. Run `zboot boot` again, and the stm32-zboot.bin will be generated by config.json. 45 | 46 | ### Make app project 47 | 48 | 1. Prepare a normal project for your hardware. 49 | 2. Adjust the starting address of your project's ROM or linker script 50 | 3. Adjust the interrupt vector address. 51 | 4. Compile and download the project. After reset, zboot will automatically load this application. 52 | 53 | ### Make upgrade file 54 | 55 | 1. Configure the project to generate bin file. 56 | 2. Run `zboot rbl xxx.bin Vx.x` to package the bin file as an upgrade file xxx.bin.rbl. 57 | 58 | ### Upgrade app 59 | 60 | 1. Transfer the xxx.bin.rbl file to swap partition in config.json (Support on-chip flash and SPIflash). 61 | 2. Reset the system, then zboot will automatically complete the application upgrade. 62 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const stm32f4 = std.Target.Query{ 4 | .cpu_arch = .thumb, 5 | .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m3 }, 6 | .os_tag = .freestanding, 7 | .abi = .eabi, 8 | }; 9 | 10 | const examples = [_]Example{ 11 | .{ .name = "stm32-app", .target = stm32f4, .root_file = "src/app_main.zig", .linker_script = "src/chip/link_app.lds" }, 12 | .{ .name = "stm32-zboot", .target = stm32f4, .root_file = "src/main.zig", .linker_script = "src/chip/link.lds" }, 13 | }; 14 | 15 | pub fn build(b: *std.Build) void { 16 | const optimize = .ReleaseSmall; 17 | 18 | const zboot_tool = b.addExecutable(.{ 19 | .name = "zboot", 20 | .optimize = .ReleaseSmall, 21 | .target = b.graph.host, 22 | .root_source_file = b.path("zboot.zig"), 23 | }); 24 | 25 | const timestamp = std.time.timestamp(); 26 | const options = b.addOptions(); 27 | options.addOption(i64, "time_stamp", timestamp); 28 | 29 | for (examples) |example| { 30 | const elf = b.addExecutable(.{ 31 | .name = b.fmt("{s}{s}", .{ example.name, ".elf" }), 32 | .root_source_file = b.path(example.root_file), 33 | .target = b.resolveTargetQuery(stm32f4), 34 | .optimize = optimize, 35 | .strip = false, // do not strip debug symbols 36 | }); 37 | 38 | elf.setLinkerScript(b.path(example.linker_script)); 39 | elf.addCSourceFile(.{ .file = b.path("src/chip/start.s"), .flags = &.{} }); 40 | elf.root_module.addOptions("timestamp", options); 41 | 42 | // reduce the size of the binary 43 | elf.link_function_sections = true; 44 | elf.link_data_sections = true; 45 | elf.link_gc_sections = true; 46 | // elf.want_lto = true; 47 | 48 | // Copy the elf to the output directory. 49 | const copy_elf = b.addInstallArtifact(elf, .{}); 50 | b.default_step.dependOn(©_elf.step); 51 | 52 | // Convert the hex from the elf 53 | const hex = b.addObjCopy(elf.getEmittedBin(), .{ .format = .hex }); 54 | hex.step.dependOn(&elf.step); 55 | // Copy the hex to the output directory 56 | const copy_hex = b.addInstallBinFile( 57 | hex.getOutput(), 58 | b.fmt("{s}{s}", .{ example.name, ".hex" }), 59 | ); 60 | b.default_step.dependOn(©_hex.step); 61 | 62 | // Convert the bin form the elf 63 | const bin = b.addObjCopy(elf.getEmittedBin(), .{ .format = .bin }); 64 | bin.step.dependOn(&elf.step); 65 | 66 | // Copy the bin to the output directory 67 | const copy_bin = b.addInstallBinFile( 68 | bin.getOutput(), 69 | b.fmt("{s}{s}", .{ example.name, ".bin" }), 70 | ); 71 | b.default_step.dependOn(©_bin.step); 72 | zboot_tool.step.dependOn(©_bin.step); 73 | } 74 | b.installArtifact(zboot_tool); 75 | } 76 | 77 | const Example = struct { 78 | target: std.Target.Query, 79 | name: []const u8, 80 | root_file: []const u8, 81 | linker_script: []const u8, 82 | }; 83 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "uart": { 3 | "enable": 1, 4 | "tx": "PA9" 5 | }, 6 | "spiflash": { 7 | "enable": 0, 8 | "cs": "PB12", 9 | "sck": "PB13", 10 | "mosi": "PC3", 11 | "miso": "PC2" 12 | }, 13 | "partition_table": { 14 | "patition": [ 15 | { 16 | "name": "boot", 17 | "flash_name": "onchip", 18 | "offset": 0, 19 | "len": 32 20 | }, 21 | { 22 | "name": "app", 23 | "flash_name": "onchip", 24 | "offset": 128, 25 | "len": 128 26 | }, 27 | { 28 | "name": "swap", 29 | "flash_name": "onchip", 30 | "offset": 256, 31 | "len": 128 32 | } 33 | ] 34 | } 35 | } -------------------------------------------------------------------------------- /docs/figures/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppy-rtos/stm32-zboot/994d1f7ae917f1913118f2992da8488ab164e1cf/docs/figures/arch.png -------------------------------------------------------------------------------- /src/app_main.zig: -------------------------------------------------------------------------------- 1 | const hal = @import("hal/hal.zig"); 2 | const sys = @import("platform/sys.zig"); 3 | 4 | export fn main() noreturn { 5 | hal.init(); 6 | sys.zconfig.probe_extconfig(sys.get_rom_end()); 7 | const zboot_config = sys.zconfig.get_config(); 8 | if (zboot_config.uart.enable) { 9 | sys.init_debug(zboot_config.uart.tx[0..]) catch {}; 10 | } 11 | sys.debug.print("Hello Zboot App v1!\r\n", .{}) catch {}; 12 | 13 | while (true) {} 14 | } 15 | -------------------------------------------------------------------------------- /src/chip/cortex-m.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mmio = @import("mmio.zig"); 3 | 4 | pub const regs = struct { 5 | // Interrupt Control and State Register 6 | pub const ICSR: *volatile mmio.Mmio(packed struct { 7 | VECTACTIVE: u9, 8 | reserved0: u2, 9 | RETTOBASE: u1, 10 | VECTPENDING: u9, 11 | reserved1: u1, 12 | ISRPENDING: u1, 13 | ISRPREEMPT: u1, 14 | reserved2: u1, 15 | PENDSTCLR: u1, 16 | PENDSTSET: u1, 17 | PENDSVCLR: u1, 18 | PENDSVSET: u1, 19 | reserved3: u2, 20 | NMIPENDSET: u1, 21 | }) = @ptrFromInt(0xE000ED04); 22 | }; 23 | 24 | pub fn executing_isr() bool { 25 | return regs.ICSR.read().VECTACTIVE != 0; 26 | } 27 | 28 | pub fn enable_interrupts() void { 29 | asm volatile ("cpsie i"); 30 | } 31 | 32 | pub fn disable_interrupts() void { 33 | asm volatile ("cpsid i"); 34 | } 35 | 36 | pub fn enable_fault_irq() void { 37 | asm volatile ("cpsie f"); 38 | } 39 | pub fn disable_fault_irq() void { 40 | asm volatile ("cpsid f"); 41 | } 42 | 43 | pub fn nop() void { 44 | asm volatile ("nop"); 45 | } 46 | pub fn wfi() void { 47 | asm volatile ("wfi"); 48 | } 49 | pub fn wfe() void { 50 | asm volatile ("wfe"); 51 | } 52 | pub fn sev() void { 53 | asm volatile ("sev"); 54 | } 55 | pub fn isb() void { 56 | asm volatile ("isb"); 57 | } 58 | pub fn dsb() void { 59 | asm volatile ("dsb"); 60 | } 61 | pub fn dmb() void { 62 | asm volatile ("dmb"); 63 | } 64 | pub fn clrex() void { 65 | asm volatile ("clrex"); 66 | } 67 | 68 | fn is_valid_field(field_name: []const u8) bool { 69 | return !std.mem.startsWith(u8, field_name, "reserved") and 70 | !std.mem.eql(u8, field_name, "initial_stack_pointer") and 71 | !std.mem.eql(u8, field_name, "reset"); 72 | } 73 | 74 | pub const peripherals = struct { 75 | /// System Tick Timer 76 | pub const SysTick = @as(*volatile types.peripherals.SysTick, @ptrFromInt(0xe000e010)); 77 | 78 | /// System Control Space 79 | pub const NVIC = @compileError("TODO"); // @ptrFromInt(*volatile types.peripherals.NVIC, 0xe000e100); 80 | 81 | /// System Control Block 82 | pub const SCB = @as(*volatile types.peripherals.SCB, @ptrFromInt(0xe000ed00)); 83 | }; 84 | 85 | pub const types = struct { 86 | pub const peripherals = struct { 87 | /// System Tick Timer 88 | pub const SysTick = extern struct { 89 | /// SysTick Control and Status Register 90 | CTRL: mmio.Mmio(packed struct(u32) { 91 | ENABLE: u1, 92 | TICKINT: u1, 93 | CLKSOURCE: u1, 94 | reserved16: u13, 95 | COUNTFLAG: u1, 96 | padding: u15, 97 | }), 98 | /// SysTick Reload Value Register 99 | LOAD: mmio.Mmio(packed struct(u32) { 100 | RELOAD: u24, 101 | padding: u8, 102 | }), 103 | /// SysTick Current Value Register 104 | VAL: mmio.Mmio(packed struct(u32) { 105 | CURRENT: u24, 106 | padding: u8, 107 | }), 108 | /// SysTick Calibration Register 109 | CALIB: mmio.Mmio(packed struct(u32) { 110 | TENMS: u24, 111 | reserved30: u6, 112 | SKEW: u1, 113 | NOREF: u1, 114 | }), 115 | }; 116 | 117 | /// System Control Block 118 | pub const SCB = extern struct { 119 | CPUID: mmio.Mmio(packed struct(u32) { 120 | REVISION: u4, 121 | PARTNO: u12, 122 | ARCHITECTURE: u4, 123 | VARIANT: u4, 124 | IMPLEMENTER: u8, 125 | }), 126 | /// Interrupt Control and State Register 127 | ICSR: mmio.Mmio(packed struct(u32) { 128 | VECTACTIVE: u9, 129 | reserved12: u3, 130 | VECTPENDING: u9, 131 | reserved22: u1, 132 | ISRPENDING: u1, 133 | ISRPREEMPT: u1, 134 | reserved25: u1, 135 | PENDSTCLR: u1, 136 | PENDSTSET: u1, 137 | PENDSVCLR: u1, 138 | PENDSVSET: u1, 139 | reserved31: u2, 140 | NMIPENDSET: u1, 141 | }), 142 | /// Vector Table Offset Register 143 | VTOR: mmio.Mmio(packed struct(u32) { 144 | reserved8: u8, 145 | TBLOFF: u24, 146 | }), 147 | /// Application Interrupt and Reset Control Register 148 | AIRCR: mmio.Mmio(packed struct(u32) { 149 | reserved1: u1, 150 | VECTCLRACTIVE: u1, 151 | SYSRESETREQ: u1, 152 | reserved15: u12, 153 | ENDIANESS: u1, 154 | VECTKEY: u16, 155 | }), 156 | /// System Control Register 157 | SCR: mmio.Mmio(packed struct(u32) { 158 | reserved1: u1, 159 | SLEEPONEXIT: u1, 160 | SLEEPDEEP: u1, 161 | reserved4: u1, 162 | SEVONPEND: u1, 163 | padding: u27, 164 | }), 165 | /// Configuration Control Register 166 | CCR: mmio.Mmio(packed struct(u32) { 167 | reserved3: u3, 168 | UNALIGN_TRP: u1, 169 | reserved9: u5, 170 | STKALIGN: u1, 171 | padding: u22, 172 | }), 173 | reserved28: [4]u8, 174 | /// System Handlers Priority Registers. [0] is RESERVED 175 | SHP: u32, 176 | reserved36: [4]u8, 177 | /// System Handler Control and State Register 178 | SHCSR: mmio.Mmio(packed struct(u32) { 179 | reserved15: u15, 180 | SVCALLPENDED: u1, 181 | padding: u16, 182 | }), 183 | }; 184 | }; 185 | }; 186 | -------------------------------------------------------------------------------- /src/chip/link.lds: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was auto-generated by microzig 3 | * 4 | * Target CPU: ARM Cortex-M4 5 | * Target Chip: STM32F407 6 | */ 7 | 8 | ENTRY(microzig_main); 9 | 10 | MEMORY 11 | { 12 | flash0 (rx!w) : ORIGIN = 0x08000000, LENGTH = 0x0020000 13 | ram0 (rw!x) : ORIGIN = 0x20000000, LENGTH = 0x00010000 14 | } 15 | 16 | SECTIONS 17 | { 18 | .text : 19 | { 20 | KEEP(*(microzig_flash_start)) 21 | *(.text*) 22 | } > flash0 23 | 24 | .ARM.exidx : { 25 | *(.ARM.exidx* .gnu.linkonce.armexidx.*) 26 | } >flash0 27 | 28 | .data : 29 | { 30 | microzig_data_start = .; 31 | *(.rodata*) 32 | *(.data*) 33 | microzig_data_end = .; 34 | KEEP(*(dconfig)) 35 | KEEP(*(fal)) 36 | } > ram0 AT> flash0 37 | 38 | .bss (NOLOAD) : 39 | { 40 | microzig_bss_start = .; 41 | *(.bss*) 42 | microzig_bss_end = .; 43 | . = ALIGN(4); 44 | microzig_stack_start = .; 45 | . = . + 0x8000; 46 | . = ALIGN(4); 47 | microzig_stack_end = .; 48 | } > ram0 49 | 50 | microzig_data_load_start = LOADADDR(.data); 51 | } 52 | -------------------------------------------------------------------------------- /src/chip/link_app.lds: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was auto-generated by microzig 3 | * 4 | * Target CPU: ARM Cortex-M4 5 | * Target Chip: STM32F407 6 | */ 7 | 8 | ENTRY(microzig_main); 9 | 10 | MEMORY 11 | { 12 | flash0 (rx!w) : ORIGIN = 0x08008000, LENGTH = 0x0020000 13 | ram0 (rw!x) : ORIGIN = 0x20000000, LENGTH = 0x00010000 14 | } 15 | 16 | SECTIONS 17 | { 18 | .text : 19 | { 20 | KEEP(*(microzig_flash_start)) 21 | *(.text*) 22 | } > flash0 23 | 24 | .ARM.exidx : { 25 | *(.ARM.exidx* .gnu.linkonce.armexidx.*) 26 | } >flash0 27 | 28 | .data : 29 | { 30 | microzig_data_start = .; 31 | *(.rodata*) 32 | *(.data*) 33 | microzig_data_end = .; 34 | KEEP(*(dconfig)) 35 | KEEP(*(fal)) 36 | } > ram0 AT> flash0 37 | 38 | .bss (NOLOAD) : 39 | { 40 | microzig_bss_start = .; 41 | *(.bss*) 42 | microzig_bss_end = .; 43 | . = ALIGN(4); 44 | microzig_stack_start = .; 45 | . = . + 0x8000; 46 | . = ALIGN(4); 47 | microzig_stack_end = .; 48 | } > ram0 49 | 50 | microzig_data_load_start = LOADADDR(.data); 51 | } 52 | -------------------------------------------------------------------------------- /src/chip/mmio.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const assert = std.debug.assert; 3 | 4 | pub fn Mmio(comptime PackedT: type) type { 5 | @setEvalBranchQuota(2_000); 6 | 7 | const size = @bitSizeOf(PackedT); 8 | if ((size % 8) != 0) 9 | @compileError("size must be divisible by 8!"); 10 | 11 | if (!std.math.isPowerOfTwo(size / 8)) 12 | @compileError("size must encode a power of two number of bytes!"); 13 | 14 | const IntT = std.meta.Int(.unsigned, size); 15 | 16 | if (@sizeOf(PackedT) != (size / 8)) 17 | @compileError(std.fmt.comptimePrint("IntT and PackedT must have the same size!, they are {} and {} bytes respectively", .{ size / 8, @sizeOf(PackedT) })); 18 | 19 | return extern struct { 20 | const Self = @This(); 21 | 22 | raw: IntT, 23 | 24 | pub const underlying_type = PackedT; 25 | 26 | pub inline fn read(addr: *volatile Self) PackedT { 27 | return @bitCast(addr.raw); 28 | } 29 | 30 | pub inline fn write(addr: *volatile Self, val: PackedT) void { 31 | comptime { 32 | assert(@bitSizeOf(PackedT) == @bitSizeOf(IntT)); 33 | } 34 | addr.write_raw(@bitCast(val)); 35 | } 36 | 37 | pub inline fn write_raw(addr: *volatile Self, val: IntT) void { 38 | addr.raw = val; 39 | } 40 | 41 | /// Set field `field_name` of this register to `value`. 42 | /// A one-field version of modify(), more helpful if `field_name` is comptime calculated. 43 | pub inline fn modify_one(addr: *volatile Self, comptime field_name: []const u8, value: anytype) void { 44 | var val = read(addr); 45 | @field(val, field_name) = value; 46 | write(addr, val); 47 | } 48 | 49 | /// For each `.Field = value` entry of `fields`: 50 | /// Set field `Field` of this register to `value`. 51 | pub inline fn modify(addr: *volatile Self, fields: anytype) void { 52 | var val = read(addr); 53 | inline for (@typeInfo(@TypeOf(fields)).@"struct".fields) |field| { 54 | @field(val, field.name) = @field(fields, field.name); 55 | } 56 | write(addr, val); 57 | } 58 | 59 | /// In field `field_name` of struct `val`, toggle (only) all bits that are set in `value`. 60 | inline fn toggle_field(val: anytype, comptime field_name: []const u8, value: anytype) void { 61 | const FieldType = @TypeOf(@field(val, field_name)); 62 | switch (@typeInfo(FieldType)) { 63 | .int => { 64 | @field(val, field_name) = @field(val, field_name) ^ value; 65 | }, 66 | .@"enum" => |enum_info| { 67 | // same as for the .Int case, but casting to and from the u... tag type U of the enum FieldType 68 | const U = enum_info.tag_type; 69 | @field(val, field_name) = 70 | @as(FieldType, @enumFromInt(@as(U, @intFromEnum(@field(val, field_name))) ^ 71 | @as(U, @intFromEnum(@as(FieldType, value))))); 72 | }, 73 | else => |T| { 74 | @compileError("unsupported register field type '" ++ @typeName(T) ++ "'"); 75 | }, 76 | } 77 | } 78 | 79 | /// In field `field_name` of this register, toggle (only) all bits that are set in `value`. 80 | /// A one-field version of toggle(), more helpful if `field_name` is comptime calculated. 81 | pub inline fn toggle_one(addr: *volatile Self, comptime field_name: []const u8, value: anytype) void { 82 | var val = read(addr); 83 | toggle_field(&val, field_name, value); 84 | write(addr, val); 85 | } 86 | 87 | /// For each `.Field = value` entry of `fields`: 88 | /// In field `F` of this register, toggle (only) all bits that are set in `value`. 89 | pub inline fn toggle(addr: *volatile Self, fields: anytype) void { 90 | var val = read(addr); 91 | inline for (@typeInfo(@TypeOf(fields)).@"struct".fields) |field| { 92 | toggle_field(&val, field.name, @field(fields, field.name)); 93 | } 94 | write(addr, val); 95 | } 96 | }; 97 | } -------------------------------------------------------------------------------- /src/chip/start.s: -------------------------------------------------------------------------------- 1 | /** 2 | ****************************************************************************** 3 | * @file startup_stm32f407xx.s 4 | * @author MCD Application Team 5 | * @brief STM32F407xx Devices vector table for GCC based toolchains. 6 | * This module performs: 7 | * - Set the initial SP 8 | * - Set the initial PC == _start, 9 | * - Set the vector table entries with the exceptions ISR address 10 | * - Branches to main in the C library (which eventually 11 | * calls main()). 12 | * After Reset the Cortex-M4 processor is in Thread mode, 13 | * priority is Privileged, and the Stack is set to Main. 14 | ****************************************************************************** 15 | * @attention 16 | * 17 | * Copyright (c) 2017 STMicroelectronics. 18 | * All rights reserved. 19 | * 20 | * This software is licensed under terms that can be found in the LICENSE file 21 | * in the root directory of this software component. 22 | * If no LICENSE file comes with this software, it is provided AS-IS. 23 | * 24 | ****************************************************************************** 25 | */ 26 | 27 | .syntax unified 28 | .cpu cortex-m4 29 | .fpu softvfp 30 | .thumb 31 | 32 | .global g_pfnVectors 33 | .global Default_Handler 34 | 35 | /** 36 | * @brief This is the code that gets called when the processor first 37 | * starts execution following a reset event. Only the absolutely 38 | * necessary set is performed, after which the application 39 | * supplied main() routine is called. 40 | * @param None 41 | * @retval : None 42 | */ 43 | 44 | .section .text._start 45 | .weak _start 46 | .type _start, %function 47 | _start: 48 | ldr sp, =microzig_stack_end /* set stack pointer */ 49 | 50 | /* Copy the data segment initializers from flash to SRAM */ 51 | ldr r0, =microzig_data_start 52 | ldr r1, =microzig_data_end 53 | ldr r2, =microzig_data_load_start 54 | movs r3, #0 55 | b LoopCopyDataInit 56 | 57 | CopyDataInit: 58 | ldr r4, [r2, r3] 59 | str r4, [r0, r3] 60 | adds r3, r3, #4 61 | 62 | LoopCopyDataInit: 63 | adds r4, r0, r3 64 | cmp r4, r1 65 | bcc CopyDataInit 66 | 67 | /* Zero fill the bss segment. */ 68 | ldr r2, =microzig_bss_start 69 | ldr r4, =microzig_bss_end 70 | movs r3, #0 71 | b LoopFillZerobss 72 | 73 | FillZerobss: 74 | str r3, [r2] 75 | adds r2, r2, #4 76 | 77 | LoopFillZerobss: 78 | cmp r2, r4 79 | bcc FillZerobss 80 | 81 | /* Call the application's entry point.*/ 82 | bl main 83 | bx lr 84 | .size _start, .-_start 85 | 86 | /** 87 | * @brief This is the code that gets called when the processor receives an 88 | * unexpected interrupt. This simply enters an infinite loop, preserving 89 | * the system state for examination by a debugger. 90 | * @param None 91 | * @retval None 92 | */ 93 | .section .text.Default_Handler,"ax",%progbits 94 | Default_Handler: 95 | Infinite_Loop: 96 | b Infinite_Loop 97 | .size Default_Handler, .-Default_Handler 98 | /****************************************************************************** 99 | * 100 | * The minimal vector table for a Cortex M3. Note that the proper constructs 101 | * must be placed on this to ensure that it ends up at physical address 102 | * 0x0000.0000. 103 | * 104 | *******************************************************************************/ 105 | .section microzig_flash_start,"a",%progbits 106 | .type g_pfnVectors, %object 107 | .size g_pfnVectors, .-g_pfnVectors 108 | 109 | g_pfnVectors: 110 | .word microzig_stack_end 111 | .word _start 112 | .word NMI_Handler 113 | .word HardFault_Handler 114 | .word MemManage_Handler 115 | .word BusFault_Handler 116 | .word UsageFault_Handler 117 | .word 0 118 | .word 0 119 | .word 0 120 | .word 0 121 | .word SVC_Handler 122 | .word DebugMon_Handler 123 | .word 0 124 | .word PendSV_Handler 125 | .word SysTick_Handler 126 | 127 | 128 | /******************************************************************************* 129 | * 130 | * Provide weak aliases for each Exception handler to the Default_Handler. 131 | * As they are weak aliases, any function with the same name will override 132 | * this definition. 133 | * 134 | *******************************************************************************/ 135 | .weak NMI_Handler 136 | .thumb_set NMI_Handler,Default_Handler 137 | 138 | .weak HardFault_Handler 139 | .thumb_set HardFault_Handler,Default_Handler 140 | 141 | .weak MemManage_Handler 142 | .thumb_set MemManage_Handler,Default_Handler 143 | 144 | .weak BusFault_Handler 145 | .thumb_set BusFault_Handler,Default_Handler 146 | 147 | .weak UsageFault_Handler 148 | .thumb_set UsageFault_Handler,Default_Handler 149 | 150 | .weak SVC_Handler 151 | .thumb_set SVC_Handler,Default_Handler 152 | 153 | .weak DebugMon_Handler 154 | .thumb_set DebugMon_Handler,Default_Handler 155 | 156 | .weak PendSV_Handler 157 | .thumb_set PendSV_Handler,Default_Handler 158 | 159 | .weak SysTick_Handler 160 | .thumb_set SysTick_Handler,Default_Handler 161 | -------------------------------------------------------------------------------- /src/chip/stm32f4/clock.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const cpu = @import("../cortex-m.zig"); 3 | const regs = @import("regs.zig").devices.stm32f4.peripherals; 4 | const hal = @import("../../hal/hal.zig"); 5 | 6 | pub fn clock_init() void { 7 | 8 | // Enable power interface clock 9 | regs.RCC.APB1ENR.modify(.{ .PWREN = 1 }); 10 | 11 | // Enable HSI 12 | regs.RCC.CR.modify(.{ .HSION = 1 }); 13 | // Wait for HSI ready 14 | while (regs.RCC.CR.read().HSIRDY != 1) { 15 | asm volatile ("nop"); 16 | } 17 | 18 | // Use HSI as system clock 19 | regs.RCC.CFGR.modify(.{ .SW = 0b00 }); 20 | 21 | // Disable PLL 22 | regs.RCC.CR.modify(.{ .PLLON = 0 }); 23 | 24 | // HSI used as PLL clock source; PLLM = 8 PLLN = 80 PLLP = 2 PLLQ = 4 25 | regs.RCC.PLLCFGR.modify(.{ .PLLSRC = 0, .PLLM = 8, .PLLN = 80, .PLLP = 0b00, .PLLQ = 4 }); 26 | 27 | // clear clock interrupt 28 | regs.RCC.CIR.write_raw(0); 29 | 30 | // Prefetch enable; Instruction cache enable; Data cache enable; Set flash latency 31 | regs.FLASH.ACR.modify(.{ .PRFTEN = 1, .ICEN = 1, .DCEN = 1, .LATENCY = 0b010 }); 32 | 33 | // Enable PLL 34 | regs.RCC.CR.modify(.{ .PLLON = 1 }); 35 | // Wait for PLL ready 36 | while (regs.RCC.CR.read().PLLRDY != 1) { 37 | asm volatile ("nop"); 38 | } 39 | // Use PLL as system clock 40 | regs.RCC.CFGR.modify(.{ .SW = 0b10 }); 41 | while (regs.RCC.CFGR.read().SWS != 0b10) { 42 | asm volatile ("nop"); 43 | } 44 | 45 | // init systick 46 | cpu.peripherals.SysTick.LOAD.raw = 0xFFFFFF; 47 | cpu.peripherals.SysTick.CTRL.modify(.{ .ENABLE = 1, .CLKSOURCE = 1 }); 48 | } 49 | 50 | pub fn clock_deinit() void { 51 | // Reset clock 52 | cpu.peripherals.SysTick.CTRL.modify(.{ .ENABLE = 0 }); 53 | regs.RCC.CFGR.raw = 0x00000000; 54 | } 55 | 56 | const HSI_VALUE = 16000000; // 16MHz 57 | const HSE_VALUE = 8000000; // 8MHz 58 | 59 | // get sysclk frequency It is calculated based on the predefined 60 | // constant and the selected clock source 61 | pub fn get_sysfreq() u32 { 62 | var pllm: u32 = 0; 63 | var pllvco: u32 = 0; 64 | var pllp: u32 = 0; 65 | var sysclockfreq: u32 = 0; 66 | 67 | switch (regs.RCC.CFGR.read().SWS) { 68 | 0b00 => sysclockfreq = HSI_VALUE, // HSI 69 | 0b01 => sysclockfreq = HSE_VALUE, // HSE 70 | 0b10 => { // PLL 71 | //PLL_VCO = (HSE_VALUE or HSI_VALUE / PLLM) * PLLN 72 | //SYSCLK = PLL_VCO / PLLP 73 | const pllcfgr = regs.RCC.PLLCFGR.read(); 74 | pllm = pllcfgr.PLLM; 75 | if (pllcfgr.PLLSRC == 1) { 76 | // HSE used as PLL clock source 77 | pllvco = (HSE_VALUE / pllm) * pllcfgr.PLLN; 78 | } else { 79 | // HSI used as PLL clock source 80 | pllvco = (HSI_VALUE / pllm) * pllcfgr.PLLN; 81 | } 82 | pllp = (pllcfgr.PLLP + 1) * 2; 83 | sysclockfreq = pllvco / pllp; 84 | }, 85 | 0b11 => return 0, 86 | } 87 | return sysclockfreq; 88 | } 89 | 90 | pub const clock: hal.clock.ClockType = .{ 91 | .init = &clock_init, 92 | .deinit = &clock_deinit, 93 | .get_sysfreq = &get_sysfreq, 94 | }; 95 | -------------------------------------------------------------------------------- /src/chip/stm32f4/flash.zig: -------------------------------------------------------------------------------- 1 | const regs = @import("regs.zig").devices.stm32f4.peripherals; 2 | const cpu = @import("../cortex-m.zig"); 3 | 4 | const Flash = @import("../../platform/fal/flash.zig"); 5 | const sys = @import("../../platform/sys.zig"); 6 | const flash_size = @import("../../hal/hal.zig").chip_flash_size; 7 | 8 | const Debug = false; 9 | 10 | const FLASH_KEYR_KEY1 = 0x45670123; 11 | const FLASH_KEYR_KEY2 = 0xcdef89ab; 12 | 13 | const FLASH_OPTKEYR_KEY1 = 0x08192a3b; 14 | const FLASH_OPTKEYR_KEY2 = 0x4c5d6e7f; 15 | 16 | // unlock stm32 flash 17 | pub fn flash_unlock() void { 18 | regs.FLASH.KEYR.raw = FLASH_KEYR_KEY1; 19 | regs.FLASH.KEYR.raw = FLASH_KEYR_KEY2; 20 | } 21 | 22 | // lock stm32 flash 23 | pub fn flash_lock() void { 24 | regs.FLASH.CR.modify(.{ .LOCK = 1 }); 25 | } 26 | // flash_clear_status_flags 27 | pub fn flash_clear_status_flags() void { 28 | regs.FLASH.SR.modify(.{ .PGPERR = 1, .PGSERR = 1, .WRPERR = 1, .PGAERR = 1, .EOP = 1 }); 29 | } 30 | 31 | pub const FLASH_CR_PROGRAM_X8 = 0; 32 | pub const FLASH_CR_PROGRAM_X16 = 1; 33 | pub const FLASH_CR_PROGRAM_X32 = 2; 34 | pub const FLASH_CR_PROGRAM_X64 = 3; 35 | 36 | pub fn flash_wait_for_last_operation() void { 37 | while (regs.FLASH.SR.read().BSY == 1) { 38 | cpu.nop(); 39 | } 40 | } 41 | 42 | pub fn flash_set_program_size(psize: u32) void { 43 | regs.FLASH.CR.modify(.{ .PSIZE = @as(u2, @intCast(psize)) }); 44 | } 45 | 46 | // Program an 8 bit Byte to FLASH 47 | pub fn flash_program_byte(address: u32, data: u8) void { 48 | flash_wait_for_last_operation(); 49 | flash_set_program_size(FLASH_CR_PROGRAM_X8); 50 | 51 | regs.FLASH.CR.modify(.{ .PG = 1 }); 52 | 53 | const addr: *volatile u8 = @ptrFromInt(address); 54 | addr.* = data; 55 | 56 | flash_wait_for_last_operation(); 57 | 58 | regs.FLASH.CR.modify(.{ .PG = 0 }); 59 | } 60 | 61 | pub fn flash_init(self: *const Flash.Flash_Dev) Flash.FlashErr { 62 | _ = self; 63 | return Flash.FlashErr.Ok; 64 | } 65 | 66 | pub fn flash_earse(self: *const Flash.Flash_Dev, addr: u32, size: u32) Flash.FlashErr { 67 | flash_unlock(); 68 | flash_clear_status_flags(); 69 | // Iterate the flash block, calculate which block addr and size belongs to, and then erase it 70 | 71 | var addr_cur = self.start; 72 | var secter_cur: u32 = 0; 73 | var is_find: bool = false; 74 | for (self.blocks) |b| { 75 | const is_ok = for (0..b.count) |i| { 76 | _ = i; 77 | 78 | if (Debug) { 79 | sys.debug.print("addr:{x}, cur_block:{x}\r\n", .{ addr, secter_cur }) catch {}; 80 | } 81 | if (is_find == false) { 82 | if (addr >= addr_cur and addr < addr_cur + b.size) { 83 | if (Debug) { 84 | sys.debug.print("finded first block:{x}\r\n", .{secter_cur}) catch {}; 85 | } 86 | is_find = true; 87 | } else { 88 | addr_cur += b.size; 89 | secter_cur += 1; 90 | continue; 91 | } 92 | } 93 | 94 | if (addr < addr_cur + b.size) { 95 | if (Debug) { 96 | sys.debug.print("chip erase secter_cur:{x}\r\n", .{secter_cur}) catch {}; 97 | } 98 | // Erase the block 99 | flash_wait_for_last_operation(); 100 | regs.FLASH.CR.modify(.{ .SER = 1, .SNB = @as(u4, @intCast(secter_cur)) }); 101 | regs.FLASH.CR.modify(.{ .STRT = 1 }); 102 | flash_wait_for_last_operation(); 103 | regs.FLASH.CR.modify(.{ .SER = 0, .SNB = 0 }); 104 | } 105 | if (addr_cur + b.size >= addr + size) { 106 | break true; 107 | } 108 | 109 | addr_cur += b.size; 110 | secter_cur += 1; 111 | } else false; 112 | if (is_ok) { 113 | break; 114 | } 115 | } 116 | 117 | flash_lock(); 118 | return Flash.FlashErr.Ok; 119 | } 120 | pub fn flash_write(self: *const Flash.Flash_Dev, addr: u32, data: []const u8) Flash.FlashErr { 121 | _ = self; 122 | flash_unlock(); 123 | flash_clear_status_flags(); 124 | 125 | for (data, 0..) |d, i| { 126 | flash_program_byte(addr + i, d); 127 | } 128 | 129 | flash_lock(); 130 | return Flash.FlashErr.Ok; 131 | } 132 | pub fn flash_read(self: *const Flash.Flash_Dev, addr: u32, data: []u8) Flash.FlashErr { 133 | _ = self; 134 | 135 | // read data from onchip flash 136 | for (data, 0..) |*d, i| { 137 | d.* = @as(*u8, @ptrFromInt(addr + i)).*; 138 | } 139 | return Flash.FlashErr.Ok; 140 | } 141 | 142 | const ops: Flash.FlashOps = .{ 143 | .init = &flash_init, 144 | .erase = &flash_earse, 145 | .write = &flash_write, 146 | .read = &flash_read, 147 | }; 148 | 149 | pub const chip_flash: Flash.Flash_Dev = .{ 150 | .name = "onchip", 151 | .start = 0x08000000, 152 | .len = 0x100000, 153 | .blocks = .{ 154 | .{ .size = 0x4000, .count = 4 }, 155 | .{ .size = 0x10000, .count = 1 }, 156 | .{ .size = 0x20000, .count = 7 }, 157 | .{ .size = 0x4000, .count = 4 }, 158 | .{ .size = 0x10000, .count = 1 }, 159 | .{ .size = 0x20000, .count = 7 }, 160 | }, 161 | .write_size = 8, 162 | .ops = &ops, 163 | }; 164 | -------------------------------------------------------------------------------- /src/chip/stm32f4/pin.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const regs = @import("regs.zig").devices.stm32f4.peripherals; 3 | const GPIO_Type = @import("regs.zig").types.peripherals.GPIOA; 4 | const RCC_Type = @import("regs.zig").types.peripherals.RCC; 5 | 6 | const hal = @import("../../hal/hal.zig"); 7 | const PinType = hal.pin.PinType; 8 | const Pin_Mode = hal.pin.Pin_Mode; 9 | const Pin_Level = hal.pin.Pin_Level; 10 | 11 | pub const ChipPinData = struct { 12 | port: *volatile GPIO_Type, 13 | pin: u8, 14 | }; 15 | 16 | // pin set mode 17 | fn set_mode(self: *const PinType, pin_mode: Pin_Mode) void { 18 | const port: *volatile GPIO_Type = @as(*volatile GPIO_Type, @ptrFromInt(self.data.port)); 19 | const pin: u8 = self.data.pin; 20 | if (pin_mode == Pin_Mode.Output) { 21 | var moder_raw: u32 = port.MODER.raw; // todo .read() 22 | moder_raw = moder_raw & ~std.math.shl(u32, 0b11, pin * 2); 23 | moder_raw = moder_raw | std.math.shl(u32, 0b01, pin * 2); 24 | port.MODER.write_raw(moder_raw); 25 | } else if (pin_mode == Pin_Mode.Input) { 26 | var moder_raw: u32 = port.MODER.raw; 27 | moder_raw = moder_raw & ~std.math.shl(u32, 0b11, pin * 2); 28 | port.MODER.raw = moder_raw; 29 | } 30 | } 31 | 32 | // pin write 33 | fn write(self: *const PinType, value: Pin_Level) void { 34 | const port: *volatile GPIO_Type = @as(*volatile GPIO_Type, @ptrFromInt(self.data.port)); 35 | const pin: u8 = self.data.pin; 36 | if (value == Pin_Level.High) { 37 | port.ODR.raw = port.ODR.raw | std.math.shl(u32, 1, pin); 38 | } else { 39 | port.ODR.raw = port.ODR.raw & ~std.math.shl(u32, 1, pin); 40 | } 41 | } 42 | 43 | // pin read 44 | fn read(self: *const PinType) Pin_Level { 45 | const port: *volatile GPIO_Type = @as(*volatile GPIO_Type, @ptrFromInt(self.data.port)); 46 | const pin: u8 = self.data.pin; 47 | const idr_raw = port.IDR.raw; 48 | const pin_set = std.math.shl(u32, 1, pin); 49 | if (idr_raw & pin_set != 0) { 50 | return Pin_Level.High; 51 | } else { 52 | return Pin_Level.Low; 53 | } 54 | } 55 | 56 | const ops: hal.pin.PinOps = .{ 57 | .mode = &set_mode, 58 | .write = &write, 59 | .read = &read, 60 | }; 61 | 62 | pub fn init(name: []const u8) !PinType { 63 | // parse name 64 | const port_num = name[1] - 'A'; 65 | const port_addr: u32 = @intFromPtr(regs.GPIOA) + 0x400 * @as(u32, port_num); 66 | const pin_num: u32 = try parseU32(name[2..]); 67 | 68 | const self: PinType = .{ .data = .{ .port = port_addr, .pin = @intCast(pin_num) }, .ops = &ops }; 69 | 70 | const port = @as(*volatile GPIO_Type, @ptrFromInt(port_addr)); 71 | const pin: u8 = @intCast(pin_num); 72 | 73 | // Enable PinX(A..) port 74 | var ahbenr_raw = regs.RCC.AHB1ENR.raw; 75 | ahbenr_raw = ahbenr_raw | std.math.shl(u32, 1, port_num); 76 | regs.RCC.AHB1ENR.raw = ahbenr_raw; 77 | // Enable PinX to output 78 | var moder_raw: u32 = port.MODER.raw; 79 | // todo: optimize 80 | moder_raw = moder_raw & ~std.math.shl(u32, 0b11, pin * 2); 81 | moder_raw = moder_raw | std.math.shl(u32, 0b01, pin * 2); 82 | port.MODER.raw = moder_raw; 83 | 84 | return self; 85 | } 86 | 87 | fn parseU32(input: []const u8) !u32 { 88 | var tmp: u32 = 0; 89 | for (input) |c| { 90 | if (c == 0) { 91 | break; 92 | } 93 | const digit = try std.fmt.charToDigit(c, 10); 94 | tmp = tmp * 10 + @as(u32, digit); 95 | } 96 | return tmp; 97 | } 98 | -------------------------------------------------------------------------------- /src/chip/stm32h7/clock.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const cpu = @import("../cortex-m.zig"); 3 | const regs = @import("regs.zig").devices.stm32h7.peripherals; 4 | const hal = @import("../../hal/hal.zig"); 5 | 6 | const RCC_CFGR_SW_HSI = 0; 7 | const RCC_CFGR_SW_CSI = 1; 8 | const RCC_CFGR_SW_HSE = 2; 9 | const RCC_CFGR_SW_PLL1 = 3; 10 | 11 | const RCC_PLLSOURCE_HSI = 0b00; 12 | const RCC_PLLSOURCE_CSI = 0b01; 13 | const RCC_PLLSOURCE_HSE = 0b10; 14 | const RCC_PLLSOURCE_NONE = 0b11; 15 | 16 | pub fn clock_init() void { 17 | 18 | // Enable power interface clock vos3 and latency4 max freq = 180Mhz 19 | regs.Flash.ACR.modify(.{ .LATENCY = 4 }); 20 | 21 | // Enable HSI 22 | regs.RCC.CR.modify(.{ .HSION = 1 }); 23 | // Wait for HSI ready 24 | while (regs.RCC.CR.read().HSIRDY != 1) { 25 | cpu.nop(); 26 | } 27 | 28 | // Use HSI as system clock 29 | regs.RCC.CFGR.modify(.{ .SW = RCC_CFGR_SW_HSI }); 30 | 31 | // Disable PLL 32 | regs.RCC.CR.modify(.{ .PLL1ON = 0 }); 33 | // waiting PLLRDY is clear 34 | while (regs.RCC.CR.read().PLL1RDY != 0) { 35 | cpu.nop(); 36 | } 37 | 38 | // HSI used as PLL clock source; freq:200Mhz 39 | regs.RCC.PLLCKSELR.modify(.{ .PLLSRC = RCC_PLLSOURCE_HSI, .DIVM1 = 4 }); 40 | regs.RCC.PLLCFGR.modify(.{ .PLL1VCOSEL = 0, .PLL1RGE = 0b11, .PLL1FRACEN = 0, .DIVP1EN = 1, .DIVQ1EN = 1, .DIVR1EN = 1 }); 41 | regs.RCC.PLL1DIVR.modify(.{ .DIVN1 = 24, .DIVP1 = 1 }); 42 | 43 | // clear clock interrupt 44 | regs.RCC.CIER.raw = 0; 45 | 46 | // Enable PLL 47 | regs.RCC.CR.modify(.{ .PLL1ON = 1 }); 48 | // Wait for PLL ready 49 | while (regs.RCC.CR.read().PLL1RDY != 1) { 50 | cpu.nop(); 51 | } 52 | // Use PLL as system clock 53 | regs.RCC.CFGR.modify(.{ .SW = RCC_CFGR_SW_PLL1 }); 54 | while (regs.RCC.CFGR.read().SWS != RCC_CFGR_SW_PLL1) { 55 | cpu.nop(); 56 | } 57 | 58 | // init systick 59 | cpu.peripherals.SysTick.LOAD.raw = 0xFFFFFF; 60 | cpu.peripherals.SysTick.CTRL.modify(.{ .ENABLE = 1, .CLKSOURCE = 1 }); 61 | } 62 | 63 | pub fn clock_deinit() void { 64 | // Reset clock 65 | cpu.peripherals.SysTick.CTRL.modify(.{ .ENABLE = 0 }); 66 | regs.RCC.CFGR.raw = 0x00000000; 67 | } 68 | 69 | const CSI_VALUE: u32 = 4000000; // 4MHz 70 | const HSI_VALUE: u32 = 64000000; // 64MHz 71 | const HSE_VALUE: u32 = 8000000; // 8MHz 72 | 73 | // get sysclk frequency It is calculated based on the predefined 74 | // constant and the selected clock source 75 | pub fn get_sysfreq() u32 { 76 | var pllm: u32 = 0; 77 | var pllvco: u32 = 0; 78 | var pllp: u32 = 0; 79 | var sysclockfreq: u32 = 0; 80 | 81 | switch (regs.RCC.CFGR.read().SWS) { 82 | RCC_CFGR_SW_HSI => if (regs.RCC.CR.read().HSIDIVF != 0) { 83 | sysclockfreq = HSI_VALUE >> regs.RCC.CR.read().HSIDIV; 84 | } else { 85 | sysclockfreq = HSI_VALUE; 86 | }, // HSI 87 | RCC_CFGR_SW_HSE => sysclockfreq = HSE_VALUE, // HSE 88 | RCC_CFGR_SW_PLL1 => { // PLL 89 | //PLL_VCO = (HSE_VALUE or HSI_VALUCFGRE / PLLM) * PLLN 90 | //SYSCLK = PLL_VCO / PLLP 91 | const pllcource = regs.RCC.PLLCKSELR.read().PLLSRC; 92 | pllm = regs.RCC.PLLCKSELR.read().DIVM1; 93 | const pllfracen = regs.RCC.PLLCFGR.read().PLL1FRACEN; 94 | if (pllfracen != 0) { 95 | // fracn not supported 96 | } 97 | 98 | if (pllm != 0) { 99 | if (pllcource == RCC_PLLSOURCE_HSE) { 100 | // HSE used as PLL clock source 101 | pllvco = (HSE_VALUE / pllm) * (regs.RCC.PLL1DIVR.read().DIVN1 + 1); 102 | } else if (pllcource == RCC_PLLSOURCE_HSI) { 103 | // HSI used as PLL clock source 104 | if (regs.RCC.CR.read().HSIDIVF != 0) { 105 | pllvco = (HSI_VALUE >> regs.RCC.CR.read().HSIDIV) / pllm * (regs.RCC.PLL1DIVR.read().DIVN1 + 1); 106 | } else { 107 | pllvco = HSI_VALUE / pllm * (regs.RCC.PLL1DIVR.read().DIVN1 + 1); 108 | } 109 | } else if (pllcource == RCC_PLLSOURCE_CSI) { 110 | // CSI not supported 111 | } 112 | pllp = (@as(u32, regs.RCC.PLL1DIVR.read().DIVP1) + 1); 113 | sysclockfreq = pllvco / pllp; 114 | } else { 115 | sysclockfreq = 0; 116 | } 117 | }, 118 | else => sysclockfreq = CSI_VALUE, // CSI 119 | } 120 | return sysclockfreq; 121 | } 122 | 123 | pub const clock: hal.clock.ClockType = .{ 124 | .init = &clock_init, 125 | .deinit = &clock_deinit, 126 | .get_sysfreq = &get_sysfreq, 127 | }; 128 | -------------------------------------------------------------------------------- /src/chip/stm32h7/flash.zig: -------------------------------------------------------------------------------- 1 | const regs = @import("regs.zig").devices.stm32h7.peripherals; 2 | const cpu = @import("../cortex-m.zig"); 3 | 4 | const Flash = @import("../../platform/fal/flash.zig"); 5 | const sys = @import("../../platform/sys.zig"); 6 | const hal = @import("../../hal/hal.zig"); 7 | 8 | const Debug = false; 9 | 10 | const FLASH_KEYR_KEY1 = 0x45670123; 11 | const FLASH_KEYR_KEY2 = 0xcdef89ab; 12 | 13 | const FLASH_OPTKEYR_KEY1 = 0x08192a3b; 14 | const FLASH_OPTKEYR_KEY2 = 0x4c5d6e7f; 15 | 16 | // unlock stm32 flash 17 | pub fn flash_unlock() void { 18 | // bank1 19 | regs.Flash.KEYR1.raw = FLASH_KEYR_KEY1; 20 | regs.Flash.KEYR1.raw = FLASH_KEYR_KEY2; 21 | 22 | // bank2 23 | regs.Flash.KEYR2.raw = FLASH_KEYR_KEY1; 24 | regs.Flash.KEYR2.raw = FLASH_KEYR_KEY2; 25 | } 26 | 27 | // lock stm32 flash 28 | pub fn flash_lock() void { 29 | regs.Flash.CR1.modify(.{ .LOCK1 = 1 }); 30 | regs.Flash.CR2.modify(.{ .LOCK2 = 1 }); 31 | } 32 | // flash_clear_status_flags 33 | pub fn flash_clear_status_flags() void { 34 | regs.Flash.CCR1.modify(.{ .CLR_PGSERR1 = 1, .CLR_WRPERR1 = 1, .CLR_EOP1 = 1, .CLR_OPERR1 = 1 }); 35 | regs.Flash.CCR2.modify(.{ .CLR_PGSERR2 = 1, .CLR_WRPERR2 = 1, .CLR_EOP2 = 1, .CLR_OPERR2 = 1 }); 36 | } 37 | 38 | pub fn flash_wait_for_last_operation() void { 39 | while (regs.Flash.SR1.read().BSY1 == 1 or regs.Flash.SR2.read().BSY2 == 1) { 40 | cpu.nop(); 41 | } 42 | } 43 | 44 | pub const FLASH_CR_PROGRAM_X8 = 0; 45 | pub const FLASH_CR_PROGRAM_X16 = 1; 46 | pub const FLASH_CR_PROGRAM_X32 = 2; 47 | pub const FLASH_CR_PROGRAM_X64 = 3; 48 | 49 | pub fn flash_set_program_size(isbank1: bool, psize: u32) void { 50 | if (isbank1) { 51 | regs.Flash.CR1.modify(.{ .PSIZE1 = @as(u2, @intCast(psize)) }); 52 | } else { 53 | regs.Flash.CR2.modify(.{ .PSIZE2 = @as(u2, @intCast(psize)) }); 54 | } 55 | } 56 | 57 | // program an word(8 * 32 bit) to Flash 58 | pub fn flash_program_32byte(address: u32, data: []const u8) void { 59 | flash_wait_for_last_operation(); 60 | 61 | if (is_bank1(address)) { 62 | while (regs.Flash.SR1.read().WBNE1 == 1) { 63 | cpu.nop(); 64 | } 65 | flash_set_program_size(true, FLASH_CR_PROGRAM_X8); 66 | regs.Flash.CR1.modify(.{ .PG1 = 1 }); 67 | } else { 68 | while (regs.Flash.SR2.read().WBNE2 == 1) { 69 | cpu.nop(); 70 | } 71 | flash_set_program_size(false, FLASH_CR_PROGRAM_X8); 72 | regs.Flash.CR2.modify(.{ .PG2 = 1 }); 73 | } 74 | 75 | for (data, 0..) |d, i| { 76 | const addr: *volatile u8 = @ptrFromInt(address + i); 77 | addr.* = d; 78 | } 79 | 80 | flash_wait_for_last_operation(); 81 | 82 | if (is_bank1(address)) { 83 | regs.Flash.CR1.modify(.{ .PG1 = 0 }); 84 | } else { 85 | regs.Flash.CR2.modify(.{ .PG2 = 0 }); 86 | } 87 | } 88 | 89 | var SECTER_PER_BANK: u32 = 1024 * 1024 / 2 / 2048; // 90 | pub fn flash_init(self: *const Flash.Flash_Dev) Flash.FlashErr { 91 | SECTER_PER_BANK = hal.chip_flash_size * 1024 / self.blocks[0].size / 2; 92 | if (Debug) { 93 | sys.debug.print("chipflash size:0x{x}, SecterPerBank:0x{x}\r\n", .{ sys.zconfig.get_config().chipflash.size, SECTER_PER_BANK }) catch {}; 94 | } 95 | return Flash.FlashErr.Ok; 96 | } 97 | 98 | // get bank1 or bank2 secter 99 | pub fn is_bank1(addr: u32) bool { 100 | if ((addr - 0x8000000) < SECTER_PER_BANK * 0x20000) { 101 | return true; 102 | } else { 103 | return false; 104 | } 105 | } 106 | 107 | pub fn flash_earse(self: *const Flash.Flash_Dev, addr: u32, size: u32) Flash.FlashErr { 108 | flash_unlock(); 109 | flash_clear_status_flags(); 110 | // Iterate the flash block, calculate which block addr and size belongs to, and then erase it 111 | 112 | var addr_cur = self.start; 113 | var secter_cur: u32 = 0; 114 | var is_find: bool = false; 115 | for (self.blocks) |b| { 116 | const is_ok = for (0..b.count) |i| { 117 | _ = i; 118 | 119 | if (Debug) { 120 | sys.debug.print("addr_cur:0x{x}, cur_block:0x{x}\r\n", .{ addr_cur, secter_cur }) catch {}; 121 | } 122 | if (is_find == false) { 123 | if (addr >= addr_cur and addr < addr_cur + b.size) { 124 | if (Debug) { 125 | sys.debug.print("finded first block:{x}\r\n", .{secter_cur}) catch {}; 126 | } 127 | is_find = true; 128 | } else { 129 | addr_cur += b.size; 130 | secter_cur += 1; 131 | continue; 132 | } 133 | } 134 | 135 | if (addr < addr_cur + b.size) { 136 | var bank: u8 = 1; 137 | var pgn = secter_cur; 138 | 139 | // Erase the block 140 | flash_wait_for_last_operation(); 141 | if (secter_cur >= SECTER_PER_BANK) { 142 | bank = 2; 143 | pgn -= SECTER_PER_BANK; 144 | } 145 | if (Debug) { 146 | sys.debug.print("chip erase bank:{d},secter_cur:{x}\r\n", .{ bank, pgn }) catch {}; 147 | } 148 | if (bank == 1) { 149 | regs.Flash.CR1.modify(.{ .SER1 = 1, .SNB1 = @as(u3, @intCast(pgn)) }); 150 | regs.Flash.CR1.modify(.{ .START1 = 1 }); 151 | 152 | flash_wait_for_last_operation(); 153 | regs.Flash.CR1.modify(.{ .SER1 = 0, .SNB1 = 0 }); 154 | } else { 155 | regs.Flash.CR2.modify(.{ .SER2 = 1, .SNB2 = @as(u3, @intCast(pgn)) }); 156 | regs.Flash.CR2.modify(.{ .START2 = 1 }); 157 | 158 | flash_wait_for_last_operation(); 159 | regs.Flash.CR2.modify(.{ .SER2 = 0, .SNB2 = 0 }); 160 | } 161 | if (Debug) { 162 | for (addr_cur..addr_cur + b.size) |check_d| { 163 | const d = @as(*u8, @ptrFromInt(check_d)).*; 164 | if (d != 0xFF) { 165 | sys.debug.print("erase failed, data check error\r\n", .{}) catch {}; 166 | break; 167 | } 168 | } 169 | } 170 | } 171 | if (addr_cur + b.size >= addr + size) { 172 | break true; 173 | } 174 | 175 | addr_cur += b.size; 176 | secter_cur += 1; 177 | } else false; 178 | if (is_ok) { 179 | break; 180 | } 181 | } 182 | 183 | flash_lock(); 184 | return Flash.FlashErr.Ok; 185 | } 186 | pub fn flash_write(self: *const Flash.Flash_Dev, addr: u32, data: []const u8) Flash.FlashErr { 187 | _ = self; 188 | var ret = Flash.FlashErr.Ok; 189 | flash_unlock(); 190 | flash_clear_status_flags(); 191 | 192 | var i: u32 = 0; 193 | while (i < data.len) : (i += 32) { 194 | flash_program_32byte(addr + i, data[i .. i + 32]); 195 | 196 | // check data 197 | for (data[i .. i + 32], 0..) |d, j| { 198 | const addr8: *const volatile u8 = @ptrFromInt(addr + i + j); 199 | if (d != addr8.*) { 200 | sys.debug.print("write failed, data check error\r\n", .{}) catch {}; 201 | ret = Flash.FlashErr.ErrWrite; 202 | break; 203 | } 204 | } 205 | } 206 | 207 | flash_lock(); 208 | return ret; 209 | } 210 | pub fn flash_read(self: *const Flash.Flash_Dev, addr: u32, data: []u8) Flash.FlashErr { 211 | _ = self; 212 | 213 | // read data from onchip flash 214 | for (data, 0..) |*d, i| { 215 | d.* = @as(*u8, @ptrFromInt(addr + i)).*; 216 | } 217 | return Flash.FlashErr.Ok; 218 | } 219 | 220 | const ops: Flash.FlashOps = .{ 221 | .init = &flash_init, 222 | .erase = &flash_earse, 223 | .write = &flash_write, 224 | .read = &flash_read, 225 | }; 226 | 227 | pub const chip_flash: Flash.Flash_Dev = .{ 228 | .name = "onchip", 229 | .start = 0x08000000, 230 | .len = 0x100000, 231 | .blocks = .{ 232 | .{ .size = 0x20000, .count = 8 }, 233 | .{ .size = 0, .count = 0 }, 234 | .{ .size = 0, .count = 0 }, 235 | .{ .size = 0, .count = 0 }, 236 | .{ .size = 0, .count = 0 }, 237 | .{ .size = 0, .count = 0 }, 238 | }, 239 | .write_size = 8, 240 | .ops = &ops, 241 | }; 242 | -------------------------------------------------------------------------------- /src/chip/stm32h7/pin.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const regs = @import("regs.zig").devices.stm32h7.peripherals; 3 | const GPIO_Type = @import("regs.zig").types.peripherals.GPIOA; 4 | const RCC_Type = @import("regs.zig").types.peripherals.RCC; 5 | 6 | const hal = @import("../../hal/hal.zig"); 7 | const PinType = hal.pin.PinType; 8 | const Pin_Mode = hal.pin.Pin_Mode; 9 | const Pin_Level = hal.pin.Pin_Level; 10 | 11 | pub const ChipPinData = struct { 12 | port: *volatile GPIO_Type, 13 | pin: u8, 14 | }; 15 | 16 | // pin set mode 17 | fn set_mode(self: *const PinType, pin_mode: Pin_Mode) void { 18 | const port: *volatile GPIO_Type = @as(*volatile GPIO_Type, @ptrFromInt(self.data.port)); 19 | const pin: u8 = self.data.pin; 20 | if (pin_mode == Pin_Mode.Output) { 21 | var moder_raw: u32 = port.MODER.raw; // todo .read() 22 | moder_raw = moder_raw & ~std.math.shl(u32, 0b11, pin * 2); 23 | moder_raw = moder_raw | std.math.shl(u32, 0b01, pin * 2); 24 | port.MODER.write_raw(moder_raw); 25 | } else if (pin_mode == Pin_Mode.Input) { 26 | var moder_raw: u32 = port.MODER.raw; 27 | moder_raw = moder_raw & ~std.math.shl(u32, 0b11, pin * 2); 28 | port.MODER.raw = moder_raw; 29 | } 30 | } 31 | 32 | // pin write 33 | fn write(self: *const PinType, value: Pin_Level) void { 34 | const port: *volatile GPIO_Type = @as(*volatile GPIO_Type, @ptrFromInt(self.data.port)); 35 | const pin: u8 = self.data.pin; 36 | if (value == Pin_Level.High) { 37 | port.ODR.raw = port.ODR.raw | std.math.shl(u32, 1, pin); 38 | } else { 39 | port.ODR.raw = port.ODR.raw & ~std.math.shl(u32, 1, pin); 40 | } 41 | } 42 | 43 | // pin read 44 | fn read(self: *const PinType) Pin_Level { 45 | const port: *volatile GPIO_Type = @as(*volatile GPIO_Type, @ptrFromInt(self.data.port)); 46 | const pin: u8 = self.data.pin; 47 | const idr_raw = port.IDR.raw; 48 | const pin_set = std.math.shl(u32, 1, pin); 49 | if (idr_raw & pin_set != 0) { 50 | return Pin_Level.High; 51 | } else { 52 | return Pin_Level.Low; 53 | } 54 | } 55 | 56 | const ops: hal.pin.PinOps = .{ 57 | .mode = &set_mode, 58 | .write = &write, 59 | .read = &read, 60 | }; 61 | 62 | pub fn init(name: []const u8) !PinType { 63 | // parse name 64 | const port_num = name[1] - 'A'; 65 | const port_addr: u32 = @intFromPtr(regs.GPIOA) + 0x400 * @as(u32, port_num); 66 | const pin_num: u32 = try parseU32(name[2..]); 67 | 68 | const self: PinType = .{ .data = .{ .port = port_addr, .pin = @intCast(pin_num) }, .ops = &ops }; 69 | 70 | const port = @as(*volatile GPIO_Type, @ptrFromInt(port_addr)); 71 | const pin: u8 = @intCast(pin_num); 72 | 73 | // Enable PinX(A..) port 74 | var ahbenr_raw = regs.RCC.AHB4ENR.raw; 75 | ahbenr_raw = ahbenr_raw | std.math.shl(u32, 1, port_num); 76 | regs.RCC.AHB4ENR.raw = ahbenr_raw; 77 | // Enable PinX to output 78 | var moder_raw: u32 = port.MODER.raw; 79 | // todo: optimize 80 | moder_raw = moder_raw & ~std.math.shl(u32, 0b11, pin * 2); 81 | moder_raw = moder_raw | std.math.shl(u32, 0b01, pin * 2); 82 | port.MODER.raw = moder_raw; 83 | 84 | return self; 85 | } 86 | 87 | fn parseU32(input: []const u8) !u32 { 88 | var tmp: u32 = 0; 89 | for (input) |c| { 90 | if (c == 0) { 91 | break; 92 | } 93 | const digit = try std.fmt.charToDigit(c, 10); 94 | tmp = tmp * 10 + @as(u32, digit); 95 | } 96 | return tmp; 97 | } 98 | -------------------------------------------------------------------------------- /src/chip/stm32l4/clock.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const cpu = @import("../cortex-m.zig"); 3 | const regs = @import("regs.zig").devices.stm32l4.peripherals; 4 | const hal = @import("../../hal/hal.zig"); 5 | 6 | const RCC_CFGR_SW_MSI = 0; 7 | const RCC_CFGR_SW_HSI = 1; 8 | const RCC_CFGR_SW_HSE = 2; 9 | const RCC_CFGR_SW_PLL = 3; 10 | 11 | const RCC_PLLSOURCE_MSI = 0b01; 12 | const RCC_PLLSOURCE_HSI = 0b10; 13 | const RCC_PLLSOURCE_HSE = 0b11; 14 | 15 | pub fn clock_init() void { 16 | 17 | // Enable power interface clock 18 | regs.FLASH.ACR.modify(.{ .LATENCY = 4 }); 19 | regs.PWR.CR1.modify(.{ .VOS = 1 }); 20 | 21 | // Enable HSI 22 | regs.RCC.CR.modify(.{ .HSION = 1 }); 23 | // Wait for HSI ready 24 | while (regs.RCC.CR.read().HSIRDY != 1) { 25 | cpu.nop(); 26 | } 27 | 28 | // Use HSI as system clock 29 | // 0x10XX 00XX where X is factory-programmed (for STM32L47x/L48x devices). 30 | // 0x40XX 00XX where X is factory-programmed (for STM32L49x/L4Ax devices) 31 | // The default value is 16 for STM32L47x/L48x devices and 64 for STM32L49x/L4Ax devices; 32 | if (regs.RCC.ICSCR.read().HSITRIM == 0x40) { 33 | regs.RCC.ICSCR.modify(.{ .HSITRIM = 64 }); // for STM32L49x/L4Ax 34 | } else { 35 | regs.RCC.ICSCR.modify(.{ .HSITRIM = 16 }); // for STM32L47x/L48x 36 | } 37 | regs.RCC.CFGR.modify(.{ .SW = RCC_CFGR_SW_HSI }); 38 | 39 | // Disable PLL 40 | regs.RCC.CR.modify(.{ .PLLON = 0 }); 41 | // waiting PLLRDY is clear 42 | while (regs.RCC.CR.read().PLLRDY != 0) { 43 | cpu.nop(); 44 | } 45 | 46 | // // HSI used as PLL clock source; PLLM = 8 PLLN = 80 PLLP = 2 PLLQ = 4 47 | regs.RCC.PLLCFGR.modify(.{ .PLLSRC = RCC_PLLSOURCE_HSI, .PLLM = 0, .PLLN = 10, .PLLP = 0, .PLLQ = 0, .PLLR = 0 }); 48 | regs.RCC.PLLCFGR.modify(.{ .PLLREN = 1 }); 49 | 50 | // clear clock interrupt 51 | regs.RCC.CIER.raw = 0; 52 | 53 | // Prefetch enable; Instruction cache enable; Data cache enable; Set flash latency 54 | regs.FLASH.ACR.modify(.{ .PRFTEN = 1, .ICEN = 1, .DCEN = 1, .LATENCY = 4 }); 55 | 56 | // Enable PLL 57 | regs.RCC.CR.modify(.{ .PLLON = 1 }); 58 | // Wait for PLL ready 59 | while (regs.RCC.CR.read().PLLRDY != 1) { 60 | cpu.nop(); 61 | } 62 | // Use PLL as system clock 63 | regs.RCC.CFGR.modify(.{ .SW = RCC_CFGR_SW_PLL }); 64 | while (regs.RCC.CFGR.read().SWS != RCC_CFGR_SW_PLL) { 65 | cpu.nop(); 66 | } 67 | 68 | // init systick 69 | cpu.peripherals.SysTick.LOAD.raw = 0xFFFFFF; 70 | cpu.peripherals.SysTick.CTRL.modify(.{ .ENABLE = 1, .CLKSOURCE = 1 }); 71 | } 72 | 73 | pub fn clock_deinit() void { 74 | // Reset clock 75 | cpu.peripherals.SysTick.CTRL.modify(.{ .ENABLE = 0 }); 76 | regs.RCC.CFGR.raw = 0x00000000; 77 | } 78 | 79 | const MSI_VALUE = 4000000; // 4MHz 80 | const HSI_VALUE = 16000000; // 16MHz 81 | const HSE_VALUE = 8000000; // 8MHz 82 | 83 | // get sysclk frequency It is calculated based on the predefined 84 | // constant and the selected clock source 85 | pub fn get_sysfreq() u32 { 86 | var pllm: u32 = 0; 87 | var pllvco: u32 = 0; 88 | var pllp: u32 = 0; 89 | var sysclockfreq: u32 = 0; 90 | 91 | switch (regs.RCC.CFGR.read().SWS) { 92 | RCC_CFGR_SW_MSI => sysclockfreq = MSI_VALUE, // MSI 93 | RCC_CFGR_SW_HSI => sysclockfreq = HSI_VALUE, // HSI 94 | RCC_CFGR_SW_HSE => sysclockfreq = HSE_VALUE, // HSE 95 | RCC_CFGR_SW_PLL => { // PLL 96 | //PLL_VCO = (HSE_VALUE or HSI_VALUCFGRE / PLLM) * PLLN 97 | //SYSCLK = PLL_VCO / PLLP 98 | const pllcfgr = regs.RCC.PLLCFGR.read(); 99 | pllm = pllcfgr.PLLM + 1; 100 | if (pllcfgr.PLLSRC == RCC_PLLSOURCE_HSE) { 101 | // HSE used as PLL clock source 102 | pllvco = (HSE_VALUE / pllm) * pllcfgr.PLLN; 103 | } else if (pllcfgr.PLLSRC == RCC_PLLSOURCE_HSI) { 104 | // HSI used as PLL clock source 105 | pllvco = (HSI_VALUE / pllm) * pllcfgr.PLLN; 106 | } else if (pllcfgr.PLLSRC == RCC_PLLSOURCE_MSI) { 107 | // MSI used as PLL clock source 108 | pllvco = (MSI_VALUE / pllm) * pllcfgr.PLLN; 109 | } 110 | pllp = (@as(u32, pllcfgr.PLLP) + 1) * 2; 111 | sysclockfreq = pllvco / pllp; 112 | }, 113 | } 114 | return sysclockfreq; 115 | } 116 | 117 | pub const clock: hal.clock.ClockType = .{ 118 | .init = &clock_init, 119 | .deinit = &clock_deinit, 120 | .get_sysfreq = &get_sysfreq, 121 | }; 122 | -------------------------------------------------------------------------------- /src/chip/stm32l4/flash.zig: -------------------------------------------------------------------------------- 1 | const regs = @import("regs.zig").devices.stm32l4.peripherals; 2 | const cpu = @import("../cortex-m.zig"); 3 | 4 | const Flash = @import("../../platform/fal/flash.zig"); 5 | const sys = @import("../../platform/sys.zig"); 6 | const hal = @import("../../hal/hal.zig"); 7 | 8 | const Debug = false; 9 | 10 | const FLASH_KEYR_KEY1 = 0x45670123; 11 | const FLASH_KEYR_KEY2 = 0xcdef89ab; 12 | 13 | const FLASH_OPTKEYR_KEY1 = 0x08192a3b; 14 | const FLASH_OPTKEYR_KEY2 = 0x4c5d6e7f; 15 | 16 | // unlock stm32 flash 17 | pub fn flash_unlock() void { 18 | regs.FLASH.KEYR.raw = FLASH_KEYR_KEY1; 19 | regs.FLASH.KEYR.raw = FLASH_KEYR_KEY2; 20 | } 21 | 22 | // lock stm32 flash 23 | pub fn flash_lock() void { 24 | regs.FLASH.CR.modify(.{ .LOCK = 1 }); 25 | } 26 | // flash_clear_status_flags 27 | pub fn flash_clear_status_flags() void { 28 | regs.FLASH.SR.modify(.{ .PGSERR = 1, .SIZERR = 1, .PGAERR = 1, .WRPERR = 1, .PROGERR = 1, .EOP = 1 }); 29 | } 30 | 31 | pub fn flash_wait_for_last_operation() void { 32 | while (regs.FLASH.SR.read().BSY == 1) { 33 | cpu.nop(); 34 | } 35 | } 36 | 37 | // program an word(64 bit) to Flash 38 | pub fn flash_program_64(address: u32, data: u64) void { 39 | flash_wait_for_last_operation(); 40 | 41 | regs.FLASH.CR.modify(.{ .PG = 1 }); 42 | 43 | const addr: *volatile u64 = @ptrFromInt(address); 44 | addr.* = data; 45 | 46 | flash_wait_for_last_operation(); 47 | 48 | regs.FLASH.CR.modify(.{ .PG = 0 }); 49 | } 50 | 51 | var SECTER_PER_BANK: u32 = 1024 * 1024 / 2 / 2048; // l496 1M flash, l475 512K flash 52 | pub fn flash_init(self: *const Flash.Flash_Dev) Flash.FlashErr { 53 | SECTER_PER_BANK = hal.chip_flash_size * 1024 / self.blocks[0].size / 2; 54 | if (Debug) { 55 | sys.debug.print("chipflash size:0x{x}, SecterPerBank:0x{x}\r\n", .{ sys.zconfig.get_config().chipflash.size, SECTER_PER_BANK }) catch {}; 56 | } 57 | return Flash.FlashErr.Ok; 58 | } 59 | 60 | pub fn flash_earse(self: *const Flash.Flash_Dev, addr: u32, size: u32) Flash.FlashErr { 61 | flash_unlock(); 62 | flash_clear_status_flags(); 63 | // Iterate the flash block, calculate which block addr and size belongs to, and then erase it 64 | 65 | var addr_cur = self.start; 66 | var secter_cur: u32 = 0; 67 | var is_find: bool = false; 68 | for (self.blocks) |b| { 69 | const is_ok = for (0..b.count) |i| { 70 | _ = i; 71 | 72 | if (Debug) { 73 | sys.debug.print("addr_cur:0x{x}, cur_block:0x{x}\r\n", .{ addr_cur, secter_cur }) catch {}; 74 | } 75 | if (is_find == false) { 76 | if (addr >= addr_cur and addr < addr_cur + b.size) { 77 | if (Debug) { 78 | sys.debug.print("finded first block:{x}\r\n", .{secter_cur}) catch {}; 79 | } 80 | is_find = true; 81 | } else { 82 | addr_cur += b.size; 83 | secter_cur += 1; 84 | continue; 85 | } 86 | } 87 | 88 | if (addr < addr_cur + b.size) { 89 | var bank: u8 = 1; 90 | var pgn = secter_cur; 91 | 92 | // Erase the block 93 | flash_wait_for_last_operation(); 94 | if (regs.FLASH.OPTR.read().DUALBANK == 0) { 95 | regs.FLASH.CR.modify(.{ .BKER = 0 }); 96 | } else { 97 | if (secter_cur < SECTER_PER_BANK) { 98 | regs.FLASH.CR.modify(.{ .BKER = 0 }); 99 | } else { 100 | regs.FLASH.CR.modify(.{ .BKER = 1 }); 101 | bank = 2; 102 | pgn -= SECTER_PER_BANK; 103 | } 104 | } 105 | if (Debug) { 106 | sys.debug.print("chip erase bank:{d},secter_cur:{x}\r\n", .{ bank, pgn }) catch {}; 107 | } 108 | regs.FLASH.CR.modify(.{ .PER = 1, .PNB = @as(u8, @intCast(pgn)) }); 109 | regs.FLASH.CR.modify(.{ .START = 1 }); 110 | flash_wait_for_last_operation(); 111 | regs.FLASH.CR.modify(.{ .PER = 0, .PNB = 0 }); 112 | if (Debug) { 113 | for (addr_cur..addr_cur + b.size) |check_d| { 114 | const d = @as(*u8, @ptrFromInt(check_d)).*; 115 | if (d != 0xFF) { 116 | sys.debug.print("erase failed, data check error\r\n", .{}) catch {}; 117 | break; 118 | } 119 | } 120 | } 121 | } 122 | if (addr_cur + b.size >= addr + size) { 123 | break true; 124 | } 125 | 126 | addr_cur += b.size; 127 | secter_cur += 1; 128 | } else false; 129 | if (is_ok) { 130 | break; 131 | } 132 | } 133 | 134 | flash_lock(); 135 | return Flash.FlashErr.Ok; 136 | } 137 | pub fn flash_write(self: *const Flash.Flash_Dev, addr: u32, data: []const u8) Flash.FlashErr { 138 | _ = self; 139 | var ret = Flash.FlashErr.Ok; 140 | flash_unlock(); 141 | flash_clear_status_flags(); 142 | 143 | var i: u32 = 0; 144 | while (i < data.len) : (i += 8) { 145 | const data64 = @as(*const volatile u64, @ptrCast(@alignCast(&data[i]))).*; 146 | flash_program_64(addr + i, data64); 147 | 148 | const addr64: *const volatile u64 = @ptrFromInt(addr + i); 149 | if (addr64.* != data64) { 150 | sys.debug.print("write failed, data check error!\r\n", .{}) catch {}; 151 | ret = Flash.FlashErr.ErrWrite; 152 | break; 153 | } 154 | } 155 | 156 | flash_lock(); 157 | return ret; 158 | } 159 | pub fn flash_read(self: *const Flash.Flash_Dev, addr: u32, data: []u8) Flash.FlashErr { 160 | _ = self; 161 | 162 | // read data from onchip flash 163 | for (data, 0..) |*d, i| { 164 | d.* = @as(*u8, @ptrFromInt(addr + i)).*; 165 | } 166 | return Flash.FlashErr.Ok; 167 | } 168 | 169 | const ops: Flash.FlashOps = .{ 170 | .init = &flash_init, 171 | .erase = &flash_earse, 172 | .write = &flash_write, 173 | .read = &flash_read, 174 | }; 175 | 176 | pub const chip_flash: Flash.Flash_Dev = .{ 177 | .name = "onchip", 178 | .start = 0x08000000, 179 | .len = 0x100000, 180 | .blocks = .{ 181 | .{ .size = 0x800, .count = 512 }, 182 | .{ .size = 0, .count = 0 }, 183 | .{ .size = 0, .count = 0 }, 184 | .{ .size = 0, .count = 0 }, 185 | .{ .size = 0, .count = 0 }, 186 | .{ .size = 0, .count = 0 }, 187 | }, 188 | .write_size = 8, 189 | .ops = &ops, 190 | }; 191 | -------------------------------------------------------------------------------- /src/chip/stm32l4/pin.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const regs = @import("regs.zig").devices.stm32l4.peripherals; 3 | const GPIO_Type = @import("regs.zig").types.peripherals.GPIOA; 4 | const RCC_Type = @import("regs.zig").types.peripherals.RCC; 5 | 6 | const hal = @import("../../hal/hal.zig"); 7 | const PinType = hal.pin.PinType; 8 | const Pin_Mode = hal.pin.Pin_Mode; 9 | const Pin_Level = hal.pin.Pin_Level; 10 | 11 | pub const ChipPinData = struct { 12 | port: *volatile GPIO_Type, 13 | pin: u8, 14 | }; 15 | 16 | // pin set mode 17 | fn set_mode(self: *const PinType, pin_mode: Pin_Mode) void { 18 | const port: *volatile GPIO_Type = @as(*volatile GPIO_Type, @ptrFromInt(self.data.port)); 19 | const pin: u8 = self.data.pin; 20 | if (pin_mode == Pin_Mode.Output) { 21 | var moder_raw: u32 = port.MODER.raw; // todo .read() 22 | moder_raw = moder_raw & ~std.math.shl(u32, 0b11, pin * 2); 23 | moder_raw = moder_raw | std.math.shl(u32, 0b01, pin * 2); 24 | port.MODER.write_raw(moder_raw); 25 | } else if (pin_mode == Pin_Mode.Input) { 26 | var moder_raw: u32 = port.MODER.raw; 27 | moder_raw = moder_raw & ~std.math.shl(u32, 0b11, pin * 2); 28 | port.MODER.raw = moder_raw; 29 | } 30 | } 31 | 32 | // pin write 33 | fn write(self: *const PinType, value: Pin_Level) void { 34 | const port: *volatile GPIO_Type = @as(*volatile GPIO_Type, @ptrFromInt(self.data.port)); 35 | const pin: u8 = self.data.pin; 36 | if (value == Pin_Level.High) { 37 | port.ODR.raw = port.ODR.raw | std.math.shl(u32, 1, pin); 38 | } else { 39 | port.ODR.raw = port.ODR.raw & ~std.math.shl(u32, 1, pin); 40 | } 41 | } 42 | 43 | // pin read 44 | fn read(self: *const PinType) Pin_Level { 45 | const port: *volatile GPIO_Type = @as(*volatile GPIO_Type, @ptrFromInt(self.data.port)); 46 | const pin: u8 = self.data.pin; 47 | const idr_raw = port.IDR.raw; 48 | const pin_set = std.math.shl(u32, 1, pin); 49 | if (idr_raw & pin_set != 0) { 50 | return Pin_Level.High; 51 | } else { 52 | return Pin_Level.Low; 53 | } 54 | } 55 | 56 | const ops: hal.pin.PinOps = .{ 57 | .mode = &set_mode, 58 | .write = &write, 59 | .read = &read, 60 | }; 61 | 62 | pub fn init(name: []const u8) !PinType { 63 | // parse name 64 | const port_num = name[1] - 'A'; 65 | const port_addr: u32 = @intFromPtr(regs.GPIOA) + 0x400 * @as(u32, port_num); 66 | const pin_num: u32 = try parseU32(name[2..]); 67 | 68 | const self: PinType = .{ .data = .{ .port = port_addr, .pin = @intCast(pin_num) }, .ops = &ops }; 69 | 70 | const port = @as(*volatile GPIO_Type, @ptrFromInt(port_addr)); 71 | const pin: u8 = @intCast(pin_num); 72 | 73 | // Enable PinX(A..) port 74 | var ahbenr_raw = regs.RCC.AHB2ENR.raw; 75 | ahbenr_raw = ahbenr_raw | std.math.shl(u32, 1, port_num); 76 | regs.RCC.AHB2ENR.raw = ahbenr_raw; 77 | // Enable PinX to output 78 | var moder_raw: u32 = port.MODER.raw; 79 | // todo: optimize 80 | moder_raw = moder_raw & ~std.math.shl(u32, 0b11, pin * 2); 81 | moder_raw = moder_raw | std.math.shl(u32, 0b01, pin * 2); 82 | port.MODER.raw = moder_raw; 83 | 84 | return self; 85 | } 86 | 87 | fn parseU32(input: []const u8) !u32 { 88 | var tmp: u32 = 0; 89 | for (input) |c| { 90 | if (c == 0) { 91 | break; 92 | } 93 | const digit = try std.fmt.charToDigit(c, 10); 94 | tmp = tmp * 10 + @as(u32, digit); 95 | } 96 | return tmp; 97 | } 98 | -------------------------------------------------------------------------------- /src/default_config.zig: -------------------------------------------------------------------------------- 1 | const zconfig = @import("platform/sys.zig").zconfig; 2 | const fal = @import("platform/fal/fal.zig"); 3 | 4 | const KiB = 1024; 5 | 6 | // default config 7 | pub const default_config = zconfig.ZbootConfig{ 8 | .magic = zconfig.ZBOOT_CONFIG_MAGIC, 9 | .uart = zconfig.UartConfig{ 10 | .enable = true, 11 | .tx = .{ 'P', 'A', '9', 0 }, 12 | }, 13 | .spiflash = zconfig.SpiFlashConfig{ 14 | .enable = true, 15 | .cs = .{ 'P', 'B', '1', '2' }, 16 | .sck = .{ 'P', 'B', '1', '3' }, 17 | .mosi = .{ 'P', 'C', '3', 0 }, 18 | .miso = .{ 'P', 'C', '2', 0 }, 19 | }, 20 | }; 21 | 22 | // default Partition 23 | pub const default_partition: [3]fal.partition.Partition = .{ .{ 24 | .magic_word = fal.partition.FAL_MAGIC_WORD, 25 | .name = .{ 'b', 'o', 'o', 't', 0, 0, 0, 0 }, 26 | .flash_name = .{ 'o', 'n', 'c', 'h', 'i', 'p', 0, 0 }, 27 | .offset = 0x00000000, 28 | .len = 0x00008000, 29 | .reserved = 0, 30 | }, .{ 31 | .magic_word = fal.partition.FAL_MAGIC_WORD, 32 | .name = .{ 'a', 'p', 'p', 0, 0, 0, 0, 0 }, 33 | .flash_name = .{ 'o', 'n', 'c', 'h', 'i', 'p', 0, 0 }, 34 | .offset = 0x00008000, 35 | .len = 0x00038000, 36 | .reserved = 0, 37 | }, .{ 38 | .magic_word = fal.partition.FAL_MAGIC_WORD, 39 | .name = .{ 's', 'w', 'a', 'p', 0, 0, 0, 0 }, 40 | .flash_name = .{ 'o', 'n', 'c', 'h', 'i', 'p', 0, 0 }, 41 | .offset = 0x00040000, 42 | .len = 0x00020000, 43 | .reserved = 0, 44 | } }; 45 | 46 | comptime { 47 | 48 | @export(&default_config, .{ 49 | .name = "default_config", 50 | .linkage = .strong, 51 | .section = "dconfig", 52 | .visibility = .default, 53 | }); 54 | 55 | @export(&default_partition, .{ 56 | .name = "default_partition", 57 | .linkage = .strong, 58 | .section = "fal", 59 | .visibility = .default, 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /src/hal/clock.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const cpu = @import("../chip/cortex-m.zig"); 4 | 5 | pub const ClockType = struct { 6 | // init the flash 7 | init: *const fn () void, 8 | // write the flash 9 | deinit: *const fn () void, 10 | // read the flash 11 | get_sysfreq: *const fn () u32, 12 | }; 13 | 14 | var clock: ClockType = undefined; 15 | var sysfreq: u32 = 0; 16 | 17 | pub fn init() void { 18 | if (@import("../hal/hal.zig").chip_series == @import("../hal/hal.zig").ChipSeriseType.STM32F4) { 19 | clock = @import("../chip/stm32f4/clock.zig").clock; 20 | 21 | clock.init(); 22 | sysfreq = clock.get_sysfreq(); 23 | } else if (@import("../hal/hal.zig").chip_series == @import("../hal/hal.zig").ChipSeriseType.STM32L4) { 24 | clock = @import("../chip/stm32l4/clock.zig").clock; 25 | 26 | clock.init(); 27 | sysfreq = clock.get_sysfreq(); 28 | } else if (@import("../hal/hal.zig").chip_series == @import("../hal/hal.zig").ChipSeriseType.STM32H7) { 29 | clock = @import("../chip/stm32h7/clock.zig").clock; 30 | 31 | clock.init(); 32 | sysfreq = clock.get_sysfreq(); 33 | } 34 | } 35 | 36 | pub fn deinit() void { 37 | clock.deinit(); 38 | } 39 | 40 | // delay_ms 41 | pub fn delay_ms(ms: u32) void { 42 | for (0..ms) |i| { 43 | _ = i; 44 | delay_us(1000); 45 | } 46 | } 47 | 48 | // delay us 49 | pub fn delay_us(us: u32) void { 50 | var ticks: u32 = 0; 51 | var start: u32 = 0; 52 | var current: u32 = 0; 53 | 54 | ticks = us * (sysfreq / 1000000); 55 | start = cpu.peripherals.SysTick.VAL.read().CURRENT; 56 | while (true) { 57 | current = cpu.peripherals.SysTick.VAL.read().CURRENT; 58 | if (start < current) { 59 | current = start + (0xFFFFFF - current); 60 | } else { 61 | current = start - current; 62 | } 63 | if (current > ticks) { 64 | break; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/hal/flash.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Flash = @import("../platform/fal/flash.zig"); 4 | const cpu = @import("../chip/cortex-m.zig"); 5 | 6 | pub var chip_flash: Flash.Flash_Dev = undefined; 7 | 8 | pub fn init() void { 9 | if (@import("../hal/hal.zig").chip_series == @import("../hal/hal.zig").ChipSeriseType.STM32F4) { 10 | chip_flash = @import("../chip/stm32f4/flash.zig").chip_flash; 11 | } else if (@import("../hal/hal.zig").chip_series == @import("../hal/hal.zig").ChipSeriseType.STM32L4) { 12 | chip_flash = @import("../chip/stm32l4/flash.zig").chip_flash; 13 | } else if (@import("../hal/hal.zig").chip_series == @import("../hal/hal.zig").ChipSeriseType.STM32H7) { 14 | chip_flash = @import("../chip/stm32h7/flash.zig").chip_flash; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/hal/hal.zig: -------------------------------------------------------------------------------- 1 | pub const pin = @import("pin.zig"); 2 | pub const uart = @import("uart.zig"); 3 | pub const spi = @import("spi.zig"); 4 | 5 | pub const clock = @import("clock.zig"); 6 | pub const flash = @import("flash.zig"); 7 | pub const cpu = @import("../chip/cortex-m.zig"); 8 | 9 | const mmio = @import("../chip/mmio.zig"); 10 | const mem = @import("std").mem; 11 | 12 | pub const CoreType = enum(u12) { 13 | M0 = 0xc20, // Cortex-M0 14 | M3 = 0xc23, // Cortex-M3 15 | M4 = 0xc24, // Cortex-M4 16 | M7 = 0xc27, // Cortex-M7 17 | M33 = 0xd21, // Cortex-M33 18 | M23 = 0xd20, // Cortex-M23 19 | M55 = 0xd22, // Cortex-M55 20 | }; 21 | 22 | pub const F4_PID = enum(u12) { 23 | STM32F40xxx_41xxx = 0x413, 24 | STM32F42xxx_43xxx = 0x419, 25 | STM32F401xB_C = 0x423, 26 | STM32F401xD_E = 0x433, 27 | STM32F410xx = 0x458, 28 | STM32F411xx = 0x431, 29 | STM32F412xx = 0x441, 30 | STM32F446xx = 0x421, 31 | STM32F469xx_479xx = 0x434, 32 | STM32F413xx_423xx = 0x463, 33 | }; 34 | 35 | pub const L4_PID = enum(u12) { 36 | STM32L412xx_422xx = 0x464, 37 | STM32L43xxx_44xxx = 0x435, 38 | STM32L45xxx_46xxx = 0x462, 39 | STM32L47xxx_48xxx = 0x415, 40 | STM32L496xx_4A6xx = 0x461, 41 | STM32L4Rxx_4Sxx = 0x470, 42 | STM32L4P5xx_Q5xx = 0x471, 43 | }; 44 | 45 | pub const H7_PID = enum(u12) { 46 | STM32H72xxx_73xxx = 0x483, 47 | STM32H74xxx_75xxx = 0x450, 48 | STM32H7A3xx_B3xx = 0x480, 49 | }; 50 | 51 | pub const ChipSeriseType = enum { 52 | STM32F4, 53 | STM32L4, 54 | STM32H7, 55 | }; 56 | 57 | const IDCODE = mmio.Mmio(packed struct(u32) { 58 | /// DEV_ID 59 | DEV_ID: u12, 60 | reserved16: u4, 61 | /// REV_ID 62 | REV_ID: u16, 63 | }); 64 | 65 | pub var chip_series: ChipSeriseType = undefined; 66 | pub var chip_flash_size: u32 = undefined; 67 | 68 | pub fn init() void { 69 | const core_type: CoreType = @enumFromInt(cpu.peripherals.SCB.CPUID.read().PARTNO); 70 | switch (core_type) { 71 | CoreType.M0 => {}, 72 | CoreType.M3 => {}, 73 | CoreType.M4 => { 74 | const idcode = @as(*volatile IDCODE, @ptrFromInt(0xe0042000)); 75 | const dev_id = idcode.read().DEV_ID; 76 | 77 | // if idcode match each F4 PID, then set chip_series to STM32F4 78 | if (dev_id == @intFromEnum(F4_PID.STM32F40xxx_41xxx) or 79 | dev_id == @intFromEnum(F4_PID.STM32F42xxx_43xxx) or 80 | dev_id == @intFromEnum(F4_PID.STM32F401xB_C) or 81 | dev_id == @intFromEnum(F4_PID.STM32F401xD_E) or 82 | dev_id == @intFromEnum(F4_PID.STM32F410xx) or 83 | dev_id == @intFromEnum(F4_PID.STM32F411xx) or 84 | dev_id == @intFromEnum(F4_PID.STM32F412xx) or 85 | dev_id == @intFromEnum(F4_PID.STM32F446xx) or 86 | dev_id == @intFromEnum(F4_PID.STM32F469xx_479xx) or 87 | dev_id == @intFromEnum(F4_PID.STM32F413xx_423xx)) 88 | { 89 | chip_series = ChipSeriseType.STM32F4; 90 | chip_flash_size = @as(*u16, @ptrFromInt(0x1FFF7A22)).*; 91 | } else if (dev_id == @intFromEnum(L4_PID.STM32L412xx_422xx) or 92 | dev_id == @intFromEnum(L4_PID.STM32L43xxx_44xxx) or 93 | dev_id == @intFromEnum(L4_PID.STM32L45xxx_46xxx) or 94 | dev_id == @intFromEnum(L4_PID.STM32L47xxx_48xxx) or 95 | dev_id == @intFromEnum(L4_PID.STM32L496xx_4A6xx) or 96 | dev_id == @intFromEnum(L4_PID.STM32L4Rxx_4Sxx) or 97 | dev_id == @intFromEnum(L4_PID.STM32L4P5xx_Q5xx)) 98 | { 99 | chip_series = ChipSeriseType.STM32L4; 100 | chip_flash_size = @as(*u16, @ptrFromInt(0x1FFF75E0)).*; 101 | } 102 | }, 103 | CoreType.M7 => { 104 | const idcode = @as(*volatile IDCODE, @ptrFromInt(0x5C001000)); 105 | const dev_id = idcode.read().DEV_ID; 106 | // if idcode match each H7 PID, then set chip_series to STM32H7 107 | if (dev_id == @intFromEnum(H7_PID.STM32H72xxx_73xxx) or 108 | dev_id == @intFromEnum(H7_PID.STM32H74xxx_75xxx) or 109 | dev_id == @intFromEnum(H7_PID.STM32H7A3xx_B3xx)) 110 | { 111 | chip_series = ChipSeriseType.STM32H7; 112 | chip_flash_size = @as(*u16, @ptrFromInt(0x1FF1E880)).*; 113 | } 114 | }, 115 | CoreType.M33 => {}, 116 | CoreType.M23 => {}, 117 | CoreType.M55 => {}, 118 | } 119 | clock.init(); 120 | flash.init(); 121 | flash.chip_flash.len = chip_flash_size * 1024; 122 | } 123 | -------------------------------------------------------------------------------- /src/hal/pin.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub const Pin_Mode = enum { Input, Output }; 4 | pub const Pin_Level = enum { Low, High }; 5 | 6 | pub const ChipPinData = struct { 7 | port: u32, 8 | pin: u8, 9 | }; 10 | 11 | pub const PinOps = struct { 12 | // init the flash 13 | mode: *const fn (self: *const PinType, pin_mode: Pin_Mode) void, 14 | // write the flash 15 | write: *const fn (self: *const PinType, value: Pin_Level) void, 16 | // read the flash 17 | read: *const fn (self: *const PinType) Pin_Level, 18 | }; 19 | 20 | pub const PinType = struct { 21 | data: ChipPinData, 22 | // ops for pin 23 | ops: *const PinOps, 24 | // set pin mode 25 | pub fn mode(self: *const @This(), pin_mode: Pin_Mode) void { 26 | self.ops.mode(self, pin_mode); 27 | } 28 | // write pin 29 | pub fn write(self: *const @This(), value: Pin_Level) void { 30 | self.ops.write(self, value); 31 | } 32 | // read pin 33 | pub fn read(self: *const @This()) Pin_Level { 34 | return self.ops.read(self); 35 | } 36 | }; 37 | 38 | pub fn Pin(name: []const u8) !PinType { 39 | if (@import("../hal/hal.zig").chip_series == @import("../hal/hal.zig").ChipSeriseType.STM32F4) { 40 | return @import("../chip/stm32f4/pin.zig").init(name); 41 | } else if (@import("../hal/hal.zig").chip_series == @import("../hal/hal.zig").ChipSeriseType.STM32L4) { 42 | return @import("../chip/stm32l4/pin.zig").init(name); 43 | } else if (@import("../hal/hal.zig").chip_series == @import("../hal/hal.zig").ChipSeriseType.STM32H7) { 44 | return @import("../chip/stm32h7/pin.zig").init(name); 45 | } 46 | return error.InvalidChipSeries; 47 | } 48 | -------------------------------------------------------------------------------- /src/hal/spi.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const hal = @import("hal.zig"); 4 | const sys = @import("../platform/sys.zig"); 5 | 6 | pub const Spi_Mode = enum { Input, Output }; 7 | pub const Spi_Level = enum { Low, High }; 8 | 9 | pub const SpiOps = struct { 10 | // write and read 11 | wr: *const fn (self: *const SpiType, write_buf: ?[]const u8, read_buf: ?[]u8) bool, 12 | }; 13 | 14 | pub const SpiType = struct { 15 | // spi pin 16 | mosi: hal.pin.PinType, 17 | miso: hal.pin.PinType, 18 | sck: hal.pin.PinType, 19 | cs: hal.pin.PinType, 20 | // ops for pin 21 | ops: *const SpiOps, 22 | // write and read 23 | pub fn wr(self: *const @This(), write_buf: ?[]const u8, read_buf: ?[]u8) bool { 24 | return self.ops.wr(self, write_buf, read_buf); 25 | } 26 | }; 27 | 28 | // write and read one byte 29 | fn wr_byte(self: *const SpiType, write_byte: u8) u8 { 30 | var i: u8 = 0; 31 | var read_byte: u8 = 0; 32 | while (i < 8) : (i += 1) { 33 | if (write_byte & (std.math.shr(u8, 0x80, i)) != 0) { 34 | self.mosi.write(hal.pin.Pin_Level.High); 35 | } else { 36 | self.mosi.write(hal.pin.Pin_Level.Low); 37 | } 38 | self.sck.write(hal.pin.Pin_Level.High); 39 | 40 | if (self.miso.read() == hal.pin.Pin_Level.High) { 41 | read_byte |= (std.math.shr(u8, 0x80, i)); 42 | } 43 | 44 | self.sck.write(hal.pin.Pin_Level.Low); 45 | } 46 | return read_byte; 47 | } 48 | 49 | pub fn wr(self: *const SpiType, write_buf: ?[]const u8, read_buf: ?[]u8) bool { 50 | // start transfer 51 | self.cs.write(hal.pin.Pin_Level.Low); 52 | 53 | // write then read 54 | if (write_buf != null) { 55 | const write_pos = write_buf.?; 56 | for (write_pos) |byte| { 57 | _ = wr_byte(self, byte); 58 | } 59 | } 60 | if (read_buf != null) { 61 | const read_pos = read_buf.?; 62 | var i: usize = 0; 63 | while (i < read_pos.len) : (i += 1) { 64 | read_pos[i] = wr_byte(self, 0xFF); 65 | } 66 | } 67 | // end transfer 68 | self.cs.write(hal.pin.Pin_Level.High); 69 | 70 | return true; 71 | } 72 | const ops: SpiOps = .{ 73 | .wr = &wr, 74 | }; 75 | 76 | pub fn Spi(mosi: []const u8, miso: []const u8, sck: []const u8, cs: []const u8) !SpiType { 77 | const self: SpiType = SpiType{ 78 | .mosi = try hal.pin.Pin(mosi), 79 | .miso = try hal.pin.Pin(miso), 80 | .sck = try hal.pin.Pin(sck), 81 | .cs = try hal.pin.Pin(cs), 82 | .ops = &ops, 83 | }; 84 | self.miso.mode(hal.pin.Pin_Mode.Input); 85 | 86 | self.cs.write(hal.pin.Pin_Level.High); 87 | self.sck.write(hal.pin.Pin_Level.Low); 88 | return self; 89 | } 90 | -------------------------------------------------------------------------------- /src/hal/uart.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const hal = @import("hal.zig"); 3 | 4 | const DEALY_US = 8; // 9600: 104; 115200:8 5 | 6 | // soft uart 7 | const Uart = struct { 8 | tx: hal.pin.PinType, 9 | 10 | pub fn init(name: []const u8) !@This() { 11 | var self = Uart{ .tx = try hal.pin.Pin(name) }; 12 | self.tx.write(hal.pin.Pin_Level.High); 13 | hal.clock.delay_us(DEALY_US); 14 | 15 | return self; 16 | } 17 | 18 | pub fn write(self: *const @This(), data: u8) void { 19 | var i: u8 = 0; 20 | var mask: u8 = 0x01; 21 | 22 | // start bit 23 | self.tx.write(hal.pin.Pin_Level.Low); 24 | hal.clock.delay_us(DEALY_US); 25 | 26 | // send data 27 | while (i < 8) { 28 | if ((data & mask) == mask) { 29 | self.tx.write(hal.pin.Pin_Level.High); 30 | } else { 31 | self.tx.write(hal.pin.Pin_Level.Low); 32 | } 33 | hal.clock.delay_us(DEALY_US); 34 | mask <<= 1; 35 | i += 1; 36 | } 37 | 38 | // stop bit 39 | self.tx.write(hal.pin.Pin_Level.High); 40 | hal.clock.delay_us(DEALY_US); 41 | } 42 | }; 43 | 44 | pub const WriteError = error{}; 45 | pub const ReadError = error{ 46 | EndOfStream, 47 | TooFewBytesReceived, 48 | TooManyBytesReceived, 49 | }; 50 | 51 | pub fn UartDebug() type { 52 | const SystemUart = Uart; 53 | return struct { 54 | const Self = @This(); 55 | 56 | internal: SystemUart, 57 | 58 | /// Initializes the UART with the given config and returns a handle to the uart. 59 | pub fn init(name: []const u8) !Self { 60 | return Self{ 61 | .internal = try SystemUart.init(name), 62 | }; 63 | } 64 | 65 | pub fn reader(self: Self) Reader { 66 | return Reader{ .context = self }; 67 | } 68 | 69 | pub fn writer(self: Self) Writer { 70 | return Writer{ .context = self }; 71 | } 72 | 73 | pub const Reader = std.io.Reader(Self, ReadError, read_some); 74 | pub const Writer = std.io.Writer(Self, WriteError, write_some); 75 | 76 | fn read_some(self: Self, buffer: []u8) ReadError!usize { 77 | _ = self; 78 | for (buffer) |*c| { 79 | _ = c; 80 | // c.* = self.internal.rx(); 81 | } 82 | return buffer.len; 83 | } 84 | fn write_some(self: Self, buffer: []const u8) WriteError!usize { 85 | for (buffer) |c| { 86 | self.internal.write(c); 87 | } 88 | return buffer.len; 89 | } 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const hal = @import("hal/hal.zig"); 3 | 4 | const sys = @import("platform/sys.zig"); 5 | const fal = @import("platform/fal/fal.zig"); 6 | const sfud = @import("platform/sfud/sfud.zig"); 7 | const ota = @import("platform/ota/ota.zig"); 8 | 9 | const timestamp = @import("timestamp"); 10 | 11 | pub fn show_logo() void { 12 | sys.debug.print("\r\n", .{}) catch {}; 13 | sys.debug.print("{s}\r\n", .{" _____ __ __ "}) catch {}; 14 | sys.debug.print("{s}\r\n", .{" /__ / / /_ ____ ____ / /_"}) catch {}; 15 | sys.debug.print("{s}\r\n", .{" / / / __ \\/ __ \\/ __ \\/ __/"}) catch {}; 16 | sys.debug.print("{s}\r\n", .{" / /__/ /_/ / /_/ / /_/ / /_ "}) catch {}; 17 | sys.debug.print("{s}\r\n", .{" /____/\\____/\\____/\\____/\\__/ "}) catch {}; 18 | sys.debug.print(" Build Time: {} \r\n", .{timestamp.time_stamp}) catch {}; 19 | } 20 | var APP_ENTRY_ADDR: usize = 0x08008000; 21 | const APP_RAM_ADDR = 0x20000000; 22 | const APP_RAM_SIZE = 0x00010000; 23 | 24 | pub fn jump_app() void { 25 | const app = fal.partition.find("app").?; 26 | 27 | const ret = ota.get_fw_info(app, true); 28 | if (ret == null) { 29 | sys.debug.print("unkown app\r\n", .{}) catch {}; 30 | } else { 31 | const ota_fw_info = ret.?; 32 | sys.debug.print("find app version:{s}\r\n", .{ota_fw_info.version}) catch {}; 33 | } 34 | 35 | const flash = fal.flash.find(app.flash_name[0..]).?; 36 | APP_ENTRY_ADDR = flash.start + app.offset; 37 | 38 | const app_stack_addr = @as(*u32, @ptrFromInt(APP_ENTRY_ADDR)).*; 39 | // Check stack address 40 | if (app_stack_addr < APP_RAM_ADDR or app_stack_addr > APP_RAM_ADDR + APP_RAM_SIZE) { 41 | sys.debug.print("Can't find app @0x{x}\r\n", .{APP_ENTRY_ADDR}) catch {}; 42 | return; 43 | } 44 | const jump_addr = @as(*u32, @ptrFromInt(APP_ENTRY_ADDR + 4)).*; 45 | const jump2app: *const fn () void = @ptrFromInt(jump_addr); 46 | 47 | // sys.debug.print("jump to app, offset:0x{x}, addr:0x{x}\r\n", .{ APP_ENTRY_ADDR, jump_addr }) catch {}; 48 | 49 | hal.clock.deinit(); 50 | jump2app(); 51 | } 52 | 53 | export fn main() noreturn { 54 | hal.init(); 55 | sys.zconfig.probe_extconfig(sys.get_rom_end()); 56 | const zboot_config = sys.zconfig.get_config(); 57 | if (zboot_config.uart.enable) { 58 | sys.init_debug(zboot_config.uart.tx[0..]) catch {}; 59 | show_logo(); 60 | } 61 | if (zboot_config.spiflash.enable) { 62 | const spi = hal.spi.Spi(zboot_config.spiflash.mosi[0..], zboot_config.spiflash.miso[0..], zboot_config.spiflash.sck[0..], zboot_config.spiflash.cs[0..]) catch unreachable; 63 | const spiflash = sfud.flash.probe("spiflash", spi); 64 | // dump spiflash info 65 | if (spiflash) |flash| { 66 | _ = flash.init(); 67 | } else { 68 | sys.debug.print("spiflash not find\r\n", .{}) catch {}; 69 | } 70 | } 71 | fal.init(); 72 | 73 | // ota check app crc 74 | // if (ota.checkFW("app") == false) { 75 | // sys.debug.print("app check failed\r\n", .{}) catch {}; 76 | // } 77 | 78 | try ota.swap(); 79 | jump_app(); 80 | 81 | while (true) {} 82 | } 83 | -------------------------------------------------------------------------------- /src/platform/fal/fal.zig: -------------------------------------------------------------------------------- 1 | pub const partition = @import("partition.zig"); 2 | pub const flash = @import("flash.zig"); 3 | 4 | const dconfig = @import("../../default_config.zig"); 5 | const sys = @import("../sys.zig"); 6 | 7 | // fal init 8 | pub fn init() void { 9 | const onchip = flash.find("onchip").?; 10 | _ = onchip.init(); 11 | partition.init(sys.get_rom_end()); 12 | if (partition.partition_table.num == 0) { 13 | sys.debug.print("partition table not find, use default partition\r\n", .{}) catch {}; 14 | // load default partition 15 | partition.init(@intFromPtr(&dconfig.default_partition)); 16 | } 17 | partition.print(); 18 | } 19 | -------------------------------------------------------------------------------- /src/platform/fal/flash.zig: -------------------------------------------------------------------------------- 1 | const sys = @import("../sys.zig"); 2 | const mem = @import("std").mem; 3 | 4 | const Debug: bool = false; 5 | 6 | // flash err code 7 | pub const FlashErr = enum(u8) { 8 | Ok = 0, 9 | Err = 1, 10 | OutOfBound = 2, 11 | Busy = 3, 12 | Transfer = 4, 13 | ErrRead = 5, 14 | ErrErase = 6, 15 | ErrWrite = 7, 16 | }; 17 | 18 | pub const FlashOps = struct { 19 | // init the flash 20 | init: ?*const fn (self: *const Flash_Dev) FlashErr, 21 | // erase the flash block 22 | erase: ?*const fn (self: *const Flash_Dev, addr: u32, size: u32) FlashErr, 23 | // write the flash 24 | write: ?*const fn (self: *const Flash_Dev, addr: u32, data: []const u8) FlashErr, 25 | // read the flash 26 | read: *const fn (self: *const Flash_Dev, addr: u32, data: []u8) FlashErr, 27 | }; 28 | 29 | pub const Flash_Dev = struct { 30 | name: []const u8, 31 | // flash device start address and len 32 | start: u32, 33 | len: u32, 34 | // the block size in the flash for erase minimum granularity 35 | blocks: [6]struct { 36 | // block size 37 | size: u32, 38 | // block count 39 | count: u32, 40 | }, 41 | // write minimum granularity, unit: bit. 42 | // 1(nor flash)/ 8(stm32f2/f4)/ 32(stm32f1)/ 64(stm32l4) 43 | // 0 will not take effect. 44 | write_size: u32, 45 | // ops for flash 46 | ops: *const FlashOps, 47 | pub fn init(self: *const @This()) FlashErr { 48 | sys.debug.print("Flash[{s}]:size:0x{x}\r\n", .{ self.name, self.len }) catch {}; 49 | if (self.ops.init) |init_fn| { 50 | return init_fn(self); 51 | } 52 | return FlashErr.Ok; 53 | } 54 | pub fn erase(self: *const @This(), addr: u32, size: u32) FlashErr { 55 | if (Debug) { 56 | sys.debug.print("flash_earse:addr:0x{x},size:0x{x}\r\n", .{ addr, size }) catch {}; 57 | } 58 | if (self.ops.erase) |erase_fn| { 59 | return erase_fn(self, addr, size); 60 | } 61 | return FlashErr.Ok; 62 | } 63 | pub fn write(self: *const @This(), addr: u32, data: []const u8) FlashErr { 64 | if (Debug) { 65 | sys.debug.print("flash_write:addr:0x{x}, data_ptr:0x{x}, size:{d} \r\n", .{ addr, @intFromPtr(data.ptr), data.len }) catch {}; 66 | // dump data 67 | for (data, 0..) |*d, i| { 68 | if (i % 16 == 0) { 69 | sys.debug.print("0x{x:0>8}:", .{addr + i}) catch {}; 70 | } 71 | sys.debug.print(" {x:0>2}", .{d.*}) catch {}; 72 | if (i % 16 == 15) { 73 | sys.debug.print("\r\n", .{}) catch {}; 74 | } 75 | } 76 | sys.debug.print("\r\n", .{}) catch {}; 77 | } 78 | if (self.ops.write) |write_fn| { 79 | return write_fn(self, addr, data); 80 | } 81 | return FlashErr.Ok; 82 | } 83 | pub fn read(self: *const @This(), addr: u32, data: []u8) FlashErr { 84 | if (Debug) { 85 | sys.debug.print("flash_read:addr:0x{x}, data_ptr:0x{x}, size:{x}\r\n", .{ addr, @intFromPtr(data.ptr), data.len }) catch {}; 86 | } 87 | const err = self.ops.read(self, addr, data); 88 | 89 | // dump data 90 | if (Debug) { 91 | for (data, 0..) |d, i| { 92 | if (i % 16 == 0) { 93 | sys.debug.print("0x{x:0>8}:", .{addr + i}) catch {}; 94 | } 95 | sys.debug.print(" {x:0>2}", .{d}) catch {}; 96 | if (i % 16 == 15) { 97 | sys.debug.print("\r\n", .{}) catch {}; 98 | } 99 | } 100 | sys.debug.print("\r\n", .{}) catch {}; 101 | } 102 | return err; 103 | } 104 | }; 105 | 106 | // flash list 107 | const flash_list: [2]*const Flash_Dev = .{ 108 | &@import("../../hal/hal.zig").flash.chip_flash, 109 | &@import("../sfud/sfud.zig").flash.spi_flash, 110 | }; 111 | 112 | // flash find 113 | pub fn find(name: []const u8) ?*const Flash_Dev { 114 | // find flash by name 115 | for (flash_list) |f| { 116 | if (mem.eql(u8, name[0..f.name.len], f.name[0..f.name.len])) { 117 | return f; 118 | } 119 | } 120 | 121 | return null; 122 | } 123 | -------------------------------------------------------------------------------- /src/platform/fal/partition.zig: -------------------------------------------------------------------------------- 1 | const mem = @import("std").mem; 2 | const sys = @import("../sys.zig"); 3 | const hal = @import("../../hal/hal.zig"); 4 | const fal = @import("fal.zig"); 5 | 6 | pub const FAL_MAGIC_WORD = 0x45503130; 7 | const FAL_MAGIC_WORD_L = 0x3130; 8 | const FAL_MAGIC_WORD_H = 0x4550; 9 | 10 | pub const partition_table_MAX = 8; 11 | const FAL_PATITION_SIZE_MAX = @sizeOf(Partition) * partition_table_MAX; 12 | pub const FAL_DEV_NAME_MAX = 8; 13 | 14 | pub const Partition = extern struct { 15 | magic_word: u32, 16 | name: [FAL_DEV_NAME_MAX]u8, 17 | flash_name: [FAL_DEV_NAME_MAX]u8, 18 | offset: u32, 19 | len: u32, 20 | reserved: u32, 21 | }; 22 | 23 | const FalPartition = struct { 24 | num: u32, 25 | partition: [partition_table_MAX]Partition, 26 | }; 27 | pub var partition_table: FalPartition = .{ .num = 0, .partition = undefined }; 28 | 29 | pub fn find(name: []const u8) ?*const Partition { 30 | var i: u32 = 0; 31 | while (i < partition_table.num) : (i += 1) { 32 | const partition = &partition_table.partition[i]; 33 | if (mem.eql(u8, name, partition.name[0..name.len])) { 34 | return partition; 35 | } 36 | } 37 | return null; 38 | } 39 | 40 | // partition init 41 | // find flash flash, init partition_table 42 | pub fn init(start_offset: u32) void { 43 | const flash = fal.flash.find("onchip").?; 44 | 45 | // find magic word 46 | var magic_word: u32 = 0; 47 | const slice = @as([*]u8, @ptrCast(&magic_word))[0..(@sizeOf(u32))]; 48 | var partition_start: u32 = start_offset; 49 | var part_is_find: bool = false; 50 | while (partition_start < start_offset + FAL_PATITION_SIZE_MAX) : (partition_start += 1) { 51 | _ = flash.read(partition_start, slice); 52 | if (magic_word == FAL_MAGIC_WORD) { 53 | part_is_find = true; 54 | break; 55 | } 56 | } 57 | partition_table.num = 0; 58 | // load partition 59 | while (part_is_find) { 60 | const part_new = @as([*]u8, @ptrCast(&partition_table.partition[partition_table.num]))[0..(@sizeOf(Partition))]; 61 | 62 | _ = flash.read(partition_start, part_new); 63 | // check magic word 64 | if (partition_table.partition[partition_table.num].magic_word != FAL_MAGIC_WORD) { 65 | break; 66 | } 67 | // check partition flash 68 | const new_flash = fal.flash.find(partition_table.partition[partition_table.num].flash_name[0..]); 69 | if (new_flash == null) { 70 | sys.debug.print("flash {s} not find\r\n", .{partition_table.partition[partition_table.num].flash_name}) catch {}; 71 | } 72 | // TODO check partition addr and len 73 | 74 | partition_table.num += 1; 75 | partition_start += @sizeOf(Partition); 76 | } 77 | } 78 | // print the partition table 79 | pub fn print() void { 80 | var i: u32 = 0; 81 | while (i < partition_table.num) { 82 | const partition = &partition_table.partition[i]; 83 | sys.debug.print("partition: {d}, name: {s}, flash_name: {s}, offset: {x}, len: {x}\r\n", .{ i, partition.name, partition.flash_name, partition.offset, partition.len }) catch {}; 84 | i += 1; 85 | } 86 | } 87 | 88 | // partition erase 89 | pub fn erase(partition: *const Partition, offset: u32, len: u32) void { 90 | const flash = fal.flash.find(partition.flash_name[0..]).?; 91 | _ = flash.erase(flash.start + partition.offset + offset, len); 92 | } 93 | 94 | // partition write 95 | pub fn write(partition: *const Partition, offset: u32, data: []const u8) void { 96 | const flash = fal.flash.find(partition.flash_name[0..]).?; 97 | _ = flash.write(flash.start + partition.offset + offset, data); 98 | } 99 | 100 | // partition read 101 | // todo add return 102 | pub fn read(partition: *const Partition, offset: u32, data: []u8) void { 103 | const flash = fal.flash.find(partition.flash_name[0..]).?; 104 | _ = flash.read(flash.start + partition.offset + offset, data); 105 | } 106 | 107 | pub fn test_flash() void { 108 | const flash = fal.partition.find("app").?; 109 | var data: [1024]u8 = undefined; 110 | var data_read: [1024]u8 = undefined; 111 | var i: u32 = 0; 112 | while (i < 1024) : (i += 1) { 113 | data[i] = @intCast(i); 114 | } 115 | fal.partition.erase(flash, 0, 1024); 116 | fal.partition.write(flash, 0, data[0..]); 117 | fal.partition.read(flash, 0, data_read[0..]); 118 | i = 0; 119 | while (i < 1024) : (i += 1) { 120 | if (data[i] != data_read[i]) { 121 | sys.debug.print("fal test fail\r\n", .{}) catch {}; 122 | return; 123 | } 124 | } 125 | sys.debug.print("fal test success\r\n", .{}) catch {}; 126 | } 127 | -------------------------------------------------------------------------------- /src/platform/ota/ota.zig: -------------------------------------------------------------------------------- 1 | const sys = @import("../sys.zig"); 2 | const fal = @import("../fal/fal.zig"); 3 | const std = @import("std"); 4 | 5 | const mem = @import("std").mem; 6 | 7 | const QBOOT_FASTLZ_BLOCK_HDR_SIZE = 4; 8 | 9 | // ota header 10 | pub const Ota_FW_Info = extern struct { 11 | magic: [4]u8, 12 | algo: Ota_Algo, 13 | algo2: Ota_Algo, 14 | time_stamp: u32, 15 | name: [16]u8, 16 | version: [24]u8, 17 | sn: [24]u8, 18 | body_crc: u32, 19 | hash_code: u32, 20 | raw_size: u32, 21 | pkg_size: u32, 22 | hdr_crc: u32, 23 | }; 24 | 25 | // zboot algo 26 | pub const Ota_Algo = enum(u16) { 27 | NONE = 0x0, 28 | XOR = 0x1, 29 | AES256 = 0x2, 30 | 31 | GZIP = 0x100, 32 | QUICKLZ = 0x200, 33 | FASTLZ = 0x300, 34 | 35 | CRYPT_STAT_MASK = 0xF, 36 | CMPRS_STAT_MASK = 0xF00, 37 | }; 38 | 39 | // print ota header 40 | pub fn fw_info_print(fw_info: *const volatile Ota_FW_Info) void { 41 | // check ota_fw_info 42 | if (fw_info.magic[0] != 'R' or fw_info.magic[1] != 'B' or fw_info.magic[2] != 'L') { 43 | sys.debug.print("magic not match\r\n", .{}) catch {}; 44 | return; 45 | } 46 | sys.debug.print("magic:{s}\r\n", .{fw_info.magic}) catch {}; 47 | switch (fw_info.algo) { 48 | Ota_Algo.NONE => sys.debug.writeAll("algo:NONE\r\n") catch {}, 49 | Ota_Algo.XOR => sys.debug.writeAll("algo:XOR\r\n") catch {}, 50 | Ota_Algo.AES256 => sys.debug.writeAll("algo:AES256\r\n") catch {}, 51 | else => sys.debug.writeAll("algo:unknown\r\n") catch {}, 52 | } 53 | switch (fw_info.algo2) { 54 | Ota_Algo.NONE => sys.debug.writeAll("algo2:NONE\r\n") catch {}, 55 | Ota_Algo.XOR => sys.debug.writeAll("algo2:XOR\r\n") catch {}, 56 | Ota_Algo.AES256 => sys.debug.writeAll("algo2:AES256\r\n") catch {}, 57 | else => sys.debug.writeAll("algo2:unknown\r\n") catch {}, 58 | } 59 | sys.debug.print("time_stamp:{d}\r\n", .{fw_info.time_stamp}) catch {}; 60 | sys.debug.print("name:{s}\r\n", .{fw_info.name}) catch {}; 61 | sys.debug.print("version:{s}\r\n", .{fw_info.version}) catch {}; 62 | sys.debug.print("sn:{s}\r\n", .{fw_info.sn}) catch {}; 63 | sys.debug.print("body_crc:{x}\r\n", .{fw_info.body_crc}) catch {}; 64 | sys.debug.print("hash_code:{x}\r\n", .{fw_info.hash_code}) catch {}; 65 | sys.debug.print("raw_size:{d}\r\n", .{fw_info.raw_size}) catch {}; 66 | sys.debug.print("pkg_size:{d}\r\n", .{fw_info.pkg_size}) catch {}; 67 | sys.debug.print("hdr_crc:{x}\r\n", .{fw_info.hdr_crc}) catch {}; 68 | } 69 | 70 | fn strlen(str: []const u8) u32 { 71 | var i: u32 = 0; 72 | while (str[i] != 0) : (i += 1) {} 73 | return i; 74 | } 75 | const table = [_]u32{ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; 76 | 77 | // Calculate the CRC32 value of a memory buffer. 78 | pub fn crc32(crc_input: u32, buf: []const u8) u32 { 79 | const init: u32 = 0xFFFFFFFF; 80 | var crc = crc_input ^ init; 81 | 82 | for (buf) |byte| { 83 | crc = table[(crc ^ byte) & 0xFF] ^ (crc >> 8); 84 | } 85 | 86 | return crc ^ init; 87 | } 88 | 89 | // ota check: check ota header 90 | pub fn get_fw_info(part: *const fal.partition.Partition, tail: bool) ?Ota_FW_Info { 91 | var ota_fw_info: Ota_FW_Info = undefined; 92 | var slice_info = @as([*]u8, @ptrCast(&ota_fw_info))[0..(@sizeOf(Ota_FW_Info))]; 93 | 94 | if (tail) { 95 | fal.partition.read(part, part.len - @sizeOf(Ota_FW_Info), slice_info[0..]); 96 | } else { 97 | fal.partition.read(part, 0, slice_info[0..]); 98 | } 99 | 100 | // check ota_fw_info 101 | if (ota_fw_info.magic[0] != 'R' or ota_fw_info.magic[1] != 'B' or ota_fw_info.magic[2] != 'L') { 102 | // sys.debug.print("magic not match\r\n", .{}) catch {}; 103 | return null; 104 | } 105 | // check hdr_crc 106 | const crc = crc32(0, slice_info[0..(@sizeOf(Ota_FW_Info) - @sizeOf(u32))]); 107 | if (crc != ota_fw_info.hdr_crc) { 108 | sys.debug.print("hdr_crc not match\r\n", .{}) catch {}; 109 | // print hdr_crc and crc 110 | sys.debug.print("hdr_crc:{x}, crc:{x}\r\n", .{ ota_fw_info.hdr_crc, crc }) catch {}; 111 | return null; 112 | } 113 | 114 | return ota_fw_info; 115 | } 116 | 117 | // Calculate the hash value of a memory buffer. 118 | pub fn calc_hash(hash_input: u32, buf: []const u8) u32 { 119 | var hash = hash_input; 120 | 121 | for (buf) |byte| { 122 | hash = (byte ^ hash) * 0x01000193; 123 | } 124 | 125 | return hash; 126 | } 127 | 128 | pub const ZBOOT_HASH_FNV_SEED: u32 = 0x811C9DC5; 129 | 130 | // check partition fw hash 131 | fn checkHash(name: []const u8) bool { 132 | var buf: [1024]u8 = undefined; 133 | var offset: u32 = 0; 134 | 135 | const part = fal.partition.find(name).?; 136 | 137 | const ota_fw_info = get_fw_info(part, true); 138 | if (ota_fw_info == null) { 139 | sys.debug.print("get_fw_info failed\r\n", .{}) catch {}; 140 | return false; 141 | } 142 | const len = ota_fw_info.?.raw_size; 143 | // check hash_code 144 | var hash: u32 = ZBOOT_HASH_FNV_SEED; 145 | while (offset < len) : (offset += 1024) { 146 | const read_size = len - offset; 147 | if (read_size > 1024) { 148 | fal.partition.read(part, offset, buf[0..]); 149 | hash = calc_hash(hash, buf[0..]); 150 | } else { 151 | fal.partition.read(part, offset, buf[0..read_size]); 152 | hash = calc_hash(hash, buf[0..read_size]); 153 | } 154 | } 155 | if (hash != ota_fw_info.?.hash_code) { 156 | sys.debug.print("hash_code not match\r\n", .{}) catch {}; 157 | // print hash_code and hash 158 | sys.debug.print("hash_code:{x}, hash:{x}\r\n", .{ ota_fw_info.?.hash_code, hash }) catch {}; 159 | return false; 160 | } 161 | 162 | return true; 163 | } 164 | 165 | // check partition crc 166 | fn checkCrc(name: []const u8) bool { 167 | var buf: [1024]u8 = undefined; 168 | var offset: u32 = 0; 169 | 170 | const part = fal.partition.find(name).?; 171 | 172 | const ota_fw_info = get_fw_info(part, false); 173 | if (ota_fw_info == null) { 174 | sys.debug.print("get_fw_info failed\r\n", .{}) catch {}; 175 | return false; 176 | } 177 | var len = ota_fw_info.?.pkg_size; 178 | // check body_crc 179 | var crc: u32 = 0; 180 | offset = @sizeOf(Ota_FW_Info); 181 | len += offset; 182 | while (offset < len) : (offset += 1024) { 183 | const read_size = len - offset; 184 | if (read_size > 1024) { 185 | fal.partition.read(part, offset, buf[0..]); 186 | crc = crc32(crc, buf[0..]); 187 | } else { 188 | fal.partition.read(part, offset, buf[0..read_size]); 189 | crc = crc32(crc, buf[0..read_size]); 190 | } 191 | } 192 | if (crc != ota_fw_info.?.body_crc) { 193 | sys.debug.print("body_crc not match\r\n", .{}) catch {}; 194 | // print body_crc and crc 195 | sys.debug.print("body_crc:{x}, crc:{x}\r\n", .{ ota_fw_info.?.body_crc, crc }) catch {}; 196 | return false; 197 | } 198 | 199 | return true; 200 | } 201 | 202 | // check fw in partition, check crc in swap, and check hash in other partition 203 | pub fn checkFW(name: []const u8) bool { 204 | if (mem.eql(u8, name[0..], "swap")) { 205 | return checkCrc(name); 206 | } else { 207 | return checkHash(name); 208 | } 209 | } 210 | 211 | // ota swap: move swap to target partition 212 | pub fn swap() !void { 213 | const part = fal.partition.find("swap").?; 214 | const ret = get_fw_info(part, false); 215 | if (ret == null) { 216 | return; 217 | } 218 | const ota_fw_info = ret.?; 219 | 220 | // check swap partition crc 221 | if (checkCrc("swap") == false) { 222 | sys.debug.print("swap partition crc check failed\r\n", .{}) catch {}; 223 | return; 224 | } 225 | 226 | // check app partition 227 | const part_target = fal.partition.find(@volatileCast(ota_fw_info.name[0..strlen(@volatileCast(ota_fw_info.name[0..]))])).?; 228 | const ret2 = get_fw_info(part_target, true); 229 | if (ret2 == null) { 230 | sys.debug.print("not find ota_fw_info in target partition\r\n", .{}) catch {}; 231 | sys.debug.print("swap [{s}] {s} => {s}\r\n", .{ ota_fw_info.name, "unkown", ota_fw_info.version }) catch {}; 232 | } else { 233 | const ota_fw_info_target = ret2.?; 234 | 235 | // check app 236 | // ota check app crc 237 | if (checkFW("app") == false) { 238 | sys.debug.print("app check failed\r\n", .{}) catch {}; 239 | } else if (mem.eql(u8, ota_fw_info_target.version[0..], ota_fw_info.version[0..])) { 240 | // if new version is same as old version, don't swap 241 | sys.debug.print("version is same, don't need swap\r\n", .{}) catch {}; 242 | return; 243 | } 244 | 245 | sys.debug.print("swap [{s}] {s} => {s}\r\n", .{ ota_fw_info.name, ota_fw_info_target.version, ota_fw_info.version }) catch {}; 246 | } 247 | 248 | if (@intFromEnum(ota_fw_info.algo) & @intFromEnum(Ota_Algo.CMPRS_STAT_MASK) != 0) { 249 | const algo1 = @intFromEnum(ota_fw_info.algo) & @intFromEnum(Ota_Algo.CMPRS_STAT_MASK); 250 | switch (@as(Ota_Algo, @enumFromInt(algo1))) { 251 | Ota_Algo.GZIP => sys.debug.print("algo:GZIP\r\n", .{}) catch {}, 252 | Ota_Algo.QUICKLZ => sys.debug.print("algo:QUICKLZ\r\n", .{}) catch {}, 253 | Ota_Algo.FASTLZ => sys.debug.print("algo:FASTLZ\r\n", .{}) catch {}, 254 | else => sys.debug.print("algo:unknown\r\n", .{}) catch {}, 255 | } 256 | } 257 | // fastlz decompress 258 | if (@intFromEnum(ota_fw_info.algo) & @intFromEnum(Ota_Algo.CMPRS_STAT_MASK) == @intFromEnum(Ota_Algo.FASTLZ)) { 259 | sys.debug.print("fastlz decompress\r\n", .{}) catch {}; 260 | 261 | // write decompress data to target partition 262 | fal.partition.erase(part_target, 0, part_target.len); 263 | var read_pos: usize = @sizeOf(Ota_FW_Info); 264 | var write_pos: usize = 0; 265 | while (read_pos < @sizeOf(Ota_FW_Info) + ota_fw_info.pkg_size) { 266 | var block_header: [QBOOT_FASTLZ_BLOCK_HDR_SIZE]u8 = undefined; 267 | var buffer: [1024 * 4]u8 = undefined; 268 | var o_buf: [1024 * 4]u8 = undefined; 269 | 270 | // read block size 271 | fal.partition.read(part, read_pos, block_header[0..]); 272 | read_pos += block_header.len; 273 | 274 | // decompress the buffer 275 | const blk_size = fastlz_get_block_size(block_header[0..]); 276 | if (blk_size <= 0) { 277 | sys.debug.print("blk_size error: {d}\r\n", .{blk_size}) catch {}; 278 | return; 279 | } 280 | sys.debug.print("=", .{}) catch {}; 281 | 282 | // read block data 283 | fal.partition.read(part, read_pos, buffer[0..blk_size]); 284 | read_pos += blk_size; 285 | 286 | const dec_size = fastlz1_decompress(o_buf[0..], buffer[0..blk_size]); 287 | // sys.debug.print("fastlz_decompress returned {d}\r\n", .{dec_size}) catch {}; 288 | 289 | // write to file 290 | fal.partition.write(part_target, write_pos, o_buf[0..dec_size]); 291 | write_pos += dec_size; 292 | } 293 | } else { 294 | fal.partition.erase(part_target, 0, part_target.len); 295 | // copy data 296 | var buf: [1024]u8 = undefined; 297 | var offset: u32 = 0; 298 | while (offset < ota_fw_info.raw_size) : (offset += 1024) { 299 | const read_size = ota_fw_info.raw_size - offset; 300 | 301 | sys.debug.print("=", .{}) catch {}; 302 | if (read_size > 1024) { 303 | fal.partition.read(part, offset + @sizeOf(Ota_FW_Info), &buf); 304 | fal.partition.write(part_target, offset, &buf); 305 | } else { 306 | var tmp_bug: [1024]u8 = undefined; 307 | fal.partition.read(part, offset + @sizeOf(Ota_FW_Info), &tmp_bug); 308 | fal.partition.write(part_target, offset, &tmp_bug); 309 | } 310 | } 311 | } 312 | // write ota_info to target partition end 313 | const slice_info = @as([*]const u8, @ptrCast((&ota_fw_info)))[0..(@sizeOf(Ota_FW_Info))]; 314 | fal.partition.write(part_target, part_target.len - @sizeOf(Ota_FW_Info), slice_info[0..]); 315 | 316 | sys.debug.print("\r\nswap success, start clean swap parttion\r\n", .{}) catch {}; 317 | } 318 | 319 | fn fastlz_get_block_size(comp_datas: []const u8) u32 { 320 | var block_size: u32 = 0; 321 | for (0..QBOOT_FASTLZ_BLOCK_HDR_SIZE) |i| { 322 | block_size <<= 8; 323 | block_size += comp_datas[i]; 324 | } 325 | return (block_size); 326 | } 327 | 328 | fn FASTLZ_BOUND_CHECK(cond: bool) void { 329 | if (cond) return; 330 | sys.debug.print("fastlz:corrupt\r\n", .{}) catch {}; 331 | } 332 | 333 | pub fn fastlz1_decompress(output: []u8, input: []const u8) usize { 334 | var ipIdx: u32 = 0; 335 | const ipLimitIdx = input.len; 336 | var opIdx: u32 = 0; 337 | const opLimitIdx = output.len; 338 | 339 | var ctrl: u8 = input[ipIdx] & 0b11111; 340 | ipIdx += 1; 341 | 342 | var loop = true; 343 | while (loop) { 344 | var len: u32 = ctrl >> 5; 345 | const ofs: u32 = std.math.shl(u32, (ctrl & 0b11111), 8); 346 | 347 | if (ctrl >= 0b100000) { 348 | var refIdx: u32 = opIdx - ofs - 1; 349 | len -= 1; 350 | if (len == 7 - 1) { 351 | len += input[ipIdx]; 352 | ipIdx += 1; 353 | } 354 | 355 | FASTLZ_BOUND_CHECK(ipIdx < ipLimitIdx); 356 | refIdx -= input[ipIdx]; 357 | ipIdx += 1; 358 | 359 | if (ipIdx < ipLimitIdx) { 360 | ctrl = input[ipIdx]; 361 | ipIdx += 1; 362 | } else { 363 | loop = false; 364 | } 365 | 366 | FASTLZ_BOUND_CHECK(opIdx + len + 3 <= opLimitIdx); 367 | FASTLZ_BOUND_CHECK(refIdx < opLimitIdx); 368 | if (refIdx == opIdx) { 369 | const b = output[refIdx]; 370 | len += 3; 371 | @memset(output[opIdx .. opIdx + len], b); 372 | opIdx += len; 373 | } else { 374 | len += 3; 375 | std.mem.copyForwards(u8, output[opIdx .. opIdx + len], output[refIdx .. refIdx + len]); 376 | opIdx += len; 377 | refIdx += len; 378 | } 379 | } else { 380 | ctrl += 1; 381 | 382 | FASTLZ_BOUND_CHECK(opIdx + ctrl <= opLimitIdx); 383 | FASTLZ_BOUND_CHECK(ipIdx + ctrl <= ipLimitIdx); 384 | std.mem.copyForwards(u8, output[opIdx .. opIdx + ctrl], input[ipIdx .. ipIdx + ctrl]); 385 | opIdx += ctrl; 386 | ipIdx += ctrl; 387 | 388 | loop = ipIdx < ipLimitIdx; 389 | if (loop) { 390 | ctrl = input[ipIdx]; 391 | ipIdx += 1; 392 | } 393 | } 394 | } 395 | return opIdx; 396 | } 397 | 398 | const MAX_COPY = 32; 399 | const MAX_LEN = 264; // 256 + 8 400 | const HASH_LOG = 13; 401 | const HASH_SIZE = (1 << HASH_LOG); 402 | const HASH_MASK = (HASH_SIZE - 1); 403 | const MAX_L1_DISTANCE = 8192; 404 | 405 | fn fastlz_readu16(p: []const u8) u16 { 406 | return @as(u16, p[0]) | std.math.shl(u16, p[1], 8); 407 | } 408 | 409 | fn fastlz_hash(p: []const u8) u32 { 410 | var v: u32 = fastlz_readu16(p); 411 | v ^= (fastlz_readu16(p[1..]) ^ std.math.shr(u32, v, (16 - HASH_LOG))); 412 | v &= HASH_MASK; 413 | return v; 414 | } 415 | // The output buffer must be at least 5% larger than the input buffer and can not be smaller than 66 bytes. 416 | pub fn fastlz_buffer_padding(x: u32) u32 { 417 | return (66 + (x) * 5 / 100); 418 | } 419 | 420 | pub fn fastlz1_compress(output: []u8, input: []const u8) usize { 421 | var ipIdx: u32 = 0; 422 | const ipBoundIdx = input.len - 2; 423 | const ipLimitIdx = input.len - 12; 424 | var opIdx: u32 = 0; 425 | var copy: u32 = 2; 426 | 427 | var htab: [HASH_SIZE]u32 = undefined; 428 | var hval: u32 = undefined; 429 | 430 | if (input.len < 4) { 431 | if (input.len > 0) { 432 | output[0] = @intCast(input.len - 1); 433 | mem.copyForwards(u8, output[1 .. 1 + input.len], input); 434 | } 435 | return input.len + 1; 436 | } 437 | 438 | for (0..(HASH_SIZE)) |i| { 439 | htab[i] = 0; 440 | } 441 | 442 | output[0] = @intCast(MAX_COPY - 1); 443 | output[1] = input[0]; 444 | output[2] = input[1]; 445 | opIdx = 3; 446 | ipIdx = 2; 447 | 448 | while (ipIdx < ipLimitIdx) { 449 | var refIdx: u32 = 0; 450 | var distance: u32 = 0; 451 | var len: u32 = 3; 452 | 453 | var anchorIdx: u32 = ipIdx; 454 | 455 | // find potential match 456 | hval = fastlz_hash(input[ipIdx..]); 457 | refIdx = htab[hval]; 458 | 459 | // calculate distance to the match 460 | distance = anchorIdx - refIdx; 461 | // update hash table 462 | htab[hval] = anchorIdx; 463 | // is this a match? check the first 3 bytes 464 | if (distance == 0 or distance >= MAX_L1_DISTANCE or input[refIdx] != input[ipIdx] or input[refIdx + 1] != input[ipIdx + 1] or input[refIdx + 2] != input[ipIdx + 2]) { 465 | refIdx += 3; 466 | ipIdx += 3; 467 | 468 | output[opIdx] = input[anchorIdx]; 469 | opIdx += 1; 470 | anchorIdx += 1; 471 | ipIdx = anchorIdx; 472 | copy += 1; 473 | if (copy == MAX_COPY) { 474 | copy = 0; 475 | output[opIdx] = @intCast(MAX_COPY - 1); 476 | opIdx += 1; 477 | } 478 | continue; 479 | } else { 480 | refIdx += 3; 481 | ipIdx += 3; 482 | } 483 | // last matched byte 484 | ipIdx = anchorIdx + len; 485 | 486 | // distance is biased 487 | distance -= 1; 488 | 489 | if (distance == 0) { 490 | // zero distance means a run 491 | const x = input[ipIdx - 1]; 492 | while (ipIdx < ipBoundIdx) { 493 | if (input[refIdx] != x) { 494 | refIdx += 1; 495 | break; 496 | } else { 497 | ipIdx += 1; 498 | refIdx += 1; 499 | } 500 | } 501 | } else { 502 | while (true) { 503 | // safe because the outer check against ip limit 504 | if (input[refIdx] != input[ipIdx]) { 505 | refIdx += 1; 506 | ipIdx += 1; 507 | break; 508 | } else { 509 | ipIdx += 1; 510 | refIdx += 1; 511 | } 512 | if (input[refIdx] != input[ipIdx]) { 513 | refIdx += 1; 514 | ipIdx += 1; 515 | break; 516 | } else { 517 | ipIdx += 1; 518 | refIdx += 1; 519 | } 520 | 521 | if (input[refIdx] != input[ipIdx]) { 522 | refIdx += 1; 523 | ipIdx += 1; 524 | break; 525 | } else { 526 | ipIdx += 1; 527 | refIdx += 1; 528 | } 529 | 530 | if (input[refIdx] != input[ipIdx]) { 531 | refIdx += 1; 532 | ipIdx += 1; 533 | break; 534 | } else { 535 | ipIdx += 1; 536 | refIdx += 1; 537 | } 538 | 539 | if (input[refIdx] != input[ipIdx]) { 540 | refIdx += 1; 541 | ipIdx += 1; 542 | break; 543 | } else { 544 | ipIdx += 1; 545 | refIdx += 1; 546 | } 547 | 548 | if (input[refIdx] != input[ipIdx]) { 549 | refIdx += 1; 550 | ipIdx += 1; 551 | break; 552 | } else { 553 | ipIdx += 1; 554 | refIdx += 1; 555 | } 556 | 557 | if (input[refIdx] != input[ipIdx]) { 558 | refIdx += 1; 559 | ipIdx += 1; 560 | break; 561 | } else { 562 | ipIdx += 1; 563 | refIdx += 1; 564 | } 565 | 566 | if (input[refIdx] != input[ipIdx]) { 567 | refIdx += 1; 568 | ipIdx += 1; 569 | break; 570 | } else { 571 | ipIdx += 1; 572 | refIdx += 1; 573 | } 574 | 575 | while (ipIdx < ipBoundIdx) { 576 | if (input[refIdx] != input[ipIdx]) { 577 | refIdx += 1; 578 | ipIdx += 1; 579 | break; 580 | } else { 581 | ipIdx += 1; 582 | refIdx += 1; 583 | } 584 | } 585 | break; 586 | } 587 | } 588 | // if we have copied something, adjust the copy count 589 | if (copy != 0) { 590 | // copy is biased, '0' means 1 byte copy 591 | output[opIdx - copy - 1] = @intCast(copy - 1); 592 | } else { 593 | // back, to overwrite the copy count 594 | opIdx -= 1; 595 | } 596 | // reset literal counter 597 | copy = 0; 598 | // length is biased, '1' means a match of 3 bytes 599 | ipIdx -= 3; 600 | len = ipIdx - anchorIdx; 601 | 602 | // encode the match 603 | if (len > MAX_LEN - 2) { 604 | while (len > MAX_LEN - 2) { 605 | output[opIdx] = @intCast((7 << 5) + (distance >> 8)); 606 | output[opIdx + 1] = @intCast(MAX_LEN - 2 - 7 - 2); 607 | output[opIdx + 2] = @intCast(distance & 255); 608 | opIdx += 3; 609 | len -= MAX_LEN - 2; 610 | } 611 | } 612 | 613 | if (len < 7) { 614 | output[opIdx] = @intCast((len << 5) + (distance >> 8)); 615 | output[opIdx + 1] = @intCast(distance & 255); 616 | opIdx += 2; 617 | } else { 618 | output[opIdx] = @intCast((7 << 5) + (distance >> 8)); 619 | output[opIdx + 1] = @intCast(len - 7); 620 | output[opIdx + 2] = @intCast(distance & 255); 621 | opIdx += 3; 622 | } 623 | // update the hash at match boundary 624 | hval = fastlz_hash(input[ipIdx..]); 625 | htab[hval] = ipIdx; 626 | ipIdx += 1; 627 | hval = fastlz_hash(input[ipIdx..]); 628 | htab[hval] = ipIdx; 629 | ipIdx += 1; 630 | 631 | // assuming literal copy 632 | output[opIdx] = @intCast(MAX_COPY - 1); 633 | opIdx += 1; 634 | } 635 | 636 | // left-over as literal copy 637 | while (ipIdx < input.len) { 638 | output[opIdx] = input[ipIdx]; 639 | opIdx += 1; 640 | ipIdx += 1; 641 | copy += 1; 642 | if (copy == MAX_COPY) { 643 | copy = 0; 644 | output[opIdx] = @intCast(MAX_COPY - 1); 645 | opIdx += 1; 646 | } 647 | } 648 | 649 | // if we have copied something, adjust the copy length 650 | if (copy != 0) { 651 | output[opIdx - copy - 1] = @intCast(copy - 1); 652 | } else { 653 | opIdx -= 1; 654 | } 655 | 656 | return opIdx; 657 | } 658 | -------------------------------------------------------------------------------- /src/platform/sfud/sfud.zig: -------------------------------------------------------------------------------- 1 | pub const flash = @import("sfud_flash.zig"); 2 | -------------------------------------------------------------------------------- /src/platform/sfud/sfud_flash.zig: -------------------------------------------------------------------------------- 1 | const hal = @import("../../hal/hal.zig"); 2 | const sys = @import("../sys.zig"); 3 | const std = @import("std"); 4 | 5 | const Flash = @import("../fal/flash.zig"); 6 | const sfdp = @import("./sfud_sfdp.zig"); 7 | 8 | // sfud flash structure 9 | pub const SfudFlash = struct { 10 | spi: hal.spi.SpiType, 11 | 12 | capacity: u32, // flash capacity 13 | write_size: u32, // flash write size 14 | erase_gran: u32, // erase granularity 15 | erase_gran_cmd: u8, // erase granularity command 16 | addr_in_4_byte: bool, // address in 4 byte mode 17 | }; 18 | 19 | // SFUD CMD define 20 | pub const SfudCmd = enum(u8) { 21 | WriteEnable = 0x06, 22 | WriteDisable = 0x04, 23 | ReadStatusRegister = 0x05, 24 | VolatileSrWriteEnable = 0x50, 25 | WriteStatusRegister = 0x01, 26 | PageProgram = 0x02, 27 | AaiWordProgram = 0xAD, 28 | EraseChip = 0xC7, 29 | ReadData = 0x03, 30 | DualOutputReadData = 0x3B, 31 | DualIoReadData = 0xBB, 32 | QuadIoReadData = 0xEB, 33 | QuadOutputReadData = 0x6B, 34 | ManufacturerDeviceId = 0x90, 35 | JedecId = 0x9F, 36 | ReadUniqueId = 0x4B, 37 | ReadSfdpRegister = 0x5A, 38 | EnableReset = 0x66, 39 | Reset = 0x99, 40 | Enter4bAddressMode = 0xB7, 41 | Exit4bAddressMode = 0xE9, 42 | }; 43 | 44 | // status register bits 45 | const SFUD_STATUS_REGISTER_BUSY = 0x01; // busing 46 | const SFUD_STATUS_REGISTER_WEL = 0x02; // write enable latch 47 | const SFUD_STATUS_REGISTER_SRP = 0x80; // status register protect 48 | 49 | const SFUD_WRITE_MAX_PAGE_SIZE = 256; 50 | 51 | // read status register 52 | pub fn read_status_register(flash: *const SfudFlash) u8 { 53 | var cmd: [1]u8 = undefined; 54 | var status: [1]u8 = undefined; 55 | cmd[0] = @intFromEnum(SfudCmd.ReadStatusRegister); 56 | _ = flash.spi.wr(cmd[0..], status[0..]); 57 | return status[0]; 58 | } 59 | 60 | // wait for last operation 61 | pub fn wait_for_last_operation(flash: *const SfudFlash) bool { 62 | var status: u8 = 0; 63 | var retry_times: u32 = 1000000; 64 | while (true) { 65 | status = read_status_register(flash); 66 | if ((status & SFUD_STATUS_REGISTER_BUSY) == 0) { 67 | break; 68 | } 69 | retry_times -= 1; 70 | if (retry_times == 0) { 71 | sys.debug.print("Error: Flash wait busy has an error.\r\n", .{}) catch {}; 72 | return false; 73 | } 74 | } 75 | return true; 76 | } 77 | // set flash write enable 78 | pub fn set_flash_write_enable(flash: *const SfudFlash, enabled: bool) bool { 79 | var cmd: [1]u8 = undefined; 80 | var register_status: u8 = 0; 81 | 82 | if (enabled) { 83 | cmd[0] = @intFromEnum(SfudCmd.WriteEnable); 84 | } else { 85 | cmd[0] = @intFromEnum(SfudCmd.WriteDisable); 86 | } 87 | const result = flash.spi.wr(cmd[0..], null); 88 | if (result) { 89 | register_status = read_status_register(flash); 90 | } 91 | if (result) { 92 | if (enabled and (register_status & SFUD_STATUS_REGISTER_WEL) == 0) { 93 | sys.debug.print("Error: Can't enable write status.\r\n", .{}) catch {}; 94 | return false; 95 | } else if (!enabled and (register_status & SFUD_STATUS_REGISTER_WEL) == 1) { 96 | sys.debug.print("Error: Can't disable write status.\r\n", .{}) catch {}; 97 | return false; 98 | } 99 | } 100 | return result; 101 | } 102 | 103 | // set 4 byte address mode 104 | pub fn set_4_byte_address_mode(flash: *SfudFlash, enabled: bool) bool { 105 | var cmd: [1]u8 = undefined; 106 | if (enabled) { 107 | cmd[0] = @intFromEnum(SfudCmd.Enter4bAddressMode); 108 | } else { 109 | cmd[0] = @intFromEnum(SfudCmd.Exit4bAddressMode); 110 | } 111 | // set the flash write enable 112 | var result = set_flash_write_enable(flash, true); 113 | if (result) { 114 | result = flash.spi.wr(cmd[0..], null); 115 | } 116 | 117 | if (result) { 118 | flash.addr_in_4_byte = enabled; 119 | if (enabled) { 120 | sys.debug.print("Enter 4-Byte addressing mode success.\r\n", .{}) catch {}; 121 | } else { 122 | sys.debug.print("Exit 4-Byte addressing mode success.\r\n", .{}) catch {}; 123 | } 124 | } 125 | return result; 126 | } 127 | 128 | pub fn flash_erase(self: *const Flash.Flash_Dev, addr: u32, size: u32) Flash.FlashErr { 129 | _ = self; 130 | var cmd_data: [5]u8 = undefined; 131 | var cmd_size: u8 = 0; 132 | var cur_erase_cmd: u8 = 0; 133 | var cur_erase_size: u32 = 0; 134 | 135 | if (addr + size > sfud_flash.capacity) { 136 | sys.debug.print("Error: Flash address is out of bound.\r\n", .{}) catch {}; 137 | return Flash.FlashErr.OutOfBound; 138 | } 139 | 140 | var result: bool = true; 141 | var cur_addr = addr; 142 | var remain_size = size; 143 | 144 | while (remain_size > 0) { 145 | cur_erase_cmd = sfud_flash.erase_gran_cmd; 146 | cur_erase_size = sfud_flash.erase_gran; 147 | 148 | result = set_flash_write_enable(&sfud_flash, true); 149 | if (result != true) { 150 | sys.debug.print("Error: Flash write enable failed.\r\n", .{}) catch {}; 151 | return Flash.FlashErr.ErrWrite; 152 | } 153 | 154 | cmd_size = make_flash_cmd_addr(&sfud_flash, cur_erase_cmd, cur_addr, &cmd_data); 155 | result = sfud_flash.spi.wr(cmd_data[0..cmd_size], null); 156 | if (result != true) { 157 | sys.debug.print("Error: Flash erase SPI communicate error.\r\n", .{}) catch {}; 158 | return Flash.FlashErr.Transfer; 159 | } 160 | result = wait_for_last_operation(&sfud_flash); 161 | if (result != true) { 162 | sys.debug.print("Error: Flash wait busy has an error.\r\n", .{}) catch {}; 163 | return Flash.FlashErr.Busy; 164 | } 165 | 166 | // make erase align and calculate next erase address 167 | if (cur_addr % cur_erase_size != 0) { 168 | if (remain_size > cur_erase_size - (cur_addr % cur_erase_size)) { 169 | remain_size -= cur_erase_size - (cur_addr % cur_erase_size); 170 | cur_addr += cur_erase_size - (cur_addr % cur_erase_size); 171 | } else { 172 | break; 173 | } 174 | } else { 175 | if (remain_size > cur_erase_size) { 176 | remain_size -= cur_erase_size; 177 | cur_addr += cur_erase_size; 178 | } else { 179 | break; 180 | } 181 | } 182 | } 183 | // set the flash write disable 184 | _ = set_flash_write_enable(&sfud_flash, false); 185 | return Flash.FlashErr.Ok; 186 | } 187 | 188 | pub fn flash_write(self: *const Flash.Flash_Dev, addr: u32, data: []const u8) Flash.FlashErr { 189 | _ = self; 190 | var cmd_data: [5 + SFUD_WRITE_MAX_PAGE_SIZE]u8 = undefined; 191 | var cmd_size: u8 = 0; 192 | var data_size: u32 = 0; 193 | var write_pos = addr; 194 | 195 | sys.debug.print("SFUD Flash write addr: 0x{x} size: 0x{x}\r\n", .{ addr, data.len }) catch {}; 196 | 197 | if (addr + data.len > sfud_flash.capacity) { 198 | sys.debug.print("Error: Flash address is out of bound.\r\n", .{}) catch {}; 199 | return Flash.FlashErr.OutOfBound; 200 | } 201 | var size: u32 = data.len; 202 | while (size > 0) { 203 | // print write_pos and size 204 | sys.debug.print("Write pos: 0x{x} size: 0x{x}\r\n", .{ write_pos, size }) catch {}; 205 | // set the flash write enable 206 | var result = set_flash_write_enable(&sfud_flash, true); 207 | if (result != true) { 208 | sys.debug.print("Error: Flash write enable failed.\r\n", .{}) catch {}; 209 | return Flash.FlashErr.ErrWrite; 210 | } 211 | cmd_size = make_flash_cmd_addr(&sfud_flash, SfudCmd.PageProgram, write_pos, &cmd_data); 212 | 213 | // make write align and calculate next write address 214 | if (write_pos % sfud_flash.write_size != 0) { 215 | if (size > sfud_flash.write_size - (write_pos % sfud_flash.write_size)) { 216 | data_size = sfud_flash.write_size - (write_pos % sfud_flash.write_size); 217 | } else { 218 | data_size = size; 219 | } 220 | } else { 221 | if (size > sfud_flash.write_size) { 222 | data_size = sfud_flash.write_size; 223 | } else { 224 | data_size = size; 225 | } 226 | } 227 | 228 | std.mem.copyForwards(u8, cmd_data[cmd_size .. cmd_size + data_size], data[(write_pos - addr) .. (write_pos - addr) + data_size]); 229 | result = sfud_flash.spi.wr(cmd_data[0 .. cmd_size + data_size], null); 230 | if (result != true) { 231 | sys.debug.print("Error: Flash write SPI communicate error.\r\n", .{}) catch {}; 232 | return Flash.FlashErr.Transfer; 233 | } 234 | result = wait_for_last_operation(&sfud_flash); 235 | if (result != true) { 236 | sys.debug.print("Error: Flash wait busy has an error.\r\n", .{}) catch {}; 237 | return Flash.FlashErr.Busy; 238 | } 239 | 240 | size -= data_size; 241 | write_pos += data_size; 242 | } 243 | // set the flash write disable 244 | _ = set_flash_write_enable(&sfud_flash, false); 245 | return Flash.FlashErr.Ok; 246 | } 247 | 248 | pub fn flash_read(self: *const Flash.Flash_Dev, addr: u32, data: []u8) Flash.FlashErr { 249 | _ = self; 250 | 251 | var result: bool = true; 252 | var cmd_data: [5]u8 = undefined; 253 | var cmd_size: u8 = 0; 254 | 255 | if (addr + data.len > sfud_flash.capacity) { 256 | sys.debug.print("Error: Flash address is out of bound.\r\n", .{}) catch {}; 257 | return Flash.FlashErr.OutOfBound; 258 | } 259 | result = wait_for_last_operation(&sfud_flash); 260 | if (result != true) { 261 | sys.debug.print("Error: Flash wait busy has an error.\r\n", .{}) catch {}; 262 | return Flash.FlashErr.Busy; 263 | } 264 | cmd_size = make_flash_cmd_addr(&sfud_flash, SfudCmd.ReadData, addr, &cmd_data); 265 | result = sfud_flash.spi.wr(cmd_data[0..cmd_size], data[0..]); 266 | if (result != true) { 267 | sys.debug.print("Error: Flash read SPI communicate error.\r\n", .{}) catch {}; 268 | return Flash.FlashErr.Transfer; 269 | } 270 | return Flash.FlashErr.Ok; 271 | } 272 | 273 | // make cmd addr 274 | fn make_flash_cmd_addr(flash: *const SfudFlash, cmd: anytype, addr: u32, cmd_addr: []u8) u8 { 275 | var cmd_size: u8 = 0; 276 | cmd_addr[0] = if (@TypeOf(cmd) == SfudCmd) @intFromEnum(cmd) else cmd; 277 | if (flash.addr_in_4_byte) { 278 | cmd_addr[1] = @intCast(std.math.shr(u32, addr, 24)); 279 | cmd_addr[2] = @intCast(std.math.shr(u32, addr, 16)); 280 | cmd_addr[3] = @intCast(std.math.shr(u32, addr, 8)); 281 | cmd_addr[4] = @intCast(std.math.shr(u32, addr, 0)); 282 | cmd_size = 5; 283 | } else { 284 | cmd_addr[1] = @intCast(std.math.shr(u32, addr, 16)); 285 | cmd_addr[2] = @intCast(std.math.shr(u32, addr, 8)); 286 | cmd_addr[3] = @intCast(std.math.shr(u32, addr, 0)); 287 | cmd_size = 4; 288 | } 289 | return cmd_size; 290 | } 291 | 292 | const ops: Flash.FlashOps = .{ 293 | .init = null, 294 | .erase = &flash_erase, 295 | .write = &flash_write, 296 | .read = &flash_read, 297 | }; 298 | 299 | pub var sfud_flash: SfudFlash = .{ 300 | .spi = undefined, 301 | .capacity = 0, 302 | .write_size = SFUD_WRITE_MAX_PAGE_SIZE, 303 | .erase_gran = 0, 304 | .erase_gran_cmd = 0, 305 | .addr_in_4_byte = false, 306 | }; 307 | pub var spi_flash = Flash.Flash_Dev{ 308 | .name = "spiflash", 309 | .start = 0x00000000, 310 | .len = 0, 311 | .blocks = .{ 312 | .{ .size = 0, .count = 0 }, 313 | .{ .size = 0, .count = 0 }, 314 | .{ .size = 0, .count = 0 }, 315 | .{ .size = 0, .count = 0 }, 316 | .{ .size = 0, .count = 0 }, 317 | .{ .size = 0, .count = 0 }, 318 | }, 319 | .write_size = SFUD_WRITE_MAX_PAGE_SIZE, 320 | .ops = &ops, 321 | }; 322 | 323 | // probe flash by spi 324 | pub fn probe(name: []const u8, spi: hal.spi.SpiType) ?*const Flash.Flash_Dev { 325 | _ = name; 326 | sfud_flash.spi = spi; 327 | if (sfdp.check_sfdp_header(&sfud_flash) == false) { 328 | return null; 329 | } 330 | if (sfdp.get_basic_para(&sfud_flash) == false) { 331 | return null; 332 | } 333 | 334 | // if the flash is large than 16MB (256Mb) then enter in 4-Byte addressing mode 335 | if (sfud_flash.capacity > (1 << 24)) { 336 | _ = set_4_byte_address_mode(&sfud_flash, true); 337 | } else { 338 | sfud_flash.addr_in_4_byte = false; 339 | } 340 | spi_flash.len = sfud_flash.capacity; 341 | spi_flash.blocks[0].size = sfud_flash.erase_gran; 342 | spi_flash.blocks[0].count = sfud_flash.capacity / sfud_flash.erase_gran; 343 | return &spi_flash; 344 | } 345 | -------------------------------------------------------------------------------- /src/platform/sfud/sfud_sfdp.zig: -------------------------------------------------------------------------------- 1 | const hal = @import("../../hal/hal.zig"); 2 | const sys = @import("../sys.zig"); 3 | const std = @import("std"); 4 | 5 | const SfudFlash = @import("sfud_flash.zig").SfudFlash; 6 | const SfudCmd = @import("sfud_flash.zig").SfudCmd; 7 | 8 | const sfdp_signature = 0x50444653; 9 | const sfdp_basic_para_table_len = 9; 10 | 11 | // SFDP Header structure 12 | const SfdpHeader = packed struct { 13 | signature: u32, // SFDP signature 14 | minor_rev: u8, 15 | major_rev: u8, 16 | nph: u8, // Number of parameter headers 17 | sap: u8, // SFDP addressability parameter 18 | }; 19 | 20 | // SFDP parameter header structure 21 | const SfdpParaHeader = packed struct { 22 | id: u8, // Parameter ID LSB 23 | minor_rev: u8, 24 | major_rev: u8, 25 | len: u8, // Parameter table length(in double words) 26 | ptp: u24, // Parameter table 24bit pointer (byte address) 27 | id_msb: u8, // Parameter ID MSB 28 | }; 29 | 30 | // check sfdp header 31 | pub fn check_sfdp_header(flash: *const SfudFlash) bool { 32 | var header: SfdpHeader = undefined; 33 | var slice_header = @as([*]u8, @ptrCast((&header)))[0..(@sizeOf(SfdpHeader))]; 34 | if (read_sfdp_data(flash, 0, slice_header[0..])) { 35 | // check SFDP header 36 | if (header.signature != sfdp_signature) { 37 | // reset flash and try agine. 38 | _ = reset_flash(flash); 39 | hal.clock.delay_ms(10); 40 | if (read_sfdp_data(flash, 0, slice_header[0..])) { 41 | // check SFDP header 42 | if (header.signature != sfdp_signature) { 43 | return false; 44 | } 45 | return true; 46 | } 47 | return false; 48 | } 49 | } 50 | return true; 51 | } 52 | 53 | // JEDEC Basic Flash Parameter Table 54 | const JBFPTable = packed struct { 55 | DWORD1: packed struct { erase_size: u2, write_gran: u1, used: u29 }, 56 | DWORD2: packed struct { flash_density: u31, abvoe_4g: u1 }, 57 | DWORD3: u32, 58 | DWORD4: u32, 59 | DWORD5: u32, 60 | DWORD6: u32, 61 | DWORD7: u32, 62 | DWORD8: packed struct { erase1_gran: u8, erase1_gran_cmd: u8, erase2_gran: u8, erase2_gran_cmd: u8 }, 63 | }; 64 | 65 | // probe basic flash parameter table 66 | pub fn get_basic_para(flash: *SfudFlash) bool { 67 | var header: SfdpParaHeader = undefined; 68 | var slice_header = @as([*]u8, @ptrCast((&header)))[0..(@sizeOf(SfdpParaHeader))]; 69 | if (read_sfdp_data(flash, 8, slice_header[0..])) { 70 | // check SFDP para header 71 | if (header.major_rev > 1) { 72 | sys.debug.print("This reversion(V{d}.{d}) JEDEC flash parameter header is not supported.", .{ header.major_rev, header.minor_rev }) catch {}; 73 | return false; 74 | } 75 | } 76 | var table: JBFPTable = undefined; 77 | var slice_table = @as([*]u8, @ptrCast((&table)))[0..(@sizeOf(JBFPTable))]; 78 | _ = read_sfdp_data(flash, @intCast(header.ptp), slice_table[0..]); 79 | // dump flash density 80 | if (table.DWORD2.abvoe_4g == 0) { 81 | flash.capacity = 1 + (table.DWORD2.flash_density >> 3); 82 | } else { 83 | flash.capacity = std.math.shl(u32, 1, table.DWORD2.flash_density - 3); 84 | } 85 | 86 | // dump erase granularity 87 | if (table.DWORD8.erase1_gran != 0x0C) { 88 | sys.debug.print("Erase granularity 4K is not supported.\r\n", .{}) catch {}; 89 | } 90 | flash.erase_gran = std.math.shl(u32, 1, table.DWORD8.erase1_gran); 91 | flash.erase_gran_cmd = table.DWORD8.erase1_gran_cmd; 92 | return true; 93 | } 94 | 95 | fn reset_flash(flash: *const SfudFlash) bool { 96 | const cmd = [_]u8{ @intFromEnum(SfudCmd.EnableReset), 0 }; 97 | if (!flash.spi.wr(cmd[0..], null)) { 98 | return false; 99 | } 100 | const cmd1 = [_]u8{ @intFromEnum(SfudCmd.Reset), 0 }; 101 | if (!flash.spi.wr(cmd1[0..], null)) { 102 | return false; 103 | } 104 | return true; 105 | } 106 | 107 | // read_sfdp_data 108 | fn read_sfdp_data(flash: *const SfudFlash, offset: u32, read_buf: []u8) bool { 109 | const cmd = [_]u8{ @intFromEnum(SfudCmd.ReadSfdpRegister), @intCast(std.math.shr(u32, offset, 16)), @intCast(std.math.shr(u32, offset, 8)), @intCast(std.math.shr(u32, offset, 0)), 0xFF }; 110 | return flash.spi.wr(cmd[0..], read_buf); 111 | } 112 | 113 | // dump sfdp header 114 | fn dump_sfdp_header(header: SfdpHeader) void { 115 | sys.debug.print("signature: 0x{x}\r\n", .{header.signature}) catch {}; 116 | sys.debug.print("minor_rev: 0x{x}\r\n", .{header.minor_rev}) catch {}; 117 | sys.debug.print("major_rev: 0x{x}\r\n", .{header.major_rev}) catch {}; 118 | sys.debug.print("nph: 0x{x}\r\n", .{header.nph}) catch {}; 119 | sys.debug.print("sap: 0x{x}\r\n", .{header.sap}) catch {}; 120 | } 121 | 122 | // dump sfdp para header 123 | fn dump_sfdp_para_header(header: SfdpParaHeader) void { 124 | sys.debug.print("id: 0x{x}\r\n", .{header.id}) catch {}; 125 | sys.debug.print("minor_rev: 0x{x}\r\n", .{header.minor_rev}) catch {}; 126 | sys.debug.print("major_rev: 0x{x}\r\n", .{header.major_rev}) catch {}; 127 | sys.debug.print("len: 0x{x}\r\n", .{header.len}) catch {}; 128 | sys.debug.print("ptp: 0x{x}\r\n", .{header.ptp}) catch {}; 129 | sys.debug.print("id_msb: 0x{x}\r\n", .{header.id_msb}) catch {}; 130 | } 131 | -------------------------------------------------------------------------------- /src/platform/sys.zig: -------------------------------------------------------------------------------- 1 | const hal = @import("../hal/hal.zig"); 2 | pub const zconfig = @import("sys/config.zig"); 3 | 4 | pub var debug: hal.uart.UartDebug().Writer = undefined; 5 | 6 | pub fn init_debug(name: []const u8) !void { 7 | debug = (try hal.uart.UartDebug().init(name)).writer(); 8 | } 9 | 10 | /// Contains references to the microzig .data and .bss sections, also 11 | /// contains the initial load address for .data if it is in flash. 12 | pub const sections = struct { 13 | // it looks odd to just use a u8 here, but in C it's common to use a 14 | // char when linking these values from the linkerscript. What's 15 | // important is the addresses of these values. 16 | extern var microzig_data_start: u8; 17 | extern var microzig_data_end: u8; 18 | extern var microzig_bss_start: u8; 19 | extern var microzig_bss_end: u8; 20 | extern const microzig_data_load_start: u8; 21 | }; 22 | 23 | pub fn get_rom_end() usize { 24 | const data_start: [*]u8 = @ptrCast(§ions.microzig_data_start); 25 | const data_end: [*]u8 = @ptrCast(§ions.microzig_data_end); 26 | const data_len = @intFromPtr(data_end) - @intFromPtr(data_start); 27 | const data_src: u32 = @intFromPtr(§ions.microzig_data_load_start); 28 | return data_src + data_len; 29 | } 30 | -------------------------------------------------------------------------------- /src/platform/sys/config.zig: -------------------------------------------------------------------------------- 1 | pub const PIN_NAME_MAX = 4; 2 | // magic num 3 | pub const ZBOOT_CONFIG_MAGIC = 0x544F425A; // TOBZ --> ZBOT 4 | 5 | pub const UartConfig = extern struct { 6 | enable: bool, 7 | tx: [PIN_NAME_MAX]u8, 8 | }; 9 | 10 | // spi flash config 11 | pub const SpiFlashConfig = extern struct { 12 | enable: bool, 13 | cs: [PIN_NAME_MAX]u8, 14 | sck: [PIN_NAME_MAX]u8, 15 | mosi: [PIN_NAME_MAX]u8, 16 | miso: [PIN_NAME_MAX]u8, 17 | }; 18 | 19 | pub const ZbootConfig = extern struct { 20 | // magic num 21 | magic: u32, 22 | uart: UartConfig, 23 | spiflash: SpiFlashConfig, 24 | }; 25 | 26 | const dconfig = @import("../../default_config.zig"); 27 | var zboot_config: *const ZbootConfig = &dconfig.default_config; 28 | 29 | // probe config from rom end 30 | pub fn probe_extconfig(addr: u32) void { 31 | var align_addr: u32 = (addr + 3) & ~@as(u32, 3); // Align to 4 bytes 32 | const end_addr = addr + 0x1000; 33 | 34 | while (align_addr <= end_addr) { 35 | const magic_word: *u32 = @ptrFromInt(align_addr); 36 | if (magic_word.* == ZBOOT_CONFIG_MAGIC) { 37 | const extconfig: *ZbootConfig = @ptrFromInt(align_addr); 38 | zboot_config = extconfig; 39 | return; 40 | } 41 | align_addr += 4; 42 | } 43 | } 44 | 45 | // get config 46 | pub fn get_config() *const ZbootConfig { 47 | return zboot_config; 48 | } 49 | -------------------------------------------------------------------------------- /zboot.zig: -------------------------------------------------------------------------------- 1 | /// zboot 2 | /// Author: @flyboy 3 | /// 4 | const std = @import("std"); 5 | const mem = std.mem; 6 | const json = std.json; 7 | 8 | const Debug = false; 9 | const KiB = 1024; 10 | 11 | const ZC = @import("src/platform/sys.zig").zconfig; 12 | const Part = @import("src/platform/fal/fal.zig").partition; 13 | const OTA = @import("src/platform/ota/ota.zig"); 14 | 15 | const stm32zboot = @embedFile("zig-out/bin/stm32-zboot.bin"); 16 | const stm32app = @embedFile("zig-out/bin/stm32-app.bin"); 17 | const configjson = @embedFile("config.json"); 18 | 19 | pub var partition_num: u32 = 0; 20 | pub var default_partition: [Part.partition_table_MAX]Part.Partition = undefined; 21 | pub var default_zconfig: ZC.ZbootConfig = undefined; 22 | 23 | const JsonUart = struct { 24 | enable: u32, 25 | tx: []u8, 26 | }; 27 | 28 | const JsonSpiFlash = struct { 29 | enable: u32, 30 | cs: []u8, 31 | sck: []u8, 32 | mosi: []u8, 33 | miso: []u8, 34 | }; 35 | 36 | const JsonPartition = struct { 37 | name: []u8, 38 | flash_name: []u8, 39 | offset: u32, 40 | len: u32, 41 | }; 42 | 43 | const JsonPartitionTable = struct { 44 | patition: []JsonPartition, 45 | }; 46 | 47 | const JsonConfig = struct { 48 | uart: JsonUart, 49 | spiflash: JsonSpiFlash, 50 | partition_table: JsonPartitionTable, 51 | }; 52 | 53 | const BUF_SIZE = 1024; 54 | 55 | fn help() void { 56 | std.debug.print("Usage: \n", .{}); 57 | std.debug.print(" zboot boot: gen stm32-zboot.bin [config.json] \n", .{}); 58 | std.debug.print(" zboot app: gen stm32-app.bin \n", .{}); 59 | std.debug.print(" zboot rbl : tar xxx.bin to xxx.rbl \n", .{}); 60 | // std.debug.print(" zboot allbin: tar stm32-zboot|app|[swap] to all.bin \n", .{}); 61 | } 62 | 63 | pub fn main() !void { 64 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 65 | defer _ = gpa.deinit(); 66 | const allocator = gpa.allocator(); 67 | 68 | const args = try std.process.argsAlloc(allocator); 69 | defer std.process.argsFree(allocator, args); 70 | 71 | if (Debug) { 72 | std.debug.print("arg: {s},{d}\n", .{ args[1], args.len }); 73 | } 74 | 75 | var input_file: []u8 = undefined; 76 | if (args.len > 1) { 77 | // if argv[1] == boot gen stm32-zboot.bin 78 | if (mem.eql(u8, args[1], "boot")) { 79 | // if config.json not exist, gen config.json 80 | std.fs.cwd().access("config.json", .{}) catch { 81 | std.debug.print("=> gen config.json\n", .{}); 82 | try gen_configjson(); 83 | }; 84 | std.debug.print("=> gen stm32-zboot.bin\n", .{}); 85 | try gen_boot(); 86 | return; 87 | } else if (mem.eql(u8, args[1], "rbl")) { 88 | if (args.len < 3) { 89 | help(); 90 | return; 91 | } 92 | input_file = args[2]; 93 | try gen_rbl(input_file, args[3]); 94 | return; 95 | } else if (mem.eql(u8, args[1], "app")) { 96 | try gen_app(); 97 | std.debug.print("=> gen stm32-app.bin\n", .{}); 98 | return; 99 | } 100 | } else { 101 | help(); 102 | return; 103 | } 104 | } 105 | 106 | fn gen_configjson() !void { 107 | const bin_file = try std.fs.cwd().createFile("./config.json", .{}); 108 | 109 | var buf_write = std.io.bufferedWriter(bin_file.writer()); 110 | var out_stream = buf_write.writer(); 111 | 112 | _ = out_stream.write(configjson) catch { 113 | std.debug.print("write file error\n", .{}); 114 | return; 115 | }; 116 | try buf_write.flush(); 117 | bin_file.close(); 118 | } 119 | 120 | fn gen_boot() !void { 121 | const bin_file = try std.fs.cwd().createFile("./stm32-zboot.bin", .{}); 122 | 123 | var buf_write = std.io.bufferedWriter(bin_file.writer()); 124 | var out_stream = buf_write.writer(); 125 | 126 | // find magic word 'Z' 'B' 'O' 'T' 0x544F425A from tail of bin file 127 | var magic_offset: usize = 0; 128 | var offset = stm32zboot.len - @sizeOf(ZC.ZbootConfig); 129 | while (offset > 0) { 130 | const magic_buf = stm32zboot[offset..(offset + 4)]; 131 | if (magic_buf[0] == 0x5A and magic_buf[1] == 0x42 and magic_buf[2] == 0x4F and magic_buf[3] == 0x54) { 132 | magic_offset = offset; 133 | break; 134 | } 135 | offset -= 1; 136 | } 137 | if (magic_offset == 0) { 138 | std.debug.print("can't find ZBOOT_CONFIG_MAGIC\n", .{}); 139 | return; 140 | } 141 | 142 | _ = out_stream.write(stm32zboot[0..magic_offset]) catch { 143 | std.debug.print("write file error\n", .{}); 144 | return; 145 | }; 146 | 147 | // parse json file 148 | try json_parse("config.json"); 149 | // write zbootconfig data 150 | default_zconfig.magic = ZC.ZBOOT_CONFIG_MAGIC; 151 | const slice_config = @as([*]u8, @ptrCast((&default_zconfig)))[0..(@sizeOf(ZC.ZbootConfig))]; 152 | _ = out_stream.write(slice_config) catch { 153 | std.debug.print("write file error\n", .{}); 154 | return; 155 | }; 156 | 157 | // write partition data 158 | const slice = @as([*]u8, @ptrCast((&default_partition)))[0..(@sizeOf(Part.Partition) * partition_num)]; 159 | _ = out_stream.write(slice) catch { 160 | std.debug.print("write file error\n", .{}); 161 | return; 162 | }; 163 | try buf_write.flush(); 164 | bin_file.close(); 165 | } 166 | 167 | fn gen_app() !void { 168 | const bin_file = try std.fs.cwd().createFile("./stm32-app.bin", .{}); 169 | 170 | var buf_write = std.io.bufferedWriter(bin_file.writer()); 171 | var out_stream = buf_write.writer(); 172 | 173 | // find magic word 'Z' 'B' 'O' 'T' 0x544F425A from tail of bin file 174 | var magic_offset: usize = 0; 175 | var offset = stm32app.len - @sizeOf(ZC.ZbootConfig); 176 | while (offset > 0) { 177 | const magic_buf = stm32app[offset..(offset + 4)]; 178 | if (magic_buf[0] == 0x5A and magic_buf[1] == 0x42 and magic_buf[2] == 0x4F and magic_buf[3] == 0x54) { 179 | magic_offset = offset; 180 | break; 181 | } 182 | offset -= 1; 183 | } 184 | if (magic_offset == 0) { 185 | std.debug.print("can't find ZBOOT_CONFIG_MAGIC\n", .{}); 186 | return; 187 | } 188 | 189 | _ = out_stream.write(stm32app[0..magic_offset]) catch { 190 | std.debug.print("write file error\n", .{}); 191 | return; 192 | }; 193 | 194 | // parse json file 195 | try json_parse("config.json"); 196 | // write zbootconfig data 197 | default_zconfig.magic = ZC.ZBOOT_CONFIG_MAGIC; 198 | const slice_config = @as([*]u8, @ptrCast((&default_zconfig)))[0..(@sizeOf(ZC.ZbootConfig))]; 199 | _ = out_stream.write(slice_config) catch { 200 | std.debug.print("write file error\n", .{}); 201 | return; 202 | }; 203 | 204 | try buf_write.flush(); 205 | bin_file.close(); 206 | } 207 | 208 | const FLZ_BUF_SIZE = 4096; 209 | const TEMP_FILE = "_crom_tmp.tmp"; 210 | 211 | fn gen_rbl(input_file: []u8, version: []u8) !void { 212 | var file = try std.fs.cwd().openFile(input_file, .{}); 213 | defer file.close(); 214 | 215 | // ready ota header 216 | var fw_info: OTA.Ota_FW_Info = .{ 217 | .magic = .{ 'R', 'B', 'L', 0 }, 218 | .algo = .FASTLZ, 219 | .algo2 = .NONE, 220 | .time_stamp = 0, 221 | .name = .{ 'a', 'p', 'p' } ++ .{0} ** 13, 222 | .version = .{0} ** 24, 223 | .sn = .{'0'} ** 24, 224 | .body_crc = 0, 225 | .hash_code = 0, 226 | .raw_size = 0, 227 | .pkg_size = 0, 228 | .hdr_crc = 0, 229 | }; 230 | 231 | const file_size = try file.getEndPos(); 232 | mem.copyForwards(u8, &fw_info.version, version[0..version.len]); 233 | fw_info.raw_size = @intCast(file_size); 234 | fw_info.time_stamp = @intCast(std.time.milliTimestamp()); 235 | 236 | // body crc 237 | var buf_reader = std.io.bufferedReader(file.reader()); 238 | var in_stream = buf_reader.reader(); 239 | 240 | var buf: [FLZ_BUF_SIZE]u8 = undefined; 241 | var crc: u32 = 0; 242 | var hash: u32 = OTA.ZBOOT_HASH_FNV_SEED; 243 | 244 | const crom_file = try std.fs.cwd().createFile(TEMP_FILE, .{}); 245 | 246 | while (true) { 247 | const size = try in_stream.read(buf[0..]); 248 | if (size == 0) { 249 | break; 250 | } 251 | hash = OTA.calc_hash(hash, buf[0..size]); 252 | var crom_buf: [FLZ_BUF_SIZE + OTA.fastlz_buffer_padding(FLZ_BUF_SIZE)]u8 = undefined; 253 | for (&crom_buf) |*b| { 254 | b.* = 0; 255 | } 256 | const compress_size = OTA.fastlz1_compress(crom_buf[0..], buf[0..size]); 257 | const buffer_hdr: [4]u8 = .{ 258 | @intCast(compress_size / (1 << 24)), 259 | @intCast((compress_size % (1 << 24)) / (1 << 16)), 260 | @intCast((compress_size % (1 << 16)) / (1 << 8)), 261 | @intCast(compress_size % (1 << 8)), 262 | }; 263 | _ = crom_file.writeAll(buffer_hdr[0..]) catch { 264 | std.debug.print("write file error\n", .{}); 265 | return; 266 | }; 267 | _ = crom_file.writeAll(crom_buf[0..compress_size]) catch { 268 | std.debug.print("write crom file error\n", .{}); 269 | return; 270 | }; 271 | 272 | crc = OTA.crc32(crc, buffer_hdr[0..]); 273 | crc = OTA.crc32(crc, crom_buf[0..compress_size]); 274 | if (size < 1024) { 275 | break; 276 | } 277 | } 278 | crom_file.close(); 279 | 280 | // write rbl file 281 | file = try std.fs.cwd().openFile(TEMP_FILE, .{}); 282 | buf_reader = std.io.bufferedReader(file.reader()); 283 | in_stream = buf_reader.reader(); 284 | 285 | var file_name_buf: [512]u8 = undefined; 286 | const file_name = try std.fmt.bufPrint(&file_name_buf, "{s}.rbl", .{input_file}); 287 | std.debug.print("=> {s}\n", .{file_name}); 288 | const bin_file = try std.fs.cwd().createFile(file_name, .{}); 289 | 290 | var buf_write = std.io.bufferedWriter(bin_file.writer()); 291 | var out_stream = buf_write.writer(); 292 | 293 | var read_buf: [BUF_SIZE]u8 = undefined; 294 | var offset: usize = 0; 295 | var write_offset: usize = 0; 296 | 297 | // write ota header 298 | 299 | fw_info.body_crc = crc; 300 | fw_info.hash_code = hash; 301 | fw_info.pkg_size = @intCast(try file.getEndPos()); 302 | fw_info.hdr_crc = OTA.crc32(0, @as([*]u8, @ptrCast(&fw_info))[0..(@sizeOf(OTA.Ota_FW_Info) - @sizeOf(u32))]); 303 | if (Debug) { 304 | std.debug.print("crc: {x}, hash: {x}\n", .{ crc, hash }); 305 | std.debug.print("hdr_crc: {x}\n", .{fw_info.hdr_crc}); 306 | } 307 | const slice_ota = @as([*]u8, @ptrCast((&fw_info)))[0..(@sizeOf(OTA.Ota_FW_Info))]; 308 | _ = out_stream.write(slice_ota) catch { 309 | std.debug.print("write file error\n", .{}); 310 | return; 311 | }; 312 | 313 | // read bin file and write to another file 314 | while (true) { 315 | const size = try in_stream.readAll(&read_buf); 316 | offset += size; 317 | 318 | if (Debug) { 319 | std.debug.print("read: {d}, offset:{x}\n", .{ size, offset }); 320 | } 321 | const ret = try out_stream.write(read_buf[0..size]); 322 | write_offset += ret; 323 | 324 | if (Debug) { 325 | std.debug.print("write: {d}, offset:{x}\n", .{ ret, write_offset }); 326 | } 327 | if (ret != size) { 328 | std.debug.print("write file error: {}\n", .{ret}); 329 | return; 330 | } 331 | if (size < BUF_SIZE) { 332 | try buf_write.flush(); 333 | break; 334 | } 335 | } 336 | 337 | try buf_write.flush(); 338 | bin_file.close(); 339 | // delete crom file 340 | try std.fs.cwd().deleteFile(TEMP_FILE); 341 | } 342 | 343 | pub fn json_parse(config_file: []const u8) !void { 344 | const allocator = std.heap.page_allocator; 345 | var buf: [4096]u8 = undefined; 346 | var file = try std.fs.cwd().openFile(config_file, .{}); 347 | defer file.close(); 348 | 349 | const size = try file.readAll(buf[0..]); 350 | 351 | if (Debug) { 352 | // dump the file contents 353 | std.debug.print("config.json: {s}\n", .{buf[0..size]}); 354 | } 355 | const root = try std.json.parseFromSlice(JsonConfig, allocator, buf[0..size], .{ .allocate = .alloc_always }); 356 | 357 | const json_config = root.value; 358 | 359 | // parse uart config 360 | if (Debug) { 361 | std.debug.print("uart enable:{d}\n", .{json_config.uart.enable}); 362 | std.debug.print("uart tx:{s}\n", .{json_config.uart.tx}); 363 | } 364 | if (json_config.uart.tx.len > ZC.PIN_NAME_MAX) { 365 | std.debug.print("uart tx is too long\n", .{}); 366 | return; 367 | } 368 | if (json_config.uart.enable == 1) { 369 | default_zconfig.uart.enable = true; 370 | } else { 371 | default_zconfig.uart.enable = false; 372 | } 373 | mem.copyForwards(u8, &default_zconfig.uart.tx, json_config.uart.tx[0..json_config.uart.tx.len]); 374 | 375 | // parse spiflash config 376 | if (Debug) { 377 | std.debug.print("spi enable:{d}\n", .{json_config.spiflash.enable}); 378 | std.debug.print("spi cs:{s}\n", .{json_config.spiflash.cs}); 379 | std.debug.print("spi sck:{s}\n", .{json_config.spiflash.sck}); 380 | std.debug.print("spi mosi:{s}\n", .{json_config.spiflash.mosi}); 381 | std.debug.print("spi miso:{s}\n", .{json_config.spiflash.miso}); 382 | } 383 | if (json_config.spiflash.enable == 1) { 384 | default_zconfig.spiflash.enable = true; 385 | } else { 386 | default_zconfig.spiflash.enable = false; 387 | } 388 | mem.copyForwards(u8, &default_zconfig.spiflash.cs, json_config.spiflash.cs[0..json_config.spiflash.cs.len]); 389 | mem.copyForwards(u8, &default_zconfig.spiflash.sck, json_config.spiflash.sck[0..json_config.spiflash.sck.len]); 390 | mem.copyForwards(u8, &default_zconfig.spiflash.mosi, json_config.spiflash.mosi[0..json_config.spiflash.mosi.len]); 391 | mem.copyForwards(u8, &default_zconfig.spiflash.miso, json_config.spiflash.miso[0..json_config.spiflash.miso.len]); 392 | 393 | // parse fal partition config 394 | for (json_config.partition_table.patition, 0..) |patition, i| { 395 | if (Debug) { 396 | std.debug.print("config.patition[{d}].name: {s}\n", .{ i, patition.name }); 397 | std.debug.print("config.patition[{d}].flash_name: {s}\n", .{ i, patition.flash_name }); 398 | std.debug.print("config.patition[{d}].offset: {d}\n", .{ i, patition.offset }); 399 | std.debug.print("config.patition[{d}].len: {d}\n", .{ i, patition.len }); 400 | } 401 | if (patition.flash_name.len > Part.FAL_DEV_NAME_MAX) { 402 | std.debug.print("flash_name is too long\n", .{}); 403 | return; 404 | } 405 | if (patition.name.len > Part.FAL_DEV_NAME_MAX) { 406 | std.debug.print("name is too long\n", .{}); 407 | return; 408 | } 409 | default_partition[i].magic_word = Part.FAL_MAGIC_WORD; 410 | mem.copyForwards(u8, &default_partition[i].name, patition.name[0..patition.name.len]); 411 | mem.copyForwards(u8, &default_partition[i].flash_name, patition.flash_name[0..patition.flash_name.len]); 412 | default_partition[i].offset = patition.offset * KiB; 413 | default_partition[i].len = patition.len * KiB; 414 | default_partition[i].reserved = 0; 415 | 416 | partition_num = @intCast(i + 1); 417 | } 418 | } 419 | --------------------------------------------------------------------------------