Demand paging, the RTL8168H, links that come up late, and Ctrl+Alt+F1..F12

- Anonymous memory can be reserved without being backed: large mappings,
  MAP_NORESERVE and PROT_NONE are recorded and each page appears, zeroed,
  on first touch -- from trap() for the program, from uva2ka() for the
  kernel on its behalf. WebKit reserves 4 GB at start and touches a sliver;
  backing it all failed with ENOMEM and Epiphany aborted.
- r8169: the 8168G generation and later (the laptop's 8168H, XID 541) are
  taken from the firmware's out-of-band mode, get their PLL powered, the
  receive gate opened, and their PHY reached through the OCP window. The
  PHY's power-down bit is cleared; the old code kept it while restarting
  autonegotiation. Untested on the hardware so far.
- The network thread follows each device's link into lwIP. The link was
  read once at attach, 0.02 s into boot; real autonegotiation takes
  seconds, so on hardware the interface stayed down for good.
- Twelve terminals, switched with Ctrl+Alt+F1..F12 by default: the keys X
  uses, so the console and the desktop agree. AltGr+F-key is gone from
  the defaults (chvt -c altgr+F brings it back).
- SO_PASSCRED on Unix sockets, with SCM_CREDENTIALS delivered by recvmsg.
  WebKit sets it on its IPC sockets and aborted when refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-25 11:27:48 +01:00
commit 54b25708c3
24 changed files with 4166 additions and 68 deletions

View file

@ -19,13 +19,14 @@
// guest cannot detect this: a key the host swallowed is indistinguishable from // guest cannot detect this: a key the host swallowed is indistinguishable from
// a key nobody pressed. So it is a setting. // a key nobody pressed. So it is a setting.
// //
// chvt -c altgr+F AltGr+F1..F6 (the default) // chvt -c ctrl+alt+F Ctrl+Alt+F1..F12 (the default)
// chvt -c ctrl+shift+F Ctrl+Shift+F1..F6 // chvt -c altgr+F AltGr+F1..F12
// chvt -c alt+1 Alt+1..6 // chvt -c ctrl+shift+F Ctrl+Shift+F1..F12
// chvt -c alt+1 Alt+1..9
// chvt -c off no chord at all // chvt -c off no chord at all
// //
// Whatever the modifiers are, they also bind minus and equals to the previous // Whatever the modifiers are, they also bind minus and equals to the previous
// and next terminal in use -- AltGr+- and AltGr+= by default. // and next terminal in use -- Ctrl+Alt+- and Ctrl+Alt+= by default.
#include "core/types.h" #include "core/types.h"
#include "uapi/user.h" #include "uapi/user.h"

View file

@ -243,9 +243,9 @@
<span class="nv-steps__marker">4</span> <span class="nv-steps__marker">4</span>
<div class="nv-steps__body"> <div class="nv-steps__body">
<span class="nv-overline nv-text-tertiary">Step four</span> <span class="nv-overline nv-text-tertiary">Step four</span>
<h3 class="nv-steps__title">Six terminals</h3> <h3 class="nv-steps__title">Twelve terminals</h3>
<p class="nv-steps__text"> <p class="nv-steps__text">
<code class="nv-code">AltGr+F1</code>…<code class="nv-code">F6</code> <code class="nv-code">Ctrl+Alt+F1</code>…<code class="nv-code">F12</code>
switches between them — the one chord a Linux host does not take for switches between them — the one chord a Linux host does not take for
itself — or <code class="nv-code">chvt N</code> from a shell. itself — or <code class="nv-code">chvt N</code> from a shell.
</p> </p>

View file

@ -347,7 +347,7 @@
</div> </div>
<div class="nv-card__body nv-text-secondary"> <div class="nv-card__body nv-text-secondary">
Six of them, each with its own cell grid, scrollback and foreground Six of them, each with its own cell grid, scrollback and foreground
job — switched with <code class="nv-code">AltGr+F1</code>…<code class="nv-code">F6</code>, job — switched with <code class="nv-code">Ctrl+Alt+F1</code>…<code class="nv-code">F12</code>,
which is the one chord a Linux host does not take for itself. The console which is the one chord a Linux host does not take for itself. The console
draws to whichever framebuffer is present, from the kernel's first line. draws to whichever framebuffer is present, from the kernel's first line.
</div> </div>

Binary file not shown.

View file

@ -18,7 +18,7 @@ its distribution.
| API | **SchAPI** | The native programming interface — *designed, not built* | | API | **SchAPI** | The native programming interface — *designed, not built* |
| Compositor | **Schcom** | Owns the display; v0 runs | | Compositor | **Schcom** | Owns the display; v0 runs |
Six virtual terminals sit under all of it -- `/dev/tty1`..`tty6`, AltGr+F1..F6 Twelve virtual terminals sit under all of it -- `/dev/tty1`..`tty12`, Ctrl+Alt+F1..F12
or `chvt N` -- or `chvt N` --
each with its own screen, line discipline and foreground process group. See each with its own screen, line discipline and foreground process group. See
[terminals.md](terminals.md). [terminals.md](terminals.md).

View file

@ -1,12 +1,13 @@
<!-- SPDX-License-Identifier: GPL-2.0-or-later --> <!-- SPDX-License-Identifier: GPL-2.0-or-later -->
# Virtual terminals # Virtual terminals
Six of them, `/dev/tty1` through `/dev/tty6`. Twelve of them, `/dev/tty1` through `/dev/tty12`, one per function key.
| | | | | |
|---|---| |---|---|
| **AltGr+F1 .. F6** | go to that terminal | | **Ctrl+Alt+F1 .. F12** | go to that terminal — on the console and under X alike |
| **AltGr+-** / **AltGr+=** | previous / next terminal *in use* | | **Alt+F1 .. F12** | the same, on the text console only (X keeps plain Alt) |
| **Ctrl+Alt+-** / **Ctrl+Alt+=** | previous / next terminal *in use* |
| `chvt N` | the same, from a script or a serial console | | `chvt N` | the same, from a script or a serial console |
The chord is settable — `chvt -c` — for the reasons below. Each one is an independent terminal: its own screen, its own The chord is settable — `chvt -c` — for the reasons below. Each one is an independent terminal: its own screen, its own
@ -14,8 +15,9 @@ cursor, its own line discipline, its own foreground process group and its own
session. Two shells on two terminals are two unrelated processes that happen to session. Two shells on two terminals are two unrelated processes that happen to
share a keyboard and a screen. share a keyboard and a screen.
A seventh is deliberately not bound. That is where a display server goes, A display server takes one of them with `VT_SETMODE` and does its own
and it will claim switching with `VT_SETMODE` rather than a chord. switching; X switches with Ctrl+Alt+F-key, which is why that is the default
here too.
--- ---
@ -202,8 +204,9 @@ So the kernel ships a default and lets it be moved:
```sh ```sh
chvt # report the active terminal and the current chord chvt # report the active terminal and the current chord
chvt -c altgr+F # AltGr+F1..F6 (the default) chvt -c ctrl+alt+F # Ctrl+Alt+F1..F12 (the default)
chvt -c ctrl+shift+F # Ctrl+Shift+F1..F6 chvt -c altgr+F # AltGr+F1..F12 (the default until 2026-10)
chvt -c ctrl+shift+F # Ctrl+Shift+F1..F12
chvt -c alt+1 # Alt+1..6 chvt -c alt+1 # Alt+1..6
chvt -c off # no chord; chvt N is then the only way chvt -c off # no chord; chvt N is then the only way
``` ```
@ -211,23 +214,23 @@ chvt -c off # no chord; chvt N is then the only way
`off` is a legitimate setting, not a failure mode. On a machine whose host `off` is a legitimate setting, not a failure mode. On a machine whose host
grabs everything worth binding, `chvt N` from a shell is the honest answer. grabs everything worth binding, `chvt N` from a shell is the honest answer.
The default is **AltGr+F1..F6**, and the `AltGr+`letter row above needs reading The default is **Ctrl+Alt+F1..F12**. It was AltGr+F-key for a long time —
carefully: what a Polish layout wants is AltGr with a **letter**. AltGr with a AltGr with a **function key** produces nothing on any layout, no window manager
**function key** produces nothing on any layout, no window manager binds it, binds it, and no application asks for it — but X owns the keyboard while it
and no application asks for it. It is the one combination all three parties runs and switches only with Ctrl+Alt+F-key, so the console and the desktop
leave alone. answered different keys for the same thing. One chord everywhere wins. Inside
a VM whose host grabs Ctrl+Alt+F-key, `chvt -c altgr+F` brings the old one
back.
That distinction is only expressible because Right Alt is tracked as its own That distinction is only expressible because Right Alt is tracked as its own
modifier bit. It used to share `ALT`, which is harmless for typing — neither is modifier bit. It used to share `ALT`, which is harmless for typing — neither is
used to look up a character — and made this impossible to say. used to look up a character — and made this impossible to say.
Verified rather than argued: with the default set, `AltGr+F2` switches, AltGr+letter still reaches the shell untouched, so ą is unaffected either way.
`AltGr+F1` returns to a pixel-identical tty1, and `AltGr+a` still reaches the
shell, so ą is untouched.
Whatever the modifiers are, they also bind **minus** and **equals** to the Whatever the modifiers are, they also bind **minus** and **equals** to the
previous and next terminal. Those step only through terminals that are *in previous and next terminal. Those step only through terminals that are *in
use* — the same `opened` count `VT_GETSTATE` reports — because six terminals use* — the same `opened` count `VT_GETSTATE` reports — because twelve terminals
with two shells on them means four blank screens in between, and cycling past with two shells on them means four blank screens in between, and cycling past
them is worse than not having the key. them is worse than not having the key.

3705
docs/niveus/dist/niveus.css vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -546,6 +546,7 @@ int sockbind_in(struct socket*, uint addr, ushort port);
int sockconnect_in(struct socket*, uint addr, ushort port); int sockconnect_in(struct socket*, uint addr, ushort port);
int sock_domain(struct socket*); int sock_domain(struct socket*);
int sock_peercred(struct socket*, int*, uint*, uint*); int sock_peercred(struct socket*, int*, uint*, uint*);
int sock_passcred(struct socket*);
// Socket options. The syscall layer does the user-memory marshalling and these // Socket options. The syscall layer does the user-memory marshalling and these
// do the work, because struct socket's fields live in kernel/net/unix.c and the // do the work, because struct socket's fields live in kernel/net/unix.c and the
// syscall layer deliberately cannot see them. // syscall layer deliberately cannot see them.
@ -733,6 +734,7 @@ int net_eth_detach(struct device *);
int net_detach(struct device *); int net_detach(struct device *);
int net_attach_late(struct device *); int net_attach_late(struct device *);
int net_eth_poll(void); int net_eth_poll(void);
void net_eth_link_check(void);
int net_eth_count(void); int net_eth_count(void);
void net_eth_counts(int i, uint64 *rx, uint64 *tx); void net_eth_counts(int i, uint64 *rx, uint64 *tx);
struct netif *net_eth_netif(int i); struct netif *net_eth_netif(int i);
@ -805,6 +807,8 @@ void inotify_file(struct file *f, uint32 mask);
uint32 inotify_cookie(void); uint32 inotify_cookie(void);
int ev_fionread(struct evobj *o); int ev_fionread(struct evobj *o);
int pipe_avail(struct pipe *p); int pipe_avail(struct pipe *p);
int vm_fault(addr_t va);
pte_t* walkpte(pml4e_t *pgdir, const void *va);
int path_identity(char *path, int follow, int onlydir, uint *dev, uint64 *inum); int path_identity(char *path, int follow, int onlydir, uint *dev, uint64 *inum);
int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing); int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing);
void shm_detach(int id); void shm_detach(int id);

