Linux's own r8169 through LDLS, deep idle, and the GPU plan

The RTL8168 is now driven by Linux's r8169, unmodified, in a process.
`make ldls-r8169` stages it from the running kernel together with libphy,
mdio-bus, mdio_devres and the Realtek PHY driver, the kernel's BTF and
the RTL PHY firmware, and etc/schinit/ldls-r8169.job starts it at boot
with its log in /var/log/ldls-r8169.log. The native driver is out of the
registry; its code, now with Linux's 8168H setup, stays as the fallback.

LDLS grows what that needed:
- ldlsd loads a comma-separated module set in dependency order, each
  linking against the ones before it, with BTF read before any
  init_module runs;
- a driver core (bus match, driver probe), devres, kernel printf, real
  udelay/msleep/ktime and 1000 Hz jiffies, phylib's bitmaps and
  link_mode_params, firmware from /lib/firmware, and the symbols whose
  stub zero was a lie (list validity, try_module_get, dev_err_probe,
  eth_platform_get_mac_address);
- pdev->bus is a real pci_bus, and cmovcc from per-CPU memory is
  rewritten like the other %gs forms.
In QEMU all five modules load, link and initialise, and r8169 registers
and finds no device. Probe and traffic are untested until real hardware.

The kernel turns ASPM off on Realtek NIC links at boot, as r8169 does
through pci_disable_link_state, which a process cannot.

Idle CPUs enter ACPI _CST's C2 (CC6 on Zen) by port read or MWAIT when
the APIC timer keeps running, instead of only ever halting at C1.

docs/plan/gpu.md: amdgpu and nouveau through LDLS, with measurements.

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 14:26:16 +01:00
commit 4ad49ed5f2
18 changed files with 1945 additions and 81 deletions

View file

@ -41,6 +41,7 @@ static int ready;
static int s5_typ_a = -1, s5_typ_b = -1;
static uacpi_interrupt_ret power_button(uacpi_handle ctx);
static void cstate_find(void);
void
acpi_aml_init(void)
@ -98,6 +99,7 @@ acpi_aml_init(void)
}
ready = 1;
kinfo("acpi: namespace loaded\n");
cstate_find();
}
// --- power -------------------------------------------------------------------
@ -526,3 +528,111 @@ acpi_psu_get(int i, struct acpi_psu *b)
}
return 0;
}
// --- idle states --------------------------------------------------------------
//
// An idle CPU used to run hlt and nothing else. hlt is C1: the core stops
// executing and stays powered. The states below it -- CC6 on Zen, C3 and
// deeper on Intel -- switch the core off, and the firmware lists how to reach
// them in each processor's _CST. Without them a laptop sitting at a shell
// keeps every core warm and the fans going.
//
// _CST is a count followed by one package per state: a register (a Generic
// Register descriptor in a buffer), a type (1 = C1, 2 = C2, 3 = C3), the
// worst-case wake-up latency in microseconds, and the power. A register in
// system I/O space is entered by reading it; one in functional fixed hardware
// is MWAIT, with the hint in the address.
//
// Only types 1 and 2 are taken. C3 asks the OS to stop bus-master activity
// or flush caches first (ACPI 8.1.4), which this does not do.
struct acpi_idle acpi_idle;
static int cst_done;
static void
cst_parse(uacpi_namespace_node *node)
{
uacpi_object *o = 0;
uacpi_object_array p, e;
uacpi_data_view d;
if (uacpi_eval_package(node, "_CST", 0, &o) != UACPI_STATUS_OK)
return;
if (uacpi_object_get_package(o, &p) == UACPI_STATUS_OK) {
for (uacpi_size i = 1; i < p.count; i++) {
if (uacpi_object_get_package(p.objects[i], &e) != UACPI_STATUS_OK ||
e.count < 4)
continue;
uint32 type = pkg_int(&e, 1), lat = pkg_int(&e, 2);
if (type != 2 || lat > 1000)
continue;
if (uacpi_object_get_buffer(e.objects[0], &d) != UACPI_STATUS_OK ||
d.length < 15 || d.const_bytes[0] != 0x82)
continue;
uint8 space = d.const_bytes[3];
uint64 addr = 0;
for (int k = 0; k < 8; k++)
addr |= (uint64)d.const_bytes[7 + k] << (8 * k);
if (space == UACPI_ADDRESS_SPACE_SYSTEM_IO && addr && addr < 0x10000) {
acpi_idle.kind = ACPI_IDLE_IO;
} else if (space == UACPI_ADDRESS_SPACE_FFIXEDHW && (d.const_bytes[4] == 1)) {
// Intel's FFH encoding: vendor 1, class 2 (native C-state), and
// the MWAIT hint in the low byte of the address.
acpi_idle.kind = ACPI_IDLE_MWAIT;
} else {
continue;
}
acpi_idle.addr = addr;
acpi_idle.latency = lat;
break;
}
}
uacpi_object_unref(o);
}
static uacpi_iteration_decision
found_cpu(void *user, uacpi_namespace_node *node, uacpi_u32 depth)
{
(void)user; (void)depth;
if (cst_done)
return UACPI_ITERATION_DECISION_BREAK;
cst_parse(node);
cst_done = 1; // every processor's _CST is the same in practice
return UACPI_ITERATION_DECISION_BREAK;
}
static void
cstate_find(void)
{
uint32 a, b, c, dd;
// The local APIC timer is what wakes an idle CPU for the scheduler tick.
// Below C1 it may stop, unless the CPU says it keeps running (ARAT,
// CPUID 6 EAX bit 2); without that an idle CPU would sleep for good.
cpuid_leaf(6, &a, &b, &c, &dd);
if (!(a & 4)) {
kinfo("acpi: no always-running APIC timer; idle stays at C1\n");
return;
}
uacpi_namespace_for_each_child(uacpi_namespace_root(), found_cpu, 0,
UACPI_OBJECT_PROCESSOR_BIT,
UACPI_MAX_DEPTH_ANY, 0);
if (!cst_done)
uacpi_find_devices("ACPI0007", found_cpu, 0);
if (acpi_idle.kind == ACPI_IDLE_MWAIT) {
cpuid_leaf(1, &a, &b, &c, &dd);
if (!(c & (1u << 3))) // no MONITOR/MWAIT after all
acpi_idle.kind = ACPI_IDLE_HLT;
}
if (acpi_idle.kind == ACPI_IDLE_IO)
kinfo("acpi: idle CPUs enter C2 through port %x (wake-up %d us)\n",
(uint)acpi_idle.addr, acpi_idle.latency);
else if (acpi_idle.kind == ACPI_IDLE_MWAIT)
kinfo("acpi: idle CPUs enter C2 by MWAIT hint %x (wake-up %d us)\n",
(uint)acpi_idle.addr, acpi_idle.latency);
else
kinfo("acpi: no usable C2 in _CST; idle stays at C1\n");
}

View file

