Plan 9.r post 02/B4BH - AHCI patch, creating isos and stupid ideas

Tags: @9rhiz @English

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.0

I 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.

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)
end

I 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)
end

Rebuilding 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)
end

Both 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.