View file

@ -92,12 +92,10 @@
// leaves `chvt N` as the only way to switch, which is a legitimate choice on // leaves `chvt N` as the only way to switch, which is a legitimate choice on
// a machine whose host takes everything. // a machine whose host takes everything.
// //
// The default is AltGr+F1..F6, and the "AltGr wants it" line above needs // The default is Ctrl+Alt+F1..F12: the keys X switches with, so the console
// reading carefully: what a Polish layout wants is AltGr+*letter*. AltGr with // and the desktop answer the same chord. It used to be AltGr+F-key, which
// a function key produces nothing on any layout, no window manager binds it, // nothing else claims -- Right Alt being a separate modifier bit here is what
// and no application asks for it. Right Alt being a separate modifier bit // makes that expressible, and `chvt -c altgr+F` still sets it.
// here is what makes the distinction expressible at all -- it used to share
// ALT, which is harmless for typing and useless for this.
#define KBD_VT_SWITCH (KBD_SPECIAL + 0x20) #define KBD_VT_SWITCH (KBD_SPECIAL + 0x20)
// Previous / next terminal, on the same modifiers as the chord above. The // Previous / next terminal, on the same modifiers as the chord above. The

View file

@ -26,10 +26,10 @@
#include "core/param.h" #include "core/param.h"
#include "uapi/posix.h" #include "uapi/posix.h"
// Six, and Linux's numbering: /dev/tty1 through /dev/tty6, reached with // Twelve, one per function key, and Linux's numbering: /dev/tty1 through
// Alt+1..Alt+6. A seventh is deliberately not bound -- that is where a // /dev/tty12, reached with Ctrl+Alt+F1..F12 (and plain Alt on the text
// display server goes, and Schcom will want it. // console). The key codes leave room for sixteen; see KBD_VT_SWITCH.
#define NVT 6 #define NVT 12
// A cell. // A cell.
// //

