Skip to content

286pm ext buffers#2725

Closed
duzenko wants to merge 2 commits into
ghaerr:masterfrom
duzenko:286pm-ext-buffers
Closed

286pm ext buffers#2725
duzenko wants to merge 2 commits into
ghaerr:masterfrom
duzenko:286pm-ext-buffers

Conversation

@duzenko

@duzenko duzenko commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

This closes one of the protected mode gaps - EXT buffer.
Two commits: one fixes it in the conventional memory and the other moves it to high memory.
Can be split in two PRs or used for research of alternative solution.
No hurry to review.

duzenko and others added 2 commits July 4, 2026 15:09
The external buffer pool addresses each L2 buffer as `b_L2seg = seg + n*64`
(paragraphs) -- real-mode segment arithmetic.  In 286 protected mode `seg` is a
GDT selector, so `selector + n*64` produces a garbage selector, and the kernel
#GPs the instant it touches any buffer past the first (during root mount).  So
CONFIG_FS_EXTERNAL_BUFFER could not be enabled in PM: it was stuck at the L1-only
8K cache.

Give each L2 buffer its own GDT descriptor based at the chunk's physical address
+ n*BLOCK_SIZE, reachable at selector:0 (matching the existing "no offset to
buffer" design).  The copy path (fmemcpyw loading that selector) then works
unchanged.  GDT budget is fine: 64 buffer descriptors of 502 dynamic slots.

Validated on QEMU/KVM: PM boots with CONFIG_FS_EXTERNAL_BUFFER=y, mounts root
through the ext buffers (no #GP), meminfo shows the 64K BUF pool as a selector,
and write/read/cached-read match real-mode ext buffers with no measurable
overhead.  First step toward a PM buffer cache above 1MB -- same descriptor
mechanism, just base the buffers > 1MB.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-on to the descriptor-based ext buffer support: instead of allocating the
L2 pool from the conventional arena (where it competes with processes for the
639K), source it from extended memory above 1MB.  On a 286 that region is
reached naturally through GDT descriptors, so no unreal mode / LOADALL / INT15.

- setup.S / pm286.c: enable the A20 gate on the PM path (fast A20 via port 0x92
  in gdt_init; setup.S's enable_a20_gate only fires for hma=kernel).  Without
  A20 a >1MB access wraps into low memory.
- pm286.c / seg286.h: himem_alloc() -- a bump allocator over the extended memory
  reported by SETUP_XMS_KBYTES (INT 15h AH=88h at boot), handing out 1K-granular
  physical chunks above 1MB.
- buffer.c: in PM the ext alloc loop takes its chunk from himem_alloc() and
  add_buffers() gives each buffer its own descriptor based at that >1MB physical.
  Widen add_buffers()'s 'seg' parameter from ramdesc_t to addr_t: with
  CONFIG_FS_XMS off ramdesc_t is 16-bit (seg_t), which truncated the 32-bit himem
  physical to 0 (descriptors ended up based at 0, corrupting low memory).

Validated on QEMU/KVM: boots, mounts root and does file I/O through the >1MB
buffers, at the same speed as conventional/real-mode ext buffers.  Frees ~64K
(the pool size) of the conventional pool, verified same-setup (both telnet+net):
conventional ext 212K free vs himem 275K free.  Conventional footprint is now
just the 8K L1 staging area + buffer heads; the 64K payload lives above 1MB.

Not yet surfaced in meminfo (himem is a separate pool, like XMS -- neither an
arena segment nor a heap block); a separate "extended pool" report line would
close that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ghaerr

ghaerr commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Hello @duzenko,

Let's keep this open for a while, while the initial PM port is better integrated, and more time can be spent reflecting on proper kernel design for it and additional enhancements like this for PM. This PR contains all sorts of problems: ifdefs, duplicated allocation routines, non-portable A20 code, etc.

I potentially like the idea of using AI for research ideas, but in general think building a kernel using AI isn't a great idea, unless you're just doing it for yourself. The tool excels at reading entire source trees so it can quickly produce a to-the-point solution that can work, but as you can see, almost always just produces #ifdefs shoe-horning its tightly-defined solution into the source tree. Very kludgy and a maintenance nightmare. Proper kernel design will take a lot more than that. For instance, this PR contains completely non-portable inb/outb instructions to enable the A20 gate right in the middle of a buffer routine (very bad design), despite the fact that there's already very specialized and highly tested A20 code already available elsewhere in the system (that analyzation path was likely eliminated early on until the tool later realized it required A20 and didn't perform another top-down analysis, etc).

I imagine the day is here where non-programmers can create large enhancements quite quickly, but from the code I'm seeing, it appears to be just more and more code piled onto a system. For already-large 32- and 64-bit systems with large flat address spaces and seemingly infinite space for kernel size and applications, this isn't a big problem, but has the potential to turn into spaghetti code, almost requiring an AI to maintain it. We will see how that works out in the years to come, for other systems. For ELKS, the problem is in reverse, where resources are extremely limited, and lots of thought as well as a good philosophy has to be present in order to have the system work well, but remaining within its (16-bit) limitations. Do a lot with very little, definitely old-skool. IMO, this philosophy should not change just because the system gets protected mode operation and suddenly has tons more space to operate in.

For non-mission-critical, or ordinary applications written with AI, I think the problem is much less severe, as the application serves as the entire container for any problems described above. This in many ways is similar to all applications in the wild: the range of design goodness covers the entire span from ugly to beautiful, well-designed to terrible, but applications are quite compartmentalized and relatively easy to replace (at least for command line apps).

In summary, this PR gives some potentially interesting ideas for how to extend the kernel buffer system to XMS completely contained within ifdefs. An actual solution will require a rethink of the buffer system such that no ifdefs are required and appropriate wrapper functions are linked, depending on the configured options and architecture.

Thank you!

@ghaerr

ghaerr commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Hello @duzenko,

Thank you for posting this.

as long I'm not the one reviewing its "output" :)

As you could probably tell from my post above, it's easy to get overwhelmed trying to keep up with AI output, especially in PR form :)

While AI's actual code may sometimes not be very fitting for its intended purpose, I'm finding that they can still be thought provoking. In this case, I've realized we should be able to fairly easily be able to use most all of the existing ELKS XMS code in our new PM mode for buffers and other XMS access, like RAM disks.

Currently, ELKS XMS uses 80386 'unreal' mode or the 80286 LOADALL instruction to gain CPU access to XMS memory. For a PM kernel a GDT and protected mode is setup and enabled directly after boot, but the XMS initialization could still be configured and executed, and then use the standard XMS execution path for XMS buffer access. The magic then happens in an enhancement to xms_fmemcpy, where currently linear32_fmemcpy is used. linear32_fmemcpy works in 386+ unreal mode by previously setting a GDT descriptor to all of main and XMS memory, then using the passed 'long offset' to rapidly copy a memory block using 'addr32' prefixes to 16-bit instructions, which is very fast.

For the PM kernel, a replacement pm_linear32_fmemcpy or possibly int15_fmemcpy could update a single GDT entry and use its selector to quickly access any memory location, which would also be very fast. The existing 286 XMS code uses int15_fmemcpy which updates an internal GDT before calling BIOS INT 15h. This same routine could be mostly used for PM, and then do an internal fast block copy rather than calling INT 15, which transitions from real mode <-> PM and back.

Protected mode 80286 segments are limited to 64K bytes, while PM on 386+ allows for 4G. While using a single selector for all of memory could be appealing, that's potentially less secure, can't work the same way on 80286, and wouldn't use the same path as the existing XMS code, where the XMS_INT15 path already updates a (different) GDT.

The above method is probably better than this AI-PR's solution, which allocates as many selectors as there are EXT or XMS buffers. You may be aware that ELKS can be configured to use 2500 XMS buffers, so that'd be a huge drain on the desc_alloc GDT allocator requiring 20K bytes for the GDT in the kernel data segment (which was originally set to 512 selectors max). (BTW, the latest cleanup decreased MAX_GDT_ENTRIES from 512 to 100, which reduced the kernel data segment requirement from 4K to 800 bytes, and it seems to work well running networking, etc).

I'll start work on integrating ELKS XMS to PM. Perhaps 0.9.2 should be released beforehand. I enjoy writing kernel enhancements by hand, but appreciate your posting AI "ideas" (even as code or PRs) to start the process of old-fashioned human thought on a subject.

Thank you!

@ghaerr

ghaerr commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Completed in #2729.

@ghaerr ghaerr closed this Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants