This commit is contained in:
Michal Moskal 2018-06-18 16:08:27 -07:00
Родитель 7bd27eab3f
Коммит 148edc6068
26 изменённых файлов: 1745 добавлений и 331 удалений

334
.gitignore поставляемый
Просмотреть файл

@ -1,330 +1,4 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
built/
tmp
uf2daemon/uf2d
uf2daemon/uf2d86

Просмотреть файл

@ -1,5 +1,55 @@
# Contributing
# UF2 Flashing for Linux
This repo contains scripts and patches to build a sample Linux image
based on [piCore](http://www.tinycorelinux.net/ports.html)
for Raspberry Pi Zero.
The image is meant to boot very quickly (currently at around 7s),
and expose a USB mass storage device (pen drive), which can be used
to program a Raspberry Pi Zero with [UF2 files](https://github.com/Microsoft/uf2),
usually generated from [Microsoft MakeCode](https://github.com/Microsoft/pxt)
and in particular from [MakeCode Arcade](https://arcade.microsoft.com).
The image was tested on a Raspberry Pi Zero Rev 1.3 and Zero W Rev 1.3.
It could theoretically work on the original Pi A/A+, but wasn't
tested. Other models lack the OTG ID pin, and thus cannot be used in
USB device mode.
PRs are welcome!
## Building
Building the image requires [Docker](https://www.docker.com/).
Go to `image/` and run `./build.sh`. The image will land in `built/boot/*`.
### "Burning" image
All files in `built/boot/` need to be copied to a FAT32-formatted SD card.
There is no ext4 partition to worry about, and you don't need to use any
special software to "burn" the image.
Regular SD cards come preformatted as FAT32. If you have a previous
Raspberry Pi image on the card you can format it, or just move all files in
the first partition into a sub-folder if it's reasonably big.
Any SD card should do. You don't need much space (currently around 13MB),
and the Pi will only read a few MBs upon startup, so the speed isn't very important.
### Docker image
If you want to build the Docker image (`pext/rpi`) yourself,
use the `docker/build.sh` script. Usually, you can just pull it
from Docker Hub (which will just happen automatically).
The image is based on
[sdthirlwall/raspberry-pi-cross-compiler](https://hub.docker.com/r/sdthirlwall/raspberry-pi-cross-compiler/)
and contains stock piCore 9.0.3 and sources of its kernel.
## License
MIT
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us

60
docker/Dockerfile Normal file
Просмотреть файл

@ -0,0 +1,60 @@
# ------------------------------------------------------------------------------
# Pull base image
FROM sdthirlwall/raspberry-pi-cross-compiler
MAINTAINER Michal Moskal <michal@moskal.me>
USER root
RUN curl https://deb.nodesource.com/node_6.x/pool/main/n/nodejs/nodejs_6.11.0-1nodesource1~jessie1_amd64.deb > node.deb \
&& dpkg -i node.deb \
&& rm node.deb
RUN apt-get install mtools cpio bc p7zip-full squashfs-tools
RUN mkdir -p /picore/kernel
WORKDIR /picore/kernel
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
RUN tar xf linux-rpi-4.9.22.tar.xz
RUN mv linux-rpi-4.9.22 linux-rpi
RUN rm linux-rpi-4.9.22.tar.xz
RUN curl http://www.tinycorelinux.net/9.x/armv6/releases/RPi/src/kernel/4.9.22-piCore.config.xz | xzcat > linux-rpi/.config
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
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
WORKDIR /picore/kernel/linux-rpi
RUN make ARCH=arm CROSS_COMPILE=/rpxc/bin/arm-linux-gnueabihf- modules_prepare
RUN echo "#!/bin/sh" > mkusb.sh
RUN echo "make ARCH=arm CROSS_COMPILE=/rpxc/bin/arm-linux-gnueabihf- SUBDIRS=drivers/usb -j10 modules" >> mkusb.sh
RUN chmod +x mkusb.sh
RUN ./mkusb.sh
RUN mkdir /picore/img
WORKDIR /picore/img
RUN curl http://www.tinycorelinux.net/9.x/armv6/releases/RPi/piCore-9.0.3.zip > picore.zip
RUN 7z x picore.zip
RUN mkdir /picore/boot
RUN mcopy -s -i piCore-9.0.3.img@@4096K ::* ../boot
WORKDIR /picore
RUN rm -rf img
RUN mkdir rootfs
RUN cd rootfs && zcat ../boot/9.0.3.gz | cpio -i -H newc -d
RUN mkdir rootfsv7
RUN cd rootfsv7 && zcat ../boot/9.0.3v7.gz | cpio -i -H newc -d
WORKDIR /picore
RUN git clone https://github.com/WiringPi/WiringPi
WORKDIR /picore/WiringPi/wiringPi
# WiringPi build script doesn't do cross-compile
RUN arm-linux-gnueabihf-gcc -g -ffunction-sections -fdata-sections -Os -c *.c -I .
RUN arm-linux-gnueabihf-ar rcs libwiringPi.a *.o
RUN cp libwiringPi.a /rpxc/arm-linux-gnueabihf/lib/
RUN cp *.h /rpxc/arm-linux-gnueabihf/libc/usr/include/
WORKDIR /picore
RUN rm -rf WiringPi
RUN useradd -m build
USER root
COPY go.js /home/build

3
docker/build.sh Executable file
Просмотреть файл

@ -0,0 +1,3 @@
#!/bin/sh
docker build -t pext/rpi -f Dockerfile .

17
docker/go.js Normal file
Просмотреть файл

@ -0,0 +1,17 @@
var fs = require("fs")
var child_process = require("child_process")
process.stdin.setEncoding("utf8")
process.stdout.setEncoding("utf8")
var buf = ""
process.stdin.on("data", function(d) { buf += d })
process.stdin.on("end", function() {
handle(JSON.parse(buf))
})
function handle(req) {
fs.writeFileSync("builder.js", req.builderJs);
global.buildReq = req;
require("./builder")
}

1
image/boot/cmdline.txt Normal file
Просмотреть файл

@ -0,0 +1 @@
dwc_otg.lpm_enable=0 console=ttyAMA0,115200 root=/dev/ram0 elevator=deadline rootwait quiet nortc loglevel=5 noembed waitusb=1 norestore

85
image/boot/config.txt Normal file
Просмотреть файл

@ -0,0 +1,85 @@
# For more options and information see
# http://www.raspberrypi.org/documentation/configuration/config-txt.md
# Some settings may impact device functionality. See link above for details
[PI0]
initramfs 9.0.3.gz followkernel
kernel kernel4922.img
cmdline cmdline.txt
[PI1]
initramfs 9.0.3.gz followkernel
kernel kernel4922.img
cmdline cmdline.txt
[PI2]
initramfs 9.0.3v7.gz followkernel
kernel kernel4922v7.img
cmdline cmdline.txt
[PI3]
initramfs 9.0.3v7.gz followkernel
kernel kernel4922v7.img
cmdline cmdline3.txt
[ALL]
# uncomment if you get no picture on HDMI for a default "safe" mode
#hdmi_safe=1
# uncomment this if your display has a black border of unused pixels visible
# and your display can output without overscan
#disable_overscan=1
# uncomment the following to adjust overscan. Use positive numbers if console
# goes off screen, and negative if there is too much border
#overscan_left=16
#overscan_right=16
#overscan_top=16
#overscan_bottom=16
# uncomment to force a console size. By default it will be display's size minus
# overscan.
#framebuffer_width=1280
#framebuffer_height=720
# uncomment if hdmi display is not detected and composite is being output
#hdmi_force_hotplug=1
# uncomment to force a specific HDMI mode (this will force VGA)
#hdmi_group=1
#hdmi_mode=1
# uncomment to force a HDMI mode rather than DVI. This can make audio work in
# DMT (computer monitor) modes
#hdmi_drive=2
# uncomment to increase signal to HDMI, if you have interference, blanking, or
# no display
#config_hdmi_boost=4
# uncomment for composite PAL
#sdtv_mode=2
#uncomment to overclock the arm. 700 MHz is the default.
#arm_freq=800
#----------------------------------------------------
# Enable peripheral buses
dtparam=i2c=on,spi=on,i2s=on
# Enable onboard audio
dtparam=audio=on
dtoverlay=dwc2
# Enable serial console
enable_uart=1
[PI3]
dtoverlay=pi3-disable-bt
[ALL]

15
image/build.sh Executable file
Просмотреть файл

@ -0,0 +1,15 @@
#!/bin/sh
TCZ="gdb alsa-modules-4.9.22-piCore alsa-oss alsa-plugins alsa alsa-utils libasound libasound-dev"
f="$1"
if [ "X$f" = X ] ; then
mkdir -p ../built/tcz
for t in $TCZ ; do
test -f ../built/tcz/$t.tcz || curl http://www.tinycorelinux.net/9.x/armv6/tcz/$t.tcz > ../built/tcz/$t.tcz
done
rm -rf ../built/boot
f=/build/image/inner.sh
fi
docker run -i -t --rm -v `cd .. && pwd`:/build pext/rpi "$f"

54
image/inner.sh Executable file
Просмотреть файл

@ -0,0 +1,54 @@
#!/bin/sh
set -ex
# build uf2
cd /build/uf2daemon
make
cd /picore/boot
# remove stuff we don't support yet anyway
rm *v7* *_cd.* *_x.* *_db.*
# overlay files
cp -r /build/image/boot/* .
# extract TCZs
cd /picore
mkdir sq
for f in /build/built/tcz/*.tcz ; do
unsquashfs $f
cp -r squashfs-root/* sq/
rm -rf squashfs-root
done
cp sq/usr/local/bin/gdbserver rootfs/usr/bin/
for mod in snd-pcm-oss snd-mixer-oss snd-soc-core snd-bcm2835 snd-pcm-dmaengine snd-pcm snd-timer snd-compress snd ; do
p=`find sq -name $mod.ko`
cp $p rootfs/lib/modules/4.9.22-piCore/kernel/drivers/
done
#cp -r sq/* rootfs/
cp -r /build/image/rootfs/* rootfs/
cp /build/uf2daemon/uf2d rootfs/sbin/
cd rootfs
patch -p1 < /build/image/rootfs.patch
# kernel modules
cd /picore/kernel/linux-rpi
patch drivers/usb/gadget/function/f_mass_storage.c < /build/kernel/f_mass_storage.c.sync.patch
./mkusb.sh
dst=/picore/rootfs/lib/modules/4.9.22-piCore/kernel
for d in drivers/usb/dwc2 drivers/usb/gadget \
drivers/usb/gadget/legacy drivers/usb/gadget/function drivers/usb/gadget/udc ; do
mkdir -p $dst/$d
cp $d/*.ko $dst/$d
done
# create new image
cd /picore/rootfs
find | cpio -o -R 0:0 -H newc | gzip -4 > ../boot/9.0.3.gz
# Copy out results to host
mkdir -p /build/built
cp -r /picore/boot /build/built/boot

48
image/rootfs.patch Normal file
Просмотреть файл

@ -0,0 +1,48 @@
Only in orig/etc/init.d/: busybox-aliases
Only in orig/etc/init.d/: dhcp.sh
Only in orig/etc/init.d/: rc.shutdown
Only in orig/etc/init.d/: rcS
Only in orig/etc/init.d/: services
Only in orig/etc/init.d/: settime.sh
diff -ur orig/etc/init.d/tc-config rootfs/etc/init.d/tc-config
--- orig/etc/init.d/tc-config 2018-06-15 09:35:38.000000000 -0700
+++ rootfs/etc/init.d/tc-config 2018-06-16 23:20:09.000000000 -0700
@@ -25,8 +25,8 @@
# Main
-clear
-echo "${GREEN}Booting ${YELLOW}Core $VERSION ${NORMAL}"
+#clear
+echo "${GREEN}Booting ${YELLOW}Core $VERSION - Arcade Pi ${NORMAL}"
echo "${GREEN}Running Linux Kernel ${YELLOW}$KERNEL${GREEN}.${NORMAL}"
export PATH=/usr/local/sbin:/usr/local/bin:"$PATH"
@@ -102,6 +102,8 @@
done
fi
+depmod -a
+
# Start Udev to populate /dev and handle hotplug events
echo -n "${BLUE}Starting udev daemon for hotplug support...${NORMAL}"
#/sbin/udevd --daemon 2>/dev/null >/dev/null
@@ -536,6 +538,8 @@
sync
wait $fstab_pid
+
+if false ; then
MSSG="${BLUE}Loading extensions...${NORMAL}"
if [ -n "$SHOWAPPS" ]; then
touch /etc/sysconfig/showapps
@@ -557,6 +561,7 @@
echo -n "${RED}Press Enter key.${NORMAL}"; read ans
fi
fi
+fi
[ -n "$KEYMAP" ] || KEYMAP="us"
if [ -f "/usr/share/kmap/$KEYMAP.kmap" ]; then
Only in orig/etc/init.d/: tc-restore.sh
Only in orig/etc/init.d/: tc_noscan.lst

Просмотреть файл

16
image/rootfs/opt/bootlocal.sh Executable file
Просмотреть файл

@ -0,0 +1,16 @@
#!/bin/sh
# Start serial terminal
/usr/sbin/startserialtty &
# Set CPU frequency governor to ondemand (default is performance)
echo ondemand > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# Load modules
/sbin/modprobe i2c-dev
/opt/menustart.sh &
# ------ Put other system startup commands below this line

14
image/rootfs/opt/bootsync.sh Executable file
Просмотреть файл

@ -0,0 +1,14 @@
#!/bin/sh
# put other system startup commands here, the boot process will wait until they complete.
# Use bootlocal.sh for system startup commands that can run in the background
# and therefore not slow down the boot process.
/usr/bin/sethostname box
set -x
modprobe snd_pcm_oss
modprobe nbd
modprobe dwc2
modprobe libcomposite
mkdir /sd
mount /dev/mmcblk0p1 /sd/
/sbin/uf2d /dev/nbd0
/opt/bootlocal.sh &

18
image/rootfs/opt/menustart.sh Executable file
Просмотреть файл

@ -0,0 +1,18 @@
#!/bin/sh
ok=1
while : ; do
if kill -0 `cat /tmp/pxt-pid` 2>/dev/null ; then
ok=1
else
if [ $ok = 0 ] ; then
if test -x /sd/prj/.menu.elf ; then
/sd/prj/.menu.elf
fi
sleep 5
ok=1
else
ok=0
fi
fi
sleep 0.5
done

5
image/rootfs/opt/msdoff.sh Executable file
Просмотреть файл

@ -0,0 +1,5 @@
#!/bin/sh
set -x
rm /tmp/msd-ok
killall gdbserver 2>/dev/null
rmmod g_multi

6
image/rootfs/opt/msdon.sh Executable file
Просмотреть файл

@ -0,0 +1,6 @@
#!/bin/sh
set -xe
test -f /tmp/msd-ok && exit
modprobe g_multi file=/dev/nbd0 iSerialNumber=123456789 stall=0
touch /tmp/msd-ok
/opt/rungdb.sh &

4
image/rootfs/opt/rungdb.sh Executable file
Просмотреть файл

@ -0,0 +1,4 @@
#!/bin/sh
killall gdbserver 2>/dev/null
sleep 3
gdbserver --multi /dev/ttyGS0

Просмотреть файл

@ -0,0 +1 @@
http://repo.tinycorelinux.net/

Просмотреть файл

@ -0,0 +1,16 @@
#!/bin/sh
model=`cat /proc/device-tree/model`
if [ "${model:0:20}" = "Raspberry Pi 3 Model" -o "${model:0:19}" = "Raspberry Pi Zero W" ]; then
port=ttyS0
else
port=ttyAMA0
fi
# Start serial terminal on Raspberry Pi
while :
do
echo Start serial on $port
/sbin/getty -L $port 115200 screen
done

Просмотреть файл

@ -0,0 +1,11 @@
--- f_mass_storage.c 2018-06-16 23:26:01.000000000 -0700
+++ kernel/f_mass_storage.c 2018-06-15 16:15:16.000000000 -0700
@@ -781,7 +781,7 @@
return -EINVAL;
}
spin_lock(&curlun->filp->f_lock);
- curlun->filp->f_flags &= ~O_SYNC; /* Default is not to wait */
+ curlun->filp->f_flags |= O_SYNC; /* Default is to wait - we're using local NBD */
spin_unlock(&curlun->filp->f_lock);
/*

8
uf2daemon/Makefile Normal file
Просмотреть файл

@ -0,0 +1,8 @@
CFLAGS = -std=c99 -W -Wall
SRC = main.c fat.c
all:
arm-linux-gnueabihf-gcc -Os -s $(CFLAGS) $(SRC) -o uf2d
x86:
gcc -DX86=1 -g $(CFLAGS) $(SRC) -o uf2d86

867
uf2daemon/fat.c Normal file
Просмотреть файл

@ -0,0 +1,867 @@
#define PRODUCT_NAME "Arcade Pi"
#define VOLUME_LABEL "ARCADE"
#define INDEX_URL "https://arcade.makecode.com"
#define BOARD_ID "Arcade-RPi-v0"
#define _XOPEN_SOURCE 500
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <assert.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <spawn.h>
#define max(a, b) \
({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a > _b ? _a : _b; \
})
#define min(a, b) \
({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a < _b ? _a : _b; \
})
#include "uf2.h"
#define DBG LOG
typedef struct {
uint8_t JumpInstruction[3];
uint8_t OEMInfo[8];
uint16_t SectorSize;
uint8_t SectorsPerCluster;
uint16_t ReservedSectors;
uint8_t FATCopies;
uint16_t RootDirectoryEntries;
uint16_t TotalSectors16;
uint8_t MediaDescriptor;
uint16_t SectorsPerFAT;
uint16_t SectorsPerTrack;
uint16_t Heads;
uint32_t HiddenSectors;
uint32_t TotalSectors32;
uint8_t PhysicalDriveNum;
uint8_t Reserved;
uint8_t ExtendedBootSig;
uint32_t VolumeSerialNumber;
char VolumeLabel[11];
uint8_t FilesystemIdentifier[8];
} __attribute__((packed)) FAT_BootBlock;
typedef struct {
char name[8];
char ext[3];
uint8_t attrs;
uint8_t reserved;
uint8_t createTimeFine;
uint16_t createTime;
uint16_t createDate;
uint16_t lastAccessDate;
uint16_t highStartCluster;
uint16_t updateTime;
uint16_t updateDate;
uint16_t startCluster;
uint32_t size;
} __attribute__((packed)) DirEntry;
typedef struct {
uint8_t seqno;
uint16_t name0[5];
uint8_t attrs;
uint8_t type;
uint8_t checksum;
uint16_t name1[6];
uint16_t startCluster;
uint16_t name2[2];
} __attribute__((packed)) VFatEntry;
STATIC_ASSERT(sizeof(DirEntry) == 32);
#define STR0(x) #x
#define STR(x) STR0(x)
const char infoUf2File[] = //
"UF2 Bootloader " UF2_VERSION "\r\n"
"Model: " PRODUCT_NAME "\r\n"
"Board-ID: " BOARD_ID "\r\n";
const char indexFile[] = //
"<!doctype html>\n"
"<html>"
"<body>"
"<script>\n"
"location.replace(\"" INDEX_URL "\");\n"
"</script>"
"</body>"
"</html>\n";
#define RESERVED_SECTORS 1
#define ROOT_DIR_SECTORS 4
#define SECTORS_PER_FAT ((NUM_FAT_BLOCKS * 2 + 511) / 512)
#define START_FAT0 RESERVED_SECTORS
#define START_FAT1 (START_FAT0 + SECTORS_PER_FAT)
#define START_ROOTDIR (START_FAT1 + SECTORS_PER_FAT)
#define START_CLUSTERS (START_ROOTDIR + ROOT_DIR_SECTORS)
#define ROOT_DIR_ENTRIES (ROOT_DIR_SECTORS * 512 / 32)
#define F_TEXT 1
#define F_UF2 2
#define F_DIR 4
#define F_CONT 8
#define F_RAW 16
static const FAT_BootBlock BootBlock = {
.JumpInstruction = {0xeb, 0x3c, 0x90},
.OEMInfo = "UF2 UF2 ",
.SectorSize = 512,
.SectorsPerCluster = 1,
.ReservedSectors = RESERVED_SECTORS,
.FATCopies = 2,
.RootDirectoryEntries = ROOT_DIR_ENTRIES,
.TotalSectors16 = NUM_FAT_BLOCKS - 2,
.MediaDescriptor = 0xF8,
.SectorsPerFAT = SECTORS_PER_FAT,
.SectorsPerTrack = 1,
.Heads = 1,
.ExtendedBootSig = 0x29,
.VolumeSerialNumber = 0x00420042,
.VolumeLabel = VOLUME_LABEL,
.FilesystemIdentifier = "FAT16 ",
};
int currCluster = 2;
struct FsEntry *rootDir;
struct ClusterData *firstCluster, *lastCluster;
int numWrites = 0;
#define MAX_BLOCKS 8000
typedef struct {
uint32_t numBlocks;
uint32_t numWritten;
uint8_t writtenMask[MAX_BLOCKS / 8 + 1];
} WriteState;
char execPath[300];
static WriteState wrState;
#define ZERO(v) memset(&v, 0, sizeof(v))
void setupFs();
void enableMSD(int enabled);
#define MAGIC_CLUSTER 0x4242c180
#define MAGIC_FSENTRY 0x4200c180
typedef struct ClusterData {
int magic;
int flags;
int numclusters;
struct stat st;
struct ClusterData *dnext;
struct ClusterData *cnext;
struct FsEntry *dirdata;
struct FsEntry *myfile;
char name[0];
} ClusterData;
typedef struct FsEntry {
int magic;
int startCluster;
uint8_t attrs;
int size;
int numdirentries;
time_t ctime, mtime;
struct FsEntry *next;
struct ClusterData *data;
char fatname[12];
char vfatname[0];
} FsEntry;
struct DirMap {
const char *mapName;
const char *fsName;
int mode;
};
static void freeChain(FsEntry *p) {
FsEntry *n;
while (p) {
n = p->next;
assert(p->magic == MAGIC_FSENTRY);
p->magic = 0xdead;
free(p);
p = n;
}
}
void rereadData() {
lastCluster = NULL;
numWrites = 0;
currCluster = 2;
for (FsEntry *p = rootDir; p; p = p->next) {
if (p->data)
freeChain(p->data->dirdata);
}
freeChain(rootDir);
rootDir = NULL;
while (firstCluster) {
lastCluster = firstCluster;
firstCluster = firstCluster->cnext;
assert(lastCluster->magic == MAGIC_CLUSTER);
lastCluster->magic = 0xdead;
free(lastCluster);
}
ZERO(wrState);
setupFs();
}
struct DirMap dirMaps[] = { //
#ifdef X86
{"foo qux baz", "dirs/bar", F_UF2}, //
{"foo", "dirs/foo", F_UF2}, //
{"xyz", "dirs/bar2", F_UF2}, //
{"Temporary Logs", "/tmp/logs", F_RAW},
#else
{"Projects", "/sd/prj", F_UF2},
{"Temporary Logs", "/tmp/logs", F_RAW},
{"Permanent Logs", "/sd/logs", F_RAW},
#endif
{NULL, NULL, 0}};
void timeToFat(time_t t, uint16_t *dateP, uint16_t *timeP) {
struct tm tm;
localtime_r(&t, &tm);
if (timeP)
*timeP = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec / 2);
if (dateP)
*dateP = (max(0, tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday;
}
void padded_memcpy(char *dst, const char *src, int len) {
for (int i = 0; i < len; ++i) {
if (*src)
*dst = *src++;
else
*dst = ' ';
dst++;
}
}
int lastMapMode;
char *expandMap(const char *mapName) {
static char mapbuf[300];
const char *rest = "";
for (int i = 0; i < (int)sizeof(mapbuf); ++i) {
char c = mapName[i];
if (c == '/' || c == 0) {
mapbuf[i] = 0;
rest = mapName + i;
break;
}
mapbuf[i] = c;
}
for (int i = 0; dirMaps[i].mapName; ++i) {
if (strcmp(dirMaps[i].mapName, mapbuf) == 0) {
strcpy(mapbuf, dirMaps[i].fsName);
strcat(mapbuf, rest);
lastMapMode = dirMaps[i].mode;
return mapbuf;
}
}
return NULL;
}
ClusterData *mkClusterData(int namelen) {
ClusterData *c = malloc(sizeof(*c) + namelen + 1);
memset(c, 0, sizeof(*c) + namelen + 1);
c->magic = MAGIC_CLUSTER;
return c;
}
ClusterData *readDir(const char *mapName) {
DIR *d = opendir(expandMap(mapName));
if (!d)
return NULL;
ClusterData *res = NULL;
for (;;) {
struct dirent *ent = readdir(d);
if (!ent)
break;
ClusterData *c = mkClusterData(strlen(mapName) + 1 + strlen(ent->d_name));
c->flags = lastMapMode;
c->dnext = res;
sprintf(c->name, "%s/%s", mapName, ent->d_name);
int err = stat(expandMap(c->name), &c->st);
assert(err >= 0);
if (S_ISREG(c->st.st_mode) && strlen(c->name) < UF2_FILENAME_MAX && ent->d_name[0] != '.') {
if (c->flags & F_RAW)
c->numclusters = (c->st.st_size + 511) / 512;
else
c->numclusters = (c->st.st_size + 255) / 256;
} else {
free(c);
continue;
}
res = c;
}
closedir(d);
return res;
}
int filechar(int c) {
if (!c)
return 0;
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') ||
strchr("_-", c);
}
void copyFsChars(char *dst, const char *src, int len) {
for (int i = 0; i < len; ++i) {
if (filechar(*src))
dst[i] = toupper(*src++);
else {
if (*src == '.')
src = "";
if (*src == 0)
dst[i] = ' ';
else
dst[i] = '_';
while (*src && !filechar(*src))
src++;
}
}
}
FsEntry *mkFsEntry(const char *name) {
int sz = sizeof(FsEntry) + strlen(name) + 1;
FsEntry *e = malloc(sz);
memset(e, 0, sz);
e->magic = MAGIC_FSENTRY;
e->startCluster = currCluster;
e->next = NULL;
// +1 for final 0x0000, and +12 for alignment
e->numdirentries = 1 + (strlen(name) + 1 + 12) / 13;
strcpy(e->vfatname, name);
const char *src = name;
copyFsChars(e->fatname, src, 8);
while (*src && *src != '.')
src++;
if (*src == '.')
src++;
else
src = "";
copyFsChars(e->fatname + 8, src, 3);
return e;
}
void addClusterData(ClusterData *c, FsEntry *e) {
currCluster += c->numclusters;
if (firstCluster == NULL) {
firstCluster = c;
} else {
lastCluster->cnext = c;
}
lastCluster = c;
if (c->st.st_ctime)
e->ctime = min(e->ctime, c->st.st_ctime);
e->mtime = max(e->mtime, c->st.st_mtime);
c->myfile = e;
DBG("add cluster: flags=%d size=%d numcl=%d", c->flags, (int)c->st.st_size, c->numclusters);
}
FsEntry *addRootText(const char *filename, const char *contents) {
FsEntry *e = mkFsEntry(filename);
e->next = rootDir;
rootDir = e;
int sz = strlen(contents);
e->size = sz;
if (sz > 0) {
assert(sz <= 512);
ClusterData *c = mkClusterData(sz);
c->st.st_mtime = c->st.st_ctime = time(NULL);
c->flags = F_TEXT;
strcpy(c->name, contents);
c->st.st_size = sz;
c->numclusters = 1;
addClusterData(c, e);
}
return e;
}
int baseLen(const char *a) {
int len = 0;
while (*a && *a != '.') {
a++;
len++;
}
return len;
}
int nameMatches(const char *a, const char *b) {
for (;;) {
if ((*a == 0 || *a == '.') && (*b == 0 || *b == '.'))
return 1;
if (*a != *b)
return 0;
a++;
b++;
}
}
void setFatNames(FsEntry *dirent) {
for (FsEntry *p = dirent; p; p = p->next) {
// check for collisions
int k = 1;
retry:
for (FsEntry *o = dirent; o && o != p; o = o->next) {
if (strcmp(o->fatname, p->fatname) == 0) {
char buf[20];
sprintf(buf, "~%d", k++);
int len = strlen(buf);
memcpy(p->fatname + 8 - len, buf, len);
goto retry;
}
}
DBG("setname: %s [%s] cl=%s @ %d sz=%d dents=%d", p->vfatname, p->fatname,
p->data ? p->data->name : "(no data)", p->startCluster, p->size, p->numdirentries);
}
}
void addFullDir(const char *mapName) {
int numEntries = 0;
FsEntry *dirents = NULL;
time_t mtime = 0, ctime = 0;
for (ClusterData *cl = readDir(mapName); cl; cl = cl->dnext) {
if (cl->cnext || cl == lastCluster)
continue; // already done
// vfat entries
const char *filename = strchr(cl->name, '/') + 1;
int len = baseLen(filename) + 4;
char namebuf[len];
if (cl->flags & F_UF2) {
memcpy(namebuf, filename, len - 4);
strcpy(namebuf + len - 4, ".uf2");
filename = namebuf;
}
FsEntry *fent = mkFsEntry(filename);
numEntries += fent->numdirentries;
fent->next = dirents;
fent->data = cl;
fent->size = cl->flags & F_UF2 ? cl->numclusters * 512 : cl->st.st_size;
dirents = fent;
addClusterData(cl, fent);
if (cl->flags & F_UF2)
for (ClusterData *other = cl->dnext; other; other = other->dnext) {
if (nameMatches(cl->name, other->name)) {
other->flags |= F_CONT;
fent->size += other->numclusters * 512;
addClusterData(other, fent);
}
}
if (mtime == 0) {
mtime = fent->mtime;
ctime = fent->ctime;
} else {
mtime = max(mtime, fent->mtime);
ctime = min(ctime, fent->ctime);
}
}
if (numEntries == 0)
return; // skip empty dirs
setFatNames(dirents);
FsEntry *dent = mkFsEntry(mapName);
dent->data = mkClusterData(0);
dent->data->dirdata = dirents;
dent->data->numclusters = (numEntries + 16) / 16; // at least 1
addClusterData(dent->data, dent);
dent->mtime = mtime;
dent->ctime = ctime;
dent->next = rootDir;
dent->attrs = 0x10;
dent->data->flags = F_DIR;
rootDir = dent;
}
void addFileForward(const char *filename, const char *srcFilename, int size) {
FsEntry *e = mkFsEntry(filename);
e->next = rootDir;
rootDir = e;
e->size = size;
ClusterData *c = mkClusterData(strlen(srcFilename));
int err = stat(srcFilename, &c->st);
if (err < 0) {
// if the file doesn't exists (yet), use current time in hope it will appear
c->st.st_mtime = c->st.st_ctime = time(NULL);
}
c->flags = F_RAW;
strcpy(c->name, srcFilename);
c->st.st_size = size;
c->numclusters = (c->st.st_size + 255) / 256;
addClusterData(c, e);
}
void setupFs() {
addRootText("info_uf2.txt", infoUf2File);
addRootText("index.html", indexFile);
addFileForward("dmesg.txt", "/tmp/dmesg.txt", 128 * 1024);
for (int i = 0; dirMaps[i].mapName; ++i) {
addFullDir(dirMaps[i].mapName);
}
setFatNames(rootDir); // make names unique
FsEntry *e = addRootText(BootBlock.VolumeLabel, "");
e->numdirentries = 1;
e->attrs = 0x28;
}
#define WRITE_ENT(v) \
do { \
if (skip++ >= 0) \
*dest++ = v; \
if (skip >= 256) \
return; \
cl++; \
} while (0)
void readFat(uint16_t *dest, int skip) {
int cl = 0;
skip = -skip;
WRITE_ENT(0xfff0);
WRITE_ENT(0xffff);
for (ClusterData *c = firstCluster; c; c = c->cnext) {
for (int i = 0; i < c->numclusters - 1; i++)
WRITE_ENT(cl + 1);
if (c->cnext && c->cnext->flags & F_CONT)
WRITE_ENT(cl + 1);
else
WRITE_ENT(0xffff);
}
}
// note that ptr might be unaligned
const char *copyVFatName(const char *ptr, void *dest, int len) {
uint8_t *dst = dest;
for (int i = 0; i < len; ++i) {
if (ptr == NULL) {
*dst++ = 0xff;
*dst++ = 0xff;
} else {
*dst++ = *ptr;
*dst++ = 0;
if (*ptr)
ptr++;
else
ptr = NULL;
}
}
return ptr;
}
uint8_t fatChecksum(const char *name) {
uint8_t sum = 0;
for (int i = 0; i < 11; ++i)
sum = ((sum & 1) << 7) + (sum >> 1) + *name++;
return sum;
}
void readDirData(uint8_t *dest, FsEntry *dirdata, int blkno) {
DirEntry *d = (void *)dest;
int idx = blkno * -16;
for (FsEntry *e = dirdata; e; e = e->next) {
if (idx >= 16)
break;
// DBG("dir idx=%d %s", idx, e->vfatname);
for (int i = 0; i < e->numdirentries; ++i, ++idx) {
if (0 <= idx && idx < 16) {
if (i == e->numdirentries - 1) {
memcpy(d->name, e->fatname, 11);
d->attrs = e->attrs;
d->size = e->size;
d->startCluster = e->startCluster;
timeToFat(e->mtime, &d->updateDate, &d->updateTime);
timeToFat(e->ctime, &d->createDate, &d->createTime);
} else {
VFatEntry *f = (void *)d;
int seq = e->numdirentries - i - 2;
f->seqno = seq + 1; // they start at 1
if (i == 0)
f->seqno |= 0x40;
f->attrs = 0x0F;
f->type = 0x00;
f->checksum = fatChecksum(e->fatname);
f->startCluster = 0;
const char *ptr = e->vfatname + (13 * seq);
ptr = copyVFatName(ptr, f->name0, 5);
ptr = copyVFatName(ptr, f->name1, 6);
ptr = copyVFatName(ptr, f->name2, 2);
}
d++;
}
}
}
}
void readBlock(uint8_t *dest, int blkno) {
// DBG("readbl %d", blkno);
int blkno0 = blkno;
for (ClusterData *c = firstCluster; c; c = c->cnext) {
// DBG("off=%d sz=%d", blkno, c->numclusters);
if (blkno >= c->numclusters) {
blkno -= c->numclusters;
continue;
}
// DBG("readbl off=%d %p", blkno, c);
if (c->dirdata) {
readDirData(dest, c->dirdata, blkno);
} else if (c->flags & F_TEXT) {
strcpy((char *)dest, c->name);
} else if (c->flags & F_RAW) {
memset(dest, '\n', 512);
int fd = open(c->name[0] == '/' ? c->name : expandMap(c->name), O_RDONLY);
if (fd >= 0) {
lseek(fd, blkno * 512, SEEK_SET);
read(fd, dest, 512);
close(fd);
}
} else if (c->flags & F_UF2) {
UF2_Block *bl = (void *)dest;
bl->magicStart0 = UF2_MAGIC_START0;
bl->magicStart1 = UF2_MAGIC_START1;
bl->magicEnd = UF2_MAGIC_END;
bl->flags = UF2_FLAG_FILE;
bl->blockNo = blkno0 - (c->myfile->startCluster - 2);
bl->numBlocks = c->myfile->size / 512;
bl->targetAddr = blkno * 256;
bl->payloadSize = 256;
bl->fileSize = c->st.st_size;
int fd = open(expandMap(c->name), O_RDONLY);
if (fd >= 0) {
lseek(fd, bl->targetAddr, SEEK_SET);
bl->payloadSize = read(fd, bl->data, 256);
close(fd);
} else {
bl->payloadSize = -1;
}
if (bl->payloadSize < 475 - strlen(c->name))
strcpy((char *)bl->data + bl->payloadSize, c->name);
}
return;
}
}
void read_block(uint32_t block_no, uint8_t *data) {
memset(data, 0, 512);
uint32_t sectionIdx = block_no;
if (block_no == 0) {
memcpy(data, &BootBlock, sizeof(BootBlock));
data[510] = 0x55;
data[511] = 0xaa;
// logval("data[0]", data[0]);
} else if (block_no < START_ROOTDIR) {
sectionIdx -= START_FAT0;
if (sectionIdx >= SECTORS_PER_FAT) // second copy of fat?
sectionIdx -= SECTORS_PER_FAT;
readFat((void *)data, sectionIdx * 256);
} else if (block_no < START_CLUSTERS) {
sectionIdx -= START_ROOTDIR;
readDirData(data, rootDir, sectionIdx);
} else {
sectionIdx -= START_CLUSTERS;
readBlock(data, sectionIdx);
}
}
extern char **environ;
void restartProgram() {
LOG("start %s", execPath);
pid_t pid;
if (!(pid = fork())) {
if (!fork()) {
LOG("syncing");
if (execPath[0]) {
int fd = open(execPath, O_RDWR);
fsync(fd);
close(fd);
}
LOG("restart FS");
enableMSD(0);
sleep(2);
enableMSD(1);
exit(0);
} else {
if (execPath[0] && !fork()) {
char *argv[] = {execPath, "--uf2", NULL};
execve(execPath, argv, environ);
exit(127);
} else {
exit(0);
}
}
} else {
int wstatus = 0;
waitpid(pid, &wstatus, 0);
LOG("started %s", execPath);
}
}
void write_block(uint32_t block_no, uint8_t *data) {
WriteState *state = &wrState;
UF2_Block *bl = (void *)data;
#if 0
if (bl->magicStart0 == 0x20da6d81 && bl->magicStart1 == 0x747e09d4) {
DBG("restart req, #wr=%d", numWrites);
if (numWrites) {
restartFs();
}
return;
}
#endif
numWrites++;
if (!is_uf2_block(bl)) {
return;
}
(void)block_no;
bl->data[475] = 0; // make sure we have NUL terminator
char *fn0 = (char *)bl->data + bl->payloadSize;
int namelen = 0;
if (bl->payloadSize <= UF2_MAX_PAYLOAD) {
namelen = strlen(fn0);
}
if ((bl->flags & UF2_FLAG_FILE) && bl->fileSize <= UF2_MAX_FILESIZE &&
bl->targetAddr < bl->fileSize && 1 <= namelen && namelen <= UF2_FILENAME_MAX) {
char *firstSL = strchr(fn0, '/');
char *lastSL = strrchr(fn0, '/');
if (!lastSL)
lastSL = fn0;
else
lastSL++;
int baseLen = strlen(lastSL);
char fallback[strlen(dirMaps[0].fsName) + 1 + baseLen + 1];
sprintf(fallback, "%s/%s", dirMaps[0].fsName, lastSL);
char *fn = NULL;
if (firstSL && firstSL + 1 == lastSL)
fn = expandMap(fn0);
if (!fn)
fn = fallback;
char *p = strrchr(fn, '/');
*p = 0;
mkdir(fn, 0777);
*p = '/';
int fd = open(fn, O_WRONLY | O_CREAT, 0777);
if (fd < 0 && errno == ETXTBSY) {
unlink(fn);
fd = open(fn, O_WRONLY | O_CREAT, 0777);
}
if (fd >= 0) {
ftruncate(fd, bl->fileSize);
lseek(fd, bl->targetAddr, SEEK_SET);
// DBG("write %d bytes at %d to %s", bl->payloadSize, bl->targetAddr, fn);
write(fd, bl->data, bl->payloadSize);
close(fd);
if (strlen(fn) > 4 && !strcmp(fn + strlen(fn) - 4, ".elf")) {
strcpy(execPath, fn);
}
}
}
if (state && bl->numBlocks) {
if (state->numBlocks != bl->numBlocks) {
if (bl->numBlocks >= MAX_BLOCKS || state->numBlocks)
state->numBlocks = 0xffffffff;
else
state->numBlocks = bl->numBlocks;
}
if (bl->blockNo < MAX_BLOCKS) {
uint8_t mask = 1 << (bl->blockNo % 8);
uint32_t pos = bl->blockNo / 8;
if (!(state->writtenMask[pos] & mask)) {
// logval("incr", state->numWritten);
state->writtenMask[pos] |= mask;
state->numWritten++;
// DBG("write %d/%d #%d", state->numWritten, state->numBlocks, bl->blockNo);
}
if (state->numWritten >= state->numBlocks) {
LOG("writing done");
rereadData();
restartProgram();
ZERO(execPath);
LOG("forked");
}
}
} else {
// TODO timeout for restart?
}
}

241
uf2daemon/main.c Normal file
Просмотреть файл

@ -0,0 +1,241 @@
#define _GNU_SOURCE 1
#include <sys/types.h>
#include <linux/nbd.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdarg.h>
#include "uf2.h"
const char *dev_file = "/dev/nbd0";
#define NUM_BLOCKS NUM_FAT_BLOCKS
uint64_t ntohll(uint64_t a) {
return ((uint64_t)ntohl(a & 0xffffffff) << 32) | ntohl(a >> 32);
}
#define htonll ntohll
void mylog(const char *fmt, ...) {
va_list args;
char *p, *p2;
va_start(args, fmt);
vasprintf(&p, fmt, args);
va_end(args);
if (p[0] != '<')
asprintf(&p2, "<4>%s\n", p);
else
asprintf(&p2, "%s\n", p);
int len = strlen(p2);
#ifdef X86
write(2, p2, len);
#else
int fd = open("/dev/kmsg", O_WRONLY);
write(fd, p2, len);
close(fd);
#endif
free(p);
free(p2);
}
void readAll(int fd, void *dst, uint32_t length) {
while (length) {
int curr = read(fd, dst, length);
if (curr < 0)
FAIL("read failed on fd:%d", fd);
length -= curr;
dst = (char *)dst + curr;
}
}
void writeAll(int fd, void *dst, uint32_t length) {
while (length) {
int curr = write(fd, dst, length);
if (curr < 0)
FAIL("write failed on fd:%d", fd);
length -= curr;
dst = (char *)dst + curr;
}
}
int nbd;
int sock;
int sockets[2];
struct nbd_request request;
struct nbd_reply reply;
void nbd_ioctl(unsigned id, int arg) {
int err = ioctl(nbd, id, arg);
if (err < 0)
FAIL("ioctl(%u) failed [%s]", id, strerror(errno));
}
void startclient() {
close(sockets[0]);
nbd_ioctl(NBD_SET_SOCK, sockets[1]);
nbd_ioctl(NBD_DO_IT, 0);
nbd_ioctl(NBD_CLEAR_QUE, 0);
nbd_ioctl(NBD_CLEAR_SOCK, 0);
exit(0);
}
void handleread(int off, int len) {
uint8_t buf[512];
// LOG("read @%d len=%d", off, len);
reply.error = 0; // htonl(EPERM);
writeAll(sock, &reply, sizeof(struct nbd_reply));
for (int i = 0; i < len; ++i) {
read_block(off + i, buf);
writeAll(sock, buf, 512);
}
}
void handlewrite(int off, int len) {
uint8_t buf[512];
// LOG("write @%d len=%d", off, len);
for (int i = 0; i < len; ++i) {
readAll(sock, buf, 512);
write_block(off + i, buf);
}
reply.error = 0;
writeAll(sock, &reply, sizeof(struct nbd_reply));
}
void setupFs();
void runNBD() {
setupFs();
//struct sigaction sigchld_action = {.sa_handler = SIG_DFL, .sa_flags = SA_NOCLDWAIT};
//sigaction(SIGCHLD, &sigchld_action, NULL);
int err = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
assert(err >= 0);
nbd = open(dev_file, O_RDWR);
assert(nbd >= 0);
nbd_ioctl(BLKFLSBUF, 0);
nbd_ioctl(NBD_SET_BLKSIZE, 512);
nbd_ioctl(NBD_SET_SIZE_BLOCKS, NUM_BLOCKS);
nbd_ioctl(NBD_CLEAR_SOCK, 0);
if (!fork())
startclient();
int fd = open(dev_file, O_RDONLY);
assert(fd != -1);
close(fd);
close(sockets[1]);
sock = sockets[0];
reply.magic = htonl(NBD_REPLY_MAGIC);
reply.error = htonl(0);
LOG("NBD loop");
for (;;) {
// nbd_ioctl(BLKFLSBUF, 0); // flush buffers - we don't want the kernel to cache the writes
int nread = read(sock, &request, sizeof(request));
if (nread < 0) {
LOG("nbd read err %s", strerror(errno));
continue;
}
if (nread == 0)
return;
assert(nread == sizeof(request));
memcpy(reply.handle, request.handle, sizeof(reply.handle));
reply.error = htonl(0);
assert(request.magic == htonl(NBD_REQUEST_MAGIC));
uint32_t len = ntohl(request.len);
assert((len & 511) == 0);
len >>= 9;
uint64_t from = ntohll(request.from);
assert((from & 511) == 0);
from >>= 9;
switch (ntohl(request.type)) {
case NBD_CMD_READ:
handleread(from, len);
break;
case NBD_CMD_WRITE:
handlewrite(from, len);
break;
case NBD_CMD_DISC:
return;
default:
FAIL("invalid cmd: %d", ntohl(request.type));
}
}
}
void enableMSD(int enabled) {
#ifndef X86
LOG("%sable MSD", enabled ? "en" : "dis");
if (enabled)
system("/opt/msdon.sh");
else
system("/opt/msdoff.sh");
if (!enabled && nbd)
nbd_ioctl(BLKFLSBUF, 0);
#else
LOG("fake enable MSD: %d", enabled);
#endif
}
int main(int argc, char **argv) {
#ifndef X86
daemon(0, 1);
#endif
if (argc > 1)
dev_file = argv[1];
for (;;) {
pid_t child = fork();
if (child == 0) {
runNBD();
return 0;
}
sleep(1);
enableMSD(1);
int wstatus = 0;
waitpid(child, &wstatus, 0);
enableMSD(0); // force "eject"
if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) {
LOG("abnormal child return, %d, exit: %d, signal: %d", child,
WIFEXITED(wstatus) ? WEXITSTATUS(wstatus) : -1,
WIFSIGNALED(wstatus) ? WTERMSIG(wstatus) : -1);
sleep(5);
} else {
sleep(2);
}
}
return 0;
}

46
uf2daemon/uf2.h Normal file
Просмотреть файл

@ -0,0 +1,46 @@
#ifndef UF2_H
#define UF2_H 1
#include "uf2format.h"
#include <stdio.h>
#include <string.h>
#ifndef INDEX_URL
#define INDEX_URL "https://www.pxt.io/"
#endif
#define UF2_VERSION_BASE "v0.1.0"
// needs to be more than ~4200 and less than ~65000 (to force FAT16)
#define NUM_FAT_BLOCKS 65000
#define UF2_VERSION UF2_VERSION_BASE " F"
//! Static block size for all memories
#define UDI_MSC_BLOCK_SIZE 512L
void read_block(uint32_t block_no, uint8_t *data);
void write_block(uint32_t block_no, uint8_t *data);
#define CONCAT_1(a, b) a##b
#define CONCAT_0(a, b) CONCAT_1(a, b)
#define STATIC_ASSERT(e) enum { CONCAT_0(_static_assert_, __LINE__) = 1 / ((e) ? 1 : 0) }
extern const char infoUf2File[];
void readAll(int fd, void *dst, uint32_t length);
STATIC_ASSERT(sizeof(UF2_Block) == 512);
void mylog(const char *fmt, ...);
#define FAIL(args...) \
do { \
mylog("<4>" args); \
exit(1); \
} while (0)
#define LOG mylog
#endif

48
uf2daemon/uf2format.h Normal file
Просмотреть файл

@ -0,0 +1,48 @@
#ifndef UF2FORMAT_H
#define UF2FORMAT_H 1
#include <stdint.h>
#include <stdbool.h>
// All entries are little endian.
// if you increase that, you will also need to update the linker script file
#define APP_START_ADDRESS 0x00002000
#define UF2_MAGIC_START0 0x0A324655UL // "UF2\n"
#define UF2_MAGIC_START1 0x9E5D5157UL // Randomly selected
#define UF2_MAGIC_END 0x0AB16F30UL // Ditto
// If set, the block is "comment" and should not be flashed to the device
#define UF2_FLAG_NOFLASH 0x00000001
#define UF2_FLAG_FILE 0x00001000
#define UF2_FILENAME_MAX 150
#define UF2_MAX_PAYLOAD (476 - 10) // leaving some space for filename
// for this bootloader
#define UF2_MAX_FILESIZE (64 * 1024 * 1024)
typedef struct {
// 32 byte header
uint32_t magicStart0;
uint32_t magicStart1;
uint32_t flags;
uint32_t targetAddr;
uint32_t payloadSize;
uint32_t blockNo;
uint32_t numBlocks;
uint32_t fileSize;
// raw data, followed by filename (NUL-terminated) at payloadSize
uint8_t data[476];
// store magic also at the end to limit damage from partial block reads
uint32_t magicEnd;
} UF2_Block;
static inline bool is_uf2_block(void *data) {
UF2_Block *bl = (UF2_Block *)data;
return bl->magicStart0 == UF2_MAGIC_START0 && bl->magicStart1 == UF2_MAGIC_START1 &&
bl->magicEnd == UF2_MAGIC_END;
}
#endif

106
uf2daemon/uf2hid.h Normal file
Просмотреть файл

@ -0,0 +1,106 @@
#ifndef UF2_HID_H
#define UF2_HID_H 1
#define HF2_CMD_BININFO 0x0001
// no arguments
#define HF2_MODE_BOOTLOADER 0x01
#define HF2_MODE_USERSPACE 0x02
struct HF2_BININFO_Result {
uint32_t mode;
uint32_t flash_page_size;
uint32_t flash_num_pages;
uint32_t max_message_size;
};
#define HF2_CMD_INFO 0x0002
// no arguments
// results is utf8 character array
#define HF2_CMD_RESET_INTO_APP 0x0003
// no arguments, no result
#define HF2_CMD_RESET_INTO_BOOTLOADER 0x0004
// no arguments, no result
#define HF2_CMD_START_FLASH 0x0005
// no arguments, no result
#define HF2_CMD_WRITE_FLASH_PAGE 0x0006
struct HF2_WRITE_FLASH_PAGE_Command {
uint32_t target_addr;
uint32_t data[0];
};
// no result
#define HF2_CMD_CHKSUM_PAGES 0x0007
struct HF2_CHKSUM_PAGES_Command {
uint32_t target_addr;
uint32_t num_pages;
};
struct HF2_CHKSUM_PAGES_Result {
uint16_t chksums[0 /* num_pages */];
};
#define HF2_CMD_READ_WORDS 0x0008
struct HF2_READ_WORDS_Command {
uint32_t target_addr;
uint32_t num_words;
};
struct HF2_READ_WORDS_Result {
uint32_t words[0 /* num_words */];
};
#define HF2_CMD_WRITE_WORDS 0x0009
struct HF2_WRITE_WORDS_Command {
uint32_t target_addr;
uint32_t num_words;
uint32_t words[0 /* num_words */];
};
// no result
#define HF2_CMD_DMESG 0x0010
// no arguments
// results is utf8 character array
typedef struct {
uint32_t command_id;
uint16_t tag;
uint8_t reserved0;
uint8_t reserved1;
union {
struct HF2_WRITE_FLASH_PAGE_Command write_flash_page;
struct HF2_WRITE_WORDS_Command write_words;
struct HF2_READ_WORDS_Command read_words;
struct HF2_CHKSUM_PAGES_Command chksum_pages;
};
} HF2_Command;
typedef struct {
uint16_t tag;
union {
struct {
uint8_t status;
uint8_t status_info;
};
uint16_t status16;
};
union {
struct HF2_BININFO_Result bininfo;
uint8_t data8[0];
uint16_t data16[0];
uint32_t data32[0];
};
} HF2_Response;
#define HF2_FLAG_SERIAL_OUT 0x80
#define HF2_FLAG_SERIAL_ERR 0xC0
#define HF2_FLAG_CMDPKT_LAST 0x40
#define HF2_FLAG_CMDPKT_BODY 0x00
#define HF2_FLAG_MASK 0xC0
#define HF2_SIZE_MASK 63
#define HF2_STATUS_OK 0x00
#define HF2_STATUS_INVALID_CMD 0x01
#endif