From 04540602cd98514938c1fa42705bf5ad95be330d Mon Sep 17 00:00:00 2001
From: Sascha Wildner <saw@online.de>
Date: Thu, 2 Oct 2025 22:04:45 +0200
Subject: [PATCH 08/11] kernel/pci: D3hot/cold.

---
 share/man/man9/pci.9           | 10 ++++--
 sys/bus/pci/pci.c              |  8 +++--
 sys/bus/pci/pcivar.h           | 27 +++++++++-------
 sys/dev/acpica/Makefile        |  2 +-
 sys/dev/acpica/acpi.c          |  5 +--
 sys/dev/acpica/acpi_pci.c      |  8 ++---
 sys/dev/acpica/acpi_powerres.c | 58 +++++++++++++++++++++++++---------
 sys/dev/acpica/acpivar.h       | 10 ++++++
 8 files changed, 88 insertions(+), 40 deletions(-)

diff --git a/share/man/man9/pci.9 b/share/man/man9/pci.9
index 0caf95e7d5..55cd3b4b7a 100644
--- a/share/man/man9/pci.9
+++ b/share/man/man9/pci.9
@@ -25,7 +25,7 @@
 .\"
 .\" $FreeBSD: src/share/man/man9/pci.9,v 1.11 2008/02/15 23:56:33 danger Exp $
 .\"
-.Dd November 9, 2014
+.Dd October 2, 2025
 .Dt PCI 9
 .Os
 .Sh NAME
@@ -168,10 +168,14 @@ Busses in this state can cause devices to lose some context.
 Devices
 .Em must
 be prepared for the bus to be in this state or higher.
-.It Dv PCI_POWERSTATE_D3
+.It Dv PCI_POWERSTATE_D3_HOT
 State in which the device is off and not running.
 Device context is lost, and power from the device can
-be removed.
+be (but is not necessarily) removed.
+.It Dv PCI_POWERSTATE_D3_COLD
+Same as
+.Dv PCI_POWERSTATE_D3_HOT ,
+except power has been removed from the device.
 .It Dv PCI_POWERSTATE_UNKNOWN
 State of the device is unknown.
 .El
diff --git a/sys/bus/pci/pci.c b/sys/bus/pci/pci.c
index 860ee6ff5f..da28c616e1 100644
--- a/sys/bus/pci/pci.c
+++ b/sys/bus/pci/pci.c
@@ -63,6 +63,9 @@
 #include <bus/u4b/controller/ohcireg.h>
 #include <bus/u4b/controller/uhcireg.h>
 
+#include <contrib/dev/acpica/source/include/acpi.h>
+#include <dev/acpica/acpivar.h>
+
 #include "pcib_if.h"
 #include "pci_if.h"
 
@@ -2234,9 +2237,10 @@ pci_set_powerstate_method(device_t dev, device_t child, int state)
 
 	if (bootverbose)
 		kprintf(
-		    "pci%d:%d:%d:%d: Transition from D%d to D%d\n",
+		    "pci%d:%d:%d:%d: Transition from %s to %s\n",
 		    dinfo->cfg.domain, dinfo->cfg.bus, dinfo->cfg.slot,
-		    dinfo->cfg.func, oldstate, state);
+		    dinfo->cfg.func, acpi_d_state_to_str(oldstate),
+		    acpi_d_state_to_str(state));
 
 	PCI_WRITE_CONFIG(dev, child, cfg->pp.pp_status, status, 2);
 	if (delay)
diff --git a/sys/bus/pci/pcivar.h b/sys/bus/pci/pcivar.h
index 9dff3a358d..f87b91f5b6 100644
--- a/sys/bus/pci/pcivar.h
+++ b/sys/bus/pci/pcivar.h
@@ -401,22 +401,25 @@ uint16_t pcie_get_max_readrq(device_t);
 /*
  * PCI power states are as defined by ACPI:
  *
- * D0	State in which device is on and running.  It is receiving full
- *	power from the system and delivering full functionality to the user.
- * D1	Class-specific low-power state in which device context may or may not
- *	be lost.  Buses in D1 cannot do anything to the bus that would force
- *	devices on that bus to lose context.
- * D2	Class-specific low-power state in which device context may or may
- *	not be lost.  Attains greater power savings than D1.  Buses in D2
- *	can cause devices on that bus to lose some context.  Devices in D2
- *	must be prepared for the bus to be in D2 or higher.
- * D3	State in which the device is off and not running.  Device context is
- *	lost.  Power can be removed from the device.
+ * D0	  State in which device is on and running.  It is receiving full
+ *	  power from the system and delivering full functionality to the user.
+ * D1	  Class-specific low-power state in which device context may or may not
+ *	  be lost.  Buses in D1 cannot do anything to the bus that would force
+ *	  devices on that bus to lose context.
+ * D2	  Class-specific low-power state in which device context may or may
+ *	  not be lost.  Attains greater power savings than D1.  Buses in D2
+ *	  can cause devices on that bus to lose some context.  Devices in D2
+ *	  must be prepared for the bus to be in D2 or higher.
+ * D3hot  State in which the device is off and not running.  Device context is
+ *	  lost.  Power can be removed from the device.
+ * D3cold Same as D3hot, but power has been removed from the device.
  */
 #define	PCI_POWERSTATE_D0	0
 #define	PCI_POWERSTATE_D1	1
 #define	PCI_POWERSTATE_D2	2
-#define	PCI_POWERSTATE_D3	3
+#define	PCI_POWERSTATE_D3_HOT	3
+#define	PCI_POWERSTATE_D3_COLD	4
+#define	PCI_POWERSTATE_D3	PCI_POWERSTATE_D3_COLD
 #define	PCI_POWERSTATE_UNKNOWN	-1
 
 static __inline int
diff --git a/sys/dev/acpica/Makefile b/sys/dev/acpica/Makefile
index a37f66ce2f..fa462852df 100644
--- a/sys/dev/acpica/Makefile
+++ b/sys/dev/acpica/Makefile
@@ -75,7 +75,7 @@ SRCS+=  OsdCache.c OsdDebug.c
 SRCS+=  OsdHardware.c OsdInterrupt.c OsdMemory.c OsdSchedule.c
 SRCS+=  OsdStream.c OsdSynch.c OsdTable.c OsdEnvironment.c
 SRCS+=  opt_acpi.h opt_ddb.h opt_ktr.h
-SRCS+=  device_if.h bus_if.h pci_if.h pcib_if.h isa_if.h acpi_if.h
+SRCS+=  device_if.h bus_if.h pcib_if.h isa_if.h acpi_if.h
 SRCS+=  acpi_quirks.h acpi_wmi_if.h cpu_if.h
 
 .if !defined(KERNBUILDDIR)
diff --git a/sys/dev/acpica/acpi.c b/sys/dev/acpica/acpi.c
index 68c31c0cee..62f135b58b 100644
--- a/sys/dev/acpica/acpi.c
+++ b/sys/dev/acpica/acpi.c
@@ -1589,8 +1589,9 @@ acpi_set_powerstate_method(device_t bus, device_t child, int state)
     status = acpi_pwr_switch_consumer(h, state);
     if (ACPI_FAILURE(status) && status != AE_NOT_FOUND
 	&& status != AE_BAD_PARAMETER)
-	device_printf(bus, "failed to set ACPI power state D%d on %s: %s\n",
-	    state, acpi_name(h), AcpiFormatException(status));
+	device_printf(bus, "failed to set ACPI power state %s on %s: %s\n",
+	    acpi_d_state_to_str(state), acpi_name(h),
+	    AcpiFormatException(status));
 
     return (error);
 }
