Plan 9.r post 02/B4BH - AHCI patch, creating isos and stupid ideas
DISCLAIMER: If you want to know what I discover
inside the box keep on reading. If you want to know how to use the box
properly please go to 9front.org. For
plan9 experts reading this: I promise to do everything by the book one
day and as much as I can as a go, but that is not my priority.
This
is about my learning process: Everything I post worked at the time, but
has probably been replace by something better or more correct by
now.
AHCI
From 3e81bb2f1b9ab78c0f140a7526414252406f552f Mon Sep 17 00:00:00 2001
From: Rhizoome <alter@rhizoome.ch>
Date: Sat, 11 Apr 2026 04:03:18 +0200
Subject: [PATCH] 144d/a801: vendor runs this ssd in ahci mode
S4LN058A01[SSUBX] AHCI SSD Controller (vendor slot)
-- @pub @kernel @hardware
---
sys/src/9/pc/sdiahci.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/sys/src/9/pc/sdiahci.c b/sys/src/9/pc/sdiahci.c
index 3207fb57..aada71df 100644
--- a/sys/src/9/pc/sdiahci.c
+++ b/sys/src/9/pc/sdiahci.c
@@ -2073,6 +2073,9 @@ didtype(Pcidev *p)
switch(p->vid){
default:
return -1;
+ case 0x144d:
+ if((p->did & 0xffff) == 0xa801)
+ return Tahci; /* Samsung SSD AHCI vendor special */
case 0x8086:
if((p->did & 0xffff) == 0x1e02)
return Tich; /* c210 */
--
2.53.0I have a first patch. 9front uses a fixed list of PCI devices it
considers valid AHCI controllers plus this line
if(p->ccrb == Pcibcstore && p->ccru == 6 && p->ccrp == 1)
- I guess if the device correctly identifies itself. The vendor of my
device asked Samsung to create a proprietary interface for this SSD and
to put an AHCI controller on the SSD. You can buy adapters for that
interface and you can connect a normal NVMe SSD to it; which is what I
did for my main device and its backup. I knew that the AHCI-SSD runs as
normal SATA3 device on Linux, so I assumed it’ll work fine if 9front
detects it.
igfx
I also ran the kernel with my print statements the first time and the ones I added to vgaigfx.c did not appear, but the ones I used to debug the AHCI device did. So it remains a mystery how 9front displays the native graphics modes of the device.
Development
I decided to do all my
development in the main 9front repo. This means scripts, tools and
kernel patches all live in that repo. 9front integrated the repo fully:
The 9front repo is the same structure as the “root” partition. I did the
multiple branches for each sub-project dance for a long time and its too
tedious; so I will just mark the project and my intents in the commit
using @tag. I also found other people publishing patches
against the 9front repo and I mirrored their repos locally.
Learning
I learned a lot about plan9. There is a good entry in 9fronts wiki
that is called: unix2plan9. So now I
am holding much less tools wrong. A funny example was walk;
it is very central to plan9 and I didn’t find it. I wanted
find and I couldn’t find the replacement. So I used
du, which does the same as walk but also
displays disk usage.
Non-disappointing 9pfs
Here someone ported 9p from some kind of 9front. Its been handed over many times. You need to compile it against fuse2, since I usually have fuse3 installed, I just compiled it to a static binary. So the problems I reported in the last post are fixed with this.
Building 9front
I added port 4241 which gives you raw access to rc. It will display stderr on the serial-console, I didn’t find out how to get both: raw rc and stderr redirection. But for scripting the build process I don’t need stderr, just for debugging; so it is good enough. The script is my terrible lua code repo has been updated.
hostfwd=tcp:127.0.0.1:4241-:4241aux/listen1 -t tcp!*!4241 rc &
I use lua-socket to connect to rc and send commands directly. While
plan9s namespaces are just awesome, for my scripting it is a bit
tedious. I could not convince aux/listen1 to just use the
users namespace. I think it actually not possible in plan9. So you have
to store the steps to get a the correct namespace in a script and
execute it every time you connect. Checkout the output of
ns if you are following this on 9front.
Sysupdate
function nf:sysupdate()
self:exec("git/get git://10.0.2.2/srv/9front.git", true, true)
self:exec("git/pull -f -u origin", true, true)
self:exec("rm adm/timezone/Egypt", true)
self:exec("rm adm/timezone/README", true)
self:exec("git/branch 9rhiz", true, true)
self:exec("git/pull -u origin", true, true)
self:exec("git/revert .", true)
self:exec("cd /lib/firmware && git/init -u git://10.0.2.2/srv/firmware.git", true, true)
self:exec("cd /lib/firmware && git/get git://10.0.2.2/srv/firmware.git", true, true)
self:exec("cd /lib/firmware && git/pull -f -u origin", true, true)
self:exec("cd /lib/firmware && git/branch front", true, true)
self:exec("cd /lib/firmware && git/pull -u origin", true, true)
self:exec("cd /lib/firmware && git/revert .", true, true)
self:exec("rm -rf /lib/firmware/.git", true, true)
endI am just following FQA 5
- Building the System from Source. By default any
exec() binds the 9front repo into the root:
bind -ac /dist/9front /; cd /. plan9 is absolutely awesome
with using binding consistently. Replacing many POSIX features
effortless. And the 9front repo is the same content as your
root.
Git9 has a bit of trouble talking to git-daemon. I don’t want to
serve git using a 9front VM and since I got it working; it is good
enough. All the git/get and git/pull commands
are basically redundant, but each fails in a different way and together
they actually do a correct pull.
Build
function nf:build()
self:exec(". /sys/lib/rootstub", true)
self:exec("cd /sys/src && mk install", true)
self:exec("cd /sys/man && mk", true)
self:exec("cd /sys/src/9/pc64 && mk install", true)
endRebuilding the complete system is very simple. Just
mk install /sys/src and /sys/src/9/pc64 (the kernel). The
branch 9rhiz points to
the same git-revision as the release of 9front I am using. I didn’t add
any new commits from upstream; just my AHCI fix.
ISO
function nf:iso()
self:exec(
"bind /root /n/src9 && " ..
"bind -ac /dist/9front /n/src9 && " ..
"mkdir -p /n/src9/acme/bin/68020 && " ..
"mkdir -p /n/src9/acme/bin/68000 && " ..
"cd /sys/lib/dist && " ..
"mk /tmp/9front.amd64.iso", true)
endBoth iso creation and inst/start (the installer) use the
content of the current system. / is a mixture of many binds
and mounts, but the clean root is stored at /root. The
mk, the installer and the iso creation process use
proto-files. The global ones are stored in
/sys/lib/sysconfig/proto. These files define how to install
things.
mode=ug+rw
mode=o-w
uid=sys
gid=sys
adm d775 adm adm
uid=adm
gid=adm
timezone d775
*sys/lib/sysconfig/proto/distproto starts like this. To
me it is seems to be a good thing. For example the warnings I got during
iso creation showed me that I was missing the contents of
/lib/firmware. Probably the only downside is the
maintenance; the iso creation also warns about files that aren’t part of
9front anymore. So people forgot to remove them from the
proto-files.
Install
I used my iso to install 9front on my test-device with the AHCI-SSD.
It didn’t create the efi partitions content correctly.
Actually the /EFI directory was missing completely. I
believe the iso downloaded from 9front.org did that correctly. But I
just needed to copy the /EFI directory of the iso manually.
I think the iso you can download is created slightly different, but I am
not sure. The iso meta-data looked different. But the system now works,
runs gefs and I can continue to fix the hardware
quirks.
Some kind of stupid idea
I do a lot of exploration using a editor, some source-code and entr. entr runs arbitrary commands when it detects a change in files you gave it via stdin. So I write, save, check, write, save, check, write, save, check, write, save, check…. It actually works almost as well with compiled languages, but not having to worry about types and memory during exploration is a very important bonus.
I desperately wanted a good version of lua on 9front. Lua has about
90 opcodes. This is a switch with 90 cases. 9c will just
create 90 ifs (CMPL plus JEQ) in a row.
CMPL CX,$2
JEQ ,-4(PC)I know that lua is very optimized. The developers actually optimize
the order of the opcodes, do branch-prediction analysis and tests on
less complex compiler-chains. But my brain just goes bananas if it knows
that in the worst case 90 CMPL and JEQ are executed, when there are easy
ways to get from O(n) to O(1).
There are multiple methods to create a better result. One is to do
computed gotos: An array of jump targets. 9c can’t do this.
Not even the assembler 9a can do this. The complex
compiler-chains emit something similar directly from the
switch-statement. Another way: Implement each opcode as a function.
Cproc emits the switch-statements as binary search which is
O(log n).
With 9c you can actually do a good O(1) VM.
Because calling is extremely cheap.
struct A {
int a;
};
void
func(struct A* a) {
a->a += 1;
}
void
main(void) {
struct A a;
a.a = 1;
func(&a);
}
Results in:
> 6c -S c.c
TEXT func+0(SB),0,$0
INCL ,(BP)
RET ,
TEXT main+0(SB),0,$32
MOVL $1,a+-8(SP)
LEAQ a+-8(SP),BP
CALL ,func+0(SB)
RET ,
END ,So you can implement it as an array of function pointers. I considered changing the switch-statements in luas VM to functions, but it is a lot of work and I only know if it really helps after I done it. Also from now on I maintain my own fork of lua.
But the main reason: Lua isn’t very plan9. So I
thought about creating something similar as C API. Everything is
dynamic, only one C-type, which represents numbers, strings and lists
[tabl^] and are converted by the functions as needed and if possible.
With GC and functions similar to lua. You can call all plan9 APIs. I
think apart from everything now being a function like
add(list). I could actually create quite a nice syntax.
Back in 2015 I made similar things. As usual I like to keep things fluid
until I understand better what I am doing. Also 9front has libavl so I
don’t have to implement hash-tables or trees (avl is a tree). Of course
now I maintain a C-script-API. [tabl^]: Probably lua style tables aka a
hybrid list-dict-set, but the simple version, no meta-tables.