@ -678,6 +678,41 @@ pci_dump(struct sbuf *out, const char *arg)
}
}
// ASPM off on Realtek NICs, as Linux's r8169 does at probe through
// pci_disable_link_state(): "Disable ASPM L1 as that cause random device stop
// working problems as well as full system hangs". Here because the driver now
// runs in a process, and /dev/pci gives a process no config writes. Both ends
// of the link: the NIC's PCIe Link Control and the bridge above it, low two
// bits, L0s and L1.
static void
link_aspm_off(struct pci_dev *d)
{
uint8 cap = pci_find_cap(d, 0x10);
if (cap) {
uint32 v = pci_read32(d->bus, d->slot, d->func, cap + 0x10);
if (v & 3)
pci_write32(d->bus, d->slot, d->func, cap + 0x10, v & ~3u);
}
}
static void
realtek_aspm_off(void)
{
for (int i = 0; i < ndevs; i++) {
struct pci_dev *d = &devs[i];
if (d->vendor != 0x10EC || (d->device != 0x8168 && d->device != 0x8161 &&
d->device != 0x8125 && d->device != 0x8136))
continue;
link_aspm_off(d);
for (int j = 0; j < ndevs; j++)
if ((devs[j].header_type & 0x7F) == 1 &&
pci_read8(devs[j].bus, devs[j].slot, devs[j].func, PCI_SECONDARY_BUS) == d->bus)
link_aspm_off(&devs[j]);
kinfo("pci: %02x:%02x.%d Realtek NIC, ASPM off on its link\n",
d->bus, d->slot, d->func);
}
}
void
pci_init(void)
{
@ -693,6 +728,7 @@ pci_init(void)
scan_bus(0);
kinfo("pci: %d device%s\n", ndevs, ndevs == 1 ? "" : "s");
realtek_aspm_off();
// Before any driver binds: the line each device's config space names is
// the 8259's number, which is not the IOAPIC pin it is wired to. ACPI's

View file

@ -49,6 +49,14 @@
#define R_RCR 0x44 // Rx config
#define R_9346CR 0x50 // unlocks the config registers
#define R_CONFIG1 0x52
#define R_CONFIG2 0x53
#define R_CONFIG3 0x54
#define R_CONFIG5 0x56
#define R_EEE_LED 0x1B
#define R_EPHYAR 0x80 // the PCIe PHY's registers
#define R_DLLPR 0xD0
#define R_MISC1 0xF2
#define R_INTRMIT 0xE2 // interrupt mitigation
#define R_PHYAR 0x60 // the MII window
#define R_PHYSTATUS 0x6C
#define R_PMCH 0x6F // PLL power
@ -107,6 +115,11 @@
// MXDMA short. It would have worked and been slower, which is the worst kind
// of wrong to leave in a register constant.
#define RCR_UNLIMITED 0x0000E700
// What Linux's rtl_init_rxcfg() writes on the 8168G and later instead:
// 128-byte early interrupt, multiple descriptors per DMA, unlimited burst,
// early receive off.
#define RCR_GEN_G (0x8000 | 0x4000 | 0x0700 | 0x0800)
#define TCR_AUTO_FIFO 0x00000080 // 8168E-VL and later: the chip sizes its FIFOs
#define TCR_IFG_STD 0x03000000
#define TCR_MXDMA 0x00000700
@ -282,7 +295,21 @@ 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.
// The extended (ERI) registers. `mask` picks the bytes, bit 0 the lowest:
// several of these registers share a word with ones that must not change.
static void
eri_write_mask(struct r8169 *r, int addr, int mask, uint32 val)
{
wr32(r, R_ERIDR, val);
wr32(r, R_ERIAR, OCP_FLAG | ((uint32)mask << 12) | addr);
for (int i = 0; i < 100; i++) {
microdelay(10);
if (!(rd32(r, R_ERIAR) & OCP_FLAG))
return;
}
}
// All four bytes at once.
static uint32
eri_read(struct r8169 *r, int addr)
{
@ -315,7 +342,7 @@ 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:
case 0x540: case 0x541: case 0x6C0:
return 1;
}
return 0;
@ -347,6 +374,202 @@ r8169_takeover_g(struct r8169 *r)
wait_ll_ready(r);
}
// --- the 8168H as Linux sets it up ------------------------------------------
//
// From Linux 6.18 drivers/net/ethernet/realtek/r8169_main.c
// (rtl_hw_start_8168h_1, rtl_hw_aspm_clkreq_enable) and r8169_phy_config.c
// (rtl8168h_2_hw_phy_config), GPL-2.0, carried over register for register.
// Without them the chip runs on its power-on FIFO sizes and PHY tuning and
// with ASPM as the firmware left it, and moves frames, slowly and with drops.
// The one part left out is the PHY firmware patch (rtl_nic/rtl8168h-2.fw),
// which needs the root filesystem this runs before.
static void
ephy_write(struct r8169 *r, int reg, uint16 val)
{
wr32(r, R_EPHYAR, 0x80000000u | ((uint32)(reg & 0x1F) << 16) | val);
for (int i = 0; i < 100; i++) {
microdelay(10);
if (!(rd32(r, R_EPHYAR) & 0x80000000u))
break;
}
microdelay(10);
}
static uint16
ephy_read(struct r8169 *r, int reg)
{
wr32(r, R_EPHYAR, (uint32)(reg & 0x1F) << 16);
for (int i = 0; i < 100; i++) {
microdelay(10);
uint32 v = rd32(r, R_EPHYAR);
if (v & 0x80000000u)
return (uint16)v;
}
return 0xFFFF;
}
static void
mac_ocp_modify(struct r8169 *r, uint16 reg, uint16 clear, uint16 set)
{
mac_ocp_write(r, reg, (mac_ocp_read(r, reg) & ~clear) | set);
}
static int
phy_read_paged(struct r8169 *r, int page, int reg)
{
mii_write(r, 0x1F, (uint16)page);
int v = mii_read(r, reg);
mii_write(r, 0x1F, 0);
return v;
}
static void
phy_write_paged(struct r8169 *r, int page, int reg, uint16 val)
{
mii_write(r, 0x1F, (uint16)page);
mii_write(r, reg, val);
mii_write(r, 0x1F, 0);
}
static void
phy_modify_paged(struct r8169 *r, int page, int reg, uint16 clear, uint16 set)
{
mii_write(r, 0x1F, (uint16)page);
int v = mii_read(r, reg);
if (v >= 0)
mii_write(r, reg, (uint16)((v & ~clear) | set));
mii_write(r, 0x1F, 0);
}
static void
phy_param(struct r8169 *r, uint16 parm, uint16 clear, uint16 set)
{
mii_write(r, 0x1F, 0x0A43);
mii_write(r, 0x13, parm);
int v = mii_read(r, 0x14);
if (v >= 0)
mii_write(r, 0x14, (uint16)((v & ~clear) | set));
mii_write(r, 0x1F, 0);
}
// PCIe Link Control, low two bits: L0s and L1. Linux clears L1 on this chip
// and on the bridge above it unless the vendor has marked it safe, for "random
// device stop working problems as well as full system hangs". Firmware
// leaves it on. A link that drops into L1 between frames costs a wake-up per
// burst and on some boards stalls outright, which is a download that crawls.
static void
aspm_off(struct pci_dev *d)
{
uint8 cap = pci_find_cap(d, 0x10);
if (cap) {
uint32 v = pci_read32(d->bus, d->slot, d->func, cap + 0x10);
pci_write32(d->bus, d->slot, d->func, cap + 0x10, v & ~3u);
}
}
static void
r8169_aspm_off(struct r8169 *r)
{
struct pci_dev *d = r->pdev;
// The vendor's flag that ASPM is safe on this board (rtl_aspm_is_safe).
if (mac_ocp_read(r, 0xC0B2) & 0xF)
return;
aspm_off(d);
for (int i = 0; i < pci_count(); i++) {
struct pci_dev *b = pci_get(i);
if (b && (b->header_type & 0x7F) == 1 &&
pci_read8(b->bus, b->slot, b->func, 0x19) == d->bus)
aspm_off(b);
}
// And in the chip: no L1.2 trigger, no clock request, ASPM off.
mac_ocp_modify(r, 0xE092, 0x00FF, 0);
wr8(r, R_CONFIG2, rd8(r, R_CONFIG2) & ~0x80);
wr8(r, R_CONFIG5, rd8(r, R_CONFIG5) & ~0x01);
}
// rtl_hw_start_8168h_1. Config registers must be unlocked.
static void
r8169_start_8168h(struct r8169 *r)
{
static const struct { uint8 reg; uint16 clear, set; } ephy[] = {
{ 0x1E, 0x0800, 0x0001 },
{ 0x1D, 0x0000, 0x0800 },
{ 0x05, 0xFFFF, 0x2089 },
{ 0x06, 0xFFFF, 0x5881 },
{ 0x04, 0xFFFF, 0x854A },
{ 0x01, 0xFFFF, 0x068B },
};
for (unsigned i = 0; i < sizeof ephy / sizeof ephy[0]; i++)
ephy_write(r, ephy[i].reg,
(ephy_read(r, ephy[i].reg) & ~ephy[i].clear) | ephy[i].set);
eri_write(r, 0xC8, (0x08u << 16) | 0x02); // FIFO sizes
eri_write(r, 0xE8, (0x10u << 16) | 0x06);
eri_write_mask(r, 0xCC, 0x1, 0x38); // pause thresholds
eri_write_mask(r, 0xD0, 0x1, 0x48);
eri_write(r, 0xDC, eri_read(r, 0xDC) & ~1u); // reset the packet filter
eri_write(r, 0xDC, eri_read(r, 0xDC) | 1u);
eri_write(r, 0xDC, eri_read(r, 0xDC) | 0x1C);
eri_write_mask(r, 0x5F0, 0x3, 0x4F87);
wr32(r, R_MISC, rd32(r, R_MISC) & ~MISC_RXDV_GATE);
eri_write_mask(r, 0xC0, 0x3, 0);
eri_write_mask(r, 0xB8, 0x3, 0);
wr8(r, R_EEE_LED, rd8(r, R_EEE_LED) & ~0x07); // rtl8168_config_eee_mac
eri_write(r, 0x1B0, eri_read(r, 0x1B0) | 0x3);
wr8(r, R_DLLPR, rd8(r, R_DLLPR) & ~0x40); // PFM off
wr8(r, R_MISC1, rd8(r, R_MISC1) & ~0x40); // PFM in D3cold off
wr8(r, R_DLLPR, rd8(r, R_DLLPR) & ~0x80); // 10M power saving off
eri_write(r, 0x1B0, eri_read(r, 0x1B0) & ~(1u << 12));
wr8(r, R_CONFIG3, rd8(r, R_CONFIG3) & ~0x02); // Rdy_to_L23 off
int saw = phy_read_paged(r, 0x0C42, 0x13);
if (saw > 0 && (saw & 0x3FFF) > 0)
mac_ocp_modify(r, 0xD412, 0x0FFF, (16000000 / (saw & 0x3FFF)) & 0x0FFF);
mac_ocp_modify(r, 0xE056, 0x00F0, 0x0000);
mac_ocp_modify(r, 0xE052, 0x6000, 0x8008);
mac_ocp_modify(r, 0xE0D6, 0x01FF, 0x017F);
mac_ocp_modify(r, 0xD420, 0x0FFF, 0x047F);
mac_ocp_write(r, 0xE63E, 0x0001);
mac_ocp_write(r, 0xE63E, 0x0000);
mac_ocp_write(r, 0xC094, 0x0000);
mac_ocp_write(r, 0xC09E, 0x0000);
}
// rtl8168h_2_hw_phy_config, less the firmware patch.
static void
r8169_phy_8168h(struct r8169 *r)
{
phy_param(r, 0x808A, 0x003F, 0x000A); // CHIN EST parameter
phy_param(r, 0x0811, 0x0000, 0x0800); // R-tune and PGA-retune on
phy_modify_paged(r, 0x0A42, 0x16, 0x0000, 0x0002);
phy_modify_paged(r, 0x0A44, 0x11, 0, 1u << 11); // gphy 10M
// ADC bias offset, from the MAC's OCP space.
mac_ocp_write(r, 0xDD02, 0x807D);
uint16 d1 = mac_ocp_read(r, 0xDD02), d2 = mac_ocp_read(r, 0xDD00);
uint16 ioffset = ((d2 >> 1) & 0x7FF8) | (d2 & 0x0007) | ((d1 & 0x80) ? 0x8000 : 0);
if (ioffset != 0xFFFF)
phy_write_paged(r, 0x0BCF, 0x16, ioffset);
// TX low-pass corner.
int data = phy_read_paged(r, 0x0BCD, 0x16);
if (data >= 0) {
uint16 rlen = (data & 0xF) > 3 ? (data & 0xF) - 3 : 0;
phy_write_paged(r, 0x0BCD, 0x17, rlen | rlen << 4 | rlen << 8 | rlen << 12);
}
phy_modify_paged(r, 0x0A44, 0x11, 1u << 7, 0); // PHY PFM off
phy_modify_paged(r, 0x0A43, 0x10, 1u << 0, 0); // 10M PLL off: off
phy_modify_paged(r, 0x0A43, 0x10, 1u << 2, 0); // ALDPS off
phy_modify_paged(r, 0x0A43, 0x11, 0, 1u << 4); // EEE
}
// 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.
@ -620,6 +843,10 @@ r8169_probe(struct pci_dev *d)
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);
if (xid == 0x541 || xid == 0x6C0) {
r8169_aspm_off(r);
r8169_start_8168h(r);
}
}
// CPCR is deliberately left alone.
@ -635,13 +862,16 @@ r8169_probe(struct pci_dev *d)
// unlike the 8139 it has no other programming model to be in.
wr16(r, R_RMS, BUFSZ);
wr8(r, R_MTPS, 0x3B);
// EarlySize on the 8168E-VL and later, TxPacketMax before, as in Linux.
wr8(r, R_MTPS, r->gen_g ? 0x27 : 0x3B);
wr16(r, R_INTRMIT, 0); // no interrupt coalescing
wr64(r, R_RDSAR, r->rx_phys);
wr64(r, R_TNPDS, r->tx_phys);
wr32(r, R_TCR, TCR_IFG_STD | TCR_MXDMA);
wr32(r, R_RCR, RCR_APM | RCR_AM | RCR_AB | RCR_UNLIMITED);
wr32(r, R_TCR, TCR_IFG_STD | TCR_MXDMA | (r->gen_g ? TCR_AUTO_FIFO : 0));
wr32(r, R_RCR, RCR_APM | RCR_AM | RCR_AB |
(r->gen_g ? RCR_GEN_G : RCR_UNLIMITED));
// Multicast: everything. The stack filters, and a hash filter that is wrong
// drops IPv6 neighbour discovery in a way that looks like a routing problem.
@ -651,6 +881,11 @@ r8169_probe(struct pci_dev *d)
wr8(r, R_CR, CR_RE | CR_TE);
wr8(r, R_9346CR, C9346_LOCK);
{
uint32 xid = (rd32(r, R_TCR) >> 20) & 0xFCF;
if (xid == 0x541 || xid == 0x6C0)
r8169_phy_8168h(r);
}
r8169_phy_up(r);
wr16(r, R_ISR, 0xFFFF); // clear anything left over