diff --git a/sys/dev/acpica/acpi_pci.c b/sys/dev/acpica/acpi_pci.c
index f5019cacb9..21eed0e13b 100644
--- a/sys/dev/acpica/acpi_pci.c
+++ b/sys/dev/acpica/acpi_pci.c
@@ -43,9 +43,6 @@
 #include <bus/pci/pcivar.h>
 #include <bus/pci/pci_private.h>
 
-#include "pcib_if.h"
-#include "pci_if.h"
-
 /* Hooks for the ACPICA debugging infrastructure. */
 #define _COMPONENT	ACPI_BUS
 ACPI_MODULE_NAME("PCI")
@@ -189,8 +186,9 @@ acpi_pci_set_powerstate_method(device_t dev, device_t child, int state)
 	status = acpi_pwr_switch_consumer(h, state);
 	if (ACPI_FAILURE(status) && status != AE_NOT_FOUND)
 		device_printf(dev,
-		    "Failed to set ACPI power state D%d on %s: %s\n",
-		    state, acpi_name(h), AcpiFormatException(status));
+		    "Failed to set ACPI power state %s on %s: %s\n",
+		    acpi_d_state_to_str(state), acpi_name(h),
+		    AcpiFormatException(status));
 	if (old_state > state)
 		error = pci_set_powerstate_method(dev, child, state);
 
diff --git a/sys/dev/acpica/acpi_powerres.c b/sys/dev/acpica/acpi_powerres.c
index d00f5bb495..56342cfe19 100644
--- a/sys/dev/acpica/acpi_powerres.c
+++ b/sys/dev/acpica/acpi_powerres.c
@@ -311,7 +311,7 @@ acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state)
     ACPI_BUFFER			reslist_buffer;
     ACPI_OBJECT			*reslist_object;
     ACPI_STATUS			status;
-    char			*method_name, *reslist_name;
+    char			*method_name, *reslist_name = NULL;
 
     ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
 
@@ -330,9 +330,26 @@ acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state)
 	    panic("acpi added power consumer but can't find it");
     }
 
-    /* Check for valid transitions.  We can only go to D0 from D3. */
+    /* Stop here if we're already at the target D-state. */
+    if (pc->ac_state == state) {
+	status = AE_OK;
+	goto out;
+    }
+
+    /*
+     * Check for valid transitions.  From D3hot or D3cold, we can only go to D0.
+     * The exception to this is going from D3hot to D3cold or the other way
+     * around.  This is because they both use _PS3, so the only difference when
+     * doing these transitions is whether or not the power resources for _PR3
+     * are on for devices which support D3cold, and turning these power
+     * resources on/off is always perfectly fine (ACPI 7.3.11).
+     */
     status = AE_BAD_PARAMETER;
-    if (pc->ac_state == ACPI_STATE_D3 && state != ACPI_STATE_D0)
+    if (pc->ac_state == ACPI_STATE_D3_HOT && state != ACPI_STATE_D0 &&
+	state != ACPI_STATE_D3_COLD)
+	goto out;
+    if (pc->ac_state == ACPI_STATE_D3_COLD && state != ACPI_STATE_D0 &&
+	state != ACPI_STATE_D3_HOT)
 	goto out;
 
     /* Find transition mechanism(s) */
@@ -349,15 +366,20 @@ acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state)
 	method_name = "_PS2";
 	reslist_name = "_PR2";
 	break;
-    case ACPI_STATE_D3:
+    case ACPI_STATE_D3_HOT:
 	method_name = "_PS3";
 	reslist_name = "_PR3";
 	break;
+    case ACPI_STATE_D3_COLD:
+	method_name = "_PS3";
+	reslist_name = NULL;
+	break;
     default:
 	goto out;
     }
-    ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "setup to switch %s D%d -> D%d\n",
-		     acpi_name(consumer), pc->ac_state, state));
+    ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "setup to switch %s %s -> %s\n",
+		     acpi_name(consumer), acpi_d_state_to_str(pc->ac_state),
+		     acpi_d_state_to_str(state)));
 
     /*
      * Verify that this state is supported, ie. one of method or
@@ -371,7 +393,8 @@ acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state)
      */
     if (ACPI_FAILURE(AcpiGetHandle(consumer, method_name, &method_handle)))
 	method_handle = NULL;
