├── .clang-format ├── .gitignore ├── LICENSE ├── README.md ├── SECURITY.md ├── cfg ├── adafruit-joy-bonnet │ ├── README.md │ └── arcade.cfg ├── big-arcade │ └── arcade.cfg ├── oneup │ └── arcade.cfg └── wooden-arcade │ └── arcade.cfg ├── docker ├── Dockerfile ├── build.sh ├── config └── go.js ├── image ├── boot │ ├── cmdline.txt │ └── config.txt ├── boot3 │ ├── cmdline3.txt │ └── config.txt ├── build.sh ├── inner.sh ├── rootfs.patch ├── rootfs │ ├── etc │ │ └── sysconfig │ │ │ └── superuser │ ├── opt │ │ ├── bootlocal.sh │ │ ├── bootsync.sh │ │ ├── menustart.sh │ │ ├── msdoff.sh │ │ ├── msdon.sh │ │ ├── rungdb.sh │ │ └── tcemirror │ └── usr │ │ └── sbin │ │ └── startserialtty └── rootfs3.patch ├── kernel └── f_mass_storage.c.sync.patch └── uf2daemon ├── Makefile ├── fat.c ├── main.c ├── uf2.h ├── uf2format.h └── uf2hid.h /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | UseTab: Never 4 | ColumnLimit: 100 5 | AccessModifierOffset: -4 6 | AllowShortFunctionsOnASingleLine: Inline 7 | SortIncludes: false 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | built/ 2 | tmp 3 | uf2daemon/uf2d 4 | uf2daemon/uf2d86 5 | sq 6 | bench 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UF2 Flashing for Linux 2 | 3 | This repo contains scripts and patches to build a sample Linux image 4 | based on [piCore](http://www.tinycorelinux.net/ports.html) 5 | for the Raspberry Pi Zero. 6 | The image is meant to boot very quickly (currently at around 7s), 7 | and expose a USB mass storage device (pen drive), which can be used 8 | to program a Raspberry Pi Zero with [UF2 files](https://github.com/Microsoft/uf2), 9 | usually generated from [Microsoft MakeCode](https://github.com/Microsoft/pxt) 10 | and in particular from [MakeCode Arcade](https://arcade.makecode.com). 11 | 12 | The image was tested on a Raspberry Pi Zero Rev 1.3 and Zero W Rev 1.3. 13 | It could theoretically work on the original Pi A/A+, but wasn't 14 | tested. Other models lack the OTG ID pin, and thus cannot be used in 15 | USB device mode. 16 | 17 | PRs are welcome! 18 | 19 | ## Building 20 | 21 | Building the image requires [Docker](https://www.docker.com/). 22 | 23 | Go to `image/` and run `./build.sh`. The image will land in `built/boot/*`. 24 | 25 | ### Configuring keys 26 | 27 | After you're done building, copy one of the `arcade.cfg` files in `cfg/` folder 28 | to `built/boot/*` so that it ends up on the SD card. 29 | You can also create your own `arcade.cfg` file if you have the buttons 30 | connected differently. 31 | The pin numbers in there are BCM pin numbers, not physical pin numbers, see https://pinout.xyz/ 32 | 33 | It's also possible to use regular Linux key codes if your buttons appear as a standard keyboard. 34 | This is enabled by setting `SCAN_CODES=/dev/input/event1` or similar. 35 | Use `evtest` program to figure out the scan codes and use these scan codes instead of BCM pin numbers. 36 | 37 | ### "Burning" image 38 | 39 | All files in `built/boot/` need to be copied to a FAT32-formatted SD card. 40 | There is no ext4 partition to worry about, and you don't need to use any 41 | special software to "burn" the image. 42 | The files need to sit in the root folder of the SD card, i.e., 43 | you should have file `d:/9.0.3.gz`, `d:/cmdline.txt`, as well 44 | as `d:/arcade.cfg` (if your SD drive is `d:/`; on macOS it will 45 | be `/Volumes/NO NAME/9.0.3.gz` etc). 46 | 47 | Regular SD cards come preformatted as FAT32. If you have a previous 48 | Raspberry Pi image on the card you can format it, or just move all files in 49 | the first partition into a sub-folder if it's reasonably big. 50 | 51 | Any SD card should do. You don't need much space (currently around 13MB), 52 | and the Pi will only read a few MBs upon startup, so the speed isn't very important. 53 | 54 | ### Docker image 55 | 56 | If you want to build the Docker image (`pext/rpi`) yourself, 57 | use the `docker/build.sh` script. Usually, you can just pull it 58 | from Docker Hub (which will just happen automatically). 59 | The image is based on 60 | [sdthirlwall/raspberry-pi-cross-compiler](https://hub.docker.com/r/sdthirlwall/raspberry-pi-cross-compiler/) 61 | and contains stock piCore 9.0.3 and sources of its kernel. 62 | 63 | ### Menu program 64 | 65 | Sources are here: https://github.com/microsoft/pxt-arcade-cabinet-menu 66 | 67 | ### Button Configuration 68 | 69 | * 4 player configuration test https://github.com/microsoft/pxt-arcade/blob/master/docs/hardware/raspberry-pi/cardboard-control-panel/configurator.ts 70 | * Another program for testing buttons: `https://arcade.makecode.com/81381-26574-00648-24234` 71 | 72 | ### gdbserver 73 | 74 | By default gdbserver runs on the serial port exposed by the `g_multi` gadget. 75 | To connect to it on macOS run `arm-linux-gnueabihf-gdb` and then do the following: 76 | ``` 77 | (gdb) target extended-remote /dev/cu.usbmodem141123 78 | ``` 79 | where the numbers at the end will vary. Do not use `/dev/tty.usbmodem...`, as this will 80 | just hang. 81 | 82 | ## License 83 | 84 | The contents of this repo are released under the MIT license. 85 | 86 | The images that you build will contain software under all sorts of licenses, including GPL. 87 | 88 | ## Contributing 89 | 90 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 91 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 92 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 93 | 94 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 95 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 96 | provided by the bot. You will only need to do this once across all repos using our CLA. 97 | 98 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 99 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 100 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 101 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /cfg/adafruit-joy-bonnet/README.md: -------------------------------------------------------------------------------- 1 | # Pi Arcade config for Joy Bonnet 2 | 3 | [Adafruit Joy Bonnet](https://www.adafruit.com/product/3464) snaps on top of Pi Zero. 4 | 5 | Button layout: 6 | 7 | ``` 8 | 1, 2 - Exit game (return to menu); takes ~1s 9 | SELECT - Menu; currently unused 10 | START - Restart game 11 | A, X - A 12 | B, Y - B 13 | Joystick - Directions 14 | ``` 15 | -------------------------------------------------------------------------------- /cfg/adafruit-joy-bonnet/arcade.cfg: -------------------------------------------------------------------------------- 1 | BTN_A=12,16 2 | BTN_B=6,13 3 | BTN_MENU=20 4 | BTN_EXIT=22,23 5 | BTN_RESET=26 6 | JOYSTICK_ADDR=0x48 7 | -------------------------------------------------------------------------------- /cfg/big-arcade/arcade.cfg: -------------------------------------------------------------------------------- 1 | BTN_A=12 2 | BTN_B=16 3 | BTN_RESET=23 4 | BTN_EXIT=20 5 | BTN_MENU=24 6 | BTN_LEFT=26 7 | BTN_RIGHT=19 8 | BTN_UP=6 9 | BTN_DOWN=13 10 | 11 | -------------------------------------------------------------------------------- /cfg/oneup/arcade.cfg: -------------------------------------------------------------------------------- 1 | BTN_A=23 2 | BTN_B=18 3 | BTN_RESET=27 4 | BTN_EXIT=25 5 | BTN_MENU=12 6 | BTN_LEFT=17 7 | BTN_RIGHT=4 8 | BTN_UP=22 9 | BTN_DOWN=24 10 | 11 | -------------------------------------------------------------------------------- /cfg/wooden-arcade/arcade.cfg: -------------------------------------------------------------------------------- 1 | BTN_RESET=4 2 | BTN_EXIT=3 3 | BTN_MENU=2 4 | BTN_A=26 5 | BTN_B=19 6 | BTN_LEFT=13 7 | BTN_UP=6 8 | BTN_RIGHT=5 9 | BTN_DOWN=0 10 | BTN_A2=11 11 | BTN_B2=9 12 | BTN_LEFT2=10 13 | BTN_UP2=22 14 | BTN_RIGHT2=27 15 | BTN_DOWN2=17 16 | BTN_A3=21 17 | BTN_B3=20 18 | BTN_LEFT3=16 19 | BTN_UP3=12 20 | BTN_RIGHT3=1 21 | BTN_DOWN3=7 22 | BTN_A4=8 23 | BTN_B4=25 24 | BTN_LEFT4=24 25 | BTN_UP4=23 26 | BTN_RIGHT4=18 27 | BTN_DOWN4=15 -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------ 2 | # Pull base image 3 | FROM sdthirlwall/raspberry-pi-cross-compiler 4 | MAINTAINER Michal Moskal 5 | 6 | USER root 7 | 8 | RUN curl https://deb.nodesource.com/node_6.x/pool/main/n/nodejs/nodejs_6.11.0-1nodesource1~jessie1_amd64.deb > node.deb \ 9 | && dpkg -i node.deb \ 10 | && rm node.deb 11 | 12 | RUN apt-get update 13 | RUN apt-get install -y mtools cpio bc p7zip-full squashfs-tools bison flex libssl-dev fatcat 14 | 15 | # RPI0 16 | 17 | RUN mkdir -p /picore/kernel 18 | WORKDIR /picore/kernel 19 | RUN curl http://www.tinycorelinux.net/9.x/armv6/releases/RPi/src/kernel/linux-rpi-4.9.22.tar.xz > linux-rpi-4.9.22.tar.xz \ 20 | && tar xf linux-rpi-4.9.22.tar.xz \ 21 | && mv linux-rpi-4.9.22 linux-rpi \ 22 | && rm linux-rpi-4.9.22.tar.xz 23 | RUN curl http://www.tinycorelinux.net/9.x/armv6/releases/RPi/src/kernel/4.9.22-piCore.config.xz | xzcat > linux-rpi/.config 24 | RUN curl http://www.tinycorelinux.net/9.x/armv6/releases/RPi/src/kernel/4.9.22-piCore_Module.symvers.xz | xzcat > linux-rpi/Module.symvers 25 | RUN curl http://www.tinycorelinux.net/9.x/armv6/releases/RPi/src/kernel/4.9.22-piCore_System.map.xz | xzcat > linux-rpi/System.map 26 | 27 | WORKDIR /picore/kernel/linux-rpi 28 | 29 | RUN make ARCH=arm CROSS_COMPILE=/rpxc/bin/arm-linux-gnueabihf- modules_prepare 30 | RUN echo "#!/bin/sh" > mkusb.sh 31 | RUN echo "make ARCH=arm CROSS_COMPILE=/rpxc/bin/arm-linux-gnueabihf- SUBDIRS=drivers/usb -j10 modules" >> mkusb.sh 32 | RUN chmod +x mkusb.sh 33 | RUN ./mkusb.sh 34 | 35 | RUN mkdir /picore/img 36 | WORKDIR /picore/img 37 | RUN curl http://www.tinycorelinux.net/9.x/armv6/releases/RPi/piCore-9.0.3.zip > picore.zip 38 | RUN 7z x picore.zip 39 | RUN mkdir /picore/boot 40 | RUN mcopy -s -i piCore-9.0.3.img@@4096K ::* ../boot 41 | WORKDIR /picore 42 | RUN rm -rf img 43 | RUN mkdir rootfs 44 | RUN cd rootfs && zcat ../boot/9.0.3.gz | cpio -i -H newc -d 45 | 46 | # RPI3 47 | 48 | RUN mkdir -p /picore/kernel3 49 | WORKDIR /picore/kernel3 50 | RUN curl http://tinycorelinux.net/11.x/armv7/releases/RPi/src/kernel/linux-rpi-4.19.81.tar.xz > linux-rpi.tar.xz \ 51 | && tar xf linux-rpi.tar.xz \ 52 | && mv linux-rpi-4.19.81 linux-rpi \ 53 | && rm linux-rpi.tar.xz 54 | #RUN curl http://www.tinycorelinux.net/10.x/armv7/releases/RPi/src/kernel/4.19.58-piCore-v7.config.xz | xzcat > linux-rpi/.config 55 | RUN curl http://tinycorelinux.net/11.x/armv7/releases/RPi/src/kernel/Module.symvers > linux-rpi/Module.symvers 56 | RUN curl http://tinycorelinux.net/11.x/armv7/releases/RPi/src/kernel/System.map > linux-rpi/System.map 57 | 58 | RUN mkdir /picore/img3 59 | WORKDIR /picore/img3 60 | RUN curl http://tinycorelinux.net/11.x/armv7/test_releases/RPi/piCore-11.0alpha1a.zip > picore.zip 61 | RUN mkdir /picore/boot3 62 | RUN 7z x picore.zip \ 63 | && dd if=piCore-11.0alpha1a.img of=fat.img bs=4194304 skip=1 count=23 \ 64 | && fatcat fat.img -x /picore/boot3 65 | 66 | WORKDIR /picore/kernel3/linux-rpi 67 | 68 | COPY config .config 69 | RUN make ARCH=arm CROSS_COMPILE=/rpxc/bin/arm-linux-gnueabihf- modules_prepare 70 | RUN echo "#!/bin/sh" > mkusb.sh 71 | RUN echo "make ARCH=arm CROSS_COMPILE=/rpxc/bin/arm-linux-gnueabihf- SUBDIRS=drivers/usb -j10 modules" >> mkusb.sh 72 | RUN chmod +x mkusb.sh 73 | RUN ./mkusb.sh 74 | 75 | WORKDIR /picore 76 | RUN rm -rf img3 77 | RUN mkdir rootfs3 78 | RUN cd rootfs3 && zcat ../boot3/11.0alpha1av7.gz | cpio -i -H newc -d 79 | 80 | # Generic 81 | 82 | WORKDIR /picore 83 | RUN git clone https://github.com/WiringPi/WiringPi 84 | WORKDIR /picore/WiringPi/wiringPi 85 | # WiringPi build script doesn't do cross-compile 86 | RUN arm-linux-gnueabihf-gcc -g -ffunction-sections -fdata-sections -Os -c *.c -I . 87 | RUN arm-linux-gnueabihf-ar rcs libwiringPi.a *.o 88 | RUN cp libwiringPi.a /rpxc/arm-linux-gnueabihf/lib/ 89 | RUN cp *.h /rpxc/arm-linux-gnueabihf/libc/usr/include/ 90 | WORKDIR /picore 91 | RUN rm -rf WiringPi 92 | 93 | RUN curl http://www.tinycorelinux.net/9.x/armv6/tcz/libasound.tcz > libasound.tcz 94 | RUN curl http://www.tinycorelinux.net/9.x/armv6/tcz/libasound-dev.tcz > libasound-dev.tcz 95 | RUN mkdir sq/ 96 | RUN unsquashfs libasound.tcz && cp -r squashfs-root/* sq/ && rm -rf squashfs-root 97 | RUN unsquashfs libasound-dev.tcz && cp -r squashfs-root/* sq/ && rm -rf squashfs-root 98 | RUN cp -a sq/usr/local/lib/libasound* /rpxc/arm-linux-gnueabihf/lib/ 99 | RUN cp -ar sq/usr/local/include/* /rpxc/arm-linux-gnueabihf/libc/usr/include/ 100 | RUN rm -rf sq 101 | 102 | RUN useradd -m build 103 | USER root 104 | COPY go.js /home/build 105 | 106 | -------------------------------------------------------------------------------- /docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker build -t pext/rpi:rpi3 --squash -f Dockerfile . 4 | -------------------------------------------------------------------------------- /docker/go.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs") 2 | var child_process = require("child_process") 3 | 4 | process.stdin.setEncoding("utf8") 5 | process.stdout.setEncoding("utf8") 6 | 7 | var buf = "" 8 | process.stdin.on("data", function(d) { buf += d }) 9 | process.stdin.on("end", function() { 10 | handle(JSON.parse(buf)) 11 | }) 12 | 13 | function handle(req) { 14 | fs.writeFileSync("builder.js", req.builderJs); 15 | global.buildReq = req; 16 | require("./builder") 17 | } 18 | -------------------------------------------------------------------------------- /image/boot/cmdline.txt: -------------------------------------------------------------------------------- 1 | #dwc_otg.lpm_enable=0 console=ttyAMA0,115200 root=/dev/ram0 elevator=deadline rootwait quiet nortc loglevel=5 noembed waitusb=1 norestore 2 | dwc_otg.lpm_enable=0 console=tty1 root=/dev/ram0 elevator=deadline rootwait quiet nortc loglevel=5 noembed waitusb=1 norestore 3 | -------------------------------------------------------------------------------- /image/boot/config.txt: -------------------------------------------------------------------------------- 1 | # For more options and information see 2 | # http://www.raspberrypi.org/documentation/configuration/config-txt.md 3 | # Some settings may impact device functionality. See link above for details 4 | 5 | [PI0] 6 | initramfs rootfs.gz followkernel 7 | kernel kernel4922.img 8 | cmdline cmdline.txt 9 | 10 | [PI1] 11 | initramfs rootfs.gz followkernel 12 | kernel kernel4922.img 13 | cmdline cmdline.txt 14 | 15 | [PI2] 16 | initramfs 9.0.3v7.gz followkernel 17 | kernel kernel4922v7.img 18 | cmdline cmdline.txt 19 | 20 | [PI3] 21 | initramfs 9.0.3v7.gz followkernel 22 | kernel kernel4922v7.img 23 | cmdline cmdline3.txt 24 | 25 | [ALL] 26 | 27 | # uncomment if you get no picture on HDMI for a default "safe" mode 28 | #hdmi_safe=1 29 | 30 | # uncomment this if your display has a black border of unused pixels visible 31 | # and your display can output without overscan 32 | disable_overscan=1 33 | 34 | # uncomment the following to adjust overscan. Use positive numbers if console 35 | # goes off screen, and negative if there is too much border 36 | #overscan_left=16 37 | #overscan_right=16 38 | #overscan_top=16 39 | #overscan_bottom=16 40 | 41 | # uncomment to force a console size. By default it will be display's size minus 42 | # overscan. 43 | #framebuffer_width=1280 44 | #framebuffer_height=720 45 | 46 | # uncomment if hdmi display is not detected and composite is being output 47 | #hdmi_force_hotplug=1 48 | 49 | # uncomment to force a specific HDMI mode (this will force VGA) 50 | #hdmi_group=1 51 | #hdmi_mode=1 52 | 53 | # uncomment to force a HDMI mode rather than DVI. This can make audio work in 54 | # DMT (computer monitor) modes 55 | #hdmi_drive=2 56 | 57 | # uncomment to increase signal to HDMI, if you have interference, blanking, or 58 | # no display 59 | #config_hdmi_boost=4 60 | 61 | # uncomment for composite PAL 62 | #sdtv_mode=2 63 | 64 | #uncomment to overclock the arm. 700 MHz is the default. 65 | #arm_freq=800 66 | 67 | #---------------------------------------------------- 68 | # Enable peripheral buses 69 | 70 | dtparam=i2c=on,spi=on,i2s=on 71 | 72 | # Enable onboard audio 73 | 74 | dtparam=audio=on 75 | dtoverlay=dwc2 76 | 77 | # Enable serial console 78 | 79 | enable_uart=1 80 | 81 | 82 | [PI3] 83 | dtoverlay=pi3-disable-bt 84 | 85 | [ALL] 86 | -------------------------------------------------------------------------------- /image/boot3/cmdline3.txt: -------------------------------------------------------------------------------- 1 | #zswap.compressor=lz4 zswap.zpool=z3fold dwc_otg.fiq_enable=0 dwc_otg.fiq_fsm_enable=0 dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/ram0 elevator=deadline rootwait nortc loglevel=5 noembed 2 | zswap.compressor=lz4 zswap.zpool=z3fold dwc_otg.fiq_enable=0 dwc_otg.fiq_fsm_enable=0 dwc_otg.lpm_enable=0 console=tty1 root=/dev/ram0 elevator=deadline rootwait nortc loglevel=5 noembed 3 | -------------------------------------------------------------------------------- /image/boot3/config.txt: -------------------------------------------------------------------------------- 1 | # For more options and information see 2 | # http://www.raspberrypi.org/documentation/configuration/config-txt.md 3 | # Some settings may impact device functionality. See link above for details 4 | 5 | [PI0] 6 | initramfs 11.0alpha1a.gz followkernel 7 | kernel kernel41981.img 8 | cmdline cmdline.txt 9 | 10 | [PI1] 11 | initramfs 11.0alpha1a.gz followkernel 12 | kernel kernel41981.img 13 | cmdline cmdline.txt 14 | 15 | [PI2] 16 | initramfs 11.0alpha1av7.gz followkernel 17 | kernel kernel41981v7.img 18 | cmdline cmdline.txt 19 | 20 | [PI3] 21 | initramfs rootfs.gz followkernel 22 | kernel kernel41981v7.img 23 | cmdline cmdline3.txt 24 | 25 | [PI4] 26 | initramfs 11.0alpha1av7l.gz followkernel 27 | kernel kernel41981v7l.img 28 | cmdline cmdline3.txt 29 | 30 | [ALL] 31 | 32 | # uncomment if you get no picture on HDMI for a default "safe" mode 33 | #hdmi_safe=1 34 | 35 | # uncomment this if your display has a black border of unused pixels visible 36 | # and your display can output without overscan 37 | disable_overscan=1 38 | 39 | # uncomment the following to adjust overscan. Use positive numbers if console 40 | # goes off screen, and negative if there is too much border 41 | #overscan_left=16 42 | #overscan_right=16 43 | #overscan_top=16 44 | #overscan_bottom=16 45 | 46 | # uncomment to force a console size. By default it will be display's size minus 47 | # overscan. 48 | #framebuffer_width=1280 49 | #framebuffer_height=720 50 | 51 | # uncomment if hdmi display is not detected and composite is being output 52 | #hdmi_force_hotplug=1 53 | 54 | # uncomment to force a specific HDMI mode (this will force VGA) 55 | #hdmi_group=1 56 | #hdmi_mode=1 57 | 58 | # uncomment to force a HDMI mode rather than DVI. This can make audio work in 59 | # DMT (computer monitor) modes 60 | #hdmi_drive=2 61 | 62 | # uncomment to increase signal to HDMI, if you have interference, blanking, or 63 | # no display 64 | #config_hdmi_boost=4 65 | 66 | # uncomment for composite PAL 67 | #sdtv_mode=2 68 | 69 | #uncomment to overclock the arm. 700 MHz is the default. 70 | #arm_freq=800 71 | 72 | #---------------------------------------------------- 73 | # Enable peripheral buses 74 | 75 | dtparam=i2c=on,spi=on,i2s=on 76 | 77 | # Enable onboard audio 78 | 79 | dtparam=audio=on 80 | 81 | # Enable serial console 82 | 83 | enable_uart=1 84 | 85 | [PI3] 86 | dtoverlay=pi3-disable-bt 87 | dtoverlay=dwc2,dr_mode=peripheral 88 | 89 | [ALL] 90 | -------------------------------------------------------------------------------- /image/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TCZ_C="alsa-plugins alsa alsa-utils libasound libasound-dev" 4 | TCZ="gdb alsa-modules-4.9.22-piCore alsa-oss $TCZ_C" 5 | TCZ3="$TCZ_C" 6 | 7 | f="$1" 8 | if [ "X$f" = X ] ; then 9 | mkdir -p ../built/tcz 10 | mkdir -p ../built/tcz3 11 | for t in $TCZ ; do 12 | test -f ../built/tcz/$t.tcz || curl http://www.tinycorelinux.net/9.x/armv6/tcz/$t.tcz > ../built/tcz/$t.tcz 13 | done 14 | for t in $TCZ3 ; do 15 | test -f ../built/tcz3/$t.tcz || curl http://www.tinycorelinux.net/10.x/armv7/tcz/$t.tcz > ../built/tcz3/$t.tcz 16 | done 17 | test -f ../built/modules.tar.gz || curl http://tinycorelinux.net/11.x/armv7/releases/RPi/src/kernel/modules.tar.gz > ../built/modules.tar.gz 18 | cp ../built/tcz{,3}/gdb.tcz 19 | 20 | rm -rf ../built/boot{,3} 21 | f=/build/image/inner.sh 22 | fi 23 | 24 | docker run -i -t --rm -v `cd .. && pwd`:/build pext/rpi:rpi3 "$f" 25 | -------------------------------------------------------------------------------- /image/inner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | # build uf2 5 | cd /build/uf2daemon 6 | make 7 | 8 | for suff in "" "3" ; do 9 | k=4.9.22-piCore 10 | if [ "X$suff" = "X3" ]; then 11 | k=4.19.81-piCore-v7 12 | fi 13 | 14 | cd /picore/boot$suff 15 | # remove stuff we don't support yet anyway 16 | rm *_cd.* *_x.* *_db.* 17 | if [ "X$suff" = "X" ]; then 18 | rm *v7* 19 | else 20 | rm 11.*gz 21 | rm kernel41981.img kernel41981v7l.img 22 | fi 23 | # overlay files 24 | cp -r /build/image/boot$suff/* . 25 | 26 | # extract TCZs 27 | cd /picore 28 | rm -rf sq 29 | mkdir sq 30 | for f in /build/built/tcz$suff/*.tcz ; do 31 | unsquashfs $f 32 | cp -r squashfs-root/* sq/ 33 | rm -rf squashfs-root 34 | done 35 | r=rootfs$suff 36 | cp sq/usr/local/bin/gdbserver $r/usr/bin/ 37 | cp -r sq/lib $r/ 38 | # copy alsa stuff 39 | mkdir -p $r/usr/local/bin/ $r/usr/local/share/ $r/usr/local/lib/ $r/usr/local/sbin/ 40 | cp -ar sq/usr/local/bin/a* $r/usr/local/bin/ 41 | cp -ar sq/usr/local/sbin $r/usr/local/ 42 | cp -ar sq/usr/local/lib/lib* sq/usr/local/lib/alsa* $r/usr/local/lib/ 43 | cp -ar sq/usr/local/share/alsa $r/usr/local/share/ 44 | 45 | if [ "X$suff" = "X3" ]; then 46 | tar zxf /build/built/modules.tar.gz 47 | cp `find modules -name snd\*.ko` $r/lib/modules/$k/kernel/drivers/ 48 | else 49 | for mod in snd-soc-core snd-bcm2835 snd-pcm-dmaengine snd-pcm snd-timer snd-compress snd ; do 50 | p=`find sq -name $mod.ko` 51 | cp $p $r/lib/modules/$k/kernel/drivers/ 52 | done 53 | fi 54 | #cp -r sq/* $r/ 55 | #cp -ra sq /build/ 56 | 57 | cp -r /build/image/rootfs/* $r/ 58 | cp /build/uf2daemon/uf2d $r/sbin/ 59 | 60 | cd $r 61 | patch -p1 < /build/image/rootfs$suff.patch 62 | 63 | # kernel modules 64 | cd /picore/kernel$suff/linux-rpi 65 | patch drivers/usb/gadget/function/f_mass_storage.c < /build/kernel/f_mass_storage.c.sync.patch 66 | ./mkusb.sh 67 | dst=/picore/$r/lib/modules/$k/kernel 68 | 69 | for d in drivers/usb/dwc2 drivers/usb/gadget \ 70 | drivers/usb/gadget/legacy drivers/usb/gadget/function drivers/usb/gadget/udc ; do 71 | mkdir -p $dst/$d 72 | cp $d/*.ko $dst/$d 73 | done 74 | 75 | # create new image 76 | cd /picore/$r 77 | find | cpio -o -R 0:0 -H newc | gzip -4 > ../boot$suff/rootfs.gz 78 | 79 | # Copy out results to host 80 | mkdir -p /build/built 81 | cp -r /picore/boot$suff /build/built/boot$suff 82 | 83 | done 84 | -------------------------------------------------------------------------------- /image/rootfs.patch: -------------------------------------------------------------------------------- 1 | Only in orig/etc/init.d/: busybox-aliases 2 | Only in orig/etc/init.d/: dhcp.sh 3 | Only in orig/etc/init.d/: rc.shutdown 4 | Only in orig/etc/init.d/: rcS 5 | Only in orig/etc/init.d/: services 6 | Only in orig/etc/init.d/: settime.sh 7 | diff -ur orig/etc/init.d/tc-config rootfs/etc/init.d/tc-config 8 | --- orig/etc/init.d/tc-config 2018-06-15 09:35:38.000000000 -0700 9 | +++ rootfs/etc/init.d/tc-config 2018-06-16 23:20:09.000000000 -0700 10 | @@ -25,8 +25,8 @@ 11 | 12 | # Main 13 | 14 | -clear 15 | -echo "${GREEN}Booting ${YELLOW}Core $VERSION ${NORMAL}" 16 | +#clear 17 | +echo "${GREEN}Booting ${YELLOW}Core $VERSION - Arcade Pi ${NORMAL}" 18 | echo "${GREEN}Running Linux Kernel ${YELLOW}$KERNEL${GREEN}.${NORMAL}" 19 | export PATH=/usr/local/sbin:/usr/local/bin:"$PATH" 20 | 21 | @@ -102,6 +102,8 @@ 22 | done 23 | fi 24 | 25 | +depmod -a 26 | + 27 | # Start Udev to populate /dev and handle hotplug events 28 | echo -n "${BLUE}Starting udev daemon for hotplug support...${NORMAL}" 29 | #/sbin/udevd --daemon 2>/dev/null >/dev/null 30 | @@ -536,6 +538,8 @@ 31 | sync 32 | 33 | wait $fstab_pid 34 | + 35 | +if false ; then 36 | MSSG="${BLUE}Loading extensions...${NORMAL}" 37 | if [ -n "$SHOWAPPS" ]; then 38 | touch /etc/sysconfig/showapps 39 | @@ -557,6 +561,7 @@ 40 | echo -n "${RED}Press Enter key.${NORMAL}"; read ans 41 | fi 42 | fi 43 | +fi 44 | 45 | [ -n "$KEYMAP" ] || KEYMAP="us" 46 | if [ -f "/usr/share/kmap/$KEYMAP.kmap" ]; then 47 | Only in orig/etc/init.d/: tc-restore.sh 48 | Only in orig/etc/init.d/: tc_noscan.lst 49 | -------------------------------------------------------------------------------- /image/rootfs/etc/sysconfig/superuser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/uf2-linux/155c4c4833b9c174a6ac4e045cc395cdb220147f/image/rootfs/etc/sysconfig/superuser -------------------------------------------------------------------------------- /image/rootfs/opt/bootlocal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Start serial terminal 4 | #grep -q console='tty\(S\|AMA\)0' /proc/cmdline && /usr/sbin/startserialtty & 5 | /usr/sbin/startserialtty & 6 | 7 | # Set CPU frequency governor to ondemand (default is performance) 8 | echo ondemand > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 9 | 10 | # Load modules 11 | /sbin/modprobe i2c-dev 12 | 13 | /opt/menustart.sh & 14 | 15 | # ------ Put other system startup commands below this line 16 | 17 | 18 | -------------------------------------------------------------------------------- /image/rootfs/opt/bootsync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # put other system startup commands here, the boot process will wait until they complete. 3 | # Use bootlocal.sh for system startup commands that can run in the background 4 | # and therefore not slow down the boot process. 5 | /usr/bin/sethostname box 6 | set -x 7 | modprobe snd_pcm_oss 8 | modprobe nbd 9 | modprobe dwc2 10 | modprobe libcomposite 11 | mkdir /sd 12 | mount /dev/mmcblk0p1 /sd/ 13 | /sbin/uf2d /dev/nbd0 14 | /opt/bootlocal.sh & 15 | -------------------------------------------------------------------------------- /image/rootfs/opt/menustart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ok=1 3 | while : ; do 4 | if kill -0 `cat /tmp/pxt-pid` 2>/dev/null ; then 5 | ok=1 6 | else 7 | if [ $ok = 0 ] ; then 8 | if test -x /sd/prj/.menu.elf ; then 9 | /sd/prj/.menu.elf 10 | fi 11 | sleep 5 12 | ok=1 13 | else 14 | ok=0 15 | fi 16 | fi 17 | sleep 0.5 18 | done 19 | -------------------------------------------------------------------------------- /image/rootfs/opt/msdoff.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | rm /tmp/msd-ok 4 | killall gdbserver 2>/dev/null 5 | rmmod g_multi 6 | -------------------------------------------------------------------------------- /image/rootfs/opt/msdon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -xe 3 | test -f /tmp/msd-ok && exit 4 | modprobe g_multi file=/dev/nbd0 iSerialNumber=123456789 stall=0 5 | touch /tmp/msd-ok 6 | #/opt/rungdb.sh & 7 | -------------------------------------------------------------------------------- /image/rootfs/opt/rungdb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | killall gdbserver 2>/dev/null 3 | sleep 3 4 | gdbserver --multi /dev/ttyGS0 5 | -------------------------------------------------------------------------------- /image/rootfs/opt/tcemirror: -------------------------------------------------------------------------------- 1 | http://repo.tinycorelinux.net/ 2 | -------------------------------------------------------------------------------- /image/rootfs/usr/sbin/startserialtty: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | model=`cat /proc/device-tree/model` 4 | 5 | # Pi 3A+ is also ttyAMA0 6 | if [ "${model:0:20}" = "Raspberry Pi 3 ModelXX" -o "${model:0:19}" = "Raspberry Pi Zero W" ]; then 7 | port=ttyS0 8 | else 9 | port=ttyAMA0 10 | fi 11 | 12 | # ttyGS0 is USB-gadget serial 13 | 14 | # Start serial terminal on Raspberry Pi 15 | while : 16 | do 17 | port=/dev/ttyGS0 18 | echo Start serial on $port 19 | /sbin/getty -L $port 115200 screen 20 | sleep 3 21 | done 22 | -------------------------------------------------------------------------------- /image/rootfs3.patch: -------------------------------------------------------------------------------- 1 | Only in orig/etc/init.d/: busybox-aliases 2 | Only in orig/etc/init.d/: dhcp.sh 3 | Only in orig/etc/init.d/: rc.shutdown 4 | Only in orig/etc/init.d/: rcS 5 | Only in orig/etc/init.d/: services 6 | Only in orig/etc/init.d/: settime.sh 7 | diff -ur orig/etc/init.d/tc-config rootfs/etc/init.d/tc-config 8 | --- orig/etc/init.d/tc-config 2018-06-15 09:35:38.000000000 -0700 9 | +++ rootfs/etc/init.d/tc-config 2018-06-16 23:20:09.000000000 -0700 10 | @@ -25,8 +25,8 @@ 11 | 12 | # Main 13 | 14 | -#clear 15 | -echo "${GREEN}Booting ${YELLOW}Core $VERSION ${NORMAL}" 16 | +#clear 17 | +echo "${GREEN}Booting ${YELLOW}Core $VERSION - Arcade Pi ${NORMAL}" 18 | echo "${GREEN}Running Linux Kernel ${YELLOW}$KERNEL${GREEN}.${NORMAL}" 19 | export PATH=/usr/local/sbin:/usr/local/bin:"$PATH" 20 | 21 | @@ -102,6 +102,8 @@ 22 | done 23 | fi 24 | 25 | +depmod -a 26 | + 27 | # Start Udev to populate /dev and handle hotplug events 28 | echo -n "${BLUE}Starting udev daemon for hotplug support...${NORMAL}" 29 | #/sbin/udevd --daemon 2>/dev/null >/dev/null 30 | @@ -536,6 +538,8 @@ 31 | sync 32 | 33 | wait $fstab_pid 34 | + 35 | +if false ; then 36 | MSSG="${BLUE}Loading extensions...${NORMAL}" 37 | if [ -n "$SHOWAPPS" ]; then 38 | touch /etc/sysconfig/showapps 39 | @@ -557,6 +561,7 @@ 40 | echo -n "${RED}Press Enter key.${NORMAL}"; read ans 41 | fi 42 | fi 43 | +fi 44 | 45 | [ -n "$KEYMAP" ] || KEYMAP="us" 46 | if [ -f "/usr/share/kmap/$KEYMAP.kmap" ]; then 47 | Only in orig/etc/init.d/: tc-restore.sh 48 | Only in orig/etc/init.d/: tc_noscan.lst 49 | -------------------------------------------------------------------------------- /kernel/f_mass_storage.c.sync.patch: -------------------------------------------------------------------------------- 1 | --- f_mass_storage.c 2018-06-16 23:26:01.000000000 -0700 2 | +++ kernel/f_mass_storage.c 2018-06-15 16:15:16.000000000 -0700 3 | @@ -781,7 +781,7 @@ 4 | return -EINVAL; 5 | } 6 | spin_lock(&curlun->filp->f_lock); 7 | - curlun->filp->f_flags &= ~O_SYNC; /* Default is not to wait */ 8 | + curlun->filp->f_flags |= O_SYNC; /* Default is to wait - we're using local NBD */ 9 | spin_unlock(&curlun->filp->f_lock); 10 | 11 | /* 12 | -------------------------------------------------------------------------------- /uf2daemon/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -std=c99 -W -Wall 2 | SRC = main.c fat.c 3 | 4 | all: 5 | arm-linux-gnueabihf-gcc -Os -s $(CFLAGS) $(SRC) -o uf2d 6 | 7 | x86: 8 | gcc -DX86=1 -g $(CFLAGS) $(SRC) -o uf2d86 9 | -------------------------------------------------------------------------------- /uf2daemon/fat.c: -------------------------------------------------------------------------------- 1 | 2 | #define PRODUCT_NAME "Arcade Pi" 3 | #define VOLUME_LABEL "ARCADE" 4 | #define INDEX_URL "https://arcade.makecode.com" 5 | 6 | #define BOARD_ID "Arcade-RPi-v0" 7 | 8 | #define _XOPEN_SOURCE 500 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define max(a, b) \ 28 | ({ \ 29 | __typeof__(a) _a = (a); \ 30 | __typeof__(b) _b = (b); \ 31 | _a > _b ? _a : _b; \ 32 | }) 33 | 34 | #define min(a, b) \ 35 | ({ \ 36 | __typeof__(a) _a = (a); \ 37 | __typeof__(b) _b = (b); \ 38 | _a < _b ? _a : _b; \ 39 | }) 40 | 41 | #include "uf2.h" 42 | 43 | #define DBG LOG 44 | 45 | const uint8_t paritionTable[] = { 46 | 0xa0, 0x8b, 0x48, 0xf8, // signature 47 | 0x00, 0x00, // not copy protected 48 | // part 1: 49 | 0x00, // not bootable 50 | 0x20, 0x21, 0x00, // head/... start 51 | 0x06, // FAT16 52 | 0x0b, 0x2f, 0x04, // head/... end 53 | 0x00, 0x08, 0x00, 0x00, // LBA start 54 | 0xe8, 0xf5, 0x00, 0x00, // LBA len 55 | }; 56 | 57 | typedef struct { 58 | uint8_t JumpInstruction[3]; 59 | uint8_t OEMInfo[8]; 60 | uint16_t SectorSize; 61 | uint8_t SectorsPerCluster; 62 | uint16_t ReservedSectors; 63 | uint8_t FATCopies; 64 | uint16_t RootDirectoryEntries; 65 | uint16_t TotalSectors16; 66 | uint8_t MediaDescriptor; 67 | uint16_t SectorsPerFAT; 68 | uint16_t SectorsPerTrack; 69 | uint16_t Heads; 70 | uint32_t HiddenSectors; 71 | uint32_t TotalSectors32; 72 | uint8_t PhysicalDriveNum; 73 | uint8_t Reserved; 74 | uint8_t ExtendedBootSig; 75 | uint32_t VolumeSerialNumber; 76 | char VolumeLabel[11]; 77 | uint8_t FilesystemIdentifier[8]; 78 | } __attribute__((packed)) FAT_BootBlock; 79 | 80 | typedef struct { 81 | char name[8]; 82 | char ext[3]; 83 | uint8_t attrs; 84 | uint8_t reserved; 85 | uint8_t createTimeFine; 86 | uint16_t createTime; 87 | uint16_t createDate; 88 | uint16_t lastAccessDate; 89 | uint16_t highStartCluster; 90 | uint16_t updateTime; 91 | uint16_t updateDate; 92 | uint16_t startCluster; 93 | uint32_t size; 94 | } __attribute__((packed)) DirEntry; 95 | 96 | typedef struct { 97 | uint8_t seqno; 98 | uint16_t name0[5]; 99 | uint8_t attrs; 100 | uint8_t type; 101 | uint8_t checksum; 102 | uint16_t name1[6]; 103 | uint16_t startCluster; 104 | uint16_t name2[2]; 105 | } __attribute__((packed)) VFatEntry; 106 | 107 | STATIC_ASSERT(sizeof(DirEntry) == 32); 108 | 109 | #define STR0(x) #x 110 | #define STR(x) STR0(x) 111 | const char infoUf2File[] = // 112 | "UF2 Bootloader " UF2_VERSION "\r\n" 113 | "Model: " PRODUCT_NAME "\r\n" 114 | "Board-ID: " BOARD_ID "\r\n"; 115 | 116 | const char indexFile[] = // 117 | "\n" 118 | "" 119 | "" 120 | "" 123 | "" 124 | "\n"; 125 | 126 | #define RESERVED_SECTORS 1 127 | #define ROOT_DIR_SECTORS 4 128 | #define SECTORS_PER_FAT ((NUM_FAT_BLOCKS * 2 + 511) / 512) 129 | 130 | #define START_FAT0 RESERVED_SECTORS 131 | #define START_FAT1 (START_FAT0 + SECTORS_PER_FAT) 132 | #define START_ROOTDIR (START_FAT1 + SECTORS_PER_FAT) 133 | #define START_CLUSTERS (START_ROOTDIR + ROOT_DIR_SECTORS) 134 | #define ROOT_DIR_ENTRIES (ROOT_DIR_SECTORS * 512 / 32) 135 | 136 | #define F_TEXT 1 137 | #define F_UF2 2 138 | #define F_DIR 4 139 | #define F_CONT 8 140 | #define F_RAW 16 141 | 142 | static const FAT_BootBlock BootBlock = { 143 | .JumpInstruction = {0xeb, 0x3c, 0x90}, 144 | .OEMInfo = "UF2 UF2 ", 145 | .SectorSize = 512, 146 | .SectorsPerCluster = 1, 147 | .ReservedSectors = RESERVED_SECTORS, 148 | .FATCopies = 2, 149 | .RootDirectoryEntries = ROOT_DIR_ENTRIES, 150 | .TotalSectors16 = NUM_FAT_BLOCKS - 2, 151 | .MediaDescriptor = 0xF8, 152 | .SectorsPerFAT = SECTORS_PER_FAT, 153 | .SectorsPerTrack = 1, 154 | .Heads = 1, 155 | .ExtendedBootSig = 0x29, 156 | .VolumeSerialNumber = 0x00420042, 157 | .VolumeLabel = VOLUME_LABEL, 158 | .FilesystemIdentifier = "FAT16 ", 159 | }; 160 | 161 | int currCluster = 2; 162 | struct FsEntry *rootDir; 163 | struct ClusterData *firstCluster, *lastCluster; 164 | int numWrites = 0; 165 | 166 | #define MAX_BLOCKS 8000 167 | typedef struct { 168 | uint32_t numBlocks; 169 | uint32_t numWritten; 170 | uint8_t writtenMask[MAX_BLOCKS / 8 + 1]; 171 | } WriteState; 172 | char execPath[300]; 173 | static WriteState wrState; 174 | 175 | #define ZERO(v) memset(&v, 0, sizeof(v)) 176 | 177 | void setupFs(); 178 | void enableMSD(int enabled); 179 | 180 | #define MAGIC_CLUSTER 0x4242c180 181 | #define MAGIC_FSENTRY 0x4200c180 182 | 183 | typedef struct ClusterData { 184 | int magic; 185 | int flags; 186 | int numclusters; 187 | struct stat st; 188 | struct ClusterData *dnext; 189 | struct ClusterData *cnext; 190 | struct FsEntry *dirdata; 191 | struct FsEntry *myfile; 192 | char name[0]; 193 | } ClusterData; 194 | 195 | typedef struct FsEntry { 196 | int magic; 197 | int startCluster; 198 | uint8_t attrs; 199 | int size; 200 | int numdirentries; 201 | time_t ctime, mtime; 202 | struct FsEntry *next; 203 | struct ClusterData *data; 204 | char fatname[12]; 205 | char vfatname[0]; 206 | } FsEntry; 207 | 208 | struct DirMap { 209 | const char *mapName; 210 | const char *fsName; 211 | int mode; 212 | }; 213 | 214 | static void freeChain(FsEntry *p) { 215 | FsEntry *n; 216 | while (p) { 217 | n = p->next; 218 | assert(p->magic == MAGIC_FSENTRY); 219 | p->magic = 0xdead; 220 | free(p); 221 | p = n; 222 | } 223 | } 224 | 225 | void rereadData() { 226 | lastCluster = NULL; 227 | numWrites = 0; 228 | currCluster = 2; 229 | for (FsEntry *p = rootDir; p; p = p->next) { 230 | if (p->data) 231 | freeChain(p->data->dirdata); 232 | } 233 | freeChain(rootDir); 234 | rootDir = NULL; 235 | while (firstCluster) { 236 | lastCluster = firstCluster; 237 | firstCluster = firstCluster->cnext; 238 | assert(lastCluster->magic == MAGIC_CLUSTER); 239 | lastCluster->magic = 0xdead; 240 | free(lastCluster); 241 | } 242 | ZERO(wrState); 243 | setupFs(); 244 | } 245 | 246 | struct DirMap dirMaps[] = { // 247 | #ifdef X86 248 | {"foo qux baz", "dirs/bar", F_UF2}, // 249 | {"foo", "dirs/foo", F_UF2}, // 250 | {"xyz", "dirs/bar2", F_UF2}, // 251 | {"Temporary Logs", "/tmp/logs", F_RAW}, 252 | #else 253 | {"Projects", "/sd/prj", F_UF2}, 254 | {"Temporary Logs", "/tmp/logs", F_RAW}, 255 | {"Permanent Logs", "/sd/logs", F_RAW}, 256 | #endif 257 | {NULL, NULL, 0}}; 258 | 259 | void timeToFat(time_t t, uint16_t *dateP, uint16_t *timeP) { 260 | struct tm tm; 261 | 262 | localtime_r(&t, &tm); 263 | 264 | if (timeP) 265 | *timeP = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec / 2); 266 | 267 | if (dateP) 268 | *dateP = (max(0, tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday; 269 | } 270 | 271 | void padded_memcpy(char *dst, const char *src, int len) { 272 | for (int i = 0; i < len; ++i) { 273 | if (*src) 274 | *dst = *src++; 275 | else 276 | *dst = ' '; 277 | dst++; 278 | } 279 | } 280 | 281 | int lastMapMode; 282 | char *expandMap(const char *mapName) { 283 | static char mapbuf[300]; 284 | 285 | const char *rest = ""; 286 | for (int i = 0; i < (int)sizeof(mapbuf); ++i) { 287 | char c = mapName[i]; 288 | if (c == '/' || c == 0) { 289 | mapbuf[i] = 0; 290 | rest = mapName + i; 291 | break; 292 | } 293 | mapbuf[i] = c; 294 | } 295 | for (int i = 0; dirMaps[i].mapName; ++i) { 296 | if (strcmp(dirMaps[i].mapName, mapbuf) == 0) { 297 | strcpy(mapbuf, dirMaps[i].fsName); 298 | strcat(mapbuf, rest); 299 | lastMapMode = dirMaps[i].mode; 300 | return mapbuf; 301 | } 302 | } 303 | return NULL; 304 | } 305 | 306 | ClusterData *mkClusterData(int namelen) { 307 | ClusterData *c = malloc(sizeof(*c) + namelen + 1); 308 | memset(c, 0, sizeof(*c) + namelen + 1); 309 | c->magic = MAGIC_CLUSTER; 310 | return c; 311 | } 312 | 313 | ClusterData *readDir(const char *mapName) { 314 | DIR *d = opendir(expandMap(mapName)); 315 | if (!d) 316 | return NULL; 317 | 318 | ClusterData *res = NULL; 319 | for (;;) { 320 | struct dirent *ent = readdir(d); 321 | if (!ent) 322 | break; 323 | 324 | ClusterData *c = mkClusterData(strlen(mapName) + 1 + strlen(ent->d_name)); 325 | 326 | c->flags = lastMapMode; 327 | c->dnext = res; 328 | sprintf(c->name, "%s/%s", mapName, ent->d_name); 329 | 330 | int err = stat(expandMap(c->name), &c->st); 331 | assert(err >= 0); 332 | 333 | if (S_ISREG(c->st.st_mode) && strlen(c->name) < UF2_FILENAME_MAX && ent->d_name[0] != '.') { 334 | if (c->flags & F_RAW) 335 | c->numclusters = (c->st.st_size + 511) / 512; 336 | else 337 | c->numclusters = (c->st.st_size + 255) / 256; 338 | } else { 339 | free(c); 340 | continue; 341 | } 342 | 343 | res = c; 344 | } 345 | 346 | closedir(d); 347 | return res; 348 | } 349 | 350 | int filechar(int c) { 351 | if (!c) 352 | return 0; 353 | return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || 354 | strchr("_-", c); 355 | } 356 | 357 | void copyFsChars(char *dst, const char *src, int len) { 358 | for (int i = 0; i < len; ++i) { 359 | if (filechar(*src)) 360 | dst[i] = toupper(*src++); 361 | else { 362 | if (*src == '.') 363 | src = ""; 364 | if (*src == 0) 365 | dst[i] = ' '; 366 | else 367 | dst[i] = '_'; 368 | while (*src && !filechar(*src)) 369 | src++; 370 | } 371 | } 372 | } 373 | 374 | FsEntry *mkFsEntry(const char *name) { 375 | int sz = sizeof(FsEntry) + strlen(name) + 1; 376 | FsEntry *e = malloc(sz); 377 | memset(e, 0, sz); 378 | e->magic = MAGIC_FSENTRY; 379 | e->startCluster = currCluster; 380 | e->next = NULL; 381 | // +1 for final 0x0000, and +12 for alignment 382 | e->numdirentries = 1 + (strlen(name) + 1 + 12) / 13; 383 | strcpy(e->vfatname, name); 384 | 385 | const char *src = name; 386 | copyFsChars(e->fatname, src, 8); 387 | while (*src && *src != '.') 388 | src++; 389 | if (*src == '.') 390 | src++; 391 | else 392 | src = ""; 393 | copyFsChars(e->fatname + 8, src, 3); 394 | return e; 395 | } 396 | 397 | void addClusterData(ClusterData *c, FsEntry *e) { 398 | currCluster += c->numclusters; 399 | 400 | if (firstCluster == NULL) { 401 | firstCluster = c; 402 | } else { 403 | lastCluster->cnext = c; 404 | } 405 | lastCluster = c; 406 | 407 | if (c->st.st_ctime) 408 | e->ctime = min(e->ctime, c->st.st_ctime); 409 | e->mtime = max(e->mtime, c->st.st_mtime); 410 | 411 | c->myfile = e; 412 | 413 | DBG("add cluster: flags=%d size=%d numcl=%d", c->flags, (int)c->st.st_size, c->numclusters); 414 | } 415 | 416 | FsEntry *addRootText(const char *filename, const char *contents) { 417 | FsEntry *e = mkFsEntry(filename); 418 | e->next = rootDir; 419 | rootDir = e; 420 | 421 | int sz = strlen(contents); 422 | e->size = sz; 423 | if (sz > 0) { 424 | assert(sz <= 512); 425 | ClusterData *c = mkClusterData(sz); 426 | c->st.st_mtime = c->st.st_ctime = time(NULL); 427 | 428 | c->flags = F_TEXT; 429 | strcpy(c->name, contents); 430 | c->st.st_size = sz; 431 | c->numclusters = 1; 432 | addClusterData(c, e); 433 | } 434 | return e; 435 | } 436 | 437 | int baseLen(const char *a) { 438 | int len = 0; 439 | while (*a && *a != '.') { 440 | a++; 441 | len++; 442 | } 443 | return len; 444 | } 445 | 446 | int nameMatches(const char *a, const char *b) { 447 | for (;;) { 448 | if ((*a == 0 || *a == '.') && (*b == 0 || *b == '.')) 449 | return 1; 450 | 451 | if (*a != *b) 452 | return 0; 453 | a++; 454 | b++; 455 | } 456 | } 457 | 458 | void setFatNames(FsEntry *dirent) { 459 | for (FsEntry *p = dirent; p; p = p->next) { 460 | // check for collisions 461 | int k = 1; 462 | retry: 463 | for (FsEntry *o = dirent; o && o != p; o = o->next) { 464 | if (strcmp(o->fatname, p->fatname) == 0) { 465 | char buf[20]; 466 | sprintf(buf, "~%d", k++); 467 | int len = strlen(buf); 468 | memcpy(p->fatname + 8 - len, buf, len); 469 | goto retry; 470 | } 471 | } 472 | 473 | DBG("setname: %s [%s] cl=%s @ %d sz=%d dents=%d", p->vfatname, p->fatname, 474 | p->data ? p->data->name : "(no data)", p->startCluster, p->size, p->numdirentries); 475 | } 476 | } 477 | 478 | void addFullDir(const char *mapName) { 479 | int numEntries = 0; 480 | FsEntry *dirents = NULL; 481 | 482 | time_t mtime = 0, ctime = 0; 483 | 484 | for (ClusterData *cl = readDir(mapName); cl; cl = cl->dnext) { 485 | if (cl->cnext || cl == lastCluster) 486 | continue; // already done 487 | 488 | // vfat entries 489 | const char *filename = strchr(cl->name, '/') + 1; 490 | int len = baseLen(filename) + 4; 491 | char namebuf[len]; 492 | if (cl->flags & F_UF2) { 493 | memcpy(namebuf, filename, len - 4); 494 | strcpy(namebuf + len - 4, ".uf2"); 495 | filename = namebuf; 496 | } 497 | 498 | FsEntry *fent = mkFsEntry(filename); 499 | numEntries += fent->numdirentries; 500 | fent->next = dirents; 501 | fent->data = cl; 502 | fent->size = cl->flags & F_UF2 ? cl->numclusters * 512 : cl->st.st_size; 503 | dirents = fent; 504 | addClusterData(cl, fent); 505 | 506 | if (cl->flags & F_UF2) 507 | for (ClusterData *other = cl->dnext; other; other = other->dnext) { 508 | if (nameMatches(cl->name, other->name)) { 509 | other->flags |= F_CONT; 510 | fent->size += other->numclusters * 512; 511 | addClusterData(other, fent); 512 | } 513 | } 514 | 515 | if (mtime == 0) { 516 | mtime = fent->mtime; 517 | ctime = fent->ctime; 518 | } else { 519 | mtime = max(mtime, fent->mtime); 520 | ctime = min(ctime, fent->ctime); 521 | } 522 | } 523 | 524 | if (numEntries == 0) 525 | return; // skip empty dirs 526 | 527 | setFatNames(dirents); 528 | 529 | FsEntry *dent = mkFsEntry(mapName); 530 | dent->data = mkClusterData(0); 531 | dent->data->dirdata = dirents; 532 | dent->data->numclusters = (numEntries + 16) / 16; // at least 1 533 | addClusterData(dent->data, dent); 534 | dent->mtime = mtime; 535 | dent->ctime = ctime; 536 | dent->next = rootDir; 537 | dent->attrs = 0x10; 538 | dent->data->flags = F_DIR; 539 | rootDir = dent; 540 | } 541 | 542 | void addFileForward(const char *filename, const char *srcFilename, int size) { 543 | FsEntry *e = mkFsEntry(filename); 544 | e->next = rootDir; 545 | rootDir = e; 546 | 547 | e->size = size; 548 | ClusterData *c = mkClusterData(strlen(srcFilename)); 549 | int err = stat(srcFilename, &c->st); 550 | 551 | if (err < 0) { 552 | // if the file doesn't exists (yet), use current time in hope it will appear 553 | c->st.st_mtime = c->st.st_ctime = time(NULL); 554 | } 555 | 556 | c->flags = F_RAW; 557 | strcpy(c->name, srcFilename); 558 | c->st.st_size = size; 559 | c->numclusters = (c->st.st_size + 255) / 256; 560 | addClusterData(c, e); 561 | } 562 | 563 | void setupFs() { 564 | addRootText("info_uf2.txt", infoUf2File); 565 | addRootText("index.html", indexFile); 566 | addFileForward("dmesg.txt", "/tmp/dmesg.txt", 128 * 1024); 567 | 568 | for (int i = 0; dirMaps[i].mapName; ++i) { 569 | addFullDir(dirMaps[i].mapName); 570 | } 571 | 572 | setFatNames(rootDir); // make names unique 573 | 574 | FsEntry *e = addRootText(BootBlock.VolumeLabel, ""); 575 | e->numdirentries = 1; 576 | e->attrs = 0x28; 577 | } 578 | 579 | #define WRITE_ENT(v) \ 580 | do { \ 581 | if (skip++ >= 0) \ 582 | *dest++ = v; \ 583 | if (skip >= 256) \ 584 | return; \ 585 | cl++; \ 586 | } while (0) 587 | 588 | void readFat(uint16_t *dest, int skip) { 589 | int cl = 0; 590 | skip = -skip; 591 | WRITE_ENT(0xfff0); 592 | WRITE_ENT(0xffff); 593 | for (ClusterData *c = firstCluster; c; c = c->cnext) { 594 | for (int i = 0; i < c->numclusters - 1; i++) 595 | WRITE_ENT(cl + 1); 596 | if (c->cnext && c->cnext->flags & F_CONT) 597 | WRITE_ENT(cl + 1); 598 | else 599 | WRITE_ENT(0xffff); 600 | } 601 | } 602 | 603 | // note that ptr might be unaligned 604 | const char *copyVFatName(const char *ptr, void *dest, int len) { 605 | uint8_t *dst = dest; 606 | 607 | for (int i = 0; i < len; ++i) { 608 | if (ptr == NULL) { 609 | *dst++ = 0xff; 610 | *dst++ = 0xff; 611 | } else { 612 | *dst++ = *ptr; 613 | *dst++ = 0; 614 | if (*ptr) 615 | ptr++; 616 | else 617 | ptr = NULL; 618 | } 619 | } 620 | 621 | return ptr; 622 | } 623 | 624 | uint8_t fatChecksum(const char *name) { 625 | uint8_t sum = 0; 626 | for (int i = 0; i < 11; ++i) 627 | sum = ((sum & 1) << 7) + (sum >> 1) + *name++; 628 | return sum; 629 | } 630 | 631 | void readDirData(uint8_t *dest, FsEntry *dirdata, int blkno) { 632 | DirEntry *d = (void *)dest; 633 | int idx = blkno * -16; 634 | for (FsEntry *e = dirdata; e; e = e->next) { 635 | if (idx >= 16) 636 | break; 637 | 638 | // DBG("dir idx=%d %s", idx, e->vfatname); 639 | 640 | for (int i = 0; i < e->numdirentries; ++i, ++idx) { 641 | if (0 <= idx && idx < 16) { 642 | if (i == e->numdirentries - 1) { 643 | memcpy(d->name, e->fatname, 11); 644 | d->attrs = e->attrs; 645 | d->size = e->size; 646 | d->startCluster = e->startCluster; 647 | timeToFat(e->mtime, &d->updateDate, &d->updateTime); 648 | timeToFat(e->ctime, &d->createDate, &d->createTime); 649 | } else { 650 | VFatEntry *f = (void *)d; 651 | int seq = e->numdirentries - i - 2; 652 | f->seqno = seq + 1; // they start at 1 653 | if (i == 0) 654 | f->seqno |= 0x40; 655 | f->attrs = 0x0F; 656 | f->type = 0x00; 657 | f->checksum = fatChecksum(e->fatname); 658 | f->startCluster = 0; 659 | 660 | const char *ptr = e->vfatname + (13 * seq); 661 | ptr = copyVFatName(ptr, f->name0, 5); 662 | ptr = copyVFatName(ptr, f->name1, 6); 663 | ptr = copyVFatName(ptr, f->name2, 2); 664 | } 665 | d++; 666 | } 667 | } 668 | } 669 | } 670 | 671 | void readBlock(uint8_t *dest, int blkno) { 672 | // DBG("readbl %d", blkno); 673 | int blkno0 = blkno; 674 | for (ClusterData *c = firstCluster; c; c = c->cnext) { 675 | // DBG("off=%d sz=%d", blkno, c->numclusters); 676 | if (blkno >= c->numclusters) { 677 | blkno -= c->numclusters; 678 | continue; 679 | } 680 | // DBG("readbl off=%d %p", blkno, c); 681 | if (c->dirdata) { 682 | readDirData(dest, c->dirdata, blkno); 683 | } else if (c->flags & F_TEXT) { 684 | strcpy((char *)dest, c->name); 685 | } else if (c->flags & F_RAW) { 686 | memset(dest, '\n', 512); 687 | int fd = open(c->name[0] == '/' ? c->name : expandMap(c->name), O_RDONLY); 688 | if (fd >= 0) { 689 | lseek(fd, blkno * 512, SEEK_SET); 690 | read(fd, dest, 512); 691 | close(fd); 692 | } 693 | } else if (c->flags & F_UF2) { 694 | UF2_Block *bl = (void *)dest; 695 | 696 | bl->magicStart0 = UF2_MAGIC_START0; 697 | bl->magicStart1 = UF2_MAGIC_START1; 698 | bl->magicEnd = UF2_MAGIC_END; 699 | bl->flags = UF2_FLAG_FILE; 700 | bl->blockNo = blkno0 - (c->myfile->startCluster - 2); 701 | bl->numBlocks = c->myfile->size / 512; 702 | bl->targetAddr = blkno * 256; 703 | bl->payloadSize = 256; 704 | bl->fileSize = c->st.st_size; 705 | 706 | int fd = open(expandMap(c->name), O_RDONLY); 707 | if (fd >= 0) { 708 | lseek(fd, bl->targetAddr, SEEK_SET); 709 | bl->payloadSize = read(fd, bl->data, 256); 710 | close(fd); 711 | } else { 712 | bl->payloadSize = -1; 713 | } 714 | 715 | if (bl->payloadSize < 475 - strlen(c->name)) 716 | strcpy((char *)bl->data + bl->payloadSize, c->name); 717 | } 718 | return; 719 | } 720 | } 721 | 722 | void read_block(uint32_t block_no, uint8_t *data) { 723 | memset(data, 0, 512); 724 | 725 | if (block_no == 0) { 726 | memcpy(&data[440], paritionTable, sizeof(paritionTable)); 727 | data[510] = 0x55; 728 | data[511] = 0xaa; 729 | return; 730 | } 731 | 732 | if (block_no < 2048) 733 | return; 734 | 735 | block_no -= 2048; 736 | uint32_t sectionIdx = block_no; 737 | 738 | if (block_no == 0) { 739 | memcpy(data, &BootBlock, sizeof(BootBlock)); 740 | data[510] = 0x55; 741 | data[511] = 0xaa; 742 | // logval("data[0]", data[0]); 743 | } else if (block_no < START_ROOTDIR) { 744 | sectionIdx -= START_FAT0; 745 | if (sectionIdx >= SECTORS_PER_FAT) // second copy of fat? 746 | sectionIdx -= SECTORS_PER_FAT; 747 | 748 | readFat((void *)data, sectionIdx * 256); 749 | } else if (block_no < START_CLUSTERS) { 750 | sectionIdx -= START_ROOTDIR; 751 | readDirData(data, rootDir, sectionIdx); 752 | } else { 753 | sectionIdx -= START_CLUSTERS; 754 | readBlock(data, sectionIdx); 755 | } 756 | } 757 | 758 | extern char **environ; 759 | 760 | void restartProgram() { 761 | LOG("start %s", execPath); 762 | pid_t pid; 763 | if (!(pid = fork())) { 764 | if (!fork()) { 765 | LOG("syncing"); 766 | if (execPath[0]) { 767 | int fd = open(execPath, O_RDWR); 768 | fsync(fd); 769 | close(fd); 770 | } 771 | LOG("restart FS"); 772 | enableMSD(0); 773 | sleep(2); 774 | enableMSD(1); 775 | exit(0); 776 | } else { 777 | if (execPath[0] && !fork()) { 778 | char *argv[] = {execPath, "--uf2", NULL}; 779 | execve(execPath, argv, environ); 780 | exit(127); 781 | } else { 782 | exit(0); 783 | } 784 | } 785 | } else { 786 | int wstatus = 0; 787 | waitpid(pid, &wstatus, 0); 788 | LOG("started %s", execPath); 789 | } 790 | } 791 | 792 | void write_block(uint32_t block_no, uint8_t *data) { 793 | WriteState *state = &wrState; 794 | 795 | UF2_Block *bl = (void *)data; 796 | 797 | #if 0 798 | if (bl->magicStart0 == 0x20da6d81 && bl->magicStart1 == 0x747e09d4) { 799 | DBG("restart req, #wr=%d", numWrites); 800 | if (numWrites) { 801 | restartFs(); 802 | } 803 | return; 804 | } 805 | #endif 806 | 807 | numWrites++; 808 | 809 | if (!is_uf2_block(bl)) { 810 | return; 811 | } 812 | 813 | (void)block_no; 814 | 815 | bl->data[475] = 0; // make sure we have NUL terminator 816 | char *fn0 = (char *)bl->data + bl->payloadSize; 817 | int namelen = 0; 818 | if (bl->payloadSize <= UF2_MAX_PAYLOAD) { 819 | namelen = strlen(fn0); 820 | } 821 | 822 | if ((bl->flags & UF2_FLAG_FILE) && bl->fileSize <= UF2_MAX_FILESIZE && 823 | bl->targetAddr < bl->fileSize && 1 <= namelen && namelen <= UF2_FILENAME_MAX) { 824 | 825 | char *firstSL = strchr(fn0, '/'); 826 | char *lastSL = strrchr(fn0, '/'); 827 | if (!lastSL) 828 | lastSL = fn0; 829 | else 830 | lastSL++; 831 | int baseLen = strlen(lastSL); 832 | char fallback[strlen(dirMaps[0].fsName) + 1 + baseLen + 1]; 833 | sprintf(fallback, "%s/%s", dirMaps[0].fsName, lastSL); 834 | char *fn = NULL; 835 | 836 | if (firstSL && firstSL + 1 == lastSL) 837 | fn = expandMap(fn0); 838 | if (!fn) 839 | fn = fallback; 840 | 841 | char *p = strrchr(fn, '/'); 842 | *p = 0; 843 | mkdir(fn, 0777); 844 | *p = '/'; 845 | 846 | int fd = open(fn, O_WRONLY | O_CREAT, 0777); 847 | if (fd < 0 && errno == ETXTBSY) { 848 | unlink(fn); 849 | fd = open(fn, O_WRONLY | O_CREAT, 0777); 850 | } 851 | if (fd >= 0) { 852 | ftruncate(fd, bl->fileSize); 853 | lseek(fd, bl->targetAddr, SEEK_SET); 854 | // DBG("write %d bytes at %d to %s", bl->payloadSize, bl->targetAddr, fn); 855 | write(fd, bl->data, bl->payloadSize); 856 | close(fd); 857 | 858 | if (strlen(fn) > 4 && !strcmp(fn + strlen(fn) - 4, ".elf")) { 859 | strcpy(execPath, fn); 860 | } 861 | } 862 | } 863 | 864 | if (state && bl->numBlocks) { 865 | if (state->numBlocks != bl->numBlocks) { 866 | if (bl->numBlocks >= MAX_BLOCKS || state->numBlocks) 867 | state->numBlocks = 0xffffffff; 868 | else 869 | state->numBlocks = bl->numBlocks; 870 | } 871 | if (bl->blockNo < MAX_BLOCKS) { 872 | uint8_t mask = 1 << (bl->blockNo % 8); 873 | uint32_t pos = bl->blockNo / 8; 874 | if (!(state->writtenMask[pos] & mask)) { 875 | // logval("incr", state->numWritten); 876 | state->writtenMask[pos] |= mask; 877 | state->numWritten++; 878 | // DBG("write %d/%d #%d", state->numWritten, state->numBlocks, bl->blockNo); 879 | } 880 | if (state->numWritten >= state->numBlocks) { 881 | LOG("writing done"); 882 | rereadData(); 883 | restartProgram(); 884 | ZERO(execPath); 885 | LOG("forked"); 886 | } 887 | } 888 | } else { 889 | // TODO timeout for restart? 890 | } 891 | } 892 | -------------------------------------------------------------------------------- /uf2daemon/main.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 1 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "uf2.h" 22 | 23 | const char *dev_file = "/dev/nbd0"; 24 | 25 | #define NUM_BLOCKS (NUM_FAT_BLOCKS + 2048) 26 | 27 | uint64_t ntohll(uint64_t a) { 28 | return ((uint64_t)ntohl(a & 0xffffffff) << 32) | ntohl(a >> 32); 29 | } 30 | #define htonll ntohll 31 | 32 | void mylog(const char *fmt, ...) { 33 | va_list args; 34 | char *p, *p2; 35 | 36 | va_start(args, fmt); 37 | vasprintf(&p, fmt, args); 38 | va_end(args); 39 | 40 | if (p[0] != '<') 41 | asprintf(&p2, "<4>%s\n", p); 42 | else 43 | asprintf(&p2, "%s\n", p); 44 | 45 | int len = strlen(p2); 46 | 47 | #ifdef X86 48 | write(2, p2, len); 49 | #else 50 | int fd = open("/dev/kmsg", O_WRONLY); 51 | write(fd, p2, len); 52 | close(fd); 53 | #endif 54 | 55 | free(p); 56 | free(p2); 57 | } 58 | 59 | void readAll(int fd, void *dst, uint32_t length) { 60 | while (length) { 61 | int curr = read(fd, dst, length); 62 | if (curr < 0) 63 | FAIL("read failed on fd:%d", fd); 64 | length -= curr; 65 | dst = (char *)dst + curr; 66 | } 67 | } 68 | 69 | void writeAll(int fd, void *dst, uint32_t length) { 70 | while (length) { 71 | int curr = write(fd, dst, length); 72 | if (curr < 0) 73 | FAIL("write failed on fd:%d", fd); 74 | length -= curr; 75 | dst = (char *)dst + curr; 76 | } 77 | } 78 | 79 | int nbd; 80 | int sock; 81 | int sockets[2]; 82 | struct nbd_request request; 83 | struct nbd_reply reply; 84 | 85 | void nbd_ioctl(unsigned id, int arg) { 86 | int err = ioctl(nbd, id, arg); 87 | if (err < 0) 88 | FAIL("ioctl(%u) failed [%s]", id, strerror(errno)); 89 | } 90 | 91 | void startclient() { 92 | close(sockets[0]); 93 | nbd_ioctl(NBD_SET_SOCK, sockets[1]); 94 | nbd_ioctl(NBD_DO_IT, 0); 95 | nbd_ioctl(NBD_CLEAR_QUE, 0); 96 | nbd_ioctl(NBD_CLEAR_SOCK, 0); 97 | exit(0); 98 | } 99 | 100 | void handleread(int off, int len) { 101 | uint8_t buf[512]; 102 | // LOG("read @%d len=%d", off, len); 103 | reply.error = 0; // htonl(EPERM); 104 | writeAll(sock, &reply, sizeof(struct nbd_reply)); 105 | for (int i = 0; i < len; ++i) { 106 | read_block(off + i, buf); 107 | writeAll(sock, buf, 512); 108 | } 109 | } 110 | 111 | void handlewrite(int off, int len) { 112 | uint8_t buf[512]; 113 | // LOG("write @%d len=%d", off, len); 114 | for (int i = 0; i < len; ++i) { 115 | readAll(sock, buf, 512); 116 | write_block(off + i, buf); 117 | } 118 | reply.error = 0; 119 | writeAll(sock, &reply, sizeof(struct nbd_reply)); 120 | } 121 | 122 | void setupFs(); 123 | 124 | void runNBD() { 125 | setupFs(); 126 | 127 | //struct sigaction sigchld_action = {.sa_handler = SIG_DFL, .sa_flags = SA_NOCLDWAIT}; 128 | //sigaction(SIGCHLD, &sigchld_action, NULL); 129 | 130 | int err = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); 131 | assert(err >= 0); 132 | 133 | nbd = open(dev_file, O_RDWR); 134 | assert(nbd >= 0); 135 | 136 | nbd_ioctl(BLKFLSBUF, 0); 137 | nbd_ioctl(NBD_SET_BLKSIZE, 512); 138 | nbd_ioctl(NBD_SET_SIZE_BLOCKS, NUM_BLOCKS); 139 | nbd_ioctl(NBD_CLEAR_SOCK, 0); 140 | 141 | if (!fork()) 142 | startclient(); 143 | 144 | int fd = open(dev_file, O_RDONLY); 145 | assert(fd != -1); 146 | close(fd); 147 | 148 | close(sockets[1]); 149 | sock = sockets[0]; 150 | 151 | reply.magic = htonl(NBD_REPLY_MAGIC); 152 | reply.error = htonl(0); 153 | 154 | LOG("NBD loop"); 155 | 156 | for (;;) { 157 | // nbd_ioctl(BLKFLSBUF, 0); // flush buffers - we don't want the kernel to cache the writes 158 | int nread = read(sock, &request, sizeof(request)); 159 | 160 | if (nread < 0) { 161 | LOG("nbd read err %s", strerror(errno)); 162 | continue; 163 | } 164 | if (nread == 0) 165 | return; 166 | assert(nread == sizeof(request)); 167 | memcpy(reply.handle, request.handle, sizeof(reply.handle)); 168 | reply.error = htonl(0); 169 | 170 | assert(request.magic == htonl(NBD_REQUEST_MAGIC)); 171 | 172 | uint32_t len = ntohl(request.len); 173 | assert((len & 511) == 0); 174 | len >>= 9; 175 | uint64_t from = ntohll(request.from); 176 | assert((from & 511) == 0); 177 | from >>= 9; 178 | 179 | switch (ntohl(request.type)) { 180 | case NBD_CMD_READ: 181 | handleread(from, len); 182 | break; 183 | case NBD_CMD_WRITE: 184 | handlewrite(from, len); 185 | break; 186 | case NBD_CMD_DISC: 187 | return; 188 | default: 189 | FAIL("invalid cmd: %d", ntohl(request.type)); 190 | } 191 | } 192 | } 193 | 194 | void enableMSD(int enabled) { 195 | #ifndef X86 196 | LOG("%sable MSD", enabled ? "en" : "dis"); 197 | if (enabled) 198 | system("/opt/msdon.sh"); 199 | else 200 | system("/opt/msdoff.sh"); 201 | if (!enabled && nbd) 202 | nbd_ioctl(BLKFLSBUF, 0); 203 | #else 204 | LOG("fake enable MSD: %d", enabled); 205 | #endif 206 | } 207 | 208 | int main(int argc, char **argv) { 209 | #ifndef X86 210 | daemon(0, 1); 211 | #endif 212 | 213 | if (argc > 1) 214 | dev_file = argv[1]; 215 | 216 | for (;;) { 217 | pid_t child = fork(); 218 | if (child == 0) { 219 | runNBD(); 220 | return 0; 221 | } 222 | 223 | sleep(1); 224 | enableMSD(1); 225 | 226 | int wstatus = 0; 227 | waitpid(child, &wstatus, 0); 228 | enableMSD(0); // force "eject" 229 | 230 | if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { 231 | LOG("abnormal child return, %d, exit: %d, signal: %d", child, 232 | WIFEXITED(wstatus) ? WEXITSTATUS(wstatus) : -1, 233 | WIFSIGNALED(wstatus) ? WTERMSIG(wstatus) : -1); 234 | sleep(5); 235 | } else { 236 | sleep(2); 237 | } 238 | } 239 | 240 | return 0; 241 | } -------------------------------------------------------------------------------- /uf2daemon/uf2.h: -------------------------------------------------------------------------------- 1 | #ifndef UF2_H 2 | #define UF2_H 1 3 | 4 | #include "uf2format.h" 5 | #include 6 | #include 7 | 8 | #ifndef INDEX_URL 9 | #define INDEX_URL "https://www.pxt.io/" 10 | #endif 11 | 12 | #define UF2_VERSION_BASE "v0.1.0" 13 | 14 | // needs to be more than ~4200 and less than ~65000 (to force FAT16) 15 | #define NUM_FAT_BLOCKS 62952 16 | 17 | #define UF2_VERSION UF2_VERSION_BASE " F" 18 | 19 | //! Static block size for all memories 20 | #define UDI_MSC_BLOCK_SIZE 512L 21 | 22 | void read_block(uint32_t block_no, uint8_t *data); 23 | 24 | void write_block(uint32_t block_no, uint8_t *data); 25 | 26 | #define CONCAT_1(a, b) a##b 27 | #define CONCAT_0(a, b) CONCAT_1(a, b) 28 | #define STATIC_ASSERT(e) enum { CONCAT_0(_static_assert_, __LINE__) = 1 / ((e) ? 1 : 0) } 29 | 30 | extern const char infoUf2File[]; 31 | 32 | void readAll(int fd, void *dst, uint32_t length); 33 | 34 | STATIC_ASSERT(sizeof(UF2_Block) == 512); 35 | 36 | void mylog(const char *fmt, ...); 37 | 38 | #define FAIL(args...) \ 39 | do { \ 40 | mylog("<4>" args); \ 41 | exit(1); \ 42 | } while (0) 43 | 44 | #define LOG mylog 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /uf2daemon/uf2format.h: -------------------------------------------------------------------------------- 1 | #ifndef UF2FORMAT_H 2 | #define UF2FORMAT_H 1 3 | 4 | #include 5 | #include 6 | 7 | // All entries are little endian. 8 | 9 | // if you increase that, you will also need to update the linker script file 10 | #define APP_START_ADDRESS 0x00002000 11 | 12 | #define UF2_MAGIC_START0 0x0A324655UL // "UF2\n" 13 | #define UF2_MAGIC_START1 0x9E5D5157UL // Randomly selected 14 | #define UF2_MAGIC_END 0x0AB16F30UL // Ditto 15 | 16 | // If set, the block is "comment" and should not be flashed to the device 17 | #define UF2_FLAG_NOFLASH 0x00000001 18 | #define UF2_FLAG_FILE 0x00001000 19 | #define UF2_FILENAME_MAX 150 20 | #define UF2_MAX_PAYLOAD (476 - 10) // leaving some space for filename 21 | // for this bootloader 22 | #define UF2_MAX_FILESIZE (64 * 1024 * 1024) 23 | 24 | typedef struct { 25 | // 32 byte header 26 | uint32_t magicStart0; 27 | uint32_t magicStart1; 28 | uint32_t flags; 29 | uint32_t targetAddr; 30 | uint32_t payloadSize; 31 | uint32_t blockNo; 32 | uint32_t numBlocks; 33 | uint32_t fileSize; 34 | 35 | // raw data, followed by filename (NUL-terminated) at payloadSize 36 | uint8_t data[476]; 37 | 38 | // store magic also at the end to limit damage from partial block reads 39 | uint32_t magicEnd; 40 | } UF2_Block; 41 | 42 | static inline bool is_uf2_block(void *data) { 43 | UF2_Block *bl = (UF2_Block *)data; 44 | return bl->magicStart0 == UF2_MAGIC_START0 && bl->magicStart1 == UF2_MAGIC_START1 && 45 | bl->magicEnd == UF2_MAGIC_END; 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /uf2daemon/uf2hid.h: -------------------------------------------------------------------------------- 1 | #ifndef UF2_HID_H 2 | #define UF2_HID_H 1 3 | 4 | #define HF2_CMD_BININFO 0x0001 5 | // no arguments 6 | #define HF2_MODE_BOOTLOADER 0x01 7 | #define HF2_MODE_USERSPACE 0x02 8 | struct HF2_BININFO_Result { 9 | uint32_t mode; 10 | uint32_t flash_page_size; 11 | uint32_t flash_num_pages; 12 | uint32_t max_message_size; 13 | }; 14 | 15 | #define HF2_CMD_INFO 0x0002 16 | // no arguments 17 | // results is utf8 character array 18 | 19 | #define HF2_CMD_RESET_INTO_APP 0x0003 20 | // no arguments, no result 21 | 22 | #define HF2_CMD_RESET_INTO_BOOTLOADER 0x0004 23 | // no arguments, no result 24 | 25 | #define HF2_CMD_START_FLASH 0x0005 26 | // no arguments, no result 27 | 28 | #define HF2_CMD_WRITE_FLASH_PAGE 0x0006 29 | struct HF2_WRITE_FLASH_PAGE_Command { 30 | uint32_t target_addr; 31 | uint32_t data[0]; 32 | }; 33 | // no result 34 | 35 | #define HF2_CMD_CHKSUM_PAGES 0x0007 36 | struct HF2_CHKSUM_PAGES_Command { 37 | uint32_t target_addr; 38 | uint32_t num_pages; 39 | }; 40 | struct HF2_CHKSUM_PAGES_Result { 41 | uint16_t chksums[0 /* num_pages */]; 42 | }; 43 | 44 | #define HF2_CMD_READ_WORDS 0x0008 45 | struct HF2_READ_WORDS_Command { 46 | uint32_t target_addr; 47 | uint32_t num_words; 48 | }; 49 | struct HF2_READ_WORDS_Result { 50 | uint32_t words[0 /* num_words */]; 51 | }; 52 | 53 | #define HF2_CMD_WRITE_WORDS 0x0009 54 | struct HF2_WRITE_WORDS_Command { 55 | uint32_t target_addr; 56 | uint32_t num_words; 57 | uint32_t words[0 /* num_words */]; 58 | }; 59 | // no result 60 | 61 | #define HF2_CMD_DMESG 0x0010 62 | // no arguments 63 | // results is utf8 character array 64 | 65 | typedef struct { 66 | uint32_t command_id; 67 | uint16_t tag; 68 | uint8_t reserved0; 69 | uint8_t reserved1; 70 | 71 | union { 72 | struct HF2_WRITE_FLASH_PAGE_Command write_flash_page; 73 | struct HF2_WRITE_WORDS_Command write_words; 74 | struct HF2_READ_WORDS_Command read_words; 75 | struct HF2_CHKSUM_PAGES_Command chksum_pages; 76 | }; 77 | } HF2_Command; 78 | 79 | typedef struct { 80 | uint16_t tag; 81 | union { 82 | struct { 83 | uint8_t status; 84 | uint8_t status_info; 85 | }; 86 | uint16_t status16; 87 | }; 88 | union { 89 | struct HF2_BININFO_Result bininfo; 90 | uint8_t data8[0]; 91 | uint16_t data16[0]; 92 | uint32_t data32[0]; 93 | }; 94 | } HF2_Response; 95 | 96 | #define HF2_FLAG_SERIAL_OUT 0x80 97 | #define HF2_FLAG_SERIAL_ERR 0xC0 98 | #define HF2_FLAG_CMDPKT_LAST 0x40 99 | #define HF2_FLAG_CMDPKT_BODY 0x00 100 | #define HF2_FLAG_MASK 0xC0 101 | #define HF2_SIZE_MASK 63 102 | 103 | #define HF2_STATUS_OK 0x00 104 | #define HF2_STATUS_INVALID_CMD 0x01 105 | 106 | #endif 107 | --------------------------------------------------------------------------------