diff --git a/.gitignore b/.gitignore index 919a231..e3e9589 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ zig-out/ zig-cache/ .DS_Store .gdbinit -.lldbinit +.lldbinit \ No newline at end of file diff --git a/core/LICENSE b/core/LICENSE new file mode 100644 index 0000000..2a30142 --- /dev/null +++ b/core/LICENSE @@ -0,0 +1,11 @@ +Copyright (c) 2022 Matthew Knight, Felix Queißner and Maciej Kuliński + +This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. diff --git a/core/README.adoc b/core/README.adoc new file mode 100644 index 0000000..d21a7d2 --- /dev/null +++ b/core/README.adoc @@ -0,0 +1,162 @@ +:imagesdir: design +:toc: macro + +image::logo-text-auto.svg[] + +image::https://img.shields.io/discord/824493524413710336.svg?logo=discord[link=https://discord.gg/ShUWykk38X] + +[NOTE] +This is in development; breaks in the API are bound to happen. + +toc::[] + +== What version of Zig to use + +0.11.0 + +== Contributing + +Please see the https://github.com/orgs/ZigEmbeddedGroup/projects/1/views/1[project page], it's used as a place to brainstorm and organize work in ZEG. There will be issues marked as `good first issue` or drafts for larger ideas that need scoping/breaking ground on. + +== Introduction + +This repo contains the infrastructure for getting started in an embedded Zig project; it "gets you to main()". Specifically, it offers: + +* a single easy-to-use builder function that: +** generates your linker script +** sets up packages and startup code +* generalized interfaces for common devices, such as UART. +* device drivers for interacting with external hardware +* an uncomplicated method to define xref:interrupts[interrupts] + +== Getting Started + +Visit https://github.com/ZigEmbeddedGroup/microzig-examples to find examples for your specific board. + +== Design + +For MicroZig internals please see the xref:docs/design.adoc[Design Document]. + +== Does MicroZig support X hardware? + +MicroZig is designed to cover as wide a swath of hardware as possible. The https://github.com/ZigEmbeddedGroup[Zig Embedded Group] has some repositories that contain hardware-specific code. You will find them with the `hardware-support-package` label. If you can't find your specific device, it doesn't mean that you can't run Zig on it, it's likely you're just the first! In that case, see xref:#getting-microzig-on-new-hardware[Getting MicroZig on New Hardware]. + +Start with an empty Zig project by running `zig init-exe`, and add the hardware support package as a submodule. We'll use `microchip-atmega` in our example: + +[source,zig] +---- +const std = @import("std"); +const atmega = @import("deps/microchip-atmega/build.zig"); + +// the hardware support package should have microzig as a dependency +const microzig = @import("deps/hardware_support_package/deps/microzig/build.zig"); + +pub fn build(b: *std.build.Builder) !void { + const optimize = b.standardOptimizeOption(.{}); + var exe = microzig.addEmbeddedExecutable( b, .{ + .name = "my-executable", + .source_file = .{ + .path = "src/main.zig", + }, + .backing = .{ + .board = atmega.boards.arduino_nano, + + // instead of a board, you can use the raw chip as well + // .chip = atmega.chips.atmega328p, + }, + .optimize = optimize, + }); + exe.installArtifact(b); +} +---- + +`zig build` and now you have an executable for an Arduino Nano. In your application you can import `microzig` in order to interact with the hardware: + +[source,zig] +---- +const microzig = @import("microzig"); + +// `microzig.config`: comptime access to configuration +// `microzig.chip`: access to register definitions, generated code +// `microzig.board`: access to board information +// `microzig.hal`: access to hand-written code for interacting with the hardware +// `microzig.cpu`: access to AVR5 specific functions + +pub fn main() !void { + // your program here +} +---- + +== Getting MicroZig on New Hardware + +If you have a board/chip that isn't defined in microzig, you can set it up yourself! You need to have: + +* SVD or ATDF file defining registers +* flash and ram address and sizes + +First, use https://github.com/ZigEmbeddedGroup/regz[Regz] to generate the register definitions for your chip and save them to a file. Then define the chip: + +[source,zig] +---- +const nrf52832 = Chip{ + .name = "nRF52832", + .source = .{ + .path = "path/to/generated/file.zig", + }, + .cpu = cpus.cortex_m4, + .memory_regions = &.{ + MemoryRegion{ .offset = 0x00000000, .length = 0x80000, .kind = .flash }, + MemoryRegion{ .offset = 0x20000000, .length = 0x10000, .kind = .ram }, + }, +}; + +const backing = .{ + .chip = nrf52832, +}; +---- + +It's important that the chip name actually matches one of the entries under `devices` in the generated code. + +=== Optional: JSON Register Schema + +You can also invoke `regz` to generate a JSON representation of the hardware: + +[source] +---- +regz --json +---- + +This file could then be used by tooling. You can add it to a `Chip` like so: + + +[source,zig] +---- +const nrf52832 = Chip{ + .name = "nRF52832", + .json_register_schema = .{ + .path = "path/to.json", + }, + // ... +}; +---- + +== Interrupts + +The currently supported architectures for interrupt vector generation are ARM and AVR. To define the Interrupt Service Routine (ISR) for a given interrupt, you create a function with the same name in an `interrupts` namespace, which is nested in a `microzig_options` namespace: + +[source,zig] +---- +pub const microzig_options = struct { + pub const interrupts = struct { + pub fn PCINT0() void { + // interrupt handling code + } + }; +} + +pub fn main() !void { + // my application +} +---- + +We're using compile-time checks along with the generated code to determine the list of interrupts. If a function is defined whose name is not in this list, you'll get a compiler error with the list of interrupts/valid names. diff --git a/core/build.zig b/core/build.zig new file mode 100644 index 0000000..748833f --- /dev/null +++ b/core/build.zig @@ -0,0 +1,979 @@ +//! Some words on the build script here: +//! We cannot use a test runner here as we're building for freestanding. +//! This means we need to use addExecutable() instead of using + +const std = @import("std"); +const uf2 = @import("uf2"); + +//////////////////////////////////////// +// MicroZig Gen 2 Interface // +//////////////////////////////////////// + +fn root() []const u8 { + return comptime (std.fs.path.dirname(@src().file) orelse "."); +} +const build_root = root(); + +const MicroZig = @This(); + +b: *std.Build, +self: *std.Build.Dependency, + +/// Creates a new instance of the MicroZig build support. +/// +/// This is necessary as we need to keep track of some internal state to prevent +/// duplicated work per firmware built. +pub fn init(b: *std.Build, dependency_name: []const u8) *MicroZig { + const mz = b.allocator.create(MicroZig) catch @panic("out of memory"); + mz.* = MicroZig{ + .b = b, + .self = b.dependency(dependency_name, .{}), + }; + return mz; +} + +/// This build script validates usage patterns we expect from MicroZig +pub fn build(b: *std.Build) !void { + const uf2_dep = b.dependency("uf2", .{}); + + const build_test = b.addTest(.{ + .root_source_file = .{ .path = "build.zig" }, + }); + + build_test.addAnonymousModule("uf2", .{ + .source_file = .{ .cwd_relative = uf2_dep.builder.pathFromRoot("build.zig") }, + }); + + const install_docs = b.addInstallDirectory(.{ + .source_dir = build_test.getEmittedDocs(), + .install_dir = .prefix, + .install_subdir = "docs", + }); + + b.getInstallStep().dependOn(&install_docs.step); + + // const backings = @import("test/backings.zig"); + // const optimize = b.standardOptimizeOption(.{}); + + // const minimal = addEmbeddedExecutable(b, .{ + // .name = "minimal", + // .source_file = .{ + // .path = comptime root_dir() ++ "/test/programs/minimal.zig", + // }, + // .backing = backings.minimal, + // .optimize = optimize, + // }); + + // const has_hal = addEmbeddedExecutable(b, .{ + // .name = "has_hal", + // .source_file = .{ + // .path = comptime root_dir() ++ "/test/programs/has_hal.zig", + // }, + // .backing = backings.has_hal, + // .optimize = optimize, + // }); + + // const has_board = addEmbeddedExecutable(b, .{ + // .name = "has_board", + // .source_file = .{ + // .path = comptime root_dir() ++ "/test/programs/has_board.zig", + // }, + // .backing = backings.has_board, + // .optimize = optimize, + // }); + + // const core_tests = b.addTest(.{ + // .root_source_file = .{ + // .path = comptime root_dir() ++ "/src/core.zig", + // }, + // .optimize = optimize, + // }); + + // const test_step = b.step("test", "build test programs"); + // test_step.dependOn(&minimal.inner.step); + // test_step.dependOn(&has_hal.inner.step); + // test_step.dependOn(&has_board.inner.step); + // test_step.dependOn(&b.addRunArtifact(core_tests).step); +} + +/// The resulting binary format for the firmware file. +/// A lot of embedded systems don't use plain ELF files, thus we provide means +/// to convert the resulting ELF into other common formats. +pub const BinaryFormat = union(enum) { + /// [Executable and Linkable Format](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format), the standard output from the compiler. + elf, + + /// A flat binary, contains only the loaded portions of the firmware with an unspecified base offset. + bin, + + /// The [Intel HEX](https://en.wikipedia.org/wiki/Intel_HEX) format, contains + /// an ASCII description of what memory to load where. + hex, + + /// A [Device Firmware Upgrade](https://www.usb.org/sites/default/files/DFU_1.1.pdf) file. + dfu, + + /// The [USB Flashing Format (UF2)](https://github.com/microsoft/uf2) designed by Microsoft. + uf2: uf2.FamilyId, + + /// The [firmware format](https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/firmware-image-format.html) used by the [esptool](https://github.com/espressif/esptool) bootloader. + esp, + + /// Custom option for non-standard formats. + custom: *Custom, + + /// Returns the standard extension for the resulting binary file. + pub fn getExtension(format: BinaryFormat) []const u8 { + return switch (format) { + .elf => ".elf", + .bin => ".bin", + .hex => ".hex", + .dfu => ".dfu", + .uf2 => ".uf2", + .esp => ".bin", + + .custom => |c| c.extension, + }; + } + + pub const Custom = struct { + /// The standard extension of the format. + extension: []const u8, + + /// A function that will convert a given `elf` file into the custom output format. + /// + /// The `*Custom` format is passed so contextual information can be obtained by using + /// `@fieldParentPtr` to provide access to tooling. + convert: *const fn (*Custom, elf: std.Build.LazyPath) std.Build.LazyPath, + }; + + const Enum = std.meta.Tag(BinaryFormat); + + const Context = struct { + pub fn hash(self: @This(), fmt: BinaryFormat) u32 { + _ = self; + + var hasher = std.hash.XxHash32.init(0x1337_42_21); + + hasher.update(@tagName(fmt)); + + switch (fmt) { + .elf, .bin, .hex, .dfu, .esp => |val| { + if (@TypeOf(val) != void) @compileError("Missing update: Context.hash now requires special care!"); + }, + + .uf2 => |family_id| hasher.update(@tagName(family_id)), + .custom => |custom| hasher.update(std.mem.asBytes(custom)), + } + + return hasher.final(); + } + + pub fn eql(self: @This(), fmt_a: BinaryFormat, fmt_b: BinaryFormat, index: usize) bool { + _ = self; + _ = index; + if (@as(BinaryFormat.Enum, fmt_a) != @as(BinaryFormat.Enum, fmt_b)) + return false; + + return switch (fmt_a) { + .elf, .bin, .hex, .dfu, .esp => |val| { + if (@TypeOf(val) != void) @compileError("Missing update: Context.eql now requires special care!"); + return true; + }, + + .uf2 => |a| (a == fmt_b.uf2), + .custom => |a| (a == fmt_b.custom), + }; + } + }; +}; + +/// The CPU model a target uses. +/// +/// The CPUs usually require special care on how to do interrupts, and getting an entry point. +/// +/// MicroZig officially only supports the CPUs listed here, but other CPUs might be provided +/// via the `custom` field. +pub const CpuModel = union(enum) { + avr5, + cortex_m0, + cortex_m0plus, + cortex_m3, + cortex_m4, + riscv32_imac, + + custom: *const Cpu, + + pub fn getDescriptor(model: CpuModel) *const Cpu { + return switch (@as(std.meta.Tag(CpuModel), model)) { + inline else => |tag| &@field(cpus, @tagName(tag)), + .custom => model.custom, + }; + } +}; + +/// A cpu descriptor. +pub const Cpu = struct { + /// Display name of the CPU. + name: []const u8, + + /// Source file providing startup code and memory initialization routines. + source_file: std.build.LazyPath, + + /// The compiler target we use to compile all the code. + target: std.zig.CrossTarget, +}; + +/// A descriptor for memory regions in a microcontroller. +pub const MemoryRegion = struct { + /// The type of the memory region for generating a proper linker script. + kind: Kind, + offset: u64, + length: u64, + + pub const Kind = union(enum) { + /// This is a (normally) immutable memory region where the code is stored. + flash, + + /// This is a mutable memory region for data storage. + ram, + + /// This is a memory region that maps MMIO devices. + io, + + /// This is a memory region that exists, but is reserved and must not be used. + reserved, + + /// This is a memory region used for internal linking tasks required by the board support package. + private: PrivateRegion, + }; + + pub const PrivateRegion = struct { + /// The name of the memory region. Will not have an automatic numeric counter and must be unique. + name: []const u8, + + /// Is the memory region executable? + executable: bool, + + /// Is the memory region readable? + readable: bool, + + /// Is the memory region writable? + writeable: bool, + }; +}; + +/// Defines a custom microcontroller. +pub const Chip = struct { + /// The display name of the controller. + name: []const u8, + + /// (optional) link to the documentation/vendor page of the controller. + url: ?[]const u8 = null, + + /// The cpu model this controller uses. + cpu: CpuModel, + + /// The provider for register definitions. + register_definition: union(enum) { + /// Use `regz` to create a zig file from a JSON schema. + json: std.Build.LazyPath, + + /// Use `regz` to create a json file from a SVD schema. + svd: std.Build.LazyPath, + + /// Use `regz` to create a zig file from an ATDF schema. + atdf: std.Build.LazyPath, + + /// Use the provided file directly as the chip file. + zig: std.Build.LazyPath, + }, + + /// The memory regions that are present in this chip. + memory_regions: []const MemoryRegion, +}; + +/// Defines a hardware abstraction layer. +pub const HardwareAbstractionLayer = struct { + /// Root source file for this HAL. + source_file: std.Build.LazyPath, +}; + +/// Provides a description of a board. +/// +/// Boards provide additional information to a chip and HAL package. +/// For example, they can list attached peripherials, external crystal frequencies, +/// flash sizes, ... +pub const BoardDefinition = struct { + /// Display name of the board + name: []const u8, + + /// (optional) link to the documentation/vendor page of the board. + url: ?[]const u8 = null, + + /// Provides the root file for the board definition. + source_file: std.Build.LazyPath, +}; + +/// The linker script used to link the firmware. +pub const LinkerScript = union(enum) { + /// Auto-generated linker script derived from the memory regions of the chip. + generated, + + /// Externally defined linker script. + source_file: std.build.LazyPath, +}; + +/// A compilation target for MicroZig. Provides information about the chip, +/// hal, board and so on. +/// +/// This is used instead of `std.zig.CrossTarget` to define a MicroZig Firmware. +pub const Target = struct { + /// The preferred binary format of this MicroZig target. If `null`, the user must + /// explicitly give the `.format` field during a call to `getEmittedBin()` or installation steps. + preferred_format: ?BinaryFormat, + + /// The chip this target uses, + chip: Chip, + + /// Usually, embedded projects are single-threaded and single-core applications. Platforms that + /// support multiple CPUs should set this to `false`. + single_threaded: bool = true, + + /// Determines whether the compiler_rt package is bundled with the application or not. + /// This should always be true except for platforms where compiler_rt cannot be built right now. + bundle_compiler_rt: bool = true, + + /// (optional) Provides a default hardware abstraction layer that is used. + /// If `null`, no `microzig.hal` will be available. + hal: ?HardwareAbstractionLayer = null, + + /// (optional) Provides description of external hardware and connected devices + /// like oscillators and such. + /// + /// This structure isn't used by MicroZig itself, but can be utilized from the HAL + /// if present. + board: ?BoardDefinition = null, + + /// (optional) Provide a custom linker script for the hardware or define a custom generation. + linker_script: LinkerScript = .generated, + + /// (optional) Further configures the created firmware depending on the chip and/or board settings. + /// This can be used to set/change additional properties on the created `*Firmware` object. + configure: ?*const fn (host_build: *std.Build, *Firmware) void = null, + + /// (optional) Post processing step that will patch up and modify the elf file if necessary. + binary_post_process: ?*const fn (host_build: *std.Build, std.Build.LazyPath) std.Build.LazyPath = null, +}; + +/// Options to the `addFirmware` function. +pub const FirmwareOptions = struct { + /// The name of the firmware file. + name: []const u8, + + /// The MicroZig target that the firmware is built for. Either a board or a chip. + target: Target, + + /// The optimization level that should be used. Usually `ReleaseSmall` or `Debug` is a good choice. + /// Also using `std.Build.standardOptimizeOption` is a good idea. + optimize: std.builtin.OptimizeMode, + + /// The root source file for the application. This is your `src/main.zig` file. + source_file: std.Build.LazyPath, + + // Overrides: + + /// If set, overrides the `single_threaded` property of the target. + single_threaded: ?bool = null, + + /// If set, overrides the `bundle_compiler_rt` property of the target. + bundle_compiler_rt: ?bool = null, + + /// If set, overrides the `hal` property of the target. + hal: ?HardwareAbstractionLayer = null, + + /// If set, overrides the `board` property of the target. + board: ?BoardDefinition = null, + + /// If set, overrides the `linker_script` property of the target. + linker_script: ?LinkerScript = null, +}; + +/// Declares a new MicroZig firmware file. +pub fn addFirmware( + /// The MicroZig instance that should be used to create the firmware. + mz: *MicroZig, + /// The instance of the `build.zig` that is calling this function. + host_build: *std.Build, + /// Options that define how the firmware is built. + options: FirmwareOptions, +) *Firmware { + const micro_build = mz.self.builder; + + const chip = &options.target.chip; + const cpu = chip.cpu.getDescriptor(); + const maybe_hal = options.hal orelse options.target.hal; + const maybe_board = options.board orelse options.target.board; + + const linker_script = options.linker_script orelse options.target.linker_script; + + // TODO: let the user override which ram section to use the stack on, + // for now just using the first ram section in the memory region list + const first_ram = blk: { + for (chip.memory_regions) |region| { + if (region.kind == .ram) + break :blk region; + } else @panic("no ram memory region found for setting the end-of-stack address"); + }; + + // On demand, generate chip definitions via regz: + const chip_source = switch (chip.register_definition) { + .json, .atdf, .svd => |file| blk: { + const regz_exe = mz.dependency("regz", .{ .optimize = .ReleaseSafe }).artifact("regz"); + + const regz_gen = host_build.addRunArtifact(regz_exe); + + regz_gen.addArg("--schema"); // Explicitly set schema type, one of: svd, atdf, json + regz_gen.addArg(@tagName(chip.register_definition)); + + regz_gen.addArg("--output_path"); // Write to a file + const zig_file = regz_gen.addOutputFileArg("chip.zig"); + + regz_gen.addFileArg(file); + + break :blk zig_file; + }, + + .zig => |src| src, + }; + + const config = host_build.addOptions(); + config.addOption(bool, "has_hal", (maybe_hal != null)); + config.addOption(bool, "has_board", (maybe_board != null)); + + config.addOption(?[]const u8, "board_name", if (maybe_board) |brd| brd.name else null); + + config.addOption([]const u8, "chip_name", chip.name); + config.addOption([]const u8, "cpu_name", chip.name); + config.addOption(usize, "end_of_stack", first_ram.offset + first_ram.length); + + const fw: *Firmware = host_build.allocator.create(Firmware) catch @panic("out of memory"); + fw.* = Firmware{ + .mz = mz, + .host_build = host_build, + .artifact = host_build.addExecutable(.{ + .name = options.name, + .optimize = options.optimize, + .target = cpu.target, + .linkage = .static, + .root_source_file = .{ .cwd_relative = mz.self.builder.pathFromRoot("src/start.zig") }, + }), + .target = options.target, + .output_files = Firmware.OutputFileMap.init(host_build.allocator), + + .config = config, + + .modules = .{ + .microzig = micro_build.createModule(.{ + .source_file = .{ .cwd_relative = micro_build.pathFromRoot("src/microzig.zig") }, + .dependencies = &.{ + .{ + .name = "config", + .module = micro_build.createModule(.{ .source_file = config.getSource() }), + }, + }, + }), + + .cpu = undefined, + .chip = undefined, + + .board = null, + .hal = null, + + .app = undefined, + }, + }; + errdefer fw.output_files.deinit(); + + fw.modules.chip = micro_build.createModule(.{ + .source_file = chip_source, + .dependencies = &.{ + .{ .name = "microzig", .module = fw.modules.microzig }, + }, + }); + fw.modules.microzig.dependencies.put("chip", fw.modules.chip) catch @panic("out of memory"); + + fw.modules.cpu = micro_build.createModule(.{ + .source_file = cpu.source_file, + .dependencies = &.{ + .{ .name = "microzig", .module = fw.modules.microzig }, + }, + }); + fw.modules.microzig.dependencies.put("cpu", fw.modules.cpu) catch @panic("out of memory"); + + if (maybe_hal) |hal| { + fw.modules.hal = micro_build.createModule(.{ + .source_file = hal.source_file, + .dependencies = &.{ + .{ .name = "microzig", .module = fw.modules.microzig }, + }, + }); + fw.modules.microzig.dependencies.put("hal", fw.modules.hal.?) catch @panic("out of memory"); + } + + if (maybe_board) |brd| { + fw.modules.board = micro_build.createModule(.{ + .source_file = brd.source_file, + .dependencies = &.{ + .{ .name = "microzig", .module = fw.modules.microzig }, + }, + }); + fw.modules.microzig.dependencies.put("board", fw.modules.board.?) catch @panic("out of memory"); + } + + fw.modules.app = host_build.createModule(.{ + .source_file = options.source_file, + .dependencies = &.{ + .{ .name = "microzig", .module = fw.modules.microzig }, + }, + }); + + const umm = mz.dependency("umm-zig", .{}).module("umm"); + fw.modules.microzig.dependencies.put("umm", umm) catch @panic("out of memory"); + + fw.artifact.addModule("app", fw.modules.app); + fw.artifact.addModule("microzig", fw.modules.microzig); + + fw.artifact.strip = false; // we always want debug symbols, stripping brings us no benefit on embedded + fw.artifact.single_threaded = options.single_threaded orelse fw.target.single_threaded; + fw.artifact.bundle_compiler_rt = options.bundle_compiler_rt orelse fw.target.bundle_compiler_rt; + + switch (linker_script) { + .generated => { + fw.artifact.setLinkerScript( + generateLinkerScript(host_build, chip.*) catch @panic("out of memory"), + ); + }, + + .source_file => |source| { + fw.artifact.setLinkerScriptPath(source); + }, + } + + if (options.target.configure) |configure| { + configure(host_build, fw); + } + + return fw; +} + +/// Configuration options for firmware installation. +pub const InstallFirmwareOptions = struct { + /// Overrides the output format for the binary. If not set, the standard preferred file format for the firmware target is used. + format: ?BinaryFormat = null, +}; + +/// Adds a new dependency to the `install` step that will install the `firmware` into the folder `$prefix/firmware`. +pub fn installFirmware( + /// The MicroZig instance that was used to create the firmware. + mz: *MicroZig, + /// The instance of the `build.zig` that should perform installation. + b: *std.Build, + /// The firmware that should be installed. Please make sure that this was created with the same `MicroZig` instance as `mz`. + firmware: *Firmware, + /// Optional configuration of the installation process. Pass `.{}` if you're not sure what to do here. + options: InstallFirmwareOptions, +) void { + std.debug.assert(mz == firmware.mz); + const install_step = addInstallFirmware(mz, b, firmware, options); + b.getInstallStep().dependOn(&install_step.step); +} + +/// Creates a new `std.Build.Step.InstallFile` instance that will install the given firmware to `$prefix/firmware`. +/// +/// **NOTE:** This does not actually install the firmware yet. You have to add the returned step as a dependency to another step. +/// If you want to just install the firmware, use `installFirmware` instead! +pub fn addInstallFirmware( + /// The MicroZig instance that was used to create the firmware. + mz: *MicroZig, + /// The instance of the `build.zig` that should perform installation. + b: *std.Build, + /// The firmware that should be installed. Please make sure that this was created with the same `MicroZig` instance as `mz`. + firmware: *Firmware, + /// Optional configuration of the installation process. Pass `.{}` if you're not sure what to do here. + options: InstallFirmwareOptions, +) *std.Build.Step.InstallFile { + const format = firmware.resolveFormat(options.format); + + const basename = b.fmt("{s}{s}", .{ + firmware.artifact.name, + format.getExtension(), + }); + + _ = mz; + + return b.addInstallFileWithDir(firmware.getEmittedBin(format), .{ .custom = "firmware" }, basename); +} + +/// Declaration of a firmware build. +pub const Firmware = struct { + const OutputFileMap = std.ArrayHashMap(BinaryFormat, std.Build.LazyPath, BinaryFormat.Context, false); + + const Modules = struct { + app: *std.Build.Module, + cpu: *std.Build.Module, + chip: *std.Build.Module, + board: ?*std.Build.Module, + hal: ?*std.Build.Module, + microzig: *std.Build.Module, + }; + + // privates: + mz: *MicroZig, + host_build: *std.Build, + target: Target, + output_files: OutputFileMap, + + // publics: + + /// The artifact that is built by Zig. + artifact: *std.Build.Step.Compile, + + /// The options step that provides `microzig.config`. If you need custom configuration, you can add this here. + config: *std.Build.Step.Options, + + /// Declaration of the MicroZig modules used by this firmware. + modules: Modules, + + /// Path to the emitted elf file, if any. + emitted_elf: ?std.Build.LazyPath = null, + + /// Returns the emitted ELF file for this firmware. This is useful if you need debug information + /// or want to use a debugger like Segger, ST-Link or similar. + /// + /// **NOTE:** This is similar, but not equivalent to `std.Build.Step.Compile.getEmittedBin`. The call on the compile step does + /// not include post processing of the ELF files necessary by certain targets. + pub fn getEmittedElf(firmware: *Firmware) std.Build.LazyPath { + if (firmware.emitted_elf == null) { + const raw_elf = firmware.artifact.getEmittedBin(); + firmware.emitted_elf = if (firmware.target.binary_post_process) |binary_post_process| + binary_post_process(firmware.host_build, raw_elf) + else + raw_elf; + } + return firmware.emitted_elf.?; + } + + /// Returns the emitted binary for this firmware. The file is either in the preferred file format for + /// the target or in `format` if not null. + /// + /// **NOTE:** The file returned here is the same file that will be installed. + pub fn getEmittedBin(firmware: *Firmware, format: ?BinaryFormat) std.Build.LazyPath { + const actual_format = firmware.resolveFormat(format); + + const gop = firmware.output_files.getOrPut(actual_format) catch @panic("out of memory"); + if (!gop.found_existing) { + const elf_file = firmware.getEmittedElf(); + + const basename = firmware.host_build.fmt("{s}{s}", .{ + firmware.artifact.name, + actual_format.getExtension(), + }); + + gop.value_ptr.* = switch (actual_format) { + .elf => elf_file, + + .bin => blk: { + const objcopy = firmware.host_build.addObjCopy(elf_file, .{ + .basename = basename, + .format = .bin, + }); + + break :blk objcopy.getOutput(); + }, + + .hex => blk: { + const objcopy = firmware.host_build.addObjCopy(elf_file, .{ + .basename = basename, + .format = .hex, + }); + + break :blk objcopy.getOutput(); + }, + + .uf2 => |family_id| blk: { + const uf2_exe = firmware.mz.dependency("uf2", .{ .optimize = .ReleaseSafe }).artifact("elf2uf2"); + + const convert = firmware.host_build.addRunArtifact(uf2_exe); + + convert.addArg("--family-id"); + convert.addArg(firmware.host_build.fmt("0x{X:0>4}", .{@intFromEnum(family_id)})); + + convert.addArg("--elf-path"); + convert.addFileArg(elf_file); + + convert.addArg("--output-path"); + break :blk convert.addOutputFileArg(basename); + }, + + .dfu => buildConfigError(firmware.host_build, "DFU is not implemented yet. See https://github.com/ZigEmbeddedGroup/microzig/issues/145 for more details!", .{}), + .esp => buildConfigError(firmware.host_build, "ESP firmware image is not implemented yet. See https://github.com/ZigEmbeddedGroup/microzig/issues/146 for more details!", .{}), + + .custom => |generator| generator.convert(generator, elf_file), + }; + } + return gop.value_ptr.*; + } + + pub const AppDependencyOptions = struct { + depend_on_microzig: bool = false, + }; + + /// Adds a regular dependency to your application. + pub fn addAppDependency(fw: *Firmware, name: []const u8, module: *std.Build.Module, options: AppDependencyOptions) void { + if (options.depend_on_microzig) { + module.dependencies.put("microzig", fw.modules.microzig) catch @panic("OOM"); + } + fw.modules.app.dependencies.put(name, module) catch @panic("OOM"); + } + + pub fn addIncludePath(fw: *Firmware, path: std.Build.LazyPath) void { + fw.artifact.addIncludePath(path); + } + + pub fn addSystemIncludePath(fw: *Firmware, path: std.Build.LazyPath) void { + fw.artifact.addSystemIncludePath(path); + } + + pub fn addCSourceFile(fw: *Firmware, source: std.Build.Step.Compile.CSourceFile) void { + fw.artifact.addCSourceFile(source); + } + + pub fn addOptions(fw: *Firmware, module_name: []const u8, options: *std.Build.OptionsStep) void { + fw.artifact.addOptions(module_name, options); + fw.modules.app.dependencies.put( + module_name, + fw.host_build.createModule(.{ + .source_file = options.getOutput(), + }), + ) catch @panic("OOM"); + } + + pub fn addObjectFile(fw: *Firmware, source: std.Build.LazyPath) void { + fw.artifact.addObjectFile(source); + } + + fn resolveFormat(firmware: *Firmware, format: ?BinaryFormat) BinaryFormat { + if (format) |fmt| return fmt; + + if (firmware.target.preferred_format) |fmt| return fmt; + + buildConfigError(firmware.host_build, "{s} has no preferred output format, please provide one in the `format` option.", .{ + firmware.target.chip.name, + }); + } +}; + +pub const cpus = struct { + pub const avr5 = Cpu{ + .name = "AVR5", + .source_file = .{ .path = build_root ++ "/src/cpus/avr5.zig" }, + .target = std.zig.CrossTarget{ + .cpu_arch = .avr, + .cpu_model = .{ .explicit = &std.Target.avr.cpu.avr5 }, + .os_tag = .freestanding, + .abi = .eabi, + }, + }; + + pub const cortex_m0 = Cpu{ + .name = "ARM Cortex-M0", + .source_file = .{ .path = build_root ++ "/src/cpus/cortex-m.zig" }, + .target = std.zig.CrossTarget{ + .cpu_arch = .thumb, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0 }, + .os_tag = .freestanding, + .abi = .eabi, + }, + }; + + pub const cortex_m0plus = Cpu{ + .name = "ARM Cortex-M0+", + .source_file = .{ .path = build_root ++ "/src/cpus/cortex-m.zig" }, + .target = std.zig.CrossTarget{ + .cpu_arch = .thumb, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0plus }, + .os_tag = .freestanding, + .abi = .eabi, + }, + }; + + pub const cortex_m3 = Cpu{ + .name = "ARM Cortex-M3", + .source_file = .{ .path = build_root ++ "/src/cpus/cortex-m.zig" }, + .target = std.zig.CrossTarget{ + .cpu_arch = .thumb, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m3 }, + .os_tag = .freestanding, + .abi = .eabi, + }, + }; + + pub const cortex_m4 = Cpu{ + .name = "ARM Cortex-M4", + .source_file = .{ .path = build_root ++ "/src/cpus/cortex-m.zig" }, + .target = std.zig.CrossTarget{ + .cpu_arch = .thumb, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m4 }, + .os_tag = .freestanding, + .abi = .eabi, + }, + }; + + pub const riscv32_imac = Cpu{ + .name = "RISC-V 32-bit", + .source_file = .{ .path = build_root ++ "/src/cpus/riscv32.zig" }, + .target = std.zig.CrossTarget{ + .cpu_arch = .riscv32, + .cpu_model = .{ .explicit = &std.Target.riscv.cpu.sifive_e21 }, + .os_tag = .freestanding, + .abi = .none, + }, + }; +}; + +fn buildConfigError(b: *std.Build, comptime fmt: []const u8, args: anytype) noreturn { + const msg = b.fmt(fmt, args); + @panic(msg); +} + +fn dependency(mz: *MicroZig, name: []const u8, args: anytype) *std.Build.Dependency { + return mz.self.builder.dependency(name, args); +} + +fn generateLinkerScript(b: *std.Build, chip: Chip) !std.Build.LazyPath { + const cpu = chip.cpu.getDescriptor(); + + var contents = std.ArrayList(u8).init(b.allocator); + const writer = contents.writer(); + try writer.print( + \\/* + \\ * This file was auto-generated by microzig + \\ * + \\ * Target CPU: {[cpu]s} + \\ * Target Chip: {[chip]s} + \\ */ + \\ + // This is not the "true" entry point, but there's no such thing on embedded platforms + // anyways. This is the logical entrypoint that should be invoked when + // stack, .data and .bss are set up and the CPU is ready to be used. + \\ENTRY(microzig_main); + \\ + \\ + , .{ + .cpu = cpu.name, + .chip = chip.name, + }); + + try writer.writeAll("MEMORY\n{\n"); + { + var counters = [4]usize{ 0, 0, 0, 0 }; + for (chip.memory_regions) |region| { + // flash (rx!w) : ORIGIN = 0x00000000, LENGTH = 512k + + switch (region.kind) { + .flash => { + try writer.print(" flash{d} (rx!w)", .{counters[0]}); + counters[0] += 1; + }, + + .ram => { + try writer.print(" ram{d} (rw!x)", .{counters[1]}); + counters[1] += 1; + }, + + .io => { + try writer.print(" io{d} (rw!x)", .{counters[2]}); + counters[2] += 1; + }, + + .reserved => { + try writer.print(" reserved{d} (rw!x)", .{counters[3]}); + counters[3] += 1; + }, + + .private => |custom| { + try writer.print(" {s} (", .{custom.name}); + if (custom.readable) try writer.writeAll("r"); + if (custom.writeable) try writer.writeAll("w"); + if (custom.executable) try writer.writeAll("x"); + + if (!custom.readable or !custom.writeable or !custom.executable) { + try writer.writeAll("!"); + if (!custom.readable) try writer.writeAll("r"); + if (!custom.writeable) try writer.writeAll("w"); + if (!custom.executable) try writer.writeAll("x"); + } + try writer.writeAll(")"); + }, + } + try writer.print(" : ORIGIN = 0x{X:0>8}, LENGTH = 0x{X:0>8}\n", .{ region.offset, region.length }); + } + } + + try writer.writeAll("}\n\nSECTIONS\n{\n"); + { + try writer.writeAll( + \\ .text : + \\ { + \\ KEEP(*(microzig_flash_start)) + \\ *(.text*) + \\ } > flash0 + \\ + \\ + ); + + switch (cpu.target.getCpuArch()) { + .arm, .thumb => try writer.writeAll( + \\ .ARM.exidx : { + \\ *(.ARM.exidx* .gnu.linkonce.armexidx.*) + \\ } >flash0 + \\ + \\ + ), + else => {}, + } + + try writer.writeAll( + \\ .data : + \\ { + \\ microzig_data_start = .; + \\ *(.rodata*) + \\ *(.data*) + \\ microzig_data_end = .; + \\ } > ram0 AT> flash0 + \\ + \\ .bss (NOLOAD) : + \\ { + \\ microzig_bss_start = .; + \\ *(.bss*) + \\ microzig_bss_end = .; + \\ } > ram0 + \\ + \\ microzig_data_load_start = LOADADDR(.data); + \\ + ); + } + try writer.writeAll("}\n"); + + // TODO: Assert that the flash can actually hold all data! + // try writer.writeAll( + // \\ + // \\ ASSERT( (SIZEOF(.text) + SIZEOF(.data) > LENGTH(flash0)), "Error: .text + .data is too large for flash!" ); + // \\ + // ); + + const write = b.addWriteFiles(); + + return write.add("linker.ld", contents.items); +} diff --git a/core/build.zig.zon b/core/build.zig.zon new file mode 100644 index 0000000..437acb5 --- /dev/null +++ b/core/build.zig.zon @@ -0,0 +1,29 @@ +.{ + .name = "microzig", + .version = "0.1.0", + .paths = .{ + "build.zig", + "build.zig.zon", + "design", + "docs", + "LICENSE", + "README.adoc", + "src", + "test", + "thoughts.md", + }, + .dependencies = .{ + .uf2 = .{ + .url = "https://github.com/ZigEmbeddedGroup/uf2/archive/8037b439ccbac862471392b25e94a8995d784e2c.tar.gz", + .hash = "1220cc66563fc1ecefca7990968441dc9d4db717884ffa9a2de657f60ed4bb74a70a", + }, + .regz = .{ + .url = "https://github.com/ZigEmbeddedGroup/regz/archive/d66ffd56f51fc46c071412141b5d0c74dc83c310.tar.gz", + .hash = "122002c5f2e31c11373ede6e8a8dd9a61aabd60d38df667ec33b5f994d1f0b503823", + }, + .@"umm-zig" = .{ + .url = "https://github.com/ZigEmbeddedGroup/umm-zig/archive/99d815adfbc5cc4ad385dd765a6192f85e54179f.tar.gz", + .hash = "12207ef7375ea45e97f4fba9c5dfa74d022902893c4dbf1a0076726b7ec39a02ea3f", + }, + }, +} diff --git a/core/design/logo-text-auto.svg b/core/design/logo-text-auto.svg new file mode 100644 index 0000000..ab40494 --- /dev/null +++ b/core/design/logo-text-auto.svg @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/core/design/logo-text-brightmode.svg b/core/design/logo-text-brightmode.svg new file mode 100644 index 0000000..c048b67 --- /dev/null +++ b/core/design/logo-text-brightmode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/design/logo-text-darkmode.svg b/core/design/logo-text-darkmode.svg new file mode 100644 index 0000000..f744c99 --- /dev/null +++ b/core/design/logo-text-darkmode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/design/logo-text-inkscape.svg b/core/design/logo-text-inkscape.svg new file mode 100644 index 0000000..210065c --- /dev/null +++ b/core/design/logo-text-inkscape.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/design/logo.svg b/core/design/logo.svg new file mode 100644 index 0000000..c48f717 --- /dev/null +++ b/core/design/logo.svg @@ -0,0 +1 @@ + diff --git a/core/design/social-media-preview.png b/core/design/social-media-preview.png new file mode 100644 index 0000000..7bc20d2 Binary files /dev/null and b/core/design/social-media-preview.png differ diff --git a/core/design/social-media-preview.xcf b/core/design/social-media-preview.xcf new file mode 100644 index 0000000..6346ef8 Binary files /dev/null and b/core/design/social-media-preview.xcf differ diff --git a/core/docs/design.adoc b/core/docs/design.adoc new file mode 100644 index 0000000..075279a --- /dev/null +++ b/core/docs/design.adoc @@ -0,0 +1,103 @@ += MicroZig Design +:imagesdir: images +:toc: macro + +toc::[] + +== Dependency Tree + +The build portion of MicroZig sets up a dependency graph like the following. + +image::deps.svg[] + +Your application lives in `app`; that's where `main()` resides. `root` contains the entry point and will set up [zero-initialized data] and [uninitialized data]. This is all encapsulated in an `EmbeddedExecutable` object. It has methods to add dependencies acquired from the package manager. + +The `microzig` module has different namespaces, some are static, but the nodes you see in the diagram above are switched out according to your configured hardware. + +== Configurable Modules under `microzig` + +The configurable modules, with the exception of `config`, are able to import `microzig`. This exists so that one module may access another through `microzig`. This allows us to have patterns like the `hal` grabbing the frequency of an external crystal oscillator from `board`. Information stays where it's relevant. Circular dependencies of declarations will result in a compile error. + +=== `cpu` + +This module models your specific CPU and is important for initializing memory. Generally, you shouldn't need to define this yourself, it's likely that MicroZig will have the definition for you. + +Further research is needed for SOCs with multiple, heterogeneous CPUs. Likely it means patching together multiple `EmbeddedExecutable`s. + +=== `chip` + +This module is intended for generated code from https://github.com/ZigEmbeddedGroup/regz[Regz]. You can handwrite this code if you like, but needs to be structured as follows: + +[source,zig] +---- +pub const types = struct { + // type definitions for peripherals here +}; + +pub const devices = struct { + pub const chip_name = struct { + // peripherals and interrupt table here ... + }; +}; +---- + +This code generation has a `devices` namespace where your specific hardware will reside. When defining a `Chip`, which is ultimately used in the creation of an `EmbeddedExecutable`, the name must exactly match the name under the `devices` namespace. It's okay if the name has whitespace, for that we can use `@""` notation. + +Let's say we had a device with the name `STM32F103`. We'd define our chip as: + +[source,zig] +---- +pub const stm32f103 = microzig.Chip{ + .name = "STM32F103", + .cpu = microzig.cpus.cortex_m3, + .source = .{ + .path = "path/to/generated.zig", + }, + .json_register_schema = .{ + .path = "path/to/generated.json", + }, + .hal = .{ + .path = "path/to/hal.zig", + }, + .memory_regions = &.{ + MemoryRegion{ .offset = 0x08000000, .length = 64 * 1024, .kind = .flash }, + MemoryRegion{ .offset = 0x20000000, .length = 20 * 1024, .kind = .ram }, + }, +}; +---- + +As discussed, the `name` must match a namespace under `devices` in the `chip` source. + +TODO + +When making a package that defines chips for others, see xref:hardware_support_packages.adoc[Hardware Support Packages] and xref:tricks.adoc#packaging-and-paths[Packaging and Paths]. + +=== `hal` + +This module contains hand-written code for interacting with the chip. + +TODO + +=== `board` + +TODO + +=== `config` + +TODO + +== Static Namespaces under `microzig` + +TODO + +== Linkerscript Generation + +TODO + +== JSON register schema + +TODO + +== Interrupts + +TODO diff --git a/core/docs/hardware_support_packages.adoc b/core/docs/hardware_support_packages.adoc new file mode 100644 index 0000000..595f082 --- /dev/null +++ b/core/docs/hardware_support_packages.adoc @@ -0,0 +1,7 @@ += Hardware Support Packages +:imagesdir: images +:toc: macro + +toc::[] + +This document goes over the _suggested_ way to structure a hardware support package. At the end of the day, MicroZig is designed for flexibility, we do not require all "supported" hardware be defined in one monorepo. Instead, we make it so that you can define your hardware locally if that best suits your project. diff --git a/core/docs/images/deps.dot b/core/docs/images/deps.dot new file mode 100644 index 0000000..1577418 --- /dev/null +++ b/core/docs/images/deps.dot @@ -0,0 +1,23 @@ +digraph { + root -> app + root -> microzig + + app -> microzig + app -> A + app -> B + + subgraph cluster_0 { + style="dotted" + graph [labelloc=b] + A -> C + B -> C + B -> D + label = "Application Dependencies"; + } + + microzig -> config + microzig -> cpu [dir="both"] + microzig -> chip [dir="both"] + microzig -> board [dir="both", style="dashed"] + microzig -> hal [dir="both", style="dashed"] +} diff --git a/core/docs/images/deps.svg b/core/docs/images/deps.svg new file mode 100644 index 0000000..20fd6e1 --- /dev/null +++ b/core/docs/images/deps.svg @@ -0,0 +1,171 @@ + + + + + + + + +cluster_0 + +Application Dependencies + + + +root + +root + + + +app + +app + + + +root->app + + + + + +microzig + +microzig + + + +root->microzig + + + + + +app->microzig + + + + + +A + +A + + + +app->A + + + + + +B + +B + + + +app->B + + + + + +config + +config + + + +microzig->config + + + + + +cpu + +cpu + + + +microzig->cpu + + + + + + +chip + +chip + + + +microzig->chip + + + + + + +board + +board + + + +microzig->board + + + + + + +hal + +hal + + + +microzig->hal + + + + + + +C + +C + + + +A->C + + + + + +B->C + + + + + +D + +D + + + +B->D + + + + + diff --git a/core/docs/tricks.adoc b/core/docs/tricks.adoc new file mode 100644 index 0000000..6eda21f --- /dev/null +++ b/core/docs/tricks.adoc @@ -0,0 +1,3 @@ += Tips and Tricks + +This document has some `build.zig` tricks for other pages to reference. diff --git a/core/src/core.zig b/core/src/core.zig new file mode 100644 index 0000000..232f0f7 --- /dev/null +++ b/core/src/core.zig @@ -0,0 +1,9 @@ +pub const experimental = @import("core/experimental.zig"); +pub const heap = @import("core/heap.zig"); +/// USB data types and helper functions +pub const usb = @import("core/usb.zig"); + +test "core tests" { + _ = usb; + _ = heap; +} diff --git a/core/src/core/experimental.zig b/core/src/core/experimental.zig new file mode 100644 index 0000000..e0058f8 --- /dev/null +++ b/core/src/core/experimental.zig @@ -0,0 +1,12 @@ +//! These are experimental generic interfaces. We want to see more use and +//! discussion of them before committing them to microzig's "official" API. +//! +//! They are bound to have breaking changes in the future, or even disappear, +//! so use at your own risk. +pub const clock = @import("experimental/clock.zig"); +pub const debug = @import("experimental/debug.zig"); +pub const gpio = @import("experimental/gpio.zig"); +pub const i2c = @import("experimental/i2c.zig"); +pub const Pin = @import("experimental/pin.zig").Pin; +pub const spi = @import("experimental/spi.zig"); +pub const uart = @import("experimental/uart.zig"); diff --git a/core/src/core/experimental/clock.zig b/core/src/core/experimental/clock.zig new file mode 100644 index 0000000..8c51b1f --- /dev/null +++ b/core/src/core/experimental/clock.zig @@ -0,0 +1,68 @@ +const std = @import("std"); +const hal = @import("hal"); +const app = struct {}; // workaround for error: no package named 'app' available within package 'root.microzig' +const board = @import("board"); +const cpu = @import("cpu"); +const config = @import("config"); + +/// An enumeration of clock sources. +pub const Source = enum { + none, + application, + board, + hal, + cpu, +}; + +/// A struct containing the frequency in hertz for each clock domain +pub const Clocks = std.enums.EnumFieldStruct(hal.clock.Domain, u32, null); + +/// Is `true` when microzig has a clock frequency available. +/// Clock can be provided by several clock sources +pub const has_clock = (clock_source_type != void); + +/// Returns `true` when the frequency can change at runtime. +pub const is_dynamic = has_clock and !@typeInfo(@TypeOf(&@field(clock_source_type, freq_decl_name))).Pointer.is_const; + +/// Contains the source which provides microzig with clock information. +pub const source: Source = switch (clock_source_type) { + app => .application, + board => .board, + hal => .hal, + cpu => .cpu, + no_clock_source_type => .none, + else => unreachable, +}; + +/// Ensures that microzig has a clock available. This will @compileError when no clock is available, otherwise, it will be a no-op. +pub fn ensure() void { + if (!has_clock) + @compileError("microzig requires the clock frequency to perform this operation. Please export a const or var clock_frequencies from your root file that contains the clock frequency for all chip clock domains in hertz!"); +} + +/// Returns the Clocks struct, with all clock domains frequencies in hertz. +pub inline fn get() Clocks { + ensure(); + return @field(clock_source_type, freq_decl_name); +} + +const freq_decl_name = "clock_frequencies"; + +const no_clock_source_type = opaque {}; +const clock_source_type = if (@hasDecl(app, freq_decl_name)) + app +else if (config.has_board and @hasDecl(board, freq_decl_name)) + board +else if (@hasDecl(hal, freq_decl_name)) + hal +else if (@hasDecl(cpu, freq_decl_name)) + cpu +else + no_clock_source_type; + +comptime { + if (source != .application) { + if (is_dynamic) + @compileError("clock source " ++ @tagName(source) ++ " is not allowed to be dynamic. Only the application (root file) is allowed to provide a dynamic clock frequency!"); + } +} diff --git a/core/src/core/experimental/debug.zig b/core/src/core/experimental/debug.zig new file mode 100644 index 0000000..767f8c3 --- /dev/null +++ b/core/src/core/experimental/debug.zig @@ -0,0 +1,41 @@ +const std = @import("std"); +const config = @import("config"); +const board = @import("board"); + +pub fn busy_sleep(comptime limit: comptime_int) void { + if (limit <= 0) @compileError("limit must be non-negative!"); + + comptime var bits = 0; + inline while ((1 << bits) <= limit) { + bits += 1; + } + + const I = std.meta.Int(.unsigned, bits); + + var i: I = 0; + while (i < limit) : (i += 1) { + @import("std").mem.doNotOptimizeAway(i); + } +} + +const DebugErr = error{}; +fn writer_write(ctx: void, string: []const u8) DebugErr!usize { + _ = ctx; + write(string); + return string.len; +} + +const DebugWriter = std.io.Writer(void, DebugErr, writer_write); + +pub fn write(string: []const u8) void { + if (!config.has_board) + return; + if (!@hasDecl(board, "debug_write")) + return; + + board.debug_write(string); +} + +pub fn writer() DebugWriter { + return DebugWriter{ .context = {} }; +} diff --git a/core/src/core/experimental/gpio.zig b/core/src/core/experimental/gpio.zig new file mode 100644 index 0000000..bddaf10 --- /dev/null +++ b/core/src/core/experimental/gpio.zig @@ -0,0 +1,164 @@ +const std = @import("std"); +const micro = @import("microzig"); +const hal = @import("hal"); + +pub const Mode = enum { + input, + output, + input_output, + open_drain, + generic, + alternate_function, +}; + +pub const State = enum(u1) { + low = 0, + high = 1, + + pub fn value(self: State) u1 { + return @intFromEnum(self); + } +}; + +pub const Drive = enum(u1) { + disabled = 0, + enabled = 1, +}; + +pub const Direction = enum(u1) { + input = 0, + output = 1, +}; + +pub fn Gpio(comptime pin: type, comptime config: anytype) type { + const mode = @as(Mode, config.mode); + const Generic = struct { + // all pins: + fn init() void { + switch (mode) { + .input, .generic, .input_output => { + set_direction(.input, undefined); + }, + .output => { + if (comptime !@hasField(@TypeOf(config), "initial_state")) + @compileError("An output pin requires initial_state to be either .high or .low"); + set_direction(.output, switch (config.initial_state) { + .low => State.low, + .high => State.high, + else => @compileError("An output pin requires initial_state to be either .high or .low"), + }); + }, + .open_drain => { + if (comptime !@hasField(@TypeOf(config), "initial_state")) + @compileError("An open_drain pin requires initial_state to be either .floating or .driven"); + set_direction(.input, undefined); + set_drive(switch (config.initial_state) { + .floating => Drive.disabled, + .driven => Drive.enabled, + else => @compileError("An open_drain pin requires initial_state to be either .floating or .driven"), + }); + }, + .alternate_function => { + if (comptime @hasDecl(hal.gpio, "AlternateFunction")) { + const alternate_function = @as(hal.gpio.AlternateFunction, config.alternate_function); + set_alternate_function(alternate_function); + } else { + @compileError("Alternate Function not supported yet"); + } + }, + } + } + + fn read() State { + return hal.gpio.read(pin.source_pin); + } + + // outputs: + fn write(state: State) void { + hal.gpio.write(pin.source_pin, state); + } + + fn set_to_high() void { + write(.high); + } + fn set_to_low() void { + write(.low); + } + fn toggle() void { + if (comptime @hasDecl(hal.gpio, "toggle")) { + hal.gpio.toggle(pin.source_pin); + } else { + write(switch (read()) { + .low => State.high, + .high => State.low, + }); + } + } + + // bi-di: + fn set_direction(dir: Direction, output_state: State) void { + switch (dir) { + .output => { + hal.gpio.setOutput(pin.source_pin); + write(output_state); + }, + .input => hal.gpio.setInput(pin.source_pin), + } + } + fn get_direction() Direction { + if (hal.gpio.isOutput(pin.source_pin)) { + return .output; + } else { + return .input; + } + } + + // open drain + fn set_drive(drive: Drive) void { + _ = drive; + @compileError("open drain not implemented yet!"); + } + fn get_drive() Drive { + @compileError("open drain not implemented yet!"); + } + + // alternate function + fn set_alternate_function(af: hal.gpio.AlternateFunction) void { + hal.gpio.setAlternateFunction(pin.source_pin, af); + } + }; + // return only a subset of Generic for the requested pin. + switch (mode) { + .input => return struct { + pub const init = Generic.init; + pub const read = Generic.read; + }, + .output => return struct { + pub const init = Generic.init; + pub const read = Generic.read; + pub const write = Generic.write; + pub const setToHigh = Generic.setToHigh; + pub const setToLow = Generic.setToLow; + pub const toggle = Generic.toggle; + }, + .input_output => return struct { + pub const init = Generic.init; + pub const read = Generic.read; + pub const write = Generic.write; + pub const setToHigh = Generic.setToHigh; + pub const setToLow = Generic.setToLow; + pub const setDirection = Generic.setDirection; + pub const getDirection = Generic.getDirection; + }, + .open_drain => return struct { + pub const init = Generic.init; + pub const read = Generic.read; + pub const setDrive = Generic.setDrive; + pub const getDrive = Generic.getDrive; + }, + .alternate_function => return struct { + pub const init = Generic.init; + }, + .generic => Generic, + } +} diff --git a/core/src/core/experimental/i2c.zig b/core/src/core/experimental/i2c.zig new file mode 100644 index 0000000..52fbaa3 --- /dev/null +++ b/core/src/core/experimental/i2c.zig @@ -0,0 +1,167 @@ +const std = @import("std"); +const hal = @import("hal"); +const cfg = @import("config"); + +pub fn I2CController(comptime index: usize, comptime pins: Pins) type { + const SystemI2CController = hal.I2CController(index, pins); + + const I2CDevice = struct { + const Device = @This(); + + internal: SystemI2CController, + address: u7, + + const Direction = enum { read, write }; + fn Transfer(comptime direction: Direction) type { + return switch (direction) { + .read => struct { + const Self = @This(); + + state: SystemI2CController.ReadState, + + pub const Reader = std.io.Reader(*Self, ReadError, read_some); + + /// NOTE that some platforms, notably most (all?) STM32 microcontrollers, + /// allow only a single read call per transfer. + pub fn reader(self: *Self) Reader { + return Reader{ .context = self }; + } + + fn read_some(self: *Self, buffer: []u8) ReadError!usize { + try self.state.read_no_eof(buffer); + return buffer.len; + } + + /// STOP the transfer, invalidating this object. + pub fn stop(self: *Self) !void { + try self.state.stop(); + } + + /// RESTART to a new transfer, invalidating this object. + /// Note that some platforms set the repeated START condition + /// on the first read or write call. + pub fn restart_transfer(self: *Self, comptime new_direction: Direction) !Transfer(new_direction) { + return Transfer(direction){ .state = try self.state.restart_transfer(new_direction) }; + } + }, + .write => struct { + const Self = @This(); + + state: SystemI2CController.WriteState, + + pub const Writer = std.io.Writer(*Self, WriteError, write_some); + + /// NOTE that some platforms, notably most (all?) STM32 microcontrollers, + /// will not immediately write all bytes, but postpone that + /// to the next write call, or to the stop() call. + pub fn writer(self: *Self) Writer { + return Writer{ .context = self }; + } + + fn write_some(self: *Self, buffer: []const u8) WriteError!usize { + try self.state.write_all(buffer); + return buffer.len; + } + + /// STOP the transfer, invalidating this object. + pub fn stop(self: *Self) !void { + try self.state.stop(); + } + + /// RESTART to a new transfer, invalidating this object. + /// Note that some platforms set the repeated START condition + /// on the first read or write call. + pub fn restart_transfer(self: *Self, comptime new_direction: Direction) !Transfer(new_direction) { + return switch (new_direction) { + .read => Transfer(new_direction){ .state = try self.state.restart_read() }, + .write => Transfer(new_direction){ .state = try self.state.restart_write() }, + }; + } + }, + }; + } + + /// START a new transfer. + /// Note that some platforms set the START condition + /// on the first read or write call. + pub fn start_transfer(self: Device, comptime direction: Direction) !Transfer(direction) { + return switch (direction) { + .read => Transfer(direction){ .state = try SystemI2CController.ReadState.start(self.address) }, + .write => Transfer(direction){ .state = try SystemI2CController.WriteState.start(self.address) }, + }; + } + + /// Shorthand for 'register-based' devices + pub fn write_register(self: Device, register_address: u8, byte: u8) ReadError!void { + try self.write_registers(register_address, &.{byte}); + } + + /// Shorthand for 'register-based' devices + pub fn write_registers(self: Device, register_address: u8, buffer: []const u8) ReadError!void { + var wt = try self.start_transfer(.write); + defer wt.stop() catch {}; + try wt.writer().writeByte(register_address); + try wt.writer().writeAll(buffer); + } + + /// Shorthand for 'register-based' devices + pub fn read_register(self: Device, register_address: u8) ReadError!u8 { + var buffer: [1]u8 = undefined; + try self.read_registers(register_address, &buffer); + return buffer[0]; + } + + /// Shorthand for 'register-based' devices + pub fn read_registers(self: Device, register_address: u8, buffer: []u8) ReadError!void { + var rt = write_and_restart: { + var wt = try self.start_transfer(.write); + errdefer wt.stop() catch {}; + try wt.writer().writeByte(1 << 7 | register_address); // MSB == 'keep sending until I STOP' + break :write_and_restart try wt.restart_transfer(.read); + }; + defer rt.stop() catch {}; + try rt.reader().readNoEof(buffer); + } + }; + + return struct { + const Self = @This(); + + internal: SystemI2CController, + variable_so_struct_is_not_of_size_zero_and_mcu_hangs_when_returning_struct_on_avr: if (std.mem.eql(u8, cfg.chip_name, "ATmega328P")) u8 else void = undefined, + + pub fn init(config: Config) InitError!Self { + return Self{ + .internal = try SystemI2CController.init(config), + }; + } + + pub fn device(self: Self, address: u7) I2CDevice { + return I2CDevice{ .internal = self.internal, .address = address }; + } + }; +} + +/// The pin configuration. This is used to optionally configure specific pins to be used with the chosen I2C. +/// This makes sense only with microcontrollers supporting multiple pins for an I2C peripheral. +pub const Pins = struct { + scl: ?type = null, + sda: ?type = null, +}; + +/// An I2C configuration. +pub const Config = struct { + /// The target speed in bit/s. Note that the actual speed can differ from this, due to prescaler rounding. + target_speed: u32, +}; + +pub const InitError = error{ + InvalidBusFrequency, + InvalidSpeed, +}; +pub const WriteError = error{}; +pub const ReadError = error{ + EndOfStream, + TooFewBytesReceived, + TooManyBytesReceived, +}; diff --git a/core/src/core/experimental/pin.zig b/core/src/core/experimental/pin.zig new file mode 100644 index 0000000..42e8492 --- /dev/null +++ b/core/src/core/experimental/pin.zig @@ -0,0 +1,47 @@ +const std = @import("std"); +const config = @import("config"); +const chip = @import("chip"); +const board = @import("board"); +const hal = @import("hal"); + +/// Returns a type that will manage the Pin defined by `spec`. +/// Spec is either the pin as named in the datasheet of the chip +/// or, if a board is present, as named on the board. +/// +/// When a name conflict happens, pin names can be prefixed with `board:` +/// to force-select a pin from the board and with `chip:` to force-select +/// the pin from the chip. +pub fn Pin(comptime spec: []const u8) type { + // TODO: Depened on board and chip here + + const board_namespace = "board:"; + const chip_namespace = "chip:"; + + // Pins can be namespaced with "board:" for board and "chip:" for chip + // These namespaces are not passed to hal.parse_pin() + const pin = if (std.mem.startsWith(u8, spec, board_namespace)) + hal.parse_pin(@field(board.pin_map, spec[board_namespace.len..])) + else if (std.mem.startsWith(u8, spec, chip_namespace)) + hal.parse_pin(spec[chip_namespace.len..]) + else if (config.has_board and @hasField(@TypeOf(board.pin_map), spec)) + hal.parse_pin(@field(board.pin_map, spec)) + else + hal.parse_pin(spec); + + return struct { + pub const name = if (std.mem.startsWith(u8, spec, board_namespace)) + // Remove the board: prefix + spec[board_namespace.len..] + else if (std.mem.startsWith(u8, spec, chip_namespace)) + // Remove the chip: prefix + spec[chip_namespace.len..] + else + spec; + + pub const source_pin = pin; + + pub fn route(target: pin.Targets) void { + hal.route_pin(source_pin, target); + } + }; +} diff --git a/core/src/core/experimental/spi.zig b/core/src/core/experimental/spi.zig new file mode 100644 index 0000000..7db6ab5 --- /dev/null +++ b/core/src/core/experimental/spi.zig @@ -0,0 +1,146 @@ +const std = @import("std"); +const hal = @import("hal"); +const clock = @import("clock.zig"); // Is there a different/better way? + +/// The SPI bus with the given environment-specific number. +/// Only 'master' mode is supported currently. +pub fn SpiBus(comptime index: usize) type { + const SystemSpi = hal.SpiBus(index); + + return struct { + /// A SPI 'slave' device, selected via the given CS pin. + /// (Default is CS=low to select.) + pub fn SpiDevice(comptime cs_pin: type, comptime config: DeviceConfig) type { + return struct { + const SelfSpiDevice = @This(); + + internal: SystemSpi, + + /// A 'transfer' is defined as a sequence of reads/writes that require + /// the SPI device to be continuously enabled via its 'chip select' (CS) line. + const Transfer = struct { + const SelfTransfer = @This(); + + device: SelfSpiDevice, + + fn transceive_byte(self: *SelfTransfer, write_byte: u8, read_pointer: *u8) !void { + try self.device.internal.transceive_byte(write_byte, read_pointer); + } + + pub const Writer = std.io.Writer(*SelfTransfer, WriteError, write_some); + + /// Return a standard Writer (which ignores the bytes read). + pub fn writer(self: *SelfTransfer) Writer { + return Writer{ .context = self }; + } + + fn write_some(self: *SelfTransfer, buffer: []const u8) WriteError!usize { + try self.device.internal.write_all(buffer); + return buffer.len; + } + + pub const Reader = std.io.Reader(*SelfTransfer, ReadError, read_some); + + /// Return a standard Reader (which writes arbitrary bytes). + pub fn reader(self: *SelfTransfer) Reader { + return Reader{ .context = self }; + } + + fn read_some(self: *SelfTransfer, buffer: []u8) ReadError!usize { + try self.device.internal.read_into(buffer); + return buffer.len; + } + + /// end the current transfer, releasing via the CS pin + pub fn end(self: *SelfTransfer) void { + self.device.internal.end_transfer(cs_pin, config); + } + }; + + /// start a new transfer, selecting using the CS pin + pub fn begin_transfer(self: SelfSpiDevice) !Transfer { + self.internal.switch_to_device(cs_pin, config); + self.internal.begin_transfer(cs_pin, config); + return Transfer{ .device = self }; + } + + pub fn transceive(self: SelfSpiDevice, write_buffer: []const u8, read_buffer: []u8) !void { + std.debug.assert(write_buffer.len == read_buffer.len); + var transfer = try self.begin_transfer(); + defer transfer.end(); + for (write_buffer, 0..) |_, i| { + try transfer.transceive_byte(write_buffer[i], &read_buffer[i]); + } + } + + /// Shorthand for 'register-based' devices + pub fn write_register(self: SelfSpiDevice, register_address: u8, byte: u8) ReadError!void { + try self.write_registers(register_address, &.{byte}); + } + + /// Shorthand for 'register-based' devices + pub fn write_registers(self: SelfSpiDevice, register_address: u8, buffer: []const u8) ReadError!void { + var transfer = try self.begin_transfer(); + defer transfer.end(); + // write auto-increment, starting at given register + try transfer.writer().writeByte(0b01_000000 | register_address); + try transfer.writer().writeAll(buffer); + } + + /// Shorthand for 'register-based' devices + pub fn read_register(self: SelfSpiDevice, register_address: u8) ReadError!u8 { + var buffer: [1]u8 = undefined; + try self.read_registers(register_address, &buffer); + return buffer[0]; + } + + /// Shorthand for 'register-based' devices + pub fn read_registers(self: SelfSpiDevice, register_address: u8, buffer: []u8) ReadError!void { + var transfer = try self.begin_transfer(); + defer transfer.end(); + // read auto-increment, starting at given register + try transfer.writer().writeByte(0b11_000000 | register_address); + try transfer.reader().readNoEof(buffer); + } + }; + } + + const SelfSpiBus = @This(); + + internal: SystemSpi, + + /// Initialize this SPI bus and return a handle to it. + pub fn init(config: BusConfig) InitError!SelfSpiBus { + clock.ensure(); // TODO: Wat? + return SelfSpiBus{ + .internal = try SystemSpi.init(config), + }; + } + + /// Create (a descriptor for) a device on this SPI bus. + pub fn device(self: SelfSpiBus, comptime cs_pin: type, config: DeviceConfig) SpiDevice(cs_pin, config) { + return SpiDevice(cs_pin, config){ .internal = self.internal }; + } + }; +} + +/// A SPI bus configuration. +/// (There are no bus configuration options yet.) +pub const BusConfig = struct { + // Later: add common options +}; + +/// A SPI device configuration (excluding the CS pin). +/// (There are no device configuration options yet.) +pub const DeviceConfig = struct { + // TODO: add common options, like clock polarity and phase, and CS polarity +}; + +pub const InitError = error{ +// TODO: add common options +}; + +pub const WriteError = error{}; +pub const ReadError = error{ + EndOfStream, +}; diff --git a/core/src/core/experimental/uart.zig b/core/src/core/experimental/uart.zig new file mode 100644 index 0000000..e64dd5e --- /dev/null +++ b/core/src/core/experimental/uart.zig @@ -0,0 +1,107 @@ +const std = @import("std"); +const hal = @import("hal"); +const clock = @import("clock.zig"); + +pub fn Uart(comptime index: usize, comptime pins: Pins) type { + const SystemUart = hal.Uart(index, pins); + return struct { + const Self = @This(); + + internal: SystemUart, + + /// Initializes the UART with the given config and returns a handle to the uart. + pub fn init(config: Config) InitError!Self { + clock.ensure(); + return Self{ + .internal = try SystemUart.init(config), + }; + } + + /// If the UART is already initialized, try to return a handle to it, + /// else initialize with the given config. + pub fn get_or_init(config: Config) InitError!Self { + if (!@hasDecl(SystemUart, "get_or_init")) { + // fallback to reinitializing the UART + return init(config); + } + return Self{ + .internal = try SystemUart.get_or_init(config), + }; + } + + pub fn can_read(self: Self) bool { + return self.internal.can_read(); + } + + pub fn can_write(self: Self) bool { + return self.internal.can_write(); + } + + pub fn reader(self: Self) Reader { + return Reader{ .context = self }; + } + + pub fn writer(self: Self) Writer { + return Writer{ .context = self }; + } + + pub const Reader = std.io.Reader(Self, ReadError, read_some); + pub const Writer = std.io.Writer(Self, WriteError, write_some); + + fn read_some(self: Self, buffer: []u8) ReadError!usize { + for (buffer) |*c| { + c.* = self.internal.rx(); + } + return buffer.len; + } + fn write_some(self: Self, buffer: []const u8) WriteError!usize { + for (buffer) |c| { + self.internal.tx(c); + } + return buffer.len; + } + }; +} + +/// The pin configuration. This is used to optionally configure specific pins to be used with the chosen UART. +/// This makes sense only with microcontrollers supporting multiple pins for a UART peripheral. +pub const Pins = struct { + tx: ?type = null, + rx: ?type = null, +}; + +/// A UART configuration. The config defaults to the *8N1* setting, so "8 data bits, no parity, 1 stop bit" which is the +/// most common serial format. +pub const Config = struct { + /// TODO: Make this optional, to support STM32F303 et al. auto baud-rate detection? + baud_rate: u32, + stop_bits: StopBits = .one, + parity: ?Parity = null, + data_bits: DataBits = .eight, +}; + +// TODO: comptime verify that the enums are valid +pub const DataBits = hal.uart.DataBits; +pub const StopBits = hal.uart.StopBits; +pub const Parity = hal.uart.Parity; + +pub const InitError = error{ + UnsupportedWordSize, + UnsupportedParity, + UnsupportedStopBitCount, + UnsupportedBaudRate, +}; + +pub const ReadError = error{ + /// The input buffer received a byte while the receive fifo is already full. + /// Devices with no fifo fill overrun as soon as a second byte arrives. + Overrun, + /// A byte with an invalid parity bit was received. + ParityError, + /// The stop bit of our byte was not valid. + FramingError, + /// The break interrupt error will happen when RXD is logic zero for + /// the duration of a full byte. + BreakInterrupt, +}; +pub const WriteError = error{}; diff --git a/core/src/core/heap.zig b/core/src/core/heap.zig new file mode 100644 index 0000000..ae6501a --- /dev/null +++ b/core/src/core/heap.zig @@ -0,0 +1,5 @@ +pub const UmmAllocator = @import("umm").UmmAllocator; + +test "heap tests" { + _ = UmmAllocator; +} diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig new file mode 100644 index 0000000..a7b4e56 --- /dev/null +++ b/core/src/core/usb.zig @@ -0,0 +1,875 @@ +//! Abstract USB device implementation +//! +//! This can be used to setup a USB device. +//! +//! ## Usage +//! +//! 1. Define the functions (`pub const F = struct { ... }`) required by `Usb()` (see below) +//! 2. Call `pub const device = Usb(F)` +//! 3. Define the device configuration (DeviceConfiguration) +//! 4. Initialize the device in main by calling `usb.init_clk()` and `usb.init_device(device_conf)` +//! 5. Call `usb.task()` within the main loop + +const std = @import("std"); + +/// USB Human Interface Device (HID) +pub const hid = @import("usb/hid.zig"); + +/// Create a USB device +/// +/// # Arguments +/// +/// This is a abstract USB device implementation that requires a handful of functions +/// to work correctly: +/// +/// * `usb_init_clk() void` - Initialize the USB clock +/// * `usb_init_device(*DeviceConfiguration) - Initialize the USB device controller (e.g. enable interrupts, etc.) +/// * `usb_start_tx(*EndpointConfiguration, []const u8)` - Transmit the given bytes over the specified endpoint +/// * `usb_start_rx(*usb.EndpointConfiguration, n: usize)` - Receive n bytes over the specified endpoint +/// * `get_interrupts() InterruptStatus` - Return which interrupts haven't been handled yet +/// * `get_setup_packet() SetupPacket` - Return the USB setup packet received (called if SetupReq received). Make sure to clear the status flag yourself! +/// * `bus_reset() void` - Called on a bus reset interrupt +/// * `set_address(addr: u7) void` - Set the given address +/// * `get_EPBIter(*const DeviceConfiguration) EPBIter` - Return an endpoint buffer iterator. Each call to next returns an unhandeled endpoint buffer with data. How next is implemented depends on the system. +/// The functions must be grouped under the same name space and passed to the fuction at compile time. +/// The functions will be accessible to the user through the `callbacks` field. +pub fn Usb(comptime f: anytype) type { + return struct { + /// The usb configuration set + var usb_config: ?*DeviceConfiguration = null; + /// The clock has been initialized [Y/n] + var clk_init: bool = false; + + /// Index of enpoint buffer 0 out + pub const EP0_OUT_IDX = 0; + /// Index of enpoint buffer 0 in + pub const EP0_IN_IDX = 1; + /// The callbacks passed provided by the caller + pub const callbacks = f; + + /// Initialize the USB clock + pub fn init_clk() void { + f.usb_init_clk(); + clk_init = true; + } + + /// Initialize the usb device using the given configuration + /// + /// This function will return an error if the clock hasn't been initialized. + pub fn init_device(device_config: *DeviceConfiguration) !void { + if (!clk_init) return error.UninitializedClock; + + f.usb_init_device(device_config); + usb_config = device_config; + } + + /// Usb task function meant to be executed in regular intervals after + /// initializing the device. + /// + /// This function will return an error if the device hasn't been initialized. + pub fn task(debug: bool) !void { + if (usb_config == null) return error.UninitializedDevice; + + // We'll keep some state in Plain Old Static Local Variables: + const S = struct { + // When the host gives us a new address, we can't just slap it into + // registers right away, because we have to do an acknowledgement step using + // our _old_ address. + var new_address: ?u8 = null; + // Flag recording whether the host has configured us with a + // `SetConfiguration` message. + var configured = false; + // Flag recording whether we've set up buffer transfers after being + // configured. + var started = false; + // Some scratch space that we'll use for things like preparing string + // descriptors for transmission. + var tmp: [64]u8 = .{0} ** 64; + }; + + // Check which interrupt flags are set. + const ints = f.get_interrupts(); + + // Setup request received? + if (ints.SetupReq) { + if (debug) std.log.info("setup req", .{}); + + // Get the setup request setup packet + const setup = f.get_setup_packet(); + + // Reset PID to 1 for EP0 IN. Every DATA packet we send in response + // to an IN on EP0 needs to use PID DATA1, and this line will ensure + // that. + usb_config.?.endpoints[EP0_IN_IDX].next_pid_1 = true; + + // Attempt to parse the request type and request into one of our + // known enum values, and then inspect them. (These will return None + // if we get an unexpected numeric value.) + const reqty = Dir.of_endpoint_addr(setup.request_type); + const req = SetupRequest.from_u8(setup.request); + + if (reqty == Dir.Out and req != null and req.? == SetupRequest.SetAddress) { + // The new address is in the bottom 8 bits of the setup + // packet value field. Store it for use later. + S.new_address = @as(u8, @intCast(setup.value & 0xff)); + // The address will actually get set later, we have + // to use address 0 to send a status response. + f.usb_start_tx( + usb_config.?.endpoints[EP0_IN_IDX], //EP0_IN_CFG, + &.{}, // <- see, empty buffer + ); + if (debug) std.log.info(" SetAddress: {}", .{S.new_address.?}); + } else if (reqty == Dir.Out and req != null and req.? == SetupRequest.SetConfiguration) { + // We only have one configuration, and it doesn't really + // mean anything to us -- more of a formality. All we do in + // response to this is: + S.configured = true; + f.usb_start_tx( + usb_config.?.endpoints[EP0_IN_IDX], //EP0_IN_CFG, + &.{}, // <- see, empty buffer + ); + if (debug) std.log.info(" SetConfiguration", .{}); + } else if (reqty == Dir.Out) { + // This is sort of a hack, but: if we get any other kind of + // OUT, just acknowledge it with the same zero-length status + // phase that we use for control transfers that we _do_ + // understand. This keeps the host from spinning forever + // while we NAK. + // + // This behavior copied shamelessly from the C example. + f.usb_start_tx( + usb_config.?.endpoints[EP0_IN_IDX], // EP0_IN_CFG, + &.{}, // <- see, empty buffer + ); + if (debug) std.log.info(" Just OUT", .{}); + } else if (reqty == Dir.In and req != null and req.? == SetupRequest.GetDescriptor) { + // Identify the requested descriptor type, which is in the + // _top_ 8 bits of value. + const descriptor_type = DescType.from_u16(setup.value >> 8); + if (debug) std.log.info(" GetDescriptor: {}", .{setup.value >> 8}); + if (descriptor_type) |dt| { + switch (dt) { + .Device => { + if (debug) std.log.info(" Device", .{}); + // TODO: this sure looks like a duplicate, but it's + // a duplicate that was present in the C + // implementation. + usb_config.?.endpoints[EP0_IN_IDX].next_pid_1 = true; + + const dc = usb_config.?.device_descriptor.serialize(); + @memcpy(S.tmp[0..dc.len], &dc); + + // Configure EP0 IN to send the device descriptor + // when it's next asked. + f.usb_start_tx( + usb_config.?.endpoints[EP0_IN_IDX], + S.tmp[0..dc.len], + ); + }, + .Config => { + if (debug) std.log.info(" Config", .{}); + // Config descriptor requests are slightly unusual. + // We can respond with just our config descriptor, + // but we can _also_ append our interface and + // endpoint descriptors to the end, saving some + // round trips. We'll choose to do this if the + // number of bytes the host will accept (in the + // `length` field) is large enough. + var used: usize = 0; + + const cd = usb_config.?.config_descriptor.serialize(); + @memcpy(S.tmp[used .. used + cd.len], &cd); + used += cd.len; + + if (setup.length > used) { + // Do the rest! + // + // This is slightly incorrect because the host + // might have asked for a number of bytes in + // between the size of a config descriptor, and + // the amount we're going to send back. However, + // in practice, the host always asks for either + // (1) the exact size of a config descriptor, or + // (2) 64 bytes, and this all fits in 64 bytes. + const id = usb_config.?.interface_descriptor.serialize(); + @memcpy(S.tmp[used .. used + id.len], &id); + used += id.len; + + // Seems like the host does not bother asking for the + // hid descriptor so we'll just send it with the + // other descriptors. + if (usb_config.?.hid) |hid_conf| { + const hd = hid_conf.hid_descriptor.serialize(); + @memcpy(S.tmp[used .. used + hd.len], &hd); + used += hd.len; + } + + // TODO: depending on the number of endpoints + // this might not fit in 64 bytes -> split message + // into multiple packets + for (usb_config.?.endpoints[2..]) |ep| { + const ed = ep.descriptor.serialize(); + @memcpy(S.tmp[used .. used + ed.len], &ed); + used += ed.len; + } + } + + // Set up EP0 IN to send the stuff we just composed. + f.usb_start_tx( + usb_config.?.endpoints[EP0_IN_IDX], + S.tmp[0..used], + ); + }, + .String => { + if (debug) std.log.info(" String", .{}); + // String descriptor index is in bottom 8 bits of + // `value`. + const i: usize = @intCast(setup.value & 0xff); + const bytes = StringBlk: { + if (i == 0) { + // Special index 0 requests the language + // descriptor. + break :StringBlk usb_config.?.lang_descriptor; + } else { + // Otherwise, set up one of our strings. + const s = usb_config.?.descriptor_strings[i - 1]; + const len = 2 + s.len; + + S.tmp[0] = @intCast(len); + S.tmp[1] = 0x03; + @memcpy(S.tmp[2..len], s); + + break :StringBlk S.tmp[0..len]; + } + }; + // Set up EP0 IN to send whichever thing we just + // decided on. + f.usb_start_tx( + usb_config.?.endpoints[EP0_IN_IDX], + bytes, + ); + }, + .Interface => { + if (debug) std.log.info(" Interface", .{}); + // We don't expect the host to send this because we + // delivered our interface descriptor with the + // config descriptor. + // + // Should probably implement it, though, because + // otherwise the host will be unhappy. TODO. + // + // Note that the C example gets away with ignoring + // this. + }, + .Endpoint => { + if (debug) std.log.info(" Endpoint", .{}); + // Same deal as interface descriptors above. + }, + .DeviceQualifier => { + if (debug) std.log.info(" DeviceQualifier", .{}); + // We will just copy parts of the DeviceDescriptor because + // the DeviceQualifierDescriptor can be seen as a subset. + const dqd = DeviceQualifierDescriptor{ + .bcd_usb = usb_config.?.device_descriptor.bcd_usb, + .device_class = usb_config.?.device_descriptor.device_class, + .device_subclass = usb_config.?.device_descriptor.device_subclass, + .device_protocol = usb_config.?.device_descriptor.device_protocol, + .max_packet_size0 = usb_config.?.device_descriptor.max_packet_size0, + .num_configurations = usb_config.?.device_descriptor.num_configurations, + }; + + const data = dqd.serialize(); + @memcpy(S.tmp[0..data.len], &data); + + f.usb_start_tx( + usb_config.?.endpoints[EP0_IN_IDX], + S.tmp[0..data.len], + ); + }, + } + } else { + // Maybe the unknown request type is a hid request + + if (usb_config.?.hid) |hid_conf| { + const _hid_desc_type = hid.DescType.from_u16(setup.value >> 8); + + if (_hid_desc_type) |hid_desc_type| { + switch (hid_desc_type) { + .Hid => { + if (debug) std.log.info(" HID", .{}); + + const hd = hid_conf.hid_descriptor.serialize(); + @memcpy(S.tmp[0..hd.len], &hd); + + f.usb_start_tx( + usb_config.?.endpoints[EP0_IN_IDX], + S.tmp[0..hd.len], + ); + }, + .Report => { + if (debug) std.log.info(" Report", .{}); + + // The report descriptor is already a (static) + // u8 array, i.e., we can pass it directly + f.usb_start_tx( + usb_config.?.endpoints[EP0_IN_IDX], + hid_conf.report_descriptor, + ); + }, + .Physical => { + if (debug) std.log.info(" Physical", .{}); + // Ignore for now + }, + } + } else { + // It's not a valid HID request. This can totally happen + // we'll just ignore it for now... + } + } + } + } else if (reqty == Dir.In) { + if (debug) std.log.info(" Just IN", .{}); + // Other IN request. Ignore. + } else { + if (debug) std.log.info(" This is unexpected", .{}); + // Unexpected request type or request bits. This can totally + // happen (yay, hardware!) but is rare in practice. Ignore + // it. + } + } // <-- END of setup request handling + + // Events on one or more buffers? (In practice, always one.) + if (ints.BuffStatus) { + if (debug) std.log.info("buff status", .{}); + var iter = f.get_EPBIter(usb_config.?); + + while (iter.next(&iter)) |epb| { + if (debug) std.log.info(" data: {any}", .{epb.buffer}); + + // Perform any required action on the data. For OUT, the `data` + // will be whatever was sent by the host. For IN, it's a copy of + // whatever we sent. + switch (epb.endpoint.descriptor.endpoint_address) { + EP0_IN_ADDR => { + if (debug) std.log.info(" EP0_IN_ADDR", .{}); + // We use this opportunity to finish the delayed + // SetAddress request, if there is one: + if (S.new_address) |addr| { + // Change our address: + f.set_address(@intCast(addr)); + } else { + // Otherwise, we've just finished sending + // something to the host. We expect an ensuing + // status phase where the host sends us (via EP0 + // OUT) a zero-byte DATA packet, so, set that + // up: + f.usb_start_rx( + usb_config.?.endpoints[EP0_OUT_IDX], // EP0_OUT_CFG, + 0, + ); + } + }, + else => { + if (debug) std.log.info(" ELSE, ep_addr: {}", .{ + epb.endpoint.descriptor.endpoint_address & 0x7f, + }); + // Handle user provided endpoints. + + // Invoke the callback (if the user provides one). + if (epb.endpoint.callback) |callback| callback(usb_config.?, epb.buffer); + }, + } + } + } // <-- END of buf status handling + + // Has the host signaled a bus reset? + if (ints.BusReset) { + if (debug) std.log.info("bus reset", .{}); + + // Reset the device + f.bus_reset(); + + // Reset our state. + S.new_address = null; + S.configured = false; + S.started = false; + } + + // If we have been configured but haven't reached this point yet, set up + // our custom EP OUT's to receive whatever data the host wants to send. + if (S.configured and !S.started) { + // We can skip the first two endpoints because those are EP0_OUT and EP0_IN + for (usb_config.?.endpoints[2..]) |ep| { + if (Dir.of_endpoint_addr(ep.descriptor.endpoint_address) == .Out) { + // Hey host! we expect data! + f.usb_start_rx( + ep, + 64, + ); + } + } + S.started = true; + } + } + }; +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Data Types +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +// ------------------------- +// | DeviceDescriptor | +// ------------------------- +// | +// v +// ------------------------- +// | ConfigurationDesc | +// ------------------------- +// | +// v +// ------------------------- +// | InterfaceDescriptor | +// ------------------------- +// | | +// v ------------------------------ +// ------------------------- | +// | EndpointDescriptor | v +// ------------------------- --------------------- +// | HID Descriptor | +// --------------------- + +/// Types of USB descriptor +pub const DescType = enum(u8) { + Device = 0x01, + Config = 0x02, + String = 0x03, + Interface = 0x04, + Endpoint = 0x05, + DeviceQualifier = 0x06, + //-------- Class Specific Descriptors ---------- + // 0x21 ... + + pub fn from_u16(v: u16) ?@This() { + return switch (v) { + 1 => @This().Device, + 2 => @This().Config, + 3 => @This().String, + 4 => @This().Interface, + 5 => @This().Endpoint, + 6 => @This().DeviceQualifier, + else => null, + }; + } +}; + +/// Types of transfer that can be indicated by the `attributes` field on +/// `EndpointDescriptor`. +pub const TransferType = enum(u2) { + Control = 0, + Isochronous = 1, + Bulk = 2, + Interrupt = 3, +}; + +/// The types of USB SETUP requests that we understand. +pub const SetupRequest = enum(u8) { + /// Asks the device to send a certain descriptor back to the host. Always + /// used on an IN request. + GetDescriptor = 0x06, + /// Notifies the device that it's being moved to a different address on the + /// bus. Always an OUT. + SetAddress = 0x05, + /// Configures a device by choosing one of the options listed in its + /// descriptors. Always an OUT. + SetConfiguration = 0x09, + + pub fn from_u8(request: u8) ?@This() { + return switch (request) { + 0x06 => SetupRequest.GetDescriptor, + 0x05 => SetupRequest.SetAddress, + 0x09 => SetupRequest.SetConfiguration, + else => null, + }; + } +}; + +/// USB deals in two different transfer directions, called OUT (host-to-device) +/// and IN (device-to-host). In the vast majority of cases, OUT is represented +/// by a 0 byte, and IN by an `0x80` byte. +pub const Dir = enum(u8) { + Out = 0, + In = 0x80, + + pub inline fn endpoint(self: @This(), num: u8) u8 { + return num | @intFromEnum(self); + } + + pub inline fn of_endpoint_addr(addr: u8) @This() { + return if (addr & @intFromEnum(@This().In) != 0) @This().In else @This().Out; + } +}; + +/// Describes an endpoint within an interface +pub const EndpointDescriptor = extern struct { + /// Length of this struct, must be 7. + length: u8, + /// Type of this descriptor, must be `Endpoint`. + descriptor_type: DescType, + /// Address of this endpoint, where the bottom 4 bits give the endpoint + /// number (0..15) and the top bit distinguishes IN (1) from OUT (0). + endpoint_address: u8, + /// Endpoint attributes; the most relevant part is the bottom 2 bits, which + /// control the transfer type using the values from `TransferType`. + attributes: u8, + /// Maximum packet size this endpoint can accept/produce. + max_packet_size: u16, + /// Interval for polling interrupt/isochronous endpoints (which we don't + /// currently support) in milliseconds. + interval: u8, + + pub fn serialize(self: *const @This()) [7]u8 { + var out: [7]u8 = undefined; + out[0] = 7; // length + out[1] = @intFromEnum(self.descriptor_type); + out[2] = self.endpoint_address; + out[3] = self.attributes; + out[4] = @intCast(self.max_packet_size & 0xff); + out[5] = @intCast((self.max_packet_size >> 8) & 0xff); + out[6] = self.interval; + return out; + } +}; + +/// Description of an interface within a configuration. +pub const InterfaceDescriptor = extern struct { + /// Length of this structure, must be 9. + length: u8, + /// Type of this descriptor, must be `Interface`. + descriptor_type: DescType, + /// ID of this interface. + interface_number: u8, + /// Allows a single `interface_number` to have several alternate interface + /// settings, where each alternate increments this field. Normally there's + /// only one, and `alternate_setting` is zero. + alternate_setting: u8, + /// Number of endpoint descriptors in this interface. + num_endpoints: u8, + /// Interface class code, distinguishing the type of interface. + interface_class: u8, + /// Interface subclass code, refining the class of interface. + interface_subclass: u8, + /// Protocol within the interface class/subclass. + interface_protocol: u8, + /// Index of interface name within string descriptor table. + interface_s: u8, + + pub fn serialize(self: *const @This()) [9]u8 { + var out: [9]u8 = undefined; + out[0] = 9; // length + out[1] = @intFromEnum(self.descriptor_type); + out[2] = self.interface_number; + out[3] = self.alternate_setting; + out[4] = self.num_endpoints; + out[5] = self.interface_class; + out[6] = self.interface_subclass; + out[7] = self.interface_protocol; + out[8] = self.interface_s; + return out; + } +}; + +/// Description of a single available device configuration. +pub const ConfigurationDescriptor = extern struct { + /// Length of this structure, must be 9. + length: u8, + /// Type of this descriptor, must be `Config`. + descriptor_type: DescType, + /// Total length of all descriptors in this configuration, concatenated. + /// This will include this descriptor, plus at least one interface + /// descriptor, plus each interface descriptor's endpoint descriptors. + total_length: u16, + /// Number of interface descriptors in this configuration. + num_interfaces: u8, + /// Number to use when requesting this configuration via a + /// `SetConfiguration` request. + configuration_value: u8, + /// Index of this configuration's name in the string descriptor table. + configuration_s: u8, + /// Bit set of device attributes: + /// + /// - Bit 7 should be set (indicates that device can be bus powered in USB + /// 1.0). + /// - Bit 6 indicates that the device can be self-powered. + /// - Bit 5 indicates that the device can signal remote wakeup of the host + /// (like a keyboard). + /// - The rest are reserved and should be zero. + attributes: u8, + /// Maximum device power consumption in units of 2mA. + max_power: u8, + + pub fn serialize(self: *const @This()) [9]u8 { + var out: [9]u8 = undefined; + out[0] = 9; // length + out[1] = @intFromEnum(self.descriptor_type); + out[2] = @intCast(self.total_length & 0xff); + out[3] = @intCast((self.total_length >> 8) & 0xff); + out[4] = self.num_interfaces; + out[5] = self.configuration_value; + out[6] = self.configuration_s; + out[7] = self.attributes; + out[8] = self.max_power; + return out; + } +}; + +/// Describes a device. This is the most broad description in USB and is +/// typically the first thing the host asks for. +pub const DeviceDescriptor = extern struct { + /// Length of this structure, must be 18. + length: u8, + /// Type of this descriptor, must be `Device`. + descriptor_type: DescType, + /// Version of the device descriptor / USB protocol, in binary-coded + /// decimal. This is typically `0x01_10` for USB 1.1. + bcd_usb: u16, + /// Class of device, giving a broad functional area. + device_class: u8, + /// Subclass of device, refining the class. + device_subclass: u8, + /// Protocol within the subclass. + device_protocol: u8, + /// Maximum unit of data this device can move. + max_packet_size0: u8, + /// ID of product vendor. + vendor: u16, + /// ID of product. + product: u16, + /// Device version number, as BCD again. + bcd_device: u16, + /// Index of manufacturer name in string descriptor table. + manufacturer_s: u8, + /// Index of product name in string descriptor table. + product_s: u8, + /// Index of serial number in string descriptor table. + serial_s: u8, + /// Number of configurations supported by this device. + num_configurations: u8, + + pub fn serialize(self: *const @This()) [18]u8 { + var out: [18]u8 = undefined; + out[0] = 18; // length + out[1] = @intFromEnum(self.descriptor_type); + out[2] = @intCast(self.bcd_usb & 0xff); + out[3] = @intCast((self.bcd_usb >> 8) & 0xff); + out[4] = self.device_class; + out[5] = self.device_subclass; + out[6] = self.device_protocol; + out[7] = self.max_packet_size0; + out[8] = @intCast(self.vendor & 0xff); + out[9] = @intCast((self.vendor >> 8) & 0xff); + out[10] = @intCast(self.product & 0xff); + out[11] = @intCast((self.product >> 8) & 0xff); + out[12] = @intCast(self.bcd_device & 0xff); + out[13] = @intCast((self.bcd_device >> 8) & 0xff); + out[14] = self.manufacturer_s; + out[15] = self.product_s; + out[16] = self.serial_s; + out[17] = self.num_configurations; + return out; + } +}; + +/// USB Device Qualifier Descriptor +/// This descriptor is mostly the same as the DeviceDescriptor +pub const DeviceQualifierDescriptor = extern struct { + /// Length of this structure, must be 18. + length: u8 = 10, + /// Type of this descriptor, must be `Device`. + descriptor_type: DescType = DescType.DeviceQualifier, + /// Version of the device descriptor / USB protocol, in binary-coded + /// decimal. This is typically `0x01_10` for USB 1.1. + bcd_usb: u16, + /// Class of device, giving a broad functional area. + device_class: u8, + /// Subclass of device, refining the class. + device_subclass: u8, + /// Protocol within the subclass. + device_protocol: u8, + /// Maximum unit of data this device can move. + max_packet_size0: u8, + /// Number of configurations supported by this device. + num_configurations: u8, + /// Reserved for future use; must be 0 + reserved: u8 = 0, + + pub fn serialize(self: *const @This()) [10]u8 { + var out: [10]u8 = undefined; + out[0] = 10; // length + out[1] = @intFromEnum(self.descriptor_type); + out[2] = @intCast(self.bcd_usb & 0xff); + out[3] = @intCast((self.bcd_usb >> 8) & 0xff); + out[4] = self.device_class; + out[5] = self.device_subclass; + out[6] = self.device_protocol; + out[7] = self.max_packet_size0; + out[8] = self.num_configurations; + out[9] = self.reserved; + return out; + } +}; + +/// Layout of an 8-byte USB SETUP packet. +pub const SetupPacket = extern struct { + /// Request type; in practice, this is always either OUT (host-to-device) or + /// IN (device-to-host), whose values are given in the `Dir` enum. + request_type: u8, + /// Request. Standard setup requests are in the `SetupRequest` enum. + /// Devices can extend this with additional types as long as they don't + /// conflict. + request: u8, + /// A simple argument of up to 16 bits, specific to the request. + value: u16, + /// Not used in the requests we support. + index: u16, + /// If data will be transferred after this request (in the direction given + /// by `request_type`), this gives the number of bytes (OUT) or maximum + /// number of bytes (IN). + length: u16, +}; + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Driver support stuctures +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +pub const EndpointConfiguration = struct { + descriptor: *const EndpointDescriptor, + /// Index of this endpoint's control register in the `ep_control` array. + /// + /// TODO: this can be derived from the endpoint address, perhaps it should + /// be. + endpoint_control_index: ?usize, + /// Index of this endpoint's buffer control register in the + /// `ep_buffer_control` array. + /// + /// TODO this, too, can be derived. + buffer_control_index: usize, + + /// Index of this endpoint's data buffer in the array of data buffers + /// allocated from DPRAM. This can be arbitrary, and endpoints can even + /// share buffers if you're careful. + data_buffer_index: usize, + + /// Keeps track of which DATA PID (DATA0/DATA1) is expected on this endpoint + /// next. If `true`, we're expecting `DATA1`, otherwise `DATA0`. + next_pid_1: bool, + + /// Optional callback for custom OUT endpoints. This function will be called + /// if the device receives data on the corresponding endpoint. + callback: ?*const fn (dc: *DeviceConfiguration, data: []const u8) void = null, +}; + +pub const DeviceConfiguration = struct { + device_descriptor: *const DeviceDescriptor, + interface_descriptor: *const InterfaceDescriptor, + config_descriptor: *const ConfigurationDescriptor, + lang_descriptor: []const u8, + descriptor_strings: []const []const u8, + hid: ?struct { + hid_descriptor: *const hid.HidDescriptor, + report_descriptor: []const u8, + } = null, + endpoints: [4]*EndpointConfiguration, +}; + +/// Buffer pointers, once they're prepared and initialized. +pub const Buffers = struct { + /// Fixed EP0 Buffer0, defined by the hardware + ep0_buffer0: [*]u8, + /// Fixed EP0 Buffer1, defined by the hardware and NOT USED in this driver + ep0_buffer1: [*]u8, + /// /// Remaining buffer pool + rest: [16][*]u8, + + /// Gets a buffer corresponding to a `data_buffer_index` in a + /// `EndpointConfiguration`. + pub fn get(self: *@This(), i: usize) [*]u8 { + return switch (i) { + 0 => self.ep0_buffer0, + 1 => self.ep0_buffer1, + else => self.rest[i - 2], + }; + } +}; + +// Handy constants for the endpoints we use here +pub const EP0_IN_ADDR: u8 = Dir.In.endpoint(0); +pub const EP0_OUT_ADDR: u8 = Dir.Out.endpoint(0); +const EP1_OUT_ADDR: u8 = Dir.Out.endpoint(1); +const EP1_IN_ADDR: u8 = Dir.In.endpoint(1); +const EP2_IN_ADDR: u8 = Dir.In.endpoint(2); + +/// USB interrupt status +/// +/// __Note__: Available interrupts may change from device to device. +pub const InterruptStatus = struct { + /// Host: raised every time the host sends a SOF (Start of Frame) + BuffStatus: bool = false, + BusReset: bool = false, + /// Set when the device connection state changes + DevConnDis: bool = false, + /// Set when the device suspend state changes + DevSuspend: bool = false, + /// Set when the device receives a resume from the host + DevResumeFromHost: bool = false, + /// Setup Request + SetupReq: bool = false, +}; + +pub const EPBError = error{ + /// The system has received a buffer event for an unknown endpoint (this is super unlikely) + UnknownEndpoint, + /// The buffer is not available (this is super unlikely) + NotAvailable, +}; + +/// Element returned by the endpoint buffer iterator (EPBIter) +pub const EPB = struct { + /// The endpoint the data belongs to + endpoint: *EndpointConfiguration, + /// Data buffer + buffer: []u8, +}; + +/// Iterator over all input buffers that hold data +pub const EPBIter = struct { + /// Bitmask of the input buffers to handle + bufbits: u32, + /// The last input buffer handled. This can be used to flag the input buffer as handled on the + /// next call. + last_bit: ?u32 = null, + /// Point to the device configuration (to get access to the endpoint buffers defined by the user) + device_config: *const DeviceConfiguration, + /// Get the next available input buffer + next: *const fn (self: *@This()) ?EPB, +}; + +/// Convert an utf8 into an utf16 (little endian) string +pub fn utf8Toutf16Le(comptime s: []const u8) [s.len << 1]u8 { + const l = s.len << 1; + var ret: [l]u8 = .{0} ** l; + var i: usize = 0; + while (i < s.len) : (i += 1) { + ret[i << 1] = s[i]; + } + return ret; +} + +test "tests" { + _ = hid; +} + +test "utf8 to utf16" { + try std.testing.expectEqualSlices(u8, "M\x00y\x00 \x00V\x00e\x00n\x00d\x00o\x00r\x00", &utf8Toutf16Le("My Vendor")); + try std.testing.expectEqualSlices(u8, "R\x00a\x00s\x00p\x00b\x00e\x00r\x00r\x00y\x00 \x00P\x00i\x00", &utf8Toutf16Le("Raspberry Pi")); +} diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig new file mode 100644 index 0000000..c63e28a --- /dev/null +++ b/core/src/core/usb/hid.zig @@ -0,0 +1,458 @@ +//! USB HID +//! +//! ## Interfaces +//! +//! HID class devices use Control and Interrupt pipes for communication +//! +//! 1. Control for USB control and data classes, reports, data from host +//! 2. Interrupt for asynchronous and low latency data to the device +//! +//! ## Settings +//! +//! ### UsbInterfaceDescriptor related settings: +//! The class type for a HID class device is defined by the Interface descriptor. +//! The subclass field is used to identify Boot Devices. +//! +//! * `interface_subclass` - 1 if boot interface, 0 else (most of the time) +//! * `interface_protocol` - 0 if no boot interface, 1 if keyboard boot interface, 2 if mouse BI +//! +//! ## Nice to know +//! +//! The host usually won't ask for the HID descriptor even if the other descriptors +//! indicate that the given device is a HID device, i. e., you should just pass the +//! HID descriptor together with the configuration descriptor. +//! +//! ## Descriptors +//! +//! ### Report +//! +//! A Report descriptor describes each piece of data that the device generates +//! and what the data is actually measuring (e.g., position or button state). The +//! descriptor consists of one or more items and answers the following questions: +//! +//! 1. Where should the input be routed to (e.g., mouse or joystick API)? +//! 2. Should the software assign a functionality to the input? +//! +//! ### Physical +//! +//! ... +//! +//! ### HID +//! +//! The HID descriptor identifies the length and type of subordinate descriptors for device. + +const std = @import("std"); + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Common Data Types +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +// ... +// | +// v +// ------------------------- +// | InterfaceDescriptor | +// ------------------------- +// | | +// | ----------------- +// | | +// v v +// ... -------------------------- +// | HidDescriptor | +// -------------------------- +// | | +// ------ -------- +// | | +// v v +// ----------------------- --------------------- +// | ReportDescriptor | | PhysicalDesc | +// ----------------------- --------------------- + +pub const DescType = enum(u8) { + /// HID descriptor + Hid = 0x21, + /// Report descriptor + Report = 0x22, + /// Physical descriptor + Physical = 0x23, + + pub fn from_u16(v: u16) ?@This() { + return switch (v) { + 0x21 => @This().Hid, + 0x22 => @This().Report, + 0x23 => @This().Physical, + else => null, + }; + } +}; + +/// USB HID descriptor +pub const HidDescriptor = extern struct { + length: u8 = 9, + descriptor_type: DescType = DescType.Hid, + /// Numeric expression identifying the HID Class Specification release + bcd_hid: u16, + /// Numeric expression identifying country code of the localized hardware + country_code: u8, + /// Numeric expression specifying the number of class descriptors + num_descriptors: u8, + /// Type of HID class report + report_type: DescType = DescType.Report, + /// The total size of the Report descriptor + report_length: u16, + + pub fn serialize(self: *const @This()) [9]u8 { + var out: [9]u8 = undefined; + out[0] = 9; // length + out[1] = @intFromEnum(self.descriptor_type); + out[2] = @intCast(self.bcd_hid & 0xff); + out[3] = @intCast((self.bcd_hid >> 8) & 0xff); + out[4] = self.country_code; + out[5] = self.num_descriptors; + out[6] = @intFromEnum(self.report_type); + out[7] = @intCast(self.report_length & 0xff); + out[8] = @intCast((self.report_length >> 8) & 0xff); + return out; + } +}; + +/// HID interface Subclass (for UsbInterfaceDescriptor) +pub const Subclass = enum(u8) { + /// No Subclass + None = 0, + /// Boot Interface Subclass + Boot = 1, +}; + +/// HID interface protocol +pub const Protocol = enum(u8) { + /// No protocol + None = 0, + /// Keyboard (only if boot interface) + Keyboard = 1, + /// Mouse (only if boot interface) + Mouse = 2, +}; + +/// HID request report type +pub const ReportType = enum(u8) { + Invalid = 0, + Input = 1, + Output = 2, + Feature = 3, +}; + +/// HID class specific control request +pub const Request = enum(u8) { + GetReport = 0x01, + GetIdle = 0x02, + GetProtocol = 0x03, + SetReport = 0x09, + SetIdle = 0x0a, + SetProtocol = 0x0b, +}; + +/// HID country codes +pub const CountryCode = enum(u8) { + NotSupported = 0, + Arabic, + Belgian, + CanadianBilingual, + CanadianFrench, + CzechRepublic, + Danish, + Finnish, + French, + German, + Greek, + Hebrew, + Hungary, + International, + Italian, + JapanKatakana, + Korean, + LatinAmerica, + NetherlandsDutch, + Norwegian, + PersianFarsi, + Poland, + Portuguese, + Russia, + Slovakia, + Spanish, + Swedish, + SwissFrench, + SwissGerman, + Switzerland, + Taiwan, + TurkishQ, + Uk, + Us, + Yugoslavia, + TurkishF, +}; + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Report Descriptor Data Types +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +pub const ReportItemTypes = enum(u2) { + Main = 0, + Global = 1, + Local = 2, +}; + +pub const ReportItemMainGroup = enum(u4) { + Input = 8, + Output = 9, + Collection = 10, + Feature = 11, + CollectionEnd = 12, +}; + +pub const CollectionItem = enum(u8) { + Physical = 0, + Application, + Logical, + Report, + NamedArray, + UsageSwitch, + UsageModifier, +}; + +pub const GlobalItem = enum(u4) { + UsagePage = 0, + LogicalMin = 1, + LogicalMax = 2, + PhysicalMin = 3, + PhysicalMax = 4, + UnitExponent = 5, + Unit = 6, + ReportSize = 7, + ReportId = 8, + ReportCount = 9, + Push = 10, + Pop = 11, +}; + +pub const LocalItem = enum(u4) { + Usage = 0, + UsageMin = 1, + UsageMax = 2, + DesignatorIndex = 3, + DesignatorMin = 4, + DesignatorMax = 5, + StringIndex = 7, + StringMin = 8, + StringMax = 9, + Delimiter = 10, +}; + +pub const UsageTable = struct { + const fido: [2]u8 = "\xD0\xF1".*; +}; + +pub const FidoAllianceUsage = struct { + const u2fhid: [1]u8 = "\x01".*; + const data_in: [1]u8 = "\x20".*; + const data_out: [1]u8 = "\x21".*; +}; + +const HID_DATA: u8 = 0 << 0; +const HID_CONSTANT: u8 = 1 << 0; + +const HID_ARRAY = 0 << 1; +const HID_VARIABLE = 1 << 1; + +const HID_ABSOLUTE = 0 << 2; +const HID_RELATIVE = 1 << 2; + +const HID_WRAP_NO = 0 << 3; +const HID_WRAP = 1 << 3; + +const HID_LINEAR = 0 << 4; +const HID_NONLINEAR = 1 << 4; + +const HID_PREFERRED_STATE = 0 << 5; +const HID_PREFERRED_NO = 1 << 5; + +const HID_NO_NULL_POSITION = 0 << 6; +const HID_NULL_STATE = 1 << 6; + +const HID_NON_VOLATILE = 0 << 7; +const HID_VOLATILE = 1 << 7; + +const HID_BITFIELD = 0 << 8; +const HID_BUFFERED_BYTES = 1 << 8; + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Report Descriptor Functions +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +pub fn hid_report_item( + comptime n: u2, + typ: u2, + tag: u4, + data: [n]u8, +) [n + 1]u8 { + var out: [n + 1]u8 = undefined; + + out[0] = (@as(u8, @intCast(tag)) << 4) | (@as(u8, @intCast(typ)) << 2) | n; + + var i: usize = 0; + while (i < n) : (i += 1) { + out[i + 1] = data[i]; + } + + return out; +} + +// Main Items +// ------------------------------------------------- + +pub fn hid_collection(data: CollectionItem) [2]u8 { + return hid_report_item( + 1, + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.Collection), + std.mem.toBytes(@intFromEnum(data)), + ); +} + +pub fn hid_input(data: u8) [2]u8 { + return hid_report_item( + 1, + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.Input), + std.mem.toBytes(data), + ); +} + +pub fn hid_output(data: u8) [2]u8 { + return hid_report_item( + 1, + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.Output), + std.mem.toBytes(data), + ); +} + +pub fn hid_collection_end() [1]u8 { + return hid_report_item( + 0, + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.CollectionEnd), + .{}, + ); +} + +// Global Items +// ------------------------------------------------- + +pub fn hid_usage_page(comptime n: u2, usage: [n]u8) [n + 1]u8 { + return hid_report_item( + n, + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.UsagePage), + usage, + ); +} + +pub fn hid_logical_min(comptime n: u2, data: [n]u8) [n + 1]u8 { + return hid_report_item( + n, + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.LogicalMin), + data, + ); +} + +pub fn hid_logical_max(comptime n: u2, data: [n]u8) [n + 1]u8 { + return hid_report_item( + n, + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.LogicalMax), + data, + ); +} + +pub fn hid_report_size(comptime n: u2, data: [n]u8) [n + 1]u8 { + return hid_report_item( + n, + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.ReportSize), + data, + ); +} + +pub fn hid_report_count(comptime n: u2, data: [n]u8) [n + 1]u8 { + return hid_report_item( + n, + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.ReportCount), + data, + ); +} + +// Local Items +// ------------------------------------------------- + +pub fn hid_usage(comptime n: u2, data: [n]u8) [n + 1]u8 { + return hid_report_item( + n, + @intFromEnum(ReportItemTypes.Local), + @intFromEnum(LocalItem.Usage), + data, + ); +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Report Descriptors +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +pub const ReportDescriptorFidoU2f = hid_usage_page(2, UsageTable.fido) // +++ hid_usage(1, FidoAllianceUsage.u2fhid) // +++ hid_collection(CollectionItem.Application) // +// Usage Data In +++ hid_usage(1, FidoAllianceUsage.data_in) // +++ hid_logical_min(1, "\x00".*) // +++ hid_logical_max(2, "\xff\x00".*) // +++ hid_report_size(1, "\x08".*) // +++ hid_report_count(1, "\x40".*) // +++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // +// Usage Data Out +++ hid_usage(1, FidoAllianceUsage.data_out) // +++ hid_logical_min(1, "\x00".*) // +++ hid_logical_max(2, "\xff\x00".*) // +++ hid_report_size(1, "\x08".*) // +++ hid_report_count(1, "\x40".*) // +++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // +// End +++ hid_collection_end(); + +test "create hid report item" { + const r = hid_report_item( + 2, + 0, + 3, + "\x22\x11".*, + ); + + try std.testing.expectEqual(@as(usize, @intCast(3)), r.len); + try std.testing.expectEqual(@as(u8, @intCast(50)), r[0]); + try std.testing.expectEqual(@as(u8, @intCast(0x22)), r[1]); + try std.testing.expectEqual(@as(u8, @intCast(0x11)), r[2]); +} + +test "create hid fido usage page" { + const f = hid_usage_page(2, UsageTable.fido); + + try std.testing.expectEqual(@as(usize, @intCast(3)), f.len); + try std.testing.expectEqual(@as(u8, @intCast(6)), f[0]); + try std.testing.expectEqual(@as(u8, @intCast(0xd0)), f[1]); + try std.testing.expectEqual(@as(u8, @intCast(0xf1)), f[2]); +} + +test "report descriptor fido" { + _ = ReportDescriptorFidoU2f; +} diff --git a/core/src/cpus/avr5.zig b/core/src/cpus/avr5.zig new file mode 100644 index 0000000..117a70f --- /dev/null +++ b/core/src/cpus/avr5.zig @@ -0,0 +1,167 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const root = @import("root"); + +pub fn enable_interrupts() void { + asm volatile ("sei"); +} + +pub fn disable_interrupts() void { + asm volatile ("cli"); +} + +pub inline fn sbi(comptime reg: u5, comptime bit: u3) void { + asm volatile ("sbi %[reg], %[bit]" + : + : [reg] "I" (reg), + [bit] "I" (bit), + ); +} + +pub inline fn cbi(comptime reg: u5, comptime bit: u3) void { + asm volatile ("cbi %[reg], %[bit]" + : + : [reg] "I" (reg), + [bit] "I" (bit), + ); +} + +pub const vector_table = blk: { + std.debug.assert(std.mem.eql(u8, "RESET", std.meta.fields(microzig.chip.VectorTable)[0].name)); + var asm_str: []const u8 = "jmp microzig_start\n"; + + const has_interrupts = @hasDecl(root, "microzig_options") and @hasDecl(root.microzig_options, "interrupts"); + if (has_interrupts) { + const interrupts = root.microzig_options.interrupts; + if (@hasDecl(interrupts, "RESET")) + @compileError("Not allowed to overload the reset vector"); + + inline for (std.meta.declarations(interrupts)) |decl| { + if (!@hasField(microzig.chip.VectorTable, decl.name)) { + var msg: []const u8 = "There is no such interrupt as '" ++ decl.name ++ "'. ISRs the 'interrupts' namespace must be one of:\n"; + inline for (std.meta.fields(microzig.chip.VectorTable)) |field| { + if (!std.mem.eql(u8, "RESET", field.name)) { + msg = msg ++ " " ++ field.name ++ "\n"; + } + } + + @compileError(msg); + } + } + } + + inline for (std.meta.fields(microzig.chip.VectorTable)[1..]) |field| { + const new_insn = if (has_interrupts) overload: { + const interrupts = root.microzig_options.interrupts; + if (@hasDecl(interrupts, field.name)) { + const handler = @field(interrupts, field.name); + + const isr = make_isr_handler(field.name, handler); + + break :overload "jmp " ++ isr.exported_name; + } else { + break :overload "jmp microzig_unhandled_vector"; + } + } else "jmp microzig_unhandled_vector"; + + asm_str = asm_str ++ new_insn ++ "\n"; + } + + const T = struct { + fn _start() callconv(.Naked) void { + asm volatile (asm_str); + } + }; + + break :blk T._start; +}; + +fn make_isr_handler(comptime name: []const u8, comptime func: anytype) type { + const calling_convention = switch (@typeInfo(@TypeOf(func))) { + .Fn => |info| info.calling_convention, + else => @compileError("Declarations in 'interrupts' namespace must all be functions. '" ++ name ++ "' is not a function"), + }; + + switch (calling_convention) { + .Unspecified, .Signal, .Interrupt => {}, + else => @compileError("Calling conventions for interrupts must be 'Interrupt', 'Signal', or unspecified. The signal calling convention leaves global interrupts disabled during the ISR, where the interrupt calling conventions enables global interrupts for nested ISRs."), + } + + return struct { + pub const exported_name = "microzig_isr_" ++ name; + + pub fn isr_vector() callconv(.Signal) void { + @call(.always_inline, func, .{}); + } + + comptime { + const options = .{ .name = exported_name, .linkage = .Strong }; + @export(isr_vector, options); + } + }; +} + +pub const startup_logic = struct { + export fn microzig_unhandled_vector() callconv(.Naked) noreturn { + @panic("Unhandled interrupt"); + } + + extern fn microzig_main() noreturn; + + export fn microzig_start() callconv(.Naked) noreturn { + // At startup the stack pointer is at the end of RAM + // so, no need to set it manually! + + copy_data_to_ram(); + clear_bss(); + + microzig_main(); + } + + fn copy_data_to_ram() void { + asm volatile ( + \\ ; load Z register with the address of the data in flash + \\ ldi r30, lo8(microzig_data_load_start) + \\ ldi r31, hi8(microzig_data_load_start) + \\ ; load X register with address of the data in ram + \\ ldi r26, lo8(microzig_data_start) + \\ ldi r27, hi8(microzig_data_start) + \\ ; load address of end of the data in ram + \\ ldi r24, lo8(microzig_data_end) + \\ ldi r25, hi8(microzig_data_end) + \\ rjmp .L2 + \\ + \\.L1: + \\ lpm r18, Z+ ; copy from Z into r18 and increment Z + \\ st X+, r18 ; store r18 at location X and increment X + \\ + \\.L2: + \\ cp r26, r24 + \\ cpc r27, r25 ; check and branch if we are at the end of data + \\ brne .L1 + ); + // Probably a good idea to add clobbers here, but compiler doesn't seem to care + } + + fn clear_bss() void { + asm volatile ( + \\ ; load X register with the beginning of bss section + \\ ldi r26, lo8(microzig_bss_start) + \\ ldi r27, hi8(microzig_bss_start) + \\ ; load end of the bss in registers + \\ ldi r24, lo8(microzig_bss_end) + \\ ldi r25, hi8(microzig_bss_end) + \\ ldi r18, 0x00 + \\ rjmp .L4 + \\ + \\.L3: + \\ st X+, r18 + \\ + \\.L4: + \\ cp r26, r24 + \\ cpc r27, r25 ; check and branch if we are at the end of bss + \\ brne .L3 + ); + // Probably a good idea to add clobbers here, but compiler doesn't seem to care + } +}; diff --git a/core/src/cpus/cortex-m.zig b/core/src/cpus/cortex-m.zig new file mode 100644 index 0000000..b9b5828 --- /dev/null +++ b/core/src/cpus/cortex-m.zig @@ -0,0 +1,293 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const mmio = microzig.mmio; +const root = @import("root"); + +pub const regs = struct { + // Interrupt Control and State Register + pub const ICSR: *volatile mmio.Mmio(packed struct { + VECTACTIVE: u9, + reserved0: u2, + RETTOBASE: u1, + VECTPENDING: u9, + reserved1: u1, + ISRPENDING: u1, + ISRPREEMPT: u1, + reserved2: u1, + PENDSTCLR: u1, + PENDSTSET: u1, + PENDSVCLR: u1, + PENDSVSET: u1, + reserved3: u2, + NMIPENDSET: u1, + }) = @ptrFromInt(0xE000ED04); +}; + +pub fn executing_isr() bool { + return regs.ICSR.read().VECTACTIVE != 0; +} + +pub fn enable_interrupts() void { + asm volatile ("cpsie i"); +} + +pub fn disable_interrupts() void { + asm volatile ("cpsid i"); +} + +pub fn enable_fault_irq() void { + asm volatile ("cpsie f"); +} +pub fn disable_fault_irq() void { + asm volatile ("cpsid f"); +} + +pub fn nop() void { + asm volatile ("nop"); +} +pub fn wfi() void { + asm volatile ("wfi"); +} +pub fn wfe() void { + asm volatile ("wfe"); +} +pub fn sev() void { + asm volatile ("sev"); +} +pub fn isb() void { + asm volatile ("isb"); +} +pub fn dsb() void { + asm volatile ("dsb"); +} +pub fn dmb() void { + asm volatile ("dmb"); +} +pub fn clrex() void { + asm volatile ("clrex"); +} + +pub const startup_logic = struct { + extern fn microzig_main() noreturn; + + // it looks odd to just use a u8 here, but in C it's common to use a + // char when linking these values from the linkerscript. What's + // important is the addresses of these values. + extern var microzig_data_start: u8; + extern var microzig_data_end: u8; + extern var microzig_bss_start: u8; + extern var microzig_bss_end: u8; + extern const microzig_data_load_start: u8; + + pub fn _start() callconv(.C) noreturn { + + // fill .bss with zeroes + { + const bss_start: [*]u8 = @ptrCast(µzig_bss_start); + const bss_end: [*]u8 = @ptrCast(µzig_bss_end); + const bss_len = @intFromPtr(bss_end) - @intFromPtr(bss_start); + + @memset(bss_start[0..bss_len], 0); + } + + // load .data from flash + { + const data_start: [*]u8 = @ptrCast(µzig_data_start); + const data_end: [*]u8 = @ptrCast(µzig_data_end); + const data_len = @intFromPtr(data_end) - @intFromPtr(data_start); + const data_src: [*]const u8 = @ptrCast(µzig_data_load_start); + + @memcpy(data_start[0..data_len], data_src[0..data_len]); + } + + microzig_main(); + } +}; + +fn is_valid_field(field_name: []const u8) bool { + return !std.mem.startsWith(u8, field_name, "reserved") and + !std.mem.eql(u8, field_name, "initial_stack_pointer") and + !std.mem.eql(u8, field_name, "reset"); +} + +const VectorTable = if (@hasDecl(root, "microzig_options") and @hasDecl(root.microzig_options, "VectorTable")) + root.microzig_options.VectorTable +else if (microzig.hal != void and @hasDecl(microzig.hal, "VectorTable")) + microzig.hal.VectorTable +else + microzig.chip.VectorTable; + +// will be imported by microzig.zig to allow system startup. +pub var vector_table: VectorTable = blk: { + var tmp: VectorTable = .{ + .initial_stack_pointer = microzig.config.end_of_stack, + .Reset = .{ .C = microzig.cpu.startup_logic._start }, + }; + if (@hasDecl(root, "microzig_options") and @hasDecl(root.microzig_options, "interrupts")) { + const interrupts = root.microzig_options.interrupts; + if (@typeInfo(interrupts) != .Struct) + @compileLog("root.interrupts must be a struct"); + + inline for (@typeInfo(interrupts).Struct.decls) |decl| { + const function = @field(interrupts, decl.name); + + if (!@hasField(VectorTable, decl.name)) { + var msg: []const u8 = "There is no such interrupt as '" ++ decl.name ++ "'. Declarations in 'interrupts' must be one of:\n"; + inline for (std.meta.fields(VectorTable)) |field| { + if (is_valid_field(field.name)) { + msg = msg ++ " " ++ field.name ++ "\n"; + } + } + + @compileError(msg); + } + + if (!is_valid_field(decl.name)) + @compileError("You are not allowed to specify '" ++ decl.name ++ "' in the vector table, for your sins you must now pay a $5 fine to the ZSF: https://github.com/sponsors/ziglang"); + + @field(tmp, decl.name) = create_interrupt_vector(function); + } + } + break :blk tmp; +}; + +fn create_interrupt_vector( + comptime function: anytype, +) microzig.interrupt.Handler { + const calling_convention = @typeInfo(@TypeOf(function)).Fn.calling_convention; + return switch (calling_convention) { + .C => .{ .C = function }, + .Naked => .{ .Naked = function }, + // for unspecified calling convention we are going to generate small wrapper + .Unspecified => .{ + .C = struct { + fn wrapper() callconv(.C) void { + if (calling_convention == .Unspecified) // TODO: workaround for some weird stage1 bug + @call(.always_inline, function, .{}); + } + }.wrapper, + }, + + else => |val| { + const conv_name = inline for (std.meta.fields(std.builtin.CallingConvention)) |field| { + if (val == @field(std.builtin.CallingConvention, field.name)) + break field.name; + } else unreachable; + + @compileError("unsupported calling convention for interrupt vector: " ++ conv_name); + }, + }; +} + +pub const peripherals = struct { + /// System Tick Timer + pub const SysTick = @as(*volatile types.peripherals.SysTick, @ptrFromInt(0xe000e010)); + + /// System Control Space + pub const NVIC = @compileError("TODO"); // @ptrFromInt(*volatile types.peripherals.NVIC, 0xe000e100); + + /// System Control Block + pub const SCB = @as(*volatile types.peripherals.SCB, @ptrFromInt(0xe000ed00)); +}; + +pub const types = struct { + pub const peripherals = struct { + /// System Tick Timer + pub const SysTick = extern struct { + /// SysTick Control and Status Register + CTRL: mmio.Mmio(packed struct(u32) { + ENABLE: u1, + TICKINT: u1, + CLKSOURCE: u1, + reserved16: u13, + COUNTFLAG: u1, + padding: u15, + }), + /// SysTick Reload Value Register + LOAD: mmio.Mmio(packed struct(u32) { + RELOAD: u24, + padding: u8, + }), + /// SysTick Current Value Register + VAL: mmio.Mmio(packed struct(u32) { + CURRENT: u24, + padding: u8, + }), + /// SysTick Calibration Register + CALIB: mmio.Mmio(packed struct(u32) { + TENMS: u24, + reserved30: u6, + SKEW: u1, + NOREF: u1, + }), + }; + + /// System Control Block + pub const SCB = extern struct { + CPUID: mmio.Mmio(packed struct(u32) { + REVISION: u4, + PARTNO: u12, + ARCHITECTURE: u4, + VARIANT: u4, + IMPLEMENTER: u8, + }), + /// Interrupt Control and State Register + ICSR: mmio.Mmio(packed struct(u32) { + VECTACTIVE: u9, + reserved12: u3, + VECTPENDING: u9, + reserved22: u1, + ISRPENDING: u1, + ISRPREEMPT: u1, + reserved25: u1, + PENDSTCLR: u1, + PENDSTSET: u1, + PENDSVCLR: u1, + PENDSVSET: u1, + reserved31: u2, + NMIPENDSET: u1, + }), + /// Vector Table Offset Register + VTOR: mmio.Mmio(packed struct(u32) { + reserved8: u8, + TBLOFF: u24, + }), + /// Application Interrupt and Reset Control Register + AIRCR: mmio.Mmio(packed struct(u32) { + reserved1: u1, + VECTCLRACTIVE: u1, + SYSRESETREQ: u1, + reserved15: u12, + ENDIANESS: u1, + VECTKEY: u16, + }), + /// System Control Register + SCR: mmio.Mmio(packed struct(u32) { + reserved1: u1, + SLEEPONEXIT: u1, + SLEEPDEEP: u1, + reserved4: u1, + SEVONPEND: u1, + padding: u27, + }), + /// Configuration Control Register + CCR: mmio.Mmio(packed struct(u32) { + reserved3: u3, + UNALIGN_TRP: u1, + reserved9: u5, + STKALIGN: u1, + padding: u22, + }), + reserved28: [4]u8, + /// System Handlers Priority Registers. [0] is RESERVED + SHP: u32, + reserved36: [4]u8, + /// System Handler Control and State Register + SHCSR: mmio.Mmio(packed struct(u32) { + reserved15: u15, + SVCALLPENDED: u1, + padding: u16, + }), + }; + }; +}; diff --git a/core/src/cpus/riscv32.zig b/core/src/cpus/riscv32.zig new file mode 100644 index 0000000..b7c1b96 --- /dev/null +++ b/core/src/cpus/riscv32.zig @@ -0,0 +1,907 @@ +const std = @import("std"); +const root = @import("root"); +const microzig = @import("microzig"); + +pub fn enable_interrupts() void { + @panic("TODO"); + // asm volatile ("sei"); +} + +pub fn disable_interrupts() void { + @panic("TODO"); + // asm volatile ("cli"); +} + +pub fn wfi() void { + asm volatile ("wfi"); +} + +pub fn wfe() void { + asm volatile ("csrs 0x810, 0x1"); + wfi(); + asm volatile ("csrs 0x810, 0x1"); +} + +pub fn pmp_open_all_space() void { + // Config entry0 addr to all 1s to make the range cover all space + asm volatile ("li x6, 0xffffffff" ::: "x6"); + asm volatile ("csrw pmpaddr0, x6"); + // Config entry0 cfg to make it NAPOT address mode, and R/W/X okay + asm volatile ("li x6, 0x7f" ::: "x6"); + asm volatile ("csrw pmpcfg0, x6"); +} + +pub fn switch_m2u_mode() void { + asm volatile ("la x6, 1f " ::: "x6"); + asm volatile ("csrw mepc, x6"); + asm volatile ("mret"); + asm volatile ("1:"); +} + +// riscv_xlen < 64 +pub const STORE = "sw"; +pub const LOAD = "lw"; +pub const LOG_REGBYTES = 2; +pub const REGBYTES = (1 << LOG_REGBYTES); + +pub const startup_logic = struct { + extern fn microzig_main() noreturn; + + // it looks odd to just use a u8 here, but in C it's common to use a + // char when linking these values from the linkerscript. What's + // important is the addresses of these values. + extern var microzig_data_start: u8; + extern var microzig_data_end: u8; + extern var microzig_bss_start: u8; + extern var microzig_bss_end: u8; + extern const microzig_data_load_start: u8; + + pub fn _start() callconv(.C) noreturn { + + // fill .bss with zeroes + { + const bss_start: [*]u8 = @ptrCast(µzig_bss_start); + const bss_end: [*]u8 = @ptrCast(µzig_bss_end); + const bss_len = @intFromPtr(bss_end) - @intFromPtr(bss_start); + + @memset(bss_start[0..bss_len], 0); + } + + // load .data from flash + { + const data_start: [*]u8 = @ptrCast(µzig_data_start); + const data_end: [*]u8 = @ptrCast(µzig_data_end); + const data_len = @intFromPtr(data_end) - @intFromPtr(data_start); + const data_src: [*]const u8 = @ptrCast(µzig_data_load_start); + + @memcpy(data_start[0..data_len], data_src[0..data_len]); + } + + microzig_main(); + } +}; + +pub const MSTATUS_UIE = 0x00000001; +pub const MSTATUS_SIE = 0x00000002; +pub const MSTATUS_HIE = 0x00000004; +pub const MSTATUS_MIE = 0x00000008; +pub const MSTATUS_UPIE = 0x00000010; +pub const MSTATUS_SPIE = 0x00000020; +pub const MSTATUS_HPIE = 0x00000040; +pub const MSTATUS_MPIE = 0x00000080; +pub const MSTATUS_SPP = 0x00000100; +pub const MSTATUS_MPP = 0x00001800; +pub const MSTATUS_FS = 0x00006000; +pub const MSTATUS_XS = 0x00018000; +pub const MSTATUS_MPRV = 0x00020000; +pub const MSTATUS_PUM = 0x00040000; +pub const MSTATUS_MXR = 0x00080000; +pub const MSTATUS_VM = 0x1F000000; +pub const MSTATUS32_SD = 0x80000000; +pub const MSTATUS64_SD = 0x8000000000000000; +pub const SSTATUS_UIE = 0x00000001; +pub const SSTATUS_SIE = 0x00000002; +pub const SSTATUS_UPIE = 0x00000010; +pub const SSTATUS_SPIE = 0x00000020; +pub const SSTATUS_SPP = 0x00000100; +pub const SSTATUS_FS = 0x00006000; +pub const SSTATUS_XS = 0x00018000; +pub const SSTATUS_PUM = 0x00040000; +pub const SSTATUS32_SD = 0x80000000; +pub const SSTATUS64_SD = 0x8000000000000000; +pub const DCSR_XDEBUGVER = 3 << 30; +pub const DCSR_NDRESET = 1 << 29; +pub const DCSR_FULLRESET = 1 << 28; +pub const DCSR_EBREAKM = 1 << 15; +pub const DCSR_EBREAKH = 1 << 14; +pub const DCSR_EBREAKS = 1 << 13; +pub const DCSR_EBREAKU = 1 << 12; +pub const DCSR_STOPCYCLE = 1 << 10; +pub const DCSR_STOPTIME = 1 << 9; +pub const DCSR_CAUSE = 7 << 6; +pub const DCSR_DEBUGINT = 1 << 5; +pub const DCSR_HALT = 1 << 3; +pub const DCSR_STEP = 1 << 2; +pub const DCSR_PRV = 3 << 0; +pub const DCSR_CAUSE_NONE = 0; +pub const DCSR_CAUSE_SWBP = 1; +pub const DCSR_CAUSE_HWBP = 2; +pub const DCSR_CAUSE_DEBUGINT = 3; +pub const DCSR_CAUSE_STEP = 4; +pub const DCSR_CAUSE_HALT = 5; +pub inline fn MCONTROL_TYPE(xlen: anytype) @TypeOf(0xf << (xlen - 4)) { + return 0xf << (xlen - 4); +} +pub inline fn MCONTROL_DMODE(xlen: anytype) @TypeOf(1 << (xlen - 5)) { + return 1 << (xlen - 5); +} +pub inline fn MCONTROL_MASKMAX(xlen: anytype) @TypeOf(0x3f << (xlen - 11)) { + return @as(c_ulonglong, 0x3f) << (xlen - 11); +} +pub const MCONTROL_SELECT = 1 << 19; +pub const MCONTROL_TIMING = 1 << 18; +pub const MCONTROL_ACTION = 0x3f << 12; +pub const MCONTROL_CHAIN = 1 << 11; +pub const MCONTROL_MATCH = 0xf << 7; +pub const MCONTROL_M = 1 << 6; +pub const MCONTROL_H = 1 << 5; +pub const MCONTROL_S = 1 << 4; +pub const MCONTROL_U = 1 << 3; +pub const MCONTROL_EXECUTE = 1 << 2; +pub const MCONTROL_STORE = 1 << 1; +pub const MCONTROL_LOAD = 1 << 0; +pub const MCONTROL_TYPE_NONE = 0; +pub const MCONTROL_TYPE_MATCH = 2; +pub const MCONTROL_ACTION_DEBUG_EXCEPTION = 0; +pub const MCONTROL_ACTION_DEBUG_MODE = 1; +pub const MCONTROL_ACTION_TRACE_START = 2; +pub const MCONTROL_ACTION_TRACE_STOP = 3; +pub const MCONTROL_ACTION_TRACE_EMIT = 4; +pub const MCONTROL_MATCH_EQUAL = 0; +pub const MCONTROL_MATCH_NAPOT = 1; +pub const MCONTROL_MATCH_GE = 2; +pub const MCONTROL_MATCH_LT = 3; +pub const MCONTROL_MATCH_MASK_LOW = 4; +pub const MCONTROL_MATCH_MASK_HIGH = 5; +pub const MIP_SSIP = 1 << IRQ_S_SOFT; +pub const MIP_HSIP = 1 << IRQ_H_SOFT; +pub const MIP_MSIP = 1 << IRQ_M_SOFT; +pub const MIP_STIP = 1 << IRQ_S_TIMER; +pub const MIP_HTIP = 1 << IRQ_H_TIMER; +pub const MIP_MTIP = 1 << IRQ_M_TIMER; +pub const MIP_SEIP = 1 << IRQ_S_EXT; +pub const MIP_HEIP = 1 << IRQ_H_EXT; +pub const MIP_MEIP = 1 << IRQ_M_EXT; +pub const MIE_SSIE = MIP_SSIP; +pub const MIE_HSIE = MIP_HSIP; +pub const MIE_MSIE = MIP_MSIP; +pub const MIE_STIE = MIP_STIP; +pub const MIE_HTIE = MIP_HTIP; +pub const MIE_MTIE = MIP_MTIP; +pub const MIE_SEIE = MIP_SEIP; +pub const MIE_HEIE = MIP_HEIP; +pub const MIE_MEIE = MIP_MEIP; +pub const SIP_SSIP = MIP_SSIP; +pub const SIP_STIP = MIP_STIP; +pub const PRV_U = 0; +pub const PRV_S = 1; +pub const PRV_H = 2; +pub const PRV_M = 3; +pub const VM_MBARE = 0; +pub const VM_MBB = 1; +pub const VM_MBBID = 2; +pub const VM_SV32 = 8; +pub const VM_SV39 = 9; +pub const VM_SV48 = 10; +pub const IRQ_S_SOFT = 1; +pub const IRQ_H_SOFT = 2; +pub const IRQ_M_SOFT = 3; +pub const IRQ_S_TIMER = 5; +pub const IRQ_H_TIMER = 6; +pub const IRQ_M_TIMER = 7; +pub const IRQ_S_EXT = 9; +pub const IRQ_H_EXT = 10; +pub const IRQ_M_EXT = 11; +pub const IRQ_COP = 12; +pub const IRQ_HOST = 13; +pub const DEFAULT_RSTVEC = 0x00001000; +pub const DEFAULT_NMIVEC = 0x00001004; +pub const DEFAULT_MTVEC = 0x00001010; +pub const CONFIG_STRING_ADDR = 0x0000100C; +pub const EXT_IO_BASE = 0x40000000; +pub const DRAM_BASE = 0x80000000; +pub const PTE_V = 0x001; +pub const PTE_R = 0x002; +pub const PTE_W = 0x004; +pub const PTE_X = 0x008; +pub const PTE_U = 0x010; +pub const PTE_G = 0x020; +pub const PTE_A = 0x040; +pub const PTE_D = 0x080; +pub const PTE_SOFT = 0x300; +pub const PTE_PPN_SHIFT = 10; +pub inline fn PTE_TABLE(PTE: anytype) @TypeOf((PTE & (((PTE_V | PTE_R) | PTE_W) | PTE_X)) == PTE_V) { + return (PTE & (((PTE_V | PTE_R) | PTE_W) | PTE_X)) == PTE_V; +} +pub const MSTATUS_SD = MSTATUS32_SD; +pub const SSTATUS_SD = SSTATUS32_SD; +pub const RISCV_PGLEVEL_BITS = 10; +pub const RISCV_PGSHIFT = 12; +pub const RISCV_PGSIZE = 1 << RISCV_PGSHIFT; +pub const RISCV_ENCODING_H = ""; +pub const MATCH_BEQ = 0x63; +pub const MASK_BEQ = 0x707f; +pub const MATCH_BNE = 0x1063; +pub const MASK_BNE = 0x707f; +pub const MATCH_BLT = 0x4063; +pub const MASK_BLT = 0x707f; +pub const MATCH_BGE = 0x5063; +pub const MASK_BGE = 0x707f; +pub const MATCH_BLTU = 0x6063; +pub const MASK_BLTU = 0x707f; +pub const MATCH_BGEU = 0x7063; +pub const MASK_BGEU = 0x707f; +pub const MATCH_JALR = 0x67; +pub const MASK_JALR = 0x707f; +pub const MATCH_JAL = 0x6f; +pub const MASK_JAL = 0x7f; +pub const MATCH_LUI = 0x37; +pub const MASK_LUI = 0x7f; +pub const MATCH_AUIPC = 0x17; +pub const MASK_AUIPC = 0x7f; +pub const MATCH_ADDI = 0x13; +pub const MASK_ADDI = 0x707f; +pub const MATCH_SLLI = 0x1013; +pub const MASK_SLLI = 0xfc00707f; +pub const MATCH_SLTI = 0x2013; +pub const MASK_SLTI = 0x707f; +pub const MATCH_SLTIU = 0x3013; +pub const MASK_SLTIU = 0x707f; +pub const MATCH_XORI = 0x4013; +pub const MASK_XORI = 0x707f; +pub const MATCH_SRLI = 0x5013; +pub const MASK_SRLI = 0xfc00707f; +pub const MATCH_SRAI = 0x40005013; +pub const MASK_SRAI = 0xfc00707f; +pub const MATCH_ORI = 0x6013; +pub const MASK_ORI = 0x707f; +pub const MATCH_ANDI = 0x7013; +pub const MASK_ANDI = 0x707f; +pub const MATCH_ADD = 0x33; +pub const MASK_ADD = 0xfe00707f; +pub const MATCH_SUB = 0x40000033; +pub const MASK_SUB = 0xfe00707f; +pub const MATCH_SLL = 0x1033; +pub const MASK_SLL = 0xfe00707f; +pub const MATCH_SLT = 0x2033; +pub const MASK_SLT = 0xfe00707f; +pub const MATCH_SLTU = 0x3033; +pub const MASK_SLTU = 0xfe00707f; +pub const MATCH_XOR = 0x4033; +pub const MASK_XOR = 0xfe00707f; +pub const MATCH_SRL = 0x5033; +pub const MASK_SRL = 0xfe00707f; +pub const MATCH_SRA = 0x40005033; +pub const MASK_SRA = 0xfe00707f; +pub const MATCH_OR = 0x6033; +pub const MASK_OR = 0xfe00707f; +pub const MATCH_AND = 0x7033; +pub const MASK_AND = 0xfe00707f; +pub const MATCH_ADDIW = 0x1b; +pub const MASK_ADDIW = 0x707f; +pub const MATCH_SLLIW = 0x101b; +pub const MASK_SLLIW = 0xfe00707f; +pub const MATCH_SRLIW = 0x501b; +pub const MASK_SRLIW = 0xfe00707f; +pub const MATCH_SRAIW = 0x4000501b; +pub const MASK_SRAIW = 0xfe00707f; +pub const MATCH_ADDW = 0x3b; +pub const MASK_ADDW = 0xfe00707f; +pub const MATCH_SUBW = 0x4000003b; +pub const MASK_SUBW = 0xfe00707f; +pub const MATCH_SLLW = 0x103b; +pub const MASK_SLLW = 0xfe00707f; +pub const MATCH_SRLW = 0x503b; +pub const MASK_SRLW = 0xfe00707f; +pub const MATCH_SRAW = 0x4000503b; +pub const MASK_SRAW = 0xfe00707f; +pub const MATCH_LB = 0x3; +pub const MASK_LB = 0x707f; +pub const MATCH_LH = 0x1003; +pub const MASK_LH = 0x707f; +pub const MATCH_LW = 0x2003; +pub const MASK_LW = 0x707f; +pub const MATCH_LD = 0x3003; +pub const MASK_LD = 0x707f; +pub const MATCH_LBU = 0x4003; +pub const MASK_LBU = 0x707f; +pub const MATCH_LHU = 0x5003; +pub const MASK_LHU = 0x707f; +pub const MATCH_LWU = 0x6003; +pub const MASK_LWU = 0x707f; +pub const MATCH_SB = 0x23; +pub const MASK_SB = 0x707f; +pub const MATCH_SH = 0x1023; +pub const MASK_SH = 0x707f; +pub const MATCH_SW = 0x2023; +pub const MASK_SW = 0x707f; +pub const MATCH_SD = 0x3023; +pub const MASK_SD = 0x707f; +pub const MATCH_FENCE = 0xf; +pub const MASK_FENCE = 0x707f; +pub const MATCH_FENCE_I = 0x100f; +pub const MASK_FENCE_I = 0x707f; +pub const MATCH_MUL = 0x2000033; +pub const MASK_MUL = 0xfe00707f; +pub const MATCH_MULH = 0x2001033; +pub const MASK_MULH = 0xfe00707f; +pub const MATCH_MULHSU = 0x2002033; +pub const MASK_MULHSU = 0xfe00707f; +pub const MATCH_MULHU = 0x2003033; +pub const MASK_MULHU = 0xfe00707f; +pub const MATCH_DIV = 0x2004033; +pub const MASK_DIV = 0xfe00707f; +pub const MATCH_DIVU = 0x2005033; +pub const MASK_DIVU = 0xfe00707f; +pub const MATCH_REM = 0x2006033; +pub const MASK_REM = 0xfe00707f; +pub const MATCH_REMU = 0x2007033; +pub const MASK_REMU = 0xfe00707f; +pub const MATCH_MULW = 0x200003b; +pub const MASK_MULW = 0xfe00707f; +pub const MATCH_DIVW = 0x200403b; +pub const MASK_DIVW = 0xfe00707f; +pub const MATCH_DIVUW = 0x200503b; +pub const MASK_DIVUW = 0xfe00707f; +pub const MATCH_REMW = 0x200603b; +pub const MASK_REMW = 0xfe00707f; +pub const MATCH_REMUW = 0x200703b; +pub const MASK_REMUW = 0xfe00707f; +pub const MATCH_AMOADD_W = 0x202f; +pub const MASK_AMOADD_W = 0xf800707f; +pub const MATCH_AMOXOR_W = 0x2000202f; +pub const MASK_AMOXOR_W = 0xf800707f; +pub const MATCH_AMOOR_W = 0x4000202f; +pub const MASK_AMOOR_W = 0xf800707f; +pub const MATCH_AMOAND_W = 0x6000202f; +pub const MASK_AMOAND_W = 0xf800707f; +pub const MATCH_AMOMIN_W = 0x8000202f; +pub const MASK_AMOMIN_W = 0xf800707f; +pub const MATCH_AMOMAX_W = 0xa000202f; +pub const MASK_AMOMAX_W = 0xf800707f; +pub const MATCH_AMOMINU_W = 0xc000202f; +pub const MASK_AMOMINU_W = 0xf800707f; +pub const MATCH_AMOMAXU_W = 0xe000202f; +pub const MASK_AMOMAXU_W = 0xf800707f; +pub const MATCH_AMOSWAP_W = 0x800202f; +pub const MASK_AMOSWAP_W = 0xf800707f; +pub const MATCH_LR_W = 0x1000202f; +pub const MASK_LR_W = 0xf9f0707f; +pub const MATCH_SC_W = 0x1800202f; +pub const MASK_SC_W = 0xf800707f; +pub const MATCH_AMOADD_D = 0x302f; +pub const MASK_AMOADD_D = 0xf800707f; +pub const MATCH_AMOXOR_D = 0x2000302f; +pub const MASK_AMOXOR_D = 0xf800707f; +pub const MATCH_AMOOR_D = 0x4000302f; +pub const MASK_AMOOR_D = 0xf800707f; +pub const MATCH_AMOAND_D = 0x6000302f; +pub const MASK_AMOAND_D = 0xf800707f; +pub const MATCH_AMOMIN_D = 0x8000302f; +pub const MASK_AMOMIN_D = 0xf800707f; +pub const MATCH_AMOMAX_D = 0xa000302f; +pub const MASK_AMOMAX_D = 0xf800707f; +pub const MATCH_AMOMINU_D = 0xc000302f; +pub const MASK_AMOMINU_D = 0xf800707f; +pub const MATCH_AMOMAXU_D = 0xe000302f; +pub const MASK_AMOMAXU_D = 0xf800707f; +pub const MATCH_AMOSWAP_D = 0x800302f; +pub const MASK_AMOSWAP_D = 0xf800707f; +pub const MATCH_LR_D = 0x1000302f; +pub const MASK_LR_D = 0xf9f0707f; +pub const MATCH_SC_D = 0x1800302f; +pub const MASK_SC_D = 0xf800707f; +pub const MATCH_ECALL = 0x73; +pub const MASK_ECALL = 0xffffffff; +pub const MATCH_EBREAK = 0x100073; +pub const MASK_EBREAK = 0xffffffff; +pub const MATCH_URET = 0x200073; +pub const MASK_URET = 0xffffffff; +pub const MATCH_SRET = 0x10200073; +pub const MASK_SRET = 0xffffffff; +pub const MATCH_HRET = 0x20200073; +pub const MASK_HRET = 0xffffffff; +pub const MATCH_MRET = 0x30200073; +pub const MASK_MRET = 0xffffffff; +pub const MATCH_DRET = 0x7b200073; +pub const MASK_DRET = 0xffffffff; +pub const MATCH_SFENCE_VM = 0x10400073; +pub const MASK_SFENCE_VM = 0xfff07fff; +pub const MATCH_WFI = 0x10500073; +pub const MASK_WFI = 0xffffffff; +pub const MATCH_CSRRW = 0x1073; +pub const MASK_CSRRW = 0x707f; +pub const MATCH_CSRRS = 0x2073; +pub const MASK_CSRRS = 0x707f; +pub const MATCH_CSRRC = 0x3073; +pub const MASK_CSRRC = 0x707f; +pub const MATCH_CSRRWI = 0x5073; +pub const MASK_CSRRWI = 0x707f; +pub const MATCH_CSRRSI = 0x6073; +pub const MASK_CSRRSI = 0x707f; +pub const MATCH_CSRRCI = 0x7073; +pub const MASK_CSRRCI = 0x707f; +pub const MATCH_FADD_S = 0x53; +pub const MASK_FADD_S = 0xfe00007f; +pub const MATCH_FSUB_S = 0x8000053; +pub const MASK_FSUB_S = 0xfe00007f; +pub const MATCH_FMUL_S = 0x10000053; +pub const MASK_FMUL_S = 0xfe00007f; +pub const MATCH_FDIV_S = 0x18000053; +pub const MASK_FDIV_S = 0xfe00007f; +pub const MATCH_FSGNJ_S = 0x20000053; +pub const MASK_FSGNJ_S = 0xfe00707f; +pub const MATCH_FSGNJN_S = 0x20001053; +pub const MASK_FSGNJN_S = 0xfe00707f; +pub const MATCH_FSGNJX_S = 0x20002053; +pub const MASK_FSGNJX_S = 0xfe00707f; +pub const MATCH_FMIN_S = 0x28000053; +pub const MASK_FMIN_S = 0xfe00707f; +pub const MATCH_FMAX_S = 0x28001053; +pub const MASK_FMAX_S = 0xfe00707f; +pub const MATCH_FSQRT_S = 0x58000053; +pub const MASK_FSQRT_S = 0xfff0007f; +pub const MATCH_FADD_D = 0x2000053; +pub const MASK_FADD_D = 0xfe00007f; +pub const MATCH_FSUB_D = 0xa000053; +pub const MASK_FSUB_D = 0xfe00007f; +pub const MATCH_FMUL_D = 0x12000053; +pub const MASK_FMUL_D = 0xfe00007f; +pub const MATCH_FDIV_D = 0x1a000053; +pub const MASK_FDIV_D = 0xfe00007f; +pub const MATCH_FSGNJ_D = 0x22000053; +pub const MASK_FSGNJ_D = 0xfe00707f; +pub const MATCH_FSGNJN_D = 0x22001053; +pub const MASK_FSGNJN_D = 0xfe00707f; +pub const MATCH_FSGNJX_D = 0x22002053; +pub const MASK_FSGNJX_D = 0xfe00707f; +pub const MATCH_FMIN_D = 0x2a000053; +pub const MASK_FMIN_D = 0xfe00707f; +pub const MATCH_FMAX_D = 0x2a001053; +pub const MASK_FMAX_D = 0xfe00707f; +pub const MATCH_FCVT_S_D = 0x40100053; +pub const MASK_FCVT_S_D = 0xfff0007f; +pub const MATCH_FCVT_D_S = 0x42000053; +pub const MASK_FCVT_D_S = 0xfff0007f; +pub const MATCH_FSQRT_D = 0x5a000053; +pub const MASK_FSQRT_D = 0xfff0007f; +pub const MATCH_FLE_S = 0xa0000053; +pub const MASK_FLE_S = 0xfe00707f; +pub const MATCH_FLT_S = 0xa0001053; +pub const MASK_FLT_S = 0xfe00707f; +pub const MATCH_FEQ_S = 0xa0002053; +pub const MASK_FEQ_S = 0xfe00707f; +pub const MATCH_FLE_D = 0xa2000053; +pub const MASK_FLE_D = 0xfe00707f; +pub const MATCH_FLT_D = 0xa2001053; +pub const MASK_FLT_D = 0xfe00707f; +pub const MATCH_FEQ_D = 0xa2002053; +pub const MASK_FEQ_D = 0xfe00707f; +pub const MATCH_FCVT_W_S = 0xc0000053; +pub const MASK_FCVT_W_S = 0xfff0007f; +pub const MATCH_FCVT_WU_S = 0xc0100053; +pub const MASK_FCVT_WU_S = 0xfff0007f; +pub const MATCH_FCVT_L_S = 0xc0200053; +pub const MASK_FCVT_L_S = 0xfff0007f; +pub const MATCH_FCVT_LU_S = 0xc0300053; +pub const MASK_FCVT_LU_S = 0xfff0007f; +pub const MATCH_FMV_X_S = 0xe0000053; +pub const MASK_FMV_X_S = 0xfff0707f; +pub const MATCH_FCLASS_S = 0xe0001053; +pub const MASK_FCLASS_S = 0xfff0707f; +pub const MATCH_FCVT_W_D = 0xc2000053; +pub const MASK_FCVT_W_D = 0xfff0007f; +pub const MATCH_FCVT_WU_D = 0xc2100053; +pub const MASK_FCVT_WU_D = 0xfff0007f; +pub const MATCH_FCVT_L_D = 0xc2200053; +pub const MASK_FCVT_L_D = 0xfff0007f; +pub const MATCH_FCVT_LU_D = 0xc2300053; +pub const MASK_FCVT_LU_D = 0xfff0007f; +pub const MATCH_FMV_X_D = 0xe2000053; +pub const MASK_FMV_X_D = 0xfff0707f; +pub const MATCH_FCLASS_D = 0xe2001053; +pub const MASK_FCLASS_D = 0xfff0707f; +pub const MATCH_FCVT_S_W = 0xd0000053; +pub const MASK_FCVT_S_W = 0xfff0007f; +pub const MATCH_FCVT_S_WU = 0xd0100053; +pub const MASK_FCVT_S_WU = 0xfff0007f; +pub const MATCH_FCVT_S_L = 0xd0200053; +pub const MASK_FCVT_S_L = 0xfff0007f; +pub const MATCH_FCVT_S_LU = 0xd0300053; +pub const MASK_FCVT_S_LU = 0xfff0007f; +pub const MATCH_FMV_S_X = 0xf0000053; +pub const MASK_FMV_S_X = 0xfff0707f; +pub const MATCH_FCVT_D_W = 0xd2000053; +pub const MASK_FCVT_D_W = 0xfff0007f; +pub const MATCH_FCVT_D_WU = 0xd2100053; +pub const MASK_FCVT_D_WU = 0xfff0007f; +pub const MATCH_FCVT_D_L = 0xd2200053; +pub const MASK_FCVT_D_L = 0xfff0007f; +pub const MATCH_FCVT_D_LU = 0xd2300053; +pub const MASK_FCVT_D_LU = 0xfff0007f; +pub const MATCH_FMV_D_X = 0xf2000053; +pub const MASK_FMV_D_X = 0xfff0707f; +pub const MATCH_FLW = 0x2007; +pub const MASK_FLW = 0x707f; +pub const MATCH_FLD = 0x3007; +pub const MASK_FLD = 0x707f; +pub const MATCH_FSW = 0x2027; +pub const MASK_FSW = 0x707f; +pub const MATCH_FSD = 0x3027; +pub const MASK_FSD = 0x707f; +pub const MATCH_FMADD_S = 0x43; +pub const MASK_FMADD_S = 0x600007f; +pub const MATCH_FMSUB_S = 0x47; +pub const MASK_FMSUB_S = 0x600007f; +pub const MATCH_FNMSUB_S = 0x4b; +pub const MASK_FNMSUB_S = 0x600007f; +pub const MATCH_FNMADD_S = 0x4f; +pub const MASK_FNMADD_S = 0x600007f; +pub const MATCH_FMADD_D = 0x2000043; +pub const MASK_FMADD_D = 0x600007f; +pub const MATCH_FMSUB_D = 0x2000047; +pub const MASK_FMSUB_D = 0x600007f; +pub const MATCH_FNMSUB_D = 0x200004b; +pub const MASK_FNMSUB_D = 0x600007f; +pub const MATCH_FNMADD_D = 0x200004f; +pub const MASK_FNMADD_D = 0x600007f; +pub const MATCH_C_NOP = 0x1; +pub const MASK_C_NOP = 0xffff; +pub const MATCH_C_ADDI16SP = 0x6101; +pub const MASK_C_ADDI16SP = 0xef83; +pub const MATCH_C_JR = 0x8002; +pub const MASK_C_JR = 0xf07f; +pub const MATCH_C_JALR = 0x9002; +pub const MASK_C_JALR = 0xf07f; +pub const MATCH_C_EBREAK = 0x9002; +pub const MASK_C_EBREAK = 0xffff; +pub const MATCH_C_LD = 0x6000; +pub const MASK_C_LD = 0xe003; +pub const MATCH_C_SD = 0xe000; +pub const MASK_C_SD = 0xe003; +pub const MATCH_C_ADDIW = 0x2001; +pub const MASK_C_ADDIW = 0xe003; +pub const MATCH_C_LDSP = 0x6002; +pub const MASK_C_LDSP = 0xe003; +pub const MATCH_C_SDSP = 0xe002; +pub const MASK_C_SDSP = 0xe003; +pub const MATCH_C_ADDI4SPN = 0x0; +pub const MASK_C_ADDI4SPN = 0xe003; +pub const MATCH_C_FLD = 0x2000; +pub const MASK_C_FLD = 0xe003; +pub const MATCH_C_LW = 0x4000; +pub const MASK_C_LW = 0xe003; +pub const MATCH_C_FLW = 0x6000; +pub const MASK_C_FLW = 0xe003; +pub const MATCH_C_FSD = 0xa000; +pub const MASK_C_FSD = 0xe003; +pub const MATCH_C_SW = 0xc000; +pub const MASK_C_SW = 0xe003; +pub const MATCH_C_FSW = 0xe000; +pub const MASK_C_FSW = 0xe003; +pub const MATCH_C_ADDI = 0x1; +pub const MASK_C_ADDI = 0xe003; +pub const MATCH_C_JAL = 0x2001; +pub const MASK_C_JAL = 0xe003; +pub const MATCH_C_LI = 0x4001; +pub const MASK_C_LI = 0xe003; +pub const MATCH_C_LUI = 0x6001; +pub const MASK_C_LUI = 0xe003; +pub const MATCH_C_SRLI = 0x8001; +pub const MASK_C_SRLI = 0xec03; +pub const MATCH_C_SRAI = 0x8401; +pub const MASK_C_SRAI = 0xec03; +pub const MATCH_C_ANDI = 0x8801; +pub const MASK_C_ANDI = 0xec03; +pub const MATCH_C_SUB = 0x8c01; +pub const MASK_C_SUB = 0xfc63; +pub const MATCH_C_XOR = 0x8c21; +pub const MASK_C_XOR = 0xfc63; +pub const MATCH_C_OR = 0x8c41; +pub const MASK_C_OR = 0xfc63; +pub const MATCH_C_AND = 0x8c61; +pub const MASK_C_AND = 0xfc63; +pub const MATCH_C_SUBW = 0x9c01; +pub const MASK_C_SUBW = 0xfc63; +pub const MATCH_C_ADDW = 0x9c21; +pub const MASK_C_ADDW = 0xfc63; +pub const MATCH_C_J = 0xa001; +pub const MASK_C_J = 0xe003; +pub const MATCH_C_BEQZ = 0xc001; +pub const MASK_C_BEQZ = 0xe003; +pub const MATCH_C_BNEZ = 0xe001; +pub const MASK_C_BNEZ = 0xe003; +pub const MATCH_C_SLLI = 0x2; +pub const MASK_C_SLLI = 0xe003; +pub const MATCH_C_FLDSP = 0x2002; +pub const MASK_C_FLDSP = 0xe003; +pub const MATCH_C_LWSP = 0x4002; +pub const MASK_C_LWSP = 0xe003; +pub const MATCH_C_FLWSP = 0x6002; +pub const MASK_C_FLWSP = 0xe003; +pub const MATCH_C_MV = 0x8002; +pub const MASK_C_MV = 0xf003; +pub const MATCH_C_ADD = 0x9002; +pub const MASK_C_ADD = 0xf003; +pub const MATCH_C_FSDSP = 0xa002; +pub const MASK_C_FSDSP = 0xe003; +pub const MATCH_C_SWSP = 0xc002; +pub const MASK_C_SWSP = 0xe003; +pub const MATCH_C_FSWSP = 0xe002; +pub const MASK_C_FSWSP = 0xe003; +pub const MATCH_CUSTOM0 = 0xb; +pub const MASK_CUSTOM0 = 0x707f; +pub const MATCH_CUSTOM0_RS1 = 0x200b; +pub const MASK_CUSTOM0_RS1 = 0x707f; +pub const MATCH_CUSTOM0_RS1_RS2 = 0x300b; +pub const MASK_CUSTOM0_RS1_RS2 = 0x707f; +pub const MATCH_CUSTOM0_RD = 0x400b; +pub const MASK_CUSTOM0_RD = 0x707f; +pub const MATCH_CUSTOM0_RD_RS1 = 0x600b; +pub const MASK_CUSTOM0_RD_RS1 = 0x707f; +pub const MATCH_CUSTOM0_RD_RS1_RS2 = 0x700b; +pub const MASK_CUSTOM0_RD_RS1_RS2 = 0x707f; +pub const MATCH_CUSTOM1 = 0x2b; +pub const MASK_CUSTOM1 = 0x707f; +pub const MATCH_CUSTOM1_RS1 = 0x202b; +pub const MASK_CUSTOM1_RS1 = 0x707f; +pub const MATCH_CUSTOM1_RS1_RS2 = 0x302b; +pub const MASK_CUSTOM1_RS1_RS2 = 0x707f; +pub const MATCH_CUSTOM1_RD = 0x402b; +pub const MASK_CUSTOM1_RD = 0x707f; +pub const MATCH_CUSTOM1_RD_RS1 = 0x602b; +pub const MASK_CUSTOM1_RD_RS1 = 0x707f; +pub const MATCH_CUSTOM1_RD_RS1_RS2 = 0x702b; +pub const MASK_CUSTOM1_RD_RS1_RS2 = 0x707f; +pub const MATCH_CUSTOM2 = 0x5b; +pub const MASK_CUSTOM2 = 0x707f; +pub const MATCH_CUSTOM2_RS1 = 0x205b; +pub const MASK_CUSTOM2_RS1 = 0x707f; +pub const MATCH_CUSTOM2_RS1_RS2 = 0x305b; +pub const MASK_CUSTOM2_RS1_RS2 = 0x707f; +pub const MATCH_CUSTOM2_RD = 0x405b; +pub const MASK_CUSTOM2_RD = 0x707f; +pub const MATCH_CUSTOM2_RD_RS1 = 0x605b; +pub const MASK_CUSTOM2_RD_RS1 = 0x707f; +pub const MATCH_CUSTOM2_RD_RS1_RS2 = 0x705b; +pub const MASK_CUSTOM2_RD_RS1_RS2 = 0x707f; +pub const MATCH_CUSTOM3 = 0x7b; +pub const MASK_CUSTOM3 = 0x707f; +pub const MATCH_CUSTOM3_RS1 = 0x207b; +pub const MASK_CUSTOM3_RS1 = 0x707f; +pub const MATCH_CUSTOM3_RS1_RS2 = 0x307b; +pub const MASK_CUSTOM3_RS1_RS2 = 0x707f; +pub const MATCH_CUSTOM3_RD = 0x407b; +pub const MASK_CUSTOM3_RD = 0x707f; +pub const MATCH_CUSTOM3_RD_RS1 = 0x607b; +pub const MASK_CUSTOM3_RD_RS1 = 0x707f; +pub const MATCH_CUSTOM3_RD_RS1_RS2 = 0x707b; +pub const MASK_CUSTOM3_RD_RS1_RS2 = 0x707f; +pub const CSR_FFLAGS = 0x1; +pub const CSR_FRM = 0x2; +pub const CSR_FCSR = 0x3; +pub const CSR_CYCLE = 0xc00; +pub const CSR_TIME = 0xc01; +pub const CSR_INSTRET = 0xc02; +pub const CSR_HPMCOUNTER3 = 0xc03; +pub const CSR_HPMCOUNTER4 = 0xc04; +pub const CSR_HPMCOUNTER5 = 0xc05; +pub const CSR_HPMCOUNTER6 = 0xc06; +pub const CSR_HPMCOUNTER7 = 0xc07; +pub const CSR_HPMCOUNTER8 = 0xc08; +pub const CSR_HPMCOUNTER9 = 0xc09; +pub const CSR_HPMCOUNTER10 = 0xc0a; +pub const CSR_HPMCOUNTER11 = 0xc0b; +pub const CSR_HPMCOUNTER12 = 0xc0c; +pub const CSR_HPMCOUNTER13 = 0xc0d; +pub const CSR_HPMCOUNTER14 = 0xc0e; +pub const CSR_HPMCOUNTER15 = 0xc0f; +pub const CSR_HPMCOUNTER16 = 0xc10; +pub const CSR_HPMCOUNTER17 = 0xc11; +pub const CSR_HPMCOUNTER18 = 0xc12; +pub const CSR_HPMCOUNTER19 = 0xc13; +pub const CSR_HPMCOUNTER20 = 0xc14; +pub const CSR_HPMCOUNTER21 = 0xc15; +pub const CSR_HPMCOUNTER22 = 0xc16; +pub const CSR_HPMCOUNTER23 = 0xc17; +pub const CSR_HPMCOUNTER24 = 0xc18; +pub const CSR_HPMCOUNTER25 = 0xc19; +pub const CSR_HPMCOUNTER26 = 0xc1a; +pub const CSR_HPMCOUNTER27 = 0xc1b; +pub const CSR_HPMCOUNTER28 = 0xc1c; +pub const CSR_HPMCOUNTER29 = 0xc1d; +pub const CSR_HPMCOUNTER30 = 0xc1e; +pub const CSR_HPMCOUNTER31 = 0xc1f; +pub const CSR_SSTATUS = 0x100; +pub const CSR_SIE = 0x104; +pub const CSR_STVEC = 0x105; +pub const CSR_SSCRATCH = 0x140; +pub const CSR_SEPC = 0x141; +pub const CSR_SCAUSE = 0x142; +pub const CSR_SBADADDR = 0x143; +pub const CSR_SIP = 0x144; +pub const CSR_SPTBR = 0x180; +pub const CSR_MSTATUS = 0x300; +pub const CSR_MISA = 0x301; +pub const CSR_MEDELEG = 0x302; +pub const CSR_MIDELEG = 0x303; +pub const CSR_MIE = 0x304; +pub const CSR_MTVEC = 0x305; +pub const CSR_MCOUNTEREN = 0x306; +pub const CSR_MSCRATCH = 0x340; +pub const CSR_MEPC = 0x341; +pub const CSR_MCAUSE = 0x342; +pub const CSR_MBADADDR = 0x343; +pub const CSR_MIP = 0x344; +pub const CSR_TSELECT = 0x7a0; +pub const CSR_TDATA1 = 0x7a1; +pub const CSR_TDATA2 = 0x7a2; +pub const CSR_TDATA3 = 0x7a3; +pub const CSR_DCSR = 0x7b0; +pub const CSR_DPC = 0x7b1; +pub const CSR_DSCRATCH = 0x7b2; +pub const CSR_MCYCLE = 0xb00; +pub const CSR_MINSTRET = 0xb02; +pub const CSR_MHPMCOUNTER3 = 0xb03; +pub const CSR_MHPMCOUNTER4 = 0xb04; +pub const CSR_MHPMCOUNTER5 = 0xb05; +pub const CSR_MHPMCOUNTER6 = 0xb06; +pub const CSR_MHPMCOUNTER7 = 0xb07; +pub const CSR_MHPMCOUNTER8 = 0xb08; +pub const CSR_MHPMCOUNTER9 = 0xb09; +pub const CSR_MHPMCOUNTER10 = 0xb0a; +pub const CSR_MHPMCOUNTER11 = 0xb0b; +pub const CSR_MHPMCOUNTER12 = 0xb0c; +pub const CSR_MHPMCOUNTER13 = 0xb0d; +pub const CSR_MHPMCOUNTER14 = 0xb0e; +pub const CSR_MHPMCOUNTER15 = 0xb0f; +pub const CSR_MHPMCOUNTER16 = 0xb10; +pub const CSR_MHPMCOUNTER17 = 0xb11; +pub const CSR_MHPMCOUNTER18 = 0xb12; +pub const CSR_MHPMCOUNTER19 = 0xb13; +pub const CSR_MHPMCOUNTER20 = 0xb14; +pub const CSR_MHPMCOUNTER21 = 0xb15; +pub const CSR_MHPMCOUNTER22 = 0xb16; +pub const CSR_MHPMCOUNTER23 = 0xb17; +pub const CSR_MHPMCOUNTER24 = 0xb18; +pub const CSR_MHPMCOUNTER25 = 0xb19; +pub const CSR_MHPMCOUNTER26 = 0xb1a; +pub const CSR_MHPMCOUNTER27 = 0xb1b; +pub const CSR_MHPMCOUNTER28 = 0xb1c; +pub const CSR_MHPMCOUNTER29 = 0xb1d; +pub const CSR_MHPMCOUNTER30 = 0xb1e; +pub const CSR_MHPMCOUNTER31 = 0xb1f; +pub const CSR_MUCOUNTEREN = 0x320; +pub const CSR_MSCOUNTEREN = 0x321; +pub const CSR_MHPMEVENT3 = 0x323; +pub const CSR_MHPMEVENT4 = 0x324; +pub const CSR_MHPMEVENT5 = 0x325; +pub const CSR_MHPMEVENT6 = 0x326; +pub const CSR_MHPMEVENT7 = 0x327; +pub const CSR_MHPMEVENT8 = 0x328; +pub const CSR_MHPMEVENT9 = 0x329; +pub const CSR_MHPMEVENT10 = 0x32a; +pub const CSR_MHPMEVENT11 = 0x32b; +pub const CSR_MHPMEVENT12 = 0x32c; +pub const CSR_MHPMEVENT13 = 0x32d; +pub const CSR_MHPMEVENT14 = 0x32e; +pub const CSR_MHPMEVENT15 = 0x32f; +pub const CSR_MHPMEVENT16 = 0x330; +pub const CSR_MHPMEVENT17 = 0x331; +pub const CSR_MHPMEVENT18 = 0x332; +pub const CSR_MHPMEVENT19 = 0x333; +pub const CSR_MHPMEVENT20 = 0x334; +pub const CSR_MHPMEVENT21 = 0x335; +pub const CSR_MHPMEVENT22 = 0x336; +pub const CSR_MHPMEVENT23 = 0x337; +pub const CSR_MHPMEVENT24 = 0x338; +pub const CSR_MHPMEVENT25 = 0x339; +pub const CSR_MHPMEVENT26 = 0x33a; +pub const CSR_MHPMEVENT27 = 0x33b; +pub const CSR_MHPMEVENT28 = 0x33c; +pub const CSR_MHPMEVENT29 = 0x33d; +pub const CSR_MHPMEVENT30 = 0x33e; +pub const CSR_MHPMEVENT31 = 0x33f; +pub const CSR_MVENDORID = 0xf11; +pub const CSR_MARCHID = 0xf12; +pub const CSR_MIMPID = 0xf13; +pub const CSR_MHARTID = 0xf14; +pub const CSR_CYCLEH = 0xc80; +pub const CSR_TIMEH = 0xc81; +pub const CSR_INSTRETH = 0xc82; +pub const CSR_HPMCOUNTER3H = 0xc83; +pub const CSR_HPMCOUNTER4H = 0xc84; +pub const CSR_HPMCOUNTER5H = 0xc85; +pub const CSR_HPMCOUNTER6H = 0xc86; +pub const CSR_HPMCOUNTER7H = 0xc87; +pub const CSR_HPMCOUNTER8H = 0xc88; +pub const CSR_HPMCOUNTER9H = 0xc89; +pub const CSR_HPMCOUNTER10H = 0xc8a; +pub const CSR_HPMCOUNTER11H = 0xc8b; +pub const CSR_HPMCOUNTER12H = 0xc8c; +pub const CSR_HPMCOUNTER13H = 0xc8d; +pub const CSR_HPMCOUNTER14H = 0xc8e; +pub const CSR_HPMCOUNTER15H = 0xc8f; +pub const CSR_HPMCOUNTER16H = 0xc90; +pub const CSR_HPMCOUNTER17H = 0xc91; +pub const CSR_HPMCOUNTER18H = 0xc92; +pub const CSR_HPMCOUNTER19H = 0xc93; +pub const CSR_HPMCOUNTER20H = 0xc94; +pub const CSR_HPMCOUNTER21H = 0xc95; +pub const CSR_HPMCOUNTER22H = 0xc96; +pub const CSR_HPMCOUNTER23H = 0xc97; +pub const CSR_HPMCOUNTER24H = 0xc98; +pub const CSR_HPMCOUNTER25H = 0xc99; +pub const CSR_HPMCOUNTER26H = 0xc9a; +pub const CSR_HPMCOUNTER27H = 0xc9b; +pub const CSR_HPMCOUNTER28H = 0xc9c; +pub const CSR_HPMCOUNTER29H = 0xc9d; +pub const CSR_HPMCOUNTER30H = 0xc9e; +pub const CSR_HPMCOUNTER31H = 0xc9f; +pub const CSR_MCYCLEH = 0xb80; +pub const CSR_MINSTRETH = 0xb82; +pub const CSR_MHPMCOUNTER3H = 0xb83; +pub const CSR_MHPMCOUNTER4H = 0xb84; +pub const CSR_MHPMCOUNTER5H = 0xb85; +pub const CSR_MHPMCOUNTER6H = 0xb86; +pub const CSR_MHPMCOUNTER7H = 0xb87; +pub const CSR_MHPMCOUNTER8H = 0xb88; +pub const CSR_MHPMCOUNTER9H = 0xb89; +pub const CSR_MHPMCOUNTER10H = 0xb8a; +pub const CSR_MHPMCOUNTER11H = 0xb8b; +pub const CSR_MHPMCOUNTER12H = 0xb8c; +pub const CSR_MHPMCOUNTER13H = 0xb8d; +pub const CSR_MHPMCOUNTER14H = 0xb8e; +pub const CSR_MHPMCOUNTER15H = 0xb8f; +pub const CSR_MHPMCOUNTER16H = 0xb90; +pub const CSR_MHPMCOUNTER17H = 0xb91; +pub const CSR_MHPMCOUNTER18H = 0xb92; +pub const CSR_MHPMCOUNTER19H = 0xb93; +pub const CSR_MHPMCOUNTER20H = 0xb94; +pub const CSR_MHPMCOUNTER21H = 0xb95; +pub const CSR_MHPMCOUNTER22H = 0xb96; +pub const CSR_MHPMCOUNTER23H = 0xb97; +pub const CSR_MHPMCOUNTER24H = 0xb98; +pub const CSR_MHPMCOUNTER25H = 0xb99; +pub const CSR_MHPMCOUNTER26H = 0xb9a; +pub const CSR_MHPMCOUNTER27H = 0xb9b; +pub const CSR_MHPMCOUNTER28H = 0xb9c; +pub const CSR_MHPMCOUNTER29H = 0xb9d; +pub const CSR_MHPMCOUNTER30H = 0xb9e; +pub const CSR_MHPMCOUNTER31H = 0xb9f; +pub const CSR_MTVT = 0x307; +pub const CSR_MNXTI = 0x345; +pub const CSR_MCOUNTINHIBIT = 0x320; +pub const CSR_MNVEC = 0x7C3; +pub const CSR_MTVT2 = 0x7EC; +pub const CSR_JALMNXTI = 0x7ED; +pub const CSR_PUSHMCAUSE = 0x7EE; +pub const CSR_PUSHMEPC = 0x7EF; +pub const CSR_PUSHMSUBM = 0x7EB; +pub const CSR_WFE = 0x810; +pub const CSR_SLEEPVALUE = 0x811; +pub const CSR_TXEVT = 0x812; +pub const CSR_MMISC_CTL = 0x7d0; +pub const CSR_MSUBM = 0x7c4; +pub const CAUSE_MISALIGNED_FETCH = 0x0; +pub const CAUSE_FAULT_FETCH = 0x1; +pub const CAUSE_ILLEGAL_INSTRUCTION = 0x2; +pub const CAUSE_BREAKPOINT = 0x3; +pub const CAUSE_MISALIGNED_LOAD = 0x4; +pub const CAUSE_FAULT_LOAD = 0x5; +pub const CAUSE_MISALIGNED_STORE = 0x6; +pub const CAUSE_FAULT_STORE = 0x7; +pub const CAUSE_USER_ECALL = 0x8; +pub const CAUSE_SUPERVISOR_ECALL = 0x9; +pub const CAUSE_HYPERVISOR_ECALL = 0xa; +pub const CAUSE_MACHINE_ECALL = 0xb; diff --git a/core/src/drivers.zig b/core/src/drivers.zig new file mode 100644 index 0000000..6ed2897 --- /dev/null +++ b/core/src/drivers.zig @@ -0,0 +1 @@ +pub const experimental = @import("drivers/experimental.zig"); diff --git a/core/src/drivers/experimental.zig b/core/src/drivers/experimental.zig new file mode 100644 index 0000000..a3ec727 --- /dev/null +++ b/core/src/drivers/experimental.zig @@ -0,0 +1,7 @@ +//! These are experimental driver interfaces. We want to see more use and +//! discussion of them before committing them to microzig's "official" API. +//! +//! They are bound to have breaking changes in the future, or even disappear, +//! so use at your own risk. +pub const button = @import("experimental/button.zig"); +pub const quadrature = @import("experimental/quadrature.zig"); diff --git a/core/src/drivers/experimental/button.zig b/core/src/drivers/experimental/button.zig new file mode 100644 index 0000000..7667ac6 --- /dev/null +++ b/core/src/drivers/experimental/button.zig @@ -0,0 +1,67 @@ +const std = @import("std"); +const micro = @import("microzig"); + +pub const Event = enum { + /// Nothing has changed. + idle, + + /// The button was pressed. Will only trigger once per press. + /// Use `Button.isPressed()` to check if the button is currently held. + pressed, + + /// The button was released. Will only trigger once per release. + /// Use `Button.isPressed()` to check if the button is currently held. + released, +}; + +pub fn Button( + /// The GPIO pin the button is connected to. Will be initialized when calling Button.init + comptime gpio: type, + /// The active state for the button. Use `.high` for active-high, `.low` for active-low. + comptime active_state: micro.gpio.State, + /// Optional filter depth for debouncing. If `null` is passed, 16 samples are used to debounce the button, + /// otherwise the given number of samples is used. + comptime filter_depth: ?comptime_int, +) type { + return struct { + const Self = @This(); + const DebounceFilter = std.meta.Int(.unsigned, filter_depth orelse 16); + + debounce: DebounceFilter, + state: micro.gpio.State, + + pub fn init() Self { + gpio.init(); + return Self{ + .debounce = 0, + .state = gpio.read(), + }; + } + + /// Polls for the button state. Returns the change event for the button if any. + pub fn poll(self: *Self) Event { + const state = gpio.read(); + const active_unfiltered = (state == active_state); + + const previous_debounce = self.debounce; + self.debounce <<= 1; + if (active_unfiltered) { + self.debounce |= 1; + } + + if (active_unfiltered and previous_debounce == 0) { + return .pressed; + } else if (!active_unfiltered and self.debounce == 0 and previous_debounce != 0) { + return .released; + } else { + return .idle; + } + } + + /// Returns `true` when the button is pressed. + /// Will only be updated when `poll` is regularly called. + pub fn is_pressed(self: *Self) bool { + return (self.debounce != 0); + } + }; +} diff --git a/core/src/drivers/experimental/quadrature.zig b/core/src/drivers/experimental/quadrature.zig new file mode 100644 index 0000000..c76ecab --- /dev/null +++ b/core/src/drivers/experimental/quadrature.zig @@ -0,0 +1,50 @@ +const micro = @import("microzig"); + +pub const Event = enum { + /// No change since the last decoding happened + idle, + /// The quadrature signal incremented a step. + increment, + /// The quadrature signal decremented a step. + decrement, + /// The quadrature signal skipped a sequence point and entered a invalid state. + @"error", +}; + +pub fn Decoder(comptime pin_a: type, comptime pin_b: type) type { + return struct { + const Self = @This(); + + last_a: micro.gpio.State, + last_b: micro.gpio.State, + + pub fn init() Self { + pin_a.init(); + pin_b.init(); + return Self{ + .last_a = pin_a.read(), + .last_b = pin_b.read(), + }; + } + + pub fn tick(self: *Self) Event { + var a = pin_a.read(); + var b = pin_b.read(); + defer self.last_a = a; + defer self.last_b = b; + + const enable = a.value() ^ b.value() ^ self.last_a.value() ^ self.last_b.value(); + const direction = a.value() ^ self.last_b.value(); + + if (enable != 0) { + if (direction != 0) { + return .increment; + } else { + return .decrement; + } + } else { + return .idle; + } + } + }; +} diff --git a/core/src/interrupt.zig b/core/src/interrupt.zig new file mode 100644 index 0000000..1be5c91 --- /dev/null +++ b/core/src/interrupt.zig @@ -0,0 +1,76 @@ +const std = @import("std"); +const micro = @import("microzig.zig"); + +/// Unmasks the given interrupt and enables its execution. +/// Note that interrupts must be globally enabled with `sei()` as well. +pub fn enable(comptime interrupt: anytype) void { + _ = interrupt; + @compileError("not implemented yet!"); +} + +/// Masks the given interrupt and disables its execution. +pub fn disable(comptime interrupt: anytype) void { + _ = interrupt; + @compileError("not implemented yet!"); +} + +/// Returns true when the given interrupt is unmasked. +pub fn is_enabled(comptime interrupt: anytype) bool { + _ = interrupt; + @compileError("not implemented yet!"); +} + +/// *Set Enable Interrupt*, will enable IRQs globally, but keep the masking done via +/// `enable` and `disable` intact. +pub fn enable_interrupts() void { + micro.cpu.enable_interrupts(); +} + +/// *Clear Enable Interrupt*, will disable IRQs globally, but keep the masking done via +/// `enable` and `disable` intact. +pub fn disable_interrupts() void { + micro.cpu.disable_interrupts(); +} + +/// Returns true, when interrupts are globally enabled via `sei()`. +pub fn globally_enabled() bool { + @compileError("not implemented yet!"); +} + +/// Enters a critical section and disables interrupts globally. +/// Call `.leave()` on the return value to restore the previous state. +pub fn enter_critical_section() CriticalSection { + var section = CriticalSection{ + .enable_on_leave = globally_enabled(), + }; + disable_interrupts(); + return section; +} + +/// A critical section structure that allows restoring the interrupt +/// status that was set before entering. +const CriticalSection = struct { + enable_on_leave: bool, + + /// Leaves the critical section and restores the interrupt state. + pub fn leave(self: @This()) void { + if (self.enable_on_leave) { + enable_interrupts(); + } + } +}; + +// TODO: update with arch specifics +pub const Handler = extern union { + C: *const fn () callconv(.C) void, + Naked: *const fn () callconv(.Naked) void, + // Interrupt is not supported on arm +}; + +pub const unhandled = Handler{ + .C = struct { + fn tmp() callconv(.C) noreturn { + @panic("unhandled interrupt"); + } + }.tmp, +}; diff --git a/core/src/microzig.zig b/core/src/microzig.zig new file mode 100644 index 0000000..348889c --- /dev/null +++ b/core/src/microzig.zig @@ -0,0 +1,71 @@ +//! This is the entry point and root file of microzig. +//! If you do a @import("microzig"), you'll *basically* get this file. +//! +//! But microzig employs a proxy tactic + +const std = @import("std"); +const root = @import("root"); +const builtin = @import("builtin"); + +/// The app that is currently built. +pub const app = @import("app"); + +/// Contains build-time generated configuration options for microzig. +/// Contains a CPU target description, chip, board and cpu information +/// and so on. +pub const config = @import("config"); + +/// Provides access to the low level features of the CPU. +pub const cpu = @import("cpu"); + +/// Provides access to the low level features of the current microchip. +pub const chip = struct { + const inner = @import("chip"); + pub const types = inner.types; + pub usingnamespace @field(inner.devices, config.chip_name); +}; + +/// Provides higher level APIs for interacting with hardware +pub const hal = if (config.has_hal) @import("hal") else void; + +/// Provides access to board features or is `void` when no board is present. +pub const board = if (config.has_board) @import("board") else void; + +pub const mmio = @import("mmio.zig"); +pub const interrupt = @import("interrupt.zig"); +pub const core = @import("core.zig"); +pub const drivers = @import("drivers.zig"); + +/// The microzig default panic handler. Will disable interrupts and loop endlessly. +pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { + + // utilize logging functions + std.log.err("microzig PANIC: {s}", .{message}); + + if (builtin.cpu.arch != .avr) { + var index: usize = 0; + var iter = std.debug.StackIterator.init(@returnAddress(), null); + while (iter.next()) |address| : (index += 1) { + if (index == 0) { + std.log.err("stack trace:", .{}); + } + std.log.err("{d: >3}: 0x{X:0>8}", .{ index, address }); + } + } + if (@import("builtin").mode == .Debug) { + // attach a breakpoint, this might trigger another + // panic internally, so only do that in debug mode. + std.log.info("triggering breakpoint...", .{}); + @breakpoint(); + } + hang(); +} + +/// Hangs the processor and will stop doing anything useful. Use with caution! +pub fn hang() noreturn { + cpu.disable_interrupts(); + while (true) { + // "this loop has side effects, don't optimize the endless loop away please. thanks!" + asm volatile ("" ::: "memory"); + } +} diff --git a/core/src/mmio.zig b/core/src/mmio.zig new file mode 100644 index 0000000..83c7699 --- /dev/null +++ b/core/src/mmio.zig @@ -0,0 +1,55 @@ +const std = @import("std"); +const assert = std.debug.assert; + +pub fn Mmio(comptime PackedT: type) type { + const size = @bitSizeOf(PackedT); + if ((size % 8) != 0) + @compileError("size must be divisible by 8!"); + + if (!std.math.isPowerOfTwo(size / 8)) + @compileError("size must encode a power of two number of bytes!"); + + const IntT = std.meta.Int(.unsigned, size); + + if (@sizeOf(PackedT) != (size / 8)) + @compileError(std.fmt.comptimePrint("IntT and PackedT must have the same size!, they are {} and {} bytes respectively", .{ size / 8, @sizeOf(PackedT) })); + + return extern struct { + const Self = @This(); + + raw: IntT, + + pub const underlying_type = PackedT; + + pub inline fn read(addr: *volatile Self) PackedT { + return @bitCast(addr.raw); + } + + pub inline fn write(addr: *volatile Self, val: PackedT) void { + comptime { + assert(@bitSizeOf(PackedT) == @bitSizeOf(IntT)); + } + addr.write_raw(@bitCast(val)); + } + + pub fn write_raw(addr: *volatile Self, val: IntT) void { + addr.raw = val; + } + + pub inline fn modify(addr: *volatile Self, fields: anytype) void { + var val = read(addr); + inline for (@typeInfo(@TypeOf(fields)).Struct.fields) |field| { + @field(val, field.name) = @field(fields, field.name); + } + write(addr, val); + } + + pub inline fn toggle(addr: *volatile Self, fields: anytype) void { + var val = read(addr); + inline for (@typeInfo(@TypeOf(fields)).Struct.fields) |field| { + @field(val, @tagName(field.default_value.?)) = !@field(val, @tagName(field.default_value.?)); + } + write(addr, val); + } + }; +} diff --git a/core/src/start.zig b/core/src/start.zig new file mode 100644 index 0000000..7271ad2 --- /dev/null +++ b/core/src/start.zig @@ -0,0 +1,161 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const app = @import("app"); + +pub usingnamespace app; + +// Use microzig panic handler if not defined by an application +pub usingnamespace if (!@hasDecl(app, "panic")) + struct { + pub const panic = microzig.panic; + } +else + struct {}; + +// Conditionally provide a default no-op logFn if app does not have one +// defined. Parts of microzig use the stdlib logging facility and +// compilations will now fail on freestanding systems that use it but do +// not explicitly set `root.std_options.logFn` +pub usingnamespace if (!@hasDecl(app, "std_options")) + struct { + pub const std_options = struct { + pub fn logFn( + comptime message_level: std.log.Level, + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: anytype, + ) void { + _ = message_level; + _ = scope; + _ = format; + _ = args; + } + }; + } +else + struct { + comptime { + // Technically the compiler's errors should be good enough that we + // shouldn't include errors like this, but since we add default + // behavior we should clarify the situation for the user. + if (!@hasDecl(app.std_options, "logFn")) + @compileError("By default MicroZig provides a no-op logging function. Since you are exporting `std_options`, you must export the stdlib logging function yourself."); + } + }; + +// Startup logic: +comptime { + // Instantiate the startup logic for the given CPU type. + // This usually implements the `_start` symbol that will populate + // the sections .data and .bss with the correct data. + // .rodata is not always necessary to be populated (flash based systems + // can just index flash, while harvard or flash-less architectures need + // to copy .rodata into RAM). + _ = microzig.cpu.startup_logic; + + // Export the vector table to flash start if we have any. + // For a lot of systems, the vector table provides a reset vector + // that is either called (Cortex-M) or executed (AVR) when initalized. + + // Allow board and chip to override CPU vector table. + const export_opts = .{ + .name = "vector_table", + .section = "microzig_flash_start", + .linkage = .Strong, + }; + + if ((microzig.board != void and @hasDecl(microzig.board, "vector_table"))) + @export(microzig.board.vector_table, export_opts) + else if (@hasDecl(microzig.chip, "vector_table")) + @export(microzig.chip.vector_table, export_opts) + else if (@hasDecl(microzig.cpu, "vector_table")) + @export(microzig.cpu.vector_table, export_opts) + else if (@hasDecl(app, "interrupts")) + @compileError("interrupts not configured"); +} + +/// This is the logical entry point for microzig. +/// It will invoke the main function from the root source file +/// and provides error return handling as well as a event loop if requested. +/// +/// Why is this function exported? +/// This is due to the modular design of microzig to allow the "chip" dependency of microzig +/// to call into our main function here. If we would use a normal function call, we'd have a +/// circular dependency between the `microzig` and `chip` package. This function is also likely +/// to be invoked from assembly, so it's also convenient in that regard. +export fn microzig_main() noreturn { + if (!@hasDecl(app, "main")) + @compileError("The root source file must provide a public function main!"); + + const main = @field(app, "main"); + const info: std.builtin.Type = @typeInfo(@TypeOf(main)); + + const invalid_main_msg = "main must be either 'pub fn main() void' or 'pub fn main() !void'."; + if (info != .Fn or info.Fn.params.len > 0) + @compileError(invalid_main_msg); + + const return_type = info.Fn.return_type orelse @compileError(invalid_main_msg); + + if (info.Fn.calling_convention == .Async) + @compileError("TODO: Embedded event loop not supported yet. Please try again later."); + + // A hal can export a default init function that runs before main for + // procedures like clock configuration. The user may override and customize + // this functionality by providing their own init function. + // function. + if (@hasDecl(app, "init")) + app.init() + else if (microzig.hal != void and @hasDecl(microzig.hal, "init")) + microzig.hal.init(); + + if (@typeInfo(return_type) == .ErrorUnion) { + main() catch |err| { + // TODO: + // - Compute maximum size on the type of "err" + // - Do not emit error names when std.builtin.strip is set. + var msg: [64]u8 = undefined; + @panic(std.fmt.bufPrint(&msg, "main() returned error {s}", .{@errorName(err)}) catch @panic("main() returned error.")); + }; + } else { + main(); + } + + // main returned, just hang around here a bit + microzig.hang(); +} + +/// Contains references to the microzig .data and .bss sections, also +/// contains the initial load address for .data if it is in flash. +pub const sections = struct { + // it looks odd to just use a u8 here, but in C it's common to use a + // char when linking these values from the linkerscript. What's + // important is the addresses of these values. + extern var microzig_data_start: u8; + extern var microzig_data_end: u8; + extern var microzig_bss_start: u8; + extern var microzig_bss_end: u8; + extern const microzig_data_load_start: u8; +}; + +pub fn initialize_system_memories() void { + @setCold(true); + + // fill .bss with zeroes + { + const bss_start: [*]u8 = @ptrCast(§ions.microzig_bss_start); + const bss_end: [*]u8 = @ptrCast(§ions.microzig_bss_end); + const bss_len = @intFromPtr(bss_end) - @intFromPtr(bss_start); + + @memset(bss_start[0..bss_len], 0); + } + + // load .data from flash + { + const data_start: [*]u8 = @ptrCast(§ions.microzig_data_start); + const data_end: [*]u8 = @ptrCast(§ions.microzig_data_end); + const data_len = @intFromPtr(data_end) - @intFromPtr(data_start); + const data_src: [*]const u8 = @ptrCast(§ions.microzig_data_load_start); + + @memcpy(data_start[0..data_len], data_src[0..data_len]); + } +} diff --git a/core/test/README.adoc b/core/test/README.adoc new file mode 100644 index 0000000..83697ac --- /dev/null +++ b/core/test/README.adoc @@ -0,0 +1,3 @@ += MicroZig Test Area + +These are minimal tests to validate MicroZig behavior. diff --git a/core/test/backings.zig b/core/test/backings.zig new file mode 100644 index 0000000..0a08dc5 --- /dev/null +++ b/core/test/backings.zig @@ -0,0 +1,67 @@ +//! This file contains some backings for different test programs +const std = @import("std"); +const microzig = @import("../build.zig"); + +fn root_dir() []const u8 { + return std.fs.path.dirname(@src().file) orelse unreachable; +} + +const cpu = microzig.Cpu{ + .name = "my_cpu", + .source = .{ + .path = root_dir() ++ "/cpu.zig", + }, + + // this will actually make it a native target, this is still fine + .target = std.zig.CrossTarget{ + .cpu_arch = .thumb, + .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0 }, + .os_tag = .freestanding, + .abi = .none, + }, +}; + +const minimal_chip = microzig.Chip{ + .name = "minimal_chip", + .cpu = cpu, + .source = .{ + .path = root_dir() ++ "/chip.zig", + }, + .memory_regions = &.{ + .{ .offset = 0x00000, .length = 0x10000, .kind = .flash }, + .{ .offset = 0x10000, .length = 0x10000, .kind = .ram }, + }, +}; + +const chip_with_hal = microzig.Chip{ + .name = "chip_with_hal", + .cpu = cpu, + .source = .{ + .path = root_dir() ++ "/chip.zig", + }, + .hal = .{ + .path = root_dir() ++ "/hal.zig", + }, + .memory_regions = &.{ + .{ .offset = 0x00000, .length = 0x10000, .kind = .flash }, + .{ .offset = 0x10000, .length = 0x10000, .kind = .ram }, + }, +}; + +pub const minimal = microzig.Backing{ + .chip = minimal_chip, +}; + +pub const has_hal = microzig.Backing{ + .chip = chip_with_hal, +}; + +pub const has_board = microzig.Backing{ + .board = .{ + .name = "has_board", + .chip = minimal_chip, + .source = .{ + .path = root_dir() ++ "/board.zig", + }, + }, +}; diff --git a/core/test/board.zig b/core/test/board.zig new file mode 100644 index 0000000..e69de29 diff --git a/core/test/chip.zig b/core/test/chip.zig new file mode 100644 index 0000000..a3ad12e --- /dev/null +++ b/core/test/chip.zig @@ -0,0 +1,6 @@ +pub const types = struct {}; + +pub const devices = struct { + pub const minimal_chip = struct {}; + pub const chip_with_hal = struct {}; +}; diff --git a/core/test/cpu.zig b/core/test/cpu.zig new file mode 100644 index 0000000..43dee20 --- /dev/null +++ b/core/test/cpu.zig @@ -0,0 +1,4 @@ +pub const startup_logic = struct {}; + +pub fn enable_interrupts() void {} +pub fn disable_interrupts() void {} diff --git a/core/test/hal.zig b/core/test/hal.zig new file mode 100644 index 0000000..e69de29 diff --git a/core/test/programs/blinky.zig b/core/test/programs/blinky.zig new file mode 100644 index 0000000..16a841d --- /dev/null +++ b/core/test/programs/blinky.zig @@ -0,0 +1,43 @@ +const micro = @import("microzig"); + +// Configures the led_pin to a hardware pin +const led_pin = if (micro.config.has_board) + switch (micro.config.board_name) { + .@"Arduino Nano" => micro.Pin("D13"), + .@"Arduino Uno" => micro.Pin("D13"), + .@"mbed LPC1768" => micro.Pin("LED-1"), + .STM32F3DISCOVERY => micro.Pin("LD3"), + .STM32F4DISCOVERY => micro.Pin("LD5"), + .STM32F429IDISCOVERY => micro.Pin("LD4"), + .@"Longan Nano" => micro.Pin("PA2"), + else => @compileError("unknown board"), + } +else switch (micro.config.chip_name) { + .ATmega328p => micro.Pin("PB5"), + .@"NXP LPC1768" => micro.Pin("P1.18"), + .STM32F103x8 => micro.Pin("PC13"), + .GD32VF103x8 => micro.Pin("PA2"), + else => @compileError("unknown chip"), +}; + +pub fn main() void { + const led = micro.Gpio(led_pin, .{ + .mode = .output, + .initial_state = .low, + }); + led.init(); + + while (true) { + busyloop(); + led.toggle(); + } +} + +fn busyloop() void { + const limit = 100_000; + + var i: u24 = 0; + while (i < limit) : (i += 1) { + @import("std").mem.doNotOptimizeAway(i); + } +} diff --git a/core/test/programs/has_board.zig b/core/test/programs/has_board.zig new file mode 100644 index 0000000..8141376 --- /dev/null +++ b/core/test/programs/has_board.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const assert = std.debug.assert; +const microzig = @import("microzig"); + +comptime { + assert(microzig.config.has_board); + assert(!microzig.config.has_hal); +} + +pub fn main() void {} diff --git a/core/test/programs/has_dependencies.zig b/core/test/programs/has_dependencies.zig new file mode 100644 index 0000000..e69de29 diff --git a/core/test/programs/has_hal.zig b/core/test/programs/has_hal.zig new file mode 100644 index 0000000..eed6b0c --- /dev/null +++ b/core/test/programs/has_hal.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const assert = std.debug.assert; +const microzig = @import("microzig"); + +comptime { + assert(microzig.config.has_hal); + assert(!microzig.config.has_board); +} + +pub fn main() void {} diff --git a/core/test/programs/interrupt.zig b/core/test/programs/interrupt.zig new file mode 100644 index 0000000..34a3c68 --- /dev/null +++ b/core/test/programs/interrupt.zig @@ -0,0 +1,19 @@ +const micro = @import("microzig"); +const builtin = @import("builtin"); + +pub const interrupts = switch (builtin.cpu.arch) { + .avr => struct { + pub fn INT0() void { + @panic("hit PCINT0"); + } + }, + else => struct { + pub fn SysTick() void { + @panic("hit systick!"); + } + }, +}; + +pub fn main() void { + while (true) {} +} diff --git a/core/test/programs/minimal.zig b/core/test/programs/minimal.zig new file mode 100644 index 0000000..3ef20f7 --- /dev/null +++ b/core/test/programs/minimal.zig @@ -0,0 +1,11 @@ +const std = @import("std"); +const assert = std.debug.assert; +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; + +comptime { + assert(!microzig.config.has_board); + assert(!microzig.config.has_hal); +} + +pub fn main() void {} diff --git a/core/test/programs/uart-sync.zig b/core/test/programs/uart-sync.zig new file mode 100644 index 0000000..7d58367 --- /dev/null +++ b/core/test/programs/uart-sync.zig @@ -0,0 +1,82 @@ +const micro = @import("microzig"); + +// Configures the led_pin and uart index +const cfg = if (micro.config.has_board) + switch (micro.config.board_name) { + .@"mbed LPC1768" => .{ + .led_pin = micro.Pin("LED-1"), + .uart_idx = 1, + .pins = .{}, + }, + .STM32F3DISCOVERY => .{ + .led_pin = micro.Pin("LD3"), + .uart_idx = 1, + .pins = .{}, + }, + .STM32F4DISCOVERY => .{ + .led_pin = micro.Pin("LD5"), + .uart_idx = 2, + .pins = .{ .tx = micro.Pin("PA2"), .rx = micro.Pin("PA3") }, + }, + .@"Longan Nano" => .{ + .led_pin = micro.Pin("PA2"), + .uart_idx = 1, + .pins = .{ .tx = null, .rx = null }, + }, + .@"Arduino Uno" => .{ + .led_pin = micro.Pin("D13"), + .uart_idx = 0, + .pins = .{}, + }, + else => @compileError("unknown board"), + } +else switch (micro.config.chip_name) { + .@"NXP LPC1768" => .{ .led_pin = micro.Pin("P1.18"), .uart_idx = 1, .pins = .{} }, + .GD32VF103x8 => .{ .led_pin = micro.Pin("PA2"), .uart_idx = 1, .pins = .{} }, + else => @compileError("unknown chip"), +}; + +pub fn main() !void { + const led = micro.Gpio(cfg.led_pin, .{ + .mode = .output, + .initial_state = .low, + }); + led.init(); + + var uart = micro.Uart(cfg.uart_idx, cfg.pins).init(.{ + .baud_rate = 9600, + .stop_bits = .one, + .parity = null, + .data_bits = .eight, + }) catch |err| { + blinkError(led, err); + + micro.hang(); + }; + + var out = uart.writer(); + + while (true) { + led.setToHigh(); + try out.writeAll("Hello microzig!\r\n"); + led.setToLow(); + micro.debug.busySleep(100_000); + } +} + +fn blinkError(led: anytype, err: micro.uart.InitError) void { + var blinks: u3 = + switch (err) { + error.UnsupportedBaudRate => 1, + error.UnsupportedParity => 2, + error.UnsupportedStopBitCount => 3, + error.UnsupportedWordSize => 4, + }; + + while (blinks > 0) : (blinks -= 1) { + led.setToHigh(); + micro.debug.busySleep(1_000_000); + led.setToLow(); + micro.debug.busySleep(1_000_000); + } +} diff --git a/core/thoughts.md b/core/thoughts.md new file mode 100644 index 0000000..3df4111 --- /dev/null +++ b/core/thoughts.md @@ -0,0 +1,262 @@ +# microzig Design Meeting + +## Package structure + +`microzig` package exports all functions and types available in the HAL as well as `microzig.mcu` which will provide access to the MCU. All functions in `microzig` which are not generic will be inline-forwarded to the `board` or `mcu` package. + +``` +root + |- std + |- microzig + | |- mcu (should be specified if "board" does not exit) + | |- build_options (defines if "mcu" and/or "board" exists) + | \- board (must export ".mcu") + | \- mcu + \- mcu (user decision) +``` + +## Minimal Root file + +Declaring `panic` in the root file will cause `builtin` to reference the proper package which will then instantiate microzig which will reference `mcu` package which will instantiate `reset` (or similar) which will invoke `microzig.main()` which will invoke `root.main()`. Oh boy :laughing: + +```zig +const micro = @import("micro"); + +pub const panic = micro.panic; // this will instantiate microzig + +comptime { _ = micro }; // this should not be necessary + +pub fn main() void { + +} +``` + +## `microzig.mcu` + +`microzig` exports a symbol `mcu` which will provide access to the MCU, CPU and board features + +```zig +// mcu.zig in microzig.zig +const config = @import("build_options"); + +usingnamespace if(config.has_board) + @import("board").mcu +else + @import("mcu"); + +pub const has_board = config.has_board; +pub const board = @import("board"); +``` + +## Interrupt API + +All interrupt handlers are static and will be collected from `root.interrupt_handlers`. Each symbol will be verified if it's a valid interrupt vector. +A symbol can be a function `fn() void`, or a anonymous enum literal `.hang` or `.reset`. + +`microzig.interrupts.enable` and `microzig.interrupts.disable` will allow masking/unmasking those interrupts, `.cli()` and `.sei()` will globally disable/enable interrupts + +`microzig.interrupts` also provides some handling of critical sections + +```zig +pub fn main() void { + micro.interrupts.enable(.WDT); // enables the WDT interrupt + micro.interrupts.disable(.WDT); // enables the WDT interrupt + micro.interrupts.enable(.WTF); // yields compile error "WTF is not a valid interrupt" + micro.interrupts.enable(.PCINT0); // yields compile error "PCINT0 has no defined interrupt handler" + micro.interrupts.enable(.NMI); // yields compile error "NMI cannot be masked" + micro.interrupts.enableAll(); + micro.interrupts.batchEnable(.{ .NMI, .WDT }); + + micro.interrupts.cli(); // set interrupt enabled (global enable) + + { // critical section + var crit = micro.interrupts.enterCriticalSection(); + defer crit.leave(); + } +} + +var TIMER2_OVF_RUNTIME: fn()void = foo; + +pub const interrupt_handlers = struct { + + // AVR + pub fn TIMER2_OVF() void { TIMER2_OVF_RUNTIME(); } + pub fn WDT() void { } + + pub const PCINT1 = .reset; + pub const PCINT2 = .hang; + + // cortex-mX exceptions + pub fn NMI() void { } + pub fn HardFault() void {} + + // LPC 1768 interrupt/irq + pub fn SSP1() void { } +}; +``` + +## Timer API + +microzig should allow having a general purpose timer mechanism + +```zig +pub var cpu_frequency = 16.0 * micro.clock.mega_hertz; + +pub const sleep_mode = .timer; // timer, busyloop, whatever + +pub fn main() !void { + led.init(); + + while(true) { + led.toggle(); + micro.sleep(100_000); // sleep 100ms + } +} +``` + +## GPIO API + +`microzig.Pin` parses a pin definition and returns a type that encodes all relevant info and functions to route that pin. +`microzig.Gpio` is a GPIO port/pin configuration that allows modifying pin levels. + +```zig + +// micro.Pin returns a type containing all relevant pin information +const status_led_pin = micro.Pin("PA3"); + +// generate a runtime possible pin that cannot be used in all APIs +var generic_pic: micro.RuntimePin.init(status_led_pin); + +// 4 Bit IEEE-488 bit banging register +const serial_out = micro.GpioOutputRegister(.{ + micro.Pin("PA0"), + micro.Pin("PA1"), + micro.Pin("PA3"), // whoopsies, i miswired, let the software fix that + micro.Pin("PA2"), +}); + +pub fn bitBang(nibble: u4) void { + serial_out.write(nibble); +} + +pub fn main() !void { + + // Route all gpio pins from the bit bang register + inline for(serial_out.pins) |pin| { + pin.route(".gpio"); + } + serial_out.init(); + + // route that pin to UART.RXD + status_led_pin.route(.uart0_rxd); + + //var uart_read_dma_channel = micro.Dma.init(.{.channel = 1}); + + const status_led = micro.Gpio(status_led_pin, .{ + .mode = .output, // { input, output, input_output, open_drain, generic } + .initial_state = .unspecificed, // { unspecified, low, high, floating, driven } + }); + status_led.init(); + + switch(status_led.mode) { + // only reading API is available + .input => { + _ = status_led.read(); + }, + + // reading and writing is available + .output => { + _ = status_led.read(); + status_led.write(.high); + + // "subvariant" of the write + status_led.toggle(); + status_led.setToHigh(); + status_led.setToLow(); + }, + + // reading, writing and changing direction is available + .input_output => { + status_led.setDirection(.input, undefined); + _ = status_led.read(); + status_led.setDirection(.output, .high); // reqires a defined state after setting the direction + status_led.write(.high); + }, + + // reading and setDive is available + .open_drain => { + status_led.setDrive(.disabled); + _ = status_led.read(); + status_led.setDrive(.enabled); + }, + + // will have all available APIs enabled + .generic => {}, + } + + // AVR: PORTA[3] => "PA3" + // NXP: PORT[1][3] => "P1.3" + // PICO: PORT[1] => "P3" + // STM: PORT[A][3] => "A3" + // ESP32: PORT[1] => "P3" +``` + + +## UART example + +```zig +const std = @import("std"); +const micro = @import("µzig"); + +// if const it can be comptime-optimized +pub var cpu_frequency = 100.0 * micro.clock.mega_hertz; + +// if this is enabled, a event loop will run +// in microzig.main() that allows using `async`/`await` "just like that" *grin* +pub const io_mode = .evented; + +pub fn main() !void { + var debug_port = micro.Uart.init(0, .{ + .baud_rate = 9600, + .stop_bits = .@"2", + .parity = .none, // { none, even, odd, mark, space } + .data_bits = .@"8", // 5, 6, 7, 8, or 9 data bits + //.in_dma_channel = 0, + //.out_dma_channel = 0, + }); + + debug_port.configureDMA(???); + + try debug_port.writer().writeAll("Hello, World!"); + + var line_buffer: [64]u8 = undefined; + const len = try debug_port.reader().readUntilDelimiter(&line_buffer, '\n'); +} +``` + + + + +## Initialization + +somewhere inside micro.zig +```zig +extern fn reset() noreturn { + if(@hasDecl(mcu, "mcu_reset")) { + mcu.mcu_reset(); + @unreachable(); + } + // zeroing bss, copying .data, setting stack address + + if(@hasDecl(root, "early_main")) // idk about the name + root.early_main(); + else { + mcu.init(); + + if(@hasDecl(root, "main")) + root.main(); + else + @compileError("main or entry_main missing") + } +} +```