Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
BIN
.#README
Normal file
BIN
.#README
Normal file
Binary file not shown.
18
.gitattributes
vendored
Normal file
18
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# Keep LF in the working tree, on Windows included.
|
||||
#
|
||||
# Git's autocrlf would otherwise rewrite line endings on checkout, and this
|
||||
# tree does not tolerate that: the build runs under Git Bash, and a Makefile
|
||||
# recipe or a .S file with CRLF fails in ways that point nowhere near the
|
||||
# actual cause.
|
||||
* text eol=lf
|
||||
|
||||
# Binary, never touched.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.ttf binary
|
||||
*.img binary
|
||||
*.iso binary
|
||||
*.tar.gz binary
|
||||
*.tgz binary
|
||||
*.zip binary
|
||||
*.wad binary
|
||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Build output. Everything here is reproducible from `make`.
|
||||
build/
|
||||
schunix.img
|
||||
schunix.iso
|
||||
*.o
|
||||
*.d
|
||||
*.asm
|
||||
*.sym
|
||||
|
||||
# The staged root filesystem. Assembled by the build from cmd/, lib/ and
|
||||
# ports/ -- tracking it would mean committing a second copy of every binary
|
||||
# on every build.
|
||||
sysroot/
|
||||
|
||||
# Upstream tarballs, fetched not authored: musl, toybox, mksh, dash, the DOOM
|
||||
# WADs and usb.ids. 61 MB of other people's release artefacts, all of which
|
||||
# the build downloads again if they are missing. The patches that matter are
|
||||
# under ports/*/patches/ and those *are* tracked.
|
||||
dist/
|
||||
|
||||
# Test and screenshot output.
|
||||
*.log
|
||||
*.ppm
|
||||
*.png.tmp
|
||||
BIN
Assets/Schunix Full Logo.png
Normal file
BIN
Assets/Schunix Full Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
159
Assets/Schunix Full Logo.svg
Normal file
159
Assets/Schunix Full Logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
BIN
Assets/Schunix Small Logo.png
Normal file
BIN
Assets/Schunix Small Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
120
Assets/Schunix Small Logo.svg
Normal file
120
Assets/Schunix Small Logo.svg
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="1920"
|
||||
height="1080"
|
||||
viewBox="0 0 1920 1080"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.4.4 (dcaf3e7, 2026-05-05)"
|
||||
sodipodi:docname="Schunix Small Logo.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#000000"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
inkscape:zoom="0.54276993"
|
||||
inkscape:cx="982.92107"
|
||||
inkscape:cy="614.44082"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1009"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1">
|
||||
<linearGradient
|
||||
id="linearGradient5"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
style="stop-color:#c9c9c9;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop6" />
|
||||
<stop
|
||||
style="stop-color:#636363;stop-opacity:1;"
|
||||
offset="0.5"
|
||||
id="stop8" />
|
||||
<stop
|
||||
style="stop-color:#a2a2a2;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop7" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
style="stop-color:#444444;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1" />
|
||||
<stop
|
||||
style="stop-color:#b2b2b2;stop-opacity:1;"
|
||||
offset="0.25"
|
||||
id="stop5" />
|
||||
<stop
|
||||
style="stop-color:#909090;stop-opacity:1;"
|
||||
offset="0.5"
|
||||
id="stop3" />
|
||||
<stop
|
||||
style="stop-color:#4c4c4c;stop-opacity:1;"
|
||||
offset="0.75"
|
||||
id="stop4" />
|
||||
<stop
|
||||
style="stop-color:#808080;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop2" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1"
|
||||
id="linearGradient2"
|
||||
x1="480.19177"
|
||||
y1="439.3587"
|
||||
x2="819.08807"
|
||||
y2="723.7265"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5"
|
||||
id="linearGradient7"
|
||||
x1="354.53467"
|
||||
y1="585.88361"
|
||||
x2="968.30945"
|
||||
y2="585.88361"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="logo"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="matrix(0.9235878,0,0,0.9235878,-243.88631,0.4743235)">
|
||||
<g
|
||||
id="g8"
|
||||
inkscape:label="SmallLogo"
|
||||
transform="matrix(1.8840278,0,0,1.8840278,58.429052,-517.93737)">
|
||||
<path
|
||||
id="path1"
|
||||
style="fill:none;stroke:url(#linearGradient7);stroke-width:35.2052;paint-order:markers fill stroke"
|
||||
d="m 950.70685,585.88361 c 0,159.76758 -129.51721,289.28479 -289.28479,289.28479 -159.76758,0 -289.28479,-129.51721 -289.28479,-289.28479 0,-159.76758 129.51721,-289.28479 289.28479,-289.28479 109.46795,0 204.73463,60.80288 253.87001,150.47863 22.5773,41.20526 35.41478,88.50653 35.41478,138.80616 z"
|
||||
sodipodi:nodetypes="ssssss"
|
||||
inkscape:label="2" />
|
||||
<path
|
||||
d="m 614.91693,442.64998 9.0023,9.00229 v 12.86042 l -9.0023,9.00229 h -6.4302 -6.43021 -34.72313 -41.15333 -34.72313 l -25.72083,25.72083 25.72083,25.72084 h 34.72313 34.72312 6.43021 6.43021 l 4.50115,4.50114 4.50114,4.50115 32.15104,32.15104 4.50115,4.50115 4.50115,4.50114 v 6.43021 6.43021 34.72312 34.72313 6.43021 6.43021 l -4.50115,4.50114 -4.50115,4.50115 -32.15104,32.15104 -4.50114,4.50115 -4.50115,4.50114 h -6.43021 -6.43021 -34.72312 -34.72313 -6.43021 -6.4302 l -4.50115,-4.50114 -4.50115,-4.50115 -32.15104,-32.15104 -4.50114,-4.50115 -4.50115,-4.50114 v -12.86042 l 9.00229,-9.00229 h 12.86042 l 4.50115,4.50114 4.50114,4.50115 32.15104,32.15104 h 34.72313 34.72312 l 32.15105,-32.15104 v -34.72313 -34.72312 l -32.15105,-32.15104 h -34.72312 -34.72313 -6.43021 -6.4302 l -4.50115,-4.50115 -4.50115,-4.50114 -32.15104,-32.15105 -4.50114,-4.50114 -4.50115,-4.50115 V 492.8056 l 4.50115,-4.50114 4.50114,-4.50115 32.15104,-32.15104 4.50115,-4.50115 4.50115,-4.50114 h 6.4302 6.43021 34.72313 41.15333 34.72313 6.43021 z m 246.91977,0 9.0023,9.00229 v 12.86042 l -4.50115,4.50114 -4.50115,4.50115 -32.15104,32.15104 -4.50114,4.50115 -4.50115,4.50114 -25.72083,25.72084 25.72083,25.72083 4.50115,4.50115 4.50114,4.50114 32.15104,32.15104 4.50115,4.50115 4.50115,4.50115 v 6.4302 6.43021 34.72313 34.72312 6.43021 6.43021 l -9.0023,9.00229 h -12.86041 l -9.00229,-9.00229 v -6.43021 -6.43021 -34.72312 -34.72313 l -32.15105,-32.15104 -4.50114,-4.50115 -4.50115,-4.50114 -25.72083,-25.72083 -25.72084,25.72083 -4.50114,4.50114 -4.50115,4.50115 -32.15104,32.15104 v 34.72313 34.72312 6.43021 6.43021 l -9.00229,9.00229 h -12.86042 l -9.00229,-9.00229 v -6.43021 -6.43021 -34.72312 -34.72313 -6.43021 -6.4302 l 4.50115,-4.50115 4.50114,-4.50115 32.15104,-32.15104 4.50115,-4.50114 4.50115,-4.50115 25.72083,-25.72083 -25.72083,-25.72084 -4.50115,-4.50114 -4.50115,-4.50115 -32.15104,-32.15104 -4.50114,-4.50115 -4.50115,-4.50114 v -12.86042 l 9.00229,-9.00229 h 12.86042 l 4.50114,4.50114 4.50115,4.50115 32.15104,32.15104 4.50115,4.50115 4.50114,4.50114 25.72084,25.72084 25.72083,-25.72084 4.50115,-4.50114 4.50114,-4.50115 32.15105,-32.15104 4.50114,-4.50115 4.50115,-4.50114 z"
|
||||
id="text1"
|
||||
style="font-size:370.38px;font-family:miracode;-inkscape-font-specification:miracode;text-align:center;text-anchor:middle;fill:url(#linearGradient2);stroke-width:11.801;paint-order:markers fill stroke"
|
||||
aria-label="SX"
|
||||
inkscape:label="1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
BIN
Assets/Schunix Text Logo.png
Normal file
BIN
Assets/Schunix Text Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
85
Assets/Schunix Text Logo.svg
Normal file
85
Assets/Schunix Text Logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.9 KiB |
187
LICENSE
Normal file
187
LICENSE
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship made available under
|
||||
the License, as indicated by a copyright notice that is included in
|
||||
or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean, as submitted to the Licensor for inclusion
|
||||
in the Work by the copyright owner or by an individual or Legal Entity
|
||||
authorized to submit on behalf of the copyright owner. For the purposes
|
||||
of this definition, "contribution" means any work of authorship,
|
||||
including the original version of the Work and any modifications or
|
||||
additions to that Work or Derivative Works of the Work.
|
||||
|
||||
"Contributor" shall mean Licensor and any Legal Entity on behalf of
|
||||
whom a Contribution has been received by the Licensor and included
|
||||
within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by the combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a cross-claim
|
||||
or counterclaim in a lawsuit) alleging that the Work or any
|
||||
Contribution embodied within the Work constitutes patent or
|
||||
contributory patent infringement, then any patent licenses granted to
|
||||
You under this License for that Work shall terminate as of the date
|
||||
such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, You must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, in
|
||||
at least one of the following places: within a NOTICE text
|
||||
file distributed as part of the Derivative Works; within
|
||||
the Source form or documentation, if provided along with the
|
||||
Derivative Works; or, within a display generated by the
|
||||
Derivative Works, if and wherever such third-party notices
|
||||
normally appear. The contents of the NOTICE file are for
|
||||
informational purposes only and do not modify the License.
|
||||
You may add Your own attribution notices within Derivative
|
||||
Works that You distribute, alongside or in addition to the
|
||||
NOTICE text from the Work, provided that such additional
|
||||
attribution notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own license statement for Your modifications and
|
||||
may provide additional grant of rights to use, copy, modify, and
|
||||
distribute those modifications as a separate work, subject to the
|
||||
terms and conditions of this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any conditions of TITLE,
|
||||
NONINFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
You are solely responsible for determining the appropriateness of
|
||||
using or reproducing the Work and assume any risks associated with
|
||||
Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or exemplary damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (even if such Contributor has been advised of the possibility
|
||||
of such damages).
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may offer such
|
||||
obligations only on Your behalf and on Your sole responsibility,
|
||||
not on behalf of any other Contributor, and only if You agree to
|
||||
indemnify, defend, and hold each Contributor harmless for any
|
||||
liability incurred by, or claims asserted against, such Contributor
|
||||
by reason of your accepting any warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2026 Nexoniarz
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
NOTE: The kernel (kernel/) is derived from xv6 (MIT License, Copyright
|
||||
2006-2019 Frans Kaashoek, Robert Morris, Russ Cox, M.I.T. CSAIL).
|
||||
The MIT license notice is preserved in LICENSE.MIT and must not be
|
||||
removed. Third-party software redistributed in a built image -- musl,
|
||||
toybox and mksh, all under ports/ -- keeps its own license; see NOTICE.
|
||||
|
||||
Which outside code may be copied into Schunix and which may not -- the
|
||||
BSD/GPL rules, the NetBSD advertising clause, and what is deliberately
|
||||
never shipped -- is written up in docs/licensing.md. Read it before
|
||||
copying anyone else's code in.
|
||||
29
LICENSE.MIT
Normal file
29
LICENSE.MIT
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
The Schunix kernel is a derivative work of xv6, which carries the licence
|
||||
below. The MIT licence requires this notice be preserved in all copies and
|
||||
substantial portions of the software, so this file must survive any future
|
||||
change to the Schunix licence. See NOTICE and docs/licensing.md.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
The xv6 software is:
|
||||
|
||||
Copyright (c) 2006-2019 Frans Kaashoek, Robert Morris, Russ Cox,
|
||||
Massachusetts Institute of Technology
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
806
Makefile
Normal file
806
Makefile
Normal file
|
|
@ -0,0 +1,806 @@
|
|||
BUILD = build
|
||||
SYSROOT = sysroot
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Parallelism
|
||||
#
|
||||
# Nothing here is order-dependent beyond what the rules already declare, so
|
||||
# there is no reason to compile one file at a time. Override with `make -j1`
|
||||
# when you want readable output from a failing build.
|
||||
# -----------------------------------------------------------------------
|
||||
NPROC := $(shell nproc 2>/dev/null || echo 4)
|
||||
MAKEFLAGS += -j$(NPROC)
|
||||
|
||||
# Kernel object files — organized by subsystem
|
||||
KERN_ARCH = $(addprefix $(BUILD)/kernel/arch/x86_64/, \
|
||||
acpi.o e820.o entry.o trapasm.o swtch.o \
|
||||
trap.o vm.o lapic.o ioapic.o mp.o)
|
||||
|
||||
KERN_CORE = $(addprefix $(BUILD)/kernel/core/, \
|
||||
main.o kalloc.o dma.o spinlock.o sleeplock.o string.o console.o \
|
||||
panic.o uaccess.o qr.o time.o)
|
||||
|
||||
KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \
|
||||
bio.o cdev.o device.o fb.o ide.o input.o irq.o kbd.o mouse.o \
|
||||
pci.o text.o ttf.o uart.o vga.o ramdisk.o procfs.o)
|
||||
|
||||
# Drivers for hardware that has to be *found* rather than assumed. Anything
|
||||
# discovered by walking a bus belongs here; the fixed-address legacy devices
|
||||
# (console, keyboard, PIT, the IDE ports) stay in dev/.
|
||||
#
|
||||
# drivers/<class>/<driver>/ -- one directory per driver, so a driver that grows
|
||||
# a register header, a second source file or a firmware blob has somewhere to
|
||||
# put it. Classes match include/device.h's DEVCLASS_*. Everything flat was
|
||||
# already eight files with four called usb_something, and each driver added made
|
||||
# the directory harder to read. The compile rule uses `mkdir -p $(dir $@)`, so
|
||||
# the depth costs nothing.
|
||||
KERN_DRIVERS = $(BUILD)/kernel/drivers/registry.o
|
||||
KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/vga/vga_pci.o
|
||||
KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/virtio_gpu/virtio_gpu.o
|
||||
KERN_DRIVERS += $(BUILD)/kernel/drivers/net/e1000/e1000.o
|
||||
KERN_DRIVERS += $(BUILD)/kernel/drivers/bus/xhci/xhci.o
|
||||
KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/core/usb.o
|
||||
KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hid/usb_hid.o
|
||||
KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/storage/usb_storage.o
|
||||
KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hub/usb_hub.o
|
||||
|
||||
KERN_FS = $(addprefix $(BUILD)/kernel/fs/, \
|
||||
exec.o file.o fs.o log.o pipe.o procfs.o)
|
||||
|
||||
# SchFS. Every one of these is the very same source the host tools compile, so
|
||||
# the kernel and mkschfs cannot disagree about the on-disk format: there is one
|
||||
# implementation, found and fixed on the host where a test takes a second.
|
||||
KERN_FS += $(addprefix $(BUILD)/kernel/fs/schfs/, \
|
||||
btree.o space.o vol.o obj.o objw.o kmount.o vfs.o)
|
||||
|
||||
# Sockets. Unix domain only for now; an IP stack becomes more files here
|
||||
# rather than a reshaping of what is already written.
|
||||
KERN_NET = $(addprefix $(BUILD)/kernel/net/, \
|
||||
unix.o)
|
||||
|
||||
KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \
|
||||
proc.o)
|
||||
|
||||
KERN_SYS = $(addprefix $(BUILD)/kernel/sys/, \
|
||||
syscall.o sys_proc.o sys_fs.o sys_mem.o sys_net.o \
|
||||
sys_time.o sys_sig.o sys_compat.o sys_poll.o linux_abi.o)
|
||||
|
||||
# vectors.o lives alongside trapasm in the arch dir
|
||||
KERN_ARCH += $(BUILD)/kernel/arch/x86_64/vectors.o
|
||||
|
||||
KERNOBJS = $(KERN_ARCH) $(KERN_CORE) $(KERN_DEV) $(KERN_DRIVERS) \
|
||||
$(KERN_FS) $(KERN_NET) $(KERN_PROC) $(KERN_SYS)
|
||||
# entry.o must be first in link order; remove it from KERNOBJS for the rule
|
||||
KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS))
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# First-party userland
|
||||
#
|
||||
# lib/ startup glue + syscall stubs every cmd/ program links against
|
||||
# cmd/ one directory per program: cmd/<name>/main.c -> sysroot/bin/<name>
|
||||
#
|
||||
# To add a program: mkdir cmd/foo, write cmd/foo/main.c, add "foo" to CMDS.
|
||||
# Third-party software does NOT go here — see ports/.
|
||||
# -----------------------------------------------------------------------
|
||||
ULIB = $(addprefix $(BUILD)/lib/, start.o glue.o usys.o)
|
||||
CMDS = init fbinfo shmtest socktest inputtest
|
||||
CMDBINS = $(addprefix $(BUILD)/cmd/, $(CMDS))
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Toolchain
|
||||
# -----------------------------------------------------------------------
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_P := $(shell uname -p)
|
||||
|
||||
# Windows, under Git Bash or MSYS2 — WINHOST.
|
||||
#
|
||||
# Building natively on Windows rather than in WSL, because WSL2 is a real Linux
|
||||
# kernel in a Hyper-V VM and so hard-requires virtualisation; turning that on
|
||||
# puts the Hyper-V hypervisor underneath the whole machine and demotes VMware
|
||||
# to running nested on top of it, which is a real and permanent cost to pay for
|
||||
# a build environment.
|
||||
#
|
||||
# The usual answer is a cross binutils+gcc, which is a day of work that then
|
||||
# rots. clang does not need any of it: it is a cross-compiler by construction,
|
||||
# so one ordinary LLVM install targets both the bare-metal kernel and Linux
|
||||
# userland, and llvm-objcopy/objdump stand in for binutils. Everything else the
|
||||
# build shells out to — perl, sed, dd, sha256sum — comes with Git Bash.
|
||||
ifneq (,$(filter MINGW% MSYS% CYGWIN%,$(UNAME_S)))
|
||||
WINHOST := 1
|
||||
endif
|
||||
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
ifeq ($(UNAME_P),aarch64)
|
||||
TOOLPREFIX = x86_64-linux-gnu-
|
||||
else
|
||||
TOOLPREFIX =
|
||||
endif
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
TOOLPREFIX = x86_64-elf-
|
||||
endif
|
||||
|
||||
QEMU = qemu-system-x86_64
|
||||
|
||||
# Display for every QEMU target.
|
||||
#
|
||||
# virtio-gpu is what the display class binds, and what a compositor should be
|
||||
# developed against: presenting is explicit, so the host never shows a
|
||||
# half-drawn frame.
|
||||
#
|
||||
# stdvga stays alongside it and is not optional. The bootloader draws the splash
|
||||
# through VBE in 16-bit real mode and there is no virtio path that early, so a
|
||||
# machine with only virtio-gpu boots blind until the kernel is up. Both devices:
|
||||
# virtio-gpu wins the display class, stdvga carries the splash and text console.
|
||||
QEMU_DISPLAY = -vga std -device virtio-gpu-pci
|
||||
|
||||
ifdef WINHOST
|
||||
# An explicit --target is what makes clang emit ELF instead of the COFF it
|
||||
# would default to on this host. Without it every object is silently the
|
||||
# wrong format and the first thing to complain is the linker.
|
||||
CC = clang --target=x86_64-elf
|
||||
CC32 = clang --target=i386-elf
|
||||
LD = ld.lld
|
||||
OBJCOPY = llvm-objcopy
|
||||
OBJDUMP = llvm-objdump
|
||||
# ld.lld has no --no-warn-rwx-segments: it does not emit that warning, so
|
||||
# there is nothing to suppress and the flag is a hard error.
|
||||
LDFLAGS = -m elf_x86_64 -nostdlib
|
||||
# The bootloader, the AP trampoline, initcode and the cmd/ programs are all
|
||||
# linked at addresses below one megabyte. GNU ld places a section wherever
|
||||
# -Ttext says; lld first checks it against a default image base of 0x400000
|
||||
# and refuses, because for a hosted program that would be a bug. Saying the
|
||||
# base is zero is how you tell it this is not a hosted program.
|
||||
LD_LOWBASE = --image-base=0
|
||||
# Host tools (mkfs, mkschfs, the SchFS suites) are ordinary Windows programs.
|
||||
# They must be built with mingw-w64 rather than clang's default MSVC target:
|
||||
# tools/mkfs.c includes <unistd.h> and <dirent.h>, and the Windows SDK has
|
||||
# neither.
|
||||
HOSTCC = gcc
|
||||
# mingw-w64's gcc warns about things glibc's does not (notably %lld/%zu in
|
||||
# printf against msvcrt). Those warnings are about the host build only and
|
||||
# say nothing about the image, so they must not stop it.
|
||||
HOSTWARN = -Wall
|
||||
# MinGW gcc appends .exe when the -o name has no extension, so a rule
|
||||
# whose target lacks it is never considered built -- mkfs was recompiled on
|
||||
# every single invocation of make, forever, because of this.
|
||||
EXE = .exe
|
||||
# `python3` is not on PATH from the python.org installer; `python` is.
|
||||
PYTHON = python
|
||||
# Compiler for ports/, which build *Linux userland* against musl -- a third
|
||||
# target, distinct from both the kernel (bare-metal ELF) and the host tools
|
||||
# (Windows PE). mingw's gcc cannot do it: it targets Windows, so linking it
|
||||
# against musl gives undefined `stderr` and `__errno_location`.
|
||||
#
|
||||
# -nostdlib with the crt files named explicitly, because clang otherwise
|
||||
# reaches for gcc's crtbeginT.o and libgcc, which do not exist on this host.
|
||||
MUSLPREFIX = $(abspath $(BUILD))/ports/musl/prefix
|
||||
PORTCC = clang --target=x86_64-unknown-linux-musl -fuse-ld=lld
|
||||
PORTCRT = -nostdlib $(MUSLPREFIX)/lib/crt1.o $(MUSLPREFIX)/lib/crti.o
|
||||
PORTEND = $(MUSLPREFIX)/lib/crtn.o
|
||||
else
|
||||
CC = $(TOOLPREFIX)gcc
|
||||
CC32 = $(CC) -m32
|
||||
LD = $(TOOLPREFIX)ld
|
||||
OBJCOPY = $(TOOLPREFIX)objcopy
|
||||
OBJDUMP = $(TOOLPREFIX)objdump
|
||||
LDFLAGS = -m elf_x86_64 -nostdlib --no-warn-rwx-segments
|
||||
HOSTCC = gcc
|
||||
HOSTWARN = -Werror -Wall
|
||||
EXE =
|
||||
PYTHON = python3
|
||||
LD_LOWBASE =
|
||||
PORTCC = gcc
|
||||
PORTCRT =
|
||||
PORTEND =
|
||||
endif
|
||||
|
||||
# Host tools are generated output and live under build/, per docs/layout.md.
|
||||
# $(EXE) matters: MinGW appends it, and a rule whose target lacks it never
|
||||
# looks satisfied.
|
||||
MKFS = $(BUILD)/tools/mkfs$(EXE)
|
||||
MKSCHFS = $(BUILD)/tools/mkschfs$(EXE)
|
||||
SCHFS_TEST = $(BUILD)/tools/schfs_test$(EXE)
|
||||
SCHFS_OBJT = $(BUILD)/tools/schfs_objtest$(EXE)
|
||||
SCHFS_FSCK = $(BUILD)/tools/schfs_fsck$(EXE)
|
||||
|
||||
OPT ?= -O0
|
||||
# -ftls-model=local-exec: the kernel is *the* executable, never a shared
|
||||
# object, so its __thread variables (cpu, proc) are always at a fixed negative
|
||||
# offset from the thread pointer. Saying so lets the compiler emit a direct
|
||||
# `movq %rcx, %fs:-0x10` and never materialise the thread pointer as a value.
|
||||
#
|
||||
# GCC picked local-exec on its own for this code; clang did not, and generated
|
||||
# `movq %fs:0x0, %rax; addq $-0x10, %rax; ...` instead -- which reads the TCB
|
||||
# self-pointer at %fs:0, a word this kernel does not maintain and something
|
||||
# else has apparently always been scribbling on. Harmless for fifteen years
|
||||
# because nothing ever read it; instant triple fault the moment a compiler did.
|
||||
XFLAGS = -m64 -DX64 -mcmodel=large -mtls-direct-seg-refs -mno-red-zone
|
||||
XFLAGS += -ftls-model=local-exec
|
||||
|
||||
# Probed once, with :=, not on every expansion of CFLAGS. CFLAGS is a
|
||||
# recursively-expanded variable, so a $(shell) appended to it re-ran the probe
|
||||
# on every single compile -- one extra gcc process per object file, for an
|
||||
# answer that cannot change during a build.
|
||||
NO_SSP := $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)
|
||||
|
||||
CFLAGS = -fno-pic -static -fno-builtin -fno-strict-aliasing -Wall -MD -ggdb -fno-omit-frame-pointer
|
||||
CFLAGS += -ffreestanding -fno-common -nostdlib -I./include $(XFLAGS) $(OPT)
|
||||
CFLAGS += $(NO_SSP) $(CFLAGS_EXTRA)
|
||||
|
||||
# sys/ also needs to find table.inc via a relative include
|
||||
CFLAGS_SYS = $(CFLAGS) -I./kernel/sys
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Ramdisk root (opt-in): make RAMROOT=1
|
||||
#
|
||||
# Links a complete SCHFS image into the kernel and boots from it instead of
|
||||
# the IDE disk, so the physical disk can be reformatted from a running system.
|
||||
# Off by default because it adds RAMDISK_BLOCKS KB to the kernel, and stage2
|
||||
# loads the kernel by PIO one sector at a time -- a 6 MB kernel takes visibly
|
||||
# longer to boot than a 350 KB one.
|
||||
# -----------------------------------------------------------------------
|
||||
RAMDISK_BLOCKS ?= 6000
|
||||
|
||||
# Inodes for the ramdisk root. The inode table is preallocated, so mkfs's
|
||||
# default of 12000 cost 1501 of those 6000 blocks -- a quarter of the image, to
|
||||
# describe about 260 files. 2000 is still an order of magnitude of headroom and
|
||||
# costs 251 blocks.
|
||||
RAMDISK_NINODES ?= 2000
|
||||
|
||||
# Kept out of the ramdisk root.
|
||||
#
|
||||
# Empty right now, and the machinery is kept anyway, because this is a licensing
|
||||
# rule before it is a size one: the ramdisk image is linked *into the kernel
|
||||
# binary*, so anything staged in sysroot/ when it is built becomes part of a
|
||||
# distributable artifact. Anything that arrives in sysroot/ under a licence that
|
||||
# cannot be combined with Apache-2.0, or that simply is not ours to redistribute,
|
||||
# belongs on this list rather than in someone's memory.
|
||||
#
|
||||
# This is not hypothetical: a 33 MB test payload once landed here and overflowed
|
||||
# the 6 MB image, and mkfs's "increase FSSIZE" message then pointed at a constant
|
||||
# that had nothing to do with it.
|
||||
# usb.ids is 730 KB of a 6 MB image -- 12% of the ramdisk root -- and nothing
|
||||
# needs it to boot. It is a lookup table for a userspace tool, so it stays on
|
||||
# the disk image and out of the kernel binary.
|
||||
RAMDISK_EXCLUDE = usr/share/usb.ids
|
||||
RAMDISK_EXCLUDE_ARGS = $(foreach p,$(RAMDISK_EXCLUDE),--exclude $(p))
|
||||
|
||||
ifdef RAMROOT
|
||||
CFLAGS += -DRAMDISK_ROOT
|
||||
RAMDISK_OBJ = $(BUILD)/kernel/ramdisk_obj
|
||||
else
|
||||
RAMDISK_OBJ =
|
||||
endif
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Top-level targets
|
||||
# -----------------------------------------------------------------------
|
||||
all: schunix.img
|
||||
|
||||
schunix.img: $(BUILD)/boot/bootblock $(BUILD)/boot/stage2 $(BUILD)/kernel/kernel $(BUILD)/schfs.img
|
||||
dd if=/dev/zero of=schunix.img count=10000
|
||||
dd if=$(BUILD)/boot/bootblock of=schunix.img conv=notrunc
|
||||
dd if=$(BUILD)/boot/stage2 of=schunix.img seek=1 conv=notrunc
|
||||
# seek is 1 + STAGE2_SECTORS, not a literal. It was 25 against a 24-sector
|
||||
# stage2; raising STAGE2_SECTORS for the boot logo without touching it wrote
|
||||
# the kernel straight over stage2's own tail.
|
||||
dd if=$(BUILD)/kernel/kernel of=schunix.img seek=$$(( 1 + $(STAGE2_SECTORS) )) conv=notrunc
|
||||
|
||||
kernel: $(BUILD)/kernel/kernel
|
||||
|
||||
# Build the ramdisk-root variant. This is a target rather than just
|
||||
# "make RAMROOT=1" because toggling RAMROOT changes CFLAGS (-DRAMDISK_ROOT),
|
||||
# and make has no way to notice that: objects compiled for one setting would be
|
||||
# silently reused for the other, leaving ROOTDEV pointing at the wrong device.
|
||||
# Dropping the compiled objects first makes the switch always correct.
|
||||
ramroot:
|
||||
rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd
|
||||
$(MAKE) RAMROOT=1
|
||||
|
||||
cmd: $(CMDBINS)
|
||||
|
||||
ports:
|
||||
$(MAKE) -C ports
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Bootable ISO
|
||||
#
|
||||
# Always a ramdisk-root build. An ISO that needed a second disk to find its
|
||||
# root filesystem would not be an install medium, it would be a puzzle -- and
|
||||
# read-only media cannot host a writable root anyway. Same object-wiping
|
||||
# preamble as the `ramroot` target, for the same reason: RAMROOT changes
|
||||
# CFLAGS and make cannot see that.
|
||||
# -----------------------------------------------------------------------
|
||||
iso:
|
||||
rm -rf $(BUILD)/kernel $(BUILD)/lib $(BUILD)/cmd
|
||||
$(MAKE) RAMROOT=1 schunix.iso
|
||||
|
||||
schunix.iso: $(BUILD)/boot/cdboot $(BUILD)/boot/stage2 $(BUILD)/kernel/kernel
|
||||
STAGE2_SECTORS=$(STAGE2_SECTORS) sh tools/mkiso.sh $@
|
||||
|
||||
# Boot the ISO the way a machine booting off a disc would: no hard disk at
|
||||
# all, so a kernel that still secretly depends on one fails here rather than
|
||||
# in someone's optical drive.
|
||||
qemu-iso: schunix.iso
|
||||
$(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \
|
||||
-cdrom schunix.iso -boot d \
|
||||
-m 1G -smp 2 -machine pc-i440fx-9.2
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Bootloader — two stages. Stage 1 (bootblock) is the actual 512-byte MBR
|
||||
# sector: BIOS-imposed hard limit, so it only loads stage 2 off disk and
|
||||
# jumps to it. Stage 2 does VBE/A20/protected-mode setup, draws the boot
|
||||
# splash, and loads the kernel — not size-constrained the same way, just
|
||||
# padded out to a fixed STAGE2_SECTORS*512 bytes so the kernel's starting
|
||||
# sector on disk is predictable (kept in sync with STAGE2_SECTORS in
|
||||
# boot/bootasm.S and boot/stage2main.c).
|
||||
# -----------------------------------------------------------------------
|
||||
# STAGE2_SECTORS used to be written out three times -- here, in boot/bootasm.S
|
||||
# and in boot/stage2main.c -- and docs/layout.md carried a warning about keeping
|
||||
# them in step. They are now all fed from this one variable via -D, so the
|
||||
# warning describes a problem that can no longer happen. Both C/asm files keep
|
||||
# an #ifndef fallback purely so they still read sensibly on their own.
|
||||
#
|
||||
# The ceiling is memory, not disk: stage2 is loaded at 0x7E00 and the multiboot
|
||||
# kernel-search buffer begins at 0x10000, so 65 sectors is the hard maximum.
|
||||
# stage2main.c static-asserts this. padstage2.pl fails the build if the compiled
|
||||
# binary overflows the budget, rather than truncating it -- a short stage2 would
|
||||
# shift the kernel's start sector and fail in a way that looks nothing like its
|
||||
# cause.
|
||||
STAGE2_SECTORS = 48
|
||||
STAGE2_DEF = -DSTAGE2_SECTORS=$(STAGE2_SECTORS)
|
||||
# Byte budget is computed in the recipe with shell arithmetic, not $(shell expr
|
||||
# ... \* 512): make hands the backslash through untouched, sh then treats the
|
||||
# `*` as a glob, and expr gets a directory listing. It "worked" to the extent
|
||||
# that make carried on and padstage2.pl received nonsense.
|
||||
STAGE2_BYTES = $$(( $(STAGE2_SECTORS) * 512 ))
|
||||
|
||||
$(BUILD)/boot/bootblock: boot/bootasm.S
|
||||
@mkdir -p $(BUILD)/boot
|
||||
$(CC32) $(STAGE2_DEF) -fno-builtin -fno-pic -nostdinc -I./include -c boot/bootasm.S -o $(BUILD)/boot/bootasm.o
|
||||
$(LD) -m elf_i386 -nostdlib $(LD_LOWBASE) -n -N -e start -Ttext 0x7C00 \
|
||||
-o $(BUILD)/boot/bootblocktmp.o $(BUILD)/boot/bootasm.o
|
||||
$(OBJCOPY) -S -O binary -j .text $(BUILD)/boot/bootblocktmp.o $(BUILD)/boot/bootblock
|
||||
./tools/sign.pl $(BUILD)/boot/bootblock
|
||||
|
||||
# Stage 1 for optical media. Same job as bootblock, far less of it: El Torito
|
||||
# has already loaded stage2 alongside this sector by the time it runs, so it
|
||||
# only records that we came from a CD and jumps. See boot/cdboot.S.
|
||||
$(BUILD)/boot/cdboot: boot/cdboot.S
|
||||
@mkdir -p $(BUILD)/boot
|
||||
$(CC32) -fno-builtin -fno-pic -nostdinc -I./include -c boot/cdboot.S -o $(BUILD)/boot/cdbootasm.o
|
||||
$(LD) -m elf_i386 -nostdlib $(LD_LOWBASE) -n -N -e start -Ttext 0x7C00 \
|
||||
-o $(BUILD)/boot/cdboottmp.o $(BUILD)/boot/cdbootasm.o
|
||||
$(OBJCOPY) -S -O binary -j .text $(BUILD)/boot/cdboottmp.o $@
|
||||
./tools/sign.pl $@
|
||||
|
||||
$(BUILD)/boot/stage2: boot/stage2.S boot/stage2main.c include/logo.h
|
||||
@mkdir -p $(BUILD)/boot
|
||||
$(CC32) $(STAGE2_DEF) -fno-builtin -fno-pic -nostdinc -I./include -c boot/stage2.S -o $(BUILD)/boot/stage2asm.o
|
||||
$(CC32) $(STAGE2_DEF) -fno-builtin -fno-pic -O -nostdinc -I./include -c boot/stage2main.c -o $(BUILD)/boot/stage2main.o
|
||||
$(LD) -m elf_i386 -nostdlib $(LD_LOWBASE) -n -N -e stage2start -Ttext 0x7E00 \
|
||||
-o $(BUILD)/boot/stage2tmp.o $(BUILD)/boot/stage2asm.o $(BUILD)/boot/stage2main.o
|
||||
$(OBJCOPY) -S -O binary -j .text -j .rodata -j .data $(BUILD)/boot/stage2tmp.o $(BUILD)/boot/stage2
|
||||
./tools/padstage2.pl $(BUILD)/boot/stage2 $(STAGE2_BYTES)
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Kernel binary
|
||||
# -----------------------------------------------------------------------
|
||||
$(BUILD)/kernel/kernel: $(KERNOBJS) $(BUILD)/kernel/initcode_obj $(BUILD)/kernel/entryother_obj $(RAMDISK_OBJ) kernel/kernel.ld
|
||||
@mkdir -p $(BUILD)/kernel
|
||||
$(LD) $(LDFLAGS) -T kernel/kernel.ld -o $@ \
|
||||
$(BUILD)/kernel/arch/x86_64/entry.o $(KERN_NOENTRY) \
|
||||
$(BUILD)/kernel/initcode_obj $(BUILD)/kernel/entryother_obj $(RAMDISK_OBJ)
|
||||
$(OBJDUMP) -S $@ > $(BUILD)/kernel/kernel.asm
|
||||
|
||||
# The ramdisk root image, and the object wrapping it for the linker. Built from
|
||||
# the same staged sysroot as the main image, just capped at RAMDISK_BLOCKS --
|
||||
# mkfs's content dedup means the whole userland still fits in a few MB.
|
||||
$(BUILD)/fs-ram.img: $(MKFS) $(BUILD)/sysroot.stamp
|
||||
@mkdir -p $(BUILD)
|
||||
@rm -f $@
|
||||
$(MKFS) $@ --size $(RAMDISK_BLOCKS) --ninodes $(RAMDISK_NINODES) \
|
||||
$(RAMDISK_EXCLUDE_ARGS) --sysroot $(SYSROOT)
|
||||
|
||||
$(BUILD)/kernel/ramdisk_obj: $(BUILD)/fs-ram.img
|
||||
@mkdir -p $(BUILD)/kernel
|
||||
$(LD) -r -b binary -m elf_x86_64 -o $@ $<
|
||||
$(OBJCOPY) \
|
||||
--redefine-sym _binary_build_fs_ram_img_start=_binary_ramdisk_start \
|
||||
--redefine-sym _binary_build_fs_ram_img_end=_binary_ramdisk_end \
|
||||
--redefine-sym _binary_build_fs_ram_img_size=_binary_ramdisk_size \
|
||||
$@
|
||||
|
||||
# AP startup blob
|
||||
$(BUILD)/kernel/entryother: kernel/arch/x86_64/entryother.S
|
||||
@mkdir -p $(BUILD)/kernel
|
||||
$(CC) $(CFLAGS) -fno-pic -nostdinc -I./include -c $< -o $(BUILD)/kernel/entryother.o
|
||||
$(LD) $(LDFLAGS) $(LD_LOWBASE) -e start -Ttext 0x7000 -o $(BUILD)/kernel/entryothertmp.o $(BUILD)/kernel/entryother.o
|
||||
$(OBJCOPY) -S -O binary -j .text $(BUILD)/kernel/entryothertmp.o $@
|
||||
|
||||
# NOTE: ld -r -b binary derives its symbol names from the *path* of the input
|
||||
# file, so these --redefine-sym pairs must be kept in sync with the build
|
||||
# directory layout above. Move the file, and the left-hand names change.
|
||||
$(BUILD)/kernel/entryother_obj: $(BUILD)/kernel/entryother
|
||||
$(LD) -r -b binary -m elf_x86_64 -o $@ $<
|
||||
$(OBJCOPY) \
|
||||
--redefine-sym _binary_build_kernel_entryother_start=_binary_entryother_start \
|
||||
--redefine-sym _binary_build_kernel_entryother_end=_binary_entryother_end \
|
||||
--redefine-sym _binary_build_kernel_entryother_size=_binary_entryother_size \
|
||||
$@
|
||||
|
||||
# Init stub (first user-space code, embedded in the kernel image)
|
||||
$(BUILD)/lib/initcode: lib/initcode.S
|
||||
@mkdir -p $(BUILD)/lib
|
||||
$(CC) $(CFLAGS) -fno-pic -nostdinc -I./include -c $< -o $(BUILD)/lib/initcode.o
|
||||
$(LD) -m elf_x86_64 -nostdlib $(LD_LOWBASE) -N -e start -Ttext 0 -o $(BUILD)/lib/initcodetmp.o $(BUILD)/lib/initcode.o
|
||||
$(OBJCOPY) -S -O binary $(BUILD)/lib/initcodetmp.o $@
|
||||
|
||||
$(BUILD)/kernel/initcode_obj: $(BUILD)/lib/initcode
|
||||
@mkdir -p $(BUILD)/kernel
|
||||
$(LD) -r -b binary -m elf_x86_64 -o $@ $<
|
||||
$(OBJCOPY) \
|
||||
--redefine-sym _binary_build_lib_initcode_start=_binary_initcode_start \
|
||||
--redefine-sym _binary_build_lib_initcode_end=_binary_initcode_end \
|
||||
--redefine-sym _binary_build_lib_initcode_size=_binary_initcode_size \
|
||||
$@
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# vectors.S — generated from perl script
|
||||
# -----------------------------------------------------------------------
|
||||
kernel/arch/x86_64/vectors.S: tools/vectors.pl
|
||||
perl tools/vectors.pl > $@
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Compile rules for kernel/
|
||||
# -----------------------------------------------------------------------
|
||||
$(BUILD)/kernel/arch/x86_64/%.o: kernel/arch/x86_64/%.c
|
||||
@mkdir -p $(BUILD)/kernel/arch/x86_64
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/kernel/arch/x86_64/%.o: kernel/arch/x86_64/%.S
|
||||
@mkdir -p $(BUILD)/kernel/arch/x86_64
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/kernel/core/%.o: kernel/core/%.c
|
||||
@mkdir -p $(BUILD)/kernel/core
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/kernel/dev/%.o: kernel/dev/%.c
|
||||
@mkdir -p $(BUILD)/kernel/dev
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# `$(dir $@)` rather than a fixed path: drivers are grouped into one directory
|
||||
# per device class (gpu/, net/, bus/, usb/), so the output directory varies with
|
||||
# the target and a hardcoded mkdir only creates the top one.
|
||||
$(BUILD)/kernel/drivers/%.o: kernel/drivers/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/kernel/fs/%.o: kernel/fs/%.c
|
||||
@mkdir -p $(BUILD)/kernel/fs
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/kernel/fs/schfs/%.o: kernel/fs/schfs/%.c
|
||||
@mkdir -p $(BUILD)/kernel/fs/schfs
|
||||
$(CC) $(CFLAGS) -I./kernel/fs/schfs -c $< -o $@
|
||||
|
||||
$(BUILD)/kernel/net/%.o: kernel/net/%.c
|
||||
@mkdir -p $(BUILD)/kernel/net
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/kernel/proc/%.o: kernel/proc/%.c
|
||||
@mkdir -p $(BUILD)/kernel/proc
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# sys/ gets an extra -I so syscall.c can do #include "table.inc"
|
||||
$(BUILD)/kernel/sys/%.o: kernel/sys/%.c
|
||||
@mkdir -p $(BUILD)/kernel/sys
|
||||
$(CC) $(CFLAGS_SYS) -c $< -o $@
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# First-party userland: lib/ + cmd/
|
||||
# -----------------------------------------------------------------------
|
||||
$(BUILD)/lib/%.o: lib/%.c
|
||||
@mkdir -p $(BUILD)/lib
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/lib/%.o: lib/%.S
|
||||
@mkdir -p $(BUILD)/lib
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/cmd/%: cmd/%/main.c $(ULIB)
|
||||
@mkdir -p $(BUILD)/cmd
|
||||
$(CC) $(CFLAGS) -c $< -o $(BUILD)/cmd/$*.o
|
||||
$(LD) $(LDFLAGS) $(LD_LOWBASE) -e _start -Ttext 0x4000 -o $@ $(BUILD)/cmd/$*.o $(ULIB)
|
||||
$(OBJCOPY) -S $@
|
||||
|
||||
$(SYSROOT)/bin/%: $(BUILD)/cmd/% | $(SYSROOT)/bin
|
||||
cp $< $@
|
||||
chmod 755 $@
|
||||
|
||||
$(SYSROOT)/bin:
|
||||
mkdir -p $@
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Filesystem image
|
||||
# -----------------------------------------------------------------------
|
||||
# -----------------------------------------------------------------------
|
||||
# SchFS B-tree torture test (host program).
|
||||
#
|
||||
# The B-tree underpins every record type in SchFS, so it is developed and
|
||||
# tested here -- against a reference model, with simulated crashes -- before
|
||||
# it is linked into the kernel. Run it after any change to
|
||||
# kernel/fs/schfs/btree.c.
|
||||
# -----------------------------------------------------------------------
|
||||
schfs-test: $(SCHFS_TEST)
|
||||
$(SCHFS_TEST)
|
||||
|
||||
# Use ASan/UBSan when the runtime libraries are actually installed; they catch
|
||||
# exactly the pointer-arithmetic mistakes this code is most exposed to, but not
|
||||
# every machine has libasan, so fall back rather than failing the build.
|
||||
SAN := $(shell echo 'int main(void){return 0;}' > /tmp/.santest.c 2>/dev/null && \
|
||||
gcc -fsanitize=address,undefined -o /tmp/.santest /tmp/.santest.c \
|
||||
>/dev/null 2>&1 && echo -fsanitize=address,undefined)
|
||||
|
||||
SCHFS_SRC = kernel/fs/schfs/btree.c kernel/fs/schfs/btree.h \
|
||||
kernel/fs/schfs/space.c kernel/fs/schfs/vol.c \
|
||||
kernel/fs/schfs/obj.c kernel/fs/schfs/objw.c \
|
||||
kernel/fs/schfs/schfs_int.h include/schfs.h
|
||||
|
||||
# -iquote, not -I: include/ has its own errno.h, stat.h, fcntl.h and types.h,
|
||||
# and -I would put them ahead of the system ones for a host program that needs
|
||||
# the real thing. -iquote applies only to "quoted" includes, so btree.h still
|
||||
# finds schfs.h while <errno.h> still means the C library's.
|
||||
HOSTCFLAGS = -Wall -Wextra -O1 -g $(SAN) -iquote ./include
|
||||
|
||||
$(SCHFS_TEST): tools/schfs_test.c $(SCHFS_SRC)
|
||||
@mkdir -p $(BUILD)/tools
|
||||
@mkdir -p $(BUILD)
|
||||
$(HOSTCC) $(HOSTCFLAGS) -o $@ tools/schfs_test.c
|
||||
|
||||
# The object layer -- files, directories, and whether deleting things gives the
|
||||
# space back. Runs against a memory-backed volume, so a full create/write/
|
||||
# delete/remount cycle takes milliseconds instead of a rebuild and a boot.
|
||||
$(SCHFS_OBJT): tools/schfs_objtest.c $(SCHFS_SRC)
|
||||
@mkdir -p $(BUILD)/tools
|
||||
@mkdir -p $(BUILD)
|
||||
$(HOSTCC) $(HOSTCFLAGS) -o $@ tools/schfs_objtest.c
|
||||
|
||||
schfs-objtest: $(SCHFS_OBJT)
|
||||
$(SCHFS_OBJT)
|
||||
|
||||
# The image writer and checker. Both #include the real filesystem sources, so
|
||||
# there is exactly one implementation of the on-disk format: a host tool and the
|
||||
# kernel cannot drift apart because there is nothing to drift from.
|
||||
$(MKSCHFS): tools/mkschfs.c $(SCHFS_SRC)
|
||||
@mkdir -p $(BUILD)/tools
|
||||
@mkdir -p $(BUILD)
|
||||
$(HOSTCC) $(HOSTCFLAGS) -o $@ tools/mkschfs.c
|
||||
|
||||
$(SCHFS_FSCK): tools/schfs_fsck.c $(SCHFS_SRC)
|
||||
@mkdir -p $(BUILD)/tools
|
||||
@mkdir -p $(BUILD)
|
||||
$(HOSTCC) $(HOSTCFLAGS) -o $@ tools/schfs_fsck.c
|
||||
|
||||
mkschfs: $(MKSCHFS)
|
||||
schfs-fsck: $(SCHFS_FSCK)
|
||||
|
||||
# End-to-end filesystem test: build an image from a directory tree, read it
|
||||
# back with schfs_fsck, and diff the result against the original.
|
||||
#
|
||||
# The oracle here is the host's own filesystem, which knows nothing about SchFS.
|
||||
# schfs-test proves the B-tree matches a model written alongside it; this proves
|
||||
# the *filesystem* matches a tree that exists independently of it, so a writer
|
||||
# and reader that agree on something wrong are still caught.
|
||||
schfs-image-test: $(MKSCHFS) $(SCHFS_FSCK)
|
||||
@sh tools/schfs_roundtrip.sh $(MKSCHFS) $(SCHFS_FSCK)
|
||||
|
||||
# Every SchFS suite. Run this after touching anything under kernel/fs/schfs.
|
||||
schfs-check: schfs-test schfs-objtest schfs-image-test
|
||||
|
||||
# Crash the kernel on purpose and photograph the result.
|
||||
#
|
||||
# The crash screen is the one part of the system you cannot look at by using
|
||||
# the system, which is exactly why it rots. This makes "does it still render"
|
||||
# a question with a thirty-second answer. Needs a ramroot build first.
|
||||
crashshot:
|
||||
pwsh -File tools/crashshot.ps1
|
||||
|
||||
# Read the QR code back off a captured crash screen. End-to-end on purpose: it
|
||||
# reads the pixels that were painted, so payload construction in panic context,
|
||||
# the encoder, the module scaling and the quiet zone are all covered by whether
|
||||
# text comes back. Run `make crashshot` first. Needs the same Python packages
|
||||
# as `qrcheck`.
|
||||
crashqr:
|
||||
$(PYTHON) tools/crashqr.py
|
||||
|
||||
$(MKFS): tools/mkfs.c include/fs.h include/stat.h include/param.h
|
||||
@mkdir -p $(BUILD)/tools
|
||||
$(HOSTCC) $(HOSTWARN) -I. -o $(MKFS) tools/mkfs.c
|
||||
|
||||
# Stage everything a running system needs into sysroot/, then image it.
|
||||
#
|
||||
# ports/ is built via its own Makefile rather than per-port from here, because
|
||||
# that is where the inter-port ordering lives (mksh and toybox both link
|
||||
# against musl, whose install prefix is under build/ — so `make clean` wipes it
|
||||
# and musl must be rebuildable as part of the normal chain, not out-of-band).
|
||||
#
|
||||
# On Windows the ports are NOT rebuilt. musl, mksh and toybox are Linux
|
||||
# userland built by configure scripts and kconfig, and getting those to run
|
||||
# under Git Bash against a clang cross-target is a separate project from
|
||||
# building the OS. Their outputs are already staged in sysroot/ and change
|
||||
# perhaps twice a year, so the cost of not rebuilding them here is close to
|
||||
# zero -- and it is stated out loud rather than silently skipped, because a
|
||||
# port that quietly does not rebuild is exactly the kind of thing that wastes
|
||||
# an afternoon later.
|
||||
sysroot-install: $(addprefix $(SYSROOT)/bin/, $(CMDS))
|
||||
ifdef WINHOST
|
||||
@echo "ports: skipped (Windows host) -- musl/mksh/toybox are used as already staged in $(SYSROOT)"
|
||||
@test -f $(SYSROOT)/bin/sh || { echo "ports: $(SYSROOT)/bin/sh is missing and cannot be built here; build ports on a Linux host once" >&2; exit 1; }
|
||||
else
|
||||
$(MAKE) -C ports CC=gcc
|
||||
endif
|
||||
|
||||
# A stamp whose timestamp advances only when the staged tree actually changes.
|
||||
#
|
||||
# make cannot depend on "the contents of a directory", and sysroot/ is filled
|
||||
# by two different builds (cmd/ from here, ports/ recursively), so the images
|
||||
# below used to depend on the phony sysroot-install -- which meant every image
|
||||
# was rebuilt on every invocation, whether or not a single byte had moved.
|
||||
# That cost 100 seconds of a 108-second no-op build.
|
||||
#
|
||||
# Path, size and mtime rather than content hashes: reading every staged file
|
||||
# would undo the point, and a build system that lies about a file whose mtime
|
||||
# and size both stayed the same has bigger problems. The cmp-then-move keeps
|
||||
# the stamp's mtime unchanged when the summary is unchanged, which is the
|
||||
# whole trick -- writing it unconditionally would just move the problem.
|
||||
$(BUILD)/sysroot.stamp: sysroot-install
|
||||
@mkdir -p $(BUILD)
|
||||
@find $(SYSROOT) -type f -printf '%p %s %T@\n' | sort > $@.new
|
||||
@cmp -s $@.new $@ || mv -f $@.new $@
|
||||
@rm -f $@.new
|
||||
|
||||
$(BUILD)/fs.img: $(MKFS) $(BUILD)/sysroot.stamp
|
||||
@mkdir -p $(BUILD)
|
||||
@echo "Creating Schunix File System Image..."
|
||||
@# A running QEMU holds this file open, and on Windows that makes rm fail
|
||||
@# with "Device or resource busy" -- which stops the build *after* the
|
||||
@# kernel has already relinked, leaving schunix.img stale. The next test run
|
||||
@# then boots the previous kernel and the change appears to have done
|
||||
@# nothing, which has cost several debugging cycles. Say so plainly.
|
||||
@rm -f $@ || { \
|
||||
echo ""; \
|
||||
echo " $@ is in use -- a QEMU is still holding it."; \
|
||||
echo " Close it, or: pwsh -Command \"Get-Process qemu-system-x86_64 |"; \
|
||||
echo " Where-Object { -not \$$_.MainWindowTitle } | Stop-Process -Force\""; \
|
||||
echo ""; \
|
||||
exit 1; \
|
||||
}
|
||||
$(MKFS) $@ --sysroot $(SYSROOT)
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# QEMU
|
||||
# -----------------------------------------------------------------------
|
||||
qemu: schunix.img $(BUILD)/schfs.img
|
||||
$(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \
|
||||
-hda schunix.img -hdb $(BUILD)/schfs.img \
|
||||
-m 1G -smp 2 -machine pc-i440fx-9.2
|
||||
|
||||
# The root filesystem. Built from the staged sysroot; the kernel mounts it as
|
||||
# / in forkret().
|
||||
$(BUILD)/schfs.img: $(MKSCHFS) $(BUILD)/sysroot.stamp
|
||||
@mkdir -p $(BUILD)
|
||||
@rm -f $@
|
||||
./$(MKSCHFS) $@ --size 8000 --label schunix --sysroot $(SYSROOT)
|
||||
|
||||
# Boot with a SchFS volume attached as the second disk.
|
||||
#
|
||||
# Needs the ramdisk-root kernel ("make ramroot" first): with RAMROOT the root
|
||||
# filesystem comes out of memory and device 1 is free, which is exactly the
|
||||
# arrangement docs/architecture/schfs.md asks for -- a new-format disk that
|
||||
# fails to mount cannot stop the system booting. Without RAMROOT, device 1 is
|
||||
# the live v1 root and the SchFS probe correctly finds nothing there.
|
||||
#
|
||||
# Once up: cat /proc/schfs
|
||||
qemu-schfs: schunix.img $(BUILD)/schfs.img
|
||||
$(QEMU) -serial mon:stdio $(QEMU_DISPLAY) -cpu qemu64,+rdtscp \
|
||||
-hda schunix.img -hdb $(BUILD)/schfs.img \
|
||||
-m 1G -smp 2 -machine pc-i440fx-9.2
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Misc
|
||||
# -----------------------------------------------------------------------
|
||||
# schunix.img is deliberately NOT here. It is a real file with real
|
||||
# prerequisites; listing it as phony made make skip the timestamp check and
|
||||
# re-run the four dd commands -- including zeroing five megabytes -- on every
|
||||
# single invocation, including ones where nothing had changed.
|
||||
# include/logo.h is generated but committed, exactly as include/font.h is: it
|
||||
# derives from art that changes about never, and making it a build dependency
|
||||
# would put Python on the critical path of every clean build for no benefit.
|
||||
# Run this by hand after changing the art.
|
||||
LOGO_SRC = Assets/Schunix Small Logo.png
|
||||
LOGO_PX = 128
|
||||
|
||||
logo:
|
||||
$(PYTHON) tools/mklogo.py "$(LOGO_SRC)" include/logo.h $(LOGO_PX)
|
||||
|
||||
# The USB vendor/product name database, from linux-usb.org.
|
||||
#
|
||||
# Dual-licensed GPL-2.0-or-later **or** 3-clause BSD; Schunix takes the BSD
|
||||
# option, which is what makes it usable at all. See NOTICE.
|
||||
#
|
||||
# Cached in dist/ and refreshed only when you ask, NOT on every build. That was
|
||||
# the request, and it is the one thing here worth pushing back on: fetching on
|
||||
# every compilation makes an offline build fail, makes the same source produce
|
||||
# different images on different days, and puts a third-party web server on the
|
||||
# critical path of `make`. dist/ exists precisely so a rebuild never needs the
|
||||
# network -- see docs/layout.md. `make usb-ids` is a deliberate act; the file it
|
||||
# writes is committed and dated, so what shipped is always knowable.
|
||||
USB_IDS_URL = http://www.linux-usb.org/usb.ids
|
||||
|
||||
usb-ids:
|
||||
@echo "==> fetching $(USB_IDS_URL)"
|
||||
@curl -sfL --max-time 60 -o dist/usb.ids.new "$(USB_IDS_URL)" || \
|
||||
{ echo " fetch failed; keeping the cached copy"; rm -f dist/usb.ids.new; exit 1; }
|
||||
@# A truncated or error-page download would otherwise silently replace a
|
||||
@# good database with rubbish. Check it looks like what it claims to be.
|
||||
@grep -q "^# Version:" dist/usb.ids.new || \
|
||||
{ echo " that is not a usb.ids file; keeping the cached copy"; \
|
||||
rm -f dist/usb.ids.new; exit 1; }
|
||||
@mv dist/usb.ids.new dist/usb.ids
|
||||
@cp dist/usb.ids $(SYSROOT)/usr/share/usb.ids
|
||||
@grep -m1 "^# Version:" dist/usb.ids
|
||||
|
||||
# Build the kernel's QR encoder on the host and check it.
|
||||
#
|
||||
# A QR symbol with a mistyped table entry still looks like a perfectly plausible
|
||||
# grid of black squares, so the self-checks in qrtest.c cover what can be
|
||||
# verified mechanically and the PNG covers the rest: point a phone at it.
|
||||
QRTEST = $(BUILD)/tools/qrtest$(EXE)
|
||||
|
||||
$(QRTEST): tools/qrtest.c kernel/core/qr.c include/qr.h
|
||||
@mkdir -p $(BUILD)/tools
|
||||
$(HOSTCC) $(HOSTWARN) -I./include -o $@ tools/qrtest.c
|
||||
|
||||
qrtest: $(QRTEST)
|
||||
@mkdir -p $(BUILD)
|
||||
./$(QRTEST) $(BUILD)/qr.png
|
||||
|
||||
# The check that actually decides whether the encoder works: build a symbol and
|
||||
# read it back with a real decoder, plus a module-for-module comparison against
|
||||
# a second encoder. Needs Python packages that are not part of the build, so it
|
||||
# is not wired into `make` -- install them wherever you like and point
|
||||
# PYTHONPATH at it:
|
||||
#
|
||||
# pip install --target build/pylibs opencv-python-headless numpy segno
|
||||
# PYTHONPATH=build/pylibs make qrcheck
|
||||
qrcheck: $(QRTEST)
|
||||
$(PYTHON) tools/qrcheck.py
|
||||
|
||||
.PHONY: all kernel ramroot cmd ports sysroot-install qemu \
|
||||
schfs-test schfs-objtest schfs-image-test schfs-check mkschfs \
|
||||
schfs-fsck crashshot qemu-schfs iso qemu-iso clean distclean \
|
||||
logo qrtest qrcheck crashqr usb-ids
|
||||
.PRECIOUS: $(BUILD)/%.o
|
||||
|
||||
# 'clean' removes generated output only. dist/ (upstream tarballs) survives,
|
||||
# so a rebuild never needs the network.
|
||||
clean:
|
||||
rm -rf $(BUILD) schunix.img schunix.iso kernel/arch/x86_64/vectors.S
|
||||
|
||||
distclean: clean
|
||||
rm -rf dist
|
||||
|
||||
# Header dependencies, from gcc -MD.
|
||||
#
|
||||
# This used to be a set of globs including `$(BUILD)/kernel/**/*.d`. make's
|
||||
# wildcard has no recursive `**`: it behaved as a single `*` and matched
|
||||
# exactly one level, so build/kernel/core/*.d was picked up while
|
||||
# build/kernel/arch/x86_64/*.d and build/kernel/fs/schfs/*.d were not.
|
||||
# Everything under arch/ and schfs/ therefore had NO header dependency
|
||||
# tracking at all -- editing include/schfs.h or include/mmu.h left stale
|
||||
# objects that linked without complaint. find actually recurses.
|
||||
-include $(shell find $(BUILD) -name '*.d' 2>/dev/null)
|
||||
145
NOTICE
Normal file
145
NOTICE
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
Schunix
|
||||
Copyright 2026 Nexoniarz
|
||||
|
||||
This product includes software developed as part of the Schunix project,
|
||||
licensed under the Apache License, Version 2.0. See LICENSE.
|
||||
|
||||
================================================================================
|
||||
Third-party software
|
||||
================================================================================
|
||||
|
||||
Schunix incorporates and redistributes software written by others. Their
|
||||
licences apply to their code and are NOT superseded by the Schunix licence.
|
||||
A built Schunix image contains binaries derived from all of the following.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
xv6-x86_64 / xv6 MIT License
|
||||
--------------------------------------------------------------------------------
|
||||
Copyright (c) 2006-2019 Frans Kaashoek, Robert Morris, Russ Cox,
|
||||
Massachusetts Institute of Technology CSAIL
|
||||
|
||||
The Schunix kernel is a derivative work of xv6. Substantial portions of
|
||||
kernel/ -- notably the process, trap, virtual memory, buffer cache and v1
|
||||
filesystem code -- descend from it, however heavily rewritten. The MIT licence
|
||||
requires that this notice be preserved in all copies; see LICENSE.MIT.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
musl libc 1.2.5 MIT License
|
||||
--------------------------------------------------------------------------------
|
||||
Copyright (c) 2005-2020 Rich Felker and contributors
|
||||
|
||||
Built from ports/musl. Provides the C library every userland program links
|
||||
against. See the extracted source's COPYRIGHT file for the full contributor
|
||||
list, which the MIT terms require be carried along with binaries.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
toybox 0.8.12 Zero-Clause BSD (0BSD)
|
||||
--------------------------------------------------------------------------------
|
||||
Copyright (c) 2006-2024 Rob Landley and contributors
|
||||
|
||||
Built from ports/toybox. Supplies the standard command set in /bin. 0BSD
|
||||
imposes no attribution requirement at all; this entry is courtesy, not
|
||||
obligation.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
mksh R59c MirOS Licence
|
||||
--------------------------------------------------------------------------------
|
||||
Copyright (c) 2002-2023 Thorsten Glaser and contributors
|
||||
Portions derived from the Public Domain Korn Shell (pdksh)
|
||||
|
||||
Built from ports/mksh. Supplies /bin/sh. The MirOS Licence is permissive and
|
||||
requires the copyright notice be retained.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Intel 8254x device identifiers BSD 3-Clause
|
||||
--------------------------------------------------------------------------------
|
||||
Copyright (c) 2001-2020, Intel Corporation
|
||||
All rights reserved.
|
||||
|
||||
The PCI device IDs in include/e1000.h are transcribed from FreeBSD's
|
||||
sys/dev/e1000/e1000_hw.h, which carries the Intel 3-clause BSD licence above
|
||||
(verified: no advertising clause). Only the identifier constants are used --
|
||||
data, not implementation. kernel/drivers/net/e1000/e1000.c is written from
|
||||
Intel's public manual 317453 and contains no FreeBSD or Intel driver code.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the conditions of the 3-clause BSD
|
||||
licence are met, including retention of this notice and the disclaimer, and
|
||||
that neither the name of Intel Corporation nor the names of its contributors
|
||||
are used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
usb.ids -- USB vendor and product database BSD 3-Clause (dual-licensed)
|
||||
--------------------------------------------------------------------------------
|
||||
Maintained by Stephen J. Gowdy <linux.usb.ids@gmail.com>
|
||||
http://www.linux-usb.org/usb.ids
|
||||
|
||||
Distributed by its maintainer under the terms of *either* the GNU General
|
||||
Public License version 2 or later, *or* the 3-clause BSD Licence. Schunix takes
|
||||
the BSD option; the GPL option is not exercised and could not be, since
|
||||
Apache-2.0 and GPL-2.0-only cannot be combined (see docs/licensing.md section
|
||||
2).
|
||||
|
||||
Shipped verbatim as /usr/share/usb.ids. Cached in dist/ and refreshed with
|
||||
`make usb-ids`, never automatically -- the version and date are in the file's
|
||||
own header, so what shipped is always knowable.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Roboto Apache-2.0
|
||||
--------------------------------------------------------------------------------
|
||||
Copyright the Roboto Project Authors (https://github.com/googlefonts/roboto)
|
||||
|
||||
Shipped as /usr/share/fonts/Roboto-Regular.ttf, from ports/Roboto. Licensed
|
||||
under Apache-2.0 -- the same licence as Schunix itself, so it carries no
|
||||
obligation beyond this notice. Intended for the desktop, window manager and
|
||||
compositor.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
JetBrains Mono SIL OFL 1.1
|
||||
--------------------------------------------------------------------------------
|
||||
Copyright 2020 The JetBrains Mono Project Authors
|
||||
(https://github.com/JetBrains/JetBrainsMono)
|
||||
|
||||
Shipped as /usr/share/fonts/JetBrainsMono-Regular.ttf, from ports/JetBrains_Mono
|
||||
(full licence text at ports/JetBrains_Mono/OFL.txt). Intended for the terminal
|
||||
and TTY.
|
||||
|
||||
Under the SIL Open Font License 1.1 the font remains under OFL even inside this
|
||||
Apache-2.0 system -- that does not reach any other part of Schunix -- and it may
|
||||
not be sold on its own. Bundling it with an operating system is expressly
|
||||
permitted. JetBrains Mono declares no Reserved Font Name, so a modified version
|
||||
(a pre-rasterised bitmap, for instance) may keep the name.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
"New File System For XV6" MIT License
|
||||
--------------------------------------------------------------------------------
|
||||
Copyright (c) Qi Ma, Leyi Pan, Ao Sun, Peiran Xu
|
||||
|
||||
kernel/fs/fs.c and parts of tools/mkfs.c derive from this project, which extends
|
||||
xv6's file system with features Schunix still uses today:
|
||||
|
||||
- the HTree hashed directory index (BKDRHash, HASHRANGE, the primary/overflow
|
||||
bucket layout), after Daniel Phillips, "A Directory Index for EXT2",
|
||||
5th Annual Linux Showcase & Conference, 2001
|
||||
- Ext4-style extents (T_EXTENT)
|
||||
- the inode bitmap
|
||||
- symbolic links, file permissions, lseek and append
|
||||
|
||||
Licensed under the MIT License, which permits this use and requires that the
|
||||
copyright notice and permission notice be retained. This entry is that
|
||||
retention; it was missing until 2026-08-09 and is recorded here regardless of
|
||||
what happens to the code, because the obligation attaches to every copy already
|
||||
made.
|
||||
|
||||
Distinct from xv6 itself (LICENSE.MIT), which this project in turn derives from.
|
||||
|
||||
================================================================================
|
||||
|
||||
The build downloads upstream tarballs into dist/ and applies the patches in
|
||||
ports/<name>/patches/. Nothing in ports/ is Schunix code; do not add first-party
|
||||
work there. See docs/layout.md.
|
||||
|
||||
Licence questions -- what may be copied in, what may never be, and how to check
|
||||
a NetBSD file's clause before using it -- are worked through in
|
||||
docs/licensing.md.
|
||||
101
boot/bootasm.S
Normal file
101
boot/bootasm.S
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# Stage 1 — the actual MBR boot sector. Hard-limited to 510 bytes (+2-byte
|
||||
# 0x55AA signature) by the BIOS, so there's no room here for anything but
|
||||
# "load stage 2 off disk and jump to it". All the VBE/A20/protected-mode/
|
||||
# splash/kernel-loading work now lives in stage2.S + stage2main.c, which
|
||||
# aren't sector-size-constrained.
|
||||
#
|
||||
# How many sectors of stage2 to pull in. This, the padding the Makefile gives
|
||||
# stage2's binary, and the "+(STAGE2_SECTORS+1)" sector math in stage2main.c's
|
||||
# readseg() all have to agree on where the kernel starts on disk -- so the
|
||||
# Makefile passes -DSTAGE2_SECTORS and is the single authority. The value below
|
||||
# is only a fallback for reading this file standalone.
|
||||
#
|
||||
# It is 48, not the old 24, because stage2 now carries the boot logo
|
||||
# (include/logo.h, 8 KiB). The real ceiling is memory rather than this number:
|
||||
# stage2 loads at 0x7E00 and the multiboot kernel-search buffer starts at
|
||||
# 0x10000, so stage2 cannot exceed 0x8200 bytes = 65 sectors.
|
||||
#ifndef STAGE2_SECTORS
|
||||
#define STAGE2_SECTORS 48
|
||||
#endif
|
||||
#define STAGE2_ADDR 0x7E00
|
||||
|
||||
.code16
|
||||
.global start
|
||||
start:
|
||||
cli
|
||||
xorw %ax, %ax
|
||||
movw %ax, %ds
|
||||
movw %ax, %es
|
||||
movw %ax, %ss
|
||||
movw $0x7c00, %sp
|
||||
|
||||
# Read STAGE2_SECTORS sectors, starting at disk sector 1 (LBA), into
|
||||
# memory right after this boot sector (0x7E00). Raw IDE PIO (LBA28) —
|
||||
# same register convention as stage2main.c's own readsect(), just
|
||||
# inlined here since we can't call C code yet. Port numbers 0x1F2-0x1F7
|
||||
# are all > 0xFF, so they must go through %dx — `out $imm8,%al` only
|
||||
# encodes an 8-bit port and silently truncates anything larger.
|
||||
# "You were not booted from a CD." stage2main.c reads this byte to choose
|
||||
# between ATA and ATAPI when it loads the kernel, and nothing else clears
|
||||
# it -- so a warm reboot from a disc could otherwise leave the flag set and
|
||||
# send a perfectly ordinary hard-disk boot looking for an optical drive.
|
||||
# Placed one byte past the verbose flag; both sit clear of the 256-byte VBE
|
||||
# mode info block that stage2.S is about to write at 0x30000.
|
||||
movw $0x3000, %ax
|
||||
movw %ax, %es
|
||||
movw $0x101, %di
|
||||
movb $0, %al
|
||||
movb %al, %es:(%di)
|
||||
xorw %ax, %ax
|
||||
movw %ax, %es
|
||||
|
||||
movw $STAGE2_ADDR, %di # ES:DI destination, ES=0
|
||||
movw $1, %si # first LBA sector to read
|
||||
movb $STAGE2_SECTORS, %bl
|
||||
|
||||
.Lsector:
|
||||
.Lwait1:
|
||||
movw $0x1F7, %dx
|
||||
inb %dx, %al
|
||||
andb $0xC0, %al
|
||||
cmpb $0x40, %al
|
||||
jne .Lwait1
|
||||
|
||||
movw $0x1F2, %dx
|
||||
movb $1, %al
|
||||
outb %al, %dx # sector count = 1
|
||||
|
||||
incw %dx # 0x1F3
|
||||
movw %si, %ax
|
||||
outb %al, %dx # LBA [7:0]
|
||||
|
||||
incw %dx # 0x1F4
|
||||
xorb %al, %al
|
||||
outb %al, %dx # LBA [15:8]
|
||||
|
||||
incw %dx # 0x1F5
|
||||
outb %al, %dx # LBA [23:16]
|
||||
|
||||
incw %dx # 0x1F6
|
||||
movb $0xE0, %al
|
||||
outb %al, %dx # drive 0, LBA mode
|
||||
|
||||
incw %dx # 0x1F7
|
||||
movb $0x20, %al
|
||||
outb %al, %dx # READ SECTORS command
|
||||
|
||||
.Lwait2:
|
||||
inb %dx, %al # %dx still 0x1F7
|
||||
andb $0xC0, %al
|
||||
cmpb $0x40, %al
|
||||
jne .Lwait2
|
||||
|
||||
movw $256, %cx # 256 words = 512 bytes
|
||||
movw $0x1F0, %dx
|
||||
rep insw # reads into ES:DI, DI += 512 automatically
|
||||
|
||||
incw %si
|
||||
decb %bl
|
||||
jnz .Lsector
|
||||
|
||||
ljmp $0, $STAGE2_ADDR
|
||||
48
boot/cdboot.S
Normal file
48
boot/cdboot.S
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Stage 1 for optical media -- the El Torito "no emulation" boot sector.
|
||||
#
|
||||
# This exists because bootasm.S cannot boot a CD. It loads stage 2 by talking
|
||||
# straight to the primary IDE controller's ATA registers, and a CD is not an
|
||||
# ATA disk: it answers to the ATAPI packet interface instead, and under
|
||||
# El Torito it may not even be the drive at 0x1F0. Rather than teach a
|
||||
# 512-byte sector two protocols, optical media get their own first stage.
|
||||
#
|
||||
# It has almost nothing to do, because El Torito's "load segment / sector
|
||||
# count" fields do the reading for us: tools/mkiso.sh asks for
|
||||
# 1 + STAGE2_SECTORS virtual sectors at 0x7C00, so by the time this runs the
|
||||
# BIOS has already placed stage 2 at 0x7E00 -- exactly where bootasm.S would
|
||||
# have put it. The only real work is recording *which* medium we came from,
|
||||
# since stage2main.c has to read the kernel back with the matching protocol.
|
||||
#
|
||||
# The flag goes next to the verbose-boot flag, one byte past it, for the same
|
||||
# reason that one sits where it does: it is clear of the 256-byte VBE mode
|
||||
# info block at 0x30000 that stage2.S fills in a moment from now.
|
||||
|
||||
#define STAGE2_ADDR 0x7E00
|
||||
#define BOOT_MEDIA_FLAG_SEG 0x3000
|
||||
#define BOOT_MEDIA_FLAG_OFF 0x101
|
||||
#define BOOT_MEDIA_CD 1
|
||||
|
||||
.code16
|
||||
.global start
|
||||
start:
|
||||
cli
|
||||
xorw %ax, %ax
|
||||
movw %ax, %ds
|
||||
movw %ax, %ss
|
||||
movw $0x7c00, %sp
|
||||
|
||||
# "You were booted from a CD." stage2main.c reads this to decide between
|
||||
# ATA and ATAPI; without it the loader would read the kernel off whatever
|
||||
# happens to be sitting at 0x1F0, which on a CD boot is usually nothing.
|
||||
movw $BOOT_MEDIA_FLAG_SEG, %ax
|
||||
movw %ax, %es
|
||||
movw $BOOT_MEDIA_FLAG_OFF, %di
|
||||
movb $BOOT_MEDIA_CD, %al
|
||||
movb %al, %es:(%di)
|
||||
|
||||
# stage2.S expects zeroed segments and an inherited stack, same as it gets
|
||||
# from bootasm.S.
|
||||
xorw %ax, %ax
|
||||
movw %ax, %es
|
||||
|
||||
ljmp $0, $STAGE2_ADDR
|
||||
129
boot/stage2.S
Normal file
129
boot/stage2.S
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# Stage 2 — loaded by stage1 at 0x7E00, still in 16-bit real mode. Does the
|
||||
# VBE mode setup, A20 line, and real->protected mode switch that used to
|
||||
# live directly in the boot sector, then hands off to stage2main() (C) to
|
||||
# draw a splash and load the actual kernel. Not sector-size-constrained —
|
||||
# only bounded by STAGE2_SECTORS in bootasm.S / the Makefile.
|
||||
#include "../include/asm.h"
|
||||
#include "../include/memlayout.h"
|
||||
#include "../include/mmu.h"
|
||||
|
||||
.code16
|
||||
.global stage2start
|
||||
stage2start:
|
||||
# Segments are already zeroed by stage1; %sp is inherited too.
|
||||
|
||||
movw $bootmsg, %si
|
||||
call print16
|
||||
|
||||
# VBE Setup. Mode info block goes at 0x30000, not 0x9000 — stage2 itself
|
||||
# is loaded starting at 0x7E00 and, with the boot-menu/progress-bar code,
|
||||
# is now big enough to reach 0x9000, so writing the VBE block there would
|
||||
# clobber stage2's own tail end. 0x30000 is clear of stage2 (ends well
|
||||
# under 0xA000), the multiboot kernel-search buffer (0x10000-0x14000),
|
||||
# and the kernel's own load address (0x100000).
|
||||
movw $0x3000, %ax
|
||||
movw %ax, %es
|
||||
xorw %di, %di # ES:DI = 3000:0000 = flat 0x30000
|
||||
movw $0x0143, %cx
|
||||
movw $0x4F01, %ax
|
||||
int $0x10
|
||||
|
||||
cmpw $0x004F, %ax
|
||||
jne e820
|
||||
movw $0x4143, %bx
|
||||
movw $0x4F02, %ax
|
||||
int $0x10
|
||||
|
||||
# BIOS E820 memory map — the standard firmware interface every serious
|
||||
# loader (GRUB, Linux's own boot stub, the BSDs) uses to learn the real
|
||||
# RAM layout instead of guessing. Entry count goes to flat 0x30200,
|
||||
# 24-byte entries start at 0x30208 (both inside the 0x3000 segment the
|
||||
# VBE block already claimed; verbose flag sits at +0x100). Capped at 32
|
||||
# entries so a pathological BIOS can't run us past the buffer.
|
||||
e820:
|
||||
movw $0x3000, %ax
|
||||
movw %ax, %es
|
||||
xorl %ebx, %ebx # continuation cookie, 0 = first call
|
||||
movw $0x208, %di
|
||||
xorl %ebp, %ebp # entry count
|
||||
e820loop:
|
||||
movl $0xE820, %eax
|
||||
movl $0x534D4150, %edx # 'SMAP'
|
||||
movl $24, %ecx
|
||||
int $0x15
|
||||
jc e820done # carry = end of map (or unsupported BIOS)
|
||||
cmpl $0x534D4150, %eax
|
||||
jne e820done
|
||||
addw $24, %di
|
||||
incl %ebp
|
||||
cmpl $32, %ebp
|
||||
jae e820done
|
||||
testl %ebx, %ebx
|
||||
jnz e820loop
|
||||
e820done:
|
||||
movl %ebp, %es:(0x200)
|
||||
xorw %ax, %ax
|
||||
movw %ax, %es
|
||||
|
||||
seta20.1:
|
||||
inb $0x64, %al
|
||||
testb $0x2, %al
|
||||
jnz seta20.1
|
||||
movb $0xd1, %al
|
||||
outb %al, $0x64
|
||||
seta20.2:
|
||||
inb $0x64, %al
|
||||
testb $0x2, %al
|
||||
jnz seta20.2
|
||||
movb $0xdf, %al
|
||||
outb %al, $0x60
|
||||
|
||||
lgdt gdtdesc
|
||||
movl %cr0, %eax
|
||||
orl $CR0_PE, %eax
|
||||
movl %eax, %cr0
|
||||
|
||||
ljmp $(SEG_KCODE<<3), $start32
|
||||
|
||||
# Print null-terminated string at DS:SI via BIOS teletype (real mode only).
|
||||
# Clobbers AX, BX, SI.
|
||||
print16:
|
||||
.Lp16loop:
|
||||
lodsb
|
||||
testb %al, %al
|
||||
jz .Lp16done
|
||||
movb $0x0E, %ah
|
||||
movw $0x0007, %bx
|
||||
int $0x10
|
||||
jmp .Lp16loop
|
||||
.Lp16done:
|
||||
ret
|
||||
|
||||
bootmsg:
|
||||
.asciz "Schunix booting...\r\n"
|
||||
|
||||
.code32
|
||||
start32:
|
||||
movw $(SEG_KDATA<<3), %ax
|
||||
movw %ax, %ds
|
||||
movw %ax, %es
|
||||
movw %ax, %ss
|
||||
xorw %ax, %ax
|
||||
movw %ax, %fs
|
||||
movw %ax, %gs
|
||||
|
||||
movl $0x7c00, %esp
|
||||
call stage2main
|
||||
|
||||
spin:
|
||||
jmp spin
|
||||
|
||||
.p2align 2
|
||||
gdt:
|
||||
SEG_NULLASM
|
||||
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)
|
||||
SEG_ASM(STA_W, 0x0, 0xffffffff)
|
||||
|
||||
gdtdesc:
|
||||
.word (gdtdesc - gdt - 1)
|
||||
.long gdt
|
||||
878
boot/stage2main.c
Normal file
878
boot/stage2main.c
Normal file
|
|
@ -0,0 +1,878 @@
|
|||
#include "../include/types.h"
|
||||
#include "../include/x86.h"
|
||||
#include "../include/memlayout.h"
|
||||
#include "../include/font.h"
|
||||
|
||||
#define SECTSIZE 512
|
||||
|
||||
// Kernel starts right after stage2 on disk. The Makefile passes
|
||||
// -DSTAGE2_SECTORS so this, bootasm.S and stage2's padding cannot drift apart;
|
||||
// the value here is a fallback for reading the file on its own.
|
||||
#ifndef STAGE2_SECTORS
|
||||
#define STAGE2_SECTORS 48
|
||||
#endif
|
||||
|
||||
// stage2 is loaded at 0x7E00 and must not reach the multiboot kernel-search
|
||||
// buffer at 0x10000. Nothing checks this at runtime, and the failure it would
|
||||
// produce -- a kernel image partly overwritten by the loader that loaded it --
|
||||
// would be spectacularly hard to read backwards from.
|
||||
_Static_assert(0x7E00 + STAGE2_SECTORS * 512 <= 0x10000,
|
||||
"stage2 would overlap the kernel-search buffer at 0x10000");
|
||||
|
||||
// One palette for the whole system; see include/theme.h. The splash used to
|
||||
// use the panic screen's deep purple and gold, which meant a healthy boot was
|
||||
// dressed identically to a crash.
|
||||
#include "theme.h"
|
||||
// The boot logo, generated from Assets/ by tools/mklogo.py. Shared verbatim
|
||||
// with kernel/dev/vga.c so there is one mask, not two.
|
||||
#include "logo.h"
|
||||
|
||||
// VBE mode info block, written here by stage2.S's VBE call. NOT at 0x9000
|
||||
// (the classic single-stage bootloader's choice) — stage2's own code now
|
||||
// runs from 0x7E00 up to nearly 0x9000 once the boot-menu/progress-bar
|
||||
// logic is included, so a block at 0x9000 would get partially overwritten
|
||||
// by stage2's own tail end. 0x30000 is clear of stage2, the multiboot
|
||||
// kernel-search buffer (0x10000-0x14000), and the kernel's load address
|
||||
// (0x100000).
|
||||
#define VBE_INFO_ADDR 0x30000
|
||||
|
||||
// Fixed low-memory scratch byte, read by kernel/core/main.c's kmain() before
|
||||
// any other init: 1 if the user picked "verbose" from the boot menu. Placed
|
||||
// just past the VBE info block (which is at most 256 bytes), NOT at 0x7000
|
||||
// — kernel/core/main.c's startothers() already uses 0x7000 for the AP
|
||||
// (secondary CPU) boot trampoline. That's write-after-read from this flag's
|
||||
// perspective (verbose is read before startothers() runs), so it wasn't an
|
||||
// active bug, but reusing a documented-elsewhere address is exactly the
|
||||
// kind of thing that turns into one later.
|
||||
#define BOOT_VERBOSE_FLAG_ADDR (VBE_INFO_ADDR + 0x100)
|
||||
|
||||
// Set by boot/cdboot.S when we were booted from optical media. One byte past
|
||||
// the verbose flag, for the same reason that one sits where it does: clear of
|
||||
// the 256-byte VBE mode info block at VBE_INFO_ADDR.
|
||||
#define BOOT_MEDIA_FLAG_ADDR (VBE_INFO_ADDR + 0x101)
|
||||
#define BOOT_MEDIA_CD 1
|
||||
|
||||
// E820 memory map, collected by stage2.S in real mode (the same firmware
|
||||
// interface GRUB and Linux's boot stub use). Count at +0x200, then 24-byte
|
||||
// entries: {uint64 base; uint64 length; uint32 type; uint32 acpi_ext}.
|
||||
// The kernel can read the same addresses later; the bootloader uses it to
|
||||
// report detected RAM like any grown-up loader does.
|
||||
#define E820_COUNT_ADDR (VBE_INFO_ADDR + 0x200)
|
||||
#define E820_MAP_ADDR (VBE_INFO_ADDR + 0x208)
|
||||
#define E820_USABLE 1
|
||||
|
||||
// NOT include/types.h's uint64: that's `unsigned long`, which is 4 bytes
|
||||
// in this file's -m32 build and would silently halve the struct.
|
||||
typedef unsigned long long e820_u64;
|
||||
|
||||
struct e820entry {
|
||||
e820_u64 base;
|
||||
e820_u64 length;
|
||||
uint32 type;
|
||||
uint32 acpi_ext;
|
||||
} __attribute__((packed));
|
||||
|
||||
// Total usable RAM in MB according to the firmware's E820 map.
|
||||
static uint32
|
||||
e820_usable_mb(void)
|
||||
{
|
||||
uint32 count = *(uint32*)E820_COUNT_ADDR;
|
||||
struct e820entry *e = (struct e820entry*)E820_MAP_ADDR;
|
||||
e820_u64 total = 0;
|
||||
if (count > 32) return 0; // garbage count: BIOS didn't fill the map
|
||||
for (uint32 i = 0; i < count; i++)
|
||||
if (e[i].type == E820_USABLE)
|
||||
total += e[i].length;
|
||||
return (uint32)(total >> 20);
|
||||
}
|
||||
|
||||
// Minimal unsigned-to-decimal for the memory line; no printf down here.
|
||||
static char *
|
||||
utoa10(uint32 v, char *buf)
|
||||
{
|
||||
char tmp[12];
|
||||
int n = 0;
|
||||
do { tmp[n++] = '0' + v % 10; v /= 10; } while (v);
|
||||
int i = 0;
|
||||
while (n) buf[i++] = tmp[--n];
|
||||
buf[i] = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
struct mbheader {
|
||||
uint32 magic;
|
||||
uint32 flags;
|
||||
uint32 checksum;
|
||||
uint32 header_addr;
|
||||
uint32 load_addr;
|
||||
uint32 load_end_addr;
|
||||
uint32 bss_end_addr;
|
||||
uint32 entry_addr;
|
||||
};
|
||||
|
||||
void readseg(uchar*, uint, uint);
|
||||
static void draw_centered(int y, const char *s, uint32 color);
|
||||
static void draw_memory_line(int y, uint32 color);
|
||||
|
||||
// Which medium the kernel has to be read back from. cdboot.S sets the flag
|
||||
// this is loaded from; bootasm.S clears it, and 0 means "an ATA disk, the way
|
||||
// it has always been". The ATAPI machinery itself is down at the bottom of
|
||||
// the file next to readsect(), which is the only thing that uses it.
|
||||
#define CD_SECTSIZE 2048
|
||||
|
||||
static int g_from_cd;
|
||||
static int g_atapi_io = 0; // 0 until a drive is found
|
||||
static int g_atapi_slave = 0;
|
||||
static uint g_cd_base512 = 0; // where our boot image starts, in 512s
|
||||
static int atapi_find(void);
|
||||
static int atapi_boot_base(void);
|
||||
|
||||
// --- Framebuffer state ---------------------------------------------------
|
||||
// Standalone drawing (not the kernel's vga.c): paging isn't set up yet, so
|
||||
// the LFB physical address read out of the VBE mode info block is used
|
||||
// directly, no P2V translation needed. Only handles 32bpp (what QEMU's
|
||||
// std VGA actually returns for the mode this bootloader requests) —
|
||||
// anything else just skips all drawing rather than risk drawing garbage.
|
||||
|
||||
static uint8 *g_fb;
|
||||
static uint32 g_stride;
|
||||
static int g_width, g_height;
|
||||
static int g_have_fb;
|
||||
|
||||
static void
|
||||
fb_init(void)
|
||||
{
|
||||
g_stride = *(ushort*)(VBE_INFO_ADDR + 16);
|
||||
g_width = *(ushort*)(VBE_INFO_ADDR + 18);
|
||||
g_height = *(ushort*)(VBE_INFO_ADDR + 20);
|
||||
uchar bpp = *(uchar*)(VBE_INFO_ADDR + 25);
|
||||
uint32 fbaddr = *(uint32*)(VBE_INFO_ADDR + 40);
|
||||
g_fb = (uint8*)fbaddr;
|
||||
g_have_fb = (bpp == 32 && fbaddr && g_width && g_height);
|
||||
}
|
||||
|
||||
static void
|
||||
draw_pixel(int x, int y, uint32 color)
|
||||
{
|
||||
*(uint32*)(g_fb + y * g_stride + x * 4) = color;
|
||||
}
|
||||
|
||||
// --- Boot logo -------------------------------------------------------------
|
||||
//
|
||||
// include/logo.h is a 4-bit coverage mask generated from Assets/ by
|
||||
// tools/mklogo.py (`make logo`). It costs 8 KiB, which is most of the reason
|
||||
// STAGE2_SECTORS went from 24 to 48.
|
||||
//
|
||||
// Colour is SCH_FG, not SCH_ACCENT. The source art is a silver gradient and the
|
||||
// mask preserves its relative values, so compositing coverage straight to
|
||||
// brightness reproduces that gradient. The accent stays reserved for the single
|
||||
// rule under the wordmark -- one spot colour, per include/theme.h.
|
||||
static void
|
||||
draw_logo(int x0, int y0)
|
||||
{
|
||||
if (!g_have_fb)
|
||||
return;
|
||||
for (int dy = 0; dy < LOGO_H; dy++) {
|
||||
int y = y0 + dy;
|
||||
if (y < 0 || y >= g_height)
|
||||
continue;
|
||||
uint32 *row = (uint32*)(g_fb + y * g_stride);
|
||||
for (int dx = 0; dx < LOGO_W; dx++) {
|
||||
int x = x0 + dx;
|
||||
if (x < 0 || x >= g_width)
|
||||
continue;
|
||||
unsigned a = LOGO_INK(dy * LOGO_W + dx);
|
||||
// Skip empty pixels rather than painting them in the background colour.
|
||||
// The mask is square but the mark is a circle, so most of it is empty;
|
||||
// filling that would stamp a visible box onto the splash.
|
||||
if (a == 0)
|
||||
continue;
|
||||
if (a >= 255) {
|
||||
row[x] = SCH_FG;
|
||||
continue;
|
||||
}
|
||||
// Blend towards SCH_BG per channel. Integer only -- there is no FPU set
|
||||
// up at this point in boot, and no libgcc to fake one with.
|
||||
uint32 r = (((SCH_FG >> 16) & 0xFF) * a + ((SCH_BG >> 16) & 0xFF) * (255 - a)) / 255;
|
||||
uint32 g = (((SCH_FG >> 8) & 0xFF) * a + ((SCH_BG >> 8) & 0xFF) * (255 - a)) / 255;
|
||||
uint32 b = (((SCH_FG ) & 0xFF) * a + ((SCH_BG ) & 0xFF) * (255 - a)) / 255;
|
||||
row[x] = (r << 16) | (g << 8) | b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fill_rect(int x0, int y0, int w, int h, uint32 color)
|
||||
{
|
||||
// Compute each row's base pointer once instead of re-deriving it (via a
|
||||
// multiply + add) for every single pixel through draw_pixel() — this is
|
||||
// the full-screen splash fill, 480,000 pixels at 800x600, done at least
|
||||
// twice per boot.
|
||||
for (int y = y0; y < y0 + h; y++) {
|
||||
uint32 *row = (uint32*)(g_fb + y * g_stride) + x0;
|
||||
for (int x = 0; x < w; x++)
|
||||
row[x] = color;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
draw_char(int x, int y, uchar c, uint32 color, uint32 bg)
|
||||
{
|
||||
if (c > 127) return;
|
||||
for (int row = 0; row < 16; row++) {
|
||||
uchar bits = font_data[c][row];
|
||||
for (int col = 0; col < 8; col++)
|
||||
draw_pixel(x + col, y + row, (bits & (0x80 >> col)) ? color : bg);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
draw_string(int x, int y, const char *s, uint32 color, uint32 bg)
|
||||
{
|
||||
while (*s) {
|
||||
draw_char(x, y, *s, color, bg);
|
||||
x += 8;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
// Pads with trailing spaces (drawn in bg) so re-drawing a shorter string
|
||||
// over a longer one doesn't leave stale glyphs behind.
|
||||
static void
|
||||
draw_string_padded(int x, int y, const char *s, uint32 color, uint32 bg, int cols)
|
||||
{
|
||||
int n = 0;
|
||||
while (s[n]) { draw_char(x + n * 8, y, s[n], color, bg); n++; }
|
||||
while (n < cols) { draw_char(x + n * 8, y, ' ', color, bg); n++; }
|
||||
}
|
||||
|
||||
// --- Keyboard: raw PS/2 scancode polling, no interrupts, no BIOS calls --
|
||||
// (BIOS INT 16h only works in real mode; we're in protected mode by the
|
||||
// time any of this runs.) Scancode set 1: F8 make code 0x42, arrows are
|
||||
// extended (0xE0 prefix byte), Enter 0x1C, digits '1'-'3' are 0x02-0x04.
|
||||
|
||||
static int
|
||||
kbd_haskey(void)
|
||||
{
|
||||
return inb(0x64) & 1;
|
||||
}
|
||||
|
||||
static int
|
||||
kbd_readscancode(void)
|
||||
{
|
||||
return inb(0x60);
|
||||
}
|
||||
|
||||
// Blocking read of one logical key: plain scancodes pass through, extended
|
||||
// (0xE0-prefixed) scancodes come back OR'd with 0x100 so callers can tell
|
||||
// "up arrow" (0x148) apart from a plain byte that happens to equal 0x48.
|
||||
static int
|
||||
kbd_getkey(void)
|
||||
{
|
||||
int sc;
|
||||
for (;;) {
|
||||
while (!kbd_haskey())
|
||||
;
|
||||
sc = kbd_readscancode();
|
||||
// 0xE0 is the extended-key prefix, not a break code, even though it
|
||||
// also has bit 7 set — must be checked before the break-code test
|
||||
// below or every arrow/Home/End/etc. press gets silently swallowed.
|
||||
if (sc == 0xE0) {
|
||||
while (!kbd_haskey())
|
||||
;
|
||||
sc = kbd_readscancode();
|
||||
if (sc & 0x80) continue; // extended break code: ignore, try again
|
||||
return 0x100 | sc;
|
||||
}
|
||||
if (sc & 0x80) continue; // plain break code: ignore, try again
|
||||
return sc;
|
||||
}
|
||||
}
|
||||
|
||||
// Polls for up to `iterations` busy-loop spins, watching for F8's make
|
||||
// code. Returns 1 if seen, 0 on timeout. Ignores everything else so a
|
||||
// stray keypress during the splash doesn't do anything surprising.
|
||||
static int
|
||||
wait_for_f8(uint32 iterations)
|
||||
{
|
||||
for (volatile uint32 i = 0; i < iterations; i++) {
|
||||
if (kbd_haskey()) {
|
||||
int sc = kbd_readscancode();
|
||||
if (sc == 0x42)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- ATA drive presence (primary channel: master/slave) ------------------
|
||||
// Standard "floating bus" trick: select the drive, write a distinguishable
|
||||
// pattern to two data-path registers, and read it back. A missing drive
|
||||
// leaves the bus floating and the readback won't match.
|
||||
//
|
||||
// Only the primary channel (0x1F0) is checked. QEMU's IDE emulation
|
||||
// answers the same pattern test "successfully" on the secondary channel
|
||||
// (0x170) even with nothing attached to it — its floating-bus behavior
|
||||
// doesn't match real hardware closely enough to tell present from absent —
|
||||
// so showing it would just be confidently wrong. This project only ever
|
||||
// configures drives on the primary channel (-hda/-hdb) anyway.
|
||||
|
||||
static int
|
||||
ata_present(ushort iobase, int slave)
|
||||
{
|
||||
outb(iobase + 6, slave ? 0xB0 : 0xA0);
|
||||
inb(iobase + 7); inb(iobase + 7); inb(iobase + 7); inb(iobase + 7);
|
||||
outb(iobase + 2, 0xAA);
|
||||
outb(iobase + 3, 0x55);
|
||||
outb(iobase + 2, 0x55);
|
||||
outb(iobase + 3, 0xAA);
|
||||
outb(iobase + 2, 0xAA);
|
||||
outb(iobase + 3, 0x55);
|
||||
return inb(iobase + 2) == 0xAA && inb(iobase + 3) == 0x55;
|
||||
}
|
||||
|
||||
// --- Real-time load progress bar -----------------------------------------
|
||||
|
||||
#define BAR_WIDTH 400
|
||||
#define BAR_HEIGHT 18
|
||||
|
||||
// --- Splash layout -------------------------------------------------------
|
||||
//
|
||||
// Every vertical position on the splash derives from here. It used to be a
|
||||
// handful of independent offsets from the screen centre -- wordmark at cy-18,
|
||||
// rule at cy+6, bar at cy+30 -- which was fine while the tallest element was a
|
||||
// line of text. Adding a 128px mark on top of that stack pushed the group's
|
||||
// optical centre about 80px above the screen's, so the whole composition read
|
||||
// as top-heavy with a large void underneath.
|
||||
//
|
||||
// Now the group is measured and centred as a unit. The progress bar counts
|
||||
// towards that height even though it is only on screen during the kernel load:
|
||||
// if it did not, everything would jump upwards the moment the bar appeared.
|
||||
#define SPLASH_MARK_GAP 24 // mark to wordmark
|
||||
#define SPLASH_TEXT_H 16 // one line of the 8x16 font
|
||||
#define SPLASH_RULE_GAP 8 // wordmark to rule
|
||||
#define SPLASH_RULE_H 2
|
||||
#define SPLASH_BAR_GAP 30 // rule to bar; the bar's label lives in here
|
||||
|
||||
#define SPLASH_GROUP_H (LOGO_H + SPLASH_MARK_GAP + SPLASH_TEXT_H + \
|
||||
SPLASH_RULE_GAP + SPLASH_RULE_H + SPLASH_BAR_GAP + BAR_HEIGHT)
|
||||
|
||||
#define SPLASH_MARK_Y ((g_height - SPLASH_GROUP_H) / 2)
|
||||
#define SPLASH_TEXT_Y (SPLASH_MARK_Y + LOGO_H + SPLASH_MARK_GAP)
|
||||
#define SPLASH_RULE_Y (SPLASH_TEXT_Y + SPLASH_TEXT_H + SPLASH_RULE_GAP)
|
||||
#define SPLASH_BAR_Y (SPLASH_RULE_Y + SPLASH_RULE_H + SPLASH_BAR_GAP)
|
||||
|
||||
static uint32 g_load_total, g_load_done;
|
||||
static int g_progress_active;
|
||||
static int g_bar_x, g_bar_y;
|
||||
static int g_last_pct;
|
||||
|
||||
static void
|
||||
progress_label(const char *label)
|
||||
{
|
||||
draw_string_padded(g_bar_x, g_bar_y - 22, label, SCH_ACCENT, SCH_BG, BAR_WIDTH / 8);
|
||||
}
|
||||
|
||||
// Border never changes once drawn — separated out so it's only ever drawn
|
||||
// once per bar instead of being redrawn on every single tick.
|
||||
static void
|
||||
progress_border(void)
|
||||
{
|
||||
for (int x = g_bar_x; x < g_bar_x + BAR_WIDTH; x++) {
|
||||
draw_pixel(x, g_bar_y, SCH_FG);
|
||||
draw_pixel(x, g_bar_y + BAR_HEIGHT, SCH_FG);
|
||||
}
|
||||
for (int y = g_bar_y; y <= g_bar_y + BAR_HEIGHT; y++) {
|
||||
draw_pixel(g_bar_x, y, SCH_FG);
|
||||
draw_pixel(g_bar_x + BAR_WIDTH, y, SCH_FG);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
progress_fill(int pct)
|
||||
{
|
||||
if (pct > 100) pct = 100;
|
||||
int fillw = (BAR_WIDTH - 4) * pct / 100;
|
||||
for (int y = g_bar_y + 2; y < g_bar_y + BAR_HEIGHT - 1; y++)
|
||||
for (int x = g_bar_x + 2; x < g_bar_x + BAR_WIDTH - 2; x++)
|
||||
draw_pixel(x, y, (x - g_bar_x - 2 < fillw) ? SCH_ACCENT : SCH_BG);
|
||||
}
|
||||
|
||||
static void
|
||||
progress_start(const char *label, uint32 total)
|
||||
{
|
||||
if (!g_have_fb) return;
|
||||
g_bar_x = (g_width - BAR_WIDTH) / 2;
|
||||
g_bar_y = SPLASH_BAR_Y;
|
||||
g_load_total = total;
|
||||
g_load_done = 0;
|
||||
g_last_pct = -1;
|
||||
g_progress_active = 1;
|
||||
progress_label(label);
|
||||
progress_border();
|
||||
progress_fill(0);
|
||||
g_last_pct = 0;
|
||||
}
|
||||
|
||||
static void
|
||||
progress_tick(uint32 n)
|
||||
{
|
||||
if (!g_have_fb || !g_progress_active) return;
|
||||
g_load_done += n;
|
||||
// Deliberately all 32-bit. The uint64 cast this used to carry was there to
|
||||
// stop `done * 100` overflowing, but a 64-bit divide in 32-bit code calls
|
||||
// libgcc's __udivdi3, which a freestanding bootloader does not link -- it
|
||||
// only ever built because uint64 was accidentally 32 bits wide here.
|
||||
//
|
||||
// Scaling both sides to KiB first keeps the multiply inside 32 bits for any
|
||||
// image up to 4 GiB, which is comfortably more than a kernel will ever be.
|
||||
uint32 done_k = g_load_done >> 10;
|
||||
uint32 total_k = g_load_total >> 10;
|
||||
uint32 pct = total_k ? (done_k * 100) / total_k : 0;
|
||||
if (pct > 100) pct = 100;
|
||||
// A full-bar redraw is a few thousand pixel writes; without this, a
|
||||
// ~330KB kernel image redraws it ~650 times (once per 512-byte sector)
|
||||
// instead of at most 100 (once per percentage point) — the single
|
||||
// biggest chunk of the bootloader's own wall-clock time.
|
||||
if ((int)pct == g_last_pct) return;
|
||||
g_last_pct = (int)pct;
|
||||
progress_fill((int)pct);
|
||||
}
|
||||
|
||||
// --- Boot menu -------------------------------------------------------
|
||||
|
||||
static void
|
||||
reboot_to_firmware(void)
|
||||
{
|
||||
if (g_have_fb) {
|
||||
fill_rect(0, 0, g_width, g_height, SCH_BG);
|
||||
draw_string((g_width - 26 * 8) / 2, g_height / 2 - 8,
|
||||
"Rebooting to firmware...", SCH_ACCENT, SCH_BG);
|
||||
}
|
||||
outb(0x64, 0xFE); // pulse the keyboard controller's reset line
|
||||
for (;;)
|
||||
;
|
||||
}
|
||||
|
||||
static void
|
||||
show_drive_line(int row, const char *label, int present)
|
||||
{
|
||||
draw_string(g_width / 2 - 160, 340 + row * 20, label, SCH_FG, SCH_BG);
|
||||
draw_string(g_width / 2 + 20, 340 + row * 20,
|
||||
present ? "present" : "not present",
|
||||
present ? SCH_ACCENT : SCH_DIM, SCH_BG);
|
||||
}
|
||||
|
||||
// Returns 1 for "boot verbose", 0 for "boot normal". Only returns at all
|
||||
// once the user picks a boot option; "reboot" never returns.
|
||||
static int
|
||||
boot_menu(void)
|
||||
{
|
||||
const char *items[] = {
|
||||
"Boot Schunix",
|
||||
"Boot Schunix (verbose)",
|
||||
"Reboot to firmware setup",
|
||||
};
|
||||
int nitems = 3;
|
||||
int selected = 0;
|
||||
|
||||
int hda = ata_present(0x1F0, 0);
|
||||
int hdb = ata_present(0x1F0, 1);
|
||||
|
||||
for (;;) {
|
||||
fill_rect(0, 0, g_width, g_height, SCH_BG);
|
||||
draw_centered(60, "Schunix Boot Manager", SCH_ACCENT);
|
||||
draw_memory_line(84, SCH_DIM);
|
||||
|
||||
for (int i = 0; i < nitems; i++) {
|
||||
uint32 fg = (i == selected) ? SCH_ACCENT : SCH_FG;
|
||||
draw_string(g_width / 2 - 140, 140 + i * 24, (i == selected) ? "> " : " ", fg, SCH_BG);
|
||||
draw_string(g_width / 2 - 124, 140 + i * 24, items[i], fg, SCH_BG);
|
||||
}
|
||||
|
||||
draw_string(g_width / 2 - 160, 300, "Detected drives:", SCH_FG, SCH_BG);
|
||||
show_drive_line(0, "Primary Master (hda)", hda);
|
||||
show_drive_line(1, "Primary Slave (hdb)", hdb);
|
||||
|
||||
draw_string(g_width / 2 - 168, g_height - 40,
|
||||
"Up/Down to choose, Enter to select", SCH_DIM, SCH_BG);
|
||||
|
||||
int key = kbd_getkey();
|
||||
if (key == 0x148) selected = (selected + nitems - 1) % nitems; // up
|
||||
else if (key == 0x150) selected = (selected + 1) % nitems; // down
|
||||
else if (key == 0x02) selected = 0; // '1'
|
||||
else if (key == 0x03) selected = 1; // '2'
|
||||
else if (key == 0x04) { reboot_to_firmware(); } // '3'
|
||||
else if (key == 0x1C) { // Enter
|
||||
if (selected == 2)
|
||||
reboot_to_firmware();
|
||||
return selected == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Centered one-liner helper; strlen computed here so callers stop
|
||||
// hand-counting characters (three of them had it wrong at some point).
|
||||
static void
|
||||
draw_centered(int y, const char *s, uint32 color)
|
||||
{
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
draw_string((g_width - n * 8) / 2, y, s, color, SCH_BG);
|
||||
}
|
||||
|
||||
// "Memory: NNNN MB" — assembled by hand, no printf in the bootloader.
|
||||
static void
|
||||
draw_memory_line(int y, uint32 color)
|
||||
{
|
||||
char line[32];
|
||||
char num[12];
|
||||
int i = 0;
|
||||
const char *pfx = "Memory: ";
|
||||
while (*pfx) line[i++] = *pfx++;
|
||||
utoa10(e820_usable_mb(), num);
|
||||
for (int j = 0; num[j]; j++) line[i++] = num[j];
|
||||
const char *sfx = " MB";
|
||||
while (*sfx) line[i++] = *sfx++;
|
||||
line[i] = 0;
|
||||
draw_centered(y, line, color);
|
||||
}
|
||||
|
||||
static void
|
||||
splash(void)
|
||||
{
|
||||
if (!g_have_fb) return;
|
||||
|
||||
fill_rect(0, 0, g_width, g_height, SCH_BG);
|
||||
|
||||
// The product, not the loader.
|
||||
//
|
||||
// This used to read "Schunix Boot Manager / version 1.0 / Memory: 1023 MB /
|
||||
// Press F8 for boot menu...", centred, in gold on purple. Every part of that
|
||||
// is something a system shows its developer rather than its user: the name
|
||||
// of the component doing the work, its version, and a memory figure nobody
|
||||
// asked for. No shipping OS names its bootloader on the splash.
|
||||
//
|
||||
// What is left is the name, a rule in the accent colour, and the one hint
|
||||
// that is actually actionable -- moved to the bottom of the screen, where a
|
||||
// hint belongs, rather than sitting under the title as though it were part
|
||||
// of the identity.
|
||||
// The mark, drawn here in the loader rather than by the kernel for a timing
|
||||
// reason: the kernel does not reach vga_init() until the last few hundred
|
||||
// milliseconds of a ~2.8s boot, so a kernel-drawn logo appeared for about one
|
||||
// frame before console_takeover() wiped it. Here it is up for the whole boot.
|
||||
draw_logo((g_width - LOGO_W) / 2, SPLASH_MARK_Y);
|
||||
|
||||
draw_centered(SPLASH_TEXT_Y, "S C H U N I X", SCH_FG);
|
||||
|
||||
// A 120x2 rule, centred, in the accent -- slightly wider than the 104px
|
||||
// wordmark so it reads as an underline rather than a strikethrough. Two
|
||||
// pixels rather than one: a single-pixel line looks like a rendering
|
||||
// artefact at this size.
|
||||
fill_rect((g_width - 120) / 2, SPLASH_RULE_Y, 120, SPLASH_RULE_H, SCH_ACCENT);
|
||||
|
||||
draw_centered(g_height - 40, "Press F8 for boot options", SCH_DIM);
|
||||
}
|
||||
|
||||
void
|
||||
stage2main(void)
|
||||
{
|
||||
struct mbheader *hdr;
|
||||
uint32 *x = (uint32*) 0x10000;
|
||||
uint n;
|
||||
int verbose = 0;
|
||||
|
||||
fb_init();
|
||||
|
||||
// Sort out where the kernel is coming from before anything is drawn, so a
|
||||
// disc we cannot read says so on the splash instead of failing silently
|
||||
// several seconds later in the middle of the progress bar.
|
||||
if (*(uchar*)BOOT_MEDIA_FLAG_ADDR == BOOT_MEDIA_CD) {
|
||||
if (atapi_find())
|
||||
g_cd_base512 = atapi_boot_base() * (CD_SECTSIZE / SECTSIZE);
|
||||
g_from_cd = (g_atapi_io != 0 && g_cd_base512 != 0);
|
||||
}
|
||||
|
||||
splash();
|
||||
|
||||
// ~0.6s at QEMU TCG speeds (measured), watching for F8. Shortened from
|
||||
// ~1s (5000000) — still enough time to react to, but it's flat overhead
|
||||
// on every single boot whether or not anyone wants the menu.
|
||||
if (wait_for_f8(3000000))
|
||||
verbose = boot_menu();
|
||||
|
||||
*(uchar*)BOOT_VERBOSE_FLAG_ADDR = verbose;
|
||||
|
||||
if (g_have_fb) {
|
||||
fill_rect(0, 0, g_width, g_height, SCH_BG);
|
||||
draw_string((g_width - 7 * 8) / 2, g_height / 2 - 50, "SCHUNIX", SCH_ACCENT, SCH_BG);
|
||||
}
|
||||
|
||||
// Booted from a disc we then could not read. Falling through would send
|
||||
// readseg() at the primary ATA channel, which on a CD-only machine is empty
|
||||
// -- so it would grind through a bounded wait per sector and then jump into
|
||||
// whatever the uninitialised buffer happened to hold. Stopping with a
|
||||
// legible reason is the only useful thing left to do. This is drawn after
|
||||
// the clear above on purpose: an earlier version put it on the splash,
|
||||
// where it was wiped a moment later and nobody ever saw it.
|
||||
if (*(uchar*)BOOT_MEDIA_FLAG_ADDR == BOOT_MEDIA_CD && !g_from_cd) {
|
||||
draw_centered(g_height / 2, "No readable boot disc found.", SCH_ACCENT);
|
||||
draw_centered(g_height / 2 + 20, "The drive did not answer ATAPI.", SCH_DIM);
|
||||
for (;;)
|
||||
asm volatile("hlt");
|
||||
}
|
||||
|
||||
if (g_have_fb)
|
||||
progress_start("Detecting kernel...", 16384);
|
||||
|
||||
// Read 32 sectors (16KB) looking for the multiboot header.
|
||||
readseg((uchar*)x, 16384, 0);
|
||||
|
||||
for (n = 0; n < 16384/4; n++) {
|
||||
if (x[n] == 0x1BADB002) {
|
||||
if ((x[n] + x[n+1] + x[n+2]) == 0) {
|
||||
hdr = (struct mbheader *) (x + n);
|
||||
|
||||
if (g_have_fb)
|
||||
progress_start("Loading kernel...", hdr->load_end_addr - hdr->load_addr);
|
||||
|
||||
// Load the actual kernel segments
|
||||
readseg((uchar*) hdr->load_addr,
|
||||
hdr->load_end_addr - hdr->load_addr,
|
||||
(n * 4) - (hdr->header_addr - hdr->load_addr));
|
||||
|
||||
// Zero out BSS
|
||||
if (hdr->bss_end_addr > hdr->load_end_addr)
|
||||
stosb((void*) hdr->load_end_addr, 0, hdr->bss_end_addr - hdr->load_end_addr);
|
||||
|
||||
if (g_have_fb)
|
||||
progress_label("Starting Schunix...");
|
||||
|
||||
// Jump to kernel entry. NOT a normal C call: kernel/arch/x86_64/
|
||||
// entry.S's mboot_entry reads its "argument" (the framebuffer
|
||||
// physical address) out of EAX directly, not off the stack — a
|
||||
// multiboot-style raw-register handoff, not cdecl. A plain
|
||||
// `((void(*)(uint32))entry)(fbaddr)` pushes fbaddr on the stack
|
||||
// per cdecl and leaves EAX as whatever happened to be lying around;
|
||||
// it only ever "worked" because simpler bootmain.c code coincidentally
|
||||
// left the right value in EAX as a side effect of its own codegen.
|
||||
// Adding the boot-menu/progress-bar code changed that codegen and
|
||||
// broke it silently (kernel received garbage instead of the real
|
||||
// LFB address). Pin it down explicitly instead of hoping.
|
||||
{
|
||||
uint32 fbaddr = *(uint32*)(VBE_INFO_ADDR + 40);
|
||||
uint32 entry = hdr->entry_addr;
|
||||
asm volatile("jmp *%0" : : "r"(entry), "a"(fbaddr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bounded, because there might not be a disk there at all.
|
||||
//
|
||||
// This used to be `while((inb(0x1F7) & 0xC0) != 0x40);`. On a machine with no
|
||||
// ATA drive on the primary channel the status register reads 0xFF forever and
|
||||
// the loader hung on a splash screen with no message and no way to tell what
|
||||
// had gone wrong -- which is exactly what happened the first time a CD boot
|
||||
// fell back to this path. A bounded wait turns that into a failed read, and
|
||||
// stage2main() says so on screen.
|
||||
void waitdisk(void) {
|
||||
for (int i = 0; i < 4000000; i++)
|
||||
if ((inb(0x1F7) & 0xC0) == 0x40)
|
||||
return;
|
||||
}
|
||||
|
||||
void readsect_ata(void *dst, uint offset) {
|
||||
waitdisk();
|
||||
outb(0x1F2, 1);
|
||||
outb(0x1F3, offset);
|
||||
outb(0x1F4, offset >> 8);
|
||||
outb(0x1F5, offset >> 16);
|
||||
outb(0x1F6, (offset >> 24) | 0xE0);
|
||||
outb(0x1F7, 0x20);
|
||||
waitdisk();
|
||||
insl(0x1F0, dst, SECTSIZE/4);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ATAPI -- reading the kernel back off a CD.
|
||||
//
|
||||
// An optical drive does not answer the ATA commands above. It speaks the
|
||||
// packet interface: you hand it a SCSI command descriptor block through the
|
||||
// data port and it hands back 2048-byte logical blocks. The register file and
|
||||
// the PIO loop are otherwise the same, which is why this lives here rather
|
||||
// than forcing the loader back into real mode to use INT 13h -- by the time
|
||||
// the kernel is read, stage2.S has long since switched to protected mode and
|
||||
// the BIOS is gone.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void bcopy(void *dst, const void *src, uint n) {
|
||||
uchar *d = dst;
|
||||
const uchar *s = src;
|
||||
while (n--) *d++ = *s++;
|
||||
}
|
||||
|
||||
// A channel has to be reset before its signature means anything.
|
||||
//
|
||||
// ATAPI devices report themselves by leaving 0x14 in LBA-mid and 0xEB in
|
||||
// LBA-high -- but only until something issues a command, after which those
|
||||
// registers hold that command's byte count instead. The BIOS has just
|
||||
// finished reading this very disc in order to load us, so on a CD boot the
|
||||
// signature is *always* stale by the time we look. Resetting the channel is
|
||||
// what puts it back.
|
||||
static void atapi_reset(int io) {
|
||||
int ctrl = io + 0x206; // 0x1F0 -> 0x3F6, 0x170 -> 0x376
|
||||
|
||||
outb(ctrl, 0x04); // SRST
|
||||
for (int i = 0; i < 10000; i++) // hold it well past the required 5us
|
||||
inb(ctrl);
|
||||
outb(ctrl, 0x00);
|
||||
|
||||
for (int i = 0; i < 1000000; i++) // bounded: an empty channel reads 0xFF
|
||||
if (!(inb(io + 7) & 0x80)) // and would otherwise spin forever
|
||||
break;
|
||||
}
|
||||
|
||||
static int atapi_find(void) {
|
||||
static const int bases[2] = { 0x1F0, 0x170 };
|
||||
|
||||
for (int b = 0; b < 2; b++) {
|
||||
atapi_reset(bases[b]);
|
||||
for (int s = 0; s < 2; s++) {
|
||||
outb(bases[b] + 6, s ? 0xB0 : 0xA0);
|
||||
// Four throwaway status reads: the standard 400ns settle after a drive
|
||||
// select, before the register file is meaningful.
|
||||
inb(bases[b] + 7); inb(bases[b] + 7);
|
||||
inb(bases[b] + 7); inb(bases[b] + 7);
|
||||
if (inb(bases[b] + 4) == 0x14 && inb(bases[b] + 5) == 0xEB) {
|
||||
g_atapi_io = bases[b];
|
||||
g_atapi_slave = s;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// One 2048-byte logical block. Returns 0 on success.
|
||||
static int atapi_read_block(uint lba, void *dst) {
|
||||
int io = g_atapi_io;
|
||||
uchar cdb[12];
|
||||
int i;
|
||||
|
||||
outb(io + 6, g_atapi_slave ? 0xB0 : 0xA0);
|
||||
inb(io + 7); inb(io + 7); inb(io + 7); inb(io + 7);
|
||||
|
||||
outb(io + 1, 0); // features: PIO, no DMA
|
||||
outb(io + 4, CD_SECTSIZE & 0xFF); // how much we are willing to take
|
||||
outb(io + 5, CD_SECTSIZE >> 8);
|
||||
outb(io + 7, 0xA0); // PACKET
|
||||
|
||||
// Wait for the drive to ask for the command packet: BSY clear, DRQ set.
|
||||
for (i = 0; i < 0x1000000; i++) {
|
||||
uchar st = inb(io + 7);
|
||||
if (st & 0x01) return -1; // ERR
|
||||
if (!(st & 0x80) && (st & 0x08)) break;
|
||||
}
|
||||
if (i == 0x1000000) return -1;
|
||||
|
||||
// READ(12). Big-endian LBA and transfer length, because SCSI is.
|
||||
for (i = 0; i < 12; i++) cdb[i] = 0;
|
||||
cdb[0] = 0xA8;
|
||||
cdb[2] = (uchar)(lba >> 24);
|
||||
cdb[3] = (uchar)(lba >> 16);
|
||||
cdb[4] = (uchar)(lba >> 8);
|
||||
cdb[5] = (uchar)lba;
|
||||
cdb[9] = 1; // one block
|
||||
outsl(io, cdb, 3); // 12 bytes = 3 dwords
|
||||
|
||||
// Wait for the data itself.
|
||||
for (i = 0; i < 0x1000000; i++) {
|
||||
uchar st = inb(io + 7);
|
||||
if (st & 0x01) return -1;
|
||||
if (!(st & 0x80) && (st & 0x08)) break;
|
||||
}
|
||||
if (i == 0x1000000) return -1;
|
||||
|
||||
insl(io, dst, CD_SECTSIZE / 4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Where our boot image sits on the disc.
|
||||
//
|
||||
// Sector numbers on a CD are absolute, but readseg() counts from the start of
|
||||
// the boot image -- which on a hard disk is the start of the disk and on a CD
|
||||
// is wherever the ISO's filesystem happened to leave room. El Torito writes
|
||||
// that address down, so we read it rather than guess:
|
||||
//
|
||||
// LBA 17 Boot Record Volume Descriptor, holding a pointer at offset 0x47
|
||||
// catalog validation entry (32 bytes), then the default entry, whose
|
||||
// "Load RBA" at offset 8 is the boot image's LBA
|
||||
//
|
||||
// Returns 0 if the disc does not look like something we booted from.
|
||||
static int atapi_boot_base(void) {
|
||||
static uchar buf[CD_SECTSIZE];
|
||||
uint catalog, image;
|
||||
|
||||
if (atapi_read_block(17, buf) != 0)
|
||||
return 0;
|
||||
if (buf[0] != 0 || buf[1] != 'C' || buf[2] != 'D' || buf[3] != '0' ||
|
||||
buf[4] != '0' || buf[5] != '1')
|
||||
return 0;
|
||||
|
||||
catalog = (uint)buf[0x47] | ((uint)buf[0x48] << 8) |
|
||||
((uint)buf[0x49] << 16) | ((uint)buf[0x4A] << 24);
|
||||
if (catalog == 0)
|
||||
return 0;
|
||||
|
||||
if (atapi_read_block(catalog, buf) != 0)
|
||||
return 0;
|
||||
if (buf[32] != 0x88) // default entry not marked bootable
|
||||
return 0;
|
||||
|
||||
image = (uint)buf[40] | ((uint)buf[41] << 8) |
|
||||
((uint)buf[42] << 16) | ((uint)buf[43] << 24);
|
||||
return image;
|
||||
}
|
||||
|
||||
// A CD block holds four 512-byte sectors, and readseg() asks for them one at
|
||||
// a time and in order -- so caching the last block turns four reads into one.
|
||||
// Without it every byte of the kernel would be fetched four times.
|
||||
static uchar g_cd_cache[CD_SECTSIZE];
|
||||
static uint g_cd_cached = 0xFFFFFFFF;
|
||||
|
||||
static void readsect_atapi(void *dst, uint offset) {
|
||||
uint abs = offset + g_cd_base512;
|
||||
uint blk = abs / (CD_SECTSIZE / SECTSIZE);
|
||||
uint sub = abs % (CD_SECTSIZE / SECTSIZE);
|
||||
|
||||
if (blk != g_cd_cached) {
|
||||
if (atapi_read_block(blk, g_cd_cache) != 0) {
|
||||
// Nothing useful to do here: there is no console yet and no way back.
|
||||
// Leaving the buffer alone means the multiboot search simply fails to
|
||||
// match, which stops with the splash up rather than jumping to garbage.
|
||||
return;
|
||||
}
|
||||
g_cd_cached = blk;
|
||||
}
|
||||
bcopy(dst, g_cd_cache + sub * SECTSIZE, SECTSIZE);
|
||||
}
|
||||
|
||||
void readsect(void *dst, uint offset) {
|
||||
if (g_from_cd)
|
||||
readsect_atapi(dst, offset);
|
||||
else
|
||||
readsect_ata(dst, offset);
|
||||
progress_tick(SECTSIZE);
|
||||
}
|
||||
|
||||
void readseg(uchar* pa, uint count, uint offset) {
|
||||
uchar* epa = pa + count;
|
||||
pa -= offset % SECTSIZE;
|
||||
// Kernel starts right after stage2 (1 boot sector + STAGE2_SECTORS
|
||||
// stage2 sectors), not immediately after just the boot sector.
|
||||
offset = (offset / SECTSIZE) + (1 + STAGE2_SECTORS);
|
||||
for(; pa < epa; pa += SECTSIZE, offset++)
|
||||
readsect(pa, offset);
|
||||
}
|
||||
219
cmd/fbinfo/main.c
Normal file
219
cmd/fbinfo/main.c
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
// fbinfo -- ask /dev/fb0 what it is, and optionally draw on it.
|
||||
//
|
||||
// This exists because the framebuffer is the one device whose correctness you
|
||||
// cannot check by reading kernel source: the numbers have to come back from
|
||||
// the hardware the bootloader actually programmed. Three places in the kernel
|
||||
// used to assume the framebuffer lived at 0xfd000000 and was 800x600x32; they
|
||||
// agreed with QEMU's stdvga by luck, and nothing would have told us when the
|
||||
// luck ran out. Now `fbinfo` would.
|
||||
//
|
||||
// fbinfo print the geometry
|
||||
// fbinfo fill draw a gradient over the whole screen through mmap
|
||||
// fbinfo bars draw colour bars through write(), no mmap involved
|
||||
//
|
||||
// The two drawing modes take different paths on purpose. If `fill` works and
|
||||
// `bars` does not, the mapping is right and the device's read/write is wrong;
|
||||
// if it is the other way round, the reverse. Having both makes a failure say
|
||||
// which half broke.
|
||||
|
||||
#include "types.h"
|
||||
#include "user.h"
|
||||
#include "fcntl.h"
|
||||
#include "fb.h"
|
||||
|
||||
#define PROT_READ 0x1
|
||||
#define PROT_WRITE 0x2
|
||||
#define MAP_SHARED 0x01
|
||||
|
||||
static void
|
||||
puts(const char *s)
|
||||
{
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
write(1, (void*)s, n);
|
||||
}
|
||||
|
||||
// Unsigned decimal. No printf in lib/, and pulling one in for six numbers
|
||||
// would be a worse trade than fourteen lines.
|
||||
static void
|
||||
putu(unsigned long v)
|
||||
{
|
||||
char buf[24];
|
||||
int i = 0;
|
||||
|
||||
if (v == 0) {
|
||||
write(1, "0", 1);
|
||||
return;
|
||||
}
|
||||
while (v > 0 && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i > 0) {
|
||||
i--;
|
||||
write(1, &buf[i], 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
puthex(unsigned long v)
|
||||
{
|
||||
static const char d[] = "0123456789abcdef";
|
||||
char buf[16];
|
||||
int i = 0;
|
||||
|
||||
write(1, "0x", 2);
|
||||
if (v == 0) {
|
||||
write(1, "0", 1);
|
||||
return;
|
||||
}
|
||||
while (v > 0 && i < (int)sizeof(buf)) {
|
||||
buf[i++] = d[v & 0xf];
|
||||
v >>= 4;
|
||||
}
|
||||
while (i > 0) {
|
||||
i--;
|
||||
write(1, &buf[i], 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Name, then spaces out to a fixed column. Padding inside each string
|
||||
// literal is what produced "bits_per_pixel32": the longest name happened to
|
||||
// be exactly as wide as the column, so it got no separator at all and the
|
||||
// value ran straight into it.
|
||||
static void
|
||||
label(const char *name)
|
||||
{
|
||||
int n = 0;
|
||||
while (name[n]) n++;
|
||||
write(1, (void*)name, n);
|
||||
while (n < 16) { write(1, " ", 1); n++; }
|
||||
}
|
||||
|
||||
static void
|
||||
field(const char *name, unsigned long v)
|
||||
{
|
||||
label(name);
|
||||
putu(v);
|
||||
puts("\n");
|
||||
}
|
||||
|
||||
static void
|
||||
chanline(const char *name, struct fb_bitfield *b)
|
||||
{
|
||||
label(name);
|
||||
puts("offset ");
|
||||
putu(b->offset);
|
||||
puts(", length ");
|
||||
putu(b->length);
|
||||
if (b->length == 0)
|
||||
puts(" (absent)");
|
||||
puts("\n");
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
struct fb_var_screeninfo var;
|
||||
struct fb_fix_screeninfo fix;
|
||||
int fd;
|
||||
|
||||
fd = open("/dev/fb0", O_RDWR);
|
||||
if (fd < 0) {
|
||||
puts("fbinfo: cannot open /dev/fb0\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ioctl(fd, FBIOGET_VSCREENINFO, &var) < 0) {
|
||||
puts("fbinfo: FBIOGET_VSCREENINFO failed\n");
|
||||
return 1;
|
||||
}
|
||||
if (ioctl(fd, FBIOGET_FSCREENINFO, &fix) < 0) {
|
||||
puts("fbinfo: FBIOGET_FSCREENINFO failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (argc < 2) {
|
||||
label("id");
|
||||
puts(fix.id);
|
||||
puts("\n");
|
||||
field("xres", var.xres);
|
||||
field("yres", var.yres);
|
||||
field("bits_per_pixel", var.bits_per_pixel);
|
||||
label("smem_start");
|
||||
puthex(fix.smem_start);
|
||||
puts("\n");
|
||||
field("smem_len", fix.smem_len);
|
||||
field("line_length", fix.line_length);
|
||||
|
||||
// The stride is the number worth staring at. A card is free to pad
|
||||
// each row, and a program that computes row starts as
|
||||
// y * xres * bytes_per_pixel instead of y * line_length draws a
|
||||
// picture that shears progressively down the screen.
|
||||
if (fix.line_length != var.xres * (var.bits_per_pixel / 8)) {
|
||||
label("note");
|
||||
puts("stride is padded; rows are not xres*bpp/8 apart\n");
|
||||
}
|
||||
|
||||
chanline("red", &var.red);
|
||||
chanline("green", &var.green);
|
||||
chanline("blue", &var.blue);
|
||||
chanline("transp", &var.transp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argv[1][0] == 'f') { // fill
|
||||
unsigned char *fb = mmap(0, fix.smem_len,
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (fb == 0 || (long)fb < 0) {
|
||||
puts("fbinfo: mmap failed\n");
|
||||
return 1;
|
||||
}
|
||||
unsigned bpp = var.bits_per_pixel / 8;
|
||||
for (unsigned y = 0; y < var.yres; y++) {
|
||||
unsigned char *row = fb + (unsigned long)y * fix.line_length;
|
||||
for (unsigned x = 0; x < var.xres; x++) {
|
||||
unsigned r = (x * 255) / (var.xres ? var.xres : 1);
|
||||
unsigned g = (y * 255) / (var.yres ? var.yres : 1);
|
||||
unsigned long px = ((unsigned long)r << var.red.offset) |
|
||||
((unsigned long)g << var.green.offset) |
|
||||
((unsigned long)0x80 << var.blue.offset);
|
||||
unsigned char *p = row + (unsigned long)x * bpp;
|
||||
for (unsigned b = 0; b < bpp; b++)
|
||||
p[b] = (unsigned char)(px >> (b * 8));
|
||||
}
|
||||
}
|
||||
puts("fbinfo: gradient drawn through mmap\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (argv[1][0] == 'b') { // bars
|
||||
// One scanline at a time through write(). No mapping: this is the
|
||||
// path that used to be devsw's nullwrite, i.e. a silent no-op.
|
||||
static unsigned char row[8192];
|
||||
unsigned bpp = var.bits_per_pixel / 8;
|
||||
unsigned len = fix.line_length;
|
||||
if (len > sizeof(row))
|
||||
len = sizeof(row);
|
||||
|
||||
for (unsigned y = 0; y < var.yres; y++) {
|
||||
unsigned band = (y * 8) / (var.yres ? var.yres : 1);
|
||||
unsigned long px =
|
||||
((unsigned long)((band & 1) ? 255 : 0) << var.red.offset) |
|
||||
((unsigned long)((band & 2) ? 255 : 0) << var.green.offset) |
|
||||
((unsigned long)((band & 4) ? 255 : 0) << var.blue.offset);
|
||||
for (unsigned x = 0; x * bpp + bpp <= len; x++)
|
||||
for (unsigned b = 0; b < bpp; b++)
|
||||
row[x * bpp + b] = (unsigned char)(px >> (b * 8));
|
||||
if (write(fd, row, len) != (int)len) {
|
||||
puts("fbinfo: short write to /dev/fb0\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
puts("fbinfo: colour bars drawn through write()\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
puts("usage: fbinfo [fill|bars]\n");
|
||||
return 1;
|
||||
}
|
||||
124
cmd/init/main.c
Normal file
124
cmd/init/main.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Schunix — minimal init (PID 1)
|
||||
|
||||
#include "types.h"
|
||||
#include "user.h"
|
||||
#include "fcntl.h"
|
||||
|
||||
static void
|
||||
xputs(int fd, const char *s)
|
||||
{
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
write(fd, (void *)s, n);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
if (getpid() != 1)
|
||||
exit();
|
||||
|
||||
// Open console as fd 0; dup to fd 1 (stdout) and fd 2 (stderr).
|
||||
if (open("console", O_RDWR) < 0) {
|
||||
mknod("console", 1, 1);
|
||||
open("console", O_RDWR);
|
||||
}
|
||||
dup(0);
|
||||
dup(0);
|
||||
|
||||
// --- /dev ------------------------------------------------------------
|
||||
// Minor numbers under major 1 follow Linux's mem-major layout where it
|
||||
// costs nothing (null=2, zero=5, full=7, random=8, urandom=9).
|
||||
// /dev/hole (was minor 4) is gone: it backed an abandoned experiment and
|
||||
// nothing has referenced it since.
|
||||
mkdir("/dev");
|
||||
mknod("/dev/console", 1, 1);
|
||||
mknod("/dev/null", 1, 2);
|
||||
mknod("/dev/keyboard", 1, 3);
|
||||
mknod("/dev/zero", 1, 5);
|
||||
mknod("/dev/full", 1, 7);
|
||||
mknod("/dev/random", 1, 8);
|
||||
mknod("/dev/urandom", 1, 9);
|
||||
mknod("/dev/fb0", 2, 0);
|
||||
mknod("/dev/mouse", 3, 1);
|
||||
mknod("/dev/tty", 1, 1); // alias for console — must pass TCGETS ioctl
|
||||
mknod("/dev/ttyS0", 4, 64);
|
||||
mknod("/dev/stdin", 1, 1);
|
||||
mknod("/dev/stdout", 1, 1);
|
||||
mknod("/dev/stderr", 1, 1);
|
||||
|
||||
// Raw input events, Linux evdev layout (kernel/dev/input.c). /dev/keyboard
|
||||
// and /dev/mouse above are the old character-and-position interfaces; these
|
||||
// are the ones with key releases and button transitions, which is what a
|
||||
// game or a compositor needs and what neither of the others can express.
|
||||
mkdir("/dev/input");
|
||||
mknod("/dev/input/event0", 7, 0); // keyboard
|
||||
mknod("/dev/input/event1", 7, 1); // mouse
|
||||
|
||||
// --- /proc -----------------------------------------------------------
|
||||
// Synthesised status files, major 6 (kernel/dev/procfs.c). These are the
|
||||
// global ones; per-process /proc/<pid>/ directories need synthetic
|
||||
// directory support in the filesystem, which does not exist yet.
|
||||
mkdir("/proc");
|
||||
// /proc is a real filesystem now (kernel/fs/procfs.c), not a handful of
|
||||
// mknod'd nodes: it has to be, because one directory per process cannot be
|
||||
// created ahead of time. This is what makes `ps` and `top` work.
|
||||
//
|
||||
// The mknod'd global files below still happen, and are simply covered by
|
||||
// the mount. They stay for now so that a failure here degrades to the old
|
||||
// behaviour instead of losing /proc entirely.
|
||||
mount("proc", "/proc", "proc", 0, 0);
|
||||
mknod("/proc/meminfo", 6, 1);
|
||||
mknod("/proc/uptime", 6, 2);
|
||||
mknod("/proc/cpuinfo", 6, 3);
|
||||
mknod("/proc/version", 6, 4);
|
||||
mknod("/proc/stat", 6, 5);
|
||||
mknod("/proc/loadavg", 6, 6);
|
||||
mknod("/proc/mounts", 6, 7);
|
||||
mknod("/proc/filesystems", 6, 8);
|
||||
// SchFS volume summary: label, generation, free space, and the root
|
||||
// directory. Reachable whether or not the volume is mounted into the
|
||||
// namespace, which is what makes it useful when a mount has gone wrong.
|
||||
mknod("/proc/schfs", 6, 9);
|
||||
// Write 'c' here to crash on purpose. The crash screen is otherwise the
|
||||
// one part of the system that can only be seen by having a bug.
|
||||
mknod("/proc/sysrq", 6, 10);
|
||||
mknod("/proc/pci", 6, 11);
|
||||
mknod("/proc/interrupts", 6, 12);
|
||||
|
||||
mkdir("/sys");
|
||||
|
||||
xputs(1, "\nSchunix\n");
|
||||
xputs(1, "init: ready (pid 1)\n");
|
||||
|
||||
for (;;) {
|
||||
int pid = fork();
|
||||
if (pid < 0) {
|
||||
xputs(2, "init: fork failed\n");
|
||||
sleep(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// exec.c auto-enables Linux ABI for musl binaries (entry >= 0x100000).
|
||||
// Do NOT call enable_linux_abi() here — it corrupts Schunix syscall
|
||||
// numbers for the exec/exit calls that follow in this child.
|
||||
// Standardized naming: the shell is always /bin/sh, regardless of
|
||||
// which implementation backs it (dash, mksh, etc.) — no ksh/mksh/
|
||||
// other aliases are looked up here.
|
||||
static char *argv[] = { "sh", 0 };
|
||||
exec("/bin/sh", argv);
|
||||
xputs(2, "init: no shell found; place sh in /bin\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Reap zombies; the loop restarts the shell when it exits.
|
||||
int wpid;
|
||||
while ((wpid = wait()) >= 0 && wpid != pid)
|
||||
;
|
||||
// Brief pause before restarting so we don't spin on a missing shell.
|
||||
sleep(100);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
124
cmd/inputtest/main.c
Normal file
124
cmd/inputtest/main.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// inputtest -- read raw events from /dev/input/eventN.
|
||||
//
|
||||
// The point of the raw devices is everything the console cannot say: which
|
||||
// key was released, whether one is still held, and where the mouse went. None
|
||||
// of that shows up in a character stream, so none of it can be tested by
|
||||
// typing into a shell.
|
||||
//
|
||||
// inputtest read the keyboard until Esc is released
|
||||
// inputtest mouse read the mouse until interrupted
|
||||
// inputtest -n N stop after N events (used by the boot check)
|
||||
//
|
||||
// Prints one line per event: type, code and value, decoded where there is a
|
||||
// name for it.
|
||||
|
||||
#include "types.h"
|
||||
#include "user.h"
|
||||
#include "fcntl.h"
|
||||
#include "input.h"
|
||||
|
||||
static void
|
||||
puts_(const char *s)
|
||||
{
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
write(1, (void*)s, n);
|
||||
}
|
||||
|
||||
static void
|
||||
putn_(int v)
|
||||
{
|
||||
char buf[12];
|
||||
int i = 0, neg = 0;
|
||||
if (v < 0) { neg = 1; v = -v; }
|
||||
if (v == 0) buf[i++] = '0';
|
||||
while (v) { buf[i++] = '0' + (v % 10); v /= 10; }
|
||||
if (neg) buf[i++] = '-';
|
||||
while (i--) write(1, &buf[i], 1);
|
||||
}
|
||||
|
||||
static const char *
|
||||
typename_(int t)
|
||||
{
|
||||
switch (t) {
|
||||
case EV_SYN: return "SYN";
|
||||
case EV_KEY: return "KEY";
|
||||
case EV_REL: return "REL";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
streq(const char *a, const char *b)
|
||||
{
|
||||
while (*a && *a == *b) { a++; b++; }
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
const char *path = "/dev/input/event0";
|
||||
int limit = 0, count = 0, i;
|
||||
int fd;
|
||||
struct input_event ev;
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (streq(argv[i], "mouse"))
|
||||
path = "/dev/input/event1";
|
||||
else if (streq(argv[i], "-n") && i + 1 < argc) {
|
||||
const char *p = argv[++i];
|
||||
limit = 0;
|
||||
while (*p >= '0' && *p <= '9') limit = limit * 10 + (*p++ - '0');
|
||||
}
|
||||
}
|
||||
|
||||
fd = open((char*)path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
puts_("inputtest: cannot open ");
|
||||
puts_(path);
|
||||
puts_("\n");
|
||||
exit();
|
||||
}
|
||||
puts_("inputtest: reading ");
|
||||
puts_(path);
|
||||
puts_("\n");
|
||||
|
||||
for (;;) {
|
||||
int n = read(fd, &ev, sizeof(ev));
|
||||
|
||||
// A short read would mean the device handed back part of a struct,
|
||||
// which would desynchronise every event after it. Saying so is more
|
||||
// use than silently carrying on with garbage.
|
||||
if (n == 0) { puts_("inputtest: end of stream\n"); break; }
|
||||
if (n < 0) { puts_("inputtest: read error\n"); break; }
|
||||
if (n != sizeof(ev)) {
|
||||
puts_("inputtest: SHORT READ -- events are not whole\n");
|
||||
break;
|
||||
}
|
||||
|
||||
puts_(" ");
|
||||
puts_(typename_(ev.type));
|
||||
puts_(" code ");
|
||||
putn_(ev.code);
|
||||
puts_(" value ");
|
||||
putn_(ev.value);
|
||||
if (ev.type == EV_KEY)
|
||||
puts_(ev.value ? " (press)" : " (release)");
|
||||
puts_("\n");
|
||||
|
||||
// Esc released ends the keyboard run, so a person can stop it without
|
||||
// a signal. The boot check uses -n instead.
|
||||
if (ev.type == EV_KEY && ev.code == KEY_ESC && ev.value == 0)
|
||||
break;
|
||||
|
||||
if (limit && ++count >= limit) {
|
||||
puts_("inputtest: event limit reached\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
puts_("inputtest: done\n");
|
||||
exit();
|
||||
}
|
||||
112
cmd/shmtest/main.c
Normal file
112
cmd/shmtest/main.c
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// shmtest -- prove that shared memory is actually shared, and that a
|
||||
// file-backed mmap returns the file.
|
||||
//
|
||||
// Both of these are things you cannot check by reading a return value. shmget
|
||||
// and shmat returned -ENOSYS until now, and mmap of a file "succeeded" while
|
||||
// handing back zeroed anonymous pages -- a lie that looks exactly like
|
||||
// success. The only convincing test is for one process to write and a
|
||||
// different one to read.
|
||||
//
|
||||
// shmtest run every check
|
||||
//
|
||||
// Prints one line per check, and a final verdict a script can grep for.
|
||||
|
||||
#include "types.h"
|
||||
#include "user.h"
|
||||
#include "fcntl.h"
|
||||
|
||||
#define PROT_READ 0x1
|
||||
#define PROT_WRITE 0x2
|
||||
#define MAP_PRIVATE 0x02
|
||||
#define MAP_SHARED 0x01
|
||||
#define IPC_PRIVATE 0
|
||||
#define IPC_CREAT 01000
|
||||
|
||||
static int failures;
|
||||
|
||||
static void
|
||||
puts_(const char *s)
|
||||
{
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
write(1, (void*)s, n);
|
||||
}
|
||||
|
||||
static void
|
||||
check(int ok, const char *what)
|
||||
{
|
||||
puts_(ok ? " [ok] " : " FAIL: ");
|
||||
puts_(what);
|
||||
puts_("\n");
|
||||
if (!ok) failures++;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
// --- shared memory ----------------------------------------------------
|
||||
int id = shmget(IPC_PRIVATE, 8192, IPC_CREAT | 0600);
|
||||
check(id >= 0, "shmget created a segment");
|
||||
if (id < 0) { puts_("shmtest: FAILED\n"); return 1; }
|
||||
|
||||
char *p = shmat(id, 0, 0);
|
||||
check((long)p > 0, "shmat mapped it");
|
||||
if ((long)p <= 0) { puts_("shmtest: FAILED\n"); return 1; }
|
||||
|
||||
// A fresh segment must be zeroed, or it leaks whatever the previous
|
||||
// tenant left in those pages to whoever gets them next.
|
||||
int zeroed = 1;
|
||||
for (int i = 0; i < 8192; i++)
|
||||
if (p[i] != 0) { zeroed = 0; break; }
|
||||
check(zeroed, "a new segment reads as zeros");
|
||||
|
||||
p[0] = 'P';
|
||||
p[1] = 0;
|
||||
|
||||
int pid = fork();
|
||||
if (pid == 0) {
|
||||
// The child inherited the attachment through fork. If the pages had
|
||||
// been copied instead of shared, the parent would never see this.
|
||||
p[0] = 'C';
|
||||
p[100] = 'X';
|
||||
exit();
|
||||
}
|
||||
wait();
|
||||
|
||||
check(p[0] == 'C', "a write by the child is visible in the parent");
|
||||
check(p[100] == 'X', "and so is one further into the segment");
|
||||
|
||||
// --- file-backed mmap --------------------------------------------------
|
||||
int fd = open("/tmp/shmtest.dat", O_CREATE | O_RDWR);
|
||||
check(fd >= 0, "created a file to map");
|
||||
if (fd >= 0) {
|
||||
write(fd, "SCHUNIX-MMAP-OK", 15);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
fd = open("/tmp/shmtest.dat", O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
char *m = mmap(0, 4096, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
int ok = ((long)m > 0) &&
|
||||
m[0]=='S' && m[1]=='C' && m[2]=='H' && m[3]=='U' &&
|
||||
m[8]=='M' && m[13]=='O' && m[14]=='K';
|
||||
check(ok, "mmap of a file returns the file, not zeros");
|
||||
close(fd);
|
||||
} else {
|
||||
check(0, "mmap of a file returns the file, not zeros");
|
||||
}
|
||||
|
||||
// MAP_SHARED + PROT_WRITE on a file cannot be honoured without a page
|
||||
// cache, and is refused rather than quietly downgraded to a private copy.
|
||||
fd = open("/tmp/shmtest.dat", O_RDWR);
|
||||
if (fd >= 0) {
|
||||
void *m = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
check((long)m < 0, "writable MAP_SHARED on a file is refused, not faked");
|
||||
close(fd);
|
||||
}
|
||||
|
||||
check(shmdt(p) == 0, "shmdt released the attachment");
|
||||
|
||||
puts_(failures ? "shmtest: FAILED\n" : "shmtest: all checks passed\n");
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
305
cmd/socktest/main.c
Normal file
305
cmd/socktest/main.c
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
// socktest -- prove that Unix domain sockets carry data between processes.
|
||||
//
|
||||
// Every part of a socket is invisible from a return value. bind() returning 0
|
||||
// proves nothing; a socket layer that accepts every call and drops every byte
|
||||
// would pass a test built on return codes alone. So each check here has one
|
||||
// process write and a *different* process read, and compares the bytes.
|
||||
//
|
||||
// socktest run every check
|
||||
//
|
||||
// Prints one line per check and a final verdict a script can grep for.
|
||||
|
||||
#include "types.h"
|
||||
#include "user.h"
|
||||
#include "socket.h"
|
||||
|
||||
static int failures;
|
||||
|
||||
static void
|
||||
puts_(const char *s)
|
||||
{
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
write(1, (void*)s, n);
|
||||
}
|
||||
|
||||
static void
|
||||
putn_(int v)
|
||||
{
|
||||
char buf[12];
|
||||
int i = 0, neg = 0;
|
||||
if (v < 0) { neg = 1; v = -v; }
|
||||
if (v == 0) buf[i++] = '0';
|
||||
while (v) { buf[i++] = '0' + (v % 10); v /= 10; }
|
||||
if (neg) buf[i++] = '-';
|
||||
while (i--) write(1, &buf[i], 1);
|
||||
}
|
||||
|
||||
static void
|
||||
check(int ok, const char *what)
|
||||
{
|
||||
puts_(ok ? " [ok] " : " FAIL: ");
|
||||
puts_(what);
|
||||
puts_("\n");
|
||||
if (!ok) failures++;
|
||||
}
|
||||
|
||||
static int
|
||||
streq(const char *a, const char *b)
|
||||
{
|
||||
while (*a && *a == *b) { a++; b++; }
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
// Fill a sockaddr_un and return the length to pass to bind/connect: the
|
||||
// family plus the path plus its NUL, which is what musl computes and what the
|
||||
// kernel side is written to expect.
|
||||
static int
|
||||
mkaddr(struct sockaddr_un *sa, const char *path)
|
||||
{
|
||||
int i = 0;
|
||||
for (i = 0; i < UNIX_PATH_MAX - 1 && path[i]; i++)
|
||||
sa->sun_path[i] = path[i];
|
||||
sa->sun_path[i] = '\0';
|
||||
sa->sun_family = AF_UNIX;
|
||||
return (int)sizeof(sa->sun_family) + i + 1;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1. A round trip over a named socket, between two processes.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void
|
||||
test_roundtrip(void)
|
||||
{
|
||||
struct sockaddr_un sa;
|
||||
int len = mkaddr(&sa, "/tmp/socktest");
|
||||
int srv, pid, cli, n;
|
||||
char buf[64];
|
||||
|
||||
puts_("named socket, two processes:\n");
|
||||
|
||||
srv = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
check(srv >= 0, "socket() returns a descriptor");
|
||||
if (srv < 0) return;
|
||||
|
||||
check(bind(srv, (struct sockaddr *)&sa, len) == 0, "bind() to /tmp/socktest");
|
||||
check(listen(srv, 4) == 0, "listen()");
|
||||
|
||||
// A second bind to the same name must be refused, or two servers can
|
||||
// silently steal each other's clients.
|
||||
{
|
||||
int dup_ = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
check(bind(dup_, (struct sockaddr *)&sa, len) < 0,
|
||||
"a second bind to the same name is refused");
|
||||
close(dup_);
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid == 0) {
|
||||
// Child: the client. Connects, sends a question, reads the answer.
|
||||
int c = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
char reply[64];
|
||||
int r;
|
||||
|
||||
close(srv);
|
||||
if (connect(c, (struct sockaddr *)&sa, len) < 0) {
|
||||
puts_(" FAIL: child connect()\n");
|
||||
exit();
|
||||
}
|
||||
write(c, "ping", 4);
|
||||
|
||||
r = read(c, reply, sizeof(reply) - 1);
|
||||
if (r < 0) r = 0;
|
||||
reply[r] = '\0';
|
||||
if (!streq(reply, "pong"))
|
||||
puts_(" FAIL: child did not receive pong\n");
|
||||
close(c);
|
||||
exit();
|
||||
}
|
||||
|
||||
cli = accept(srv, 0, 0);
|
||||
check(cli >= 0, "accept() returns a connection");
|
||||
if (cli < 0) { close(srv); wait(); return; }
|
||||
|
||||
n = read(cli, buf, sizeof(buf) - 1);
|
||||
if (n < 0) n = 0;
|
||||
buf[n] = '\0';
|
||||
check(streq(buf, "ping"), "server read the client's bytes");
|
||||
|
||||
check(write(cli, "pong", 4) == 4, "server replied on the same socket");
|
||||
|
||||
close(cli);
|
||||
close(srv);
|
||||
wait();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2. Errors that must actually be errors.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void
|
||||
test_errors(void)
|
||||
{
|
||||
struct sockaddr_un sa;
|
||||
int len = mkaddr(&sa, "/tmp/nobody-listening");
|
||||
int s;
|
||||
|
||||
puts_("error paths:\n");
|
||||
|
||||
s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
check(connect(s, (struct sockaddr *)&sa, len) < 0,
|
||||
"connect() to a name nobody bound fails");
|
||||
close(s);
|
||||
|
||||
check(socket(AF_INET, SOCK_STREAM, 0) < 0, "AF_INET is refused, not faked");
|
||||
check(socket(AF_UNIX, SOCK_DGRAM, 0) < 0, "SOCK_DGRAM is refused, not faked");
|
||||
|
||||
// read/write on a plain file descriptor must not be mistaken for a socket,
|
||||
// and socket calls on one must be refused.
|
||||
check(listen(1, 4) < 0, "listen() on a non-socket fd fails");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3. End-of-file. A reader whose peer closes must be told, not left hanging.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void
|
||||
test_eof(void)
|
||||
{
|
||||
struct sockaddr_un sa;
|
||||
int len = mkaddr(&sa, "/tmp/socktest-eof");
|
||||
int srv, cli, pid, n;
|
||||
char buf[16];
|
||||
|
||||
puts_("end of file:\n");
|
||||
|
||||
srv = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
bind(srv, (struct sockaddr *)&sa, len);
|
||||
listen(srv, 4);
|
||||
|
||||
pid = fork();
|
||||
if (pid == 0) {
|
||||
int c = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
close(srv);
|
||||
connect(c, (struct sockaddr *)&sa, len);
|
||||
write(c, "x", 1);
|
||||
close(c); // the peer goes away with the byte still unread
|
||||
exit();
|
||||
}
|
||||
|
||||
cli = accept(srv, 0, 0);
|
||||
n = read(cli, buf, sizeof(buf));
|
||||
check(n == 1, "the byte sent before close() is still delivered");
|
||||
|
||||
n = read(cli, buf, sizeof(buf));
|
||||
check(n == 0, "the next read returns 0 rather than blocking");
|
||||
|
||||
close(cli);
|
||||
close(srv);
|
||||
wait();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 4. socketpair: bidirectional, no name, no listener.
|
||||
// ---------------------------------------------------------------------------
|
||||
static void
|
||||
test_socketpair(void)
|
||||
{
|
||||
int sv[2], n;
|
||||
char buf[16];
|
||||
|
||||
puts_("socketpair:\n");
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
|
||||
check(0, "socketpair() succeeds");
|
||||
return;
|
||||
}
|
||||
check(1, "socketpair() succeeds");
|
||||
|
||||
write(sv[0], "a", 1);
|
||||
n = read(sv[1], buf, sizeof(buf));
|
||||
check(n == 1 && buf[0] == 'a', "0 -> 1 carries a byte");
|
||||
|
||||
write(sv[1], "b", 1);
|
||||
n = read(sv[0], buf, sizeof(buf));
|
||||
check(n == 1 && buf[0] == 'b', "1 -> 0 carries a byte, on the same pair");
|
||||
|
||||
close(sv[0]);
|
||||
close(sv[1]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 5. A message longer than the socket buffer, so the writer has to block and
|
||||
// the reader has to drain in more than one go.
|
||||
// ---------------------------------------------------------------------------
|
||||
#define BIG 12000
|
||||
|
||||
static void
|
||||
test_big(void)
|
||||
{
|
||||
int sv[2], pid, got = 0, ok = 1, i;
|
||||
static char out[BIG], in[BIG];
|
||||
|
||||
puts_("a transfer larger than the buffer:\n");
|
||||
|
||||
for (i = 0; i < BIG; i++)
|
||||
out[i] = (char)('A' + (i % 26));
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
|
||||
check(0, "socketpair() for the big transfer");
|
||||
return;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid == 0) {
|
||||
int sent = 0, r;
|
||||
close(sv[1]);
|
||||
while (sent < BIG) {
|
||||
r = write(sv[0], out + sent, BIG - sent);
|
||||
if (r <= 0) break;
|
||||
sent += r;
|
||||
}
|
||||
close(sv[0]);
|
||||
exit();
|
||||
}
|
||||
|
||||
close(sv[0]);
|
||||
while (got < BIG) {
|
||||
int r = read(sv[1], in + got, BIG - got);
|
||||
if (r <= 0) break;
|
||||
got += r;
|
||||
}
|
||||
close(sv[1]);
|
||||
wait();
|
||||
|
||||
check(got == BIG, "every byte arrived");
|
||||
if (got != BIG) {
|
||||
puts_(" got ");
|
||||
putn_(got);
|
||||
puts_(" of ");
|
||||
putn_(BIG);
|
||||
puts_("\n");
|
||||
}
|
||||
for (i = 0; i < got; i++)
|
||||
if (in[i] != out[i]) { ok = 0; break; }
|
||||
check(ok, "and in the right order");
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
puts_("socktest: unix domain sockets\n");
|
||||
|
||||
test_roundtrip();
|
||||
test_errors();
|
||||
test_eof();
|
||||
test_socketpair();
|
||||
test_big();
|
||||
|
||||
if (failures == 0)
|
||||
puts_("socktest: ALL CHECKS PASSED\n");
|
||||
else {
|
||||
puts_("socktest: ");
|
||||
putn_(failures);
|
||||
puts_(" CHECK(S) FAILED\n");
|
||||
}
|
||||
exit();
|
||||
}
|
||||
666
docs/architecture/kernel-history.md
Normal file
666
docs/architecture/kernel-history.md
Normal file
|
|
@ -0,0 +1,666 @@
|
|||
# Kernel history — what broke, and what the fix actually was
|
||||
|
||||
Not a design document. This is the record of bugs that were expensive to find
|
||||
and whose causes are not visible from the code that fixed them, plus the few
|
||||
subsystem write-ups (bootloader, boot speed, naming) that have nowhere better
|
||||
to live.
|
||||
|
||||
It is worth keeping because every entry here is a thing that looked like a
|
||||
different problem than it was.
|
||||
|
||||
> **Where the old design plan went.** Sections 1-6 of this file used to hold a
|
||||
> "Segregated Kernel" design: a Ring 0 Alpha / Ring 0 Beta split with driver
|
||||
> modules isolated by CR3 switching, from an `IDEA.txt` that no longer exists.
|
||||
> None of it was ever implemented, and it is superseded by
|
||||
> [schapi.md](schapi.md) — a macOS-shaped system answers driver isolation with
|
||||
> userspace drivers and a personality boundary, not a bespoke in-kernel ring
|
||||
> split. It also referenced a Zig toolchain that is now clang/LLVM. Removed
|
||||
> rather than left to be mistaken for a plan.
|
||||
|
||||
---
|
||||
|
||||
## 1. Fixed: boot didn't reliably reach a working shell
|
||||
|
||||
Investigated 2026-07-05, before phase 1 started (predates this design, not
|
||||
caused by it). Originally manifested as `ksh` (mksh) crashing on every
|
||||
launch, apparently inside musl's mallocng allocator (`get_meta`,
|
||||
`src/malloc/mallocng/meta.h`, deliberately executing `hlt` on detecting
|
||||
"corrupted" heap metadata — a `trap 13` GPF at a fixed rip). It wasn't a
|
||||
musl bug. Six real kernel bugs were found and fixed across three
|
||||
investigation passes, and mksh boots and runs cleanly with all of them in
|
||||
place — the calloc assertion was a symptom of real memory/pointer-handling
|
||||
bugs downstream in the kernel, not anything musl or mallocng did wrong.
|
||||
|
||||
Fixed:
|
||||
- `copyuvm()` (`kernel/arch/x86_64/vm.c`, used by `fork()`) never copied
|
||||
`mmap()`-backed `proc->vma[]` regions into the child — only `[0, sz)`.
|
||||
Fixed via new `copy_vma_range()`, called from `fork()` (`kernel/proc/proc.c`).
|
||||
- `kmem.freelist` (`kernel/core/kalloc.c`) could get corrupted by a wild
|
||||
write elsewhere and would silently hand back a bad pointer, panicking far
|
||||
from the actual cause. Added a canary check (`check_run()`) at the top of
|
||||
both `kalloc()`/`kfree()` so corruption panics immediately at the point
|
||||
it's touched, with a clear message.
|
||||
- No TLB invalidation existed anywhere except the full `lcr3` reload on
|
||||
context switch — `mappages()`/`vm_unmap_range()` never called `invlpg`.
|
||||
Fixed by adding `invlpg()` (`include/x86.h`) to both functions.
|
||||
- **`MSR_STAR` was misconfigured** (`syscallinit()`, `kernel/arch/x86_64/vm.c`):
|
||||
`SYSRETQ` computes the returned `cs`/`ss` as `STAR[63:48]+16`/`+8`, which
|
||||
requires the *compat* 32-bit code selector (`SEG_UCODE32`) in that field —
|
||||
the kernel was putting the real 64-bit `USER_CS` there instead. Every
|
||||
`sysretq` silently loaded an invalid `cs` (`0x3b`, pointing at the TSS
|
||||
descriptor)/`ss` pair; QEMU's TCG doesn't validate segment descriptors on
|
||||
`SYSRET`, so this went unnoticed until a hardware interrupt forced a
|
||||
strictly-validated `IRETQ` with that bogus `cs` still loaded, which
|
||||
correctly `#GP`'d.
|
||||
- `sys_mmap`'s non-`MAP_FIXED` path (`kernel/sys/sys_mem.c`) assigned a fixed
|
||||
8MB virtual-address window per `vma[]` slot regardless of the requested
|
||||
length. A single allocation over 8MB (e.g. `realloc()` growth past that
|
||||
size) mapped straight into a neighboring live mapping and hit
|
||||
`mappages()`'s `panic("remap")`. Replaced with a real bump allocator
|
||||
(`proc->mmap_next`, `include/proc.h`), so every mapping gets its own
|
||||
disjoint, exactly-sized range regardless of size.
|
||||
- **`uva2ka()`** (`kernel/arch/x86_64/vm.c`) returned only the page-aligned
|
||||
base address of a user pointer's page, silently dropping the in-page byte
|
||||
offset. Direct callers (`sys_ioctl`'s `TIOCGPGRP`/`TCGETS`, `sys_getcwd`,
|
||||
signal masks, `times()`, `sysinfo()`, ...) read/wrote near the wrong
|
||||
address whenever the pointer wasn't page-aligned — i.e. almost always.
|
||||
Fixed at the source so every caller benefits.
|
||||
- **`fetchint`/`fetchaddr`/`fetchstr`/`argptr`** (`kernel/sys/syscall.c`)
|
||||
rejected any address `>= proc->sz`, wrongly assuming every valid user
|
||||
pointer lives in the heap. Stack addresses and `mmap()`'d buffers (used
|
||||
constantly by real programs, e.g. path-resolution buffers) live above
|
||||
`proc->sz` and were being rejected outright. Fixed to rely solely on
|
||||
`uva2ka()`'s page-table validation.
|
||||
|
||||
Bringing in toybox (see "Coreutils" below) surfaced four more, all fixed:
|
||||
- Linux syscall 58 (`vfork`) wasn't in the syscall-ABI translation table at
|
||||
all (`kernel/sys/linux_abi.c`) — every external command spawn from a
|
||||
program that prefers `vfork()` over `fork()` failed outright. Mapped to
|
||||
the existing `fork()` (a fully correct, if slightly slower, substitute).
|
||||
- `sys_stat`/`fstat`/`fstatat` (`kernel/sys/sys_fs.c`) filled in Schunix's
|
||||
own native `struct stat` layout regardless of the caller — musl/toybox
|
||||
parse a fixed Linux ABI byte layout instead, so `st_mode` was garbage and
|
||||
tools like `ls` couldn't tell files from directories, or executables from
|
||||
non-executables. Added `struct linux_stat` + `stati_linux()`, gated on
|
||||
Linux-ABI mode (`proc->syscall_map`).
|
||||
- `sys_open`/`sys_openat` required `omode == O_RDONLY` *exactly* to open a
|
||||
directory, but `opendir()`-style calls legitimately OR in `O_DIRECTORY`/
|
||||
`O_CLOEXEC` alongside it. Fixed to check just the access-mode bits
|
||||
(`O_ACCMODE`, added to `include/fcntl.h`).
|
||||
- `sys_getdents64` read one `struct dirent` (16 bytes) at a time — a full
|
||||
`bmap()`+`bread()` per slot. Directories here are hash-indexed (see
|
||||
`tree_search()`/`tree_expand()` below), so a directory with a few dozen
|
||||
real entries can have a size in the hundreds of KB, nearly all empty
|
||||
slots — this made listing such a directory look hung (it wasn't a
|
||||
deadlock, just thousands of redundant single-slot disk reads). Rewrote it
|
||||
to read a full block at a time and resolve each entry's type via
|
||||
`iget(inum)` (exported from `fs.c`, it was `static`) instead of
|
||||
re-searching the whole directory by name with `dirlookup()` for every
|
||||
entry. That rewrite's first version introduced a **new** self-deadlock:
|
||||
the "." entry's inode number equals the directory's own, and `iget()`
|
||||
correctly hands back the *same, already-locked* `struct inode*` — calling
|
||||
`ilock()` on it again sleeps forever on a lock the process already holds.
|
||||
Fixed by special-casing `xde->inum == ip->inum` (we already know it's a
|
||||
directory, no need to re-lock at all).
|
||||
|
||||
A synthetic stress test (interleaved malloc/free, realloc growth crossing
|
||||
`MMAP_THRESHOLD`, calloc, large allocations) runs to completion with zero
|
||||
crashes, and mksh now boots straight to an interactive prompt, runs external
|
||||
programs, pipes, and `ls`/`mkdir`/`grep` without issue.
|
||||
|
||||
## 2. Naming standardization
|
||||
|
||||
Schunix standardizes on exactly one name per role, regardless of the
|
||||
underlying implementation: the shell is always installed and exec'd as
|
||||
`/bin/sh` (currently mksh, built from `ports/mksh/`), and PID 1 is always
|
||||
`/bin/init`. `cmd/init/main.c`'s exec loop only ever tries `/bin/sh` — no
|
||||
ksh/mksh/dash/zsh/other-name fallbacks. dash was tried first
|
||||
(BSD-licensed, minimal) while the boot crash above was still open, then
|
||||
replaced with mksh once the underlying kernel bugs were fixed and dash's
|
||||
lack of interactive features (history, completion, line editing) made it
|
||||
feel unusably bare for day-to-day use; the dash port's source tree was left in
|
||||
place rather than deleted, in case it's useful again (e.g. as a `/bin/sh`
|
||||
POSIX-strict target distinct from an interactive login shell).
|
||||
|
||||
## 3. Coreutils
|
||||
|
||||
`ports/toybox/` (0BSD-licensed, https://landley.net/toybox/) provides `ls`,
|
||||
`cat`, `cp`, `mv`, `rm`, `mkdir`, `grep`, `sed`, `find`, `chmod`, `date`,
|
||||
`sort`, and more — see the `APPLETS` list in `ports/toybox/Makefile`. It builds
|
||||
as a single multicall binary; `tools/mkfs.c` doesn't understand symlinks or
|
||||
hardlink dedup, so `ports/toybox/Makefile`'s `install` target copies the built
|
||||
binary once per applet name into `sysroot/bin/` rather than symlinking
|
||||
(costs disk space per applet — currently ~44MB total — instead of one
|
||||
shared inode, but needed no mkfs changes).
|
||||
|
||||
## 4. Two-stage bootloader + PSOGM splash
|
||||
|
||||
`boot/` used to be a single BIOS boot sector doing everything (VBE mode
|
||||
set, A20, GDT, protected-mode switch, multiboot kernel scan/load) — silent,
|
||||
and packed to within 4 bytes of the hard 510-byte MBR limit, so there was
|
||||
no room to add so much as a print statement.
|
||||
|
||||
Split into two stages, the standard way real bootloaders solve this:
|
||||
|
||||
- **Stage 1** (`boot/bootasm.S`, still the literal MBR sector, still
|
||||
≤510 bytes) does exactly one thing: raw IDE PIO (LBA28) reads
|
||||
`STAGE2_SECTORS` (16) sectors starting at disk sector 1 into memory at
|
||||
`0x7E00`, then jumps there. Nothing else fits, nothing else is needed.
|
||||
(Caught a real bug writing this: `outb $0x1F7,%al`-style immediates
|
||||
silently truncate to 8 bits — ports 0x1F2-0x1F7 are all > 0xFF and need
|
||||
the `%dx` register form, or you're hitting the wrong I/O port entirely.)
|
||||
- **Stage 2** (`boot/stage2.S` + `boot/stage2main.c`, loaded at `0x7E00`,
|
||||
occupies disk sectors 1-16) does everything stage 1 used to skip straight
|
||||
to, plus a boot banner (BIOS teletype, real mode, before the VBE mode
|
||||
switch — note this only reaches the video device, not serial, so it
|
||||
won't show up in a `-serial file:...`-captured log), a splash screen, a
|
||||
real-time loading bar, and an F8 boot menu (all described below). Not
|
||||
size-constrained the same way as stage 1 — `tools/padstage2.pl` zero-pads
|
||||
it out to a fixed `STAGE2_SECTORS*512` (8192) bytes so the kernel's
|
||||
on-disk starting sector stays predictable regardless of stage2's actual
|
||||
compiled size, and errors out (like `tools/sign.pl` does for stage 1) if
|
||||
it ever grows past that budget instead of silently corrupting the layout.
|
||||
|
||||
The kernel now starts at disk sector 17 instead of 1 (`schunix.img`'s `dd
|
||||
... seek=17` in the root `Makefile`, and the equivalent `+(1+STAGE2_SECTORS)`
|
||||
in `stage2main.c`'s `readseg()` — all three places that encode
|
||||
`STAGE2_SECTORS` have to agree, or the kernel won't be found on disk at
|
||||
all). `boot/bootmain.c` no longer exists; its logic was split between
|
||||
`stage2.S` (real-mode/protected-mode setup) and `stage2main.c` (splash +
|
||||
the unchanged multiboot-header-scan-and-load logic).
|
||||
|
||||
The VBE mode info block itself lives at `0x30000`, not the classic
|
||||
single-stage bootloader's `0x9000` — stage2's own code now runs from
|
||||
`0x7E00` up to nearly `0x9000` once the boot-menu/progress-bar logic is
|
||||
included, and a block at `0x9000` gets partially overwritten by stage2's
|
||||
own tail end (found this the hard way: the kernel's `LFB:` cprintf started
|
||||
reporting a different wrong value on every boot, because it was reading
|
||||
whatever stage2 code/data now happened to occupy that address).
|
||||
`kernel/dev/vga.c`'s `vga_init()` has to agree on `0x30000` too, since it
|
||||
independently re-reads mode fields from the same block.
|
||||
|
||||
Splash/progress bar/boot menu rendering is a small standalone pixel
|
||||
renderer in `stage2main.c` (not the kernel's `vga.c` — paging isn't set up
|
||||
yet, so the LFB physical address from the VBE block is used as a plain
|
||||
pointer) reusing the kernel's own font data (`include/font.h`) directly.
|
||||
It fills the framebuffer with PSOGM's purple (matching
|
||||
`kernel/core/console.c`'s panic screen) and draws "SCHUNIX" centered, then:
|
||||
|
||||
- **Real-time loading bar**: `readsect()` (in `stage2main.c`, not the
|
||||
kernel) calls `progress_tick()` after every 512-byte disk read, updating
|
||||
an actual pixel-filled bar against the real total byte count — first a
|
||||
small one while scanning for the multiboot header, then a second,
|
||||
properly-sized one once the header's found and the true kernel size is
|
||||
known, labeled "Loading kernel...". Not simulated/timed — it reflects
|
||||
real bytes read off disk.
|
||||
- **F8 boot menu**: `wait_for_f8()` busy-polls the keyboard controller
|
||||
(`inb(0x64)`/`inb(0x60)`, raw scancodes — real-mode BIOS keyboard calls
|
||||
aren't available once we're in protected mode) for about a second after
|
||||
the splash appears. F8 (scancode `0x42`) drops into `boot_menu()`: three
|
||||
options (boot normal, boot verbose, reboot to firmware setup) navigated
|
||||
with up/down arrows and Enter, plus a "detected drives" panel from
|
||||
`ata_present()` — a standard floating-bus presence check (select the
|
||||
drive, write a pattern to two registers, read it back) on the primary
|
||||
IDE channel's master/slave (the only channel this project ever actually
|
||||
puts drives on; QEMU's IDE emulation answers the same test
|
||||
"successfully" on the unpopulated secondary channel too, so that channel
|
||||
isn't shown — it'd just be confidently wrong). "Reboot to firmware
|
||||
setup" pulses the keyboard controller's reset line
|
||||
(`outb(0x64, 0xFE)`, same technique as `kernel/sys/sys_proc.c`'s
|
||||
`sys_reboot()`); a real bootloader can't jump into BIOS/UEFI setup
|
||||
directly, so this is the closest honest equivalent — it gives the user
|
||||
a fresh POST during which their firmware's own setup-menu key works.
|
||||
- **Verbose boot**: picking "Boot Schunix (verbose)" writes a flag byte to
|
||||
`BOOT_VERBOSE_FLAG_ADDR` (`VBE_INFO_ADDR + 0x100`, i.e. just past the VBE
|
||||
block) before jumping to the kernel. `kernel/core/main.c`'s `kmain()`
|
||||
reads it into a global `boot_verbose` as its very first action and, if
|
||||
set, prints a line after every subsystem init call
|
||||
(`uartearlyinit`, `kinit1`, ..., `sinit`) — real per-step boot logging,
|
||||
not a canned message.
|
||||
|
||||
Two bugs worth remembering if this code gets touched again:
|
||||
|
||||
- **Keyboard extended-scancode handling**: `0xE0` (the prefix byte for
|
||||
arrow keys and friends) also has bit 7 set, the same bit used to detect
|
||||
key-*release* ("break") codes. A naive "skip anything with bit 7 set,
|
||||
then check for 0xE0" loop skips the 0xE0 byte itself before ever testing
|
||||
for it, so extended keys silently never register. The 0xE0 check has to
|
||||
come before the break-code check, not after.
|
||||
- **The kernel entry call is not a real C function call.** Both the
|
||||
original single-stage `bootmain()` and the new `stage2main()` do
|
||||
`((void(*)(uint32))entry)(fbaddr)` — normal cdecl, which pushes `fbaddr`
|
||||
on the stack. But `kernel/arch/x86_64/entry.S`'s `mboot_entry` reads its
|
||||
framebuffer argument out of **EAX** (a multiboot-style raw-register
|
||||
handoff, then `mov %eax,%ebp` / `mov %ebp,%ebx` / `mov %rbx,%rdi` carries
|
||||
it through the 32→64-bit transition into `kmain()`'s first argument).
|
||||
The cdecl call never puts anything in EAX — it "worked" for years purely
|
||||
because simple bootmain.c code happened, as a side effect of its own
|
||||
codegen, to leave the right value sitting in EAX right up until the
|
||||
`call` instruction. Adding the splash/menu/progress-bar code changed
|
||||
that codegen and broke it silently: the kernel booted fine and reported
|
||||
a plausible-looking but wrong `LFB:` address (garbage left over in EAX
|
||||
from unrelated prior code, different on every boot). Fixed by pinning
|
||||
the argument down explicitly with `asm volatile("jmp *%0" : : "r"(entry),
|
||||
"a"(fbaddr))` instead of hoping the compiler's register allocation
|
||||
cooperates. If this code moves around again, re-verify with a screendump
|
||||
taken *immediately* before the jump compared against the kernel's own
|
||||
`LFB:` log line — they silently diverging is exactly this bug.
|
||||
|
||||
## 5. `*at()` syscalls ignored/rejected real dirfds
|
||||
|
||||
`ls -l`/`ls -lR` (and, by the same code path, `find`/`du`/anything using
|
||||
toybox's `lib/dirtree.c`) would hang on `ls -l` and either hang or silently
|
||||
misbehave on recursive listings. Root cause: `kernel/sys/sys_fs.c`'s
|
||||
`*at()` syscalls (`openat`, `fstatat`, `unlinkat`, `renameat`, `linkat`,
|
||||
`symlinkat`, `mkdirat`, `mknodat`, `readlinkat`, `faccessat`) only handled
|
||||
two cases for their `dirfd` argument — `AT_FDCWD` (resolve relative to
|
||||
`proc->cwd`) or an absolute path (resolve from root, ignoring `dirfd`
|
||||
entirely). A **real** dirfd with a **relative** path — e.g.
|
||||
`fstatat(open("/bin", O_RDONLY), "ls", &st, ...)`, the standard, efficient
|
||||
way any directory-tree walker avoids rebuilding full path strings for every
|
||||
entry — was either rejected outright (`fstatat`, `unlinkat`, ...) or
|
||||
silently ignored in favor of `proc->cwd` (`openat`, which never checked at
|
||||
all — worse than rejecting, since it can silently resolve the *wrong*
|
||||
file if cwd happens to contain a same-named entry).
|
||||
|
||||
Fixed by adding a real dirfd-relative path resolver: `kernel/fs/fs.c`'s
|
||||
`namex()` was refactored into `namex_from(struct inode *start, ...)`,
|
||||
parameterizing what used to be a hardcoded `idup(proc->cwd)`, with two new
|
||||
exported entry points, `nameifrom()`/`nameiparentfrom()`. `sys_fs.c` gained
|
||||
`at_dir(dirfd)` (resolves `AT_FDCWD` or a real fd to a starting inode) and
|
||||
`at_namei()`/`at_nameiparent()` built on top of it, plus a `createat()`
|
||||
(parameterizing `create()`'s hardcoded `nameiparent()` call the same way).
|
||||
Every `*at()` syscall now uses these instead of its own inline
|
||||
absolute-path/`AT_FDCWD`-only check.
|
||||
|
||||
**Known follow-on issue, not yet root-caused**: heavy `ls`/`ls -lR` use
|
||||
across many commands in one long-running shell session can eventually make
|
||||
a *later*, unrelated directory hang (reproduced with `ls -lR /var` after
|
||||
~10 prior commands in the same session; the identical command succeeds
|
||||
immediately on a fresh boot). Not an inode-table or file-table exhaustion
|
||||
— both `iget()` and `filealloc()` fail cleanly (panic / return 0
|
||||
respectively) when full, and neither fired. Smells like a lock or
|
||||
reference-count leak somewhere in the *at() paths above, or in toybox's
|
||||
own fd handling, that only manifests after enough accumulated opens/closes.
|
||||
Next step: instrument `itable`/`ftable` occupancy counts and watch them
|
||||
across a long `ls`-heavy session to see what's actually climbing.
|
||||
|
||||
## 6. Boot speed
|
||||
|
||||
Two cheap, real wins in `boot/stage2main.c`, no functional changes:
|
||||
|
||||
- `progress_tick()` (called once per 512-byte disk sector) used to trigger
|
||||
a full progress-bar redraw — border plus fill, a few thousand pixel
|
||||
writes — on *every* sector. For the ~330KB kernel image that's ~650 full
|
||||
redraws (the multiboot-header-scan phase adds another ~32) for a bar
|
||||
that only has 101 visually distinct states (0-100%). Split into
|
||||
`progress_border()` (drawn once, in `progress_start()`, since the border
|
||||
never changes) and `progress_fill()`, and `progress_tick()` now skips the
|
||||
redraw entirely unless the percentage actually changed. This was the
|
||||
single biggest chunk of the bootloader's own wall-clock time.
|
||||
- `fill_rect()` (the full-screen splash fill, 480,000 pixels at 800x600,
|
||||
run at least twice per boot) used to call `draw_pixel()` per pixel, which
|
||||
recomputes `g_fb + y*stride + x*4` from scratch every time. Now computes
|
||||
each row's base pointer once and indexes into it.
|
||||
- The F8 boot-menu wait was shortened from ~1s (`5000000` busy-loop
|
||||
iterations) to ~0.6s (`3000000`) — flat overhead on every boot whether
|
||||
or not the menu is wanted, so it's the most direct lever available
|
||||
without giving up the feature entirely.
|
||||
|
||||
## 7. `*at()` syscalls, real signal delivery, and mksh job control (in progress)
|
||||
|
||||
`ls -l`/`ls -la` hanging outright turned out to be the same root cause
|
||||
described in section 11 above (dirfd-relative path resolution) — fixed
|
||||
and verified extensively (plain `ls`, `ls -l`, `ls -la`, `ls -lR`, piped
|
||||
output, all confirmed working repeatedly).
|
||||
|
||||
A *second*, deeper issue was found investigating a follow-up report
|
||||
(`cd` with no `HOME` set, then `cd var`, then `ls` hanging): mksh's
|
||||
interactive job control (`ports/mksh (mksh R59c source): jobs.c`) blocks in
|
||||
`sigsuspend()` waiting for `SIGCHLD`, expecting its own handler
|
||||
(`j_sigchld()`) to run, reap the child via `waitpid(-1, &status, WNOHANG)`,
|
||||
and update its internal job-state tracking. This kernel had **no real
|
||||
signal delivery at all** — `sys_rt_sigaction()` was a no-op stub, so no
|
||||
handler was ever invoked for anything, and `sigsuspend()` just slept out
|
||||
one tick and returned `-EINTR` unconditionally. mksh's wait loop had no
|
||||
way to ever learn the child had exited, hanging forever.
|
||||
|
||||
Real signal delivery was implemented from scratch:
|
||||
- `struct proc` (`include/proc.h`) gained `sig_handler[32]`/
|
||||
`sig_restorer[32]` (populated by `sys_rt_sigaction()`,
|
||||
`kernel/sys/sys_sig.c`), `saved_sigtf` (stashed pre-signal trapframe),
|
||||
and `in_signal` (re-entrancy guard). `allocproc()` zeroes these for a
|
||||
freshly claimed ptable slot (they weren't before — a reused slot
|
||||
silently inherited a previous, unrelated process's handler addresses,
|
||||
which crashed the *next* process to occupy that slot the moment
|
||||
anything tried to delivver a signal to it). `fork()` copies handler/
|
||||
restorer/mask from parent to child (POSIX semantics) but not pending
|
||||
signals.
|
||||
- `kernel/proc/proc.c`'s `exit()` sets `SIGCHLD` pending on the parent.
|
||||
- `kernel/sys/syscall.c`'s `syscall()` calls `deliver_signals(tf)` in its
|
||||
tail, right before returning to user mode: if there's a pending,
|
||||
unblocked signal with a registered handler, it redirects the trapframe
|
||||
to jump to the handler (`rdi`=signal number per SysV ABI, `rip`=handler)
|
||||
instead of resuming the interrupted code, having first stashed the
|
||||
original trapframe in `proc->saved_sigtf`.
|
||||
- The return address is **not** `sig_restorer[sig]` — checked, `SA_RESTORER`
|
||||
is not defined anywhere in this musl build, so `sigaction()` never fills
|
||||
in a restorer; it's always 0, and pushing 0 as a return address crashes
|
||||
with `rip=0` the instant the handler function returns. Instead,
|
||||
`deliver_signals()` injects a tiny 7-byte trampoline (`mov eax,15;
|
||||
syscall` — `rt_sigreturn`) directly onto the user's own stack, below the
|
||||
current `rsp` (clear of the SysV red zone), and points the return
|
||||
address at that — the standard technique for when the C library doesn't
|
||||
supply its own restorer.
|
||||
- `sys_rt_sigreturn()` restores `*proc->tf` from `proc->saved_sigtf`,
|
||||
clearing `in_signal`.
|
||||
|
||||
**A real bug found and fixed along the way**: the first version of
|
||||
`sys_rt_sigaction()` translated the user's `struct sigaction` pointer
|
||||
*once* via `uva2ka()` and then did a bulk `memset`+copy across the whole
|
||||
~152-byte structure. `uva2ka()` only guarantees the *page containing that
|
||||
address* is mapped, not everything up to some further offset — when the
|
||||
user's pointer landed close enough to a page boundary, the write ran past
|
||||
the mapped page into whatever physical page happened to be next in kernel
|
||||
address space (P2V is a flat offset, so "next page" there has nothing to
|
||||
do with the user's actual next page), corrupting unrelated kernel memory.
|
||||
This crashed every freshly `exec`'d `/bin/sh` almost immediately, since
|
||||
mksh calls `sigaction()` during its own startup. Fixed by only touching
|
||||
the two individual 8-byte fields actually needed (`sa_handler` at offset
|
||||
0, `sa_restorer` at offset 144), each through its own `uva2ka()` call.
|
||||
|
||||
`sys_waitpid()`/`sys_wait4()` (`kernel/sys/sys_proc.c`) were also rewritten
|
||||
on top of a new `waitpid(pid, status, options)` in `kernel/proc/proc.c`
|
||||
(refactored from the old any-child-only, always-blocking `wait()`, which
|
||||
is now just `waitpid(-1, 0, 0)`): a specific `pid` (not just "any child"),
|
||||
`WNOHANG` (return 0 immediately instead of blocking), and — importantly —
|
||||
a genuinely optional status pointer. The old code ran a NULL status
|
||||
pointer through `argptr()`, which rejects NULL/unmapped addresses outright
|
||||
(page 0 is deliberately unmapped) — but `waitpid(pid, NULL, WNOHANG)` is
|
||||
completely standard, and is exactly what mksh's job control does.
|
||||
|
||||
**Status: signal delivery mechanically works** — verified directly (a
|
||||
debug build logged the handler being invoked with the correct trapframe
|
||||
and `sys_rt_sigreturn()` restoring cleanly, no crash, repeatable). **mksh's
|
||||
job control still doesn't reliably progress afterward**, including for
|
||||
plain non-piped foreground commands, not just the original piped-into-head
|
||||
case. Root cause not yet isolated. Leading suspects, in order of
|
||||
likelihood:
|
||||
- `sys_setpgid()`/`sys_getpgid()` are no-op stubs (`kernel/sys/sys_proc.c`)
|
||||
— `j_sigchld()` (`ports/mksh (mksh R59c source): jobs.c:1344-1348`) has an early-out
|
||||
keyed on `j->ppid == procpid`, which may never resolve the way mksh
|
||||
expects without real process-group tracking.
|
||||
- `getrusage(RUSAGE_CHILDREN, ...)` — called at the top of `j_sigchld()` —
|
||||
isn't verified to behave sanely under this kernel's implementation
|
||||
(`kernel/sys/sys_proc.c`'s `sys_getrusage()` only fills in `ru_utime`
|
||||
from the *calling* process's own tick count, not accumulated child
|
||||
time; `RUSAGE_CHILDREN` isn't distinguished from `RUSAGE_SELF` at all).
|
||||
- Possibly something in how `j_sigchld()` matches a reaped `pid` against
|
||||
its own `job_list` (`ports/mksh (mksh R59c source): jobs.c:1372-1385`) silently fails
|
||||
to find the job for reasons not yet traced.
|
||||
|
||||
This was left here deliberately rather than continuing to iterate blind:
|
||||
SIGCHLD delivery is left **enabled** (`kernel/proc/proc.c`'s `exit()`)
|
||||
since disabling it guarantees zero chance of mksh's wait loop ever exiting,
|
||||
whereas leaving it on at least gives the cases that don't hit the
|
||||
still-unknown remaining gap a chance to work. Next session should start
|
||||
by instrumenting `j_sigchld()`'s own control flow (or strace-equivalent
|
||||
logging in the kernel around `sys_setpgid`/`sys_getrusage`/`sys_waitpid`
|
||||
calls specifically while reproducing `cd; cd var; ls` or even simpler
|
||||
`ls -l /bin` followed by a second command) rather than re-deriving the
|
||||
signal-delivery mechanism, which is now confirmed solid.
|
||||
|
||||
---
|
||||
|
||||
## 8. Two ABI assumptions that only GCC was keeping true
|
||||
|
||||
Found when the build moved from GCC-in-WSL to clang-on-Windows. Neither is a
|
||||
clang bug; both are places where the code relied on GCC doing something it
|
||||
never promised, and both had been latent for years.
|
||||
|
||||
### `%fs:0` was being used as scratch — it is the TCB self-pointer
|
||||
|
||||
`syscall_entry` (`kernel/arch/x86_64/trapasm.S`) stashed `%rax` at `%fs:(0)`
|
||||
while it fetched `proc` to find the kernel stack. The comment called it a
|
||||
"per-CPU scratch slot". It is not: the x86-64 TLS ABI reserves the word *at*
|
||||
the thread pointer to hold the thread pointer, and a compiler may read it to
|
||||
materialise the TP before applying a variable's offset.
|
||||
|
||||
GCC compiles `__thread` access to a direct `%fs:-8` segment reference and never
|
||||
reads that word, so the clobber was invisible. clang at `-O0` emits
|
||||
`movq %fs:0, %rax; addq $-8, %rax; (%rax)` — reading back the syscall number
|
||||
this code had just stored. The first process's first syscall is
|
||||
`exec` (7), so `proc` resolved to address `7 - 8 = -1` and the kernel
|
||||
page-faulted recursively until the stack was gone.
|
||||
|
||||
Fixed by moving the scratch to `%fs:(8)`. Positive offsets are safe: the TLS
|
||||
block lives at negative offsets and `seginit` places the thread pointer 2048
|
||||
bytes into a 4096-byte page.
|
||||
|
||||
**How it was found** is the part worth copying next time. Three rounds of
|
||||
`cprintf` narrowed it to "the word is right at `mpmain`, wrong by the first
|
||||
trap" and no further — the instrumentation perturbed the timing enough to move
|
||||
the symptom. A hardware watchpoint through QEMU's gdb stub named the
|
||||
instruction on the first run:
|
||||
|
||||
```
|
||||
qemu ... -S -gdb tcp::26000
|
||||
gdb -batch -x tcp.gdb # hbreak mpmain; watch *(unsigned long*)kernel_fs_base
|
||||
```
|
||||
|
||||
`seginit` also now writes the self-pointer, which it never did. That is ABI
|
||||
-correct regardless and costs one store per CPU.
|
||||
|
||||
### `_start` read incoming registers with `register ... asm("rdi")`
|
||||
|
||||
`lib/glue.c` captured `argc`/`argv` from `%rdi`/`%rsi` using GCC local register
|
||||
variables. Both compilers only guarantee that binding for inline-asm operands;
|
||||
using it to pick up an incoming argument is undefined. clang ignored it and
|
||||
read two uninitialised stack slots:
|
||||
|
||||
```asm
|
||||
movl -0x4(%rbp), %eax # should have been %edi
|
||||
movq -0x10(%rbp), %rax # should have been %rsi
|
||||
```
|
||||
|
||||
Every program still ran. `init`, `shmtest` and `socktest` take no arguments and
|
||||
passed their entire suites; the only symptom in the whole system was
|
||||
`inputtest -n 6` ignoring its limit. Moved to `lib/start.S`, where reading
|
||||
incoming registers is expressible and correct — `exec` passes argc/argv in the
|
||||
SysV argument registers already, so `_start` calls `main` without touching
|
||||
them.
|
||||
|
||||
---
|
||||
|
||||
## 9. The boot looked like a hobby OS, and two things were why
|
||||
|
||||
Not a bug — a design problem, fixed by looking at a screenshot instead of the
|
||||
code.
|
||||
|
||||
**The display was the kernel log.** Boot printed `pci: 6 devices` and
|
||||
`vga: display controller 1234:1111, aperture fd000000 (16 MB)` to the screen
|
||||
the user is looking at. That single fact is most of what separates a hobby
|
||||
project from a product; every shipping system puts that behind a flag.
|
||||
|
||||
`cons_serial_only` already existed for panic, so the fix was to start it *set*.
|
||||
Everything still goes out the UART — which is where `tools/bootcheck.ps1` reads
|
||||
it from, so all five suites kept passing untouched — and `console_takeover()`
|
||||
clears it on the first `exec()`, the moment userspace genuinely starts.
|
||||
`vga_init` no longer clears the screen, so the splash survives the whole of
|
||||
boot rather than being wiped a few hundred milliseconds in.
|
||||
|
||||
**There were three unrelated designs.** The splash was deep purple with gold
|
||||
text, deliberately matching the *panic* screen. The console was cream with
|
||||
near-black text. So a healthy boot was dressed as a crash, then abruptly
|
||||
became an amber terminal. `include/theme.h` is now the one palette, and the
|
||||
rule is: **normal states are calm and dark; only failure is loud.** PSOGM keeps
|
||||
the purple entirely to itself, which is what makes it mean something.
|
||||
|
||||
The splash copy went from "Schunix Boot Manager / version 1.0 / Memory: 1023 MB
|
||||
/ Press F8 for boot menu..." to the product name, a rule in the accent colour,
|
||||
and the one hint that is actionable — moved to the bottom, where a hint
|
||||
belongs. No shipping OS names its bootloader on the splash.
|
||||
|
||||
### Three latent bugs it flushed out
|
||||
|
||||
Retheming meant `stage2main.c` included `types.h` properly for the first time,
|
||||
and the 32-bit build stopped compiling:
|
||||
|
||||
- `_Static_assert(sizeof(uint64) == 8)` fired. `uint64` was `unsigned long`,
|
||||
which is **4 bytes** in the `-m32` loader — the file even carried a local
|
||||
`e820_u64` workaround with a comment saying so. Now conditioned on
|
||||
`__SIZEOF_LONG__` rather than on the platform, which covers LP64, LLP64 *and*
|
||||
ILP32 correctly.
|
||||
- `x86.h`'s `stosq` and `xchg` use `rep stosq` and `xchgq`, which do not exist
|
||||
in 32-bit mode. They only ever compiled because `uint64` was silently
|
||||
narrowed. Now behind `#ifdef __x86_64__`.
|
||||
- The progress bar computed `(uint64)done * 100 / total`, a 64-bit divide that
|
||||
needs libgcc's `__udivdi3` — which a freestanding loader does not link. Now
|
||||
scaled to KiB and done in 32 bits.
|
||||
|
||||
All three had been sitting in a bootloader that worked fine, waiting for
|
||||
someone to make `uint64` mean what it says.
|
||||
|
||||
---
|
||||
|
||||
## 10. A full-screen program typed the whole session into the shell
|
||||
|
||||
Keys reached `/dev/input/event0` **and** the tty line discipline at the same
|
||||
time. That is correct for two consumers of one keyboard and completely wrong
|
||||
for a full-screen program: while it had the display, the shell behind it was
|
||||
quietly accumulating every keystroke, and the first Enter afterwards ran
|
||||
whatever had piled up. Arrow keys arrived as CP437 glyphs on the console for
|
||||
good measure.
|
||||
|
||||
The fix is the standard one and it is a *primitive*, not a patch: an exclusive
|
||||
grab, `EVIOCGRAB`, with Linux's ioctl number. While a device is grabbed its
|
||||
events still flow to `/dev/input/eventN` but both keyboard drivers withhold the
|
||||
decoded character from the console.
|
||||
|
||||
Three details that matter more than the feature:
|
||||
|
||||
- **The grab is released by the devsw `close` hook**, not by the program. A
|
||||
program that grabs the keyboard and then crashes would otherwise leave the
|
||||
console permanently deaf, with no way to type the command that fixes it.
|
||||
- **Modifier state is still tracked while grabbed** (`kbdgetc` keeps updating
|
||||
shift/caps and only skips the character). Otherwise releasing the grab would
|
||||
hand the line discipline a stuck Shift.
|
||||
- **The USB path updates `prev[]` before returning**, or the next report would
|
||||
look like every held key had just been pressed again.
|
||||
|
||||
It is also exactly what Schcom will need, which is why it lives in the input
|
||||
layer rather than in any one client.
|
||||
|
||||
### The near-miss worth remembering
|
||||
|
||||
The first attempt appeared to work: the harness passed, every assertion green.
|
||||
It had not worked at all — the ioctl call was never inserted into the client
|
||||
source, because a scripted string replacement silently failed to match. The
|
||||
`grep` that should have caught it printed three hits (a comment, a `#define`,
|
||||
and the *release* call) and was read as confirmation without counting what
|
||||
they were.
|
||||
|
||||
What actually caught it was reading the transcript rather than the verdict:
|
||||
four bare `#` prompts after quitting, which is precisely the symptom the change
|
||||
was supposed to remove. `input_grab()` now announces itself, so "did the grab
|
||||
happen" is never again a thing anyone has to infer.
|
||||
|
||||
---
|
||||
|
||||
## 11. `ps` and `top`, and why they never worked
|
||||
|
||||
/proc was a set of character devices: init ran `mknod("/proc/uptime", 6, 2)` and
|
||||
friends, so every file was a real inode on the root filesystem with a
|
||||
major/minor behind it. That is fine for a fixed list and cannot work at all for
|
||||
the thing /proc is mostly for -- one directory per process. You cannot mknod a
|
||||
directory for a process that does not exist yet and will be gone in a minute.
|
||||
`ps` calls `dirtree_flagread("/proc")`, got a listing with no numbers in it, and
|
||||
reported nothing. It was never a `ps` problem.
|
||||
|
||||
So /proc is now a real filesystem behind the same `struct fs_ops` vtable SchFS
|
||||
uses (`kernel/fs/procfs.c`, `PROCFS_DEV`), mounted by init with
|
||||
`mount("proc", "/proc", "proc", ...)`. Nothing is stored: directory listings
|
||||
are generated from the process table when asked, file contents when read.
|
||||
|
||||
Three things worth keeping in mind:
|
||||
|
||||
- **The split is deliberate.** `kernel/fs/procfs.c` owns the *namespace* (what
|
||||
exists); `kernel/dev/procfs.c` keeps the *generators* (what the global files
|
||||
say). They change for different reasons, so `proc_gen_global()` is the only
|
||||
thing that crosses between them.
|
||||
- **procfs never touches ptable.** `proc_snapshot()` copies what is needed out
|
||||
from under the lock, so a process that exits between the readdir that listed
|
||||
it and the open that reads it produces a stale line rather than a
|
||||
use-after-free.
|
||||
- **Sizes are reported as zero**, exactly as Linux does. A synthetic file has
|
||||
no length until it is generated, and generating every one on every `stat()`
|
||||
to fill in a number almost nothing reads would be absurd.
|
||||
|
||||
### What the suite caught
|
||||
|
||||
Marking every procfs inode read-only broke `echo s > /proc/sysrq`, and three
|
||||
USB checks failed on it within a minute of the change -- they drive the disk
|
||||
through sysrq. /proc is read-only *except* sysrq, which is the one deliberate
|
||||
write target, and the code now says so in one place.
|
||||
|
||||
### Still not right
|
||||
|
||||
`ps -o comm` prints `[init]` rather than `init`. That field comes from
|
||||
`/proc/<pid>/exe`, which Linux makes a **symlink** and toybox `readlink()`s;
|
||||
ours is a regular file, so the readlink fails and toybox brackets the name as
|
||||
it would for a kernel thread. Default `ps` and `top` output are unaffected.
|
||||
Fixing it needs symlink support in procfs, which belongs with the rest of the
|
||||
VFS work rather than as a one-off.
|
||||
|
||||
---
|
||||
|
||||
## 12. A test program crashed at random, and had been typing its own launch command
|
||||
|
||||
Symptom: a full-screen framebuffer program, launched with arguments, worked
|
||||
four times and then died with
|
||||
|
||||
pid 9 <prog>: trap 14 err 4 on cpu 0 rip 0x2269be addr 0x18--kill proc
|
||||
|
||||
`err 4` is a user-mode read of a non-present page and `addr 0x18` is a null
|
||||
pointer at offset 24. `llvm-nm` put `rip` inside the routine that opens the
|
||||
program's overlay view, which dereferences a pointer the program only fills in
|
||||
once a level is loaded -- at offset 0x18 on x86-64, because the three 64-bit
|
||||
words of a base struct sit in front of it. So the program had been asked to open
|
||||
that view before there was anything to show.
|
||||
|
||||
**The technique is the transferable part:** the fault address alone said
|
||||
"null pointer plus 24", which is nearly useless; `llvm-nm` on the faulting `rip`
|
||||
turned it into a function name, and the offset then identified the exact field.
|
||||
Reach for that before adding printfs to a program you did not write.
|
||||
|
||||
The cause was ours, and it was visible in the log the whole time: every run
|
||||
printed a burst of garbage bytes *before* the grab was taken, and the amount
|
||||
differed each run.
|
||||
|
||||
**`/dev/input/eventN` had no notion of being opened.** Events were queued from
|
||||
boot onwards whether or not anything was listening, into one 256-entry ring
|
||||
with one read cursor. When the program opened the device it inherited the entire
|
||||
backlog -- including the keystrokes of the command line that had launched it,
|
||||
which the shell had already consumed -- and replayed them as input. Whether that
|
||||
crashed depended on which keys happened to be sitting there, which is exactly
|
||||
the "sometimes" in the report.
|
||||
|
||||
Fixed by giving `struct devsw` an `open` hook to match `close`, and having
|
||||
`/dev/input`:
|
||||
|
||||
- drop the backlog on open (`r = w`) -- a reader starts from *now*;
|
||||
- only queue events while `opens > 0`, which is what Linux does: an evdev
|
||||
device with no clients does not accumulate.
|
||||
|
||||
Linux gets both for free by giving every open its own client queue. One shared
|
||||
ring plus "start from now" is the same answer for a single reader, which is the
|
||||
only case that exists today; per-client queues become necessary the moment
|
||||
Schcom and a client both want the keyboard.
|
||||
|
||||
### Also fixed
|
||||
|
||||
`cd` into a file reported "Operation not permitted". It was right to fail, but
|
||||
`sys_chdir` returned a bare `-1`, so the shell printed whatever errno happened
|
||||
to be lying around. It returns `-ENOTDIR` now, and `-ENOENT`/`-EFAULT` on the
|
||||
other two failure paths -- "Not a directory" sends you to the right place,
|
||||
"Operation not permitted" sends you looking at permissions.
|
||||
239
docs/architecture/schapi.md
Normal file
239
docs/architecture/schapi.md
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
# SchAPI, and Schunix as a system
|
||||
|
||||
Status: **direction agreed, not yet implemented.** Nothing in this document
|
||||
changes what is being built this week; it changes what everything is being
|
||||
built *towards*. See §7 for what actually happens now.
|
||||
|
||||
---
|
||||
|
||||
## 1. The decision
|
||||
|
||||
Schunix stops being "a Unix-like kernel with a userland bolted on" and becomes
|
||||
a **system**: a kernel, a native API, and the services and applications that
|
||||
sit on it, designed together.
|
||||
|
||||
The model is NeXTSTEP/macOS, not Linux. Specifically:
|
||||
|
||||
- A kernel that provides mechanism, with **more than one personality** on top.
|
||||
- **POSIX/Unix is a personality, not the identity.** It stays, completely and
|
||||
seriously, because it is how you get software. It is not the thing programs
|
||||
are expected to be written against.
|
||||
- **SchAPI** is the native interface: higher-level, object-shaped, designed for
|
||||
the GUI and services rather than retrofitted onto 1970s process semantics.
|
||||
|
||||
Two systems are being borrowed from, and they are *different* systems. It is
|
||||
worth being precise about which, because they disagree:
|
||||
|
||||
| Borrowed | From | Why |
|
||||
|---|---|---|
|
||||
| Layered architecture, personalities, native API over a Unix substrate | macOS / NeXTSTEP | It is the only mainstream design that got a modern API onto a Unix base without abandoning either |
|
||||
| Permanent backward compatibility | **Windows** | macOS is emphatically *not* this — Apple dropped 32-bit apps, Carbon, kexts and OpenGL, each time on a deadline |
|
||||
|
||||
That mix is coherent, but it is a mix. Taking the architecture from Apple and
|
||||
the compatibility policy from Microsoft is a deliberate choice, and the rest of
|
||||
this document is mostly about making that choice survivable — because
|
||||
compatibility-forever is the single most expensive promise an OS can make, and
|
||||
Windows pays for it every day.
|
||||
|
||||
---
|
||||
|
||||
## 2. The one mechanism that makes the promise affordable
|
||||
|
||||
**The stable interface is a library, not the syscall table.**
|
||||
|
||||
This is the whole trick, and it is what lets macOS keep decade-old binaries
|
||||
running while freely renumbering its syscalls. On macOS, nothing may issue a
|
||||
syscall directly; you link `libSystem`. Apple therefore owes compatibility only
|
||||
at the library boundary, and the kernel stays editable forever.
|
||||
|
||||
Linux made the opposite choice — the syscall ABI *is* the contract, "we do not
|
||||
break userspace" — and consequently every syscall mistake ever made is still
|
||||
there, with a `2` or a `64` suffixed to the name of its replacement.
|
||||
|
||||
So, for Schunix:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ applications, SchWM, SchDE │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ SchAPI │ POSIX / Linux personality│ ← THE STABLE BOUNDARY
|
||||
├──────────────────┴───────────────────────────┤
|
||||
│ libschunix — the only thing that may syscall│
|
||||
├──────────────────────────────────────────────┤
|
||||
│ syscall table — PRIVATE, unstable, renumberable
|
||||
├──────────────────────────────────────────────┤
|
||||
│ kernel │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Rules that follow, and they are not negotiable if the promise is to hold:
|
||||
|
||||
1. **A program that issues `syscall` directly is unsupported.** It may break at
|
||||
any release. Only `libschunix` may.
|
||||
2. `libschunix` exports versioned symbols. An entry point's behaviour never
|
||||
changes; a changed behaviour is a new version of the symbol.
|
||||
3. The kernel may renumber, merge, split or delete syscalls at will, provided
|
||||
`libschunix` still presents the same surface.
|
||||
|
||||
This *reconciles* with the existing decision to adopt Linux syscall numbering
|
||||
(`docs/roadmap.md`, and the reason musl/toybox/mksh work at all). Linux
|
||||
numbering does not go away — it becomes the **Linux personality's** entry path,
|
||||
which is exactly what it already is in practice. It is a compatibility surface
|
||||
that happens to be entered via `syscall`, and it is frozen because *Linux*
|
||||
froze it, not because we did.
|
||||
|
||||
---
|
||||
|
||||
## 3. Stability tiers
|
||||
|
||||
"Nothing can ever be removed" is only affordable if you are precise about what
|
||||
"anything" covers. Windows' real problem was never the documented API — it was
|
||||
that undocumented behaviour became load-bearing, and then it was too late.
|
||||
|
||||
Every public interface carries exactly one tier:
|
||||
|
||||
| Tier | Promise | May change |
|
||||
|---|---|---|
|
||||
| `stable` | Forever, from the release that introduced it | Never. Additions only. |
|
||||
| `provisional` | Until the next release | Freely |
|
||||
| `internal` | None | Freely, without notice |
|
||||
|
||||
**Everything in Schunix today is `provisional`.** The freeze starts at 1.0 and
|
||||
covers only what is marked `stable` at that moment. This is not a way of
|
||||
dodging the commitment — it is the only way to make it, because a promise made
|
||||
now would permanently enshrine, among other things, a `fcntl(F_SETFL)` that
|
||||
was a documented no-op until last week and a `struct file` whose `O_NONBLOCK`
|
||||
handling had never been exercised.
|
||||
|
||||
Nothing becomes `stable` by being old. It becomes `stable` by someone deciding
|
||||
it is right and writing that down.
|
||||
|
||||
---
|
||||
|
||||
## 4. `compat/` — what it actually means
|
||||
|
||||
The instinct is right: old interfaces should not litter the current tree. But
|
||||
moving *source* into a folder does not reduce the *runtime* cost, and the
|
||||
runtime cost is the one that hurts.
|
||||
|
||||
The rule that keeps compatibility cheap:
|
||||
|
||||
> **A compatibility entry point must be implementable as a translation into the
|
||||
> current API. If it cannot be, that is a defect in the current API — fix the
|
||||
> current API before freezing it.**
|
||||
|
||||
So `compat/` holds shims, not parallel implementations. A shim:
|
||||
|
||||
- lives in `compat/<release-it-was-current-in>/`,
|
||||
- is written in terms of today's API and nothing else,
|
||||
- is never called by new code,
|
||||
- carries its own tests, which are the only thing that proves the promise is
|
||||
still being kept.
|
||||
|
||||
When a shim *cannot* be expressed as a translation, that is the alarm. It means
|
||||
the new API lost information the old one carried, and it is far cheaper to
|
||||
notice that before 1.0 than to maintain two real implementations forever. That
|
||||
is precisely the trap Win32 fell into.
|
||||
|
||||
---
|
||||
|
||||
## 5. Taking from the BSDs
|
||||
|
||||
`src-trunk/` is a NetBSD checkout, and a FreeBSD one is being added alongside
|
||||
it. Both are reference material and parts bins; neither is built and neither
|
||||
may ever end up in an image.
|
||||
|
||||
**Two trees is not redundancy.** They are worth having together for three
|
||||
reasons that a single tree cannot give:
|
||||
|
||||
- Where an RFC is ambiguous — and TCP's are, constantly — two independent
|
||||
implementations agreeing is far stronger evidence than one implementation
|
||||
asserting.
|
||||
- Where one is licence-encumbered, the other may not be. NetBSD's
|
||||
`tcp_input.c` carries a WIDE Project advertising clause that Berkeley's
|
||||
rescission does not cover; FreeBSD's equivalent may well be 2-clause. See
|
||||
[../licensing.md](../licensing.md) §3.
|
||||
- They have opposite biases, and knowing which you are reading matters:
|
||||
|
||||
| | FreeBSD | NetBSD |
|
||||
|---|---|---|
|
||||
| Stack quality | The better stack in absolute terms — faster, more complete, more actively developed | Solid, conservative, less featureful |
|
||||
| Extractability | Welded to FreeBSD: `epoch(9)`, `uma`, `netisr`, VIMAGE/vnet, `if_t`, a rich locking vocabulary | Portability *is* the project's identity; assumes far less of its host kernel |
|
||||
| Use it as | The reference you check yourself against | The code you actually lift |
|
||||
|
||||
That table is the same lesson §1 of the roadmap already learned the hard way
|
||||
with FreeBSD's `sys/dev/usb`: 100k lines welded to newbus, where porting the
|
||||
USB stack means porting the device framework first. Networking is the same
|
||||
shape. Read FreeBSD, lift NetBSD, write Schunix.
|
||||
|
||||
### Licensing — read this before copying anything
|
||||
|
||||
NetBSD is not uniformly licensed: 2-clause, UC 3-clause and a minority of
|
||||
4-clause-with-advertising files all appear, and only the first two are
|
||||
comfortable. The per-template rules, the measured counts, and the one-line
|
||||
check to run before copying a file are in [../licensing.md](../licensing.md)
|
||||
§3. Do not copy from `src-trunk/` without reading it.
|
||||
|
||||
### What is actually worth taking
|
||||
|
||||
Ranked by value, not by size:
|
||||
|
||||
1. **`sys/net`, `sys/netinet` — the TCP/IP stack.** The largest single win
|
||||
available. Phase 2 of the roadmap wants ARP/IPv4/ICMP/UDP/TCP, and this is a
|
||||
mature, clean, readable, 2-clause-heavy implementation of exactly that.
|
||||
2. **`sys/kern/subr_pool.c` — the pool/slab allocator.** Schunix has `kalloc`
|
||||
and a page at a time. Every subsystem written since has invented its own
|
||||
fixed-size table to work around that.
|
||||
3. **`autoconf` — the driver framework** (`config(1)`, `cfdriver`/`cfattach`,
|
||||
`device_t`). This is the grown-up version of what `kernel/drivers/registry.c`
|
||||
and `kernel/dev/irq.c` are hand-rolling. Adopt the *model*; do not try to
|
||||
port the code wholesale.
|
||||
4. `sys/kern/subr_*` generally — rbtree, workqueue, condvar, kmem.
|
||||
|
||||
Low value: `lib/libc` (musl already fills that role), `sys/dev/usb` (we have a
|
||||
working xHCI stack written from the spec).
|
||||
|
||||
**Take designs before code.** Every file copied is a file to be understood,
|
||||
relicensed-in-notice, and maintained forever under the §3 promise.
|
||||
|
||||
---
|
||||
|
||||
## 6. What SchAPI is, concretely
|
||||
|
||||
Not yet designed. What is decided about it:
|
||||
|
||||
- It is the native interface; POSIX is available beneath it but not the idiom.
|
||||
- It is reached through `libschunix`, never by direct syscall.
|
||||
- It is designed *with* Schcom in mind, because a compositor and its clients
|
||||
are the first real users and their needs (IPC, shared buffers, event streams,
|
||||
object lifetimes) are what will shape it.
|
||||
- The IPC primitive comes first. Everything in a NeXT-shaped system rests on
|
||||
it, and unix domain sockets — already built and working — are the substrate,
|
||||
not the interface.
|
||||
|
||||
What is explicitly *not* decided: whether it is object-oriented in the Objective-C
|
||||
sense, what language it is expressed in, and whether it takes Mach's port model
|
||||
or something smaller. Those need their own document, written when Schcom has
|
||||
told us what it actually needs.
|
||||
|
||||
---
|
||||
|
||||
## 7. What changes now
|
||||
|
||||
Almost nothing, on purpose. The ordering does not change:
|
||||
|
||||
- **The native Windows build must boot first.** The clang-built kernel
|
||||
currently reaches `sinit` and dies on the first trap. A direction is worth
|
||||
nothing while the tree does not run.
|
||||
- Then the roadmap continues as written: network, GPU, fonts, Schcom.
|
||||
|
||||
What this document changes *today* is only this:
|
||||
|
||||
1. `src-trunk/` is reference material, excluded from every build and image.
|
||||
2. New interfaces are marked `provisional` from now on, so that §3 has
|
||||
something to work with at 1.0.
|
||||
3. When the network stack is written, NetBSD's is the starting point rather
|
||||
than a blank file — with the licence check in §5 applied per file.
|
||||
4. `libschunix` is created when there is a second personality to justify it.
|
||||
Until then, the *rule* stands even though the library does not exist yet:
|
||||
no new first-party program issues raw syscalls.
|
||||
403
docs/architecture/schfs.md
Normal file
403
docs/architecture/schfs.md
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
# SchFS — design
|
||||
|
||||
**Status: phases 1 and 2 are done.** The copy-on-write B-tree, the volume layer,
|
||||
the object layer, a working image writer and a checker all exist, and the kernel
|
||||
mounts a SchFS volume read-only at boot. Phase 3 (writes and transaction groups)
|
||||
is next. See §8 for exactly what is built and how to run its tests.
|
||||
|
||||
Supersedes the on-disk format described in `include/fs.h`. Keeps the name SchFS;
|
||||
the on-disk magic changes so v1 images are detected and rejected rather than
|
||||
silently misread.
|
||||
|
||||
The brief was "the best aspects of BeFS, ZFS and btrfs." Those three disagree
|
||||
with each other in places, so this document says explicitly what is taken from
|
||||
each and what is deliberately left behind.
|
||||
|
||||
## 1. What each one is actually good at
|
||||
|
||||
**ZFS — never be inconsistent.** Copy-on-write: no block that is part of the
|
||||
live tree is ever overwritten. An update writes new blocks, then atomically
|
||||
flips a single root pointer. A crash therefore lands on either the old tree or
|
||||
the new one — never a half-written mix. Consequence: **no fsck**, ever. Paired
|
||||
with end-to-end checksums where a block's checksum is stored in its *parent*
|
||||
pointer, not in the block itself, which makes the whole tree self-validating and
|
||||
catches misdirected writes and bit rot that a self-stored checksum cannot.
|
||||
|
||||
**btrfs — one data structure, reused everywhere.** btrfs has essentially one
|
||||
engine: a copy-on-write B-tree. Inodes, directory entries, extent maps, free
|
||||
space, and snapshots are all just different key types in that same tree. For a
|
||||
small team this is the single most important idea in the whole design: you write
|
||||
and debug *one* tricky data structure, then get five subsystems from it. Also
|
||||
takes extents with **reference counts**, which is what makes snapshots and
|
||||
reflink copies fall out almost for free.
|
||||
|
||||
**BeFS — the part that feels like a database, at a size one person can build.**
|
||||
BeFS is a 64-bit journaled filesystem whose distinguishing feature is B+tree
|
||||
**indexes over file attributes**, with a query interface on top. That is the
|
||||
"SQL-like" capability, and unlike WinFS it actually shipped. Critically, BeFS is
|
||||
also the right *scale* model: a small team, a readable codebase, and a book
|
||||
(Giampaolo, *Practical File System Design*, free online) that describes it
|
||||
end to end.
|
||||
|
||||
## 2. What is deliberately not taken
|
||||
|
||||
- **No ZFS storage pools / RAID-Z / vdevs.** Multi-disk redundancy is a large
|
||||
subsystem that solves a problem Schunix does not have yet.
|
||||
- **No btrfs-style rebalancing or online format conversion.** Enormous
|
||||
complexity, and it is the historical source of btrfs's worst bugs.
|
||||
- **No actual SQL.** No parser, no query planner, no relational engine. See §6 —
|
||||
the useful part of "database filesystem" is the *index*, not the language.
|
||||
WinFS spent 2003-2006 and a large team on the other interpretation and
|
||||
shipped nothing.
|
||||
- **No deduplication.** ZFS's dedup is famous for needing more RAM than anyone
|
||||
expects. Extent refcounts give reflink/snapshot benefits without it.
|
||||
|
||||
## 3. On-disk shape
|
||||
|
||||
```
|
||||
+--------------------+ block 0 boot block (untouched by the FS)
|
||||
+--------------------+ blocks 1..N uberblock array (128 slots, rotated)
|
||||
+--------------------+
|
||||
| CoW B-tree | one tree, many key types:
|
||||
| | (ino, INODE, 0) -> inode record
|
||||
| | (ino, DIRENT, hash) -> directory entry
|
||||
| | (ino, EXTENT, offset) -> extent mapping
|
||||
| | (ino, ATTR, nameid) -> extended attribute
|
||||
| | (idx, INDEX, key) -> attribute index entry
|
||||
+--------------------+
|
||||
| free space | extent-based, tracked as tree keys, not a bitmap
|
||||
+--------------------+
|
||||
```
|
||||
|
||||
**Uberblock array (from ZFS).** 128 slots written round-robin, each carrying a
|
||||
transaction number and a checksum. Mount scans all slots and takes the
|
||||
highest-numbered one that validates. This is what makes crash recovery
|
||||
"pick the newest good root" instead of "replay a journal."
|
||||
|
||||
**Everything is one B-tree (from btrfs).** Keyed by
|
||||
`(objectid, type, offset)`, 24 bytes. Adding a feature usually means adding a
|
||||
key type, not a new on-disk structure.
|
||||
|
||||
**Checksums live in the parent (from ZFS).** Each internal node stores, for
|
||||
every child pointer, that child's CRC32c. A block that is read is verified
|
||||
against the pointer that led to it. v1 already chose CRC32c and already has
|
||||
checksum fields in `dinode` and `superblock` — same algorithm, better placement.
|
||||
|
||||
### Widths — every v1 limit that has to die
|
||||
|
||||
| v1 | limit | v2 |
|
||||
|---|---|---|
|
||||
| `dirent.inum` is `ushort` | 65,535 files, total | `uint64` object ids |
|
||||
| `dinode.size` is `uint` | 4 GB max file | `uint64` |
|
||||
| `uint` block numbers, BSIZE 1024 | 4 TB volume | `uint64` blocks, 4096-byte |
|
||||
| `DIRSIZ` 14 | 14-char filenames | variable, up to 255 |
|
||||
| fixed hash table per dir | every dir ≥113 KB, capped at 119 KB | B+tree, pay per entry |
|
||||
| `addrs[NDIRECT+3]` | block-at-a-time | extents with refcounts |
|
||||
|
||||
Block size moves 1024 → 4096 to match modern media and make extents worthwhile.
|
||||
|
||||
## 4. What already exists and gets reused
|
||||
|
||||
Not starting from zero:
|
||||
|
||||
- **`struct schfs_extent`** is already defined in `include/fs.h` and unused.
|
||||
The extent concept is already sketched; v2 gives it refcounts and 64-bit
|
||||
fields.
|
||||
- **CRC32c checksum fields** already exist in `dinode` and `superblock`, and
|
||||
mkfs already computes them. Same algorithm, relocated into parent pointers.
|
||||
- **`kernel/fs/log.c`** is a working transaction log with `begin_op`/`end_op`
|
||||
call sites throughout the FS. Under CoW the log is not needed for consistency,
|
||||
but `begin_op`/`end_op` are exactly the right places to bracket a transaction
|
||||
group, so the *call sites* survive even though the implementation is replaced.
|
||||
- **The buffer cache** (`bio.c`) is format-agnostic and needs no change beyond
|
||||
the block-size constant.
|
||||
|
||||
## 5. Phasing
|
||||
|
||||
Each phase leaves a bootable system. Do not start phase 2 before phase 1 is
|
||||
solid — the B-tree is the load-bearing piece and everything else assumes it.
|
||||
|
||||
**Phase 1 — the B-tree, in userland first.** Write the CoW B-tree as a plain C
|
||||
library with a block-device abstraction, and test it on a *host* file with a
|
||||
fuzz/torture harness (random insert/delete/lookup, simulated crash at every
|
||||
write point, verify the tree always mounts). This is the one component where a
|
||||
subtle bug is unfixable later, and testing it in the kernel is far harder. It is
|
||||
also portable straight into `tools/mkfs`.
|
||||
|
||||
**Phase 2 — read-only mount. DONE.** New magic, uberblock scan, tree walk, inode
|
||||
and dirent key types. `tools/mkschfs` writes images; the kernel only reads them.
|
||||
The system boots from a ramdisk root (see `docs/layout.md` and the ramdisk work)
|
||||
so a broken disk cannot stop you from booting.
|
||||
|
||||
Went further than planned in two places, both cheaply: data blocks carry
|
||||
checksums too (a `CSUM` key type, as btrfs does — without it "end-to-end
|
||||
checksums" would cover only metadata, which is the part least likely to rot),
|
||||
and extents landed early because reserving a file's blocks in one go was
|
||||
simpler than not doing it.
|
||||
|
||||
Not yet done: SchFS files are not reachable through `open(2)`. The mount is
|
||||
observable through `/proc/schfs` and through the kernel API in `schfs_int.h`,
|
||||
but wiring it into `namei`/`readi` means the VFS work that phase 3 needs anyway.
|
||||
|
||||
**Phase 3 — writes, transaction groups. DONE.** Every `schfs_k*` call that
|
||||
changes anything is one transaction group, committed by writing a new uberblock
|
||||
before it returns. The full write API — create, link, unlink, rename, write at
|
||||
an arbitrary offset, truncate — lives in `objw.c` and is exercised by
|
||||
`tools/schfs_objtest.c`.
|
||||
|
||||
Two things went differently from the plan above, both for reasons worth
|
||||
recording.
|
||||
|
||||
*Free space is not tracked as tree keys.* Allocating from a free map that lives
|
||||
in the tree mutates the tree, which allocates, which changes the map; btrfs
|
||||
resolves that with a fixpoint loop and ZFS with log-structured space maps. SchFS
|
||||
sidesteps it: the committed tree already says exactly which blocks are live, so
|
||||
the map is **derived** by walking the tree once at mount and held only in RAM.
|
||||
It cannot disagree with the filesystem, because it is computed from it, and a
|
||||
crash raises no question at all. The cost is mount time proportional to metadata
|
||||
size, and a ceiling on volume size set by how much bitmap fits in memory. If
|
||||
mount time ever hurts, the map can be cached on disk purely as a hint,
|
||||
validated against the generation and discarded on a mismatch — an addition, not
|
||||
a format change. See `kernel/fs/schfs/space.c`.
|
||||
|
||||
*Data blocks needed a generation stamp too.* The B-tree already rewrites its own
|
||||
nodes in place within a group, since a block that no committed root can reach is
|
||||
nobody's fallback. Data blocks had no such marker, so every hundred-byte write
|
||||
copied a whole 4 KB block and split an extent record around it. `schfs_extent`
|
||||
gained a 64-bit `gen`, and the same rule now applies to file data.
|
||||
|
||||
**Phase 3½ — reachable through `open(2)`. DONE.** SchFS is a mounted filesystem:
|
||||
|
||||
```
|
||||
mkdir /mnt
|
||||
mount -t schfs /dev/hdb /mnt
|
||||
ls /mnt ; echo hi > /mnt/tmp/w ; cat /mnt/tmp/w ; mkdir /mnt/tmp/d
|
||||
```
|
||||
|
||||
The VFS grew a real dispatch layer rather than a set of `if (dev == SCHFS_DEV)`
|
||||
checks. `struct fs_ops` (include/file.h) is one table per filesystem, chosen by
|
||||
`iget()` from the device number and hung off the inode; v1's implementations
|
||||
keep their bodies and are simply named `v1_*`. Adding a third filesystem is a
|
||||
new table and nothing else. `kernel/fs/schfs/vfs.c` is SchFS's, and it is pure
|
||||
translation — object ids in, inodes out — because all the filesystem logic
|
||||
already lives below it.
|
||||
|
||||
Three things had to change on the way, none of them SchFS's fault:
|
||||
|
||||
- `struct inode.inum` and `.size` were 32-bit, capping objects at 2³² and files
|
||||
at 4 GiB on a filesystem that promises far more. Both are `uint64` now, as are
|
||||
`readi`/`writei` offsets and `struct file.off`.
|
||||
- `struct dirent` carries a 16-bit inum and a 14-byte name, so it cannot
|
||||
represent a SchFS entry at all. `getdents64` is now an fs_op: each filesystem
|
||||
emits the record stream itself, and `f->off` is whatever position it needs to
|
||||
resume. v1 walks its hash buckets; SchFS walks a key range and synthesises
|
||||
"." and "..", which it does not store.
|
||||
- Mounting is a substitution made during path resolution (`mount_add` /
|
||||
`mount_cross` in `fs.c`). Nothing on either disk records it. The reverse
|
||||
direction matters as much as the forward one: `..` at a mounted root has to
|
||||
leave the mount, and the filesystem underneath has no idea it is mounted
|
||||
anywhere.
|
||||
|
||||
Three long-standing bugs surfaced the moment real commands ran against it, all
|
||||
in the shared VFS rather than in SchFS:
|
||||
|
||||
- `include/fcntl.h` used xv6's open flags while every program in the userland
|
||||
sends Linux's. `O_APPEND` (0x400) landed on `O_TRUNC`, so `cmd >> file`
|
||||
*truncated* it; `O_TRUNC` (0x200) landed on `O_CREATE`, so `cmd > file` left
|
||||
whatever was past the new end in place. Nothing about either pointed at a
|
||||
header.
|
||||
- `sys_openat` silently dropped `O_TRUNC` and `O_APPEND` while `sys_open`
|
||||
honoured both, so the same redirection behaved differently depending on which
|
||||
entry point libc happened to call.
|
||||
- `rmdir` had no Linux syscall number, so every Linux-ABI program got ENOSYS.
|
||||
|
||||
**Phase 4 — extents with refcounts.** Enables reflink copies and snapshots
|
||||
nearly for free.
|
||||
|
||||
**Phase 5 — attribute index and query.** See below.
|
||||
|
||||
## 6. The "SQL" part, honestly
|
||||
|
||||
What you want from "works like SQL" is: *ask a question about a lot of files and
|
||||
get an answer fast, without walking every directory*. That is an **index**, not
|
||||
a query language. BeFS's design:
|
||||
|
||||
- Files carry typed attributes (`string`, `int`, `time`, …).
|
||||
- An attribute can be *indexed*: a dedicated B+tree keyed by attribute value,
|
||||
mapping value → object ids.
|
||||
- A query is a predicate over indexed attributes, evaluated by intersecting
|
||||
index ranges.
|
||||
|
||||
In SchFS this is just another key type — `(idx, INDEX, key)` — in the tree
|
||||
that already exists. That is the whole reason to build phases 1-4 the btrfs way:
|
||||
the "database filesystem" feature becomes a few hundred lines on top of
|
||||
machinery already written and debugged, rather than a separate engine.
|
||||
|
||||
A concrete `query()` syscall and a `find`-like tool come after that, and are
|
||||
small by comparison.
|
||||
|
||||
## 7. Honest cost
|
||||
|
||||
Phases 1-3 are roughly 3,000-4,000 lines and are the bulk of the work. Phase 1
|
||||
alone (B-tree + torture harness) is worth doing carefully and unhurriedly; every
|
||||
later phase compounds on it.
|
||||
|
||||
This is the largest single piece of work in Schunix. It is also entirely
|
||||
ordinary filesystem engineering with excellent published references
|
||||
(Giampaolo for BeFS; the btrfs design papers; the ZFS uberblock/checksum-in-
|
||||
parent scheme) — there is no research risk here, only volume.
|
||||
|
||||
**Do this before the installer.** The installer's entire job is to create and
|
||||
populate this format. Building it against v1 means building it twice.
|
||||
|
||||
## 7½. Limits
|
||||
|
||||
Three different things get called "the limit", and they are worth keeping apart:
|
||||
what the **on-disk format** can express, what **this implementation** can
|
||||
currently mount, and what the **rest of Schunix** can carry. The smallest one is
|
||||
what you actually hit.
|
||||
|
||||
### The format
|
||||
|
||||
| | limit | where it comes from |
|
||||
|---|---|---|
|
||||
| Volume | **64 ZiB** (2⁶⁴ × 4 KiB blocks) | `uint64` block numbers |
|
||||
| File | **16 EiB** (2⁶⁴ bytes) | `schfs_inode.size` |
|
||||
| Files per volume | **1.8 × 10¹⁹** | `uint64` object ids |
|
||||
| Hard links per file | **4.3 × 10⁹** | `uint32 nlink` |
|
||||
| Filename | **255 bytes** | `SCHFS_NAME_MAX` |
|
||||
| Entries per directory | no fixed limit | one tree key per name hash |
|
||||
| Extent | **64 ZiB** in one record | `uint64 nblocks` |
|
||||
| Tree depth | **8 levels** | `SCHFS_MAX_LEVEL` |
|
||||
| Block size | **4096 bytes**, fixed | `SCHFS_BLKSIZE`; a mismatch is refused at mount |
|
||||
| Transaction groups | **1.8 × 10¹⁹** | `uint64` generation, never reused |
|
||||
|
||||
Directory *depth* is unbounded on disk; path resolution is what limits it.
|
||||
|
||||
### This implementation
|
||||
|
||||
| | limit | where it comes from |
|
||||
|---|---|---|
|
||||
| Volume, mounted | **64 GiB** | the in-RAM allocation bitmap: `SCHFS_SPACE_PAGES` (512) pages × 32768 blocks |
|
||||
| Volume, via the buffer cache | **4 TiB** | `bio.c` addresses blocks with a `uint`, and one SchFS block spans four of them |
|
||||
| RAM per mount | **8 KiB per GiB** of volume | two bitmaps, one bit per block each |
|
||||
| Inline value in a leaf | **1016 bytes** | `SCHFS_MAX_INLINE` = a quarter of a node |
|
||||
| Colliding names in one directory | ~**60**, sharing a hash | the packed list for one hash must fit in one inline value |
|
||||
| Directory entries at 8-byte names | ~**42** per key, unlimited keys | as above |
|
||||
| Records per leaf / children per node | **127 / 84** | 4 KiB minus a 32-byte header |
|
||||
| Mount time | one pass over the metadata | the allocation map is derived, not stored |
|
||||
|
||||
The 64 GiB mount ceiling is the one that bites first, and it is one constant:
|
||||
raising `SCHFS_SPACE_PAGES` to 4096 makes it 512 GiB at 64 KiB of pointers per
|
||||
mount. It is set where it is because the page-pointer vector lives inside
|
||||
`struct schfs_vol`, which the kernel holds as static storage.
|
||||
|
||||
### The rest of Schunix
|
||||
|
||||
These are VFS and driver limits rather than SchFS ones:
|
||||
|
||||
- `MAXPATH` caps a path at **128 bytes**.
|
||||
- `NMOUNT` allows **8** mounts.
|
||||
- The IDE driver is 28-bit LBA: **128 GiB** per disk.
|
||||
- A single `read`/`write` call is capped at **4 GiB** (`n` is a `uint`); the
|
||||
file itself is not.
|
||||
|
||||
The two that used to be here — a 16-bit inode number in `struct dirent` and a
|
||||
32-bit `inum`/`size` in `struct inode` — are gone. `getdents64` is a per-
|
||||
filesystem operation now, so SchFS emits its own records with 64-bit inode
|
||||
numbers and names up to 255 bytes, and the inode carries 64-bit numbers and
|
||||
sizes throughout.
|
||||
|
||||
## 8. What exists now
|
||||
|
||||
```
|
||||
include/schfs.h on-disk format + compile-time layout assertions
|
||||
kernel/fs/schfs/btree.{c,h} the copy-on-write B-tree and its cursor
|
||||
kernel/fs/schfs/schfs_int.h volume + object layer interface
|
||||
kernel/fs/schfs/space.c allocation bitmap, derived from the tree at mount
|
||||
kernel/fs/schfs/vol.c uberblock ring: open, format, commit
|
||||
kernel/fs/schfs/obj.c read side: inodes, dirents, extents, path lookup
|
||||
kernel/fs/schfs/objw.c write side: create, link, unlink, rename, write
|
||||
kernel/fs/schfs/kmount.c kernel: block device, mount, transaction bracket
|
||||
kernel/fs/schfs/vfs.c the fs_ops table: SchFS behind open/read/write
|
||||
tools/mkschfs.c build an image from a directory tree
|
||||
tools/schfs_fsck.c verify, list, extract
|
||||
tools/schfs_test.c B-tree torture harness
|
||||
tools/schfs_objtest.c object layer: files, directories, space accounting
|
||||
tools/schfs_roundtrip.sh end-to-end filesystem test
|
||||
tools/mutate.sh mutation-tests the suites themselves
|
||||
tools/bootcheck.ps1 boots under QEMU and drives the shell
|
||||
```
|
||||
|
||||
Everything except `kmount.c` is compiled *both* into the kernel and into the
|
||||
host tools, so the writer and the reader cannot drift apart: there is one
|
||||
implementation of the format and nothing to disagree with.
|
||||
|
||||
### Running the tests
|
||||
|
||||
```
|
||||
make schfs-check all three suites
|
||||
make schfs-test B-tree only
|
||||
make schfs-objtest object layer only
|
||||
make schfs-image-test round-trip only
|
||||
```
|
||||
|
||||
`make schfs-check` after touching anything under `kernel/fs/schfs`.
|
||||
|
||||
The round-trip's oracle is the host's own filesystem: an image is built from a
|
||||
directory tree, extracted with a separate program, and `diff -r`'d against the
|
||||
original. It also asserts hard-link counts (which `diff` cannot see), checks
|
||||
that rebuilding produces byte-identical output, and runs the whole thing a
|
||||
second time with the name hash cut to 6 bits so the packed-dirent collision path
|
||||
— which a 64-bit hash would never reach — is actually exercised.
|
||||
|
||||
`schfs_objtest` runs against a memory-backed volume, so a full
|
||||
create/write/delete/remount cycle takes milliseconds. Its most important check
|
||||
is the least glamorous: after deleting everything, free space must be back to
|
||||
exactly what it was on an empty volume. Every other test in the suite still
|
||||
passes on a filesystem that leaks a block on every delete.
|
||||
|
||||
`tools/mutate.sh` injects six deliberate bugs and fails if any of them goes
|
||||
undetected. It is how the suites are kept honest; see the note in §9.
|
||||
|
||||
### Verifying on real hardware
|
||||
|
||||
```
|
||||
make ramroot root from RAM, so device 1 is free
|
||||
make build/schfs.img
|
||||
make qemu-schfs then, at the shell: cat /proc/schfs
|
||||
```
|
||||
|
||||
`.\run.ps1 -SchFS` does the same on Windows, and `tools\bootcheck.ps1 -SchFS`
|
||||
does it unattended and asserts on the transcript.
|
||||
|
||||
## 9. Things learned the hard way
|
||||
|
||||
Recorded because each one cost real time and none is obvious from the code.
|
||||
|
||||
- **The root's object id must be reserved.** `next_objectid` started at
|
||||
`SCHFS_FIRST_OBJECTID`, which *is* the root's id, so the first directory
|
||||
created silently became the root. It then contained itself, and every tree
|
||||
walk recursed until the stack ran out. `schfs_fsck --check` now detects
|
||||
directory loops explicitly rather than crashing.
|
||||
- **Cursor buffers alias.** A range callback gets a pointer into the cursor's
|
||||
block buffer, and almost every readdir caller immediately looks the entry up,
|
||||
which overwrites it. Directory iteration copies each entry out before calling
|
||||
back. Extracting a tree with directories reparented under their own children
|
||||
is what this looks like when it goes wrong.
|
||||
- **A 4096-byte block does not fit on a 4096-byte kernel stack.** The cursor
|
||||
exists precisely to avoid one block buffer per tree level, but
|
||||
`schfs_vol_open` still kept a whole uberblock as a local and blew the stack on
|
||||
the first boot. The kstack canary caught it cleanly.
|
||||
- **Extents only merge if you allocate the run first.** Writing an extent record
|
||||
allocates blocks for its own copy-on-write path, so allocating data one block
|
||||
at a time guarantees the blocks are never adjacent and every block needs its
|
||||
own extent. `schfs_file_reserve` claims the whole run up front; an 822 KB
|
||||
binary is one extent instead of 201.
|
||||
- **Freed blocks are reusable during a build.** Copy-on-write forbids reusing a
|
||||
released block because the previously committed tree still points at it — but
|
||||
a build has committed nothing. Recycling them cut the test image from 3596
|
||||
blocks to 374.
|
||||
- **Reproducibility dies from atime.** mkschfs reads every source file, which
|
||||
updates its atime, so copying atime into the image made two builds of the same
|
||||
tree differ. All three timestamps come from mtime.
|
||||
130
docs/drivers.md
Normal file
130
docs/drivers.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# Drivers
|
||||
|
||||
How hardware gets found, claimed and used, and where drivers that are not part
|
||||
of the build will attach.
|
||||
|
||||
---
|
||||
|
||||
## 1. The device model
|
||||
|
||||
There is one list of everything attached, whatever bus found it:
|
||||
`include/device.h` and `kernel/dev/device.c`, readable at `/proc/devices`.
|
||||
|
||||
```
|
||||
name bus class driver device
|
||||
kbd0 platform input ps2kbd PS/2 keyboard
|
||||
mouse0 platform input ps2mouse PS/2 mouse
|
||||
uart0 platform other uart8250 16550 serial port
|
||||
ide0 platform storage ide ATA disk controller
|
||||
vga0 pci display vga display controller
|
||||
```
|
||||
|
||||
Before this there were two registries in `kernel/drivers/registry.c` — an array
|
||||
of PCI drivers and an array of USB drivers — with nothing that knew about both.
|
||||
That is sufficient for *binding* and useless for the three things wanted next.
|
||||
|
||||
**Enumeration.** "What is in this machine, and what claimed it" had no answer
|
||||
covering a USB disk and a PCI display at once. A device manager is exactly that
|
||||
question.
|
||||
|
||||
**Classes.** A compositor should ask for *the display* and not care whether that
|
||||
is the VBE framebuffer or virtio-gpu. Without a class every consumer hardcodes a
|
||||
driver, and adding the second driver means editing the consumers — which is how
|
||||
a framebuffer address ended up hardcoded in three places here once already.
|
||||
|
||||
**A boundary for drivers that are not compiled in.** They have to attach
|
||||
somewhere that is not an array fixed at link time.
|
||||
|
||||
Two vocabularies are kept deliberately separate: a device's **bus** is
|
||||
provenance (`platform`, `pci`, `usb`), its **class** is capability
|
||||
(`display`, `input`, `storage`, `net`, `bus`). A USB disk and a SATA disk are
|
||||
both `storage` despite sharing no numbering at all. Where a driver claims a
|
||||
device, the driver's opinion wins over the descriptor's: `struct usb_driver` has
|
||||
a `devclass` field separate from the USB interface class, because the descriptor
|
||||
says what the device *claims* to be and the driver knows what it *made* of it.
|
||||
|
||||
It is a flat list with a bus tag, **not** a device tree. Nothing yet needs to
|
||||
walk a hierarchy, and a tree nobody traverses is just a harder list. Hot-unplug
|
||||
would need more; the `in_use` flag is the hook.
|
||||
|
||||
Platform devices are registered in one place
|
||||
(`platform_register_devices()` in `registry.c`) rather than inside each driver's
|
||||
init, for the same reason the driver arrays are written out by hand: a list you
|
||||
can read beats a list assembled by side effects.
|
||||
|
||||
---
|
||||
|
||||
## 2. Where this is going: a hybrid model
|
||||
|
||||
The intended shape is the NeXTSTEP/macOS one rather than Linux's — a device is
|
||||
an object with a class and an ops vtable, and consumers talk to the class.
|
||||
|
||||
Two things often get bundled together as "loadable drivers" and they are
|
||||
separable:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Loadable modules** | A stable driver ABI, drivers as files loaded at runtime (`.sys`, `.kext`). A linker/loader job on top of the model above. |
|
||||
| **Drivers out of kernel** | Drivers as user-space processes (macOS DriverKit, which is why kexts are being retired). An IPC and memory-mapping job; the `AF_UNIX` and shared-memory work it needs already exists. |
|
||||
|
||||
The second is more in the spirit of the SchAPI direction and is slower and
|
||||
larger. Neither is built yet. What exists is the attachment point they both
|
||||
need.
|
||||
|
||||
---
|
||||
|
||||
## 3. Linux drivers
|
||||
|
||||
**Decided 2026-08-08.** Linux drivers are not shipped with Schunix and never
|
||||
will be. The user may install one on their own machine, through a device
|
||||
manager or equivalent, and Schunix will load it.
|
||||
|
||||
The licensing reasoning matters and is worth stating exactly, because it is
|
||||
easy to get wrong in both directions:
|
||||
|
||||
- **Distribution is what triggers the GPL.** Shipping a GPL-2.0-only driver in
|
||||
an Apache-2.0 image is what cannot be done — see [licensing.md](licensing.md)
|
||||
§2. A user obtaining a driver themselves and loading it on their own machine
|
||||
is their act, not ours, and is not a distribution by us.
|
||||
- **Implementing a loader is not copying.** Writing support for a module format
|
||||
and a driver ABI from published documentation produces our own work. It is a
|
||||
clean-room implementation, and it stays one only if it is written from
|
||||
documentation rather than transcribed from Linux source. Reading Linux to
|
||||
understand *hardware* is fine; translating its code is not, whatever the
|
||||
identifiers say.
|
||||
|
||||
So the rule for this subsystem is the same as everywhere else in the tree: the
|
||||
loader is ours and Apache-2.0, what it loads is the user's business, and nothing
|
||||
GPL ever enters an image we build. `RAMDISK_EXCLUDE` and `NOTPORTS` are the
|
||||
existing mechanisms for keeping that true mechanically rather than by intention.
|
||||
|
||||
---
|
||||
|
||||
## 4. Adding a driver today
|
||||
|
||||
1. Write `kernel/drivers/<class>/<name>/<name>.c` exporting a
|
||||
`struct pci_driver` or `struct usb_driver`. One directory per driver, so a
|
||||
driver that grows a register header, a second source file or a firmware blob
|
||||
has somewhere to put it:
|
||||
|
||||
kernel/drivers/
|
||||
registry.c
|
||||
gpu/vga/ gpu/virtio_gpu/
|
||||
net/e1000/
|
||||
bus/xhci/
|
||||
usb/core/ usb/hid/ usb/storage/ usb/hub/
|
||||
|
||||
2. Declare it in `kernel/drivers/registry.c` and add it to the array.
|
||||
3. Add the object to `KERN_DRIVERS` in the `Makefile`.
|
||||
|
||||
Registration in the device model is automatic — `pci_bind()` and
|
||||
`usb_bind_all()` publish whatever they claim, so a driver cannot forget to. Set
|
||||
`devclass` on a USB driver, or the device lands in `other`.
|
||||
|
||||
The best source for a new driver is the **specification**, not another kernel:
|
||||
xHCI, AHCI, virtio, NVMe and the Intel datasheets are public documents with no
|
||||
licence on what you write from them. `kernel/drivers/bus/xhci/xhci.c` came from the spec, and so did
|
||||
`net/e1000/` and `gpu/virtio_gpu/`. Where a *table* of constants is genuinely
|
||||
faster to transcribe than to retype from a PDF -- device IDs, register offsets --
|
||||
taking it from a BSD tree is fine after the licence check, and goes in `NOTICE`.
|
||||
That is data; the logic stays ours.
|
||||
175
docs/layout.md
Normal file
175
docs/layout.md
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
# Repository layout
|
||||
|
||||
One rule holds the whole thing together:
|
||||
|
||||
> **A directory contains either hand-written source or generated output. Never both.**
|
||||
|
||||
Everything generated lives under `build/`. Delete `build/` and you lose nothing
|
||||
that can't be rebuilt (except upstream tarballs, which live in `dist/` and are
|
||||
deliberately kept out of `clean`).
|
||||
|
||||
```
|
||||
schunix/
|
||||
├─ boot/ Stage 1 MBR + stage 2 loader (splash, F8 menu, kernel load)
|
||||
├─ kernel/ The kernel, by subsystem: arch/ core/ dev/ fs/ proc/ sys/
|
||||
├─ include/ Headers shared by kernel, userland, and tools/mkfs
|
||||
├─ lib/ First-party userland runtime: _start glue, syscall stubs,
|
||||
│ the initcode blob embedded in the kernel, user.ld
|
||||
├─ cmd/ First-party programs — one directory per program:
|
||||
│ cmd/<name>/main.c → sysroot/bin/<name>
|
||||
├─ ports/ Third-party software — one directory per port.
|
||||
│ Contains ONLY a Makefile and optional patches/.
|
||||
├─ projects/ Things that run ON Schunix rather than things that ARE it:
|
||||
│ the compositor, window manager, desktop, and installer.
|
||||
│ Not part of the kernel build. See projects/README.md.
|
||||
├─ tools/ Host-side helpers, SOURCE ONLY: mkfs.c, the SchFS test
|
||||
│ suites, sign.pl, vectors.pl, and the PowerShell harnesses.
|
||||
│ Their binaries build into build/tools/ -- this directory
|
||||
│ used to hold both, which is the one rule this layout has.
|
||||
│ tools/loc.ps1 counts how much of the tree is first-party.
|
||||
├─ sysroot/ Staging for the disk image. etc/ is hand-maintained;
|
||||
│ bin/ lib/ sbin/ are populated by `make sysroot-install`.
|
||||
├─ dist/ Upstream tarball cache. Survives `make clean`.
|
||||
├─ Refrence/ A FreeBSD checkout (Refrence/freebsd-src), kept as reference
|
||||
│ material and a parts bin. Never built, never in an image.
|
||||
│ Read it when a specification is ambiguous; copy from it only
|
||||
│ after the per-file licence check in docs/licensing.md.
|
||||
│ A full clone is 2.1 GB and a previous NetBSD one was deleted
|
||||
│ for that reason. Only sys/dev, sys/net and sys/netinet have
|
||||
│ ever been read, so a sparse checkout is ~100 MB:
|
||||
│ git clone --filter=blob:none --sparse <url> Refrence/freebsd-src
|
||||
│ git -C Refrence/freebsd-src sparse-checkout set │ sys/dev sys/net sys/netinet
|
||||
├─ src-trunk/ A NetBSD checkout, kept as reference material and a parts
|
||||
│ bin. NOT built, NOT installed, and it must never reach an
|
||||
│ image. Copying from it is governed by the per-file licence
|
||||
│ check in docs/architecture/schapi.md §5 — NetBSD is not
|
||||
│ uniformly licensed.
|
||||
├─ docs/ Design docs
|
||||
└─ build/ ALL generated output (see below)
|
||||
```
|
||||
|
||||
## build/
|
||||
|
||||
```
|
||||
build/
|
||||
├─ boot/ bootblock, stage2
|
||||
├─ kernel/ object files, the linked kernel, kernel.asm
|
||||
├─ lib/ libc glue objects, initcode blob
|
||||
├─ cmd/ linked first-party programs
|
||||
├─ fs.img the filesystem image
|
||||
└─ ports/<name>/
|
||||
├─ src/ extracted upstream tarball
|
||||
├─ obj/ out-of-tree build directory
|
||||
└─ prefix/ install prefix (musl's headers and libs land here)
|
||||
```
|
||||
|
||||
## Adding your own program
|
||||
|
||||
```sh
|
||||
mkdir cmd/hello
|
||||
$EDITOR cmd/hello/main.c # include "types.h", "user.h", ...
|
||||
```
|
||||
|
||||
Then add `hello` to `CMDS` in the root `Makefile`. It gets linked against
|
||||
`lib/` and installed to `sysroot/bin/hello` by `make sysroot-install`.
|
||||
|
||||
## Adding a third-party port
|
||||
|
||||
```sh
|
||||
mkdir ports/foo
|
||||
$EDITOR ports/foo/Makefile # start with `include ../mk/port.mk`
|
||||
```
|
||||
|
||||
`port.mk` derives everything from the directory name — it gives you `$(SRCDIR)`,
|
||||
`$(BUILDDIR)`, `$(PREFIX)`, `$(DISTDIR)`, `$(SYSROOT)`, and `$(MUSL)` (the
|
||||
musl install prefix that every port links against). Follow the pattern in
|
||||
`ports/mksh/Makefile`: fetch tarball to `$(DISTDIR)`, extract to `$(SRCDIR)`,
|
||||
build into `$(BUILDDIR)`, install into `$(SYSROOT)`. `clean` removes
|
||||
`$(PORTDIR)` and nothing else.
|
||||
|
||||
`ports/Makefile` picks up the new directory automatically. If it links against
|
||||
musl, add `foo: musl` there so ordering is right.
|
||||
|
||||
Local modifications to upstream go in `ports/foo/patches/*.patch` and are
|
||||
applied after extraction — never by editing files under `build/`, which are
|
||||
wiped on every clean.
|
||||
|
||||
## Ramdisk root
|
||||
|
||||
```sh
|
||||
make # normal: root on the IDE disk (build/fs.img as hdb)
|
||||
make ramroot # root from a SCHFS image linked into the kernel
|
||||
```
|
||||
|
||||
`make ramroot` builds `build/fs-ram.img` (default 6000 blocks, override with
|
||||
`RAMDISK_BLOCKS=`) and links it into the kernel, so the system boots to a shell
|
||||
with **no data disk attached at all**. That is what makes destructive work on
|
||||
the real disk survivable — reformatting it, or bringing up a new on-disk format
|
||||
— and it is the first piece of the installer.
|
||||
|
||||
Use `make ramroot`, not `make RAMROOT=1`: the flag changes `CFLAGS`, which make
|
||||
cannot see, so objects compiled for one setting would be silently reused for
|
||||
the other and `ROOTDEV` would point at the wrong device. The target drops the
|
||||
compiled objects first. Switching back is the same — plain `make` after a
|
||||
`ramroot` build reuses stale objects, so run `rm -rf build/kernel build/lib
|
||||
build/cmd` first (or just `make clean`).
|
||||
|
||||
The image costs boot time: stage2 loads the kernel by PIO one sector at a time,
|
||||
so a 6 MB ramroot kernel is visibly slower to start than the 350 KB default.
|
||||
|
||||
**What may go in it is a licensing question, not only a size one.** The image is
|
||||
linked into the kernel binary, so anything in `sysroot/` when it is built becomes
|
||||
part of a distributable artifact. A file staged there under a licence that
|
||||
cannot be combined with Apache-2.0 does not merely ride along — it makes the
|
||||
kernel itself undistributable. `RAMDISK_EXCLUDE` in the Makefile is the list of
|
||||
paths kept out, and enforcing it in the build rather than relying on someone
|
||||
remembering to unstage something first is deliberate. It has been needed once
|
||||
already: a 33 MB test payload landed in `sysroot/` and both overflowed the image
|
||||
and would have gone into the kernel binary.
|
||||
|
||||
Three knobs, all in the Makefile, none of them `FSSIZE`:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `RAMDISK_BLOCKS` | image size, 6000 (× 1 KiB `BSIZE`) |
|
||||
| `RAMDISK_NINODES` | inode table size, 2000 |
|
||||
| `RAMDISK_EXCLUDE` | sysroot-relative paths to omit, with their subtrees |
|
||||
|
||||
`RAMDISK_NINODES` is worth knowing about because the inode table is
|
||||
*preallocated*: mkfs's default of 12000 inodes cost 1501 of the 6000 blocks — a
|
||||
quarter of the image — to describe about 260 files. At 2000 it costs 251, and
|
||||
metadata overall fell from 1596 blocks to 345.
|
||||
|
||||
If `mkfs` reports the image is full, read its message: it now prints the
|
||||
metadata breakdown and names the right knob for whichever image overflowed. It
|
||||
used to say only "increase FSSIZE in param.h", which is a constant that has
|
||||
nothing to do with the ramdisk root and sends you to the wrong file.
|
||||
|
||||
## Two things that will bite you
|
||||
|
||||
**`ld -r -b binary` derives symbol names from the input file's path.** The
|
||||
kernel embeds two blobs this way (`initcode`, `entryother`), and the root
|
||||
Makefile has `--redefine-sym` lines mapping e.g.
|
||||
`_binary_build_lib_initcode_start` → `_binary_initcode_start`. Move either
|
||||
blob's build location and those symbol names change; update the Makefile to
|
||||
match or the kernel fails to link.
|
||||
|
||||
**`STAGE2_SECTORS` now lives in exactly one place: the root `Makefile`.** It used
|
||||
to be written out by hand in five — `boot/bootasm.S`, `boot/stage2main.c`'s
|
||||
`readseg()`, the `dd ... seek=` offset, and twice more inside `tools/mkiso.sh`
|
||||
(its stage2 size assertion and `-boot-load-size`). This note used to warn you to
|
||||
keep three of them in step and did not know about the other two.
|
||||
|
||||
Raising it from 24 to 48 for the boot logo demonstrated the problem: the `dd
|
||||
seek=25` offset stayed behind, so the kernel was written directly over stage2's
|
||||
own tail. Now the Makefile passes `-DSTAGE2_SECTORS` to both boot sources,
|
||||
derives the padding budget and the `seek=` offset from the same variable, and
|
||||
exports it to `mkiso.sh`. The `#define`s that remain in the two boot sources are
|
||||
`#ifndef`-guarded fallbacks so those files still read sensibly alone; the
|
||||
Makefile always wins.
|
||||
|
||||
Two things still fail the build rather than silently misbooting, and should stay
|
||||
that way: `tools/padstage2.pl` errors if stage2 overflows its budget instead of
|
||||
truncating it, and `boot/stage2main.c` static-asserts that stage2 cannot reach
|
||||
the multiboot kernel-search buffer at `0x10000` — which puts the hard ceiling at
|
||||
65 sectors, since stage2 loads at `0x7E00`.
|
||||
241
docs/licensing.md
Normal file
241
docs/licensing.md
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
# Licensing
|
||||
|
||||
The one page to check before copying anyone else's code into Schunix.
|
||||
|
||||
Referenced from `LICENSE`, `LICENSE.MIT` and `NOTICE`, all three of which
|
||||
pointed here long before this file existed.
|
||||
|
||||
---
|
||||
|
||||
## 1. Schunix is Apache-2.0
|
||||
|
||||
Copyright Nexoniarz. See `LICENSE`.
|
||||
|
||||
Apache-2.0 was chosen over MIT/BSD for one reason that actually matters at OS
|
||||
scale: it contains an **express patent grant** and a patent-retaliation clause.
|
||||
MIT and BSD are silent on patents, which means a contributor can hand you code
|
||||
and later assert a patent over it. For a kernel that is intended to accept
|
||||
outside contributions, that silence is the expensive kind.
|
||||
|
||||
What that grant means in practice: Apache-2.0 §2 gives everyone a perpetual,
|
||||
worldwide, irrevocable licence to reproduce, modify, sublicense and distribute
|
||||
Schunix. Any additional restriction stated elsewhere — in a README, a website,
|
||||
or a policy — does not narrow it. The licence text is what holds up.
|
||||
|
||||
If restricted distribution is ever genuinely wanted, that is a licence change
|
||||
(a bespoke proprietary licence, or something like BUSL-1.1 with a conversion
|
||||
date), not a wording change, and it means giving up the Apache grant.
|
||||
|
||||
---
|
||||
|
||||
## 2. What can be copied in
|
||||
|
||||
| Licence | Verdict | Obligation |
|
||||
|---|---|---|
|
||||
| Public domain, 0BSD, CC0 | **Yes** | None |
|
||||
| MIT, ISC | **Yes** | Keep the notice |
|
||||
| BSD 2-clause | **Yes** | Keep the notice |
|
||||
| BSD 3-clause | **Yes** | Keep the notice; no endorsement claims |
|
||||
| BSD **4-clause** (advertising) | **Avoid** — see §3 | Notice *and* an acknowledgement in advertising materials |
|
||||
| Apache-2.0 | **Yes** | Keep NOTICE contents |
|
||||
| MPL-2.0 | Case by case | File-level copyleft; keep those files separate |
|
||||
| **GPL-2.0-only** | **No** | — |
|
||||
| **GPL-3.0 / LGPL-3.0** | **No** for linked code | — |
|
||||
|
||||
**Every "yes" above requires an entry in `NOTICE`.** That file is the project's
|
||||
attribution record and the thing that makes the permissive licences actually
|
||||
satisfied rather than merely tolerated.
|
||||
|
||||
### Why GPL-2.0-only is a hard no
|
||||
|
||||
Apache-2.0 imposes conditions GPLv2 treats as "further restrictions" under its
|
||||
§6 — principally the patent-retaliation term. The FSF and the ASF agree on
|
||||
this: **Apache-2.0 code and GPL-2.0-only code cannot be combined into one
|
||||
distributable work.** It is not a matter of taste or of how carefully the files
|
||||
are kept apart in the tree; it is that the combined binary cannot satisfy both
|
||||
licences at once.
|
||||
|
||||
GPL-3.0 fixed this in the other direction (Apache-2.0 is compatible *into*
|
||||
GPLv3), but that does not help: the result would have to ship under GPLv3, and
|
||||
Schunix does not.
|
||||
|
||||
**Practical consequence:** BSD drivers can be adopted. Linux drivers cannot,
|
||||
ever, no matter how convenient. This is why the USB stack was written from the
|
||||
specification rather than ported.
|
||||
|
||||
---
|
||||
|
||||
## 3. NetBSD in particular
|
||||
|
||||
`src-trunk/` is a NetBSD checkout kept as reference material and a parts bin
|
||||
(see [architecture/schapi.md](architecture/schapi.md) §5). NetBSD is **not
|
||||
uniformly licensed**, and the difference matters per file:
|
||||
|
||||
| Template | Status |
|
||||
|---|---|
|
||||
| 2-clause BSD — modern NetBSD Foundation files | **Fine** |
|
||||
| 3-clause, "Regents of the University of California" | **Fine.** Berkeley formally rescinded the advertising clause in 1999 |
|
||||
| 4-clause with the advertising clause, **non-UC** copyright holder | **Avoid.** Berkeley's rescission covers Berkeley's copyright, not other people's |
|
||||
|
||||
The last row is real, not hypothetical. Measured on this checkout:
|
||||
|
||||
```
|
||||
sys/kern 26 of 220 files carry the advertising clause
|
||||
sys/dev 500 of 3565
|
||||
lib/libc 101 of 1575
|
||||
sys/net 5 of 180 ← the TCP/IP stack is nearly clean
|
||||
```
|
||||
|
||||
4-clause is still permissive and still redistributable; the burden is an
|
||||
advertising obligation that accumulates quietly and is tedious to honour
|
||||
properly. Prefer a 2-clause or UC-Regents file. If a 4-clause file is genuinely
|
||||
the right answer, it goes into `NOTICE` with the full clause quoted.
|
||||
|
||||
Check before copying, not after:
|
||||
|
||||
```sh
|
||||
grep -l "All advertising materials" <file>
|
||||
```
|
||||
|
||||
### And check *whose* clause it is
|
||||
|
||||
A file can carry several licence blocks from several copyright holders. Finding
|
||||
the advertising clause is only half the check — Berkeley's rescission covers
|
||||
Berkeley's copyright and nobody else's, so the question is always *whose*
|
||||
clause you found.
|
||||
|
||||
Measured on `src-trunk`, the networking tree:
|
||||
|
||||
```
|
||||
sys/net 106 files, 5 with the advertising clause
|
||||
sys/netinet 121 files, 25 " " "
|
||||
sys/netinet6 51 files, 5 " " "
|
||||
```
|
||||
|
||||
Encouraging at first glance — the 25 in `sys/netinet` are almost all SCTP,
|
||||
DCCP and multicast routing, none of which Schunix needs. `ip_input.c`,
|
||||
`ip_output.c`, `udp_usrreq.c`, `if_arp.c`, `ip_icmp.c`, `if.c` and `route.c`
|
||||
are all UC 3-clause, i.e. fine.
|
||||
|
||||
**But `tcp_input.c` and `tcp_output.c` are not.** Their advertising clause
|
||||
belongs to the **WIDE Project** (the KAME IPv6 people), whose copyright sits at
|
||||
the top of the file; the UC block is a separate licence further down. Berkeley
|
||||
never spoke for WIDE, so the rescission does not reach it. The two most
|
||||
valuable files in the stack are the two encumbered ones.
|
||||
|
||||
That is not fatal — 4-clause is still permissive — but it is exactly the case
|
||||
where a **second BSD tree earns its disk space**: FreeBSD's TCP has been
|
||||
through a relicensing effort NetBSD's has not, so the same file there may well
|
||||
be 2-clause. Check it the same way before assuming either:
|
||||
|
||||
```sh
|
||||
grep -n "Copyright" <file> | head # who claims it
|
||||
grep -n -B6 "All advertising" <file> # whose clause is it
|
||||
```
|
||||
|
||||
### FreeBSD's TCP is clean where NetBSD's is not
|
||||
|
||||
Checked, 2026-08-08, exactly that way. Against FreeBSD `main`:
|
||||
`sys/netinet/tcp_input.c` contains **no advertising clause at all**. Its
|
||||
copyright holders are the UC Regents, Swinburne University, Lawrence Stewart,
|
||||
the FreeBSD Foundation and Juniper Networks -- all BSD-2 or BSD-3.
|
||||
|
||||
So if BSD networking code is taken, it must come from **FreeBSD, not NetBSD**,
|
||||
and the specific reason is TCP. This reverses the earlier assumption in
|
||||
`docs/roadmap.md` that NetBSD was the intended starting point; NetBSD remains
|
||||
the better-written reference to *read*, but FreeBSD is the one that can be
|
||||
copied from.
|
||||
|
||||
|
||||
### The third option: lwIP
|
||||
|
||||
Before porting either BSD stack, weigh **lwIP** against both. Verified
|
||||
2026-08-08 from its `COPYING`: 3-clause BSD, Swedish Institute of Computer
|
||||
Science, **no advertising clause anywhere in the file**. Compatible, one NOTICE
|
||||
entry, done.
|
||||
|
||||
The licence is the smaller half of the argument. The larger half is that
|
||||
FreeBSD's `sys/netinet` is not a library -- `tcp_input.c` alone is 122 KB and
|
||||
reaches into mbufs, `epoch(9)`, netisr, VNET, `uma(9)`, sockbufs, inpcb hash
|
||||
tables and FreeBSD's locking. Taking it means porting FreeBSD's kernel
|
||||
infrastructure first and finding the TCP somewhere underneath, which is the same
|
||||
conclusion `docs/roadmap.md` §1 already reached about `sys/dev/usb`.
|
||||
|
||||
lwIP was written for the case Schunix is in: a small operating system with a raw
|
||||
Ethernet driver and no infrastructure to speak of. Its port layer is a handful
|
||||
of files, it has no dependency on the host kernel's allocator or locking, and it
|
||||
brings ARP, IPv4, ICMP, UDP, TCP, DHCP and DNS.
|
||||
|
||||
### Fonts
|
||||
|
||||
Chosen 2026-08-09, both licences checked at source rather than assumed:
|
||||
|
||||
| Font | Where | Licence |
|
||||
|---|---|---|
|
||||
| Roboto | desktop, window manager, compositor | **Apache-2.0** |
|
||||
| JetBrains Mono | terminal, TTY | **SIL OFL 1.1** |
|
||||
|
||||
Roboto is the same licence as Schunix, so it is the easy case: a NOTICE entry
|
||||
and nothing else.
|
||||
|
||||
OFL is permissive and bundling is precisely what it is for, with two conditions
|
||||
worth knowing before anyone subsets or converts a font:
|
||||
|
||||
- **The font stays under OFL** even inside an Apache-2.0 system. It does not
|
||||
infect anything else; it just cannot be relicensed.
|
||||
- **It may not be sold on its own.** Shipping it as part of an OS is fine.
|
||||
- **Reserved Font Names.** JetBrains Mono declares *none* -- its copyright line
|
||||
names no reserved name -- so a modified version may keep the name. Most OFL
|
||||
fonts do reserve theirs, and then a derivative must be renamed.
|
||||
Pre-rasterising a font into bitmaps at build time **is** a modification, so
|
||||
this is not a theoretical point for this project.
|
||||
|
||||
---
|
||||
|
||||
## 4. Things that are deliberately not shipped
|
||||
|
||||
Some code is fine to *build and run locally* and impossible to *distribute*
|
||||
with Schunix. Keeping the distinction sharp is what stops it eroding:
|
||||
|
||||
- **`Refrence/`** — a FreeBSD checkout (`Refrence/freebsd-src`), kept as
|
||||
reference material and a parts bin. Never built, never in an image. The
|
||||
directory name is spelled as it is on disk; renaming it is a one-line change
|
||||
if it ever grates.
|
||||
- **`src-trunk/`** — a NetBSD checkout, same rules. Currently absent; it was
|
||||
deleted for disk space and can be re-fetched sparsely if needed.
|
||||
- **`dist/`** — upstream tarballs and other downloaded blobs. Cached so a
|
||||
rebuild never needs the network; not part of any image, and not all of it is
|
||||
redistributable.
|
||||
|
||||
The mechanisms that keep this from eroding, since good intentions have already
|
||||
failed once here:
|
||||
|
||||
- `NOTPORTS` in `ports/Makefile` — a port that lives in the tree but is never
|
||||
built by `make`.
|
||||
- `RAMDISK_EXCLUDE` in the root `Makefile` — paths kept out of the ramdisk root.
|
||||
This one matters more than it looks: the ramdisk image is linked *into the
|
||||
kernel binary*, so anything sitting in `sysroot/` when that image is built
|
||||
becomes part of a distributable artifact. A GPL-2.0 or non-redistributable
|
||||
file staged there does not merely ride along, it makes the kernel itself
|
||||
undistributable.
|
||||
|
||||
A previous acceptance test — a ported 1993 shooter, GPL-2.0 engine on
|
||||
non-redistributable shareware data — was carried under exactly these rules and
|
||||
has since been removed. It is worth recording *why* it was safe: it was never in
|
||||
`PORTS`, and its 33 MB of data was excluded from the ramdisk. It is equally
|
||||
worth recording that the safety depended on two separate opt-outs staying
|
||||
correct, which is a lot of standing risk for a test that had stopped finding
|
||||
bugs. See `docs/roadmap.md` Phase 7.
|
||||
|
||||
---
|
||||
|
||||
## 5. Specifications are not code
|
||||
|
||||
A published specification carries no copyright over an independent
|
||||
implementation of it. The xHCI, USB, PCI, fbdev and ELF specifications were all
|
||||
read and implemented directly, which is why those drivers have no licence
|
||||
entanglement at all despite equivalents existing in both Linux and BSD.
|
||||
|
||||
When a subsystem is small enough to write from the spec, that is usually the
|
||||
cheapest option in the long run — not because copying is hard, but because a
|
||||
copied file is a file to be understood, attributed and maintained forever.
|
||||
481
docs/roadmap.md
Normal file
481
docs/roadmap.md
Normal file
|
|
@ -0,0 +1,481 @@
|
|||
# Roadmap
|
||||
|
||||
Written 2026-08-04, from the plan: USB, GPU, fonts, networking and sockets,
|
||||
then a compositor (Schcom), a window manager (SchWM) and a desktop (SchDE),
|
||||
plus an installer and a `.sip` package format.
|
||||
|
||||
This file records two things that are easy to lose: the *order* the work has
|
||||
to happen in, and the decisions that constrain it.
|
||||
|
||||
**Superseded in direction, not in order.** Schunix is now aimed at being a
|
||||
*system* rather than a Unix-like kernel with a userland attached: a NeXTSTEP/
|
||||
macOS-shaped layering, with POSIX kept complete but demoted from identity to
|
||||
personality, and a native API (**SchAPI**) above it. See
|
||||
[architecture/schapi.md](architecture/schapi.md). That decision changes what
|
||||
the phases below are building towards; it does not reorder them, and it does
|
||||
not start until the tree builds and boots natively again.
|
||||
|
||||
That assumption about §1 has since been revised. NetBSD's TCP/IP stack was the
|
||||
intended starting point; the per-file licence check found that NetBSD's
|
||||
`tcp_input.c` and `tcp_output.c` carry the WIDE Project's 4-clause advertising
|
||||
clause, which Berkeley's rescission does not reach. FreeBSD's equivalent is
|
||||
clean, and lwIP is cleaner still and a far smaller port. See Phase 2 below and
|
||||
docs/licensing.md §3.
|
||||
|
||||
---
|
||||
|
||||
## 1. Where drivers can come from
|
||||
|
||||
**BSD: yes. Linux: no.** This is not a preference, it is a licence
|
||||
incompatibility, and it decides the whole driver strategy.
|
||||
|
||||
The reasoning — Apache-2.0's patent terms counting as "further restrictions"
|
||||
under GPLv2 §6, which is why the two cannot be combined and redistributed — is
|
||||
in [licensing.md](licensing.md), along with the per-licence table and the
|
||||
NetBSD-specific rules. Read it before copying anything. The short version:
|
||||
BSD-2 and BSD-3 code can be used directly with a `NOTICE` entry; GPL-2.0-only
|
||||
code can never be, at any distance.
|
||||
|
||||
There is a third source that is better than either: **the specifications**.
|
||||
xHCI, AHCI, virtio, NVMe, the Intel e1000 and i915 datasheets are all public
|
||||
documents with no licence attached to what you write from them. That is where
|
||||
`kernel/drivers/xhci.c` came from.
|
||||
|
||||
One caveat about "port it from BSD" as a plan, learned by looking: FreeBSD's
|
||||
`sys/dev/usb` is roughly 100k lines welded to newbus, devclass, `bus_dma`,
|
||||
callouts, taskqueues and FreeBSD's locking. Porting it means porting
|
||||
FreeBSD's device framework first and finding the USB stack somewhere
|
||||
underneath. NetBSD's is more portable and the same argument applies with a
|
||||
smaller multiplier. **BSD is far more valuable as the reference you check
|
||||
yourself against than as the code you paste.** Read it when the spec is
|
||||
ambiguous — which it is, often — and write your own.
|
||||
|
||||
Reading Linux for *understanding* is fine and normal. Translating a Linux
|
||||
driver function-by-function is not; the result is a derivative work whatever
|
||||
the variable names say.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ordering, and why it is not the order it was asked in
|
||||
|
||||
The requested order was USB → GPU → fonts → network → GUI → installer. That
|
||||
is a reasonable order by *interest*. By dependency it wants one change:
|
||||
|
||||
**Shared driver infrastructure comes before all of it.** USB, any GPU beyond
|
||||
the VBE framebuffer, and any NIC all need the same three things, none of
|
||||
which existed:
|
||||
|
||||
- a way to register an interrupt handler at runtime (`kernel/dev/irq.c`)
|
||||
- physically contiguous memory for rings and descriptors
|
||||
(`kernel/core/dma.c`)
|
||||
- PCI capability walking and MSI (`kernel/dev/pci.c`)
|
||||
|
||||
That work is done. It was the first step of USB, not a detour from it.
|
||||
|
||||
The other honest note: **USB is the item that does least for the GUI.** Under
|
||||
QEMU there is already a PS/2 keyboard and mouse, so USB buys real hardware
|
||||
support — which matters, but not for getting Schcom on screen. Sockets and
|
||||
raw input events are what actually block the compositor. USB is being done
|
||||
first because it was asked for first; the ordering below reflects that with
|
||||
the dependencies fixed.
|
||||
|
||||
---
|
||||
|
||||
## 3. The plan
|
||||
|
||||
### Phase 1 — USB *(done, bar the boot path)*
|
||||
|
||||
- [x] Shared infrastructure: IRQ registration, DMA arena, PCI capabilities
|
||||
- [x] xHCI: reset, DCBAA, command ring, event ring, scratchpad
|
||||
- [x] Port enumeration, Enable Slot, Address Device, control transfers
|
||||
- [x] Device descriptors read from real devices (USB2 and USB3 paths)
|
||||
- [x] Configuration and interface descriptor parsing, `SET_CONFIGURATION`
|
||||
- [x] Interrupt endpoints, driven from the IRQ handler
|
||||
- [x] HID boot protocol: keyboard and mouse into the existing input paths
|
||||
- [x] Bulk endpoints, bulk-only transport, SCSI, a block device at
|
||||
`USBDISK_DEV` — a superblock reads back through `bread()`
|
||||
- [x] Hubs — route strings, hub slot contexts, per-port reset and power. A
|
||||
mouse behind a hub enumerates a tier deep, which is what proves it
|
||||
- [ ] Making a USB disk mountable and bootable (needs the root device to be
|
||||
selectable rather than compiled in)
|
||||
|
||||
### Phase 2 — Sockets, raw input, and networking
|
||||
|
||||
Do sockets **before** the network stack. **Unix domain sockets are what Schcom
|
||||
needs** — clients and compositor have to pass messages and share buffers, and
|
||||
a pipe cannot carry an shm handle. Unix sockets are also far simpler than
|
||||
TCP/IP, so they are the right thing to build the socket layer's shape around.
|
||||
|
||||
- [x] `socket`/`bind`/`listen`/`accept`/`connect`/`send`/`recv` syscalls, at
|
||||
their real Linux numbers — they were `NO_LINUX` stubs, so a musl program
|
||||
calling `socket()` never even reached the stub
|
||||
- [x] `AF_UNIX` SOCK_STREAM, `socketpair`, `shutdown`; names live in a table
|
||||
rather than the filesystem (see the comment at the top of
|
||||
`kernel/net/unix.c` for what that costs)
|
||||
- [x] Raw input events — `/dev/input/event0` and `event1`, Linux evdev layout,
|
||||
fed by the PS/2 keyboard and mouse and by USB HID. The console keeps its
|
||||
own path; both consumers see every key
|
||||
- [ ] `SCM_RIGHTS` for passing descriptors. Possibly unnecessary: an shm id is
|
||||
an integer and travels fine over a stream socket, so this waits until
|
||||
something actually needs to pass an open file
|
||||
- [x] e1000 driver — `kernel/drivers/net/e1000.c`, from Intel's public manual
|
||||
317453. MAC from EEPROM, transmit and receive rings, link detection.
|
||||
Run with `-netdev user,id=n0 -device e1000,netdev=n0`.
|
||||
**Verified to carry real traffic**: `echo n > /proc/sysrq` sends a
|
||||
hand-built ARP request and receives the gateway's reply, which exercises
|
||||
the descriptor formats, the DD handshake, the tail bookkeeping, the DMA
|
||||
addresses and the MAC filter in one exchange. That check came before any
|
||||
protocol stack on purpose -- a stack on an unproven driver fails in ways
|
||||
indistinguishable from its own bugs
|
||||
- [ ] ARP, IPv4, ICMP, UDP, then TCP. **Not written from scratch** -- decided
|
||||
2026-08-08. Writing TCP is the one part of this where doing it yourself is
|
||||
a bad trade: it is not the packet format that is hard, it is congestion
|
||||
control, retransmission, window management and twenty years of accumulated
|
||||
corner cases that only appear against a real peer.
|
||||
**Preferred base: lwIP** (BSD-3, no advertising clause, verified). It was
|
||||
written for exactly this situation -- a small OS with a raw Ethernet
|
||||
driver -- and its port layer is a handful of files.
|
||||
**If BSD instead: FreeBSD, not NetBSD.** NetBSD's `tcp_input.c` carries
|
||||
the WIDE Project's 4-clause advertising clause; FreeBSD's has none. But
|
||||
FreeBSD's `sys/netinet` is not a library -- it reaches into mbufs,
|
||||
epoch(9), netisr, VNET and uma(9), so taking it means porting FreeBSD's
|
||||
kernel infrastructure first. See docs/licensing.md §3.
|
||||
- [ ] `AF_INET` on top of the same socket layer
|
||||
|
||||
### Phase 3 — GPU
|
||||
|
||||
"Basic GPU driver" has a right answer and a wrong one. The wrong one is
|
||||
starting with Intel or AMD hardware: modern GPU drivers are enormous and most
|
||||
of the size is mode setting, power management and a command submission model
|
||||
that only pays off once there is something to submit.
|
||||
|
||||
- [x] **virtio-gpu**, 2D. `kernel/drivers/virtio_gpu.c`, written from the OASIS
|
||||
specification. Full path: GET_DISPLAY_INFO, RESOURCE_CREATE_2D,
|
||||
RESOURCE_ATTACH_BACKING, SET_SCANOUT, TRANSFER_TO_HOST_2D,
|
||||
RESOURCE_FLUSH. Bound as the display class ahead of the VBE framebuffer.
|
||||
Run with `-vga std -device virtio-gpu-pci`; keep `-vga std`, because the
|
||||
bootloader's splash goes through VBE and there is no virtio path that
|
||||
early
|
||||
- [ ] A `/dev/dri`-style device with mode setting, so resolution stops being
|
||||
whatever the bootloader asked VBE for
|
||||
- [ ] Double buffering and vblank — Schcom needs both to not tear
|
||||
- [ ] Only then: real hardware, and only 2D/blit acceleration
|
||||
|
||||
### Phase 4 — Fonts
|
||||
|
||||
This is a bigger job than it sounds and it should be sized honestly. A TTF/OTF
|
||||
rasteriser needs: `cmap`/`glyf`/`loca`/`head`/`hmtx` table parsing, quadratic
|
||||
béziers for TrueType outlines and *cubic* ones for OpenType/CFF, a scanline
|
||||
rasteriser with anti-aliasing, and a glyph cache.
|
||||
|
||||
The bigger structural problem is that the console assumes a fixed 8×16 cell
|
||||
grid throughout — `vga_putc`, the cursor, the ANSI erase operations. Variable
|
||||
width text is not a font change, it is a text-rendering layer.
|
||||
|
||||
- [x] **The text layer** — `include/text.h` + `kernel/dev/text.c`. Glyph
|
||||
metrics, a `struct surface` target so a compositor can draw off-screen,
|
||||
8-bit coverage with real blending, UTF-8, and `font_ops` so a rasteriser
|
||||
plugs in as a second provider. This was the actual blocker: the font was
|
||||
never the problem, the character-cell grid was.
|
||||
- [ ] Port the console onto it and delete the cell grid
|
||||
- [x] A TrueType rasteriser — `kernel/dev/ttf.c`, glyf outlines, composite
|
||||
glyphs, cmap format 4, anti-aliased scanline fill. Host harness at
|
||||
`tools/ttftest.c`
|
||||
- [x] A glyph cache — direct-mapped over ASCII into a 48 KB pool
|
||||
- [ ] Load a font from disk and point the console at it, deleting the cell grid
|
||||
- [ ] CFF/Type 2 charstrings, so OTF fonts work too (a separate `font_ops`
|
||||
provider; a bytecode interpreter, not an extension of the glyf path)
|
||||
- [ ] A rasteriser. `stb_truetype.h` is public domain and therefore usable
|
||||
as-is if wanted; writing one is also reasonable and more interesting
|
||||
- [ ] **The typefaces, decided 2026-08-09.** Both verified, not assumed:
|
||||
**Roboto** for the desktop, window manager and compositor --
|
||||
**Apache-2.0**, the same licence as Schunix, so it needs nothing beyond a
|
||||
NOTICE entry.
|
||||
**JetBrains Mono** for the terminal and TTY -- **SIL OFL 1.1**, and
|
||||
notably with *no* Reserved Font Name declared, so it may be modified and
|
||||
still called JetBrains Mono. That matters more than it sounds:
|
||||
pre-rasterising a font into bitmaps at build time is a modification, and
|
||||
an RFN would have forbidden keeping the name on the result.
|
||||
OFL's one real constraint is that the font may not be sold on its own and
|
||||
must stay under OFL; bundling it in an Apache-2.0 system is exactly the
|
||||
case it is written for.
|
||||
- [ ] Text layout (advance widths, kerning) as a library in `lib/`
|
||||
- [ ] The console keeps its fixed grid; proportional text belongs to the GUI
|
||||
|
||||
**Font licensing: public domain or very permissive only.** No SIL OFL if it
|
||||
can be avoided, no "free for personal use", no CC-BY-SA. The constraint is
|
||||
the same one that governs drivers (§1): a font file is redistributed inside
|
||||
every Schunix image, so its licence attaches to the image.
|
||||
|
||||
Safe choices, in rough order of usefulness:
|
||||
|
||||
- **CC0 / public domain** — best case, nothing to comply with.
|
||||
- **MIT / BSD / Apache-2.0 fonts** — fine, needs a `NOTICE` entry.
|
||||
- **SIL OFL** — usable but has a real string attached: a modified font must
|
||||
be renamed, and OFL fonts cannot be sold on their own. Workable, but read
|
||||
it before committing one.
|
||||
|
||||
Whatever is chosen, the licence goes in `NOTICE` **before** the file is
|
||||
committed, not after.
|
||||
|
||||
### Phase 5 — The GUI, as separate projects
|
||||
|
||||
See `projects/README.md`. Schcom, SchWM and SchDE live outside the kernel
|
||||
tree on purpose.
|
||||
|
||||
- [ ] **Schcom** — the compositor. Owns the framebuffer, composites client
|
||||
buffers, routes input. Needs: unix sockets, shm (done), raw input
|
||||
events, and a GPU driver with double buffering.
|
||||
- [ ] **SchWM** — window management on top of Schcom's protocol
|
||||
- [ ] **SchDE** — panel, launcher, settings
|
||||
|
||||
Raw input is the missing kernel piece nobody thinks of until it bites:
|
||||
keyboard events currently only arrive through the tty line discipline, cooked
|
||||
and line-buffered and delivered to one foreground process. A compositor needs
|
||||
key-down/key-up with modifiers and no line discipline at all — an evdev-style
|
||||
`/dev/input`.
|
||||
|
||||
### Phase 6 — Installer and `.sip`
|
||||
|
||||
`.sip` (Schunix Installation Package), the stated model being WIM in Windows
|
||||
PE: a *file-based* image rather than a block image, so one file can hold a
|
||||
whole filesystem, be applied to a partition of any size, and hold several
|
||||
images that share their common files.
|
||||
|
||||
The pieces for this mostly exist already. `tools/mkschfs.c` does content
|
||||
dedup with an `(st_dev, st_ino)` fast path; SchFS's B-tree is content
|
||||
addressed. A sensible `.sip` is: a header, a file index (path, metadata,
|
||||
content hash), and a blob store keyed by hash, with compression per blob.
|
||||
|
||||
- [ ] `.sip` format specification, written down before any code
|
||||
- [ ] `tools/mksip` — capture a directory tree into a `.sip`
|
||||
- [ ] Kernel or userspace apply path
|
||||
- [ ] An installation environment that boots from the ISO, partitions,
|
||||
applies the `.sip`, and installs the bootloader
|
||||
|
||||
---
|
||||
|
||||
### Phase 7 — The full-stack acceptance test *(served its purpose; removed)*
|
||||
|
||||
For a while this phase was a ported copy of a well-known 1993 shooter, kept as
|
||||
an acceptance test rather than a feature. The reasoning was sound and is worth
|
||||
keeping even though the test is gone: a large program that somebody else wrote,
|
||||
which exercises `/dev/fb0`, `/dev/input/event0`, `mmap`, the ELF loader,
|
||||
`gettimeofday` and a fixed-rate tic loop *simultaneously* and notices when any
|
||||
of them lie, is a much harsher test than anything written in-house — because it
|
||||
was not written against our bugs.
|
||||
|
||||
It found real ones, all of which are fixed and covered by narrower tests now:
|
||||
|
||||
- `fcntl(F_SETFL, O_NONBLOCK)` was a documented no-op, so the first read blocked
|
||||
forever and looked exactly like a hung renderer.
|
||||
- `/dev/input/eventN` replayed its backlog to each new opener, handing a program
|
||||
the keystrokes that had launched it as input.
|
||||
- Without `EVIOCGRAB`, a full-screen program and the shell behind it both read
|
||||
the same keystrokes.
|
||||
|
||||
It was removed once it stopped finding anything: the port was ~100k lines of
|
||||
third-party source under licences that can never ship in an Apache-2.0 image,
|
||||
and carrying it had a standing cost — 33 MB of non-redistributable data staged
|
||||
into `sysroot/`, which is linked into the kernel binary for a ramdisk-root
|
||||
build. That combination is a licensing accident waiting to happen, for a test
|
||||
that was passing every time.
|
||||
|
||||
**What replaced it:** `tools/bootcheck.ps1` with its `-Sock`, `-Evdev` and `-Ps`
|
||||
modes, plus `tools/splashshot.ps1` for the boot path. These are narrower and do
|
||||
not prove the whole stack composes, which is the honest cost of the removal.
|
||||
|
||||
**The lesson worth keeping** is the shape of the test, not the program: the next
|
||||
full-stack test should be something large that we did not write, driven to a
|
||||
real pixel-level assertion rather than a log line. `tools/splashshot.ps1`'s
|
||||
colour counting exists because a log-only harness once passed on a blank screen.
|
||||
|
||||
### Phase 8 — The system layer: `schinit`, firmware, and a crash channel
|
||||
|
||||
Three ideas parked here rather than started, because each one is a project and
|
||||
the phases above are the agreed order. Recorded so they are not re-derived from
|
||||
scratch later.
|
||||
|
||||
**`schinit`, a launchd-shaped init.** The point of copying launchd rather than
|
||||
sysvinit is not the config format, it is *demand launching*: launchd holds the
|
||||
listening socket or watches the path, and starts the job when something actually
|
||||
arrives. That inverts the dependency graph — jobs stop needing to declare what
|
||||
must start before them, because the thing they depend on answers on first use.
|
||||
Schunix already has the two pieces that makes possible: `AF_UNIX` sockets
|
||||
(`kernel/net/unix.c`) and a real `/proc`. What is missing is a supervisor that
|
||||
can hold a socket on a job's behalf and hand it over on exec, plus a job
|
||||
description format. Prerequisite for this being worth anything: `ps` and `top`
|
||||
must be able to enumerate processes, or the supervisor is unobservable.
|
||||
|
||||
**A crash screen you can work in, and a QR code channel out of it.** PSOGM
|
||||
(`kernel/core/panic.c`) currently renders and stops. The idea is two staged
|
||||
things, and the staging matters:
|
||||
|
||||
- *An interactive mode — "a small OS inside the crash".* This is the right
|
||||
framing and it is worth stating precisely, because it decides the design. At
|
||||
panic time the kernel is the only thing running, which is exactly why it can
|
||||
offer what a live system cannot: walk the stack, read another process's
|
||||
memory, dump page tables, inspect the process table — with no scheduler and no
|
||||
locking to respect, because nothing else can race you.
|
||||
|
||||
What makes it a *system* rather than a print statement is that it keeps the
|
||||
base machine's drivers. Concretely, the ones that work with interrupts off:
|
||||
the VGA/framebuffer (`vga_draw_pixel` is a plain memory write), the PS/2
|
||||
keyboard (pollable via port 0x60/0x64), and the serial port. Those three are
|
||||
a complete console. Everything else — IDE, USB, the timer, anything that
|
||||
sleeps or takes a sleeplock — is off limits, because the lock it needs may be
|
||||
precisely the one held by the CPU that just died.
|
||||
|
||||
So the shape is a **polled event loop over preallocated state**: no `kalloc`,
|
||||
no `sleep`/`wakeup`, no `acquire` on anything a live path might hold, one
|
||||
static command buffer, and a driver set restricted to the three above. That
|
||||
restriction is not a limitation to work around later; it is the invariant that
|
||||
makes the mode trustworthy when everything else is not. Anything that wants
|
||||
more should be answered by rebooting into a recovery environment instead.
|
||||
|
||||
A useful precedent to read but not copy: DDB (NetBSD/FreeBSD's in-kernel
|
||||
debugger) is exactly this, and its hard-won rule is the same one — a debugger
|
||||
that can deadlock is worse than no debugger.
|
||||
- *A QR code.* **Done.** `kernel/core/qr.c` (byte mode, EC level L, versions
|
||||
1–6, no allocation) and a card on the right of the crash screen. A panic
|
||||
report is worth nothing if it cannot leave a machine whose network stack is
|
||||
dead, and a phone camera is an out-of-band channel needing no driver on our
|
||||
side. Deliberately *not* a URL: it encodes the report itself and reaches
|
||||
nothing.
|
||||
|
||||
It ended up unconditional rather than behind Enter, which is better — it
|
||||
costs nothing to always draw, and requiring a keypress would mean the one
|
||||
case where input is least trustworthy is also the case where the report is
|
||||
hardest to retrieve. Verified end-to-end by `make crashshot && make crashqr`,
|
||||
which decodes the symbol out of the captured framebuffer.
|
||||
|
||||
**UEFI.** Not a feature so much as the point at which the current bootloader
|
||||
stops being extendable: it is 16-bit real mode, INT 10h VBE, and PIO ATA, none
|
||||
of which exist on a UEFI-only machine. The realistic shape is a *second*
|
||||
loader — an EFI application that gets the framebuffer from GOP, the memory map
|
||||
from `GetMemoryMap()`, loads the kernel off a FAT ESP, and enters at the same
|
||||
place `boot/stage2main.c` does — rather than a port of the existing one. Worth
|
||||
noting that the splash work just done survives that transition: it is a mask, a
|
||||
palette and a layout, none of which care how the pixels were found. What does
|
||||
not survive is every assumption in `stage2main.c` about where things live in low
|
||||
memory (see `docs/layout.md`).
|
||||
|
||||
### SchFS beyond Schunix
|
||||
|
||||
Three ideas parked 2026-08-08, in the order they should happen.
|
||||
|
||||
**An on-disk format version, and an upgrade path.** This one is not optional and
|
||||
gets harder every day it is postponed: the moment an image exists that someone
|
||||
cares about, changing the layout means being able to read the old one. The
|
||||
superblock already carries a version field and a CRC32c; what is missing is a
|
||||
*policy* — a documented rule for what a version bump means, a refusal to mount
|
||||
something newer than the driver understands (rather than misreading it), and an
|
||||
explicit upgrade step. Cheap now, expensive later, and it blocks the other two:
|
||||
a second implementation is only meaningful against a format that is pinned.
|
||||
|
||||
**A Linux implementation.** The realistic first form is **FUSE**, not a kernel
|
||||
module — it needs no GPL entanglement (`libfuse` is LGPL and used across a
|
||||
process boundary), it can reuse the read/write logic already in `tools/mkschfs`
|
||||
and `tools/schfs_fsck` rather than being written twice, and it turns "does our
|
||||
filesystem work" into something testable with `fsx` and `xfstests`, which is a
|
||||
far harsher examination than anything in this tree. A Linux *kernel* module is a
|
||||
different project and carries the licence problem that
|
||||
[licensing.md](licensing.md) describes.
|
||||
|
||||
**The missing features.** Worth listing honestly before adding any: no journal
|
||||
replay across an unclean shutdown beyond what CoW gives for free, no snapshots
|
||||
(despite the B-tree being copy-on-write, which is most of the work), no extended
|
||||
attributes, no sparse-file support, and no online `fsck`. Snapshots are the one
|
||||
where the existing design already pays for most of the cost.
|
||||
|
||||
---
|
||||
|
||||
## 3b. Known broken
|
||||
|
||||
Two things that are wrong *now*, both reported 2026-08-08 and both diagnosed
|
||||
rather than merely observed. Neither blocks the GUI work, which is why they are
|
||||
here and not in a phase.
|
||||
|
||||
### ~~SMP is broken~~ *(fixed 2026-08-08)*
|
||||
|
||||
**SMP works.** One, two and four processors all boot; every CPU reports in with
|
||||
`cpu%d: starting`, and `/proc/cpuinfo` lists them all. `smp_start_aps` in
|
||||
`kernel/core/main.c` is 1.
|
||||
|
||||
Five distinct bugs, each hidden behind the one before it, and all invisible
|
||||
under QEMU because it publishes only one processor in the legacy MP table.
|
||||
VMware published the real count, which is why the triple fault only ever
|
||||
appeared there.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| CPU enumeration | `mpinit()` read the 1994 MP table. `kernel/arch/x86_64/acpi.c` now reads the ACPI MADT. |
|
||||
| triple fault | `entry64high` had **no AP branch** — every AP took the BSP's stack and ran `kmain()` twice. Now tests `EBX == 1`. |
|
||||
| `panic("acquire")` | `seginit()` called `kalloc()` before `%fs` existed; kalloc locks, locking reads `cpu`. BSP now preallocates each AP's page. |
|
||||
| `panic("sched locks")` | `kernel_fs_base` was **one global** read by `trapasm.S` on every entry, so all CPUs shared one per-CPU block. Now `%gs:0`. |
|
||||
| null `proc` in `sleep()` | `cpu`/`proc` were `__thread` — per *thread*, so the compiler cached the thread pointer across `sched()`, but `%fs` is per *CPU*. Now `mycpu()`/`myproc()`. |
|
||||
| `#GP` in `syscall_entry` | The new per-CPU slots landed on `%fs:(8)`, the syscall scratch word — the same memory as `%gs:8`. Moved to +16 and +24. |
|
||||
|
||||
The `__thread` one is the lesson worth keeping: **`__thread` is wrong for
|
||||
per-CPU kernel data.** It means per thread, and the compiler is entitled to
|
||||
cache the thread pointer across calls on that basis. A kernel thread changes CPU
|
||||
at every `sched()`, so the assumption is false for exactly the variables that
|
||||
most need to be right. `include/proc.h` carries the disassembly that proves it.
|
||||
|
||||
Per-CPU block layout, shared with assembly and not to be reordered:
|
||||
|
||||
+0 self-pointer x86-64 TLS ABI (%fs:0)
|
||||
+8 syscall scratch trapasm.S parks %rsp here
|
||||
+16 struct cpu *
|
||||
+24 struct proc *
|
||||
|
||||
### ~~The kernel demands 224 MB of RAM~~ *(fixed 2026-08-08)*
|
||||
|
||||
`kernel/arch/x86_64/e820.c` reads the firmware's memory map and sets `phystop`
|
||||
from it. **The floor went from 224 MB to 32 MB**, and the kernel now sees all
|
||||
the memory a machine actually has: 512 MB in gives `MemTotal: 524160 kB`, where
|
||||
before it reported 224 MB whatever was installed.
|
||||
|
||||
Three details worth keeping:
|
||||
|
||||
- It takes the end of the usable region **containing the kernel**, not the
|
||||
highest usable address anywhere. A memory map has reserved holes in it, and
|
||||
`freerange()` walks straight from the end of the kernel image to `phystop`
|
||||
handing out every page between — so the global maximum would put firmware
|
||||
tables on the free list, failing later and elsewhere and looking like anything
|
||||
but a memory map problem.
|
||||
- The ceiling is **3 GB**, because `kvmalloc()` maps 0–4 GB as four 1 GB pages
|
||||
and marks the top one uncached for PCI BARs.
|
||||
- Below `PHYSTOP_MIN` it prints why and stops, rather than triple faulting in
|
||||
silence. At 24 MB: `e820: only 23 MB usable; Schunix needs at least 24 MB`.
|
||||
|
||||
The original text is kept below because the reasoning still applies to the SMP
|
||||
half of §3b, which is the same shape of bug: a constant nobody revisited.
|
||||
|
||||
### The kernel demanded 224 MB of RAM
|
||||
|
||||
**Symptom as reported:** the OS needs ~256 MB, which is absurd next to Windows 98
|
||||
or an early Linux.
|
||||
|
||||
**Diagnosis:** it is one constant. `PHYSTOP` in `include/memlayout.h` is
|
||||
`0xE000000` — 224 MB — inherited unchanged from xv6, and `main()` hands
|
||||
`P2V(PHYSTOP)` straight to `kinit1()`/`dma_init()`. The kernel therefore
|
||||
*assumes* that much physical memory exists and walks off the end of RAM if it
|
||||
does not. Measured 2026-08-08: boots at 224M and above, triple faults at 192M
|
||||
and below. Nothing is actually being *used* — the requirement is entirely
|
||||
fictional.
|
||||
|
||||
**The fix is already half-built.** `boot/stage2.S` collects a full E820 memory
|
||||
map in real mode and leaves it at `VBE_INFO_ADDR + 0x200` (count) and `+ 0x208`
|
||||
(24-byte entries); `boot/stage2main.c` already parses it to print detected RAM.
|
||||
**The kernel never reads it.** So: have `main()` take the top of the highest
|
||||
usable E820 region instead of the constant, clamp it to what the direct map
|
||||
covers, and keep `PHYSTOP` only as a fallback for the case where the firmware
|
||||
returned nothing. That should drop the floor to something in the tens of
|
||||
megabytes and is a contained change.
|
||||
|
||||
---
|
||||
|
||||
## 4. Kept out of scope on purpose
|
||||
|
||||
- Anything that needs pushing or publishing. This work stays local.
|
||||
21
include/asm.h
Normal file
21
include/asm.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// assembler macros to create x86 segments
|
||||
//
|
||||
|
||||
#define SEG_NULLASM \
|
||||
.word 0, 0; \
|
||||
.byte 0, 0, 0, 0
|
||||
|
||||
// The 0xC0 means the limit is in 4096-byte units
|
||||
// and (for executable segments) 32-bit mode.
|
||||
#define SEG_ASM(type,base,lim) \
|
||||
.word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \
|
||||
.byte (((base) >> 16) & 0xff), (0x90 | (type)), \
|
||||
(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
|
||||
|
||||
#define STA_X 0x8 // Executable segment
|
||||
#define STA_E 0x4 // Expand down (non-executable segments)
|
||||
#define STA_C 0x4 // Conforming code segment (executable only)
|
||||
#define STA_W 0x2 // Writeable (non-executable segments)
|
||||
#define STA_R 0x2 // Readable (executable segments)
|
||||
#define STA_A 0x1 // Accessed
|
||||
14
include/buf.h
Normal file
14
include/buf.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
struct buf {
|
||||
int flags;
|
||||
uint dev;
|
||||
uint blockno;
|
||||
struct sleeplock lock;
|
||||
uint refcnt;
|
||||
struct buf *prev; // LRU cache list
|
||||
struct buf *next;
|
||||
struct buf *qnext; // disk queue
|
||||
uchar data[BSIZE];
|
||||
};
|
||||
#define B_VALID 0x2 // buffer has been read from disk
|
||||
#define B_DIRTY 0x4 // buffer needs to be written to disk
|
||||
|
||||
3
include/config_sinit.h
Normal file
3
include/config_sinit.h
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
static char *const rcinitcmd[] = { "/bin/sh", NULL };
|
||||
static char *const rcrebootcmd[] = { "/bin/halt", "-rb", NULL };
|
||||
static char *const rcpoweroffcmd[] = { "/bin/halt", "-po", NULL };
|
||||
15
include/date.h
Normal file
15
include/date.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
struct rtcdate {
|
||||
uint second;
|
||||
uint minute;
|
||||
uint hour;
|
||||
uint day;
|
||||
uint month;
|
||||
uint year;
|
||||
};
|
||||
|
||||
struct timeval {
|
||||
long tv_sec;
|
||||
long tv_usec;
|
||||
};
|
||||
466
include/defs.h
Normal file
466
include/defs.h
Normal file
|
|
@ -0,0 +1,466 @@
|
|||
#pragma once
|
||||
|
||||
struct buf;
|
||||
struct context;
|
||||
struct file;
|
||||
struct inode;
|
||||
struct pipe;
|
||||
struct proc;
|
||||
struct rtcdate;
|
||||
struct spinlock;
|
||||
struct sleeplock;
|
||||
struct stat;
|
||||
struct superblock;
|
||||
struct trapframe;
|
||||
struct dirent;
|
||||
|
||||
// entryother.S / syscall.S
|
||||
void syscall_entry(void);
|
||||
void ignore_sysret(void);
|
||||
|
||||
// bio.c
|
||||
void binit(void);
|
||||
struct buf* bread(uint, uint);
|
||||
void brelse(struct buf*);
|
||||
void bwrite(struct buf*);
|
||||
|
||||
// console.c
|
||||
struct termios;
|
||||
void consoleinit(void);
|
||||
extern int tty_fg_pgrp;
|
||||
// The terminal's line-discipline settings. TCGETS/TCSETS in sys_fs.c are the
|
||||
// only callers; the console honours ICANON, ECHO, ECHOE, ECHONL, ISIG,
|
||||
// ICRNL, INLCR, IGNCR, ISTRIP and VMIN.
|
||||
void cons_get_termios(struct termios *);
|
||||
void cons_set_termios(const struct termios *);
|
||||
void cprintf(char*, ...);
|
||||
void consoleintr(int(*)(void));
|
||||
void panic(char*) __attribute__((noreturn));
|
||||
|
||||
// exec.c
|
||||
int exec(char*, char**);
|
||||
|
||||
// file.c
|
||||
struct file* filealloc(void);
|
||||
int fdalloc(struct file*);
|
||||
void fileclose(struct file*);
|
||||
struct file* filedup(struct file*);
|
||||
void fileinit(void);
|
||||
int fileread(struct file*, char*, int n);
|
||||
int file_poll(struct file*, int events);
|
||||
int filestat(struct file*, struct stat*);
|
||||
int filewrite(struct file*, char*, int n);
|
||||
|
||||
// fs.c
|
||||
void fsinit(int);
|
||||
int dirlink(struct inode*, char*, uint64);
|
||||
struct inode* dirlookup(struct inode*, char*, uint*);
|
||||
// Remove the entry `name` at the cookie dirlookup returned in *poff.
|
||||
int dirunlink(struct inode*, char*, uint);
|
||||
// 1 if the directory holds no real entries, 0 if it does, -1 on error.
|
||||
int dirempty(struct inode*);
|
||||
// Fill `out` with getdents64 records starting at *poff, which is an opaque
|
||||
// position the filesystem advances. Returns bytes written, 0 at end of
|
||||
// directory. The inode must be locked.
|
||||
int getdents(struct inode*, char *out, int cap, uint64 *poff);
|
||||
// Mount table: a substitution applied during path resolution. Nothing on disk
|
||||
// records a mount, so unmounting is forgetting.
|
||||
void mountinit(void);
|
||||
int mount_add(uint dev, uint64 inum, uint fsdev, uint64 root);
|
||||
int mount_del_by_root(uint fsdev, uint64 root);
|
||||
struct inode* ialloc(uint, short);
|
||||
struct inode* iget(uint, uint64);
|
||||
struct inode* idup(struct inode*);
|
||||
void iinit(void);
|
||||
void ilock(struct inode*);
|
||||
void iput(struct inode*);
|
||||
void iunlock(struct inode*);
|
||||
void iunlockput(struct inode*);
|
||||
void iupdate(struct inode*);
|
||||
int namecmp(const char*, const char*);
|
||||
struct inode* namei(char*);
|
||||
struct inode* nameiparent(char*, char*);
|
||||
struct inode* nameifrom(struct inode*, char*);
|
||||
struct inode* nameiparentfrom(struct inode*, char*, char*);
|
||||
int readi(struct inode*, int, uint64, uint64, uint);
|
||||
void stati(struct inode*, struct stat*);
|
||||
int writei(struct inode*, int, uint64, uint64, uint);
|
||||
void itrunc(struct inode*);
|
||||
void itrunc_to(struct inode*, uint64);
|
||||
unsigned int BKDRHash(char * str);
|
||||
uint tree_expand(struct inode *ip, uint off);
|
||||
void tree_search(struct inode* dp, char* name, uint* poff, struct dirent* de,uint add);
|
||||
void build_dir_tree(struct inode* dp,uint64 pinum);
|
||||
struct inode* divesymlink(struct inode *);
|
||||
void readsb(int dev, struct superblock *sb);
|
||||
|
||||
// pci.c -- bus enumeration. Drivers that need to *find* their hardware use
|
||||
// these; drivers for things that have lived at a fixed address since 1984
|
||||
// (the PIC, the PIT, the legacy IDE ports) do not.
|
||||
struct pci_dev;
|
||||
struct pci_driver;
|
||||
void pci_init(void);
|
||||
uint32 pci_read32(uint8, uint8, uint8, uint8);
|
||||
uint16 pci_read16(uint8, uint8, uint8, uint8);
|
||||
uint8 pci_read8(uint8, uint8, uint8, uint8);
|
||||
void pci_write32(uint8, uint8, uint8, uint8, uint32);
|
||||
void pci_enable(struct pci_dev *, uint16 bits);
|
||||
uint8 pci_find_cap(struct pci_dev *, uint8 id);
|
||||
int pci_msi_enable(struct pci_dev *, int vector, int cpu);
|
||||
int pci_count(void);
|
||||
struct pci_dev *pci_get(int);
|
||||
struct pci_dev *pci_find_class(uint8 class, uint8 subclass);
|
||||
struct pci_dev *pci_find_id(uint16 vendor, uint16 device);
|
||||
void pci_bind(const struct pci_driver *);
|
||||
int pci_info(char *buf, int cap);
|
||||
|
||||
// drivers/registry.c -- the one list of every driver in the kernel.
|
||||
void pci_bind_all(void);
|
||||
|
||||
// xhci.c — `echo u > /proc/sysrq`
|
||||
void xhci_dump(void);
|
||||
|
||||
// ide.c
|
||||
void ideinit(void);
|
||||
void ideintr(void);
|
||||
void iderw(struct buf*);
|
||||
// For probes, not for reads: iderw() panics on an absent drive, so anything
|
||||
// that is guessing whether a disk exists has to ask here first.
|
||||
int ide_disk_present(int dev);
|
||||
|
||||
// fs/schfs/kmount.c -- SchFS.
|
||||
//
|
||||
// Objects are named by 64-bit object id, not by a path: resolution is the
|
||||
// caller's business, so these compose with a VFS rather than assuming one.
|
||||
// Every schfs_k* that changes anything is one transaction and is durable by the
|
||||
// time it returns.
|
||||
struct schfs_inode;
|
||||
void schfs_kmount(uint dev);
|
||||
int schfs_kmounted(void);
|
||||
uint64 schfs_klookup(const char *path);
|
||||
int schfs_kstat(uint64 ino, struct schfs_inode *out);
|
||||
long schfs_kread(uint64 ino, uint64 off, void *buf, unsigned n);
|
||||
int schfs_kreaddir(uint64 dir,
|
||||
int (*fn)(uint64, unsigned short, const char *,
|
||||
unsigned, void *),
|
||||
void *arg);
|
||||
int schfs_kinfo(char *buf, int cap);
|
||||
|
||||
int schfs_kcreate(uint64 dir, const char *name, unsigned namelen,
|
||||
unsigned short type, uint mode,
|
||||
unsigned short major, unsigned short minor, uint64 *out);
|
||||
long schfs_kwrite(uint64 ino, uint64 off, const void *buf,
|
||||
unsigned n);
|
||||
int schfs_ktruncate(uint64 ino, uint64 size);
|
||||
int schfs_kunlink(uint64 dir, const char *name, unsigned namelen);
|
||||
int schfs_klink(uint64 dir, const char *name, unsigned namelen,
|
||||
uint64 ino);
|
||||
int schfs_krename(uint64 olddir, const char *oldname,
|
||||
unsigned oldlen, uint64 newdir, const char *newname,
|
||||
unsigned newlen);
|
||||
int schfs_ksetattr(uint64 ino, const struct schfs_inode *in);
|
||||
int schfs_ksync(void);
|
||||
|
||||
// Pieces the VFS layer (fs/schfs/vfs.c) needs, where a directory entry and the
|
||||
// object it names are separate operations. The VFS keeps the link count.
|
||||
uint64 schfs_klookup_at(uint64 dir, const char *name, unsigned len);
|
||||
int schfs_kdirempty(uint64 dir);
|
||||
int schfs_kalloc_object(unsigned short type, uint mode,
|
||||
uint64 *out);
|
||||
int schfs_kdiradd(uint64 dir, const char *name, unsigned len,
|
||||
uint64 ino, unsigned short type);
|
||||
int schfs_kdirdel(uint64 dir, const char *name, unsigned len);
|
||||
int schfs_kfree(uint64 ino);
|
||||
uint64 schfs_kroot(void);
|
||||
|
||||
// ramdisk.c
|
||||
uint uptime(void);
|
||||
|
||||
// procfs.c
|
||||
int procread(struct inode*, uint, char*, int);
|
||||
int procwrite(struct inode*, uint, char*, int);
|
||||
// Shared with the /proc filesystem (kernel/fs/procfs.c): one owns the
|
||||
// namespace, the other the content.
|
||||
int proc_gen_global(int id, char *buf, int cap);
|
||||
int proc_write_sysrq(int user_src, uint64 src, uint n);
|
||||
void procfs_mount(void);
|
||||
|
||||
// input.c -- raw evdev event devices
|
||||
void inputinit(void);
|
||||
int inputread(struct inode*, uint, char*, int);
|
||||
int inputwrite(struct inode*, uint, char*, int);
|
||||
int inputready(struct inode*);
|
||||
void inputclose(struct inode*);
|
||||
void inputopen(struct inode*);
|
||||
int input_ioctl(struct inode*, int request, addr_t arg);
|
||||
int input_grab(int dev, int on);
|
||||
int input_is_grabbed(int dev);
|
||||
void input_report(int dev, int type, int code, int value);
|
||||
void input_sync(int dev);
|
||||
int input_ready(int dev);
|
||||
void input_ps2_scancode(int raw, int e0);
|
||||
void input_key(int hid_usage, int down);
|
||||
int input_hid_keycode(int hid_usage);
|
||||
void input_mouse(int dx, int dy, int buttons);
|
||||
|
||||
void ramdiskinit(void);
|
||||
void ramdiskrw(struct buf*);
|
||||
int ramdisk_present(void);
|
||||
|
||||
// usb_storage.c
|
||||
void usb_storage_rw(struct buf*);
|
||||
int usb_storage_present(void);
|
||||
void usb_storage_dump(void); // `echo s > /proc/sysrq`
|
||||
|
||||
// ioapic.c
|
||||
void ioapicenable(int irq, int cpu);
|
||||
void ioapicroute(int irq, int cpu, int level_low);
|
||||
void ioapicmask(int irq);
|
||||
extern uchar ioapicid;
|
||||
void ioapicinit(void);
|
||||
|
||||
// dma.c — see include/dma.h for the full contract
|
||||
void dma_init(void*, void*);
|
||||
void *dma_alloc(uint64 size, uint64 align);
|
||||
void dma_free(void*, uint64 size);
|
||||
addr_t dma_phys(const void*);
|
||||
uint64 dma_free_bytes(void);
|
||||
uint64 dma_total_bytes(void);
|
||||
|
||||
// kalloc.c
|
||||
char* kalloc(void);
|
||||
uint64 kalloc_free_bytes(void);
|
||||
void kfree(char*);
|
||||
void kinit1(void*, void*);
|
||||
void kinit2(void);
|
||||
|
||||
// kbd.c
|
||||
void kbdintr(void);
|
||||
|
||||
// mouse.c
|
||||
void mouseinit(void);
|
||||
void mouseintr(void);
|
||||
void mouse_inject(int dx, int dy, int buttons);
|
||||
int get_mouse_x(void);
|
||||
int get_mouse_y(void);
|
||||
uint8 get_mouse_buttons(void);
|
||||
|
||||
// lapic.c
|
||||
void cmostime(struct rtcdate *r);
|
||||
|
||||
// time.c -- wall-clock time, from the RTC read once at boot plus uptime.
|
||||
void time_init(void);
|
||||
void wallclock(uint64 *sec, uint64 *usec);
|
||||
extern uint64 boot_epoch;
|
||||
int cpunum(void);
|
||||
extern volatile uint* lapic;
|
||||
void lapiceoi(void);
|
||||
void lapicinit(void);
|
||||
void lapicstartap(uchar, uint);
|
||||
void microdelay(int);
|
||||
|
||||
// log.c
|
||||
void initlog(int dev);
|
||||
void log_write(struct buf*);
|
||||
void begin_op();
|
||||
void end_op();
|
||||
|
||||
// mp.c
|
||||
extern int ismp;
|
||||
void mpinit(void);
|
||||
|
||||
// pipe.c
|
||||
int pipealloc(struct file**, struct file**);
|
||||
void pipeclose(struct pipe*, int);
|
||||
int piperead(struct pipe*, char*, int);
|
||||
int pipewrite(struct pipe*, char*, int);
|
||||
|
||||
// net/unix.c
|
||||
struct socket;
|
||||
void sockinit(void);
|
||||
struct socket* socknew(int domain, int type);
|
||||
void sockclose(struct socket*);
|
||||
void sockdup(struct socket*);
|
||||
int sockread(struct socket*, char*, int);
|
||||
int sockwrite(struct socket*, char*, int);
|
||||
int sockbind(struct socket*, const char *path);
|
||||
int socklisten(struct socket*, int backlog);
|
||||
struct socket* sockaccept(struct socket*);
|
||||
int sockconnect(struct socket*, const char *path);
|
||||
int sockreadable(struct socket*);
|
||||
int sock_hup(struct socket*);
|
||||
int sock_shutdown(struct socket*, int how);
|
||||
int sockpair(struct socket **a, struct socket **b);
|
||||
const char* sockname(struct socket*);
|
||||
|
||||
// proc.c
|
||||
void exit(int status);
|
||||
int fork(void);
|
||||
int growproc(int64);
|
||||
int kill(int);
|
||||
int sigsend_pgrp(int pgid, int sig);
|
||||
int proc_pgid(int pid);
|
||||
int proc_setpgid(int pid, int pgid);
|
||||
int proc_setsid(void);
|
||||
int sigsend(int pid, int sig);
|
||||
int proc_exists(int);
|
||||
int proc_num(void);
|
||||
struct procinfo;
|
||||
int proc_snapshot(int idx, struct procinfo *out);
|
||||
int proc_snapshot_pid(int pid, struct procinfo *out);
|
||||
void pinit(void);
|
||||
void procdump(void);
|
||||
void scheduler(void) __attribute__((noreturn));
|
||||
void sched(void);
|
||||
void sleep(void*, struct spinlock*);
|
||||
int wait(void);
|
||||
int waitpid(int, int*, int);
|
||||
void wakeup(void*);
|
||||
void yield(void);
|
||||
void sinit(void);
|
||||
void kstack_check_canary(const char *where);
|
||||
|
||||
// swtch.S
|
||||
void swtch(struct context**, struct context*);
|
||||
|
||||
// spinlock.c
|
||||
void acquire(struct spinlock*);
|
||||
void getcallerpcs(void*, addr_t*);
|
||||
void getstackpcs(addr_t*, addr_t*);
|
||||
int holding(struct spinlock*);
|
||||
void initlock(struct spinlock*, char*);
|
||||
void release(struct spinlock*);
|
||||
void pushcli(void);
|
||||
void popcli(void);
|
||||
|
||||
// sleeplock.c
|
||||
void acquiresleep(struct sleeplock*);
|
||||
void releasesleep(struct sleeplock*);
|
||||
int holdingsleep(struct sleeplock*);
|
||||
void initsleeplock(struct sleeplock*, char*);
|
||||
|
||||
// string.c
|
||||
int memcmp(const void*, const void*, uint64);
|
||||
void* memmove(void*, const void*, uint64);
|
||||
void* memset(void*, int, uint64);
|
||||
char* safestrcpy(char*, const char*, int);
|
||||
int strlen(const char*);
|
||||
int strnlen(const char*, int);
|
||||
int strncmp(const char*, const char*, uint64);
|
||||
char* strncpy(char*, const char*, int);
|
||||
|
||||
// syscall.c
|
||||
void syscall(struct trapframe *);
|
||||
void syscallinit(void);
|
||||
void deliver_signals(struct trapframe *);
|
||||
int argint(int, int*);
|
||||
int argptr(int, char**, int);
|
||||
int argstr(int, char**);
|
||||
int argaddr(int, addr_t*);
|
||||
int fetchaddr(addr_t, addr_t*);
|
||||
int fetchstr(addr_t, char**);
|
||||
int fetchint(addr_t, int*);
|
||||
|
||||
// trap.c
|
||||
void idtinit(void);
|
||||
extern uint ticks;
|
||||
void tvinit(void);
|
||||
extern struct spinlock tickslock;
|
||||
|
||||
// uart.c
|
||||
void uartearlyinit(void);
|
||||
void uartinit(void);
|
||||
void uartintr(void);
|
||||
void uartputc(int);
|
||||
|
||||
// vga.c
|
||||
void vga_init(uint64);
|
||||
void vga_draw_pixel(int, int, uint32);
|
||||
void vga_draw_char(int, int, uchar, uint32);
|
||||
void vga_puts(int, int, char*, uint32);
|
||||
void vga_putc(int, uint32);
|
||||
void vga_clear(uint32);
|
||||
void vga_screen_reset(void);
|
||||
void vga_set_bg(uint32);
|
||||
// Crash-screen primitives: the panic handler lays itself out rather than
|
||||
// printing. See kernel/core/panic.c.
|
||||
int vga_ready(void);
|
||||
void vga_dims(int *w, int *h);
|
||||
int vga_char_w(void);
|
||||
int vga_char_h(void);
|
||||
// Framebuffer geometry. vga.c reads the VBE mode info, so it is the only
|
||||
// thing that knows where the framebuffer is; nobody else may assume.
|
||||
uint64 vga_fb_phys(void);
|
||||
uint64 vga_fb_size(void);
|
||||
uint32 vga_fb_stride(void);
|
||||
uint32 vga_fb_bpp(void);
|
||||
volatile uchar *vga_fb_ptr(void);
|
||||
|
||||
// fb.c -- /dev/fb0, major 2.
|
||||
void fbclose(struct inode*);
|
||||
// Called from sys_mmap: a mapping of /dev/fb0 is a licence to draw with no
|
||||
// further syscalls, so it is the last the kernel hears about it.
|
||||
void fb_mapped(void);
|
||||
int fbread(struct inode*, uint, char*, int);
|
||||
int fbwrite(struct inode*, uint, char*, int);
|
||||
int fb_ioctl(int request, addr_t arg);
|
||||
// drivers/net/e1000 -- raw Ethernet. No protocol stack above it yet, so these
|
||||
// are the whole interface: queue a frame, take a frame, and the MAC.
|
||||
int e1000_transmit(const void *frame, int len);
|
||||
int e1000_receive(void *out, int cap);
|
||||
const unsigned char *e1000_mac(void);
|
||||
void e1000_selftest(void);
|
||||
|
||||
// registry.c
|
||||
void platform_register_devices(void);
|
||||
|
||||
void e820_init(void);
|
||||
int acpi_init(void);
|
||||
void vga_fill_rect(int x, int y, int w, int h, uint32);
|
||||
void vga_fill_circle(int cx, int cy, int r, uint32);
|
||||
void vga_text_at(int, int, const char*, uint32);
|
||||
void vga_draw_logo(int x, int y, uint32 color, uint32 bg);
|
||||
void vga_logo_centered(int cy, uint32 color, uint32 bg);
|
||||
// Cursor addressing and erasing, for the console's ANSI escape parser.
|
||||
int vga_cols(void);
|
||||
int vga_rows(void);
|
||||
void vga_cursor_get(int *col, int *row);
|
||||
void vga_cursor_set(int col, int row);
|
||||
void vga_erase_display(int mode);
|
||||
void vga_erase_line(int mode);
|
||||
|
||||
// console.c / panic.c
|
||||
void cons_panic_mode(void);
|
||||
extern int cons_serial_only;
|
||||
void console_takeover(void);
|
||||
|
||||
// vm.c
|
||||
void seginit(void);
|
||||
void kvmalloc(void);
|
||||
pml4e_t* setupkvm(void);
|
||||
char* uva2ka(pml4e_t*, char*);
|
||||
addr_t allocuvm(pml4e_t*, uint64, uint64);
|
||||
addr_t deallocuvm(pml4e_t*, uint64, uint64);
|
||||
void freevm(pml4e_t*);
|
||||
void inituvm(pml4e_t*, char*, uint64);
|
||||
int loaduvm(pml4e_t*, char*, struct inode*, uint64, uint64);
|
||||
pml4e_t* copyuvm(pml4e_t*, uint64);
|
||||
void switchuvm(struct proc*);
|
||||
void switchkvm(void);
|
||||
int pipe_poll(struct pipe*, int readable, int writable);
|
||||
int console_input_ready(void);
|
||||
int copyin(pml4e_t*, addr_t, void*, uint64);
|
||||
int copyout(pml4e_t*, addr_t, void*, uint64);
|
||||
void vm_unmap_range(pml4e_t*, uint64, uint64);
|
||||
// Unmap without freeing: for pages this process does not own — device memory
|
||||
// and shared segments. vm_unmap_range() would hand them to kfree.
|
||||
void vm_unmap_norelease(pml4e_t*, uint64, uint64);
|
||||
// Shared memory (sys_mem.c). shm_ref/shm_detach maintain the attachment
|
||||
// count that decides when a segment's pages go back.
|
||||
void shm_ref(int id);
|
||||
void shm_detach(int id);
|
||||
void proc_release_shared(struct proc *);
|
||||
int copy_vma_range(pml4e_t *dstpgdir, pml4e_t *srcpgdir, uint64 va, uint64 length, int is_fb);
|
||||
void clearpteu(pml4e_t *pgdir, char *uva);
|
||||
|
||||
#define NELEM(x) (sizeof(x)/sizeof((x)[0]))
|
||||
89
include/device.h
Normal file
89
include/device.h
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#pragma once
|
||||
|
||||
// The device model: one list of everything attached, whatever bus found it.
|
||||
//
|
||||
// Before this there were two registries in kernel/drivers/registry.c -- one
|
||||
// array of PCI drivers, one of USB drivers -- and nothing that knew about both.
|
||||
// That is fine for binding drivers to hardware, which is all it was for, and
|
||||
// useless for the three things wanted next:
|
||||
//
|
||||
// *Enumeration.* "What is in this machine, and what claimed it" had no
|
||||
// answer that covered a USB disk and a PCI display at once. A device manager
|
||||
// is exactly that question, so it needs exactly this list.
|
||||
//
|
||||
// *Classes.* A compositor should ask for "the display" and not care whether
|
||||
// that is the VBE framebuffer or virtio-gpu. Without a class, every consumer
|
||||
// hardcodes a driver, and adding the second driver means editing the
|
||||
// consumers -- which is how a framebuffer address ends up hardcoded in three
|
||||
// places, as it once was here.
|
||||
//
|
||||
// *A boundary for drivers that are not compiled in.* Drivers the user
|
||||
// installs (see docs/drivers.md) have to attach somewhere that is not "an
|
||||
// array in registry.c", because that array is fixed at link time.
|
||||
//
|
||||
// The shape is deliberately the one NeXTSTEP/macOS uses rather than Linux's:
|
||||
// a device is an object with a class and an ops vtable, and consumers talk to
|
||||
// the class. It is *not* a device tree -- there is no parent/child nesting
|
||||
// here, only a flat list with a bus tag, because nothing yet needs to walk a
|
||||
// hierarchy and a tree that nobody traverses is just a harder list.
|
||||
|
||||
#define DEV_NAMELEN 24
|
||||
|
||||
// Which bus found it. This is provenance, not capability -- what the device
|
||||
// *does* is its class.
|
||||
enum dev_bus {
|
||||
DEVBUS_PLATFORM = 0, // found by looking, not by enumerating: PS/2, VBE
|
||||
DEVBUS_PCI,
|
||||
DEVBUS_USB,
|
||||
DEVBUS_NBUS
|
||||
};
|
||||
|
||||
// What it is. A consumer asks for a class and gets whatever is bound.
|
||||
enum dev_class {
|
||||
DEVCLASS_OTHER = 0,
|
||||
DEVCLASS_DISPLAY,
|
||||
DEVCLASS_INPUT,
|
||||
DEVCLASS_STORAGE,
|
||||
DEVCLASS_NET,
|
||||
DEVCLASS_BUS, // a controller that finds more devices (xHCI, hubs)
|
||||
DEVCLASS_NCLASS
|
||||
};
|
||||
|
||||
struct device {
|
||||
char name[DEV_NAMELEN]; // "vga0", "xhci0" -- unique, human-readable
|
||||
char desc[48]; // free text for the enumerator
|
||||
enum dev_bus bus;
|
||||
enum dev_class class;
|
||||
|
||||
// Where it came from, for the enumerator to print. Zero when not applicable.
|
||||
uint16 vendor, product;
|
||||
uint8 b, s, f; // PCI bus/slot/func
|
||||
|
||||
const char *driver; // whatever claimed it, or 0 for unclaimed
|
||||
void *ops; // class-specific vtable; see display.h
|
||||
void *priv; // the driver's own state
|
||||
|
||||
int in_use;
|
||||
};
|
||||
|
||||
#define NDEVICE 32
|
||||
|
||||
// Register a device. Returns it, or 0 if the table is full. The name is made
|
||||
// unique by appending an index, so a caller passes "vga" and gets "vga0".
|
||||
struct device *device_add(const char *base, enum dev_bus bus,
|
||||
enum dev_class class, const char *driver);
|
||||
|
||||
// Set the human-readable description. Bounded copy into the fixed field, so
|
||||
// callers do not each need to know how long it is.
|
||||
void device_set_desc(struct device *, const char *);
|
||||
|
||||
// Walk. Returns 0 past the end; index is dense over registered devices.
|
||||
struct device *device_at(int i);
|
||||
int device_count(void);
|
||||
|
||||
// First device of a class with a bound driver, or 0. This is how a consumer
|
||||
// finds "the display" without naming a driver.
|
||||
struct device *device_find_class(enum dev_class class);
|
||||
|
||||
const char *dev_bus_name(enum dev_bus);
|
||||
const char *dev_class_name(enum dev_class);
|
||||
56
include/display.h
Normal file
56
include/display.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
// The display class: what a consumer talks to instead of a driver.
|
||||
//
|
||||
// A compositor should ask for "the display" and get whatever is bound --
|
||||
// today the VBE framebuffer, later virtio-gpu. Without this, every consumer
|
||||
// names a driver, and adding the second display driver means editing all of
|
||||
// them. That is not hypothetical here: the framebuffer's physical address was
|
||||
// once hardcoded in three separate places.
|
||||
//
|
||||
// Found with device_find_class(DEVCLASS_DISPLAY); see include/device.h.
|
||||
|
||||
struct device;
|
||||
|
||||
struct display_mode {
|
||||
int width, height;
|
||||
int bpp;
|
||||
uint32 stride; // bytes per row of the *visible* framebuffer
|
||||
};
|
||||
|
||||
struct display_ops {
|
||||
void (*mode)(struct device *, struct display_mode *);
|
||||
|
||||
// The buffer to draw into, and its stride. Returns 0 if double buffering is
|
||||
// unavailable, in which case the caller should draw directly and accept
|
||||
// tearing rather than not drawing at all.
|
||||
//
|
||||
// Allocated on first call, never at boot: it is around two megabytes at
|
||||
// 800x600, and a machine whose compositor never starts should not pay that.
|
||||
void *(*acquire)(struct device *, uint32 *stride_out);
|
||||
|
||||
// Copy a rectangle of the back buffer to the display. Coordinates are in
|
||||
// pixels; the whole screen is (0, 0, width, height).
|
||||
void (*present)(struct device *, int x, int y, int w, int h);
|
||||
|
||||
// Block until the display is between frames. Optional -- a driver that
|
||||
// cannot tell leaves this null, and present() then simply tears less than
|
||||
// drawing directly would.
|
||||
void (*wait_vblank)(struct device *);
|
||||
};
|
||||
|
||||
// The bound display, or 0. Convenience over device_find_class().
|
||||
struct device *display_get(void);
|
||||
|
||||
// Wrappers that do nothing gracefully when there is no display or the driver
|
||||
// left an op null, so callers are not each written around a maybe-null vtable.
|
||||
int display_mode(struct display_mode *out);
|
||||
void *display_acquire(uint32 *stride_out);
|
||||
void display_present(int x, int y, int w, int h);
|
||||
void display_wait_vblank(void);
|
||||
|
||||
// Drive the whole path once and draw a test pattern. `echo d > /proc/sysrq`.
|
||||
void display_selftest(void);
|
||||
|
||||
// Draw a text specimen through the display class. `echo t > /proc/sysrq`.
|
||||
void text_selftest(void);
|
||||
43
include/dma.h
Normal file
43
include/dma.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
// Physically contiguous memory for devices that read and write it themselves.
|
||||
//
|
||||
// kalloc() hands out 4 KB pages from a free list, in whatever order they were
|
||||
// released. That is exactly what a page table wants and exactly what a device
|
||||
// cannot use: an xHCI command ring, a NIC's descriptor array or a virtio
|
||||
// queue is a single physically contiguous object that the hardware walks
|
||||
// without any idea that paging exists. Two kalloc'd pages are adjacent in the
|
||||
// kernel's address space only by luck.
|
||||
//
|
||||
// So DMA memory comes from an arena carved out of the top of physical memory
|
||||
// before kalloc ever sees it, and is handed out contiguous and aligned.
|
||||
//
|
||||
// On x86 this memory needs no special cacheability. The PCI bus is cache
|
||||
// coherent: a device's write invalidates the corresponding cache line, and a
|
||||
// CPU write is snooped out of the store buffer before the device sees stale
|
||||
// data. Architectures without that guarantee need explicit cache maintenance
|
||||
// around every descriptor access, and porting this file is where that would
|
||||
// go. What is *not* free even here is ordering -- the compiler and the CPU
|
||||
// will happily reorder stores to a ring, so a driver still needs a barrier
|
||||
// between filling a descriptor and telling the device about it.
|
||||
|
||||
#define DMA_ARENA_BYTES (4*1024*1024)
|
||||
|
||||
void dma_init(void *vstart, void *vend);
|
||||
|
||||
// Zeroed, physically contiguous, at least `align`-aligned (align must be a
|
||||
// power of two). Returns a kernel virtual address, or 0.
|
||||
void *dma_alloc(uint64 size, uint64 align);
|
||||
|
||||
// `size` must be the size the allocation was made with, as in Linux's
|
||||
// dma_free_coherent(). Tracking it here would mean a second table whose only
|
||||
// job is to remember something the caller already knows.
|
||||
void dma_free(void *p, uint64 size);
|
||||
|
||||
// The address to give the device. Kernel memory is direct-mapped, so this is
|
||||
// just V2P -- but drivers should say what they mean, because on a machine
|
||||
// with an IOMMU these stop being the same number.
|
||||
addr_t dma_phys(const void *p);
|
||||
|
||||
uint64 dma_free_bytes(void);
|
||||
uint64 dma_total_bytes(void);
|
||||
165
include/e1000.h
Normal file
165
include/e1000.h
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
#pragma once
|
||||
|
||||
// Intel 8254x gigabit Ethernet — the register and descriptor layout.
|
||||
//
|
||||
// From Intel's public "PCI/PCI-X Family of Gigabit Ethernet Controllers
|
||||
// Software Developer's Manual" (317453). A public document with no licence on
|
||||
// what you write from it, which is why this is written rather than ported: see
|
||||
// docs/licensing.md and docs/roadmap.md §1.
|
||||
//
|
||||
// QEMU's `-device e1000` is an 82540EM and implements enough of the manual to
|
||||
// be worth targeting; it is also what the default `-net nic` gives you.
|
||||
|
||||
#define E1000_VENDOR 0x8086
|
||||
|
||||
// The 8254x family: PCI/PCI-X gigabit controllers that share the register map,
|
||||
// descriptor format and EEPROM interface this driver implements.
|
||||
//
|
||||
// The device IDs are transcribed from FreeBSD's `sys/dev/e1000/e1000_hw.h`
|
||||
// (Copyright (c) 2001-2020, Intel Corporation, BSD 3-clause -- verified to
|
||||
// carry no advertising clause; see docs/licensing.md and NOTICE). They are
|
||||
// data, not logic: the driver below is still written from Intel's manual
|
||||
// 317453, and nothing of FreeBSD's implementation is used.
|
||||
//
|
||||
// This matters more than it looks. The driver bound exactly one device -- the
|
||||
// 82540EM QEMU emulates -- which is fine for a virtual machine and useless on
|
||||
// the sort of second-hand hardware a system like this actually gets run on.
|
||||
// These are the cards in every desktop and server of that generation.
|
||||
//
|
||||
// Deliberately *not* included: the PCIe 8257x parts (82571-82574) and anything
|
||||
// newer. They present a similar register map but differ in NVM access and
|
||||
// initialisation, and claiming a device this driver has not been shown to
|
||||
// initialise is worse than not claiming it -- the card binds, reports a MAC of
|
||||
// nonsense, and nothing works for reasons that look nothing like the cause.
|
||||
#define E1000_DEV_82540EM 0x100E
|
||||
#define E1000_DEV_82540EM_LOM 0x1015
|
||||
#define E1000_DEV_82540EP_LOM 0x1016
|
||||
#define E1000_DEV_82540EP 0x1017
|
||||
#define E1000_DEV_82540EP_LP 0x101E
|
||||
#define E1000_DEV_82541EI 0x1013
|
||||
#define E1000_DEV_82541EI_MOBILE 0x1018
|
||||
#define E1000_DEV_82541ER_LOM 0x1014
|
||||
#define E1000_DEV_82541ER 0x1078
|
||||
#define E1000_DEV_82541GI 0x1076
|
||||
#define E1000_DEV_82541GI_MOBILE 0x1077
|
||||
#define E1000_DEV_82541GI_LF 0x107C
|
||||
#define E1000_DEV_82544EI_FIBER 0x1009
|
||||
#define E1000_DEV_82544GC_COPPER 0x100C
|
||||
#define E1000_DEV_82544GC_LOM 0x100D
|
||||
#define E1000_DEV_82545EM_COPPER 0x100F
|
||||
#define E1000_DEV_82545EM_FIBER 0x1011
|
||||
#define E1000_DEV_82545GM_COPPER 0x1026
|
||||
#define E1000_DEV_82545GM_FIBER 0x1027
|
||||
#define E1000_DEV_82545GM_SERDES 0x1028
|
||||
#define E1000_DEV_82546EB_COPPER 0x1010
|
||||
#define E1000_DEV_82546EB_FIBER 0x1012
|
||||
#define E1000_DEV_82546EB_QUAD 0x101D
|
||||
#define E1000_DEV_82546GB_COPPER 0x1079
|
||||
#define E1000_DEV_82546GB_FIBER 0x107A
|
||||
#define E1000_DEV_82546GB_SERDES 0x107B
|
||||
#define E1000_DEV_82547EI 0x1019
|
||||
|
||||
// --- registers, byte offsets into BAR 0 ------------------------------------
|
||||
|
||||
#define E1000_CTRL 0x0000 // device control
|
||||
#define E1000_STATUS 0x0008 // device status
|
||||
#define E1000_EERD 0x0014 // EEPROM read
|
||||
#define E1000_ICR 0x00C0 // interrupt cause read
|
||||
#define E1000_IMS 0x00D0 // interrupt mask set
|
||||
#define E1000_IMC 0x00D8 // interrupt mask clear
|
||||
|
||||
#define E1000_RCTL 0x0100 // receive control
|
||||
#define E1000_TCTL 0x0400 // transmit control
|
||||
#define E1000_TIPG 0x0410 // transmit inter-packet gap
|
||||
|
||||
#define E1000_RDBAL 0x2800 // receive descriptor base, low
|
||||
#define E1000_RDBAH 0x2804 // receive descriptor base, high
|
||||
#define E1000_RDLEN 0x2808 // receive descriptor length, in bytes
|
||||
#define E1000_RDH 0x2810 // receive descriptor head
|
||||
#define E1000_RDT 0x2818 // receive descriptor tail
|
||||
|
||||
#define E1000_TDBAL 0x3800
|
||||
#define E1000_TDBAH 0x3804
|
||||
#define E1000_TDLEN 0x3808
|
||||
#define E1000_TDH 0x3810
|
||||
#define E1000_TDT 0x3818
|
||||
|
||||
#define E1000_MTA 0x5200 // multicast table array, 128 entries
|
||||
#define E1000_RAL 0x5400 // receive address low (the MAC filter)
|
||||
#define E1000_RAH 0x5404 // receive address high
|
||||
|
||||
// CTRL
|
||||
#define E1000_CTRL_RST (1 << 26)
|
||||
#define E1000_CTRL_ASDE (1 << 5) // auto-speed detection
|
||||
#define E1000_CTRL_SLU (1 << 6) // set link up
|
||||
|
||||
// STATUS
|
||||
#define E1000_STATUS_LU (1 << 1) // link up
|
||||
|
||||
// EERD
|
||||
#define E1000_EERD_START (1 << 0)
|
||||
#define E1000_EERD_DONE (1 << 4)
|
||||
#define E1000_EERD_ADDR_SHIFT 8
|
||||
#define E1000_EERD_DATA_SHIFT 16
|
||||
|
||||
// RCTL
|
||||
#define E1000_RCTL_EN (1 << 1) // receiver enable
|
||||
#define E1000_RCTL_SBP (1 << 2) // store bad packets
|
||||
#define E1000_RCTL_UPE (1 << 3) // unicast promiscuous
|
||||
#define E1000_RCTL_MPE (1 << 4) // multicast promiscuous
|
||||
#define E1000_RCTL_LBM_NONE (0 << 6)
|
||||
#define E1000_RCTL_BAM (1 << 15) // accept broadcast
|
||||
#define E1000_RCTL_SZ_2048 (0 << 16) // buffer size, with BSEX clear
|
||||
#define E1000_RCTL_SECRC (1 << 26) // strip the Ethernet CRC
|
||||
|
||||
// TCTL
|
||||
#define E1000_TCTL_EN (1 << 1)
|
||||
#define E1000_TCTL_PSP (1 << 3) // pad short packets
|
||||
#define E1000_TCTL_CT_SHIFT 4 // collision threshold
|
||||
#define E1000_TCTL_COLD_SHIFT 12 // collision distance
|
||||
|
||||
// Transmit descriptor command bits
|
||||
#define E1000_TXD_CMD_EOP (1 << 0) // end of packet
|
||||
#define E1000_TXD_CMD_IFCS (1 << 1) // insert FCS/CRC
|
||||
#define E1000_TXD_CMD_RS (1 << 3) // report status
|
||||
#define E1000_TXD_STAT_DD (1 << 0) // descriptor done
|
||||
|
||||
// Receive descriptor status bits
|
||||
#define E1000_RXD_STAT_DD (1 << 0)
|
||||
#define E1000_RXD_STAT_EOP (1 << 1)
|
||||
|
||||
// Interrupt causes worth unmasking.
|
||||
#define E1000_ICR_TXDW (1 << 0) // transmit descriptor written back
|
||||
#define E1000_ICR_LSC (1 << 2) // link status change
|
||||
#define E1000_ICR_RXDMT0 (1 << 4) // receive descriptor minimum threshold
|
||||
#define E1000_ICR_RXT0 (1 << 7) // receiver timer
|
||||
|
||||
// --- descriptors -----------------------------------------------------------
|
||||
//
|
||||
// Both are 16 bytes and the hardware reads them by physical address, so the
|
||||
// rings must be physically contiguous and 16-byte aligned.
|
||||
|
||||
struct e1000_tx_desc {
|
||||
uint64 addr;
|
||||
uint16 length;
|
||||
uint8 cso; // checksum offset
|
||||
uint8 cmd;
|
||||
uint8 status;
|
||||
uint8 css; // checksum start
|
||||
uint16 special;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct e1000_rx_desc {
|
||||
uint64 addr;
|
||||
uint16 length;
|
||||
uint16 checksum;
|
||||
uint8 status;
|
||||
uint8 errors;
|
||||
uint16 special;
|
||||
} __attribute__((packed));
|
||||
|
||||
// Ring sizes. The manual requires the descriptor ring length to be a multiple
|
||||
// of 128 bytes, i.e. a multiple of 8 descriptors.
|
||||
#define E1000_NTX 16
|
||||
#define E1000_NRX 16
|
||||
#define E1000_BUFSZ 2048
|
||||
35
include/elf.h
Normal file
35
include/elf.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#define ELF_MAGIC 0x464C457FU
|
||||
|
||||
struct elfhdr {
|
||||
uint32 magic;
|
||||
uchar elf[12];
|
||||
uint16 type;
|
||||
uint16 machine;
|
||||
uint32 version;
|
||||
uint64 entry;
|
||||
uint64 phoff;
|
||||
uint64 shoff;
|
||||
uint32 flags;
|
||||
uint16 ehsize;
|
||||
uint16 phentsize;
|
||||
uint16 phnum;
|
||||
uint16 shentsize;
|
||||
uint16 shnum;
|
||||
uint16 shstrndx;
|
||||
};
|
||||
|
||||
struct proghdr {
|
||||
uint32 type;
|
||||
uint32 flags;
|
||||
uint64 off;
|
||||
uint64 vaddr;
|
||||
uint64 paddr;
|
||||
uint64 filesz;
|
||||
uint64 memsz;
|
||||
uint64 align;
|
||||
};
|
||||
|
||||
#define ELF_PROG_LOAD 1
|
||||
#define ELF_PROG_FLAG_EXEC 1
|
||||
#define ELF_PROG_FLAG_WRITE 2
|
||||
#define ELF_PROG_FLAG_READ 4
|
||||
60
include/errno.h
Normal file
60
include/errno.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// Error numbers, matching the Linux x86_64 ABI values so that musl-linked
|
||||
// programs interpret syscall failures correctly. Kernel code returns these
|
||||
// negated (e.g. `return -ENOSYS;`), exactly like Linux does internally.
|
||||
#ifndef SCHUNIX_ERRNO_H
|
||||
#define SCHUNIX_ERRNO_H
|
||||
|
||||
#define EPERM 1 // Operation not permitted
|
||||
#define ENOENT 2 // No such file or directory
|
||||
#define ESRCH 3 // No such process
|
||||
#define EINTR 4 // Interrupted system call
|
||||
#define EIO 5 // I/O error
|
||||
#define ENXIO 6 // No such device or address
|
||||
#define E2BIG 7 // Argument list too long
|
||||
#define ENOEXEC 8 // Exec format error
|
||||
#define EBADF 9 // Bad file descriptor
|
||||
#define ECHILD 10 // No child processes
|
||||
#define EAGAIN 11 // Try again
|
||||
#define ENOMEM 12 // Out of memory
|
||||
#define EACCES 13 // Permission denied
|
||||
#define EFAULT 14 // Bad address
|
||||
#define EBUSY 16 // Device or resource busy
|
||||
#define EEXIST 17 // File exists
|
||||
#define EXDEV 18 // Cross-device link
|
||||
#define ENODEV 19 // No such device
|
||||
#define ENOTDIR 20 // Not a directory
|
||||
#define EISDIR 21 // Is a directory
|
||||
#define EINVAL 22 // Invalid argument
|
||||
#define ENFILE 23 // File table overflow
|
||||
#define EMFILE 24 // Too many open files
|
||||
#define ENOTTY 25 // Not a typewriter
|
||||
#define EFBIG 27 // File too large
|
||||
#define ENOSPC 28 // No space left on device
|
||||
#define ESPIPE 29 // Illegal seek
|
||||
#define EROFS 30 // Read-only file system
|
||||
#define EMLINK 31 // Too many links
|
||||
#define EPIPE 32 // Broken pipe
|
||||
#define ERANGE 34 // Math result not representable
|
||||
#define ENAMETOOLONG 36 // File name too long
|
||||
#define ENOSYS 38 // Function not implemented
|
||||
#define ENOTEMPTY 39 // Directory not empty
|
||||
#define ELOOP 40 // Too many symbolic links encountered
|
||||
|
||||
// Sockets. Same Linux x86_64 values, so strerror() in a musl-linked program
|
||||
// prints the message the programmer expected rather than a neighbouring one.
|
||||
#define ENOTSOCK 88 // Socket operation on non-socket
|
||||
#define EDESTADDRREQ 89 // Destination address required
|
||||
#define EMSGSIZE 90 // Message too long
|
||||
#define EPROTOTYPE 91 // Protocol wrong type for socket
|
||||
#define ENOPROTOOPT 92 // Protocol not available
|
||||
#define EPROTONOSUPPORT 93 // Protocol not supported
|
||||
#define EOPNOTSUPP 95 // Operation not supported on transport endpoint
|
||||
#define EAFNOSUPPORT 97 // Address family not supported by protocol
|
||||
#define EADDRINUSE 98 // Address already in use
|
||||
#define EADDRNOTAVAIL 99 // Cannot assign requested address
|
||||
#define ECONNRESET 104 // Connection reset by peer
|
||||
#define EISCONN 106 // Transport endpoint is already connected
|
||||
#define ENOTCONN 107 // Transport endpoint is not connected
|
||||
#define ECONNREFUSED 111 // Connection refused
|
||||
|
||||
#endif // SCHUNIX_ERRNO_H
|
||||
95
include/fb.h
Normal file
95
include/fb.h
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
#pragma once
|
||||
|
||||
// Framebuffer device ABI -- /dev/fb0.
|
||||
//
|
||||
// These are Linux's fbdev request numbers and structure layouts, not ours.
|
||||
// Schunix invented this device, so nothing forced the choice; matching Linux
|
||||
// was still the right one. A framebuffer is the first thing a graphics
|
||||
// program looks for, and every such program already knows how to ask
|
||||
// FBIOGET_VSCREENINFO. Inventing a private struct would have meant patching
|
||||
// each one we ever port, and -- worse -- a Linux-ABI binary calling
|
||||
// FBIOGET_VSCREENINFO would have gotten a *differently shaped* answer to a
|
||||
// request it thought it understood, which is the failure mode that made
|
||||
// `>>` truncate back when include/fcntl.h held xv6's open flags instead of
|
||||
// Linux's. Same lesson, applied before it bites rather than after.
|
||||
//
|
||||
// Plain C types throughout (no types.h) so userspace can include this
|
||||
// directly. Both sides are LP64.
|
||||
|
||||
#define FBIOGET_VSCREENINFO 0x4600
|
||||
#define FBIOPUT_VSCREENINFO 0x4601
|
||||
#define FBIOGET_FSCREENINFO 0x4602
|
||||
#define FBIOBLANK 0x4611
|
||||
|
||||
// fb_fix_screeninfo.type
|
||||
#define FB_TYPE_PACKED_PIXELS 0
|
||||
|
||||
// fb_fix_screeninfo.visual
|
||||
#define FB_VISUAL_TRUECOLOR 2
|
||||
|
||||
// fb_var_screeninfo.activate
|
||||
#define FB_ACTIVATE_NOW 0
|
||||
|
||||
// One colour channel's position within a pixel. `offset` is the bit position
|
||||
// of the channel's least significant bit; `length` is how many bits it gets.
|
||||
// A length of 0 means the channel does not exist -- which is how an opaque
|
||||
// framebuffer reports `transp`.
|
||||
struct fb_bitfield {
|
||||
unsigned int offset;
|
||||
unsigned int length;
|
||||
unsigned int msb_right; // != 0 means bit 0 is the MSB; never here
|
||||
};
|
||||
|
||||
struct fb_var_screeninfo {
|
||||
unsigned int xres; // visible resolution
|
||||
unsigned int yres;
|
||||
unsigned int xres_virtual; // virtual resolution; == visible, no panning
|
||||
unsigned int yres_virtual;
|
||||
unsigned int xoffset; // offset of visible area within virtual
|
||||
unsigned int yoffset;
|
||||
unsigned int bits_per_pixel;
|
||||
unsigned int grayscale; // 0 == colour
|
||||
struct fb_bitfield red;
|
||||
struct fb_bitfield green;
|
||||
struct fb_bitfield blue;
|
||||
struct fb_bitfield transp;
|
||||
unsigned int nonstd; // 0 == standard pixel format
|
||||
unsigned int activate;
|
||||
unsigned int height; // physical size in mm; 0 == unknown, and
|
||||
unsigned int width; // VBE does not tell us, so it stays 0
|
||||
unsigned int accel_flags;
|
||||
// Timings. The mode is whatever the bootloader's VBE call established and
|
||||
// we cannot change it, so these are all reported as zero rather than
|
||||
// invented.
|
||||
unsigned int pixclock;
|
||||
unsigned int left_margin;
|
||||
unsigned int right_margin;
|
||||
unsigned int upper_margin;
|
||||
unsigned int lower_margin;
|
||||
unsigned int hsync_len;
|
||||
unsigned int vsync_len;
|
||||
unsigned int sync;
|
||||
unsigned int vmode;
|
||||
unsigned int rotate;
|
||||
unsigned int colorspace;
|
||||
unsigned int reserved[4];
|
||||
};
|
||||
|
||||
struct fb_fix_screeninfo {
|
||||
char id[16]; // "schunixfb"
|
||||
unsigned long smem_start; // physical address of the framebuffer
|
||||
unsigned int smem_len; // its length in bytes
|
||||
unsigned int type;
|
||||
unsigned int type_aux;
|
||||
unsigned int visual;
|
||||
unsigned short xpanstep; // 0 == panning unsupported
|
||||
unsigned short ypanstep;
|
||||
unsigned short ywrapstep;
|
||||
unsigned int line_length; // bytes per scanline (the VBE stride, which
|
||||
// is NOT always xres * bytes_per_pixel)
|
||||
unsigned long mmio_start; // no separate register aperture
|
||||
unsigned int mmio_len;
|
||||
unsigned int accel; // no acceleration
|
||||
unsigned short capabilities;
|
||||
unsigned short reserved[2];
|
||||
};
|
||||
43
include/fcntl.h
Normal file
43
include/fcntl.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
// open(2) flags.
|
||||
//
|
||||
// These are the *Linux x86-64* values, and they have to be: every program in
|
||||
// the userland is built against a libc that sends Linux flag values, and the
|
||||
// kernel reads them with these constants. They disagreed for a long time, with
|
||||
// results that looked like filesystem corruption rather than like a mistake --
|
||||
// Linux O_APPEND (0x400) landed on Schunix's O_TRUNC, so `cmd >> file`
|
||||
// truncated the file it was meant to extend, and Linux O_TRUNC (0x200) landed
|
||||
// on O_CREATE, so `cmd > file` left whatever was past the new end in place.
|
||||
// Neither failure pointed anywhere near a header.
|
||||
//
|
||||
// Do not tidy these into a neat sequence. They are an external interface.
|
||||
|
||||
#define O_RDONLY 0x000000
|
||||
#define O_WRONLY 0x000001
|
||||
#define O_RDWR 0x000002
|
||||
#define O_ACCMODE 0x000003
|
||||
|
||||
#define O_CREATE 0x000040 /* Linux O_CREAT (0100) */
|
||||
#define O_CREAT O_CREATE /* the spelling every program uses */
|
||||
#define O_EXCL 0x000080 /* Linux O_EXCL (0200) */
|
||||
#define O_NOCTTY 0x000100 /* Linux O_NOCTTY (0400) */
|
||||
#define O_TRUNC 0x000200 /* Linux O_TRUNC (01000) */
|
||||
#define O_APPEND 0x000400 /* Linux O_APPEND (02000) */
|
||||
#define O_NONBLOCK 0x000800 /* Linux O_NONBLOCK (04000); no-op here */
|
||||
#define O_DIRECTORY 0x010000 /* Linux O_DIRECTORY (0200000) */
|
||||
#define O_NOFOLLOW 0x020000 /* Linux O_NOFOLLOW (0400000) */
|
||||
#define O_CLOEXEC 0x080000 /* Linux O_CLOEXEC (02000000) */
|
||||
|
||||
// Schunix-only, placed above every flag Linux defines so it can never be
|
||||
// mistaken for one. 0x40000 was used before and collides with Linux O_NOATIME.
|
||||
#define O_EXTENT 0x8000000 /* sparse-extent hint */
|
||||
|
||||
#define F_DUPFD 0
|
||||
#define F_GETFD 1
|
||||
#define F_SETFD 2
|
||||
#define F_GETFL 3
|
||||
#define F_SETFL 4
|
||||
#define F_DUPFD_CLOEXEC 1030 // Linux value
|
||||
|
||||
#define FD_CLOEXEC 1
|
||||
132
include/file.h
Normal file
132
include/file.h
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
struct file {
|
||||
enum { FD_NONE, FD_PIPE, FD_INODE, FD_SOCKET } type;
|
||||
int ref; // reference count
|
||||
char readable;
|
||||
char writable;
|
||||
// O_NONBLOCK. A property of the open file description, not of the
|
||||
// descriptor, which is why it lives here and not in myproc()->ofile_flags --
|
||||
// dup() shares it and F_SETFL on either fd changes it for both, exactly as
|
||||
// POSIX says.
|
||||
char nonblock;
|
||||
struct pipe *pipe;
|
||||
struct inode *ip;
|
||||
struct socket *sock;
|
||||
uint64 off;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Filesystem operations
|
||||
//
|
||||
// One table per filesystem, chosen by iget() from the device number and hung
|
||||
// off the inode. Everything above this line -- the syscall layer, exec, the
|
||||
// page cache -- calls the wrappers in fs.c and never learns which filesystem it
|
||||
// is talking to.
|
||||
//
|
||||
// A vtable rather than a chain of `if (dev == SCHFS_DEV)` because there are two
|
||||
// filesystems today and there will be more: the whole point of the exercise is
|
||||
// that adding one is a new table, not another branch in fifteen functions.
|
||||
//
|
||||
// Every hook takes an inode that is already locked, except ialloc (which has no
|
||||
// inode yet) and iload (which is what makes one valid in the first place).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct stat;
|
||||
|
||||
struct fs_ops {
|
||||
const char *name;
|
||||
|
||||
// Fill in the type/size/link fields of a freshly iget()'d inode.
|
||||
void (*iload)(struct inode *);
|
||||
// Write those fields back to disk.
|
||||
void (*istore)(struct inode *);
|
||||
// The last link is gone and nobody has it open: release the object.
|
||||
void (*ifree)(struct inode *);
|
||||
|
||||
int (*read)(struct inode *, int user_dst, uint64 dst, uint64 off, uint n);
|
||||
int (*write)(struct inode *, int user_src, uint64 src, uint64 off, uint n);
|
||||
// Set the length. Growing is allowed only where the filesystem can represent
|
||||
// the gap; v1 cannot and ignores it.
|
||||
void (*truncate)(struct inode *, uint64 newsize);
|
||||
|
||||
// Directory operations. `poff` is an opaque cookie identifying where the
|
||||
// entry lives, meaningful only to the same filesystem's dirunlink.
|
||||
struct inode *(*dirlookup)(struct inode *dp, char *name, uint *poff);
|
||||
int (*dirlink)(struct inode *dp, char *name, uint64 inum);
|
||||
int (*dirunlink)(struct inode *dp, char *name, uint off);
|
||||
int (*dirempty)(struct inode *dp);
|
||||
|
||||
// Create an object with no name yet. The caller sets nlink and calls
|
||||
// dirlink; until it does, the object is unreachable.
|
||||
struct inode *(*ialloc)(uint dev, short type);
|
||||
|
||||
// Read directory entries into a getdents64-shaped stream. `off` is an opaque
|
||||
// position that starts at 0 and is echoed back; returns bytes written, 0 at
|
||||
// the end of the directory, or negative on error.
|
||||
int (*getdents)(struct inode *dp, char *out, int cap, uint64 *off);
|
||||
};
|
||||
|
||||
// In-memory inode.
|
||||
//
|
||||
// `inum` and `size` are 64-bit because the filesystem underneath may be: SchFS
|
||||
// object ids and file sizes are both uint64 on disk, and a 32-bit copy here
|
||||
// would silently truncate them into a different file. The v1 filesystem uses a
|
||||
// small fraction of the range and is unaffected.
|
||||
struct inode {
|
||||
uint dev;
|
||||
uint64 inum;
|
||||
int ref;
|
||||
struct sleeplock lock;
|
||||
int valid;
|
||||
const struct fs_ops *ops; // chosen by iget from dev; never null once held
|
||||
|
||||
short type;
|
||||
short major;
|
||||
short minor;
|
||||
short nlink;
|
||||
uint64 size;
|
||||
|
||||
uint rwmode;
|
||||
uint supermode;
|
||||
uint showmode;
|
||||
uint atime;
|
||||
uint mtime;
|
||||
uint ctime;
|
||||
uint addrs[NDIRECT+3];
|
||||
};
|
||||
#define I_VALID 0x2
|
||||
|
||||
// Device number that means "the mounted SchFS volume". Real disks are 0 and 1,
|
||||
// and this is deliberately far from them so a stray value never looks like a
|
||||
// SchFS inode.
|
||||
#define SCHFS_DEV 64
|
||||
|
||||
// Device number that means "the synthetic /proc". Like SCHFS_DEV, it is
|
||||
// deliberately nowhere near a real disk's 0 or 1.
|
||||
#define PROCFS_DEV 65
|
||||
|
||||
// table mapping major device number to
|
||||
// device functions
|
||||
struct devsw {
|
||||
int (*read)(struct inode*, uint, char*, int);
|
||||
int (*write)(struct inode*, uint, char*, int);
|
||||
// Optional. Returns 0 when a read would block, which is the only thing
|
||||
// fileread needs to know to honour O_NONBLOCK without every device driver
|
||||
// growing a flags argument. A device that omits this is treated as always
|
||||
// ready, which is the right answer for a disk and for /dev/zero.
|
||||
int (*ready)(struct inode*);
|
||||
// Optional. The last descriptor for this open file is going away. A device
|
||||
// that hands a program exclusive-feeling control of the hardware needs this
|
||||
// to put the hardware back -- /dev/fb0 uses it to give the text console its
|
||||
// screen back when whatever was drawing on it exits.
|
||||
void (*close)(struct inode*);
|
||||
// Optional. A descriptor has just been opened on this device. /dev/input
|
||||
// uses it to discard events that arrived while nobody was listening -- see
|
||||
// inputopen().
|
||||
void (*open)(struct inode*);
|
||||
};
|
||||
|
||||
extern struct devsw devsw[];
|
||||
|
||||
#define CONSOLE 1
|
||||
// Major number of /dev/input/eventN. The minor is the device index.
|
||||
#define INPUT_MAJOR 7
|
||||
4627
include/font.h
Normal file
4627
include/font.h
Normal file
File diff suppressed because it is too large
Load diff
203
include/fs.h
Normal file
203
include/fs.h
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
// SCHFS - On-disk file system format.
|
||||
// Both the kernel and user programs use this header file.
|
||||
//
|
||||
// Changes from original:
|
||||
// - struct dinode gains atime, mtime, ctime (timestamps) and checksum
|
||||
// (CRC32c). Padding reduced from 44 to 28 bytes; struct stays 128 bytes.
|
||||
// - struct superblock gains checksum field.
|
||||
// - struct fsv1_extent added for T_EXTENT inode block mapping.
|
||||
// - OVERFLOW_DIR_START / OVERFLOW_DIR_END replace the bare "113 * BSIZE"
|
||||
// magic numbers used in fs.c and mkfs.c.
|
||||
|
||||
#ifndef FS_H
|
||||
#define FS_H
|
||||
|
||||
#define ROOTINO 1 // root i-number
|
||||
#define BSIZE 1024 // block size
|
||||
|
||||
// Permission mode bits (rwmode field)
|
||||
#define RW 3 // 11 read+write
|
||||
#define W_ 1 // 01 write-only
|
||||
#define R_ 2 // 10 read-only
|
||||
#define NONE 0 // 00 no access
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Superblock
|
||||
// ---------------------------------------------------------------------------
|
||||
// Disk layout:
|
||||
// [ boot | super | log | inode-bitmap | inode-blocks | block-bitmap | data ]
|
||||
|
||||
struct superblock {
|
||||
uint magic; // Must be FSMAGIC
|
||||
uint size; // Size of file system image (blocks)
|
||||
uint nblocks; // Number of data blocks
|
||||
uint ninodes; // Number of inodes
|
||||
uint nlog; // Number of log blocks
|
||||
uint logstart; // Block number of first log block
|
||||
uint inodestart; // Block number of first inode block
|
||||
uint bmapstart; // Block number of first free-block bitmap block
|
||||
uint ibmapstart; // Block number of first inode-bitmap block
|
||||
uint freeinodes; // Count of free inodes
|
||||
uint freeblocks; // Count of free blocks
|
||||
uint checksum; // CRC32c of this struct (field zeroed during computation)
|
||||
};
|
||||
|
||||
extern struct superblock sb;
|
||||
|
||||
#define FSMAGIC 0x10203040
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Block / inode geometry
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define NDIRECT 12
|
||||
#define NINDIRECT (BSIZE / sizeof(uint))
|
||||
|
||||
// Maximum file size in blocks: direct + 1x + 2x + 3x indirect
|
||||
#define MAXFILE (NDIRECT \
|
||||
+ NINDIRECT \
|
||||
+ (NINDIRECT * NINDIRECT) \
|
||||
+ (NINDIRECT * NINDIRECT * NINDIRECT))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Extent descriptor (T_EXTENT inodes only)
|
||||
//
|
||||
// Up to MAX_INLINE_EXTENTS are packed into ip->addrs[0..NDIRECT-1].
|
||||
// addrs[] = NDIRECT uints = 12*4 = 48 bytes
|
||||
// each fsv1_extent = 12 bytes → 48/12 = 4 inline extents
|
||||
// (addrs[NDIRECT], [NDIRECT+1], [NDIRECT+2] remain as indirect pointers)
|
||||
//
|
||||
// For files needing more extents, addrs[NDIRECT] points to an indirect block
|
||||
// of BSIZE/sizeof(struct fsv1_extent) = 85 additional extents.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Renamed from "schfs_extent". The new format in include/schfs.h has its own,
|
||||
// completely different, struct of that name -- 64-bit fields and a refcount --
|
||||
// and any file including both headers hit a redefinition error. Two structs
|
||||
// with one name and two layouts is a trap worth removing rather than working
|
||||
// around; this one belongs to the v1 format and dies with it.
|
||||
struct fsv1_extent {
|
||||
uint start_block; // logical block number in the file (first of run)
|
||||
uint start_blkaddr; // physical disk block address
|
||||
uint len; // number of contiguous blocks in this run
|
||||
};
|
||||
|
||||
// Inline extents packed into addrs[0 .. NDIRECT-1] only.
|
||||
#define MAX_INLINE_EXTENTS (NDIRECT * sizeof(uint) / sizeof(struct fsv1_extent))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// On-disk inode (exactly 128 bytes; IPB = 8 inodes per 1024-byte block)
|
||||
//
|
||||
// Byte layout:
|
||||
// 0 short type 2
|
||||
// 2 short major 2
|
||||
// 4 short minor 2
|
||||
// 6 short nlink 2
|
||||
// 8 uint size 4
|
||||
// 12 uint rwmode 4
|
||||
// 16 uint supermode 4
|
||||
// 20 uint addrs[15] 60 T_EXTENT: packed fsv1_extent[4] in [0..11], rest normal
|
||||
// 80 uint showmode 4
|
||||
// 84 uint atime 4 ← last access time (Unix seconds)
|
||||
// 88 uint mtime 4 ← last modification time
|
||||
// 92 uint ctime 4 ← last metadata-change time
|
||||
// 96 uint checksum 4 ← CRC32c (zeroed during computation)
|
||||
// 100 uchar pad[28] 28 ← was 44; reduced by 16 for the 4 new fields
|
||||
// 128 bytes total
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct dinode {
|
||||
short type; // file type
|
||||
short major; // major device number (T_DEVICE only)
|
||||
short minor; // minor device number (T_DEVICE only)
|
||||
short nlink; // hard-link count
|
||||
uint size; // file size in bytes
|
||||
uint rwmode; // read/write permission flags
|
||||
uint supermode; // superuser-only flag
|
||||
uint addrs[NDIRECT + 3]; // block addresses / packed extent entries
|
||||
uint showmode; // visibility flag
|
||||
uint atime; // last access time (seconds since epoch)
|
||||
uint mtime; // last modification time
|
||||
uint ctime; // last metadata-change time
|
||||
uint checksum; // CRC32c of this dinode (field = 0 when hashing)
|
||||
uchar pad[28]; // reserved padding (struct = 128 bytes)
|
||||
};
|
||||
|
||||
// Inodes per block
|
||||
#define IPB (BSIZE / sizeof(struct dinode))
|
||||
|
||||
// Block containing inode i
|
||||
#define IBLOCK(i, sb) ((i) / IPB + (sb).inodestart)
|
||||
|
||||
// Bitmap bits per block
|
||||
#define BPB (BSIZE * 8)
|
||||
|
||||
// Block of free-block bitmap containing bit for block b
|
||||
#define BBLOCK(b, sb) ((b) / BPB + (sb).bmapstart)
|
||||
|
||||
// Block of inode bitmap containing bit for inode i
|
||||
#define IBBLOCK(i, sb) ((i) / BPB + (sb).ibmapstart)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Directory entry
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// 30, not 14.
|
||||
//
|
||||
// 14 was xv6's, and the v1 filesystem truncated to it *silently*: a file called
|
||||
// JetBrainsMono-Regular.ttf was stored as "JetBrainsMono-", could not be opened
|
||||
// by its real name, and would have collided outright with
|
||||
// JetBrainsMono-Bold.ttf. Any two files sharing a 14-character prefix mapped to
|
||||
// one entry. That is data loss, not a display limit.
|
||||
//
|
||||
// The value is chosen so that `struct dirent` is exactly 32 bytes, because the
|
||||
// directory HTree below is laid out in *bytes* and a bucket must stay 256 bytes
|
||||
// (see DIRENT_BUCKET_BYTES). Going from 16-byte to 32-byte entries therefore
|
||||
// halves the slots per bucket from 16 to 8 and changes no offset anywhere else
|
||||
// -- HASHRANGE, the overflow region and MAXFSIZE all keep their values. The
|
||||
// cost is more overflow use in heavily collided directories, which is a far
|
||||
// better trade than silently mangling names.
|
||||
//
|
||||
// It is still not POSIX's 255. Getting there means reshaping the directory
|
||||
// format, which wants the on-disk version policy SchFS is getting first.
|
||||
#define DIRSIZ 30
|
||||
|
||||
struct dirent {
|
||||
ushort inum;
|
||||
char name[DIRSIZ];
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Directory HTree constants
|
||||
//
|
||||
// BKDRHash(name) returns a value in [16, 16 + HASHRANGE).
|
||||
// Each bucket is 256 bytes: 8 dirent slots of 32 bytes. It was 16 slots of 16
|
||||
// until DIRSIZ grew; the byte size is what the layout depends on, so it is the
|
||||
// thing held constant.
|
||||
//
|
||||
// With HASHRANGE=443:
|
||||
// max primary end byte = (16 + 443) * 256 = 117 504 (~114.75 blocks)
|
||||
// OVERFLOW_DIR_START = 113 * BSIZE = 115 712
|
||||
// OVERFLOW_DIR_END = 115 * BSIZE = 117 760
|
||||
// MAXFSIZE = 119 * BSIZE = 121 856
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define DIRENT_NUM 7168
|
||||
#define FIRST_LAYER_NODE_NUM 112
|
||||
#define OVERFLOW_BLOCK (FIRST_LAYER_NODE_NUM + 1)
|
||||
#define NUM_DIRENT_PER_BLOCK (BSIZE / sizeof(struct dirent))
|
||||
#define SECOND_LAYER_NODE_NUM NUM_DIRENT_PER_BLOCK
|
||||
#define HASHRANGE 443
|
||||
|
||||
// One hash bucket, in bytes. Every offset in the HTree is derived from this, so
|
||||
// it must not change when DIRSIZ does -- see the note on DIRSIZ.
|
||||
#define DIRENT_BUCKET_BYTES 256
|
||||
#define DIRENT_PER_BUCKET (DIRENT_BUCKET_BYTES / sizeof(struct dirent))
|
||||
#define MAXFSIZE (119 * BSIZE)
|
||||
|
||||
// Overflow bucket: shared between fs.c and mkfs.c.
|
||||
// These replace the bare "113 * BSIZE" / "115 * BSIZE" literals.
|
||||
#define OVERFLOW_DIR_START (113 * BSIZE)
|
||||
#define OVERFLOW_DIR_END (115 * BSIZE)
|
||||
|
||||
#endif // FS_H
|
||||
93
include/input.h
Normal file
93
include/input.h
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
// Raw input events -- Linux's evdev interface.
|
||||
//
|
||||
// The console gives characters. That is the right thing for a shell and the
|
||||
// wrong thing for everything else: a character stream has no key *releases*,
|
||||
// no way to tell left shift from right, no notion of a key being held, and no
|
||||
// mouse at all. A game holding W to walk forward, or a compositor deciding
|
||||
// which window a click landed in, needs the events underneath the characters.
|
||||
//
|
||||
// The layout is Linux's, deliberately. It is what every program that reads input
|
||||
// already speaks -- SDL, X, libinput, every game engine -- so a program that
|
||||
// runs on Linux reads input here without a porting layer. It costs nothing to
|
||||
// match: the struct is four fields.
|
||||
//
|
||||
// Nodes are /dev/input/event0 (keyboard) and /dev/input/event1 (mouse).
|
||||
|
||||
struct input_timeval {
|
||||
int64 tv_sec;
|
||||
int64 tv_usec;
|
||||
};
|
||||
|
||||
struct input_event {
|
||||
struct input_timeval time;
|
||||
uint16 type;
|
||||
uint16 code;
|
||||
int32 value;
|
||||
};
|
||||
|
||||
// Event types.
|
||||
#define EV_SYN 0x00
|
||||
#define EV_KEY 0x01
|
||||
#define EV_REL 0x02
|
||||
#define EV_ABS 0x03
|
||||
#define EV_MSC 0x04
|
||||
|
||||
#define SYN_REPORT 0
|
||||
|
||||
// Relative axes.
|
||||
#define REL_X 0x00
|
||||
#define REL_Y 0x01
|
||||
#define REL_WHEEL 0x08
|
||||
|
||||
// Buttons live in the EV_KEY code space alongside keys, which is why the
|
||||
// keyboard's codes stop well below 0x100.
|
||||
#define BTN_LEFT 0x110
|
||||
#define BTN_RIGHT 0x111
|
||||
#define BTN_MIDDLE 0x112
|
||||
|
||||
// Key codes worth naming. The rest are the AT scancode-set-1 numbers, which
|
||||
// Linux's keycodes were derived from and still match for everything below
|
||||
// 0x54 -- that is not a coincidence and it is why the PS/2 path needs no
|
||||
// translation table for unprefixed bytes.
|
||||
#define KEY_RESERVED 0
|
||||
#define KEY_ESC 1
|
||||
#define KEY_ENTER 28
|
||||
#define KEY_LEFTCTRL 29
|
||||
#define KEY_LEFTSHIFT 42
|
||||
#define KEY_RIGHTSHIFT 54
|
||||
#define KEY_LEFTALT 56
|
||||
#define KEY_SPACE 57
|
||||
#define KEY_RIGHTCTRL 97
|
||||
#define KEY_RIGHTALT 100
|
||||
#define KEY_HOME 102
|
||||
#define KEY_UP 103
|
||||
#define KEY_PAGEUP 104
|
||||
#define KEY_LEFT 105
|
||||
#define KEY_RIGHT 106
|
||||
#define KEY_END 107
|
||||
#define KEY_DOWN 108
|
||||
#define KEY_PAGEDOWN 109
|
||||
#define KEY_INSERT 110
|
||||
#define KEY_DELETE 111
|
||||
#define KEY_LEFTMETA 125
|
||||
#define KEY_RIGHTMETA 126
|
||||
|
||||
#define KEY_MAX 255
|
||||
|
||||
// EVIOCGRAB -- take/release exclusive access to a device.
|
||||
//
|
||||
// Linux's _IOW('E', 0x90, int). Spelled as the resulting constant rather than
|
||||
// built from an _IOW macro because Schunix has no ioctl-encoding macros and
|
||||
// inventing a set just for this would be a bigger change than the feature.
|
||||
// A non-zero argument grabs, zero releases; the argument is passed by value.
|
||||
#define EVIOCGRAB 0x40044590
|
||||
|
||||
// Which device an event came from. Both are minor numbers under the input
|
||||
// major, and both are the index into the driver's array.
|
||||
#define INPUT_KBD 0
|
||||
#define INPUT_MOUSE 1
|
||||
#define NINPUTDEV 2
|
||||
43
include/irq.h
Normal file
43
include/irq.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
// Hardware interrupt registration.
|
||||
//
|
||||
// Until this existed, trap() was a switch statement listing every interrupt
|
||||
// the kernel knew about, and adding a device meant editing the trap handler.
|
||||
// That is fine while the devices are the four the PC has always had at fixed
|
||||
// pins; it stops being fine the moment a device's IRQ is whatever the BIOS
|
||||
// decided to put in its PCI config space this boot. A driver cannot write
|
||||
// `case T_IRQ0 + IRQ_XHCI:` because there is no such constant -- it only
|
||||
// learns its line at probe time.
|
||||
//
|
||||
// So: a line has a chain of handlers, a driver adds itself to one at probe
|
||||
// time, and enabling the line is a consequence of registering rather than a
|
||||
// separate call somebody can forget.
|
||||
//
|
||||
// Chains matter more than they look. PCI interrupt pins are *shared*: four
|
||||
// INTx lines are routed to a handful of IOAPIC inputs, so two unrelated
|
||||
// devices routinely land on the same one. A handler must therefore check
|
||||
// whether its own device actually raised the interrupt and do nothing if it
|
||||
// did not.
|
||||
|
||||
#define NIRQ 24 // IOAPIC redirection entries on a PC
|
||||
#define NCHAIN 4 // handlers that may share one line
|
||||
|
||||
typedef void (*irq_handler_t)(void *arg);
|
||||
|
||||
// Returns 0, or -1 if the line is out of range, reserved, or full.
|
||||
// Registering enables the line; the last unregister masks it again.
|
||||
//
|
||||
// `level_low` selects PCI's signalling (level-triggered, active low) rather
|
||||
// than ISA's (edge-triggered, active high). Getting this wrong does not fail
|
||||
// cleanly: an edge-configured level interrupt fires once and then wedges,
|
||||
// because the device keeps the line asserted and no further edge ever comes.
|
||||
int irq_register(int irq, irq_handler_t fn, void *arg, const char *name,
|
||||
int level_low);
|
||||
void irq_unregister(int irq, irq_handler_t fn, void *arg);
|
||||
|
||||
// Called from trap() with the IOAPIC pin number, not the vector.
|
||||
void irq_dispatch(int irq);
|
||||
|
||||
void irq_init(void);
|
||||
int irq_info(char *buf, int cap); // /proc/interrupts
|
||||
112
include/kbd.h
Normal file
112
include/kbd.h
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// PC keyboard interface constants
|
||||
|
||||
#define KBSTATP 0x64 // kbd controller status port(I)
|
||||
#define KBS_DIB 0x01 // kbd data in buffer
|
||||
#define KBDATAP 0x60 // kbd data port(I)
|
||||
|
||||
#define NO 0
|
||||
|
||||
#define SHIFT (1<<0)
|
||||
#define CTL (1<<1)
|
||||
#define ALT (1<<2)
|
||||
|
||||
#define CAPSLOCK (1<<3)
|
||||
#define NUMLOCK (1<<4)
|
||||
#define SCROLLLOCK (1<<5)
|
||||
|
||||
#define E0ESC (1<<6)
|
||||
|
||||
// Special keycodes
|
||||
#define KEY_HOME 0xE0
|
||||
#define KEY_END 0xE1
|
||||
#define KEY_UP 0xE2
|
||||
#define KEY_DN 0xE3
|
||||
#define KEY_LF 0xE4
|
||||
#define KEY_RT 0xE5
|
||||
#define KEY_PGUP 0xE6
|
||||
#define KEY_PGDN 0xE7
|
||||
#define KEY_INS 0xE8
|
||||
#define KEY_DEL 0xE9
|
||||
|
||||
// C('A') == Control-A
|
||||
#define C(x) (x - '@')
|
||||
|
||||
static uchar shiftcode[256] =
|
||||
{
|
||||
[0x1D] CTL,
|
||||
[0x2A] SHIFT,
|
||||
[0x36] SHIFT,
|
||||
[0x38] ALT,
|
||||
[0x9D] CTL,
|
||||
[0xB8] ALT
|
||||
};
|
||||
|
||||
static uchar togglecode[256] =
|
||||
{
|
||||
[0x3A] CAPSLOCK,
|
||||
[0x45] NUMLOCK,
|
||||
[0x46] SCROLLLOCK
|
||||
};
|
||||
|
||||
static uchar normalmap[256] =
|
||||
{
|
||||
NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00
|
||||
'7', '8', '9', '0', '-', '=', '\b', '\t',
|
||||
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10
|
||||
'o', 'p', '[', ']', '\n', NO, 'a', 's',
|
||||
'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20
|
||||
'\'', '`', NO, '\\', 'z', 'x', 'c', 'v',
|
||||
'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30
|
||||
NO, ' ', NO, NO, NO, NO, NO, NO,
|
||||
NO, NO, NO, NO, NO, NO, NO, '7', // 0x40
|
||||
'8', '9', '-', '4', '5', '6', '+', '1',
|
||||
'2', '3', '0', '.', NO, NO, NO, NO, // 0x50
|
||||
[0x9C] '\n', // KP_Enter
|
||||
[0xB5] '/', // KP_Div
|
||||
[0xC8] KEY_UP, [0xD0] KEY_DN,
|
||||
[0xC9] KEY_PGUP, [0xD1] KEY_PGDN,
|
||||
[0xCB] KEY_LF, [0xCD] KEY_RT,
|
||||
[0x97] KEY_HOME, [0xCF] KEY_END,
|
||||
[0xD2] KEY_INS, [0xD3] KEY_DEL
|
||||
};
|
||||
|
||||
static uchar shiftmap[256] =
|
||||
{
|
||||
NO, 033, '!', '@', '#', '$', '%', '^', // 0x00
|
||||
'&', '*', '(', ')', '_', '+', '\b', '\t',
|
||||
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10
|
||||
'O', 'P', '{', '}', '\n', NO, 'A', 'S',
|
||||
'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20
|
||||
'"', '~', NO, '|', 'Z', 'X', 'C', 'V',
|
||||
'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30
|
||||
NO, ' ', NO, NO, NO, NO, NO, NO,
|
||||
NO, NO, NO, NO, NO, NO, NO, '7', // 0x40
|
||||
'8', '9', '-', '4', '5', '6', '+', '1',
|
||||
'2', '3', '0', '.', NO, NO, NO, NO, // 0x50
|
||||
[0x9C] '\n', // KP_Enter
|
||||
[0xB5] '/', // KP_Div
|
||||
[0xC8] KEY_UP, [0xD0] KEY_DN,
|
||||
[0xC9] KEY_PGUP, [0xD1] KEY_PGDN,
|
||||
[0xCB] KEY_LF, [0xCD] KEY_RT,
|
||||
[0x97] KEY_HOME, [0xCF] KEY_END,
|
||||
[0xD2] KEY_INS, [0xD3] KEY_DEL
|
||||
};
|
||||
|
||||
static uchar ctlmap[256] =
|
||||
{
|
||||
NO, NO, NO, NO, NO, NO, NO, NO,
|
||||
NO, NO, NO, NO, NO, NO, NO, NO,
|
||||
C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'),
|
||||
C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'),
|
||||
C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO,
|
||||
NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'),
|
||||
C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO,
|
||||
[0x9C] '\r', // KP_Enter
|
||||
[0xB5] C('/'), // KP_Div
|
||||
[0xC8] KEY_UP, [0xD0] KEY_DN,
|
||||
[0xC9] KEY_PGUP, [0xD1] KEY_PGDN,
|
||||
[0xCB] KEY_LF, [0xCD] KEY_RT,
|
||||
[0x97] KEY_HOME, [0xCF] KEY_END,
|
||||
[0xD2] KEY_INS, [0xD3] KEY_DEL
|
||||
};
|
||||
|
||||
534
include/logo.h
Normal file
534
include/logo.h
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
#pragma once
|
||||
|
||||
// GENERATED by tools/mklogo.py -- do not edit. Run `make logo`.
|
||||
// source: Assets/Schunix Small Logo.png
|
||||
//
|
||||
// An ink-coverage mask, not colour: 0 is background and full scale is
|
||||
// solid logo. Whoever draws it picks the colour, so the palette stays in
|
||||
// include/theme.h and can change without regenerating anything.
|
||||
//
|
||||
// Two pixels per byte, high nibble first. Included by boot/stage2main.c
|
||||
// as well as kernel/dev/vga.c, and stage2 must fit below the 0x10000
|
||||
// kernel-search buffer -- hence 4 bits rather than 8.
|
||||
|
||||
#define LOGO_W 128
|
||||
#define LOGO_H 128
|
||||
|
||||
// Ink at linear pixel index i, 0..255.
|
||||
#define LOGO_INK(i) ((unsigned char)((((i) & 1) ? (logo_mask[(i) >> 1] & 0xF) \
|
||||
: (logo_mask[(i) >> 1] >> 4)) * 17))
|
||||
|
||||
static const unsigned char logo_mask[LOGO_W * LOGO_H / 2] = {
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0x56,0x79,0xaa,0xbb,
|
||||
0xbb,0xaa,0x98,0x75,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x47,0x9b,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xb9,0x74,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x47,0x9a,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xba,0x75,0x20,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x15,0x8a,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xb9,0x51,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x37,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0x84,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x01,0x58,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0x96,0x10,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x01,0x69,0x99,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xa7,0x10,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x58,0x99,0x99,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xba,0x87,0x76,0x66,
|
||||
0x66,0x67,0x89,0xab,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xaa,0xa6,0x10,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x48,0x88,0x99,0x99,0x99,0x99,0xaa,0xaa,0xaa,0xa9,0x65,0x31,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x13,0x57,0x9b,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xaa,0xaa,0x95,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,
|
||||
0x88,0x88,0x99,0x99,0x99,0x99,0xaa,0xa9,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0xab,0xba,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,
|
||||
0x93,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x16,0x88,
|
||||
0x88,0x88,0x99,0x99,0x99,0x99,0x95,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x6a,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,
|
||||
0x99,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x78,0x88,
|
||||
0x88,0x88,0x99,0x99,0x99,0x62,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0xaa,0xaa,0xaa,0xaa,0xaa,
|
||||
0x99,0x99,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x78,0x88,
|
||||
0x88,0x88,0x99,0x98,0x51,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0xaa,0xaa,0xaa,0xaa,
|
||||
0x99,0x99,0x97,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x16,0x77,0x78,0x88,
|
||||
0x88,0x88,0x98,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xaa,0xaa,0xaa,
|
||||
0x99,0x99,0x99,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x77,0x77,0x78,0x88,
|
||||
0x88,0x88,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xaa,0xaa,
|
||||
0x99,0x99,0x99,0x99,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x78,0x88,
|
||||
0x88,0x61,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0xaa,
|
||||
0x99,0x99,0x99,0x99,0x95,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x77,0x77,0x77,0x78,0x88,
|
||||
0x83,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,
|
||||
0x99,0x99,0x99,0x99,0x99,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x77,0x77,0x78,0x86,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
|
||||
0x79,0x99,0x99,0x99,0x99,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x67,0x77,0x77,0x77,0x77,0x30,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x04,0x99,0x99,0x99,0x99,0x99,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x67,0x77,0x77,0x77,0x61,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x28,0x99,0x99,0x99,0x99,0x97,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x66,0x67,0x77,0x77,0x76,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x79,0x99,0x99,0x99,0x98,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x66,0x67,0x77,0x77,0x50,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x06,0x99,0x99,0x99,0x98,0x85,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x66,0x66,0x67,0x77,0x74,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x59,0x99,0x99,0x98,0x88,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x66,0x66,0x67,0x77,0x30,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x04,0x99,0x99,0x98,0x88,0x83,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x56,0x66,0x66,0x67,0x73,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x49,0x99,0x98,0x88,0x88,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x66,0x66,0x66,0x67,0x30,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x05,0x99,0x98,0x88,0x88,0x70,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x66,0x66,0x66,0x64,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x59,0x98,0x88,0x88,0x86,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x02,0x56,0x66,0x66,0x66,0x50,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x06,0x98,0x88,0x88,0x88,0x30,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x05,0x56,0x66,0x66,0x65,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x88,0x88,0x88,0x71,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x45,0x56,0x66,0x66,0x61,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x88,0x88,0x88,0x86,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x02,0x55,0x56,0x66,0x66,0x20,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x88,0x88,0x88,0x88,0x30,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x05,0x55,0x56,0x66,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0x88,0x88,0x88,0x70,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x35,0x55,0x56,0x66,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x88,0x88,0x88,0x84,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x55,0x55,0x56,0x66,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x88,0x88,0x88,0x87,0x10,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x03,0x55,0x55,0x56,0x64,0x00,0x00,0x00,0x00,0x05,0xaa,0xaa,0xa9,
|
||||
0x99,0x98,0x88,0x88,0x77,0x77,0x66,0x66,0x65,0x55,0x55,0x30,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x35,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x29,0x98,0x10,0x00,0x00,0x68,0x88,0x88,0x88,0x50,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x15,0x55,0x55,0x56,0x50,0x00,0x00,0x00,0x00,0x5e,0xee,0xdd,0xdc,
|
||||
0xcc,0xcb,0xbb,0xaa,0xaa,0x99,0x98,0x88,0x77,0x77,0x66,0x63,0x00,0x00,0x00,0x00,
|
||||
0x00,0x03,0x77,0x77,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x02,0xbc,0xcc,0xb1,0x00,0x00,0x18,0x88,0x88,0x88,0x81,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x35,0x55,0x55,0x56,0x30,0x00,0x00,0x00,0x06,0xee,0xed,0xdd,0xdc,
|
||||
0xcc,0xbb,0xba,0xaa,0xa9,0x99,0x88,0x88,0x77,0x76,0x66,0x66,0x10,0x00,0x00,0x00,
|
||||
0x00,0x07,0x77,0x77,0x75,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x3b,0xcc,0xcc,0xd8,0x00,0x00,0x04,0x88,0x88,0x88,0x85,0x00,0x00,0x00,
|
||||
0x00,0x00,0x01,0x55,0x55,0x55,0x55,0x00,0x00,0x00,0x00,0x6e,0xee,0xdd,0xdd,0xcc,
|
||||
0xcb,0xbb,0xba,0xaa,0x99,0x98,0x88,0x87,0x77,0x66,0x66,0x66,0x10,0x00,0x00,0x00,
|
||||
0x00,0x07,0x77,0x77,0x78,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x03,0xbc,0xcc,0xcc,0xd8,0x00,0x00,0x00,0x88,0x88,0x88,0x87,0x10,0x00,0x00,
|
||||
0x00,0x00,0x03,0x55,0x55,0x55,0x53,0x00,0x00,0x00,0x05,0xee,0xee,0xdd,0xdc,0xcc,
|
||||
0xbb,0xbb,0xaa,0xa9,0x99,0x98,0x88,0x77,0x76,0x66,0x66,0x66,0x10,0x00,0x00,0x00,
|
||||
0x00,0x07,0x77,0x77,0x78,0x85,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x2b,0xcc,0xcc,0xcd,0xd8,0x00,0x00,0x00,0x48,0x88,0x88,0x87,0x40,0x00,0x00,
|
||||
0x00,0x00,0x04,0x55,0x55,0x55,0x50,0x00,0x00,0x00,0x6e,0xee,0xed,0xdd,0xcc,0xcc,
|
||||
0xbb,0xba,0xaa,0x99,0x99,0x88,0x87,0x77,0x76,0x66,0x66,0x62,0x00,0x00,0x00,0x00,
|
||||
0x00,0x02,0x77,0x77,0x88,0x88,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x03,0xbc,0xcc,0xcc,0xdd,0xa1,0x00,0x00,0x00,0x07,0x88,0x88,0x87,0x70,0x00,0x00,
|
||||
0x00,0x00,0x25,0x55,0x55,0x55,0x20,0x00,0x00,0x06,0xfe,0xee,0xdd,0xdc,0xa7,0x77,
|
||||
0x77,0x66,0x66,0x65,0x55,0x55,0x54,0x44,0x44,0x33,0x44,0x20,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x27,0x78,0x88,0x88,0x86,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x3b,0xcc,0xcc,0xcd,0xda,0x00,0x00,0x00,0x00,0x04,0x88,0x88,0x87,0x73,0x00,0x00,
|
||||
0x00,0x00,0x45,0x55,0x55,0x55,0x00,0x00,0x00,0x5e,0xee,0xed,0xdd,0xc9,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x03,0x78,0x88,0x88,0x88,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,
|
||||
0xbc,0xcc,0xcc,0xdd,0xb1,0x00,0x00,0x00,0x00,0x00,0x88,0x88,0x87,0x76,0x00,0x00,
|
||||
0x00,0x01,0x45,0x55,0x55,0x53,0x00,0x00,0x05,0xee,0xee,0xdd,0xdd,0x80,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x28,0x88,0x88,0x88,0x86,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3b,
|
||||
0xcc,0xcc,0xcd,0xda,0x10,0x00,0x00,0x00,0x00,0x00,0x58,0x88,0x87,0x77,0x10,0x00,
|
||||
0x00,0x02,0x45,0x55,0x55,0x51,0x00,0x00,0x5e,0xee,0xed,0xdd,0xd8,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x99,0x60,0x00,0x00,0x00,0x00,0x00,0x03,0xbc,
|
||||
0xcc,0xcc,0xcd,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x88,0x87,0x77,0x40,0x00,
|
||||
0x00,0x04,0x45,0x55,0x55,0x50,0x00,0x04,0xee,0xee,0xed,0xdd,0x90,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x38,0x88,0x88,0x99,0x96,0x00,0x00,0x00,0x00,0x00,0x2b,0xcc,
|
||||
0xcc,0xcc,0xdb,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x88,0x87,0x77,0x70,0x00,
|
||||
0x00,0x14,0x45,0x55,0x55,0x20,0x00,0x08,0xee,0xee,0xdd,0xd8,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x03,0x88,0x89,0x99,0x99,0x60,0x00,0x00,0x00,0x02,0xbc,0xcc,
|
||||
0xcc,0xcd,0xa1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x88,0x87,0x77,0x71,0x00,
|
||||
0x00,0x24,0x45,0x55,0x55,0x00,0x00,0x08,0xee,0xed,0xdd,0xc8,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x38,0x99,0x99,0x9a,0xa7,0x00,0x00,0x00,0x2b,0xcc,0xcc,
|
||||
0xcc,0xda,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x88,0x87,0x77,0x73,0x00,
|
||||
0x00,0x34,0x45,0x55,0x54,0x00,0x00,0x05,0xee,0xdd,0xdc,0xcc,0x80,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x03,0x99,0x99,0xaa,0xaa,0x70,0x00,0x02,0xbb,0xcc,0xcc,
|
||||
0xcd,0xb1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x87,0x77,0x75,0x00,
|
||||
0x00,0x44,0x45,0x55,0x52,0x00,0x00,0x00,0x5d,0xdd,0xdc,0xcc,0xb7,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x39,0x99,0xaa,0xaa,0xa7,0x00,0x2b,0xbc,0xcc,0xcc,
|
||||
0xda,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x87,0x77,0x77,0x00,
|
||||
0x01,0x44,0x45,0x55,0x51,0x00,0x00,0x00,0x05,0xdd,0xcc,0xcb,0xbb,0x70,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x9a,0xaa,0xaa,0xab,0x82,0xbb,0xcc,0xcc,0xcc,
|
||||
0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x87,0x77,0x77,0x10,
|
||||
0x02,0x44,0x45,0x55,0x50,0x00,0x00,0x00,0x00,0x5c,0xcc,0xbb,0xbb,0xa7,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3a,0xaa,0xaa,0xbb,0xbb,0xbc,0xcc,0xcc,0xcb,
|
||||
0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x87,0x77,0x77,0x30,
|
||||
0x03,0x44,0x45,0x55,0x40,0x00,0x00,0x00,0x00,0x05,0xcc,0xbb,0xba,0xaa,0x75,0x55,
|
||||
0x55,0x44,0x44,0x43,0x33,0x33,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xaa,0xab,0xbb,0xbb,0xcc,0xcc,0xcc,0xa1,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x87,0x77,0x77,0x50,
|
||||
0x04,0x44,0x45,0x55,0x30,0x00,0x00,0x00,0x00,0x00,0x5b,0xbb,0xaa,0xaa,0x99,0x98,
|
||||
0x88,0x77,0x77,0x66,0x66,0x66,0x66,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4a,0xbb,0xbb,0xbc,0xcc,0xcc,0xca,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x87,0x77,0x77,0x60,
|
||||
0x04,0x44,0x45,0x55,0x20,0x00,0x00,0x00,0x00,0x00,0x04,0xba,0xaa,0xa9,0x99,0x88,
|
||||
0x87,0x77,0x76,0x66,0x66,0x66,0x66,0x65,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xbb,0xbb,0xcc,0xcc,0xcc,0xb1,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x87,0x77,0x77,0x70,
|
||||
0x14,0x44,0x45,0x55,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x4a,0xaa,0x99,0x98,0x88,
|
||||
0x77,0x77,0x66,0x66,0x66,0x66,0x66,0x66,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6b,0xbb,0xcc,0xcc,0xcc,0x10,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x87,0x77,0x77,0x71,
|
||||
0x14,0x44,0x45,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xa9,0x99,0x88,0x88,
|
||||
0x77,0x76,0x66,0x66,0x66,0x66,0x66,0x67,0x75,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0xab,0xbc,0xcc,0xcc,0xcd,0x90,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x87,0x77,0x77,0x72,
|
||||
0x24,0x44,0x45,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x39,0x98,0x88,0x87,
|
||||
0x77,0x66,0x66,0x66,0x66,0x66,0x66,0x77,0x77,0x60,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3a,0xbb,0xcc,0xcc,0xcc,0xdd,0xd9,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x77,0x77,0x77,0x73,
|
||||
0x24,0x44,0x45,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x76,0x66,0x65,
|
||||
0x55,0x55,0x44,0x55,0x55,0x56,0x67,0x77,0x77,0x76,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xbb,0xbc,0xcc,0xcc,0xcd,0xdd,0xdd,0xa0,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0x77,0x77,0x74,
|
||||
0x34,0x44,0x45,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x02,0x67,0x77,0x77,0x77,0x60,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2a,0xbb,0xcc,0xcc,0xcc,0xdd,0xdd,0xde,0xea,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x77,0x74,
|
||||
0x34,0x44,0x45,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x16,0x77,0x77,0x77,0x76,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xab,0xbc,0xcc,0xcc,0xa5,0xcd,0xdd,0xee,0xee,
|
||||
0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x75,
|
||||
0x34,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x77,0x77,0x77,0x77,0x60,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x3b,0xbb,0xbc,0xcc,0xca,0x00,0x4d,0xdd,0xee,0xee,
|
||||
0xe9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x75,
|
||||
0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x77,0x77,0x78,0x86,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x02,0xab,0xbb,0xcc,0xcc,0xb1,0x00,0x05,0xde,0xee,0xee,
|
||||
0xdd,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76,
|
||||
0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x77,0x77,0x88,0x88,0x10,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x3a,0xbb,0xbc,0xcc,0xca,0x10,0x00,0x00,0x4d,0xee,0xed,
|
||||
0xdd,0xd9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76,
|
||||
0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x77,0x88,0x88,0x10,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x03,0xbb,0xbb,0xcc,0xcc,0xa0,0x00,0x00,0x00,0x05,0xee,0xdd,
|
||||
0xdd,0xdd,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76,
|
||||
0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x78,0x88,0x88,0x10,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x2a,0xbb,0xbc,0xcc,0xca,0x10,0x00,0x00,0x00,0x00,0x5d,0xdd,
|
||||
0xdd,0xdd,0xc9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76,
|
||||
0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x88,0x88,0x88,0x10,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x02,0xab,0xbb,0xcc,0xcc,0xa1,0x00,0x00,0x00,0x00,0x00,0x05,0xdd,
|
||||
0xdd,0xdd,0xcc,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76,
|
||||
0x44,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x88,0x88,0x88,0x10,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x2a,0xbb,0xbb,0xcc,0xca,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5d,
|
||||
0xdd,0xdc,0xcc,0xc8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x77,0x77,0x76,
|
||||
0x34,0x44,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x88,0x88,0x88,0x10,0x00,0x00,0x00,
|
||||
0x00,0x00,0x02,0xab,0xbb,0xbc,0xcc,0xa1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,
|
||||
0xdd,0xcc,0xcc,0xcc,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x76,
|
||||
0x34,0x44,0x45,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x10,0x00,0x00,0x00,
|
||||
0x00,0x00,0x2a,0xbb,0xbb,0xcc,0xca,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x5c,0xcc,0xcc,0xcc,0xc8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x75,
|
||||
0x34,0x44,0x45,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x10,0x00,0x00,0x00,
|
||||
0x00,0x02,0xab,0xbb,0xbc,0xcc,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x05,0xcc,0xcc,0xcc,0xbb,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x77,0x75,
|
||||
0x24,0x44,0x45,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x10,0x00,0x00,0x00,
|
||||
0x00,0x0a,0xbb,0xbb,0xcc,0xca,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x5c,0xcc,0xcb,0xbb,0xb6,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0x77,0x77,0x74,
|
||||
0x24,0x44,0x45,0x54,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x89,0x10,0x00,0x00,0x00,
|
||||
0x00,0x0b,0xbb,0xbc,0xcc,0xa1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x05,0xcc,0xbb,0xbb,0xb7,0x00,0x00,0x00,0x00,0x00,0x00,0x77,0x77,0x77,0x73,
|
||||
0x14,0x44,0x45,0x55,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x99,0x10,0x00,0x00,0x00,
|
||||
0x00,0x1b,0xbb,0xbc,0xca,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x4b,0xbb,0xbb,0xb7,0x00,0x00,0x00,0x00,0x00,0x00,0x87,0x77,0x77,0x72,
|
||||
0x14,0x44,0x45,0x55,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x89,0x99,0x10,0x00,0x00,0x00,
|
||||
0x00,0x1b,0xbb,0xcc,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x09,0xbb,0xbb,0xb7,0x00,0x00,0x00,0x00,0x00,0x01,0x87,0x77,0x77,0x71,
|
||||
0x04,0x44,0x45,0x55,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x99,0x99,0x10,0x00,0x00,0x00,
|
||||
0x00,0x1b,0xbc,0xcc,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x09,0xbb,0xbb,0xa7,0x00,0x00,0x00,0x00,0x00,0x02,0x87,0x77,0x77,0x70,
|
||||
0x04,0x44,0x45,0x55,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x99,0x99,0x10,0x00,0x00,0x00,
|
||||
0x00,0x1b,0xcc,0xcc,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x09,0xbb,0xbb,0xa7,0x00,0x00,0x00,0x00,0x00,0x04,0x87,0x77,0x77,0x70,
|
||||
0x03,0x44,0x45,0x55,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x89,0x99,0x99,0x10,0x00,0x00,0x00,
|
||||
0x00,0x1c,0xcc,0xcc,0xc4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x09,0xbb,0xba,0xa7,0x00,0x00,0x00,0x00,0x00,0x06,0x87,0x77,0x77,0x50,
|
||||
0x02,0x44,0x45,0x55,0x50,0x00,0x00,0x00,0x05,0x65,0x20,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x99,0x99,0x9a,0x10,0x00,0x00,0x00,
|
||||
0x00,0x1c,0xcc,0xcc,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xbb,0xaa,0xa7,0x00,0x00,0x00,0x00,0x00,0x07,0x87,0x77,0x77,0x30,
|
||||
0x01,0x44,0x45,0x55,0x51,0x00,0x00,0x00,0x66,0x66,0x62,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x99,0x99,0xaa,0x10,0x00,0x00,0x00,
|
||||
0x00,0x1c,0xcc,0xcd,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xba,0xaa,0xa7,0x00,0x00,0x00,0x00,0x00,0x18,0x87,0x77,0x77,0x20,
|
||||
0x00,0x44,0x45,0x55,0x52,0x00,0x00,0x03,0x66,0x66,0x66,0x20,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x99,0x99,0xaa,0x10,0x00,0x00,0x00,
|
||||
0x00,0x1c,0xcc,0xdd,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x00,0x48,0x87,0x77,0x77,0x00,
|
||||
0x00,0x34,0x45,0x55,0x54,0x00,0x00,0x03,0x66,0x66,0x66,0x62,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x99,0x9a,0xaa,0x20,0x00,0x00,0x00,
|
||||
0x00,0x1c,0xcc,0xdd,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x00,0x68,0x87,0x77,0x76,0x00,
|
||||
0x00,0x24,0x45,0x55,0x55,0x00,0x00,0x03,0x66,0x66,0x66,0x66,0x20,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x99,0x99,0xaa,0xaa,0x10,0x00,0x00,0x00,
|
||||
0x00,0x1c,0xcd,0xdd,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x01,0x88,0x87,0x77,0x74,0x00,
|
||||
0x00,0x14,0x45,0x55,0x55,0x20,0x00,0x00,0x46,0x66,0x66,0x66,0x72,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x99,0x9a,0xaa,0xa3,0x00,0x00,0x00,0x00,
|
||||
0x00,0x1c,0xdd,0xdd,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x04,0x88,0x87,0x77,0x72,0x00,
|
||||
0x00,0x04,0x45,0x55,0x55,0x40,0x00,0x00,0x04,0x66,0x66,0x67,0x77,0x20,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x99,0x99,0xaa,0xaa,0x30,0x00,0x00,0x00,0x00,
|
||||
0x00,0x1d,0xdd,0xdd,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x07,0x88,0x87,0x77,0x70,0x00,
|
||||
0x00,0x03,0x45,0x55,0x55,0x51,0x00,0x00,0x00,0x46,0x66,0x77,0x77,0x73,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x99,0x9a,0xaa,0xa4,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x1d,0xdd,0xdd,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x18,0x88,0x87,0x77,0x50,0x00,
|
||||
0x00,0x01,0x45,0x55,0x55,0x53,0x00,0x00,0x00,0x04,0x66,0x77,0x77,0x77,0x32,0x22,
|
||||
0x22,0x22,0x22,0x22,0x22,0x26,0x99,0x99,0x9a,0xaa,0x30,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x1d,0xdd,0xde,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x48,0x88,0x87,0x77,0x20,0x00,
|
||||
0x00,0x00,0x45,0x55,0x55,0x55,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x77,0x77,0x77,
|
||||
0x88,0x88,0x88,0x88,0x88,0x99,0x99,0x99,0xaa,0xa3,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x1d,0xdd,0xee,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x00,0x78,0x88,0x87,0x76,0x00,0x00,
|
||||
0x00,0x00,0x25,0x55,0x55,0x55,0x20,0x00,0x00,0x00,0x05,0x77,0x77,0x77,0x77,0x78,
|
||||
0x88,0x88,0x88,0x88,0x88,0x99,0x99,0x9a,0xaa,0x40,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x1d,0xde,0xee,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x03,0x88,0x88,0x87,0x73,0x00,0x00,
|
||||
0x00,0x00,0x04,0x55,0x55,0x55,0x50,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x77,0x78,
|
||||
0x88,0x88,0x88,0x88,0x89,0x99,0x99,0xaa,0xa3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x1d,0xee,0xee,0xe4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x07,0x88,0x88,0x87,0x70,0x00,0x00,
|
||||
0x00,0x00,0x03,0x55,0x55,0x55,0x52,0x00,0x00,0x00,0x00,0x05,0x77,0x77,0x77,0x88,
|
||||
0x88,0x88,0x88,0x88,0x99,0x99,0x9a,0xaa,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x1e,0xee,0xee,0xd4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x08,0xaa,0xaa,0xa6,0x00,0x00,0x00,0x38,0x88,0x88,0x87,0x50,0x00,0x00,
|
||||
0x00,0x00,0x01,0x55,0x55,0x55,0x55,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x78,0x88,
|
||||
0x88,0x88,0x88,0x89,0x99,0x99,0xaa,0xa4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x0b,0xee,0xee,0xc2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x05,0xaa,0xaa,0xa4,0x00,0x00,0x00,0x78,0x88,0x88,0x87,0x20,0x00,0x00,
|
||||
0x00,0x00,0x00,0x45,0x55,0x55,0x56,0x20,0x00,0x00,0x00,0x00,0x05,0x77,0x88,0x88,
|
||||
0x88,0x88,0x88,0x99,0x99,0x99,0xaa,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0xae,0xec,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x5a,0xaa,0x30,0x00,0x00,0x04,0x88,0x88,0x88,0x86,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x15,0x55,0x55,0x56,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x88,0x88,0x88,0x82,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x04,0x55,0x55,0x56,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x88,0x88,0x88,0x60,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x01,0x55,0x55,0x56,0x66,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x88,0x10,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x35,0x55,0x56,0x66,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x88,0x88,0x88,0x85,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x05,0x55,0x56,0x66,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x58,0x88,0x88,0x88,0x71,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x02,0x55,0x56,0x66,0x66,0x20,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x88,0x88,0x88,0x88,0x40,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x45,0x56,0x66,0x66,0x60,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x88,0x88,0x88,0x87,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x15,0x56,0x66,0x66,0x65,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x88,0x88,0x88,0x81,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x03,0x56,0x66,0x66,0x66,0x40,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x05,0x98,0x88,0x88,0x88,0x40,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x66,0x66,0x66,0x63,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x49,0x98,0x88,0x88,0x87,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x66,0x66,0x66,0x67,0x30,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x03,0x99,0x98,0x88,0x88,0x71,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x66,0x66,0x66,0x67,0x72,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x39,0x99,0x98,0x88,0x88,0x20,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x66,0x66,0x67,0x77,0x20,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x03,0x99,0x99,0x98,0x88,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x66,0x66,0x67,0x77,0x73,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x49,0x99,0x99,0x98,0x88,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x46,0x66,0x67,0x77,0x77,0x40,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x04,0x99,0x99,0x99,0x98,0x86,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x66,0x67,0x77,0x77,0x75,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x69,0x99,0x99,0x99,0x98,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x56,0x67,0x77,0x77,0x77,0x60,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x17,0x99,0x99,0x99,0x99,0x97,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x67,0x77,0x77,0x77,0x77,0x20,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x03,0x99,0x99,0x99,0x99,0x99,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x77,0x77,0x78,0x85,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x59,0x99,0x99,0x99,0x99,0x97,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x77,0x77,0x77,0x78,0x88,
|
||||
0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,
|
||||
0x99,0x99,0x99,0x99,0x99,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x77,0x77,0x78,0x88,
|
||||
0x88,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0xaa,
|
||||
0x99,0x99,0x99,0x99,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x77,0x77,0x78,0x88,
|
||||
0x88,0x88,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x9a,0xaa,
|
||||
0x99,0x99,0x99,0x99,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x77,0x78,0x88,
|
||||
0x88,0x88,0x98,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x9a,0xaa,0xaa,
|
||||
0x99,0x99,0x99,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x67,0x78,0x88,
|
||||
0x88,0x88,0x99,0x98,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x9a,0xaa,0xaa,0xaa,
|
||||
0x99,0x99,0x98,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x78,0x88,
|
||||
0x88,0x88,0x99,0x99,0x98,0x51,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x9a,0xaa,0xaa,0xaa,0xaa,
|
||||
0x99,0x99,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x88,
|
||||
0x88,0x88,0x99,0x99,0x99,0x99,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,
|
||||
0x99,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,
|
||||
0x88,0x88,0x99,0x99,0x99,0x99,0xaa,0xa7,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x8b,0xba,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,
|
||||
0x95,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
|
||||
0x58,0x88,0x99,0x99,0x99,0x99,0xaa,0xaa,0xaa,0x97,0x53,0x10,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x01,0x35,0x8a,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xaa,0xaa,0xa7,
|
||||
0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x01,0x68,0x99,0x99,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0x98,0x76,0x54,0x44,
|
||||
0x44,0x45,0x67,0x8a,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xaa,0xa8,0x20,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x02,0x79,0x99,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0xaa,0xa8,0x30,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x02,0x69,0x99,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0xaa,0xa7,0x30,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x01,0x58,0x99,0xaa,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0xaa,0x95,0x10,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x36,0x9a,0xaa,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0xbb,0xba,0x73,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x68,0xaa,0xaa,0xbb,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0xbb,0xbb,0x96,0x30,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x68,0xab,0xbb,0xbb,0xbb,0xcc,0xcc,
|
||||
0xcc,0xcc,0xcc,0xcb,0xbb,0xbb,0x96,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x34,0x67,0x89,0xab,0xbb,
|
||||
0xbb,0xba,0x98,0x76,0x53,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
};
|
||||
49
include/memlayout.h
Normal file
49
include/memlayout.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
// Memory layout
|
||||
|
||||
#define EXTMEM 0x100000 // Start of extended memory
|
||||
// Top of usable physical memory. A *variable*, read from the firmware's E820
|
||||
// map at boot -- see kernel/arch/x86_64/e820.c. It used to be the constant
|
||||
// below, which was not a limit on what the kernel used but an assertion that
|
||||
// exactly 224 MB existed: less and the allocator walked off the end of RAM,
|
||||
// more and it was invisible.
|
||||
//
|
||||
// The macro spelling is kept so that the twenty-odd call sites read the same as
|
||||
// they always did. It must not be used before e820_init(), which runs first
|
||||
// thing in main() for that reason.
|
||||
#ifndef __ASSEMBLER__
|
||||
extern uint64 phystop;
|
||||
#define PHYSTOP phystop
|
||||
#endif
|
||||
|
||||
// What to assume when the firmware gives us nothing usable: the old constant,
|
||||
// so that path behaves exactly as the kernel always did.
|
||||
#define PHYSTOP_FALLBACK 0xE000000ULL
|
||||
|
||||
// kvmalloc() maps physical 0-4 GB as four 1 GB pages and marks the top one
|
||||
// uncached, because that is where PCI BARs and the framebuffer sit. 3 GB is
|
||||
// therefore the most that can be treated as RAM.
|
||||
#define PHYSTOP_MAX 0xC0000000ULL
|
||||
|
||||
// Below this the kernel image, its page tables and the DMA arena do not fit.
|
||||
#define PHYSTOP_MIN (24ULL * 1024 * 1024)
|
||||
|
||||
// Key addresses for address space layout
|
||||
#define KERNBASE 0xFFFF800000000000 // First kernel virtual address
|
||||
#define KERNLINK (KERNBASE+EXTMEM) // Address where kernel is linked
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
static inline addr_t v2p(void *a) {
|
||||
return ((addr_t)(a)) - ((addr_t)KERNBASE);
|
||||
}
|
||||
static inline void *p2v(addr_t a) {
|
||||
return (void *)((a) + ((addr_t)KERNBASE));
|
||||
}
|
||||
#endif
|
||||
|
||||
#define V2P(a) (((addr_t) (a)) - KERNBASE)
|
||||
#define P2V(a) (((void *) (a)) + KERNBASE)
|
||||
|
||||
#define V2P_WO(x) ((x) - KERNBASE) // same as V2P, but without casts
|
||||
#define P2V_WO(x) ((x) + KERNBASE) // same as P2V, but without casts
|
||||
130
include/mmu.h
Normal file
130
include/mmu.h
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#pragma once
|
||||
|
||||
// Eflags register
|
||||
#define FL_CF 0x00000001 // Carry Flag
|
||||
#define FL_PF 0x00000004 // Parity Flag
|
||||
#define FL_AF 0x00000010 // Auxiliary carry Flag
|
||||
#define FL_ZF 0x00000040 // Zero Flag
|
||||
#define FL_SF 0x00000080 // Sign Flag
|
||||
#define FL_TF 0x00000100 // Trap Flag
|
||||
#define FL_IF 0x00000200 // Interrupt Enable
|
||||
#define FL_DF 0x00000400 // Direction Flag
|
||||
#define FL_OF 0x00000800 // Overflow Flag
|
||||
#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask
|
||||
#define FL_NT 0x00004000 // Nested Task
|
||||
#define FL_RF 0x00010000 // Resume Flag
|
||||
#define FL_VM 0x00020000 // Virtual 8086 mode
|
||||
#define FL_AC 0x00040000 // Alignment Check
|
||||
#define FL_VIF 0x00080000 // Virtual Interrupt Flag
|
||||
#define FL_VIP 0x00100000 // Virtual Interrupt Pending
|
||||
#define FL_ID 0x00200000 // ID flag
|
||||
|
||||
// Control Register flags
|
||||
#define CR0_PE 0x00000001 // Protection Enable
|
||||
#define CR0_MP 0x00000002 // Monitor coProcessor
|
||||
#define CR0_EM 0x00000004 // Emulation
|
||||
#define CR0_TS 0x00000008 // Task Switched
|
||||
#define CR0_ET 0x00000010 // Extension Type
|
||||
#define CR0_NE 0x00000020 // Numeric Errror
|
||||
#define CR0_WP 0x00010000 // Write Protect
|
||||
#define CR0_AM 0x00040000 // Alignment Mask
|
||||
#define CR0_NW 0x20000000 // Not Writethrough
|
||||
#define CR0_CD 0x40000000 // Cache Disable
|
||||
#define CR0_PG 0x80000000 // Paging
|
||||
|
||||
#define CR4_PSE 0x00000010 // Page size extension
|
||||
#define CR4_PAE 0x00000020 // Physical address extensions
|
||||
#define CR4_OSXFSR 0x00000200 // OS supports FXSAVE and FXRSTOR
|
||||
#define CR4_OSXMMEXCPT 0x00000400 // OS supports SSE exceptions
|
||||
|
||||
// Model specific registers
|
||||
#define MSR_EFER 0xC0000080 // Extended Feature Enable Register
|
||||
#define MSR_STAR 0xC0000081 // Legacy Mode SYSCALL Target
|
||||
#define MSR_LSTAR 0xC0000082 // Long Mode SYSCALL Target
|
||||
#define MSR_CSTAR 0xC0000083 // Compatibility Mode SYSCALL Target
|
||||
#define MSR_SFMASK 0xC0000084 // SYSCALL Flag Mask
|
||||
|
||||
// Segment selectors
|
||||
#define SEG_KCODE 1
|
||||
#define SEG_KDATA 2
|
||||
#define SEG_UCODE32 3
|
||||
#define SEG_UDATA 4
|
||||
#define SEG_UCODE 5
|
||||
#define SEG_KCPU 6
|
||||
#define SEG_TSS 7 // TSS takes 2 slots in 64-bit GDT
|
||||
|
||||
#define NSEGS 9
|
||||
|
||||
#define DPL_USER 0x3
|
||||
|
||||
#define USER_CS ((SEG_UCODE<<3)|DPL_USER)
|
||||
#define USER_DS ((SEG_UDATA<<3)|DPL_USER)
|
||||
#define KERNEL_CS (SEG_KCODE<<3)
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
// Segment Descriptor
|
||||
struct segdesc {
|
||||
uint32 lim_15_0 : 16; // Low bits of segment limit
|
||||
uint32 base_15_0 : 16; // Low bits of segment base address
|
||||
uint32 base_23_16 : 8; // Middle bits of segment base address
|
||||
uint32 type : 4; // Segment type
|
||||
uint32 s : 1; // 0 = system, 1 = application
|
||||
uint32 dpl : 2; // Descriptor Privilege Level
|
||||
uint32 p : 1; // Present
|
||||
uint32 lim_19_16 : 4; // High bits of segment limit
|
||||
uint32 avl : 1; // Available for software use
|
||||
uint32 l : 1; // 1 = 64-bit segment (Long mode)
|
||||
uint32 db : 1; // 0 = 16-bit, 1 = 32-bit
|
||||
uint32 g : 1; // Granularity
|
||||
uint32 base_31_24 : 8; // High bits of segment base address
|
||||
};
|
||||
|
||||
// 64-bit System Segment Descriptor (e.g., TSS)
|
||||
struct desclong {
|
||||
struct segdesc sd;
|
||||
uint32 base_63_32; // High bits of 64-bit base address
|
||||
uint32 res; // Reserved
|
||||
};
|
||||
|
||||
#define SEG(type, base, lim, dpl) (struct segdesc) \
|
||||
{ ((lim) >> 12) & 0xffff, (uint32)(base) & 0xffff, \
|
||||
((uint32)(base) >> 16) & 0xff, type, 1, dpl, 1, \
|
||||
(uint32)(lim) >> 28, 0, 1, 0, 1, (uint32)(base) >> 24 }
|
||||
|
||||
#endif
|
||||
|
||||
// Application segment type bits
|
||||
#define STA_X 0x8 // Executable segment
|
||||
#define STA_W 0x2 // Writeable (non-executable)
|
||||
#define STA_R 0x2 // Readable (executable)
|
||||
|
||||
// System segment type bits
|
||||
#define STS_T64A 0x9 // Available 64-bit TSS
|
||||
#define STS_T64B 0xB // Busy 64-bit TSS
|
||||
#define STS_IG64 0xE // 64-bit Interrupt Gate
|
||||
#define STS_TG64 0xF // 64-bit Trap Gate
|
||||
|
||||
// Page table structure: 4 levels
|
||||
#define PMX(va) (((addr_t)(va) >> 39) & 0x1FF)
|
||||
#define PDPX(va) (((addr_t)(va) >> 30) & 0x1FF)
|
||||
#define PDX(va) (((addr_t)(va) >> 21) & 0x1FF)
|
||||
#define PTX(va) (((addr_t)(va) >> 12) & 0x1FF)
|
||||
|
||||
#define PGSIZE 4096
|
||||
#define PXMASK 0x1FF
|
||||
|
||||
#define PGROUNDUP(sz) (((sz)+((addr_t)PGSIZE-1)) & ~((addr_t)(PGSIZE-1)))
|
||||
#define PGROUNDDOWN(a) (((a)) & ~((addr_t)(PGSIZE-1)))
|
||||
|
||||
// Page table/directory entry flags
|
||||
#define PTE_P 0x001 // Present
|
||||
#define PTE_W 0x002 // Writeable
|
||||
#define PTE_U 0x004 // User
|
||||
#define PTE_PWT 0x008 // Write-Through
|
||||
#define PTE_PCD 0x010 // Cache-Disable
|
||||
#define PTE_A 0x020 // Accessed
|
||||
#define PTE_D 0x040 // Dirty
|
||||
#define PTE_PS 0x080 // Page Size (Huge Page)
|
||||
|
||||
#define PTE_ADDR(pte) ((addr_t)(pte) & 0x000FFFFFFFFFF000ull)
|
||||
#define PTE_FLAGS(pte) ((addr_t)(pte) & 0xFFF)
|
||||
54
include/mp.h
Normal file
54
include/mp.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// See MultiProcessor Specification Version 1.[14]
|
||||
|
||||
struct mp { // floating pointer
|
||||
uchar signature[4]; // "_MP_"
|
||||
uint32 physaddr; // 32-bit phys addr of MP config table
|
||||
uchar length; // 1
|
||||
uchar specrev; // [14]
|
||||
uchar checksum; // all bytes must add up to 0
|
||||
uchar type; // MP system config type
|
||||
uchar imcrp;
|
||||
uchar reserved[3];
|
||||
};
|
||||
|
||||
struct mpconf { // configuration table header
|
||||
uchar signature[4]; // "PCMP"
|
||||
ushort length; // total table length
|
||||
uchar version; // [14]
|
||||
uchar checksum; // all bytes must add up to 0
|
||||
uchar product[20]; // product id
|
||||
uint32 oemtable_p; // OEM table pointer
|
||||
ushort oemlength; // OEM table length
|
||||
ushort entry; // entry count
|
||||
uint32 lapicaddr_p; // address of local APIC
|
||||
ushort xlength; // extended table length
|
||||
uchar xchecksum; // extended table checksum
|
||||
uchar reserved;
|
||||
};
|
||||
|
||||
struct mpproc { // processor table entry
|
||||
uchar type; // entry type (0)
|
||||
uchar apicid; // local APIC id
|
||||
uchar version; // local APIC verison
|
||||
uchar flags; // CPU flags
|
||||
#define MPBOOT 0x02 // This proc is the bootstrap processor.
|
||||
uchar signature[4]; // CPU signature
|
||||
uint feature; // feature flags from CPUID instruction
|
||||
uchar reserved[8];
|
||||
};
|
||||
|
||||
struct mpioapic { // I/O APIC table entry
|
||||
uchar type; // entry type (2)
|
||||
uchar apicno; // I/O APIC id
|
||||
uchar version; // I/O APIC version
|
||||
uchar flags; // I/O APIC flags
|
||||
uint32 addr_p; // I/O APIC address
|
||||
};
|
||||
|
||||
// Table entry types
|
||||
#define MPPROC 0x00 // One per processor
|
||||
#define MPBUS 0x01 // One per bus
|
||||
#define MPIOAPIC 0x02 // One per I/O APIC
|
||||
#define MPIOINTR 0x03 // One per bus interrupt source
|
||||
#define MPLINTR 0x04 // One per system interrupt source
|
||||
|
||||
28
include/param.h
Normal file
28
include/param.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#define NPROC 128 // maximum number of processes
|
||||
#define KSTACKSIZE 4096 // size of per-process kernel stack (increased for x64)
|
||||
#define KSTACK_CANARY 0xDEADC0DEDEADC0DEULL // sentinel at kstack[0], see kstack_check_canary()
|
||||
#define NCPU 16 // maximum number of CPUs
|
||||
#define NOFILE 128 // open files per process
|
||||
#define NFILE 512 // open files per system
|
||||
#define NINODE 200 // maximum number of active i-nodes
|
||||
#define NDEV 10 // maximum major device number
|
||||
#define RAMDISK_DEV 2 // memory-backed block device (kernel/dev/ramdisk.c)
|
||||
#define USBDISK_DEV 3 // USB mass storage (kernel/drivers/usb_storage.c)
|
||||
|
||||
// Device the root filesystem is mounted from. Building with RAMROOT=1 defines
|
||||
// RAMDISK_ROOT and boots from the SCHFS image linked into the kernel instead
|
||||
// of the IDE disk, leaving the physical disk free to be reformatted from a
|
||||
// running system. See kernel/dev/ramdisk.c.
|
||||
#ifdef RAMDISK_ROOT
|
||||
#define ROOTDEV RAMDISK_DEV
|
||||
#else
|
||||
#define ROOTDEV 1 // device number of file system root disk
|
||||
#endif
|
||||
#define MAXARG 32 // max exec arguments
|
||||
#define MAXPATH 128 // max path
|
||||
#define MAXOPBLOCKS 30 // max # of blocks any FS op writes
|
||||
#define LOGSIZE (MAXOPBLOCKS*3) // max data blocks in on-disk log
|
||||
#define NBUF (MAXOPBLOCKS*3) // size of disk block cache
|
||||
#define FSSIZE 150000 // size of file system in blocks
|
||||
110
include/pci.h
Normal file
110
include/pci.h
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#pragma once
|
||||
|
||||
// PCI bus enumeration and driver binding.
|
||||
//
|
||||
// Until this existed, every driver in Schunix found its hardware by knowing
|
||||
// where it was: IDE at 0x1F0 because IDE has always been at 0x1F0, the
|
||||
// framebuffer wherever the BIOS's VBE call said. That works for exactly the
|
||||
// devices that predate PCI and for nothing else. A NIC, an AHCI controller,
|
||||
// a virtio device or a real GPU has no fixed address at all -- you find it by
|
||||
// asking the bus what is plugged into it, which is what this does.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config space
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define PCI_CONFIG_ADDR 0xCF8
|
||||
#define PCI_CONFIG_DATA 0xCFC
|
||||
|
||||
#define PCI_VENDOR_ID 0x00
|
||||
#define PCI_DEVICE_ID 0x02
|
||||
#define PCI_COMMAND 0x04
|
||||
#define PCI_STATUS 0x06
|
||||
#define PCI_REVISION 0x08
|
||||
#define PCI_PROG_IF 0x09
|
||||
#define PCI_SUBCLASS 0x0A
|
||||
#define PCI_CLASS 0x0B
|
||||
#define PCI_HEADER_TYPE 0x0E
|
||||
#define PCI_BAR0 0x10
|
||||
#define PCI_SECONDARY_BUS 0x19
|
||||
#define PCI_INTERRUPT_LINE 0x3C
|
||||
#define PCI_INTERRUPT_PIN 0x3D
|
||||
|
||||
// PCI_COMMAND bits a driver usually wants set before touching its device.
|
||||
#define PCI_CMD_IO 0x0001
|
||||
#define PCI_CMD_MEMORY 0x0002
|
||||
#define PCI_CMD_BUS_MASTER 0x0004
|
||||
|
||||
// Base classes worth naming. The full list is long and mostly irrelevant here.
|
||||
#define PCI_CLASS_STORAGE 0x01
|
||||
#define PCI_CLASS_NETWORK 0x02
|
||||
#define PCI_CLASS_DISPLAY 0x03
|
||||
#define PCI_CLASS_MULTIMEDIA 0x04
|
||||
#define PCI_CLASS_BRIDGE 0x06
|
||||
#define PCI_CLASS_SERIAL_BUS 0x0C
|
||||
|
||||
#define PCI_STATUS_CAPLIST 0x0010 // device has a capability list
|
||||
#define PCI_CAP_PTR 0x34 // offset of the first capability
|
||||
|
||||
#define PCI_CAP_MSI 0x05
|
||||
#define PCI_CAP_MSIX 0x11
|
||||
#define PCI_CAP_VENDOR 0x09 // virtio's configuration lives here
|
||||
#define PCI_CAP_PCIE 0x10
|
||||
|
||||
#define PCI_ANY 0xFFFF // wildcard in a match table
|
||||
|
||||
#define NPCI 64 // devices we will remember
|
||||
|
||||
struct pci_bar {
|
||||
uint64 base; // already masked; 0 means "no BAR here"
|
||||
uint64 size; // determined by write-ones-and-read-back
|
||||
int is_io; // I/O port range rather than memory
|
||||
int is_64; // consumed the following BAR slot too
|
||||
int prefetch;
|
||||
};
|
||||
|
||||
struct pci_dev {
|
||||
uint8 bus, slot, func;
|
||||
uint16 vendor, device;
|
||||
uint8 class, subclass, prog_if, revision;
|
||||
uint8 irq; // interrupt line, 0xFF when unrouted
|
||||
uint8 header_type;
|
||||
struct pci_bar bar[6];
|
||||
const char *driver; // name of whatever claimed it, or 0
|
||||
};
|
||||
|
||||
// One row of a driver's match table. Terminate the table with a row whose
|
||||
// vendor is 0.
|
||||
struct pci_match {
|
||||
uint16 vendor, device; // PCI_ANY to ignore
|
||||
uint8 class, subclass; // used only when vendor is PCI_ANY
|
||||
};
|
||||
|
||||
// Declared here rather than defs.h because both take a struct pci_dev, which
|
||||
// only this header defines. See kernel/dev/device.c for the model they feed.
|
||||
// device.h is included rather than forward-declaring `enum dev_class`, which
|
||||
// is a compiler extension and warns.
|
||||
#include "device.h"
|
||||
|
||||
enum dev_class pci_class_of(const struct pci_dev *);
|
||||
void pci_describe(const struct pci_dev *, char *out, int cap);
|
||||
|
||||
struct pci_driver {
|
||||
const char *name;
|
||||
// Base for the device's name in /proc/devices, if the driver's own name
|
||||
// makes a bad one. `e1000` plus an index reads as "e10000"; a NIC wants to
|
||||
// be `eth0`. Optional -- null means use `name`.
|
||||
const char *devname;
|
||||
const struct pci_match *match;
|
||||
// Class-specific vtable, published on the device when this driver claims
|
||||
// it -- struct display_ops for a display, and so on. Optional; a driver
|
||||
// with nothing to offer a consumer leaves it null.
|
||||
//
|
||||
// It lives on the driver rather than being set inside probe() because
|
||||
// pci_bind() registers the device *after* probe returns, so a driver has
|
||||
// nothing to attach it to at the point it would want to.
|
||||
const void *ops;
|
||||
// Return 0 to claim the device. A driver that returns non-zero leaves it
|
||||
// unclaimed for anything else that matches.
|
||||
int (*probe)(struct pci_dev *);
|
||||
};
|
||||
289
include/posix.h
Normal file
289
include/posix.h
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Schunix — POSIX-compatibility types used by kernel syscall handlers.
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
#include "date.h"
|
||||
|
||||
struct iovec {
|
||||
void *iov_base;
|
||||
uint64 iov_len;
|
||||
};
|
||||
|
||||
// Linux's IOV_MAX. readv/writev used to cap iovcnt at 16 -- which is legal,
|
||||
// since POSIX only requires 16 -- and returned EINVAL above it. But programs
|
||||
// are written against the platform they run on, and on Linux that limit is
|
||||
// 1024: toybox's `yes` builds a 256-entry iovec and writev()s it in a loop,
|
||||
// so `yes | head -2` failed with "Invalid argument" before producing a single
|
||||
// byte. Nothing here costs per-entry memory; the count is just a loop bound.
|
||||
#define UIO_MAXIOV 1024
|
||||
|
||||
// Simplified termios (matches Linux x86_64 layout enough for basic use).
|
||||
struct termios {
|
||||
uint32 c_iflag;
|
||||
uint32 c_oflag;
|
||||
uint32 c_cflag;
|
||||
uint32 c_lflag;
|
||||
uint8 c_line;
|
||||
uint8 c_cc[19];
|
||||
uint32 c_ispeed;
|
||||
uint32 c_ospeed;
|
||||
};
|
||||
|
||||
// Terminal flags, with Linux's values. The kernel honours the ones marked
|
||||
// below; the rest are accepted and stored so a program that sets them and
|
||||
// reads them back sees what it wrote, which is what cfmakeraw() and every
|
||||
// line-editing library actually check.
|
||||
//
|
||||
// c_iflag
|
||||
#define IGNBRK 0000001
|
||||
#define BRKINT 0000002
|
||||
#define IGNPAR 0000004
|
||||
#define PARMRK 0000010
|
||||
#define INPCK 0000020
|
||||
#define ISTRIP 0000040 // honoured: strip the eighth bit
|
||||
#define INLCR 0000100 // honoured: NL -> CR on input
|
||||
#define IGNCR 0000200 // honoured: drop CR
|
||||
#define ICRNL 0000400 // honoured: CR -> NL on input
|
||||
#define IUCLC 0001000
|
||||
#define IXON 0002000
|
||||
#define IXANY 0004000
|
||||
#define IXOFF 0010000
|
||||
#define IMAXBEL 0020000
|
||||
#define IUTF8 0040000
|
||||
|
||||
// c_oflag
|
||||
#define OPOST 0000001 // honoured: enable output processing
|
||||
#define OLCUC 0000002
|
||||
#define ONLCR 0000004 // honoured: NL -> CR NL on output
|
||||
#define OCRNL 0000010
|
||||
#define ONOCR 0000020
|
||||
#define ONLRET 0000040
|
||||
|
||||
// c_cflag
|
||||
#define CS8 0000060
|
||||
#define CREAD 0000200
|
||||
#define HUPCL 0002000
|
||||
#define CLOCAL 0004000
|
||||
|
||||
// c_lflag
|
||||
#define ISIG 0000001 // honoured: INTR/QUIT/SUSP generate signals
|
||||
#define ICANON 0000002 // honoured: line-at-a-time with editing
|
||||
#define ECHO 0000010 // honoured
|
||||
#define ECHOE 0000020 // honoured: ERASE rubs the character out
|
||||
#define ECHOK 0000040
|
||||
#define ECHONL 0000100 // honoured: echo NL even with ECHO off
|
||||
#define NOFLSH 0000200
|
||||
#define TOSTOP 0000400
|
||||
#define IEXTEN 0100000
|
||||
|
||||
// c_cc indices (Linux order). VMIN/VTIME share slots with VEOF/VEOL, which is
|
||||
// why they are only meaningful in the mode the other pair is not.
|
||||
#define VINTR 0
|
||||
#define VQUIT 1
|
||||
#define VERASE 2
|
||||
#define VKILL 3
|
||||
#define VEOF 4
|
||||
#define VTIME 5
|
||||
#define VMIN 6
|
||||
#define VSWTC 7
|
||||
#define VSTART 8
|
||||
#define VSTOP 9
|
||||
#define VSUSP 10
|
||||
#define VEOL 11
|
||||
#define VREPRINT 12
|
||||
#define VDISCARD 13
|
||||
#define VWERASE 14
|
||||
#define VLNEXT 15
|
||||
#define VEOL2 16
|
||||
|
||||
struct winsize {
|
||||
uint16 ws_row;
|
||||
uint16 ws_col;
|
||||
uint16 ws_xpixel;
|
||||
uint16 ws_ypixel;
|
||||
};
|
||||
|
||||
struct rlimit {
|
||||
uint64 rlim_cur;
|
||||
uint64 rlim_max;
|
||||
};
|
||||
|
||||
// Linux ioctl codes we handle.
|
||||
#define TCGETS 0x5401
|
||||
#define TCSETS 0x5402
|
||||
#define TCSETSW 0x5403
|
||||
#define TCSETSF 0x5404
|
||||
#define TIOCGPGRP 0x540F
|
||||
#define TIOCSPGRP 0x5410
|
||||
#define TIOCGWINSZ 0x5413
|
||||
#define TIOCSWINSZ 0x5414
|
||||
#define TIOCSCTTY 0x540E
|
||||
#define TIOCGSERIAL 0x541E
|
||||
#define FIONREAD 0x541B
|
||||
#define TIOCEXCL 0x540C
|
||||
#define TIOCNXCL 0x540D
|
||||
|
||||
// arch_prctl commands
|
||||
#define ARCH_SET_GS 0x1001
|
||||
#define ARCH_SET_FS 0x1002
|
||||
#define ARCH_GET_FS 0x1003
|
||||
#define ARCH_GET_GS 0x1004
|
||||
|
||||
// clone flags (subset)
|
||||
#define CLONE_VM 0x00000100
|
||||
#define CLONE_THREAD 0x00010000
|
||||
#define CLONE_SIGHAND 0x00000800
|
||||
|
||||
// futex ops
|
||||
#define FUTEX_WAIT 0
|
||||
#define FUTEX_WAKE 1
|
||||
#define FUTEX_WAIT_PRIVATE 128
|
||||
#define FUTEX_WAKE_PRIVATE 129
|
||||
|
||||
// Resource limit codes (POSIX)
|
||||
#define RLIMIT_AS 9
|
||||
#define RLIMIT_STACK 3
|
||||
#define RLIMIT_NOFILE 7
|
||||
|
||||
// clock IDs
|
||||
#define CLOCK_REALTIME 0
|
||||
#define CLOCK_MONOTONIC 1
|
||||
|
||||
struct timespec {
|
||||
long tv_sec;
|
||||
long tv_nsec;
|
||||
};
|
||||
|
||||
struct itimerval {
|
||||
struct timeval it_interval;
|
||||
struct timeval it_value;
|
||||
};
|
||||
|
||||
struct rusage {
|
||||
struct timeval ru_utime;
|
||||
struct timeval ru_stime;
|
||||
long ru_maxrss;
|
||||
long ru_ixrss;
|
||||
long ru_idrss;
|
||||
long ru_isrss;
|
||||
long ru_minflt;
|
||||
long ru_majflt;
|
||||
long ru_nswap;
|
||||
long ru_inblock;
|
||||
long ru_oublock;
|
||||
long ru_msgsnd;
|
||||
long ru_msgrcv;
|
||||
long ru_nsignals;
|
||||
long ru_nvcsw;
|
||||
long ru_nivcsw;
|
||||
};
|
||||
|
||||
struct tms {
|
||||
long tms_utime;
|
||||
long tms_stime;
|
||||
long tms_cutime;
|
||||
long tms_cstime;
|
||||
};
|
||||
|
||||
struct sysinfo {
|
||||
long uptime;
|
||||
uint64 loads[3];
|
||||
uint64 totalram;
|
||||
uint64 freeram;
|
||||
uint64 sharedram;
|
||||
uint64 bufferram;
|
||||
uint64 totalswap;
|
||||
uint64 freeswap;
|
||||
uint16 procs;
|
||||
uint16 pad;
|
||||
uint32 pad2;
|
||||
uint64 totalhigh;
|
||||
uint64 freehigh;
|
||||
uint32 mem_unit;
|
||||
char _f[8];
|
||||
};
|
||||
|
||||
struct statfs {
|
||||
int64 f_type;
|
||||
int64 f_bsize;
|
||||
int64 f_blocks;
|
||||
int64 f_bfree;
|
||||
int64 f_bavail;
|
||||
int64 f_files;
|
||||
int64 f_ffree;
|
||||
int64 f_fsid[2];
|
||||
int64 f_namelen;
|
||||
int64 f_frsize;
|
||||
int64 f_flags;
|
||||
int64 f_spare[4];
|
||||
};
|
||||
|
||||
// at-syscall dirfd sentinel
|
||||
#define AT_FDCWD (-100)
|
||||
#define AT_REMOVEDIR 0x200
|
||||
#define AT_SYMLINK_NOFOLLOW 0x100
|
||||
#define AT_EMPTY_PATH 0x1000
|
||||
#define AT_NO_AUTOMOUNT 0x800
|
||||
|
||||
// prctl options
|
||||
#define PR_SET_NAME 15
|
||||
#define PR_GET_NAME 16
|
||||
#define PR_SET_DUMPABLE 4
|
||||
#define PR_GET_DUMPABLE 3
|
||||
|
||||
// signal numbers (Linux x86_64 values), used by kill dispatch and
|
||||
// default-action classification in sigsend()
|
||||
#define SIGHUP 1
|
||||
#define SIGINT 2
|
||||
#define SIGQUIT 3
|
||||
#define SIGILL 4
|
||||
#define SIGABRT 6
|
||||
#define SIGBUS 7
|
||||
#define SIGFPE 8
|
||||
#define SIGKILL 9
|
||||
#define SIGUSR1 10
|
||||
#define SIGSEGV 11
|
||||
#define SIGUSR2 12
|
||||
#define SIGPIPE 13
|
||||
#define SIGALRM 14
|
||||
#define SIGTERM 15
|
||||
#define SIGCHLD 17
|
||||
#define SIGCONT 18
|
||||
#define SIGSTOP 19
|
||||
// Job-control and the remaining standard signals. A shell needs the TSTP/
|
||||
// TTIN/TTOU trio to implement ^Z and background-job terminal access at all;
|
||||
// they were simply absent, so any reference to them failed to compile.
|
||||
#define SIGTRAP 5
|
||||
#define SIGTSTP 20
|
||||
#define SIGTTIN 21
|
||||
#define SIGTTOU 22
|
||||
#define SIGURG 23
|
||||
#define SIGXCPU 24
|
||||
#define SIGXFSZ 25
|
||||
#define SIGVTALRM 26
|
||||
#define SIGPROF 27
|
||||
#define SIGWINCH 28
|
||||
#define SIGIO 29
|
||||
#define SIGPWR 30
|
||||
#define SIGSYS 31
|
||||
|
||||
// getrusage who
|
||||
#define RUSAGE_SELF 0
|
||||
#define RUSAGE_CHILDREN (-1)
|
||||
|
||||
// fallocate mode
|
||||
#define FALLOC_FL_KEEP_SIZE 1
|
||||
|
||||
// poll(2) event bits (Linux values)
|
||||
#define POLLIN 0x001
|
||||
#define POLLPRI 0x002
|
||||
#define POLLOUT 0x004
|
||||
#define POLLERR 0x008
|
||||
#define POLLHUP 0x010
|
||||
#define POLLNVAL 0x020
|
||||
|
||||
struct pollfd {
|
||||
int fd;
|
||||
short events;
|
||||
short revents;
|
||||
};
|
||||
240
include/proc.h
Normal file
240
include/proc.h
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "traps.h"
|
||||
|
||||
#define NVMA 64
|
||||
|
||||
struct vma {
|
||||
uint64 addr;
|
||||
uint64 length;
|
||||
int used;
|
||||
// The pages under this mapping are not ours to free on unmap: either they
|
||||
// are device memory (the framebuffer) or they belong to something with a
|
||||
// longer life than this process (a shared memory segment). This was
|
||||
// `is_fb`, which was the only such case until shared memory existed --
|
||||
// and freeing another process's shared pages on exit is exactly the kind
|
||||
// of bug that shows up somewhere else entirely, hours later.
|
||||
int no_free;
|
||||
int shmid; // shared segment this came from, or -1
|
||||
};
|
||||
|
||||
// Per-CPU state
|
||||
struct cpu {
|
||||
uchar id;
|
||||
uchar apicid; // Local APIC ID
|
||||
struct context *scheduler; // swtch() here to enter scheduler
|
||||
volatile uint64 started; // Has the CPU started?
|
||||
int ncli; // Depth of pushcli nesting.
|
||||
int intena; // Were interrupts enabled before pushcli?
|
||||
void *local; // CPU-local storage (GS base)
|
||||
};
|
||||
|
||||
extern struct cpu cpus[NCPU];
|
||||
extern int ncpu; // processors present
|
||||
extern int ncpu_online; // processors actually running
|
||||
|
||||
// --- per-CPU variables ------------------------------------------------------
|
||||
//
|
||||
// These were `__thread struct cpu *cpu` and `__thread struct proc *proc`, and
|
||||
// that was wrong in a way that only appears once a second processor runs.
|
||||
//
|
||||
// `__thread` means *per thread*, and the compiler is entitled to assume a
|
||||
// thread's pointer never changes: it reads %fs:0 once and keeps the value in a
|
||||
// register or spilled on the stack across calls. clang did exactly that in
|
||||
// sleep():
|
||||
//
|
||||
// movq %fs:0x0, %rax # read the thread pointer
|
||||
// movq %rax, -0x18(%rbp) # spill it
|
||||
// callq sched # <-- the CPU can change here
|
||||
// movq -0x18(%rbp), %rax # reload the OLD pointer
|
||||
// movq (%rax,%rcx), %rax # proc, from the previous CPU's block
|
||||
// movq $0x0, 0x40(%rax) # NULL, because that CPU set proc = 0
|
||||
//
|
||||
// But %fs here is *per CPU*, not per thread, and a kernel thread changes CPU at
|
||||
// every sched(). So the assumption is false for exactly the variables that
|
||||
// most need to be right, and the failure is a null dereference deep in the
|
||||
// scheduler on a machine with more than one core.
|
||||
//
|
||||
// The fix is to read the value at each use, with no cached base: an explicit
|
||||
// %gs-relative access that the compiler cannot hoist across a call. `volatile`
|
||||
// is what forbids that, and the memory clobber on the setters keeps stores in
|
||||
// order with the rest of the scheduler's bookkeeping.
|
||||
//
|
||||
// Layout of the per-CPU block, whose base is in both FS_BASE and GS_BASE, set
|
||||
// by seginit() in kernel/arch/x86_64/vm.c:
|
||||
//
|
||||
// +0 self-pointer required by the x86-64 TLS ABI (%fs:0)
|
||||
// +8 syscall scratch trapasm.S parks %rsp here across the stack switch
|
||||
// +16 struct cpu *
|
||||
// +24 struct proc *
|
||||
//
|
||||
// Offset 8 is NOT free. syscall_entry uses %fs:(8) as its scratch slot, and
|
||||
// because seginit() sets GS base and FS base to the same address, %fs:8 and
|
||||
// %gs:8 are the same memory. Putting `cpu` there made every syscall overwrite
|
||||
// it with %rsp -- a #GP in syscall_entry itself, several layers below anything
|
||||
// that could report it.
|
||||
//
|
||||
// trapasm.S reads %gs:24 for proc on the way out to user mode, so these three
|
||||
// offsets are shared with assembly and must not be reordered.
|
||||
#define PERCPU_SELF 0
|
||||
#define PERCPU_SCRATCH 8
|
||||
#define PERCPU_CPU 16
|
||||
#define PERCPU_PROC 24
|
||||
|
||||
static inline struct cpu *
|
||||
cpu_get(void)
|
||||
{
|
||||
struct cpu *c;
|
||||
asm volatile("movq %%gs:16, %0" : "=r" (c));
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline void
|
||||
cpu_set(struct cpu *c)
|
||||
{
|
||||
asm volatile("movq %0, %%gs:16" :: "r" (c) : "memory");
|
||||
}
|
||||
|
||||
static inline struct proc *
|
||||
proc_get(void)
|
||||
{
|
||||
struct proc *p;
|
||||
asm volatile("movq %%gs:24, %0" : "=r" (p));
|
||||
return p;
|
||||
}
|
||||
|
||||
static inline void
|
||||
proc_set(struct proc *p)
|
||||
{
|
||||
asm volatile("movq %0, %%gs:24" :: "r" (p) : "memory");
|
||||
}
|
||||
|
||||
// Named mycpu()/myproc(), as xv6-riscv does, and *not* spelled `cpu`/`proc`.
|
||||
// A macro by those names would also rewrite every `struct cpu` and
|
||||
// `struct proc`, because macros have no namespace and tags do -- which is
|
||||
// exactly why the variables could carry those names in the first place.
|
||||
#define mycpu() cpu_get()
|
||||
#define myproc() proc_get()
|
||||
|
||||
// Saved registers for kernel context switches (x86_64 ABI)
|
||||
struct context {
|
||||
uint64 r15;
|
||||
uint64 r14;
|
||||
uint64 r13;
|
||||
uint64 r12;
|
||||
uint64 rbx;
|
||||
uint64 rbp;
|
||||
uint64 rip;
|
||||
};
|
||||
|
||||
enum procstate { UNUSED, EMBRYO, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };
|
||||
|
||||
// Per-process state
|
||||
// A copy of the interesting fields of one process, taken under ptable.lock.
|
||||
// procfs reads these rather than struct proc, so nothing outside proc.c needs
|
||||
// the lock or the layout.
|
||||
struct procinfo {
|
||||
int pid, ppid, pgid, sid;
|
||||
uint uid;
|
||||
int state; // enum procstate
|
||||
uint64 sz;
|
||||
int killed;
|
||||
char name[16];
|
||||
char exe[64]; // full path, for /proc/<pid>/exe
|
||||
};
|
||||
|
||||
struct proc {
|
||||
uint64 sz; // Size of process memory (bytes) [+0x00]
|
||||
pml4e_t* pgdir; // Page table (PML4 on x86_64) [+0x08]
|
||||
char *kstack; // Bottom of kernel stack [+0x10]
|
||||
uint64 fs_base; // User TLS base (IA32_FS_BASE MSR) [+0x18] ← trapasm.S depends on this offset
|
||||
enum procstate state; // Process state [+0x20]
|
||||
int pid; // [+0x24]
|
||||
struct proc *parent; // [+0x28]
|
||||
struct trapframe *tf; // [+0x30]
|
||||
struct context *context; // [+0x38]
|
||||
void *chan; // [+0x40]
|
||||
int killed; // [+0x48]
|
||||
int _pad; // [+0x4c]
|
||||
struct file *ofile[NOFILE]; // Open files [+0x50]
|
||||
struct inode *cwd; // follows
|
||||
char name[16]; //
|
||||
// The path exec() was given. `name` is only the last component and only 15
|
||||
// characters of it, which is what /proc/<pid>/stat wants; this is the whole
|
||||
// path, reported by /proc/<pid>/exe.
|
||||
//
|
||||
// Note it is exposed as a regular *file*, not the symlink Linux uses, so
|
||||
// `cat /proc/1/exe` works but readlink() does not. That is why `ps -o comm`
|
||||
// still prints "[init]": toybox readlink()s exe and brackets the name when
|
||||
// that fails. Default ps and top output is unaffected. Fixing it properly
|
||||
// means symlink support in procfs, which is worth doing with the rest of the
|
||||
// VFS work rather than on its own.
|
||||
char exe[64];
|
||||
uint64 signal_mask;
|
||||
uint64 pending_signals;
|
||||
int alarm_ticks;
|
||||
char ofile_flags[NOFILE];
|
||||
|
||||
// Real signal delivery (added for SIGCHLD — see kernel/sys/syscall.c's
|
||||
// deliver_signals() and kernel/proc/proc.c's exit()). sig_handler[sig]/
|
||||
// sig_restorer[sig] come straight from the user's struct sigaction
|
||||
// (sys_rt_sigaction); 0 means no handler installed. saved_sigtf stashes
|
||||
// the pre-signal trapframe so sys_rt_sigreturn() can restore it; in_signal
|
||||
// prevents re-entering a handler while one is already running (we don't
|
||||
// support nested signal delivery).
|
||||
uint64 sig_handler[32];
|
||||
uint64 sig_restorer[32];
|
||||
struct trapframe saved_sigtf;
|
||||
int in_signal;
|
||||
|
||||
// rt_sigsuspend() support: the pre-suspend mask to restore once the
|
||||
// handler that woke us finishes (sigsuspend swaps the mask atomically
|
||||
// for the duration of the wait -- POSIX). sigsusp_active is set while a
|
||||
// restore is owed; sys_rt_sigreturn() performs it.
|
||||
uint64 sigsusp_oldmask;
|
||||
int sigsusp_active;
|
||||
|
||||
struct vma vma[NVMA]; // VMA tracking for mmap
|
||||
uint64 mmap_next; // next free mmap VA (bump allocator, see sys_mmap)
|
||||
|
||||
// ABI translation (Linux compat syscall number map)
|
||||
int *syscall_map;
|
||||
int map_size;
|
||||
|
||||
// Current working directory path; maintained by sys_chdir / sys_fchdir.
|
||||
char cwd_path[256];
|
||||
|
||||
// --- POSIX process identity -------------------------------------------
|
||||
// Appended at the end of the struct deliberately: trapasm.S hard-codes the
|
||||
// offsets of the fields at the top (see the _Static_assert on fs_base in
|
||||
// kernel/proc/proc.c), so nothing may be inserted above them.
|
||||
|
||||
// Exit status, already encoded the way wait(2) reports it -- see
|
||||
// W_EXITCODE() below. Read by waitpid() before the slot is recycled.
|
||||
int xstate;
|
||||
|
||||
// Signal that killed us, if any. Set by sigsend() alongside ->killed so the
|
||||
// eventual exit() can report *which* signal, instead of a blanket SIGKILL.
|
||||
int killsig;
|
||||
|
||||
// Process group and session. Job control is defined entirely in terms of
|
||||
// these: a shell puts each pipeline in its own process group and signals the
|
||||
// group, not individual pids. Without them a shell's wait loop cannot work,
|
||||
// which is why mksh's job control never made progress.
|
||||
int pgid;
|
||||
int sid;
|
||||
|
||||
// Real/effective/saved user and group ids. Nothing enforces permissions with
|
||||
// them yet (the on-disk format has no owner field until SchFS), but they
|
||||
// are tracked honestly so getuid()/setuid() stop lying to userland.
|
||||
uint uid, euid, suid;
|
||||
uint gid, egid, sgid;
|
||||
};
|
||||
#define FD_CLOEXEC 1
|
||||
|
||||
// wait(2) status encoding, as understood by musl's WIFEXITED/WEXITSTATUS/
|
||||
// WIFSIGNALED/WTERMSIG macros:
|
||||
// normal exit -> (code & 0xff) << 8
|
||||
// killed by signal -> sig & 0x7f
|
||||
#define W_EXITCODE(code, sig) ((((code) & 0xff) << 8) | ((sig) & 0x7f))
|
||||
42
include/qr.h
Normal file
42
include/qr.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
// A QR encoder, for getting a crash report off a machine whose network stack is
|
||||
// dead.
|
||||
//
|
||||
// A panic report is worth nothing if it cannot leave the machine, and at panic
|
||||
// time there is no network, no filesystem write that can be trusted, and often
|
||||
// no second machine on the desk. A phone camera is a working out-of-band
|
||||
// channel that needs no drivers on our side at all. See docs/roadmap.md Phase 8.
|
||||
//
|
||||
// Deliberately small, in three ways:
|
||||
//
|
||||
// - Byte mode only. The report is arbitrary bytes; alphanumeric mode would
|
||||
// pack tighter but only for a restricted character set.
|
||||
// - Error correction level L only. The screen is a clean, evenly lit,
|
||||
// high-contrast source -- the failure mode QR's stronger levels protect
|
||||
// against (dirt, print damage, curved packaging) does not apply.
|
||||
// - Versions 1 to 6 only. Version 7 is where QR starts requiring an extra
|
||||
// 18-bit version-information block in two more places on the symbol, and
|
||||
// version 10 is where the character count field grows and the alignment
|
||||
// pattern grid becomes 2-dimensional. Stopping at 6 avoids both, and
|
||||
// 134 bytes is a large crash report.
|
||||
//
|
||||
// If a payload ever needs more than 134 bytes, the honest fix is to shorten the
|
||||
// report -- not to reach for version 10 and its two extra tables.
|
||||
|
||||
#define QR_MAX_VERSION 6
|
||||
#define QR_MAX_SIZE (17 + 4 * QR_MAX_VERSION) // 41 modules per side
|
||||
#define QR_MAX_BYTES 134
|
||||
|
||||
struct qr {
|
||||
int size; // modules per side; 0 if unencoded
|
||||
unsigned char mod[QR_MAX_SIZE][QR_MAX_SIZE]; // 1 = dark
|
||||
unsigned char fn[QR_MAX_SIZE][QR_MAX_SIZE]; // 1 = function pattern
|
||||
};
|
||||
|
||||
// Encode len bytes into q. Picks the smallest version that fits.
|
||||
// Returns 0, or -1 if len > QR_MAX_BYTES.
|
||||
int qr_encode(struct qr *q, const unsigned char *data, int len);
|
||||
|
||||
// Largest payload, in bytes, for a given version (1..QR_MAX_VERSION).
|
||||
int qr_capacity(int version);
|
||||
246
include/schfs.h
Normal file
246
include/schfs.h
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
// SchFS — on-disk format.
|
||||
//
|
||||
// Design rationale lives in docs/architecture/schfs.md. In short: copy-on-write
|
||||
// with the checksum of a block stored in the pointer that leads to it (ZFS),
|
||||
// one B-tree reused for every kind of record (btrfs), and attribute indexes as
|
||||
// just another record type (BeFS).
|
||||
//
|
||||
// This header is shared by the kernel and by host tools (tools/mkfs,
|
||||
// tools/schfs_test), so it must stay free of kernel-only types.
|
||||
|
||||
#ifndef SCHFS_H
|
||||
#define SCHFS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SCHFS_MAGIC 0x53636846u // "SchF"
|
||||
#define SCHFS_NODE_MAGIC 0x536e6f64u // "Snod"
|
||||
|
||||
// 4096 to match modern media and make extents worth having. The old format
|
||||
// used 1024, which is also why its directories were so wasteful.
|
||||
#define SCHFS_BLKSIZE 4096
|
||||
|
||||
// Deepest tree we will walk without recursing. 8 levels of a ~84-way internal
|
||||
// node is far more than any plausible volume needs.
|
||||
#define SCHFS_MAX_LEVEL 8
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Keys
|
||||
//
|
||||
// Every record in the filesystem is addressed by one 24-byte key, sorted by
|
||||
// (objectid, type, offset). This is the whole reason there is only one tree:
|
||||
// inodes, directory entries, extent maps and indexes are just different `type`
|
||||
// values in the same keyspace.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define SCHFS_KEY_INODE 1 // (ino, INODE, 0) -> struct schfs_inode
|
||||
#define SCHFS_KEY_DIRENT 2 // (dirino,DIRENT, namehash) -> packed dirent list
|
||||
#define SCHFS_KEY_EXTENT 3 // (ino, EXTENT, fileblk) -> struct schfs_extent
|
||||
#define SCHFS_KEY_ATTR 4 // (ino, ATTR, namehash) -> attribute value
|
||||
#define SCHFS_KEY_INDEX 5 // (idxid, INDEX, valuehash) -> objectid
|
||||
#define SCHFS_KEY_CSUM 6 // (CSUM, CSUM, diskblk) -> uint32 CRC32c
|
||||
|
||||
// Data-block checksums live under one reserved object id, exactly as btrfs
|
||||
// keeps a checksum tree. Tree nodes carry their checksum in their parent
|
||||
// pointer, but file *data* blocks have no parent to hold it — without these
|
||||
// records "end-to-end checksums" would only cover metadata, which is the part
|
||||
// least likely to rot.
|
||||
#define SCHFS_CSUM_OBJECTID 1ULL
|
||||
|
||||
// Object ids below this are reserved; the root directory is the first real one.
|
||||
#define SCHFS_FIRST_OBJECTID 16ULL
|
||||
#define SCHFS_ROOT_OBJECTID 16ULL
|
||||
|
||||
struct schfs_key {
|
||||
uint64_t objectid;
|
||||
uint64_t offset;
|
||||
uint8_t type;
|
||||
uint8_t pad[7];
|
||||
}; /* 24 bytes */
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Node header — identical for internal nodes and leaves.
|
||||
//
|
||||
// `blocknr` is a self-pointer: a block that was written to the wrong place
|
||||
// still checksums correctly, so the only way to catch a misdirected write is
|
||||
// for the block to say where it thinks it lives.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct schfs_node_hdr {
|
||||
uint32_t magic; // SCHFS_NODE_MAGIC
|
||||
uint32_t crc; // CRC32c of the whole block, this field zeroed
|
||||
uint64_t blocknr; // where this block believes it lives
|
||||
uint64_t generation; // transaction that wrote it
|
||||
uint16_t level; // 0 = leaf, >0 = internal
|
||||
uint16_t nritems;
|
||||
uint32_t pad;
|
||||
}; /* 32 bytes */
|
||||
|
||||
// Internal node entry: a key and the child it leads to, plus that child's
|
||||
// checksum and generation. Storing the checksum *here* rather than in the
|
||||
// child is what makes the tree self-validating.
|
||||
struct schfs_kptr {
|
||||
struct schfs_key key;
|
||||
uint64_t blocknr;
|
||||
uint64_t generation;
|
||||
uint32_t crc;
|
||||
uint32_t pad;
|
||||
}; /* 48 bytes */
|
||||
|
||||
// Leaf entry. Items grow forward from the end of the header; their data grows
|
||||
// backward from the end of the block, exactly like btrfs. `offset` is measured
|
||||
// from the start of the block.
|
||||
struct schfs_item {
|
||||
struct schfs_key key;
|
||||
uint32_t offset;
|
||||
uint32_t size;
|
||||
}; /* 32 bytes */
|
||||
|
||||
#define SCHFS_NODE_SPACE (SCHFS_BLKSIZE - (int)sizeof(struct schfs_node_hdr))
|
||||
#define SCHFS_MAX_KPTR (SCHFS_NODE_SPACE / (int)sizeof(struct schfs_kptr))
|
||||
#define SCHFS_MAX_ITEMS (SCHFS_NODE_SPACE / (int)sizeof(struct schfs_item))
|
||||
|
||||
// Largest value that can live inline in a leaf. Anything bigger becomes an
|
||||
// extent record pointing at data blocks.
|
||||
#define SCHFS_MAX_INLINE (SCHFS_NODE_SPACE / 4)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Uberblock
|
||||
//
|
||||
// A ring of these is written round-robin. Mounting means reading them all and
|
||||
// taking the highest generation that checksums — that is the entire crash
|
||||
// recovery story, in place of a journal replay.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define SCHFS_UBER_COUNT 128
|
||||
#define SCHFS_UBER_START 1 // block 0 stays free for a boot sector
|
||||
|
||||
// First block available for the tree and for file data: everything below is
|
||||
// the boot block and the uberblock ring.
|
||||
#define SCHFS_FIRST_BLOCK (SCHFS_UBER_START + SCHFS_UBER_COUNT)
|
||||
|
||||
#define SCHFS_VERSION 1
|
||||
#define SCHFS_LABEL_MAX 32
|
||||
|
||||
struct schfs_uberblock {
|
||||
uint32_t magic; // SCHFS_MAGIC
|
||||
uint32_t crc; // CRC32c of this struct, this field zeroed
|
||||
uint32_t version; // SCHFS_VERSION
|
||||
uint32_t blocksize; // SCHFS_BLKSIZE; a reader must refuse a mismatch
|
||||
uint64_t generation; // transaction number; highest valid one wins
|
||||
uint64_t root; // block number of the tree root
|
||||
uint32_t root_crc; // checksum of that root block
|
||||
uint32_t pad0;
|
||||
uint64_t root_generation;
|
||||
uint64_t total_blocks;
|
||||
// Informational only. Which blocks are actually free is derived from the
|
||||
// tree at mount rather than stored, so this is what the volume believed at
|
||||
// commit time — useful for df before anything is mounted, never trusted.
|
||||
uint64_t free_blocks;
|
||||
uint64_t reserved0;
|
||||
uint64_t next_objectid;
|
||||
uint64_t root_objectid; // normally SCHFS_ROOT_OBJECTID
|
||||
uint8_t uuid[16];
|
||||
char label[SCHFS_LABEL_MAX];
|
||||
uint8_t pad[SCHFS_BLKSIZE - 136];
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Records
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define SCHFS_T_FILE 1
|
||||
#define SCHFS_T_DIR 2
|
||||
#define SCHFS_T_DEV 3
|
||||
#define SCHFS_T_SYMLINK 4
|
||||
|
||||
struct schfs_inode {
|
||||
uint64_t size;
|
||||
uint64_t blocks;
|
||||
uint64_t atime, mtime, ctime;
|
||||
// Containing directory. Meaningful for directories only, where it is what
|
||||
// ".." resolves to — neither "." nor ".." is stored on disk, so a directory
|
||||
// costs nothing until it has real entries. For files with several hard
|
||||
// links "parent" is simply the first directory that linked them and must
|
||||
// not be trusted.
|
||||
uint64_t parent;
|
||||
uint32_t mode; // real POSIX mode bits, at last
|
||||
uint32_t uid, gid;
|
||||
uint32_t nlink;
|
||||
uint16_t type; // SCHFS_T_*
|
||||
uint16_t rdev_major, rdev_minor;
|
||||
uint16_t pad;
|
||||
uint32_t flags; // SCHFS_I_* — none defined yet
|
||||
uint32_t gen; // transaction that last changed this inode;
|
||||
// what an incremental send/snapshot diff needs
|
||||
};
|
||||
|
||||
// An extent maps a run of file blocks onto a run of disk blocks. refs is what
|
||||
// makes snapshots and reflink copies nearly free: sharing a range is a refcount
|
||||
// bump rather than a data copy.
|
||||
struct schfs_extent {
|
||||
uint64_t disk_block;
|
||||
uint64_t nblocks;
|
||||
// Transaction group that allocated this run. A run stamped with the group
|
||||
// currently being built cannot be reached from any committed root, so its
|
||||
// blocks may be overwritten in place — which is what stops a program that
|
||||
// writes a hundred bytes at a time from copying a whole 4 KB block, and
|
||||
// then a whole tree path, on every call.
|
||||
//
|
||||
// A full 64 bits, matching the tree's generation counter, because a 32-bit
|
||||
// one would wrap after four billion commits and a wrapped comparison says
|
||||
// "same group" about a block belonging to a committed tree — the one
|
||||
// mistake this field exists to prevent. Only ever trusted together with
|
||||
// refs == 1; a shared run belongs to somebody else as well.
|
||||
uint64_t gen;
|
||||
uint32_t refs;
|
||||
uint32_t pad;
|
||||
};
|
||||
|
||||
// Directory entry payload. The key's offset field holds the name hash, so a
|
||||
// lookup is one tree descent rather than a directory scan.
|
||||
//
|
||||
// Two names in one directory can hash the same, and the key would then be
|
||||
// identical — the second insert would silently replace the first. So the value
|
||||
// is a *packed list*: one schfs_dirent_rec plus its name bytes, repeated. A
|
||||
// 64-bit hash makes lists longer than one vanishingly rare, but "vanishingly
|
||||
// rare" is not "impossible", and a filesystem that loses a file under a hash
|
||||
// collision is not one to build on.
|
||||
struct schfs_dirent_rec {
|
||||
uint64_t objectid;
|
||||
uint16_t type;
|
||||
uint16_t namelen;
|
||||
uint32_t pad;
|
||||
/* name bytes follow, namelen of them, not NUL-terminated */
|
||||
};
|
||||
|
||||
#define SCHFS_NAME_MAX 255
|
||||
|
||||
// Step from one packed dirent to the next. Records are 8-byte aligned so the
|
||||
// uint64 objectid of the next one is naturally aligned.
|
||||
#define SCHFS_DIRENT_LEN(namelen) \
|
||||
((unsigned)((sizeof(struct schfs_dirent_rec) + (namelen) + 7) & ~7u))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compile-time layout checks.
|
||||
//
|
||||
// Every one of these is a number that ends up baked into images on disk. A
|
||||
// silent change to any of them — adding a field, changing a type, a compiler
|
||||
// padding a struct differently — makes every existing image unreadable in a way
|
||||
// that looks like corruption rather than like a mistake. Failing the build is
|
||||
// enormously cheaper than debugging that.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define SCHFS_ASSERT_SIZE(t, n) \
|
||||
typedef char schfs_sizecheck_##t[(sizeof(struct t) == (n)) ? 1 : -1]
|
||||
|
||||
SCHFS_ASSERT_SIZE(schfs_key, 24);
|
||||
SCHFS_ASSERT_SIZE(schfs_node_hdr, 32);
|
||||
SCHFS_ASSERT_SIZE(schfs_kptr, 48);
|
||||
SCHFS_ASSERT_SIZE(schfs_item, 32);
|
||||
SCHFS_ASSERT_SIZE(schfs_uberblock, SCHFS_BLKSIZE);
|
||||
SCHFS_ASSERT_SIZE(schfs_inode, 80);
|
||||
SCHFS_ASSERT_SIZE(schfs_extent, 32);
|
||||
SCHFS_ASSERT_SIZE(schfs_dirent_rec, 16);
|
||||
|
||||
#endif /* SCHFS_H */
|
||||
9
include/sleeplock.h
Normal file
9
include/sleeplock.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Long-term locks for processes
|
||||
struct sleeplock {
|
||||
uint locked; // Is the lock held?
|
||||
struct spinlock lk; // spinlock protecting this sleep lock
|
||||
|
||||
// For debugging:
|
||||
char *name; // Name of lock.
|
||||
int pid; // Process holding lock
|
||||
};
|
||||
81
include/socket.h
Normal file
81
include/socket.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
|
||||
// Unix domain sockets.
|
||||
//
|
||||
// These exist for Schcom. A compositor and its clients have to exchange
|
||||
// messages, and a pipe cannot do it: a pipe is one-directional, has no
|
||||
// notion of a connection, and gives no way for a server to be *found* by a
|
||||
// client that starts later. A socket with a name in the filesystem is
|
||||
// exactly the missing piece.
|
||||
//
|
||||
// AF_INET is deliberately absent. The socket layer is being built around the
|
||||
// local case first because that is the one the GUI needs and the one whose
|
||||
// semantics are simplest; a network stack plugs in underneath the same
|
||||
// syscalls later rather than reshaping them.
|
||||
|
||||
#define AF_UNIX 1
|
||||
#define AF_LOCAL 1
|
||||
#define AF_INET 2 // recognised so it can be refused honestly
|
||||
|
||||
#define SOCK_STREAM 1
|
||||
#define SOCK_DGRAM 2
|
||||
|
||||
#define UNIX_PATH_MAX 108
|
||||
|
||||
#define SHUT_RD 0
|
||||
#define SHUT_WR 1
|
||||
#define SHUT_RDWR 2
|
||||
|
||||
struct sockaddr_un {
|
||||
uint16 sun_family;
|
||||
char sun_path[UNIX_PATH_MAX];
|
||||
};
|
||||
|
||||
// The generic form syscalls take. Same shape as Linux's, so a musl-linked
|
||||
// program's struct lines up byte for byte.
|
||||
struct sockaddr {
|
||||
uint16 sa_family;
|
||||
char sa_data[UNIX_PATH_MAX];
|
||||
};
|
||||
|
||||
#ifdef _KERNEL_SOCKET_INTERNALS
|
||||
|
||||
#define NSOCK 32 // sockets in the system
|
||||
#define SOCKBUF 4096 // receive buffer per connected socket
|
||||
#define NBACKLOG 8
|
||||
|
||||
enum sockstate {
|
||||
SS_FREE = 0,
|
||||
SS_UNBOUND,
|
||||
SS_BOUND,
|
||||
SS_LISTENING,
|
||||
SS_CONNECTED,
|
||||
};
|
||||
|
||||
// Every field here is protected by the socket table's single lock in
|
||||
// kernel/net/unix.c -- there is deliberately no per-socket lock; see the
|
||||
// comment at the top of that file for why.
|
||||
struct socket {
|
||||
int ref;
|
||||
int type;
|
||||
int state;
|
||||
|
||||
char name[UNIX_PATH_MAX]; // the bound path, for a listener
|
||||
|
||||
// A connected socket's peer. Writing to a socket appends to the *peer's*
|
||||
// buffer, which is what makes one buffer per socket enough for a
|
||||
// bidirectional connection.
|
||||
struct socket *peer;
|
||||
int peer_closed;
|
||||
|
||||
char data[SOCKBUF];
|
||||
uint nread, nwrite;
|
||||
|
||||
// Connections waiting to be accepted, on a listener.
|
||||
struct socket *queue[NBACKLOG];
|
||||
int nqueue;
|
||||
};
|
||||
|
||||
#endif
|
||||
9
include/spinlock.h
Normal file
9
include/spinlock.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Mutual exclusion lock.
|
||||
struct spinlock {
|
||||
uint64 locked; // Is the lock held?
|
||||
// For debugging:
|
||||
char *name; // Name of lock.
|
||||
struct cpu *cpu; // The cpu holding the lock.
|
||||
addr_t pcs[10]; // The call stack (an array of program counters)
|
||||
// that locked the lock.
|
||||
};
|
||||
24
include/stat.h
Normal file
24
include/stat.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
#define T_DIR 1 // Directory
|
||||
#define T_FILE 2 // File
|
||||
#define T_DEV 3 // Device
|
||||
#define T_SYMLINK 4 // Divesymlink
|
||||
#define T_EXTENT 5 // bmap & itrunc
|
||||
|
||||
struct stat {
|
||||
int32 type; // File type (T_DIR, T_FILE, or T_DEV)
|
||||
int32 dev; // File system's disk device ID
|
||||
uint64 ino; // Inode number
|
||||
int32 nlink; // Number of hard links to file
|
||||
uint32 pad; // Padding for 64-bit alignment
|
||||
uint64 size; // Size of file in bytes
|
||||
uint rwmode; // Permissions
|
||||
uint supermode; // Mode
|
||||
uint showmode; // Visiblity
|
||||
uint atime; // Last access time
|
||||
uint mtime; // Last modification time
|
||||
uint ctime; // Last metadata-change time
|
||||
uint addrs[12+3]; // Block Addresses
|
||||
};
|
||||
49
include/syscall.h
Normal file
49
include/syscall.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
// Syscall numbers and definition macros — all derived from
|
||||
// kernel/sys/table.inc, which is the single source of truth.
|
||||
//
|
||||
// Do NOT add SYS_* constants by hand, and do NOT add anything to
|
||||
// kernel/sys/linux_abi.c: add one SYSCALL() line to table.inc and write the
|
||||
// body with SYSCALL_DEFINE(). The constant, the dispatch slot and the Linux
|
||||
// ABI mapping are all generated from that one line.
|
||||
|
||||
#define NSYSCALLS 512
|
||||
|
||||
// Scratch slot for syscalls with no Linux equivalent. Every such entry lands
|
||||
// on this one index of the translation map, which is never dispatched (Linux
|
||||
// x86_64 numbers currently top out around 450). Using a real index rather
|
||||
// than a negative sentinel keeps the generated map a plain designated
|
||||
// initialiser instead of needing a second macro.
|
||||
#define NO_LINUX 511
|
||||
|
||||
#ifdef __ASSEMBLER__
|
||||
// In .S files GCC passes through the C preprocessor but the assembler
|
||||
// sees the output, so emit .set directives instead of an enum.
|
||||
#define SYSCALL(nr, lnr, name) .set SYS_##name, nr
|
||||
#define SYSCALL_ALIAS(lnr, name)
|
||||
#include "../kernel/sys/table.inc"
|
||||
#undef SYSCALL
|
||||
#undef SYSCALL_ALIAS
|
||||
#else
|
||||
// In C files generate a proper enum so constants work in switch/designators.
|
||||
enum syscall_nr {
|
||||
_SYS_RESERVED = 0,
|
||||
#define SYSCALL(nr, lnr, name) SYS_##name = (nr),
|
||||
#define SYSCALL_ALIAS(lnr, name)
|
||||
#include "../kernel/sys/table.inc"
|
||||
#undef SYSCALL
|
||||
#undef SYSCALL_ALIAS
|
||||
};
|
||||
|
||||
// Every syscall body is written as:
|
||||
//
|
||||
// SYSCALL_DEFINE(getpid)
|
||||
// {
|
||||
// return myproc()->pid;
|
||||
// }
|
||||
//
|
||||
// so the signature lives in exactly one place and every implementation is
|
||||
// findable with a single grep.
|
||||
#define SYSCALL_DEFINE(name) addr_t sys_##name(void)
|
||||
#endif
|
||||
86
include/text.h
Normal file
86
include/text.h
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#pragma once
|
||||
|
||||
// Text rendering.
|
||||
//
|
||||
// The console draws text by indexing `font_data[256][16]` and stepping the
|
||||
// cursor eight pixels. That is a *character cell grid*, and it is the thing in
|
||||
// the way -- not the font. A grid cannot do variable-width glyphs, cannot
|
||||
// position text anywhere except on a multiple of eight, cannot anti-alias, and
|
||||
// cannot represent a codepoint above 255. Swapping the bitmap for a nicer
|
||||
// bitmap changes none of that.
|
||||
//
|
||||
// So this is a layer, not a font. Three things it fixes:
|
||||
//
|
||||
// *Glyphs have metrics.* A glyph knows its own size, its bearing and how far
|
||||
// to advance afterwards. That is what variable-width text is; everything else
|
||||
// follows from it.
|
||||
//
|
||||
// *It draws into a surface, not into the screen.* A compositor draws into an
|
||||
// off-screen buffer and presents it. Rendering that writes straight to the
|
||||
// VGA aperture is unusable to it, which is why this takes a target.
|
||||
//
|
||||
// *The font is behind an interface.* The bitmap font is one implementation.
|
||||
// A TrueType rasteriser is another, and arrives without any caller changing:
|
||||
// glyphs carry 8-bit coverage, so an anti-aliased outline renderer already
|
||||
// has somewhere to put its output.
|
||||
|
||||
struct font;
|
||||
|
||||
// Somewhere to draw. Deliberately not a `struct device` or a framebuffer: the
|
||||
// same code has to work on the virtio-gpu back buffer, on a window's own
|
||||
// buffer, and on the VGA aperture, and none of those know about each other.
|
||||
struct surface {
|
||||
void *pixels; // 32-bit 0x00RRGGBB, the format everything here uses
|
||||
uint32 stride; // bytes per row, not pixels -- they differ on real hardware
|
||||
int width, height;
|
||||
};
|
||||
|
||||
// One rendered glyph. `cov` is 8-bit coverage, not a bitmap: 0 is background,
|
||||
// 255 is solid, and everything between is an anti-aliased edge. A bitmap font
|
||||
// fills it with 0 and 255 only, and costs nothing for the generality.
|
||||
struct glyph {
|
||||
const unsigned char *cov; // w*h bytes, row-major
|
||||
int w, h; // size of the coverage bitmap
|
||||
int bearing_x, bearing_y; // offset from the pen to the top-left of `cov`
|
||||
int advance; // how far the pen moves afterwards
|
||||
};
|
||||
|
||||
struct font_ops {
|
||||
// Fill `out` for `cp`. Returns 0, or -1 if the font has no such glyph --
|
||||
// callers substitute rather than failing, because a missing glyph must not
|
||||
// stop a line of text.
|
||||
int (*glyph)(struct font *, uint32 cp, struct glyph *out);
|
||||
int (*height)(struct font *); // line height
|
||||
int (*baseline)(struct font *); // pen offset from the top of the line
|
||||
};
|
||||
|
||||
struct font {
|
||||
const struct font_ops *ops;
|
||||
void *priv;
|
||||
};
|
||||
|
||||
// The built-in bitmap font, as a font provider. Always available, needs no
|
||||
// allocation, and is what the console falls back to before any filesystem
|
||||
// exists to load a real typeface from.
|
||||
struct font *font_builtin(void);
|
||||
|
||||
// Draw one UTF-8 string. Returns the x advance.
|
||||
//
|
||||
// `x, y` is the pen position on the *baseline*, which is how every text API
|
||||
// that is not a terminal works -- glyphs of different heights then sit on a
|
||||
// common line instead of being top-aligned.
|
||||
int text_draw(struct surface *, struct font *, int x, int y,
|
||||
const char *utf8, uint32 color);
|
||||
|
||||
// Width the same string would occupy, without drawing it. Needed before you
|
||||
// draw, for centring and for laying out anything at all.
|
||||
int text_measure(struct font *, const char *utf8);
|
||||
|
||||
// One glyph, for callers doing their own layout.
|
||||
int text_draw_glyph(struct surface *, struct font *, int x, int y,
|
||||
uint32 cp, uint32 color);
|
||||
|
||||
// Decode one UTF-8 sequence. Returns the codepoint and advances *p.
|
||||
// Malformed input yields U+FFFD and consumes one byte, so a decoder loop
|
||||
// always terminates.
|
||||
uint32 utf8_next(const char **p);
|
||||
57
include/theme.h
Normal file
57
include/theme.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
// Schunix visual identity — one palette, shared by everything that draws.
|
||||
//
|
||||
// This header exists because there were three unrelated designs and the seams
|
||||
// showed. The bootloader splash was deep purple with gold text, deliberately
|
||||
// matching the *panic* screen; the kernel console was cream with near-black
|
||||
// text; the panic screen was the purple both were copying. So a normal boot
|
||||
// looked like a crash, then abruptly became an amber terminal, and none of the
|
||||
// three looked like the same product.
|
||||
//
|
||||
// The rule now: **normal states are calm and dark; only failure is loud.**
|
||||
// Boot, console and shell share SCH_BG/SCH_FG. The panic screen keeps the
|
||||
// full-bleed purple entirely to itself, which is what makes it mean something.
|
||||
//
|
||||
// Included by boot/stage2main.c (freestanding, pre-kernel), kernel/dev/vga.c,
|
||||
// kernel/core/console.c and kernel/core/panic.c. Plain #defines only — no
|
||||
// types, no code — because stage2 is compiled 32-bit against a different
|
||||
// world than the kernel.
|
||||
//
|
||||
// Colours are 0xRRGGBB. vga_draw_pixel packs them for whatever depth the
|
||||
// framebuffer actually is.
|
||||
|
||||
// --- the product ---------------------------------------------------------
|
||||
// Near-black with a slight blue cast rather than pure #000: pure black on an
|
||||
// LCD reads as "the machine is off", and every serious platform sits a few
|
||||
// points above it.
|
||||
#define SCH_BG 0x0C0E13
|
||||
#define SCH_FG 0xE6E8EC // primary text
|
||||
#define SCH_DIM 0x707784 // secondary text, hints, version strings
|
||||
#define SCH_ACCENT 0x7C5CFF // the one accent: violet, keeping the
|
||||
// project's purple lineage without the
|
||||
// eye-searing chroma of the old splash
|
||||
#define SCH_ACCENT_DIM 0x2A2440 // unused track behind a progress bar
|
||||
#define SCH_OK 0x4ADE80 // reserved: success states
|
||||
#define SCH_WARN 0xFBBF24 // reserved: warnings that are not panics
|
||||
|
||||
// --- failure -------------------------------------------------------------
|
||||
// PSOGM. Deliberately unlike anything above, because its whole job is to be
|
||||
// unmistakable. See kernel/core/panic.c.
|
||||
//
|
||||
// These are the values panic.c actually renders. It used to carry a second,
|
||||
// different set of PSOGM_* defines of its own, so the two disagreed about what
|
||||
// the crash screen looked like and this header was quietly fiction. There was
|
||||
// also a PSOGM_TITLE in gold here, left over from a splash design that no
|
||||
// longer exists -- the screen is one background and two weights of text, on
|
||||
// purpose, because a crash screen with an accent colour is a crash screen
|
||||
// someone was enjoying designing.
|
||||
#define PSOGM_BG 0x4B1D9A // the purple
|
||||
#define PSOGM_FG 0xFFFFFF // the face, the headline, the values
|
||||
#define PSOGM_DIM 0xC3AEEC // labels and the closing lines
|
||||
|
||||
// The one exception to "two weights of text": the QR card has to be a light
|
||||
// panel with dark modules, because that is what a scanner needs. It reads as a
|
||||
// label stuck onto the screen, which is roughly what it is.
|
||||
#define PSOGM_CARD 0xFFFFFF
|
||||
#define PSOGM_INK 0x000000
|
||||
69
include/traps.h
Normal file
69
include/traps.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#pragma once
|
||||
|
||||
// x86 trap and interrupt constants.
|
||||
|
||||
// Processor-defined exceptions:
|
||||
#define T_DIVIDE 0 // divide error
|
||||
#define T_DEBUG 1 // debug exception
|
||||
#define T_NMI 2 // non-maskable interrupt
|
||||
#define T_BRKPT 3 // breakpoint
|
||||
#define T_OFLOW 4 // overflow
|
||||
#define T_BOUND 5 // bounds check
|
||||
#define T_ILLOP 6 // illegal opcode
|
||||
#define T_DEVICE 7 // device not available
|
||||
#define T_DBLFLT 8 // double fault
|
||||
#define T_TSS 10 // invalid task switch segment
|
||||
#define T_SEGNP 11 // segment not present
|
||||
#define T_STACK 12 // stack exception
|
||||
#define T_GPFLT 13 // general protection fault
|
||||
#define T_PGFLT 14 // page fault
|
||||
#define T_FPERR 16 // floating point error
|
||||
#define T_ALIGN 17 // aligment check
|
||||
#define T_MCHK 18 // machine check
|
||||
#define T_SIMDERR 19 // SIMD floating point error
|
||||
|
||||
// IRQ offsets
|
||||
#define T_IRQ0 32 // IRQ 0 corresponds to int T_IRQ
|
||||
|
||||
#define IRQ_TIMER 0
|
||||
#define IRQ_KBD 1
|
||||
#define IRQ_COM1 4
|
||||
#define IRQ_MOUSE 12
|
||||
#define IRQ_IDE 14
|
||||
#define IRQ_ERROR 19
|
||||
#define IRQ_SPURIOUS 31
|
||||
|
||||
// System call interrupt number for legacy int 0x40 or 0x80 if used
|
||||
// although syscall/sysret is preferred for x64
|
||||
#define T_SYSCALL 0x40
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
struct trapframe {
|
||||
// Registers saved by trapasm.S
|
||||
uint64 rax;
|
||||
uint64 rbx;
|
||||
uint64 rcx;
|
||||
uint64 rdx;
|
||||
uint64 rbp;
|
||||
uint64 rsi;
|
||||
uint64 rdi;
|
||||
uint64 r8;
|
||||
uint64 r9;
|
||||
uint64 r10;
|
||||
uint64 r11;
|
||||
uint64 r12;
|
||||
uint64 r13;
|
||||
uint64 r14;
|
||||
uint64 r15;
|
||||
|
||||
uint64 trapno;
|
||||
|
||||
// Error code and info saved by hardware
|
||||
uint64 err;
|
||||
uint64 rip;
|
||||
uint64 cs;
|
||||
uint64 rflags;
|
||||
uint64 rsp;
|
||||
uint64 ss;
|
||||
};
|
||||
#endif
|
||||
49
include/ttf.h
Normal file
49
include/ttf.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
// TrueType outlines, as a font provider for include/text.h.
|
||||
//
|
||||
// Scope, and why it is this and not "TTF and OTF":
|
||||
//
|
||||
// .ttf and .otf are both sfnt containers -- the same table directory, the same
|
||||
// cmap, the same hmtx. What differs is how outlines are stored. TrueType puts
|
||||
// quadratic B-spline contours in a `glyf` table; OpenType/PostScript puts cubic
|
||||
// béziers in a `CFF ` table encoded as Type 2 charstrings, which is a
|
||||
// stack-based interpreted format and a separate project.
|
||||
//
|
||||
// Checked before writing any of it: JetBrains Mono and Roboto -- the two fonts
|
||||
// this system is going to use -- both ship `glyf` and neither ships `CFF `. So
|
||||
// CFF support would buy nothing today, and the honest thing is to implement the
|
||||
// format the fonts actually use and say so, rather than half-implement two.
|
||||
//
|
||||
// Everything here is from Apple's TrueType Reference Manual and the OpenType
|
||||
// specification, both public documents.
|
||||
|
||||
struct ttf {
|
||||
const unsigned char *data;
|
||||
uint32 len;
|
||||
|
||||
uint32 glyf, glyf_len;
|
||||
uint32 loca, loca_len;
|
||||
uint32 cmap4; // offset of the format 4 subtable, 0 if none
|
||||
uint32 hmtx;
|
||||
int num_glyphs;
|
||||
int num_hmetrics;
|
||||
int units_per_em;
|
||||
int long_loca; // indexToLocFormat: 0 = 16-bit offsets, 1 = 32
|
||||
int ascent, descent;
|
||||
|
||||
// Rasterisation target size, in pixels per em. Set by ttf_open.
|
||||
int px;
|
||||
};
|
||||
|
||||
// Parse a font already in memory. Returns 0, or -1 if it is not a TrueType
|
||||
// font this can read. `data` must outlive the struct -- nothing is copied.
|
||||
int ttf_open(struct ttf *, const void *data, uint32 len, int pixels_per_em);
|
||||
|
||||
// Rasterise one codepoint into `cov`. Returns 0, or -1 if the font has no such
|
||||
// glyph or it will not fit. Exposed because the host harness uses it directly.
|
||||
int ttf_render(struct ttf *, uint32 cp, struct glyph *out, unsigned char *cov,
|
||||
int covcap, int *ox, int *oy);
|
||||
|
||||
// A font provider over it, for text.h. The struct must outlive the font.
|
||||
struct font *ttf_font(struct ttf *);
|
||||
79
include/types.h
Normal file
79
include/types.h
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
typedef unsigned int uint;
|
||||
typedef unsigned short ushort;
|
||||
typedef unsigned char uchar;
|
||||
|
||||
typedef unsigned char uint8;
|
||||
typedef unsigned short uint16;
|
||||
typedef unsigned int uint32;
|
||||
|
||||
typedef signed char int8;
|
||||
typedef signed short int16;
|
||||
typedef signed int int32;
|
||||
|
||||
// uint64 has to be 64 bits everywhere this header is used, and `unsigned long`
|
||||
// is not that everywhere. Linux and the x86_64-elf kernel target are LP64, so
|
||||
// long is 64-bit; Windows is LLP64, where long is 32-bit and only long long is
|
||||
// 64-bit.
|
||||
//
|
||||
// This matters far more than a portability nicety. The host tools -- mkfs,
|
||||
// mkschfs, the SchFS suites -- include this header *and* the on-disk structure
|
||||
// definitions in fs.h and schfs.h. Building them on Windows with a 32-bit
|
||||
// uint64 does not fail: it quietly produces images whose inode numbers, sizes
|
||||
// and block pointers are half the width the kernel reads back, which shows up
|
||||
// as a corrupt filesystem long after the tool that wrote it has exited.
|
||||
//
|
||||
// Spelled as long/long long rather than <stdint.h>'s uint64_t so the `%l`
|
||||
// format specifiers used throughout the kernel stay correct and untouched.
|
||||
// Conditioned on the actual width of `long` rather than on the platform,
|
||||
// because there are three cases and only one of them is about Windows:
|
||||
// x86_64-elf / Linux LP64 -> long is 64-bit
|
||||
// Windows LLP64 -> long is 32-bit, long long is 64
|
||||
// i386-elf (boot/) ILP32 -> long is 32-bit, and stage2main.c includes
|
||||
// this header too
|
||||
#if defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ == 8
|
||||
typedef unsigned long uint64;
|
||||
typedef signed long int64;
|
||||
#else
|
||||
typedef unsigned long long uint64;
|
||||
typedef signed long long int64;
|
||||
#endif
|
||||
|
||||
// Belt and braces: if the above is ever wrong on some new host, say so at
|
||||
// compile time rather than in a filesystem image.
|
||||
_Static_assert(sizeof(uint64) == 8, "uint64 must be 64 bits");
|
||||
_Static_assert(sizeof(int64) == 8, "int64 must be 64 bits");
|
||||
|
||||
typedef uint64 uintptr_t;
|
||||
typedef int64 intptr_t;
|
||||
typedef int64 ptrdiff_t;
|
||||
|
||||
typedef uint64 addr_t;
|
||||
typedef uint64 size_t;
|
||||
typedef int64 ssize_t;
|
||||
|
||||
#ifndef MKFS
|
||||
#ifndef _SIGSET_T
|
||||
#define _SIGSET_T
|
||||
typedef uint64 sigset_t;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef addr_t pte_t;
|
||||
typedef addr_t pde_t;
|
||||
typedef addr_t pdpe_t;
|
||||
typedef addr_t pml4e_t;
|
||||
|
||||
struct mouse_data {
|
||||
int x;
|
||||
int y;
|
||||
uint8 buttons;
|
||||
};
|
||||
|
||||
#ifndef NULL
|
||||
#define NULL ((void*)0)
|
||||
#endif
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
49
include/uaccess.h
Normal file
49
include/uaccess.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// User-memory access API, modeled on Linux's <linux/uaccess.h>.
|
||||
//
|
||||
// Rules for kernel code touching user pointers:
|
||||
//
|
||||
// 1. Never dereference a user-supplied address without validating it
|
||||
// first. A syscall argument can point anywhere, including at kernel
|
||||
// memory or at nothing at all.
|
||||
//
|
||||
// 2. Never assume a user buffer is physically contiguous. uva2ka() only
|
||||
// translates ONE page; the direct-map alias it returns runs into an
|
||||
// unrelated physical page at the next 4K boundary. Any multi-byte
|
||||
// access through it must stay within a single page.
|
||||
//
|
||||
// These helpers encapsulate both rules. Because Schunix (like old x86
|
||||
// without SMAP) keeps the user page table active in kernel mode, a raw
|
||||
// user virtual address is directly dereferenceable once the whole range
|
||||
// has been validated — that is what user_ptr() hands back, and why the
|
||||
// result is safe across page boundaries where a uva2ka() pointer is not.
|
||||
#ifndef SCHUNIX_UACCESS_H
|
||||
#define SCHUNIX_UACCESS_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
// Nonzero if [uaddr, uaddr+size) is entirely mapped, user-accessible
|
||||
// memory in the current process. size == 0 is always ok.
|
||||
int access_ok(addr_t uaddr, uint64 size);
|
||||
|
||||
// Validate the whole range and return it as a directly usable pointer
|
||||
// (the raw user VA). 0 on bad/NULL address.
|
||||
void *user_ptr(addr_t uaddr, uint64 size);
|
||||
|
||||
// Copy between kernel and user memory, page by page.
|
||||
// Return 0 on success, -EFAULT on any unmapped page.
|
||||
int copy_from_user(void *dst, addr_t uaddr, uint64 n);
|
||||
int copy_to_user(addr_t uaddr, const void *src, uint64 n);
|
||||
|
||||
// Fixed-size scalar variants, in the spirit of Linux's get_user/put_user.
|
||||
#define get_user(x, uaddr) copy_from_user(&(x), (addr_t)(uaddr), sizeof(x))
|
||||
#define put_user(x, uaddr) copy_to_user((addr_t)(uaddr), &(x), sizeof(x))
|
||||
|
||||
// Longest string fetchstr()/strlen_user() will scan before giving up.
|
||||
#define USER_STR_MAX 32768
|
||||
|
||||
// Length of the NUL-terminated string at uaddr (excluding the NUL),
|
||||
// validating every page it spans. -EFAULT if it runs off mapped memory
|
||||
// or exceeds USER_STR_MAX.
|
||||
long strlen_user(addr_t uaddr);
|
||||
|
||||
#endif // SCHUNIX_UACCESS_H
|
||||
270
include/usb.h
Normal file
270
include/usb.h
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
#pragma once
|
||||
|
||||
#include "device.h"
|
||||
|
||||
// USB, the parts that are the same whatever host controller you have.
|
||||
//
|
||||
// Everything here comes from the USB 2.0 specification chapter 9, which every
|
||||
// USB device implements regardless of speed, class or vendor. That is what
|
||||
// makes enumeration possible at all: before you know anything about a device
|
||||
// you can still ask it what it is, because the question is standardised.
|
||||
|
||||
// bmRequestType
|
||||
#define USB_DIR_OUT 0x00
|
||||
#define USB_DIR_IN 0x80
|
||||
#define USB_TYPE_STANDARD 0x00
|
||||
#define USB_TYPE_CLASS 0x20
|
||||
#define USB_RECIP_DEVICE 0x00
|
||||
#define USB_RECIP_INTERFACE 0x01
|
||||
#define USB_RECIP_ENDPOINT 0x02
|
||||
#define USB_RECIP_OTHER 0x03 // a hub's downstream port
|
||||
|
||||
// bRequest, standard requests
|
||||
#define USB_REQ_GET_STATUS 0
|
||||
#define USB_REQ_CLEAR_FEATURE 1
|
||||
#define USB_REQ_SET_FEATURE 3
|
||||
#define USB_REQ_SET_ADDRESS 5
|
||||
#define USB_REQ_GET_DESCRIPTOR 6
|
||||
#define USB_REQ_SET_DESCRIPTOR 7
|
||||
#define USB_REQ_GET_CONFIG 8
|
||||
#define USB_REQ_SET_CONFIG 9
|
||||
#define USB_REQ_GET_INTERFACE 10
|
||||
#define USB_REQ_SET_INTERFACE 11
|
||||
|
||||
// Descriptor types (the high byte of wValue in a GET_DESCRIPTOR)
|
||||
#define USB_DT_DEVICE 1
|
||||
#define USB_DT_CONFIG 2
|
||||
#define USB_DT_STRING 3
|
||||
#define USB_DT_INTERFACE 4
|
||||
#define USB_DT_ENDPOINT 5
|
||||
|
||||
// Class codes we care about
|
||||
#define USB_CLASS_HID 0x03
|
||||
#define USB_CLASS_MASS_STORAGE 0x08
|
||||
#define USB_CLASS_HUB 0x09
|
||||
|
||||
// HID subclass/protocol. Boot protocol is the one a BIOS uses, and the reason
|
||||
// a USB keyboard works before any operating system has loaded: the report
|
||||
// layout is fixed, so it can be read without parsing a report descriptor.
|
||||
#define USB_HID_SUBCLASS_BOOT 0x01
|
||||
#define USB_HID_PROTO_KEYBOARD 0x01
|
||||
#define USB_HID_PROTO_MOUSE 0x02
|
||||
|
||||
// Bus speeds.
|
||||
//
|
||||
// These are xHCI's numbering, used as the common currency rather than
|
||||
// translated at the boundary. That is a deliberate choice: there is one host
|
||||
// controller, the values have to reach a slot context eventually, and a
|
||||
// second enum whose only job is to be converted into this one would be
|
||||
// ceremony. If a second controller ever appears, this is the thing to
|
||||
// revisit -- but a class driver still has no business including xhci.h.
|
||||
#define USB_SPEED_FULL 1 // 12 Mb/s
|
||||
#define USB_SPEED_LOW 2 // 1.5 Mb/s
|
||||
#define USB_SPEED_HIGH 3 // 480 Mb/s
|
||||
#define USB_SPEED_SUPER 4 // 5 Gb/s
|
||||
|
||||
// An eight-byte SETUP packet, the header of every control transfer.
|
||||
struct usb_setup {
|
||||
uint8 bmRequestType;
|
||||
uint8 bRequest;
|
||||
uint16 wValue;
|
||||
uint16 wIndex;
|
||||
uint16 wLength;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct usb_device_desc {
|
||||
uint8 bLength;
|
||||
uint8 bDescriptorType;
|
||||
uint16 bcdUSB;
|
||||
uint8 bDeviceClass;
|
||||
uint8 bDeviceSubClass;
|
||||
uint8 bDeviceProtocol;
|
||||
uint8 bMaxPacketSize0;
|
||||
uint16 idVendor;
|
||||
uint16 idProduct;
|
||||
uint16 bcdDevice;
|
||||
uint8 iManufacturer;
|
||||
uint8 iProduct;
|
||||
uint8 iSerialNumber;
|
||||
uint8 bNumConfigurations;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct usb_config_desc {
|
||||
uint8 bLength;
|
||||
uint8 bDescriptorType;
|
||||
uint16 wTotalLength;
|
||||
uint8 bNumInterfaces;
|
||||
uint8 bConfigurationValue;
|
||||
uint8 iConfiguration;
|
||||
uint8 bmAttributes;
|
||||
uint8 bMaxPower;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct usb_interface_desc {
|
||||
uint8 bLength;
|
||||
uint8 bDescriptorType;
|
||||
uint8 bInterfaceNumber;
|
||||
uint8 bAlternateSetting;
|
||||
uint8 bNumEndpoints;
|
||||
uint8 bInterfaceClass;
|
||||
uint8 bInterfaceSubClass;
|
||||
uint8 bInterfaceProtocol;
|
||||
uint8 iInterface;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct usb_endpoint_desc {
|
||||
uint8 bLength;
|
||||
uint8 bDescriptorType;
|
||||
uint8 bEndpointAddress; // bit 7: 1 = IN
|
||||
uint8 bmAttributes; // bits 1:0: 0 ctrl, 1 iso, 2 bulk, 3 interrupt
|
||||
uint16 wMaxPacketSize;
|
||||
uint8 bInterval;
|
||||
} __attribute__((packed));
|
||||
|
||||
#define USB_EP_XFERTYPE(a) ((a) & 0x03)
|
||||
#define USB_EP_BULK 2
|
||||
#define USB_EP_INTERRUPT 3
|
||||
#define USB_EP_IS_IN(a) (((a) & 0x80) != 0)
|
||||
#define USB_EP_NUM(a) ((a) & 0x0F)
|
||||
|
||||
// Mass storage: SCSI transparent command set over bulk-only transport.
|
||||
#define USB_MSC_SUBCLASS_SCSI 0x06
|
||||
#define USB_MSC_PROTO_BOT 0x50
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Hubs
|
||||
//
|
||||
// A hub is a USB device like any other, and everything it does is done by
|
||||
// sending it ordinary control requests. There is no separate bus protocol:
|
||||
// "reset port 3" is a SET_FEATURE addressed to the hub.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define USB_DT_HUB 0x29
|
||||
|
||||
// Port features, the wValue of SET_FEATURE / CLEAR_FEATURE on a port.
|
||||
#define PORT_FEAT_ENABLE 1
|
||||
#define PORT_FEAT_RESET 4
|
||||
#define PORT_FEAT_POWER 8
|
||||
#define PORT_FEAT_C_CONNECTION 16
|
||||
#define PORT_FEAT_C_ENABLE 17
|
||||
#define PORT_FEAT_C_RESET 20
|
||||
|
||||
// wPortStatus, the first half of a port's GET_STATUS reply.
|
||||
#define PORTSTAT_CONNECTION (1 << 0)
|
||||
#define PORTSTAT_ENABLE (1 << 1)
|
||||
#define PORTSTAT_RESET (1 << 4)
|
||||
#define PORTSTAT_POWER (1 << 8)
|
||||
#define PORTSTAT_LOWSPEED (1 << 9)
|
||||
#define PORTSTAT_HIGHSPEED (1 << 10)
|
||||
|
||||
struct usb_hub_desc {
|
||||
uint8 bDescLength;
|
||||
uint8 bDescriptorType;
|
||||
uint8 bNbrPorts;
|
||||
uint16 wHubCharacteristics;
|
||||
uint8 bPwrOn2PwrGood; // in 2 ms units
|
||||
uint8 bHubContrCurrent;
|
||||
} __attribute__((packed));
|
||||
|
||||
// Bits 1:0 of wHubCharacteristics: 00 ganged power, 01 per-port.
|
||||
#define HUB_CHAR_TT_MASK 0x0060
|
||||
|
||||
// HID class requests, on the interface rather than the device.
|
||||
#define USB_HID_SET_IDLE 0x0A
|
||||
#define USB_HID_SET_PROTOCOL 0x0B
|
||||
#define USB_HID_PROTOCOL_BOOT 0
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The interface between the USB core and a host controller
|
||||
//
|
||||
// One host controller exists (xHCI) and one probably always will, but the
|
||||
// core has no business knowing about TRBs and slot IDs, and the controller
|
||||
// has no business knowing what a HID report is. This is where the line goes.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct usb_dev;
|
||||
|
||||
struct usb_hc_ops {
|
||||
// A control transfer on endpoint 0. `buf`, if any, must be DMA memory.
|
||||
int (*control)(struct usb_dev *, struct usb_setup *, void *buf, int len);
|
||||
// Make an endpoint usable. Returns 0, or -1 if the controller refused.
|
||||
int (*open_ep)(struct usb_dev *, const struct usb_endpoint_desc *);
|
||||
// Queue one IN transfer. Completion arrives via dev->complete.
|
||||
int (*queue_in)(struct usb_dev *, int ep_addr, void *buf, int len);
|
||||
// One bulk transfer, synchronously. Sleeps, so process context only.
|
||||
// Returns bytes transferred, or -1.
|
||||
int (*bulk)(struct usb_dev *, int ep_addr, void *buf, int len);
|
||||
|
||||
// Bring up a device found on a downstream port of `hub`. The controller
|
||||
// works out the route string from the hub's own position.
|
||||
void (*attach)(struct usb_dev *hub, int hub_port, int speed);
|
||||
|
||||
// Tell the controller this device is a hub with `nports` ports. It has to
|
||||
// know: a hub is scheduled differently from anything else, and devices
|
||||
// behind it are addressed relative to it.
|
||||
int (*set_hub)(struct usb_dev *, int nports, int mtt);
|
||||
};
|
||||
|
||||
#define USB_MAXCONF 256 // bytes of configuration descriptor we keep
|
||||
|
||||
struct usb_dev {
|
||||
const struct usb_hc_ops *ops;
|
||||
void *hc; // the controller's own handle for this device
|
||||
int slot;
|
||||
int speed;
|
||||
int port; // ROOT hub port this whole branch hangs off
|
||||
|
||||
// Where the device sits in the tree.
|
||||
//
|
||||
// A USB device has no address of its own that describes its position -- the
|
||||
// controller finds it by being told the path. `route` is xHCI's route
|
||||
// string: five nibbles, one per tier, each holding the downstream port
|
||||
// number taken at that tier. Tier 0 is a device on a root port, and its
|
||||
// route is 0.
|
||||
uint32 route;
|
||||
int tier;
|
||||
|
||||
// Transaction translator, for a full or low speed device behind a
|
||||
// high-speed hub. The controller has to know which hub is doing the
|
||||
// speed conversion on its behalf, because it is the hub, not the device,
|
||||
// that it actually schedules against.
|
||||
int tt_slot, tt_port;
|
||||
|
||||
int in_use;
|
||||
|
||||
struct usb_device_desc desc;
|
||||
uchar conf[USB_MAXCONF]; // the configuration descriptor and everything
|
||||
int conflen; // that follows it, exactly as the device sent
|
||||
|
||||
// The first interface, which is all a boot-protocol or bulk-only device
|
||||
// needs, and the endpoints belonging to it.
|
||||
struct usb_interface_desc iface;
|
||||
struct usb_endpoint_desc intr_in;
|
||||
struct usb_endpoint_desc bulk_in;
|
||||
struct usb_endpoint_desc bulk_out;
|
||||
int has_intr_in, has_bulk_in, has_bulk_out;
|
||||
|
||||
// Filled in by whichever class driver claimed the device.
|
||||
const char *driver;
|
||||
void *drvdata;
|
||||
void (*complete)(struct usb_dev *, void *buf, int len);
|
||||
};
|
||||
|
||||
// One row per class driver. Matching is on the *interface* descriptor,
|
||||
// because bDeviceClass is very often 0 ("look at the interfaces").
|
||||
struct usb_driver {
|
||||
const char *name;
|
||||
uint8 class, subclass, protocol; // 0xFF matches anything
|
||||
// What the rest of the kernel should consider this device to be, once this
|
||||
// driver has claimed it. Distinct from `class` above, which is the USB
|
||||
// interface class the descriptor advertises: the driver knows what it made
|
||||
// of the device, the descriptor only says what the device claims to be.
|
||||
enum dev_class devclass;
|
||||
int (*probe)(struct usb_dev *);
|
||||
};
|
||||
|
||||
// Called by a host controller once a device is addressed and its device
|
||||
// descriptor has been read. The core reads the configuration, selects it, and
|
||||
// offers the device to the class drivers.
|
||||
void usb_attach(struct usb_dev *dev);
|
||||
|
||||
117
include/user.h
Normal file
117
include/user.h
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
#ifndef USER_H
|
||||
#define USER_H
|
||||
|
||||
#include "types.h"
|
||||
#include "date.h"
|
||||
|
||||
struct stat;
|
||||
struct rtcdate;
|
||||
struct mouse_data;
|
||||
|
||||
// --- system calls ---
|
||||
int fork(void);
|
||||
void exit(void) __attribute__((noreturn));
|
||||
int wait(void);
|
||||
int pipe(int*);
|
||||
int write(int, void*, int);
|
||||
int read(int, void*, int);
|
||||
int close(int);
|
||||
int kill(int);
|
||||
int exec(char*, char**);
|
||||
int open(char*, int);
|
||||
int mknod(char*, short, short);
|
||||
int unlink(char*);
|
||||
int fstat(int fd, struct stat*);
|
||||
int link(char*, char*);
|
||||
int mkdir(char*);
|
||||
int chdir(char*);
|
||||
int dup(int);
|
||||
int dup2(int, int);
|
||||
int fcntl(int, int, int);
|
||||
long lseek(int, long, int);
|
||||
int getpid(void);
|
||||
char* sbrk(uint64);
|
||||
int sleep(int);
|
||||
int uptime(void);
|
||||
int vga_sync(void*);
|
||||
int getmouse(struct mouse_data*);
|
||||
int reboot(void);
|
||||
int shutdown(void);
|
||||
int sigfillset(sigset_t *set);
|
||||
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
|
||||
int sigwait(const sigset_t *set, int *sig);
|
||||
int alarm(int seconds);
|
||||
int waitpid(int pid, int *status, int options);
|
||||
int setsid(void);
|
||||
void* mmap(void*, uint64, int, int, int, uint64);
|
||||
// Shared memory. shmat returns the attach address, or a small negative value
|
||||
// on failure — there is no errno in this runtime, so check for < 0.
|
||||
int shmget(int key, uint64 size, int flags);
|
||||
void* shmat(int id, void *addr, int flags);
|
||||
int shmdt(void *addr);
|
||||
int munmap(void*, uint64);
|
||||
int delete(char*);
|
||||
int show(char*);
|
||||
int showstat(char*, struct stat*);
|
||||
int ftruncate(int, int);
|
||||
int truncate(char*, int);
|
||||
int rename(const char*, const char*);
|
||||
int symlink(char*, char*);
|
||||
int readlink(char*, char*, int);
|
||||
int getuid(void);
|
||||
int geteuid(void);
|
||||
int nanosleep(void*);
|
||||
int mprotect(void*, uint64, int);
|
||||
int ioctl(int, uint64, void*);
|
||||
char* getcwd(char*, int);
|
||||
int arch_prctl(int, uint64);
|
||||
int set_tid_address(int*);
|
||||
int set_robust_list(void*, int);
|
||||
int futex(int*, int, int, void*, int*, int);
|
||||
int clone(uint64, void*, void*, void*, void*);
|
||||
void exit_group(int) __attribute__((noreturn));
|
||||
int wait4(int, int*, int, void*);
|
||||
int getgid(void);
|
||||
int getegid(void);
|
||||
int umask(int);
|
||||
int writev(int, void*, int);
|
||||
int readv(int, void*, int);
|
||||
void* brk(void*);
|
||||
int prlimit(int, int, void*, void*);
|
||||
int lstat(const char*, struct stat*);
|
||||
int access(const char*, int);
|
||||
int openat(int, const char*, int, ...);
|
||||
int fchdir(int);
|
||||
int enable_linux_abi(void);
|
||||
int getrlimit(int, void*);
|
||||
int setuid(int);
|
||||
int setgid(int);
|
||||
int getsid(int);
|
||||
int dup3(int, int, int);
|
||||
int pipe2(int*, int);
|
||||
int clock_gettime(int, void*);
|
||||
int getppid(void);
|
||||
int rt_sigaction(int, void*, void*, uint64);
|
||||
int mount(const char *src, const char *tgt, const char *fstype, uint64 flags, void *data);
|
||||
|
||||
// Sockets. `struct sockaddr` and the AF_*/SOCK_* constants are in socket.h;
|
||||
// include it as well as this header to use them.
|
||||
struct sockaddr;
|
||||
int socket(int domain, int type, int protocol);
|
||||
int bind(int fd, const struct sockaddr *addr, int addrlen);
|
||||
int listen(int fd, int backlog);
|
||||
int accept(int fd, struct sockaddr *addr, int *addrlen);
|
||||
int connect(int fd, const struct sockaddr *addr, int addrlen);
|
||||
int socketpair(int domain, int type, int protocol, int sv[2]);
|
||||
int sendto(int fd, const void *buf, int len, int flags, const struct sockaddr*, int);
|
||||
int recvfrom(int fd, void *buf, int len, int flags, struct sockaddr*, int*);
|
||||
int sockshutdown(int fd, int how);
|
||||
int setsockopt(int fd, int level, int opt, const void *val, int len);
|
||||
int getsockopt(int fd, int level, int opt, void *val, int *len);
|
||||
|
||||
// --- ulib.c ---
|
||||
int stat(const char*, struct stat*);
|
||||
char* gets(char*, int max);
|
||||
|
||||
#endif // USER_H
|
||||
174
include/virtio.h
Normal file
174
include/virtio.h
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
#pragma once
|
||||
|
||||
// virtio 1.0 over PCI — the transport, and virtio-gpu's 2D protocol.
|
||||
//
|
||||
// Written from the OASIS specification (virtio 1.1, chapters 2, 4.1 and 5.7),
|
||||
// which is a public document with no licence on what you write from it. Same
|
||||
// reasoning as kernel/drivers/xhci.c.
|
||||
//
|
||||
// Why virtio-gpu at all, when there is already a framebuffer: the VBE
|
||||
// framebuffer is a flat block of memory the loader happened to be told about.
|
||||
// It has no mode setting, no notion of a frame being complete, and no way to
|
||||
// ask the host to present anything -- so a compositor drawing into it is
|
||||
// racing the scanout with no way to synchronise. virtio-gpu has resources,
|
||||
// explicit transfer and an explicit flush, which is the model every real GPU
|
||||
// driver has. It is the smallest device that teaches the right shape.
|
||||
|
||||
// --- PCI transport ---------------------------------------------------------
|
||||
|
||||
#define VIRTIO_VENDOR 0x1AF4
|
||||
// Modern (1.0) device IDs are 0x1040 + the virtio device type. GPU is type 16.
|
||||
#define VIRTIO_DEV_GPU 0x1050
|
||||
|
||||
// Vendor-specific PCI capability, which is how a virtio device says where in
|
||||
// its BARs each configuration structure lives.
|
||||
#define PCI_CAP_ID_VNDR 0x09
|
||||
|
||||
#define VIRTIO_PCI_CAP_COMMON_CFG 1
|
||||
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2
|
||||
#define VIRTIO_PCI_CAP_ISR_CFG 3
|
||||
#define VIRTIO_PCI_CAP_DEVICE_CFG 4
|
||||
|
||||
// Offsets within the common configuration structure. Written out rather than
|
||||
// declared as a struct: the fields are little-endian by specification and of
|
||||
// mixed width, and a struct invites the compiler to pad or a caller to forget
|
||||
// that these are MMIO and must be accessed at exactly the stated width.
|
||||
#define VCOMMON_DFEAT_SEL 0x00 // le32
|
||||
#define VCOMMON_DFEAT 0x04 // le32
|
||||
#define VCOMMON_GFEAT_SEL 0x08 // le32
|
||||
#define VCOMMON_GFEAT 0x0C // le32
|
||||
#define VCOMMON_MSIX_CFG 0x10 // le16
|
||||
#define VCOMMON_NUM_QUEUES 0x12 // le16
|
||||
#define VCOMMON_STATUS 0x14 // u8
|
||||
#define VCOMMON_CFG_GEN 0x15 // u8
|
||||
#define VCOMMON_Q_SELECT 0x16 // le16
|
||||
#define VCOMMON_Q_SIZE 0x18 // le16
|
||||
#define VCOMMON_Q_MSIX 0x1A // le16
|
||||
#define VCOMMON_Q_ENABLE 0x1C // le16
|
||||
#define VCOMMON_Q_NOTIFY_OFF 0x1E // le16
|
||||
#define VCOMMON_Q_DESC 0x20 // le64
|
||||
#define VCOMMON_Q_DRIVER 0x28 // le64
|
||||
#define VCOMMON_Q_DEVICE 0x30 // le64
|
||||
|
||||
// device_status bits, set in this order during bring-up.
|
||||
#define VSTATUS_ACK 1
|
||||
#define VSTATUS_DRIVER 2
|
||||
#define VSTATUS_DRIVER_OK 4
|
||||
#define VSTATUS_FEATURES_OK 8
|
||||
#define VSTATUS_FAILED 0x80
|
||||
|
||||
#define VIRTIO_F_VERSION_1 32 // feature bit: this is a 1.0 device
|
||||
|
||||
// --- split virtqueue -------------------------------------------------------
|
||||
|
||||
#define VRING_DESC_F_NEXT 1
|
||||
#define VRING_DESC_F_WRITE 2 // device writes it; i.e. this is our buffer
|
||||
|
||||
struct vring_desc {
|
||||
uint64 addr;
|
||||
uint32 len;
|
||||
uint16 flags;
|
||||
uint16 next;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct vring_avail {
|
||||
uint16 flags;
|
||||
uint16 idx;
|
||||
uint16 ring[];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct vring_used_elem {
|
||||
uint32 id;
|
||||
uint32 len;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct vring_used {
|
||||
uint16 flags;
|
||||
uint16 idx;
|
||||
struct vring_used_elem ring[];
|
||||
} __attribute__((packed));
|
||||
|
||||
// --- virtio-gpu, 2D --------------------------------------------------------
|
||||
|
||||
#define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0100
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0101
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_UNREF 0x0102
|
||||
#define VIRTIO_GPU_CMD_SET_SCANOUT 0x0103
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_FLUSH 0x0104
|
||||
#define VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 0x0105
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING 0x0106
|
||||
|
||||
#define VIRTIO_GPU_RESP_OK_NODATA 0x1100
|
||||
#define VIRTIO_GPU_RESP_OK_DISPLAY_INFO 0x1101
|
||||
|
||||
#define VIRTIO_GPU_MAX_SCANOUTS 16
|
||||
|
||||
// B8G8R8X8, which is what a 32-bit little-endian 0x00RRGGBB word looks like in
|
||||
// memory -- the same layout vga.c already draws into, so a framebuffer can be
|
||||
// handed over without a conversion pass.
|
||||
#define VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM 2
|
||||
|
||||
struct virtio_gpu_ctrl_hdr {
|
||||
uint32 type;
|
||||
uint32 flags;
|
||||
uint64 fence_id;
|
||||
uint32 ctx_id;
|
||||
uint32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_rect {
|
||||
uint32 x, y, width, height;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_display_one {
|
||||
struct virtio_gpu_rect r;
|
||||
uint32 enabled;
|
||||
uint32 flags;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resp_display_info {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_display_one pmodes[VIRTIO_GPU_MAX_SCANOUTS];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resource_create_2d {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
uint32 resource_id;
|
||||
uint32 format;
|
||||
uint32 width;
|
||||
uint32 height;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_mem_entry {
|
||||
uint64 addr;
|
||||
uint32 length;
|
||||
uint32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resource_attach_backing {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
uint32 resource_id;
|
||||
uint32 nr_entries;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_set_scanout {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_rect r;
|
||||
uint32 scanout_id;
|
||||
uint32 resource_id;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_transfer_to_host_2d {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_rect r;
|
||||
uint64 offset;
|
||||
uint32 resource_id;
|
||||
uint32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resource_flush {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_rect r;
|
||||
uint32 resource_id;
|
||||
uint32 padding;
|
||||
} __attribute__((packed));
|
||||
219
include/x86.h
Normal file
219
include/x86.h
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
#pragma once
|
||||
|
||||
struct dtr {
|
||||
ushort limit;
|
||||
uint64 base;
|
||||
} __attribute__((packed));
|
||||
|
||||
static inline uchar
|
||||
inb(ushort port)
|
||||
{
|
||||
uchar data;
|
||||
asm volatile("in %1,%0" : "=a" (data) : "d" (port));
|
||||
return data;
|
||||
}
|
||||
|
||||
static inline void
|
||||
insl(int port, void *addr, int cnt)
|
||||
{
|
||||
asm volatile("cld; rep insl" :
|
||||
"=D" (addr), "=c" (cnt) :
|
||||
"d" (port), "0" (addr), "1" (cnt) :
|
||||
"memory", "cc");
|
||||
}
|
||||
|
||||
static inline void
|
||||
outb(ushort port, uchar data)
|
||||
{
|
||||
asm volatile("out %0,%1" : : "a" (data), "d" (port));
|
||||
}
|
||||
|
||||
static inline uint
|
||||
inl(ushort port)
|
||||
{
|
||||
uint data;
|
||||
asm volatile("inl %1,%0" : "=a" (data) : "d" (port));
|
||||
return data;
|
||||
}
|
||||
|
||||
static inline void
|
||||
outw(ushort port, ushort data)
|
||||
{
|
||||
asm volatile("out %0,%1" : : "a" (data), "d" (port));
|
||||
}
|
||||
|
||||
// PCI configuration space is reached through a pair of 32-bit ports, so this
|
||||
// is not optional decoration -- see kernel/dev/pci.c.
|
||||
static inline void
|
||||
outl(ushort port, uint data)
|
||||
{
|
||||
asm volatile("outl %0,%1" : : "a" (data), "d" (port));
|
||||
}
|
||||
|
||||
static inline void
|
||||
outsl(int port, const void *addr, int cnt)
|
||||
{
|
||||
asm volatile("cld; rep outsl" :
|
||||
"=S" (addr), "=c" (cnt) :
|
||||
"d" (port), "0" (addr), "1" (cnt) :
|
||||
"cc");
|
||||
}
|
||||
|
||||
static inline void
|
||||
stosb(void *addr, int data, int cnt)
|
||||
{
|
||||
asm volatile("cld; rep stosb" :
|
||||
"=D" (addr), "=c" (cnt) :
|
||||
"0" (addr), "1" (cnt), "a" (data) :
|
||||
"memory", "cc");
|
||||
}
|
||||
|
||||
// 64-bit-only helpers. `rep stosq` and `xchgq` do not exist in 32-bit mode, so
|
||||
// these must not even be parsed by the -m32 bootloader build -- they compiled
|
||||
// there before only because uint64 was accidentally 32 bits wide, which is the
|
||||
// bug include/types.h now asserts against.
|
||||
#ifdef __x86_64__
|
||||
static inline void
|
||||
stosq(void *addr, uint64 data, int cnt)
|
||||
{
|
||||
asm volatile("cld; rep stosq" :
|
||||
"=D" (addr), "=c" (cnt) :
|
||||
"0" (addr), "1" (cnt), "a" (data) :
|
||||
"memory", "cc");
|
||||
}
|
||||
|
||||
struct segdesc;
|
||||
|
||||
static inline void
|
||||
lgdt(struct segdesc *p, int size)
|
||||
{
|
||||
struct dtr d;
|
||||
d.limit = size - 1;
|
||||
d.base = (uint64)p;
|
||||
asm volatile("lgdt (%0)" : : "r" (&d));
|
||||
}
|
||||
|
||||
struct gatedesc;
|
||||
|
||||
static inline void
|
||||
lidt(struct gatedesc *p, int size)
|
||||
{
|
||||
struct dtr d;
|
||||
d.limit = size - 1;
|
||||
d.base = (uint64)p;
|
||||
asm volatile("lidt (%0)" : : "r" (&d));
|
||||
}
|
||||
|
||||
static inline void
|
||||
ltr(ushort sel)
|
||||
{
|
||||
asm volatile("ltr %0" : : "r" (sel));
|
||||
}
|
||||
|
||||
static inline uint64
|
||||
readeflags(void)
|
||||
{
|
||||
uint64 flags;
|
||||
asm volatile("pushfq; pop %0" : "=r" (flags));
|
||||
return flags;
|
||||
}
|
||||
|
||||
static inline void
|
||||
cli(void)
|
||||
{
|
||||
asm volatile("cli");
|
||||
}
|
||||
|
||||
static inline void
|
||||
sti(void)
|
||||
{
|
||||
asm volatile("sti");
|
||||
}
|
||||
|
||||
static inline void
|
||||
hlt(void)
|
||||
{
|
||||
asm volatile("hlt");
|
||||
}
|
||||
|
||||
static inline uint64
|
||||
xchg(volatile uint64 *addr, uint64 newval)
|
||||
{
|
||||
uint64 result;
|
||||
asm volatile("lock; xchgq %0, %1" :
|
||||
"+m" (*addr), "=a" (result) :
|
||||
"1" (newval) :
|
||||
"cc");
|
||||
return result;
|
||||
}
|
||||
#endif /* __x86_64__ */
|
||||
|
||||
static inline uint64
|
||||
rcr2(void)
|
||||
{
|
||||
uint64 val;
|
||||
asm volatile("mov %%cr2,%0" : "=r" (val));
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline void
|
||||
lcr3(uint64 val)
|
||||
{
|
||||
asm volatile("mov %0,%%cr3" : : "r" (val));
|
||||
}
|
||||
|
||||
// Invalidate the TLB entry for one page. Needed any time a PTE is changed
|
||||
// or cleared for an address space that may still be the active CR3 —
|
||||
// otherwise the CPU can keep translating through the stale entry until the
|
||||
// next full CR3 reload (context switch), silently reading/writing the
|
||||
// wrong physical page.
|
||||
static inline void
|
||||
invlpg(void *va)
|
||||
{
|
||||
asm volatile("invlpg (%0)" : : "r" (va) : "memory");
|
||||
}
|
||||
|
||||
static inline void
|
||||
wrmsr(uint32 msr, uint64 val)
|
||||
{
|
||||
uint32 low = (uint32)val;
|
||||
uint32 high = (uint32)(val >> 32);
|
||||
asm volatile("wrmsr" : : "c" (msr), "a" (low), "d" (high));
|
||||
}
|
||||
|
||||
static inline uint64
|
||||
rdmsr(uint32 msr)
|
||||
{
|
||||
uint32 low, high;
|
||||
asm volatile("rdmsr" : "=a" (low), "=d" (high) : "c" (msr));
|
||||
return ((uint64)high << 32) | low;
|
||||
}
|
||||
|
||||
static inline uint64
|
||||
readcs(void)
|
||||
{
|
||||
uint64 val;
|
||||
asm volatile("mov %%cs, %0" : "=r" (val));
|
||||
return val;
|
||||
}
|
||||
|
||||
// CPUID. `leaf` in EAX, results in EAX/EBX/ECX/EDX.
|
||||
//
|
||||
// %rbx is callee-saved *and* the PIC base register on some builds, so it is
|
||||
// exchanged out rather than named as an operand -- naming it directly is the
|
||||
// classic way to get "inline asm clobbers reserved register" or, worse, silent
|
||||
// corruption in the caller.
|
||||
static inline void
|
||||
cpuid_leaf(uint32 leaf, uint32 *a, uint32 *b, uint32 *c, uint32 *d)
|
||||
{
|
||||
uint32 ra, rb, rc, rd;
|
||||
asm volatile("xchgq %%rbx, %q1\n\t"
|
||||
"cpuid\n\t"
|
||||
"xchgq %%rbx, %q1"
|
||||
: "=a"(ra), "=&r"(rb), "=c"(rc), "=d"(rd)
|
||||
: "0"(leaf), "2"(0));
|
||||
if (a) *a = ra;
|
||||
if (b) *b = rb;
|
||||
if (c) *c = rc;
|
||||
if (d) *d = rd;
|
||||
}
|
||||
161
include/xhci.h
Normal file
161
include/xhci.h
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
#pragma once
|
||||
|
||||
// eXtensible Host Controller Interface -- the register and data-structure
|
||||
// definitions from the xHCI 1.2 specification.
|
||||
//
|
||||
// Why xHCI and not UHCI/OHCI/EHCI: xHCI is the only host controller interface
|
||||
// that speaks every USB speed, so one driver covers USB 1.1 through 3.x. The
|
||||
// older controllers would each need their own, and a machine with all of them
|
||||
// needs the companion-controller handover dance on top. There is no hardware
|
||||
// worth supporting that has no xHCI.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Capability registers -- at the start of BAR0, read-only
|
||||
// ---------------------------------------------------------------------------
|
||||
#define XHCI_CAPLENGTH 0x00 // byte: where the operational regs begin
|
||||
#define XHCI_HCIVERSION 0x02 // word
|
||||
#define XHCI_HCSPARAMS1 0x04
|
||||
#define XHCI_HCSPARAMS2 0x08
|
||||
#define XHCI_HCSPARAMS3 0x0C
|
||||
#define XHCI_HCCPARAMS1 0x10
|
||||
#define XHCI_DBOFF 0x14 // doorbell array offset from BAR0
|
||||
#define XHCI_RTSOFF 0x18 // runtime register offset from BAR0
|
||||
|
||||
#define HCS1_MAXSLOTS(x) ((x) & 0xFF)
|
||||
#define HCS1_MAXINTRS(x) (((x) >> 8) & 0x7FF)
|
||||
#define HCS1_MAXPORTS(x) (((x) >> 24) & 0xFF)
|
||||
|
||||
// Scratchpad buffer count is split across two fields, high part first.
|
||||
#define HCS2_SPB_HI(x) (((x) >> 21) & 0x1F)
|
||||
#define HCS2_SPB_LO(x) (((x) >> 27) & 0x1F)
|
||||
|
||||
#define HCC1_AC64 (1 << 0) // 64-bit addressing
|
||||
#define HCC1_CSZ (1 << 2) // contexts are 64 bytes, not 32
|
||||
#define HCC1_XECP(x) (((x) >> 16) & 0xFFFF) // extended caps, in dwords
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Operational registers -- at BAR0 + CAPLENGTH
|
||||
// ---------------------------------------------------------------------------
|
||||
#define XHCI_USBCMD 0x00
|
||||
#define XHCI_USBSTS 0x04
|
||||
#define XHCI_PAGESIZE 0x08
|
||||
#define XHCI_DNCTRL 0x14
|
||||
#define XHCI_CRCR 0x18 // 64-bit
|
||||
#define XHCI_DCBAAP 0x30 // 64-bit
|
||||
#define XHCI_CONFIG 0x38
|
||||
#define XHCI_PORTSC(p) (0x400 + 0x10 * ((p) - 1)) // ports are 1-based
|
||||
|
||||
#define CMD_RS (1 << 0) // run/stop
|
||||
#define CMD_HCRST (1 << 1) // host controller reset
|
||||
#define CMD_INTE (1 << 2) // interrupter enable
|
||||
#define CMD_HSEE (1 << 3)
|
||||
|
||||
#define STS_HCH (1 << 0) // halted
|
||||
#define STS_HSE (1 << 2) // host system error
|
||||
#define STS_EINT (1 << 3) // event interrupt (write 1 to clear)
|
||||
#define STS_PCD (1 << 4) // port change detect (write 1 to clear)
|
||||
#define STS_CNR (1 << 11) // controller not ready
|
||||
|
||||
#define CRCR_RCS (1 << 0) // ring cycle state
|
||||
|
||||
// PORTSC. The change bits are write-1-to-clear, and so is PED -- writing back
|
||||
// a value read from this register clears whatever happened to be set. Every
|
||||
// write has to mask them off first.
|
||||
#define PORT_CCS (1 << 0) // current connect status
|
||||
#define PORT_PED (1 << 1) // port enabled (RW1C!)
|
||||
#define PORT_OCA (1 << 3)
|
||||
#define PORT_PR (1 << 4) // port reset
|
||||
#define PORT_PLS(x) (((x) >> 5) & 0xF)
|
||||
#define PORT_PP (1 << 9) // port power
|
||||
#define PORT_SPEED(x) (((x) >> 10) & 0xF)
|
||||
#define PORT_CSC (1 << 17)
|
||||
#define PORT_PEC (1 << 18)
|
||||
#define PORT_PRC (1 << 21)
|
||||
#define PORT_RW1C 0x00FE0002u // change bits 17..23, plus PED
|
||||
|
||||
// PORTSC speed encoding. These are the same numbers as USB_SPEED_* in usb.h,
|
||||
// which is not a coincidence -- the core uses this encoding directly so that
|
||||
// nothing has to be translated on its way to a slot context.
|
||||
#define XSPEED_FULL USB_SPEED_FULL
|
||||
#define XSPEED_LOW USB_SPEED_LOW
|
||||
#define XSPEED_HIGH USB_SPEED_HIGH
|
||||
#define XSPEED_SUPER USB_SPEED_SUPER
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Runtime registers -- at BAR0 + RTSOFF. Interrupter n is at 0x20 + 32*n.
|
||||
// ---------------------------------------------------------------------------
|
||||
#define XHCI_MFINDEX 0x00
|
||||
#define XHCI_IR0 0x20
|
||||
#define IR_IMAN 0x00
|
||||
#define IR_IMOD 0x04
|
||||
#define IR_ERSTSZ 0x08
|
||||
#define IR_ERSTBA 0x10 // 64-bit
|
||||
#define IR_ERDP 0x18 // 64-bit
|
||||
|
||||
#define IMAN_IP (1 << 0) // interrupt pending (RW1C)
|
||||
#define IMAN_IE (1 << 1) // interrupt enable
|
||||
#define ERDP_EHB (1 << 3) // event handler busy (RW1C)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Transfer Request Blocks
|
||||
// ---------------------------------------------------------------------------
|
||||
struct trb {
|
||||
uint64 param;
|
||||
uint32 status;
|
||||
uint32 control;
|
||||
} __attribute__((packed));
|
||||
|
||||
#define TRB_TYPE(t) (((t) & 0x3F) << 10)
|
||||
#define TRB_GET_TYPE(c) (((c) >> 10) & 0x3F)
|
||||
#define TRB_C (1 << 0) // cycle bit
|
||||
#define TRB_TC (1 << 1) // toggle cycle (link TRBs)
|
||||
#define TRB_ISP (1 << 2)
|
||||
#define TRB_CH (1 << 4) // chain
|
||||
#define TRB_IOC (1 << 5) // interrupt on completion
|
||||
#define TRB_IDT (1 << 6) // immediate data
|
||||
#define TRB_SLOT(s) ((uint32)(s) << 24)
|
||||
#define TRB_GET_SLOT(c) (((c) >> 24) & 0xFF)
|
||||
#define TRB_GET_EPID(c) (((c) >> 16) & 0x1F) // transfer events
|
||||
|
||||
// Transfer TRBs
|
||||
#define TRB_NORMAL 1
|
||||
#define TRB_SETUP 2
|
||||
#define TRB_DATA 3
|
||||
#define TRB_STATUS 4
|
||||
#define TRB_LINK 6
|
||||
// Command TRBs
|
||||
#define TRB_ENABLE_SLOT 9
|
||||
#define TRB_DISABLE_SLOT 10
|
||||
#define TRB_ADDRESS_DEV 11
|
||||
#define TRB_CONFIG_EP 12
|
||||
#define TRB_EVAL_CTX 13
|
||||
#define TRB_RESET_EP 14
|
||||
#define TRB_NOOP_CMD 23
|
||||
// Event TRBs
|
||||
#define TRB_XFER_EVENT 32
|
||||
#define TRB_CMD_COMPLETE 33
|
||||
#define TRB_PORT_STATUS 34
|
||||
|
||||
#define TRB_CC(status) (((status) >> 24) & 0xFF)
|
||||
#define CC_SUCCESS 1
|
||||
#define CC_SHORT_PACKET 13
|
||||
|
||||
// Setup Stage transfer type, and Data Stage direction.
|
||||
#define TRT_NONE 0
|
||||
#define TRT_OUT 2
|
||||
#define TRT_IN 3
|
||||
#define DIR_IN (1 << 16)
|
||||
|
||||
// Endpoint types in an endpoint context. Direction is part of the type, and
|
||||
// the numbering is not arbitrary: OUT types are 1-3, control is 4, and the IN
|
||||
// types are the OUT ones plus four.
|
||||
#define EPTYPE_ISOCH_OUT 1
|
||||
#define EPTYPE_BULK_OUT 2
|
||||
#define EPTYPE_INT_OUT 3
|
||||
#define EPTYPE_CONTROL 4
|
||||
#define EPTYPE_ISOCH_IN 5
|
||||
#define EPTYPE_BULK_IN 6
|
||||
#define EPTYPE_INT_IN 7
|
||||
|
||||
// Doorbell targets: 0 is the command ring; for a device slot, DCI 1 is EP0.
|
||||
#define DCI_EP0 1
|
||||
264
kernel/arch/x86_64/acpi.c
Normal file
264
kernel/arch/x86_64/acpi.c
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
// Find the processors, the way firmware from this century describes them.
|
||||
//
|
||||
// mpinit() walks the Intel MP table, which is from 1994. Modern firmware fills
|
||||
// it in minimally or omits it: measured 2026-08-08, QEMU/SeaBIOS with -smp 4
|
||||
// publishes a 208-byte MP table containing exactly *one* processor entry, then
|
||||
// buses and interrupt assignments. Everything since has expected the operating
|
||||
// system to read the ACPI MADT instead.
|
||||
//
|
||||
// The consequence was not that SMP was slow, it was that ncpu was 1 and SMP was
|
||||
// silently never exercised. Machines whose firmware does publish a full MP
|
||||
// table -- VMware, older hardware -- took the other path, started their APs,
|
||||
// and triple faulted. So this file is a prerequisite for debugging that: until
|
||||
// the CPUs are visible under QEMU there is no way to reproduce the crash except
|
||||
// on the hardware it happens on.
|
||||
//
|
||||
// Written from the ACPI specification. No table layout here is copied from
|
||||
// another kernel; they are all in chapter 5 of the published standard.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
|
||||
extern struct cpu cpus[NCPU];
|
||||
extern int ncpu;
|
||||
extern uchar ioapicid;
|
||||
extern volatile uint *lapic;
|
||||
|
||||
// The Root System Description Pointer. Found by signature, not by being told
|
||||
// where it is -- it sits either in the Extended BIOS Data Area or in the BIOS
|
||||
// read-only region, on a 16-byte boundary.
|
||||
struct acpi_rsdp {
|
||||
char sig[8]; // "RSD PTR "
|
||||
uint8 checksum; // over the first 20 bytes
|
||||
char oemid[6];
|
||||
uint8 revision; // 0 = ACPI 1.0 (RSDT), 2+ = 2.0 (XSDT)
|
||||
uint32 rsdt_addr;
|
||||
// ACPI 2.0 and later only, and only valid when revision >= 2.
|
||||
uint32 length;
|
||||
uint64 xsdt_addr;
|
||||
uint8 ext_checksum; // over `length` bytes
|
||||
uint8 reserved[3];
|
||||
} __attribute__((packed));
|
||||
|
||||
// Every ACPI table starts with this.
|
||||
struct acpi_sdt {
|
||||
char sig[4];
|
||||
uint32 length;
|
||||
uint8 revision;
|
||||
uint8 checksum;
|
||||
char oemid[6];
|
||||
char oem_table_id[8];
|
||||
uint32 oem_revision;
|
||||
uint32 creator_id;
|
||||
uint32 creator_revision;
|
||||
} __attribute__((packed));
|
||||
|
||||
// The Multiple APIC Description Table: signature "APIC".
|
||||
struct acpi_madt {
|
||||
struct acpi_sdt h;
|
||||
uint32 lapic_addr;
|
||||
uint32 flags; // bit 0: legacy 8259 PICs are present
|
||||
} __attribute__((packed));
|
||||
|
||||
#define MADT_PCAT_COMPAT 1
|
||||
|
||||
// MADT entry types. Only these three are read; the rest are interrupt
|
||||
// routing overrides that ioapic.c does not consult yet.
|
||||
#define MADT_LAPIC 0
|
||||
#define MADT_IOAPIC 1
|
||||
#define MADT_LAPIC_OVERRIDE 5
|
||||
|
||||
struct madt_entry {
|
||||
uint8 type;
|
||||
uint8 length;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct madt_lapic {
|
||||
struct madt_entry e;
|
||||
uint8 acpi_id;
|
||||
uint8 apic_id;
|
||||
uint32 flags; // bit 0: enabled, bit 1: online-capable
|
||||
} __attribute__((packed));
|
||||
|
||||
#define LAPIC_ENABLED 1
|
||||
#define LAPIC_ONLINE_CAPABLE 2
|
||||
|
||||
struct madt_ioapic {
|
||||
struct madt_entry e;
|
||||
uint8 id;
|
||||
uint8 reserved;
|
||||
uint32 addr;
|
||||
uint32 gsi_base;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct madt_lapic_override {
|
||||
struct madt_entry e;
|
||||
uint16 reserved;
|
||||
uint64 addr;
|
||||
} __attribute__((packed));
|
||||
|
||||
static int
|
||||
checksum_ok(const void *p, uint32 len)
|
||||
{
|
||||
const uint8 *b = p;
|
||||
uint8 sum = 0;
|
||||
for (uint32 i = 0; i < len; i++)
|
||||
sum += b[i];
|
||||
return sum == 0;
|
||||
}
|
||||
|
||||
// Scan a physical range for the RSDP signature. It is 16-byte aligned by
|
||||
// specification, which makes this cheap enough to do twice.
|
||||
static struct acpi_rsdp *
|
||||
scan_rsdp(uint64 base, uint64 len)
|
||||
{
|
||||
for (uint64 off = 0; off + sizeof(struct acpi_rsdp) <= len; off += 16) {
|
||||
struct acpi_rsdp *r = (struct acpi_rsdp *)P2V(base + off);
|
||||
if (memcmp(r->sig, "RSD PTR ", 8) != 0)
|
||||
continue;
|
||||
// Signature alone is not enough: the string can appear in unrelated data,
|
||||
// and following a bad pointer here means faulting before there is anything
|
||||
// to report the fault with.
|
||||
if (!checksum_ok(r, 20))
|
||||
continue;
|
||||
return r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct acpi_rsdp *
|
||||
find_rsdp(void)
|
||||
{
|
||||
// The EBDA, whose segment address the BIOS leaves at 0x40E.
|
||||
uint16 ebda_seg = *(uint16 *)P2V(0x40E);
|
||||
if (ebda_seg) {
|
||||
struct acpi_rsdp *r = scan_rsdp((uint64)ebda_seg << 4, 1024);
|
||||
if (r)
|
||||
return r;
|
||||
}
|
||||
return scan_rsdp(0xE0000, 0x20000);
|
||||
}
|
||||
|
||||
// Walk the RSDT or XSDT looking for a table by signature.
|
||||
//
|
||||
// Two nearly identical loops rather than one clever one: the only difference is
|
||||
// the pointer width -- 32-bit entries in the RSDT, 64-bit in the XSDT -- and
|
||||
// reading a 64-bit pointer out of a 32-bit array is exactly the sort of bug
|
||||
// that produces a plausible address pointing at nothing.
|
||||
static struct acpi_sdt *
|
||||
find_table(struct acpi_rsdp *rsdp, const char *sig)
|
||||
{
|
||||
struct acpi_sdt *root;
|
||||
int wide;
|
||||
|
||||
if (rsdp->revision >= 2 && rsdp->xsdt_addr) {
|
||||
root = (struct acpi_sdt *)P2V(rsdp->xsdt_addr);
|
||||
wide = 1;
|
||||
} else {
|
||||
root = (struct acpi_sdt *)P2V((uint64)rsdp->rsdt_addr);
|
||||
wide = 0;
|
||||
}
|
||||
|
||||
if (!checksum_ok(root, root->length))
|
||||
return 0;
|
||||
|
||||
uint32 n = (root->length - sizeof(struct acpi_sdt)) / (wide ? 8 : 4);
|
||||
for (uint32 i = 0; i < n; i++) {
|
||||
uint64 pa;
|
||||
if (wide) {
|
||||
// Byte-wise, because the array is packed and 8-byte entries in it are
|
||||
// not guaranteed to be 8-byte aligned.
|
||||
uint8 *p = (uint8 *)(root + 1) + i * 8;
|
||||
pa = 0;
|
||||
for (int b = 7; b >= 0; b--)
|
||||
pa = (pa << 8) | p[b];
|
||||
} else {
|
||||
pa = ((uint32 *)(root + 1))[i];
|
||||
}
|
||||
struct acpi_sdt *t = (struct acpi_sdt *)P2V(pa);
|
||||
if (memcmp(t->sig, sig, 4) == 0)
|
||||
return t;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns 1 if the processors were enumerated, 0 to fall back to mpinit().
|
||||
int
|
||||
acpi_init(void)
|
||||
{
|
||||
struct acpi_rsdp *rsdp = find_rsdp();
|
||||
if (!rsdp)
|
||||
return 0;
|
||||
|
||||
struct acpi_madt *madt = (struct acpi_madt *)find_table(rsdp, "APIC");
|
||||
if (!madt || !checksum_ok(madt, madt->h.length))
|
||||
return 0;
|
||||
|
||||
lapic = (uint *)P2V((uint64)madt->lapic_addr);
|
||||
|
||||
uint8 *p = (uint8 *)madt + sizeof(struct acpi_madt);
|
||||
uint8 *e = (uint8 *)madt + madt->h.length;
|
||||
int found_ioapic = 0;
|
||||
|
||||
while (p + sizeof(struct madt_entry) <= e) {
|
||||
struct madt_entry *ent = (struct madt_entry *)p;
|
||||
// A zero length would spin here forever. Firmware tables are not always
|
||||
// well formed, and an infinite loop this early is indistinguishable from a
|
||||
// hang with no output at all.
|
||||
if (ent->length < sizeof(struct madt_entry))
|
||||
break;
|
||||
|
||||
switch (ent->type) {
|
||||
case MADT_LAPIC: {
|
||||
struct madt_lapic *l = (struct madt_lapic *)p;
|
||||
// Skip processors that are neither enabled nor online-capable: those are
|
||||
// sockets the firmware is describing but not offering.
|
||||
if (!(l->flags & (LAPIC_ENABLED | LAPIC_ONLINE_CAPABLE)))
|
||||
break;
|
||||
if (ncpu < NCPU) {
|
||||
cpus[ncpu].apicid = l->apic_id;
|
||||
ncpu++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MADT_IOAPIC: {
|
||||
struct madt_ioapic *io = (struct madt_ioapic *)p;
|
||||
// The first one only. Multi-I/O-APIC routing needs the interrupt source
|
||||
// overrides too, which nothing here reads yet, so claiming to support it
|
||||
// would be worse than not.
|
||||
if (!found_ioapic) {
|
||||
ioapicid = io->id;
|
||||
found_ioapic = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MADT_LAPIC_OVERRIDE: {
|
||||
struct madt_lapic_override *o = (struct madt_lapic_override *)p;
|
||||
lapic = (uint *)P2V(o->addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
p += ent->length;
|
||||
}
|
||||
|
||||
if (ncpu == 0)
|
||||
return 0;
|
||||
|
||||
// The 8259s are still there and still wired to the CPU. On a system with an
|
||||
// IMCR, this switches interrupt delivery from the PICs to the APIC; on one
|
||||
// without, the ports are unclaimed and the writes go nowhere. mpinit() does
|
||||
// the same thing guarded by the MP table's imcrp flag.
|
||||
if (madt->flags & MADT_PCAT_COMPAT) {
|
||||
outb(0x22, 0x70);
|
||||
outb(0x23, inb(0x23) | 1);
|
||||
}
|
||||
|
||||
cprintf("acpi: %d cpu%s, lapic at %p, ioapic id %d\n",
|
||||
ncpu, ncpu == 1 ? "" : "s", lapic, ioapicid);
|
||||
return 1;
|
||||
}
|
||||
105
kernel/arch/x86_64/e820.c
Normal file
105
kernel/arch/x86_64/e820.c
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
// Find out how much memory this machine actually has.
|
||||
//
|
||||
// PHYSTOP used to be a constant: 0xE000000, 224 MB, inherited from xv6 and
|
||||
// never revisited. That is not a limit on how much memory the kernel *uses* --
|
||||
// it is an assertion that exactly that much exists. Boot a machine with less
|
||||
// and kinit1() walks off the end of RAM into nothing; boot one with more and
|
||||
// every byte above 224 MB is invisible. Measured before this file existed:
|
||||
// Schunix booted at 224 MB and triple faulted at 192 MB, for no reason at all.
|
||||
//
|
||||
// The firmware has known the answer the whole time. boot/stage2.S asks it with
|
||||
// INT 0x15 / E820 in real mode -- the same interface Linux's boot stub and GRUB
|
||||
// use -- and leaves the result in low memory. stage2main.c already parses it to
|
||||
// print detected RAM on the splash. The kernel simply never read it.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
|
||||
// Set before kinit1() and never again. Starts at the old constant so that a
|
||||
// crash between entry and e820_init() behaves the way it always did rather
|
||||
// than in some new way.
|
||||
uint64 phystop = PHYSTOP_FALLBACK;
|
||||
|
||||
// Where stage2 left the map. Must match E820_COUNT_ADDR / E820_MAP_ADDR in
|
||||
// boot/stage2main.c, which are themselves relative to VBE_INFO_ADDR.
|
||||
#define E820_COUNT_ADDR (0x30000 + 0x200)
|
||||
#define E820_MAP_ADDR (0x30000 + 0x208)
|
||||
|
||||
#define E820_USABLE 1
|
||||
#define E820_MAXENTRIES 32
|
||||
|
||||
struct e820entry {
|
||||
uint64 base;
|
||||
uint64 length;
|
||||
uint32 type;
|
||||
uint32 acpi_ext;
|
||||
} __attribute__((packed));
|
||||
|
||||
void
|
||||
e820_init(void)
|
||||
{
|
||||
uint32 count = *(uint32 *)P2V(E820_COUNT_ADDR);
|
||||
struct e820entry *e = (struct e820entry *)P2V(E820_MAP_ADDR);
|
||||
|
||||
// A count of zero means the firmware declined; anything absurd means we are
|
||||
// reading something that is not a memory map. Either way the constant is the
|
||||
// only honest answer left.
|
||||
if (count == 0 || count > E820_MAXENTRIES) {
|
||||
cprintf("e820: no usable map (count %d), assuming %d MB\n",
|
||||
count, (int)(PHYSTOP_FALLBACK / (1024 * 1024)));
|
||||
return;
|
||||
}
|
||||
|
||||
// The end of the usable region that *contains the kernel*, not the highest
|
||||
// usable address anywhere.
|
||||
//
|
||||
// This distinction matters and is easy to get wrong. A memory map is not one
|
||||
// run of RAM: there are reserved holes in it -- ACPI tables, firmware
|
||||
// scratch, the legacy hole below 1 MB -- and kalloc's freerange() walks
|
||||
// straight from the end of the kernel image to phystop handing out every page
|
||||
// between. Taking the global maximum would step over a reserved region and
|
||||
// put firmware tables on the free list, which fails later, elsewhere, and
|
||||
// looks like anything but a memory map problem.
|
||||
uint64 top = 0;
|
||||
for (uint32 i = 0; i < count; i++) {
|
||||
if (e[i].type != E820_USABLE)
|
||||
continue;
|
||||
uint64 base = e[i].base, end = base + e[i].length;
|
||||
if (base <= EXTMEM && EXTMEM < end) {
|
||||
top = end;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (top == 0) {
|
||||
cprintf("e820: no usable region covers 0x%x, assuming %d MB\n",
|
||||
EXTMEM, (int)(PHYSTOP_FALLBACK / (1024 * 1024)));
|
||||
return;
|
||||
}
|
||||
|
||||
// kvmalloc() maps physical 0-4 GB with four 1 GB pages, and marks the top one
|
||||
// uncached because that is where PCI BARs and the framebuffer live. So 3 GB
|
||||
// is the ceiling for anything the kernel will treat as RAM: above it the
|
||||
// direct map either does not exist or is device memory that would be
|
||||
// catastrophically slow to allocate from and is not ours to hand out.
|
||||
if (top > PHYSTOP_MAX)
|
||||
top = PHYSTOP_MAX;
|
||||
|
||||
top &= ~(uint64)(PGSIZE - 1);
|
||||
|
||||
// Below this the kernel image, its page tables and the DMA arena do not fit,
|
||||
// and continuing produces a failure much harder to read than this line.
|
||||
if (top < PHYSTOP_MIN) {
|
||||
cprintf("e820: only %d MB usable; Schunix needs at least %d MB\n",
|
||||
(int)(top / (1024 * 1024)), (int)(PHYSTOP_MIN / (1024 * 1024)));
|
||||
// Left at the fallback deliberately rather than lowered: a machine this
|
||||
// small is going to fail, and it should fail having said why.
|
||||
return;
|
||||
}
|
||||
|
||||
phystop = top;
|
||||
cprintf("e820: %d MB usable (%d entries)\n",
|
||||
(int)(phystop / (1024 * 1024)), count);
|
||||
}
|
||||
174
kernel/arch/x86_64/entry.S
Normal file
174
kernel/arch/x86_64/entry.S
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/* entry.S
|
||||
*
|
||||
* Copyright (c) 2013 Brian Swetland
|
||||
* Modified by Nexoniarz
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#define mboot_magic 0x1badb002
|
||||
#define mboot_flags 0x00010000
|
||||
#include "mmu.h"
|
||||
|
||||
.code64
|
||||
.global mboot_header
|
||||
.global mboot_entry
|
||||
|
||||
mboot_header:
|
||||
.long mboot_magic
|
||||
.long mboot_flags
|
||||
.long (-mboot_magic -mboot_flags)
|
||||
.long mboot_load_addr
|
||||
.long mboot_load_addr
|
||||
.long mboot_load_end
|
||||
.long mboot_bss_end
|
||||
.long mboot_entry_addr
|
||||
|
||||
.code32
|
||||
mboot_entry:
|
||||
mov %eax, %ebp
|
||||
|
||||
# bootstrap page tables (0x1000 - 0x3000)
|
||||
xor %eax, %eax
|
||||
mov $0x1000, %edi
|
||||
mov $0x2000, %ecx
|
||||
rep stosb
|
||||
|
||||
# Bootstrap page tables (Identity mapping 4GB)
|
||||
# PML4T[0] i PML4T[256] -> PDPT pod 0x2000
|
||||
mov $(0x2000 | PTE_P | PTE_W), %edx
|
||||
mov %edx, 0x1000
|
||||
mov %edx, 0x1800
|
||||
|
||||
# PDPT mapping 4GB using 1GB huge pages
|
||||
mov $(0x00000000 | PTE_P | PTE_PS | PTE_W), %edx
|
||||
mov %edx, 0x2000
|
||||
mov $(0x40000000 | PTE_P | PTE_PS | PTE_W), %edx
|
||||
mov %edx, 0x2008
|
||||
mov $(0x80000000 | PTE_P | PTE_PS | PTE_W), %edx
|
||||
mov %edx, 0x2010
|
||||
mov $(0xC0000000 | PTE_P | PTE_PS | PTE_W), %edx
|
||||
mov %edx, 0x2018
|
||||
|
||||
mov %ebp, %ebx
|
||||
|
||||
.global entry32mp
|
||||
entry32mp:
|
||||
mov $0x1000, %eax
|
||||
mov %eax, %cr3
|
||||
|
||||
lgdt (gdtr64 - mboot_header + mboot_load_addr)
|
||||
|
||||
mov %cr4, %eax
|
||||
bts $5, %eax
|
||||
mov %eax, %cr4
|
||||
|
||||
mov $MSR_EFER, %ecx
|
||||
rdmsr
|
||||
bts $0, %eax
|
||||
bts $8, %eax
|
||||
wrmsr
|
||||
|
||||
mov %cr0, %eax
|
||||
orl $(CR0_PG | CR0_WP | CR0_MP), %eax
|
||||
mov %eax, %cr0
|
||||
|
||||
ljmp $8, $(entry64low - mboot_header + mboot_load_addr)
|
||||
|
||||
.align 16
|
||||
gdtr64:
|
||||
.word gdt64_end - gdt64_begin - 1;
|
||||
.quad gdt64_begin - mboot_header + mboot_load_addr
|
||||
|
||||
.align 16
|
||||
gdt64_begin:
|
||||
.long 0x00000000
|
||||
.long 0x00000000
|
||||
.long 0x00000000 # 64-bit code
|
||||
.long 0x00209800
|
||||
.long 0x00000000 # 64-bit data
|
||||
.long 0x00009000
|
||||
gdt64_end:
|
||||
|
||||
.align 16
|
||||
.code64
|
||||
entry64low:
|
||||
movabs $entry64high, %rax
|
||||
jmp *%rax
|
||||
|
||||
.global _start
|
||||
_start:
|
||||
entry64high:
|
||||
xor %rax, %rax
|
||||
mov %ax, %ss
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
|
||||
# Is this an application processor, or the boot processor?
|
||||
#
|
||||
# This check used to be a comment saying it had not been written yet, and
|
||||
# every AP fell straight through into the BSP path below: it set RSP to the
|
||||
# *boot processor's* stack, passed its own flag to kmain() as a framebuffer
|
||||
# address, and re-ran the entire bootstrap -- e820_init, kinit1, kvmalloc --
|
||||
# on a second core sharing one stack with the first. That is the SMP triple
|
||||
# fault. QEMU hid it for years by never reporting more than one CPU (see
|
||||
# kernel/arch/x86_64/acpi.c); VMware did not.
|
||||
#
|
||||
# EBX is the discriminator, and entryother.S sets it to 1 immediately before
|
||||
# entering long mode for exactly this purpose. It is safe: on the boot
|
||||
# processor EBX carries the VESA framebuffer address from the loader, which is
|
||||
# either zero or a physical MMIO address, and can never be 1.
|
||||
cmp $1, %ebx
|
||||
je entry64mp
|
||||
|
||||
# Setup initial stack
|
||||
movabs $0xFFFF800000010000, %rax
|
||||
mov %rax, %rsp
|
||||
|
||||
# Pass VESA address to main (1st argument in RDI)
|
||||
mov %rbx, %rdi
|
||||
|
||||
call kmain
|
||||
|
||||
.global __deadloop
|
||||
__deadloop:
|
||||
jmp .
|
||||
|
||||
entry64mp:
|
||||
mov $0x7000, %rax
|
||||
mov -16(%rax), %rsp
|
||||
jmp mpenter
|
||||
|
||||
.global wrmsr
|
||||
wrmsr:
|
||||
mov %rdi, %rcx
|
||||
mov %rsi, %rax
|
||||
shr $32, %rsi
|
||||
mov %rsi, %rdx
|
||||
wrmsr
|
||||
retq
|
||||
|
||||
.global ignore_sysret
|
||||
ignore_sysret:
|
||||
mov $-38, %rax
|
||||
sysretq
|
||||
78
kernel/arch/x86_64/entryother.S
Normal file
78
kernel/arch/x86_64/entryother.S
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#include "asm.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
|
||||
# Each non-boot CPU ("AP") is started up in response to a STARTUP
|
||||
# IPI from the boot CPU. Section B.4.2 of the Multi-Processor
|
||||
# Specification says that the AP will start in real mode with CS:IP
|
||||
# set to XY00:0000, where XY is an 8-bit value sent with the
|
||||
# STARTUP. Thus this code must start at a 4096-byte boundary.
|
||||
#
|
||||
# Because this code sets DS to zero, it must sit
|
||||
# at an address in the low 2^16 bytes.
|
||||
#
|
||||
# Startothers (in main.c) sends the STARTUPs one at a time.
|
||||
# It copies this code (start) at 0x7000. It puts the address of
|
||||
# a newly allocated per-core stack in start-4,the address of the
|
||||
# place to jump to (mpenter) in start-8, and the physical address
|
||||
# of entrypgdir in start-12.
|
||||
#
|
||||
# This code is identical to bootasm.S except:
|
||||
# - it does not need to enable A20
|
||||
# - it uses the address at start-4, start-8, and start-12
|
||||
|
||||
.code16
|
||||
.global start
|
||||
start:
|
||||
cli
|
||||
|
||||
xorw %ax,%ax
|
||||
movw %ax,%ds
|
||||
movw %ax,%es
|
||||
movw %ax,%ss
|
||||
|
||||
lgdt gdtdesc
|
||||
movl %cr0, %eax
|
||||
orl $CR0_PE, %eax
|
||||
movl %eax, %cr0
|
||||
|
||||
ljmpl $(SEG_KCODE<<3), $(start32)
|
||||
//PAGEBREAK!
|
||||
|
||||
.code32
|
||||
start32:
|
||||
movw $(SEG_KDATA<<3), %ax
|
||||
movw %ax, %ds
|
||||
movw %ax, %es
|
||||
movw %ax, %ss
|
||||
movw $0, %ax
|
||||
movw %ax, %fs
|
||||
movw %ax, %gs
|
||||
|
||||
# defer paging until we switch to 64bit mode
|
||||
# set ebx=1 so shared boot code knows we're booting a secondary core
|
||||
mov $1, %ebx
|
||||
|
||||
# Switch to the stack allocated by startothers()
|
||||
movl (start-4), %esp
|
||||
# Call mpenter()
|
||||
call *(start-8)
|
||||
|
||||
movw $0x8a00, %ax
|
||||
movw %ax, %dx
|
||||
outw %ax, %dx
|
||||
movw $0x8ae0, %ax
|
||||
outw %ax, %dx
|
||||
spin:
|
||||
jmp spin
|
||||
|
||||
.p2align 2
|
||||
gdt:
|
||||
SEG_NULLASM
|
||||
SEG_ASM(STA_X|STA_R, 0, 0xffffffff)
|
||||
SEG_ASM(STA_W, 0, 0xffffffff)
|
||||
|
||||
|
||||
gdtdesc:
|
||||
.word (gdtdesc - gdt - 1)
|
||||
.long gdt
|
||||
102
kernel/arch/x86_64/ioapic.c
Normal file
102
kernel/arch/x86_64/ioapic.c
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// The I/O APIC manages hardware interrupts for an SMP system.
|
||||
// http://www.intel.com/design/chipsets/datashts/29056601.pdf
|
||||
// See also picirq.c.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "traps.h"
|
||||
#include "memlayout.h"
|
||||
|
||||
#define IOAPIC 0xFEC00000 // Default physical address of IO APIC
|
||||
|
||||
#define REG_ID 0x00 // Register index: ID
|
||||
#define REG_VER 0x01 // Register index: version
|
||||
#define REG_TABLE 0x10 // Redirection table base
|
||||
|
||||
// The redirection table starts at REG_TABLE and uses
|
||||
// two registers to configure each interrupt.
|
||||
// The first (low) register in a pair contains configuration bits.
|
||||
// The second (high) register contains a bitmask telling which
|
||||
// CPUs can serve that interrupt.
|
||||
#define INT_DISABLED 0x00010000 // Interrupt disabled
|
||||
#define INT_LEVEL 0x00008000 // Level-triggered (vs edge-)
|
||||
#define INT_ACTIVELOW 0x00002000 // Active low (vs high)
|
||||
#define INT_LOGICAL 0x00000800 // Destination is CPU id (vs APIC ID)
|
||||
|
||||
volatile struct ioapic *ioapic;
|
||||
|
||||
// IO APIC MMIO structure: write reg, then read or write data.
|
||||
struct ioapic {
|
||||
uint reg;
|
||||
uint pad[3];
|
||||
uint data;
|
||||
};
|
||||
|
||||
static uint
|
||||
ioapicread(int reg)
|
||||
{
|
||||
ioapic->reg = reg;
|
||||
return ioapic->data;
|
||||
}
|
||||
|
||||
static void
|
||||
ioapicwrite(int reg, uint data)
|
||||
{
|
||||
ioapic->reg = reg;
|
||||
ioapic->data = data;
|
||||
}
|
||||
|
||||
void
|
||||
ioapicinit(void)
|
||||
{
|
||||
int i, id, maxintr;
|
||||
|
||||
ioapic = P2V((volatile struct ioapic*)IOAPIC);
|
||||
maxintr = (ioapicread(REG_VER) >> 16) & 0xFF;
|
||||
id = ioapicread(REG_ID) >> 24;
|
||||
if(id != ioapicid)
|
||||
cprintf("ioapicinit: id isn't equal to ioapicid; not a MP\n");
|
||||
|
||||
// Mark all interrupts edge-triggered, active high, disabled,
|
||||
// and not routed to any CPUs.
|
||||
for(i = 0; i <= maxintr; i++){
|
||||
ioapicwrite(REG_TABLE+2*i, INT_DISABLED | (T_IRQ0 + i));
|
||||
ioapicwrite(REG_TABLE+2*i+1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Route an interrupt to a CPU and unmask it.
|
||||
//
|
||||
// `level_low` picks PCI's signalling instead of ISA's. This distinction did
|
||||
// not matter while every interrupt in the kernel came from an ISA-era device
|
||||
// at a fixed pin, all of which are edge-triggered and active high -- which is
|
||||
// why the original ioapicenable() hardcoded exactly that. A PCI device wired
|
||||
// through an INTx pin is the opposite on both counts, and configuring it as
|
||||
// edge/high does not fail cleanly: the device raises the line, we take one
|
||||
// interrupt, and because the level never goes away there is no second edge
|
||||
// and the device is never heard from again.
|
||||
void
|
||||
ioapicroute(int irq, int cpunum, int level_low)
|
||||
{
|
||||
uint lo = T_IRQ0 + irq;
|
||||
|
||||
if(level_low)
|
||||
lo |= INT_LEVEL | INT_ACTIVELOW;
|
||||
|
||||
// Program the destination before unmasking, so the entry is never briefly
|
||||
// live pointing at CPU 0 when it was meant for another.
|
||||
ioapicwrite(REG_TABLE+2*irq+1, cpunum << 24);
|
||||
ioapicwrite(REG_TABLE+2*irq, lo);
|
||||
}
|
||||
|
||||
void
|
||||
ioapicmask(int irq)
|
||||
{
|
||||
ioapicwrite(REG_TABLE+2*irq, INT_DISABLED | (T_IRQ0 + irq));
|
||||
}
|
||||
|
||||
void
|
||||
ioapicenable(int irq, int cpunum)
|
||||
{
|
||||
ioapicroute(irq, cpunum, 0);
|
||||
}
|
||||
245
kernel/arch/x86_64/lapic.c
Normal file
245
kernel/arch/x86_64/lapic.c
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
// The local APIC manages internal (non-I/O) interrupts.
|
||||
// See Chapter 8 & Appendix C of Intel processor manual volume 3.
|
||||
|
||||
#include "param.h"
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "date.h"
|
||||
#include "memlayout.h"
|
||||
#include "traps.h"
|
||||
#include "mmu.h"
|
||||
#include "x86.h"
|
||||
#include "proc.h" // ncpu
|
||||
|
||||
// Local APIC registers, divided by 4 for use as uint[] indices.
|
||||
#define ID (0x0020/4) // ID
|
||||
#define VER (0x0030/4) // Version
|
||||
#define TPR (0x0080/4) // Task Priority
|
||||
#define EOI (0x00B0/4) // EOI
|
||||
#define SVR (0x00F0/4) // Spurious Interrupt Vector
|
||||
#define ENABLE 0x00000100 // Unit Enable
|
||||
#define ESR (0x0280/4) // Error Status
|
||||
#define ICRLO (0x0300/4) // Interrupt Command
|
||||
#define SINIT 0x00000500 // INIT/RESET
|
||||
#define STARTUP 0x00000600 // Startup IPI
|
||||
#define DELIVS 0x00001000 // Delivery status
|
||||
#define ASSERT 0x00004000 // Assert interrupt (vs deassert)
|
||||
#define LEVEL 0x00008000 // Level triggered
|
||||
#define BCAST 0x00080000 // Send to all APICs, including self.
|
||||
#define BUSY 0x00001000
|
||||
#define ICRHI (0x0310/4) // Interrupt Command [63:32]
|
||||
#define TIMER (0x0320/4) // Local Vector Table 0 (TIMER)
|
||||
#define X1 0x0000000B // divide counts by 1
|
||||
#define PERIODIC 0x00020000 // Periodic
|
||||
#define PCINT (0x0340/4) // Performance Counter LVT
|
||||
#define LINT0 (0x0350/4) // Local Vector Table 1 (LINT0)
|
||||
#define LINT1 (0x0360/4) // Local Vector Table 2 (LINT1)
|
||||
#define ERROR (0x0370/4) // Local Vector Table 3 (ERROR)
|
||||
#define MASKED 0x00010000 // Interrupt masked
|
||||
#define TICR (0x0380/4) // Timer Initial Count
|
||||
#define TCCR (0x0390/4) // Timer Current Count
|
||||
#define TDCR (0x03E0/4) // Timer Divide Configuration
|
||||
|
||||
volatile uint *lapic; // Initialized in mp.c
|
||||
|
||||
static void
|
||||
lapicw(int index, int value)
|
||||
{
|
||||
lapic[index] = value;
|
||||
lapic[ID]; // wait for write to finish, by reading
|
||||
}
|
||||
|
||||
void
|
||||
lapicinit(void)
|
||||
{
|
||||
if(!lapic)
|
||||
return;
|
||||
|
||||
// Enable local APIC; set spurious interrupt vector.
|
||||
lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS));
|
||||
|
||||
// The timer repeatedly counts down at bus frequency
|
||||
// from lapic[TICR] and then issues an interrupt.
|
||||
// If xv6 cared more about precise timekeeping,
|
||||
// TICR would be calibrated using an external time source.
|
||||
lapicw(TDCR, X1);
|
||||
lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));
|
||||
lapicw(TICR, 10000000);
|
||||
|
||||
// Disable logical interrupt lines.
|
||||
lapicw(LINT0, MASKED);
|
||||
lapicw(LINT1, MASKED);
|
||||
|
||||
// Disable performance counter overflow interrupts
|
||||
// on machines that provide that interrupt entry.
|
||||
if(((lapic[VER]>>16) & 0xFF) >= 4)
|
||||
lapicw(PCINT, MASKED);
|
||||
|
||||
// Map error interrupt to IRQ_ERROR.
|
||||
lapicw(ERROR, T_IRQ0 + IRQ_ERROR);
|
||||
|
||||
// Clear error status register (requires back-to-back writes).
|
||||
lapicw(ESR, 0);
|
||||
lapicw(ESR, 0);
|
||||
|
||||
// Ack any outstanding interrupts.
|
||||
lapicw(EOI, 0);
|
||||
|
||||
// Send an Init Level De-Assert to synchronise arbitration ID's.
|
||||
lapicw(ICRHI, 0);
|
||||
lapicw(ICRLO, BCAST | SINIT | LEVEL);
|
||||
while(lapic[ICRLO] & DELIVS)
|
||||
;
|
||||
|
||||
// Enable interrupts on the APIC (but not on the processor).
|
||||
lapicw(TPR, 0);
|
||||
}
|
||||
|
||||
int
|
||||
cpunum(void)
|
||||
{
|
||||
int apicid, i;
|
||||
|
||||
// Cannot call cpu when interrupts are enabled:
|
||||
// result not guaranteed to last long enough to be used!
|
||||
// Would prefer to panic but even printing is chancy here:
|
||||
// almost everything, including cprintf and panic, calls cpu,
|
||||
// often indirectly through acquire and release.
|
||||
if(readeflags()&FL_IF){
|
||||
static int n;
|
||||
if(n++ == 0)
|
||||
cprintf("cpu called from %x with interrupts enabled\n",
|
||||
__builtin_return_address(0));
|
||||
}
|
||||
|
||||
if (!lapic)
|
||||
return 0;
|
||||
|
||||
apicid = lapic[ID] >> 24;
|
||||
for (i = 0; i < ncpu; ++i) {
|
||||
if (cpus[i].apicid == apicid)
|
||||
return i;
|
||||
}
|
||||
panic("unknown apicid\n");
|
||||
}
|
||||
|
||||
// Acknowledge interrupt.
|
||||
void
|
||||
lapiceoi(void)
|
||||
{
|
||||
if(lapic)
|
||||
lapicw(EOI, 0);
|
||||
}
|
||||
|
||||
// Spin for a given number of microseconds.
|
||||
// On real hardware would want to tune this dynamically.
|
||||
void
|
||||
microdelay(int us)
|
||||
{
|
||||
}
|
||||
|
||||
#define CMOS_PORT 0x70
|
||||
#define CMOS_RETURN 0x71
|
||||
|
||||
// Start additional processor running entry code at addr.
|
||||
// See Appendix B of MultiProcessor Specification.
|
||||
void
|
||||
lapicstartap(uchar apicid, uint addr)
|
||||
{
|
||||
int i;
|
||||
ushort *wrv;
|
||||
|
||||
// "The BSP must initialize CMOS shutdown code to 0AH
|
||||
// and the warm reset vector (DWORD based at 40:67) to point at
|
||||
// the AP startup code prior to the [universal startup algorithm]."
|
||||
outb(CMOS_PORT, 0xF); // offset 0xF is shutdown code
|
||||
outb(CMOS_PORT+1, 0x0A);
|
||||
wrv = (ushort*)P2V((0x40<<4 | 0x67)); // Warm reset vector
|
||||
wrv[0] = 0;
|
||||
wrv[1] = addr >> 4;
|
||||
|
||||
// "Universal startup algorithm."
|
||||
// Send INIT (level-triggered) interrupt to reset other CPU.
|
||||
lapicw(ICRHI, apicid<<24);
|
||||
lapicw(ICRLO, SINIT | LEVEL | ASSERT);
|
||||
microdelay(200);
|
||||
lapicw(ICRLO, SINIT | LEVEL);
|
||||
microdelay(100); // should be 10ms, but too slow in Bochs!
|
||||
|
||||
// Send startup IPI (twice!) to enter code.
|
||||
// Regular hardware is supposed to only accept a STARTUP
|
||||
// when it is in the halted state due to an INIT. So the second
|
||||
// should be ignored, but it is part of the official Intel algorithm.
|
||||
// Bochs complains about the second one. Too bad for Bochs.
|
||||
for(i = 0; i < 2; i++){
|
||||
lapicw(ICRHI, apicid<<24);
|
||||
lapicw(ICRLO, STARTUP | (addr>>12));
|
||||
microdelay(200);
|
||||
}
|
||||
}
|
||||
|
||||
#define CMOS_STATA 0x0a
|
||||
#define CMOS_STATB 0x0b
|
||||
#define CMOS_UIP (1 << 7) // RTC update in progress
|
||||
|
||||
#define SECS 0x00
|
||||
#define MINS 0x02
|
||||
#define HOURS 0x04
|
||||
#define DAY 0x07
|
||||
#define MONTH 0x08
|
||||
#define YEAR 0x09
|
||||
|
||||
static uint cmos_read(uint reg)
|
||||
{
|
||||
outb(CMOS_PORT, reg);
|
||||
microdelay(200);
|
||||
|
||||
return inb(CMOS_RETURN);
|
||||
}
|
||||
|
||||
static void fill_rtcdate(struct rtcdate *r)
|
||||
{
|
||||
r->second = cmos_read(SECS);
|
||||
r->minute = cmos_read(MINS);
|
||||
r->hour = cmos_read(HOURS);
|
||||
r->day = cmos_read(DAY);
|
||||
r->month = cmos_read(MONTH);
|
||||
r->year = cmos_read(YEAR);
|
||||
}
|
||||
//PAGEBREAK!
|
||||
|
||||
// qemu seems to use 24-hour GWT and the values are BCD encoded
|
||||
void cmostime(struct rtcdate *r)
|
||||
{
|
||||
struct rtcdate t1, t2;
|
||||
int sb, bcd;
|
||||
|
||||
sb = cmos_read(CMOS_STATB);
|
||||
|
||||
bcd = (sb & (1 << 2)) == 0;
|
||||
|
||||
// make sure CMOS doesn't modify time while we read it
|
||||
for(;;) {
|
||||
fill_rtcdate(&t1);
|
||||
if(cmos_read(CMOS_STATA) & CMOS_UIP)
|
||||
continue;
|
||||
fill_rtcdate(&t2);
|
||||
if(memcmp(&t1, &t2, sizeof(t1)) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// convert
|
||||
if(bcd) {
|
||||
#define CONV(x) (t1.x = ((t1.x >> 4) * 10) + (t1.x & 0xf))
|
||||
CONV(second);
|
||||
CONV(minute);
|
||||
CONV(hour );
|
||||
CONV(day );
|
||||
CONV(month );
|
||||
CONV(year );
|
||||
#undef CONV
|
||||
}
|
||||
|
||||
*r = t1;
|
||||
r->year += 2000;
|
||||
}
|
||||
159
kernel/arch/x86_64/mp.c
Normal file
159
kernel/arch/x86_64/mp.c
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
// Multiprocessor support
|
||||
// Search memory for MP description structures.
|
||||
// http://developer.intel.com/design/pentium/datashts/24201606.pdf
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mp.h"
|
||||
#include "x86.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
|
||||
struct cpu cpus[NCPU];
|
||||
// Processors the firmware says exist.
|
||||
int ncpu;
|
||||
|
||||
// Processors actually running, which is not the same number and must not be
|
||||
// confused with it. Enumeration is cheap and startup is where the difficulty
|
||||
// is: right now the APs are found and deliberately not started, so this stays
|
||||
// at 1 while ncpu is 2, 4 or 16.
|
||||
//
|
||||
// /proc/cpuinfo reports *this* one, matching Linux, where cpuinfo lists online
|
||||
// CPUs. Reporting ncpu there told a VM with four processors that it had four
|
||||
// working ones, which was a comfortable thing to read and false.
|
||||
int ncpu_online = 1;
|
||||
uchar ioapicid;
|
||||
|
||||
static uchar
|
||||
sum(uchar *addr, int len)
|
||||
{
|
||||
int i, sum;
|
||||
|
||||
sum = 0;
|
||||
for(i=0; i<len; i++)
|
||||
sum += addr[i];
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Look for an MP structure in the len bytes at addr.
|
||||
static struct mp*
|
||||
mpsearch1(addr_t a, int len)
|
||||
{
|
||||
uchar *e, *p, *addr;
|
||||
addr = P2V(a);
|
||||
e = addr+len;
|
||||
for(p = addr; p < e; p += sizeof(struct mp))
|
||||
if(memcmp(p, "_MP_", 4) == 0 && sum(p, sizeof(struct mp)) == 0)
|
||||
return (struct mp*)p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Search for the MP Floating Pointer Structure, which according to the
|
||||
// spec is in one of the following three locations:
|
||||
// 1) in the first KB of the EBDA;
|
||||
// 2) in the last KB of system base memory;
|
||||
// 3) in the BIOS ROM between 0xE0000 and 0xFFFFF.
|
||||
static struct mp*
|
||||
mpsearch(void)
|
||||
{
|
||||
uchar *bda;
|
||||
uint p;
|
||||
struct mp *mp;
|
||||
|
||||
bda = (uchar *) P2V(0x400);
|
||||
if((p = ((bda[0x0F]<<8)| bda[0x0E]) << 4)){
|
||||
if((mp = mpsearch1(p, 1024)))
|
||||
return mp;
|
||||
} else {
|
||||
p = ((bda[0x14]<<8)|bda[0x13])*1024;
|
||||
if((mp = mpsearch1(p-1024, 1024)))
|
||||
return mp;
|
||||
}
|
||||
return mpsearch1(0xF0000, 0x10000);
|
||||
}
|
||||
|
||||
// Search for an MP configuration table. For now,
|
||||
// don't accept the default configurations (physaddr == 0).
|
||||
// Check for correct signature, calculate the checksum and,
|
||||
// if correct, check the version.
|
||||
// To do: check extended table checksum.
|
||||
static struct mpconf*
|
||||
mpconfig(struct mp **pmp)
|
||||
{
|
||||
struct mpconf *conf;
|
||||
struct mp *mp;
|
||||
|
||||
if((mp = mpsearch()) == 0 || mp->physaddr == 0)
|
||||
return 0;
|
||||
conf = (struct mpconf*) P2V((addr_t) mp->physaddr);
|
||||
if(memcmp(conf, "PCMP", 4) != 0)
|
||||
return 0;
|
||||
if(conf->version != 1 && conf->version != 4)
|
||||
return 0;
|
||||
if(sum((uchar*)conf, conf->length) != 0)
|
||||
return 0;
|
||||
*pmp = mp;
|
||||
return conf;
|
||||
}
|
||||
|
||||
void
|
||||
mpinit(void)
|
||||
{
|
||||
uchar *p, *e;
|
||||
struct mp *mp;
|
||||
struct mpconf *conf;
|
||||
struct mpproc *mproc;
|
||||
struct mpioapic *ioapic;
|
||||
|
||||
if((conf = mpconfig(&mp)) == 0) {
|
||||
cprintf("No other CPUs found.\n");
|
||||
return;
|
||||
}
|
||||
lapic = (uint*)P2V(conf->lapicaddr_p);
|
||||
for(p=(uchar*)(conf+1), e=(uchar*)conf+conf->length; p<e; ){
|
||||
switch(*p){
|
||||
case MPPROC:
|
||||
mproc = (struct mpproc*)p;
|
||||
if(ncpu < NCPU) {
|
||||
cpus[ncpu].apicid = mproc->apicid; // apicid may differ from ncpu
|
||||
ncpu++;
|
||||
}
|
||||
p += sizeof(struct mpproc);
|
||||
continue;
|
||||
case MPIOAPIC:
|
||||
ioapic = (struct mpioapic*)p;
|
||||
ioapicid = ioapic->apicno;
|
||||
p += sizeof(struct mpioapic);
|
||||
continue;
|
||||
case MPBUS:
|
||||
case MPIOINTR:
|
||||
case MPLINTR:
|
||||
p += 8;
|
||||
continue;
|
||||
default:
|
||||
panic("Major problem parsing mp config.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
// "Seems we are" is doing a lot of work here, and the number is usually
|
||||
// wrong. This walks the *legacy Intel MP table* (1994), which modern firmware
|
||||
// fills in minimally or not at all -- QEMU/SeaBIOS emits exactly one
|
||||
// processor entry regardless of -smp, because it expects the OS to enumerate
|
||||
// CPUs from the ACPI MADT instead. Verified 2026-08-08: with -smp 4 the table
|
||||
// is 208 bytes and contains one type-0 entry, then buses, an I/O APIC and
|
||||
// interrupt assignments. So ncpu is 1 on QEMU no matter what, and SMP is
|
||||
// silently never exercised there.
|
||||
//
|
||||
// See docs/roadmap.md "Known broken" -- this is half of the SMP bug, and the
|
||||
// reason the other half (AP startup triple-faulting) cannot be reproduced
|
||||
// under QEMU until the MADT is parsed.
|
||||
cprintf("Seems we are SMP, ncpu = %d\n",ncpu);
|
||||
if(mp->imcrp){
|
||||
// Bochs doesn't support IMCR, so this doesn't run on Bochs.
|
||||
// But it would on real hardware.
|
||||
outb(0x22, 0x70); // Select IMCR
|
||||
outb(0x23, inb(0x23) | 1); // Mask external interrupts.
|
||||
}
|
||||
}
|
||||
30
kernel/arch/x86_64/swtch.S
Normal file
30
kernel/arch/x86_64/swtch.S
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Context switch
|
||||
#
|
||||
# void swtch(struct context **old, struct context *new);
|
||||
#
|
||||
# Save current register context in old
|
||||
# and then load register context from new.
|
||||
|
||||
.global swtch
|
||||
swtch:
|
||||
# Save old callee-save registers
|
||||
pushq %rbp
|
||||
pushq %rbx
|
||||
pushq %r12
|
||||
pushq %r13
|
||||
pushq %r14
|
||||
pushq %r15
|
||||
|
||||
# Switch stacks
|
||||
movq %rsp, (%rdi)
|
||||
movq %rsi, %rsp
|
||||
|
||||
# Load new callee-save registers
|
||||
popq %r15
|
||||
popq %r14
|
||||
popq %r13
|
||||
popq %r12
|
||||
popq %rbx
|
||||
popq %rbp
|
||||
|
||||
retq #??
|
||||
159
kernel/arch/x86_64/trap.c
Normal file
159
kernel/arch/x86_64/trap.c
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "irq.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
#include "traps.h"
|
||||
#include "spinlock.h"
|
||||
#include "posix.h"
|
||||
|
||||
uint *idt;
|
||||
extern addr_t vectors[];
|
||||
struct spinlock tickslock;
|
||||
uint ticks;
|
||||
|
||||
static void
|
||||
mkgate(uint *idt, uint n, addr_t kva, uint pl)
|
||||
{
|
||||
uint64 addr = (uint64) kva;
|
||||
|
||||
n *= 4;
|
||||
idt[n+0] = (addr & 0xFFFF) | (KERNEL_CS << 16);
|
||||
idt[n+1] = (addr & 0xFFFF0000) | 0x8E00 | ((pl & 3) << 13);
|
||||
idt[n+2] = addr >> 32;
|
||||
idt[n+3] = 0;
|
||||
}
|
||||
|
||||
void idtinit(void)
|
||||
{
|
||||
struct {
|
||||
ushort limit;
|
||||
uint64 base;
|
||||
} __attribute__((packed)) idtdesc;
|
||||
|
||||
idtdesc.limit = PGSIZE - 1;
|
||||
idtdesc.base = (uint64)idt;
|
||||
|
||||
asm volatile("lidt %0" : : "m" (idtdesc));
|
||||
}
|
||||
|
||||
void tvinit(void)
|
||||
{
|
||||
int n;
|
||||
idt = (uint*) kalloc();
|
||||
memset(idt, 0, PGSIZE);
|
||||
for (n = 0; n < 256; n++)
|
||||
mkgate(idt, n, vectors[n], 0);
|
||||
mkgate(idt, T_SYSCALL, vectors[T_SYSCALL], 3);
|
||||
}
|
||||
|
||||
uint
|
||||
uptime(void)
|
||||
{
|
||||
uint xticks;
|
||||
|
||||
acquire(&tickslock);
|
||||
xticks = ticks;
|
||||
release(&tickslock);
|
||||
return xticks;
|
||||
}
|
||||
|
||||
// Which signal a CPU fault corresponds to, so a process killed by a fault is
|
||||
// reported to wait() as died-by-signal with a sensible cause rather than a
|
||||
// blanket SIGKILL.
|
||||
static int
|
||||
trapsig(uint64 trapno)
|
||||
{
|
||||
switch(trapno){
|
||||
case T_DIVIDE: return SIGFPE;
|
||||
case T_ILLOP: return SIGILL;
|
||||
case T_GPFLT: return SIGSEGV;
|
||||
case T_PGFLT: return SIGSEGV;
|
||||
case T_BRKPT: return SIGTRAP;
|
||||
case T_DEBUG: return SIGTRAP;
|
||||
default: return SIGSEGV;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
trap(struct trapframe *tf)
|
||||
{
|
||||
kstack_check_canary("trap entry");
|
||||
switch(tf->trapno){
|
||||
case T_IRQ0 + IRQ_TIMER:
|
||||
if(cpunum() == 0){
|
||||
acquire(&tickslock);
|
||||
ticks++;
|
||||
wakeup(&ticks);
|
||||
release(&tickslock);
|
||||
}
|
||||
lapiceoi();
|
||||
break;
|
||||
case T_IRQ0 + IRQ_SPURIOUS:
|
||||
cprintf("cpu%d: spurious interrupt at %p:%p\n",
|
||||
cpunum(), tf->cs, tf->rip);
|
||||
lapiceoi();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Every other IOAPIC pin goes through the registration table. The device
|
||||
// interrupts used to be listed here one case at a time, which cannot work
|
||||
// for anything found on the PCI bus: a driver does not know its line
|
||||
// until it reads the device's config space at probe time, so there is no
|
||||
// constant to write a case label for. See include/irq.h.
|
||||
if(tf->trapno >= T_IRQ0 && tf->trapno < T_IRQ0 + NIRQ){
|
||||
irq_dispatch(tf->trapno - T_IRQ0);
|
||||
lapiceoi();
|
||||
break;
|
||||
}
|
||||
|
||||
if(myproc() == 0 || (tf->cs&3) == 0){
|
||||
cprintf("unexpected trap %d from cpu %d rip %p (cr2=0x%p) err=0x%p\n",
|
||||
tf->trapno, cpunum(), tf->rip, rcr2(), tf->err);
|
||||
if (myproc())
|
||||
cprintf("proc id: %d\n", myproc()->pid);
|
||||
if (tf->trapno == T_GPFLT) {
|
||||
uint64 *iretframe = (uint64*)tf->rsp;
|
||||
cprintf("iretq frame @ %p: rip=%p cs=%p rflags=%p rsp=%p ss=%p\n",
|
||||
iretframe, iretframe[0], iretframe[1], iretframe[2],
|
||||
iretframe[3], iretframe[4]);
|
||||
if (myproc())
|
||||
cprintf("this tf=%p myproc()->tf=%p myproc()->kstack=%p (tf-kstack=%p)\n",
|
||||
tf, myproc()->tf, myproc()->kstack, (uint64)tf - (uint64)myproc()->kstack);
|
||||
}
|
||||
panic("trap");
|
||||
}
|
||||
cprintf("pid %d %s: trap %d err %d on cpu %d "
|
||||
"rip 0x%p addr 0x%p--kill proc\n",
|
||||
myproc()->pid, myproc()->name, tf->trapno, tf->err, cpunum(), tf->rip,
|
||||
rcr2());
|
||||
// General-purpose register dump. This used to be followed by ~25 lines
|
||||
// that walked musl's mallocng group/meta structures out of rax/rdi and
|
||||
// dumped the user stack -- scaffolding for the boot-crash investigation
|
||||
// recorded in docs/architecture/kernel-history.md section 1. That
|
||||
// investigation is closed and the underlying kernel bugs are fixed, so
|
||||
// the allocator-specific part is gone; the registers stay, since they are
|
||||
// useful for any fault.
|
||||
if (tf->trapno == T_GPFLT) {
|
||||
cprintf(" rax=%p rbx=%p rcx=%p rdx=%p rsi=%p rdi=%p rbp=%p rsp=%p\n",
|
||||
tf->rax, tf->rbx, tf->rcx, tf->rdx, tf->rsi, tf->rdi, tf->rbp, tf->rsp);
|
||||
}
|
||||
myproc()->killed = 1;
|
||||
if(myproc()->killsig == 0)
|
||||
myproc()->killsig = trapsig(tf->trapno);
|
||||
}
|
||||
|
||||
if(myproc() && myproc()->killed && (tf->cs&3) == DPL_USER)
|
||||
exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL));
|
||||
|
||||
if(myproc() && myproc()->state == RUNNING && tf->trapno == T_IRQ0+IRQ_TIMER)
|
||||
yield();
|
||||
|
||||
if(myproc() && myproc()->killed && (tf->cs&3) == DPL_USER)
|
||||
exit(W_EXITCODE(0, myproc()->killsig ? myproc()->killsig : SIGKILL));
|
||||
|
||||
kstack_check_canary("trap exit");
|
||||
}
|
||||
257
kernel/arch/x86_64/trapasm.S
Normal file
257
kernel/arch/x86_64/trapasm.S
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
# trapasm.S — kernel/user entry/exit with FS_BASE swapping for TLS
|
||||
#
|
||||
# The kernel uses __thread struct cpu *cpu and __thread struct proc *proc,
|
||||
# accessed via %fs (IA32_FS_BASE MSR, 0xC0000100). seginit() sets this to
|
||||
# kernel_fs_base. User-space TLS (musl) uses the same MSR via arch_prctl
|
||||
# ARCH_SET_FS (saved in proc->fs_base).
|
||||
#
|
||||
# Rule: on ENTRY from user (syscall or interrupt), restore kernel FS_BASE.
|
||||
# on EXIT to user (sysretq or iretq), write proc->fs_base to MSR.
|
||||
#
|
||||
# proc->fs_base is at byte offset 0x18 (24) inside struct proc.
|
||||
# proc pointer is at %gs:24 (PERCPU_PROC in include/proc.h); cpu is at %gs:16.
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Helper macro: write kernel_fs_base to IA32_FS_BASE.
|
||||
# Clobbers %rax, %rcx, %rdx. Caller must save them if needed.
|
||||
# -----------------------------------------------------------------------
|
||||
# Read from %gs, not from a global.
|
||||
#
|
||||
# This used to be `movq kernel_fs_base(%rip), %rax`, and kernel_fs_base was a
|
||||
# single global that every CPU wrote during seginit(). With one processor that
|
||||
# is merely untidy. With two it is fatal: whichever CPU ran seginit() last wins,
|
||||
# and from the first trap onward *every* CPU restores that one CPU's per-CPU
|
||||
# block -- so `cpu` and `proc` are shared, pushcli/popcli nesting counts collide,
|
||||
# and the kernel dies in panic("sched locks") shortly after the APs start.
|
||||
#
|
||||
# GS base is set per CPU by seginit() to the same value as FS base, and %gs:0
|
||||
# holds the block's self-pointer. So this reads *this* CPU's kernel FS base with
|
||||
# no global involved.
|
||||
#
|
||||
# No swapgs, deliberately. The usual reason for it is that userspace owns GS;
|
||||
# here it does not -- sys_arch_prctl() implements only ARCH_SET_FS/ARCH_GET_FS
|
||||
# and rejects everything else, musl uses FS for TLS on x86-64, and the only
|
||||
# other writes to %gs are selector loads in entry.S and entryother.S that happen
|
||||
# before seginit(). GS is the kernel's alone, so it can simply stay pointing at
|
||||
# the per-CPU block. If ARCH_SET_GS is ever implemented, this must become a
|
||||
# proper swapgs pair.
|
||||
.macro RESTORE_KERNEL_FS
|
||||
movq %gs:0, %rax
|
||||
movq %rax, %rdx
|
||||
shrq $32, %rdx
|
||||
movl $0xC0000100, %ecx
|
||||
wrmsr
|
||||
.endm
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Helper macro: write proc->fs_base to IA32_FS_BASE.
|
||||
# Requires %fs to still hold the kernel base.
|
||||
# Clobbers %rax, %rcx, %rdx. Caller must save them if needed.
|
||||
# -----------------------------------------------------------------------
|
||||
.macro WRITE_USER_FS
|
||||
# proc, from the per-CPU block at %gs:24 (PERCPU_PROC in include/proc.h).
|
||||
# This used to be %fs:(-8), the __thread slot, which no longer exists --
|
||||
# see the comment in include/proc.h for why those stopped being __thread.
|
||||
movq %gs:24, %rax # proc pointer
|
||||
movq 0x18(%rax), %rax # proc->fs_base (offset 24 = 0x18)
|
||||
movq %rax, %rdx
|
||||
shrq $32, %rdx
|
||||
movl $0xC0000100, %ecx
|
||||
wrmsr
|
||||
.endm
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# alltraps — IDT interrupt/fault entry
|
||||
# Stack on entry (from user mode, CPU has done privilege switch):
|
||||
# [rsp+0] trapno (pushed by vectors.S)
|
||||
# [rsp+8] errorcode
|
||||
# [rsp+16] rip
|
||||
# [rsp+24] cs ← bit 1:0 == 3 means user mode
|
||||
# [rsp+32] rflags
|
||||
# [rsp+40] user_rsp
|
||||
# [rsp+48] user_ss
|
||||
# -----------------------------------------------------------------------
|
||||
.global alltraps
|
||||
alltraps:
|
||||
# Always restore kernel FS_BASE before touching %fs:, even when the
|
||||
# saved cs says we were already in kernel mode. A fault/interrupt can
|
||||
# land in that narrow kernel-mode window after syscall_trapret/trapret
|
||||
# have already done WRITE_USER_FS (cs is still 0x8 there, but FS_BASE
|
||||
# is the *user* value) — the old cs-based skip trusted that combination
|
||||
# never happens and read %fs:(-8) as garbage (proc/cpu == NULL) in that
|
||||
# case. RESTORE_KERNEL_FS is just a wrmsr of an already-known value when
|
||||
# we truly were in the kernel, so doing it unconditionally is cheap and
|
||||
# always correct.
|
||||
pushq %rax
|
||||
pushq %rcx
|
||||
pushq %rdx
|
||||
RESTORE_KERNEL_FS
|
||||
popq %rdx
|
||||
popq %rcx
|
||||
popq %rax
|
||||
|
||||
# Build trap frame.
|
||||
pushq %r15
|
||||
pushq %r14
|
||||
pushq %r13
|
||||
pushq %r12
|
||||
pushq %r11
|
||||
pushq %r10
|
||||
pushq %r9
|
||||
pushq %r8
|
||||
pushq %rdi
|
||||
pushq %rsi
|
||||
pushq %rbp
|
||||
pushq %rdx
|
||||
pushq %rcx
|
||||
pushq %rbx
|
||||
pushq %rax
|
||||
|
||||
movq %rsp, %rdi # frame in arg1
|
||||
callq trap
|
||||
# Return falls through to trapret...
|
||||
|
||||
.global trapret
|
||||
trapret:
|
||||
popq %rax
|
||||
popq %rbx
|
||||
popq %rcx
|
||||
popq %rdx
|
||||
popq %rbp
|
||||
popq %rsi
|
||||
popq %rdi
|
||||
popq %r8
|
||||
popq %r9
|
||||
popq %r10
|
||||
popq %r11
|
||||
popq %r12
|
||||
popq %r13
|
||||
popq %r14
|
||||
popq %r15
|
||||
|
||||
addq $16, %rsp # discard trapno and errorcode
|
||||
|
||||
# If returning to user mode, write proc->fs_base to FS_BASE MSR.
|
||||
# Stack now: [rsp+0]=rip, [rsp+8]=cs, [rsp+16]=rflags, [rsp+24]=rsp, [rsp+32]=ss
|
||||
testb $3, 8(%rsp)
|
||||
jz .Ltrapret_kret
|
||||
|
||||
# cli before WRITE_USER_FS: close the same race as in syscall_trapret.
|
||||
# iretq will atomically restore RFLAGS (IF=1) when switching to user mode.
|
||||
pushq %rax
|
||||
pushq %rcx
|
||||
pushq %rdx
|
||||
cli
|
||||
WRITE_USER_FS
|
||||
popq %rdx
|
||||
popq %rcx
|
||||
popq %rax
|
||||
|
||||
.Ltrapret_kret:
|
||||
iretq
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# syscall_entry — SYSCALL instruction entry point
|
||||
# On entry: rcx=user rip, r11=user rflags, rsp=user stack, IF cleared.
|
||||
# FS_BASE may be the user's TLS value (set at last sysretq).
|
||||
# -----------------------------------------------------------------------
|
||||
.global syscall_entry
|
||||
syscall_entry:
|
||||
# Swap FS_BASE to kernel value BEFORE any %fs: access.
|
||||
# Save rax/rcx/rdx to single-CPU scratch globals (IF is off, no race).
|
||||
movq %rax, _sc_rax(%rip)
|
||||
movq %rcx, _sc_rcx(%rip) # user rip (SYSCALL stores it here)
|
||||
movq %rdx, _sc_rdx(%rip)
|
||||
RESTORE_KERNEL_FS # clobbers rax, rcx, rdx — that's OK
|
||||
movq _sc_rax(%rip), %rax
|
||||
movq _sc_rcx(%rip), %rcx
|
||||
movq _sc_rdx(%rip), %rdx
|
||||
|
||||
# Switch to kernel stack: store user rsp at top of proc->kstack.
|
||||
#
|
||||
# The scratch slot is %fs:(8), NOT %fs:(0). %fs:(0) is the TCB
|
||||
# self-pointer -- the x86-64 TLS ABI reserves the word at the thread
|
||||
# pointer to hold the thread pointer itself, and a compiler is entitled to
|
||||
# read it to materialise the TP before applying a variable's offset.
|
||||
#
|
||||
# It used to be %fs:(0), and that survived for years only because GCC
|
||||
# compiles `proc`/`cpu` into direct `%fs:-8` segment references and never
|
||||
# touches the word. clang at -O0 does `movq %fs:0, %rax; addq $-8, %rax;
|
||||
# (%rax)` instead -- so it read the syscall number this line had just
|
||||
# stored (7, SYS_exec, from initcode), computed 7-8 = -1, and page-faulted
|
||||
# on the very first syscall of the very first process.
|
||||
#
|
||||
# Positive offsets are safe: the TLS block occupies negative offsets
|
||||
# (-16 = cpu, -8 = proc) and seginit puts the thread pointer 2048 bytes
|
||||
# into a 4096-byte page, so %fs:(8) onward is 2040 bytes we own outright.
|
||||
movq %rax, %fs:(8) # save rax in per-CPU scratch slot
|
||||
movq %gs:24, %rax # proc pointer (PERCPU_PROC)
|
||||
movq 0x10(%rax), %rax # proc->kstack (offset 16)
|
||||
addq $(4096-16), %rax # point to tf->rsp slot
|
||||
movq %rsp, (%rax) # save user rsp
|
||||
movq %rax, %rsp # switch to kernel stack
|
||||
movq %fs:(8), %rax # restore rax
|
||||
|
||||
pushq %r11 # rflags
|
||||
pushq $0 # cs (ignored)
|
||||
pushq %rcx # rip (next user instruction)
|
||||
|
||||
pushq $0 # err
|
||||
pushq $0 # trapno (ignored)
|
||||
|
||||
pushq %r15
|
||||
pushq %r14
|
||||
pushq %r13
|
||||
pushq %r12
|
||||
pushq %r11
|
||||
pushq %r10
|
||||
pushq %r9
|
||||
pushq %r8
|
||||
pushq %rdi
|
||||
pushq %rsi
|
||||
pushq %rbp
|
||||
pushq %rdx
|
||||
pushq %rcx
|
||||
pushq %rbx
|
||||
pushq %rax
|
||||
|
||||
movq %rsp, %rdi # frame in arg1
|
||||
callq syscall
|
||||
# Return falls through to syscall_trapret...
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# syscall_trapret — return path for SYSCALL-based syscalls
|
||||
# -----------------------------------------------------------------------
|
||||
.global syscall_trapret
|
||||
syscall_trapret:
|
||||
# Disable interrupts BEFORE writing user FS_BASE to the MSR.
|
||||
# Without cli here, a timer IRQ between WRITE_USER_FS and the cli below
|
||||
# would arrive in ring-0 with user FS_BASE set, causing %fs:(-8) to return
|
||||
# garbage (proc=NULL) → panic in the interrupt handler.
|
||||
# rax/rcx/rdx are clobbered by WRITE_USER_FS then restored by the pops.
|
||||
cli
|
||||
WRITE_USER_FS
|
||||
|
||||
popq %rax
|
||||
popq %rbx
|
||||
popq %rcx
|
||||
popq %rdx
|
||||
popq %rbp
|
||||
popq %rsi
|
||||
popq %rdi
|
||||
popq %r8
|
||||
popq %r9
|
||||
popq %r10
|
||||
popq %r11
|
||||
popq %r12
|
||||
popq %r13
|
||||
popq %r14
|
||||
popq %r15
|
||||
|
||||
addq $40, %rsp # discard trapno, errorcode, rip, cs, rflags
|
||||
|
||||
# Mask interrupts: sysretq is unsafe with IF set on non-canonical RSP.
|
||||
# See https://www.felixcloutier.com/x86/sysret
|
||||
cli
|
||||
movq (%rsp), %rsp # restore the user stack
|
||||
sysretq
|
||||
1281
kernel/arch/x86_64/vectors.S
Normal file
1281
kernel/arch/x86_64/vectors.S
Normal file
File diff suppressed because it is too large
Load diff
631
kernel/arch/x86_64/vm.c
Normal file
631
kernel/arch/x86_64/vm.c
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
#include "param.h"
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "x86.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
|
||||
int mappages(pml4e_t *pgdir, void *va, addr_t size, addr_t pa, int perm);
|
||||
static pte_t * walkpgdir(pml4e_t *pml4, const void *va, int alloc);
|
||||
|
||||
|
||||
// `cpu` and `proc` are no longer __thread variables; they live at fixed offsets
|
||||
// in the per-CPU block and are read through %gs at each use. See the long
|
||||
// comment in include/proc.h for why -- the short version is that __thread means
|
||||
// per *thread*, the compiler caches the thread pointer across calls on that
|
||||
// basis, and a kernel thread changes CPU at every sched().
|
||||
|
||||
// Kernel FS_BASE set by seginit(); read by trapasm.S to restore after user TLS
|
||||
uint64 kernel_fs_base;
|
||||
// Single-CPU scratch for syscall_entry FS swap (safe while IF cleared by SYSCALL/SFMASK)
|
||||
uint64 _sc_rax, _sc_rcx, _sc_rdx;
|
||||
|
||||
static pml4e_t *kpml4;
|
||||
static pdpe_t *kpdpt;
|
||||
|
||||
struct tassdesc {
|
||||
uint lim_15_0 : 16;
|
||||
uint base_15_0 : 16;
|
||||
uint base_23_16 : 8;
|
||||
uint type : 4;
|
||||
uint s : 1;
|
||||
uint dpl : 2;
|
||||
uint p : 1;
|
||||
uint lim_19_16 : 4;
|
||||
uint avl : 1;
|
||||
uint l : 1;
|
||||
uint d : 1;
|
||||
uint g : 1;
|
||||
uint base_31_24 : 8;
|
||||
uint32 base_63_32;
|
||||
uint32 res;
|
||||
};
|
||||
|
||||
void
|
||||
syscallinit(void)
|
||||
{
|
||||
// SYSRETQ does NOT use USER_CS/USER_DS directly. Per the AMD64/Intel
|
||||
// convention, it computes CS = STAR[63:48]+16 and SS = STAR[63:48]+8
|
||||
// (RPL forced to 3), so STAR[63:48] must hold the *compat* 32-bit user
|
||||
// code selector (SEG_UCODE32), relying on the GDT's consecutive
|
||||
// [UCODE32, UDATA, UCODE] layout — NOT USER_CS itself. Using USER_CS
|
||||
// here made every sysretq load CS=USER_CS+16 (an invalid selector,
|
||||
// pointing past UCODE into the TSS descriptor) and SS=USER_CS+8. QEMU's
|
||||
// TCG apparently doesn't validate segment descriptors on SYSRET, so this
|
||||
// went unnoticed until an interrupt forced a strictly-validated iretq
|
||||
// with that same bogus CS still loaded, which correctly #GP'd.
|
||||
wrmsr(MSR_STAR,
|
||||
((((uint64)((SEG_UCODE32 << 3) | DPL_USER) << 16) | (uint64)KERNEL_CS) << 32));
|
||||
wrmsr(MSR_LSTAR, (addr_t)syscall_entry);
|
||||
wrmsr(MSR_CSTAR, (addr_t)ignore_sysret);
|
||||
wrmsr(MSR_SFMASK, FL_TF|FL_DF|FL_IF|FL_IOPL_MASK|FL_AC|FL_NT);
|
||||
}
|
||||
|
||||
void
|
||||
seginit(void)
|
||||
{
|
||||
struct segdesc *gdt;
|
||||
uint *tss;
|
||||
uint64 addr;
|
||||
void *local;
|
||||
struct cpu *c;
|
||||
|
||||
// The per-CPU page, preallocated by the boot processor for every AP.
|
||||
//
|
||||
// It must not be kalloc()'d here on an application processor. kalloc takes a
|
||||
// spinlock; acquire() calls holding(); holding() reads `cpu`, which is a
|
||||
// __thread variable reached through %fs -- and %fs is set up four lines
|
||||
// below. So an AP allocating its own page reads the per-CPU pointer before
|
||||
// the per-CPU pointer exists, and the kernel dies in panic("acquire") with
|
||||
// every lock appearing to be held.
|
||||
//
|
||||
// startothers() fills c->local in before starting each AP, on the boot
|
||||
// processor, where locking works.
|
||||
c = &cpus[cpunum()];
|
||||
local = c->local;
|
||||
if (local == 0) {
|
||||
local = kalloc(); // the boot processor's own, taken normally
|
||||
memset(local, 0, PGSIZE);
|
||||
}
|
||||
|
||||
gdt = (struct segdesc*) local;
|
||||
tss = (uint*) (((char*) local) + 1024);
|
||||
tss[16] = 0x00680000;
|
||||
|
||||
kernel_fs_base = ((uint64) local) + 2048;
|
||||
wrmsr(0xC0000100, kernel_fs_base);
|
||||
|
||||
// The TCB self-pointer: the word at %fs:0 must hold the thread pointer.
|
||||
//
|
||||
// This is part of the x86-64 TLS ABI and it was missing, which worked only
|
||||
// by accident. GCC with -mtls-direct-seg-refs compiles `cpu = c` into a
|
||||
// single `movq %rcx, %fs:-0x10` and never needs the thread pointer as a
|
||||
// value, so nothing ever read this word. clang materialises it instead --
|
||||
// `movq %fs:0x0, %rax; addq $-0x10, %rax; movq %rcx, (%rax)` -- which read
|
||||
// the zero left by the memset above and stored through address
|
||||
// 0xfffffffffffffff0, three instructions later.
|
||||
//
|
||||
// Writing it costs one store per CPU and removes the dependency on a
|
||||
// particular compiler's TLS optimisation.
|
||||
*(uint64 *) kernel_fs_base = kernel_fs_base;
|
||||
|
||||
// Clear the two per-CPU slots before anything can read them. Their offsets
|
||||
// are PERCPU_CPU and PERCPU_PROC in include/proc.h and are shared with
|
||||
// trapasm.S, which reads %gs:16 for proc on the way out to user mode.
|
||||
((uint64 *) kernel_fs_base)[2] = 0; // struct cpu * (PERCPU_CPU)
|
||||
((uint64 *) kernel_fs_base)[3] = 0; // struct proc * (PERCPU_PROC)
|
||||
|
||||
// The same address in GS base, per CPU.
|
||||
//
|
||||
// trapasm.S needs to find *this* CPU's kernel FS base on every entry from
|
||||
// user mode, and it has no register to work from at that instant -- which is
|
||||
// why it used to read the kernel_fs_base global. That global is written by
|
||||
// every CPU during its own seginit(), so with more than one processor the
|
||||
// last writer wins and every CPU restores one CPU's per-CPU block. GS base is
|
||||
// per-CPU in hardware, so %gs:0 answers the question with no global at all.
|
||||
//
|
||||
// Safe because GS belongs entirely to the kernel here: sys_arch_prctl()
|
||||
// implements ARCH_SET_FS and ARCH_GET_FS only, and musl uses FS for TLS on
|
||||
// x86-64. See the comment on RESTORE_KERNEL_FS.
|
||||
wrmsr(0xC0000101, kernel_fs_base); // IA32_GS_BASE
|
||||
|
||||
c->local = local;
|
||||
|
||||
cpu_set(c);
|
||||
proc_set(0);
|
||||
|
||||
// Two of the entries below are deliberately null descriptors, and neither is
|
||||
// dead code even though both look it:
|
||||
//
|
||||
// SEG_UCODE32 is never loaded, but SYSRETQ computes CS from STAR[63:48]+16
|
||||
// and SS from +8, so the *slot* must exist to keep the
|
||||
// [UCODE32, UDATA, UCODE] triple consecutive. See syscallinit.
|
||||
// SEG_KCPU is a leftover slot number from 32-bit xv6, where %gs held the
|
||||
// per-CPU pointer. Schunix uses FS_BASE/TLS instead, but the
|
||||
// index has to stay occupied or SEG_TSS stops landing at 7.
|
||||
//
|
||||
// Deleting either moves every descriptor after it and breaks sysret.
|
||||
addr = (uint64) tss;
|
||||
gdt[0] = (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
gdt[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0, 0);
|
||||
gdt[SEG_KDATA] = SEG(STA_W, 0, 0, 0);
|
||||
gdt[SEG_UCODE32] = (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
gdt[SEG_UDATA] = SEG(STA_W, 0, 0, DPL_USER);
|
||||
gdt[SEG_UCODE] = SEG(STA_X|STA_R, 0, 0, DPL_USER);
|
||||
gdt[SEG_KCPU] = (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
struct tassdesc *td = (struct tassdesc*)(gdt + SEG_TSS);
|
||||
td->lim_15_0 = 0x67;
|
||||
td->base_15_0 = addr & 0xffff;
|
||||
td->base_23_16 = (addr >> 16) & 0xff;
|
||||
td->type = STS_T64A;
|
||||
td->s = 0;
|
||||
td->dpl = 0;
|
||||
td->p = 1;
|
||||
td->lim_19_16 = 0;
|
||||
td->avl = 0;
|
||||
td->l = 0;
|
||||
td->d = 0;
|
||||
td->g = 0;
|
||||
td->base_31_24 = (addr >> 24) & 0xff;
|
||||
td->base_63_32 = (addr >> 32) & 0xffffffff;
|
||||
td->res = 0;
|
||||
|
||||
lgdt((void*) gdt, (NSEGS+1) * sizeof(struct segdesc));
|
||||
ltr(SEG_TSS << 3);
|
||||
}
|
||||
|
||||
pml4e_t*
|
||||
setupkvm(void)
|
||||
{
|
||||
pml4e_t *pml4 = (pml4e_t*) kalloc();
|
||||
if(pml4 == 0) return 0;
|
||||
memset(pml4, 0, PGSIZE);
|
||||
|
||||
pml4[PMX(KERNBASE)] = v2p(kpdpt) | PTE_P | PTE_W;
|
||||
|
||||
// The framebuffer is deliberately NOT mapped here.
|
||||
//
|
||||
// This used to identity-map 0xfd000000 for 800*600*4 bytes, user-writable,
|
||||
// into every process that ever existed. Three things were wrong with that.
|
||||
// The address and the size were both guesses that only hold on QEMU's
|
||||
// stdvga at one resolution; setupkvm runs before vga_init, so on the first
|
||||
// call nothing had even read the VBE block yet; and every process could
|
||||
// scribble on the screen without asking anyone.
|
||||
//
|
||||
// Nothing used it -- userspace reaches the framebuffer through
|
||||
// mmap("/dev/fb0"), which maps the real base at the real size. So it is
|
||||
// gone, and a process gets the framebuffer exactly when it asks for it.
|
||||
|
||||
return pml4;
|
||||
}
|
||||
|
||||
void
|
||||
kvmalloc(void)
|
||||
{
|
||||
kpml4 = (pml4e_t*) kalloc();
|
||||
memset(kpml4, 0, PGSIZE);
|
||||
|
||||
kpdpt = (pdpe_t*) kalloc();
|
||||
memset(kpdpt, 0, PGSIZE);
|
||||
|
||||
kpml4[0] = v2p(kpdpt) | PTE_P | PTE_W;
|
||||
kpml4[PMX(KERNBASE)] = v2p(kpdpt) | PTE_P | PTE_W;
|
||||
|
||||
for(uint64 i = 0; i < 4; i++) {
|
||||
uint64 flags = PTE_PS | PTE_P | PTE_W;
|
||||
if(i >= 3) {
|
||||
flags |= PTE_PCD | PTE_PWT;
|
||||
}
|
||||
kpdpt[i] = (i << 30) | flags;
|
||||
}
|
||||
|
||||
switchkvm();
|
||||
}
|
||||
|
||||
void
|
||||
switchuvm(struct proc *p)
|
||||
{
|
||||
pushcli();
|
||||
if(p->pgdir == 0)
|
||||
panic("switchuvm: no pgdir");
|
||||
uint *tss = (uint*) (((char*) mycpu()->local) + 1024);
|
||||
const addr_t stktop = (addr_t)p->kstack + KSTACKSIZE;
|
||||
tss[1] = (uint)stktop;
|
||||
tss[2] = (uint)(stktop >> 32);
|
||||
lcr3(v2p(p->pgdir));
|
||||
popcli();
|
||||
}
|
||||
|
||||
static pte_t *
|
||||
walkpgdir(pml4e_t *pml4, const void *va, int alloc)
|
||||
{
|
||||
pml4e_t *pml4e;
|
||||
pdpe_t *pdp, *pdpe;
|
||||
pde_t *pd, *pde;
|
||||
pte_t *pgtab;
|
||||
|
||||
pml4e = &pml4[PMX(va)];
|
||||
if(*pml4e & PTE_P)
|
||||
pdp = (pdpe_t*)P2V(PTE_ADDR(*pml4e));
|
||||
else {
|
||||
if(!alloc || (pdp = (pdpe_t*)kalloc()) == 0)
|
||||
return 0;
|
||||
memset(pdp, 0, PGSIZE);
|
||||
*pml4e = V2P(pdp) | PTE_P | PTE_W | PTE_U;
|
||||
}
|
||||
|
||||
pdpe = &pdp[PDPX(va)];
|
||||
if(*pdpe & PTE_P)
|
||||
pd = (pde_t*)P2V(PTE_ADDR(*pdpe));
|
||||
else {
|
||||
if(!alloc || (pd = (pde_t*)kalloc()) == 0)
|
||||
return 0;
|
||||
memset(pd, 0, PGSIZE);
|
||||
*pdpe = V2P(pd) | PTE_P | PTE_W | PTE_U;
|
||||
}
|
||||
|
||||
pde = &pd[PDX(va)];
|
||||
if(*pde & PTE_P)
|
||||
pgtab = (pte_t*)P2V(PTE_ADDR(*pde));
|
||||
else {
|
||||
if(!alloc || (pgtab = (pte_t*)kalloc()) == 0)
|
||||
return 0;
|
||||
memset(pgtab, 0, PGSIZE);
|
||||
*pde = V2P(pgtab) | PTE_P | PTE_W | PTE_U;
|
||||
}
|
||||
|
||||
return (pte_t*)&pgtab[PTX(va)];
|
||||
}
|
||||
|
||||
void
|
||||
switchkvm(void)
|
||||
{
|
||||
lcr3(v2p(kpml4));
|
||||
}
|
||||
|
||||
int
|
||||
mappages(pml4e_t *pgdir, void *va, addr_t size, addr_t pa, int perm)
|
||||
{
|
||||
char *a, *last;
|
||||
pte_t *pte;
|
||||
|
||||
a = (char*)PGROUNDDOWN((addr_t)va);
|
||||
last = (char*)PGROUNDDOWN(((addr_t)va) + size - 1);
|
||||
for(;;){
|
||||
if((pte = walkpgdir(pgdir, a, 1)) == 0)
|
||||
return -1;
|
||||
if(*pte & PTE_P)
|
||||
panic("remap");
|
||||
*pte = pa | perm | PTE_P | PTE_U;
|
||||
invlpg(a);
|
||||
if(a == last)
|
||||
break;
|
||||
a += PGSIZE;
|
||||
pa += PGSIZE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
vm_unmap_range(pml4e_t *pgdir, uint64 va, uint64 length)
|
||||
{
|
||||
uint64 a = PGROUNDDOWN(va);
|
||||
uint64 end = PGROUNDUP(va + length);
|
||||
for (; a < end; a += PGSIZE) {
|
||||
pte_t *pte = walkpgdir(pgdir, (void*)a, 0);
|
||||
if (pte && (*pte & PTE_P)) {
|
||||
uint64 pa = PTE_ADDR(*pte);
|
||||
kfree((char*)P2V(pa));
|
||||
*pte = 0;
|
||||
invlpg((void*)a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tear down a mapping without freeing what is underneath it.
|
||||
//
|
||||
// vm_unmap_range() kfree()s every page it unmaps, which is right for an
|
||||
// anonymous mapping and catastrophic for anything shared: the framebuffer is
|
||||
// device memory that was never allocated, and a shared memory segment's pages
|
||||
// belong to the segment, not to whichever process happens to be detaching.
|
||||
// Handing either of those to kfree puts them on the free list while another
|
||||
// process is still reading and writing them.
|
||||
void
|
||||
vm_unmap_norelease(pml4e_t *pgdir, uint64 va, uint64 length)
|
||||
{
|
||||
uint64 a = PGROUNDDOWN(va);
|
||||
uint64 end = PGROUNDUP(va + length);
|
||||
for (; a < end; a += PGSIZE) {
|
||||
pte_t *pte = walkpgdir(pgdir, (void*)a, 0);
|
||||
if (pte && (*pte & PTE_P)) {
|
||||
*pte = 0;
|
||||
invlpg((void*)a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Duplicate an mmap()'d VMA region from src into dst for fork().
|
||||
// Framebuffer mappings are re-pointed at the same physical MMIO range
|
||||
// (shared, never copied/freed); anonymous mappings get fresh pages with
|
||||
// the parent's contents copied in — mirrors what copyuvm() does for the
|
||||
// [0, sz) heap range, which never covers mmap()'d addresses.
|
||||
int
|
||||
copy_vma_range(pml4e_t *dstpgdir, pml4e_t *srcpgdir, uint64 va, uint64 length, int share)
|
||||
{
|
||||
uint64 a = PGROUNDDOWN(va);
|
||||
uint64 end = PGROUNDUP(va + length);
|
||||
for (; a < end; a += PGSIZE) {
|
||||
if (share) {
|
||||
// Point the child at the very same physical pages, whatever they are.
|
||||
//
|
||||
// This used to special-case the framebuffer and map vga_fb_phys()
|
||||
// explicitly. Reading the parent's own PTE instead covers device memory
|
||||
// and shared memory segments with one rule, and cannot disagree with
|
||||
// what the parent actually has mapped -- which the explicit version
|
||||
// could, and did, when the address was hardcoded.
|
||||
pte_t *spte = walkpgdir(srcpgdir, (void*)a, 0);
|
||||
if (!spte || !(*spte & PTE_P))
|
||||
continue;
|
||||
if (mappages(dstpgdir, (void*)a, PGSIZE, PTE_ADDR(*spte), PTE_FLAGS(*spte)) < 0)
|
||||
return -1;
|
||||
continue;
|
||||
}
|
||||
pte_t *spte = walkpgdir(srcpgdir, (void*)a, 0);
|
||||
if (!spte || !(*spte & PTE_P))
|
||||
continue;
|
||||
char *mem = kalloc();
|
||||
if (!mem)
|
||||
return -1;
|
||||
memmove(mem, (char*)P2V(PTE_ADDR(*spte)), PGSIZE);
|
||||
if (mappages(dstpgdir, (void*)a, PGSIZE, V2P(mem), PTE_FLAGS(*spte)) < 0) {
|
||||
kfree(mem);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
inituvm(pde_t *pgdir, char *sinit, uint64 sz)
|
||||
{
|
||||
char *mem;
|
||||
|
||||
if(sz >= PGSIZE)
|
||||
panic("inituvm: more than a page");
|
||||
mem = kalloc();
|
||||
memset(mem, 0, PGSIZE);
|
||||
mappages(pgdir, 0, PGSIZE, v2p(mem), PTE_W|PTE_U);
|
||||
memmove(mem, sinit, sz);
|
||||
}
|
||||
|
||||
int
|
||||
loaduvm(pml4e_t *pgdir, char *addr, struct inode *ip, uint64 offset, uint64 sz)
|
||||
{
|
||||
uint64 done, n, off_in_page;
|
||||
addr_t pa;
|
||||
pte_t *pte;
|
||||
char *cur_page;
|
||||
|
||||
for(done = 0; done < sz; done += n){
|
||||
cur_page = (char*)PGROUNDDOWN((uint64)(addr + done));
|
||||
off_in_page = (uint64)(addr + done) - (uint64)cur_page;
|
||||
if((pte = walkpgdir(pgdir, cur_page, 0)) == 0)
|
||||
panic("loaduvm: address should exist");
|
||||
pa = PTE_ADDR(*pte);
|
||||
n = PGSIZE - off_in_page;
|
||||
if(n > sz - done)
|
||||
n = sz - done;
|
||||
if(readi(ip, 0, (uint64)(P2V(pa) + off_in_page), offset + done, n) != (int)n)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64
|
||||
allocuvm(pml4e_t *pgdir, uint64 oldsz, uint64 newsz)
|
||||
{
|
||||
char *mem;
|
||||
addr_t a;
|
||||
|
||||
if(newsz >= KERNBASE)
|
||||
return 0;
|
||||
if(newsz < oldsz)
|
||||
return oldsz;
|
||||
|
||||
a = PGROUNDUP(oldsz);
|
||||
for(; a < newsz; a += PGSIZE){
|
||||
mem = kalloc();
|
||||
if(mem == 0){
|
||||
deallocuvm(pgdir, newsz, oldsz);
|
||||
return 0;
|
||||
}
|
||||
memset(mem, 0, PGSIZE);
|
||||
if(mappages(pgdir, (char*)a, PGSIZE, V2P(mem), PTE_W|PTE_U) < 0){
|
||||
deallocuvm(pgdir, newsz, oldsz);
|
||||
kfree(mem);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return newsz;
|
||||
}
|
||||
|
||||
uint64
|
||||
deallocuvm(pml4e_t *pgdir, uint64 oldsz, uint64 newsz)
|
||||
{
|
||||
pte_t *pte;
|
||||
addr_t a, pa;
|
||||
|
||||
if(newsz >= oldsz)
|
||||
return oldsz;
|
||||
|
||||
a = PGROUNDUP(newsz);
|
||||
for(; a < oldsz; a += PGSIZE){
|
||||
pte = walkpgdir(pgdir, (char*)a, 0);
|
||||
if(pte && (*pte & PTE_P) != 0){
|
||||
pa = PTE_ADDR(*pte);
|
||||
if(pa == 0)
|
||||
panic("kfree");
|
||||
if(pa < PHYSTOP) {
|
||||
char *v = P2V(pa);
|
||||
kfree(v);
|
||||
}
|
||||
*pte = 0;
|
||||
}
|
||||
}
|
||||
return newsz;
|
||||
}
|
||||
|
||||
void
|
||||
freevm(pml4e_t *pml4)
|
||||
{
|
||||
uint i, j, k, l;
|
||||
pdpe_t *pdp;
|
||||
pde_t *pd;
|
||||
pte_t *pt;
|
||||
|
||||
if(pml4 == 0)
|
||||
panic("freevm: no pgdir");
|
||||
|
||||
for(i = 0; i < (512/2); i++){
|
||||
if(pml4[i] & PTE_P){
|
||||
pdp = (pdpe_t*)P2V(PTE_ADDR(pml4[i]));
|
||||
for(j = 0; j < 512; j++){
|
||||
if(pdp[j] & PTE_P){
|
||||
pd = (pde_t*)P2V(PTE_ADDR(pdp[j]));
|
||||
for(k = 0; k < 512; k++){
|
||||
if(pd[k] & PTE_P) {
|
||||
pt = (pte_t*)P2V(PTE_ADDR(pd[k]));
|
||||
for(l = 0; l < 512; l++){
|
||||
if(pt[l] & PTE_P) {
|
||||
addr_t pa = PTE_ADDR(pt[l]);
|
||||
if(pa < PHYSTOP && pa != 0) {
|
||||
kfree(P2V(pa));
|
||||
}
|
||||
}
|
||||
}
|
||||
kfree((char*)pt);
|
||||
}
|
||||
}
|
||||
kfree((char*)pd);
|
||||
}
|
||||
}
|
||||
kfree((char*)pdp);
|
||||
}
|
||||
}
|
||||
kfree((char*)pml4);
|
||||
}
|
||||
|
||||
void
|
||||
clearpteu(pml4e_t *pgdir, char *uva)
|
||||
{
|
||||
pte_t *pte;
|
||||
pte = walkpgdir(pgdir, uva, 0);
|
||||
if(pte == 0)
|
||||
panic("clearpteu");
|
||||
*pte &= ~PTE_U;
|
||||
}
|
||||
|
||||
pml4e_t*
|
||||
copyuvm(pml4e_t *pgdir, uint64 sz)
|
||||
{
|
||||
pml4e_t *d;
|
||||
pte_t *pte;
|
||||
addr_t pa, i, flags;
|
||||
char *mem;
|
||||
|
||||
if((d = setupkvm()) == 0)
|
||||
return 0;
|
||||
for(i = 0; i < sz; i += PGSIZE){
|
||||
if((pte = walkpgdir(pgdir, (void*)i, 0)) == 0)
|
||||
continue;
|
||||
if(!(*pte & PTE_P))
|
||||
continue;
|
||||
pa = PTE_ADDR(*pte);
|
||||
flags = PTE_FLAGS(*pte);
|
||||
|
||||
if(pa < PHYSTOP) {
|
||||
if((mem = kalloc()) == 0)
|
||||
goto bad;
|
||||
memmove(mem, (char*)P2V(pa), PGSIZE);
|
||||
if(mappages(d, (void*)i, PGSIZE, V2P(mem), flags) < 0) {
|
||||
kfree(mem);
|
||||
goto bad;
|
||||
}
|
||||
}
|
||||
}
|
||||
return d;
|
||||
|
||||
bad:
|
||||
freevm(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char*
|
||||
uva2ka(pml4e_t *pgdir, char *uva)
|
||||
{
|
||||
if((addr_t)uva >= KERNBASE)
|
||||
return 0;
|
||||
|
||||
pte_t *pte;
|
||||
pte = walkpgdir(pgdir, uva, 0);
|
||||
if(pte == 0 || (*pte & PTE_P) == 0)
|
||||
return 0;
|
||||
if((*pte & PTE_U) == 0)
|
||||
return 0;
|
||||
addr_t pa = (*pte) & 0x000FFFFFFFFFF000ull;
|
||||
// Include uva's in-page offset — callers dereference the returned pointer
|
||||
// directly at uva's address, not just somewhere within its page.
|
||||
return (char*)P2V(pa) + ((addr_t)uva & 0xFFF);
|
||||
}
|
||||
|
||||
int
|
||||
copyout(pml4e_t *pgdir, addr_t va, void *p, uint64 len)
|
||||
{
|
||||
char *buf, *pa0;
|
||||
addr_t n, va0;
|
||||
|
||||
buf = (char*)p;
|
||||
while(len > 0){
|
||||
va0 = PGROUNDDOWN(va);
|
||||
pa0 = uva2ka(pgdir, (char*)va0);
|
||||
|
||||
if(pa0 == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
n = PGSIZE - (va - va0);
|
||||
if(n > len)
|
||||
n = len;
|
||||
memmove(pa0 + (va - va0), buf, n);
|
||||
|
||||
len -= n;
|
||||
buf += n;
|
||||
va += n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
copyin(pml4e_t *pgdir, addr_t srcva, void *dst, uint64 len)
|
||||
{
|
||||
uint64 n, va0;
|
||||
char *pa0;
|
||||
char *dst_buf = (char*)dst;
|
||||
|
||||
while(len > 0){
|
||||
va0 = PGROUNDDOWN(srcva);
|
||||
pa0 = uva2ka(pgdir, (char*)va0);
|
||||
if(pa0 == 0)
|
||||
return -1;
|
||||
n = PGSIZE - (srcva - va0);
|
||||
if(n > len)
|
||||
n = len;
|
||||
memmove(dst_buf, pa0 + (srcva - va0), n);
|
||||
len -= n;
|
||||
dst_buf += n;
|
||||
srcva += n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
810
kernel/core/console.c
Normal file
810
kernel/core/console.c
Normal file
|
|
@ -0,0 +1,810 @@
|
|||
#include <stdarg.h>
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "theme.h"
|
||||
#include "irq.h"
|
||||
#include "param.h"
|
||||
#include "traps.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "posix.h"
|
||||
#include "x86.h"
|
||||
|
||||
void consputc(int);
|
||||
// Set by panic() once the crash screen has been painted: after that, consputc
|
||||
// spins instead of writing, so nothing else can scribble over the report.
|
||||
int panicked = 0;
|
||||
|
||||
// When set, console output goes to the serial line only and the framebuffer
|
||||
// is left alone.
|
||||
//
|
||||
// **Starts set, on purpose.** The display is the product; the serial line is
|
||||
// the diagnostics. A machine that boots by printing "pci: 6 devices" and
|
||||
// "vga: display controller 1234:1111, aperture fd000000" to the screen the
|
||||
// user is looking at is showing them a developer's log, and that single fact
|
||||
// is most of what makes an OS look like a hobby project.
|
||||
//
|
||||
// Nothing is lost: every one of those lines still goes out the UART, which is
|
||||
// where tools/bootcheck.ps1 reads them from and where you want them when
|
||||
// something goes wrong. console_takeover() clears this once userspace is up
|
||||
// and the display genuinely belongs to the console. The F8 verbose boot never
|
||||
// sets it, so "show me everything" still works.
|
||||
int cons_serial_only = 1;
|
||||
extern void vga_screen_reset(void);
|
||||
|
||||
// Text colour used when drawing to the framebuffer. Changed by SGR escapes
|
||||
// (see sgr() below); reset to the default by ESC[0m and ESC c.
|
||||
//
|
||||
// This was initialised to 7, a leftover from when it was a VGA text-mode
|
||||
// attribute byte. vga_putc takes a 32-bit RGB value, so 7 meant "very dark
|
||||
// blue" -- it looked black against the cream background purely by accident.
|
||||
static uint32 cons_fg = SCH_FG;
|
||||
|
||||
static struct {
|
||||
struct spinlock lock;
|
||||
int locking;
|
||||
} cons;
|
||||
|
||||
static char digits[] = "0123456789abcdef";
|
||||
|
||||
static void
|
||||
print_int(int64 xx, int base, int sign)
|
||||
{
|
||||
char buf[64];
|
||||
int i;
|
||||
uint64 x;
|
||||
|
||||
if(sign && (sign = (xx < 0)))
|
||||
x = -xx;
|
||||
else
|
||||
x = xx;
|
||||
|
||||
i = 0;
|
||||
do {
|
||||
buf[i++] = digits[x % base];
|
||||
} while((x /= base) != 0);
|
||||
|
||||
if(sign)
|
||||
buf[i++] = '-';
|
||||
|
||||
while(--i >= 0)
|
||||
consputc(buf[i]);
|
||||
}
|
||||
|
||||
void
|
||||
cprintf(char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int i, c, locking;
|
||||
char *s;
|
||||
|
||||
va_start(ap, fmt);
|
||||
|
||||
locking = cons.locking;
|
||||
if (locking)
|
||||
acquire(&cons.lock);
|
||||
|
||||
if (fmt == 0)
|
||||
panic("null fmt");
|
||||
|
||||
for (i = 0; (c = fmt[i] & 0xff) != 0; i++) {
|
||||
if (c != '%') {
|
||||
consputc(c);
|
||||
continue;
|
||||
}
|
||||
c = fmt[++i] & 0xff;
|
||||
if (c == 0)
|
||||
break;
|
||||
switch(c) {
|
||||
case 'd':
|
||||
print_int(va_arg(ap, int), 10, 1);
|
||||
break;
|
||||
case 'u':
|
||||
print_int(va_arg(ap, uint32), 10, 0);
|
||||
break;
|
||||
case 'l':
|
||||
print_int(va_arg(ap, int64), 10, 1);
|
||||
break;
|
||||
case 'x':
|
||||
print_int(va_arg(ap, uint32), 16, 0);
|
||||
break;
|
||||
case 'p':
|
||||
print_int(va_arg(ap, uint64), 16, 0);
|
||||
break;
|
||||
case 'b':
|
||||
print_int(va_arg(ap, uint32), 2, 0);
|
||||
break;
|
||||
case 's':
|
||||
if ((s = va_arg(ap, char*)) == 0)
|
||||
s = "(null)";
|
||||
while (*s)
|
||||
consputc(*(s++));
|
||||
break;
|
||||
case '%':
|
||||
consputc('%');
|
||||
break;
|
||||
default:
|
||||
consputc('%');
|
||||
consputc(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (locking)
|
||||
release(&cons.lock);
|
||||
}
|
||||
|
||||
// Hand the console over to panic().
|
||||
//
|
||||
// Two things change. The lock stops being taken, so a panic can report itself
|
||||
// from anywhere — including from inside a routine that already holds it. And
|
||||
// output stops going to the framebuffer, because from here on the framebuffer
|
||||
// belongs to the crash screen: without this the report would scroll straight
|
||||
// over the panel that is trying to display it.
|
||||
void
|
||||
cons_panic_mode(void)
|
||||
{
|
||||
cons.locking = 0;
|
||||
cons_serial_only = 1;
|
||||
}
|
||||
|
||||
// Hand the display over to the text console.
|
||||
//
|
||||
// Called once, when userspace is far enough along that the screen should stop
|
||||
// being a boot splash and start being a terminal. Until this runs the kernel
|
||||
// has been talking to the serial line only (see cons_serial_only), so this is
|
||||
// the moment the framebuffer changes owner -- which is why it clears first
|
||||
// rather than scrolling the shell prompt in underneath the splash.
|
||||
void
|
||||
console_takeover(void)
|
||||
{
|
||||
if (!cons_serial_only)
|
||||
return; // verbose boot: the console never left
|
||||
cons_serial_only = 0;
|
||||
vga_screen_reset();
|
||||
}
|
||||
|
||||
// Foreground process group of the console, i.e. the controlling terminal.
|
||||
// tcgetpgrp()/tcsetpgrp() read and write this through TIOCGPGRP/TIOCSPGRP in
|
||||
// sys_fs.c. Initialised to 1 -- init's process group -- so the first shell,
|
||||
// which inherits that group, already sees itself as the foreground job and
|
||||
// does not immediately stop itself with SIGTTIN.
|
||||
int tty_fg_pgrp = 1;
|
||||
|
||||
#define BACKSPACE 0x100
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Output: an ANSI escape parser, and UTF-8
|
||||
//
|
||||
// The console used to print every byte it was given, which meant every escape
|
||||
// sequence any program emitted appeared on screen as literal text -- `clear`
|
||||
// drew "^[[2J^[[H" and left the screen exactly as it found it. Colour, cursor
|
||||
// addressing, and anything full-screen were all equally impossible.
|
||||
//
|
||||
// The serial line does NOT go through any of this. On the other end of it is
|
||||
// a real terminal emulator that already understands ANSI far better than this
|
||||
// ever will, so escapes are passed through to it verbatim and interpreted
|
||||
// only for the framebuffer. Two consumers, two right answers.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// The sixteen ANSI colours as RGB, for a linear framebuffer.
|
||||
//
|
||||
// consoleinit() used to program these into the VGA DAC with set_vga_palette(),
|
||||
// which does nothing at all in a 32bpp linear mode -- there is no palette to
|
||||
// index. The colours had to live somewhere real, and this is it.
|
||||
static const uint32 ansi_rgb[16] = {
|
||||
0x000000, 0xAA0000, 0x00AA00, 0xAA5500, // black red green yellow(brown)
|
||||
0x0000AA, 0xAA00AA, 0x00AAAA, 0xAAAAAA, // blue magenta cyan white
|
||||
0x555555, 0xFF5555, 0x55FF55, 0xFFFF55, // bright versions
|
||||
0x5555FF, 0xFF55FF, 0x55FFFF, 0xFFFFFF,
|
||||
};
|
||||
|
||||
#define CONS_DEFAULT_FG SCH_FG // see include/theme.h
|
||||
#define CONS_DEFAULT_BG SCH_BG
|
||||
|
||||
static uint32 cons_bg = CONS_DEFAULT_BG;
|
||||
static int cons_bold = 0;
|
||||
|
||||
// Escape parser state.
|
||||
enum { E_NORMAL, E_ESC, E_CSI };
|
||||
static int esc_state = E_NORMAL;
|
||||
static int esc_parm[8];
|
||||
static int esc_nparm;
|
||||
static int esc_private; // saw '?' after CSI
|
||||
static int saved_col, saved_row;
|
||||
|
||||
// UTF-8 decoder state. A codepoint arrives as up to four bytes and has to be
|
||||
// reassembled before it can be turned into a glyph.
|
||||
static uint32 u8_cp;
|
||||
static int u8_need;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Unicode -> glyph
|
||||
//
|
||||
// The font's upper half is CP437, not Latin-1, so a codepoint cannot be used
|
||||
// as an index. It looks like it can -- font.h's own comments say so, labelling
|
||||
// each glyph with the Unicode character of the same number -- but the bitmaps
|
||||
// disagree: index 0xE9 draws a capital theta, and rendering U+00E9 there turns
|
||||
// "cafe" into "caf" followed by a theta. Every accented character came out as
|
||||
// an unrelated symbol, consistently enough to look almost deliberate.
|
||||
//
|
||||
// So: a table. Entry i covers codepoint 0x80 + i, and 0 means "this font has
|
||||
// no glyph for that". The C1 range (0x80-0x9F) has no printable characters at
|
||||
// all, hence the leading run of zeros.
|
||||
// ---------------------------------------------------------------------------
|
||||
static const uchar latin1_to_cp437[128] = {
|
||||
/* 0x80-0x9F: C1 controls, nothing to draw */
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
||||
/* 0xA0 */ 0x20, 0xAD, 0x9B, 0x9C, 0, 0x9D, 0, 0x15,
|
||||
/* 0xA8 */ 0, 0, 0xA6, 0xAE, 0xAA, 0x2D, 0, 0,
|
||||
/* 0xB0 */ 0xF8, 0xF1, 0xFD, 0, 0, 0xE6, 0x14, 0xFA,
|
||||
/* 0xB8 */ 0, 0, 0xA7, 0xAF, 0xAC, 0xAB, 0, 0xA8,
|
||||
/* 0xC0 */ 0, 0, 0, 0, 0x8E, 0x8F, 0x92, 0x80,
|
||||
/* 0xC8 */ 0, 0x90, 0, 0, 0, 0, 0, 0,
|
||||
/* 0xD0 */ 0, 0xA5, 0, 0, 0, 0, 0x99, 0,
|
||||
/* 0xD8 */ 0, 0, 0, 0, 0x9A, 0, 0, 0xE1,
|
||||
/* 0xE0 */ 0x85, 0xA0, 0x83, 0, 0x84, 0x86, 0x91, 0x87,
|
||||
/* 0xE8 */ 0x8A, 0x82, 0x88, 0x89, 0x8D, 0xA1, 0x8C, 0x8B,
|
||||
/* 0xF0 */ 0, 0xA4, 0x95, 0xA2, 0x93, 0, 0x94, 0xF6,
|
||||
/* 0xF8 */ 0, 0x97, 0xA3, 0x96, 0x81, 0, 0, 0x98,
|
||||
};
|
||||
|
||||
// The handful of characters above U+00FF that CP437 does have. Box drawing
|
||||
// and block elements matter because they are what every text-mode program
|
||||
// draws frames and progress bars out of.
|
||||
struct cp_pair { uint32 cp; uchar glyph; };
|
||||
static const struct cp_pair cp437_extra[] = {
|
||||
{ 0x2022, 0x07 }, /* bullet */
|
||||
{ 0x2500, 0xC4 }, { 0x2502, 0xB3 }, /* light horizontal/vertical */
|
||||
{ 0x250C, 0xDA }, { 0x2510, 0xBF },
|
||||
{ 0x2514, 0xC0 }, { 0x2518, 0xD9 },
|
||||
{ 0x251C, 0xC3 }, { 0x2524, 0xB4 },
|
||||
{ 0x252C, 0xC2 }, { 0x2534, 0xC1 }, { 0x253C, 0xC5 },
|
||||
{ 0x2550, 0xCD }, { 0x2551, 0xBA }, /* double */
|
||||
{ 0x2554, 0xC9 }, { 0x2557, 0xBB },
|
||||
{ 0x255A, 0xC8 }, { 0x255D, 0xBC },
|
||||
{ 0x2580, 0xDF }, { 0x2584, 0xDC }, { 0x2588, 0xDB },
|
||||
{ 0x258C, 0xDD }, { 0x2590, 0xDE },
|
||||
{ 0x2591, 0xB0 }, { 0x2592, 0xB1 }, { 0x2593, 0xB2 },
|
||||
{ 0x25A0, 0xFE }, /* black square */
|
||||
{ 0x221A, 0xFB }, { 0x221E, 0xEC }, /* root, infinity */
|
||||
{ 0x2260, 0, }, { 0x2264, 0xF3 }, { 0x2265, 0xF2 },
|
||||
{ 0x2248, 0xF7 }, { 0x2261, 0xF0 },
|
||||
};
|
||||
|
||||
// Returns the glyph to draw for a codepoint, or 0 if there is none.
|
||||
static int
|
||||
glyph_for(uint32 cp)
|
||||
{
|
||||
if (cp < 0x80)
|
||||
return (int)cp; // ASCII is CP437's lower half
|
||||
if (cp < 0x100)
|
||||
return latin1_to_cp437[cp - 0x80];
|
||||
|
||||
for (unsigned i = 0; i < NELEM(cp437_extra); i++)
|
||||
if (cp437_extra[i].cp == cp)
|
||||
return cp437_extra[i].glyph;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
sgr(void)
|
||||
{
|
||||
for (int i = 0; i < esc_nparm; i++) {
|
||||
int p = esc_parm[i];
|
||||
|
||||
if (p == 0) {
|
||||
cons_fg = CONS_DEFAULT_FG;
|
||||
cons_bg = CONS_DEFAULT_BG;
|
||||
cons_bold = 0;
|
||||
} else if (p == 1) {
|
||||
cons_bold = 1;
|
||||
// Re-apply brightness to a colour already chosen.
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (cons_fg == ansi_rgb[j]) { cons_fg = ansi_rgb[j + 8]; break; }
|
||||
} else if (p == 22) {
|
||||
cons_bold = 0;
|
||||
} else if (p >= 30 && p <= 37) {
|
||||
cons_fg = ansi_rgb[(p - 30) + (cons_bold ? 8 : 0)];
|
||||
} else if (p == 39) {
|
||||
cons_fg = CONS_DEFAULT_FG;
|
||||
} else if (p >= 40 && p <= 47) {
|
||||
cons_bg = ansi_rgb[p - 40];
|
||||
} else if (p == 49) {
|
||||
cons_bg = CONS_DEFAULT_BG;
|
||||
} else if (p >= 90 && p <= 97) {
|
||||
cons_fg = ansi_rgb[(p - 90) + 8];
|
||||
} else if (p >= 100 && p <= 107) {
|
||||
cons_bg = ansi_rgb[(p - 100) + 8];
|
||||
}
|
||||
}
|
||||
vga_set_bg(cons_bg);
|
||||
}
|
||||
|
||||
// One final byte of a CSI sequence, with esc_parm[] already filled in.
|
||||
static void
|
||||
csi_dispatch(int final)
|
||||
{
|
||||
int col, row;
|
||||
int n = esc_nparm > 0 ? esc_parm[0] : 0;
|
||||
int n1 = n ? n : 1; // most sequences default to 1
|
||||
|
||||
vga_cursor_get(&col, &row);
|
||||
|
||||
switch (final) {
|
||||
case 'A': vga_cursor_set(col, row - n1); break;
|
||||
case 'B': vga_cursor_set(col, row + n1); break;
|
||||
case 'C': vga_cursor_set(col + n1, row); break;
|
||||
case 'D': vga_cursor_set(col - n1, row); break;
|
||||
case 'E': vga_cursor_set(0, row + n1); break;
|
||||
case 'F': vga_cursor_set(0, row - n1); break;
|
||||
case 'G': vga_cursor_set(n1 - 1, row); break;
|
||||
case 'd': vga_cursor_set(col, n1 - 1); break;
|
||||
|
||||
case 'H': case 'f': {
|
||||
int r = esc_nparm > 0 && esc_parm[0] ? esc_parm[0] : 1;
|
||||
int c = esc_nparm > 1 && esc_parm[1] ? esc_parm[1] : 1;
|
||||
vga_cursor_set(c - 1, r - 1); // ANSI counts from 1
|
||||
break;
|
||||
}
|
||||
|
||||
case 'J': vga_erase_display(n); break;
|
||||
case 'K': vga_erase_line(n); break;
|
||||
|
||||
case 'm': sgr(); break;
|
||||
|
||||
case 's': saved_col = col; saved_row = row; break;
|
||||
case 'u': vga_cursor_set(saved_col, saved_row); break;
|
||||
|
||||
// Cursor visibility (DECTCEM) and the other private modes. There is no
|
||||
// hardware cursor being drawn, so there is nothing to show or hide --
|
||||
// swallowing them silently is the correct behaviour, and far better than
|
||||
// printing "?25l" into the middle of someone's output.
|
||||
case 'h': case 'l': break;
|
||||
|
||||
default: break; // unknown: drop it, do not print it
|
||||
}
|
||||
}
|
||||
|
||||
// Feed one output byte to the framebuffer side. Returns nothing; the serial
|
||||
// side has already had the raw byte.
|
||||
static void
|
||||
vga_feed(int c)
|
||||
{
|
||||
switch (esc_state) {
|
||||
case E_ESC:
|
||||
if (c == '[') {
|
||||
esc_state = E_CSI;
|
||||
esc_nparm = 0;
|
||||
esc_private = 0;
|
||||
for (int i = 0; i < 8; i++) esc_parm[i] = 0;
|
||||
return;
|
||||
}
|
||||
esc_state = E_NORMAL;
|
||||
if (c == 'c') { // RIS -- full reset
|
||||
cons_fg = CONS_DEFAULT_FG;
|
||||
cons_bg = CONS_DEFAULT_BG;
|
||||
vga_screen_reset();
|
||||
}
|
||||
return; // other two-byte escapes: ignored
|
||||
|
||||
case E_CSI:
|
||||
if (c == '?') { esc_private = 1; return; }
|
||||
if (c >= '0' && c <= '9') {
|
||||
if (esc_nparm == 0) esc_nparm = 1;
|
||||
if (esc_nparm <= 8)
|
||||
esc_parm[esc_nparm - 1] = esc_parm[esc_nparm - 1] * 10 + (c - '0');
|
||||
return;
|
||||
}
|
||||
if (c == ';') {
|
||||
if (esc_nparm < 8) esc_nparm++;
|
||||
return;
|
||||
}
|
||||
esc_state = E_NORMAL;
|
||||
if (!esc_private)
|
||||
csi_dispatch(c);
|
||||
return;
|
||||
}
|
||||
|
||||
// E_NORMAL
|
||||
if (c == '\x1b') { esc_state = E_ESC; return; }
|
||||
|
||||
// UTF-8 continuation. A codepoint the font cannot draw becomes '?' -- the
|
||||
// one thing it must not become is a *different* character, which is exactly
|
||||
// what indexing the font by codepoint did.
|
||||
if (u8_need > 0) {
|
||||
if ((c & 0xC0) != 0x80) { // malformed: abandon the sequence
|
||||
u8_need = 0;
|
||||
vga_putc('?', cons_fg);
|
||||
return;
|
||||
}
|
||||
u8_cp = (u8_cp << 6) | (c & 0x3F);
|
||||
if (--u8_need > 0)
|
||||
return;
|
||||
int g = glyph_for(u8_cp);
|
||||
vga_putc(g ? g : '?', cons_fg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c >= 0xF0) { u8_cp = c & 0x07; u8_need = 3; return; }
|
||||
else if (c >= 0xE0) { u8_cp = c & 0x0F; u8_need = 2; return; }
|
||||
else if (c >= 0xC0) { u8_cp = c & 0x1F; u8_need = 1; return; }
|
||||
else if (c >= 0x80) { vga_putc('?', cons_fg); return; } // stray continuation
|
||||
|
||||
vga_putc(c, cons_fg);
|
||||
}
|
||||
|
||||
void
|
||||
consputc(int c)
|
||||
{
|
||||
if (panicked) {
|
||||
cli();
|
||||
for(;;) hlt();
|
||||
}
|
||||
|
||||
if (c == BACKSPACE) {
|
||||
uartputc('\b'); uartputc(' '); uartputc('\b');
|
||||
if (!cons_serial_only)
|
||||
vga_putc(c, cons_fg);
|
||||
return;
|
||||
}
|
||||
|
||||
uartputc(c);
|
||||
if (!cons_serial_only)
|
||||
vga_feed(c & 0xff);
|
||||
}
|
||||
|
||||
#define INPUT_BUF 256
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
char buf[INPUT_BUF];
|
||||
uint r;
|
||||
uint w;
|
||||
uint e;
|
||||
uint abort_read;
|
||||
// Set by EOF (Ctrl-D) on an empty line. Canonical-mode end of input is not
|
||||
// a character -- it is the *absence* of one -- so it cannot be signalled by
|
||||
// putting something in the buffer. The old code pushed a literal ^D in and
|
||||
// had consoleread() pick it back out, which meant a ^D typed mid-line was
|
||||
// indistinguishable from one typed alone.
|
||||
uint eof;
|
||||
} input;
|
||||
|
||||
#define C(x) ((x)-'@')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The line discipline
|
||||
//
|
||||
// TCGETS used to return a hardcoded set of flags and TCSETS threw its argument
|
||||
// away, so the terminal was permanently in one mode: line-at-a-time, always
|
||||
// echoing. Anything wanting a keypress at a time -- a pager, an editor, a
|
||||
// shell doing its own line editing, which is most of what zsh is -- had no way
|
||||
// to ask for one. This is the real thing: a struct programs can write, that
|
||||
// changes what the kernel actually does.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static struct termios tio = {
|
||||
.c_iflag = ICRNL,
|
||||
.c_oflag = OPOST | ONLCR,
|
||||
.c_cflag = CS8 | CREAD | CLOCAL,
|
||||
.c_lflag = ISIG | ICANON | ECHO | ECHOE,
|
||||
.c_cc = {
|
||||
[VINTR] = C('C'), [VQUIT] = C('\\'), [VERASE] = '\x7f', [VKILL] = C('U'),
|
||||
[VEOF] = C('D'), [VSUSP] = C('Z'), [VWERASE] = C('W'), [VMIN] = 1,
|
||||
},
|
||||
.c_ispeed = 15, .c_ospeed = 15,
|
||||
};
|
||||
|
||||
void
|
||||
cons_get_termios(struct termios *out)
|
||||
{
|
||||
acquire(&input.lock);
|
||||
*out = tio;
|
||||
release(&input.lock);
|
||||
}
|
||||
|
||||
void
|
||||
cons_set_termios(const struct termios *in)
|
||||
{
|
||||
acquire(&input.lock);
|
||||
tio = *in;
|
||||
// Switching out of canonical mode has to hand over whatever has been typed
|
||||
// but not yet submitted, or a shell that turns off ICANON mid-line silently
|
||||
// eats the characters already in the edit buffer.
|
||||
if (!(tio.c_lflag & ICANON))
|
||||
input.w = input.e;
|
||||
release(&input.lock);
|
||||
wakeup(&input.r);
|
||||
}
|
||||
|
||||
static void
|
||||
echo(int c)
|
||||
{
|
||||
if (c == '\n' || c == '\r') {
|
||||
if ((tio.c_lflag & ECHO) || (tio.c_lflag & ECHONL))
|
||||
consputc('\n');
|
||||
return;
|
||||
}
|
||||
if (!(tio.c_lflag & ECHO))
|
||||
return;
|
||||
// Control characters echo as ^X, the way every other terminal shows them.
|
||||
if (c < 32 && c != '\t') {
|
||||
consputc('^');
|
||||
consputc(c + '@');
|
||||
} else {
|
||||
consputc(c);
|
||||
}
|
||||
}
|
||||
|
||||
// Rub out the last character of the current line.
|
||||
static void
|
||||
erase_one(void)
|
||||
{
|
||||
if (input.e == input.w)
|
||||
return;
|
||||
input.e--;
|
||||
if (tio.c_lflag & ECHOE)
|
||||
consputc(BACKSPACE);
|
||||
}
|
||||
|
||||
void
|
||||
consoleintr(int (*getc)(void))
|
||||
{
|
||||
int c;
|
||||
|
||||
acquire(&input.lock);
|
||||
while((c = getc()) >= 0){
|
||||
if (c == 0)
|
||||
continue;
|
||||
|
||||
// Input mapping, before anything looks at the character.
|
||||
if (tio.c_iflag & ISTRIP) c &= 0x7f;
|
||||
if (c == '\r') {
|
||||
if (tio.c_iflag & IGNCR) continue;
|
||||
if (tio.c_iflag & ICRNL) c = '\n';
|
||||
} else if (c == '\n' && (tio.c_iflag & INLCR)) {
|
||||
c = '\r';
|
||||
}
|
||||
|
||||
// Signals. These are the characters that must not reach the reader.
|
||||
//
|
||||
// Ctrl-C used to do `myproc()->killed = 1` -- and `proc` in an interrupt
|
||||
// handler is whatever happened to be running on this CPU when the key was
|
||||
// pressed, which is almost never the program the user is looking at. It
|
||||
// also set a kill flag rather than raising a signal, so nothing could
|
||||
// catch it and the shell never learned the job had died. Both wrong;
|
||||
// both fixed by sending a real signal to the foreground process group,
|
||||
// which the kernel has been tracking in tty_fg_pgrp all along.
|
||||
if (tio.c_lflag & ISIG) {
|
||||
int sig = 0;
|
||||
if (c == tio.c_cc[VINTR]) sig = SIGINT;
|
||||
else if (c == tio.c_cc[VQUIT]) sig = SIGQUIT;
|
||||
else if (c == tio.c_cc[VSUSP]) sig = SIGTSTP;
|
||||
if (sig) {
|
||||
echo(c);
|
||||
input.e = input.w = input.r; // discard the partial line
|
||||
input.abort_read = 1;
|
||||
release(&input.lock);
|
||||
sigsend_pgrp(tty_fg_pgrp, sig);
|
||||
acquire(&input.lock);
|
||||
wakeup(&input.r);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl-P: dump the process table. Not POSIX, but it is the one debugging
|
||||
// aid that works when everything else has stopped responding.
|
||||
if (c == C('P')) {
|
||||
procdump();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tio.c_lflag & ICANON) {
|
||||
if (c == tio.c_cc[VERASE] || c == C('H')) {
|
||||
erase_one();
|
||||
continue;
|
||||
}
|
||||
if (c == tio.c_cc[VKILL]) {
|
||||
while (input.e != input.w &&
|
||||
input.buf[(input.e-1) % INPUT_BUF] != '\n')
|
||||
erase_one();
|
||||
continue;
|
||||
}
|
||||
if (c == tio.c_cc[VWERASE]) {
|
||||
while (input.e != input.w &&
|
||||
input.buf[(input.e-1) % INPUT_BUF] == ' ')
|
||||
erase_one();
|
||||
while (input.e != input.w &&
|
||||
input.buf[(input.e-1) % INPUT_BUF] != ' ')
|
||||
erase_one();
|
||||
continue;
|
||||
}
|
||||
if (c == tio.c_cc[VEOF]) {
|
||||
if (input.e == input.w)
|
||||
input.eof = 1; // empty line: end of input
|
||||
input.w = input.e; // otherwise just submit what is there
|
||||
wakeup(&input.r);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (input.e - input.r >= INPUT_BUF)
|
||||
continue; // buffer full: drop it
|
||||
|
||||
// Everything else goes in, including bytes above 127 -- those are UTF-8
|
||||
// continuation bytes, and the old `c >= 32 && c <= 126` test silently
|
||||
// refused every non-ASCII character anyone tried to type.
|
||||
input.buf[input.e++ % INPUT_BUF] = c;
|
||||
echo(c);
|
||||
|
||||
// Canonical mode hands over a line at a time; raw mode hands over
|
||||
// everything as it arrives.
|
||||
if (!(tio.c_lflag & ICANON) ||
|
||||
c == '\n' || input.e == input.r + INPUT_BUF) {
|
||||
input.w = input.e;
|
||||
wakeup(&input.r);
|
||||
}
|
||||
}
|
||||
release(&input.lock);
|
||||
}
|
||||
|
||||
int
|
||||
consoleread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
uint target;
|
||||
int c;
|
||||
static uint32 prng_seed = 0x9A7B3C1D;
|
||||
|
||||
if (ip->minor == 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ip->minor == 4) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
prng_seed ^= prng_seed << 13;
|
||||
prng_seed ^= prng_seed >> 17;
|
||||
prng_seed ^= prng_seed << 5;
|
||||
dst[i] = prng_seed & 0xFF;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
iunlock(ip);
|
||||
target = n;
|
||||
acquire(&input.lock);
|
||||
while(n > 0){
|
||||
while(input.r == input.w){
|
||||
if (input.eof) {
|
||||
input.eof = 0;
|
||||
release(&input.lock);
|
||||
ilock(ip);
|
||||
return 0; // end of input
|
||||
}
|
||||
if (myproc()->killed || input.abort_read) {
|
||||
input.abort_read = 0;
|
||||
release(&input.lock);
|
||||
ilock(ip);
|
||||
return -1;
|
||||
}
|
||||
sleep(&input.r, &input.lock);
|
||||
}
|
||||
c = input.buf[input.r++ % INPUT_BUF];
|
||||
*dst++ = c;
|
||||
--n;
|
||||
|
||||
// Canonical mode returns a line at a time. Raw mode returns whatever has
|
||||
// arrived: a program that asked for a keypress must not be made to wait
|
||||
// for a newline that is never coming.
|
||||
if (tio.c_lflag & ICANON) {
|
||||
if (c == '\n')
|
||||
break;
|
||||
} else {
|
||||
if (target - n >= (uint)(tio.c_cc[VMIN] ? tio.c_cc[VMIN] : 1))
|
||||
break;
|
||||
}
|
||||
}
|
||||
release(&input.lock);
|
||||
ilock(ip);
|
||||
|
||||
return target - n;
|
||||
}
|
||||
|
||||
// Nonzero if a completed input line is waiting — poll(2)'s POLLIN answer
|
||||
// for the console tty. Mirrors the r/w cursors consoleread() blocks on.
|
||||
int
|
||||
console_input_ready(void)
|
||||
{
|
||||
int ready;
|
||||
|
||||
acquire(&input.lock);
|
||||
ready = input.r != input.w;
|
||||
release(&input.lock);
|
||||
return ready;
|
||||
}
|
||||
|
||||
int
|
||||
consolewrite(struct inode *ip, uint off, char *buf, int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (ip->minor == 2 || ip->minor == 3 || ip->minor == 4) {
|
||||
return n;
|
||||
}
|
||||
|
||||
iunlock(ip);
|
||||
acquire(&cons.lock);
|
||||
for(i = 0; i < n; i++)
|
||||
consputc(buf[i] & 0xff);
|
||||
release(&cons.lock);
|
||||
ilock(ip);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
disable_blink(void)
|
||||
{
|
||||
inb(0x3DA);
|
||||
outb(0x3C0, 0x10 | 0x20);
|
||||
uchar dummy = inb(0x3C1);
|
||||
outb(0x3C0, dummy & ~0x08);
|
||||
}
|
||||
|
||||
static void
|
||||
set_vga_palette(uchar index, uchar r, uchar g, uchar b)
|
||||
{
|
||||
outb(0x3C6, 0xFF);
|
||||
outb(0x3C8, index);
|
||||
outb(0x3C9, r & 0x3F);
|
||||
outb(0x3C9, g & 0x3F);
|
||||
outb(0x3C9, b & 0x3F);
|
||||
}
|
||||
|
||||
// The keyboard handler lives in kbd.c, but the console is what owns the
|
||||
// keyboard as an input source, so this is where the line gets claimed.
|
||||
static void
|
||||
kbd_irq(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
kbdintr();
|
||||
}
|
||||
|
||||
void
|
||||
consoleinit(void)
|
||||
{
|
||||
initlock(&cons.lock, "console");
|
||||
initlock(&input.lock, "input");
|
||||
|
||||
memset(&input, 0, sizeof(input));
|
||||
|
||||
devsw[CONSOLE].write = consolewrite;
|
||||
devsw[CONSOLE].read = consoleread;
|
||||
cons.locking = 1;
|
||||
|
||||
disable_blink();
|
||||
|
||||
set_vga_palette(0, 0, 0, 0); // Black
|
||||
set_vga_palette(1, 0, 0, 42); // Blue
|
||||
set_vga_palette(2, 0, 42, 0); // Green
|
||||
set_vga_palette(3, 0, 42, 42); // Cyan
|
||||
set_vga_palette(4, 42, 0, 0); // Red
|
||||
set_vga_palette(5, 42, 0, 42); // Magenta
|
||||
set_vga_palette(6, 42, 21, 0); // Brown
|
||||
set_vga_palette(7, 42, 42, 42); // Light Gray
|
||||
set_vga_palette(8, 21, 21, 21); // Dark Gray
|
||||
set_vga_palette(9, 21, 21, 63); // Light Blue
|
||||
set_vga_palette(10, 21, 63, 21); // Light Green
|
||||
set_vga_palette(11, 21, 63, 63); // Light Cyan
|
||||
set_vga_palette(12, 63, 21, 21); // Light Red
|
||||
set_vga_palette(13, 21, 21, 63); // Light Magenta
|
||||
set_vga_palette(14, 63, 63, 21); // Yellow
|
||||
set_vga_palette(15, 63, 63, 63); // White
|
||||
|
||||
irq_register(IRQ_KBD, kbd_irq, 0, "ps2-kbd", 0);
|
||||
}
|
||||
191
kernel/core/dma.c
Normal file
191
kernel/core/dma.c
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
// Physically contiguous memory for devices. See include/dma.h.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
#include "dma.h"
|
||||
|
||||
#define DMA_GRAIN 64 // one cache line; also xHCI's ring alignment
|
||||
#define NEXTENT 64 // free extents we can track
|
||||
|
||||
struct extent {
|
||||
addr_t base; // kernel virtual address
|
||||
uint64 len;
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct spinlock lock;
|
||||
struct extent free[NEXTENT];
|
||||
int n; // free extents, kept sorted by base
|
||||
addr_t lo, hi;
|
||||
uint64 total;
|
||||
} arena;
|
||||
|
||||
static uint64
|
||||
roundup(uint64 v, uint64 to)
|
||||
{
|
||||
return (v + to - 1) & ~(to - 1);
|
||||
}
|
||||
|
||||
void
|
||||
dma_init(void *vstart, void *vend)
|
||||
{
|
||||
initlock(&arena.lock, "dma");
|
||||
arena.lo = roundup((addr_t)vstart, PGSIZE);
|
||||
arena.hi = (addr_t)vend & ~(addr_t)(PGSIZE - 1);
|
||||
if(arena.hi <= arena.lo)
|
||||
panic("dma_init: empty arena");
|
||||
arena.free[0].base = arena.lo;
|
||||
arena.free[0].len = arena.hi - arena.lo;
|
||||
arena.n = 1;
|
||||
arena.total = arena.free[0].len;
|
||||
}
|
||||
|
||||
// Remove free[i], shifting the tail down.
|
||||
static void
|
||||
drop(int i)
|
||||
{
|
||||
int j;
|
||||
for(j = i; j + 1 < arena.n; j++)
|
||||
arena.free[j] = arena.free[j+1];
|
||||
arena.n--;
|
||||
}
|
||||
|
||||
// Insert a free extent at index i, shifting the tail up. Returns 0 if the
|
||||
// table is full, in which case the memory is leaked rather than corrupting
|
||||
// the list -- a leak is recoverable by rebooting, a corrupt free list hands
|
||||
// two drivers the same ring.
|
||||
static int
|
||||
insert(int i, addr_t base, uint64 len)
|
||||
{
|
||||
int j;
|
||||
|
||||
if(arena.n >= NEXTENT)
|
||||
return 0;
|
||||
for(j = arena.n; j > i; j--)
|
||||
arena.free[j] = arena.free[j-1];
|
||||
arena.free[i].base = base;
|
||||
arena.free[i].len = len;
|
||||
arena.n++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void *
|
||||
dma_alloc(uint64 size, uint64 align)
|
||||
{
|
||||
int i;
|
||||
addr_t start;
|
||||
|
||||
if(size == 0)
|
||||
return 0;
|
||||
if(align < DMA_GRAIN)
|
||||
align = DMA_GRAIN;
|
||||
if(align & (align - 1))
|
||||
panic("dma_alloc: alignment not a power of two");
|
||||
size = roundup(size, DMA_GRAIN);
|
||||
|
||||
acquire(&arena.lock);
|
||||
|
||||
for(i = 0; i < arena.n; i++){
|
||||
addr_t base = arena.free[i].base;
|
||||
uint64 len = arena.free[i].len;
|
||||
uint64 lead;
|
||||
|
||||
start = roundup(base, align);
|
||||
lead = start - base;
|
||||
if(lead + size > len)
|
||||
continue;
|
||||
|
||||
// Three cases: the request consumes the extent, sits at its head, sits
|
||||
// at its tail, or sits in the middle and splits it in two.
|
||||
if(lead == 0 && size == len){
|
||||
drop(i);
|
||||
} else if(lead == 0){
|
||||
arena.free[i].base += size;
|
||||
arena.free[i].len -= size;
|
||||
} else if(lead + size == len){
|
||||
arena.free[i].len = lead;
|
||||
} else {
|
||||
uint64 tail_len = len - lead - size;
|
||||
arena.free[i].len = lead;
|
||||
if(!insert(i + 1, start + size, tail_len)){
|
||||
// No room to record the tail. Give the whole extent to the caller
|
||||
// rather than lose track of where the tail was.
|
||||
arena.free[i].len = len;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
release(&arena.lock);
|
||||
memset((void*)start, 0, size);
|
||||
return (void*)start;
|
||||
}
|
||||
|
||||
release(&arena.lock);
|
||||
cprintf("dma_alloc: out of DMA memory (wanted %d bytes, %d free)\n",
|
||||
(int)size, (int)dma_free_bytes());
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
dma_free(void *p, uint64 size)
|
||||
{
|
||||
addr_t base = (addr_t)p;
|
||||
int i;
|
||||
|
||||
if(p == 0)
|
||||
return;
|
||||
size = roundup(size, DMA_GRAIN);
|
||||
if(base < arena.lo || base + size > arena.hi)
|
||||
panic("dma_free: not DMA memory");
|
||||
|
||||
acquire(&arena.lock);
|
||||
|
||||
// Find the first extent that starts after this block, so the list stays
|
||||
// sorted and neighbours end up adjacent for coalescing.
|
||||
for(i = 0; i < arena.n && arena.free[i].base < base; i++)
|
||||
;
|
||||
|
||||
if(i > 0 && arena.free[i-1].base + arena.free[i-1].len == base){
|
||||
arena.free[i-1].len += size; // merge with the block before
|
||||
i--;
|
||||
} else if(!insert(i, base, size)){
|
||||
release(&arena.lock);
|
||||
cprintf("dma_free: extent table full, leaking %d bytes\n", (int)size);
|
||||
return;
|
||||
}
|
||||
|
||||
if(i + 1 < arena.n &&
|
||||
arena.free[i].base + arena.free[i].len == arena.free[i+1].base){
|
||||
arena.free[i].len += arena.free[i+1].len; // and with the block after
|
||||
drop(i + 1);
|
||||
}
|
||||
|
||||
release(&arena.lock);
|
||||
}
|
||||
|
||||
addr_t
|
||||
dma_phys(const void *p)
|
||||
{
|
||||
return V2P((addr_t)p);
|
||||
}
|
||||
|
||||
uint64
|
||||
dma_free_bytes(void)
|
||||
{
|
||||
uint64 n = 0;
|
||||
int i;
|
||||
|
||||
for(i = 0; i < arena.n; i++)
|
||||
n += arena.free[i].len;
|
||||
return n;
|
||||
}
|
||||
|
||||
uint64
|
||||
dma_total_bytes(void)
|
||||
{
|
||||
return arena.total;
|
||||
}
|
||||
124
kernel/core/kalloc.c
Normal file
124
kernel/core/kalloc.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// Physical memory allocator, intended to allocate
|
||||
// memory for user processes, kernel stacks, page table pages,
|
||||
// and pipe buffers. Allocates 4096-byte pages.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "spinlock.h"
|
||||
|
||||
void freerange(void *vstart, void *vend);
|
||||
extern char end[]; // first address after kernel loaded from ELF file
|
||||
|
||||
|
||||
struct run {
|
||||
struct run *next;
|
||||
};
|
||||
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
int use_lock;
|
||||
struct run *freelist;
|
||||
} kmem;
|
||||
|
||||
// Catch kmem.freelist corruption at the point it's touched, rather than
|
||||
// letting a bad pointer be silently returned by kalloc() and crash whatever
|
||||
// code dereferences it minutes later on an unrelated call stack.
|
||||
static void
|
||||
check_run(struct run *r, char *where)
|
||||
{
|
||||
if(r == 0)
|
||||
return;
|
||||
if((addr_t)r % PGSIZE || (char*)r < end || V2P(r) >= PHYSTOP){
|
||||
cprintf("kalloc: corrupt freelist entry %p at %s\n", r, where);
|
||||
panic("kalloc: corrupt freelist");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
kinit1(void *vstart, void *vend)
|
||||
{
|
||||
initlock(&kmem.lock, "kmem");
|
||||
kmem.use_lock = 0;
|
||||
kmem.freelist = 0; // empty
|
||||
freerange(vstart, vend);
|
||||
}
|
||||
|
||||
void
|
||||
kinit2()
|
||||
{
|
||||
kmem.use_lock = 1;
|
||||
}
|
||||
|
||||
void
|
||||
freerange(void *vstart, void *vend)
|
||||
{
|
||||
char *p;
|
||||
p = (char*)PGROUNDUP((addr_t)vstart);
|
||||
for(; p + PGSIZE <= (char*)vend; p += PGSIZE)
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
// Free the page of physical memory pointed at by v,
|
||||
// which normally should have been returned by a
|
||||
// call to kalloc(). (The exception is when
|
||||
// initializing the allocator; see kinit above.)
|
||||
void
|
||||
kfree(char *v)
|
||||
{
|
||||
struct run *r;
|
||||
|
||||
if((addr_t)v % PGSIZE || v < end || V2P(v) >= PHYSTOP)
|
||||
panic("kfree");
|
||||
|
||||
// Fill with junk to catch dangling refs.
|
||||
memset(v, 1, PGSIZE);
|
||||
|
||||
if(kmem.use_lock)
|
||||
acquire(&kmem.lock);
|
||||
check_run(kmem.freelist, "kfree entry");
|
||||
r = (struct run*)v;
|
||||
r->next = kmem.freelist;
|
||||
kmem.freelist = r;
|
||||
if(kmem.use_lock)
|
||||
release(&kmem.lock);
|
||||
}
|
||||
|
||||
// Allocate one 4096-byte page of physical memory.
|
||||
// Returns a pointer that the kernel can use.
|
||||
// Returns 0 if the memory cannot be allocated.
|
||||
char*
|
||||
kalloc(void)
|
||||
{
|
||||
struct run *r;
|
||||
|
||||
if(kmem.use_lock)
|
||||
acquire(&kmem.lock);
|
||||
check_run(kmem.freelist, "kalloc entry");
|
||||
r = kmem.freelist;
|
||||
if(r)
|
||||
kmem.freelist = r->next;
|
||||
|
||||
if(kmem.use_lock)
|
||||
release(&kmem.lock);
|
||||
return (char*)r;
|
||||
}
|
||||
|
||||
// Total free physical memory, in bytes. Walks the free list; only /proc
|
||||
// readers call it, so the O(n) scan is not on any hot path.
|
||||
uint64
|
||||
kalloc_free_bytes(void)
|
||||
{
|
||||
struct run *r;
|
||||
uint64 n = 0;
|
||||
|
||||
if(kmem.use_lock)
|
||||
acquire(&kmem.lock);
|
||||
for(r = kmem.freelist; r; r = r->next)
|
||||
n += PGSIZE;
|
||||
if(kmem.use_lock)
|
||||
release(&kmem.lock);
|
||||
return n;
|
||||
}
|
||||
231
kernel/core/main.c
Normal file
231
kernel/core/main.c
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "x86.h"
|
||||
#include "irq.h"
|
||||
#include "dma.h"
|
||||
|
||||
static void startothers(void);
|
||||
static void mpmain(void) __attribute__((noreturn));
|
||||
extern char end[]; // first address after kernel loaded from ELF file
|
||||
|
||||
// Set by stage2main.c (boot/stage2main.c, BOOT_VERBOSE_FLAG_ADDR) when the
|
||||
// user picks "Boot Schunix (verbose)" from the F8 boot menu; read once here,
|
||||
// before anything else, so subsystem init can log its own steps.
|
||||
int boot_verbose;
|
||||
|
||||
// Must match BOOT_VERBOSE_FLAG_ADDR in boot/stage2main.c exactly: just past
|
||||
// the VBE info block, not 0x7000 (startothers() below uses that address for
|
||||
// the AP boot trampoline).
|
||||
#define BOOT_VERBOSE_FLAG_ADDR (0x30000 + 0x100)
|
||||
|
||||
// Bootstrap processor starts running C code here.
|
||||
// Allocate a real stack and switch to it, first
|
||||
// doing some setup required for memory allocator to work.
|
||||
int
|
||||
kmain(uint32 fb_addr)
|
||||
{
|
||||
boot_verbose = *(uchar*)P2V(BOOT_VERBOSE_FLAG_ADDR);
|
||||
|
||||
uartearlyinit();
|
||||
if (boot_verbose) cprintf("verbose boot: uartearlyinit done\n");
|
||||
// The DMA arena is carved off the top of physical memory *before* kalloc is
|
||||
// given the rest, because a device needs physically contiguous memory and
|
||||
// kalloc's free list makes no such promise -- two pages from it are adjacent
|
||||
// only by chance. Taking it first is the only way to be sure the region
|
||||
// exists; asking for it later would mean asking kalloc for something it
|
||||
// fundamentally cannot provide. See include/dma.h.
|
||||
// Before anything touches PHYSTOP, which is now a variable read from the
|
||||
// firmware's memory map rather than a constant asserting 224 MB exists.
|
||||
e820_init();
|
||||
kinit1(end, P2V(PHYSTOP - DMA_ARENA_BYTES));
|
||||
if (boot_verbose) cprintf("verbose boot: kinit1 done\n");
|
||||
dma_init(P2V(PHYSTOP - DMA_ARENA_BYTES), P2V(PHYSTOP));
|
||||
if (boot_verbose) cprintf("verbose boot: dma_init done\n");
|
||||
kvmalloc();
|
||||
if (boot_verbose) cprintf("verbose boot: kvmalloc done\n");
|
||||
// ACPI first, the legacy MP table only if it finds nothing. Modern firmware
|
||||
// publishes a complete MADT and an MP table with one processor in it, so
|
||||
// asking the older interface first means never seeing more than one CPU.
|
||||
// See kernel/arch/x86_64/acpi.c.
|
||||
if (!acpi_init())
|
||||
mpinit();
|
||||
if (boot_verbose) cprintf("verbose boot: cpu enumeration done\n");
|
||||
lapicinit();
|
||||
if (boot_verbose) cprintf("verbose boot: lapicinit done\n");
|
||||
tvinit();
|
||||
if (boot_verbose) cprintf("verbose boot: tvinit done\n");
|
||||
seginit();
|
||||
if (boot_verbose) cprintf("verbose boot: seginit done\n");
|
||||
ioapicinit();
|
||||
if (boot_verbose) cprintf("verbose boot: ioapicinit done\n");
|
||||
irq_init(); // must precede any driver init that registers a handler
|
||||
if (boot_verbose) cprintf("verbose boot: irq_init done\n");
|
||||
// Before the keyboard and mouse, which start producing events the moment
|
||||
// their handlers are registered.
|
||||
inputinit();
|
||||
if (boot_verbose) cprintf("verbose boot: inputinit done\n");
|
||||
consoleinit();
|
||||
if (boot_verbose) cprintf("verbose boot: consoleinit done\n");
|
||||
uartinit();
|
||||
if (boot_verbose) cprintf("verbose boot: uartinit done\n");
|
||||
mouseinit();
|
||||
if (boot_verbose) cprintf("verbose boot: mouseinit done\n");
|
||||
vga_init(fb_addr);
|
||||
if (boot_verbose) cprintf("verbose boot: vga_init done (fb=0x%x)\n", fb_addr);
|
||||
// No splash drawing here on purpose. The loader owns the splash, logo and
|
||||
// all, and vga_init() deliberately does not clear -- so it is still on the
|
||||
// display right through to console_takeover(). Painting it again from here
|
||||
// would land in the last few hundred milliseconds of boot and change nothing.
|
||||
// After vga_init, because the display driver cross-checks the framebuffer
|
||||
// address it finds on the bus against the one VBE reported -- and it can
|
||||
// only do that once vga.c has read the VBE block.
|
||||
// The devices that are simply known to be on a PC rather than enumerated by
|
||||
// any bus. Registered before pci_init() so that /proc/devices lists them
|
||||
// first, which puts the machine's fixed furniture above whatever happened to
|
||||
// be plugged into it.
|
||||
platform_register_devices();
|
||||
pci_init();
|
||||
if (boot_verbose) cprintf("verbose boot: pci_init done\n");
|
||||
pinit();
|
||||
if (boot_verbose) cprintf("verbose boot: pinit done\n");
|
||||
binit();
|
||||
if (boot_verbose) cprintf("verbose boot: binit done\n");
|
||||
fileinit();
|
||||
if (boot_verbose) cprintf("verbose boot: fileinit done\n");
|
||||
sockinit();
|
||||
if (boot_verbose) cprintf("verbose boot: sockinit done\n");
|
||||
// After the timer is running, so boot_epoch can subtract the uptime so far,
|
||||
// and before userspace, so the very first timestamp any program takes is
|
||||
// already real. Reading the RTC is slow, which is exactly why it happens once
|
||||
// here rather than on every gettimeofday().
|
||||
time_init();
|
||||
ideinit();
|
||||
if (boot_verbose) cprintf("verbose boot: ideinit done\n");
|
||||
ramdiskinit();
|
||||
if (boot_verbose) cprintf("verbose boot: ramdiskinit done\n");
|
||||
// Start the application processors.
|
||||
//
|
||||
// This was broken from the beginning of the project and is fixed as of
|
||||
// 2026-08-08. Five distinct bugs, each hidden behind the one before it, and
|
||||
// all of them invisible under QEMU because it publishes only one processor in
|
||||
// the legacy MP table -- see kernel/arch/x86_64/acpi.c. VMware published the
|
||||
// real count, which is why the triple fault only ever appeared there.
|
||||
//
|
||||
// 1. entry64high had no AP branch at all; the check was a comment saying it
|
||||
// had never been written. Every AP fell into the boot processor's path,
|
||||
// took the BSP's stack, and ran kmain() a second time.
|
||||
// 2. seginit() called kalloc() for its per-CPU page. kalloc locks, locking
|
||||
// reads `cpu`, and `cpu` is what seginit() was about to set up.
|
||||
// 3. kernel_fs_base was one global for all CPUs, and trapasm.S read it on
|
||||
// every entry from user mode, so both CPUs shared one per-CPU block.
|
||||
// 4. `cpu` and `proc` were __thread. That means per *thread*, so the compiler
|
||||
// cached the thread pointer across calls -- but %fs here is per *CPU*, and
|
||||
// a kernel thread changes CPU at every sched(). See include/proc.h.
|
||||
// 5. The new per-CPU slots collided with syscall_entry's scratch word at
|
||||
// %fs:(8), which is the same memory as %gs:8.
|
||||
//
|
||||
int smp_start_aps = 1;
|
||||
if (smp_start_aps) {
|
||||
startothers();
|
||||
if (boot_verbose) cprintf("verbose boot: startothers done\n");
|
||||
} else if (ncpu > 1) {
|
||||
cprintf("smp: %d cpus found, running on 1 (AP startup is broken)\n", ncpu);
|
||||
}
|
||||
kinit2();
|
||||
if (boot_verbose) cprintf("verbose boot: kinit2 done\n");
|
||||
sinit();
|
||||
if (boot_verbose) cprintf("verbose boot: sinit done\n");
|
||||
mpmain();
|
||||
}
|
||||
|
||||
// Other CPUs jump here from entryother.S.
|
||||
void
|
||||
mpenter(void)
|
||||
{
|
||||
switchkvm();
|
||||
seginit();
|
||||
lapicinit();
|
||||
mpmain();
|
||||
}
|
||||
|
||||
// Enable SSE/FXSAVE so user binaries can use XMM registers.
|
||||
static void
|
||||
enable_sse(void)
|
||||
{
|
||||
uint64 cr0, cr4;
|
||||
asm volatile("mov %%cr0, %0" : "=r"(cr0));
|
||||
cr0 &= ~(1UL << 2); // clear CR0.EM (no FP emulation)
|
||||
cr0 |= (1UL << 1); // set CR0.MP (monitor coprocessor)
|
||||
asm volatile("mov %0, %%cr0" :: "r"(cr0));
|
||||
asm volatile("mov %%cr4, %0" : "=r"(cr4));
|
||||
cr4 |= (1UL << 9); // CR4.OSFXSR — OS handles FXSAVE/FXRSTOR
|
||||
cr4 |= (1UL << 10); // CR4.OSXMMEXCPT — OS handles SSE exceptions
|
||||
asm volatile("mov %0, %%cr4" :: "r"(cr4));
|
||||
}
|
||||
|
||||
// Common CPU setup code.
|
||||
static void
|
||||
mpmain(void)
|
||||
{
|
||||
cprintf("cpu%d: starting\n", cpunum());
|
||||
enable_sse(); // allow user XMM/SSE instructions
|
||||
idtinit(); // load idt register
|
||||
syscallinit(); // syscall set up
|
||||
xchg(&mycpu()->started, 1); // tell startothers() we're up
|
||||
scheduler(); // start running processes
|
||||
}
|
||||
|
||||
void entry32mp(void);
|
||||
|
||||
// Start the non-boot (AP) processors.
|
||||
static void
|
||||
startothers(void)
|
||||
{
|
||||
extern uchar _binary_entryother_start[], _binary_entryother_size[];
|
||||
uchar *code;
|
||||
struct cpu *c;
|
||||
char *stack;
|
||||
|
||||
// Write entry code to unused memory at 0x7000.
|
||||
// The linker has placed the image of entryother.S in
|
||||
// _binary_entryother_start.
|
||||
code = P2V(0x7000);
|
||||
memmove(code, _binary_entryother_start,
|
||||
(addr_t)_binary_entryother_size);
|
||||
|
||||
for(c = cpus; c < cpus+ncpu; c++){
|
||||
if(c == cpus+cpunum()) // We've started already.
|
||||
continue;
|
||||
|
||||
// Tell entryother.S what stack to use, where to enter, and what
|
||||
// pgdir to use. We cannot use kpgdir yet, because the AP processor
|
||||
// is running in low memory, so we use entrypgdir for the APs too.
|
||||
// The AP's per-CPU page, allocated here rather than by the AP itself.
|
||||
// seginit() explains why: an AP cannot call kalloc() before %fs is set up,
|
||||
// because kalloc locks and locking reads the per-CPU pointer.
|
||||
if (c->local == 0) {
|
||||
c->local = kalloc();
|
||||
memset(c->local, 0, PGSIZE);
|
||||
}
|
||||
|
||||
stack = kalloc();
|
||||
*(uint32*)(code-4) = 0x8000; // enough stack to get us to entry64mp
|
||||
*(uint32*)(code-8) = v2p(entry32mp);
|
||||
*(uint64*)(code-16) = (uint64) (stack + KSTACKSIZE);
|
||||
|
||||
lapicstartap(c->apicid, V2P(code));
|
||||
|
||||
// wait for cpu to finish mpmain()
|
||||
while(c->started == 0)
|
||||
;
|
||||
|
||||
// Counted here, on the boot processor, after the AP has actually reported
|
||||
// in -- not by the AP itself. This is serialised by the loop, so it needs
|
||||
// no atomic, and it cannot overcount a processor that started and died.
|
||||
ncpu_online++;
|
||||
}
|
||||
}
|
||||
543
kernel/core/panic.c
Normal file
543
kernel/core/panic.c
Normal file
|
|
@ -0,0 +1,543 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// PSOGM — the Schunix crash screen.
|
||||
//
|
||||
// The name is a nod to the Amiga's "Guru Meditation", but nothing else about
|
||||
// this is a nod to 1985. Three things it is built around:
|
||||
//
|
||||
// *It does not clear the screen.* Whatever was being displayed stays there,
|
||||
// dimmed and pushed back, with the report floating over it. A crash screen that
|
||||
// wipes the display tells you the machine is gone; one that leaves your work
|
||||
// visible underneath tells you it stopped, which is both truer and much less
|
||||
// alarming.
|
||||
//
|
||||
// *It is not frightening.* No red, no capitals, no "FATAL". A calm sentence, a
|
||||
// face that is obviously a cartoon, and then the facts. Whoever is reading this
|
||||
// is already having a bad time.
|
||||
//
|
||||
// *It says considerably more than it needs to.* The screen is the only artefact
|
||||
// of a kernel panic — there is no core dump and no log to go back to — so
|
||||
// everything cheap to collect is collected: which CPU, which process, how far
|
||||
// into the boot, where the kernel is loaded, and a call chain. Anything left
|
||||
// out here is gone.
|
||||
//
|
||||
// Everything is also written to the serial console, in the same order. That is
|
||||
// what tools/bootcheck.ps1 reads, and it is what you get when the crash happens
|
||||
// before there is a framebuffer at all.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "x86.h"
|
||||
#include "proc.h"
|
||||
#include "spinlock.h"
|
||||
// Purple, because the name says so, and white on it because nothing else is
|
||||
// legible at every brightness a monitor might be set to. The palette lives in
|
||||
// theme.h with everything else; this file used to keep its own copy under the
|
||||
// same names but with different values, which meant theme.h described a crash
|
||||
// screen nobody had ever seen.
|
||||
#include "theme.h"
|
||||
#include "qr.h"
|
||||
|
||||
extern uint ticks;
|
||||
extern int panicked;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Small formatting helpers */
|
||||
/* */
|
||||
/* cprintf goes to the console, which during a panic is also the screen */
|
||||
/* being painted over -- so the panel builds its strings itself and */
|
||||
/* places them with vga_text_at. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static char *
|
||||
put_str(char *p, char *end, const char *s)
|
||||
{
|
||||
while (*s && p < end - 1)
|
||||
*p++ = *s++;
|
||||
*p = '\0';
|
||||
return p;
|
||||
}
|
||||
|
||||
static char *
|
||||
put_dec(char *p, char *end, uint64 v)
|
||||
{
|
||||
char tmp[24];
|
||||
int i = 0;
|
||||
if (v == 0)
|
||||
tmp[i++] = '0';
|
||||
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (v % 10); v /= 10; }
|
||||
while (i-- && p < end - 1)
|
||||
*p++ = tmp[i];
|
||||
*p = '\0';
|
||||
return p;
|
||||
}
|
||||
|
||||
static char *
|
||||
put_hex(char *p, char *end, uint64 v, int digits)
|
||||
{
|
||||
const char *d = "0123456789abcdef";
|
||||
if (p < end - 2) { *p++ = '0'; *p++ = 'x'; }
|
||||
for (int i = digits - 1; i >= 0 && p < end - 1; i--)
|
||||
*p++ = d[(v >> (i * 4)) & 0xF];
|
||||
*p = '\0';
|
||||
return p;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* The face */
|
||||
/* */
|
||||
/* ":(" — two dots and a parenthesis, drawn large. It is a sad face */
|
||||
/* only if you tilt your head, which is exactly why it works: it is */
|
||||
/* legible as a mood without being a picture of anything, and it does */
|
||||
/* not pretend the machine has feelings about what just happened. */
|
||||
/* */
|
||||
/* Drawn from primitives rather than stored as a bitmap, so it scales */
|
||||
/* with the display and costs no space in a kernel image that stage2 */
|
||||
/* loads one sector at a time by PIO. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int
|
||||
isqrt(int v)
|
||||
{
|
||||
if (v <= 0)
|
||||
return 0;
|
||||
|
||||
// Newton's method, iterated only while the estimate is still *decreasing*.
|
||||
//
|
||||
// The obvious "stop when it stops changing" version does not terminate: on a
|
||||
// non-square it settles into a two-cycle rather than a fixed point, and
|
||||
// isqrt(3) alternates between 1 and 2 forever. In a panic handler that is a
|
||||
// hang with the screen half drawn and no way to find out why.
|
||||
int x = v;
|
||||
int y = (x + 1) / 2;
|
||||
while (y < x) {
|
||||
x = y;
|
||||
y = (x + v / x) / 2;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// The "(" of ":(" — the left half of a squashed circle, so the tips point right
|
||||
// and the middle bulges left, the way the glyph does.
|
||||
static void
|
||||
draw_paren(int cx, int cy, int h, int thick, uint32 color)
|
||||
{
|
||||
int r = h / 2;
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
int dx = isqrt(r * r - dy * dy);
|
||||
// Narrow it: a true half-circle reads as a bracket, not a parenthesis.
|
||||
int x = cx - (dx * 45) / 100;
|
||||
vga_fill_circle(x, cy + dy, thick / 2, color);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
draw_sad_face(int x, int y, int h, uint32 color)
|
||||
{
|
||||
int dot = h / 12;
|
||||
int gap = h / 4;
|
||||
|
||||
// The colon.
|
||||
vga_fill_circle(x + dot, y + h / 2 - gap, dot, color);
|
||||
vga_fill_circle(x + dot, y + h / 2 + gap, dot, color);
|
||||
|
||||
// The parenthesis, a little to the right of it.
|
||||
draw_paren(x + dot * 2 + h / 2, y + h / 2, h, dot * 2, color);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* The report */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
struct field {
|
||||
const char *label;
|
||||
char value[72];
|
||||
};
|
||||
|
||||
#define NFIELD 10
|
||||
|
||||
static struct field fields[NFIELD];
|
||||
static int nfields;
|
||||
|
||||
static struct field *
|
||||
field(const char *label)
|
||||
{
|
||||
if (nfields >= NFIELD)
|
||||
return &fields[NFIELD - 1];
|
||||
struct field *f = &fields[nfields++];
|
||||
f->label = label;
|
||||
f->value[0] = '\0';
|
||||
return f;
|
||||
}
|
||||
|
||||
// Collect everything worth knowing while the machine is still in the state
|
||||
// that produced the fault. Interrupts are already off and no lock is taken:
|
||||
// a panic that deadlocks trying to report itself tells you nothing at all.
|
||||
// A short, shoutable name for the failure, derived from the panic message
|
||||
// rather than declared at every call site.
|
||||
//
|
||||
// Windows stop codes are genuinely useful: they are the thing you search for,
|
||||
// and they stay the same across builds while the prose around them changes.
|
||||
// Taking the message up to its first colon (or its first few words) gets that
|
||||
// for free -- "sysrq: crash requested" becomes SYSRQ, "short filewrite" becomes
|
||||
// SHORT_FILEWRITE -- with no risk of a call site forgetting to supply one.
|
||||
static void
|
||||
stop_code(char *out, int cap, const char *reason)
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = 0; reason[i] && n < cap - 1; i++) {
|
||||
char c = reason[i];
|
||||
if (c == ':')
|
||||
break;
|
||||
if (c == ' ' || c == '\t') {
|
||||
if (n == 0 || out[n-1] == '_')
|
||||
continue;
|
||||
out[n++] = '_';
|
||||
continue;
|
||||
}
|
||||
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')
|
||||
out[n++] = c;
|
||||
}
|
||||
while (n > 0 && out[n-1] == '_')
|
||||
n--;
|
||||
if (n == 0)
|
||||
{ out[n++] = 'P'; out[n++] = 'A'; out[n++] = 'N'; out[n++] = 'I'; out[n++] = 'C'; }
|
||||
out[n] = '\0';
|
||||
}
|
||||
|
||||
static void
|
||||
gather(char *reason, addr_t *pcs, int npcs)
|
||||
{
|
||||
char *e;
|
||||
struct field *f;
|
||||
|
||||
nfields = 0;
|
||||
|
||||
f = field("Stop code");
|
||||
stop_code(f->value, sizeof(f->value), reason);
|
||||
|
||||
f = field("Reason");
|
||||
put_str(f->value, f->value + sizeof(f->value), reason);
|
||||
|
||||
f = field("CPU");
|
||||
e = f->value + sizeof(f->value);
|
||||
put_dec(f->value, e, mycpu() ? (uint64)mycpu()->id : 0);
|
||||
if (!mycpu())
|
||||
put_str(f->value, e, "unknown (very early boot)");
|
||||
|
||||
f = field("Process");
|
||||
e = f->value + sizeof(f->value);
|
||||
if (myproc()) {
|
||||
char *p = put_str(f->value, e, myproc()->name);
|
||||
p = put_str(p, e, " pid ");
|
||||
put_dec(p, e, (uint64)myproc()->pid);
|
||||
} else {
|
||||
put_str(f->value, e, "none -- in the kernel, not on anybody's behalf");
|
||||
}
|
||||
|
||||
f = field("Uptime");
|
||||
e = f->value + sizeof(f->value);
|
||||
{
|
||||
char *p = put_dec(f->value, e, (uint64)ticks);
|
||||
p = put_str(p, e, " ticks (about ");
|
||||
p = put_dec(p, e, (uint64)ticks / 100);
|
||||
put_str(p, e, " s)");
|
||||
}
|
||||
|
||||
// Every address below is a link-time address. Printing where the kernel
|
||||
// actually sits is what lets addr2line resolve them afterwards, and it is
|
||||
// the single most useful line here when the trace is all you have.
|
||||
f = field("Kernel base");
|
||||
e = f->value + sizeof(f->value);
|
||||
put_hex(f->value, e, (uint64)KERNBASE, 16);
|
||||
|
||||
f = field("Fault PC");
|
||||
e = f->value + sizeof(f->value);
|
||||
if (npcs > 0 && pcs[0])
|
||||
put_hex(f->value, e, (uint64)pcs[0], 16);
|
||||
else
|
||||
put_str(f->value, e, "unavailable");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* The QR code */
|
||||
/* */
|
||||
/* A panic report is only worth what can be carried away from it, and */
|
||||
/* at this point there is no network, no log to write to, and often no */
|
||||
/* second machine on the desk. Photographing the screen works but */
|
||||
/* turns a call chain into something that has to be retyped by hand, */
|
||||
/* with sixteen hex digits per frame and no way to tell 8 from B. */
|
||||
/* */
|
||||
/* A phone camera is a working out-of-band channel that needs no */
|
||||
/* driver on our side at all. See kernel/core/qr.c. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
// Deliberately not a URL. This encodes the report itself, so it goes wherever
|
||||
// the person holding the phone decides -- and reaches nothing on its own.
|
||||
//
|
||||
// Compact because the budget is 134 bytes for the largest symbol this encoder
|
||||
// builds, and a crash with a deep call chain is exactly when the budget is
|
||||
// tightest. Two decisions buy most of the room: frames are stored as offsets
|
||||
// from the kernel base rather than absolute addresses, which halves 16 hex
|
||||
// digits to 8, and the fields are single letters. The base is included, so
|
||||
// nothing is lost -- addr2line wants the link-time address and can be given it.
|
||||
//
|
||||
// Fields are appended in priority order and the loop simply stops when the
|
||||
// buffer is full, so a long reason string costs call-chain depth rather than
|
||||
// producing a report that will not encode.
|
||||
static int
|
||||
build_qr_payload(char *buf, int cap, char *reason, addr_t *pcs, int npcs)
|
||||
{
|
||||
char *p = buf, *end = buf + cap;
|
||||
|
||||
// Magic and format version, so whatever reads this later can tell what it is
|
||||
// looking at and can change the layout without ambiguity.
|
||||
p = put_str(p, end, "SXPSOGM1\n");
|
||||
|
||||
// The reason, not the stop code: the stop code is derived from the reason by
|
||||
// stop_code() and carries strictly less, so encoding both would spend bytes
|
||||
// on something the reader can recompute.
|
||||
//
|
||||
// Held back from the end of the buffer so that a long message costs some of
|
||||
// the call chain but never all of it. A report with a sentence and no
|
||||
// addresses is much harder to act on than one with addresses and a truncated
|
||||
// sentence.
|
||||
{
|
||||
char *reason_end = end - 34; // three frames and their separators
|
||||
if (reason_end > p)
|
||||
p = put_str(p, reason_end, reason);
|
||||
}
|
||||
|
||||
p = put_str(p, end, "\nc");
|
||||
p = put_dec(p, end, mycpu() ? (uint64)mycpu()->id : 0);
|
||||
p = put_str(p, end, " p");
|
||||
p = put_dec(p, end, myproc() ? (uint64)myproc()->pid : 0);
|
||||
p = put_str(p, end, " t");
|
||||
p = put_dec(p, end, (uint64)ticks);
|
||||
if (myproc()) {
|
||||
p = put_str(p, end, " ");
|
||||
p = put_str(p, end, myproc()->name);
|
||||
}
|
||||
|
||||
p = put_str(p, end, "\nb");
|
||||
p = put_hex(p, end, (uint64)KERNBASE, 16);
|
||||
|
||||
p = put_str(p, end, "\n");
|
||||
for (int i = 0; i < npcs && pcs[i]; i++) {
|
||||
// Nine bytes: eight hex digits and the separator. Stopping here rather than
|
||||
// letting put_hex truncate matters -- half an address in a crash report is
|
||||
// worse than one fewer address, because it looks like a real one.
|
||||
if (end - p < 10)
|
||||
break;
|
||||
if (i)
|
||||
p = put_str(p, end, " ");
|
||||
// put_hex prefixes "0x", which is 14 wasted bytes across seven frames.
|
||||
{
|
||||
const char *d = "0123456789abcdef";
|
||||
uint64 off = (uint64)pcs[i] - (uint64)KERNBASE;
|
||||
for (int k = 7; k >= 0; k--)
|
||||
*p++ = d[(off >> (k * 4)) & 0xF];
|
||||
*p = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
return (int)(p - buf);
|
||||
}
|
||||
|
||||
// One static symbol rather than a local: struct qr is about 3.4 KB and the
|
||||
// panic path runs on whatever stack the fault left behind, which may not have
|
||||
// that much room. It is only ever touched here, after everything else has
|
||||
// stopped.
|
||||
static struct qr panic_qr;
|
||||
|
||||
// Returns the width used, so the text column knows what it may not overlap.
|
||||
static int
|
||||
draw_qr_panel(int sw, int sh, int ch, char *reason, addr_t *pcs, int npcs)
|
||||
{
|
||||
char payload[QR_MAX_BYTES];
|
||||
int n = build_qr_payload(payload, sizeof(payload), reason, pcs, npcs);
|
||||
|
||||
if (n <= 0 || qr_encode(&panic_qr, (unsigned char *)payload, n) != 0)
|
||||
return 0;
|
||||
|
||||
// Four modules of quiet zone, which the standard requires and which decoders
|
||||
// genuinely need -- without it many will not find the symbol at all.
|
||||
const int quiet = 4;
|
||||
int cells = panic_qr.size + 2 * quiet;
|
||||
|
||||
// Scale to about a quarter of the display, then clamp. The lower bound is
|
||||
// what a phone can still resolve from a photograph of a monitor; the upper
|
||||
// bound stops the thing dominating a screen whose actual job is the text.
|
||||
int scale = (sw / 4) / cells;
|
||||
if (scale < 3) scale = 3;
|
||||
if (scale > 8) scale = 8;
|
||||
|
||||
int card = cells * scale;
|
||||
int x0 = sw - card - sw / 24;
|
||||
int y0 = sh / 8;
|
||||
|
||||
vga_fill_rect(x0, y0, card, card, PSOGM_CARD);
|
||||
for (int y = 0; y < panic_qr.size; y++)
|
||||
for (int x = 0; x < panic_qr.size; x++)
|
||||
if (panic_qr.mod[y][x])
|
||||
vga_fill_rect(x0 + (x + quiet) * scale, y0 + (y + quiet) * scale,
|
||||
scale, scale, PSOGM_INK);
|
||||
|
||||
vga_text_at(x0, y0 + card + ch, "Scan for a text copy", PSOGM_DIM);
|
||||
vga_text_at(x0, y0 + card + ch * 2 + 2, "of this report.", PSOGM_DIM);
|
||||
|
||||
return card + sw / 24;
|
||||
}
|
||||
|
||||
// The frame pointers, one per line, as one string per row.
|
||||
static void
|
||||
trace_line(char *buf, int cap, int i, addr_t pc)
|
||||
{
|
||||
char *e = buf + cap;
|
||||
char *p = put_str(buf, e, " #");
|
||||
p = put_dec(p, e, (uint64)i);
|
||||
p = put_str(p, e, i < 10 ? " " : " ");
|
||||
put_hex(p, e, (uint64)pc, 16);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void
|
||||
paint(char *reason, addr_t *pcs, int npcs)
|
||||
{
|
||||
int sw, sh;
|
||||
vga_dims(&sw, &sh);
|
||||
|
||||
int ch = vga_char_h();
|
||||
int cw = vga_char_w();
|
||||
|
||||
// The whole display, one colour. An earlier version dimmed the previous
|
||||
// screen and floated a panel over it, which looked good and was wrong: the
|
||||
// thing is called a Purple Screen, and half a screen of somebody's abandoned
|
||||
// work behind the report is a distraction at the worst possible moment.
|
||||
vga_clear(PSOGM_BG);
|
||||
// Glyphs are drawn with their background filled, so the console's cream has
|
||||
// to become the background or every line of text arrives in its own box.
|
||||
vga_set_bg(PSOGM_BG);
|
||||
|
||||
// The symbol goes first, because it decides how much width the text has. It
|
||||
// is drawn before anything can fail: if the encoder refuses the payload the
|
||||
// screen simply has no card and the report is unchanged.
|
||||
draw_qr_panel(sw, sh, ch, reason, pcs, npcs);
|
||||
|
||||
int left = sw / 8;
|
||||
if (left > 110) left = 110;
|
||||
int y = sh / 8;
|
||||
|
||||
int face_h = 96;
|
||||
draw_sad_face(left, y, face_h, PSOGM_FG);
|
||||
y += face_h + ch * 2;
|
||||
|
||||
// Left-aligned, not centred. Centred text reads as a poster; this is a
|
||||
// report, and a report is read down the left edge.
|
||||
vga_text_at(left, y, "Schunix ran into a problem and needs to restart.",
|
||||
PSOGM_FG);
|
||||
y += ch + ch / 2;
|
||||
vga_text_at(left, y, "Nothing on your disks was touched by this.",
|
||||
PSOGM_DIM);
|
||||
|
||||
// A rule in the boot splash's accent, the same width as the one under the
|
||||
// wordmark there, tying this screen to the one the machine started with.
|
||||
//
|
||||
// Equal space above and below on purpose: at any less it sits close enough to
|
||||
// the line above to read as an underline of that sentence rather than as a
|
||||
// divider before the facts.
|
||||
y += ch * 2;
|
||||
vga_fill_rect(left, y, 120, 2, SCH_ACCENT);
|
||||
y += ch;
|
||||
|
||||
// Facts: quiet labels down the left, bright values in a second column.
|
||||
int vx = left + 14 * cw;
|
||||
for (int i = 0; i < nfields; i++) {
|
||||
vga_text_at(left, y, fields[i].label, PSOGM_DIM);
|
||||
vga_text_at(vx, y, fields[i].value, PSOGM_FG);
|
||||
y += ch + 2;
|
||||
}
|
||||
|
||||
y += ch;
|
||||
vga_text_at(left, y, "Call chain", PSOGM_DIM);
|
||||
y += ch + 2;
|
||||
|
||||
for (int i = 0; i < npcs && pcs[i]; i++) {
|
||||
char line[48];
|
||||
trace_line(line, sizeof(line), i, pcs[i]);
|
||||
vga_text_at(vx - 3 * cw, y, line, PSOGM_FG);
|
||||
y += ch;
|
||||
if (y > sh - ch * 5)
|
||||
break;
|
||||
}
|
||||
|
||||
// Two closing lines, bottom left: what to do with the numbers above, and
|
||||
// what this screen is called, so it can be searched for by name. Shortened
|
||||
// from the full command line, which at 62 characters ran under the QR card.
|
||||
vga_text_at(left, sh - ch * 3,
|
||||
"Resolve: addr2line -e kernel <address>", PSOGM_DIM);
|
||||
vga_text_at(left, sh - ch * 3 + ch + 4,
|
||||
"PSOGM -- Purple Screen of Guru Meditation", PSOGM_DIM);
|
||||
}
|
||||
|
||||
// Same report, down the serial line. This is the copy that survives: it is what
|
||||
// bootcheck reads, and the only one that exists when the framebuffer does not.
|
||||
static void
|
||||
report_serial(char *reason, addr_t *pcs, int npcs)
|
||||
{
|
||||
cprintf("\n");
|
||||
cprintf(" :(\n\n");
|
||||
cprintf(" Schunix ran into a problem and needs to restart.\n");
|
||||
cprintf(" PSOGM -- Purple Screen of Guru Meditation\n\n");
|
||||
|
||||
for (int i = 0; i < nfields; i++) {
|
||||
// Pad the label out so the values line up, without a printf width
|
||||
// specifier -- cprintf has no such thing.
|
||||
int n = 0;
|
||||
while (fields[i].label[n]) n++;
|
||||
cprintf(" %s", fields[i].label);
|
||||
for (int pad = n; pad < 14; pad++)
|
||||
cprintf(" ");
|
||||
cprintf("%s\n", fields[i].value);
|
||||
}
|
||||
|
||||
cprintf("\n Call chain\n");
|
||||
for (int i = 0; i < npcs && pcs[i]; i++)
|
||||
cprintf(" #%d %p\n", i, pcs[i]);
|
||||
|
||||
cprintf("\n Resolve with: addr2line -e build/kernel/kernel <address>\n");
|
||||
cprintf(" Reason: %s\n", reason);
|
||||
cprintf(" System halted.\n");
|
||||
}
|
||||
|
||||
__attribute__((noreturn))
|
||||
void
|
||||
panic(char *s)
|
||||
{
|
||||
addr_t pcs[10];
|
||||
|
||||
cli();
|
||||
// Stop every other path from writing to the console. consputc checks this
|
||||
// and spins, which is what keeps another CPU from painting over the report
|
||||
// while it is being drawn.
|
||||
cons_panic_mode();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
pcs[i] = 0;
|
||||
getcallerpcs(&s, pcs);
|
||||
|
||||
gather(s, pcs, 10);
|
||||
report_serial(s, pcs, 10);
|
||||
|
||||
if (vga_ready())
|
||||
paint(s, pcs, 10);
|
||||
|
||||
panicked = 1;
|
||||
for (;;)
|
||||
hlt();
|
||||
}
|
||||
456
kernel/core/qr.c
Normal file
456
kernel/core/qr.c
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
// QR encoder. See include/qr.h for why it exists and why it is this small.
|
||||
//
|
||||
// Freestanding on purpose: no allocation, no floating point, no libc beyond
|
||||
// memset/memmove. It has to run in panic context, where the heap may be exactly
|
||||
// what is broken. Every buffer here is either on the caller's struct or a
|
||||
// bounded local.
|
||||
//
|
||||
// The structure follows ISO/IEC 18004 in the order the standard describes it,
|
||||
// which is also the only order in which any of it can be checked:
|
||||
//
|
||||
// 1. encode the payload into data codewords
|
||||
// 2. append Reed-Solomon error correction, per block
|
||||
// 3. interleave the blocks
|
||||
// 4. draw the function patterns (finders, timing, alignment, format area)
|
||||
// 5. lay the codeword bits into the remaining modules in a zigzag
|
||||
// 6. try all 8 masks, score each, keep the best
|
||||
//
|
||||
// Verified by scanning the output with a phone, which is the only test that
|
||||
// matters and is also literally the feature.
|
||||
|
||||
#ifdef QR_HOST
|
||||
#include <string.h>
|
||||
#else
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#endif
|
||||
|
||||
#include "qr.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Version table
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// Per version, at error correction level L: total codewords, error correction
|
||||
// codewords per block, and number of blocks. These are from the standard's
|
||||
// tables and cannot be derived, so they are the one part of this file that has
|
||||
// to be right by transcription rather than by reasoning.
|
||||
//
|
||||
// A useful check on them: total - ecc*blocks must be divisible by blocks for
|
||||
// every row below, because levels 1..6 at L happen to have equally sized
|
||||
// blocks. qr_encode() asserts exactly that at runtime, which is what turns a
|
||||
// mistyped number here into a visible failure instead of an unscannable symbol.
|
||||
static const struct {
|
||||
short total; // total codewords in the symbol
|
||||
unsigned char ecc; // EC codewords per block
|
||||
unsigned char blocks; // number of RS blocks
|
||||
} vinfo[QR_MAX_VERSION + 1] = {
|
||||
{ 0, 0, 0 }, // version 0 does not exist; keeps indexing natural
|
||||
{ 26, 7, 1 },
|
||||
{ 44, 10, 1 },
|
||||
{ 70, 15, 1 },
|
||||
{ 100, 20, 1 },
|
||||
{ 134, 26, 1 },
|
||||
{ 172, 18, 2 },
|
||||
};
|
||||
|
||||
int
|
||||
qr_capacity(int version)
|
||||
{
|
||||
if (version < 1 || version > QR_MAX_VERSION)
|
||||
return 0;
|
||||
// Data codewords, less the two bytes of framing: a 4-bit mode indicator, an
|
||||
// 8-bit character count (versions 1-9), and a 4-bit terminator come to
|
||||
// exactly two codewords' worth of overhead.
|
||||
int data = vinfo[version].total - vinfo[version].ecc * vinfo[version].blocks;
|
||||
return data - 2;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GF(256) and Reed-Solomon
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Multiply in GF(256) modulo x^8 + x^4 + x^3 + x^2 + 1 (0x11D).
|
||||
//
|
||||
// Done by shift-and-add rather than log/exp tables. Two 256-byte tables plus
|
||||
// their initialisation is more code than this loop, and this needs no init step
|
||||
// at all -- which matters when the caller is a panic handler that cannot assume
|
||||
// any particular earlier function ran.
|
||||
static unsigned char
|
||||
gmul(unsigned char x, unsigned char y)
|
||||
{
|
||||
unsigned char z = 0;
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
z = (unsigned char)((z << 1) ^ ((z >> 7) * 0x1D));
|
||||
z ^= (unsigned char)(((y >> i) & 1) * x);
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
// Coefficients of the degree-`degree` generator polynomial, highest power
|
||||
// first, with the leading 1 left implicit.
|
||||
static void
|
||||
rs_divisor(int degree, unsigned char *result)
|
||||
{
|
||||
memset(result, 0, degree);
|
||||
result[degree - 1] = 1; // start at the monomial x^0
|
||||
|
||||
// Multiply by (x - r) for r = 1, a, a^2, ... Each pass shifts the polynomial
|
||||
// up one degree and folds in the new root.
|
||||
unsigned char root = 1;
|
||||
for (int i = 0; i < degree; i++) {
|
||||
for (int j = 0; j < degree; j++) {
|
||||
result[j] = gmul(result[j], root);
|
||||
if (j + 1 < degree)
|
||||
result[j] ^= result[j + 1];
|
||||
}
|
||||
root = gmul(root, 0x02);
|
||||
}
|
||||
}
|
||||
|
||||
// The remainder of data[] divided by the generator: the EC codewords.
|
||||
static void
|
||||
rs_remainder(const unsigned char *data, int len,
|
||||
const unsigned char *gen, int degree, unsigned char *result)
|
||||
{
|
||||
memset(result, 0, degree);
|
||||
for (int i = 0; i < len; i++) {
|
||||
unsigned char factor = data[i] ^ result[0];
|
||||
memmove(&result[0], &result[1], degree - 1);
|
||||
result[degree - 1] = 0;
|
||||
for (int j = 0; j < degree; j++)
|
||||
result[j] ^= gmul(gen[j], factor);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Module access
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
set_fn(struct qr *q, int x, int y, int dark)
|
||||
{
|
||||
if (x < 0 || y < 0 || x >= q->size || y >= q->size)
|
||||
return;
|
||||
q->mod[y][x] = (unsigned char)(dark != 0);
|
||||
q->fn[y][x] = 1;
|
||||
}
|
||||
|
||||
static int
|
||||
absi(int v)
|
||||
{
|
||||
return v < 0 ? -v : v;
|
||||
}
|
||||
|
||||
static int
|
||||
cheb(int dx, int dy)
|
||||
{
|
||||
int ax = absi(dx), ay = absi(dy);
|
||||
return ax > ay ? ax : ay;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Function patterns
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// A finder plus its separator, as one 9x9 stamp centred on (cx, cy).
|
||||
//
|
||||
// Writing it by Chebyshev distance rather than as a literal bitmap is not
|
||||
// cleverness for its own sake: the concentric rings *are* the definition -- dark
|
||||
// 3x3 core, light ring, dark ring, light separator -- and the edge stamps run
|
||||
// off the symbol, which set_fn() clips.
|
||||
static void
|
||||
draw_finder(struct qr *q, int cx, int cy)
|
||||
{
|
||||
for (int dy = -4; dy <= 4; dy++)
|
||||
for (int dx = -4; dx <= 4; dx++) {
|
||||
int d = cheb(dx, dy);
|
||||
set_fn(q, cx + dx, cy + dy, d != 2 && d != 4);
|
||||
}
|
||||
}
|
||||
|
||||
// The 5x5 alignment pattern: dark 3x3 outline with a light ring inside.
|
||||
static void
|
||||
draw_alignment(struct qr *q, int cx, int cy)
|
||||
{
|
||||
for (int dy = -2; dy <= 2; dy++)
|
||||
for (int dx = -2; dx <= 2; dx++)
|
||||
set_fn(q, cx + dx, cy + dy, cheb(dx, dy) != 1);
|
||||
}
|
||||
|
||||
// The 15-bit format information, twice, plus the always-dark module.
|
||||
//
|
||||
// Called once with the real mask at the end, and once early with mask 0 purely
|
||||
// to *reserve* these modules -- the zigzag below skips anything marked as a
|
||||
// function pattern, so the reservation has to exist before data is placed.
|
||||
static void
|
||||
draw_format(struct qr *q, int mask)
|
||||
{
|
||||
// 0b01 is error correction level L. Five data bits, then a BCH(15,5) check
|
||||
// computed with generator 0x537, then XOR 0x5412 so an all-zero format never
|
||||
// produces an all-zero pattern.
|
||||
int data = (1 << 3) | mask;
|
||||
int rem = data;
|
||||
for (int i = 0; i < 10; i++)
|
||||
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
|
||||
int bits = ((data << 10) | rem) ^ 0x5412;
|
||||
|
||||
int n = q->size;
|
||||
|
||||
// First copy: down the left of the top-right finder, then left along the top
|
||||
// of the bottom-left one. The 6/7 and 7/8 jogs skip the timing pattern.
|
||||
for (int i = 0; i <= 5; i++)
|
||||
set_fn(q, 8, i, (bits >> i) & 1);
|
||||
set_fn(q, 8, 7, (bits >> 6) & 1);
|
||||
set_fn(q, 8, 8, (bits >> 7) & 1);
|
||||
set_fn(q, 7, 8, (bits >> 8) & 1);
|
||||
for (int i = 9; i < 15; i++)
|
||||
set_fn(q, 14 - i, 8, (bits >> i) & 1);
|
||||
|
||||
// Second copy, so the format survives damage to either corner.
|
||||
for (int i = 0; i < 8; i++)
|
||||
set_fn(q, n - 1 - i, 8, (bits >> i) & 1);
|
||||
for (int i = 8; i < 15; i++)
|
||||
set_fn(q, 8, n - 15 + i, (bits >> i) & 1);
|
||||
|
||||
set_fn(q, 8, n - 8, 1); // the dark module, always set, always here
|
||||
}
|
||||
|
||||
static void
|
||||
draw_function_patterns(struct qr *q)
|
||||
{
|
||||
int n = q->size;
|
||||
|
||||
// Timing patterns first: the finders overwrite their ends, which is correct.
|
||||
for (int i = 0; i < n; i++) {
|
||||
set_fn(q, 6, i, i % 2 == 0);
|
||||
set_fn(q, i, 6, i % 2 == 0);
|
||||
}
|
||||
|
||||
draw_finder(q, 3, 3);
|
||||
draw_finder(q, n - 4, 3);
|
||||
draw_finder(q, 3, n - 4);
|
||||
|
||||
// For versions 2-6 the alignment centres are {6, n-7}, and three of those
|
||||
// four combinations collide with a finder. Only the bottom-right survives,
|
||||
// which is why there is no position table here. Version 7 would need one.
|
||||
if (q->size > 21)
|
||||
draw_alignment(q, n - 7, n - 7);
|
||||
|
||||
draw_format(q, 0); // reserve; overwritten once a mask is chosen
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Masking
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int
|
||||
mask_bit(int mask, int x, int y)
|
||||
{
|
||||
switch (mask) {
|
||||
case 0: return (x + y) % 2 == 0;
|
||||
case 1: return y % 2 == 0;
|
||||
case 2: return x % 3 == 0;
|
||||
case 3: return (x + y) % 3 == 0;
|
||||
case 4: return (x / 3 + y / 2) % 2 == 0;
|
||||
case 5: return x * y % 2 + x * y % 3 == 0;
|
||||
case 6: return (x * y % 2 + x * y % 3) % 2 == 0;
|
||||
case 7: return ((x + y) % 2 + x * y % 3) % 2 == 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
apply_mask(struct qr *q, int mask)
|
||||
{
|
||||
for (int y = 0; y < q->size; y++)
|
||||
for (int x = 0; x < q->size; x++)
|
||||
if (!q->fn[y][x])
|
||||
q->mod[y][x] ^= (unsigned char)mask_bit(mask, x, y);
|
||||
}
|
||||
|
||||
// The standard's four penalty rules. Lower is better.
|
||||
//
|
||||
// A decoder will read any legal mask, so this is not correctness -- it is scan
|
||||
// reliability, which is the whole point when the reader is a handheld camera
|
||||
// pointed at a screen. Rule 3 in particular exists to stop the data accidentally
|
||||
// containing something that looks like a finder pattern.
|
||||
static int
|
||||
penalty(const struct qr *q)
|
||||
{
|
||||
int n = q->size, score = 0, dark = 0;
|
||||
|
||||
for (int y = 0; y < n; y++) {
|
||||
for (int x = 0; x < n; x++)
|
||||
dark += q->mod[y][x];
|
||||
|
||||
// Rule 1, both directions: runs of five or more identical modules.
|
||||
for (int dir = 0; dir < 2; dir++) {
|
||||
int run = 1;
|
||||
int prev = dir ? q->mod[0][y] : q->mod[y][0];
|
||||
for (int i = 1; i < n; i++) {
|
||||
int cur = dir ? q->mod[i][y] : q->mod[y][i];
|
||||
if (cur == prev) {
|
||||
run++;
|
||||
} else {
|
||||
if (run >= 5) score += 3 + (run - 5);
|
||||
run = 1;
|
||||
prev = cur;
|
||||
}
|
||||
}
|
||||
if (run >= 5) score += 3 + (run - 5);
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 2: every 2x2 block of one colour.
|
||||
for (int y = 0; y + 1 < n; y++)
|
||||
for (int x = 0; x + 1 < n; x++) {
|
||||
int c = q->mod[y][x];
|
||||
if (c == q->mod[y][x + 1] && c == q->mod[y + 1][x] && c == q->mod[y + 1][x + 1])
|
||||
score += 3;
|
||||
}
|
||||
|
||||
// Rule 3: the finder-lookalike 1011101 with four light modules on either
|
||||
// side, in either direction.
|
||||
for (int dir = 0; dir < 2; dir++)
|
||||
for (int a = 0; a < n; a++) {
|
||||
unsigned bits = 0;
|
||||
for (int b = 0; b < n; b++) {
|
||||
int m = dir ? q->mod[b][a] : q->mod[a][b];
|
||||
bits = ((bits << 1) | (unsigned)m) & 0x7FF;
|
||||
if (b >= 10 && (bits == 0x05D || bits == 0x5D0))
|
||||
score += 40;
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 4: deviation from an even split of dark and light, in 5% steps.
|
||||
int total = n * n;
|
||||
int k = (absi(dark * 20 - total * 10) + total - 1) / total;
|
||||
score += k * 10;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Encode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
put_bits(unsigned char *buf, int *pos, unsigned val, int n)
|
||||
{
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
buf[*pos >> 3] |= (unsigned char)(((val >> i) & 1) << (7 - (*pos & 7)));
|
||||
(*pos)++;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
qr_encode(struct qr *q, const unsigned char *data, int len)
|
||||
{
|
||||
int v;
|
||||
|
||||
if (len < 0)
|
||||
return -1;
|
||||
for (v = 1; v <= QR_MAX_VERSION; v++)
|
||||
if (len <= qr_capacity(v))
|
||||
break;
|
||||
if (v > QR_MAX_VERSION)
|
||||
return -1;
|
||||
|
||||
int total = vinfo[v].total;
|
||||
int ecclen = vinfo[v].ecc;
|
||||
int blocks = vinfo[v].blocks;
|
||||
int datacw = total - ecclen * blocks;
|
||||
|
||||
// The table claims equal-sized blocks for every version this file supports.
|
||||
// If that is ever untrue the interleave below is silently wrong, so check it
|
||||
// rather than trust it.
|
||||
if (datacw % blocks != 0)
|
||||
return -1;
|
||||
int blocklen = datacw / blocks;
|
||||
|
||||
// --- 1. payload -> data codewords ---------------------------------------
|
||||
unsigned char cw[172]; // >= max total codewords
|
||||
memset(cw, 0, sizeof(cw));
|
||||
|
||||
int pos = 0;
|
||||
put_bits(cw, &pos, 0x4, 4); // byte mode
|
||||
put_bits(cw, &pos, (unsigned)len, 8);
|
||||
for (int i = 0; i < len; i++)
|
||||
put_bits(cw, &pos, data[i], 8);
|
||||
|
||||
// Terminator, up to four zero bits, then round up to a whole codeword.
|
||||
int spare = datacw * 8 - pos;
|
||||
put_bits(cw, &pos, 0, spare < 4 ? spare : 4);
|
||||
if (pos & 7)
|
||||
put_bits(cw, &pos, 0, 8 - (pos & 7));
|
||||
|
||||
// Fill the rest with the standard alternating pad bytes. They are specified
|
||||
// values, not arbitrary filler: a decoder uses the character count to know
|
||||
// where the data ends, but the pad pattern keeps the symbol from having large
|
||||
// uniform regions that mask selection would then have to fight.
|
||||
for (int i = pos / 8; i < datacw; i++)
|
||||
cw[i] = (i - pos / 8) % 2 == 0 ? 0xEC : 0x11;
|
||||
|
||||
// --- 2 and 3. error correction, then interleave -------------------------
|
||||
unsigned char gen[30];
|
||||
unsigned char ec[4][30];
|
||||
rs_divisor(ecclen, gen);
|
||||
for (int b = 0; b < blocks; b++)
|
||||
rs_remainder(&cw[b * blocklen], blocklen, gen, ecclen, ec[b]);
|
||||
|
||||
unsigned char out[172];
|
||||
int o = 0;
|
||||
for (int i = 0; i < blocklen; i++)
|
||||
for (int b = 0; b < blocks; b++)
|
||||
out[o++] = cw[b * blocklen + i];
|
||||
for (int i = 0; i < ecclen; i++)
|
||||
for (int b = 0; b < blocks; b++)
|
||||
out[o++] = ec[b][i];
|
||||
|
||||
// --- 4. function patterns ------------------------------------------------
|
||||
memset(q, 0, sizeof(*q));
|
||||
q->size = 17 + 4 * v;
|
||||
draw_function_patterns(q);
|
||||
|
||||
// --- 5. zigzag placement -------------------------------------------------
|
||||
//
|
||||
// Two-module-wide columns walked from the right edge leftwards, alternating
|
||||
// upward and downward. Column 6 is skipped entirely because it is the vertical
|
||||
// timing pattern -- without that step every subsequent column lands one place
|
||||
// out and the symbol is quietly unreadable.
|
||||
int bit = 0;
|
||||
int nbits = o * 8;
|
||||
for (int right = q->size - 1; right >= 1; right -= 2) {
|
||||
if (right == 6)
|
||||
right = 5;
|
||||
for (int vert = 0; vert < q->size; vert++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
int x = right - j;
|
||||
int upward = ((right + 1) & 2) == 0;
|
||||
int y = upward ? q->size - 1 - vert : vert;
|
||||
if (!q->fn[y][x] && bit < nbits) {
|
||||
q->mod[y][x] = (unsigned char)((out[bit >> 3] >> (7 - (bit & 7))) & 1);
|
||||
bit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 6. pick a mask ------------------------------------------------------
|
||||
int best = 0, bestscore = 0;
|
||||
for (int m = 0; m < 8; m++) {
|
||||
apply_mask(q, m);
|
||||
draw_format(q, m);
|
||||
int s = penalty(q);
|
||||
if (m == 0 || s < bestscore) {
|
||||
bestscore = s;
|
||||
best = m;
|
||||
}
|
||||
apply_mask(q, m); // XOR again to undo; masking is involutive
|
||||
}
|
||||
apply_mask(q, best);
|
||||
draw_format(q, best);
|
||||
|
||||
return 0;
|
||||
}
|
||||
55
kernel/core/sleeplock.c
Normal file
55
kernel/core/sleeplock.c
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// Sleeping locks
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
|
||||
void
|
||||
initsleeplock(struct sleeplock *lk, char *name)
|
||||
{
|
||||
initlock(&lk->lk, "sleep lock");
|
||||
lk->name = name;
|
||||
lk->locked = 0;
|
||||
lk->pid = 0;
|
||||
}
|
||||
|
||||
void
|
||||
acquiresleep(struct sleeplock *lk)
|
||||
{
|
||||
acquire(&lk->lk);
|
||||
while (lk->locked) {
|
||||
sleep(lk, &lk->lk);
|
||||
}
|
||||
lk->locked = 1;
|
||||
if (myproc()) {
|
||||
lk->pid = myproc()->pid;
|
||||
} else {
|
||||
lk->pid = 0;
|
||||
}
|
||||
release(&lk->lk);
|
||||
}
|
||||
|
||||
void
|
||||
releasesleep(struct sleeplock *lk)
|
||||
{
|
||||
acquire(&lk->lk);
|
||||
lk->locked = 0;
|
||||
lk->pid = 0;
|
||||
wakeup(lk);
|
||||
release(&lk->lk);
|
||||
}
|
||||
|
||||
int
|
||||
holdingsleep(struct sleeplock *lk)
|
||||
{
|
||||
acquire(&lk->lk);
|
||||
int r = lk->locked;
|
||||
release(&lk->lk);
|
||||
return r;
|
||||
}
|
||||
115
kernel/core/spinlock.c
Normal file
115
kernel/core/spinlock.c
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "spinlock.h"
|
||||
|
||||
void
|
||||
initlock(struct spinlock *lk, char *name)
|
||||
{
|
||||
lk->name = name;
|
||||
lk->locked = 0;
|
||||
lk->cpu = 0;
|
||||
}
|
||||
|
||||
void
|
||||
acquire(struct spinlock *lk)
|
||||
{
|
||||
pushcli();
|
||||
if(holding(lk))
|
||||
panic("acquire");
|
||||
|
||||
{
|
||||
uint64 spins = 0;
|
||||
while(xchg(&lk->locked, 1) != 0){
|
||||
if(++spins == 200000000ULL){
|
||||
cprintf("acquire: stuck on \"%s\" held by cpu=%p pcs0=%p pcs1=%p pcs2=%p, acquirer cpu=%p\n",
|
||||
lk->name, lk->cpu, lk->pcs[0], lk->pcs[1], lk->pcs[2], mycpu());
|
||||
panic("acquire: deadlock");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__sync_synchronize();
|
||||
|
||||
lk->cpu = mycpu();
|
||||
getcallerpcs(&lk, lk->pcs);
|
||||
}
|
||||
|
||||
void
|
||||
release(struct spinlock *lk)
|
||||
{
|
||||
if(!holding(lk))
|
||||
panic("release");
|
||||
|
||||
lk->pcs[0] = 0;
|
||||
lk->cpu = 0;
|
||||
|
||||
__sync_synchronize();
|
||||
|
||||
asm volatile("movq $0, %0" : "+m" (lk->locked) : );
|
||||
|
||||
popcli();
|
||||
}
|
||||
|
||||
void
|
||||
getcallerpcs(void *v, addr_t pcs[])
|
||||
{
|
||||
addr_t *rbp;
|
||||
|
||||
asm volatile("mov %%rbp, %0" : "=r" (rbp));
|
||||
getstackpcs(rbp, pcs);
|
||||
}
|
||||
|
||||
void
|
||||
getstackpcs(addr_t *rbp, addr_t pcs[])
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < 10; i++){
|
||||
if(rbp == 0 || rbp < (addr_t*)KERNBASE || rbp == (addr_t*)0xffffffff)
|
||||
break;
|
||||
pcs[i] = rbp[1];
|
||||
rbp = (addr_t*)rbp[0];
|
||||
}
|
||||
for(; i < 10; i++)
|
||||
pcs[i] = 0;
|
||||
}
|
||||
|
||||
int
|
||||
holding(struct spinlock *lock)
|
||||
{
|
||||
return lock->locked && lock->cpu == mycpu();
|
||||
}
|
||||
|
||||
void
|
||||
pushcli(void)
|
||||
{
|
||||
int eflags;
|
||||
|
||||
eflags = readeflags();
|
||||
cli();
|
||||
if(mycpu() == 0){
|
||||
extern uint64 kernel_fs_base;
|
||||
cprintf("pushcli: cpu==NULL cs=0x%p kernel_fs_base=0x%p actual_fs_base=0x%p\n",
|
||||
readcs(), kernel_fs_base, rdmsr(0xC0000100));
|
||||
panic("pushcli: cpu is NULL");
|
||||
}
|
||||
if(mycpu()->ncli == 0)
|
||||
mycpu()->intena = eflags & FL_IF;
|
||||
mycpu()->ncli += 1;
|
||||
}
|
||||
|
||||
void
|
||||
popcli(void)
|
||||
{
|
||||
if(readeflags()&FL_IF)
|
||||
panic("popcli - interruptible");
|
||||
if(--mycpu()->ncli < 0)
|
||||
panic("popcli");
|
||||
if(mycpu()->ncli == 0 && mycpu()->intena)
|
||||
sti();
|
||||
}
|
||||
113
kernel/core/string.c
Normal file
113
kernel/core/string.c
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#include "types.h"
|
||||
#include "x86.h"
|
||||
|
||||
void*
|
||||
memset(void *dst, int c, uint64 n)
|
||||
{
|
||||
uint64 c64;
|
||||
|
||||
if ((addr_t)dst % 8 == 0 && n % 8 == 0) {
|
||||
c &= 0xFF;
|
||||
c64 = (uint64)c;
|
||||
c64 = (c64 << 56) | (c64 << 48) | (c64 << 40) | (c64 << 32) |
|
||||
(c64 << 24) | (c64 << 16) | (c64 << 8) | c64;
|
||||
stosq(dst, c64, n / 8);
|
||||
} else {
|
||||
stosb(dst, c, n);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
int
|
||||
memcmp(const void *v1, const void *v2, uint n)
|
||||
{
|
||||
const uchar *s1, *s2;
|
||||
|
||||
s1 = v1;
|
||||
s2 = v2;
|
||||
while(n-- > 0){
|
||||
if(*s1 != *s2)
|
||||
return *s1 - *s2;
|
||||
s1++, s2++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void*
|
||||
memmove(void *dst, const void *src, uint n)
|
||||
{
|
||||
const char *s;
|
||||
char *d;
|
||||
|
||||
s = src;
|
||||
d = dst;
|
||||
if(s < d && s + n > d){
|
||||
s += n;
|
||||
d += n;
|
||||
while(n-- > 0)
|
||||
*--d = *--s;
|
||||
} else
|
||||
while(n-- > 0)
|
||||
*d++ = *s++;
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
void*
|
||||
memcpy(void *dst, const void *src, uint n)
|
||||
{
|
||||
return memmove(dst, src, n);
|
||||
}
|
||||
|
||||
int
|
||||
strncmp(const char *p, const char *q, uint n)
|
||||
{
|
||||
while(n > 0 && *p && *p == *q)
|
||||
n--, p++, q++;
|
||||
if(n == 0)
|
||||
return 0;
|
||||
return (uchar)*p - (uchar)*q;
|
||||
}
|
||||
|
||||
char*
|
||||
strncpy(char *s, const char *t, int n)
|
||||
{
|
||||
char *os = s;
|
||||
while(n-- > 0 && (*s++ = *t++) != 0)
|
||||
;
|
||||
while(n-- > 0)
|
||||
*s++ = 0;
|
||||
return os;
|
||||
}
|
||||
|
||||
char*
|
||||
safestrcpy(char *s, const char *t, int n)
|
||||
{
|
||||
char *os = s;
|
||||
if(n <= 0)
|
||||
return os;
|
||||
while(--n > 0 && (*s++ = *t++) != 0)
|
||||
;
|
||||
*s = 0;
|
||||
return os;
|
||||
}
|
||||
|
||||
int
|
||||
strlen(const char *s)
|
||||
{
|
||||
int n;
|
||||
|
||||
for(n = 0; s[n]; n++)
|
||||
;
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
strnlen(const char *s, int max)
|
||||
{
|
||||
int n;
|
||||
for(n = 0; n < max && s[n]; n++)
|
||||
;
|
||||
return n;
|
||||
}
|
||||
116
kernel/core/time.c
Normal file
116
kernel/core/time.c
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Wall-clock time.
|
||||
//
|
||||
// The kernel has always known how long it had been running -- `ticks`, bumped
|
||||
// 100 times a second by the timer interrupt -- and has never known what time it
|
||||
// was. sys_gettimeofday() returned `ticks / 100` as though it were seconds
|
||||
// since the epoch, so every Schunix machine believed it was the 1st of January
|
||||
// 1970 and that the date advanced at the rate of its own uptime. `date` printed
|
||||
// a number that barely moved, which is what it looks like when a clock is
|
||||
// counting from the wrong place rather than not counting at all.
|
||||
//
|
||||
// cmostime() in kernel/arch/x86_64/lapic.c has been able to read the hardware
|
||||
// clock the whole time. Nothing called it.
|
||||
//
|
||||
// The arrangement here is the usual one: read the RTC once at boot, convert it
|
||||
// to a Unix timestamp, and add uptime to it thereafter. The RTC is slow to read
|
||||
// (it is polled over two I/O ports and has to be sampled twice to catch it
|
||||
// mid-update) so reading it on every gettimeofday would be absurd; and `ticks`
|
||||
// is a perfectly good interval clock once it has something to count from.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "date.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
|
||||
extern uint ticks;
|
||||
|
||||
// Seconds between the Unix epoch and the moment this machine booted. Zero until
|
||||
// time_init() runs, which makes the behaviour before that point exactly what it
|
||||
// used to be -- uptime presented as absolute time -- rather than something new
|
||||
// and differently wrong.
|
||||
uint64 boot_epoch;
|
||||
|
||||
// Days from 1970-01-01 to the given civil date, proleptic Gregorian.
|
||||
//
|
||||
// Howard Hinnant's days_from_civil. It is worth using rather than a loop over
|
||||
// years because it is branchless, exact for any year, and has no table to get
|
||||
// wrong; the alternative -- accumulating 365 or 366 days per year since 1970 --
|
||||
// gets leap centuries wrong unless you remember that 1900 was not a leap year
|
||||
// and 2000 was, which is precisely the sort of thing that is discovered on a
|
||||
// February morning years later.
|
||||
//
|
||||
// The trick is shifting the year so that March is month 1, which puts the leap
|
||||
// day at the *end* of the year and makes the leap rule depend only on the year
|
||||
// number rather than on where in the year you are.
|
||||
static int64
|
||||
days_from_civil(int64 y, unsigned m, unsigned d)
|
||||
{
|
||||
y -= m <= 2;
|
||||
const int64 era = (y >= 0 ? y : y - 399) / 400;
|
||||
const unsigned yoe = (unsigned)(y - era * 400); // [0, 399]
|
||||
const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
|
||||
const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
|
||||
return era * 146097 + (int64)doe - 719468;
|
||||
}
|
||||
|
||||
// Read the hardware clock and work out when we booted.
|
||||
//
|
||||
// The RTC is read in UTC. That is not universally true on a PC -- a machine
|
||||
// that has had Windows installed on it usually keeps the RTC in local time --
|
||||
// but there is nowhere to store a timezone yet and no way to ask, and UTC is
|
||||
// the honest default for a system with no configuration. When there is a
|
||||
// settings store, this is where the offset goes.
|
||||
void
|
||||
time_init(void)
|
||||
{
|
||||
struct rtcdate r;
|
||||
|
||||
cmostime(&r);
|
||||
|
||||
// cmostime() returns a two-digit year on hardware that only stores one.
|
||||
// 2000-2099 is the right guess now and stays right for a lifetime; a machine
|
||||
// reporting 70 means the battery is flat, and the epoch is as good an answer
|
||||
// as any for that.
|
||||
int64 year = r.year;
|
||||
if (year < 100)
|
||||
year += 2000;
|
||||
|
||||
// A wildly implausible reading means the CMOS is not telling us anything.
|
||||
// Leaving boot_epoch at zero keeps the old behaviour, which is wrong but
|
||||
// predictable, rather than putting the machine somewhere in the Bronze Age.
|
||||
if (r.month < 1 || r.month > 12 || r.day < 1 || r.day > 31 ||
|
||||
r.hour > 23 || r.minute > 59 || r.second > 60 || year < 1970) {
|
||||
cprintf("time: CMOS clock is not plausible, staying at the epoch\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int64 days = days_from_civil(year, r.month, r.day);
|
||||
uint64 now = (uint64)(days * 86400) + r.hour * 3600 + r.minute * 60 + r.second;
|
||||
|
||||
// Subtract the uptime so far, so boot_epoch is the moment of boot rather than
|
||||
// the moment this function ran. It is only a fraction of a second, but it
|
||||
// keeps boot_epoch meaning exactly what its name says.
|
||||
uint64 up = ticks / 100;
|
||||
boot_epoch = now > up ? now - up : now;
|
||||
|
||||
cprintf("time: %d-%d-%d %d:%d:%d UTC from the hardware clock\n",
|
||||
(int)year, r.month, r.day, r.hour, r.minute, r.second);
|
||||
}
|
||||
|
||||
// Seconds and microseconds since the epoch. One place, so gettimeofday,
|
||||
// clock_gettime and anything added later cannot disagree about what time it is.
|
||||
void
|
||||
wallclock(uint64 *sec, uint64 *usec)
|
||||
{
|
||||
uint xticks;
|
||||
|
||||
acquire(&tickslock);
|
||||
xticks = ticks;
|
||||
release(&tickslock);
|
||||
|
||||
if (sec)
|
||||
*sec = boot_epoch + xticks / 100;
|
||||
if (usec)
|
||||
*usec = (uint64)(xticks % 100) * 10000;
|
||||
}
|
||||
79
kernel/core/uaccess.c
Normal file
79
kernel/core/uaccess.c
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// User-memory access helpers — see include/uaccess.h for the rules.
|
||||
//
|
||||
// Everything here validates against the *current* process's page table
|
||||
// (myproc()->pgdir), which is also the live CR3 while a syscall runs, so a
|
||||
// range that passes access_ok() can be dereferenced directly by kernel
|
||||
// code for the remainder of the syscall (single-threaded processes can't
|
||||
// unmap it out from under us mid-call).
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "errno.h"
|
||||
#include "uaccess.h"
|
||||
|
||||
int
|
||||
access_ok(addr_t uaddr, uint64 size)
|
||||
{
|
||||
addr_t a, end;
|
||||
|
||||
if(size == 0)
|
||||
return 1;
|
||||
end = uaddr + size;
|
||||
if(end < uaddr || end > KERNBASE)
|
||||
return 0;
|
||||
for(a = PGROUNDDOWN(uaddr); a < end; a += PGSIZE)
|
||||
if(uva2ka(myproc()->pgdir, (char*)a) == 0)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void*
|
||||
user_ptr(addr_t uaddr, uint64 size)
|
||||
{
|
||||
if(uaddr == 0 || !access_ok(uaddr, size))
|
||||
return 0;
|
||||
return (void*)uaddr;
|
||||
}
|
||||
|
||||
int
|
||||
copy_from_user(void *dst, addr_t uaddr, uint64 n)
|
||||
{
|
||||
if(uaddr + n < uaddr || uaddr + n > KERNBASE)
|
||||
return -EFAULT;
|
||||
if(copyin(myproc()->pgdir, uaddr, dst, n) < 0)
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
copy_to_user(addr_t uaddr, const void *src, uint64 n)
|
||||
{
|
||||
if(uaddr + n < uaddr || uaddr + n > KERNBASE)
|
||||
return -EFAULT;
|
||||
if(copyout(myproc()->pgdir, uaddr, (void*)src, n) < 0)
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
long
|
||||
strlen_user(addr_t uaddr)
|
||||
{
|
||||
addr_t a = uaddr;
|
||||
|
||||
while(a - uaddr < USER_STR_MAX){
|
||||
// Scan through the per-page kernel alias so an unmapped page is a
|
||||
// clean failure instead of a fault.
|
||||
char *ka = uva2ka(myproc()->pgdir, (char*)a);
|
||||
if(ka == 0)
|
||||
return -EFAULT;
|
||||
addr_t pgend = PGROUNDDOWN(a) + PGSIZE;
|
||||
for(; a < pgend; a++, ka++)
|
||||
if(*ka == 0)
|
||||
return a - uaddr;
|
||||
}
|
||||
return -EFAULT;
|
||||
}
|
||||
160
kernel/dev/bio.c
Normal file
160
kernel/dev/bio.c
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// Buffer cache.
|
||||
//
|
||||
// The buffer cache is a linked list of buf structures holding
|
||||
// cached copies of disk block contents. Caching disk blocks
|
||||
// in memory reduces the number of disk reads and also provides
|
||||
// a synchronization point for disk blocks used by multiple processes.
|
||||
//
|
||||
// Interface:
|
||||
// * To get a buffer for a particular disk block, call bread.
|
||||
// * After changing buffer data, call bwrite to write it to disk.
|
||||
// * When done with the buffer, call brelse.
|
||||
// * Do not use the buffer after calling brelse.
|
||||
// * Only one process at a time can use a buffer,
|
||||
// so do not keep them longer than necessary.
|
||||
//
|
||||
// The implementation uses two state flags internally:
|
||||
// * B_VALID: the buffer data has been read from the disk.
|
||||
// * B_DIRTY: the buffer data has been modified
|
||||
// and needs to be written to disk.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "buf.h"
|
||||
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
struct buf buf[NBUF];
|
||||
|
||||
// Linked list of all buffers, through prev/next.
|
||||
// head.next is most recently used.
|
||||
struct buf head;
|
||||
} bcache;
|
||||
|
||||
void
|
||||
binit(void)
|
||||
{
|
||||
struct buf *b;
|
||||
|
||||
initlock(&bcache.lock, "bcache");
|
||||
//PAGEBREAK!
|
||||
|
||||
// Create linked list of buffers
|
||||
bcache.head.prev = &bcache.head;
|
||||
bcache.head.next = &bcache.head;
|
||||
for(b = bcache.buf; b < bcache.buf+NBUF; b++){
|
||||
b->next = bcache.head.next;
|
||||
b->prev = &bcache.head;
|
||||
initsleeplock(&b->lock, "buffer");
|
||||
bcache.head.next->prev = b;
|
||||
bcache.head.next = b;
|
||||
}
|
||||
}
|
||||
|
||||
// Route a request to the driver that owns b->dev. Everything above this line
|
||||
// is device-agnostic; everything below picks a backend. RAMDISK_DEV is served
|
||||
// out of memory (kernel/dev/ramdisk.c), USBDISK_DEV by bulk transfers to a
|
||||
// USB stick, everything else by the IDE driver.
|
||||
static void
|
||||
bdevrw(struct buf *b)
|
||||
{
|
||||
if(b->dev == RAMDISK_DEV){
|
||||
ramdiskrw(b);
|
||||
return;
|
||||
}
|
||||
if(b->dev == USBDISK_DEV){
|
||||
usb_storage_rw(b);
|
||||
return;
|
||||
}
|
||||
iderw(b);
|
||||
}
|
||||
|
||||
// Look through buffer cache for block on device dev.
|
||||
// If not found, allocate a buffer.
|
||||
// In either case, return locked buffer.
|
||||
static struct buf*
|
||||
bget(uint dev, uint blockno)
|
||||
{
|
||||
struct buf *b;
|
||||
|
||||
acquire(&bcache.lock);
|
||||
|
||||
// Is the block already cached?
|
||||
for(b = bcache.head.next; b != &bcache.head; b = b->next){
|
||||
if(b->dev == dev && b->blockno == blockno){
|
||||
b->refcnt++;
|
||||
release(&bcache.lock);
|
||||
acquiresleep(&b->lock);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
// Not cached; recycle some unused buffer and clean buffer
|
||||
// "clean" because B_DIRTY and not locked means log.c
|
||||
// hasn't yet committed the changes to the buffer.
|
||||
for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
|
||||
if(b->refcnt == 0 && (b->flags & B_DIRTY) == 0) {
|
||||
b->dev = dev;
|
||||
b->blockno = blockno;
|
||||
b->flags = 0;
|
||||
b->refcnt = 1;
|
||||
release(&bcache.lock);
|
||||
acquiresleep(&b->lock);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
panic("bget: no buffers");
|
||||
}
|
||||
|
||||
// Return a locked buf with the contents of the indicated block.
|
||||
struct buf*
|
||||
bread(uint dev, uint blockno)
|
||||
{
|
||||
struct buf *b;
|
||||
|
||||
b = bget(dev, blockno);
|
||||
if(!(b->flags & B_VALID)) {
|
||||
bdevrw(b);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// Write b's contents to disk. Must be locked.
|
||||
void
|
||||
bwrite(struct buf *b)
|
||||
{
|
||||
if(!holdingsleep(&b->lock))
|
||||
panic("bwrite");
|
||||
b->flags |= B_DIRTY;
|
||||
bdevrw(b);
|
||||
}
|
||||
|
||||
// Release a locked buffer.
|
||||
// Move to the head of the MRU list.
|
||||
void
|
||||
brelse(struct buf *b)
|
||||
{
|
||||
if(!holdingsleep(&b->lock))
|
||||
panic("brelse");
|
||||
|
||||
releasesleep(&b->lock);
|
||||
|
||||
acquire(&bcache.lock);
|
||||
b->refcnt--;
|
||||
if (b->refcnt == 0) {
|
||||
// no one is waiting for it.
|
||||
b->next->prev = b->prev;
|
||||
b->prev->next = b->next;
|
||||
b->next = bcache.head.next;
|
||||
b->prev = &bcache.head;
|
||||
bcache.head.next->prev = b;
|
||||
bcache.head.next = b;
|
||||
}
|
||||
|
||||
release(&bcache.lock);
|
||||
}
|
||||
//PAGEBREAK!
|
||||
107
kernel/dev/cdev.c
Normal file
107
kernel/dev/cdev.c
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "x86.h"
|
||||
#include "errno.h"
|
||||
|
||||
extern int consoleread(struct inode*, uint, char*, int);
|
||||
extern int consolewrite(struct inode*, uint, char*, int);
|
||||
extern int mouseread(struct inode*, uint, char*, int);
|
||||
|
||||
static uint64 rseed = 0x2A99554433221100ULL;
|
||||
|
||||
int
|
||||
nullwrite(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
nullread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
zeroread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
memset(dst, 0, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
randomread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
for(int i = 0; i < n; i++){
|
||||
rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||
dst[i] = (rseed >> 32) & 0xFF;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// /dev/full — reads as an endless run of zero bytes, every write fails with
|
||||
// ENOSPC. Programs use it to exercise their out-of-disk-space paths.
|
||||
int
|
||||
fullwrite(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
int
|
||||
mousewrite(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
memread(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
switch(ip->minor) {
|
||||
case 1: return consoleread(ip, off, dst, n);
|
||||
case 2: return nullread(ip, off, dst, n);
|
||||
case 3: return consoleread(ip, off, dst, n);
|
||||
case 5: return zeroread(ip, off, dst, n);
|
||||
case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros
|
||||
case 8:
|
||||
case 9: return randomread(ip, off, dst, n);
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
memwrite(struct inode *ip, uint off, char *dst, int n)
|
||||
{
|
||||
switch(ip->minor) {
|
||||
case 1: return consolewrite(ip, off, dst, n);
|
||||
case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC
|
||||
case 2:
|
||||
case 5:
|
||||
case 8:
|
||||
case 9: return nullwrite(ip, off, dst, n);
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
struct devsw devsw[] = {
|
||||
[0] = { 0, 0 },
|
||||
[1] = { memread, memwrite },
|
||||
// /dev/fb0 (major 2) — the framebuffer, kernel/dev/fb.c. This used to be
|
||||
// { nullread, nullwrite }, so writing pixels to it succeeded and did
|
||||
// nothing.
|
||||
// The close hook gives the text console its screen back when whatever was
|
||||
// drawing on the framebuffer exits — see fbclose().
|
||||
[2] = { fbread, fbwrite, 0, fbclose },
|
||||
[3] = { mouseread, mousewrite },
|
||||
[4] = { 0, 0 },
|
||||
// /dev/tty (major 5) — alias for the console
|
||||
[5] = { memread, memwrite },
|
||||
// /proc status files (major 6) — see kernel/dev/procfs.c
|
||||
[6] = { procread, procwrite },
|
||||
// /dev/input/eventN (major 7) — raw evdev events, kernel/dev/input.c. The
|
||||
// minor is the device index, so event0 is the keyboard and event1 the mouse.
|
||||
[7] = { inputread, inputwrite, inputready, inputclose, inputopen },
|
||||
};
|
||||
308
kernel/dev/device.c
Normal file
308
kernel/dev/device.c
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
// The device model. See include/device.h for why it exists.
|
||||
//
|
||||
// A fixed table rather than a linked list: this is populated once during boot
|
||||
// by whichever bus found each device, and read afterwards. Nothing is removed
|
||||
// yet -- hot-unplug would need it, and when that arrives the in_use flag is
|
||||
// already the hook for it.
|
||||
//
|
||||
// No lock. Every device_add() happens during single-threaded boot, before
|
||||
// userspace exists, and every reader afterwards only walks. If a bus ever
|
||||
// enumerates at runtime -- USB hotplug is the obvious one -- this needs a
|
||||
// spinlock, and the reader in procfs needs to take it.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "device.h"
|
||||
#include "display.h"
|
||||
#include "theme.h"
|
||||
#include "text.h"
|
||||
|
||||
static struct device devices[NDEVICE];
|
||||
static int ndevices;
|
||||
|
||||
static const char *bus_names[DEVBUS_NBUS] = {
|
||||
[DEVBUS_PLATFORM] = "platform",
|
||||
[DEVBUS_PCI] = "pci",
|
||||
[DEVBUS_USB] = "usb",
|
||||
};
|
||||
|
||||
static const char *class_names[DEVCLASS_NCLASS] = {
|
||||
[DEVCLASS_OTHER] = "other",
|
||||
[DEVCLASS_DISPLAY] = "display",
|
||||
[DEVCLASS_INPUT] = "input",
|
||||
[DEVCLASS_STORAGE] = "storage",
|
||||
[DEVCLASS_NET] = "net",
|
||||
[DEVCLASS_BUS] = "bus",
|
||||
};
|
||||
|
||||
const char *
|
||||
dev_bus_name(enum dev_bus b)
|
||||
{
|
||||
if (b < 0 || b >= DEVBUS_NBUS || !bus_names[b])
|
||||
return "?";
|
||||
return bus_names[b];
|
||||
}
|
||||
|
||||
const char *
|
||||
dev_class_name(enum dev_class c)
|
||||
{
|
||||
if (c < 0 || c >= DEVCLASS_NCLASS || !class_names[c])
|
||||
return "?";
|
||||
return class_names[c];
|
||||
}
|
||||
|
||||
// How many devices already share this base name, so "vga" becomes vga0, vga1.
|
||||
static int
|
||||
count_base(const char *base)
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = 0; i < ndevices; i++) {
|
||||
const char *a = devices[i].name, *b = base;
|
||||
while (*b && *a == *b) { a++; b++; }
|
||||
// Matched the whole base and the rest is the index we appended.
|
||||
if (*b == '\0' && *a >= '0' && *a <= '9')
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
struct device *
|
||||
device_add(const char *base, enum dev_bus bus, enum dev_class class,
|
||||
const char *driver)
|
||||
{
|
||||
if (ndevices >= NDEVICE)
|
||||
return 0;
|
||||
|
||||
struct device *d = &devices[ndevices++];
|
||||
d->bus = bus;
|
||||
d->class = class;
|
||||
d->driver = driver;
|
||||
d->in_use = 1;
|
||||
|
||||
int idx = count_base(base);
|
||||
int n = 0;
|
||||
while (base[n] && n < DEV_NAMELEN - 3) {
|
||||
d->name[n] = base[n];
|
||||
n++;
|
||||
}
|
||||
// Single digit is enough for NDEVICE of 32 per base name, and a two-digit
|
||||
// path that is never taken is a two-digit path that is never tested.
|
||||
if (idx > 9)
|
||||
idx = 9;
|
||||
d->name[n++] = (char)('0' + idx);
|
||||
d->name[n] = '\0';
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void
|
||||
device_set_desc(struct device *d, const char *s)
|
||||
{
|
||||
if (!d)
|
||||
return;
|
||||
int n = 0;
|
||||
while (s[n] && n < (int)sizeof(d->desc) - 1) {
|
||||
d->desc[n] = s[n];
|
||||
n++;
|
||||
}
|
||||
d->desc[n] = '\0';
|
||||
}
|
||||
|
||||
struct device *
|
||||
device_at(int i)
|
||||
{
|
||||
if (i < 0 || i >= ndevices)
|
||||
return 0;
|
||||
return &devices[i];
|
||||
}
|
||||
|
||||
int
|
||||
device_count(void)
|
||||
{
|
||||
return ndevices;
|
||||
}
|
||||
|
||||
struct device *
|
||||
device_find_class(enum dev_class class)
|
||||
{
|
||||
for (int i = 0; i < ndevices; i++)
|
||||
// Unclaimed devices are deliberately skipped. A display nobody drives is
|
||||
// not a display you can draw on, and returning it would push the "is there
|
||||
// a driver" check out to every caller.
|
||||
if (devices[i].class == class && devices[i].driver)
|
||||
return &devices[i];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- the display class -----------------------------------------------------
|
||||
//
|
||||
// Thin wrappers over device_find_class() plus a null check on each op. They
|
||||
// exist so that consumers are not each written around a vtable that may be
|
||||
// absent -- a compositor asking to present on a machine with no display driver
|
||||
// should be a no-op, not a null dereference in the middle of a frame.
|
||||
|
||||
struct device *
|
||||
display_get(void)
|
||||
{
|
||||
return device_find_class(DEVCLASS_DISPLAY);
|
||||
}
|
||||
|
||||
int
|
||||
display_mode(struct display_mode *out)
|
||||
{
|
||||
struct device *d = display_get();
|
||||
if (!d || !d->ops || !((struct display_ops *)d->ops)->mode)
|
||||
return -1;
|
||||
((struct display_ops *)d->ops)->mode(d, out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *
|
||||
display_acquire(uint32 *stride_out)
|
||||
{
|
||||
struct device *d = display_get();
|
||||
if (!d || !d->ops || !((struct display_ops *)d->ops)->acquire)
|
||||
return 0;
|
||||
return ((struct display_ops *)d->ops)->acquire(d, stride_out);
|
||||
}
|
||||
|
||||
void
|
||||
display_present(int x, int y, int w, int h)
|
||||
{
|
||||
struct device *d = display_get();
|
||||
if (!d || !d->ops || !((struct display_ops *)d->ops)->present)
|
||||
return;
|
||||
((struct display_ops *)d->ops)->present(d, x, y, w, h);
|
||||
}
|
||||
|
||||
void
|
||||
display_wait_vblank(void)
|
||||
{
|
||||
struct device *d = display_get();
|
||||
if (!d || !d->ops || !((struct display_ops *)d->ops)->wait_vblank)
|
||||
return;
|
||||
((struct display_ops *)d->ops)->wait_vblank(d);
|
||||
}
|
||||
|
||||
// Drive the whole display path once and report what happened: find the class,
|
||||
// read the mode, acquire a back buffer, draw into RAM, present it.
|
||||
//
|
||||
// A self-test rather than a unit test because none of this can be checked
|
||||
// without a display: the assertion is "did the right pixels reach the screen",
|
||||
// and the only instrument for that is a screendump. Triggered with
|
||||
// `echo d > /proc/sysrq` and photographed by tools/dispshot.ps1.
|
||||
//
|
||||
// The pattern is chosen to be wrong in visible ways. Four solid quadrants in
|
||||
// theme colours catch a stride error (the bands would slant) and a bpp error
|
||||
// (the colours would be wrong); the white frame one pixel inside the edge
|
||||
// catches an off-by-one in the present rectangle; and the fact that it is
|
||||
// drawn entirely into RAM before a single byte reaches the framebuffer is what
|
||||
// double buffering means.
|
||||
void
|
||||
display_selftest(void)
|
||||
{
|
||||
struct display_mode m;
|
||||
uint32 stride;
|
||||
|
||||
if (display_mode(&m) != 0) {
|
||||
cprintf("display: no display bound\n");
|
||||
return;
|
||||
}
|
||||
cprintf("display: %dx%d %dbpp stride %d\n", m.width, m.height, m.bpp, m.stride);
|
||||
|
||||
unsigned char *b = display_acquire(&stride);
|
||||
if (!b) {
|
||||
cprintf("display: no back buffer available\n");
|
||||
return;
|
||||
}
|
||||
|
||||
static const uint32 quad[4] = {
|
||||
SCH_ACCENT, SCH_ACCENT_DIM, SCH_DIM, SCH_BG
|
||||
};
|
||||
for (int y = 0; y < m.height; y++) {
|
||||
uint32 *row = (uint32 *)(b + (uint64)y * stride);
|
||||
int top = y < m.height / 2;
|
||||
for (int x = 0; x < m.width; x++) {
|
||||
int left = x < m.width / 2;
|
||||
row[x] = quad[(top ? 0 : 2) + (left ? 0 : 1)];
|
||||
}
|
||||
}
|
||||
for (int x = 1; x < m.width - 1; x++) {
|
||||
*(uint32 *)(b + 1 * (uint64)stride + x * 4) = SCH_FG;
|
||||
*(uint32 *)(b + (uint64)(m.height - 2) * stride + x * 4) = SCH_FG;
|
||||
}
|
||||
for (int y = 1; y < m.height - 1; y++) {
|
||||
*(uint32 *)(b + (uint64)y * stride + 1 * 4) = SCH_FG;
|
||||
*(uint32 *)(b + (uint64)y * stride + (m.width - 2) * 4) = SCH_FG;
|
||||
}
|
||||
|
||||
// Between frames, so the blit does not land halfway down a scan-out.
|
||||
display_wait_vblank();
|
||||
display_present(0, 0, m.width, m.height);
|
||||
cprintf("display: presented a full frame\n");
|
||||
}
|
||||
|
||||
// Draw a text specimen through the display class. `echo t > /proc/sysrq`.
|
||||
//
|
||||
// It exercises the three things a character-cell console cannot do, so a
|
||||
// screenshot says whether the layer works rather than whether it compiled:
|
||||
// glyphs on a common baseline at arbitrary x (not multiples of eight), text
|
||||
// blended over an existing background rather than drawn into cleared cells, and
|
||||
// codepoints above 255.
|
||||
void
|
||||
text_selftest(void)
|
||||
{
|
||||
struct display_mode m;
|
||||
uint32 stride;
|
||||
|
||||
if (display_mode(&m) != 0) {
|
||||
cprintf("text: no display\n");
|
||||
return;
|
||||
}
|
||||
unsigned char *b = display_acquire(&stride);
|
||||
if (!b) {
|
||||
cprintf("text: no back buffer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
struct surface s = { b, stride, m.width, m.height };
|
||||
struct font *f = font_builtin();
|
||||
|
||||
// A background with structure in it, so blending is visible as blending
|
||||
// rather than as drawing onto a flat colour.
|
||||
for (int y = 0; y < m.height; y++) {
|
||||
uint32 *row = (uint32 *)(b + (uint64)y * stride);
|
||||
for (int x = 0; x < m.width; x++)
|
||||
row[x] = ((x / 32 + y / 32) & 1) ? SCH_BG : SCH_ACCENT_DIM;
|
||||
}
|
||||
|
||||
int base = f->ops->baseline(f);
|
||||
int line = f->ops->height(f) + 6;
|
||||
int y = 80;
|
||||
|
||||
text_draw(&s, f, 60, y + base, "Schunix text rendering", SCH_FG);
|
||||
y += line * 2;
|
||||
|
||||
// Deliberately not on a multiple of eight. On the old console this string
|
||||
// could only start at x = 56 or x = 64.
|
||||
text_draw(&s, f, 61, y + base, "positioned at x=61, off the cell grid", SCH_DIM);
|
||||
y += line;
|
||||
text_draw(&s, f, 63, y + base, "and here at x=63", SCH_DIM);
|
||||
y += line * 2;
|
||||
|
||||
// Above U+00FF, which a 256-entry table cannot index at all.
|
||||
text_draw(&s, f, 60, y + base,
|
||||
"utf-8: \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x90 "
|
||||
"\xe2\x96\x88\xe2\x96\x93\xe2\x96\x92\xe2\x96\x91 "
|
||||
"\xc2\xb0 \xc2\xb1 \xe2\x86\x92 \xe2\x80\xa2", SCH_ACCENT);
|
||||
y += line * 2;
|
||||
|
||||
// Measure, then centre on it -- the thing you cannot do without metrics.
|
||||
const char *msg = "measured and centred";
|
||||
int w = text_measure(f, msg);
|
||||
text_draw(&s, f, (m.width - w) / 2, y + base, msg, SCH_FG);
|
||||
|
||||
display_present(0, 0, m.width, m.height);
|
||||
cprintf("text: drew a specimen, \"%s\" measures %d px\n", msg, w);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue