├── kernel_src ├── rtld_libc.h ├── libc_private.h ├── files.h ├── acpi.h ├── kern_mmap.h ├── threads.h ├── apic.h ├── rand.c ├── linker.ld ├── common.h ├── bogfd.h ├── strtol.c ├── files.c ├── acpi.c ├── apic.c ├── start.s └── kern_mmap.c ├── .gitignore ├── overrides ├── gen_tcp_dist.erl ├── dist_util.erl ├── os.erl ├── auth.erl └── socket.erl ├── cfg ├── inetrc ├── latest_freebsd_repo.conf ├── grub.cfg └── etcpip.app ├── .gitmodules ├── scripts ├── gen_cookie.escript ├── extract_start.escript ├── timer_offset.escript ├── push_code.escript └── hardcode_files.pl ├── TODO.txt ├── src ├── crazierl_epmd.erl ├── example_client.erl ├── pci.hrl ├── hook_module.erl ├── console.erl ├── crazierl.erl ├── comport.erl ├── eth_port.erl ├── example_host.erl ├── example_host2.erl ├── acpi.erl ├── ntp.erl ├── dhcpc.erl ├── rtl_8168.erl ├── vgakb.erl ├── pci.erl └── virtio_net.erl ├── configure_line ├── README.md ├── c_src └── crazierl_nif.c ├── Makefile └── LICENSE.txt /kernel_src/rtld_libc.h: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kernel_src/libc_private.h: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | erlang-runtime* 3 | .erlang.cookie 4 | obj/ -------------------------------------------------------------------------------- /overrides/gen_tcp_dist.erl: -------------------------------------------------------------------------------- 1 | -include_lib("kernel/examples/gen_tcp_dist/src/gen_tcp_dist.erl"). 2 | -------------------------------------------------------------------------------- /cfg/inetrc: -------------------------------------------------------------------------------- 1 | {resolv_conf, ""}. 2 | {hosts_file, ""}. 3 | {lookup, [file, dns]}. 4 | {usevc, true}. 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "erlang-tcpip"] 2 | path = erlang-tcpip 3 | url = https://github.com/russor/erlang-tcpip 4 | -------------------------------------------------------------------------------- /overrides/dist_util.erl: -------------------------------------------------------------------------------- 1 | -hook([gen_challenge/0]). 2 | -include_lib("kernel/src/dist_util.erl"). 3 | 4 | hook_gen_challenge() -> 5 | binary:decode_unsigned(crypto:strong_rand_bytes(4)). 6 | 7 | -------------------------------------------------------------------------------- /scripts/gen_cookie.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | main(_) -> 4 | crypto:rand_seed(), 5 | io:format("~s", [lists:map(fun(_X) -> 6 | rand:uniform(1 + $Z - $A) - 1 + $A end, 7 | lists:seq(1,20))]). -------------------------------------------------------------------------------- /overrides/os.erl: -------------------------------------------------------------------------------- 1 | -hook([cmd/1, cmd/2]). 2 | 3 | -include_lib("kernel/src/os.erl"). 4 | 5 | hook_cmd(Cmd) -> erlang:error(system_limit, [Cmd]). 6 | hook_cmd(Cmd, Opts) -> erlang:error(system_limit, [Cmd, Opts]). 7 | -------------------------------------------------------------------------------- /cfg/latest_freebsd_repo.conf: -------------------------------------------------------------------------------- 1 | latest: { 2 | url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest", 3 | mirror_type: "srv", 4 | signature_type: "fingerprints", 5 | fingerprints: "/usr/share/keys/pkg", 6 | enabled: yes 7 | } 8 | -------------------------------------------------------------------------------- /cfg/grub.cfg: -------------------------------------------------------------------------------- 1 | set timeout=0 2 | serial --unit=0 --speed=9600 3 | terminal_input serial 4 | terminal_output serial 5 |   6 | menuentry "crazierl" { 7 | multiboot /crazierl.elf.gz kernel /libexec/ld-elf32.so.1 8 | module /initrd.gz 9 | } 10 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | current next steps: 2 | 3 | *) tune tcp-ip as needed 4 | 5 | *) port to amd64 (milestone 3) 6 | 7 | *) commercial cloud images 8 | Amazon, GCP, Digital Ocean, etc 9 | 10 | *) (more) real network card drivers 11 | 12 | *) port to aarch64 13 | -------------------------------------------------------------------------------- /kernel_src/files.h: -------------------------------------------------------------------------------- 1 | #include "/usr/src/stand/i386/libi386/multiboot.h" 2 | #include 3 | 4 | struct hardcoded_file { 5 | char* name; 6 | uint8_t* start; 7 | uint8_t* end; 8 | size_t size; 9 | }; 10 | 11 | void init_files(multiboot_module_t *); 12 | struct hardcoded_file * find_file(const char *); 13 | struct hardcoded_file * find_dir(const char *, size_t, struct hardcoded_file *); 14 | -------------------------------------------------------------------------------- /overrides/auth.erl: -------------------------------------------------------------------------------- 1 | -hook([read_cookie/0]). 2 | 3 | -include_lib("kernel/src/auth.erl"). 4 | 5 | hook_read_cookie() -> 6 | case real_read_cookie() of 7 | {error, "Failed to create cookie file '/.erlang.cookie': enoent"} -> 8 | crypto:rand_seed(), 9 | Cookie = lists:flatten(io_lib:format("~s", [lists:map(fun(_X) -> 10 | rand:uniform(1 + $Z - $A) - 1 + $A end, 11 | lists:seq(1,20))])), 12 | {ok, Cookie}; 13 | Other -> Other 14 | end. -------------------------------------------------------------------------------- /src/crazierl_epmd.erl: -------------------------------------------------------------------------------- 1 | -module(crazierl_epmd). 2 | 3 | -include_lib("kernel/include/dist.hrl"). 4 | 5 | -export([start_link/0, names/1, register_node/2, port_please/2, port_please/3, address_please/3]). 6 | 7 | start_link() -> ignore. 8 | names(_) -> {ok, []}. 9 | register_node(_, _) -> {ok, -1}. 10 | port_please(Name, Host) -> port_please(Name, Host, infinity). 11 | port_please(_, _, _) -> {port, 4370, ?ERL_DIST_VER_HIGH}. 12 | address_please(Name, Host, Family) -> 13 | io:format("yo ~p~n", [{Name, Host, Family}]), 14 | {error, unimpl}. 15 | -------------------------------------------------------------------------------- /src/example_client.erl: -------------------------------------------------------------------------------- 1 | -module (example_client). 2 | 3 | % example tcp listener 4 | 5 | -export([go/0, go/1]). 6 | 7 | go() -> go("crazierl.org"). 8 | 9 | go(Ip) -> 10 | {ok, Sock} = gen_tcp:connect(Ip, 80, [binary]), 11 | % Note! we're not sending a Host header, 12 | % this is to try to keep the response small 13 | gen_tcp:send(Sock, <<"GET / HTTP/1.0\r\n\r\n">>), 14 | loop(). 15 | 16 | loop() -> 17 | receive 18 | {tcp, _Socket, Data} -> 19 | io:format("got ~p~n", [Data]), 20 | loop(); 21 | {tcp_closed, _Socket} -> ok 22 | end. 23 | -------------------------------------------------------------------------------- /scripts/extract_start.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | main([File]) -> 4 | {ok, Contents} = file:read_file(File), 5 | {ok, Tokens, _End} = erl_scan:string(binary_to_list(Contents)), 6 | {ok, Term} = erl_parse:parse_term(Tokens), 7 | {script, _Version, Things} = Term, 8 | lists:foreach(fun 9 | ({path, Paths}) -> 10 | io:format("path\t~s~n", [string:join(Paths, "\t")]); 11 | ({primLoad, Modules}) -> 12 | ModString = lists:map(fun erlang:atom_to_list/1, Modules), 13 | io:format("primLoad\t~s~n", [string:join(ModString, "\t")]); 14 | (_) -> ok 15 | end, Things). 16 | -------------------------------------------------------------------------------- /src/pci.hrl: -------------------------------------------------------------------------------- 1 | -record (pci_common, { 2 | bus, device, function, driver, pid, 3 | vendor, device_id, class, sub_class, 4 | revision, prog_if, capabilities, msix_map 5 | }). 6 | 7 | -record (pci_device, { 8 | common, chip_vendor, chip_device_id, 9 | interrupt_line, interrupt_pin, 10 | bars 11 | }). 12 | 13 | -record (pci_bridge, { 14 | common, bars, secondary_bus 15 | }). 16 | 17 | -record (pci_mem_bar, { 18 | base, size, prefetch, type 19 | }). 20 | 21 | -record (pci_io_bar, { 22 | base, size 23 | }). 24 | 25 | -record (pci_msi_x, { 26 | enabled, mask, size, table, pending, offset 27 | }). 28 | -------------------------------------------------------------------------------- /kernel_src/acpi.h: -------------------------------------------------------------------------------- 1 | #include "threads.h" 2 | 3 | extern unsigned int numcpu; 4 | 5 | void * acpi_find_rsdt (void *); 6 | int acpi_check_table(void *); 7 | int acpi_process_madt(void *); 8 | void * acpi_find_table(void *, void *); 9 | 10 | #define CPU_ENABLED (1 << 0) 11 | #define CPU_STARTED (1 << 1) 12 | // cpu is running idle task OR did not switch tasks at last timer 13 | // used to decide which cpu to wake when a thread becomes runnable 14 | #define CPU_IDLE (1 << 2) 15 | 16 | struct cpu { 17 | uint32_t apic_id; 18 | uint32_t volatile flags; 19 | uint32_t volatile current_thread; 20 | struct threadqueue runqueue; 21 | struct threadqueue timequeue; 22 | uint32_t clock_tick; 23 | uint64_t timeout; 24 | uint64_t last_time; 25 | }; 26 | 27 | #define MAX_CPUS 256 28 | extern struct cpu cpus[]; 29 | -------------------------------------------------------------------------------- /scripts/timer_offset.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | main([Dest]) -> 4 | Node = list_to_atom(Dest), 5 | case net_adm:ping(Node) of 6 | pong -> ok; 7 | pang -> 8 | io:format("couldn't contact node ~s, can't check offset code~n", [Node]), 9 | halt(1) 10 | end, 11 | 12 | Offset1 = get_offset(Node), 13 | Offset2 = get_offset(Node), 14 | Offset3 = get_offset(Node), 15 | Offset4 = get_offset(Node), 16 | Offset5 = get_offset(Node), 17 | io:format("Offsets at ~p (UTC) (ms): ~B, ~B, ~B, ~B, ~b~n", [calendar:universal_time(), Offset1, Offset2, Offset3, Offset4, Offset5]). 18 | 19 | get_offset(Node) -> 20 | Before = os:system_time(), 21 | Theirs = erpc:call(Node, os, system_time, [], infinity), 22 | After = os:system_time(), 23 | Difference = round(((Before + After) / 2.0 ) - Theirs), 24 | erlang:convert_time_unit(Difference, native, millisecond). 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /kernel_src/kern_mmap.h: -------------------------------------------------------------------------------- 1 | #ifndef _KERNEL_MMAP_H 2 | #define _KERNEL_MMAP_H 3 | 4 | #include 5 | 6 | #define PROT_KERNEL 0x1000 7 | #define PROT_FORCE 0x2000 8 | #define MAP_EARLY MAP_RESERVED0020 /* reuse reserved flag to skip locking */ 9 | 10 | 11 | //#define MAP_BUDDY MAP_RESERVED0040 /* reuse reserved flag to note page was already allocated from buddy system */ 12 | 13 | #define PAGE_FLOOR(a) ((uintptr_t)(a) & ~(PAGE_SIZE - 1)) 14 | #define PAGE_CEIL(a) (((uintptr_t)(a) & (PAGE_SIZE - 1)) ? PAGE_FLOOR((uintptr_t)(a) + PAGE_SIZE - 1): (uintptr_t)(a)) 15 | 16 | void kern_mmap_init(unsigned int, unsigned int, uintptr_t); 17 | void kern_munmap(uint16_t, uintptr_t, uintptr_t); 18 | int kern_mmap(uintptr_t *, void *, size_t, int, int); 19 | void kern_mmap_enable_paging(); 20 | void kern_mmap_debug (uintptr_t); 21 | uintptr_t kern_mmap_physical(uintptr_t); 22 | 23 | extern uintptr_t LOW_PAGE; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /configure_line: -------------------------------------------------------------------------------- 1 | ./otp_build autoconf 2 | #DED_LD=i386-none-elf-cc LD=i386-none-elf-cc 3 | #./configure --enable-plain-emulator --disable-sctp --disable-silent-rules --without-termcap --disable-threads --without-ssl --enable-builtin-zlib --host=i386-none-elf --disable-hipe --without-dynamic-trace 4 | # working non-smp CC="cc -m32" LDFLAGS=-L/usr/lib32 ./configure --enable-plain-emulator --disable-sctp --disable-silent-rules --disable-threads --without-ssl --disable-hipe --without-dynamic-trace --prefix=/home/toast/crazierl/installed 5 | #./configure --enable-m32-build --enable-plain-emulator --disable-sctp --disable-silent-rules --disable-threads --without-ssl --disable-hipe --without-dynamic-trace --prefix=/home/toast/crazierl/installed 6 | ./configure --enable-m32-build --enable-dirty-schedulers=no --disable-sctp --disable-silent-rules --disable-hipe --without-dynamic-trace --prefix=/home/toast/crazierl/installed --with-ssl=/usr/lib32 --with-ssl-rpath=no 7 | -------------------------------------------------------------------------------- /src/hook_module.erl: -------------------------------------------------------------------------------- 1 | -module(hook_module). 2 | -export ([parse_transform/2]). 3 | 4 | parse_transform(Forms, _Options) -> 5 | process_forms(Forms, #{}, []). 6 | 7 | process_forms([], _, Acc) -> lists:reverse(Acc); 8 | 9 | % find hook 10 | process_forms([{attribute, _Anno, hook, Hooks} | T], Map, Acc) -> 11 | process_forms(T, make_map(Hooks, Map), Acc); 12 | 13 | % modify hooked function names 14 | process_forms([{function, Anno, Name, Arity, Clause}| T], Map, Acc) when is_map_key({Name, Arity}, Map) -> 15 | process_forms(T, Map, [{function, Anno, maps:get({Name, Arity}, Map), Arity, Clause}| Acc]); 16 | process_forms([H | T], Map, Acc) -> 17 | process_forms(T, Map, [H | Acc]). 18 | 19 | make_map([], Map) -> Map; 20 | make_map([{Fun, Arity} | T], Map) -> 21 | Real = list_to_atom("real_" ++ atom_to_list(Fun)), 22 | Hook = list_to_atom("hook_" ++ atom_to_list(Fun)), 23 | make_map(T, Map#{{Fun, Arity} => Real, {Hook, Arity} => Fun}). 24 | -------------------------------------------------------------------------------- /src/console.erl: -------------------------------------------------------------------------------- 1 | -module (console). 2 | -export ([start/1, init/1]). 3 | -record (s, { 4 | ports, 5 | fd 6 | }). 7 | 8 | start(Ports) -> 9 | spawn(?MODULE, init, [Ports]). 10 | 11 | init (Ports) -> 12 | init (Ports, #s{ports = []}). 13 | 14 | init ([], State) -> open_console(State); 15 | init ([{M, F, A} | Ports], #s{ports = P}) -> 16 | init(Ports, #s{ports = [apply(M, F, A) | P]}). 17 | 18 | open_console(#s{ ports = []}) -> 19 | error_logger:error_msg("no ports for console"); 20 | open_console(State) -> 21 | {ok, FD} = gen_udp:open(0, [ 22 | {inet_backend, inet}, 23 | {ifaddr, {local, "/kern/fd/std"}}, 24 | {active, true}, 25 | binary 26 | ]), 27 | loop(State#s{fd = FD}). 28 | 29 | loop (State = #s{ports = Ports, fd = FD}) -> 30 | receive 31 | {udp, FD, _, _, Data} -> 32 | lists:foreach (fun (P) -> P ! Data end, Ports); 33 | {_Port, Data} -> 34 | gen_udp:send(FD, Data); 35 | Other -> 36 | io:format("console unexpected input ~p~n", [Other]) 37 | end, 38 | loop(State). 39 | -------------------------------------------------------------------------------- /cfg/etcpip.app: -------------------------------------------------------------------------------- 1 | %%% License: 2 | %%% This code is licensed to you under the Apache License, Version 2.0 3 | %%% (the "License"); you may not use this file except in compliance with 4 | %%% the License. You may obtain a copy of the License at 5 | %%% 6 | %%% http://www.apache.org/licenses/LICENSE-2.0 7 | %%% 8 | %%% Unless required by applicable law or agreed to in writing, 9 | %%% software distributed under the License is distributed on an 10 | %%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 | %%% KIND, either express or implied. See the License for the 12 | %%% specific language governing permissions and limitations 13 | %%% under the License. 14 | %%% 15 | 16 | {application, etcpip, 17 | [ 18 | {description, "Erlang TCP/IP Stack"}, 19 | {vsn, "1"}, 20 | {registered, []}, 21 | {applications, [ 22 | kernel, 23 | stdlib%, 24 | %crypto, 25 | %inets, 26 | %wx, runtime_tools, debugger 27 | ]}, 28 | {included_applications, []}, 29 | {mod, {etcpip_app, []}}, 30 | {env, []} 31 | ] 32 | }. 33 | -------------------------------------------------------------------------------- /kernel_src/threads.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // enum magic from https://kubyshkin.name/posts/c-language-enums-tips-and-tricks/ 5 | #define THREAD_STATE_ENUM(VARIANT) \ 6 | VARIANT(EMPTY) \ 7 | VARIANT(RUNNABLE) \ 8 | VARIANT(INITING) \ 9 | VARIANT(RUNNING) \ 10 | VARIANT(UMTX_MUTEX_WAIT) \ 11 | VARIANT(UMTX_WAIT) \ 12 | VARIANT(IO_READ) \ 13 | VARIANT(IO_WRITE) \ 14 | VARIANT(WAIT_FOREVER) \ 15 | VARIANT(POLL) \ 16 | VARIANT(IDLE) 17 | 18 | #define THREAD_STATE_ENUM_VARIANT(NAME) NAME, 19 | 20 | typedef enum { 21 | THREAD_STATE_ENUM(THREAD_STATE_ENUM_VARIANT) 22 | } thread_state; 23 | 24 | TAILQ_HEAD(threadqueue, crazierl_thread); 25 | 26 | 27 | #define THREAD_PINNED (1 << 0) 28 | 29 | struct crazierl_thread { 30 | TAILQ_ENTRY(crazierl_thread) runq; 31 | TAILQ_ENTRY(crazierl_thread) timeq; 32 | TAILQ_ENTRY(crazierl_thread) waitq; 33 | struct threadqueue *waitqhead; 34 | uint64_t timeout; 35 | uint64_t time; 36 | uint64_t start; 37 | cpuset_t cpus; 38 | thread_state state; 39 | uintptr_t kern_stack_top; 40 | uintptr_t kern_stack_cur; 41 | uintptr_t tls_base; 42 | uintptr_t wait_target; 43 | uint32_t flags; // copied in thr_new 44 | char name [MAXCOMLEN + 1]; 45 | }; 46 | 47 | -------------------------------------------------------------------------------- /kernel_src/apic.h: -------------------------------------------------------------------------------- 1 | //#ifndef _APIC_H 2 | //#define _APIC_H 3 | #include 4 | #include 5 | 6 | 7 | #define MAX_IO_APICS 8 8 | #define CLOCK_MS 10 9 | extern uint8_t timer_gsirq; 10 | extern uint8_t timer_flags; 11 | extern uintptr_t local_apic; 12 | extern size_t io_apic_count; 13 | 14 | struct io_apic { 15 | uint32_t volatile *address; 16 | unsigned int base; 17 | unsigned int numintr; 18 | }; 19 | 20 | 21 | extern struct io_apic io_apics[]; 22 | 23 | void local_apic_write(unsigned int reg, uint32_t value); 24 | uint32_t local_apic_read(unsigned int reg); 25 | void ioapic_set_gsi_vector(unsigned int irq, uint8_t flags, uint8_t vector, uint8_t physcpu); 26 | void pic_setup(int master_offset, int slave_offset); 27 | void arm_timer(uint64_t wait); 28 | void ap_clock_setup(); 29 | void clock_setup(); 30 | 31 | #define FIXED_POINT_TIME_NANOSECOND(seconds, nanoseconds) (((uint64_t) seconds << 24) + (((uint64_t) nanoseconds << 24) / 1000000000)) 32 | #define FIXED_POINT_SECONDS(fpt)(fpt >> 24) 33 | #define FIXED_POINT_MILLISECONDS(fpt) (((fpt & ((1 << 24) -1)) * 1000) >> 24) 34 | #define FIXED_POINT_MICROSECONDS(fpt) (((fpt & ((1 << 24) -1)) * 1000000) >> 24) 35 | #define FIXED_POINT_NANOSECONDS(fpt) (((fpt & ((1 << 24) -1)) * 1000000000) >> 24) 36 | #define TSC_TICK_SCALE 32 37 | 38 | extern uint64_t SCALED_S_PER_TSC_TICK; 39 | 40 | //#endif -------------------------------------------------------------------------------- /kernel_src/rand.c: -------------------------------------------------------------------------------- 1 | 2 | #include "common.h" 3 | #include "bearssl_hash.h" 4 | #include "bearssl_rand.h" 5 | #include "acpi.h" 6 | 7 | DECLARE_LOCK(rand); 8 | br_hmac_drbg_context drbg; 9 | br_hmac_drbg_context drbgs[MAX_CPUS]; 10 | __thread int seedings = 256; 11 | int global_seedings = 256; 12 | 13 | void rand_init() { 14 | LOCK(&rand); 15 | br_hmac_drbg_init(&drbg, &br_sha256_vtable, NULL, 0); 16 | for (int i = 0; i < numcpu; ++i) { 17 | br_hmac_drbg_init(&drbgs[i], &br_sha256_vtable, NULL, 0); 18 | } 19 | UNLOCK(&rand); 20 | } 21 | 22 | void rand_bytes(void * out, size_t len) { 23 | if (seedings) { 24 | char buf[br_sha256_SIZE]; 25 | LOCK(&rand); 26 | br_hmac_drbg_generate(&drbg, buf, sizeof(buf)); 27 | UNLOCK(&rand); 28 | br_hmac_drbg_update(&drbgs[current_cpu], buf, sizeof(buf)); 29 | } 30 | br_hmac_drbg_generate(&drbg, out, len); 31 | } 32 | 33 | void rand_update(const void * seed, size_t len) { 34 | br_hmac_drbg_update(&drbgs[current_cpu], seed, len); 35 | if (seedings) { 36 | --seedings; 37 | char buf[br_sha256_SIZE]; 38 | br_hmac_drbg_generate(&drbgs[current_cpu], buf, sizeof(buf)); 39 | LOCK(&rand); 40 | br_hmac_drbg_update(&drbg, buf, sizeof(buf)); 41 | br_hmac_drbg_generate(&drbg, buf, sizeof(buf)); 42 | if (global_seedings) { 43 | --global_seedings; 44 | } 45 | if (global_seedings == 0) { 46 | seedings = 0; 47 | } 48 | UNLOCK(&rand); 49 | br_hmac_drbg_update(&drbgs[current_cpu], buf, sizeof(buf)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /kernel_src/linker.ld: -------------------------------------------------------------------------------- 1 | /* The bootloader will start execution at the symbol designated as the entry point. In this case, that's 'start' (defined in start.s) */ 2 | ENTRY(_start) 3 | 4 | /* Tell the linker part of the compiler where the various sections of the kernel will be put in the final kernel executable. */ 5 | SECTIONS 6 | { 7 | /* Begin putting sections at 1 Megabyte (1M), a good place for kernels to be loaded at by the bootloader. */ 8 | /* This is because memory below 1 Megabyte is reserved for other x86-related things, so we can't use it */ 9 | 10 | /* We align all sections in the executable at multiples of 4 Kilobytes (4K). This will become useful later in development when we add paging */ 11 | 12 | /* First put the multiboot header, as it's required to be near the start of the executable otherwise the bootloader won't find it */ 13 | /* The Multiboot header is Read-Only data, so we can put it in a '.rodata' section. */ 14 | . = 1M; 15 | 16 | .rodata : AT(1M) 17 | { 18 | PROVIDE (__executable_start = .); 19 | *(.multiboot) 20 | *(.rodata) 21 | } 22 | 23 | /* Executable code */ 24 | .text : ALIGN(4K) 25 | { 26 | . = ALIGN(4K); 27 | *(.text) 28 | PROVIDE (__etext = .); 29 | 30 | /* Read-write data (initialized) */ 31 | .data : ALIGN(4K) 32 | { 33 | . = ALIGN(4K); 34 | PROVIDE (__data_start = .); 35 | *(.data) 36 | } 37 | 38 | .tdata : ALIGN(4K) 39 | { 40 | . = ALIGN(4K); 41 | PROVIDE (__tdata_start = .); 42 | *(.tdata) 43 | *(.tbss) 44 | } 45 | 46 | /* Read-write data (uninitialized) and stack */ 47 | .bss : ALIGN(4K) 48 | { 49 | . = ALIGN(4K); 50 | PROVIDE (__tdata_end = .); 51 | PROVIDE (__locks_start = .); 52 | *(locks) 53 | PROVIDE (__locks_end = .); 54 | *(COMMON) 55 | *(.bss) 56 | PROVIDE (__edata = .); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /kernel_src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef _COMMON_H 2 | #define _COMMON_H 3 | 4 | #include 5 | #include 6 | 7 | 8 | #ifdef CRAZIERL_KERNEL 9 | //#define DEBUG_PRINTF(...) term_printf(__VA_ARGS__); move_cursor() 10 | #define DEBUG_PRINTF(...) 11 | #define ERROR_PRINTF(...) term_printf(__VA_ARGS__); move_cursor() 12 | #define EARLY_ERROR_PRINTF(...) early_term_printf(__VA_ARGS__) 13 | 14 | #define FIRST_IRQ_VECTOR 0x20 15 | #define TIMER_VECTOR FIRST_IRQ_VECTOR 16 | #define SWITCH_VECTOR (FIRST_IRQ_VECTOR + 1) 17 | #define HALT_VECTOR (FIRST_IRQ_VECTOR + 2) 18 | 19 | struct lock { 20 | volatile uint32_t locked; 21 | char name[12]; 22 | uint64_t lock_time; 23 | uint64_t lock_count; 24 | uint64_t contend_time; 25 | uint64_t contend_count; 26 | uint64_t start; 27 | }; 28 | 29 | #define DECLARE_LOCK(X) struct lock X __attribute((__section__("locks"))) = { .name = #X} 30 | 31 | void RELOCK(struct lock *, size_t target); 32 | void LOCK(struct lock *); 33 | void UNLOCK(struct lock *); 34 | void ASSERT_LOCK(struct lock *); 35 | 36 | void rand_init(); 37 | void rand_bytes(void * out, size_t len); 38 | void rand_update(const void * seed, size_t len); 39 | 40 | extern __thread size_t current_thread; 41 | extern __thread size_t current_cpu; 42 | _Noreturn void halt(char * message, int dontpropagate); 43 | void move_cursor(); 44 | void term_printf(const char *, ...); 45 | void early_term_printf(const char *, ...); 46 | 47 | #endif 48 | 49 | #ifndef likely 50 | #define likely(x) __builtin_expect(!!(x), 1) 51 | #define unlikely(x) __builtin_expect(!!(x), 0) 52 | #endif /* likely and unlikely */ 53 | 54 | static inline unsigned int min(unsigned int a, unsigned int b) { 55 | if (a < b) { return a; } 56 | return b; 57 | } 58 | 59 | static inline unsigned int max(unsigned int a, unsigned int b) { 60 | if (a > b) { return a; } 61 | return b; 62 | } 63 | 64 | #define PID 2 65 | #endif 66 | -------------------------------------------------------------------------------- /kernel_src/bogfd.h: -------------------------------------------------------------------------------- 1 | #define BOGFD_MAX 1024 2 | #define BNOTE_MAX (BOGFD_MAX * 4) 3 | 4 | // enum magic from https://kubyshkin.name/posts/c-language-enums-tips-and-tricks/ 5 | #define BOGFD_TYPE_ENUM(VARIANT) \ 6 | VARIANT(CLOSED) \ 7 | VARIANT(TERMIN) \ 8 | VARIANT(TERMOUT) \ 9 | VARIANT(TERMPIPED) \ 10 | VARIANT(PIPE) \ 11 | VARIANT(BOGFD_FILE) \ 12 | VARIANT(DIR) \ 13 | VARIANT(BOGFD_NULL) \ 14 | VARIANT(IRQ) \ 15 | VARIANT(UNIX) \ 16 | VARIANT(KQUEUE) \ 17 | VARIANT(PENDING) 18 | 19 | #define BOGFD_TYPE_ENUM_VARIANT(NAME) NAME, 20 | 21 | typedef enum { 22 | BOGFD_TYPE_ENUM(BOGFD_TYPE_ENUM_VARIANT) 23 | } bogfd_type; 24 | 25 | #define BOGFD_STATUS_SIZE sizeof(int) 26 | 27 | struct pipe_buffer { 28 | ssize_t length; 29 | uint8_t data[]; 30 | }; 31 | 32 | #define BOGFD_PB_LEN ((PAGE_SIZE >> 1) - sizeof(struct pipe_buffer)) 33 | 34 | #define BOGFD_BLOCKED_READ 0x10000000 35 | #define BOGFD_BLOCKED_WRITE 0x20000000 36 | 37 | 38 | struct BogusFD { 39 | bogfd_type type; 40 | unsigned long flags; 41 | struct threadqueue waiters; 42 | struct lock lock; 43 | union { 44 | struct hardcoded_file * file; 45 | struct pipe_buffer * pb; 46 | uint8_t status[BOGFD_STATUS_SIZE]; 47 | int data; 48 | }; 49 | union { 50 | struct BogusFD * pipe; 51 | uint8_t * pos; 52 | size_t namelen; 53 | char * buffer; 54 | }; 55 | size_t bnote; 56 | }; 57 | 58 | struct bnote { // like a FreeBSD knote, but Bogus 59 | struct lock lock; 60 | size_t link; // list of knotes for a kqueue 61 | size_t selnext; // list of knotes for a watched object 62 | short filter; 63 | unsigned short flags; 64 | unsigned int fflags; 65 | __int64_t data; 66 | void *udata; 67 | int status; 68 | size_t kq; // FD index of the kqueue 69 | size_t fd; // FD index of the FD (which is all we support for now) 70 | }; 71 | 72 | // real FreeBSD filters are all negative 73 | #define BNOTE_PENDING 1 74 | -------------------------------------------------------------------------------- /scripts/push_code.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | main([Dest | Modules]) -> main(Dest, Modules). 4 | 5 | main(Dest, Modules) -> 6 | Node = list_to_atom(Dest), 7 | case net_adm:ping(Node) of 8 | pong -> ok; 9 | pang -> 10 | io:format("couldn't contact node ~s, can't push code~n", [Node]), 11 | halt(1) 12 | end, 13 | ModNames = mod_names(Modules, []), 14 | Md5Mods = fun 15 | F([M | T], Acc) -> 16 | Result = case code:is_loaded(M) of 17 | false -> case code:get_object_code(M) of 18 | {M, Bin, _File} -> code:module_md5(Bin); 19 | _ -> false 20 | end; 21 | _ -> M:module_info(md5) 22 | end, 23 | F(T, [{M, Result} | Acc]); 24 | F([], Acc) -> Acc 25 | end, 26 | 27 | 28 | Md5s = erpc:call(Node, erlang, apply, [Md5Mods, [ModNames, []]], infinity), 29 | ToLoad = filter(Md5s, []), 30 | case ToLoad of 31 | [] -> 32 | io:format("no modules changed~n", []), 33 | halt(0); 34 | _ -> 35 | LoadedModules = lists:map(fun({X, _, _}) -> X end, ToLoad), 36 | erpc:call(Node, lists, foreach, [fun code:soft_purge/1, LoadedModules], infinity), 37 | AtomicResult = erpc:call(Node, code, atomic_load, [ToLoad], infinity), 38 | case AtomicResult of 39 | ok -> 40 | io:format("code loaded succssfully, modules: ~p~n", 41 | [LoadedModules]), 42 | halt(0); 43 | _ -> 44 | io:format("code load failed ~p~n", [AtomicResult]), 45 | halt(1) 46 | end 47 | end. 48 | 49 | mod_names([M | T], Acc) -> 50 | [<<"beam">>, MB, _] = lists:reverse(binary:split(list_to_binary(M), [<<"/">>, <<".">>], [global])), 51 | Mod = binary_to_atom(MB), 52 | mod_names(T, [Mod | Acc]); 53 | mod_names([], Acc) -> Acc. 54 | 55 | filter([{M, Md5} | T], Acc) -> 56 | {M, Bin, File} = code:get_object_code(M), 57 | case code:module_md5(Bin) of 58 | Md5 -> filter(T, Acc); 59 | _ -> 60 | %io:format("load ~s ~s~n", [M, File]), 61 | filter(T, [{M, File, Bin} | Acc]) 62 | end; 63 | filter([], Acc) -> Acc. 64 | -------------------------------------------------------------------------------- /src/crazierl.erl: -------------------------------------------------------------------------------- 1 | -module(crazierl). 2 | -export([start/0, inb/1, inl/1, outb/2, outl/2, map/2, map_port/2, map_addr/1, bcopy_to/3, bcopy_from/3, time_offset/2, ntp_adjtime_freq/1]). 3 | -export([open_interrupt/1]). 4 | 5 | -on_load(init/0). 6 | 7 | init() -> 8 | ok = erlang:load_nif("/obj/crazierl_nif", 0). 9 | 10 | inb(_Port) -> exit(nif_library_not_loaded). 11 | inl(_Port) -> exit(nif_library_not_loaded). 12 | outb(_Port, _Value) -> exit(nif_library_not_loaded). 13 | outl(_Port, _Value) -> exit(nif_library_not_loaded). 14 | 15 | map(_Start, _Length) -> exit(nif_library_not_loaded). 16 | map_port(_Start, _Length) -> exit(nif_library_not_loaded). 17 | map_addr(_MapResource) -> exit(nif_library_not_loaded). 18 | bcopy_to(_MapResource, _Offset, _Binary) -> exit(nif_library_not_loaded). 19 | bcopy_from(_MapResource, _Offset, _Length) -> exit(nif_library_not_loaded). 20 | 21 | time_offset(_Seconds, _MicroSeconds) -> exit(nif_library_not_loaded). 22 | ntp_adjtime_freq(_ScaledFreq) -> exit(nif_library_not_loaded). 23 | 24 | % setup console 25 | start() -> 26 | console:start([ 27 | {comport, start, [16#3f8, "/kern/ioapic/4/0"]}, 28 | {vgakb, start, [16#60, "/kern/ioapic/1/0"]} 29 | ]), 30 | pci:start(), 31 | pci:attach(virtio_net, []), 32 | pci:attach(rtl_8168, []), 33 | try 34 | ethernet_sender ! {ping, self()}, 35 | receive 36 | pong -> ok 37 | end, 38 | example_host:start(), 39 | example_host2:start(), 40 | catch dhcpc:go() 41 | catch _:_ -> ok 42 | end, 43 | motd(). 44 | 45 | open_interrupt(Irq) -> 46 | Path = io_lib:format("/kern/irq/~B", [Irq]), 47 | {ok, Socket} = gen_udp:open(0, [ 48 | {inet_backend, inet}, 49 | {ifaddr, {local, Path}}, 50 | {active, true} 51 | ]), 52 | {ok, {local, ActualPath}} = inet:sockname(Socket), 53 | {ok, [ActualIrq], []} = io_lib:fread("/kern/irq/~d", binary_to_list(ActualPath)), 54 | {Socket, ActualIrq}. 55 | 56 | motd() -> 57 | io:format("Welcome to Crazierl: an Erlang Operating System https://crazierl.org/~n" 58 | "Crazierl is licensed Apache-2.0; Copyright (c) 2019-2024~n" 59 | "Erlang/OTP is licensed Apache-2.0; Copyright Ericsson AB 2010-2023. All Rights Reserved.~n" 60 | "FreeBSD is licensed BSD-2-Clause; Copyright (c) 1992-2021 The FreeBSD Project.~n" 61 | "BearSSL is licensed MIT; Copyright (c) 2016 Thomas Pornin ~n" 62 | ). 63 | -------------------------------------------------------------------------------- /src/comport.erl: -------------------------------------------------------------------------------- 1 | -module (comport). 2 | 3 | -export ([start/2, init/3]). 4 | -record (s, { 5 | owner, 6 | io_port, 7 | irq, 8 | buffer 9 | }). 10 | 11 | start(IoPort, Interrupt) -> 12 | spawn(?MODULE, init, [self(), IoPort, Interrupt]). 13 | 14 | init(Owner, IoPort, Interrupt) -> 15 | {ok, InterruptSocket} = gen_udp:open(0, [ 16 | {inet_backend, inet}, 17 | {ifaddr, {local, Interrupt}}, 18 | {active, true} 19 | ]), 20 | 21 | % init copied from kernel.c 22 | crazierl:outb(IoPort + 1, 16#00), % Disable all interrupts 23 | crazierl:outb(IoPort + 3, 16#80), % Enable DLAB (set baud rate divisor) 24 | crazierl:outb(IoPort + 0, 16#0C), % Set divisor to 16#0C (lo byte) 9600 baud 25 | crazierl:outb(IoPort + 1, 16#00), % 16#00 (hi byte) 26 | 27 | crazierl:outb(IoPort + 3, 16#03), % 8 bits, no parity, one stop bit 28 | crazierl:outb(IoPort + 2, 16#C7), % Enable FIFO, clear them, with 14-byte threshold 29 | crazierl:outb(IoPort + 4, 16#1B), % Loopback enabled, IRQs enabled, RTS/DSR set 30 | 31 | crazierl:outb(IoPort, 16#AE), % Write to loopback 32 | 16#AE = crazierl:inb(IoPort), % Read from loopback 33 | 34 | crazierl:outb(IoPort + 4, 16#0B), % Loopback disabled, IRQs enabled, RTS/DSR set 35 | 36 | crazierl:outb(IoPort + 1, 2#11), % request irq for data avail and transmitter empty 37 | crazierl:outb(IoPort, 16#13), % XOFF 38 | loop(port_loop(#s{owner = Owner, io_port = IoPort, irq = InterruptSocket, buffer = <<>>})). 39 | 40 | loop(State = #s{buffer = B, irq = Irq}) -> 41 | NewState = receive 42 | {udp, Irq, _, _, _} -> 43 | port_loop(State); 44 | Data when is_binary(Data) -> 45 | port_loop(State#s{buffer = <>}) 46 | end, 47 | loop(NewState). 48 | 49 | port_loop(State = #s{io_port = Port, buffer = B}) -> 50 | Status = crazierl:inb(Port + 5), 51 | case {Status band 1, Status band 32} of 52 | {1, _} -> 53 | case Status band 2 of 54 | 2 -> error_logger:error_msg("com port ~.16B overrun Status: ~.16B", [Port, Status]); 55 | 0 -> ok 56 | end, 57 | State#s.owner ! {self(), [crazierl:inb(Port)]}, 58 | port_loop(State); % prioritize reading over writing! 59 | {0, 32} when B /= <<>> -> 60 | <> = B, 61 | crazierl:outb(Port, C), 62 | port_loop(State#s{buffer = NewB}); 63 | {0, 32} -> 64 | % clear possible transmit OK interrupt 65 | _InterruptId = crazierl:inb(Port + 2), 66 | State; 67 | _ -> State 68 | end. 69 | -------------------------------------------------------------------------------- /src/eth_port.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% File : eth_port.erl 3 | %%% Author : Javier Paris Fernandez 4 | %%% Description : Ethernet Port driver 5 | %%% 6 | %%% Created : 2 Aug 2004 by Javier Paris Fernandez 7 | %%% 8 | %%% Modified: 2020 by Richard Russo 9 | %%% 10 | %%% erlang-tcpip, Copyright (C) 2004 Javier Paris 11 | %%% 12 | %%% Licensed under the Apache License, Version 2.0 (the "License"); 13 | %%% you may not use this file except in compliance with the License. 14 | %%% You may obtain a copy of the License at 15 | %%% 16 | %%% http://www.apache.org/licenses/LICENSE-2.0 17 | %%% 18 | %%% Unless required by applicable law or agreed to in writing, software 19 | %%% distributed under the License is distributed on an "AS IS" BASIS, 20 | %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | %%% See the License for the specific language governing permissions and 22 | %%% limitations under the License. 23 | %%% 24 | %%%------------------------------------------------------------------- 25 | -module(eth_port). 26 | 27 | % API 28 | -export([start_reader/1, start_writer/0, send/1, get_instance/0, get_mtu/0]). 29 | 30 | %--- API ----------------------------------------------------------------------- 31 | 32 | start_reader(Iface) -> 33 | etcpip_proc:start_link(eth_port_reader, #{ 34 | init => fun() -> ok end, 35 | handle_call => fun reader_call/3 36 | }). 37 | 38 | start_writer() -> 39 | etcpip_proc:start_link(eth_port_writer, #{ 40 | init => fun() -> ok end, 41 | handle_cast => fun writer_cast/2 42 | }). 43 | 44 | send(Packet) -> etcpip_proc:cast(eth_port_writer, {send, Packet}). 45 | 46 | get_instance() -> etcpip_proc:call(eth_port_reader, get_instance). 47 | 48 | get_mtu() -> {mtu, etcpip_proc:call(eth_port_reader, get_mtu)}. 49 | 50 | %--- Reader -------------------------------------------------------------------- 51 | 52 | reader_call(get_instance, _From, State) -> 53 | {reply, {self()}, State}; 54 | reader_call(get_mtu, _From, State) -> 55 | %<> = list_to_binary(port_control(Port, 2, [])), 56 | {reply, 576, State}. 57 | 58 | %--- Writer -------------------------------------------------------------------- 59 | 60 | writer_init() -> eth_port:get_instance(). 61 | 62 | writer_cast({send, Packet}, State) -> 63 | gen_server:call(ethernet_sender, {send, Packet}), 64 | {noreply, State}. 65 | -------------------------------------------------------------------------------- /src/example_host.erl: -------------------------------------------------------------------------------- 1 | -module (example_host). 2 | 3 | % example tcp listener 4 | 5 | -export([start/0]). 6 | -export([worker_loop/2]). 7 | 8 | start() -> 9 | spawn(fun init/0). 10 | 11 | init() -> 12 | Sock = etcpip_socket:listen(80), 13 | io:format("Sock ~w~n", [Sock]), 14 | accept_loop(Sock). 15 | 16 | accept_loop(ListenSock) -> 17 | {ok, Socket} = etcpip_socket:accept(ListenSock), 18 | _Worker = spawn(?MODULE, worker_loop, [Socket, <<>>]), 19 | accept_loop(ListenSock). 20 | 21 | worker_loop(Socket, Bin) -> 22 | case erlang:decode_packet(http_bin, Bin, []) of 23 | {more, _} -> 24 | {ok, Data} = etcpip_socket:recv(Socket, 0, [], infinity), 25 | worker_loop(Socket, <>); 26 | {ok, {http_request, Method, Uri, Version}, Rest} -> 27 | header_loop(Socket, {Method, Uri, Version}, [], Rest) 28 | end. 29 | 30 | header_loop(Socket, Request, Headers, Bin) -> 31 | case erlang:decode_packet(httph_bin, Bin, []) of 32 | {more, _} -> 33 | {ok, Data} = etcpip_socket:recv(Socket, 0, [], infinity), 34 | header_loop(Socket, Request, Headers, <>); 35 | {ok, http_eoh, _Rest} -> 36 | output(Socket, Request, lists:reverse(Headers)); 37 | {ok, {http_header, Int, Field, OrigField, Val}, Rest} -> 38 | header_loop(Socket, Request, [{Int, Field, OrigField, Val}|Headers], Rest) 39 | end. 40 | 41 | output(Socket, {_Method, {abs_path, <<"/process_info">>}, _Version}, _Headers) -> 42 | Response = <<"HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n">>, 43 | etcpip_socket:send(Socket, Response, [], infinity), 44 | lists:foreach(fun (P) -> 45 | etcpip_socket:send(Socket, iolist_to_binary(io_lib:format( 46 | "~n~p~n~p~n", [P, process_info(P)])), [], infinity) 47 | end, processes()), 48 | 49 | etcpip_socket:close(Socket); 50 | 51 | output(Socket, {_Method, {abs_path, <<"/processes">>}, _Version}, _Headers) -> 52 | Response = iolist_to_binary([ 53 | "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n", 54 | io_lib:format("~B", [length(processes())]), "\r\n" 55 | ]), 56 | etcpip_socket:send(Socket, Response, [], infinity), 57 | etcpip_socket:close(Socket); 58 | 59 | output(Socket, {_Method, {abs_path, <<"/memory">>}, _Version}, _Headers) -> 60 | Response = iolist_to_binary([ 61 | "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n", 62 | io_lib:format("~w", [erlang:memory()]), "\r\n" 63 | ]), 64 | 65 | etcpip_socket:send(Socket, Response, [], infinity), 66 | etcpip_socket:close(Socket); 67 | 68 | output(Socket, {_Method, _Uri, _Version}, _Headers) -> 69 | Response = <<"HTTP/1.0 404 Not Found\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n" 70 | "Not found\r\n">>, 71 | etcpip_socket:send(Socket, Response, [], infinity), 72 | etcpip_socket:close(Socket). 73 | -------------------------------------------------------------------------------- /src/example_host2.erl: -------------------------------------------------------------------------------- 1 | -module (example_host2). 2 | 3 | % example tcp listener 4 | 5 | -export([start/0]). 6 | -export([worker_loop/2]). 7 | 8 | start() -> 9 | spawn(fun init/0). 10 | 11 | init() -> 12 | % {ok, Sock} = gen_tcp:listen(8080, [{inet_backend, socket}, binary, {packet, 0}, {active, false}, {reuseaddr, true}, {ip, any}]), 13 | {ok, Sock} = gen_tcp:listen(8080, [binary, {packet, 0}, {active, false}, {reuseaddr, true}, {ip, any}]), 14 | io:format("Sock ~w~n", [Sock]), 15 | accept_loop(Sock). 16 | 17 | accept_loop(ListenSock) -> 18 | {ok, Socket} = gen_tcp:accept(ListenSock), 19 | _Worker = spawn(?MODULE, worker_loop, [Socket, <<>>]), 20 | accept_loop(ListenSock). 21 | 22 | worker_loop(Socket, Bin) -> 23 | case erlang:decode_packet(http_bin, Bin, []) of 24 | {more, _} -> 25 | {ok, Data} = gen_tcp:recv(Socket, 0), 26 | worker_loop(Socket, <>); 27 | {ok, {http_request, Method, Uri, Version}, Rest} -> 28 | header_loop(Socket, {Method, Uri, Version}, [], Rest) 29 | end. 30 | 31 | header_loop(Socket, Request, Headers, Bin) -> 32 | case erlang:decode_packet(httph_bin, Bin, []) of 33 | {more, _} -> 34 | {ok, Data} = gen_tcp:recv(Socket, 0), 35 | header_loop(Socket, Request, Headers, <>); 36 | {ok, http_eoh, _Rest} -> 37 | output(Socket, Request, lists:reverse(Headers)); 38 | {ok, {http_header, Int, Field, OrigField, Val}, Rest} -> 39 | header_loop(Socket, Request, [{Int, Field, OrigField, Val}|Headers], Rest) 40 | end. 41 | 42 | output(Socket, {_Method, {abs_path, <<"/process_info">>}, _Version}, _Headers) -> 43 | Response = <<"HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n">>, 44 | gen_tcp:send(Socket, Response), 45 | lists:foreach(fun (P) -> 46 | gen_tcp:send(Socket, iolist_to_binary(io_lib:format( 47 | "~n~p~n~p~n", [P, process_info(P)]))) 48 | end, processes()), 49 | 50 | gen_tcp:close(Socket); 51 | 52 | output(Socket, {_Method, {abs_path, <<"/processes">>}, _Version}, _Headers) -> 53 | Response = iolist_to_binary([ 54 | "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n", 55 | io_lib:format("~B", [length(processes())]), "\r\n" 56 | ]), 57 | gen_tcp:send(Socket, Response), 58 | gen_tcp:close(Socket); 59 | 60 | output(Socket, {_Method, {abs_path, <<"/memory">>}, _Version}, _Headers) -> 61 | Response = iolist_to_binary([ 62 | "HTTP/1.0 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n", 63 | io_lib:format("~w", [erlang:memory()]), "\r\n" 64 | ]), 65 | 66 | gen_tcp:send(Socket, Response), 67 | gen_tcp:close(Socket); 68 | 69 | output(Socket, {_Method, _Uri, _Version}, _Headers) -> 70 | Response = <<"HTTP/1.0 404 Not Found\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n" 71 | "Not found\r\n">>, 72 | gen_tcp:send(Socket, Response), 73 | gen_tcp:close(Socket). 74 | -------------------------------------------------------------------------------- /overrides/socket.erl: -------------------------------------------------------------------------------- 1 | -hook([open/4, setopt/3, getopt/2, bind/2, listen/2, accept/2, connect/3, 2 | recv/4, send/4, sendto/5, recvfrom/4, 3 | close/1, sockname/1, peername/1, info/1, cancel/2]). 4 | -include_lib("kernel/src/socket.erl"). 5 | 6 | hook_open(inet, stream, tcp, Opts) -> 7 | Sock = etcpip_socket:open(tcp, Opts), 8 | {ok, {etcpip, Sock}}; 9 | hook_open(inet, dgram, udp, Opts) -> 10 | Sock = etcpip_socket:open(udp, Opts), 11 | {ok, {etcpip, Sock}}; 12 | hook_open(Domain, Type, Protocol, Opts) -> real_open(Domain, Type, Protocol, Opts). 13 | 14 | hook_setopt({etcpip, Sock}, Opt, Val) -> etcpip_socket:setopt(Sock, Opt, Val); 15 | hook_setopt(Sock, Opt, Val) -> real_setopt(Sock, Opt, Val). 16 | 17 | hook_getopt({etcpip, Sock}, Opt) -> etcpip_socket:getopt(Sock, Opt); 18 | hook_getopt(Sock, Opt) -> real_getopt(Sock, Opt). 19 | 20 | hook_bind({etcpip, Sock}, Addr) -> etcpip_socket:bind(Sock, Addr); 21 | hook_bind(Sock, Addr) -> real_bind(Sock, Addr). 22 | 23 | hook_listen({etcpip, Sock}, Backlog) -> etcpip_socket:listen(Sock, Backlog); 24 | hook_listen(Sock, Backlog) -> real_listen(Sock, Backlog). 25 | 26 | hook_accept({etcpip, Sock}, Timeout) -> 27 | case etcpip_socket:accept(Sock, Timeout) of 28 | {ok, NewSock} -> {ok, {etcpip, NewSock}}; 29 | O -> O 30 | end; 31 | hook_accept(Sock, Timeout) -> real_accept(Sock, Timeout). 32 | 33 | hook_connect({etcpip, Sock}, SockAddr, Timeout) -> 34 | etcpip_socket:connect(Sock, SockAddr, Timeout); 35 | hook_connect(Sock, SockAddr, Timeout) -> real_connect(Sock, SockAddr, Timeout). 36 | 37 | hook_recv({etcpip, Sock}, Length, Options, Timeout) -> etcpip_socket:recv(Sock, Length, Options, Timeout); 38 | hook_recv(Sock, Length, Options, Timeout) -> real_recv(Sock, Length, Options, Timeout). 39 | 40 | hook_recvfrom({etcpip, Sock}, Length, Options, Timeout) -> etcpip_socket:recvfrom(Sock, Length, Options, Timeout); 41 | hook_recvfrom(Sock, Length, Options, Timeout) -> real_recvfrom(Sock, Length, Options, Timeout). 42 | 43 | hook_send({etcpip, Sock}, Data, Flags, Timeout) -> etcpip_socket:send(Sock, Data, Flags, Timeout); 44 | hook_send(Sock, Data, Flags, Timeout) -> real_send(Sock, Data, Flags, Timeout). 45 | 46 | hook_sendto({etcpip, Sock}, Data, Dest, Flags, Timeout) -> etcpip_socket:sendto(Sock, Data, Dest, Flags, Timeout); 47 | hook_sendto(Sock, Data, Dest, Flags, Timeout) -> real_sendto(Sock, Data, Dest, Flags, Timeout). 48 | 49 | hook_close({etcpip, Sock}) -> etcpip_socket:close(Sock); 50 | hook_close(Sock) -> real_close(Sock). 51 | 52 | hook_sockname({etcpip, Sock}) -> etcpip_socket:sockname(Sock); 53 | hook_sockname(Sock) -> real_sockname(Sock). 54 | 55 | hook_peername({etcpip, Sock}) -> etcpip_socket:peername(Sock); 56 | hook_peername(Sock) -> real_peername(Sock). 57 | 58 | hook_info({etcpip, Sock}) -> etcpip_socket:info(Sock); 59 | hook_info(Sock) -> real_info(Sock). 60 | 61 | hook_cancel({etcpip, Sock}, Info) -> etcpip_socket:cancel(Sock, Info); 62 | hook_cancel(Sock, Info) -> real_cancel(Sock, Info). 63 | -------------------------------------------------------------------------------- /src/acpi.erl: -------------------------------------------------------------------------------- 1 | -module (acpi). 2 | 3 | -export ([go/0]). 4 | 5 | go() -> 6 | {ok, Map} = crazierl:map(16#E0000, 32 * 4096), 7 | BiosMem = crazierl:bcopy_from(Map, 0, 32 * 4096), 8 | find_rsdp(BiosMem). 9 | 10 | 11 | find_rsdp(<<"RSD PTR ", Rest/binary>>) -> 12 | <> = Rest, 13 | 0 = checksum(<<"RSD PTR ", Checksum, OEMID:6/binary, Version, RsdtAddress:32/little>>), 14 | 15 | io:format("found checksum=~B, OEMID=~s, Version=~B, Address = ~.16B, togo = ~B~n", 16 | [Checksum, OEMID, Version, RsdtAddress, size(Rest)]), 17 | 18 | Base = RsdtAddress band (bnot 4095), 19 | {ok, Map} = crazierl:map(Base, 4096), 20 | read_table(Map, Base, RsdtAddress - Base); 21 | 22 | find_rsdp(<<_:16/binary, Rest/binary>>) -> find_rsdp(Rest); 23 | find_rsdp(<<>>) -> not_found. 24 | 25 | read_table_pointers(_, _, <<>>) -> ok; 26 | read_table_pointers(Map, Base, <>) -> 27 | read_table(Map, Base, Address - Base), 28 | read_table_pointers(Map, Base, Rest). 29 | 30 | read_table(Map, Base, Start) -> 31 | <> = crazierl:bcopy_from(Map, Start, 8), 32 | Rest = crazierl:bcopy_from(Map, Start + 8, Length - 8), 33 | 0 = checksum (<>), 34 | 35 | <> = Rest, 38 | 39 | case Signature of 40 | <<"RSDT">> -> 41 | read_table_pointers(Map, Base, Content); 42 | <<"APIC">> -> 43 | <> = Content, 44 | io:format("local Apic at ~.16B, Flags: ~.16B~n", [LocalApic, Flags]), 45 | read_apic_table(More); 46 | _ -> 47 | io:format("found ~s table, length=~B, revision=~B, " 48 | "OEMID=~s, OEMTableId=~s, oemrevision=~B, creatorID=~.16B, " 49 | "creatorRevision=~B, content=~2000p~n", 50 | [Signature, Length, Revision, OEMId, OEMTableId, OEMRevision, 51 | CreatorId, CreatorRevision, Content]) 52 | end. 53 | 54 | read_apic_table(<<>>) -> ok; 55 | read_apic_table(<<0, 8, ProcessorId, ApicId, Flags:32/little, Rest/binary>>) -> 56 | io:format("Processor ~B, APIC ~B, Flags, ~B~n", [ProcessorId, ApicId, Flags]), 57 | read_apic_table(Rest); 58 | read_apic_table(<<1, 12, ApicId, 0, ApicAddress:32/little, InterruptBase:32/little, Rest/binary>>) -> 59 | io:format("IO-APIC ~B, Address ~.16B, Interrupt Base ~.16B~n", [ApicId, ApicAddress, InterruptBase]), 60 | read_apic_table(Rest); 61 | read_apic_table(<<2, 10, BusSource, IRQSource, GlobalSystemInterrupt:32/little, Flags:16/little, Rest/binary>>) -> 62 | io:format("ISO: BusSource ~B, IRQ Source ~B, Global Int ~B, Flags ~.16B~n", [BusSource, IRQSource, GlobalSystemInterrupt, Flags]), 63 | read_apic_table(Rest); 64 | read_apic_table(<<4, 6, ProcessorId, Flags:16/little, LINT, Rest/binary>>) -> 65 | io:format("NMI: processor ~B, flags ~.16B, Lint ~B~n", [ProcessorId, Flags, LINT]), 66 | read_apic_table(Rest); 67 | read_apic_table(<<5, 12, _:2/binary, LocalAddress:64/little, Rest/binary>>) -> 68 | io:format("local apic override at ~.16B~n", [LocalAddress]), 69 | read_apic_table(Rest). 70 | 71 | 72 | checksum(Binary) -> checksum(0, Binary). 73 | checksum(C, <>) -> checksum((C + V) band 16#ff, Rest); 74 | checksum(C, <<>>) -> C. 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /scripts/hardcode_files.pl: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use Cwd; 6 | use File::Basename; 7 | use bytes; 8 | use Data::Dumper; 9 | use File::Temp; 10 | #use Compress::LZ4; 11 | use v5.25; 12 | 13 | sub slurp 14 | { 15 | my ($filename) = @_; 16 | local $/ = undef; 17 | open my $file, '<', $filename or die "can't open $filename: $!"; 18 | my $data = <$file>; 19 | if (substr($data, 0, 4) eq "\x7FELF") { 20 | my $tmp = File::Temp->new(); 21 | if (system("strip $filename -o $tmp") == 0) { 22 | my $stripped = <$tmp>; 23 | if (length($stripped) < length($data)) { 24 | print STDERR "using stripped instead of raw for $filename ", length($stripped), " < ", length($data), "\n"; 25 | return $stripped; 26 | } 27 | } 28 | } 29 | return $data; 30 | } 31 | 32 | my ($TARGET, $RTLD, $OTP_DIR, @LOCAL_FILES) = @ARGV; 33 | 34 | my %FILES; 35 | 36 | push @LOCAL_FILES, $RTLD; 37 | 38 | open my $erl, '<', "$OTP_DIR/bin/erl" or die "can't open erl: $!"; 39 | 40 | my $bindir; 41 | while (<$erl>) { 42 | if (m@BINDIR="\$ROOTDIR/(.*)"$@) { 43 | $bindir = $1; 44 | last; 45 | } 46 | } 47 | if (!$bindir) { 48 | die "couldn't find BINDIR\n"; 49 | } 50 | 51 | push @LOCAL_FILES, "OTPDIR/$bindir/beam.smp"; 52 | 53 | $FILES{'bin/start.boot'} = slurp("$OTP_DIR/bin/start.boot"); 54 | 55 | my @start_script = `$OTP_DIR/bin/escript ./scripts/extract_start.escript $OTP_DIR/bin/start.script`; 56 | die "couldn't run extract_start.escript" unless $? == 0; 57 | 58 | chomp (@start_script); 59 | my @path = (); 60 | my %basenames = ($TARGET => "target"); 61 | foreach my $line (@start_script) { 62 | my ($type, @rest) = split /\t/, $line; 63 | if ($type eq 'path') { 64 | @path = (); 65 | foreach my $p (@rest) { 66 | $p =~ s@^\$ROOT/@@; 67 | push @path, $p; 68 | } 69 | } elsif ($type eq 'primLoad') { 70 | foreach my $m (@rest) { 71 | my $found = 0; 72 | foreach my $p (@path) { 73 | my $path_name = "$p/$m.beam"; 74 | my $full_name = "$OTP_DIR/$path_name"; 75 | if (-r $full_name) { 76 | $FILES{$path_name} = slurp ($full_name); 77 | $basenames{basename($path_name)} = $path_name; 78 | $found = 1; 79 | } 80 | } 81 | if (!$found) { 82 | die "couldn't find $m.beam"; 83 | } 84 | } 85 | } 86 | } 87 | 88 | foreach my $f (@LOCAL_FILES) { 89 | next unless $f; 90 | my $path = $f; 91 | if ($f =~ m@^OTPDIR/(.*)$@) { 92 | $f = $1; 93 | ($f) = glob ("$OTP_DIR/$f"); 94 | if (!$f) { 95 | die "couldn't glob $path $OTP_DIR"; 96 | } 97 | if (substr($f, 0, length($OTP_DIR) + 1) eq "$OTP_DIR/") { 98 | substr($f, 0, length($OTP_DIR) + 1, ''); 99 | } else { 100 | die "couldn't find $f"; 101 | } 102 | $path = "$OTP_DIR/$f"; 103 | } 104 | my $file; 105 | open $file, '<', "$path" or die "can't open $path: $!"; 106 | $f =~ s@^/@@; 107 | my $alt = $basenames{basename($f)}; 108 | if ($alt) { 109 | $f = $alt; 110 | } 111 | { 112 | local $/ = undef; 113 | $FILES{$f} = <$file>; 114 | } 115 | if ($f eq 'target' || $f =~ /\.so$/) { 116 | my @LDD = `/usr/bin/ldd $path`; 117 | foreach my $l (@LDD) { 118 | if ($l =~ m@ => (.*) \(@) { 119 | push @LOCAL_FILES, $1; 120 | } 121 | } 122 | } 123 | } 124 | 125 | my $strlen_total = 0; 126 | 127 | foreach my $file (keys %FILES) { 128 | $strlen_total += length($file) + 1; 129 | } 130 | 131 | print pack('N', scalar(%FILES)), pack('N', $strlen_total); 132 | 133 | foreach my $file (sort keys %FILES) { 134 | my $data = $FILES{$file}; 135 | # my $compressed_data = lz4_compress_hc($data, 16); 136 | # if (length($compressed_data) + 4 < length($data)) { 137 | # print "/", $file, "\0", pack('NN', length($data) | 0x80000000, length($compressed_data)), $compressed_data; 138 | # } else { 139 | print "/", $file, "\0", pack('N', length($data)), $data; 140 | # } 141 | } 142 | -------------------------------------------------------------------------------- /src/ntp.erl: -------------------------------------------------------------------------------- 1 | -module(ntp). 2 | 3 | -define (PORT, 123). 4 | 5 | -export([go/0]). 6 | 7 | 8 | go() -> 9 | List = go([], 8, 2000), 10 | Now = erlang:convert_time_unit(os:system_time(), native, nanosecond), 11 | RelativeList = lists:map(fun ({T, O}) -> {T - Now, O} end, List), 12 | {SumX, SumY, SumXX, SumXY, N} = sums(RelativeList, 0, 0, 0, 0, 0), 13 | 14 | % https://www.mathsisfun.com/data/least-squares-regression.html 15 | % m = (N Σ(xy) − Σx Σy) / N Σ(x * x) − (Σx)2 16 | % b = (Σy − m Σx) / N 17 | 18 | Frequency = (N * SumXY - SumX * SumY) / (N * SumXX - SumX * SumX), 19 | Offset = (SumY - Frequency * SumX) / N, 20 | io:format("offset = ~f x + ~f~n", [Frequency * 1_000_000, Offset]), 21 | MicroSeconds = round(Offset) div 1_000, 22 | Seconds = MicroSeconds div 1_000_000, 23 | MicroSeconds2 = MicroSeconds rem 1_000_000, 24 | io:format("~B seconds, ~B us~n", [Seconds, MicroSeconds2]), 25 | ok = crazierl:time_offset(Seconds, MicroSeconds2), 26 | ScaledFrequency = round(Frequency * 1_000_000 * (1 bsl 16)), 27 | io:format("Scaled PPM ~B~n", [ScaledFrequency]), 28 | ok = crazierl:ntp_adjtime_freq(ScaledFrequency), 29 | RelativeList. 30 | 31 | sums([], X, Y, XX, XY, N) -> {X, Y, XX, XY, N}; 32 | sums([{T, O} | Tail], X, Y, XX, XY, N) -> 33 | sums(Tail, X + T, Y + O, XX + (T * T), XY + (T * O), N +1). 34 | 35 | go(Acc, Count, Wait) -> 36 | {Acc1, Wait1} = case fetch() of 37 | {OriginNS, ResponseNS, Response, SendOrigin} -> 38 | <>= Response, 42 | ReferenceNS = convert_ntp_to_nanoseconds(ReferenceTS), 43 | ReceiveNS = convert_ntp_to_nanoseconds(ReceiveTS), 44 | TransmitNS = convert_ntp_to_nanoseconds(TransmitTS), 45 | 46 | Delay = ResponseNS - OriginNS - (TransmitNS - ReceiveNS), 47 | Offset = (TransmitNS + ReceiveNS) div 2 - (ResponseNS + OriginNS) div 2, 48 | 49 | case Stratum of 50 | 0 when ReferenceId == <<"RATE">> -> 51 | io:format("rate limited, doubling wait~n", []), 52 | {Acc, Wait * 2}; 53 | 0 -> 54 | io:format("got ~p~n", 55 | [#{delay => Delay, 56 | offset => Offset, 57 | originNs => OriginNS, 58 | responseNs => ResponseNS, 59 | leap => LeapIndicator, 60 | stratum => Stratum, 61 | precision => Precision, 62 | rootDelay => RootDelay, 63 | dispersion => Dispersion, 64 | referenceId => ReferenceId, 65 | referenceNS => ReferenceNS, 66 | receiveNS => ReceiveNS, 67 | transmitNS => TransmitNS, 68 | poll => Poll}]), 69 | {Acc, Wait}; 70 | _ -> 71 | {[{OriginNS + (ResponseNS - OriginNS) div 2, Offset} | Acc], Wait} 72 | end; 73 | timeout -> {Acc, Wait * 2} 74 | end, 75 | 76 | case Count of 77 | 1 -> Acc1; 78 | _ -> 79 | timer:sleep(Wait1), 80 | go(Acc1, Count - 1, Wait1) 81 | end. 82 | 83 | convert_ntp_to_nanoseconds(<>) -> 84 | UnixSeconds = Seconds - 2_208_988_800, % RFC 5905 Figure 4 85 | FractionNanos = erlang:convert_time_unit(Fraction, second, nanosecond) div 16#1_0000_0000, 86 | erlang:convert_time_unit(UnixSeconds, second, nanosecond) + FractionNanos. 87 | 88 | fetch() -> 89 | {ok, Port} = gen_udp:open(0, [binary]), 90 | 91 | {OriginTS, SendOrigin} = {os:system_time(), crypto:strong_rand_bytes(8)}, 92 | 93 | ok = gen_udp:send(Port, "192.168.0.10", ?PORT, 94 | << 0:2, 4:3, 3:3, 0:8, % no leap info, version 4, client, no stratum 95 | 0:8, 0:8, 0:32, 0:32, % no poll, precision, root delay, dispersion 96 | 0:32, 0:64, % no reference ID, timestamp 97 | 0:64, 0:64, % no origin or receive timestamp 98 | SendOrigin/binary>>), % send random transmit timestamp, no extensions 99 | R = receive 100 | {udp, Port, _Ip, ?PORT, Response} -> 101 | ResponseTS = os:system_time(), 102 | {erlang:convert_time_unit(OriginTS, native, nanosecond), 103 | erlang:convert_time_unit(ResponseTS, native, nanosecond), 104 | Response, SendOrigin} 105 | after 2000 -> timeout 106 | end, 107 | gen_udp:close(Port), 108 | R. 109 | -------------------------------------------------------------------------------- /kernel_src/strtol.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: BSD-3-Clause 3 | * 4 | * Copyright (c) 1990, 1993 5 | * The Regents of the University of California. All rights reserved. 6 | * 7 | * This code is derived from software contributed to Berkeley by 8 | * Chris Torek. 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions 12 | * are met: 13 | * 1. Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 2. Redistributions in binary form must reproduce the above copyright 16 | * notice, this list of conditions and the following disclaimer in the 17 | * documentation and/or other materials provided with the distribution. 18 | * 3. Neither the name of the University nor the names of its contributors 19 | * may be used to endorse or promote products derived from this software 20 | * without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 | * SUCH DAMAGE. 33 | * 34 | * From: @(#)strtol.c 8.1 (Berkeley) 6/4/93 35 | */ 36 | 37 | #include 38 | #include 39 | 40 | /* 41 | * Convert a string to a long integer. 42 | * 43 | * Ignores `locale' stuff. Assumes that the upper and lower case 44 | * alphabets and digits are each contiguous. 45 | */ 46 | long 47 | strtol(const char *nptr, char **endptr, int base) 48 | { 49 | const char *s = nptr; 50 | unsigned long acc; 51 | unsigned char c; 52 | unsigned long cutoff; 53 | int neg = 0, any, cutlim; 54 | 55 | c = *s++; 56 | if (c == '-') { 57 | neg = 1; 58 | c = *s++; 59 | } else if (c == '+') 60 | c = *s++; 61 | if ((base == 0 || base == 16) && 62 | c == '0' && (*s == 'x' || *s == 'X')) { 63 | c = s[1]; 64 | s += 2; 65 | base = 16; 66 | } 67 | if (base == 0) 68 | base = c == '0' ? 8 : 10; 69 | 70 | /* 71 | * Compute the cutoff value between legal numbers and illegal 72 | * numbers. That is the largest legal value, divided by the 73 | * base. An input number that is greater than this value, if 74 | * followed by a legal input character, is too big. One that 75 | * is equal to this value may be valid or not; the limit 76 | * between valid and invalid numbers is then based on the last 77 | * digit. For instance, if the range for longs is 78 | * [-2147483648..2147483647] and the input base is 10, 79 | * cutoff will be set to 214748364 and cutlim to either 80 | * 7 (neg==0) or 8 (neg==1), meaning that if we have accumulated 81 | * a value > 214748364, or equal but the next digit is > 7 (or 8), 82 | * the number is too big, and we will return a range error. 83 | * 84 | * Set any if any `digits' consumed; make it negative to indicate 85 | * overflow. 86 | */ 87 | cutoff = neg ? -(unsigned long)LONG_MIN : LONG_MAX; 88 | cutlim = cutoff % (unsigned long)base; 89 | cutoff /= (unsigned long)base; 90 | for (acc = 0, any = 0;; c = *s++) { 91 | if ('c' >= 0 && c <= '9') 92 | c -= '0'; 93 | else if ('c' >= 'A' && c <= 'Z') 94 | c -= 'A' - 10; 95 | else if ('c' >= 'a' && c <= 'z') 96 | c -= 'a' - 10; 97 | else 98 | break; 99 | if (c >= base) 100 | break; 101 | if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) 102 | any = -1; 103 | else { 104 | any = 1; 105 | acc *= base; 106 | acc += c; 107 | } 108 | } 109 | if (any < 0) { 110 | acc = neg ? LONG_MIN : LONG_MAX; 111 | } else if (neg) 112 | acc = -acc; 113 | if (endptr != NULL) 114 | *endptr = __DECONST(char *, any ? s - 1 : nptr); 115 | return (acc); 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Crazierl](https://crazierl.org) 2 | 3 | **Crazierl** is an Erlang operating system. 4 | 5 | **Erlang** is a programming language and runtime system for building massively scalable soft real-time systems with requirements on high availability. 6 | 7 | [Learn more about Erlang and OTP](http://erlang.org/doc/system_architecture_intro/sys_arch_intro.html). 8 | 9 | ## What does it do 10 | 11 | [See the demo](https://crazierl.org/demo.html) 12 | 13 | Crazierl is just enough of an OS to run BEAM, the Erlang/OTP VM for x86 PCs 14 | (and virtual machines). The console is the Erlang shell. Most drivers will 15 | be written in Erlang, and can be hot loaded. 16 | 17 | Drivers are present for text mode vga, pc keyboard, pc com port, virtio-net, 18 | and rtl_8168. 19 | 20 | erlang-tcpip enables a basic tcp-ip stack, and there is an example dhcp 21 | client, ntp client, and simple http server. 22 | 23 | Dist works (milestone 2), with -proto_dist gen_tcp, a custom epmd, and a little help here 24 | and there with cookies and things. 25 | 26 | Note: networking in the v86 demo is coming soon; v86 and crazierl both 27 | support virtio-net. 28 | 29 | ## Where does it run 30 | 31 | Runs on qemu, v86, and some real hardware. Needs a multiboot loader (tested 32 | on the built in multiboot loader for qemu and v86, as well as ipxe and 33 | grub). ACPI required, CPU support for SSE is required. The TSC is treated as 34 | invariant, but it's not a fatal error, as many VMs don't support it. SMP 35 | works (milestone 1), but is untested at large core counts; over 256 cores is definitely 36 | not going to work. 37 | 38 | ## How does it work 39 | 40 | Crazierl provides a (roughly) FreeBSD compatible syscall interface, and can 41 | load and run FreeBSD executables from an initrd filesystem. The Makefile 42 | will prepare an initrd that includes many files from the pkg distribution of 43 | Erlang, the FreeBSD rtld, and object files from this source, including some 44 | which override files in the Erlang distribution. 45 | 46 | ## How to build 47 | 48 | Should build with gmake on FreeBSD matching the version returned from 49 | syscall\_\_\_sysctl in kernel_src/kernel.c This will use Erlang/OTP from pkg, 50 | but you can change OPTDIR in Makefile to use a different build. Using a 51 | checked build can be very helpful for finding kernel bugs that manifest in 52 | returning improper results to BEAM. Crazierl is (currently) 32-bit, but can 53 | build on 32-bit or 64-bit FreeBSD. Send me email if you have trouble 54 | building. 55 | 56 | ## What is it based on 57 | 58 | Erlang/OTP provides for the userland and was the inspiration for the 59 | project. 60 | 61 | FreeBSD provides many things: the syscall interface, libraries and 62 | utilities, rtld: the dynamic loader. 63 | 64 | BearSSL, from FreeBSD contrib, provides the blocks to build kernel random. 65 | 66 | Erlang-Tcpip provides the tcp-ip stack. 67 | 68 | GNU Make builds it all. 69 | 70 | Initial structure of the kernel is from 71 | https://wiki.osdev.org/User:Zesterer/Bare_Bones as well as lots of other 72 | knowledge from the OS Dev wikis and forums; thank you! 73 | 74 | ## Similar projects 75 | 76 | [Ling](https://github.com/cloudozer/ling): ErlangOnXen -- an alternate VM 77 | for Erlang targetted to be a Xen guest. 78 | 79 | [HydrOS](http://www.erlang-factory.com/euc2017/sam-williams) presentation 80 | available; but I didn't find code? Seems to have a focus on a capability 81 | security model. Presentation includes some additional projects. 82 | 83 | ## Why 84 | 85 | An excellent question. It seemed like a good idea to explore the possibility 86 | of Erlang as its own system. 87 | 88 | ## Who did this 89 | 90 | [Richard Russo](mailto:crazierl@ruka.org) and friends. 91 | 92 | ## License 93 | 94 | Copyright \[2019-2024\] 95 | 96 | Licensed under the Apache License, Version 2.0 (the "License"); 97 | you may not use this file except in compliance with the License. 98 | You may obtain a copy of the License at 99 | 100 | [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 101 | 102 | Unless required by applicable law or agreed to in writing, software 103 | distributed under the License is distributed on an "AS IS" BASIS, 104 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 105 | See the License for the specific language governing permissions and 106 | limitations under the License. 107 | -------------------------------------------------------------------------------- /kernel_src/files.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "files.h" 3 | #include "kern_mmap.h" 4 | #include 5 | #include 6 | //#include 7 | 8 | 9 | struct { 10 | size_t count; 11 | size_t size; 12 | struct hardcoded_file files[]; 13 | } *hardcoded_files = NULL; 14 | 15 | uint32_t unpack_network(uintptr_t start) { 16 | return *(uint8_t*)(start + 3) | (*(uint8_t*)(start + 2) << 8) 17 | | (*(uint8_t*)(start + 1) << 16) | (*(uint8_t*)(start) << 24); 18 | } 19 | 20 | void init_files(multiboot_module_t *mod) { 21 | uintptr_t scratch; 22 | uintptr_t start = mod->mod_start; 23 | if (mod->mod_end - start < 8) { 24 | ERROR_PRINTF("initrd is too small to be useful %d\r\n", mod->mod_end - start); 25 | return; 26 | } 27 | kern_mmap(&scratch, (void *)start, mod->mod_end - start, PROT_READ | PROT_KERNEL, MAP_FIXED); 28 | uint32_t files = unpack_network(start); 29 | start += sizeof(uint32_t); 30 | 31 | uint32_t total_namelen = unpack_network(start); 32 | start += sizeof(uint32_t); 33 | 34 | uintptr_t fat_and_names; 35 | size_t fatlen = sizeof(struct hardcoded_file) * files + sizeof(*hardcoded_files); 36 | 37 | size_t total_len = total_namelen + fatlen; 38 | if (!kern_mmap(&fat_and_names, NULL, total_len, PROT_READ | PROT_WRITE | PROT_KERNEL, MAP_ANON | MAP_STACK)) { 39 | ERROR_PRINTF("couldn't allocate %d bytes for filenames and file table\r\n", total_namelen); 40 | return; 41 | } 42 | DEBUG_PRINTF("zeroing out fat_and_names %p, %d\r\n", fat_and_names, total_len); 43 | 44 | hardcoded_files = (void*) fat_and_names; 45 | hardcoded_files->count = files; 46 | hardcoded_files->size = total_len; 47 | 48 | uintptr_t names = fat_and_names + fatlen; 49 | 50 | for (int i = 0; start < mod->mod_end; ++i) { 51 | size_t strlen = strnlen((char *)start, mod->mod_end - start) + 1; 52 | if (strlen + start + sizeof(uint32_t) > mod->mod_end) { break; } 53 | size_t filelen = unpack_network(start + strlen); 54 | size_t compressedlen = 0; 55 | if (filelen & 0x80000000) { 56 | if (strlen + start + sizeof(uint32_t) * 2 > mod->mod_end) { break; } 57 | compressedlen = unpack_network(start + strlen + sizeof(uint32_t)); 58 | filelen &= 0x7FFFFFFF; 59 | } 60 | 61 | if (compressedlen) { 62 | if (strlen + start + sizeof(uint32_t) * 2 + compressedlen > mod->mod_end) { break; } 63 | } else { 64 | if (strlen + start + sizeof(uint32_t) + filelen > mod->mod_end) { break; } 65 | } 66 | 67 | uintptr_t file; 68 | size_t mmaplen = filelen; 69 | if (mmaplen & (PAGE_SIZE -1)) { 70 | mmaplen = (mmaplen & ~(PAGE_SIZE -1)) + PAGE_SIZE; 71 | } 72 | 73 | DEBUG_PRINTF("allocating %d bytes (%d) for file %s\r\n", mmaplen, filelen, (char *) start); 74 | if (!kern_mmap(&file, NULL, mmaplen, PROT_READ | PROT_WRITE | PROT_KERNEL, MAP_ANON | MAP_STACK)) { 75 | ERROR_PRINTF("couldn't allocate %d bytes (%d) for file %s\r\n", mmaplen, filelen, (char *)start); 76 | return; 77 | } 78 | 79 | memcpy((uint8_t*)names, (uint8_t*)start, strlen); 80 | hardcoded_files->files[i].name = (char *) names; 81 | names += strlen; 82 | 83 | hardcoded_files->files[i].end = (uint8_t *) (file + filelen); 84 | start += strlen + sizeof(uint32_t); 85 | 86 | if (compressedlen) { 87 | start += sizeof(uint32_t); 88 | //if (LZ4_decompress_safe((char *)start, (char *)file, compressedlen, filelen) != filelen) { 89 | ERROR_PRINTF("couldn't decompress %s\r\n", hardcoded_files->files[i].name); 90 | return; 91 | //} 92 | } else { 93 | memcpy((uint8_t *)file, (uint8_t*)start, filelen); 94 | } 95 | 96 | if (mmaplen > filelen) { 97 | explicit_bzero((uint8_t *) (file + filelen), mmaplen - filelen); 98 | } 99 | hardcoded_files->files[i].start = (uint8_t *) file; 100 | hardcoded_files->files[i].size = filelen; 101 | start += filelen; 102 | } 103 | kern_munmap(PROT_KERNEL, mod->mod_start, mod->mod_end - mod->mod_start); 104 | } 105 | 106 | struct hardcoded_file * find_file(const char * name) { 107 | if (hardcoded_files == NULL) { 108 | ERROR_PRINTF("find_file when files aren't initialized\r\n"); 109 | return NULL; 110 | } 111 | for (int i = 0; i < hardcoded_files->count; ++i) { 112 | if (hardcoded_files->files[i].name == NULL) { continue; } 113 | int cmp = strcmp(name, hardcoded_files->files[i].name); 114 | if (cmp == 0) { 115 | return &(hardcoded_files->files[i]); 116 | } else if (cmp < 0) { 117 | return NULL; 118 | } 119 | } 120 | return NULL; 121 | } 122 | 123 | struct hardcoded_file * find_dir(const char * name, size_t len, struct hardcoded_file *i) { 124 | if (hardcoded_files == NULL) { 125 | ERROR_PRINTF("find_dir when files aren't initialized\r\n"); 126 | return NULL; 127 | } 128 | if (i == NULL) { 129 | i = &hardcoded_files->files[0]; 130 | } 131 | for (; i <= &hardcoded_files->files[hardcoded_files->count -1] && i->name != NULL; ++i) { 132 | if (i->name == NULL) { continue; } 133 | int cmp = strncmp(name, i->name, len); 134 | if (cmp == 0) { 135 | if (i->name[len] == '/') { 136 | return i; 137 | } 138 | } else if (cmp < 0) { 139 | return NULL; 140 | } 141 | } 142 | return NULL; 143 | } 144 | -------------------------------------------------------------------------------- /src/dhcpc.erl: -------------------------------------------------------------------------------- 1 | -module (dhcpc). 2 | 3 | -export ([go/0]). 4 | -define (CLIENT_PORT, 68). 5 | -define (SERVER_PORT, 67). 6 | -define (BROADCAST, 16#FFFFFFFF). 7 | 8 | go() -> 9 | 10 | Sock = udp:open(?CLIENT_PORT), 11 | Xid = crypto:strong_rand_bytes(4), 12 | Terms = application:get_all_env(etcpip), 13 | {value, {mac, Mac}} = lists:keysearch(mac, 1, Terms), 14 | Discovery = << 1, 1, 6, 0, % op, hytpe, hlen, hops 15 | Xid/binary, % xid 16 | 0:16, 0:16, % secs, flags 17 | 0:32, %client address 18 | 0:32, % your address 19 | 0:32, % server address 20 | 0:32, % relay address 21 | Mac/binary, 0:(128 - size(Mac) * 8), % hardware address 22 | 0:512, % sname 23 | 0:1024, % file 24 | 99, 130, 83, 99, % magic cookie 25 | 53, 1, 1, % DHCP Discover 26 | 55, 3, 3, 12, 15, % parameter request Router, Host Name, Domain Name 27 | 255 % End 28 | >>, 29 | udp:send(0, ?CLIENT_PORT, ?BROADCAST, ?SERVER_PORT, Discovery), 30 | Options = receive 31 | {udp, {_, ?CLIENT_PORT, _, ?SERVER_PORT}, 32 | << 2, 1, 6, _Hops, 33 | Xid:4/binary, 34 | _Secs:16, _Flags:16, _CIAddr:32, 35 | MyAddr:32, _SIAddr:32, _GIAddr:32, 36 | Chaddr:128, Sname:64/binary, File:128/binary, 37 | 99, 130, 83, 99, 38 | RawOptions/binary>>} -> 39 | parse_options(RawOptions, #{}) 40 | end, 41 | ServerId = maps:get(server_id, Options), 42 | Request = << 1, 1, 6, 0, % op, hytpe, hlen, hops 43 | Xid/binary, % xid 44 | 0:16, 0:16, % secs, flags 45 | 0:32, %client address 46 | MyAddr:32, % your address 47 | 0:32, % server address 48 | 0:32, % relay address 49 | Mac/binary, 0:(128 - size(Mac) * 8), % hardware address 50 | 0:512, % sname 51 | 0:1024, % file 52 | 99, 130, 83, 99, % magic cookie 53 | 54, 4, ServerId:32, % Server ID 54 | 53, 1, 3, % DHCP Request 55 | 50, 4, MyAddr:32, % Requested IP Address 56 | 255 % DHCP Discover 57 | >>, 58 | udp:send(0, ?CLIENT_PORT, ?BROADCAST, ?SERVER_PORT, Request), 59 | receive 60 | {udp, {_, ?CLIENT_PORT, _, ?SERVER_PORT}, 61 | << 2, 1, 6, _Hops, 62 | Xid:4/binary, 63 | _Secs:16, _Flags:16, _CIAddr:32, 64 | MyAddr:32, _SIAddr:32, _GIAddr:32, 65 | Chaddr:128, Sname:64/binary, File:128/binary, 66 | 99, 130, 83, 99, 67 | RawOptions2/binary>>} -> 68 | O2 = parse_options(RawOptions2, #{}), 69 | io:format("Ip ~p, Netmask ~p, Gateway ~p~n", [etcpip_socket:unmap_ip(MyAddr), etcpip_socket:unmap_ip(maps:get(subnet_mask, O2)), etcpip_socket:unmap_ip(maps:get(router, O2))]), 70 | case {maps:get(hostname, O2, undefined), maps:get(domain_name, O2, undefined)} of 71 | {undefined, undefined} -> 72 | NodeName = io_lib:format("crazierl@~8.16.0B.nip.io", [MyAddr]), 73 | net_kernel:start([binary_to_atom(iolist_to_binary(NodeName)), longnames]); 74 | {Hostname, undefined} -> 75 | NodeName = io_lib:format("crazierl@~s", [Hostname]), 76 | net_kernel:start([binary_to_atom(iolist_to_binary(NodeName)), shortnames]); 77 | {Hostname, Domain} -> 78 | NodeName = io_lib:format("crazierl@~s.~s", [Hostname, Domain]), 79 | net_kernel:start([binary_to_atom(iolist_to_binary(NodeName)), longnames]) 80 | end, 81 | case maps:get(dns_server, O2, etcpip_socket:map_ip({8,8,8,8})) of 82 | ServerInt when is_integer(ServerInt) -> 83 | DNSIp = etcpip_socket:unmap_ip(ServerInt), 84 | io:format("dns server ~p~n", [DNSIp]), 85 | inet_db:add_ns(DNSIp); 86 | _ -> io:format("no dns server from dhcpd~n") 87 | end, 88 | 89 | etcpip_socket:new_ip(MyAddr, maps:get(subnet_mask, O2), maps:get(router, O2)); 90 | M -> io:format("got ~w~n", [M]) 91 | end. 92 | 93 | parse_options(<<>>, Map) -> Map; 94 | % DHCP Message Type 95 | parse_options(<<53, 1, 1, Rest/binary>>, Map) -> parse_options(Rest, Map#{message => discover}); 96 | parse_options(<<53, 1, 2, Rest/binary>>, Map) -> parse_options(Rest, Map#{message => offer}); 97 | parse_options(<<53, 1, 3, Rest/binary>>, Map) -> parse_options(Rest, Map#{message => request}); 98 | parse_options(<<53, 1, 4, Rest/binary>>, Map) -> parse_options(Rest, Map#{message => decline}); 99 | parse_options(<<53, 1, 5, Rest/binary>>, Map) -> parse_options(Rest, Map#{message => ack}); 100 | parse_options(<<53, 1, 6, Rest/binary>>, Map) -> parse_options(Rest, Map#{message => nak}); 101 | parse_options(<<53, 1, 7, Rest/binary>>, Map) -> parse_options(Rest, Map#{message => release}); 102 | parse_options(<<53, 1, 8, Rest/binary>>, Map) -> parse_options(Rest, Map#{message => inform}); 103 | % Subnet Mask 104 | parse_options(<<1, 4, Mask:32, Rest/binary>>, Map) -> parse_options(Rest, Map#{subnet_mask => Mask}); 105 | % Router 106 | parse_options(<<3, N, PrimaryRouter:32, _OtherRouters:(N - 4)/binary, Rest/binary>>, Map) when N rem 4 == 0 -> 107 | parse_options(Rest, Map#{router => PrimaryRouter}); 108 | % Domain Name Server 109 | parse_options(<<6, N, PrimaryDNS:32, _OtherDNS:(N - 4)/binary, Rest/binary>>, Map) when N rem 4 == 0 -> 110 | parse_options(Rest, Map#{dns_server => PrimaryDNS}); 111 | % Lease Time 112 | parse_options(<<51, 4, Time:32, Rest/binary>>, Map) -> parse_options(Rest, Map#{lease_time => Time}); 113 | % Server Identifier 114 | parse_options(<<54, 4, IP:32, Rest/binary>>, Map) -> parse_options(Rest, Map#{server_id => IP}); 115 | % Hostname 116 | parse_options(<<12, Len, Name:Len/binary, Rest/binary>>, Map) -> parse_options(Rest, Map#{hostname => Name}); 117 | % Domain name 118 | parse_options(<<15, Len, Name:Len/binary, Rest/binary>>, Map) -> parse_options(Rest, Map#{domain_name => Name}); 119 | parse_options(<<0, Rest/binary>>, Map) -> parse_options(Rest, Map); 120 | parse_options(<<255, _/binary>>, Map) -> Map; 121 | parse_options(<>, Map) -> parse_options(Rest, Map#{{unknown, N} => Data}). 122 | 123 | -------------------------------------------------------------------------------- /kernel_src/acpi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "common.h" 6 | #include "acpi.h" 7 | #include "apic.h" 8 | 9 | unsigned int numcpu; 10 | 11 | 12 | struct cpu cpus[MAX_CPUS]; 13 | 14 | uint8_t acpi_checksum(uint8_t * p, ssize_t len) { 15 | uint8_t ret = 0; 16 | while (len) { 17 | ret += *p; 18 | ++p; 19 | --len; 20 | } 21 | return ret; 22 | } 23 | 24 | int acpi_check_table(void * c) { 25 | ACPI_TABLE_HEADER *t = (ACPI_TABLE_HEADER *)c; 26 | return acpi_checksum(c, t->Length) == 0; 27 | } 28 | 29 | void * acpi_find_table(void * name, void * rsdt) { 30 | ACPI_TABLE_HEADER *t = (ACPI_TABLE_HEADER *)rsdt; 31 | if (bcmp(t->Signature, ACPI_SIG_XSDT, ACPI_NAMESEG_SIZE) == 0) { 32 | ACPI_TABLE_XSDT * table = (ACPI_TABLE_XSDT *)rsdt; 33 | for (int i = 0; (i * sizeof(table->TableOffsetEntry[0])) + sizeof(table->Header) < table->Header.Length; ++i) { 34 | if (bcmp(((ACPI_TABLE_HEADER*)table->TableOffsetEntry[i])->Signature, name, ACPI_NAMESEG_SIZE) == 0) { 35 | return (void *)table->TableOffsetEntry[i]; 36 | } 37 | } 38 | DEBUG_PRINTF("couldn't find %s in XSDT\r\n", name); 39 | return NULL; 40 | } else if (bcmp(t->Signature, ACPI_SIG_RSDT, ACPI_NAMESEG_SIZE) == 0) { 41 | ACPI_TABLE_RSDT * table = (ACPI_TABLE_RSDT *)rsdt; 42 | for (int i = 0; (i * sizeof(table->TableOffsetEntry[0])) + sizeof(table->Header) < table->Header.Length; ++i) { 43 | if (bcmp(((ACPI_TABLE_HEADER*)table->TableOffsetEntry[i])->Signature, name, ACPI_NAMESEG_SIZE) == 0) { 44 | return (void *)table->TableOffsetEntry[i]; 45 | } 46 | } 47 | DEBUG_PRINTF("couldn't find %s in RSDT\r\n", name); 48 | return NULL; 49 | } else { 50 | EARLY_ERROR_PRINTF("RSDT is not an RSDT or XSDT\r\n"); 51 | return NULL; 52 | } 53 | } 54 | 55 | // this is part ACPI parsing, but mostly kernel setup 56 | int acpi_process_madt(void * rsdt) { 57 | numcpu = 0; timer_gsirq = 0; io_apic_count = 0; 58 | ACPI_TABLE_MADT *madt = (ACPI_TABLE_MADT *)acpi_find_table(ACPI_SIG_MADT, rsdt); 59 | if (madt == NULL) { 60 | EARLY_ERROR_PRINTF("couldn't find MADT\r\n"); 61 | return 0; 62 | } 63 | if (!(acpi_checksum((void *) madt, madt->Header.Length) == 0)) { 64 | EARLY_ERROR_PRINTF("MADT checksum failed\r\n"); 65 | return 0; 66 | } 67 | local_apic = madt->Address; 68 | void *p = ((void *)madt) + sizeof(*madt); 69 | while (p < (((void *)madt) + madt->Header.Length)) { 70 | ACPI_SUBTABLE_HEADER *subhead = (ACPI_SUBTABLE_HEADER *)p; 71 | if (subhead->Type == ACPI_MADT_TYPE_LOCAL_APIC && subhead->Length == sizeof(ACPI_MADT_LOCAL_APIC)) { 72 | ACPI_MADT_LOCAL_APIC *data = (ACPI_MADT_LOCAL_APIC *)p; 73 | if (data->LapicFlags & ACPI_MADT_ENABLED) { 74 | if (numcpu < MAX_CPUS) { 75 | //EARLY_ERROR_PRINTF("Processor %d, APIC %d, Flags %x\r\n", data->ProcessorId, data->Id, data->LapicFlags); 76 | cpus[numcpu].flags = CPU_ENABLED; 77 | cpus[numcpu].apic_id = data->Id; 78 | ++numcpu; 79 | } else { 80 | EARLY_ERROR_PRINTF("Ignoring processor %d, APIC %d; more than MAX_CPUS (%d)\r\n", data->ProcessorId, data->Id, MAX_CPUS); 81 | } 82 | } else { 83 | EARLY_ERROR_PRINTF("DISABLED! Processor %d, APIC %d, Flags %x\r\n", data->ProcessorId, data->Id, data->LapicFlags); 84 | } 85 | } else if (subhead->Type == ACPI_MADT_TYPE_IO_APIC && subhead->Length == sizeof(ACPI_MADT_IO_APIC)) { 86 | ACPI_MADT_IO_APIC *data = (ACPI_MADT_IO_APIC *)p; 87 | if (io_apic_count == MAX_IO_APICS) { 88 | EARLY_ERROR_PRINTF("Too many IO-APICS (limit %d); recompile with a higher limit for your machine!\r\n", MAX_IO_APICS); 89 | return 0; 90 | } 91 | io_apics[io_apic_count].address = (volatile uint32_t *)data->Address; 92 | io_apics[io_apic_count].base = data->GlobalIrqBase; 93 | 94 | io_apics[io_apic_count].address[0] = 0; 95 | uint32_t d = io_apics[io_apic_count].address[4]; 96 | d >>= 24; 97 | d &= 0x0F; 98 | if (d != data->Id) { 99 | EARLY_ERROR_PRINTF("IO-APIC id (%d) doesn't match id from MADT (%d)\r\n", d, data->Id); 100 | //return 0; 101 | } 102 | io_apics[io_apic_count].address[0] = 1; 103 | d = io_apics[io_apic_count].address[4]; 104 | d >>= 16; 105 | d &= 0xFF; 106 | 107 | io_apics[io_apic_count].numintr = d + 1; 108 | ++io_apic_count; 109 | } else if (subhead->Type == ACPI_MADT_TYPE_INTERRUPT_OVERRIDE && subhead->Length == sizeof(ACPI_MADT_INTERRUPT_OVERRIDE)) { 110 | ACPI_MADT_INTERRUPT_OVERRIDE *data = (ACPI_MADT_INTERRUPT_OVERRIDE *)p; 111 | if (data->Bus == 0 && data->SourceIrq == 0) { 112 | timer_gsirq = data->GlobalIrq; 113 | timer_flags = data->IntiFlags; 114 | //EARLY_ERROR_PRINTF("timer irq is global IRQ %d (flags %x)\r\n", timer_gsirq, data->IntiFlags); 115 | } else { 116 | //EARLY_ERROR_PRINTF("ISO: Bus %d, SourceIRQ %d, GlobalIRQ %d, Flags %x\r\n", 117 | // data->Bus, data->SourceIrq, data->GlobalIrq, data->IntiFlags); 118 | } 119 | 120 | } else if (subhead->Type == ACPI_MADT_TYPE_LOCAL_APIC_NMI && subhead->Length == sizeof(ACPI_MADT_LOCAL_APIC_NMI)) { 121 | ACPI_MADT_LOCAL_APIC_NMI *data = (ACPI_MADT_LOCAL_APIC_NMI *)p; 122 | EARLY_ERROR_PRINTF("NMI: Processor %d, Flags %x, Lint %d\r\n", 123 | data->ProcessorId, data->IntiFlags, data->Lint); 124 | } else { 125 | EARLY_ERROR_PRINTF("uknown MADT item, type %d, length %d\r\n", subhead->Type, subhead->Length); 126 | } 127 | p += subhead->Length; 128 | } 129 | return 1; 130 | } 131 | 132 | void * check_rsdp(void *p) { 133 | ACPI_RSDP_COMMON *acpiv1 = (ACPI_RSDP_COMMON *)p; 134 | ACPI_TABLE_RSDP *acpiv2 = (ACPI_TABLE_RSDP *)p; 135 | if (bcmp(ACPI_SIG_RSDP, p, sizeof(acpiv1->Signature)) != 0) { return NULL; } 136 | if (acpiv1->Revision == 0 && acpi_checksum(p, sizeof(*acpiv1)) == 0) { 137 | return (void *)acpiv1->RsdtPhysicalAddress; 138 | } else if (acpiv2->Revision == 2 && acpi_checksum(p, sizeof(*acpiv2)) == 0){ 139 | return (void *)acpiv2->XsdtPhysicalAddress; 140 | } else { 141 | return NULL; 142 | } 143 | } 144 | 145 | void * acpi_find_rsdt (void *hint) { 146 | void * ret; 147 | if (hint != NULL) { 148 | ret = check_rsdp(hint); 149 | if (ret) { return ret; } 150 | } 151 | void * search_start = (void *) 0xE0000; 152 | void * search_end = (void *) 0x100000; 153 | 154 | void * ebda_segment = (void *) ( *(uint16_t *)0x40E << 4); 155 | if (ebda_segment <= search_start && ebda_segment + 1024 >= search_start) { 156 | search_start = ebda_segment; 157 | } else { 158 | for (hint = ebda_segment; hint < ebda_segment + 1024; hint+= 16) { 159 | ret = check_rsdp(hint); 160 | if (ret) { return ret; } 161 | } 162 | } 163 | for (hint = search_start; hint < search_end; hint+= 16) { 164 | ret = check_rsdp(hint); 165 | if (ret) { return ret;} 166 | } 167 | return NULL; 168 | } 169 | -------------------------------------------------------------------------------- /src/rtl_8168.erl: -------------------------------------------------------------------------------- 1 | -module (rtl_8168). 2 | 3 | -include ("pci.hrl"). 4 | -export([check/2, attach/2]). 5 | -record (q, {map, idx, avail}). 6 | 7 | % TODO split virtio and net bits 8 | 9 | -define (TX_DESC, 16#20). 10 | -define (COMMAND, 16#37). 11 | -define (TX_POLL, 16#38). 12 | -define (IRQ_MASK, 16#3C). 13 | -define (IRQ_STATUS, 16#3E). 14 | -define (TX_CFG, 16#40). 15 | -define (RX_CFG, 16#44). 16 | -define (CFG_ENABLE, 16#50). 17 | -define (RX_MAX, 16#DA). 18 | -define (RX_DESC, 16#E4). 19 | -define (TX_MAX, 16#EC). 20 | 21 | -define (MAX_RXLEN, 16#1FFF). 22 | -define (Q_LEN, 32). 23 | -define (Q_DESC_SIZE, 16). 24 | -define (Q_DESC_TABLE_LEN, (?Q_DESC_SIZE * ?Q_LEN)). 25 | -define (Q_MAX_PACKET, 1514). % 6 byte dest mac, 6 byte source mac, 2 byte protocol, 1500 payload 26 | -define (Q_BUFFER_SIZE, (?Q_MAX_PACKET + ?ALIGN(?Q_MAX_PACKET, 8))). % max packet + 12 byte header 27 | -define (Q_BUFFER_LEN, (?Q_BUFFER_SIZE * ?Q_LEN)). 28 | 29 | -define (Q_TOTAL_LEN, (?Q_DESC_TABLE_LEN + ?Q_BUFFER_LEN)). 30 | 31 | 32 | -define(ALIGN(Value, Align), 33 | (case Value rem Align of 34 | 0 -> Value; 35 | _ -> Value + Align - (Value rem Align) 36 | end)). 37 | 38 | check(#pci_device{common = #pci_common{vendor = 16#10EC, device_id = 16#8168}}, _Args) -> true. 39 | 40 | attach(Device, _Args) -> 41 | register(ethernet_sender, self()), 42 | Common = Device#pci_device.common, 43 | 44 | Bar = element(3, Device#pci_device.bars), 45 | {ok, MMIO} = crazierl:map(Bar#pci_mem_bar.base, Bar#pci_mem_bar.size), 46 | 47 | reset(MMIO), 48 | 49 | MacAddr = crazierl:bcopy_from(MMIO, 0, 6), 50 | 51 | {Socket, Int} = crazierl:open_interrupt(0), 52 | pci:enable_msix(Common, 0, Int), 53 | 54 | RxQ = setup_q(read), 55 | TxQ = setup_q(write), 56 | 57 | % init values taken from https://wiki.osdev.org/RTL8169 58 | crazierl:bcopy_to(MMIO, ?IRQ_MASK, <<16#9F:16/little>>), % enable TX/RX descriptor unavailable, error and ok 59 | 60 | crazierl:bcopy_to(MMIO, ?CFG_ENABLE, <<16#c0>>), % unlock configuration 61 | 62 | crazierl:bcopy_to(MMIO, ?RX_CFG, <<16#E70E:16/little>>), % unlimited dma, not promiscuous 63 | crazierl:bcopy_to(MMIO, ?RX_MAX, <>), 64 | {_, RxPhysical} = crazierl:map_addr(RxQ#q.map), 65 | crazierl:bcopy_to(MMIO, ?RX_DESC, <>), 66 | 67 | crazierl:bcopy_to(MMIO, ?COMMAND, <<16#C>>), % enable tx and rx 68 | crazierl:bcopy_to(MMIO, ?TX_CFG, <<16#0300700:32/little>>), % normal interframe gap, unlimited dma 69 | crazierl:bcopy_to(MMIO, ?TX_MAX, <<16#3B>>), 70 | {_, TxPhysical} = crazierl:map_addr(TxQ#q.map), 71 | crazierl:bcopy_to(MMIO, ?TX_DESC, <>), 72 | 73 | crazierl:bcopy_to(MMIO, ?CFG_ENABLE, <<16#0>>), % lock configuration 74 | 75 | application:set_env([ 76 | {etcpip, [ 77 | {ip, {0,0,0,0}}, 78 | {netmask, {255, 255, 255, 255}}, 79 | {gateway, {0, 0, 0, 0}}, 80 | {mac, MacAddr}, 81 | {iface, "re0"}, 82 | {ip6, [ 83 | {addr, "fe80::216:3eff:fe00:1234"} 84 | ]} 85 | ]} 86 | ], [{persistent, true}]), 87 | etcpip_socket:start(), 88 | loop(Device, MMIO, MacAddr, Socket, RxQ, TxQ). 89 | 90 | 91 | reset(MMIO) -> 92 | <> = crazierl:bcopy_from(MMIO, ?COMMAND, 1), 93 | crazierl:bcopy_to(MMIO, ?COMMAND, <>), 94 | reset_loop(MMIO). 95 | 96 | reset_loop(MMIO) -> 97 | <> = crazierl:bcopy_from(MMIO, ?COMMAND, 1), 98 | case Status band 16 of 99 | 0 -> ok; 100 | 16 -> reset_loop(MMIO) 101 | end. 102 | 103 | %% tx queue full, don't process send messages 104 | loop(Device, MMIO, MacAddr, Socket, RxQ, TxQ = #q{avail = 0}) -> 105 | {RxQ1, TxQ1} = receive 106 | {udp, Socket, _, _, _} -> 107 | %Status = crazierl:bcopy_from(MMIO, ?IRQ_STATUS, 2), 108 | crazierl:bcopy_to(MMIO, ?IRQ_STATUS, <<16#FFFF:16>>), 109 | {check_queue(RxQ, read), check_queue(TxQ, write)}; 110 | {ping, From} -> 111 | From ! pong, 112 | {RxQ, TxQ} 113 | end, 114 | loop(Device, MMIO, MacAddr, Socket, RxQ1, TxQ1); 115 | 116 | loop(Device, MMIO, MacAddr, Socket, RxQ, TxQ) -> 117 | {RxQ1, TxQ1} = receive 118 | {udp, Socket, _, _, _} -> 119 | %Status = crazierl:bcopy_from(MMIO, ?IRQ_STATUS, 2), 120 | %case Status of 121 | % <<1, 0>> -> ok; 122 | % _ -> io:format("got irq ~p~n", [Status]) 123 | %end, 124 | crazierl:bcopy_to(MMIO, ?IRQ_STATUS, <<16#FFFF:16>>), 125 | {check_queue(RxQ, read), check_queue(TxQ, write)}; 126 | {'$gen_call', From, {send, {DestMac, EtherType}, Payload}} -> 127 | Packet = <>, 128 | gen:reply(From, ok), 129 | {RxQ, add_to_queue(MMIO, TxQ, Packet)}; 130 | {'$gen_call', From, {send, Data}} -> 131 | Packet = iolist_to_binary(Data), 132 | gen:reply(From, ok), 133 | {RxQ, add_to_queue(MMIO, TxQ, Packet)}; 134 | {ping, From} -> 135 | From ! pong, 136 | {RxQ, TxQ}; 137 | Other -> 138 | io:format("got message ~p", [Other]), 139 | {RxQ, TxQ} 140 | end, 141 | loop(Device, MMIO, MacAddr, Socket, RxQ1, TxQ1). 142 | 143 | 144 | setup_q(Type) -> 145 | {ok, Map} = crazierl:map(0, ?Q_TOTAL_LEN), 146 | {_Virtual, Physical} = crazierl:map_addr(Map), 147 | {Avail, Own} = case Type of 148 | read -> {0, 1}; 149 | write -> {?Q_LEN, 0} 150 | end, 151 | DescriptorTable = virtq_descriptors(Physical + ?Q_DESC_TABLE_LEN, Own, 0, <<>>), 152 | crazierl:bcopy_to(Map, 0, <>), 153 | #q{map = Map, idx = 0, avail = Avail}. 154 | 155 | virtq_descriptors(_, _, ?Q_LEN, Acc) -> Acc; 156 | virtq_descriptors(Physical, Own, Index, Acc) -> 157 | End = case Index + 1 of 158 | ?Q_LEN -> 1; 159 | _ -> 0 160 | end, 161 | 162 | Acc0 = <>, 163 | virtq_descriptors(Physical + ?Q_BUFFER_SIZE, Own, Index + 1, Acc0). 164 | 165 | add_to_queue(MMIO, Queue = #q{map = Map, idx = Idx, avail = Avail}, Packet) when size(Packet) =< ?Q_MAX_PACKET -> 166 | crazierl:bcopy_to(Map, ?Q_DESC_TABLE_LEN + (?Q_BUFFER_SIZE * Idx), Packet), 167 | End = case Idx + 1 of 168 | ?Q_LEN -> 1; 169 | _ -> 0 170 | end, 171 | 172 | crazierl:bcopy_to(Map, ?Q_DESC_SIZE * Idx, <<(size(Packet)):16/little, 0:8, 1:1, End:1, 1:1, 1:1, 0:4>>), 173 | 174 | %io:format("wrote packet~n", []), 175 | crazierl:bcopy_to(MMIO, ?TX_POLL, <<16#40>>), 176 | 177 | Queue#q{idx = (Idx + 1) rem ?Q_LEN, avail = Avail - 1}; 178 | add_to_queue(_MMIO, Queue, Packet) -> 179 | io:format("packet too big (~B > ~B), dropping ~w~n", [size(Packet), ?Q_MAX_PACKET, Packet]), 180 | Queue. 181 | 182 | check_queue(Queue = #q{avail = ?Q_LEN}, write) -> Queue; 183 | check_queue(Queue = #q{map = Map, avail = Avail, idx = Idx}, write) -> 184 | Header = crazierl:bcopy_from(Map, ?Q_DESC_SIZE * ((Idx + Avail) rem ?Q_LEN), 4), 185 | case Header of 186 | <<_:24, 0:1, _:7>> -> 187 | Queue#q{avail = Avail + 1}; 188 | _ -> Queue 189 | end; 190 | 191 | check_queue(Queue = #q{map = Map, idx = Idx}, read) -> 192 | Header = crazierl:bcopy_from(Map, ?Q_DESC_SIZE * Idx, 4), 193 | %io:format("header ~w, Type ~s, idx ~B~n", [Header, Type, Idx]), 194 | case Header of 195 | <<_:24, 0:1, _:7>> -> process_packet (Queue, Header); 196 | _ -> Queue 197 | end. 198 | 199 | process_packet(Queue = #q{map = Map, idx = Idx}, Header) -> 200 | %io:format("reading used ring item (read) ~B ~w~n", [Idx, Header]), 201 | 202 | <> = Header, 205 | 206 | Length = LengthAndFlags band ?MAX_RXLEN, 207 | Packet = crazierl:bcopy_from(Map, ?Q_DESC_TABLE_LEN + (?Q_BUFFER_SIZE * Idx), Length), 208 | case {FirstSegment, LastSegment} of 209 | {1, 1} -> ok; 210 | _ -> io:format("funny business ~w~n", [Header]) 211 | end, 212 | 213 | Empty = <>, 214 | crazierl:bcopy_to(Map, ?Q_DESC_SIZE * Idx, Empty), 215 | 216 | eth:recv(Packet), 217 | NewIdx = (Idx + 1) rem ?Q_LEN, 218 | check_queue(Queue#q{idx = NewIdx}, read). 219 | -------------------------------------------------------------------------------- /kernel_src/apic.c: -------------------------------------------------------------------------------- 1 | #include "apic.h" 2 | #include "common.h" 3 | #include "acpi.h" 4 | 5 | // Ideally things related to APIC (Advanced Programmable Interrupt Controller), but also 6 | // includes other things in the arena of general PIC and interrupts, so more thematic 7 | // than anything 8 | 9 | #define APIC_DISABLE 0x10000 10 | #define APIC_TIMER_PERIODIC 0x20000 11 | 12 | #define APIC_TPR 0x080 // Task Priority Register 13 | #define APIC_LDR 0x0D0 14 | #define APIC_DFR 0x0E0 15 | #define APIC_SPURIOUS 0x0F0 16 | #define APIC_LVT_TIMER 0x320 17 | #define APIC_LVT_PERF 0x340 18 | #define APIC_TIMER_INITIAL 0x380 19 | #define APIC_TIMER_CURRENT 0x390 20 | #define APIC_TIMER_DIVIDE 0x3E0 21 | 22 | // https://wiki.osdev.org/8259_PIC 23 | #define PORT_PIC1_CMD 0x20 24 | #define PORT_PIC1_DATA 0x21 25 | #define PORT_PIC2_CMD 0xA0 26 | #define PORT_PIC2_DATA 0xA1 27 | 28 | #define PIC_INTERRUPT_ACK 0x20 29 | 30 | static inline void outb(uint16_t port, uint8_t val) 31 | { 32 | asm volatile ( "outb %0, %1" : : "a"(val), "Nd"(port) ); 33 | /* There's an outb %al, $imm8 encoding, for compile-time constant port numbers that fit in 8b. (N constraint). 34 | * Wider immediate constants would be truncated at assemble-time (e.g. "i" constraint). 35 | * The outb %al, %dx encoding is the only option for all other cases. 36 | * %1 expands to %dx because port is a uint16_t. %w1 could be used if we had the port number a wider C type */ 37 | } 38 | 39 | static inline uint8_t inb(uint16_t port) 40 | { 41 | uint8_t ret; 42 | asm volatile ( "inb %1, %0" 43 | : "=a"(ret) 44 | : "Nd"(port) ); 45 | return ret; 46 | } 47 | uintptr_t local_apic; 48 | uint8_t timer_gsirq; 49 | uint8_t timer_flags; 50 | unsigned int io_apic_count; 51 | uint64_t SCALED_S_PER_TSC_TICK; 52 | uint32_t clock_ticks; 53 | 54 | //extern struct io_apic io_apics[]; 55 | 56 | struct io_apic io_apics[MAX_IO_APICS]; 57 | 58 | void local_apic_write(unsigned int reg, uint32_t value) 59 | { 60 | *(uint32_t *)(local_apic + reg) = value; 61 | } 62 | 63 | uint32_t local_apic_read(unsigned int reg) { 64 | return *(uint32_t *)(local_apic + reg); 65 | } 66 | 67 | void ioapic_set_gsi_vector(unsigned int irq, uint8_t flags, uint8_t vector, uint8_t physcpu) { 68 | int ioapic = 0; 69 | unsigned int origirq = irq; 70 | while (ioapic < io_apic_count) { 71 | if (irq < io_apics[ioapic].numintr) { 72 | uint8_t index = (irq * 2) + 0x10; 73 | uint32_t lo, hi; 74 | 75 | lo = vector; 76 | if (flags & 8) { // level triggered 77 | lo |= 0x8000; 78 | } 79 | if (flags & 2) { // active low 80 | lo |= 0x2000; 81 | } 82 | hi = physcpu << 24; 83 | 84 | io_apics[ioapic].address[0] = index; 85 | io_apics[ioapic].address[4] = lo; 86 | io_apics[ioapic].address[0] = index + 1; 87 | io_apics[ioapic].address[4] = hi; 88 | return; 89 | } else { 90 | irq -= io_apics[ioapic].numintr; 91 | } 92 | ++ioapic; 93 | } 94 | ERROR_PRINTF("couldn't find IO-APIC for irq %d\r\n", origirq); 95 | halt(NULL, 0); 96 | } 97 | 98 | #define PIT_CH2_GATE 0x61 99 | #define PIT_CH0_DATA 0x40 100 | #define PIT_CH1_DATA 0x41 101 | #define PIT_CH2_DATA 0x42 102 | #define PIT_CMD 0x43 103 | 104 | #define PIT_OUT 0x20 105 | #define PIT_GATE 0x01 106 | 107 | void load_pit(uint16_t ms) 108 | { 109 | // 3579645 /3 Hz= 1193182 Hz = 1.193182 Mhz 110 | // time_in_ms = val / (3579545/3) * 1000 111 | // time_in_ms / 1000 = val / (3579545/3) 112 | // (time_in_ms / 1000) * (3579545/3) = val 113 | // (time_in_ms / 3000) * 3579545 = val 114 | 115 | // iow 10ms = 3579545 / 300 116 | 117 | // Tell PIT to sleep for 10ms 118 | // 65536 = 54.878 ms 119 | // https://wiki.osdev.org/Programmable_Interval_Timer 120 | 121 | 122 | uint16_t counter = (ms * 3579545) / 3000; 123 | 124 | uint8_t existing = inb(PIT_CH2_GATE); 125 | existing = 0xFF & ((existing & 0xfd) | 1); // disable speaker + counting 126 | // channel 2 data port = 0x61 127 | outb(PIT_CH2_GATE, existing); 128 | // 0 = binary mode 129 | // 001 = hardware re-triggerable one-shot 130 | // 11 = lobyte/hibyte 131 | // 10 = channel 2 132 | //0xB2 133 | 134 | // 0x61 bits 135 | // 7 (ro) = memory parity 136 | // 6 (ro) = i/o check 137 | // 5 (ro) = out2 138 | // 4 (ro) = out1 139 | // 3 (rw) = i/o check enable 140 | // 2 (rw) = memory parity check enable 141 | // 1 (rw) = speaker 142 | // 0 (rw) = gate2 143 | //uint8_t val = (((2 << 2) || 3) << 4) | (2); 144 | outb(PIT_CMD, 0b10110000); // 0x43 = Command regisger 145 | 146 | 147 | existing = inb(PIT_CH2_GATE); // gate low 148 | 149 | outb(PIT_CH2_DATA, counter & 0xFF); 150 | outb(PIT_CH2_DATA, 0xFF & ((counter & 0xFF00) >> 8)); 151 | 152 | // Start counting 153 | existing = 0xFF & inb(PIT_CH2_GATE); 154 | // create rising edge by toggling value down 155 | outb(PIT_CH2_GATE, existing & 0xFE); 156 | inb(PIT_CH2_GATE+1); 157 | outb(PIT_CH2_GATE, existing | 1); // gate hight 158 | 159 | } 160 | 161 | void pit_wait() 162 | { 163 | uint8_t val = 0; 164 | do { 165 | val = inb(PIT_CH2_GATE); 166 | } while ( ((val & 0x20) == 0)) ; 167 | } 168 | 169 | 170 | void arm_timer(uint64_t wait) 171 | { 172 | cpus[current_cpu].timeout = wait; 173 | if (wait == 0) { 174 | cpus[current_cpu].clock_tick = clock_ticks; 175 | local_apic_write(APIC_TIMER_INITIAL, clock_ticks); 176 | } else { 177 | uint64_t ticks = wait * clock_ticks; 178 | ticks /= FIXED_POINT_TIME_NANOSECOND(0, CLOCK_MS * 1000000); 179 | if (ticks > 0xFFFFFFFF) { 180 | ticks = 0xFFFFFFFF; 181 | } 182 | if (ticks < 1) { 183 | ticks = 1; 184 | } 185 | cpus[current_cpu].clock_tick = ticks & 0xFFFFFFFF; 186 | local_apic_write(APIC_TIMER_INITIAL, ticks & 0xFFFFFFFF); 187 | } 188 | } 189 | 190 | void ap_clock_setup() 191 | { 192 | local_apic_write(APIC_TIMER_INITIAL, clock_ticks); 193 | local_apic_write(APIC_LVT_TIMER, TIMER_VECTOR); 194 | local_apic_write(APIC_TIMER_DIVIDE, 0x3); // div 16 195 | } 196 | 197 | // Initially based on https://wiki.osdev.org/APIC_timer 198 | void clock_setup() 199 | { 200 | EARLY_ERROR_PRINTF("Starting clock timer calibration\r\n"); 201 | // divider 16 202 | local_apic_write(APIC_LVT_TIMER, TIMER_VECTOR); 203 | local_apic_write(APIC_TIMER_DIVIDE, 0x3); 204 | 205 | uint16_t calibms = 10; 206 | load_pit(calibms); 207 | local_apic_write(APIC_TIMER_INITIAL, 0xFFFFFFFF); 208 | uint64_t tsc_start = __rdtsc(); 209 | 210 | pit_wait(); 211 | 212 | local_apic_write(APIC_LVT_TIMER, APIC_DISABLE); 213 | uint64_t tsc_end = __rdtsc(); 214 | 215 | uint64_t ticks = 0xFFFFFFFF - local_apic_read(APIC_TIMER_CURRENT); 216 | 217 | EARLY_ERROR_PRINTF("Clock: %llu apic ticks have occurred in %d ms\r\n", ticks, calibms); 218 | 219 | uint64_t tsc_ticks_per_s = (ticks * 16 * 1000) / calibms; 220 | EARLY_ERROR_PRINTF("Clock: Est speed (Mhz): %llu\r\n", tsc_ticks_per_s / 1000000); 221 | 222 | uint64_t tsc_ticks = tsc_end - tsc_start; 223 | EARLY_ERROR_PRINTF("Clock: %llu tsc ticks have occurred in %d ms\r\n", tsc_ticks, calibms); 224 | tsc_ticks_per_s = (tsc_ticks * 1000) / calibms; 225 | EARLY_ERROR_PRINTF("Clock: Est speed (Mhz): %llu\r\n", tsc_ticks_per_s / 1000000); 226 | SCALED_S_PER_TSC_TICK = ((FIXED_POINT_TIME_NANOSECOND(1, 0) << TSC_TICK_SCALE)* calibms / 1000) / tsc_ticks ; 227 | EARLY_ERROR_PRINTF("(scaled) fixedpoint time per tsc_tick %llu\r\n", SCALED_S_PER_TSC_TICK); 228 | 229 | EARLY_ERROR_PRINTF("fixed point time in %llu ticks: %llu ns\r\n", tsc_ticks, FIXED_POINT_NANOSECONDS((SCALED_S_PER_TSC_TICK * tsc_ticks) >> TSC_TICK_SCALE)); 230 | 231 | // Ok, now we disable the PIT timer by putting it into one-shot mode and not firing it 232 | outb(PIT_CMD, 0b00110000); // 0x43 = Command regisger 233 | 234 | clock_ticks = (ticks * CLOCK_MS) / calibms; 235 | EARLY_ERROR_PRINTF("Clock: Will interrupt every %d ms using %u\r\n", CLOCK_MS, clock_ticks); 236 | 237 | local_apic_write(APIC_TIMER_INITIAL, clock_ticks); 238 | local_apic_write(APIC_LVT_TIMER, TIMER_VECTOR); 239 | local_apic_write(APIC_TIMER_DIVIDE, 0x3); // div 16 240 | } 241 | 242 | void pic_setup(int master_offset, int slave_offset) 243 | { 244 | 245 | // setup PIC before disabling, so spurious interrupts hit vector 0xF7 246 | outb(PORT_PIC1_CMD, 0x11); // request initialization 247 | outb(PORT_PIC2_CMD, 0x11); // request initialization 248 | outb(PORT_PIC1_DATA, master_offset); 249 | outb(PORT_PIC2_DATA, slave_offset); // offset interrupts by 0xF0 250 | outb(PORT_PIC1_DATA, 0x4); // indicate slave PIC on IRQ 2 251 | outb(PORT_PIC2_DATA, 0x2); // indicate slave PIC is slave 252 | outb(PORT_PIC1_DATA, 0x01); // set to 8086 mode 253 | outb(PORT_PIC2_DATA, 0x01); // set to 8086 mode 254 | outb(PORT_PIC1_DATA, 0xFF); // mask all interrupts 255 | outb(PORT_PIC2_DATA, 0xFF); // mask all interrupts 256 | } 257 | -------------------------------------------------------------------------------- /c_src/crazierl_nif.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "erl_nif.h" 8 | #include "kern_mmap.h" 9 | 10 | ErlNifResourceType *MMAP_TYPE; 11 | struct mmap_resource { 12 | uintptr_t start; 13 | size_t length; 14 | }; 15 | 16 | ErlNifResourceType *IOPORT_TYPE; 17 | struct ioport_resource { 18 | uint16_t start; 19 | uint16_t length; 20 | }; 21 | 22 | static ERL_NIF_TERM inb_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 23 | { 24 | unsigned int port; 25 | uint8_t ret; 26 | if (!enif_get_uint(env, argv[0], &port)) { return enif_make_badarg(env); } 27 | asm volatile ( "inb %1, %0" : "=a"(ret) : "Nd"((uint16_t)port) ); 28 | return enif_make_uint(env, ret); 29 | } 30 | 31 | static ERL_NIF_TERM inl_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 32 | { 33 | unsigned int port; 34 | uint32_t ret; 35 | if (!enif_get_uint(env, argv[0], &port)) { return enif_make_badarg(env); } 36 | asm volatile ( "inl %1, %0" : "=a"(ret) : "Nd"((uint16_t)port) ); 37 | return enif_make_uint(env, ret); 38 | } 39 | 40 | static ERL_NIF_TERM outb_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 41 | { 42 | unsigned int port, val; 43 | if (!enif_get_uint(env, argv[0], &port)) { return enif_make_badarg(env); } 44 | if (!enif_get_uint(env, argv[1], &val)) { return enif_make_badarg(env); } 45 | asm volatile ( "outb %0, %1" : : "a"((uint8_t)val), "Nd"((uint16_t)port) ); 46 | return enif_make_atom(env, "ok"); 47 | } 48 | 49 | static ERL_NIF_TERM outl_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 50 | { 51 | unsigned int port, val; 52 | if (!enif_get_uint(env, argv[0], &port)) { return enif_make_badarg(env); } 53 | if (!enif_get_uint(env, argv[1], &val)) { return enif_make_badarg(env); } 54 | asm volatile ( "outl %0, %1" : : "a"((uint32_t)val), "Nd"((uint16_t)port) ); 55 | return enif_make_atom(env, "ok"); 56 | } 57 | 58 | static ERL_NIF_TERM map_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 59 | { 60 | uintptr_t start; 61 | size_t length; 62 | if (!enif_get_uint(env, argv[0], &start)) { return enif_make_badarg(env); } 63 | if (!enif_get_uint(env, argv[1], &length)) { return enif_make_badarg(env); } 64 | 65 | void * ret; 66 | if (start) { 67 | ret = mmap((void *)start, length, PROT_READ | PROT_WRITE | PROT_FORCE, 0, -1, 0); 68 | } else { 69 | ret = mmap((void *)start, length, PROT_READ | PROT_WRITE, 0, -1, 0); 70 | } 71 | 72 | if ((start != 0 && ((uintptr_t) ret == start)) || 73 | (start == 0 && ((uintptr_t) ret != start))) { 74 | struct mmap_resource *resource = enif_alloc_resource(MMAP_TYPE, sizeof(struct mmap_resource));; 75 | resource->start = (uintptr_t) ret; 76 | resource->length = length; 77 | ERL_NIF_TERM ret = enif_make_tuple2(env, 78 | enif_make_atom(env, "ok"), 79 | enif_make_resource(env, resource) 80 | ); 81 | enif_release_resource(resource); 82 | return ret; 83 | } else { 84 | return enif_make_atom(env, "error"); 85 | } 86 | } 87 | 88 | static ERL_NIF_TERM map_port_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 89 | { 90 | unsigned int start; 91 | unsigned int length; 92 | if (!enif_get_uint(env, argv[0], &start)) { return enif_make_badarg(env); } 93 | if (!enif_get_uint(env, argv[1], &length)) { return enif_make_badarg(env); } 94 | 95 | if (start > UINT16_MAX || length > UINT16_MAX) { 96 | return enif_make_badarg(env); 97 | } 98 | 99 | struct ioport_resource *resource = enif_alloc_resource(IOPORT_TYPE, sizeof(struct ioport_resource));; 100 | resource->start = (uint16_t) start; 101 | resource->length = (uint16_t) length; 102 | ERL_NIF_TERM ret = enif_make_tuple2(env, 103 | enif_make_atom(env, "ok"), 104 | enif_make_resource(env, resource) 105 | ); 106 | enif_release_resource(resource); 107 | return ret; 108 | } 109 | 110 | static ERL_NIF_TERM map_addr_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 111 | { 112 | struct mmap_resource *resource; 113 | if (!enif_get_resource(env, argv[0], MMAP_TYPE, (void **)&resource)) { return enif_make_badarg(env); } 114 | uintptr_t physical = resource->start; 115 | return enif_make_tuple2(env, 116 | enif_make_uint64(env, resource->start), 117 | enif_make_uint64(env, physical)); 118 | } 119 | 120 | static ERL_NIF_TERM bcopy_to_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 121 | { 122 | struct mmap_resource *mmap_resource; 123 | struct ioport_resource *ioport_resource; 124 | uintptr_t offset; 125 | ErlNifBinary binary; 126 | if (!enif_get_uint(env, argv[1], &offset)) { return enif_make_badarg(env); } 127 | if (!enif_inspect_iolist_as_binary(env, argv[2], &binary)) { return enif_make_badarg(env); } 128 | if (enif_get_resource(env, argv[0], MMAP_TYPE, (void **)&mmap_resource)) { 129 | if ((offset + binary.size) > mmap_resource->length) { return enif_make_badarg(env); } 130 | 131 | bcopy(binary.data, (void *)(mmap_resource->start + offset), binary.size); 132 | __sync_synchronize(); 133 | 134 | return enif_make_atom(env, "ok"); 135 | } else if (enif_get_resource(env, argv[0], IOPORT_TYPE, (void **)&ioport_resource)) { 136 | if ((offset + binary.size) > ioport_resource->length) { return enif_make_badarg(env); } 137 | 138 | uint16_t port = ioport_resource->start + offset; 139 | switch (binary.size) { 140 | case 1: 141 | asm volatile ( "outb %0, %1" : : "a"(binary.data[0]), "Nd"(port) ); 142 | break; 143 | case 2: { 144 | uint16_t data = binary.data[1] << 8 | binary.data[0]; 145 | asm volatile ( "outw %0, %1" : : "a"(data), "Nd"(port) ); 146 | break; 147 | } 148 | case 4: { 149 | uint32_t data = binary.data[3] << 24 | binary.data[2] << 16 | binary.data[1] << 8 | binary.data[0]; 150 | asm volatile ( "outl %0, %1" : : "a"(data), "Nd"(port) ); 151 | break; 152 | } 153 | case 8: { 154 | uint32_t data = binary.data[3] << 24 | binary.data[2] << 16 | binary.data[1] << 8 | binary.data[0]; 155 | asm volatile ( "outl %0, %1" : : "a"(data), "Nd"(port) ); 156 | port += 4; 157 | data = binary.data[7] << 24 | binary.data[6] << 16 | binary.data[5] << 8 | binary.data[4]; 158 | asm volatile ( "outl %0, %1" : : "a"(data), "Nd"(port) ); 159 | break; 160 | } 161 | default: 162 | printf("bad binary size for bcopy_to %d\r\n", binary.size); 163 | return enif_make_badarg(env); 164 | /* 165 | for (size_t i = 0; i < binary.size; ++i) { 166 | uint16_t port = ioport_resource->start + offset + i; 167 | asm volatile ( "outb %0, %1" : : "a"(binary.data[i]), "Nd"(port) ); 168 | }*/ 169 | } 170 | return enif_make_atom(env, "ok"); 171 | } 172 | return enif_make_badarg(env); 173 | } 174 | 175 | static ERL_NIF_TERM bcopy_from_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 176 | { 177 | struct mmap_resource *mmap_resource; 178 | struct ioport_resource *ioport_resource; 179 | uintptr_t offset; 180 | size_t length; 181 | if (!enif_get_uint(env, argv[1], &offset)) { return enif_make_badarg(env); } 182 | if (!enif_get_uint(env, argv[2], &length)) { return enif_make_badarg(env); } 183 | if (enif_get_resource(env, argv[0], MMAP_TYPE, (void **)&mmap_resource)) { 184 | if ((offset + length) > mmap_resource->length) { return enif_make_badarg(env); } 185 | 186 | ERL_NIF_TERM binary; 187 | unsigned char * bindata = enif_make_new_binary(env, length, &binary); 188 | 189 | __sync_synchronize(); 190 | bcopy((void *) (mmap_resource->start + offset), bindata, length); 191 | return binary; 192 | } else if (enif_get_resource(env, argv[0], IOPORT_TYPE, (void **)&ioport_resource)) { 193 | if ((offset + length) > ioport_resource->length) { return enif_make_badarg(env); } 194 | 195 | ERL_NIF_TERM binary; 196 | uint16_t port = ioport_resource->start + offset; 197 | unsigned char * bindata = enif_make_new_binary(env, length, &binary); 198 | switch (length) { 199 | case 1: 200 | asm volatile ( "inb %1, %0" : "=a"(bindata[0]) : "Nd"(port) ); 201 | break; 202 | case 2: { 203 | uint16_t data; 204 | asm volatile ( "inw %1, %0" : "=a"(data) : "Nd"(port) ); 205 | bindata[0] = data & 0xFF; 206 | bindata[1] = (data >> 8) & 0xFF; 207 | break; 208 | } 209 | case 4: { 210 | uint32_t data; 211 | asm volatile ( "inl %1, %0" : "=a"(data) : "Nd"(port) ); 212 | bindata[0] = data & 0xFF; 213 | bindata[1] = (data >> 8) & 0xFF; 214 | bindata[2] = (data >> 16) & 0xFF; 215 | bindata[3] = (data >> 24) & 0xFF; 216 | break; 217 | } 218 | default: 219 | for (size_t i = 0; i < length; ++i, ++port) { 220 | asm volatile ( "inb %1, %0" : "=a"(bindata[i]) : "Nd"(port) ); 221 | } 222 | } 223 | return binary; 224 | } 225 | return enif_make_badarg(env); 226 | } 227 | 228 | static ERL_NIF_TERM time_offset_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 229 | { 230 | struct timeval tv, tv2; 231 | 232 | #if defined(__i386__) 233 | _Static_assert(sizeof(tv.tv_sec) == sizeof(int), "time_t is not an int on i386?"); 234 | if (!enif_get_int(env, argv[0], &tv.tv_sec)) { return enif_make_badarg(env); } 235 | #else 236 | _Static_assert(sizeof(tv.tv_sec) == sizeof(int64_t), "time_t is not a int64 on non-i386?"); 237 | if (!enif_get_int64(env, argv[0], &tv.tv_sec)) { return enif_make_badarg(env); } 238 | #endif 239 | 240 | _Static_assert(sizeof(tv.tv_usec) == sizeof(long), "suseconds_t is not a long?"); 241 | if (!enif_get_long(env, argv[1], &tv.tv_usec)) { return enif_make_badarg(env); } 242 | 243 | if (gettimeofday(&tv2, NULL) != 0) { 244 | return enif_make_tuple2(env, enif_make_atom(env, "error"), enif_make_int(env, errno)); 245 | } 246 | tv2.tv_sec += tv.tv_sec; 247 | tv2.tv_usec += tv.tv_usec; 248 | while (tv2.tv_usec >= 1000000) { 249 | ++tv2.tv_sec; 250 | tv2.tv_usec -= 1000000; 251 | } 252 | while (tv2.tv_usec < 0) { 253 | --tv2.tv_sec; 254 | tv2.tv_usec += 1000000; 255 | } 256 | if (settimeofday(&tv2, NULL) != 0) { 257 | return enif_make_tuple2(env, enif_make_atom(env, "error"), enif_make_int(env, errno)); 258 | } 259 | return enif_make_atom(env, "ok"); 260 | } 261 | 262 | static ERL_NIF_TERM ntp_adjtime_freq_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 263 | { 264 | struct timex tx = {0}; 265 | int result; 266 | if (!enif_get_long(env, argv[0], &tx.freq)) { return enif_make_badarg(env); } 267 | tx.modes = MOD_FREQUENCY; 268 | result = ntp_adjtime(&tx); 269 | if (result != TIME_OK) { 270 | return enif_make_tuple2(env, enif_make_atom(env, "error"), enif_make_int(env, errno)); 271 | } 272 | return enif_make_atom(env, "ok"); 273 | } 274 | 275 | static ErlNifFunc nif_funcs[] = { 276 | {"inb", 1, inb_nif}, 277 | {"inl", 1, inl_nif}, 278 | {"outb", 2, outb_nif}, 279 | {"outl", 2, outl_nif}, 280 | {"map", 2, map_nif}, 281 | {"map_port", 2, map_port_nif}, 282 | {"map_addr", 1, map_addr_nif}, 283 | {"bcopy_to", 3, bcopy_to_nif}, 284 | {"bcopy_from", 3, bcopy_from_nif}, 285 | {"time_offset", 2, time_offset_nif}, 286 | {"ntp_adjtime_freq", 1, ntp_adjtime_freq_nif} 287 | }; 288 | 289 | int load (ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) 290 | { 291 | MMAP_TYPE = enif_open_resource_type(env, NULL, "mmap", NULL, ERL_NIF_RT_CREATE, NULL); 292 | IOPORT_TYPE = enif_open_resource_type(env, NULL, "ioport", NULL, ERL_NIF_RT_CREATE, NULL); 293 | return 0; 294 | } 295 | 296 | ERL_NIF_INIT(crazierl, nif_funcs, load, NULL, NULL, NULL) 297 | -------------------------------------------------------------------------------- /src/vgakb.erl: -------------------------------------------------------------------------------- 1 | -module (vgakb). 2 | 3 | -export ([start/2, init/3]). 4 | -record (s, { 5 | owner, 6 | io_port, 7 | irq, 8 | mods, 9 | current_index, 10 | cursor_updated, 11 | in_buffer, out_buffer, frame_buffer, 12 | last_line_compare = -1 13 | }). 14 | 15 | -record (mods, { 16 | left_ctrl = false, 17 | left_shift = false, 18 | right_shift = false, 19 | left_alt = false, 20 | caps_lock = false, 21 | num_lock = false, 22 | scroll_lock = false 23 | }). 24 | 25 | -define(VGA_FB, 16#a0000). 26 | -define(VGA_LEN, 16#20000). 27 | -define(VGA_ELEMENTS, (?VGA_LEN div 2)). 28 | -define(VGA_ROWS, 25). 29 | -define(VGA_COLS, 80). 30 | -define(VGA_MEM_COLS, 128). 31 | -define(TERM_COLOR, 16#F). % Black background, White foreground 32 | 33 | start(IoPort, Interrupt) -> 34 | spawn(?MODULE, init, [self(), IoPort, Interrupt]). 35 | 36 | init(Owner, IoPort, Interrupt) -> 37 | register(?MODULE, self()), 38 | {ok, InterruptSocket} = gen_udp:open(0, [ 39 | {inet_backend, inet}, 40 | {ifaddr, {local, Interrupt}}, 41 | {active, true} 42 | ]), 43 | VgaInited = init_vga(), 44 | Key = crazierl:inb(IoPort), % read and process any pending keystroke 45 | loop(key_decode(VgaInited#s{owner = Owner, io_port = IoPort, irq = InterruptSocket, mods = #mods{}}, Key)). 46 | 47 | init_vga() -> 48 | % assumption: kernel is already writing to VGA 49 | % vga memory map is set to 16#a0000 - 16#bffff 50 | % horizontal offset set to 128 51 | % vga is in text mode 52 | % cursor is placed by kernel where we should start writing new data 53 | % CRT controller register is at 16#3D4 54 | 55 | {ok, Map} = crazierl:map(?VGA_FB, ?VGA_LEN), 56 | 57 | % fetch cursor location 58 | crazierl:outb(16#3D4, 16#F), 59 | CurIndexLo = crazierl:inb(16#3D5), 60 | crazierl:outb(16#3D4, 16#E), 61 | CurIndexHi = crazierl:inb(16#3D5), 62 | <> = <>, 63 | 64 | #s{current_index = CurIndex, cursor_updated = false, in_buffer = <<>>, out_buffer = <<>>, frame_buffer = Map}. 65 | 66 | 67 | loop(State = #s{irq = Irq, io_port = Port, in_buffer = IB}) -> 68 | Timeout = case {State#s.out_buffer, State#s.cursor_updated} of 69 | {<<>>, false} -> infinity; 70 | _ -> 0 71 | end, 72 | NewState = receive 73 | {udp, Irq, _, _, _} -> 74 | Key = crazierl:inb(Port), 75 | key_decode(State, Key); 76 | Data when is_binary(Data) -> 77 | output_decode(State#s{in_buffer = <>}) 78 | after Timeout -> 79 | flush(State) 80 | end, 81 | loop(NewState). 82 | 83 | output_decode(State = #s{out_buffer = OB, current_index = CI}) 84 | when (CI band (?VGA_MEM_COLS - 1)) + (size(OB) div 2) >= ?VGA_COLS -> 85 | output_decode(flush(State)); 86 | 87 | output_decode(State = #s{in_buffer = <>, out_buffer = OB}) 88 | when CtrlChar =< 31, OB /= <<>> -> 89 | output_decode(flush(State)); 90 | 91 | output_decode(State = #s{in_buffer = <<$\r, IB/binary>>, current_index = CI}) -> 92 | output_decode(State#s{in_buffer = IB, cursor_updated = true, current_index = CI band (bnot(?VGA_MEM_COLS - 1))}); 93 | 94 | output_decode(State = #s{in_buffer = <<$\b, IB/binary>>, current_index = CI}) -> 95 | output_decode(State#s{in_buffer = IB, cursor_updated = true, current_index = CI - 1}); 96 | 97 | output_decode(State = #s{in_buffer = <<$\n, IB/binary>>}) -> 98 | output_decode(newline(State#s{in_buffer = IB, cursor_updated = true})); 99 | 100 | % ignore bell 101 | output_decode(State = #s{in_buffer = <<$\^g, IB/binary>>}) -> 102 | output_decode(State#s{in_buffer = IB}); 103 | 104 | output_decode(State = #s{in_buffer = <<$\e, $[, $C, IB/binary>>, current_index = CI}) -> 105 | output_decode(State#s{in_buffer = IB, cursor_updated = true, current_index = CI + 1}); 106 | 107 | output_decode(State = #s{in_buffer = <<$\e, $[, $A, IB/binary>>, current_index = CI}) -> 108 | NewCI = (CI - ?VGA_MEM_COLS) band (?VGA_ELEMENTS - 1), 109 | output_decode(State#s{in_buffer = IB, cursor_updated = true, current_index = NewCI}); 110 | 111 | % ignore erase until end of screen? 112 | output_decode(State = #s{in_buffer = <<$\e, $[, $J, IB/binary>>}) -> 113 | output_decode(State#s{in_buffer = IB}); 114 | 115 | output_decode(State = #s{in_buffer = <<$\e, A, B, C, IB/binary>>}) -> 116 | % io:format("vgakb ignoring ESC + 16#~2.16.0B~2.16.0B~2.16.0B (~s)~n", [A, B, C, [A,B,C]]), 117 | output_decode(State#s{in_buffer = IB}); 118 | 119 | output_decode(State = #s{in_buffer = <<$\e, _/binary>>}) -> 120 | State; 121 | 122 | output_decode(State = #s{in_buffer = <>, out_buffer = OB}) -> 123 | output_decode(State#s{in_buffer = IB, out_buffer = <>}); 124 | 125 | output_decode(State = #s{in_buffer = <<>>}) -> 126 | State. 127 | 128 | 129 | flush (State = #s{out_buffer = <<>>, cursor_updated = true, current_index = CI}) -> % all data output, need to move cursor 130 | % set cursor location 131 | CursorIndex = CI, 132 | <> = <>, 133 | crazierl:outb(16#3D4, 16#E), 134 | crazierl:outb(16#3D5, CurIndexHi), 135 | crazierl:outb(16#3D4, 16#F), 136 | crazierl:outb(16#3D5, CurIndexLo), 137 | State#s{cursor_updated = false}; 138 | 139 | flush (State = #s{out_buffer = OB, frame_buffer = FB}) -> 140 | crazierl:bcopy_to(FB, (State#s.current_index * 2), OB), 141 | AddedIndex = State#s.current_index + (size(OB) div 2), 142 | if 143 | AddedIndex band (?VGA_MEM_COLS - 1) >= ?VGA_COLS -> 144 | newline(State#s{out_buffer = <<>>, cursor_updated = true, current_index = AddedIndex}); 145 | true -> 146 | State#s{out_buffer = <<>>, cursor_updated = true, current_index = AddedIndex} 147 | end. 148 | 149 | newline(State = #s{current_index = OldIndex, frame_buffer = FB}) -> 150 | Index = case (OldIndex band (bnot (?VGA_MEM_COLS - 1))) + ?VGA_MEM_COLS of 151 | ?VGA_ELEMENTS -> 0; 152 | Other -> Other 153 | end, 154 | 155 | % clear next line 156 | crazierl:bcopy_to(FB, (Index * 2), binary:copy(<<" ", ?TERM_COLOR:8>>, ?VGA_COLS)), 157 | RawDisplayIndex = Index - (?VGA_MEM_COLS * (?VGA_ROWS - 1)), 158 | {LineCompare, DisplayIndex} = if 159 | RawDisplayIndex < 0 -> 160 | {16 * (-RawDisplayIndex div ?VGA_MEM_COLS), RawDisplayIndex + ?VGA_ELEMENTS}; 161 | true -> 162 | {16#3FF, RawDisplayIndex} 163 | end, 164 | case State#s.last_line_compare of 165 | LineCompare -> ok; 166 | _ -> 167 | crazierl:outb(16#3D4, 16#7), 168 | Overflow = crazierl:inb(16#3D5), 169 | NewOverflow = case ((LineCompare band 16#100) /= 0) of 170 | true -> Overflow bor 2#00010000; 171 | false ->Overflow band 2#11101111 172 | end, 173 | Overflow /= NewOverflow andalso crazierl:outb(16#3D5, NewOverflow), 174 | 175 | crazierl:outb(16#3D4, 16#9), 176 | MaxScan = crazierl:inb(16#3D5), 177 | NewMaxScan = case ((LineCompare band 16#200) /= 0) of 178 | true -> MaxScan bor 2#01000000; 179 | false ->MaxScan band 2#10111111 180 | end, 181 | MaxScan /= NewMaxScan andalso crazierl:outb(16#3D5, NewMaxScan), 182 | 183 | crazierl:outb(16#3D4, 16#18), 184 | crazierl:outb(16#3D5, LineCompare band 16#FF) 185 | end, 186 | 187 | <> = <>, 188 | crazierl:outb(16#3D4, 16#C), 189 | crazierl:outb(16#3D5, IndexHi), 190 | crazierl:outb(16#3D4, 16#D), 191 | crazierl:outb(16#3D5, IndexLo), 192 | State#s{out_buffer = <<>>, cursor_updated = true, last_line_compare = LineCompare, current_index = Index}. 193 | 194 | 195 | key_decode(State, 16#e0) -> State; %ignore escapes for the moment 196 | key_decode(State, 16#e1) -> State; 197 | 198 | key_decode(State = #s{mods = Mods}, RawKey) -> 199 | {KeyMake, Key} = if 200 | RawKey band 16#80 /= 0 -> {false, RawKey band 16#7F }; 201 | true -> {true, RawKey} 202 | end, 203 | 204 | % scancode reference https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html 205 | ScanTable = if 206 | Mods#mods.left_ctrl -> { 207 | $\e, 208 | {}, 0, {}, {}, {}, $\^^, {}, {}, {}, {}, $\^_, {}, $\b, 209 | {}, $\^Q, $\^W, $\^E, $\^R, $\^T, $\^Y, $\^U, $\^I, $\^O, $\^P, $\^[, $\^], $\r, 210 | left_ctrl, $\^A, $\^S, $\^D, $\^F, $\^G, $\^H, $\^J, $\^K, $\^L, {}, {}, {}, 211 | left_shift, $\^\, $\^Z, $\^X, $\^C, $\^V, $\^B, $\^N, $\^M, {}, {}, $\^?, right_shift, 212 | {}, left_alt, $\s, caps_lock, 213 | {f, 1}, {f, 2}, {f, 3}, {f, 4}, {f, 5}, 214 | {f, 6}, {f, 7}, {f, 8}, {f, 9}, {f, 10}, 215 | num_lock, scroll_lock, 216 | {f, home}, {f, up}, {f, pgup}, {}, 217 | {f, left}, {f, keypad_5}, {f, right}, {}, 218 | {f, 'end'}, {f, down}, {f, pgdn}, 219 | {f, ins}, {f, del}, 220 | 0, 0, 0, % Alt-SysRq, ??, ?? 221 | {f, 11}, {f, 12} 222 | }; 223 | Mods#mods.left_shift orelse Mods#mods.right_shift -> { 224 | $\e, 225 | $!, $@, $#, $$, $%, $^, $&, $*, $(, $), $_, $+, $\b, 226 | $\t, $Q, $W, $E, $R, $T, $Y, $U, $I, $O, $P, ${, $}, $\r, 227 | left_ctrl, $A, $S, $D, $F, $G, $H, $J, $K, $L, $:, $\", $~, 228 | left_shift, $|, $Z, $X, $C, $V, $B, $N, $M, $<, $>, $?, right_shift, 229 | $*, left_alt, $\s, caps_lock, 230 | {f, 1}, {f, 2}, {f, 3}, {f, 4}, {f, 5}, 231 | {f, 6}, {f, 7}, {f, 8}, {f, 9}, {f, 10}, 232 | num_lock, scroll_lock, 233 | {f, home}, {f, up}, {f, pgup}, $-, 234 | {f, left}, {f, keypad_5}, {f, right}, $+, 235 | {f, 'end'}, {f, down}, {f, pgdn}, 236 | {f, ins}, {f, del}, 237 | 0, 0, 0, % Alt-SysRq, ??, ?? 238 | {f, 11}, {f, 12} 239 | }; 240 | true -> { 241 | $\e, 242 | $1, $2, $3, $4, $5, $6, $7, $8, $9, $0, $-, $=, $\b, 243 | $\t, $q, $w, $e, $r, $t, $y, $u, $i, $o, $p, $[, $], $\r, 244 | left_ctrl, $a, $s, $d, $f, $g, $h, $j, $k, $l, $;, $\', $`, 245 | left_shift, $\\, $z, $x, $c, $v, $b, $n, $m, $,, $., $/, right_shift, 246 | $*, left_alt, $\s, caps_lock, 247 | {f, 1}, {f, 2}, {f, 3}, {f, 4}, {f, 5}, 248 | {f, 6}, {f, 7}, {f, 8}, {f, 9}, {f, 10}, 249 | num_lock, scroll_lock, 250 | {f, home}, {f, up}, {f, pgup}, $-, 251 | {f, left}, {f, keypad_5}, {f, right}, $+, 252 | {f, 'end'}, {f, down}, {f, pgdn}, 253 | {f, ins}, {f, del}, 254 | 0, 0, 0, % Alt-SysRq, ??, ?? 255 | {f, 11}, {f, 12} 256 | } 257 | end, 258 | 259 | if 260 | Key > 0 andalso Key =< tuple_size(ScanTable) -> 261 | DecodedKey = element(Key, ScanTable), 262 | key_process(State, KeyMake, DecodedKey); 263 | RawKey == 16#FA -> 264 | State; 265 | true -> 266 | io:format("couldn't handle scancode ~.16B (~.16B)~n", [Key, RawKey]), 267 | State 268 | end. 269 | 270 | key_process(State = #s {mods = Mods}, Bool, Modifier) when is_atom(Modifier) -> 271 | NewMods = case Modifier of 272 | left_ctrl -> Mods#mods{left_ctrl = Bool}; 273 | left_shift -> Mods#mods{left_shift = Bool}; 274 | right_shift -> Mods#mods{right_shift = Bool}; 275 | left_alt -> Mods#mods{left_alt = Bool}; 276 | caps_lock when Bool == true -> Mods#mods{caps_lock = not Mods#mods.caps_lock}; 277 | num_lock when Bool == true -> Mods#mods{num_lock = not Mods#mods.num_lock}; 278 | scroll_lock when Bool == true -> Mods#mods{scroll_lock = not Mods#mods.scroll_lock}; 279 | _ -> Mods 280 | end, 281 | State#s{ mods = NewMods }; 282 | key_process(State, false, _DecodedKey) -> State; % ignore other key up events 283 | key_process(State, true, DecodedKey) when is_integer(DecodedKey) -> 284 | State#s.owner ! { self(), [DecodedKey] }, 285 | State; 286 | key_process(State, true, {f, up}) -> 287 | State#s.owner ! { self(), ["\e[A"]}, 288 | State; 289 | key_process(State, true, {f, down}) -> 290 | State#s.owner ! { self(), ["\e[B"]}, 291 | State; 292 | key_process(State, true, {f, right}) -> 293 | State#s.owner ! { self(), ["\e[C"]}, 294 | State; 295 | key_process(State, true, {f, left}) -> 296 | State#s.owner ! { self(), ["\e[D"]}, 297 | State; 298 | key_process(State, true, DecodedKey) -> 299 | io:format("key down ~2000p~n", [DecodedKey]), 300 | State. 301 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ERLANG_VERSION=26 2 | #OTPDIR=../installed/lib/erlang 3 | OTPDIR=erlang-runtime$(ERLANG_VERSION)/usr/local/lib/erlang$(ERLANG_VERSION) 4 | 5 | OBJDIR := obj 6 | DEPDIR := $(OBJDIR)/.deps 7 | DEPFLAGS = -MT $@ -MMD -MP 8 | HWNODE ?= 'crazierl@crazierlp.ruka.org' 9 | ROOTDIR := 10 | 11 | ERLANG_OVERRIDES = $(wildcard overrides/*.erl) 12 | 13 | ERLANG_SRCS = $(wildcard src/*.erl) 14 | ERLANG_OBJS = $(ERLANG_SRCS:src/%.erl=$(OBJDIR)/%.beam) 15 | ERLANG_OVERRIDE_OBJS = $(ERLANG_OVERRIDES:overrides/%.erl=$(OBJDIR)/%.beam) 16 | INITRD_ERLANG_OBJS = $(filter-out $(OBJDIR)/hook_module.beam,$(ERLANG_OBJS)) $(ERLANG_OVERRIDE_OBJS) 17 | KERNEL_SRCS = $(wildcard kernel_src/*.c) 18 | KERNEL_OBJS = $(KERNEL_SRCS:kernel_src/%.c=$(OBJDIR)/%.o) 19 | NIF_SRCS = $(wildcard c_src/*_nif.c) 20 | NIF_OBJS = $(NIF_SRCS:c_src/%.c=$(OBJDIR)/%.so) 21 | 22 | 23 | REAL_FBSD_KERNEL_SRCS = lib/libc/quad/qdivrem.c lib/libc/quad/udivdi3.c \ 24 | lib/libc/quad/umoddi3.c lib/libc/quad/divdi3.c \ 25 | lib/libc/stdlib/llabs.c lib/libc/stdlib/qsort.c \ 26 | lib/libc/string/bcmp.c lib/libc/string/bzero.c \ 27 | lib/libc/string/ffsl.c lib/libc/string/flsl.c \ 28 | lib/libc/string/memcpy.c \ 29 | lib/libc/string/memmove.c lib/libc/string/memset.c \ 30 | lib/libc/string/strchr.c lib/libc/string/strchrnul.c \ 31 | lib/libc/string/strcmp.c lib/libc/string/strcpy.c \ 32 | lib/libc/string/strlcpy.c lib/libc/string/strlen.c \ 33 | lib/libc/string/strncmp.c lib/libc/string/strncpy.c \ 34 | lib/libc/string/strnlen.c libexec/rtld-elf/rtld_printf.c \ 35 | sys/kern/syscalls.c sys/libkern/explicit_bzero.c 36 | FBSD_KERNEL_SRCS = $(foreach file, $(REAL_FBSD_KERNEL_SRCS), $(subst /,__,$(file))) 37 | FBSD_KERNEL_OBJS = $(FBSD_KERNEL_SRCS:%.c=$(OBJDIR)/%.o) 38 | 39 | REAL_BEARSSL_SRCS = rand/hmac_drbg.c mac/hmac.c hash/sha2small.c codec/dec32be.c \ 40 | codec/enc32be.c 41 | BEARSSL_SRCS = $(foreach file, $(REAL_BEARSSL_SRCS), $(subst /,__,$(file))) 42 | BEARSSL_OBJS = $(BEARSSL_SRCS:%.c=$(OBJDIR)/%.o) 43 | 44 | TCPIP_SRCS = $(filter-out %eth_port.erl,$(wildcard erlang-tcpip/src/*.erl)) 45 | TCPIP_OBJS = $(TCPIP_SRCS:erlang-tcpip/src/%.erl=$(OBJDIR)/%.beam) 46 | 47 | ifeq ($(wildcard $(ROOTDIR)/libexec/ld-elf32.so.1),) 48 | RTLD=$(ROOTDIR)/libexec/ld-elf.so.1 49 | else 50 | RTLD=$(ROOTDIR)/libexec/ld-elf32.so.1 51 | endif 52 | 53 | KERNEL_COMPILER=clang -Werror -m32 -mno-sse -g -ffreestanding -gdwarf-2 -mgeneral-regs-only -mno-red-zone -c -DCRAZIERL_KERNEL 54 | NIF_COMPILER=clang -Werror -m32 -fpic -g -gdwarf-2 -shared -I$(OTPDIR)/usr/include/ -I kernel_src/ 55 | 56 | run: obj/crazierl.elf obj/initrd 57 | qemu-system-i386 -cpu max --no-reboot -display none -smp 16 -s -m 512 -serial mon:stdio -kernel obj/crazierl.elf -append $(RTLD) -initrd obj/initrd \ 58 | -netdev user,hostname=localhost,id=mynet0,hostfwd=tcp:127.0.0.1:7780-:80,hostfwd=tcp:127.0.0.1:7781-:8080,hostfwd=tcp:127.0.0.1:4370-:4370 -device virtio-net,netdev=mynet0 -object filter-dump,id=mynet0,netdev=mynet0,file=/tmp/crazierl.pcap 59 | 60 | build: obj/crazierl.elf obj/initrd 61 | echo "Built" 62 | 63 | obj/crazierl.elf.gz: obj/crazierl.elf 64 | gzip -f -9 -k $^ 65 | 66 | obj/initrd.gz: obj/initrd 67 | gzip -f -9 -k $^ 68 | 69 | obj/crazierl.iso: obj/initrd.gz obj/crazierl.elf.gz cfg/grub.cfg 70 | mkdir -p obj/iso/boot/grub 71 | cp obj/initrd.gz obj/crazierl.elf.gz obj/iso 72 | cp cfg/grub.cfg obj/iso/boot/grub 73 | grub-mkrescue -o obj/crazierl.iso obj/iso/ 74 | 75 | # if I can figure out how to get iPXE to use gz files... 76 | #netboot: obj/crazierl.elf.gz obj/initrd.gz 77 | netboot: obj/crazierl.elf obj/initrd 78 | cp $^ /usr/local/www/apache24/data/tftpboot/crazierl/ 79 | 80 | push-to-demo: obj/crazierl.elf obj/initrd 81 | rsync $^ dh1.ruka.org:crazierl/ 82 | 83 | iso: obj/crazierl.iso 84 | cp $^ /usr/local/www/apache24/data/tftpboot/crazierl/ 85 | rsync $^ dh1.ruka.org:crazierl/ 86 | 87 | debug: obj/crazierl.elf obj/initrd 88 | qemu-system-i386 -display none -d cpu_reset,guest_errors -smp 1 -S -s -m 512 -serial mon:stdio -kernel obj/crazierl.elf -append $(RTLD) -initrd obj/initrd 89 | 90 | noisy: obj/crazierl.elf obj/initrd 91 | qemu-system-i386 -display none -smp 2 -d nochain,exec,cpu_reset,guest_errors -s -m 256 -serial mon:stdio -kernel obj/crazierl.elf -append $(RTLD) -initrd obj/initrd 92 | 93 | dist: .erlang.cookie obj/crazierl_epmd.beam $(OTPDIR)/bin/erl obj/gen_tcp_dist.beam 94 | $(OTPDIR)/bin/erl -no_epmd -proto_dist gen_tcp -epmd_module crazierl_epmd -sname host -pz $(shell pwd)/$(OBJDIR) -setcookie $(shell cat .erlang.cookie) 95 | 96 | dist-hw: .erlang.cookie obj/crazierl_epmd.beam $(OTPDIR)/bin/erl obj/gen_tcp_dist.beam 97 | $(OTPDIR)/bin/erl -no_epmd -proto_dist gen_tcp -epmd_module crazierl_epmd -name host -pz $(shell pwd)/$(OBJDIR) -setcookie $(shell cat .erlang.cookie) -kernel inet_dist_listen_min 4370 98 | 99 | remote-shell: .erlang.cookie obj/crazierl_epmd.beam $(OTPDIR)/bin/erl obj/gen_tcp_dist.beam 100 | $(OTPDIR)/bin/erl -hidden -no_epmd -proto_dist gen_tcp -epmd_module crazierl_epmd -remsh 'crazierl@localhost' -sname shell -pz $(shell pwd)/$(OBJDIR) -setcookie $(shell cat .erlang.cookie) 101 | 102 | remote-shell-hw: .erlang.cookie obj/crazierl_epmd.beam $(OTPDIR)/bin/erl obj/gen_tcp_dist.beam 103 | $(OTPDIR)/bin/erl -hidden -no_epmd -proto_dist gen_tcp -epmd_module crazierl_epmd -remsh $(HWNODE) -name shell -pz $(shell pwd)/$(OBJDIR) -setcookie $(shell cat .erlang.cookie) 104 | 105 | push-code: $(TCPIP_OBJS) $(INITRD_ERLANG_OBJS) $(OTPDIR)/bin/escript 106 | @ERL_FLAGS="-hidden -sname pusher -no_epmd -proto_dist gen_tcp -epmd_module crazierl_epmd -pa $(shell pwd)/$(OBJDIR) -setcookie $(shell cat .erlang.cookie)" $(OTPDIR)/bin/escript scripts/push_code.escript 'crazierl@localhost' $(TCPIP_OBJS) $(INITRD_ERLANG_OBJS) 107 | 108 | timer-offset: $(OTPDIR)/bin/escript 109 | @ERL_FLAGS="-hidden -sname timer -no_epmd -proto_dist gen_tcp -epmd_module crazierl_epmd -pa $(shell pwd)/$(OBJDIR) -setcookie $(shell cat .erlang.cookie)" $(OTPDIR)/bin/escript scripts/timer_offset.escript 'crazierl@localhost' 110 | 111 | timer-offset-hw: $(OTPDIR)/bin/escript 112 | @ERL_FLAGS="-hidden -name timer -no_epmd -proto_dist gen_tcp -epmd_module crazierl_epmd -pa $(shell pwd)/$(OBJDIR) -setcookie $(shell cat .erlang.cookie)" $(OTPDIR)/bin/escript scripts/timer_offset.escript $(HWNODE) 113 | 114 | push-code-hw: $(TCPIP_OBJS) $(INITRD_ERLANG_OBJS) $(OTPDIR)/bin/escript 115 | @ERL_FLAGS="-hidden -name pusher -no_epmd -proto_dist gen_tcp -epmd_module crazierl_epmd -pa $(shell pwd)/$(OBJDIR) -setcookie $(shell cat .erlang.cookie)" $(OTPDIR)/bin/escript scripts/push_code.escript $(HWNODE) $(TCPIP_OBJS) $(INITRD_ERLANG_OBJS) 116 | 117 | debugger: 118 | gdb -ex "set confirm off" -ex "add-symbol-file obj/crazierl.elf" -ex "add-symbol-file $$(find $(OTPDIR) -name beam.smp)" -ex "target remote localhost:1234" 119 | 120 | .PHONY: clean $(OTPDIR)/bin/erlc 121 | clean: 122 | rm -f obj/initrd obj/crazierl.elf obj/*.gz obj/*.o obj/*.beam obj/*.so obj/initrd.tmp obj/.deps/*.d obj/*.app obj/*.iso obj/iso/initrd obj/iso/crazierl.elf obj/iso/boot/grub/grub.cfg 123 | 124 | erlang-runtime$(ERLANG_VERSION)/usr/share/keys/pkg/trusted/.setup: 125 | mkdir -p erlang-runtime$(ERLANG_VERSION)/usr/share/keys/pkg 126 | cp -a /usr/share/keys/pkg/trusted erlang-runtime$(ERLANG_VERSION)/usr/share/keys/pkg 127 | touch erlang-runtime$(ERLANG_VERSION)/usr/share/keys/pkg/trusted/.setup 128 | 129 | $(OTPDIR)/bin/erl: erlang-runtime$(ERLANG_VERSION)/usr/share/keys/pkg/trusted/.setup 130 | IGNORE_OSVERSION=yes INSTALL_AS_USER=1 pkg -R ../cfg --root erlang-runtime$(ERLANG_VERSION) -o ABI=FreeBSD:14:i386 install -r latest -y erlang-runtime$(ERLANG_VERSION) 131 | touch $(OTPDIR)/bin/erl 132 | 133 | 134 | $(OTPDIR)/bin/erl.patched: $(OTPDIR)/bin/erl 135 | sed -e 's@"/usr/local/lib/erlang$(ERLANG_VERSION)"@"$(shell pwd)/$(OTPDIR)"@' -i backup $(OTPDIR)/bin/erl 136 | touch $(OTPDIR)/bin/erl.patched 137 | 138 | $(OTPDIR)/bin/erlc: $(OTPDIR)/bin/erl.patched 139 | $(OTPDIR)/bin/escript: $(OTPDIR)/bin/erlc 140 | 141 | ALL_KERNEL_OBJS = $(KERNEL_OBJS) $(FBSD_KERNEL_OBJS) $(BEARSSL_OBJS) obj/start.o 142 | $(OBJDIR)/crazierl.elf: $(ALL_KERNEL_OBJS) kernel_src/linker.ld 143 | clang -m32 -g -static -ffreestanding -nostdlib -Xlinker -Tkernel_src/linker.ld -Xlinker $(ALL_KERNEL_OBJS) -o obj/crazierl.elf -gdwarf-2 144 | 145 | obj/start.o: kernel_src/start.s | $(DEPDIR) 146 | clang -m32 -g -gdwarf-2 -c $^ -o $@ 147 | 148 | INITRD_FILES := cfg/inetrc obj/etcpip.app /etc/termcap $(NIF_OBJS) obj/checksum.so $(TCPIP_OBJS) $(INITRD_ERLANG_OBJS) 149 | 150 | .erlang.cookie: scripts/gen_cookie.escript $(OTPDIR)/bin/escript 151 | $(OTPDIR)/bin/escript scripts/gen_cookie.escript > .erlang.cookie.tmp 152 | mv .erlang.cookie.tmp .erlang.cookie 153 | 154 | obj/initrd: scripts/hardcode_files.pl scripts/extract_start.escript $(OTPDIR)/bin/escript $(INITRD_FILES) Makefile 155 | ./scripts/hardcode_files.pl beam.smp $(RTLD) $(OTPDIR) \ 156 | OTPDIR/lib/crypto-*/ebin/crypto.beam OTPDIR/lib/crypto-*/priv/lib/crypto.so OTPDIR/lib/crypto-*/priv/lib/crypto_callback.so \ 157 | OTPDIR/lib/runtime_tools-*/ebin/dbg.beam \ 158 | $(INITRD_FILES) > obj/initrd.tmp 159 | mv obj/initrd.tmp obj/initrd 160 | 161 | 162 | obj/etcpip.app: cfg/etcpip.app 163 | cp $< $@ 164 | 165 | erlang-tcpip/src: 166 | @echo "erlang-tcp submodule isn't checked out; you must run the following command:" 167 | @echo "git submodule init && git submodule update " 168 | @exit 1 169 | 170 | $(TCPIP_SRCS): erlang-tcpip/src 171 | $(TCPIP_OBJS): $(OBJDIR)/%.beam : erlang-tcpip/src/%.erl $(DEPDIR)/%.d | $(DEPDIR) $(OTPDIR)/bin/erlc 172 | $(OTPDIR)/bin/erlc -o $(OBJDIR)/ -MMD -MF $(DEPDIR)/$*.d $< 173 | 174 | $(ERLANG_OBJS): $(OBJDIR)/%.beam : src/%.erl $(DEPDIR)/%.d | $(DEPDIR) $(OTPDIR)/bin/erlc 175 | $(OTPDIR)/bin/erlc -o $(OBJDIR)/ -MMD -MF $(DEPDIR)/$*.d $< 176 | 177 | $(ERLANG_OVERRIDE_OBJS): $(OBJDIR)/%.beam : overrides/%.erl $(DEPDIR)/%.d $(OBJDIR)/hook_module.beam | $(DEPDIR) $(OTPDIR)/bin/erlc 178 | $(OTPDIR)/bin/erlc -I $(OTPDIR)/lib/kernel-*/include -pz $(shell pwd)/$(OBJDIR)/ -o $(OBJDIR)/ -MMD -MF $(DEPDIR)/$*.d '+{parse_transform,hook_module}' $< 179 | 180 | $(KERNEL_OBJS): $(OBJDIR)/%.o: kernel_src/%.c $(DEPDIR)/%.c.d | $(DEPDIR) 181 | $(KERNEL_COMPILER) $(DEPFLAGS) -MF $(DEPDIR)/$*.c.d.T $< -I /usr/src/libexec/rtld-elf/ -I /usr/src/sys/ -I /usr/src/contrib/bearssl/inc/ -o $@ 182 | mv -f $(DEPDIR)/$*.c.d.T $(DEPDIR)/$*.c.d && touch $@ 183 | 184 | $(FBSD_KERNEL_OBJS): $(OBJDIR)/%.o: $(DEPDIR)/%.c.d | $(DEPDIR) 185 | $(KERNEL_COMPILER) -I kernel_src/ $(DEPFLAGS) -MF $(DEPDIR)/$*.c.d.T $(subst __,/,/usr/src/$*.c) -o $@ 186 | mv -f $(DEPDIR)/$*.c.d.T $(DEPDIR)/$*.c.d && touch $@ 187 | 188 | $(BEARSSL_OBJS): $(OBJDIR)/%.o: $(DEPDIR)/%.c.d | $(DEPDIR) 189 | $(KERNEL_COMPILER) -I /usr/src/contrib/bearssl/src/ -I /usr/src/contrib/bearssl/inc/ $(DEPFLAGS) -MF $(DEPDIR)/$*.c.d.T $(subst __,/,/usr/src/contrib/bearssl/src/$*.c) -o $@ 190 | mv -f $(DEPDIR)/$*.c.d.T $(DEPDIR)/$*.c.d && touch $@ 191 | 192 | $(NIF_OBJS): $(OBJDIR)/%.so: c_src/%.c $(OTPDIR)/bin/erl $(DEPDIR)/%.c.d | $(DEPDIR) 193 | $(NIF_COMPILER) $(DEPFLAGS) -MF $(DEPDIR)/$*.c.d.T $< -o $@ 194 | mv -f $(DEPDIR)/$*.c.d.T $(DEPDIR)/$*.c.d && touch $@ 195 | 196 | erlang-tcpip/c_src/checksum.c: erlang-tcpip/src 197 | 198 | obj/checksum.so: erlang-tcpip/c_src/checksum.c 199 | $(NIF_COMPILER) $< -o $@ 200 | 201 | $(DEPDIR): ; @mkdir -p $@ 202 | DEPFILES := $(ERLANG_OBJS:$(OBJDIR)/%.beam=$(DEPDIR)/%.d) $(ERLANG_OVERRIDE_OBJS:$(OBJDIR)/%.beam=$(DEPDIR)/%.d) $(TCPIP_OBJS:$(OBJDIR)/%.beam=$(DEPDIR)/%.d) $(KERNEL_SRCS:kernel_src/%.c=$(DEPDIR)/%.c.d) $(FBSD_KERNEL_SRCS:%.c=$(DEPDIR)/%.c.d) $(BEARSSL_SRCS:%.c=$(DEPDIR)/%.c.d) $(NIF_SRCS:c_src/%.c=$(DEPDIR)/%.c.d) 203 | $(DEPFILES): 204 | include $(wildcard $(DEPFILES)) 205 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/pci.erl: -------------------------------------------------------------------------------- 1 | -module(pci). 2 | 3 | -export([start/0, list/0, attach/2, msix_size/1, enable_msix/3, map/3]). 4 | -include("pci.hrl"). 5 | -behavior(gen_server). 6 | 7 | -export ([init/1, handle_cast/2, handle_call/3]). 8 | 9 | 10 | -record(state, { 11 | devices 12 | }). 13 | 14 | pci_order(#pci_device{common = A}, B) -> pci_order(A, B); 15 | pci_order(#pci_bridge{common = A}, B) -> pci_order(A, B); 16 | pci_order(A, #pci_device{common = B}) -> pci_order(A, B); 17 | pci_order(A, #pci_bridge{common = B}) -> pci_order(A, B); 18 | pci_order(A, B) -> A =< B. 19 | 20 | start() -> 21 | case gen_server:start({local, ?MODULE}, ?MODULE, [], []) of 22 | {ok, Pid} -> Pid; 23 | {error, {already_started, Pid}} -> Pid; 24 | Other -> Other 25 | end. 26 | 27 | init ([]) -> 28 | process_flag(trap_exit, true), 29 | AllDevices = scan_bus(0, 0, 0, []), 30 | Sorted = lists:sort(fun pci_order/2, AllDevices), 31 | {ok, #state{devices = Sorted}}. 32 | 33 | handle_cast(_, State) -> State. 34 | 35 | handle_call({attach, Module, Args}, _From, State) -> 36 | NewDevices = 37 | lists:map(fun (Device) -> attach_device_impl(Device, Module, Args) end, State#state.devices), 38 | {reply, ok, State#state{devices = NewDevices}}; 39 | 40 | handle_call({enable_msix, #pci_common{msix_map = Map, capabilities = Capabilities} = Common, Vector, IRQ}, _From, State) -> 41 | Reply = case get_msix(Capabilities) of 42 | false -> {error, no_msix}; 43 | #pci_msi_x{size = S} when Vector >= S -> {error, invalid_vector}; 44 | #pci_msi_x{offset = Offset} -> 45 | % FIXME: x86 specific here 46 | DestAPIC = 0, 47 | <> = <<16#FEE:12, DestAPIC:8, 0:8, 0:1, 0:1, 0:2>>, 48 | AddressHi = 0, 49 | Data = IRQ bor 16#100, 50 | % FIXME: need MSI settings for other platforms 51 | crazierl:bcopy_to(Map, Vector * 16 + 0, <>), 52 | crazierl:bcopy_to(Map, Vector * 16 + 4, <>), 53 | crazierl:bcopy_to(Map, Vector * 16 + 8, <>), 54 | crazierl:bcopy_to(Map, Vector * 16 + 12,<<0:32/little>>), % not masked 55 | <> = pciConfigReadWord(Common, Offset), 56 | Enabled = Control bor 16#8000, 57 | pciConfigWriteWord(Common, Offset, <>), 58 | % set device to be bus-master, so it can actually write the MSI-X interrupt 59 | <> = pciConfigReadWord(Common, 4), 60 | case Command band 4 of 61 | 0 -> 62 | NewCommand = Command bor 4, 63 | pciConfigWriteWord(Common, 4, <>); 64 | 4 -> ok 65 | end 66 | end, 67 | {reply, Reply, State}; 68 | 69 | handle_call({msix_size, #pci_common{capabilities = Capabilities} = Common}, _From, State) -> 70 | Reply = case get_msix(Capabilities) of 71 | false -> 0; 72 | #pci_msi_x{size = S} -> S 73 | end, 74 | {reply, Reply, State}; 75 | 76 | handle_call(list, _From, State) -> 77 | {reply, State#state.devices, State}. 78 | 79 | attach_device_impl(#pci_device{common = #pci_common {pid = Pid}} = Device, _, _) when is_pid(Pid) -> Device; 80 | attach_device_impl(#pci_bridge{common = #pci_common {pid = Pid}} = Bridge, _, _) when is_pid(Pid) -> Bridge; 81 | attach_device_impl(Device, Module, Args) when is_record(Device, pci_device) -> 82 | case catch Module:check(Device, Args) of 83 | true -> 84 | Pid = spawn_link(Module, attach, [Device, Args]), 85 | Device#pci_device{common = (Device#pci_device.common)#pci_common{driver = Module, pid = Pid}}; 86 | _ -> Device 87 | end; 88 | attach_device_impl(Bridge, Module, Args) when is_record(Bridge, pci_bridge) -> 89 | case catch Module:check(Bridge, Args) of 90 | true -> 91 | Pid = spawn_link(Module, attach, [Bridge, Args]), 92 | Bridge#pci_bridge{common = (Bridge#pci_bridge.common)#pci_common{driver = Module, pid = Pid}}; 93 | _ -> Bridge 94 | end. 95 | 96 | 97 | list() -> 98 | Sorted = gen_server:call(?MODULE, list), 99 | print_pci(Sorted). 100 | 101 | attach(Module, Args) -> 102 | gen_server:call(?MODULE, {attach, Module, Args}, infinity). 103 | 104 | enable_msix(Device, Vector, IRQ) when Vector >= 0 -> 105 | gen_server:call(?MODULE, {enable_msix, Device, Vector, IRQ}, infinity). 106 | 107 | msix_size(Device) -> 108 | gen_server:call(?MODULE, {msix_size, Device}, infinity). 109 | 110 | scan_bus(_, 32, _, Acc) -> Acc; % only 32 devices per Bus 111 | scan_bus(Bus, Device, Function, Acc) -> 112 | {PCIFunction, HasMultiFunction} = probe_device(Bus, Device, Function, <<>>), 113 | Acc1 = case PCIFunction of 114 | none -> Acc; 115 | #pci_bridge{secondary_bus = NextBus} -> 116 | scan_bus(NextBus, 0, 0, [PCIFunction | Acc]); 117 | _ -> [PCIFunction | Acc] 118 | end, 119 | Acc2 = case HasMultiFunction of 120 | 1 when Function == 0-> 121 | lists:foldl(fun (N, AccX) -> 122 | scan_bus(Bus, Device, N, AccX) end, Acc1, lists:seq(1, 7)); 123 | _ -> Acc1 124 | end, 125 | case Function of 126 | 0 -> scan_bus(Bus, Device + 1, 0, Acc2); 127 | _ -> Acc2 128 | end. 129 | 130 | probe_device(_Bus, _Device, _Function, <<16#FFFFFFFF:32/little>>) -> 131 | {none, 0}; 132 | probe_device(Bus, Device, Function, Config) when size(Config) == 256 -> 133 | <> = Config, 143 | %io:format("~2000p~n", [TypeSpecific]), 144 | CapList = case HasCapabilitiesList of 145 | 0 -> []; 146 | 1 -> 147 | <> = binary:part(Config, {16#34, 1}), 148 | probe_capabilities(Config, CapPtr bsl 2, []) 149 | end, 150 | PCICommon = #pci_common{bus = Bus, device = Device, function = Function, vendor = Vendor, device_id = DeviceId, 151 | class = Class, sub_class = SubClass, revision = RevisionId, prog_if = ProgIf, 152 | capabilities = CapList}, 153 | % FIXME: disable io/mem addressing, so probing bars is safe! 154 | % maybe this should be done in the driver, devices involved in console (vga, serial, etc) 155 | % don't get disabled, or get disabled carefully 156 | Return = case HeaderType of 157 | 0 -> <> = TypeSpecific, 164 | Bars = probe_bars(PCICommon, 16#10, [BAR0, BAR1, BAR2, BAR3, BAR4, BAR5], []), 165 | NewCommon = map_msix(PCICommon, Bars), 166 | #pci_device{common = NewCommon, chip_vendor = ChipVendor, chip_device_id = ChipDeviceId, 167 | interrupt_line = InterruptLine, interrupt_pin = InterruptPIN, 168 | bars = Bars 169 | }; 170 | 1 -> <> = TypeSpecific, 172 | Bars = probe_bars(PCICommon, 16#10, [BAR0, BAR1], []), 173 | NewCommon = map_msix(PCICommon, Bars), 174 | #pci_bridge{common = NewCommon, 175 | bars = Bars, 176 | secondary_bus = SecondaryBus} 177 | end, 178 | % FIXME: restore io/mem addressing 179 | 180 | {Return, MultiFunction}; 181 | probe_device(Bus, Device, Function, Bin) -> 182 | NextWord = pciConfigReadWord(Bus, Device, Function, size(Bin)), 183 | probe_device(Bus, Device, Function, <>). 184 | 185 | % I hope the capabilities list doesn't have a loop! 186 | probe_capabilities(_, _, Acc) when length(Acc) > 255 -> 187 | lists:reverse([loop | Acc]); 188 | probe_capabilities(Config, Offset, Acc) when Offset band 3 == 0 -> 189 | <> = binary:part(Config, {Offset, 2}), 190 | Next = NextMSB bsl 2, 191 | Cap = parse_capability(Type, Config, Offset), 192 | case Next of 193 | 0 -> lists:reverse([Cap | Acc]); 194 | _ -> probe_capabilities(Config, Next, [Cap | Acc]) 195 | end. 196 | 197 | parse_capability(9, Config, Offset) -> 198 | <> = binary:part(Config, {Offset + 2, 1}), 199 | Data = binary:part(Config, {Offset + 3, Length - 3}), 200 | {vendor_specific, Data}; 201 | parse_capability(16#11, Config, Offset) -> 202 | <> = binary:part(Config, {Offset + 2, 10}), 203 | <> = <>, 204 | #pci_msi_x{enabled = Enable, mask = FunctionMask, size = Size + 1, 205 | table = {(TableOffset band 7), TableOffset band 16#FFFFFFF8}, 206 | pending = {(PendingBit band 7), PendingBit band 16#FFFFFFF8}, 207 | offset = Offset}; 208 | parse_capability(Type, _Config, Offset) -> 209 | {Type, Offset}. 210 | 211 | probe_bars(_PCI, _Offset, [], Acc) -> 212 | list_to_tuple(lists:reverse(Acc)); 213 | 214 | probe_bars(PCI, Offset, [<<0:32>> | Tail], Acc) -> 215 | probe_bars(PCI, Offset + 4, Tail, [none|Acc]); 216 | 217 | probe_bars(PCI, Offset, [<> = BARLo, <> | Tail], Acc) -> 218 | <> = <>, 219 | pciConfigWriteWord(PCI, Offset, <<16#F:4, Prefetch:1, 2:2, 0:1, 16#FFFFFF:24>>), 220 | pciConfigWriteWord(PCI, Offset + 4, <<16#FFFFFFFF:32>>), 221 | <> = pciConfigReadWord(PCI, Offset), 222 | <> = pciConfigReadWord(PCI, Offset + 4), 223 | <> = <>, 224 | pciConfigWriteWord(PCI, Offset, BARLo), 225 | pciConfigWriteWord(PCI, Offset + 4, BARHi), 226 | probe_bars(PCI, Offset + 8, Tail, [none, #pci_mem_bar{base = Base, size = -Reflect, prefetch = (Prefetch == 1), type = '64-bit'} | Acc]); 227 | 228 | probe_bars(PCI, Offset, [<> = BAR | Tail], Acc) -> 229 | <> = <>, 230 | pciConfigWriteWord(PCI, Offset, <<16#F:4, Prefetch:1, 0:1, Type:1, 0:1, 16#FFFFFF:24>>), 231 | <> = pciConfigReadWord(PCI, Offset), 232 | <> = <>, 233 | pciConfigWriteWord(PCI, Offset, BAR), 234 | TypeAtom = case Type of 235 | 0 -> '32-bit'; 236 | 1 -> '20-bit' 237 | end, 238 | probe_bars(PCI, Offset + 4, Tail, [#pci_mem_bar{base = Base, size = -Reflect, prefetch = (Prefetch == 1), type = TypeAtom} | Acc]); 239 | 240 | probe_bars(PCI, Offset, [<> = BAR | Tail], Acc) -> 241 | <> = <>, 242 | pciConfigWriteWord(PCI, Offset, <<16#FF:6, Reserved:1, 1:1, 16#FFFFFF:24>>), 243 | <> = pciConfigReadWord(PCI, Offset), 244 | Reflect = case <> of 245 | <<0:16, Val:16/signed>> -> Val; 246 | <> -> Val 247 | end, 248 | pciConfigWriteWord(PCI, Offset, BAR), 249 | probe_bars(PCI, Offset + 4, Tail, [#pci_io_bar{base = Base, size = -Reflect}| Acc]). 250 | 251 | 252 | pciFunctionAddress(Bus, Device, Function, Offset) -> 253 | <> = <<1:1, 0:7, Bus:8, Device:5, Function:3, Offset:8>>, 254 | Address. 255 | 256 | pciConfigReadWord(#pci_common{bus = Bus, device = Device, function = Function}, Offset) -> 257 | pciConfigReadWord(Bus, Device, Function, Offset). 258 | pciConfigReadWord(Bus, Device, Function, Offset) when Offset band 3 == 0 -> 259 | Address = pciFunctionAddress(Bus, Device, Function, Offset), 260 | crazierl:outl(16#CF8, Address), 261 | Out = crazierl:inl(16#CFC), 262 | <>. 263 | pciConfigWriteWord(#pci_common{bus = Bus, device = Device, function = Function}, Offset, Value) -> 264 | pciConfigWriteWord(Bus, Device, Function, Offset, Value). 265 | pciConfigWriteWord(Bus, Device, Function, Offset, Value) when is_binary(Value) -> 266 | <> = Value, 267 | pciConfigWriteWord(Bus, Device, Function, Offset, V); 268 | pciConfigWriteWord(Bus, Device, Function, Offset, Value) when Offset band 3 == 0 -> 269 | Address = pciFunctionAddress(Bus, Device, Function, Offset), 270 | crazierl:outl(16#CF8, Address), 271 | crazierl:outl(16#CFC, Value). 272 | 273 | print_pci([]) -> ok; 274 | print_pci([#pci_bridge{common = C} = B | Tail]) -> 275 | io:format("pci ~2B:~2B:~B class=~4.16.0B~4.16.0B card=~4.16.0B~4.16.0B chip=~4.16.0B~4.16.0B rev=~2.16.0B type=bridge~n", 276 | [C#pci_common.bus, C#pci_common.device, C#pci_common.function, 277 | C#pci_common.class, C#pci_common.sub_class, 278 | C#pci_common.device_id, C#pci_common.vendor, 279 | 0, 0, 280 | % B#pci_device.chip_device_id, B#pci_device.chip_vendor, 281 | C#pci_common.revision 282 | ]), 283 | print_driver(C), 284 | print_bars(B#pci_bridge.bars), 285 | print_capabilities(C#pci_common.capabilities), 286 | print_pci(Tail); 287 | print_pci([#pci_device{common = C} = D | Tail]) -> 288 | io:format("pci ~2B:~2B:~B class=~4.16.0B~4.16.0B card=~4.16.0B~4.16.0B chip=~4.16.0B~4.16.0B rev=~2.16.0B type=device irq=~B~n", 289 | [C#pci_common.bus, C#pci_common.device, C#pci_common.function, 290 | C#pci_common.class, C#pci_common.sub_class, 291 | C#pci_common.device_id, C#pci_common.vendor, 292 | D#pci_device.chip_device_id, D#pci_device.chip_vendor, 293 | C#pci_common.revision, D#pci_device.interrupt_line 294 | ]), 295 | print_driver(C), 296 | print_bars(D#pci_device.bars), 297 | print_capabilities(C#pci_common.capabilities), 298 | print_pci(Tail). 299 | 300 | print_driver(#pci_common{driver = undefined}) -> ok; 301 | print_driver(#pci_common{driver = Driver, pid = Pid}) -> 302 | io:format(" driver ~s, pid ~w~n", [Driver, Pid]). 303 | 304 | print_bars(Bars) -> 305 | print_bars(tuple_to_list(Bars), 0). 306 | print_bars([Head|Tail], Index) -> 307 | print_bar(Head, Index), 308 | print_bars(Tail, Index + 1); 309 | print_bars([], _) -> ok. 310 | 311 | print_bar(none, _) -> ok; 312 | print_bar(#pci_io_bar{base = Base, size = Size}, N) -> 313 | io:format(" bar~B = I/O size ~B, base 0x~.16B~n", [N, Size, Base]); 314 | print_bar(#pci_mem_bar{base = Base, size = Size, prefetch = Prefetch, type = Type}, N) -> 315 | io:format(" bar~B = mem size ~B, base 0x~.16B, prefetch ~p, type ~s~n", [N, Size, Base, Prefetch, Type]). 316 | 317 | print_capabilities([H|T]) -> 318 | io:format(" cap ~p~n", [H]), 319 | print_capabilities(T); 320 | print_capabilities([]) -> ok. 321 | 322 | map_msix(#pci_common{capabilities = Capabilities} = Common, Bars) -> 323 | case get_msix(Capabilities) of 324 | #pci_msi_x {size = Size, table = {BarNumber, BarOffset}} -> 325 | Length = Size * 16, 326 | Bar = element(BarNumber + 1, Bars), 327 | {ok, Map} = map(Bar, BarOffset, Length), 328 | Common#pci_common{msix_map = Map}; 329 | false -> Common 330 | end. 331 | 332 | map(#pci_mem_bar{base = Base, size = Size}, Offset, Length) when Size >= Offset + Length -> 333 | crazierl:map(Base + Offset, Length); 334 | 335 | map(#pci_io_bar{base = Base, size = Size}, Offset, Length) when Size >= Offset + Length -> 336 | crazierl:map_port(Base + Offset, Length). 337 | 338 | get_msix([#pci_msi_x{} = X | _]) -> X; 339 | get_msix([_ | T]) -> get_msix(T); 340 | get_msix([]) -> false. 341 | -------------------------------------------------------------------------------- /src/virtio_net.erl: -------------------------------------------------------------------------------- 1 | -module (virtio_net). 2 | 3 | -include ("pci.hrl"). 4 | -export([check/2, attach/2]). 5 | -record (virtio_pci_cap, {bar, offset, length, data}). 6 | -record (virtq, {map, avail_idx, used, used_idx, notify}). 7 | 8 | % TODO split virtio and net bits 9 | 10 | % TODO generate these from structs 11 | -define (DEVICE_FEATURE_SELECT, 0). 12 | -define (DEVICE_FEATURE, 4). 13 | -define (DRIVER_FEATURE_SELECT, 8). 14 | -define (DRIVER_FEATURE, 12). 15 | -define (DEVICE_STATUS, 20). 16 | -define (QUEUE_SELECT, 22). 17 | -define (QUEUE_SIZE, 24). 18 | -define (QUEUE_MSIX_VECTOR, 26). 19 | -define (QUEUE_ENABLE, 28). 20 | -define (QUEUE_NOTIFY_OFF, 30). 21 | -define (QUEUE_DESC, 32). 22 | -define (QUEUE_DRIVER, 40). 23 | -define (QUEUE_DEVICE, 48). 24 | 25 | -define (VIRTQ_LEN, (1 bsl 5)). % must be power of two 26 | -define (VIRTQ_DESC_SIZE, 16). 27 | -define (VIRTQ_DESC_TABLE_LEN, (?VIRTQ_DESC_SIZE * ?VIRTQ_LEN)). 28 | -define (VIRTQ_AVAIL_SIZE, 2). 29 | -define (VIRTQ_AVAIL_RING_LEN, (6 + (?VIRTQ_AVAIL_SIZE *?VIRTQ_LEN))). 30 | -define (VIRTQ_AVAIL_USED_PADDING, 2). % will always need to pad 2 bytes for alignment 31 | -define (VIRTQ_USED_SIZE, 8). 32 | -define (VIRTQ_USED_RING_LEN, (6 + (?VIRTQ_USED_SIZE * ?VIRTQ_LEN))). 33 | -define (VIRTQ_MAX_PACKET, 1514). % 6 byte dest mac, 6 byte source mac, 2 byte protocol, 1500 payload 34 | -define (VIRTQ_BUFFER_SIZE, (?VIRTQ_MAX_PACKET + 12)). % max packet + 12 byte header 35 | -define (VIRTQ_BUFFER_LEN, (?VIRTQ_BUFFER_SIZE * ?VIRTQ_LEN)). 36 | 37 | -define (VIRTQ_AVAIL_START, ?VIRTQ_DESC_TABLE_LEN). 38 | -define (VIRTQ_USED_START, (?VIRTQ_DESC_TABLE_LEN + ?VIRTQ_AVAIL_RING_LEN 39 | + ?VIRTQ_AVAIL_USED_PADDING)). 40 | -define (VIRTQ_BUFFER_START, (?VIRTQ_DESC_TABLE_LEN + ?VIRTQ_AVAIL_RING_LEN 41 | + ?VIRTQ_AVAIL_USED_PADDING + ?VIRTQ_USED_RING_LEN)). 42 | -define (VIRTQ_TOTAL_LEN, (?VIRTQ_BUFFER_START + ?VIRTQ_BUFFER_LEN)). 43 | 44 | 45 | check(#pci_device{common = #pci_common{vendor = 16#1AF4, device_id = 16#1000}}, _Args) -> true; 46 | check(#pci_device{common = #pci_common{vendor = 16#1AF4, device_id = 16#1041}}, _Args) -> true. 47 | 48 | attach(Device, _Args) -> 49 | register(ethernet_sender, self()), 50 | Common = Device#pci_device.common, 51 | Capabilities = parse_capabilities(Common#pci_common.capabilities, #{}), 52 | {ok, CommonMap} = map_structure(common_cfg, Device, Capabilities), 53 | ok = crazierl:bcopy_to(CommonMap, ?DEVICE_STATUS, <<0>>), % reset device 54 | <<0>> = crazierl:bcopy_from(CommonMap, ?DEVICE_STATUS, 1), % confirm 55 | ok = crazierl:bcopy_to(CommonMap, ?DEVICE_STATUS, <<1>>), % OS ACK 56 | <<1>> = crazierl:bcopy_from(CommonMap, ?DEVICE_STATUS, 1), % confirm 57 | ok = crazierl:bcopy_to(CommonMap, ?DEVICE_STATUS, <<3>>), % driver ACK 58 | 59 | ok = crazierl:bcopy_to(CommonMap, ?DEVICE_FEATURE_SELECT, <<0:32/little>>), 60 | <> = crazierl:bcopy_from(CommonMap, ?DEVICE_FEATURE, 4), 61 | ok = crazierl:bcopy_to(CommonMap, ?DEVICE_FEATURE_SELECT, <<1:32/little>>), 62 | <> = crazierl:bcopy_from(CommonMap, ?DEVICE_FEATURE, 4), 63 | DeviceFeatures = parse_features(<>), 64 | true = maps:is_key(virtio_version_1, DeviceFeatures), 65 | true = maps:is_key(mac, DeviceFeatures), 66 | DriverFeatures = make_features([virtio_version_1, mac]), 67 | <> = <>, 68 | ok = crazierl:bcopy_to(CommonMap, ?DRIVER_FEATURE_SELECT, <<0:32/little>>), 69 | ok = crazierl:bcopy_to(CommonMap, ?DRIVER_FEATURE, DriverFeaturesLo), 70 | ok = crazierl:bcopy_to(CommonMap, ?DRIVER_FEATURE_SELECT, <<1:32/little>>), 71 | ok = crazierl:bcopy_to(CommonMap, ?DRIVER_FEATURE, DriverFeaturesHi), 72 | 73 | <<3>> = crazierl:bcopy_from(CommonMap, ?DEVICE_STATUS, 1), % confirm 74 | ok = crazierl:bcopy_to(CommonMap, ?DEVICE_STATUS, <<11>>), % features_OK 75 | {ok, DeviceMap} = map_structure(device_cfg, Device, Capabilities), 76 | MacAddr = crazierl:bcopy_from(DeviceMap, 0, 6), 77 | 78 | {ok, NotifyMap} = map_structure(notify_cfg, Device, Capabilities), 79 | 80 | {RxSocket, TxSocket, ReadVector, WriteVector} = case pci:msix_size(Common) of 81 | N when N >= 2 -> 82 | {Rx, RxInt} = crazierl:open_interrupt(0), 83 | {Tx, TxInt} = crazierl:open_interrupt(0), 84 | io:format("virtio_net: using msix ~B rx ~B tx~n", [RxInt, TxInt]), 85 | ok = pci:enable_msix(Common, 0, RxInt), 86 | ok = pci:enable_msix(Common, 1, TxInt), 87 | {Rx, Tx, 0, 1}; 88 | _ -> % No MSIX, have to use the interrupt pin 89 | Interrupt = Device#pci_device.interrupt_line, 90 | io:format("virtio_net: using PCI int ~B~n", [Interrupt]), 91 | {ok, IrqSocket} = gen_udp:open(0, [ 92 | {inet_backend, inet}, 93 | {ifaddr, {local, io_lib:format("/kern/ioapic/~B/0~n", [Interrupt])}}, 94 | {active, true} 95 | ]), 96 | {ok, IsrMap} = map_structure(isr_cfg, Device, Capabilities), 97 | {IrqSocket, IsrMap, false, false} 98 | end, 99 | 100 | <> = (maps:get(notify_cfg, Capabilities))#virtio_pci_cap.data, 101 | 102 | RxQ = setup_virtq(CommonMap, read, 0, NotifyOffsetMult, NotifyMap, ReadVector), 103 | TxQ = setup_virtq(CommonMap, write, 1, NotifyOffsetMult, NotifyMap, WriteVector), 104 | 105 | <<11>> = crazierl:bcopy_from(CommonMap, ?DEVICE_STATUS, 1), % confirm 106 | ok = crazierl:bcopy_to(CommonMap, ?DEVICE_STATUS, <<15>>), % features_OK 107 | <<15>> = crazierl:bcopy_from(CommonMap, ?DEVICE_STATUS, 1), % confirm 108 | application:set_env([ 109 | {etcpip, [ 110 | {ip, {0,0,0,0}}, 111 | {netmask, {255, 255, 255, 255}}, 112 | {gateway, {0, 0, 0, 0}}, 113 | {mac, MacAddr}, 114 | {iface, "vtnet0"}, 115 | {ip6, [ 116 | {addr, "fe80::216:3eff:fe00:1234"} 117 | ]} 118 | ]} 119 | ], [{persistent, true}]), 120 | etcpip_socket:start(), 121 | 122 | RxQ2 = offer_desc(RxQ, {0, ?VIRTQ_LEN - 1}), 123 | loop(Device, MacAddr, RxSocket, TxSocket, RxQ2, TxQ). 124 | 125 | parse_capabilities([{vendor_specific, <>}|T], Map) -> 126 | Type = virtio_cfg_type(RawType), 127 | case maps:is_key(Type, Map) of 128 | true -> parse_capabilities(T, Map); 129 | false -> parse_capabilities(T, Map#{Type => #virtio_pci_cap{bar = Bar, offset = Offset, length = Length, data = Data}}) 130 | end; 131 | parse_capabilities([_H|T], Map) -> parse_capabilities(T, Map); 132 | parse_capabilities([], Map) -> Map. 133 | 134 | map_structure(Key, #pci_device{bars = Bars}, Caps) -> 135 | Cap = maps:get(Key, Caps), 136 | Bar = element(Cap#virtio_pci_cap.bar + 1, Bars), 137 | pci:map(Bar, Cap#virtio_pci_cap.offset, Cap#virtio_pci_cap.length). 138 | 139 | % tx queue full, don't process send messages 140 | loop(Device, MacAddr, RxSocket, TxSocket, RxQ, TxQ = #virtq{used = []}) -> 141 | {RxQ1, TxQ1} = receive 142 | {udp, RxSocket, _, _, _} when is_reference(TxSocket) -> 143 | crazierl:bcopy_from(TxSocket, 0, 1), 144 | {check_queue(RxQ, read), check_queue(TxQ, write)}; 145 | {udp, RxSocket, _, _, _} -> 146 | {check_queue(RxQ, read), TxQ}; 147 | {udp, TxSocket, _, _, _} -> 148 | {RxQ, check_queue(TxQ, write)}; 149 | {ping, From} -> 150 | From ! pong, 151 | {RxQ, TxQ} 152 | end, 153 | loop(Device, MacAddr, RxSocket, TxSocket, RxQ1, TxQ1); 154 | 155 | loop(Device, MacAddr, RxSocket, TxSocket, RxQ, TxQ) -> 156 | {RxQ1, TxQ1} = receive 157 | {udp, RxSocket, _, _, _} when is_reference(TxSocket) -> 158 | crazierl:bcopy_from(TxSocket, 0, 1), 159 | {check_queue(RxQ, read), check_queue(TxQ, write)}; 160 | {udp, RxSocket, _, _, _} -> 161 | {check_queue(RxQ, read), TxQ}; 162 | {udp, TxSocket, _, _, _} -> 163 | {RxQ, check_queue(TxQ, write)}; 164 | {'$gen_call', From, {send, {DestMac, EtherType}, Payload}} -> 165 | Packet = <>, 166 | gen:reply(From, ok), 167 | {RxQ, add_to_queue(TxQ, Packet)}; 168 | {'$gen_call', From, {send, Data}} -> 169 | Packet = iolist_to_binary(Data), 170 | gen:reply(From, ok), 171 | {RxQ, add_to_queue(TxQ, Packet)}; 172 | {ping, From} -> 173 | From ! pong, 174 | {RxQ, TxQ}; 175 | Other -> 176 | io:format("virtio: got message ~p~n", [Other]), 177 | {RxQ, TxQ} 178 | after 1000 -> 179 | if 180 | is_reference(TxSocket) -> crazierl:bcopy_from(TxSocket, 0, 1); 181 | true -> ok 182 | end, 183 | 184 | % check in case we missed an interrupt 185 | {check_queue(RxQ, read), check_queue(TxQ, write)} 186 | end, 187 | loop(Device, MacAddr, RxSocket, TxSocket, RxQ1, TxQ1). 188 | 189 | virtio_cfg_type(1) -> common_cfg; 190 | virtio_cfg_type(2) -> notify_cfg; 191 | virtio_cfg_type(3) -> isr_cfg; 192 | virtio_cfg_type(4) -> device_cfg; 193 | virtio_cfg_type(5) -> pci_cfg; 194 | virtio_cfg_type(O) -> {reserved, O}. 195 | 196 | parse_features(Binary) -> parse_features(Binary, #{}). 197 | 198 | parse_features(<<1:1, Rest/bitstring>>, Acc) -> 199 | parse_features(Rest, Acc#{feature_flag(bit_size(Rest)) => true}); 200 | parse_features(<<0:1, Rest/bitstring>>, Acc) -> parse_features(Rest, Acc); 201 | parse_features(<<>>, Acc) -> Acc. 202 | 203 | make_features(List) -> make_features(List, 0). 204 | 205 | make_features([H | T], Acc) -> 206 | Bit = feature_flag(H), 207 | make_features(T, Acc bor (1 bsl Bit)); 208 | make_features([], Acc) -> Acc. 209 | 210 | feature_flag(0) -> checksum; 211 | feature_flag(1) -> guest_checksum; 212 | feature_flag(2) -> control_guest_offloads; 213 | feature_flag(3) -> mtu; 214 | feature_flag(5) -> mac; 215 | feature_flag(6) -> legacy_gso; 216 | feature_flag(7) -> guest_tso4; 217 | feature_flag(8) -> guest_tso6; 218 | feature_flag(9) -> guest_ecn; 219 | feature_flag(10) -> guest_ufo; 220 | feature_flag(11) -> host_tso4; 221 | feature_flag(12) -> host_tso6; 222 | feature_flag(13) -> host_ecn; 223 | feature_flag(14) -> host_ufo; 224 | feature_flag(15) -> merge_rxbuf; 225 | feature_flag(16) -> config_status; 226 | feature_flag(17) -> control_vq; 227 | feature_flag(18) -> control_rx; 228 | feature_flag(19) -> control_vlan; 229 | feature_flag(21) -> guest_announce; 230 | feature_flag(22) -> multi_queue; 231 | feature_flag(23) -> ctrl_mac_addr; 232 | feature_flag(28) -> virtio_ring_indirect_desc; 233 | feature_flag(29) -> virtio_ring_event_idx; 234 | feature_flag(32) -> virtio_version_1; 235 | feature_flag(33) -> virtio_access_platform; 236 | feature_flag(34) -> virito_ring_packed; 237 | feature_flag(35) -> virtio_in_order; 238 | feature_flag(36) -> virtio_order_platform; 239 | feature_flag(37) -> virtio_sr_iov; 240 | feature_flag(38) -> virtio_notification_data; 241 | feature_flag(41) -> legacy_guest_rsc4; 242 | feature_flag(52) -> legacy_guest_rsc6; 243 | feature_flag(61) -> rsc_ext; 244 | feature_flag(62) -> standby; 245 | feature_flag(N) when is_integer(N) -> {unknown, N}; 246 | 247 | feature_flag(checksum) -> 0; 248 | feature_flag(guest_checksum) -> 1; 249 | feature_flag(control_guest_offloads) -> 2; 250 | feature_flag(mtu) -> 3; 251 | feature_flag(mac) -> 5; 252 | feature_flag(legacy_gso) -> 6; 253 | feature_flag(guest_tso4) -> 7; 254 | feature_flag(guest_tso6) -> 8; 255 | feature_flag(guest_ecn) -> 9; 256 | feature_flag(guest_ufo) -> 10; 257 | feature_flag(host_tso4) -> 11; 258 | feature_flag(host_tso6) -> 12; 259 | feature_flag(host_ecn) -> 13; 260 | feature_flag(host_ufo) -> 14; 261 | feature_flag(merge_rxbuf) -> 15; 262 | feature_flag(config_status) -> 16; 263 | feature_flag(control_vq) -> 17; 264 | feature_flag(control_rx) -> 18; 265 | feature_flag(control_vlan) -> 19; 266 | feature_flag(guest_announce) -> 21; 267 | feature_flag(multi_queue) -> 22; 268 | feature_flag(ctrl_mac_addr) -> 23; 269 | feature_flag(virtio_ring_indirect_desc) -> 28; 270 | feature_flag(virtio_ring_event_idx) -> 29; 271 | feature_flag(virtio_version_1) -> 32; 272 | feature_flag(virtio_access_platform) -> 33; 273 | feature_flag(virito_ring_packed) -> 34; 274 | feature_flag(virtio_in_order) -> 35; 275 | feature_flag(virtio_order_platform) -> 36; 276 | feature_flag(virtio_sr_iov) -> 37; 277 | feature_flag(virtio_notification_data) -> 38; 278 | feature_flag(legacy_guest_rsc4) -> 41; 279 | feature_flag(legacy_guest_rsc6) -> 52; 280 | feature_flag(rsc_ext) -> 61; 281 | feature_flag(standby) -> 62. 282 | 283 | setup_virtq(CommonMap, Type, QueueNum, NotifyMult, NotifyMap, Vector) -> 284 | {ok, Map} = crazierl:map(0, ?VIRTQ_TOTAL_LEN), 285 | {_Virtual, Physical} = crazierl:map_addr(Map), 286 | Flags = case Type of 287 | read -> 2; 288 | write -> 0 289 | end, 290 | DescriptorTable = virtq_descriptors(Physical + ?VIRTQ_BUFFER_START, Flags, 0, <<>>), 291 | ok = crazierl:bcopy_to(Map, 0, <>), 292 | Used = case Type of 293 | read -> ignore; 294 | write -> [{0, ?VIRTQ_LEN - 1}] 295 | end, 296 | MSIVector = case Vector of 297 | false -> 16#FFFF; 298 | _ -> Vector 299 | end, 300 | ok = crazierl:bcopy_to(CommonMap, ?QUEUE_SELECT, <>), 301 | ok = crazierl:bcopy_to(CommonMap, ?QUEUE_SIZE, <>), 302 | ok = crazierl:bcopy_to(CommonMap, ?QUEUE_MSIX_VECTOR, <>), 303 | ok = crazierl:bcopy_to(CommonMap, ?QUEUE_DESC, <>), 304 | ok = crazierl:bcopy_to(CommonMap, ?QUEUE_DRIVER, <<(Physical + ?VIRTQ_AVAIL_START):64/little>>), 305 | ok = crazierl:bcopy_to(CommonMap, ?QUEUE_DEVICE, <<(Physical + ?VIRTQ_USED_START):64/little>>), 306 | ok = crazierl:bcopy_to(CommonMap, ?QUEUE_ENABLE, <<1:16/little>>), 307 | <> = crazierl:bcopy_from(CommonMap, ?QUEUE_NOTIFY_OFF, 2), 308 | #virtq{map = Map, avail_idx = 0, used = Used, used_idx = 0, notify= {NotifyMap, NotifyOffset * NotifyMult, QueueNum}}. 309 | 310 | virtq_descriptors(_, _, ?VIRTQ_LEN, Acc) -> Acc; 311 | virtq_descriptors(Physical, Flags, Index, Acc) -> 312 | Acc0 = <>, 313 | virtq_descriptors(Physical + ?VIRTQ_BUFFER_SIZE, Flags, Index + 1, Acc0). 314 | 315 | offer_desc(Queue, {N, N}) -> offer_desc(Queue, N); 316 | offer_desc(Queue, {N, T}) -> 317 | Out = offer_desc_impl(Queue, N), 318 | offer_desc(Out, {N + 1, T}); 319 | offer_desc(Queue, N) -> 320 | Out = offer_desc_impl(Queue, N), 321 | write_avail_idx(Out), 322 | Out. 323 | 324 | offer_desc_impl(Queue = #virtq{map = Map, avail_idx = Idx}, N) -> 325 | ok = crazierl:bcopy_to(Map, ?VIRTQ_AVAIL_START + 4 + (2 * (Idx band (?VIRTQ_LEN - 1))), <>), 326 | Queue#virtq{avail_idx = Idx + 1}. 327 | 328 | write_avail_idx(#virtq{map = Map, avail_idx = Idx, notify = {NotifyMap, Offset, QueueNum}}) -> 329 | ok = crazierl:bcopy_to(Map, ?VIRTQ_AVAIL_START + 2, <>), 330 | <> = crazierl:bcopy_from(Map, ?VIRTQ_USED_START, 2), 331 | case Flags band 1 of 332 | 1 -> ok; 333 | 0 -> ok = crazierl:bcopy_to(NotifyMap, Offset, <>) 334 | end. 335 | 336 | add_to_queue(Queue = #virtq{used = []}, Packet) -> 337 | io:format("virtq full, dropping ~w~n", [Packet]), 338 | Queue; 339 | add_to_queue(Queue = #virtq{map = Map, used = Used}, Packet) when size(Packet) =< ?VIRTQ_MAX_PACKET -> 340 | {Descriptor, NewUsed} = get_descriptor(Used), 341 | %io:format("got ~p~n", [{Descriptor, NewUsed}]), 342 | ok = crazierl:bcopy_to(Map, ?VIRTQ_BUFFER_START + Descriptor * ?VIRTQ_BUFFER_SIZE, 343 | <<0, 0, 0:16, 0:16, 0:16, 0:16, 0:16, Packet/binary>>), 344 | ok = crazierl:bcopy_to(Map, Descriptor * ?VIRTQ_DESC_SIZE + 8, 345 | <<(size(Packet) + 12):32/little>>), 346 | offer_desc(Queue#virtq{used = NewUsed}, Descriptor); 347 | add_to_queue(Queue, Packet) -> 348 | io:format("packet too big (~B > ~B), dropping ~w~n", [size(Packet), ?VIRTQ_MAX_PACKET, Packet]), 349 | Queue. 350 | 351 | get_descriptor([{A, B} | T]) when B == A + 1 -> {A, [B | T]}; 352 | get_descriptor([{A, B} | T]) -> {A, [{A + 1, B} | T]}; 353 | get_descriptor([H | T]) -> {H, T}. 354 | add_to_used([B|T], H) when B == H + 1 -> [{H, B} | T]; 355 | add_to_used([B|T], H) when B == H - 1 -> [{B, H} | T]; 356 | add_to_used([{A, B}|T], H) when A == H + 1 -> [{H, B} | T]; 357 | add_to_used([{A, B}|T], H) when B == H - 1 -> [{A, H} | T]; 358 | add_to_used(T, H) -> [H | T]. 359 | 360 | check_queue(Queue = #virtq{map = Map, used_idx = Idx}, Type) -> 361 | case crazierl:bcopy_from(Map, ?VIRTQ_USED_START + 2, 2) of 362 | <> -> Queue; 363 | <> -> 364 | process_packet(Queue, Type, NewIdx) 365 | end. 366 | 367 | process_packet(Queue = #virtq{used_idx = Idx}, _, Idx) -> Queue; 368 | process_packet(Queue = #virtq{map = Map, used_idx = Idx}, read, NewIndex) -> 369 | %io:format("reading used ring item (read) ~B~n", [Idx]), 370 | <> = crazierl:bcopy_from(Map, ?VIRTQ_USED_START + 4 + (8 * (Idx band (?VIRTQ_LEN -1 ))), 8), 371 | %io:format("descriptor ~B, length ~B~n", [Id, Len]), 372 | <<_Flags:8, _GsoType:8, _HdrLen:16/little, _GsoSize:16/little, 373 | _CsumStart:16/little, _CSumOffset:16/little, _NumBuffers:16/little, 374 | Data/binary>> = crazierl:bcopy_from(Map, ?VIRTQ_BUFFER_START + Id * ?VIRTQ_BUFFER_SIZE, Len), 375 | %io:format("Flags ~.16B, GSO ~.16B, HDR Len ~B, GSO size ~B, Csum ~B @ ~B, Buffs ~B~n", 376 | % [Flags, GsoType, HdrLen, GsoSize, CsumStart, CSumOffset, NumBuffers]), 377 | 378 | %<> = Data, 379 | %case EtherType of 380 | % 16#0806 -> arp:process(?MODULE, Payload); 381 | % 16#0800 -> ip:process(?MODULE, Payload); 382 | % _ -> 383 | % io:format("~12.16.0B > ~12.16.0B, EtherType ~4.16.0B, ~w~n", [SourceMac, DestMac, EtherType, Payload]) 384 | %end, 385 | eth:recv(Data), 386 | Offered = offer_desc(Queue, Id), 387 | process_packet(Offered#virtq{used_idx = (Idx + 1) band ((1 bsl 16) - 1)}, read, NewIndex); 388 | 389 | process_packet(Queue = #virtq{map = Map, used = Used, used_idx = Idx}, write, NewIndex) -> 390 | %io:format("reading used ring item (write) ~B~n", [Idx]), 391 | <> = crazierl:bcopy_from(Map, ?VIRTQ_USED_START + 4 + (8 * (Idx band (?VIRTQ_LEN -1 ))), 8), 392 | %io:format("descriptor ~B, length ~B~n", [Id, Len]), 393 | NewUsed = add_to_used(Used, Id), 394 | process_packet(Queue#virtq{used = NewUsed, used_idx = (Idx + 1) band ((1 bsl 16) - 1)}, write, NewIndex). 395 | -------------------------------------------------------------------------------- /kernel_src/start.s: -------------------------------------------------------------------------------- 1 | // We declare the 'kernel_main' label as being external to this file. 2 | // That's because it's the name of the main C function in 'kernel.c'. 3 | .extern kernel_main 4 | 5 | // We declare the 'start' label as global (accessible from outside this file), since the linker will need to know where it is. 6 | // In a bit, we'll actually take a look at the code that defines this label. 7 | .global _start 8 | .global ap_trampoline 9 | .global ap_trampoline2 10 | 11 | .global handle_int_80 12 | .global start_entrypoint 13 | .global gen_int 14 | .global gen_error 15 | .global setup_new_stack 16 | .global setup_new_idle 17 | .global switch_thread_impl 18 | .global switch_ap_thread 19 | .global GDT 20 | .global stack_top 21 | .global LOW_PAGE 22 | .global thr_new_new_thread 23 | .global idle 24 | 25 | // Our bootloader, GRUB, needs to know some basic information about our kernel before it can boot it. 26 | // We give GRUB this information using a standard known as 'Multiboot'. 27 | // To define a valid 'Multiboot header' that will be recognised by GRUB, we need to hard code some 28 | // constants into the executable. The following code calculates those constants. 29 | .set MB_MAGIC, 0x1BADB002 // This is a 'magic' constant that GRUB will use to detect our kernel's location. 30 | .set MB_FLAGS, (1 << 0) | (1 << 1) // This tells GRUB to 1: load modules on page boundaries and 2: provide a memory map (this is useful later in development) 31 | // Finally, we calculate a checksum that includes all the previous values 32 | .set MB_CHECKSUM, (0 - (MB_MAGIC + MB_FLAGS)) 33 | 34 | // We now start the section of the executable that will contain our Multiboot header 35 | .section .multiboot 36 | .align 4 // Make sure the following data is aligned on a multiple of 4 bytes 37 | // Use the previously calculated constants in executable code 38 | .long MB_MAGIC 39 | .long MB_FLAGS 40 | // Use the checksum we calculated earlier 41 | .long MB_CHECKSUM 42 | 43 | // This section contains data initialised to zeroes when the kernel is loaded 44 | .global stack_top 45 | 46 | .section .bss 47 | // Our C code will need a stack to run. 48 | .align 16 49 | stack_bottom: 50 | .skip 4096 // Reserve a stack 51 | stack_top: 52 | 53 | // This section contains our actual assembly code to be run when our kernel loads 54 | .section .text 55 | 56 | // Here is the 'start' label we mentioned before. This is the first code that gets run in our kernel. 57 | _start: 58 | // First thing's first: we want to set up an environment that's ready to run C code. 59 | // C is very relaxed in its requirements: All we need to do is to set up the stack. 60 | // Please note that on x86, the stack grows DOWNWARD. This is why we start at the top. 61 | mov $stack_top, %esp // Set the stack pointer to the top of the stack 62 | 63 | pushl %ebx // push multiboot header 64 | pushl %eax // push multiboot magic 65 | 66 | mov (GDT), %ax 67 | mov %ax, tramp_gdtr 68 | mov $GDT, %eax 69 | mov %eax, 2 + GDT 70 | mov %eax, 2 + tramp_gdtr 71 | // setup the Global Descriptor Table with static values 72 | lgdtl (%eax) 73 | jmpl $0x18, $reload_CS 74 | 75 | reload_CS: 76 | mov $0x28, %ax 77 | mov %ax, %ds 78 | mov %ax, %es 79 | mov %ax, %fs 80 | mov %ax, %ss 81 | 82 | // Now we have a C-worthy (haha!) environment ready to run the rest of our kernel. 83 | // At this point, we can call our main C function. 84 | call kernel_main 85 | 86 | // If, by some mysterious circumstances, the kernel's C code ever returns, all we want to do is to hang the CPU 87 | hang: 88 | cli // Disable CPU interrupts 89 | hlt // Halt the CPU 90 | jmp hang // If that didn't work, loop around and try again. 91 | // handle syscall interrupts 92 | handle_int_80: 93 | push %ebp 94 | mov %esp,%ebp 95 | pushl %ecx 96 | pushl %edx 97 | pushl %esi 98 | pushl %edi 99 | pushl %ds 100 | pushl %es 101 | pushl %fs 102 | 103 | mov %gs, %dx 104 | andl $-16, %edx 105 | mov %dx, %gs 106 | lea 0x4(%ebp), %ecx 107 | 108 | pushl %ecx // interrupt frame 109 | pushl %eax // push syscall number 110 | call handle_syscall // call into C now 111 | handle_int_80_leave: 112 | addl $8, %esp // skip pushed syscall and frame pointer 113 | mov %gs, %dx 114 | orl $0xB, %edx 115 | mov %dx, %gs 116 | popl %fs 117 | popl %es 118 | popl %ds 119 | popl %edi 120 | popl %esi 121 | popl %edx 122 | popl %ecx 123 | popl %ebp 124 | iret 125 | 126 | // handle call from C, adjust the stack a bit, and jump to the 127 | // passed entrypoint 128 | start_entrypoint: 129 | popl %eax // who needs a return address? 130 | popl %ebx // new stack top 131 | popl %ecx // entrypoint 132 | popl %eax // gs segment 133 | 134 | mov %ax, %gs 135 | mov $0x23, %eax 136 | mov %ax, %ds 137 | mov %ax, %es 138 | mov %ax, %fs 139 | 140 | pushl %eax 141 | pushl %ebx 142 | 143 | pushf 144 | popl %eax 145 | orl $0x3200, %eax // enable interrupts, and allow ring 3 to do IO 146 | pushl %eax 147 | 148 | pushl $0x13 149 | pushl %ecx 150 | iret 151 | 152 | .align 8 153 | gen_int: 154 | // repeat for possible vectors 155 | // skip 00-1F 156 | // 2x 157 | .align 8 158 | call int_handler 159 | .align 8 160 | call int_handler 161 | .align 8 162 | call int_handler 163 | .align 8 164 | call int_handler 165 | .align 8 166 | call int_handler 167 | .align 8 168 | call int_handler 169 | .align 8 170 | call int_handler 171 | .align 8 172 | call int_handler 173 | .align 8 174 | call int_handler 175 | .align 8 176 | call int_handler 177 | .align 8 178 | call int_handler 179 | .align 8 180 | call int_handler 181 | .align 8 182 | call int_handler 183 | .align 8 184 | call int_handler 185 | .align 8 186 | call int_handler 187 | .align 8 188 | call int_handler 189 | 190 | // 3x 191 | .align 8 192 | call int_handler 193 | .align 8 194 | call int_handler 195 | .align 8 196 | call int_handler 197 | .align 8 198 | call int_handler 199 | .align 8 200 | call int_handler 201 | .align 8 202 | call int_handler 203 | .align 8 204 | call int_handler 205 | .align 8 206 | call int_handler 207 | .align 8 208 | call int_handler 209 | .align 8 210 | call int_handler 211 | .align 8 212 | call int_handler 213 | .align 8 214 | call int_handler 215 | .align 8 216 | call int_handler 217 | .align 8 218 | call int_handler 219 | .align 8 220 | call int_handler 221 | .align 8 222 | call int_handler 223 | 224 | // 4x 225 | .align 8 226 | call int_handler 227 | .align 8 228 | call int_handler 229 | .align 8 230 | call int_handler 231 | .align 8 232 | call int_handler 233 | .align 8 234 | call int_handler 235 | .align 8 236 | call int_handler 237 | .align 8 238 | call int_handler 239 | .align 8 240 | call int_handler 241 | .align 8 242 | call int_handler 243 | .align 8 244 | call int_handler 245 | .align 8 246 | call int_handler 247 | .align 8 248 | call int_handler 249 | .align 8 250 | call int_handler 251 | .align 8 252 | call int_handler 253 | .align 8 254 | call int_handler 255 | .align 8 256 | call int_handler 257 | 258 | // 5x 259 | .align 8 260 | call int_handler 261 | .align 8 262 | call int_handler 263 | .align 8 264 | call int_handler 265 | .align 8 266 | call int_handler 267 | .align 8 268 | call int_handler 269 | .align 8 270 | call int_handler 271 | .align 8 272 | call int_handler 273 | .align 8 274 | call int_handler 275 | .align 8 276 | call int_handler 277 | .align 8 278 | call int_handler 279 | .align 8 280 | call int_handler 281 | .align 8 282 | call int_handler 283 | .align 8 284 | call int_handler 285 | .align 8 286 | call int_handler 287 | .align 8 288 | call int_handler 289 | .align 8 290 | call int_handler 291 | 292 | // 6x 293 | .align 8 294 | call int_handler 295 | .align 8 296 | call int_handler 297 | .align 8 298 | call int_handler 299 | .align 8 300 | call int_handler 301 | .align 8 302 | call int_handler 303 | .align 8 304 | call int_handler 305 | .align 8 306 | call int_handler 307 | .align 8 308 | call int_handler 309 | .align 8 310 | call int_handler 311 | .align 8 312 | call int_handler 313 | .align 8 314 | call int_handler 315 | .align 8 316 | call int_handler 317 | .align 8 318 | call int_handler 319 | .align 8 320 | call int_handler 321 | .align 8 322 | call int_handler 323 | .align 8 324 | call int_handler 325 | 326 | // 7x 327 | .align 8 328 | call int_handler 329 | .align 8 330 | call int_handler 331 | .align 8 332 | call int_handler 333 | .align 8 334 | call int_handler 335 | .align 8 336 | call int_handler 337 | .align 8 338 | call int_handler 339 | .align 8 340 | call int_handler 341 | .align 8 342 | call int_handler 343 | .align 8 344 | call int_handler 345 | .align 8 346 | call int_handler 347 | .align 8 348 | call int_handler 349 | .align 8 350 | call int_handler 351 | .align 8 352 | call int_handler 353 | .align 8 354 | call int_handler 355 | .align 8 356 | call int_handler 357 | .align 8 358 | call int_handler 359 | 360 | // 8x 361 | .align 8 362 | call int_handler 363 | .align 8 364 | call int_handler 365 | .align 8 366 | call int_handler 367 | .align 8 368 | call int_handler 369 | .align 8 370 | call int_handler 371 | .align 8 372 | call int_handler 373 | .align 8 374 | call int_handler 375 | .align 8 376 | call int_handler 377 | .align 8 378 | call int_handler 379 | .align 8 380 | call int_handler 381 | .align 8 382 | call int_handler 383 | .align 8 384 | call int_handler 385 | .align 8 386 | call int_handler 387 | .align 8 388 | call int_handler 389 | .align 8 390 | call int_handler 391 | .align 8 392 | call int_handler 393 | 394 | // 9x 395 | .align 8 396 | call int_handler 397 | .align 8 398 | call int_handler 399 | .align 8 400 | call int_handler 401 | .align 8 402 | call int_handler 403 | .align 8 404 | call int_handler 405 | .align 8 406 | call int_handler 407 | .align 8 408 | call int_handler 409 | .align 8 410 | call int_handler 411 | .align 8 412 | call int_handler 413 | .align 8 414 | call int_handler 415 | .align 8 416 | call int_handler 417 | .align 8 418 | call int_handler 419 | .align 8 420 | call int_handler 421 | .align 8 422 | call int_handler 423 | .align 8 424 | call int_handler 425 | .align 8 426 | call int_handler 427 | 428 | // Ax 429 | .align 8 430 | call int_handler 431 | .align 8 432 | call int_handler 433 | .align 8 434 | call int_handler 435 | .align 8 436 | call int_handler 437 | .align 8 438 | call int_handler 439 | .align 8 440 | call int_handler 441 | .align 8 442 | call int_handler 443 | .align 8 444 | call int_handler 445 | .align 8 446 | call int_handler 447 | .align 8 448 | call int_handler 449 | .align 8 450 | call int_handler 451 | .align 8 452 | call int_handler 453 | .align 8 454 | call int_handler 455 | .align 8 456 | call int_handler 457 | .align 8 458 | call int_handler 459 | .align 8 460 | call int_handler 461 | 462 | // Bx 463 | .align 8 464 | call int_handler 465 | .align 8 466 | call int_handler 467 | .align 8 468 | call int_handler 469 | .align 8 470 | call int_handler 471 | .align 8 472 | call int_handler 473 | .align 8 474 | call int_handler 475 | .align 8 476 | call int_handler 477 | .align 8 478 | call int_handler 479 | .align 8 480 | call int_handler 481 | .align 8 482 | call int_handler 483 | .align 8 484 | call int_handler 485 | .align 8 486 | call int_handler 487 | .align 8 488 | call int_handler 489 | .align 8 490 | call int_handler 491 | .align 8 492 | call int_handler 493 | .align 8 494 | call int_handler 495 | 496 | // Cx 497 | .align 8 498 | call int_handler 499 | .align 8 500 | call int_handler 501 | .align 8 502 | call int_handler 503 | .align 8 504 | call int_handler 505 | .align 8 506 | call int_handler 507 | .align 8 508 | call int_handler 509 | .align 8 510 | call int_handler 511 | .align 8 512 | call int_handler 513 | .align 8 514 | call int_handler 515 | .align 8 516 | call int_handler 517 | .align 8 518 | call int_handler 519 | .align 8 520 | call int_handler 521 | .align 8 522 | call int_handler 523 | .align 8 524 | call int_handler 525 | .align 8 526 | call int_handler 527 | .align 8 528 | call int_handler 529 | 530 | // Dx 531 | .align 8 532 | call int_handler 533 | .align 8 534 | call int_handler 535 | .align 8 536 | call int_handler 537 | .align 8 538 | call int_handler 539 | .align 8 540 | call int_handler 541 | .align 8 542 | call int_handler 543 | .align 8 544 | call int_handler 545 | .align 8 546 | call int_handler 547 | .align 8 548 | call int_handler 549 | .align 8 550 | call int_handler 551 | .align 8 552 | call int_handler 553 | .align 8 554 | call int_handler 555 | .align 8 556 | call int_handler 557 | .align 8 558 | call int_handler 559 | .align 8 560 | call int_handler 561 | .align 8 562 | call int_handler 563 | 564 | // Ex 565 | .align 8 566 | call int_handler 567 | .align 8 568 | call int_handler 569 | .align 8 570 | call int_handler 571 | .align 8 572 | call int_handler 573 | .align 8 574 | call int_handler 575 | .align 8 576 | call int_handler 577 | .align 8 578 | call int_handler 579 | .align 8 580 | call int_handler 581 | .align 8 582 | call int_handler 583 | .align 8 584 | call int_handler 585 | .align 8 586 | call int_handler 587 | .align 8 588 | call int_handler 589 | .align 8 590 | call int_handler 591 | .align 8 592 | call int_handler 593 | .align 8 594 | call int_handler 595 | .align 8 596 | call int_handler 597 | 598 | // Fx 599 | .align 8 600 | call int_handler 601 | .align 8 602 | call int_handler 603 | .align 8 604 | call int_handler 605 | .align 8 606 | call int_handler 607 | .align 8 608 | call int_handler 609 | .align 8 610 | call int_handler 611 | .align 8 612 | call int_handler 613 | .align 8 614 | call int_handler 615 | .align 8 616 | call int_handler 617 | .align 8 618 | call int_handler 619 | .align 8 620 | call int_handler 621 | .align 8 622 | call int_handler 623 | .align 8 624 | call int_handler 625 | .align 8 626 | call int_handler 627 | .align 8 628 | call int_handler 629 | .align 8 630 | call int_handler 631 | 632 | 633 | int_handler: 634 | xchg %ebp, (%esp) 635 | push %eax 636 | mov %ebp, %eax 637 | mov %esp, %ebp 638 | addl $4, %ebp 639 | push %ecx 640 | push %edx 641 | 642 | sub $(gen_int), %eax 643 | shr $3, %eax 644 | push %eax 645 | 646 | mov %gs, %dx // set GS to kernel segment 647 | andl $-16, %edx 648 | mov %dx, %gs 649 | 650 | call handle_irq 651 | 652 | testl $0x3, 0x18(%esp) // set GS to user segment, if pushed CS is user segment 653 | je gen_int_done 654 | mov %gs, %dx 655 | orl $0xB, %edx 656 | mov %dx, %gs 657 | 658 | gen_int_done: 659 | pop %eax 660 | pop %edx 661 | pop %ecx 662 | pop %eax 663 | pop %ebp 664 | iret 665 | 666 | .align 8 667 | gen_error: 668 | // repeat for possible vectors 669 | // 0x 670 | .align 8 671 | call err_handler 672 | .align 8 673 | call err_handler 674 | .align 8 675 | call err_handler 676 | .align 8 677 | call err_handler 678 | .align 8 679 | call err_handler 680 | .align 8 681 | call err_handler 682 | .align 8 683 | call err_handler 684 | .align 8 685 | call err_handler 686 | .align 8 687 | call err_handler 688 | .align 8 689 | call err_handler 690 | .align 8 691 | call err_handler 692 | .align 8 693 | call err_handler 694 | .align 8 695 | call err_handler 696 | .align 8 697 | call err_handler 698 | .align 8 699 | call err_handler 700 | .align 8 701 | call err_handler 702 | 703 | // 1x 704 | .align 8 705 | call err_handler 706 | .align 8 707 | call err_handler 708 | .align 8 709 | call err_handler 710 | .align 8 711 | call err_handler 712 | .align 8 713 | call err_handler 714 | .align 8 715 | call err_handler 716 | .align 8 717 | call err_handler 718 | .align 8 719 | call err_handler 720 | .align 8 721 | call err_handler 722 | .align 8 723 | call err_handler 724 | .align 8 725 | call err_handler 726 | .align 8 727 | call err_handler 728 | .align 8 729 | call err_handler 730 | .align 8 731 | call err_handler 732 | .align 8 733 | call err_handler 734 | .align 8 735 | call err_handler 736 | 737 | err_handler: 738 | xchg %ebp, 0x4(%esp) // exchange ebp with error code 739 | xchg %eax, (%esp) // exchange eax with gen_err IP 740 | push %ecx 741 | push %edx 742 | mov %ebp, %edx // copy error code to edx 743 | lea 0xC(%esp), %ebp // point ebp to stack after old ebp 744 | 745 | lea 0x4(%ebp), %ecx 746 | pushl %ecx // interrupt frame address 747 | pushl %edx // error_code 748 | sub $(gen_error), %eax 749 | shr $3, %eax 750 | pushl %eax // interrupt vector 751 | 752 | mov %gs, %dx // set GS to kernel segment 753 | andl $-16, %edx 754 | mov %dx, %gs 755 | 756 | call handle_error 757 | 758 | testl $0x3, 0x24(%esp) // set GS to user segment, if pushed CS is user segment 759 | je gen_error_done 760 | mov %gs, %dx 761 | orl $0xB, %edx 762 | mov %dx, %gs 763 | 764 | gen_error_done: 765 | addl $12, %esp 766 | pop %edx 767 | pop %ecx 768 | pop %eax 769 | pop %ebp 770 | iret 771 | 772 | setup_new_stack: 773 | push %ebp 774 | mov %esp, %ebp 775 | mov %esp, %ecx // current thread stack pointer 776 | mov 0xC(%ebp), %edx // current thread stack top 777 | mov 0x8(%ebp), %esp // temporarily use new thread stack top 778 | 779 | mov $5, %eax 780 | copy_iframe: 781 | subl $4, %edx 782 | pushl (%edx) 783 | dec %eax 784 | jne copy_iframe 785 | 786 | push $0 // %ebp 787 | subl $4, %edx 788 | mov %esp, %ebp 789 | 790 | mov $7, %eax 791 | copy_registers: 792 | subl $4, %edx 793 | pushl (%edx) 794 | dec %eax 795 | jne copy_registers 796 | 797 | subl $8, %esp 798 | pushl $handle_int_80_leave 799 | 800 | pushl $thr_new_new_thread 801 | push %ebp 802 | 803 | push %ebx 804 | push %ecx 805 | push %edx 806 | push %esi 807 | push %edi 808 | push $0 // return 0 for child 809 | mov %esp, %eax 810 | mov %ecx, %esp // return to current thread stack 811 | setup_new_stack_done: 812 | pop %ebp 813 | ret 814 | 815 | setup_new_idle: 816 | push %ebp 817 | mov %esp, %ebp 818 | mov %esp, %ecx // current stack pointer 819 | mov 0x8(%ebp), %eax // idle thread stack pointer 820 | mov %eax, %esp 821 | mov %esp, %ebp 822 | push $idle 823 | push %ebp 824 | mov %esp, %ebp 825 | push $setup_new_stack_done 826 | push %ebp 827 | push $0 828 | push $0 829 | push $0 830 | push $0 831 | push $0 832 | push $0 833 | mov %esp, %eax 834 | mov %ecx, %esp // return to current thread 835 | pop %ebp 836 | ret 837 | 838 | switch_thread_impl: 839 | push %ebp 840 | mov %esp, %ebp 841 | 842 | push %ebx 843 | push %ecx 844 | push %edx 845 | push %esi 846 | push %edi 847 | push $0 // return from switch_thread_impl, can be modified 848 | mov 0x8(%ebp), %eax 849 | mov %esp, (%eax) // copy stack to old_thread stack pointer 850 | mov 0xC(%ebp), %esp // copy new_thread stack pointer to stack 851 | switch_thread_done: 852 | pop %eax 853 | pop %edi 854 | pop %esi 855 | pop %edx 856 | pop %ecx 857 | pop %ebx 858 | 859 | pop %ebp 860 | ret 861 | 862 | switch_ap_thread: 863 | mov 0x4(%esp), %esp 864 | jmp switch_thread_done 865 | 866 | .code16 // APs start in real mode 867 | ap_trampoline: 868 | cli 869 | mov $(tramp_gdtr - ap_trampoline), %eax 870 | lgdtl %cs:(%eax) 871 | mov %cr0, %eax 872 | or $1, %al 873 | mov %eax, %cr0 // enable protected mode 874 | jmpl $0x18, $ap_trampoline2 // bounce to the second part of the trampoline 875 | tramp_gdtr: .byte 0,0,0,0,0,0 876 | 877 | .code32 878 | ap_trampoline2: 879 | mov $0x28, %ax 880 | mov %ax, %ds 881 | mov %ax, %es 882 | mov %ax, %fs 883 | mov %ax, %ss 884 | 885 | mov LOW_PAGE, %eax 886 | addl $4096, %eax 887 | mov %eax, %esp 888 | call start_ap 889 | -------------------------------------------------------------------------------- /kernel_src/kern_mmap.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "kern_mmap.h" 3 | 4 | #include "/usr/src/stand/i386/libi386/multiboot.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define ONE_MB 0x00100000 13 | #define PAGE_DIRECTORY_BITS 22 14 | #define PAGE_LEVEL_BITS 12 15 | #define PAGE_LEVEL_MASK 0x3FF 16 | 17 | #define PAGE_PRESENT 0x01 18 | #define PAGE_READWRITE 0x02 19 | #define PAGE_USER 0x04 20 | #define PAGE_SIZE_FLAG 0x80 21 | // 0x200, 0x400, 0x800 flags are available for use by software 22 | #define PAGE_BOTH_UK 0x200 23 | #define PAGE_FORCE 0x400 24 | #define PAGE_BUDDY 0x800 25 | 26 | #define MAX_MEM_SEGMENTS 128 27 | #define MAX_PAGE_ORDER 12 28 | #define PAGE_ORDER_MASK 0x7F 29 | #define FREELIST_COUNT MAX_PAGE_ORDER + 1 30 | 31 | uintptr_t PAGE_DIRECTORY; 32 | uintptr_t PAGE_TABLE_BASE; 33 | uintptr_t LEAST_ADDR; 34 | uintptr_t MAX_ADDR; 35 | uintptr_t LOW_PAGE = 0; 36 | int PAGE_SETUP_FINISHED; 37 | 38 | struct mem_segment { 39 | uintptr_t addr; 40 | size_t len; 41 | size_t start_page; 42 | }; 43 | 44 | struct page { 45 | uint8_t order; 46 | uint8_t free; 47 | uint8_t segment1; 48 | LIST_ENTRY(page) freelist; 49 | }; 50 | 51 | struct page *PAGE_DATA; 52 | struct mem_segment mem_segments[MAX_MEM_SEGMENTS]; 53 | int mem_segment_count; 54 | 55 | LIST_HEAD(freelist, page) freelists[FREELIST_COUNT]; 56 | 57 | uint32_t * pagetable_direntry (uintptr_t logical) 58 | { 59 | return (uint32_t *) (PAGE_DIRECTORY + ((logical >> PAGE_DIRECTORY_BITS) * sizeof(uint32_t))); 60 | } 61 | 62 | uint32_t * pagetable_entry (uint32_t direntry, uintptr_t logical) 63 | { 64 | uintptr_t lookup = PAGE_FLOOR(direntry) | (((logical >> PAGE_LEVEL_BITS) & PAGE_LEVEL_MASK) * sizeof(uint32_t)); 65 | return (uint32_t *) lookup; 66 | } 67 | 68 | uintptr_t kern_mmap_physical(uintptr_t addr) { 69 | return addr; // FIXME: broken if mappings aren't 1:1 70 | } 71 | 72 | void kern_mmap_debug(uintptr_t addr) { 73 | uint32_t *direntry = pagetable_direntry(addr); 74 | uint32_t *table_entry = pagetable_entry(*direntry, addr); 75 | ERROR_PRINTF("logical %08x; dir %08x -> %08x; page %08x -> %08x\r\n", addr, direntry, *direntry, table_entry, *table_entry); 76 | } 77 | 78 | void buddy_unfree(uintptr_t); 79 | 80 | void add_page_mapping (uint16_t flags, uintptr_t logical, uintptr_t physical) { 81 | uint32_t * directory_entry = pagetable_direntry(logical); 82 | if (*directory_entry == 0) { 83 | if (unlikely(!PAGE_SETUP_FINISHED || (flags & PAGE_FORCE))) { 84 | *directory_entry = (PAGE_TABLE_BASE + ((logical >> PAGE_DIRECTORY_BITS) * PAGE_SIZE)) | (flags & (PAGE_SIZE -1)); 85 | } else { 86 | EARLY_ERROR_PRINTF("add_page_mapping (0x%x, %p, %p)\r\n", flags, logical, physical); 87 | halt("Trying to setup a mapping without a directory entry 1\r\n", 0); 88 | } 89 | } 90 | 91 | // update flags 92 | if ((flags & PAGE_USER) && !(*directory_entry & PAGE_USER)) { 93 | //DEBUG_PRINTF("Adding user flag to page_directory for %08x -> %08x\r\n", logical, physical); 94 | *directory_entry |= (PAGE_USER | PAGE_BOTH_UK); 95 | } else if (!(flags & PAGE_USER) && (*directory_entry & PAGE_USER)) { 96 | //DEBUG_PRINTF("Adding both flag to page_directory for %08x -> %08x\r\n", logical, physical); 97 | *directory_entry |= (PAGE_USER | PAGE_BOTH_UK); 98 | } 99 | 100 | if ((flags & PAGE_READWRITE) && !(*directory_entry & PAGE_READWRITE)) { 101 | *directory_entry |= PAGE_READWRITE; 102 | } 103 | if ((flags & PAGE_PRESENT) && !(*directory_entry & PAGE_PRESENT)) { 104 | *directory_entry |= PAGE_PRESENT; 105 | } 106 | 107 | uint32_t *table_entry = pagetable_entry(*directory_entry, logical); 108 | 109 | if (*table_entry == 0) { 110 | if (unlikely(!PAGE_SETUP_FINISHED || (flags & PAGE_FORCE))){ 111 | *table_entry = PAGE_FLOOR(physical) | (flags & (PAGE_SIZE - 1)); 112 | } else { 113 | EARLY_ERROR_PRINTF("add_page_mapping (0x%x, %p, %p)\r\n", flags, logical, physical); 114 | EARLY_ERROR_PRINTF("table_entry %p (%x), directory_entry %p (%x), logical %p\r\n", 115 | table_entry, *table_entry, directory_entry, *directory_entry, logical); 116 | halt("Trying to setup a mapping without a page table entry 2\r\n", 0); 117 | } 118 | } 119 | // update flags 120 | if ((flags & PAGE_USER) && !(*table_entry & PAGE_USER)) { 121 | if (*table_entry & (PAGE_PRESENT)) { 122 | //DEBUG_PRINTF("Adding user flag to page_directory for %08x -> %08x\r\n", logical, physical); 123 | *table_entry |= (PAGE_USER | PAGE_BOTH_UK); 124 | } else { 125 | *table_entry |= PAGE_USER; 126 | } 127 | } else if (!(flags & PAGE_USER) && (*table_entry & PAGE_USER)) { 128 | //DEBUG_PRINTF("Adding both flag to page_directory for %08x -> %08x\r\n", logical, physical); 129 | *table_entry |= PAGE_BOTH_UK; 130 | } 131 | 132 | if ((flags & PAGE_READWRITE) && !(*table_entry & PAGE_READWRITE)) { 133 | *table_entry |= PAGE_READWRITE; 134 | } 135 | if ((flags & PAGE_PRESENT) && !(*table_entry & PAGE_PRESENT)) { 136 | *table_entry |= PAGE_PRESENT; 137 | if (!(flags & PAGE_FORCE) && !(flags & PAGE_BUDDY)) { 138 | buddy_unfree(physical); 139 | } 140 | } 141 | } 142 | 143 | void add_page_mappings (uint16_t mappingflags, uintptr_t addr, size_t len) { 144 | while (len) { 145 | add_page_mapping(mappingflags, addr, addr); 146 | addr += PAGE_SIZE; 147 | len -= PAGE_SIZE; 148 | } 149 | } 150 | 151 | struct page *get_segment_page(struct mem_segment *segment, uintptr_t addr) { 152 | if (segment == NULL) { 153 | return NULL; 154 | } 155 | if (addr >= segment->addr && addr + PAGE_SIZE <= segment->addr + segment->len) { 156 | return &PAGE_DATA[segment->start_page + (addr - segment->addr) / PAGE_SIZE]; 157 | } 158 | return NULL; 159 | } 160 | 161 | uintptr_t get_page_addr(struct mem_segment *segment, struct page *page) { 162 | return (page - &PAGE_DATA[segment->start_page]) * PAGE_SIZE + segment->addr; 163 | } 164 | 165 | struct mem_segment *get_page_segment(struct page *page) { 166 | if (page->segment1 > 0) { 167 | return &mem_segments[page->segment1 - 1]; 168 | } 169 | return NULL; 170 | } 171 | 172 | struct mem_segment *get_addr_mem_segment(uintptr_t addr) { 173 | struct mem_segment *segment; 174 | for (segment = &mem_segments[0]; segment < &mem_segments[mem_segment_count]; segment++) { 175 | if (get_segment_page(segment, addr) != NULL) { 176 | return segment; 177 | } 178 | } 179 | return NULL; 180 | } 181 | 182 | uint8_t buddy_order(size_t len) { 183 | uint8_t order = flsl(len); 184 | if (ffsl(len) == order) { 185 | order -=1; 186 | } 187 | order -= 12; 188 | return order; 189 | } 190 | 191 | struct page *get_area_buddy(struct page *page, uint8_t order) { 192 | struct mem_segment *segment = get_page_segment(page); 193 | uintptr_t page_addr = get_page_addr(segment, page); 194 | uintptr_t buddy_addr = page_addr ^ (PAGE_SIZE << (order & PAGE_ORDER_MASK)); 195 | // page buddies can only live in the same memory segment 196 | return get_segment_page(segment, buddy_addr); 197 | } 198 | 199 | struct page *buddy_alloc(uint8_t order) { 200 | const uint8_t target_order = order; 201 | 202 | if (target_order > MAX_PAGE_ORDER) { 203 | EARLY_ERROR_PRINTF("attempted huge allocation of order %u", (unsigned int) order); 204 | halt("attempted huge allocation", 0); 205 | } 206 | 207 | // find lowest order freelist with an available page 208 | while (LIST_EMPTY(&freelists[order])) { 209 | order += 1; 210 | if (order > MAX_PAGE_ORDER) { 211 | return NULL; 212 | } 213 | } 214 | 215 | // split free areas until we reach the target order 216 | while (order > target_order) { 217 | struct page *free_area_first_page = LIST_FIRST(&freelists[order]); 218 | LIST_REMOVE(free_area_first_page, freelist); 219 | 220 | // demote the order of the first page of the free area and its buddy 221 | order -= 1; 222 | struct page *free_area_buddy_page = get_area_buddy(free_area_first_page, order); 223 | 224 | free_area_first_page->order = order; 225 | free_area_buddy_page->order = order; 226 | free_area_buddy_page->free = 1; 227 | 228 | // add the resultant areas of the split to the freelist 229 | LIST_INSERT_HEAD(&freelists[order], free_area_first_page, freelist); 230 | LIST_INSERT_HEAD(&freelists[order], free_area_buddy_page, freelist); 231 | } 232 | 233 | // we are guaranteed at least two free areas on the target freelist now 234 | struct page *free_area = LIST_FIRST(&freelists[target_order]); 235 | return free_area; 236 | } 237 | 238 | 239 | void buddy_free(struct page *area) { 240 | struct mem_segment *segment = get_page_segment(area); 241 | if (segment == NULL) { 242 | uintptr_t struct_page_addr = (uintptr_t) area; 243 | EARLY_ERROR_PRINTF("invalid free of %08x outside memory segment\r\n", struct_page_addr); 244 | halt("invalid free outside memory segment\r\n", 0); 245 | } 246 | uint8_t order = 0; 247 | struct page *p = area; 248 | while (p->free != 1 && p->order != order) { 249 | EARLY_ERROR_PRINTF("buddy free loop %p order %d\r\n", get_page_addr(segment, p), p->order); 250 | order = p->order; 251 | p = get_area_buddy(p, p->order - 1); // note p->order can't be zero 252 | } 253 | if (p->free) { 254 | uintptr_t addr = get_page_addr(segment, area); 255 | uintptr_t addr2 = get_page_addr(segment, p); 256 | EARLY_ERROR_PRINTF("double free of %p (%p order %d)\r\n", addr, addr2, p->order); 257 | halt("double free\r\n", 0); 258 | } else if (p->order > 0) { 259 | uintptr_t addr = get_page_addr(segment, area); 260 | uintptr_t addr2 = get_page_addr(segment, p); 261 | 262 | EARLY_ERROR_PRINTF("buddy free of %p (%p order %d)\r\n", addr, addr2, p->order); 263 | halt("fixme\r\n", 0); 264 | } 265 | 266 | // mark the area as free and put on freelist 267 | area->free = 1; 268 | LIST_INSERT_HEAD(&freelists[area->order], area, freelist); 269 | 270 | // merge with other areas as much as possible 271 | while (area->order < MAX_PAGE_ORDER) { 272 | struct page *buddy = get_area_buddy(area, area->order); 273 | // only merge the buddy is free and of the same order 274 | if (buddy == NULL || buddy->order != area->order || !buddy->free) { 275 | break; 276 | } 277 | 278 | // remove the two merging areas from their freelist 279 | LIST_REMOVE(area, freelist); 280 | LIST_REMOVE(buddy, freelist); 281 | 282 | // promote the order of both merging areas 283 | area->order += 1; 284 | buddy->order += 1; 285 | 286 | // mark the second area as in-use as it's being merged with the first 287 | struct page *first_area = MIN(area, buddy); 288 | struct page *second_area = MAX(area, buddy); 289 | second_area->free = 0; 290 | area = first_area; 291 | 292 | // add the new merged area to the right freelist 293 | LIST_INSERT_HEAD(&freelists[area->order], first_area, freelist); 294 | } 295 | } 296 | 297 | void buddy_free_addr(uintptr_t addr) { 298 | struct mem_segment *s = get_addr_mem_segment(addr); 299 | if (s == NULL) { 300 | halt("no segment\r\n", 0); 301 | } 302 | struct page *p = get_segment_page(s, addr); 303 | if (p == NULL) { 304 | halt("no page\r\n", 0); 305 | } 306 | //ERROR_PRINTF("buddy free %p\r\n", addr); 307 | buddy_free(p); 308 | } 309 | 310 | void buddy_unfree(uintptr_t addr) { 311 | struct mem_segment *s = get_addr_mem_segment(addr); 312 | if (s == NULL) { 313 | halt("no segment\r\n", 0); 314 | } 315 | struct page *p = get_segment_page(s, addr); 316 | if (p == NULL) { 317 | halt("no page\r\n", 0); 318 | } 319 | uint8_t order = 0; 320 | while (p->free != 1 && p->order != order) { 321 | order = p->order; 322 | p = get_area_buddy(p, p->order - 1); // note p->order can't be zero 323 | } 324 | if (p->free == 1) { 325 | order = p->order; 326 | LIST_REMOVE(p, freelist); 327 | while (order) { 328 | order -= 1; 329 | struct page *b = get_area_buddy(p, order); 330 | p->order = order; 331 | b->order = order; 332 | b->free = 1; 333 | //EARLY_ERROR_PRINTF("free %x (%x) order %d\r\n", addr, get_page_addr(s, b), b->order); 334 | 335 | if (addr >= get_page_addr(s, b)) { 336 | //EARLY_ERROR_PRINTF("unfree split order %d, %p\r\n", order, get_page_addr(get_page_segment(p), p)); 337 | LIST_INSERT_HEAD(&freelists[order], p, freelist); 338 | p = b; 339 | } else { 340 | //EARLY_ERROR_PRINTF("unfree split order %d, %p\r\n", order, get_page_addr(get_page_segment(b), b)); 341 | LIST_INSERT_HEAD(&freelists[order], b, freelist); 342 | } 343 | } 344 | //EARLY_ERROR_PRINTF("unfree %p (%p)\r\n", addr, get_page_addr(s, p)); 345 | p->free = 0; 346 | } else { 347 | EARLY_ERROR_PRINTF("unfree %p %p (%p)\r\n", addr, p, get_page_addr(s, p)); 348 | halt("not free in buddy_unfree\r\n", 0); 349 | } 350 | } 351 | 352 | void add_mem_segment(size_t segment) { 353 | // initialize the beginning of the segment for struct pages 354 | struct mem_segment * s = &mem_segments[segment]; 355 | if (segment > 0) { 356 | s->start_page = mem_segments[segment - 1].start_page + (mem_segments[segment - 1].len / PAGE_SIZE); 357 | } 358 | 359 | uintptr_t page_count = s->len / PAGE_SIZE; 360 | 361 | struct page *page, *start; 362 | start = &PAGE_DATA[s->start_page]; 363 | for (page = start; page < start + page_count; page++) { 364 | page->segment1 = segment + 1; 365 | buddy_free(page); 366 | } 367 | } 368 | 369 | DECLARE_LOCK(mmap_lock); 370 | 371 | void kern_munmap (uint16_t mode, uintptr_t addr, size_t size) 372 | { 373 | addr = PAGE_FLOOR(addr); 374 | size = PAGE_CEIL(size); 375 | DEBUG_PRINTF("unmapping %08x bytes at %08x (mode %x)\r\n", size, addr, mode); 376 | LOCK(&mmap_lock); 377 | for (; size; addr += PAGE_SIZE, size -= PAGE_SIZE) { 378 | int access_changed = 0; 379 | uint32_t * table_entry = pagetable_entry(*(pagetable_direntry(addr)), addr); 380 | if ((*table_entry & PAGE_PRESENT) == 0) { 381 | continue; 382 | } 383 | if ((mode & PROT_KERNEL) == 0) { 384 | switch (*table_entry & (PAGE_USER | PAGE_BOTH_UK)) { 385 | case PAGE_USER: 386 | access_changed = 1; 387 | *table_entry &= ~(PAGE_PRESENT | PAGE_READWRITE | PAGE_USER); 388 | break; 389 | case PAGE_USER | PAGE_BOTH_UK: 390 | access_changed = 1; 391 | *table_entry &= ~(PAGE_USER | PAGE_BOTH_UK); 392 | } 393 | } else { 394 | switch (*table_entry & (PAGE_USER | PAGE_BOTH_UK)) { 395 | case PAGE_USER | PAGE_BOTH_UK: 396 | access_changed = 1; 397 | *table_entry &= ~PAGE_BOTH_UK; 398 | break; 399 | case 0: 400 | access_changed = 1; 401 | *table_entry &= ~(PAGE_PRESENT | PAGE_READWRITE); 402 | } 403 | } 404 | if (access_changed) { 405 | asm volatile("invlpg (%0)" ::"r" (addr) : "memory"); 406 | } 407 | if (access_changed && !(*table_entry & PAGE_PRESENT)) { 408 | if (*table_entry & PAGE_FORCE) { 409 | *table_entry = 0; 410 | } else { 411 | buddy_free_addr(addr); 412 | if (addr < LEAST_ADDR) { 413 | LEAST_ADDR = addr; 414 | } 415 | if ((addr + PAGE_SIZE) > MAX_ADDR) { 416 | MAX_ADDR = addr + PAGE_SIZE; 417 | } 418 | } 419 | } 420 | } 421 | UNLOCK(&mmap_lock); 422 | } 423 | 424 | int comp_addr (const void* a, const void* b) { 425 | return ((struct mem_segment*)a)->addr - ((struct mem_segment*)b)->addr; 426 | } 427 | 428 | void kern_mmap_init (unsigned int length, unsigned int addr, uintptr_t max_used_addr) 429 | { 430 | uintptr_t mmap; 431 | size_t pages = 0; 432 | //EARLY_ERROR_PRINTF ("memory map at 0x%08x, length %d\r\n", addr, length); 433 | for (mmap = addr; mmap < (addr + length); mmap += ((multiboot_memory_map_t *)mmap)->size + sizeof (((multiboot_memory_map_t *)mmap)->size)) { 434 | multiboot_memory_map_t * mmm = (multiboot_memory_map_t *) mmap; 435 | if (mmm->type == 1 && (mmm->addr + mmm->len - 1) <= SIZE_MAX) { 436 | uintptr_t addr = mmm->addr; 437 | uintptr_t len = mmm->len; 438 | //EARLY_ERROR_PRINTF("Available memory at 0x%08x-0x%08x; 0x%08x (%u) bytes\r\n", addr, addr + len - 1, len, len); 439 | addr = PAGE_FLOOR(addr); 440 | if (addr != mmm->addr) { 441 | addr += PAGE_SIZE; 442 | len -= (addr - mmm->addr); 443 | } 444 | 445 | len = PAGE_FLOOR(len); 446 | 447 | if (addr == 0) { 448 | len -= PAGE_SIZE; 449 | addr = PAGE_SIZE; 450 | } 451 | 452 | if (addr < ONE_MB && LOW_PAGE == 0 && len >= PAGE_SIZE) { 453 | LOW_PAGE = addr; 454 | addr += PAGE_SIZE; 455 | len -= PAGE_SIZE; 456 | } 457 | if (len == 0) { 458 | continue; 459 | } 460 | 461 | if (mem_segment_count < MAX_MEM_SEGMENTS) { 462 | struct mem_segment *segment = &mem_segments[mem_segment_count]; 463 | segment->addr = addr; 464 | segment->len = len; 465 | 466 | mem_segment_count += 1; 467 | pages += len / PAGE_SIZE; 468 | } else { 469 | EARLY_ERROR_PRINTF("error adding memory segment to allocator: too many segments\r\n"); 470 | } 471 | } else { 472 | uintptr_t addr = mmm->addr; 473 | uintptr_t len = mmm->len; 474 | //EARLY_ERROR_PRINTF("unavailable memory (%d) at 0x%08x; 0x%08x (%u) bytes\r\n", mmm->type, addr, len, len); 475 | } 476 | } 477 | 478 | // sort, just in case! 479 | qsort(mem_segments, mem_segment_count, sizeof(struct mem_segment), comp_addr); 480 | for (int i = 0; i < mem_segment_count; ++i) { 481 | if (i + 1 < mem_segment_count && mem_segments[i].addr + mem_segments[i].len > mem_segments[i + 1].addr) { 482 | EARLY_ERROR_PRINTF("mem segment %d %p %d bytes overlaps mem segment %p %d bytes\r\n", 483 | i, mem_segments[i].addr, mem_segments[i].len, 484 | mem_segments[i + 1].addr, mem_segments[i + 1].len); 485 | halt("bad memory map\r\n", 0); 486 | } 487 | } 488 | 489 | for (int i = mem_segment_count - 1; i >= 0; --i) { 490 | struct mem_segment *segment = &mem_segments[i]; 491 | if (segment->len >= (PAGE_SIZE * PAGE_SIZE) + PAGE_SIZE) { 492 | // keep things simple, make a complete page table, with 4K sized pages 493 | PAGE_DIRECTORY = segment->addr + (segment->len - PAGE_SIZE); 494 | PAGE_TABLE_BASE = PAGE_DIRECTORY - (PAGE_SIZE * PAGE_SIZE); 495 | uintptr_t base = PAGE_TABLE_BASE; 496 | if (base > max_used_addr) { 497 | explicit_bzero((void *)base, PAGE_SIZE * PAGE_SIZE + PAGE_SIZE); // zero the whole structure 498 | add_page_mapping(PAGE_PRESENT | PAGE_READWRITE, PAGE_DIRECTORY, PAGE_DIRECTORY); 499 | add_page_mappings(PAGE_PRESENT | PAGE_READWRITE, base, PAGE_SIZE * PAGE_SIZE); 500 | segment->len -= (PAGE_SIZE * PAGE_SIZE) + PAGE_SIZE; 501 | pages -= (PAGE_SIZE + 1); // page directory is one page, page table is PAGE_SIZE pages 502 | break; 503 | } else { 504 | PAGE_DIRECTORY = PAGE_TABLE_BASE = 0; 505 | } 506 | } 507 | } 508 | if (PAGE_DIRECTORY == 0) { 509 | halt("couldn't find room for page table\r\n", 1); 510 | } 511 | 512 | size_t page_data_byte_size = PAGE_CEIL(sizeof(struct page) * pages); 513 | for (int i = mem_segment_count - 1; i >= 0; --i) { 514 | struct mem_segment *segment = &mem_segments[i]; 515 | if (segment->len >= page_data_byte_size) { 516 | PAGE_DATA = (struct page *)(segment->addr + segment->len - page_data_byte_size); 517 | if ((uintptr_t)PAGE_DATA > max_used_addr) { 518 | add_page_mappings(PAGE_PRESENT | PAGE_READWRITE, (uintptr_t)PAGE_DATA, page_data_byte_size); 519 | explicit_bzero(PAGE_DATA, page_data_byte_size); 520 | segment->len -= page_data_byte_size; 521 | break; 522 | } else { 523 | PAGE_DATA = NULL; 524 | } 525 | } 526 | } 527 | if (PAGE_DATA == NULL) { 528 | halt("couldn't find room for page data\r\n", 0); 529 | } 530 | 531 | for (int i = 0; i < mem_segment_count; ++i) { 532 | struct mem_segment *segment = &mem_segments[i]; 533 | if (!LEAST_ADDR || segment->addr < LEAST_ADDR) { 534 | LEAST_ADDR = segment->addr; 535 | } 536 | if (segment->addr + segment->len > MAX_ADDR) { 537 | MAX_ADDR = segment->addr + segment->len; 538 | } 539 | //EARLY_ERROR_PRINTF("add mem segment %x %x (%u)\r\n", segment->addr, segment->len, segment->len); 540 | add_page_mappings(0, segment->addr, segment->len); 541 | add_mem_segment(i); 542 | } 543 | 544 | //EARLY_ERROR_PRINTF("finished setting up pages\r\n"); 545 | PAGE_SETUP_FINISHED = 1; 546 | } 547 | 548 | int mem_available (uintptr_t addr, size_t len) 549 | { 550 | uintptr_t a = addr; 551 | size_t l = len; 552 | while (len) { 553 | uint32_t * page_table_entry = pagetable_entry(*(pagetable_direntry(addr)), addr); 554 | if (*page_table_entry == 0 || (*page_table_entry & PAGE_PRESENT)) { 555 | return 0; 556 | } 557 | addr += PAGE_SIZE; 558 | len -= PAGE_SIZE; 559 | } 560 | // REDO check with buddy allocator 561 | addr = a; 562 | len = l; 563 | while (len > 0) { 564 | struct mem_segment *s = get_addr_mem_segment(addr); 565 | if (s == NULL) { 566 | halt("no segment\r\n", 0); 567 | return 0; 568 | } 569 | struct page *p = get_segment_page(s, addr); 570 | if (p == NULL) { 571 | halt("no page\r\n", 0); 572 | return 0; 573 | } 574 | uint8_t order = 0; 575 | while (p->free != 1 && p->order != order) { 576 | order = p->order; 577 | p = get_area_buddy(p, p->order - 1); // note p->order can't be zero 578 | } 579 | if (p->free == 1) { 580 | uintptr_t endaddr = get_page_addr(s, p) + (PAGE_SIZE << (p->order)); 581 | if (endaddr > addr) { 582 | len = 0; 583 | } else { 584 | len -= (endaddr - addr); 585 | } 586 | addr = endaddr; 587 | } else { 588 | ERROR_PRINTF("addr %x, len %x (%d)\r\n", addr, len, len); 589 | ERROR_PRINTF("p addr %x, free %d, order %d\r\n", get_page_addr(s, p), p->free, p->order); 590 | halt("not free in mem_available\r\n", 0); 591 | return 0; 592 | } 593 | } 594 | return 1; 595 | } 596 | 597 | 598 | int kern_mmap (uintptr_t *ret, void * addr, size_t len, int prot, int flags) 599 | { 600 | unsigned int alignsize = PAGE_SIZE; 601 | if (flags & MAP_ALIGNMENT_MASK) { 602 | alignsize = 1 << ((flags & MAP_ALIGNMENT_MASK) >> MAP_ALIGNMENT_SHIFT); 603 | if (alignsize < PAGE_SIZE) { 604 | alignsize = PAGE_SIZE; 605 | } 606 | } 607 | uint16_t mappingflags = 0; 608 | if (prot & PROT_READ) { 609 | mappingflags |= PAGE_PRESENT; 610 | } 611 | if (prot & PROT_WRITE) { 612 | mappingflags |= PAGE_READWRITE; 613 | if (! (prot & PROT_READ)) { 614 | halt("weird protection, write but not read\r\n", 0); 615 | } 616 | } 617 | if (!(prot & PROT_KERNEL)) { 618 | mappingflags |= PAGE_USER; 619 | } 620 | if (prot & PROT_FORCE) { 621 | mappingflags |= PAGE_FORCE; 622 | } 623 | 624 | if (! ((prot & PROT_KERNEL) && (flags & MAP_EARLY))) { 625 | LOCK(&mmap_lock); 626 | } 627 | len = PAGE_CEIL(len); 628 | addr = (void*)PAGE_FLOOR(addr); 629 | if (addr != NULL && !((prot & PROT_FORCE) || (flags & MAP_FIXED)) && !mem_available((uintptr_t)addr, len)) { 630 | DEBUG_PRINTF("range %08x, %08x not available; looking for anything!\r\n", addr, len); 631 | //halt("couldn't satisfy range\r\n", 0); 632 | addr = NULL; 633 | } else if (addr != NULL && ((uintptr_t)addr & (alignsize - 1))) { 634 | ERROR_PRINTF("range %08x, %08x doesn't meet alignment size %08x; looking for anything!\r\n", addr, len, alignsize); 635 | addr = NULL; 636 | } 637 | 638 | DEBUG_PRINTF("looking for 0x%x bytes at %08x\r\n", len, addr); 639 | 640 | if (!addr) { 641 | int found = 0; 642 | if (flags & MAP_STACK) { 643 | *ret = (MAX_ADDR - len) & ~(alignsize - 1); 644 | while (*ret > LEAST_ADDR) { 645 | if (mem_available(*ret, len)) { 646 | if (*ret == MAX_ADDR - len) { 647 | MAX_ADDR -= len; 648 | } 649 | found = 1; 650 | break; 651 | } else if (*ret == MAX_ADDR - len && !mem_available(MAX_ADDR - PAGE_SIZE, PAGE_SIZE)) { 652 | MAX_ADDR -= PAGE_SIZE; 653 | } 654 | *ret -= alignsize; 655 | } 656 | } else { 657 | uint8_t order = buddy_order(max(len, alignsize)); 658 | struct page *page = buddy_alloc(order); 659 | if (page != NULL) { 660 | struct mem_segment *segment = get_page_segment(page); 661 | *ret = get_page_addr(segment, page); 662 | found = 1; 663 | } 664 | } 665 | if (!found) { 666 | *ret = ENOMEM; 667 | ERROR_PRINTF("unable to allocate\r\n"); 668 | ERROR_PRINTF("kern_mmap (%08x (%08x), %08x, %08x, %x, %x)\r\n", *ret, ret, addr, len, prot, flags); 669 | if (! ((prot & PROT_KERNEL) && (flags & MAP_EARLY))) { 670 | UNLOCK(&mmap_lock); 671 | } 672 | return 0; 673 | } 674 | } else { 675 | *ret = (uintptr_t) addr; 676 | } 677 | if ((*ret & (alignsize - 1)) != 0) { 678 | halt("bad alignment\r\n", 0); 679 | } 680 | 681 | DEBUG_PRINTF("kern_mmap (%08x-%08x, %08x, %08x, %x, %x)\r\n", *ret, (*ret + len -1), addr, len, prot, flags); 682 | add_page_mappings(mappingflags, *ret, len); 683 | if (! ((prot & PROT_KERNEL) && (flags & MAP_EARLY))) { 684 | UNLOCK(&mmap_lock); 685 | } 686 | return 1; 687 | } 688 | 689 | void kern_mmap_enable_paging() { 690 | asm volatile ( "mov %0, %%cr3" :: "a" (PAGE_DIRECTORY)); 691 | uint32_t a; 692 | asm volatile("mov %%cr0, %0" : "=a" (a)); 693 | a|= 0x80000001; 694 | a&= 0x9FFFFFFF; 695 | DEBUG_PRINTF("setting cr0 to %08x\r\n", a); 696 | asm volatile("mov %0, %%cr0" :: "a"(a)); 697 | } 698 | 699 | --------------------------------------------------------------------------------