diff --git a/06_drivers_gpio_uart/Makefile b/06_drivers_gpio_uart/Makefile
index b6b62c58..b84ca328 100644
--- a/06_drivers_gpio_uart/Makefile
+++ b/06_drivers_gpio_uart/Makefile
@@ -5,6 +5,12 @@
# Default to the RPi3
BSP ?= rpi3
+# Default to a serial device name that is common in Linux.
+DEV_SERIAL ?= /dev/ttyUSB0
+
+# Query the host system's kernel name
+UNAME_S = $(shell uname -s)
+
# BSP-specific arguments
ifeq ($(BSP),rpi3)
TARGET = aarch64-unknown-none-softfloat
@@ -51,13 +57,23 @@ KERNEL_ELF = target/$(TARGET)/release/kernel
DOCKER_IMAGE = rustembedded/osdev-utils
DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial
DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t
+DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils
+DOCKER_ARG_DEV = --privileged -v /dev:/dev
DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
DOCKER_ELFTOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE)
-EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
+# Dockerize commands that require USB device passthrough only on Linux
+ifeq ($(UNAME_S),Linux)
+ DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV)
+
+ DOCKER_MINITERM = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE)
+endif
+
+EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
+EXEC_MINITERM = ruby ../utils/miniterm.rb
-.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check
+.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check
all: $(KERNEL_BIN)
@@ -78,6 +94,9 @@ qemu: $(KERNEL_BIN)
@$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
endif
+miniterm:
+ @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL)
+
clippy:
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
diff --git a/06_drivers_gpio_uart/README.md b/06_drivers_gpio_uart/README.md
index 58d46e4c..da30d32b 100644
--- a/06_drivers_gpio_uart/README.md
+++ b/06_drivers_gpio_uart/README.md
@@ -66,18 +66,29 @@ on the SD card._
5. Insert the SD card into the RPi and connect the USB serial to your host PC.
- Wiring diagram at [top-level README](../README.md#usb-serial).
-6. Run `screen` (you might need to install it first):
+6. Run the `miniterm` target, which opens the UART device on the host:
```console
-$ sudo screen /dev/ttyUSB0 230400
+$ make miniterm
```
-> ❗ **NOTE**: Depending on your host operating system, the serial device name might differ.
-> For example, on `macOS`, it might be something like `/dev/tty.usbserial-0001`.
+> ❗ **NOTE**: `Miniterm` assumes a default serial device name of `/dev/ttyUSB0`. Depending on your
+> host operating system, the device name might differ. For example, on `macOS`, it might be
+> something like `/dev/tty.usbserial-0001`. In this case, please give the name explicitly:
-7. Hit Enter to kick off the kernel boot process. Observe the output:
```console
+$ DEV_SERIAL=/dev/tty.usbserial-0001 make miniterm
+```
+
+7. Hit Enter after seeing "`Connected`" to kick off the kernel boot process and observe
+ the output:
+
+```console
+Miniterm 1.0
+
+
+[MT] ✅ Connected
[0] Booting on: Raspberry Pi 3
[1] Drivers loaded:
1. BCM GPIO
@@ -86,7 +97,7 @@ $ sudo screen /dev/ttyUSB0 230400
[3] Echoing input now
```
-8. Exit screen by pressing ctrl-a ctrl-d or disconnecting the USB serial.
+8. Exit by pressing ctrl-c.
## Diff to previous
```diff
@@ -116,6 +127,59 @@ diff -uNr 05_safe_globals/Cargo.toml 06_drivers_gpio_uart/Cargo.toml
[target.'cfg(target_arch = "aarch64")'.dependencies]
cortex-a = { version = "4.x.x" }
+diff -uNr 05_safe_globals/Makefile 06_drivers_gpio_uart/Makefile
+--- 05_safe_globals/Makefile
++++ 06_drivers_gpio_uart/Makefile
+@@ -5,6 +5,12 @@
+ # Default to the RPi3
+ BSP ?= rpi3
+
++# Default to a serial device name that is common in Linux.
++DEV_SERIAL ?= /dev/ttyUSB0
++
++# Query the host system's kernel name
++UNAME_S = $(shell uname -s)
++
+ # BSP-specific arguments
+ ifeq ($(BSP),rpi3)
+ TARGET = aarch64-unknown-none-softfloat
+@@ -51,13 +57,23 @@
+ DOCKER_IMAGE = rustembedded/osdev-utils
+ DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial
+ DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t
++DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils
++DOCKER_ARG_DEV = --privileged -v /dev:/dev
+
+ DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
+ DOCKER_ELFTOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE)
+
+-EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
++# Dockerize commands that require USB device passthrough only on Linux
++ifeq ($(UNAME_S),Linux)
++ DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV)
++
++ DOCKER_MINITERM = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE)
++endif
++
++EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
++EXEC_MINITERM = ruby ../utils/miniterm.rb
+
+-.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check
++.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check
+
+ all: $(KERNEL_BIN)
+
+@@ -78,6 +94,9 @@
+ @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN)
+ endif
+
++miniterm:
++ @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL)
++
+ clippy:
+ RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
+
+
diff -uNr 05_safe_globals/src/_arch/aarch64/cpu.rs 06_drivers_gpio_uart/src/_arch/aarch64/cpu.rs
--- 05_safe_globals/src/_arch/aarch64/cpu.rs
+++ 06_drivers_gpio_uart/src/_arch/aarch64/cpu.rs
diff --git a/07_uart_chainloader/README.md b/07_uart_chainloader/README.md
index 907b6770..a19d23b3 100644
--- a/07_uart_chainloader/README.md
+++ b/07_uart_chainloader/README.md
@@ -103,20 +103,7 @@ Binary files 06_drivers_gpio_uart/demo_payload_rpi4.img and 07_uart_chainloader/
diff -uNr 06_drivers_gpio_uart/Makefile 07_uart_chainloader/Makefile
--- 06_drivers_gpio_uart/Makefile
+++ 07_uart_chainloader/Makefile
-@@ -5,6 +5,12 @@
- # Default to the RPi3
- BSP ?= rpi3
-
-+# Default to a serial device name that is common in Linux.
-+DEV_SERIAL ?= /dev/ttyUSB0
-+
-+# Query the host system's kernel name
-+UNAME_S = $(shell uname -s)
-+
- # BSP-specific arguments
- ifeq ($(BSP),rpi3)
- TARGET = aarch64-unknown-none-softfloat
-@@ -15,7 +21,8 @@
+@@ -21,7 +21,8 @@
OBJDUMP_BINARY = aarch64-none-elf-objdump
NM_BINARY = aarch64-none-elf-nm
LINKER_FILE = src/bsp/raspberrypi/link.ld
@@ -126,7 +113,7 @@ diff -uNr 06_drivers_gpio_uart/Makefile 07_uart_chainloader/Makefile
else ifeq ($(BSP),rpi4)
TARGET = aarch64-unknown-none-softfloat
KERNEL_BIN = kernel8.img
-@@ -25,7 +32,8 @@
+@@ -31,7 +32,8 @@
OBJDUMP_BINARY = aarch64-none-elf-objdump
NM_BINARY = aarch64-none-elf-nm
LINKER_FILE = src/bsp/raspberrypi/link.ld
@@ -136,34 +123,25 @@ diff -uNr 06_drivers_gpio_uart/Makefile 07_uart_chainloader/Makefile
endif
# Export for build.rs
-@@ -51,13 +59,24 @@
- DOCKER_IMAGE = rustembedded/osdev-utils
- DOCKER_CMD = docker run --rm -v $(shell pwd):/work/tutorial -w /work/tutorial
- DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i -t
-+DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/work/utils
-+DOCKER_ARG_DEV = --privileged -v /dev:/dev
-
- DOCKER_QEMU = $(DOCKER_CMD_INTERACT) $(DOCKER_IMAGE)
- DOCKER_ELFTOOLS = $(DOCKER_CMD) $(DOCKER_IMAGE)
-
--EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
-+# Dockerize commands that require USB device passthrough only on Linux
-+ifeq ($(UNAME_S),Linux)
-+ DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV)
-+
+@@ -67,13 +69,14 @@
+ ifeq ($(UNAME_S),Linux)
+ DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV)
+
+- DOCKER_MINITERM = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE)
+ DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_UTILS) $(DOCKER_IMAGE)
-+endif
+ endif
--.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu clippy clean readelf objdump nm check
-+EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
+ EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
+-EXEC_MINITERM = ruby ../utils/miniterm.rb
+EXEC_MINIPUSH = ruby ../utils/minipush.rb
-+
+
+-.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu chainboot clippy clean readelf objdump nm check
+.PHONY: all $(KERNEL_ELF) $(KERNEL_BIN) doc qemu qemuasm chainboot clippy clean readelf objdump nm \
+ check
all: $(KERNEL_BIN)
-@@ -71,13 +90,19 @@
+@@ -87,15 +90,18 @@
$(DOC_CMD) --document-private-items --open
ifeq ($(QEMU_MACHINE_TYPE),)
@@ -178,13 +156,14 @@ diff -uNr 06_drivers_gpio_uart/Makefile 07_uart_chainloader/Makefile
+ @$(DOCKER_QEMU) $(EXEC_QEMU) $(QEMU_RELEASE_ARGS) -kernel $(KERNEL_BIN) -d in_asm
endif
+-miniterm:
+- @$(DOCKER_MINITERM) $(EXEC_MINITERM) $(DEV_SERIAL)
+chainboot:
+ @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(CHAINBOOT_DEMO_PAYLOAD)
-+
+
clippy:
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(CLIPPY_CMD)
-
-@@ -88,7 +113,10 @@
+@@ -107,7 +113,10 @@
readelf --headers $(KERNEL_ELF)
objdump: $(KERNEL_ELF)
diff --git a/utils/minipush.rb b/utils/minipush.rb
index 61447858..9c743bd3 100755
--- a/utils/minipush.rb
+++ b/utils/minipush.rb
@@ -5,60 +5,26 @@
#
# Copyright (c) 2020 Andre Richter
-require 'rubygems'
-require 'bundler/setup'
-require 'io/console'
-require 'colorize'
+require_relative 'miniterm'
require 'ruby-progressbar'
-require 'serialport'
-require 'timeout'
require_relative 'minipush/progressbar_patch'
+require 'timeout'
-class ConnectionError < StandardError; end
class ProtocolError < StandardError; end
# The main class
-class MiniPush
+class MiniPush < MiniTerm
def initialize(serial_name, binary_image_path)
- @target_serial_name = serial_name
- @target_serial = nil
+ super(serial_name)
+
+ @name_short = 'MP'
@binary_image_path = binary_image_path
@binary_size = nil
@binary_image = nil
- @host_console = IO.console
end
private
- def serial_connected?
- File.exist?(@target_serial_name)
- end
-
- def wait_for_serial
- loop do
- break if serial_connected?
-
- print "\r[MP] ⏳ Waiting for #{@target_serial_name}"
- sleep(1)
- end
- end
-
- def open_serial
- wait_for_serial
-
- @target_serial = SerialPort.new(@target_serial_name, 230_400, 8, 1, SerialPort::NONE)
-
- # Ensure all output is immediately flushed to the device.
- @target_serial.sync = true
- rescue Errno::EACCES => e
- puts
- puts "[MP] 🚫 #{e.message} - Maybe try with 'sudo'"
- exit
- else
- puts
- puts '[MP] ✅ Connected'
- end
-
# The three characters signaling the request token are expected to arrive as the last three
# characters _at the end_ of a character stream (e.g. after a header print from Miniload).
def wait_for_binary_request
@@ -92,7 +58,7 @@ class MiniPush
def send_binary
pb = ProgressBar.create(
total: @binary_size,
- format: '[MP] ⏩ Pushing %k KiB %b🦀%i %p%% %r KiB/s %a',
+ format: "[#{@name_short}] ⏩ Pushing %k KiB %b🦀%i %p%% %r KiB/s %a",
rate_scale: ->(rate) { rate / 1024 },
length: 92
)
@@ -104,69 +70,16 @@ class MiniPush
end
end
- def terminal
- @host_console.raw!
-
- Thread.abort_on_exception = true
- Thread.report_on_exception = false
-
- # Receive from target and print on host console.
- target_to_host = Thread.new do
- loop do
- char = @target_serial.getc
-
- raise ConnectionError if char.nil?
-
- # onlcr
- @host_console.putc("\r") if char == "\n"
- @host_console.putc(char)
- end
- end
-
- # Transmit host console input to target.
- loop do
- c = @host_console.getc
-
- # CTRL + C in raw mode was pressed
- if c == "\u{3}"
- target_to_host.kill
- break
- end
-
- @target_serial.putc(c)
- end
- end
-
- def connetion_reset
- @target_serial&.close
- @target_serial = nil
- @host_console.cooked!
- end
-
- # When the serial lost power or was removed during R/W operation.
- def handle_reconnect
- connetion_reset
-
- puts
- puts "[MP] ⚡ #{'Connection Error: Reinsert the USB serial again'.light_red}"
- end
-
# When the serial is still powered.
def handle_protocol_error
connetion_reset
puts
- puts "[MP] ⚡ #{'Protocol Error: Remove and insert the USB serial again'.light_red}"
+ puts "[#{@name_short}] ⚡ " \
+ "#{'Protocol Error: Remove and insert the USB serial again'.light_red}"
sleep(1) while serial_connected?
end
- def handle_unexpected(error)
- connetion_reset
-
- puts
- puts "[MP] ⚡ #{"Unexpected Error: #{error.inspect}".light_red}"
- end
-
public
def run
@@ -187,7 +100,7 @@ class MiniPush
ensure
connetion_reset
puts
- puts '[MP] Bye 👋'
+ puts "[#{@name_short}] Bye 👋"
end
end
diff --git a/utils/miniterm.rb b/utils/miniterm.rb
new file mode 100755
index 00000000..fb05189a
--- /dev/null
+++ b/utils/miniterm.rb
@@ -0,0 +1,138 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+# SPDX-License-Identifier: MIT OR Apache-2.0
+#
+# Copyright (c) 2020 Andre Richter
+
+require 'rubygems'
+require 'bundler/setup'
+require 'io/console'
+require 'colorize'
+require 'serialport'
+
+class ConnectionError < StandardError; end
+
+# The main class
+class MiniTerm
+ def initialize(serial_name)
+ @name_short = 'MT'
+ @target_serial_name = serial_name
+ @target_serial = nil
+ @host_console = IO.console
+ end
+
+ private
+
+ def serial_connected?
+ File.exist?(@target_serial_name)
+ end
+
+ def wait_for_serial
+ loop do
+ break if serial_connected?
+
+ print "\r[#{@name_short}] ⏳ Waiting for #{@target_serial_name}"
+ sleep(1)
+ end
+ end
+
+ def open_serial
+ wait_for_serial
+
+ @target_serial = SerialPort.new(@target_serial_name, 230_400, 8, 1, SerialPort::NONE)
+
+ # Ensure all output is immediately flushed to the device.
+ @target_serial.sync = true
+ rescue Errno::EACCES => e
+ puts
+ puts "[#{@name_short}] 🚫 #{e.message} - Maybe try with 'sudo'"
+ exit
+ else
+ puts
+ puts "[#{@name_short}] ✅ Connected"
+ end
+
+ def terminal
+ @host_console.raw!
+
+ Thread.abort_on_exception = true
+ Thread.report_on_exception = false
+
+ # Receive from target and print on host console.
+ target_to_host = Thread.new do
+ loop do
+ char = @target_serial.getc
+
+ raise ConnectionError if char.nil?
+
+ # Translate incoming newline to newline + carriage return.
+ @host_console.putc("\r") if char == "\n"
+ @host_console.putc(char)
+ end
+ end
+
+ # Transmit host console input to target.
+ loop do
+ c = @host_console.getc
+
+ # CTRL + C in raw mode was pressed.
+ if c == "\u{3}"
+ target_to_host.kill
+ break
+ end
+
+ @target_serial.putc(c)
+ end
+ end
+
+ def connetion_reset
+ @target_serial&.close
+ @target_serial = nil
+ @host_console.cooked!
+ end
+
+ # When the serial lost power or was removed during R/W operation.
+ def handle_reconnect
+ connetion_reset
+
+ puts
+ puts "[#{@name_short}] ⚡ #{'Connection Error: Reinsert the USB serial again'.light_red}"
+ end
+
+ def handle_unexpected(error)
+ connetion_reset
+
+ puts
+ puts "[#{@name_short}] ⚡ #{"Unexpected Error: #{error.inspect}".light_red}"
+ end
+
+ public
+
+ def run
+ open_serial
+ terminal
+ rescue ConnectionError, EOFError, Errno::EIO
+ handle_reconnect
+ retry
+ rescue StandardError => e
+ handle_unexpected(e)
+ ensure
+ connetion_reset
+ puts
+ puts "[#{@name_short}] Bye 👋"
+ end
+end
+
+if __FILE__ == $PROGRAM_NAME
+ puts 'Miniterm 1.0'.cyan
+ puts
+
+ # CTRL + C handler. Only here to suppress Ruby's default exception print.
+ trap('INT') do
+ # The `ensure` block from `MiniTerm::run` will run after exit, restoring console state.
+ exit
+ end
+
+ MiniTerm.new(ARGV[0]).run
+end