Merge pull request #836 from kwilczynski/feature/update-kernel-releases

Update Linux kernel release to v7.2.8-5 for base and BORE kernels
This commit is contained in:
Krzysztof Wilczyński authored and GitHub committed 2026-10-07 12:44:06 +02:00
commit 6371bfb24a
325 files changed
+4747 -1308

No files matched your search

@@ -0,0 +1,413 @@
diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h
--- a/drivers/iommu/amd/amd_iommu.h
+++ b/drivers/iommu/amd/amd_iommu.h
@@ -48,6 +48,9 @@ extern u8 amd_iommu_hpt_vasize;
extern unsigned long amd_iommu_pgsize_bitmap;
extern bool amd_iommu_hatdis;
+int amd_iommu_perfopt_clear(struct amd_iommu *iommu);
+int amd_iommu_perfopt_restore(struct amd_iommu *iommu);
+
/* Protection domain ops */
void amd_iommu_init_identity_domain(void);
struct protection_domain *protection_domain_alloc(void);
diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h
--- a/drivers/iommu/amd/amd_iommu_types.h
+++ b/drivers/iommu/amd/amd_iommu_types.h
@@ -65,6 +65,7 @@
#define MMIO_MSI_ADDR_LO_OFFSET 0x015C
#define MMIO_MSI_ADDR_HI_OFFSET 0x0160
#define MMIO_MSI_DATA_OFFSET 0x0164
+#define MMIO_PERF_OPT_OFFSET 0x016C
#define MMIO_INTCAPXT_EVT_OFFSET 0x0170
#define MMIO_INTCAPXT_PPR_OFFSET 0x0178
#define MMIO_INTCAPXT_GALOG_OFFSET 0x0180
@@ -99,6 +100,8 @@
#define FEATURE_GLX GENMASK_ULL(15, 14)
#define FEATURE_GAM_VAPIC BIT_ULL(21)
#define FEATURE_PASMAX GENMASK_ULL(36, 32)
+#define FEATURE_PERF_OPT BIT_ULL(45)
+#define PERF_OPT_EN BIT(13)
#define FEATURE_GIOSUP BIT_ULL(48)
#define FEATURE_HASUP BIT_ULL(49)
#define FEATURE_EPHSUP BIT_ULL(50)
@@ -663,6 +666,9 @@ struct amd_iommu {
/* Extended features 2 */
u64 features2;
+ /* Devices requesting PerfOpt; the shared PERF_OPT_EN bit is on while >0. Protected by @lock. */
+ int perfopt_refcount;
+
/* PCI device id of the IOMMU device */
u16 devid;
@@ -824,6 +830,7 @@ struct iommu_dev_data {
u8 ppr :1; /* Enable device PPR support */
bool use_vapic; /* Enable device to use vapic mode */
bool defer_attach;
+ bool perfopt;
struct ratelimit_state rs; /* Ratelimit IOPF messages */
};
diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c
--- a/drivers/iommu/amd/init.c
+++ b/drivers/iommu/amd/init.c
@@ -1945,6 +1945,9 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h,
if (!iommu->mmio_base)
return -ENOMEM;
+ if (amd_iommu_perfopt_clear(iommu))
+ pr_err("IOMMU%d: failed to clear PerfOpt\n", iommu->index);
+
return init_iommu_from_acpi(iommu, h);
}
@@ -3035,10 +3038,46 @@ static void enable_iommus_vapic(void)
#endif
}
+static int clear_perfopt_all(void)
+{
+ struct amd_iommu *iommu;
+ int err, ret = 0;
+
+ for_each_iommu(iommu) {
+ err = amd_iommu_perfopt_clear(iommu);
+ if (err)
+ ret = err;
+ }
+
+ return ret;
+}
+
+static int restore_perfopt_all(void)
+{
+ struct amd_iommu *iommu;
+ int err, ret = 0;
+
+ for_each_iommu(iommu) {
+ err = amd_iommu_perfopt_restore(iommu);
+ if (err)
+ ret = err;
+ }
+
+ return ret;
+}
+
static void disable_iommus(void)
{
struct amd_iommu *iommu;
+ /*
+ * PerfOpt is an optional performance bit, so a failure to clear it must
+ * not skip the mandatory disable below. This also runs from the void
+ * amd_iommu_disable() shutdown/kexec path, which cannot report an error.
+ */
+ if (clear_perfopt_all())
+ pr_err("Failed to clear PerfOpt while disabling IOMMUs\n");
+
for_each_iommu(iommu)
iommu_disable(iommu);
@@ -3064,6 +3103,10 @@ static void amd_iommu_resume(void *data)
for_each_iommu(iommu)
early_enable_iommu(iommu);
+ /* early_enable_iommu() cleared PERF_OPT_EN; re-assert it from the refcount. */
+ if (restore_perfopt_all())
+ pr_err("Failed to restore PerfOpt after IOMMU resume\n");
+
iommu_enable_event_buffer();
amd_iommu_enable_interrupts();
}
diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -2352,6 +2352,9 @@ static int attach_device(struct device *dev,
if (ret)
goto out;
+ if (dev_data->perfopt)
+ goto skip_caps;
+
/* Setup GCR3 table */
if (pdom_is_sva_capable(domain)) {
ret = init_gcr3_table(dev_data, domain);
@@ -2376,6 +2379,7 @@ static int attach_device(struct device *dev,
pdev_enable_cap_ats(pdev);
}
+skip_caps:
/* Update data structures */
dev_data->domain = domain;
spin_lock_irqsave(&domain->lock, flags);
@@ -2444,6 +2448,192 @@ static void detach_device(struct device *dev)
mutex_unlock(&dev_data->mutex);
}
+/* Program the per-IOMMU PerfOpt enable bit. Caller must hold iommu->lock. */
+static int __perfopt_write(struct amd_iommu *iommu, bool enable)
+{
+ u32 old, val, readback;
+
+ if (!(readq(iommu->mmio_base + MMIO_EXT_FEATURES) & FEATURE_PERF_OPT))
+ return enable ? -ENODEV : 0;
+
+ old = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET);
+ if (old == U32_MAX)
+ return -EIO;
+
+ val = enable ? old | PERF_OPT_EN : old & ~PERF_OPT_EN;
+ if (val != old)
+ writel(val, iommu->mmio_base + MMIO_PERF_OPT_OFFSET);
+ readback = readl(iommu->mmio_base + MMIO_PERF_OPT_OFFSET);
+ if (readback == U32_MAX ||
+ (readback & PERF_OPT_EN) != (val & PERF_OPT_EN))
+ return -EIO;
+ return 0;
+}
+
+/*
+ * PERF_OPT_EN is a single bit shared by every device behind @iommu, so it is
+ * reference counted: armed on the first requesting device, cleared on the last.
+ */
+static int perfopt_get(struct amd_iommu *iommu)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ if (!iommu->mmio_base)
+ return 0;
+
+ raw_spin_lock_irqsave(&iommu->lock, flags);
+ if (iommu->perfopt_refcount == 0) {
+ ret = __perfopt_write(iommu, true);
+ if (ret)
+ goto out;
+ }
+ iommu->perfopt_refcount++;
+out:
+ raw_spin_unlock_irqrestore(&iommu->lock, flags);
+ return ret;
+}
+
+static int perfopt_put(struct amd_iommu *iommu)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ if (!iommu->mmio_base)
+ return 0;
+
+ raw_spin_lock_irqsave(&iommu->lock, flags);
+ if (iommu->perfopt_refcount > 0 && --iommu->perfopt_refcount == 0)
+ ret = __perfopt_write(iommu, false);
+ raw_spin_unlock_irqrestore(&iommu->lock, flags);
+ return ret;
+}
+
+/*
+ * Force PERF_OPT_EN off without touching the refcount (used on init, shutdown,
+ * and suspend). The count is preserved so amd_iommu_perfopt_restore() can
+ * re-arm on resume.
+ */
+int amd_iommu_perfopt_clear(struct amd_iommu *iommu)
+{
+ unsigned long flags;
+ int ret;
+
+ if (!iommu->mmio_base)
+ return 0;
+
+ raw_spin_lock_irqsave(&iommu->lock, flags);
+ ret = __perfopt_write(iommu, false);
+ raw_spin_unlock_irqrestore(&iommu->lock, flags);
+ return ret;
+}
+
+/*
+ * Re-assert PERF_OPT_EN from the refcount after the hardware was reprogrammed on
+ * resume, so devices armed before suspend keep the optimization without each
+ * consumer driver re-arming.
+ */
+int amd_iommu_perfopt_restore(struct amd_iommu *iommu)
+{
+ unsigned long flags;
+ int ret;
+
+ if (!iommu->mmio_base)
+ return 0;
+
+ raw_spin_lock_irqsave(&iommu->lock, flags);
+ ret = __perfopt_write(iommu, iommu->perfopt_refcount > 0);
+ raw_spin_unlock_irqrestore(&iommu->lock, flags);
+ return ret;
+}
+
+int amd_iommu_enable_perfopt(struct pci_dev *pdev)
+{
+ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev);
+ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev);
+ struct protection_domain *domain;
+ int ret;
+
+ if (!iommu || !dev_data)
+ return -ENODEV;
+
+ if (!(iommu->features & FEATURE_PERF_OPT))
+ return -ENODEV;
+
+ domain = dev_data->domain;
+ if (!domain)
+ return -ENODEV;
+
+ /* Already armed for this device (e.g. re-entry on resume). */
+ if (dev_data->perfopt)
+ return 0;
+
+ /*
+ * The bit is only architecturally valid while the device is untranslated:
+ * identity domain with ATS/PRI/PASID off. The identity domain is
+ * SVA-capable so attach_device() enabled ATS/PRI/PASID and built a GCR3
+ * table. Re-home the device onto the same identity domain with
+ * perfopt set, so the attach_device() skip_caps path leaves
+ * ATS/PRI/PASID off and no GCR3 table. This follows the detach/attach
+ * pattern used by amd_iommu_attach_device().
+ *
+ * Locking: this and amd_iommu_disable_perfopt() run only from the
+ * consumer driver's bind/unbind path. group->mutex is not exposed to
+ * drivers, but a device bound to its native driver cannot have its domain
+ * changed concurrently by the core (VFIO ownership is mutually exclusive;
+ * sysfs domain changes require an unused group), so the detach/attach pair
+ * is serialized without it.
+ */
+ dev_data->perfopt = true;
+ detach_device(&pdev->dev);
+ ret = attach_device(&pdev->dev, domain);
+ if (ret)
+ goto err_restore;
+
+ ret = perfopt_get(iommu);
+ if (ret)
+ goto err_rearm;
+
+ dev_info_once(&pdev->dev, "PerfOpt armed on IOMMU%d\n", iommu->index);
+ return 0;
+
+err_rearm:
+ detach_device(&pdev->dev);
+err_restore:
+ dev_data->perfopt = false;
+ if (attach_device(&pdev->dev, domain))
+ pci_err(pdev, "failed to restore state after PerfOpt setup; device left detached\n");
+ dev_err_once(&pdev->dev, "PerfOpt failed to arm on IOMMU%d (%d)\n",
+ iommu->index, ret);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(amd_iommu_enable_perfopt);
+
+void amd_iommu_disable_perfopt(struct pci_dev *pdev)
+{
+ struct iommu_dev_data *dev_data = dev_iommu_priv_get(&pdev->dev);
+ struct amd_iommu *iommu = rlookup_amd_iommu(&pdev->dev);
+ struct protection_domain *domain;
+
+ if (!iommu || !dev_data || !dev_data->perfopt || !dev_data->domain)
+ return;
+
+ if (WARN_ON(perfopt_put(iommu)))
+ pci_err(pdev, "failed to clear PerfOpt\n");
+
+ /*
+ * Restore ATS/PRI/PASID (and thus SVA) by re-homing the device onto its
+ * identity domain with the flag cleared, so a later bind without PerfOpt
+ * sees a normally-capable device. See the locking note in
+ * amd_iommu_enable_perfopt().
+ */
+ domain = dev_data->domain;
+ dev_data->perfopt = false;
+ detach_device(&pdev->dev);
+ if (attach_device(&pdev->dev, domain))
+ pci_err(pdev, "failed to restore caps after PerfOpt disable\n");
+}
+EXPORT_SYMBOL_GPL(amd_iommu_disable_perfopt);
static struct iommu_device *amd_iommu_probe_device(struct device *dev)
{
struct iommu_device *iommu_dev;
@@ -2511,6 +2701,14 @@ static struct iommu_device *amd_iommu_probe_device(struct device *dev)
static void amd_iommu_release_device(struct device *dev)
{
struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev);
+ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data);
+
+ if (dev_data->perfopt) {
+ if (WARN_ON(perfopt_put(iommu)))
+ dev_err(dev, "IOMMU%d: failed to clear PerfOpt on release\n",
+ iommu->index);
+ dev_data->perfopt = false;
+ }
WARN_ON(dev_data->domain);
@@ -2885,6 +3083,19 @@ static int blocked_domain_attach_device(struct iommu_domain *domain,
struct iommu_domain *old)
{
struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev);
+ struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data);
+
+ /*
+ * blocked_domain is also the .release_domain, so this is the normal
+ * teardown path: drop the reference and clear the flag here too, and
+ * don't fail teardown if the WARN-guarded write doesn't stick.
+ */
+ if (dev_data->perfopt) {
+ if (WARN_ON(perfopt_put(iommu)))
+ dev_err(dev, "IOMMU%d: failed to clear PerfOpt for blocked domain\n",
+ iommu->index);
+ dev_data->perfopt = false;
+ }
if (dev_data->domain)
detach_device(dev);
@@ -2953,6 +3164,9 @@ static int amd_iommu_attach_device(struct iommu_domain *dom, struct device *dev,
struct amd_iommu *iommu = get_amd_iommu_from_dev(dev);
int ret;
+ if (dev_data->perfopt && !pdom_is_in_pt_mode(domain))
+ return -EBUSY;
+
/*
* Skip attach device to domain if new domain is same as
* devices current domain
diff --git a/include/linux/amd-iommu.h b/include/linux/amd-iommu.h
--- a/include/linux/amd-iommu.h
+++ b/include/linux/amd-iommu.h
@@ -11,11 +11,11 @@
#include <linux/types.h>
struct amd_iommu;
+struct pci_dev;
#ifdef CONFIG_AMD_IOMMU
struct task_struct;
-struct pci_dev;
extern void amd_iommu_detect(void);
@@ -76,4 +76,15 @@ static inline int amd_iommu_snp_disable(void) { return 0; }
static inline bool amd_iommu_sev_tio_supported(void) { return false; }
#endif
+#ifdef CONFIG_AMD_IOMMU
+int amd_iommu_enable_perfopt(struct pci_dev *pdev);
+void amd_iommu_disable_perfopt(struct pci_dev *pdev);
+#else
+static inline int amd_iommu_enable_perfopt(struct pci_dev *pdev)
+{
+ return 0;
+}
+static inline void amd_iommu_disable_perfopt(struct pci_dev *pdev) { }
+#endif
+
#endif /* _ASM_X86_AMD_IOMMU_H */
@@ -0,0 +1,128 @@
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
@@ -160,6 +160,7 @@ struct amdgpu_watchdog_timer {
* Modules parameters.
*/
extern int amdgpu_modeset;
+extern int amdgpu_iommu_perfopt;
extern unsigned int amdgpu_vram_limit;
extern int amdgpu_vis_vram_limit;
extern int amdgpu_gart_size;
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
@@ -33,6 +33,7 @@
#include <linux/console.h>
#include <linux/slab.h>
#include <linux/iommu.h>
+#include <linux/amd-iommu.h>
#include <linux/pci.h>
#include <linux/pci-p2pdma.h>
#include <linux/apple-gmux.h>
@@ -3842,6 +3843,28 @@ amdgpu_device_should_register_switcheroo(struct amdgpu_device *adev, bool px)
apple_gmux_detect(NULL, NULL)));
}
+static inline bool amdgpu_device_identity(struct amdgpu_device *adev)
+{
+ struct pci_dev *pdev = adev->pdev;
+ struct iommu_domain *domain = iommu_get_domain_for_dev(&pdev->dev);
+
+ if (!domain)
+ return false;
+
+ return domain->type == IOMMU_DOMAIN_IDENTITY;
+}
+
+static bool amdgpu_device_use_perfopt(struct amdgpu_device *adev)
+{
+ if (amdgpu_iommu_perfopt == 0)
+ return false;
+
+ if (!(adev->flags & AMD_IS_APU))
+ return false;
+
+ return amdgpu_device_identity(adev);
+}
+
/**
* amdgpu_device_init - initialize the driver
*
@@ -4048,6 +4071,16 @@ int amdgpu_device_init(struct amdgpu_device *adev,
if (r)
return r;
+ if (amdgpu_device_use_perfopt(adev)) {
+ int perfopt_ret = amd_iommu_enable_perfopt(pdev);
+
+ /* Optional optimization; a failure to arm it must not abort probe. */
+ if (perfopt_ret)
+ dev_warn(adev->dev,
+ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n",
+ perfopt_ret);
+ }
+
/*
* No need to remove conflicting FBs for non-display class devices.
* This prevents the sysfb from being freed accidently.
@@ -4416,6 +4449,9 @@ void amdgpu_device_fini_hw(struct amdgpu_device *adev)
amdgpu_gart_dummy_page_fini(adev);
+ if (amdgpu_device_use_perfopt(adev))
+ amd_iommu_disable_perfopt(adev->pdev);
+
if (pci_dev_is_disconnected(adev->pdev))
amdgpu_device_unmap_mmio(adev);
@@ -4779,6 +4815,20 @@ int amdgpu_device_resume(struct drm_device *dev, bool notify_clients)
if (dev->switch_power_state == DRM_SWITCH_POWER_OFF)
return 0;
+ if (amdgpu_device_use_perfopt(adev)) {
+ int perfopt_ret = amd_iommu_enable_perfopt(adev->pdev);
+
+ /*
+ * Must not return on failure: a bare return would leak the
+ * SR-IOV VF exclusive-mode acquisition taken above (released
+ * via the exit: path).
+ */
+ if (perfopt_ret)
+ dev_warn(adev->dev,
+ "Failed to enable IOMMU PerfOpt (%d); continuing without it\n",
+ perfopt_ret);
+ }
+
if (adev->in_s0ix)
amdgpu_dpm_gfx_state_change(adev, sGpuChangeState_D0Entry);
diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
+++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c
@@ -185,6 +185,7 @@ char *amdgpu_disable_cu;
char *amdgpu_virtual_display;
int amdgpu_enforce_isolation = -1;
int amdgpu_modeset = -1;
+int amdgpu_iommu_perfopt = -1;
/* Specifies the default granularity for SVM, used in buffer
* migration and restoration of backing memory when handling
@@ -393,6 +394,17 @@ module_param_named(fw_load_type, amdgpu_fw_load_type, int, 0444);
MODULE_PARM_DESC(aspm, "ASPM support (1 = enable, 0 = disable, -1 = auto)");
module_param_named(aspm, amdgpu_aspm, int, 0444);
+/**
+ * DOC: iommu_perfopt (int)
+ * Control the AMD IOMMU PerfOpt DMA-latency optimization
+ * (0 = disable; -1 = enable on supported devices).
+ * This arms the IOMMU PerfOpt control (IOMMU spec, MMIO Offset 016Ch, EFR PerfOptSup / PerfOptEn)
+ * Arming it disables ATS, PRI, PASID and SVA for the GPU and removes IOMMU DMA containment for it,
+ * trading isolation for lower DMA latency.
+ */
+MODULE_PARM_DESC(iommu_perfopt, "Control IOMMU PerfOpt DMA-latency optimization (-1 = enable on supported devices, 0 = disable)");
+module_param_named(iommu_perfopt, amdgpu_iommu_perfopt, int, 0444);
+
/**
* DOC: runpm (int)
* Override for runtime power management control for dGPUs. The amdgpu driver can dynamically power down
@@ -0,0 +1,412 @@
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c
--- a/drivers/acpi/battery.c
+++ b/drivers/acpi/battery.c
@@ -736,7 +736,7 @@ static LIST_HEAD(acpi_battery_list);
static LIST_HEAD(battery_hook_list);
static DEFINE_MUTEX(hook_mutex);
-static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook)
+static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook)
{
struct acpi_battery *battery;
@@ -753,7 +753,7 @@ static void battery_hook_unregister_unlocked(struct acpi_battery_hook *hook)
pr_info("hook unregistered: %s\n", hook->name);
}
-void battery_hook_unregister(struct acpi_battery_hook *hook)
+void acpi_battery_hook_unregister(struct acpi_battery_hook *hook)
{
mutex_lock(&hook_mutex);
/*
@@ -762,13 +762,13 @@ void battery_hook_unregister(struct acpi_battery_hook *hook)
* adding a new battery.
*/
if (!list_empty(&hook->list))
- battery_hook_unregister_unlocked(hook);
+ acpi_battery_hook_unregister_unlocked(hook);
mutex_unlock(&hook_mutex);
}
-EXPORT_SYMBOL_GPL(battery_hook_unregister);
+EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister);
-void battery_hook_register(struct acpi_battery_hook *hook)
+void acpi_battery_hook_register(struct acpi_battery_hook *hook)
{
struct acpi_battery *battery;
@@ -789,7 +789,7 @@ void battery_hook_register(struct acpi_battery_hook *hook)
* hooks.
*/
pr_err("hook failed to load: %s", hook->name);
- battery_hook_unregister_unlocked(hook);
+ acpi_battery_hook_unregister_unlocked(hook);
goto end;
}
@@ -799,22 +799,22 @@ void battery_hook_register(struct acpi_battery_hook *hook)
end:
mutex_unlock(&hook_mutex);
}
-EXPORT_SYMBOL_GPL(battery_hook_register);
+EXPORT_SYMBOL_GPL(acpi_battery_hook_register);
-static void devm_battery_hook_unregister(void *data)
+static void devm_acpi_battery_hook_unregister(void *data)
{
struct acpi_battery_hook *hook = data;
- battery_hook_unregister(hook);
+ acpi_battery_hook_unregister(hook);
}
-int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook)
+int devm_acpi_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook)
{
- battery_hook_register(hook);
+ acpi_battery_hook_register(hook);
- return devm_add_action_or_reset(dev, devm_battery_hook_unregister, hook);
+ return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook);
}
-EXPORT_SYMBOL_GPL(devm_battery_hook_register);
+EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register);
/*
* This function gets called right after the battery sysfs
@@ -843,7 +843,7 @@ static void battery_hook_add_battery(struct acpi_battery *battery)
*/
pr_err("error in hook, unloading: %s",
hook_node->name);
- battery_hook_unregister_unlocked(hook_node);
+ acpi_battery_hook_unregister_unlocked(hook_node);
}
}
mutex_unlock(&hook_mutex);
@@ -876,7 +876,7 @@ static void __exit battery_hook_exit(void)
* need to remove the hooks.
*/
list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) {
- battery_hook_unregister(hook);
+ acpi_battery_hook_unregister(hook);
}
mutex_destroy(&hook_mutex);
}
diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -1681,14 +1681,14 @@ static void asus_wmi_battery_init(struct asus_wmi *asus)
asus->battery_rsoc_available = false;
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_RSOC)) {
asus->battery_rsoc_available = true;
- battery_hook_register(&battery_hook);
+ acpi_battery_hook_register(&battery_hook);
}
}
static void asus_wmi_battery_exit(struct asus_wmi *asus)
{
if (asus->battery_rsoc_available)
- battery_hook_unregister(&battery_hook);
+ acpi_battery_hook_unregister(&battery_hook);
}
/* LEDs ***********************************************************************/
diff --git a/drivers/platform/x86/ayaneo-ec.c b/drivers/platform/x86/ayaneo-ec.c
--- a/drivers/platform/x86/ayaneo-ec.c
+++ b/drivers/platform/x86/ayaneo-ec.c
@@ -493,7 +493,7 @@ static int ayaneo_ec_probe(struct platform_device *pdev)
data->battery_hook.add_battery = ayaneo_add_battery;
data->battery_hook.remove_battery = ayaneo_remove_battery;
data->battery_hook.name = "Ayaneo Battery";
- ret = devm_battery_hook_register(&pdev->dev, &data->battery_hook);
+ ret = devm_acpi_battery_hook_register(&pdev->dev, &data->battery_hook);
if (ret)
return ret;
}
diff --git a/drivers/platform/x86/dell/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c
--- a/drivers/platform/x86/dell/dell-laptop.c
+++ b/drivers/platform/x86/dell/dell-laptop.c
@@ -2446,13 +2446,13 @@ static void __init dell_battery_init(struct device *dev)
battery_supported_modes = battery_get_supported_modes();
if (battery_supported_modes != 0)
- battery_hook_register(&dell_battery_hook);
+ acpi_battery_hook_register(&dell_battery_hook);
}
static void dell_battery_exit(void)
{
if (battery_supported_modes != 0)
- battery_hook_unregister(&dell_battery_hook);
+ acpi_battery_hook_unregister(&dell_battery_hook);
}
static int __init dell_init(void)
diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c
--- a/drivers/platform/x86/dell/dell-wmi-ddv.c
+++ b/drivers/platform/x86/dell/dell-wmi-ddv.c
@@ -972,7 +972,7 @@ static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data)
data->eppid_attr.attr.mode = 0444;
data->eppid_attr.show = eppid_show;
- return devm_battery_hook_register(&data->wdev->dev, &data->hook);
+ return devm_acpi_battery_hook_register(&data->wdev->dev, &data->hook);
}
static int dell_wmi_ddv_buffer_read(struct seq_file *seq, enum dell_ddv_method method)
diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c
--- a/drivers/platform/x86/fujitsu-laptop.c
+++ b/drivers/platform/x86/fujitsu-laptop.c
@@ -267,7 +267,7 @@ static int fujitsu_battery_charge_control_add(struct device *dev)
return -ENODEV;
priv->charge_control_supported = true;
- battery_hook_register(&battery_hook);
+ acpi_battery_hook_register(&battery_hook);
return 0;
}
@@ -277,7 +277,7 @@ static void fujitsu_battery_charge_control_remove(struct device *dev)
struct fujitsu_laptop *priv = dev_get_drvdata(dev);
if (priv->charge_control_supported)
- battery_hook_unregister(&battery_hook);
+ acpi_battery_hook_unregister(&battery_hook);
}
/* Hardware access for LCD brightness control */
diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c
--- a/drivers/platform/x86/huawei-wmi.c
+++ b/drivers/platform/x86/huawei-wmi.c
@@ -512,7 +512,7 @@ static void huawei_wmi_battery_setup(struct device *dev)
return;
}
- battery_hook_register(&huawei_wmi_battery_hook);
+ acpi_battery_hook_register(&huawei_wmi_battery_hook);
device_create_file(dev, &dev_attr_charge_control_thresholds);
}
@@ -521,7 +521,7 @@ static void huawei_wmi_battery_exit(struct device *dev)
struct huawei_wmi *huawei = dev_get_drvdata(dev);
if (huawei->battery_available) {
- battery_hook_unregister(&huawei_wmi_battery_hook);
+ acpi_battery_hook_unregister(&huawei_wmi_battery_hook);
device_remove_file(dev, &dev_attr_charge_control_thresholds);
}
}
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -2197,8 +2197,8 @@ static int ideapad_check_features(struct ideapad_private *priv)
priv->battery_hook.remove_battery = ideapad_battery_remove;
priv->battery_hook.name = "Ideapad Battery Extension";
- err = devm_battery_hook_register(&priv->platform_device->dev,
- &priv->battery_hook);
+ err = devm_acpi_battery_hook_register(&priv->platform_device->dev,
+ &priv->battery_hook);
if (err)
return err;
}
diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c
--- a/drivers/platform/x86/lenovo/thinkpad_acpi.c
+++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c
@@ -10021,13 +10021,13 @@ static int __init tpacpi_battery_init(struct ibm_init_struct *ibm)
battery_quirk_table,
ARRAY_SIZE(battery_quirk_table));
- battery_hook_register(&battery_hook);
+ acpi_battery_hook_register(&battery_hook);
return 0;
}
static void tpacpi_battery_exit(void)
{
- battery_hook_unregister(&battery_hook);
+ acpi_battery_hook_unregister(&battery_hook);
}
static struct ibm_struct battery_driver_data = {
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -946,7 +946,7 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
priv->battery_hook.name = "Lenovo WMI Other Battery Extension";
priv->bh_registered = true;
- battery_hook_register(&priv->battery_hook);
+ acpi_battery_hook_register(&priv->battery_hook);
}
/**
@@ -960,7 +960,7 @@ static void lwmi_om_psy_remove(struct lwmi_om_priv *priv)
if (!priv->bh_registered)
return;
- battery_hook_unregister(&priv->battery_hook);
+ acpi_battery_hook_unregister(&priv->battery_hook);
priv->bh_registered = false;
}
diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c
--- a/drivers/platform/x86/lg-laptop.c
+++ b/drivers/platform/x86/lg-laptop.c
@@ -1053,7 +1053,7 @@ static int acpi_probe(struct platform_device *pdev)
if (ret)
goto out_platform_device;
- battery_hook_register(&battery_hook);
+ acpi_battery_hook_register(&battery_hook);
return 0;
@@ -1068,7 +1068,7 @@ static void acpi_remove(struct platform_device *pdev)
{
sysfs_remove_group(&pf_device->dev.kobj, &dev_attribute_group);
- battery_hook_unregister(&battery_hook);
+ acpi_battery_hook_unregister(&battery_hook);
platform_device_unregister(pf_device);
pf_device = NULL;
platform_driver_unregister(&pf_driver);
diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c
--- a/drivers/platform/x86/msi-ec.c
+++ b/drivers/platform/x86/msi-ec.c
@@ -1428,13 +1428,13 @@ static int __init msi_ec_init(void)
if (result < 0)
return result;
- battery_hook_register(&battery_hook);
+ acpi_battery_hook_register(&battery_hook);
return 0;
}
static void __exit msi_ec_exit(void)
{
- battery_hook_unregister(&battery_hook);
+ acpi_battery_hook_unregister(&battery_hook);
}
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/oxpec.c b/drivers/platform/x86/oxpec.c
--- a/drivers/platform/x86/oxpec.c
+++ b/drivers/platform/x86/oxpec.c
@@ -956,7 +956,7 @@ static int oxp_platform_probe(struct platform_device *pdev)
return PTR_ERR(hwdev);
if (oxp_psy_ext_supported()) {
- ret = devm_battery_hook_register(dev, &battery_hook);
+ ret = devm_acpi_battery_hook_register(dev, &battery_hook);
if (ret)
return ret;
}
diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c
--- a/drivers/platform/x86/samsung-galaxybook.c
+++ b/drivers/platform/x86/samsung-galaxybook.c
@@ -549,7 +549,8 @@ static int galaxybook_battery_threshold_init(struct samsung_galaxybook *galaxybo
galaxybook->battery_hook.remove_battery = galaxybook_battery_remove;
galaxybook->battery_hook.name = "Samsung Galaxy Book Battery Extension";
- return devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook->battery_hook);
+ return devm_acpi_battery_hook_register(&galaxybook->platform->dev,
+ &galaxybook->battery_hook);
}
/*
diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c
--- a/drivers/platform/x86/samsung-laptop.c
+++ b/drivers/platform/x86/samsung-laptop.c
@@ -1142,8 +1142,8 @@ static int __init samsung_battery_hook_init(struct samsung_laptop *samsung)
samsung->battery_hook.add_battery = samsung_battery_add;
samsung->battery_hook.remove_battery = samsung_battery_remove;
samsung->battery_hook.name = "Samsung Battery Extension";
- retval = devm_battery_hook_register(&samsung->platform_device->dev,
- &samsung->battery_hook);
+ retval = devm_acpi_battery_hook_register(&samsung->platform_device->dev,
+ &samsung->battery_hook);
}
return retval;
diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c
--- a/drivers/platform/x86/system76_acpi.c
+++ b/drivers/platform/x86/system76_acpi.c
@@ -288,12 +288,12 @@ static struct acpi_battery_hook system76_battery_hook = {
static void system76_battery_init(void)
{
- battery_hook_register(&system76_battery_hook);
+ acpi_battery_hook_register(&system76_battery_hook);
}
static void system76_battery_exit(void)
{
- battery_hook_unregister(&system76_battery_hook);
+ acpi_battery_hook_unregister(&system76_battery_hook);
}
// Get the airplane mode LED brightness
diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c
--- a/drivers/platform/x86/toshiba_acpi.c
+++ b/drivers/platform/x86/toshiba_acpi.c
@@ -3301,7 +3301,7 @@ static void toshiba_acpi_remove(struct platform_device *pdev)
}
if (dev->battery_charge_mode_supported)
- battery_hook_unregister(&battery_hook);
+ acpi_battery_hook_unregister(&battery_hook);
if (toshiba_acpi)
toshiba_acpi = NULL;
@@ -3566,7 +3566,7 @@ static int toshiba_acpi_probe(struct platform_device *pdev)
* set, this must be done after toshiba_acpi is assigned.
*/
if (dev->battery_charge_mode_supported)
- battery_hook_register(&battery_hook);
+ acpi_battery_hook_register(&battery_hook);
return 0;
diff --git a/drivers/platform/x86/uniwill/uniwill-acpi.c b/drivers/platform/x86/uniwill/uniwill-acpi.c
--- a/drivers/platform/x86/uniwill/uniwill-acpi.c
+++ b/drivers/platform/x86/uniwill/uniwill-acpi.c
@@ -2165,7 +2165,7 @@ static int uniwill_battery_init(struct uniwill_data *data)
data->hook.add_battery = uniwill_add_battery;
data->hook.remove_battery = uniwill_remove_battery;
- return devm_battery_hook_register(data->dev, &data->hook);
+ return devm_acpi_battery_hook_register(data->dev, &data->hook);
}
static int uniwill_notifier_call(struct notifier_block *nb, unsigned long action, void *dummy)
diff --git a/drivers/power/supply/cros_charge-control.c b/drivers/power/supply/cros_charge-control.c
--- a/drivers/power/supply/cros_charge-control.c
+++ b/drivers/power/supply/cros_charge-control.c
@@ -315,7 +315,7 @@ static int cros_chctl_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
- return devm_battery_hook_register(dev, &priv->battery_hook);
+ return devm_acpi_battery_hook_register(dev, &priv->battery_hook);
}
static const struct platform_device_id cros_chctl_id[] = {
diff --git a/include/acpi/battery.h b/include/acpi/battery.h
--- a/include/acpi/battery.h
+++ b/include/acpi/battery.h
@@ -18,8 +18,9 @@ struct acpi_battery_hook {
struct list_head list;
};
-void battery_hook_register(struct acpi_battery_hook *hook);
-void battery_hook_unregister(struct acpi_battery_hook *hook);
-int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook);
+void acpi_battery_hook_register(struct acpi_battery_hook *hook);
+void acpi_battery_hook_unregister(struct acpi_battery_hook *hook);
+int devm_acpi_battery_hook_register(struct device *dev,
+ struct acpi_battery_hook *hook);
#endif
@@ -0,0 +1,472 @@
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -171,8 +171,12 @@ config ACPI_AC
To compile this driver as a module, choose M here:
the module will be called ac.
+config ACPI_BATTERY_HOOKS
+ tristate
+
config ACPI_BATTERY
tristate "Battery"
+ select ACPI_BATTERY_HOOKS
select POWER_SUPPLY
default y
help
@@ -445,6 +449,7 @@ config ACPI_HOTPLUG_IOAPIC
config ACPI_SBS
tristate "Smart Battery System"
depends on X86 && ACPI_EC
+ select ACPI_BATTERY_HOOKS
select POWER_SUPPLY
help
This driver supports the Smart Battery System, another
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -97,6 +97,7 @@ obj-$(CONFIG_ACPI_NHLT) += nhlt.o
obj-$(CONFIG_ACPI_NUMA) += numa/
obj-$(CONFIG_ACPI) += acpi_memhotplug.o
obj-$(CONFIG_ACPI_HOTPLUG_IOAPIC) += ioapic.o
+obj-$(CONFIG_ACPI_BATTERY_HOOKS) += battery_hooks.o
obj-$(CONFIG_ACPI_BATTERY) += battery.o
obj-$(CONFIG_ACPI_SBS) += sbshc.o
obj-$(CONFIG_ACPI_SBS) += sbs.o
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c
--- a/drivers/acpi/battery.c
+++ b/drivers/acpi/battery.c
@@ -48,6 +48,7 @@ MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_AUTHOR("Alexey Starikovskiy <astarikovskiy@suse.de>");
MODULE_DESCRIPTION("ACPI Battery Driver");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS");
static int battery_bix_broken_package;
static int battery_notification_delay_ms;
@@ -97,7 +98,7 @@ struct acpi_battery {
struct acpi_device *device;
struct device *phys_dev;
struct notifier_block pm_nb;
- struct list_head list;
+ struct acpi_battery_hooks_list_entry hooks_list_entry;
unsigned long flags;
struct mutex property_lock; /* Protects properties below. */
@@ -723,164 +724,6 @@ static struct attribute *acpi_battery_attrs[] = {
};
ATTRIBUTE_GROUPS(acpi_battery);
-/*
- * The Battery Hooking API
- *
- * This API is used inside other drivers that need to expose
- * platform-specific behaviour within the generic driver in a
- * generic way.
- *
- */
-
-static LIST_HEAD(acpi_battery_list);
-static LIST_HEAD(battery_hook_list);
-static DEFINE_MUTEX(hook_mutex);
-
-static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook)
-{
- struct acpi_battery *battery;
-
- /*
- * In order to remove a hook, we first need to
- * de-register all the batteries that are registered.
- */
- list_for_each_entry(battery, &acpi_battery_list, list) {
- if (!hook->remove_battery(battery->bat, hook))
- power_supply_changed(battery->bat);
- }
- list_del_init(&hook->list);
-
- pr_info("hook unregistered: %s\n", hook->name);
-}
-
-void acpi_battery_hook_unregister(struct acpi_battery_hook *hook)
-{
- mutex_lock(&hook_mutex);
- /*
- * Ignore already unregistered battery hooks. This might happen
- * if a battery hook was previously unloaded due to an error when
- * adding a new battery.
- */
- if (!list_empty(&hook->list))
- acpi_battery_hook_unregister_unlocked(hook);
-
- mutex_unlock(&hook_mutex);
-}
-EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister);
-
-void acpi_battery_hook_register(struct acpi_battery_hook *hook)
-{
- struct acpi_battery *battery;
-
- mutex_lock(&hook_mutex);
- list_add(&hook->list, &battery_hook_list);
- /*
- * Now that the driver is registered, we need
- * to notify the hook that a battery is available
- * for each battery, so that the driver may add
- * its attributes.
- */
- list_for_each_entry(battery, &acpi_battery_list, list) {
- if (hook->add_battery(battery->bat, hook)) {
- /*
- * If a add-battery returns non-zero,
- * the registration of the hook has failed,
- * and we will not add it to the list of loaded
- * hooks.
- */
- pr_err("hook failed to load: %s", hook->name);
- acpi_battery_hook_unregister_unlocked(hook);
- goto end;
- }
-
- power_supply_changed(battery->bat);
- }
- pr_info("new hook: %s\n", hook->name);
-end:
- mutex_unlock(&hook_mutex);
-}
-EXPORT_SYMBOL_GPL(acpi_battery_hook_register);
-
-static void devm_acpi_battery_hook_unregister(void *data)
-{
- struct acpi_battery_hook *hook = data;
-
- acpi_battery_hook_unregister(hook);
-}
-
-int devm_acpi_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook)
-{
- acpi_battery_hook_register(hook);
-
- return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook);
-}
-EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register);
-
-/*
- * This function gets called right after the battery sysfs
- * attributes have been added, so that the drivers that
- * define custom sysfs attributes can add their own.
- */
-static void battery_hook_add_battery(struct acpi_battery *battery)
-{
- struct acpi_battery_hook *hook_node, *tmp;
-
- mutex_lock(&hook_mutex);
- INIT_LIST_HEAD(&battery->list);
- list_add(&battery->list, &acpi_battery_list);
- /*
- * Since we added a new battery to the list, we need to
- * iterate over the hooks and call add_battery for each
- * hook that was registered. This usually happens
- * when a battery gets hotplugged or initialized
- * during the battery module initialization.
- */
- list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) {
- if (hook_node->add_battery(battery->bat, hook_node)) {
- /*
- * The notification of the hook has failed, to
- * prevent further errors we will unload the hook.
- */
- pr_err("error in hook, unloading: %s",
- hook_node->name);
- acpi_battery_hook_unregister_unlocked(hook_node);
- }
- }
- mutex_unlock(&hook_mutex);
-}
-
-static void battery_hook_remove_battery(struct acpi_battery *battery)
-{
- struct acpi_battery_hook *hook;
-
- mutex_lock(&hook_mutex);
- /*
- * Before removing the hook, we need to remove all
- * custom attributes from the battery.
- */
- list_for_each_entry(hook, &battery_hook_list, list) {
- hook->remove_battery(battery->bat, hook);
- }
- /* Then, just remove the battery from the list */
- list_del(&battery->list);
- mutex_unlock(&hook_mutex);
-}
-
-static void __exit battery_hook_exit(void)
-{
- struct acpi_battery_hook *hook;
- struct acpi_battery_hook *ptr;
- /*
- * At this point, the acpi_bus_unregister_driver()
- * has called remove for all batteries. We just
- * need to remove the hooks.
- */
- list_for_each_entry_safe(hook, ptr, &battery_hook_list, list) {
- acpi_battery_hook_unregister(hook);
- }
- mutex_destroy(&hook_mutex);
-}
-
static int sysfs_add_battery(struct acpi_battery *battery)
{
struct power_supply_config psy_cfg = {
@@ -936,7 +779,7 @@ static int sysfs_add_battery(struct acpi_battery *battery)
battery->bat = NULL;
return result;
}
- battery_hook_add_battery(battery);
+ acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat);
return 0;
}
@@ -945,7 +788,7 @@ static void sysfs_remove_battery(struct acpi_battery *battery)
if (!battery->bat)
return;
- battery_hook_remove_battery(battery);
+ acpi_battery_remove_hooks(&battery->hooks_list_entry);
power_supply_unregister(battery->bat);
battery->bat = NULL;
}
@@ -1391,7 +1234,6 @@ static int __init acpi_battery_init(void)
static void __exit acpi_battery_exit(void)
{
platform_driver_unregister(&acpi_battery_driver);
- battery_hook_exit();
}
module_init(acpi_battery_init);
diff --git a/drivers/acpi/battery_hooks.c b/drivers/acpi/battery_hooks.c
new file mode 100644
--- /dev/null
+++ b/drivers/acpi/battery_hooks.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ACPI Battery Hooks
+ *
+ * Provides helpers for registering and unregistering battery hooks for
+ * drivers that use platform-specific extensions to ACPI-enumerated
+ * batteries (both ACPI Control Method batteries and Smart Battery System
+ * batteries).
+ */
+
+#define pr_fmt(fmt) "ACPI: battery: " fmt
+
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <acpi/battery.h>
+
+static LIST_HEAD(acpi_battery_list);
+static LIST_HEAD(battery_hook_list);
+static DEFINE_MUTEX(hook_mutex);
+
+static void acpi_battery_hook_unregister_unlocked(struct acpi_battery_hook *hook)
+{
+ struct acpi_battery_hooks_list_entry *entry;
+
+ /*
+ * In order to remove a hook, we first need to
+ * de-register all the batteries that are registered.
+ */
+ list_for_each_entry(entry, &acpi_battery_list, list_entry) {
+ if (!hook->remove_battery(entry->battery, hook))
+ power_supply_changed(entry->battery);
+ }
+ list_del_init(&hook->list);
+
+ pr_info("hook unregistered: %s\n", hook->name);
+}
+
+void acpi_battery_hook_unregister(struct acpi_battery_hook *hook)
+{
+ guard(mutex)(&hook_mutex);
+
+ /*
+ * Ignore already unregistered battery hooks. This might happen
+ * if a battery hook was previously unloaded due to an error when
+ * adding a new battery.
+ */
+ if (!list_empty(&hook->list))
+ acpi_battery_hook_unregister_unlocked(hook);
+}
+EXPORT_SYMBOL_GPL(acpi_battery_hook_unregister);
+
+void acpi_battery_hook_register(struct acpi_battery_hook *hook)
+{
+ struct acpi_battery_hooks_list_entry *entry;
+
+ guard(mutex)(&hook_mutex);
+
+ list_add(&hook->list, &battery_hook_list);
+ /*
+ * Now that the driver is registered, we need
+ * to notify the hook that a battery is available
+ * for each battery, so that the driver may add
+ * its attributes.
+ */
+ list_for_each_entry(entry, &acpi_battery_list, list_entry) {
+ if (hook->add_battery(entry->battery, hook)) {
+ /*
+ * If a add-battery returns non-zero,
+ * the registration of the hook has failed,
+ * and we will not add it to the list of loaded
+ * hooks.
+ */
+ pr_err("hook failed to load: %s\n", hook->name);
+ acpi_battery_hook_unregister_unlocked(hook);
+ return;
+ }
+
+ power_supply_changed(entry->battery);
+ }
+ pr_info("new hook: %s\n", hook->name);
+}
+EXPORT_SYMBOL_GPL(acpi_battery_hook_register);
+
+static void devm_acpi_battery_hook_unregister(void *data)
+{
+ struct acpi_battery_hook *hook = data;
+
+ acpi_battery_hook_unregister(hook);
+}
+
+int devm_acpi_battery_hook_register(struct device *dev,
+ struct acpi_battery_hook *hook)
+{
+ acpi_battery_hook_register(hook);
+
+ return devm_add_action_or_reset(dev, devm_acpi_battery_hook_unregister, hook);
+}
+EXPORT_SYMBOL_GPL(devm_acpi_battery_hook_register);
+
+/*
+ * This function gets called right after the battery sysfs
+ * attributes have been added, so that the drivers that
+ * define custom sysfs attributes can add their own.
+ */
+void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry,
+ struct power_supply *battery)
+{
+ struct acpi_battery_hook *hook_node, *tmp;
+
+ entry->battery = battery;
+
+ guard(mutex)(&hook_mutex);
+
+ list_add(&entry->list_entry, &acpi_battery_list);
+ /*
+ * Since we added a new battery to the list, we need to
+ * iterate over the hooks and call add_battery for each
+ * hook that was registered. This usually happens
+ * when a battery gets hotplugged or initialized
+ * during the battery module initialization.
+ */
+ list_for_each_entry_safe(hook_node, tmp, &battery_hook_list, list) {
+ if (hook_node->add_battery(entry->battery, hook_node)) {
+ /*
+ * The notification of the hook has failed, to
+ * prevent further errors we will unload the hook.
+ */
+ pr_err("error in hook, unloading: %s\n", hook_node->name);
+ acpi_battery_hook_unregister_unlocked(hook_node);
+ }
+ }
+}
+EXPORT_SYMBOL_NS_GPL(acpi_battery_add_hooks, "ACPI_BATTERY_HOOKS");
+
+void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry)
+{
+ struct acpi_battery_hook *hook;
+
+ guard(mutex)(&hook_mutex);
+ /*
+ * Before removing the hook, we need to remove all
+ * custom attributes from the battery.
+ */
+ list_for_each_entry(hook, &battery_hook_list, list)
+ hook->remove_battery(entry->battery, hook);
+
+ /* Then, just remove the battery from the list */
+ list_del(&entry->list_entry);
+ entry->battery = NULL;
+}
+EXPORT_SYMBOL_NS_GPL(acpi_battery_remove_hooks, "ACPI_BATTERY_HOOKS");
+
+MODULE_DESCRIPTION("ACPI battery hooks");
+MODULE_LICENSE("GPL");
diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c
--- a/drivers/acpi/sbs.c
+++ b/drivers/acpi/sbs.c
@@ -36,6 +36,7 @@
MODULE_AUTHOR("Alexey Starikovskiy <astarikovskiy@suse.de>");
MODULE_DESCRIPTION("Smart Battery System ACPI interface driver");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("ACPI_BATTERY_HOOKS");
static unsigned int cache_time = 1000;
module_param(cache_time, uint, 0644);
@@ -54,6 +55,7 @@ struct acpi_battery {
struct power_supply *bat;
struct power_supply_desc bat_desc;
struct acpi_sbs *sbs;
+ struct acpi_battery_hooks_list_entry hooks_list_entry;
unsigned long update_time;
char name[8];
char manufacturer_name[ACPI_SBS_BLOCK_MAX];
@@ -555,6 +557,8 @@ static int acpi_battery_add(struct acpi_sbs *sbs, int id)
goto end;
}
+ acpi_battery_add_hooks(&battery->hooks_list_entry, battery->bat);
+
end:
pr_info("%s [%s]: Battery Slot [%s] (battery %s)\n",
ACPI_SBS_DEVICE_NAME, acpi_device_bid(sbs->device),
@@ -566,8 +570,10 @@ static void acpi_battery_remove(struct acpi_sbs *sbs, int id)
{
struct acpi_battery *battery = &sbs->battery[id];
- if (battery->bat)
+ if (battery->bat) {
+ acpi_battery_remove_hooks(&battery->hooks_list_entry);
power_supply_unregister(battery->bat);
+ }
}
static int acpi_charger_add(struct acpi_sbs *sbs)
diff --git a/include/acpi/battery.h b/include/acpi/battery.h
--- a/include/acpi/battery.h
+++ b/include/acpi/battery.h
@@ -18,9 +18,18 @@ struct acpi_battery_hook {
struct list_head list;
};
+struct acpi_battery_hooks_list_entry {
+ struct list_head list_entry;
+ struct power_supply *battery;
+};
+
void acpi_battery_hook_register(struct acpi_battery_hook *hook);
void acpi_battery_hook_unregister(struct acpi_battery_hook *hook);
int devm_acpi_battery_hook_register(struct device *dev,
struct acpi_battery_hook *hook);
+void acpi_battery_add_hooks(struct acpi_battery_hooks_list_entry *entry,
+ struct power_supply *battery);
+void acpi_battery_remove_hooks(struct acpi_battery_hooks_list_entry *entry);
+
#endif
@@ -0,0 +1,209 @@
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -380,6 +380,7 @@ config SENSORS_FAM15H_POWER
config SENSORS_APPLESMC
tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)"
depends on INPUT && X86
+ depends on POWER_SUPPLY || POWER_SUPPLY=n
select NEW_LEDS
select LEDS_CLASS
help
diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c
--- a/drivers/hwmon/applesmc.c
+++ b/drivers/hwmon/applesmc.c
@@ -33,6 +33,8 @@
#include <linux/workqueue.h>
#include <linux/err.h>
#include <linux/bits.h>
+#include <linux/power_supply.h>
+#include <acpi/battery.h>
#include <asm/barrier.h>
/* data port used by Apple SMC */
@@ -76,6 +78,20 @@
#define TEMP_SENSOR_TYPE "sp78"
+/*
+ * BCLM caps charging at a percentage. BFCL only sets when the charging LED
+ * switches from orange to green.
+ */
+#define BATTERY_CHARGE_LIMIT_KEY "BCLM" /* r/w ui8 */
+#define BATTERY_CHARGE_LIMIT_LED_KEY "BFCL" /* r/w ui8 */
+
+/*
+ * Points kept between BCLM and BFCL so the LED turns green before charging
+ * stops. Measured on a MacBookAir6,2, where a margin of 1 only just ties;
+ * 3 leaves headroom for other models.
+ */
+#define APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN 3
+
/* List of keys used to read/write fan speeds */
static const char *const fan_speed_fmt[] = {
"F%dAc", /* actual speed */
@@ -131,6 +147,8 @@ static struct applesmc_registers {
int num_light_sensors; /* number of light sensors */
bool has_accelerometer; /* has motion sensor */
bool has_key_backlight; /* has keyboard backlight */
+ bool has_battery_charge_limit; /* has BCLM battery charge limit */
+ bool has_battery_charge_limit_led; /* has BFCL MagSafe LED charge limit */
bool init_complete; /* true when fully initialized */
struct applesmc_entry *cache; /* cached key entries */
const char **index; /* temperature key index */
@@ -633,6 +651,12 @@ static int applesmc_init_smcreg_try(void)
if (ret)
return ret;
ret = applesmc_has_key(BACKLIGHT_KEY, &s->has_key_backlight);
+ if (ret)
+ return ret;
+ ret = applesmc_has_key(BATTERY_CHARGE_LIMIT_KEY, &s->has_battery_charge_limit);
+ if (ret)
+ return ret;
+ ret = applesmc_has_key(BATTERY_CHARGE_LIMIT_LED_KEY, &s->has_battery_charge_limit_led);
if (ret)
return ret;
@@ -724,6 +748,120 @@ static struct platform_driver applesmc_driver = {
},
};
+static DEFINE_MUTEX(applesmc_bat_lock);
+
+static int applesmc_bat_get_property(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ u8 limit;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ scoped_guard(mutex, &applesmc_bat_lock)
+ ret = applesmc_read_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1);
+ if (ret)
+ return ret;
+ val->intval = limit;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int applesmc_bat_set_property(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ u8 limit, led_limit, readback;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ /* 20 is known to work and 10 is not accepted; the floor is unknown. */
+ if (val->intval < 20 || val->intval > 100)
+ return -EINVAL;
+
+ limit = val->intval;
+ /* At 100% charging is never cut short, so no margin is needed. */
+ led_limit = limit < 100 ?
+ limit - APPLESMC_BATTERY_CHARGE_LIMIT_LED_MARGIN : limit;
+
+ scoped_guard(mutex, &applesmc_bat_lock) {
+ ret = applesmc_write_key(BATTERY_CHARGE_LIMIT_KEY, &limit, 1);
+ if (ret)
+ return ret;
+
+ /*
+ * The SMC silently ignores values it does not accept and
+ * still reports success, so read the limit back.
+ */
+ ret = applesmc_read_key(BATTERY_CHARGE_LIMIT_KEY, &readback, 1);
+ if (ret)
+ return ret;
+ if (readback != limit)
+ return -EINVAL;
+
+ if (smcreg.has_battery_charge_limit_led) {
+ ret = applesmc_write_key(BATTERY_CHARGE_LIMIT_LED_KEY,
+ &led_limit, 1);
+ /* BCLM is already applied, so this is not an error. */
+ if (ret)
+ pr_warn("failed to update MagSafe LED threshold (BFCL): %d\n",
+ ret);
+ }
+ }
+
+ power_supply_changed(psy);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int applesmc_bat_property_is_writeable(struct power_supply *psy,
+ const struct power_supply_ext *ext,
+ void *data,
+ enum power_supply_property psp)
+{
+ return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD;
+}
+
+static const enum power_supply_property applesmc_bat_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const struct power_supply_ext applesmc_bat_ext = {
+ .name = "applesmc-charge-control",
+ .properties = applesmc_bat_props,
+ .num_properties = ARRAY_SIZE(applesmc_bat_props),
+ .get_property = applesmc_bat_get_property,
+ .set_property = applesmc_bat_set_property,
+ .property_is_writeable = applesmc_bat_property_is_writeable,
+};
+
+static int applesmc_bat_add(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ return power_supply_register_extension(battery, &applesmc_bat_ext, &pdev->dev, NULL);
+}
+
+static int applesmc_bat_remove(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ power_supply_unregister_extension(battery, &applesmc_bat_ext);
+ return 0;
+}
+
+static struct acpi_battery_hook applesmc_bat_hook = {
+ .name = "Apple SMC Battery Charge Control",
+ .add_battery = applesmc_bat_add,
+ .remove_battery = applesmc_bat_remove,
+};
+
/*
* applesmc_calibrate - Set our "resting" values. Callers must
* hold applesmc_lock.
@@ -1563,6 +1701,10 @@ static int __init applesmc_init(void)
goto out_light_ledclass;
}
+ if (smcreg.has_battery_charge_limit &&
+ IS_REACHABLE(CONFIG_ACPI_BATTERY_HOOKS))
+ acpi_battery_hook_register(&applesmc_bat_hook);
+
return 0;
out_light_ledclass:
@@ -1589,6 +1731,10 @@ static int __init applesmc_init(void)
static void __exit applesmc_exit(void)
{
+ if (smcreg.has_battery_charge_limit &&
+ IS_REACHABLE(CONFIG_ACPI_BATTERY_HOOKS))
+ acpi_battery_hook_unregister(&applesmc_bat_hook);
+
hwmon_device_unregister(hwmon_dev);
applesmc_release_key_backlight();
applesmc_release_light_sensor();
@@ -1,170 +0,0 @@
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -18,6 +18,7 @@
#include <linux/clocksource.h>
#include <linux/sched/task.h>
#include <linux/kallsyms.h>
+#include <linux/module_symbol.h>
#include <linux/security.h>
#include <linux/seq_file.h>
#include <linux/tracefs.h>
@@ -4385,6 +4386,109 @@ static int test_for_valid_rec(struct dyn_ftrace *rec)
return 1;
}
+#if defined(CONFIG_MODULES) && defined(CONFIG_KALLSYMS)
+#define FTRACE_MOD_SYMS
+/*
+ * test_for_valid_rec() resolves an address with kallsyms_lookup(), which
+ * scans the whole symbol table of a module. Calling it for every record of
+ * a module being loaded costs O(records * symbols): several seconds for a
+ * driver as large as amdgpu, all of it under ftrace_lock. Sort the symbol
+ * addresses of the module once instead and binary search them. The module
+ * symbol table itself is left untouched, as livepatch relies on its order.
+ */
+struct ftrace_mod_syms {
+ unsigned long *addrs;
+ unsigned int nr;
+};
+
+static int ftrace_cmp_addr(const void *a, const void *b)
+{
+ unsigned long x = *(const unsigned long *)a;
+ unsigned long y = *(const unsigned long *)b;
+
+ return x < y ? -1 : x > y;
+}
+
+/* Collect the symbols find_kallsyms_symbol() would consider. */
+static void ftrace_mod_syms_init(struct ftrace_mod_syms *syms,
+ struct module *mod)
+{
+ /* A coming module cannot have its kallsyms replaced under us. */
+ struct mod_kallsyms *kallsyms = rcu_dereference_raw(mod->kallsyms);
+ unsigned int i;
+
+ syms->nr = 0;
+ syms->addrs = kvmalloc_array(kallsyms->num_symtab,
+ sizeof(*syms->addrs), GFP_KERNEL);
+ if (!syms->addrs)
+ return;
+
+ for (i = 1; i < kallsyms->num_symtab; i++) {
+ const Elf_Sym *sym = &kallsyms->symtab[i];
+ const char *name = kallsyms->strtab + sym->st_name;
+
+ if (sym->st_shndx == SHN_UNDEF || *name == '\0' ||
+ is_mapping_symbol(name))
+ continue;
+ syms->addrs[syms->nr++] = kallsyms_symbol_value(sym);
+ }
+
+ sort(syms->addrs, syms->nr, sizeof(*syms->addrs), ftrace_cmp_addr, NULL);
+}
+
+/* Same answer as test_for_valid_rec(), using the sorted addresses. */
+static int test_for_valid_mod_rec(struct ftrace_mod_syms *syms,
+ struct module *mod, struct dyn_ftrace *rec)
+{
+ unsigned long ip = rec->ip, base, best;
+ unsigned int lo = 0, hi = syms->nr, mid;
+ struct module_memory *mod_mem = NULL;
+
+ if (!syms->addrs)
+ return test_for_valid_rec(rec);
+
+ for_each_mod_mem_type(type) {
+#ifndef CONFIG_KALLSYMS_ALL
+ if (!mod_mem_type_is_text(type))
+ continue;
+#endif
+ if (within_module_mem_type(ip, mod, type)) {
+ mod_mem = &mod->mem[type];
+ break;
+ }
+ }
+ if (!mod_mem)
+ goto invalid;
+ base = (unsigned long)mod_mem->base;
+
+ /* Find the last symbol at or below ip. */
+ while (lo < hi) {
+ mid = lo + (hi - lo) / 2;
+ if (syms->addrs[mid] <= ip)
+ lo = mid + 1;
+ else
+ hi = mid;
+ }
+ if (!lo)
+ goto invalid;
+ best = syms->addrs[lo - 1];
+
+ /* Weak functions can cause invalid addresses */
+ if (best < base || ip - best > FTRACE_MCOUNT_MAX_OFFSET)
+ goto invalid;
+ return 1;
+
+invalid:
+ rec->flags |= FTRACE_FL_DISABLED;
+ return 0;
+}
+
+static void ftrace_mod_syms_free(struct ftrace_mod_syms *syms)
+{
+ kvfree(syms->addrs);
+}
+#endif
+
static struct workqueue_struct *ftrace_check_wq __initdata;
static struct work_struct ftrace_check_work __initdata;
@@ -8010,11 +8114,30 @@ void ftrace_release_mod(struct module *mod)
}
}
+#ifndef FTRACE_MOD_SYMS
+struct ftrace_mod_syms { };
+
+static inline void ftrace_mod_syms_init(struct ftrace_mod_syms *syms,
+ struct module *mod) { }
+
+static inline int test_for_valid_mod_rec(struct ftrace_mod_syms *syms,
+ struct module *mod,
+ struct dyn_ftrace *rec)
+{
+ return test_for_valid_rec(rec);
+}
+
+static inline void ftrace_mod_syms_free(struct ftrace_mod_syms *syms) { }
+#endif
+
void ftrace_module_enable(struct module *mod)
{
+ struct ftrace_mod_syms syms;
struct dyn_ftrace *rec;
struct ftrace_page *pg;
+ ftrace_mod_syms_init(&syms, mod);
+
mutex_lock(&ftrace_lock);
if (ftrace_disabled)
@@ -8050,7 +8173,7 @@ void ftrace_module_enable(struct module *mod)
cond_resched();
/* Weak functions should still be ignored */
- if (!test_for_valid_rec(rec)) {
+ if (!test_for_valid_mod_rec(&syms, mod, rec)) {
/* Clear all other flags. Should not be enabled anyway */
rec->flags = FTRACE_FL_DISABLED;
continue;
@@ -8087,6 +8210,8 @@ void ftrace_module_enable(struct module *mod)
out_unlock:
mutex_unlock(&ftrace_lock);
+ ftrace_mod_syms_free(&syms);
+
process_cached_mods(mod->name);
}
@@ -0,0 +1,95 @@
diff --git a/include/linux/module.h b/include/linux/module.h
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -975,6 +975,10 @@ unsigned long module_kallsyms_lookup_name(const char *name);
unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name);
+void module_kallsyms_on_each_addr(struct module *mod,
+ void (*fn)(void *, unsigned long),
+ void *data);
+
#else /* CONFIG_MODULES && CONFIG_KALLSYMS */
static inline int module_kallsyms_on_each_symbol(const char *modname,
@@ -1018,6 +1022,12 @@ static inline unsigned long find_kallsyms_symbol_value(struct module *mod,
return 0;
}
+static inline void module_kallsyms_on_each_addr(struct module *mod,
+ void (*fn)(void *, unsigned long),
+ void *data)
+{
+}
+
#endif /* CONFIG_MODULES && CONFIG_KALLSYMS */
/* Define __free(module_put) macro for struct module *. */
diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c
--- a/kernel/module/kallsyms.c
+++ b/kernel/module/kallsyms.c
@@ -246,6 +246,18 @@ static const char *kallsyms_symbol_name(struct mod_kallsyms *kallsyms, unsigned
return kallsyms->strtab + kallsyms->symtab[symnum].st_name;
}
+/*
+ * Whether find_kallsyms_symbol() may resolve an address to symbol @symnum.
+ * Unnamed symbols are ignored: they're uninformative and inserted at a whim.
+ */
+static bool is_lookup_symbol(struct mod_kallsyms *kallsyms, unsigned int symnum)
+{
+ const char *name = kallsyms_symbol_name(kallsyms, symnum);
+
+ return kallsyms->symtab[symnum].st_shndx != SHN_UNDEF &&
+ *name != '\0' && !is_mapping_symbol(name);
+}
+
/*
* Given a module and address, find the corresponding symbol and return its name
* while providing its size and offset if needed.
@@ -286,15 +298,7 @@ static const char *find_kallsyms_symbol(struct module *mod,
const Elf_Sym *sym = &kallsyms->symtab[i];
unsigned long thisval = kallsyms_symbol_value(sym);
- if (sym->st_shndx == SHN_UNDEF)
- continue;
-
- /*
- * We ignore unnamed symbols: they're uninformative
- * and inserted at a whim.
- */
- if (*kallsyms_symbol_name(kallsyms, i) == '\0' ||
- is_mapping_symbol(kallsyms_symbol_name(kallsyms, i)))
+ if (!is_lookup_symbol(kallsyms, i))
continue;
if (thisval <= addr && thisval > bestval) {
@@ -458,6 +462,28 @@ unsigned long find_kallsyms_symbol_value(struct module *mod, const char *name)
return __find_kallsyms_symbol_value(mod, name);
}
+/*
+ * Call @fn with the address of each symbol of @mod that find_kallsyms_symbol()
+ * may resolve an address to. Unlike module_kallsyms_on_each_symbol(), this
+ * also works while @mod is still being loaded.
+ */
+void module_kallsyms_on_each_addr(struct module *mod,
+ void (*fn)(void *, unsigned long),
+ void *data)
+{
+ struct mod_kallsyms *kallsyms;
+ unsigned int i;
+
+ guard(rcu)();
+ kallsyms = rcu_dereference(mod->kallsyms);
+
+ /* ELF starts real symbols at 1. */
+ for (i = 1; i < kallsyms->num_symtab; i++) {
+ if (is_lookup_symbol(kallsyms, i))
+ fn(data, kallsyms_symbol_value(&kallsyms->symtab[i]));
+ }
+}
+
int module_kallsyms_on_each_symbol(const char *modname,
int (*fn)(void *, const char *, unsigned long),
void *data)
@@ -0,0 +1,113 @@
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -4445,11 +4445,6 @@ static int print_rec(struct seq_file *m, unsigned long ip)
return ret == NULL ? -1 : 0;
}
#else
-static inline int test_for_valid_rec(struct dyn_ftrace *rec)
-{
- return 1;
-}
-
static inline int print_rec(struct seq_file *m, unsigned long ip)
{
seq_printf(m, "%ps", (void *)ip);
@@ -7611,6 +7606,73 @@ static void test_is_sorted(unsigned long *start, unsigned long count)
}
#endif
+#ifdef FTRACE_MCOUNT_MAX_OFFSET
+struct ftrace_mod_locs {
+ unsigned long *start;
+ unsigned long count;
+ unsigned long *valid;
+};
+
+/* Mark the locations that lie at most FTRACE_MCOUNT_MAX_OFFSET after @addr. */
+static void ftrace_mark_valid_locs(void *data, unsigned long addr)
+{
+ struct ftrace_mod_locs *locs = data;
+ unsigned long lo = 0, hi = locs->count, mid, ip;
+
+ /*
+ * ftrace_call_adjust() moves a location forward by at most
+ * FTRACE_MCOUNT_MAX_OFFSET, so start looking that far before @addr.
+ */
+ while (lo < hi) {
+ mid = lo + (hi - lo) / 2;
+ if (locs->start[mid] + FTRACE_MCOUNT_MAX_OFFSET < addr)
+ lo = mid + 1;
+ else
+ hi = mid;
+ }
+
+ for (; lo < locs->count; lo++) {
+ if (locs->start[lo] > addr + FTRACE_MCOUNT_MAX_OFFSET)
+ break;
+ ip = ftrace_call_adjust(locs->start[lo]);
+ if (ip >= addr && ip - addr <= FTRACE_MCOUNT_MAX_OFFSET)
+ __set_bit(lo, locs->valid);
+ }
+}
+
+/*
+ * A weak function overridden within its module keeps its mcount location but
+ * has no symbol. Zero such locations before they become records, as sorttable
+ * does for vmlinux: keep only those with a symbol at most
+ * FTRACE_MCOUNT_MAX_OFFSET before them.
+ */
+static int ftrace_zero_weak_locs(struct module *mod, unsigned long *start,
+ unsigned long count)
+{
+ struct ftrace_mod_locs locs = { .start = start, .count = count };
+ unsigned long i;
+
+ locs.valid = bitmap_zalloc(count, GFP_KERNEL);
+ if (!locs.valid)
+ return -ENOMEM;
+
+ module_kallsyms_on_each_addr(mod, ftrace_mark_valid_locs, &locs);
+
+ for_each_clear_bit(i, locs.valid, count)
+ start[i] = 0;
+
+ bitmap_free(locs.valid);
+ return 0;
+}
+#else
+static inline int ftrace_zero_weak_locs(struct module *mod,
+ unsigned long *start,
+ unsigned long count)
+{
+ return 0;
+}
+#endif
+
static int ftrace_process_locs(struct module *mod,
unsigned long *start,
unsigned long *end)
@@ -7644,6 +7706,9 @@ static int ftrace_process_locs(struct module *mod,
test_is_sorted(start, count);
}
+ if (mod && ftrace_zero_weak_locs(mod, start, count))
+ return -ENOMEM;
+
start_pg = ftrace_allocate_pages(count, &pages);
if (!start_pg)
return -ENOMEM;
@@ -8049,13 +8114,6 @@ void ftrace_module_enable(struct module *mod)
cond_resched();
- /* Weak functions should still be ignored */
- if (!test_for_valid_rec(rec)) {
- /* Clear all other flags. Should not be enabled anyway */
- rec->flags = FTRACE_FL_DISABLED;
- continue;
- }
-
cnt = 0;
/*
+35 -5
View File
@@ -3,7 +3,7 @@
pkgbase=linux-omarchy-bore
pkgver=7.2.8
pkgrel=2
pkgrel=5
pkgdesc='Omarchy Linux (BORE CPU scheduler, ADIOS I/O scheduler)'
url='https://omarchy.org'
arch=(
@@ -165,6 +165,8 @@ source=(
0703-pci-vmd-scan-bios-enumerated-devices.patch{,.sig}
0704-pci-vmd-meteor-lake-mtl016-flush.patch{,.sig}
0705-pci-toshiba-ahci-ssd-dma-alias-quirk.patch{,.sig}
0711-iommu-amd-perfopt-support.patch{,.sig}
0712-drm-amdgpu-perfopt-in-identity-domain.patch{,.sig}
0740-hwmon-updates.patch{,.sig}
0741-hwmon-fixes-1.patch{,.sig}
0742-hwmon-fixes-2.patch{,.sig}
@@ -172,10 +174,14 @@ source=(
0800-platform-updates.patch{,.sig}
0801-amd-pmf-util-unbind-use-after-free.patch{,.sig}
0802-platform-fixes.patch{,.sig}
0803-acpi-battery-hook-api-acpi-prefix.patch{,.sig}
0804-acpi-battery-unified-hooks-acpi-sbs.patch{,.sig}
0805-hwmon-applesmc-charge-control-end-threshold.patch{,.sig}
0850-futex-wait-multiple.patch{,.sig}
0851-futex-wait-multiple-fixes.patch{,.sig}
0852-futex-wait-multiple-abi-fixes.patch{,.sig}
0860-ftrace-module-enable-sorted-symbol-search.patch{,.sig}
0860-module-add-module-kallsyms-on-each-addr.patch{,.sig}
0861-ftrace-drop-weak-function-locations-of-modules.patch{,.sig}
8204-mm-opportunistic-compaction.patch{,.sig}
8205-mm-hint-uses-allocation-order.patch{,.sig}
8206-mm-carry-order-and-hint-in-one-word.patch{,.sig}
@@ -442,6 +448,10 @@ b2sums=('5326dde778eb945f282740ef0d8765b46fbd198f1209cdfd7b91e5dac1312fc01f8bf0b
'SKIP'
'da96dbbe3b6b4e189cf7bdce14647bba07bbc5599ce28ed385a1c34d237ee7ae3ac595d2552444615f2b15c2b7eda42f40743d5bdebd443e3ec31e5743f59056'
'SKIP'
'cb2494c930b2c2c4cd6755494339c5f95ace87940b2b2e51928d98ea63262e4bf7a4245f629b8bb06e7592f45a0c379e5583c8695563011009fdb07f965d7c1a'
'SKIP'
'6083db5b92aa46bf54144b4d2fab368916974ef01334fbe934bded3818c5dfb4fc8d5d0e896bf1baddc29aac1c559f80dabc727082a757301117843def8089c8'
'SKIP'
'c25bff6e5be62aca887ae0781899e37a39e1aec90f0eb934ec5394abf560f4d02ad1fb3973a45d00058b46555bbc8d9febfcc8026e210f53a8864a91b3b80a9d'
'SKIP'
'e70e0077249bf008e069041a0239c6bb63075044cd0df3218649ff92a6a2d43467026886f2a6a70c70552268f6936764459d7cc0817a103b6a0f74c37c52c72d'
@@ -456,13 +466,21 @@ b2sums=('5326dde778eb945f282740ef0d8765b46fbd198f1209cdfd7b91e5dac1312fc01f8bf0b
'SKIP'
'4e4792acaf0455f41cb1541d20d37e7a39104aa2dfb505e9be29dc3e7fecf328557780982b32cd8f67f77594542f5b8104766a4e8b5ce2270c9c601728363b3f'
'SKIP'
'00417f024d2cc0b0fad28dec7f11c3054f5da7bf8c353d06fd1086be1926eacbe07ba924380157f270aac910d771f909f34beed0f60106f6ae60c96f58423b4a'
'SKIP'
'2c254a2df00e79f03b51d13846f232073e9276e022c7bf46bfe810cc98456d481535152afa9d915e0726e7701594453f9363b6190b8e616db203d176a08fd31f'
'SKIP'
'cbfa20431371f3332f28013bfa21beef21a5a6f616e09fa91e9adb42899cb2e1880e27cf4b90bba980bce26710af3ac3c1e49054df4e48051bcc4612732d3e9e'
'SKIP'
'aca0527f5630671a319e7ecff958a3a64f29df73b427c67c1d29763cc1801fa82e96761c3a61c22d936913943a49261da64d490a0752ba9ffc8e0c1d92f99835'
'SKIP'
'803736a8e836560b3e50b7c0c8cf1f9ebb16b8642cc7bc6e5167d19037bd757431565fe4581776e50eb2693c49f778a24fecff41a553612e7e498f755b8f2f67'
'SKIP'
'4bc132d9c34deaae9b13d08dcff8ea83aa8f9ce4398531e0e0a80df30da3808815f3a587fe5627f6553b8e9c2aea6cc1749b752250a91c94f1cf8ba4f3eb5e8d'
'SKIP'
'b76c03e8c59c0d2c06d00130cbe4984932762107f3d8180f90ac6c63bd435d6b8dfd317b9d1e756a89c513a20bdb52dcf4f3887daa6598449e560ca1076c5a1d'
'335c0c1164e9a2b9a9a252ddf7418ca0e6493f2f0fddcf3f01ae20666b6cb6713b8bf7a2a165d37485d2107b6c00f42b201974154c41c15c4456615b3bb0d78f'
'SKIP'
'01a28159b9c42e6b52147d1b061e1a2cb40ee381f8b6f7e2db3f94adc160365bc0dee602713cf42745fd2275b822b273df25d739d7b77f03273922edc746e50f'
'SKIP'
'42703ec988c2b1865fbe4eef67840f4370958e3e874d9f8a03a46c43619bed7656c56cae0ef1d8cca5340648b47ba7ddf813d25d9db3307f10146c45b8e34ade'
'SKIP'
@@ -482,7 +500,7 @@ b2sums=('5326dde778eb945f282740ef0d8765b46fbd198f1209cdfd7b91e5dac1312fc01f8bf0b
'SKIP'
'ae93d961d0eef41ed39c473fe9478b62fab13ba395333b3c11b34b7787904a3d2684520ec76dbe2603dd96a146a68bcbe98da1b7e4cc1e659246127384bce22c'
'SKIP')
b2sums_x86_64=('7bcc8b4bc054268c8aa7aec3c6db22d6773d4bca1322edc157c7d23095998eedb0c6698d042310fced5fd759347297c500d2fa987bd71684c8c043c395ce4fc8')
b2sums_x86_64=('a1961cda00700926c9618bd311b83bdbc1a392c1dde41d541de9f091b750121cc879560ffd1c398ab75078588e0677726eedb84529a13d66be934c85be5f7a73')
# https://www.kernel.org/pub/linux/kernel/v7.x/sha256sums.asc
sha256sums=('12e8d5a973d1ad7c5a5c69882e4022b131ed715db7003fdcd760ddf8c3e51941'
@@ -735,6 +753,10 @@ sha256sums=('12e8d5a973d1ad7c5a5c69882e4022b131ed715db7003fdcd760ddf8c3e51941'
'SKIP'
'3175c56fd2935848ce99fe10be1bc348231909f8479c3d3af911c5c7ab7f381b'
'SKIP'
'30a12a616afa8fe95b969e90b42aeb240962b9fab8b8bd357ac00706abdd68c7'
'SKIP'
'eb0ebb8c55332dab8931c667ec84cd32c91c657d21af4504327ae05643108e1d'
'SKIP'
'945c90d99f9a9fa47d61e471f76d17c0229ffa0fa8cda22de990a2ccc4194bb2'
'SKIP'
'70ff41b2341836c27c5545a7948258f137d991c2879cbeb29f7af740c11d20e6'
@@ -749,13 +771,21 @@ sha256sums=('12e8d5a973d1ad7c5a5c69882e4022b131ed715db7003fdcd760ddf8c3e51941'
'SKIP'
'a008c052ca657efcd4e27b8f7b819adbc9606e261aac37d41a40cf67c094cff9'
'SKIP'
'57169ef893dff957cbf33f7c0dc22f2fdfcfcb3befcd62ad59e2456484423bac'
'SKIP'
'01ecc1adbe715bb31153a2350ca46e1cb8a6e3fdc6899b2debeff015af2412ed'
'SKIP'
'a4331d3968fc5002534063b42bd219bddfe4a7d6b08e4cb30ed60f4366ca3c41'
'SKIP'
'028bf538c870465e1752514725372fa9eef6b7178d9b8d1e2ecc15edc5a63cc8'
'SKIP'
'a92a84405ee9845738c6b52d8f0f0eca16eb40afe0ebe0f4e9e1168597675311'
'SKIP'
'44a0b10a6dc83465ea86f5fba936b2bfbeffb1b3f0b2e04013bfed99ba4f6c5a'
'SKIP'
'ab4a43d7b8d56a4405aaebaac00dd33d2019cae407039fad4b0dae9ba1ffaa78'
'6f01b5357af7deed3f4bccea6678b4d9e8afc83e598978f42f244241667f975d'
'SKIP'
'0da7247035a359e2491933f9effff43ea7b29748f21a0e3080bef0018c2488e3'
'SKIP'
'f81f6b5769e47716ca318ecf76ab1c0fafa0e165fa858a64251359937bffcf04'
'SKIP'
@@ -670,6 +670,7 @@ CONFIG_ACPI_REV_OVERRIDE_POSSIBLE=y
CONFIG_ACPI_EC=y
CONFIG_ACPI_EC_DEBUGFS=m
CONFIG_ACPI_AC=y
CONFIG_ACPI_BATTERY_HOOKS=y
CONFIG_ACPI_BATTERY=y
CONFIG_ACPI_BUTTON=y
CONFIG_ACPI_VIDEO=m
@@ -0,0 +1,4 @@
{
"source": "local",
"skip_build": true
}
Loaded 100 of 325 files, more files were not shown because too many files have changed in this diff. Show more