diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/Rakefile b/Rakefile new file mode 100755 index 0000000..81d54a2 --- /dev/null +++ b/Rakefile @@ -0,0 +1,347 @@ +#!/usr/bin/env ruby +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at https://mozilla.org/MPL/2.0/. -} + +# frozen_string_literal: true + +require 'uri' +require 'net/http' +require 'open3' +require 'etc' +require 'fileutils' +require 'pathname' +require 'rake/clean' + +TMP = Pathname.new('./build') + +CLOBBER.include 'build' + +def gcc_verbose(gcc_binary) + read, write = IO.pipe + system({'LANG' => 'C'}, gcc_binary, '--verbose', err: write) + write.close + output = read.read + read.close + output +end + +class BuildTarget + def initialize(architecture) + case architecture + when /^r(isc)?v32$/ + @architecture = :rv32 + else + raise "Unsupported architecture '#{architecture}'." + end + end + + def sysroot + TMP + 'sysroot' + end + + def rootfs + TMP + 'rootfs' + end + + def target + case @architecture + when :rv32 + 'riscv32-unknown-linux-gnu' + end + end + + def build + @build ||= gcc_verbose(ENV['CC']).lines + .find { |line| line.start_with? 'Target: ' } + .split(' ').last.strip + end + + def configuration + case @architecture + when :rv32 + ['--with-arch=rv32imafdc', '--with-abi=ilp32d', '--with-tune=rocket', '--with-isa-spec=20191213'] + else + [] + end + end +end + +def download_and_unarchive(url) + case File.extname url.path + when '.bz2' + archive_type = '-j' + root_directory = File.basename url.path, '.tar.bz2' + when '.xz' + archive_type = '-J' + root_directory = File.basename url.path, '.tar.xz' + else + raise "Unsupported archive type #{url.path}." + end + target = TMP + 'tools' + Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https') do |http| + request = Net::HTTP::Get.new url.request_uri + + http.request request do |response| + case response + when Net::HTTPRedirection + download_and_unarchive URI.parse(response['location']) + when Net::HTTPSuccess + Open3.popen2 'tar', '-C', target.to_path, archive_type, '-xv' do |stdin, stdout, wait_thread| + Thread.new do + stdout.each { |line| puts line } + end + + response.read_body do |chunk| + stdin.write chunk + end + stdin.close + + wait_thread.value + end + else + response.error! + end + end + end + target + root_directory +end + +def configure_make_install(source_directory, configure_options, env, cwd) + configure = source_directory.relative_path_from(cwd) + 'configure' + system env, configure.to_path, *configure_options, chdir: cwd.to_path, exception: true + system 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path, exception: true + system env, 'make', 'install', chdir: cwd.to_path, exception: true +end + +directory(TMP + 'tools') + +namespace :cross do + BINUTILS_VERSION = '2.44' + GLIBC_VERSION = '2.41' + KERNEL_VERSION = '5.15.185' + GCC_VERSION = "15.1.0" + + task :environment, :target do |t, args| + binary_suffix = '' + output = gcc_verbose 'gcc' + + if output.start_with? 'Apple clang' + binary_suffix = "-#{GCC_VERSION.split('.').first}" + end + + ENV['CC'] = "gcc#{binary_suffix}" + ENV['CXX'] = "g++#{binary_suffix}" + end + + desc 'Build cross binutils' + task :binutils, [:target] => :environment do |t, args| + options = BuildTarget.new args[:target] + source_directory = download_and_unarchive( + URI.parse("https://ftp.gnu.org/gnu/binutils/binutils-#{BINUTILS_VERSION}.tar.xz")) + + cwd = source_directory.dirname + 'build-binutils' + cwd.mkpath + options.rootfs.mkpath + + env = ENV.slice 'CC', 'CXX' + configure_options = [ + "--prefix=#{options.rootfs.realpath}", + "--target=#{options.target}", + '--disable-nls', + '--enable-gprofng=no', + '--disable-werror', + '--enable-default-hash-style=gnu', + '--disable-libquadmath' + ] + configure_make_install source_directory, configure_options, env, cwd + end + + desc 'Build stage 1 GCC' + task :gcc1, [:target] => :environment do |t, args| + options = BuildTarget.new args[:target] + source_directory = download_and_unarchive( + URI.parse("https://gcc.gnu.org/pub/gcc/releases/gcc-#{GCC_VERSION}/gcc-#{GCC_VERSION}.tar.xz")) + + cwd = source_directory.dirname + 'build-gcc' + cwd.mkpath + options.rootfs.mkpath + options.sysroot.mkpath + + system 'contrib/download_prerequisites', chdir: source_directory.to_path, exception: true + configure_options = options.configuration + [ + "--prefix=#{options.rootfs.realpath}", + "--with-sysroot=#{options.sysroot.realpath}", + '--enable-languages=c,c++', + '--disable-shared', + '--disable-bootstrap', + '--disable-multilib', + '--disable-libmudflap', + '--disable-libssp', + '--disable-libquadmath', + '--disable-libsanitizer', + '--disable-threads', + '--disable-libatomic', + '--disable-libgomp', + '--disable-libvtv', + '--disable-libstdcxx', + '--disable-nls', + '--with-newlib', + '--without-headers', + "--target=#{options.target}", + "--build=#{options.build}", + "--host=#{options.build}" + ] + flags = '-O2 -fPIC' + env = { + 'CC' => ENV['CC'], + 'CXX' => ENV['CXX'], + 'CFLAGS' => flags, + 'CXXFLAGS' => flags, + 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}" + } + configure_make_install source_directory, configure_options, env, cwd + end + + desc 'Copy glibc headers' + task :headers, [:target] => :environment do |t, args| + options = BuildTarget.new args[:target] + source_directory = download_and_unarchive( + URI.parse("https://ftp.gnu.org/gnu/glibc/glibc-#{GLIBC_VERSION}.tar.xz")) + include_directory = TMP + 'tools/include' + + include_directory.mkpath + FileUtils.cp (source_directory + 'elf/elf.h'), (include_directory + 'elf.h') + end + + desc 'Build linux kernel' + task :kernel, [:target] => :environment do |t, args| + options = BuildTarget.new args[:target] + cwd = download_and_unarchive( + URI.parse("https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-#{KERNEL_VERSION}.tar.xz")) + + env = { + 'CROSS_COMPILE' => "#{options.target}-", + 'ARCH' => 'riscv', + 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}", + 'HOSTCFLAGS' => "-D_UUID_T -D__GETHOSTUUID_H -I#{TMP + 'tools/include'}" + } + system env, 'make', 'rv32_defconfig', chdir: cwd.to_path, exception: true + system env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path, exception: true + system env, 'make', 'headers', chdir: cwd.to_path, exception: true + + user_directory = options.sysroot + 'usr' + + user_directory.mkpath + FileUtils.cp_r (cwd + 'usr/include'), (user_directory + 'include') + end + + desc 'Build glibc' + task :glibc, [:target] => :environment do |t, args| + options = BuildTarget.new args[:target] + source_directory = TMP + "tools/glibc-#{GLIBC_VERSION}" + configure_options = [ + '--prefix=/usr', + "--host=#{options.target}", + "--target=#{options.target}", + "--build=#{options.build}", + "--enable-kernel=#{KERNEL_VERSION}", + "--with-headers=#{options.sysroot.realpath + 'usr/include'}", + '--disable-nscd', + '--disable-libquadmath', + '--disable-libitm', + '--disable-werror', + 'libc_cv_forced_unwind=yes' + ] + bin = options.rootfs.realpath + 'bin' + env = { + 'PATH' => "#{bin}:#{ENV['PATH']}", + 'MAKE' => 'make', # Otherwise it uses gnumake which can be different and too old. + 'CC' => "#{options.target}-gcc", + 'CXX' => "#{options.target}-g++" + } + cwd = source_directory.dirname + 'build-glibc' + cwd.mkpath + + configure = source_directory.relative_path_from(cwd) +'./configure' + system env, configure.to_path, *configure_options, chdir: cwd.to_path, exception: true + system env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path, exception: true + system env, 'make', "install_root=#{options.sysroot.realpath}", 'install', chdir: cwd.to_path, exception: true + end + + desc 'Build stage 2 GCC' + task :gcc2, [:target] => :environment do |t, args| + options = BuildTarget.new args[:target] + source_directory = TMP + "tools/gcc-#{GCC_VERSION}" + cwd = TMP + 'tools/build-gcc' + + FileUtils.rm_rf cwd + cwd.mkpath + + configure_options = options.configuration + [ + "--prefix=#{options.rootfs.realpath}", + "--with-sysroot=#{options.sysroot.realpath}", + '--enable-languages=c,c++,lto', + '--enable-lto', + '--enable-shared', + '--disable-bootstrap', + '--disable-multilib', + '--enable-checking=release', + '--disable-libssp', + '--disable-libquadmath', + '--enable-threads=posix', + '--with-default-libstdcxx-abi=new', + '--disable-nls', + "--target=#{options.target}", + "--build=#{options.build}", + "--host=#{options.build}" + ] + flags = '-O2 -fPIC' + env = { + 'CFLAGS' => flags, + 'CXXFLAGS' => flags, + 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}" + } + configure_make_install source_directory, configure_options, env, cwd + end +end + +task cross: TMP + 'tools' +task :cross, :target do |t, args| + args.with_defaults(target: 'riscv32-unknown-linux-gnu') + + Rake::Task['cross:binutils'].invoke args[:target] + Rake::Task['cross:gcc1'].invoke args[:target] + Rake::Task['cross:headers'].invoke args[:target] + Rake::Task['cross:kernel'].invoke args[:target] + Rake::Task['cross:glibc'].invoke args[:target] + Rake::Task['cross:gcc2'].invoke args[:target] +end + +FLAGS = ['-O2', '-g3', '-Wall', '-march=rv32imzicsr', '-mabi=ilp32', '-fno-stack-protector', '-ffreestanding', '-nostdlib', '-static'] + +CLEAN.include 'kernel.elf', 'build/riscv32' + +directory(TMP + 'riscv32') + +rule(/^#{TMP}\/[[:alnum:]]+\/[[:alnum:]]+\.o$/ => ->(match) { + [Pathname.new(match).basename.sub_ext('.s'), TMP + 'riscv32'] +}) do |t| + ENV['CC'] = 'build/rootfs/bin/riscv32-unknown-linux-gnu-gcc' + + system ENV['CC'], *FLAGS, '-c', '-o', t.name, *t.prerequisites.select { |prerequisite| prerequisite.end_with? '.s' } +end + +file 'kernel.elf' => FileList['*.s'].map { |source| (TMP + 'riscv32' + source).sub_ext('.o') } do |t| + ENV['CC'] = 'build/rootfs/bin/riscv32-unknown-linux-gnu-gcc' + + system ENV['CC'], *FLAGS, '-T', 'kernel.ld', '-Wl,-Map=kernel.map', '-o', 'kernel.elf', *t.prerequisites +end + +task default: 'kernel.elf' +task :default do |t| + QEMU = 'qemu-system-riscv32' + + system QEMU, '-machine', 'virt', '-bios', 'default', '-nographic', '-serial', 'mon:stdio', '--no-reboot', + '-kernel', *t.prerequisites +end diff --git a/run.sh b/run.sh deleted file mode 100755 index 3f33714..0000000 --- a/run.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -set -xue - -# QEMU file path -QEMU=qemu-system-riscv32 - -# Path to clang and compiler flags -CC=../riscv32-ilp32d--glibc/bin/riscv32-linux-gcc -CFLAGS="-O2 -g3 -Wall -march=rv32imzicsr -mabi=ilp32 -fno-stack-protector -ffreestanding -nostdlib -static" - -# Build the kernel -$CC $CFLAGS -T kernel.ld -Wl,-Map=kernel.map -o kernel.elf kernel.s common.s - -# Start QEMU -$QEMU -machine virt -bios default -nographic -serial mon:stdio --no-reboot -kernel kernel.elf