View file

@ -349,6 +349,7 @@ struct socket {
int o_keepalive; int o_keepalive;
int o_broadcast; int o_broadcast;
int o_nodelay; int o_nodelay;
int o_passcred; // SO_PASSCRED: SCM_CREDENTIALS on every recvmsg
// Descriptors sent to this socket and not yet received. They live on the // Descriptors sent to this socket and not yet received. They live on the
// *receiving* side, next to the bytes they were sent with, because that is // *receiving* side, next to the bytes they were sent with, because that is

View file

@ -432,6 +432,7 @@ struct statfs {
#define MAP_PRIVATE 0x02 #define MAP_PRIVATE 0x02
#define MAP_FIXED 0x10 #define MAP_FIXED 0x10
#define MAP_ANONYMOUS 0x20 #define MAP_ANONYMOUS 0x20
#define MAP_NORESERVE 0x4000
// waitpid() options. The kernel tests `options & 1`; this is that bit, named. // waitpid() options. The kernel tests `options & 1`; this is that bit, named.
#define WNOHANG 0x001 #define WNOHANG 0x001

View file

@ -160,6 +160,11 @@ trap(struct trapframe *tf)
} }
unexpected: unexpected:
// A user page fault on anonymous memory that is reserved but not backed
// yet: the page is made and the instruction runs again. See vm_fault().
if(tf->trapno == T_PGFLT && myproc() && (tf->cs&3) == DPL_USER &&
!(tf->err & 1) && vm_fault(rcr2()) == 0)
break;
if(myproc() == 0 || (tf->cs&3) == 0){ if(myproc() == 0 || (tf->cs&3) == 0){
// A kernel-mode trap ends in panic() a dozen lines down, so the console // A kernel-mode trap ends in panic() a dozen lines down, so the console
// can be put into its panic mode *now* -- and it has to be, because the // can be put into its panic mode *now* -- and it has to be, because the

View file

@ -1134,6 +1134,13 @@ copyuvm(pml4e_t *pgdir, uint64 sz)
return 0; return 0;
} }
// The PTE for va, if its page tables exist. For vm_fault().
pte_t *
walkpte(pml4e_t *pgdir, const void *va)
{
return walkpgdir(pgdir, va, 0);
}
char* char*
uva2ka(pml4e_t *pgdir, char *uva) uva2ka(pml4e_t *pgdir, char *uva)
{ {
@ -1142,6 +1149,11 @@ uva2ka(pml4e_t *pgdir, char *uva)
pte_t *pte; pte_t *pte;
pte = walkpgdir(pgdir, uva, 0); pte = walkpgdir(pgdir, uva, 0);
// Anonymous memory not touched yet: backed now, as the program's own
// access would have done. Only for the running process's own table.
if((pte == 0 || (*pte & PTE_P) == 0) && myproc() && pgdir == myproc()->pgdir &&
vm_fault((addr_t)uva) == 0)
pte = walkpgdir(pgdir, uva, 0);
if(pte == 0 || (*pte & PTE_P) == 0) if(pte == 0 || (*pte & PTE_P) == 0)
return 0; return 0;
if((*pte & PTE_U) == 0) if((*pte & PTE_U) == 0)

View file

@ -336,7 +336,7 @@ static struct spinlock input_lock;
// 1 /dev/console the active terminal, so kernel messages and a // 1 /dev/console the active terminal, so kernel messages and a
// shell started on the console both follow a switch // shell started on the console both follow a switch
// 10 /dev/tty the caller's controlling terminal // 10 /dev/tty the caller's controlling terminal
// 11..16 /dev/tty1..6 that terminal, whether or not it is on screen // 11..22 /dev/tty1..12 that terminal, whether or not it is on screen
// //
// Anything else is the console, which is what an unrecognised minor meant // Anything else is the console, which is what an unrecognised minor meant
// before there was more than one terminal to get wrong. // before there was more than one terminal to get wrong.
@ -461,7 +461,7 @@ erase_one(struct vt *v)
// Keyboard input // Keyboard input
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Alt+F1..F6, Ctrl+Alt+F1..F6 and the settable chord (AltGr+F1..F6 by // Alt+F-key, Ctrl+Alt+F-key and the settable chord (Ctrl+Alt+F1..F12 by
// default) switch terminals. kbd.c reports them as // default) switch terminals. kbd.c reports them as
// KBD_VT_SWITCH+n so that the // KBD_VT_SWITCH+n so that the
// decision of what a chord *means* is made here, where the terminals are, // decision of what a chord *means* is made here, where the terminals are,

View file

@ -9,19 +9,40 @@
// The chord, settable at run time through VT_SETCHORD. // The chord, settable at run time through VT_SETCHORD.
// //
// AltGr+F1..F6 by default. Every other candidate is taken by the host, by // Ctrl+Alt+F1..F12 by default, the same keys X switches with. It used to be
// applications, or by the keyboard layout -- see include/dev/kbd.h -- and AltGr // AltGr+F-key, chosen because nothing else claimed it -- but under X the
// with a function key is taken by none of the three: a Polish layout wants // server owns the keyboard and only Ctrl+Alt+F-key works, so the console and
// AltGr+*letter*, which still reaches the shell untouched, and no window // the desktop answered different keys for the same thing.
// manager binds AltGr with an F-key.
// //
// AltGr+minus and AltGr+equals step to the previous and next terminal on the // Ctrl+Alt+minus and Ctrl+Alt+equals step to the previous and next terminal,
// same modifiers, for when you have two and do not care which number they are. // for when you have two and do not care which number they are.
// //
// Zero modifiers disables all of it; `chvt N` still works. // Zero modifiers disables the chord; `chvt N` still works.
uint vt_chord_mods = RALT; uint vt_chord_mods = CTL | ALT;
uint vt_chord_base = SCAN_F1; uint vt_chord_base = SCAN_F1;
// Which terminal an F-key scancode names: F1..F10 are 0x3B..0x44 and F11 and
// F12 sit apart, at 0x57 and 0x58. -1 for anything else.
static int
fkey_index(uint data)
{
if (data >= SCAN_F1 && data < SCAN_F1 + 10) return (int)(data - SCAN_F1);
if (data == 0x57) return 10;
if (data == 0x58) return 11;
return -1;
}
// The terminal a chord key names, from `base`: the F-keys by fkey_index, any
// other base as a run of ten consecutive scancodes -- a digit row is 1..9, 0,
// and the minus and equals after it are already the previous and next keys.
static int
chord_index(uint data, uint base)
{
int i = base == SCAN_F1 ? fkey_index(data)
: (data >= base && data < base + 10 ? (int)(data - base) : -1);
return i < NVT ? i : -1;
}
// Which scancodes are modifiers, and which are locks. Hardware facts rather // Which scancodes are modifiers, and which are locks. Hardware facts rather
// than layout ones -- every layout agrees that 0x2A is a Shift key -- so they // than layout ones -- every layout agrees that 0x2A is a Shift key -- so they
// stay here while the character tables live in keymap.c. // stay here while the character tables live in keymap.c.
@ -146,21 +167,20 @@ kbdgetc(void)
// modifiers named, and nothing else held. // modifiers named, and nothing else held.
if (vt_chord_mods != 0 && if (vt_chord_mods != 0 &&
(shift & (CTL | SHIFT | ALT | RALT)) == vt_chord_mods) { (shift & (CTL | SHIFT | ALT | RALT)) == vt_chord_mods) {
if (data >= vt_chord_base && data < vt_chord_base + NVT) int i = chord_index(data, vt_chord_base);
return KBD_VT_SWITCH + (data - vt_chord_base); if (i >= 0)
return KBD_VT_SWITCH + i;
if (data == SCAN_MINUS) return KBD_VT_PREV; if (data == SCAN_MINUS) return KBD_VT_PREV;
if (data == SCAN_EQUALS) return KBD_VT_NEXT; if (data == SCAN_EQUALS) return KBD_VT_NEXT;
} }
// And Linux's own: Alt+F1..F6 and Ctrl+Alt+F1..F6, whatever the chord is set // And Linux's own: Alt+F-key and Ctrl+Alt+F-key, whatever the chord is set
// to. These are what anyone who has used Linux presses, on a real machine, // to. Under X the terminal is in VT_PROCESS, console_vt_hotkey() lets the
// and nothing reached them: SchOS answered only AltGr+F-keys, which no one // keys through, and X switches with them itself -- as on Linux.
// thinks to try. Under X the terminal is in VT_PROCESS, console_vt_hotkey() if (chord_index(data, SCAN_F1) >= 0 &&
// lets the keys through, and X switches with them itself -- as on Linux.
if (data >= SCAN_F1 && data < SCAN_F1 + NVT &&
((shift & (CTL | SHIFT | ALT | RALT)) == ALT || ((shift & (CTL | SHIFT | ALT | RALT)) == ALT ||
(shift & (CTL | SHIFT | ALT | RALT)) == (CTL | ALT))) (shift & (CTL | SHIFT | ALT | RALT)) == (CTL | ALT)))
return KBD_VT_SWITCH + (data - SCAN_F1); return KBD_VT_SWITCH + chord_index(data, SCAN_F1);
cp = keymap_lookup(data, shift); cp = keymap_lookup(data, shift);
if (cp == 0) if (cp == 0)

View file

@ -51,6 +51,13 @@
#define R_CONFIG1 0x52 #define R_CONFIG1 0x52
#define R_PHYAR 0x60 // the MII window #define R_PHYAR 0x60 // the MII window
#define R_PHYSTATUS 0x6C #define R_PHYSTATUS 0x6C
#define R_PMCH 0x6F // PLL power
#define R_ERIDR 0x70 // extended register window: data
#define R_ERIAR 0x74 // and address
#define R_GPHY_OCP 0xB8 // the PHY's registers, 8168G and later
#define R_MAC_OCP 0xB0 // the MAC's own OCP registers
#define R_MCU 0xD3
#define R_MISC 0xF0
#define R_RMS 0xDA // largest frame the chip will accept #define R_RMS 0xDA // largest frame the chip will accept
#define R_CPCR 0xE0 // "C+" command #define R_CPCR 0xE0 // "C+" command
#define R_RDSAR 0xE4 // Rx descriptors, 64-bit #define R_RDSAR 0xE4 // Rx descriptors, 64-bit
@ -77,6 +84,15 @@
#define PHYSTATUS_LINK 0x02 #define PHYSTATUS_LINK 0x02
#define MCU_NOW_IS_OOB 0x80 // the firmware, not a driver, owns the MAC
#define MCU_LL_READY 0x02 // link-list FIFO ready
#define MISC_RXDV_GATE 0x00080000
#define OCP_FLAG 0x80000000
#define OCP_STD_PHY_BASE 0xA400
#define BMCR_PDOWN 0x0800
#define BMCR_ANENABLE 0x1000
#define BMCR_ANRESTART 0x0200
// Rx config: accept frames addressed to us, broadcast and multicast, with the // Rx config: accept frames addressed to us, broadcast and multicast, with the
// largest DMA burst and Rx FIFO threshold the chip offers. Promiscuous is // largest DMA burst and Rx FIFO threshold the chip offers. Promiscuous is
// deliberately not set -- a NIC that hands the stack every frame on the wire // deliberately not set -- a NIC that hands the stack every frame on the wire
@ -128,6 +144,8 @@ struct r8169 {
int tx_next; int tx_next;
uint8 mac[6]; uint8 mac[6];
int ready; int ready;
int gen_g; // 8168G or later: see r8169_gen_g()
uint16 ocp_base; // the PHY page selected through register 0x1F
uint64 rx_packets, tx_packets; uint64 rx_packets, tx_packets;
struct spinlock lock; struct spinlock lock;
@ -168,7 +186,7 @@ wr64(struct r8169 *r, int o, uint64 v)
#define PHYAR_FLAG 0x80000000 #define PHYAR_FLAG 0x80000000
static void static void
mii_write(struct r8169 *r, int reg, uint16 val) phyar_write(struct r8169 *r, int reg, uint16 val)
{ {
wr32(r, R_PHYAR, PHYAR_FLAG | ((reg & 0x1F) << 16) | val); wr32(r, R_PHYAR, PHYAR_FLAG | ((reg & 0x1F) << 16) | val);
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
@ -179,7 +197,7 @@ mii_write(struct r8169 *r, int reg, uint16 val)
} }
static int static int
mii_read(struct r8169 *r, int reg) phyar_read(struct r8169 *r, int reg)
{ {
wr32(r, R_PHYAR, (reg & 0x1F) << 16); wr32(r, R_PHYAR, (reg & 0x1F) << 16);
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
@ -191,6 +209,164 @@ mii_read(struct r8169 *r, int reg)
return -1; return -1;
} }
// From the 8168G on, the PHY sits behind an OCP window instead: its standard
// registers are at 0xA400 + 2*reg, and "select page N" through register 0x1F
// becomes a base address. PHYAR still answers reads on some of these chips,
// which is how this driver got away without it -- until a write mattered.
static void
ocp_phy_write(struct r8169 *r, uint16 addr, uint16 val)
{
wr32(r, R_GPHY_OCP, OCP_FLAG | ((uint32)(addr >> 1) << 16) | val);
for (int i = 0; i < 1000; i++) {
microdelay(20);
if (!(rd32(r, R_GPHY_OCP) & OCP_FLAG))
return;
}
}
static int
ocp_phy_read(struct r8169 *r, uint16 addr)
{
wr32(r, R_GPHY_OCP, (uint32)(addr >> 1) << 16);
for (int i = 0; i < 1000; i++) {
microdelay(20);
uint32 v = rd32(r, R_GPHY_OCP);
if (v & OCP_FLAG)
return (int)(v & 0xFFFF);
}
return -1;
}
static void
mii_write(struct r8169 *r, int reg, uint16 val)
{
if (!r->gen_g) {
phyar_write(r, reg, val);
return;
}
if (reg == 0x1F) {
r->ocp_base = val ? (uint16)(val << 4) : OCP_STD_PHY_BASE;
return;
}
if (r->ocp_base != OCP_STD_PHY_BASE)
reg -= 0x10;
ocp_phy_write(r, (uint16)(r->ocp_base + reg * 2), val);
}
static int
mii_read(struct r8169 *r, int reg)
{
if (!r->gen_g)
return phyar_read(r, reg);
if (r->ocp_base != OCP_STD_PHY_BASE)
reg -= 0x10;
return ocp_phy_read(r, (uint16)(r->ocp_base + reg * 2));
}
// The MAC's OCP registers. No completion flag to wait on.
static uint16
mac_ocp_read(struct r8169 *r, uint16 reg)
{
wr32(r, R_MAC_OCP, (uint32)(reg >> 1) << 16);
return (uint16)rd32(r, R_MAC_OCP);
}
static void
mac_ocp_write(struct r8169 *r, uint16 reg, uint16 val)
{
wr32(r, R_MAC_OCP, OCP_FLAG | ((uint32)(reg >> 1) << 16) | val);
}
// The extended (ERI) registers, all four bytes at once.
static uint32
eri_read(struct r8169 *r, int addr)
{
wr32(r, R_ERIAR, (0xFu << 12) | addr);
for (int i = 0; i < 100; i++) {
microdelay(10);
if (rd32(r, R_ERIAR) & OCP_FLAG)
return rd32(r, R_ERIDR);
}
return 0;
}
static void
eri_write(struct r8169 *r, int addr, uint32 val)
{
wr32(r, R_ERIDR, val);
wr32(r, R_ERIAR, OCP_FLAG | (0xFu << 12) | addr);
for (int i = 0; i < 100; i++) {
microdelay(10);
if (!(rd32(r, R_ERIAR) & OCP_FLAG))
return;
}
}
// Which XIDs are the 8168G generation and later: 8168G, GU, 8411B, EP, H.
// They share the OCP windows and the firmware handover below.
static int
r8169_gen_g(uint32 xid)
{
switch (xid) {
case 0x4C0: case 0x4C1: case 0x5C8: case 0x509:
case 0x500: case 0x501: case 0x502:
case 0x540: case 0x541:
return 1;
}
return 0;
}
static void
wait_ll_ready(struct r8169 *r)
{
for (int i = 0; i < 42; i++) {
if (rd8(r, R_MCU) & MCU_LL_READY)
return;
microdelay(100);
}
}
// Taking the chip from the firmware. A laptop that was last running Windows,
// or that keeps Wake-on-LAN armed, hands it over in out-of-band mode with the
// receive path gated off: the MAC answers its registers and moves no frames.
static void
r8169_takeover_g(struct r8169 *r)
{
wr32(r, R_MISC, rd32(r, R_MISC) | MISC_RXDV_GATE);
wr8(r, R_CR, rd8(r, R_CR) & ~(CR_TE | CR_RE));
microdelay(1000);
wr8(r, R_MCU, rd8(r, R_MCU) & ~MCU_NOW_IS_OOB);
mac_ocp_write(r, 0xE8DE, mac_ocp_read(r, 0xE8DE) & ~(1u << 14));
wait_ll_ready(r);
mac_ocp_write(r, 0xE8DE, mac_ocp_read(r, 0xE8DE) | (1u << 15));
wait_ll_ready(r);
}
// The PHY up and negotiating: page 0, out of power-down, autoneg on and
// restarted. Power-down is what firmware leaves set after a shutdown, and
// the old code ORed autoneg into BMCR and kept it.
static void
r8169_phy_up(struct r8169 *r)
{
mii_write(r, 0x1F, 0);
int bmcr = mii_read(r, 0);
if (bmcr < 0)
return;
bmcr &= ~BMCR_PDOWN;
mii_write(r, 0, (uint16)bmcr);
if (r->gen_g) {
// Wait for the PHY to report it is on: its state, low three bits of
// OCP 0xA420, reads 3 once it is.
for (int i = 0; i < 100; i++) {
int st = ocp_phy_read(r, 0xA420);
if (st >= 0 && (st & 7) == 3)
break;
microdelay(1000);
}
}
mii_write(r, 0, (uint16)(bmcr | BMCR_ANENABLE | BMCR_ANRESTART));
}
// --- the class ops ---------------------------------------------------------- // --- the class ops ----------------------------------------------------------
static const unsigned char * static const unsigned char *
@ -392,6 +568,7 @@ r8169_probe(struct pci_dev *d)
} }
r->pdev = d; r->pdev = d;
r->ocp_base = OCP_STD_PHY_BASE;
initlock(&r->lock, "r8169"); initlock(&r->lock, "r8169");
pci_enable(d, PCI_CMD_MEMORY | PCI_CMD_BUS_MASTER); pci_enable(d, PCI_CMD_MEMORY | PCI_CMD_BUS_MASTER);
@ -404,6 +581,10 @@ r8169_probe(struct pci_dev *d)
return -1; return -1;
} }
r->gen_g = r8169_gen_g((rd32(r, R_TCR) >> 20) & 0xFCF);
if (r->gen_g)
r8169_takeover_g(r);
// Soft reset, and wait for the chip to say it finished. // Soft reset, and wait for the chip to say it finished.
wr8(r, R_CR, CR_RST); wr8(r, R_CR, CR_RST);
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
@ -426,6 +607,16 @@ r8169_probe(struct pci_dev *d)
wr8(r, R_9346CR, C9346_UNLOCK); // config registers are writable now wr8(r, R_9346CR, C9346_UNLOCK); // config registers are writable now
if (r->gen_g) {
// The PLL powered, the extra ERI bits the 8168H wants with it, and the
// receive gate opened again.
wr8(r, R_PMCH, rd8(r, R_PMCH) | 0xC0);
uint32 xid = (rd32(r, R_TCR) >> 20) & 0xFCF;
if (xid == 0x540 || xid == 0x541)
eri_write(r, 0x1A8, eri_read(r, 0x1A8) | 0xFC000000);
wr32(r, R_MISC, rd32(r, R_MISC) & ~MISC_RXDV_GATE);
}
// CPCR is deliberately left alone. // CPCR is deliberately left alone.
// //
// An earlier version set bit 3 here with a comment calling it "PCI multiple // An earlier version set bit 3 here with a comment calling it "PCI multiple
@ -455,14 +646,7 @@ r8169_probe(struct pci_dev *d)
wr8(r, R_CR, CR_RE | CR_TE); wr8(r, R_CR, CR_RE | CR_TE);
wr8(r, R_9346CR, C9346_LOCK); wr8(r, R_9346CR, C9346_LOCK);
// Restart autonegotiation. The PHY normally does this out of reset, and r8169_phy_up(r);
// asking again costs nothing and covers the case where firmware left it
// parked -- BMCR bit 12 is autoneg enable, bit 9 restarts it.
{
int bmcr = mii_read(r, 0);
if (bmcr >= 0)
mii_write(r, 0, (uint16)(bmcr | 0x1200));
}
wr16(r, R_ISR, 0xFFFF); // clear anything left over wr16(r, R_ISR, 0xFFFF); // clear anything left over
wr16(r, R_IMR, ISR_ROK | ISR_RER | ISR_TOK | ISR_TER | wr16(r, R_IMR, ISR_ROK | ISR_RER | ISR_TOK | ISR_TER |

View file

@ -164,9 +164,10 @@ usb_kbd_getc(void)
static int static int
hid_special(uchar usage, uchar mods) hid_special(uchar usage, uchar mods)
{ {
// F1..F6 with Alt, AltGr or Ctrl+Alt: switch terminals, as on Linux. // F1..F12 with left Alt or Ctrl+Alt: switch terminals, as on Linux and as
// the PS/2 path does. AltGr alone is a layout's third level, not this.
if (usage >= 0x3A && usage < 0x3A + NVT && (mods & HID_MOD_ALT) && if (usage >= 0x3A && usage < 0x3A + NVT && (mods & HID_MOD_ALT) &&
!(mods & HID_MOD_SHIFT)) !(mods & HID_MOD_SHIFT) && ((mods & 0x04) || (mods & HID_MOD_CTRL)))
return KBD_VT_SWITCH + (usage - 0x3A); return KBD_VT_SWITCH + (usage - 0x3A);
if (usage >= 0x3A && usage <= 0x45) if (usage >= 0x3A && usage <= 0x45)
return KBD_F1 + (usage - 0x3A); return KBD_F1 + (usage - 0x3A);

View file

@ -101,6 +101,12 @@ static const struct devnode devnodes[] = {
{ "tty4", 1, 14, D_ROOT }, { "tty4", 1, 14, D_ROOT },
{ "tty5", 1, 15, D_ROOT }, { "tty5", 1, 15, D_ROOT },
{ "tty6", 1, 16, D_ROOT }, { "tty6", 1, 16, D_ROOT },
{ "tty7", 1, 17, D_ROOT },
{ "tty8", 1, 18, D_ROOT },
{ "tty9", 1, 19, D_ROOT },
{ "tty10", 1, 20, D_ROOT },
{ "tty11", 1, 21, D_ROOT },
{ "tty12", 1, 22, D_ROOT },
{ "stdin", 1, 1, D_ROOT }, { "stdin", 1, 1, D_ROOT },
{ "stdout", 1, 1, D_ROOT }, { "stdout", 1, 1, D_ROOT },
{ "stderr", 1, 1, D_ROOT }, { "stderr", 1, 1, D_ROOT },

View file

@ -259,3 +259,28 @@ net_eth_poll(void)
} }
return moved; return moved;
} }
// Follow each device's link into lwIP.
//
// The link was read once, at attach, and never again. Under QEMU it is up
// before the driver asks; on a real network autonegotiation takes two or
// three seconds, so attach saw it down and the interface stayed down for
// good -- no DHCP, no traffic, the cable plugged in and nothing to show for
// it. Bringing the link up also restarts DHCP (lwIP does that itself).
void
net_eth_link_check(void)
{
for (int k = 0; k < nifs; k++) {
struct eth_if *e = &ifs[k];
if (!e->used)
continue;
int up = netdev_link_up(e->dev) != 0;
if (up == (netif_is_link_up(&e->netif) != 0))
continue;
if (up)
netif_set_link_up(&e->netif);
else
netif_set_link_down(&e->netif);
klog("net: %s link %s\n", e->dev->name, up ? "up" : "down");
}
}

View file

@ -292,12 +292,19 @@ net_thread(void)
// One bit per interface, so a second one binding later is announced too. // One bit per interface, so a second one binding later is announced too.
uint link_checked = 0;
for (;;) { for (;;) {
int moved; int moved;
lwip_sys_lock(); lwip_sys_lock();
moved = net_eth_poll(); moved = net_eth_poll();
sys_check_timeouts(); sys_check_timeouts();
// A quarter of a second is quick enough to notice a cable, and a
// register read per interface that often costs nothing.
if (ticks - link_checked >= 25) {
link_checked = ticks;
net_eth_link_check();
}
// Loopback has no driver and no interrupt: a packet sent to 127.0.0.1 // Loopback has no driver and no interrupt: a packet sent to 127.0.0.1
// sits on the loopif's queue until something drains it. With NO_SYS=1 // sits on the loopif's queue until something drains it. With NO_SYS=1
// that something is this thread. // that something is this thread.

View file

@ -271,6 +271,7 @@ sockopt_set(struct socket *s, int level, int name, int val)
case SO_REUSEADDR: s->o_reuseaddr = !!val; break; case SO_REUSEADDR: s->o_reuseaddr = !!val; break;
case SO_KEEPALIVE: s->o_keepalive = !!val; break; case SO_KEEPALIVE: s->o_keepalive = !!val; break;
case SO_BROADCAST: s->o_broadcast = !!val; break; case SO_BROADCAST: s->o_broadcast = !!val; break;
case 16: s->o_passcred = !!val; break; // SO_PASSCRED
// Accepted and left at the size we have. Not the same lie as before: // Accepted and left at the size we have. Not the same lie as before:
// Linux is explicitly free to clamp these, and sockopt_get below reports // Linux is explicitly free to clamp these, and sockopt_get below reports
// the real figure rather than the requested one. // the real figure rather than the requested one.
@ -313,6 +314,7 @@ sockopt_get(struct socket *s, int level, int name, int *out)
case SO_REUSEADDR: *out = s->o_reuseaddr; break; case SO_REUSEADDR: *out = s->o_reuseaddr; break;
case SO_KEEPALIVE: *out = s->o_keepalive; break; case SO_KEEPALIVE: *out = s->o_keepalive; break;
case SO_BROADCAST: *out = s->o_broadcast; break; case SO_BROADCAST: *out = s->o_broadcast; break;
case 16: *out = s->o_passcred; break; // SO_PASSCRED
// The real buffer, not a number somebody asked for. // The real buffer, not a number somebody asked for.
case SO_SNDBUF: case SO_SNDBUF:
case SO_RCVBUF: *out = SOCKBUF; break; case SO_RCVBUF: *out = SOCKBUF; break;
@ -1025,6 +1027,12 @@ socksendto(struct socket *s, char *buf, int n, uint addr, ushort port)
return sockwrite(s, buf, n); return sockwrite(s, buf, n);
} }
int
sock_passcred(struct socket *s)
{
return s->domain != AF_INET && s->o_passcred;
}
// SO_PEERCRED: the credentials recorded for the far end of a connected Unix // SO_PEERCRED: the credentials recorded for the far end of a connected Unix
// socket. -1 if there is no far end. // socket. -1 if there is no far end.
int int

View file

@ -123,6 +123,81 @@ mmap_va(struct proc *p, uint64 len, int mergeable)
return va; return va;
} }
// --- demand paging ---------------------------------------------------------
//
// Anonymous memory may be recorded without being backed: the page appears,
// zeroed, the first time it is touched -- by the program, through trap(), or
// by the kernel on its behalf, through uva2ka(). Large mappings are made this
// way, and so are the ones that say outright they will not all be used.
// WebKit reserves 4 GB of address space at start for its JIT and heaps and
// touches a sliver of it; backing all of it at mmap time failed with ENOMEM
// and Epiphany aborted. Go, Java and Firefox reserve the same way.
//
// fork and munmap already skip the holes in anonymous memory, so a page that
// was never touched is simply one they do not see, and a child that touches
// it gets zeroes -- which is what it would have read in the parent.
#define LAZY_MIN (1024 * 1024)
static int
mmap_lazy(int prot, int flags, uint64 length)
{
return (flags & MAP_NORESERVE) || prot == PROT_NONE || length >= LAZY_MIN;
}
static struct spinlock faultlock;
static int faultlock_ready;
// Back the page at va if it lies in this process's anonymous memory and is
// not there yet. 0 if it is there now, -1 if this is a real fault. Takes a
// spinlock and nothing that sleeps, so uva2ka() may call it from anywhere;
// the lock is what keeps two threads of one process from both mapping it.
int
vm_fault(addr_t va)
{
struct proc *p = myproc();
pte_t *pte;
char *mem;
int found = 0;
if (!p || !p->mm || va >= KERNBASE)
return -1;
va = PGROUNDDOWN(va);
for (int i = 0; i < NVMA; i++) {
struct vma *v = &p->mm->vma[i];
if (vma_anon(v) && v->addr <= va && va < v->addr + v->length) {
found = 1;
break;
}
}
if (!found)
return -1;
if ((mem = kalloc()) == 0)
return -1;
memset(mem, 0, PGSIZE);
if (!faultlock_ready) {
initlock(&faultlock, "vmfault");
faultlock_ready = 1;
}
acquire(&faultlock);
pte = walkpte(p->pgdir, (void *)va);
if (pte && (*pte & PTE_P)) {
// Another thread got here first, or the page was never missing and
// this is a protection fault: either way, not ours to fix.
int ours = (*pte & PTE_U) != 0;
release(&faultlock);
kfree(mem);
return ours ? 0 : -1;
}
if (mappages(p->pgdir, (void *)va, PGSIZE, V2P((uint64)mem), PTE_W | PTE_U) < 0) {
release(&faultlock);
kfree(mem);
return -1;
}
release(&faultlock);
return 0;
}
// Record [va, va+len) as anonymous memory wherever no mapping already covers // Record [va, va+len) as anonymous memory wherever no mapping already covers
// it, for a MAP_FIXED that may land anywhere: inside an earlier mapping, across // it, for a MAP_FIXED that may land anywhere: inside an earlier mapping, across
// the end of one, or in a gap. Below sz is the program image and its heap, // the end of one, or in a gap. Below sz is the program image and its heap,
@ -205,8 +280,16 @@ mmap_locked(void)
if (bad_user_range(va, length) || (va % PGSIZE)) if (bad_user_range(va, length) || (va % PGSIZE))
return -EINVAL; return -EINVAL;
vm_unmap_range(myproc()->pgdir, va, length); vm_unmap_range(myproc()->pgdir, va, length);
if (prot == PROT_NONE) int fixed_file = f && f->type == FD_INODE && f->ip->type != T_DEV &&
!(flags & MAP_ANONYMOUS);
// Reserved, not backed. PROT_NONE is recorded too: mprotect() does not
// change pages here, so a range reserved PROT_NONE and opened up later
// has to be memory that can appear when it is touched.
if (!fixed_file && mmap_lazy(prot, flags, length)) {
if (vma_cover_anon(myproc(), va, length) < 0)
return -ENOMEM;
return va; return va;
}
for (uint64 a = va; a < va + length; a += PGSIZE) { for (uint64 a = va; a < va + length; a += PGSIZE) {
char *mem = kalloc(); char *mem = kalloc();
if (!mem) return -ENOMEM; if (!mem) return -ENOMEM;
@ -407,6 +490,11 @@ mmap_locked(void)
// to the text console. fbclose() puts it back. // to the text console. fbclose() puts it back.
fb_mapped(); fb_mapped();
} else { } else {
if (mmap_lazy(prot, flags, length)) {
if (vma_note_anon(myproc(), va, length) < 0)
return -ENOMEM;
return va;
}
for (uint64 a = va; a < va + length; a += PGSIZE) { for (uint64 a = va; a < va + length; a += PGSIZE) {
char *mem = kalloc(); char *mem = kalloc();
if (!mem) { if (!mem) {

View file

@ -1030,7 +1030,7 @@ SYSCALL_DEFINE(recvmsg)
struct file *file; struct file *file;
struct msghdr m; struct msghdr m;
struct file *fs[SCM_MAXFD]; struct file *fs[SCM_MAXFD];
char cbuf[CMSG_SPACE(SCM_MAXFD * sizeof(int))]; char cbuf[CMSG_SPACE(SCM_MAXFD * sizeof(int)) + CMSG_SPACE(12)];
int ufd[SCM_MAXFD]; int ufd[SCM_MAXFD];
addr_t umsg; addr_t umsg;
uint64 i, clen = 0; uint64 i, clen = 0;
@ -1141,6 +1141,35 @@ SYSCALL_DEFINE(recvmsg)
} }
} }
// SO_PASSCRED: who sent it, after any descriptors. On a connected Unix
// socket that is always the peer, so its recorded credentials are the
// answer. WebKit sets the option on its IPC sockets and aborts if it is
// refused.
if (sock_passcred(s) && got > 0) {
struct { int pid; uint uid, gid; } cr;
uint64 off = clen ? CMSG_SPACE(clen - CMSG_LEN(0)) : 0;
if (sock_peercred(s, &cr.pid, &cr.uid, &cr.gid) == 0) {
if (m.msg_control && off + CMSG_LEN(sizeof(cr)) <= m.msg_controllen) {
struct cmsghdr c;
char tmp[CMSG_SPACE(sizeof(cr))];
memset(tmp, 0, sizeof(tmp));
c.cmsg_len = CMSG_LEN(sizeof(cr));
c.cmsg_level = SOL_SOCKET;
c.cmsg_type = 2; // SCM_CREDENTIALS
memmove(tmp, &c, sizeof(c));
memmove(CMSG_DATA((struct cmsghdr *)tmp), &cr, sizeof(cr));
uint64 n2 = CMSG_SPACE(sizeof(cr));
if (off + n2 > m.msg_controllen)
n2 = CMSG_LEN(sizeof(cr));
if (copy_to_user((addr_t)m.msg_control + off, tmp, n2) < 0)
return -EFAULT;
clen = off + n2;
} else {
mflags |= MSG_CTRUNC;
}
}
}
// The address, for the family that has one. Same rule as recvfrom: a Unix // The address, for the family that has one. Same rule as recvfrom: a Unix
// socket reports a length of zero rather than a sockaddr full of nothing. // socket reports a length of zero rather than a sockaddr full of nothing.
if (m.msg_name) { if (m.msg_name) {