View file

@ -33,7 +33,6 @@ extern const struct pci_driver xhci_driver;
extern const struct pci_driver virtio_gpu_driver;
extern const struct pci_driver e1000_driver;
extern const struct pci_driver ac97_driver;
extern const struct pci_driver r8169_driver;
extern const struct pci_driver rtl8139_driver;
extern const struct pci_driver ahci_driver;
extern const struct pci_driver nvme_driver;
@ -47,7 +46,9 @@ static const struct pci_driver *drivers[] = {
&virtio_gpu_driver,
&vga_pci_driver,
&e1000_driver,
&r8169_driver,
// &r8169_driver -- the RTL8168 is driven by Linux's own r8169 through
// LDLS (etc/schinit/ldls-r8169.job), which a kernel driver here would
// claim first. The native driver stays in the tree as the fallback.
&rtl8139_driver,
&xhci_driver,
&ahci_driver,

View file

@ -16,6 +16,7 @@
#include "uapi/errno.h"
#include "fs/blkdev.h"
#include "core/uaccess.h"
#include "dev/acpi.h"
// Verify myproc()->fs_base offset matches the hardcoded 0x688 in trapasm.S.
_Static_assert(__builtin_offsetof(struct proc, fs_base) == 0x18,
@ -1983,6 +1984,33 @@ sched_slice_expired(void)
return (p->slice_used + ns) >= p->slice_ns;
}
// Wait for an interrupt as deeply as the firmware allows. See acpi_idle in
// kernel/acpi/aml.c. Interrupts are off on the way down so that one arriving
// between the decision and the sleep is not lost: a pending interrupt still
// ends the sleep (MWAIT's ECX bit 0 says so explicitly; an I/O-entered C2
// wakes on it by design), and is taken at the sti after.
static void
cpu_idle(void)
{
static volatile int monitor_line[16];
switch (acpi_idle.kind) {
case ACPI_IDLE_IO:
cli();
(void)inb((ushort)acpi_idle.addr);
sti();
break;
case ACPI_IDLE_MWAIT:
cli();
asm volatile("monitor" : : "a"(monitor_line), "c"(0), "d"(0));
asm volatile("mwait" : : "a"((uint32)acpi_idle.addr), "c"(1));
sti();
break;
default:
hlt();
}
}
void
scheduler(void)
{
@ -1999,7 +2027,7 @@ scheduler(void)
// hlt() would stop every other CPU from scheduling until an interrupt
// happened to arrive here.
release(&ptable.lock);
hlt();
cpu_idle();
continue;
}