-    if (ACPI_FAILURE(AcpiGetHandle(consumer, reslist_name, &reslist_handle)))
+    if (reslist_name == NULL ||
+	ACPI_FAILURE(AcpiGetHandle(consumer, reslist_name, &reslist_handle)))
 	reslist_handle = NULL;
     if (reslist_handle == NULL && method_handle == NULL) {
 	if (state == ACPI_STATE_D0) {
@@ -379,9 +402,12 @@ acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state)
 	    status = AE_OK;
 	    goto out;
 	}
-	if (state != ACPI_STATE_D3) {
+	if (state == ACPI_STATE_D3_COLD)
+	    state = ACPI_STATE_D3_HOT;
+	if (state != ACPI_STATE_D3_HOT) {
 	    ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS,
-		"attempt to set unsupported state D%d\n", state));
+		"attempt to set unsupported state %s\n",
+		acpi_d_state_to_str(state)));
 	    goto out;
 	}
 
@@ -392,21 +418,23 @@ acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state)
 	if (ACPI_FAILURE(AcpiGetHandle(consumer, "_PR0", &pr0_handle))) {
 	    status = AE_NOT_FOUND;
 	    ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS,
-		"device missing _PR0 (desired state was D%d)\n", state));
+		"device missing _PR0 (desired state was %s)\n",
+		acpi_d_state_to_str(state)));
 	    goto out;
 	}
 	reslist_buffer.Length = ACPI_ALLOCATE_BUFFER;
 	status = AcpiEvaluateObject(pr0_handle, NULL, NULL, &reslist_buffer);
 	if (ACPI_FAILURE(status)) {
 	    ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS,
-		"can't evaluate _PR0 for device %s, state D%d\n",
-		acpi_name(consumer), state));
+		"can't evaluate _PR0 for device %s, state %s\n",
+		acpi_name(consumer), acpi_d_state_to_str(state)));
 	    goto out;
 	}
 	reslist_object = (ACPI_OBJECT *)reslist_buffer.Pointer;
 	if (!ACPI_PKG_VALID(reslist_object, 1)) {
 	    ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS,
-		"invalid package object for state D%d\n", state));
+		"invalid package object for state %s\n",
+		acpi_d_state_to_str(state)));
 	    status = AE_TYPE;
 	    goto out;
 	}
@@ -462,8 +490,8 @@ acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state)
      */
     if (ACPI_FAILURE(status = acpi_pwr_switch_power())) {
 	ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS,
-			 "failed to switch resources from %s to D%d\n",
-			  acpi_name(consumer), state));
+			 "failed to switch resources from %s to %s\n",
+			  acpi_name(consumer), acpi_d_state_to_str(state)));
 
 	/* XXX is this appropriate?  Should we return to previous state? */
 	goto out;
diff --git a/sys/dev/acpica/acpivar.h b/sys/dev/acpica/acpivar.h
index cbe948579f..0e03b1d55b 100644
--- a/sys/dev/acpica/acpivar.h
+++ b/sys/dev/acpica/acpivar.h
@@ -450,6 +450,16 @@ acpi_get_verbose(struct acpi_softc *sc)
     return (0);
 }
 
+static __inline const char *
+acpi_d_state_to_str(int state)
+{
+    const char *strs[ACPI_D_STATE_COUNT] = {"D0", "D1", "D2", "D3", "D3cold"};
+
+    KASSERT(state >= ACPI_STATE_D0 && state <= ACPI_D_STATES_MAX,
+	("state out of bounds"));
+    return (strs[state]);
+}
+
 char		*acpi_name(ACPI_HANDLE handle);
 int		acpi_avoid(ACPI_HANDLE handle);
 int		acpi_disabled(char *subsys);
-- 
2.45.2

