From f02cfe6950479b04fb75cf8424a852af7c50d3ad Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 8 May 2024 14:41:49 +0300 Subject: [PATCH 001/152] HSI: ssi_protocol: Remove unused linux/gpio.h linux/gpio.h is deprecated and subject to remove. The driver doesn't use it, simply remove the unused header. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240508114150.958025-1-andriy.shevchenko@linux.intel.com Signed-off-by: Sebastian Reichel --- drivers/hsi/clients/ssi_protocol.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/hsi/clients/ssi_protocol.c b/drivers/hsi/clients/ssi_protocol.c index 10926359e6d2..afe470f3661c 100644 --- a/drivers/hsi/clients/ssi_protocol.c +++ b/drivers/hsi/clients/ssi_protocol.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include From ebacfa1f09c14b2bc5cb02c1a518e29edea1f476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Wed, 22 May 2024 18:48:31 +0200 Subject: [PATCH 002/152] power: supply: Drop explicit initialization of struct i2c_device_id::driver_data to 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These drivers don't use the driver_data member of struct i2c_device_id, so don't explicitly initialize this member. This prepares putting driver_data in an anonymous union which requires either no initialization or named designators. But it's also a nice cleanup on its own. While add it, also remove commas after the sentinel entries. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20240522164830.61646-2-u.kleine-koenig@pengutronix.de Signed-off-by: Sebastian Reichel --- drivers/power/supply/adp5061.c | 2 +- drivers/power/supply/bq24735-charger.c | 2 +- drivers/power/supply/bq25890_charger.c | 10 +++++----- drivers/power/supply/cw2015_battery.c | 2 +- drivers/power/supply/lp8727_charger.c | 2 +- drivers/power/supply/ltc4162-l-charger.c | 4 ++-- drivers/power/supply/max14656_charger_detector.c | 2 +- drivers/power/supply/max77976_charger.c | 4 ++-- drivers/power/supply/mm8013.c | 2 +- drivers/power/supply/rt9455_charger.c | 4 ++-- drivers/power/supply/sbs-charger.c | 2 +- drivers/power/supply/sbs-manager.c | 4 ++-- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/drivers/power/supply/adp5061.c b/drivers/power/supply/adp5061.c index 3e3a0d118ce5..dac9875d993c 100644 --- a/drivers/power/supply/adp5061.c +++ b/drivers/power/supply/adp5061.c @@ -727,7 +727,7 @@ static int adp5061_probe(struct i2c_client *client) } static const struct i2c_device_id adp5061_id[] = { - { "adp5061", 0}, + { "adp5061" }, { } }; MODULE_DEVICE_TABLE(i2c, adp5061_id); diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c index 8efceeae864c..73a7fc867b03 100644 --- a/drivers/power/supply/bq24735-charger.c +++ b/drivers/power/supply/bq24735-charger.c @@ -489,7 +489,7 @@ static int bq24735_charger_probe(struct i2c_client *client) } static const struct i2c_device_id bq24735_charger_id[] = { - { "bq24735-charger", 0 }, + { "bq24735-charger" }, {} }; MODULE_DEVICE_TABLE(i2c, bq24735_charger_id); diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 03fa11a1c9b6..2f5ceaf00b94 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -1617,11 +1617,11 @@ static const struct dev_pm_ops bq25890_pm = { }; static const struct i2c_device_id bq25890_i2c_ids[] = { - { "bq25890", 0 }, - { "bq25892", 0 }, - { "bq25895", 0 }, - { "bq25896", 0 }, - {}, + { "bq25890" }, + { "bq25892" }, + { "bq25895" }, + { "bq25896" }, + {} }; MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids); diff --git a/drivers/power/supply/cw2015_battery.c b/drivers/power/supply/cw2015_battery.c index 99f3ccdc30a6..f63c3c410451 100644 --- a/drivers/power/supply/cw2015_battery.c +++ b/drivers/power/supply/cw2015_battery.c @@ -731,7 +731,7 @@ static int __maybe_unused cw_bat_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(cw_bat_pm_ops, cw_bat_suspend, cw_bat_resume); static const struct i2c_device_id cw_bat_id_table[] = { - { "cw2015", 0 }, + { "cw2015" }, { } }; diff --git a/drivers/power/supply/lp8727_charger.c b/drivers/power/supply/lp8727_charger.c index 34548a4da90b..4186fcd37512 100644 --- a/drivers/power/supply/lp8727_charger.c +++ b/drivers/power/supply/lp8727_charger.c @@ -584,7 +584,7 @@ static const struct of_device_id lp8727_dt_ids[] __maybe_unused = { MODULE_DEVICE_TABLE(of, lp8727_dt_ids); static const struct i2c_device_id lp8727_ids[] = { - {"lp8727", 0}, + { "lp8727" }, { } }; MODULE_DEVICE_TABLE(i2c, lp8727_ids); diff --git a/drivers/power/supply/ltc4162-l-charger.c b/drivers/power/supply/ltc4162-l-charger.c index f0eace731480..2e4bc74e1c4a 100644 --- a/drivers/power/supply/ltc4162-l-charger.c +++ b/drivers/power/supply/ltc4162-l-charger.c @@ -903,8 +903,8 @@ static void ltc4162l_alert(struct i2c_client *client, } static const struct i2c_device_id ltc4162l_i2c_id_table[] = { - { "ltc4162-l", 0 }, - { }, + { "ltc4162-l" }, + { } }; MODULE_DEVICE_TABLE(i2c, ltc4162l_i2c_id_table); diff --git a/drivers/power/supply/max14656_charger_detector.c b/drivers/power/supply/max14656_charger_detector.c index 89f2af72dfcd..a5b42b42d134 100644 --- a/drivers/power/supply/max14656_charger_detector.c +++ b/drivers/power/supply/max14656_charger_detector.c @@ -300,7 +300,7 @@ static int max14656_probe(struct i2c_client *client) } static const struct i2c_device_id max14656_id[] = { - { "max14656", 0 }, + { "max14656" }, {} }; MODULE_DEVICE_TABLE(i2c, max14656_id); diff --git a/drivers/power/supply/max77976_charger.c b/drivers/power/supply/max77976_charger.c index 99659dc8f5a6..d7e520da7688 100644 --- a/drivers/power/supply/max77976_charger.c +++ b/drivers/power/supply/max77976_charger.c @@ -483,8 +483,8 @@ static int max77976_probe(struct i2c_client *client) } static const struct i2c_device_id max77976_i2c_id[] = { - { MAX77976_DRIVER_NAME, 0 }, - { }, + { MAX77976_DRIVER_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, max77976_i2c_id); diff --git a/drivers/power/supply/mm8013.c b/drivers/power/supply/mm8013.c index 20c1651ca38e..5bcfaeeda3db 100644 --- a/drivers/power/supply/mm8013.c +++ b/drivers/power/supply/mm8013.c @@ -284,7 +284,7 @@ static int mm8013_probe(struct i2c_client *client) } static const struct i2c_device_id mm8013_id_table[] = { - { "mm8013", 0 }, + { "mm8013" }, {} }; MODULE_DEVICE_TABLE(i2c, mm8013_id_table); diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c index e4dbacd50a43..64a23e3d7bb0 100644 --- a/drivers/power/supply/rt9455_charger.c +++ b/drivers/power/supply/rt9455_charger.c @@ -1718,8 +1718,8 @@ static void rt9455_remove(struct i2c_client *client) } static const struct i2c_device_id rt9455_i2c_id_table[] = { - { RT9455_DRIVER_NAME, 0 }, - { }, + { RT9455_DRIVER_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, rt9455_i2c_id_table); diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c index f4adde449270..ab3f095d90ea 100644 --- a/drivers/power/supply/sbs-charger.c +++ b/drivers/power/supply/sbs-charger.c @@ -234,7 +234,7 @@ MODULE_DEVICE_TABLE(of, sbs_dt_ids); #endif static const struct i2c_device_id sbs_id[] = { - { "sbs-charger", 0 }, + { "sbs-charger" }, { } }; MODULE_DEVICE_TABLE(i2c, sbs_id); diff --git a/drivers/power/supply/sbs-manager.c b/drivers/power/supply/sbs-manager.c index 933b04806d10..7d2f39f19acb 100644 --- a/drivers/power/supply/sbs-manager.c +++ b/drivers/power/supply/sbs-manager.c @@ -389,8 +389,8 @@ static int sbsm_probe(struct i2c_client *client) } static const struct i2c_device_id sbsm_ids[] = { - { "sbs-manager", 0 }, - { "ltc1760", 0 }, + { "sbs-manager" }, + { "ltc1760" }, { } }; MODULE_DEVICE_TABLE(i2c, sbsm_ids); From 964a504b1261b641797d93cdbf79b68ff7d85720 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 31 May 2024 15:47:00 +0200 Subject: [PATCH 003/152] power: supply: leds: Add power_supply_[un]register_led_trigger() Add power_supply_[un]register_led_trigger() helper functions. The primary goal of this is as a preparation patch for adding an activate callback to the power-supply LED triggers to ensure that power-supply LEDs get the correct initial value when the LED gets registered after the power_supply has been registered (this will use the psy back pointer). There also is quite a lot of code duplication in the existing LED trigger registration in the form of the kasprintf() for the name-template for each trigger + related error handling. This duplication is removed by these new helpers. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240531134702.166145-2-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_leds.c | 157 +++++++++++++---------- include/linux/power_supply.h | 6 - 2 files changed, 86 insertions(+), 77 deletions(-) diff --git a/drivers/power/supply/power_supply_leds.c b/drivers/power/supply/power_supply_leds.c index 73935de844d9..e3f65ff2f064 100644 --- a/drivers/power/supply/power_supply_leds.c +++ b/drivers/power/supply/power_supply_leds.c @@ -19,6 +19,65 @@ /* Battery specific LEDs triggers. */ +struct power_supply_led_trigger { + struct led_trigger trig; + struct power_supply *psy; +}; + +#define trigger_to_psy_trigger(trigger) \ + container_of(trigger, struct power_supply_led_trigger, trig) + +static int power_supply_register_led_trigger(struct power_supply *psy, + const char *name_template, + struct led_trigger **tp, int *err) +{ + struct power_supply_led_trigger *psy_trig; + int ret = -ENOMEM; + + /* Bail on previous errors */ + if (err && *err) + return *err; + + psy_trig = kzalloc(sizeof(*psy_trig), GFP_KERNEL); + if (!psy_trig) + goto err_free_trigger; + + psy_trig->trig.name = kasprintf(GFP_KERNEL, name_template, psy->desc->name); + if (!psy_trig->trig.name) + goto err_free_trigger; + + psy_trig->psy = psy; + + ret = led_trigger_register(&psy_trig->trig); + if (ret) + goto err_free_name; + + *tp = &psy_trig->trig; + return 0; + +err_free_name: + kfree(psy_trig->trig.name); +err_free_trigger: + kfree(psy_trig); + if (err) + *err = ret; + + return ret; +} + +static void power_supply_unregister_led_trigger(struct led_trigger *trig) +{ + struct power_supply_led_trigger *psy_trig; + + if (!trig) + return; + + psy_trig = trigger_to_psy_trigger(trig); + led_trigger_unregister(&psy_trig->trig); + kfree(psy_trig->trig.name); + kfree(psy_trig); +} + static void power_supply_update_bat_leds(struct power_supply *psy) { union power_supply_propval status; @@ -65,69 +124,33 @@ static void power_supply_update_bat_leds(struct power_supply *psy) } } -static int power_supply_create_bat_triggers(struct power_supply *psy) -{ - psy->charging_full_trig_name = kasprintf(GFP_KERNEL, - "%s-charging-or-full", psy->desc->name); - if (!psy->charging_full_trig_name) - goto charging_full_failed; - - psy->charging_trig_name = kasprintf(GFP_KERNEL, - "%s-charging", psy->desc->name); - if (!psy->charging_trig_name) - goto charging_failed; - - psy->full_trig_name = kasprintf(GFP_KERNEL, "%s-full", psy->desc->name); - if (!psy->full_trig_name) - goto full_failed; - - psy->charging_blink_full_solid_trig_name = kasprintf(GFP_KERNEL, - "%s-charging-blink-full-solid", psy->desc->name); - if (!psy->charging_blink_full_solid_trig_name) - goto charging_blink_full_solid_failed; - - psy->charging_orange_full_green_trig_name = kasprintf(GFP_KERNEL, - "%s-charging-orange-full-green", psy->desc->name); - if (!psy->charging_orange_full_green_trig_name) - goto charging_red_full_green_failed; - - led_trigger_register_simple(psy->charging_full_trig_name, - &psy->charging_full_trig); - led_trigger_register_simple(psy->charging_trig_name, - &psy->charging_trig); - led_trigger_register_simple(psy->full_trig_name, - &psy->full_trig); - led_trigger_register_simple(psy->charging_blink_full_solid_trig_name, - &psy->charging_blink_full_solid_trig); - led_trigger_register_simple(psy->charging_orange_full_green_trig_name, - &psy->charging_orange_full_green_trig); - - return 0; - -charging_red_full_green_failed: - kfree(psy->charging_blink_full_solid_trig_name); -charging_blink_full_solid_failed: - kfree(psy->full_trig_name); -full_failed: - kfree(psy->charging_trig_name); -charging_failed: - kfree(psy->charging_full_trig_name); -charging_full_failed: - return -ENOMEM; -} - static void power_supply_remove_bat_triggers(struct power_supply *psy) { - led_trigger_unregister_simple(psy->charging_full_trig); - led_trigger_unregister_simple(psy->charging_trig); - led_trigger_unregister_simple(psy->full_trig); - led_trigger_unregister_simple(psy->charging_blink_full_solid_trig); - led_trigger_unregister_simple(psy->charging_orange_full_green_trig); - kfree(psy->charging_blink_full_solid_trig_name); - kfree(psy->full_trig_name); - kfree(psy->charging_trig_name); - kfree(psy->charging_full_trig_name); - kfree(psy->charging_orange_full_green_trig_name); + power_supply_unregister_led_trigger(psy->charging_full_trig); + power_supply_unregister_led_trigger(psy->charging_trig); + power_supply_unregister_led_trigger(psy->full_trig); + power_supply_unregister_led_trigger(psy->charging_blink_full_solid_trig); + power_supply_unregister_led_trigger(psy->charging_orange_full_green_trig); +} + +static int power_supply_create_bat_triggers(struct power_supply *psy) +{ + int err = 0; + + power_supply_register_led_trigger(psy, "%s-charging-or-full", + &psy->charging_full_trig, &err); + power_supply_register_led_trigger(psy, "%s-charging", + &psy->charging_trig, &err); + power_supply_register_led_trigger(psy, "%s-full", + &psy->full_trig, &err); + power_supply_register_led_trigger(psy, "%s-charging-blink-full-solid", + &psy->charging_blink_full_solid_trig, &err); + power_supply_register_led_trigger(psy, "%s-charging-orange-full-green", + &psy->charging_orange_full_green_trig, &err); + if (err) + power_supply_remove_bat_triggers(psy); + + return err; } /* Generated power specific LEDs triggers. */ @@ -149,20 +172,12 @@ static void power_supply_update_gen_leds(struct power_supply *psy) static int power_supply_create_gen_triggers(struct power_supply *psy) { - psy->online_trig_name = kasprintf(GFP_KERNEL, "%s-online", - psy->desc->name); - if (!psy->online_trig_name) - return -ENOMEM; - - led_trigger_register_simple(psy->online_trig_name, &psy->online_trig); - - return 0; + return power_supply_register_led_trigger(psy, "%s-online", &psy->online_trig, NULL); } static void power_supply_remove_gen_triggers(struct power_supply *psy) { - led_trigger_unregister_simple(psy->online_trig); - kfree(psy->online_trig_name); + power_supply_unregister_led_trigger(psy->online_trig); } /* Choice what triggers to create&update. */ diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index c852cc882501..e218a8ab72ee 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -310,17 +310,11 @@ struct power_supply { #ifdef CONFIG_LEDS_TRIGGERS struct led_trigger *charging_full_trig; - char *charging_full_trig_name; struct led_trigger *charging_trig; - char *charging_trig_name; struct led_trigger *full_trig; - char *full_trig_name; struct led_trigger *online_trig; - char *online_trig_name; struct led_trigger *charging_blink_full_solid_trig; - char *charging_blink_full_solid_trig_name; struct led_trigger *charging_orange_full_green_trig; - char *charging_orange_full_green_trig_name; #endif }; From 6c951a84dab9c0e051aec54d7497f25e910a4953 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 31 May 2024 15:47:01 +0200 Subject: [PATCH 004/152] power: supply: leds: Share trig pointer for online and charging_full Either 5 different LED triggers are registered for battery power-supply devices or a single online LED trigger is used for non battery power-supply devices. These 5 / 1 LED trigger(s) are never used at the same time. So there is no need for a separate LED trigger pointer for the online trigger. Rename the first battery trigger from charging_full_trig to just trig and use this for the online trigger too. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240531134702.166145-3-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_leds.c | 18 +++++++++--------- include/linux/power_supply.h | 3 +-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/power/supply/power_supply_leds.c b/drivers/power/supply/power_supply_leds.c index e3f65ff2f064..f2d6b8d33707 100644 --- a/drivers/power/supply/power_supply_leds.c +++ b/drivers/power/supply/power_supply_leds.c @@ -91,7 +91,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy) switch (status.intval) { case POWER_SUPPLY_STATUS_FULL: - led_trigger_event(psy->charging_full_trig, LED_FULL); + led_trigger_event(psy->trig, LED_FULL); led_trigger_event(psy->charging_trig, LED_OFF); led_trigger_event(psy->full_trig, LED_FULL); /* Going from blink to LED on requires a LED_OFF event to stop blink */ @@ -103,7 +103,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy) LED_FULL); break; case POWER_SUPPLY_STATUS_CHARGING: - led_trigger_event(psy->charging_full_trig, LED_FULL); + led_trigger_event(psy->trig, LED_FULL); led_trigger_event(psy->charging_trig, LED_FULL); led_trigger_event(psy->full_trig, LED_OFF); led_trigger_blink(psy->charging_blink_full_solid_trig, 0, 0); @@ -113,7 +113,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy) LED_FULL); break; default: - led_trigger_event(psy->charging_full_trig, LED_OFF); + led_trigger_event(psy->trig, LED_OFF); led_trigger_event(psy->charging_trig, LED_OFF); led_trigger_event(psy->full_trig, LED_OFF); led_trigger_event(psy->charging_blink_full_solid_trig, @@ -126,7 +126,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy) static void power_supply_remove_bat_triggers(struct power_supply *psy) { - power_supply_unregister_led_trigger(psy->charging_full_trig); + power_supply_unregister_led_trigger(psy->trig); power_supply_unregister_led_trigger(psy->charging_trig); power_supply_unregister_led_trigger(psy->full_trig); power_supply_unregister_led_trigger(psy->charging_blink_full_solid_trig); @@ -138,7 +138,7 @@ static int power_supply_create_bat_triggers(struct power_supply *psy) int err = 0; power_supply_register_led_trigger(psy, "%s-charging-or-full", - &psy->charging_full_trig, &err); + &psy->trig, &err); power_supply_register_led_trigger(psy, "%s-charging", &psy->charging_trig, &err); power_supply_register_led_trigger(psy, "%s-full", @@ -165,19 +165,19 @@ static void power_supply_update_gen_leds(struct power_supply *psy) dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval); if (online.intval) - led_trigger_event(psy->online_trig, LED_FULL); + led_trigger_event(psy->trig, LED_FULL); else - led_trigger_event(psy->online_trig, LED_OFF); + led_trigger_event(psy->trig, LED_OFF); } static int power_supply_create_gen_triggers(struct power_supply *psy) { - return power_supply_register_led_trigger(psy, "%s-online", &psy->online_trig, NULL); + return power_supply_register_led_trigger(psy, "%s-online", &psy->trig, NULL); } static void power_supply_remove_gen_triggers(struct power_supply *psy) { - power_supply_unregister_led_trigger(psy->online_trig); + power_supply_unregister_led_trigger(psy->trig); } /* Choice what triggers to create&update. */ diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index e218a8ab72ee..65082ef75692 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -309,10 +309,9 @@ struct power_supply { #endif #ifdef CONFIG_LEDS_TRIGGERS - struct led_trigger *charging_full_trig; + struct led_trigger *trig; struct led_trigger *charging_trig; struct led_trigger *full_trig; - struct led_trigger *online_trig; struct led_trigger *charging_blink_full_solid_trig; struct led_trigger *charging_orange_full_green_trig; #endif From 8179b068d5cb182a2c61f2f8365837a37f140feb Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 31 May 2024 15:47:02 +0200 Subject: [PATCH 005/152] power: supply: leds: Add activate() callback to triggers Add an activate() callback to the power-supply LED triggers so that the LED being activated will properly reflect the current power-supply state for power-supply devices which are already fully registered when the trigger gets activated. This fixes e.g. wrong LED state (1) when the LED gets registered after the power-supply device. 1) Until the psy driver has a reason to call power_supply_changed() which may take quite a while Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20240531134702.166145-4-hdegoede@redhat.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_leds.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/power/supply/power_supply_leds.c b/drivers/power/supply/power_supply_leds.c index f2d6b8d33707..f4a7e566bea1 100644 --- a/drivers/power/supply/power_supply_leds.c +++ b/drivers/power/supply/power_supply_leds.c @@ -27,6 +27,16 @@ struct power_supply_led_trigger { #define trigger_to_psy_trigger(trigger) \ container_of(trigger, struct power_supply_led_trigger, trig) +static int power_supply_led_trigger_activate(struct led_classdev *led_cdev) +{ + struct power_supply_led_trigger *psy_trig = + trigger_to_psy_trigger(led_cdev->trigger); + + /* Sync current power-supply state to LED being activated */ + power_supply_update_leds(psy_trig->psy); + return 0; +} + static int power_supply_register_led_trigger(struct power_supply *psy, const char *name_template, struct led_trigger **tp, int *err) @@ -46,6 +56,7 @@ static int power_supply_register_led_trigger(struct power_supply *psy, if (!psy_trig->trig.name) goto err_free_trigger; + psy_trig->trig.activate = power_supply_led_trigger_activate; psy_trig->psy = psy; ret = led_trigger_register(&psy_trig->trig); From ef1f6783fe03bee6acc15ef7084ab8b64c38d769 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Tue, 28 May 2024 01:06:33 +0100 Subject: [PATCH 006/152] power: supply: bd99954: remove unused struct 'battery_data' 'battery_data' is unused since the original commit 0902f8366491 ("power: supply: Support ROHM bd99954 charger"). Remove it. Signed-off-by: Dr. David Alan Gilbert Acked-by: Matti Vaittinen Link: https://lore.kernel.org/r/20240528000634.196707-2-linux@treblig.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/bd99954-charger.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/power/supply/bd99954-charger.c b/drivers/power/supply/bd99954-charger.c index 1ed1d9f99fb3..54bf88262510 100644 --- a/drivers/power/supply/bd99954-charger.c +++ b/drivers/power/supply/bd99954-charger.c @@ -70,13 +70,6 @@ #include "bd99954-charger.h" -struct battery_data { - u16 precharge_current; /* Trickle-charge Current */ - u16 fc_reg_voltage; /* Fast Charging Regulation Voltage */ - u16 voltage_min; - u16 voltage_max; -}; - /* Initial field values, converted to initial register values */ struct bd9995x_init_data { u16 vsysreg_set; /* VSYS Regulation Setting */ From a5dd84f3bde0252cb39a3ecebbe71d7c2b405194 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Tue, 28 May 2024 01:06:34 +0100 Subject: [PATCH 007/152] power: supply: ab8500: remove unused struct 'inst_curr_result_list' 'inst_curr_result_list' is unused since the original commit 13151631b5bd ("ab8500-fg: A8500 fuel gauge driver"). Remove it. Signed-off-by: Dr. David Alan Gilbert Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/20240528000634.196707-3-linux@treblig.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_fg.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 2ccaf6116c09..270874eeb934 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -149,11 +149,6 @@ struct ab8500_fg_flags { bool batt_id_received; }; -struct inst_curr_result_list { - struct list_head list; - int *result; -}; - /** * struct ab8500_fg - ab8500 FG device information * @dev: Pointer to the structure device From ad175de1f8da6043553dec4402fc1ff28494ac7b Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 29 May 2024 00:21:15 +0200 Subject: [PATCH 008/152] power: supply: hwmon: Add support for power sensors Currently, more than seven power supply drivers are supporting POWER_SUPPLY_PROP_POWER_NOW, but their power readings are not being reported through the hwmon subsystem. Fix this by adding support for power sensors to the power supply hwmon integration. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20240528222115.791511-1-W_Armin@gmx.de Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_hwmon.c | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/power/supply/power_supply_hwmon.c b/drivers/power/supply/power_supply_hwmon.c index c97893d4c25e..baacefbdf768 100644 --- a/drivers/power/supply/power_supply_hwmon.c +++ b/drivers/power/supply/power_supply_hwmon.c @@ -48,6 +48,18 @@ static int power_supply_hwmon_curr_to_property(u32 attr) } } +static int power_supply_hwmon_power_to_property(u32 attr) +{ + switch (attr) { + case hwmon_power_input: + return POWER_SUPPLY_PROP_POWER_NOW; + case hwmon_power_average: + return POWER_SUPPLY_PROP_POWER_AVG; + default: + return -EINVAL; + } +} + static int power_supply_hwmon_temp_to_property(u32 attr, int channel) { if (channel) { @@ -90,6 +102,8 @@ power_supply_hwmon_to_property(enum hwmon_sensor_types type, return power_supply_hwmon_in_to_property(attr); case hwmon_curr: return power_supply_hwmon_curr_to_property(attr); + case hwmon_power: + return power_supply_hwmon_power_to_property(attr); case hwmon_temp: return power_supply_hwmon_temp_to_property(attr, channel); default: @@ -229,6 +243,11 @@ power_supply_hwmon_read(struct device *dev, enum hwmon_sensor_types type, case hwmon_in: pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 1000); break; + case hwmon_power: + /* + * Power properties are already in microwatts. + */ + break; /* * Temp needs to be converted from 1/10 C to milli-C */ @@ -311,6 +330,10 @@ static const struct hwmon_channel_info * const power_supply_hwmon_info[] = { HWMON_C_MAX | HWMON_C_INPUT), + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | + HWMON_P_AVERAGE), + HWMON_CHANNEL_INFO(in, HWMON_I_AVERAGE | HWMON_I_MIN | @@ -359,6 +382,8 @@ int power_supply_add_hwmon_sysfs(struct power_supply *psy) case POWER_SUPPLY_PROP_CURRENT_AVG: case POWER_SUPPLY_PROP_CURRENT_MAX: case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_POWER_AVG: + case POWER_SUPPLY_PROP_POWER_NOW: case POWER_SUPPLY_PROP_TEMP: case POWER_SUPPLY_PROP_TEMP_MAX: case POWER_SUPPLY_PROP_TEMP_MIN: From 540588772ed0b191969c7902bf90d561ab0035be Mon Sep 17 00:00:00 2001 From: Adrian Huang Date: Wed, 15 May 2024 18:06:32 +0800 Subject: [PATCH 009/152] genirq/proc: Simplify irqdesc::kstat_irqs handling further Interrupts which have no action and chained interrupts can be ignored due to the following reasons (as per tglx's comment): 1) Interrupts which have no action are completely uninteresting as there is no real information attached. 2) Chained interrupts do not have a count at all. So there is no point to evaluate the number of accounted interrupts before checking for non-requested or chained interrupts. Remove the any_count logic and simply check whether the interrupt descriptor has the kstat_irqs member populated. [ tglx: Adapted to upstream changes ] Suggested-by: Thomas Gleixner Signed-off-by: Adrian Huang Signed-off-by: Thomas Gleixner Tested-by: Jiwei Sun Link: https://lore.kernel.org/r/20240515100632.1419-1-ahuang12@lenovo.com Link: https://lore.kernel.org/lkml/87h6f0knau.ffs@tglx/ --- kernel/irq/proc.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/kernel/irq/proc.c b/kernel/irq/proc.c index 5c320c3f10a7..8cccdf40725a 100644 --- a/kernel/irq/proc.c +++ b/kernel/irq/proc.c @@ -461,10 +461,10 @@ int show_interrupts(struct seq_file *p, void *v) { static int prec; - unsigned long flags, any_count = 0; int i = *(loff_t *) v, j; struct irqaction *action; struct irq_desc *desc; + unsigned long flags; if (i > ACTUAL_NR_IRQS) return 0; @@ -488,10 +488,7 @@ int show_interrupts(struct seq_file *p, void *v) if (!desc || irq_settings_is_hidden(desc)) goto outsparse; - if (desc->kstat_irqs) - any_count = kstat_irqs_desc(desc, cpu_online_mask); - - if ((!desc->action || irq_desc_is_chained(desc)) && !any_count) + if (!desc->action || irq_desc_is_chained(desc) || !desc->kstat_irqs) goto outsparse; seq_printf(p, "%*d: ", prec, i); From cb06c9826991c746039d076df10d40819f88a6bc Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Wed, 29 May 2024 09:16:28 +0000 Subject: [PATCH 010/152] genirq/debugfs: Print irqdomain flags as human-readable strings Improve the readability of irqdomain debugging information in debugfs by printing the flags field of domain files as human-readable strings instead of a raw bitmask, which aligned with the existing style used for irqchip flags in the irq debug files. Before: #cat :cpus:cpu@0:interrupt-controller name: :cpus:cpu@0:interrupt-controller size: 0 mapped: 2 flags: 0x00000003 After: #cat :cpus:cpu@0:interrupt-controller name: :cpus:cpu@0:interrupt-controller size: 0 mapped: 3 flags: 0x00000003 IRQ_DOMAIN_FLAG_HIERARCHY IRQ_DOMAIN_NAME_ALLOCATED Signed-off-by: Jinjie Ruan Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240529091628.3666379-1-ruanjinjie@huawei.com --- kernel/irq/debugfs.c | 10 ++-------- kernel/irq/internals.h | 10 ++++++++++ kernel/irq/irqdomain.c | 17 +++++++++++++++-- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/kernel/irq/debugfs.c b/kernel/irq/debugfs.c index aae0402507ed..c6ffb97966be 100644 --- a/kernel/irq/debugfs.c +++ b/kernel/irq/debugfs.c @@ -9,14 +9,8 @@ static struct dentry *irq_dir; -struct irq_bit_descr { - unsigned int mask; - char *name; -}; -#define BIT_MASK_DESCR(m) { .mask = m, .name = #m } - -static void irq_debug_show_bits(struct seq_file *m, int ind, unsigned int state, - const struct irq_bit_descr *sd, int size) +void irq_debug_show_bits(struct seq_file *m, int ind, unsigned int state, + const struct irq_bit_descr *sd, int size) { int i; diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index ed28059e9849..fe0272cd84a5 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -501,6 +501,16 @@ static inline struct irq_data *irqd_get_parent_data(struct irq_data *irqd) #ifdef CONFIG_GENERIC_IRQ_DEBUGFS #include +struct irq_bit_descr { + unsigned int mask; + char *name; +}; + +#define BIT_MASK_DESCR(m) { .mask = m, .name = #m } + +void irq_debug_show_bits(struct seq_file *m, int ind, unsigned int state, + const struct irq_bit_descr *sd, int size); + void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *desc); static inline void irq_remove_debugfs_entry(struct irq_desc *desc) { diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index aadc8891cc16..d937231a0865 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -1932,13 +1932,26 @@ static void irq_domain_free_one_irq(struct irq_domain *domain, unsigned int virq static struct dentry *domain_dir; -static void -irq_domain_debug_show_one(struct seq_file *m, struct irq_domain *d, int ind) +static const struct irq_bit_descr irqdomain_flags[] = { + BIT_MASK_DESCR(IRQ_DOMAIN_FLAG_HIERARCHY), + BIT_MASK_DESCR(IRQ_DOMAIN_NAME_ALLOCATED), + BIT_MASK_DESCR(IRQ_DOMAIN_FLAG_IPI_PER_CPU), + BIT_MASK_DESCR(IRQ_DOMAIN_FLAG_IPI_SINGLE), + BIT_MASK_DESCR(IRQ_DOMAIN_FLAG_MSI), + BIT_MASK_DESCR(IRQ_DOMAIN_FLAG_ISOLATED_MSI), + BIT_MASK_DESCR(IRQ_DOMAIN_FLAG_NO_MAP), + BIT_MASK_DESCR(IRQ_DOMAIN_FLAG_MSI_PARENT), + BIT_MASK_DESCR(IRQ_DOMAIN_FLAG_MSI_DEVICE), + BIT_MASK_DESCR(IRQ_DOMAIN_FLAG_NONCORE), +}; + +static void irq_domain_debug_show_one(struct seq_file *m, struct irq_domain *d, int ind) { seq_printf(m, "%*sname: %s\n", ind, "", d->name); seq_printf(m, "%*ssize: %u\n", ind + 1, "", d->revmap_size); seq_printf(m, "%*smapped: %u\n", ind + 1, "", d->mapcount); seq_printf(m, "%*sflags: 0x%08x\n", ind +1 , "", d->flags); + irq_debug_show_bits(m, ind, d->flags, irqdomain_flags, ARRAY_SIZE(irqdomain_flags)); if (d->ops && d->ops->debug_show) d->ops->debug_show(m, d, NULL, ind + 1); #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY From b4dc049ea3ea98df58820f988c7c9578aa076f72 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Mon, 27 May 2024 18:14:36 +0200 Subject: [PATCH 011/152] irqdomain: Add missing parameter descriptions in kernel-doc comments During compilation, several warning of the following form were raised: Function parameter or struct member 'x' not described in 'yyy' Add the missing function parameter descriptions. Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240527161450.326615-10-herve.codina@bootlin.com --- kernel/irq/irqdomain.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index d937231a0865..28709c14d894 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -111,6 +111,7 @@ EXPORT_SYMBOL_GPL(__irq_domain_alloc_fwnode); /** * irq_domain_free_fwnode - Free a non-OF-backed fwnode_handle + * @fwnode: fwnode_handle to free * * Free a fwnode_handle allocated with irq_domain_alloc_fwnode. */ @@ -982,6 +983,12 @@ EXPORT_SYMBOL_GPL(__irq_resolve_mapping); /** * irq_domain_xlate_onecell() - Generic xlate for direct one cell bindings + * @d: Interrupt domain involved in the translation + * @ctrlr: The device tree node for the device whose interrupt is translated + * @intspec: The interrupt specifier data from the device tree + * @intsize: The number of entries in @intspec + * @out_hwirq: Pointer to storage for the hardware interrupt number + * @out_type: Pointer to storage for the interrupt type * * Device Tree IRQ specifier translation function which works with one cell * bindings where the cell value maps directly to the hwirq number. @@ -1000,6 +1007,12 @@ EXPORT_SYMBOL_GPL(irq_domain_xlate_onecell); /** * irq_domain_xlate_twocell() - Generic xlate for direct two cell bindings + * @d: Interrupt domain involved in the translation + * @ctrlr: The device tree node for the device whose interrupt is translated + * @intspec: The interrupt specifier data from the device tree + * @intsize: The number of entries in @intspec + * @out_hwirq: Pointer to storage for the hardware interrupt number + * @out_type: Pointer to storage for the interrupt type * * Device Tree IRQ specifier translation function which works with two cell * bindings where the cell values map directly to the hwirq number @@ -1018,6 +1031,12 @@ EXPORT_SYMBOL_GPL(irq_domain_xlate_twocell); /** * irq_domain_xlate_onetwocell() - Generic xlate for one or two cell bindings + * @d: Interrupt domain involved in the translation + * @ctrlr: The device tree node for the device whose interrupt is translated + * @intspec: The interrupt specifier data from the device tree + * @intsize: The number of entries in @intspec + * @out_hwirq: Pointer to storage for the hardware interrupt number + * @out_type: Pointer to storage for the interrupt type * * Device Tree IRQ specifier translation function which works with either one * or two cell bindings where the cell values map directly to the hwirq number @@ -1051,6 +1070,10 @@ EXPORT_SYMBOL_GPL(irq_domain_simple_ops); /** * irq_domain_translate_onecell() - Generic translate for direct one cell * bindings + * @d: Interrupt domain involved in the translation + * @fwspec: The firmware interrupt specifier to translate + * @out_hwirq: Pointer to storage for the hardware interrupt number + * @out_type: Pointer to storage for the interrupt type */ int irq_domain_translate_onecell(struct irq_domain *d, struct irq_fwspec *fwspec, @@ -1068,6 +1091,10 @@ EXPORT_SYMBOL_GPL(irq_domain_translate_onecell); /** * irq_domain_translate_twocell() - Generic translate for direct two cell * bindings + * @d: Interrupt domain involved in the translation + * @fwspec: The firmware interrupt specifier to translate + * @out_hwirq: Pointer to storage for the hardware interrupt number + * @out_type: Pointer to storage for the interrupt type * * Device Tree IRQ specifier translation function which works with two cell * bindings where the cell values map directly to the hwirq number From 372487b295557b6c0c7ba3583fb34a65c574ff9f Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 4 Jun 2024 18:37:09 +0100 Subject: [PATCH 012/152] dt-bindings: interrupt-controller: renesas,rzg2l-irqc: Document RZ/Five SoC Document RZ/Five (R9A07G043F) IRQC bindings. The IRQC block on the RZ/Five SoC is almost identical to the one found on the RZ/G2L SoC, with the only difference being that it has additional mask control registers for NMI/IRQ/TINT. Hence new compatible string "renesas,r9a07g043f-irqc" is added for RZ/Five SoC. Signed-off-by: Lad Prabhakar Signed-off-by: Thomas Gleixner Reviewed-by: Geert Uytterhoeven Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20240604173710.534132-2-prabhakar.mahadev-lad.rj@bp.renesas.com --- .../renesas,rzg2l-irqc.yaml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/interrupt-controller/renesas,rzg2l-irqc.yaml b/Documentation/devicetree/bindings/interrupt-controller/renesas,rzg2l-irqc.yaml index daef4ee06f4e..44b6ae5fc802 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/renesas,rzg2l-irqc.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/renesas,rzg2l-irqc.yaml @@ -21,13 +21,16 @@ description: | properties: compatible: - items: - - enum: - - renesas,r9a07g043u-irqc # RZ/G2UL - - renesas,r9a07g044-irqc # RZ/G2{L,LC} - - renesas,r9a07g054-irqc # RZ/V2L - - renesas,r9a08g045-irqc # RZ/G3S - - const: renesas,rzg2l-irqc + oneOf: + - items: + - enum: + - renesas,r9a07g043u-irqc # RZ/G2UL + - renesas,r9a07g044-irqc # RZ/G2{L,LC} + - renesas,r9a07g054-irqc # RZ/V2L + - renesas,r9a08g045-irqc # RZ/G3S + - const: renesas,rzg2l-irqc + + - const: renesas,r9a07g043f-irqc # RZ/Five '#interrupt-cells': description: The first cell should contain a macro RZG2L_{NMI,IRQX} included in the From d011c022efe275791897668aa421e2db9f2e6450 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 4 Jun 2024 18:37:10 +0100 Subject: [PATCH 013/152] irqchip/renesas-rzg2l: Add support for RZ/Five SoC The IX45 block has additional mask registers (NMSK/IMSK/TMSK) compared to the RZ/G2L (family) SoC. A new rzfive_irqc_chip irq_chip is introduced for RZ/Five, where function pointers for irq_[un]mask() and irq_[dis|en]able() handle the ([un]masking of the interrupts. The irq_chip pointer is now passed as an init callback and stored in the priv pointer to differentiate between RZ/G2L and RZ/Five. Signed-off-by: Lad Prabhakar Signed-off-by: Thomas Gleixner Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20240604173710.534132-3-prabhakar.mahadev-lad.rj@bp.renesas.com --- drivers/irqchip/irq-renesas-rzg2l.c | 148 +++++++++++++++++++++++++++- 1 file changed, 145 insertions(+), 3 deletions(-) diff --git a/drivers/irqchip/irq-renesas-rzg2l.c b/drivers/irqchip/irq-renesas-rzg2l.c index f6484bf15e0b..861a0e5a3e97 100644 --- a/drivers/irqchip/irq-renesas-rzg2l.c +++ b/drivers/irqchip/irq-renesas-rzg2l.c @@ -37,6 +37,8 @@ #define TSSEL_SHIFT(n) (8 * (n)) #define TSSEL_MASK GENMASK(7, 0) #define IRQ_MASK 0x3 +#define IMSK 0x10010 +#define TMSK 0x10020 #define TSSR_OFFSET(n) ((n) % 4) #define TSSR_INDEX(n) ((n) / 4) @@ -69,12 +71,14 @@ struct rzg2l_irqc_reg_cache { /** * struct rzg2l_irqc_priv - IRQ controller private data structure * @base: Controller's base address + * @irqchip: Pointer to struct irq_chip * @fwspec: IRQ firmware specific data * @lock: Lock to serialize access to hardware registers * @cache: Registers cache for suspend/resume */ static struct rzg2l_irqc_priv { void __iomem *base; + const struct irq_chip *irqchip; struct irq_fwspec fwspec[IRQC_NUM_IRQ]; raw_spinlock_t lock; struct rzg2l_irqc_reg_cache cache; @@ -138,6 +142,111 @@ static void rzg2l_irqc_eoi(struct irq_data *d) irq_chip_eoi_parent(d); } +static void rzfive_irqc_mask_irq_interrupt(struct rzg2l_irqc_priv *priv, + unsigned int hwirq) +{ + u32 bit = BIT(hwirq - IRQC_IRQ_START); + + writel_relaxed(readl_relaxed(priv->base + IMSK) | bit, priv->base + IMSK); +} + +static void rzfive_irqc_unmask_irq_interrupt(struct rzg2l_irqc_priv *priv, + unsigned int hwirq) +{ + u32 bit = BIT(hwirq - IRQC_IRQ_START); + + writel_relaxed(readl_relaxed(priv->base + IMSK) & ~bit, priv->base + IMSK); +} + +static void rzfive_irqc_mask_tint_interrupt(struct rzg2l_irqc_priv *priv, + unsigned int hwirq) +{ + u32 bit = BIT(hwirq - IRQC_TINT_START); + + writel_relaxed(readl_relaxed(priv->base + TMSK) | bit, priv->base + TMSK); +} + +static void rzfive_irqc_unmask_tint_interrupt(struct rzg2l_irqc_priv *priv, + unsigned int hwirq) +{ + u32 bit = BIT(hwirq - IRQC_TINT_START); + + writel_relaxed(readl_relaxed(priv->base + TMSK) & ~bit, priv->base + TMSK); +} + +static void rzfive_irqc_mask(struct irq_data *d) +{ + struct rzg2l_irqc_priv *priv = irq_data_to_priv(d); + unsigned int hwirq = irqd_to_hwirq(d); + + raw_spin_lock(&priv->lock); + if (hwirq >= IRQC_IRQ_START && hwirq <= IRQC_IRQ_COUNT) + rzfive_irqc_mask_irq_interrupt(priv, hwirq); + else if (hwirq >= IRQC_TINT_START && hwirq < IRQC_NUM_IRQ) + rzfive_irqc_mask_tint_interrupt(priv, hwirq); + raw_spin_unlock(&priv->lock); + irq_chip_mask_parent(d); +} + +static void rzfive_irqc_unmask(struct irq_data *d) +{ + struct rzg2l_irqc_priv *priv = irq_data_to_priv(d); + unsigned int hwirq = irqd_to_hwirq(d); + + raw_spin_lock(&priv->lock); + if (hwirq >= IRQC_IRQ_START && hwirq <= IRQC_IRQ_COUNT) + rzfive_irqc_unmask_irq_interrupt(priv, hwirq); + else if (hwirq >= IRQC_TINT_START && hwirq < IRQC_NUM_IRQ) + rzfive_irqc_unmask_tint_interrupt(priv, hwirq); + raw_spin_unlock(&priv->lock); + irq_chip_unmask_parent(d); +} + +static void rzfive_tint_irq_endisable(struct irq_data *d, bool enable) +{ + struct rzg2l_irqc_priv *priv = irq_data_to_priv(d); + unsigned int hwirq = irqd_to_hwirq(d); + + if (hwirq >= IRQC_TINT_START && hwirq < IRQC_NUM_IRQ) { + u32 offset = hwirq - IRQC_TINT_START; + u32 tssr_offset = TSSR_OFFSET(offset); + u8 tssr_index = TSSR_INDEX(offset); + u32 reg; + + raw_spin_lock(&priv->lock); + if (enable) + rzfive_irqc_unmask_tint_interrupt(priv, hwirq); + else + rzfive_irqc_mask_tint_interrupt(priv, hwirq); + reg = readl_relaxed(priv->base + TSSR(tssr_index)); + if (enable) + reg |= TIEN << TSSEL_SHIFT(tssr_offset); + else + reg &= ~(TIEN << TSSEL_SHIFT(tssr_offset)); + writel_relaxed(reg, priv->base + TSSR(tssr_index)); + raw_spin_unlock(&priv->lock); + } else { + raw_spin_lock(&priv->lock); + if (enable) + rzfive_irqc_unmask_irq_interrupt(priv, hwirq); + else + rzfive_irqc_mask_irq_interrupt(priv, hwirq); + raw_spin_unlock(&priv->lock); + } +} + +static void rzfive_irqc_irq_disable(struct irq_data *d) +{ + irq_chip_disable_parent(d); + rzfive_tint_irq_endisable(d, false); +} + +static void rzfive_irqc_irq_enable(struct irq_data *d) +{ + rzfive_tint_irq_endisable(d, true); + irq_chip_enable_parent(d); +} + static void rzg2l_tint_irq_endisable(struct irq_data *d, bool enable) { unsigned int hw_irq = irqd_to_hwirq(d); @@ -321,7 +430,7 @@ static struct syscore_ops rzg2l_irqc_syscore_ops = { .resume = rzg2l_irqc_irq_resume, }; -static const struct irq_chip irqc_chip = { +static const struct irq_chip rzg2l_irqc_chip = { .name = "rzg2l-irqc", .irq_eoi = rzg2l_irqc_eoi, .irq_mask = irq_chip_mask_parent, @@ -338,6 +447,23 @@ static const struct irq_chip irqc_chip = { IRQCHIP_SKIP_SET_WAKE, }; +static const struct irq_chip rzfive_irqc_chip = { + .name = "rzfive-irqc", + .irq_eoi = rzg2l_irqc_eoi, + .irq_mask = rzfive_irqc_mask, + .irq_unmask = rzfive_irqc_unmask, + .irq_disable = rzfive_irqc_irq_disable, + .irq_enable = rzfive_irqc_irq_enable, + .irq_get_irqchip_state = irq_chip_get_parent_state, + .irq_set_irqchip_state = irq_chip_set_parent_state, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_type = rzg2l_irqc_set_type, + .irq_set_affinity = irq_chip_set_affinity_parent, + .flags = IRQCHIP_MASK_ON_SUSPEND | + IRQCHIP_SET_TYPE_MASKED | + IRQCHIP_SKIP_SET_WAKE, +}; + static int rzg2l_irqc_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, void *arg) { @@ -369,7 +495,7 @@ static int rzg2l_irqc_alloc(struct irq_domain *domain, unsigned int virq, if (hwirq > (IRQC_NUM_IRQ - 1)) return -EINVAL; - ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &irqc_chip, + ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, priv->irqchip, (void *)(uintptr_t)tint); if (ret) return ret; @@ -401,7 +527,8 @@ static int rzg2l_irqc_parse_interrupts(struct rzg2l_irqc_priv *priv, return 0; } -static int rzg2l_irqc_init(struct device_node *node, struct device_node *parent) +static int rzg2l_irqc_common_init(struct device_node *node, struct device_node *parent, + const struct irq_chip *irq_chip) { struct irq_domain *irq_domain, *parent_domain; struct platform_device *pdev; @@ -422,6 +549,8 @@ static int rzg2l_irqc_init(struct device_node *node, struct device_node *parent) if (!rzg2l_irqc_data) return -ENOMEM; + rzg2l_irqc_data->irqchip = irq_chip; + rzg2l_irqc_data->base = devm_of_iomap(&pdev->dev, pdev->dev.of_node, 0, NULL); if (IS_ERR(rzg2l_irqc_data->base)) return PTR_ERR(rzg2l_irqc_data->base); @@ -472,8 +601,21 @@ pm_disable: return ret; } +static int __init rzg2l_irqc_init(struct device_node *node, + struct device_node *parent) +{ + return rzg2l_irqc_common_init(node, parent, &rzg2l_irqc_chip); +} + +static int __init rzfive_irqc_init(struct device_node *node, + struct device_node *parent) +{ + return rzg2l_irqc_common_init(node, parent, &rzfive_irqc_chip); +} + IRQCHIP_PLATFORM_DRIVER_BEGIN(rzg2l_irqc) IRQCHIP_MATCH("renesas,rzg2l-irqc", rzg2l_irqc_init) +IRQCHIP_MATCH("renesas,r9a07g043f-irqc", rzfive_irqc_init) IRQCHIP_PLATFORM_DRIVER_END(rzg2l_irqc) MODULE_AUTHOR("Lad Prabhakar "); MODULE_DESCRIPTION("Renesas RZ/G2L IRQC Driver"); From ababa16fd9bd0e2727a1c31c4fb68d6be053bddc Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 6 Jun 2024 11:42:38 +0200 Subject: [PATCH 014/152] irqchip/gic-v3: Enable non-coherent redistributors/ITSes ACPI probing The GIC architecture specification defines a set of registers for redistributors and ITSes that control the sharebility and cacheability attributes of redistributors/ITSes initiator ports on the interconnect (GICR_[V]PROPBASER, GICR_[V]PENDBASER, GITS_BASER). Architecturally the GIC provides a means to drive shareability and cacheability attributes signals but it is not mandatory for designs to wire up the corresponding interconnect signals that control the cacheability/shareability of transactions. Redistributors and ITSes interconnect ports can be connected to non-coherent interconnects that are not able to manage the shareability/cacheability attributes; this implicitly makes the redistributors and ITSes non-coherent observers. To enable non-coherent GIC designs on ACPI based systems, parse the MADT GICC/GICR/ITS subtables non-coherent flags to determine whether the respective components are non-coherent observers and force the shareability attributes to be programmed into the redistributors and ITSes registers. An ACPI global function (acpi_get_madt_revision()) is added to retrieve the MADT revision, in that it is essential to check the MADT revision before checking for flags that were added with MADT revision 7 so that if the kernel is booted with an ACPI MADT table with revision < 7 it skips parsing the newly added flags (that should be zeroed reserved values for MADT versions < 7 but they could turn out to be buggy and should be ignored). Signed-off-by: Lorenzo Pieralisi Signed-off-by: Thomas Gleixner Reviewed-by: Robin Murphy Acked-by: Marc Zyngier Cc: "Rafael J. Wysocki" Link: https://lore.kernel.org/r/20240606094238.757649-2-lpieralisi@kernel.org --- drivers/acpi/processor_core.c | 15 +++++++++++++++ drivers/irqchip/irq-gic-v3-its.c | 4 ++++ drivers/irqchip/irq-gic-v3.c | 9 +++++++++ include/linux/acpi.h | 3 +++ 4 files changed, 31 insertions(+) diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index b203cfe28550..915713c0e9b7 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -215,6 +215,21 @@ phys_cpuid_t __init acpi_map_madt_entry(u32 acpi_id) return rv; } +int __init acpi_get_madt_revision(void) +{ + struct acpi_table_header *madt = NULL; + int revision; + + if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_MADT, 0, &madt))) + return -EINVAL; + + revision = madt->revision; + + acpi_put_table(madt); + + return revision; +} + static phys_cpuid_t map_mat_entry(acpi_handle handle, int type, u32 acpi_id) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 40ebf1726393..af5297ef2293 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -5600,6 +5600,10 @@ static int __init gic_acpi_parse_madt_its(union acpi_subtable_headers *header, goto node_err; } + if (acpi_get_madt_revision() >= 7 && + (its_entry->flags & ACPI_MADT_ITS_NON_COHERENT)) + its->flags |= ITS_FLAGS_FORCE_NON_SHAREABLE; + err = its_probe_one(its); if (!err) return 0; diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 6fb276504bcc..e4bc5f04226e 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -2349,6 +2349,11 @@ gic_acpi_parse_madt_redist(union acpi_subtable_headers *header, pr_err("Couldn't map GICR region @%llx\n", redist->base_address); return -ENOMEM; } + + if (acpi_get_madt_revision() >= 7 && + (redist->flags & ACPI_MADT_GICR_NON_COHERENT)) + gic_data.rdists.flags |= RDIST_FLAGS_FORCE_NON_SHAREABLE; + gic_request_region(redist->base_address, redist->length, "GICR"); gic_acpi_register_redist(redist->base_address, redist_base); @@ -2373,6 +2378,10 @@ gic_acpi_parse_madt_gicc(union acpi_subtable_headers *header, return -ENOMEM; gic_request_region(gicc->gicr_base_address, size, "GICR"); + if (acpi_get_madt_revision() >= 7 && + (gicc->flags & ACPI_MADT_GICC_NON_COHERENT)) + gic_data.rdists.flags |= RDIST_FLAGS_FORCE_NON_SHAREABLE; + gic_acpi_register_redist(gicc->gicr_base_address, redist_base); return 0; } diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 28c3fb2bef0d..000d339e1596 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -279,6 +279,9 @@ static inline bool invalid_phys_cpuid(phys_cpuid_t phys_id) return phys_id == PHYS_CPUID_INVALID; } + +int __init acpi_get_madt_revision(void); + /* Validate the processor object's proc_id */ bool acpi_duplicate_processor_id(int proc_id); /* Processor _CTS control */ From 46edf5d7aed5438078a8b4b1ab4230a41a032dd0 Mon Sep 17 00:00:00 2001 From: Ryan Sullivan Date: Thu, 6 Jun 2024 09:53:48 -0400 Subject: [PATCH 015/152] selftests/livepatch: define max test-syscall processes Define a maximum allowable number of pids that can be livepatched in test-syscall.sh as with extremely large machines the output from a large number of processes overflows the dev/kmsg "expect" buffer in the "check_result" function and causes a false error. Reported-by: CKI Project Signed-off-by: Ryan Sullivan Acked-by: Miroslav Benes Tested-by: Marcos Paulo de Souza Reviewed-by: Marcos Paulo de Souza Reviewed-by: Petr Mladek Acked-by: Joel Savitz Link: https://lore.kernel.org/r/20240606135348.4708-1-rysulliv@redhat.com Signed-off-by: Petr Mladek --- tools/testing/selftests/livepatch/test-syscall.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/livepatch/test-syscall.sh b/tools/testing/selftests/livepatch/test-syscall.sh index b76a881d4013..289eb7d4c4b3 100755 --- a/tools/testing/selftests/livepatch/test-syscall.sh +++ b/tools/testing/selftests/livepatch/test-syscall.sh @@ -15,7 +15,10 @@ setup_config start_test "patch getpid syscall while being heavily hammered" -for i in $(seq 1 $(getconf _NPROCESSORS_ONLN)); do +NPROC=$(getconf _NPROCESSORS_ONLN) +MAXPROC=128 + +for i in $(seq 1 $(($NPROC < $MAXPROC ? $NPROC : $MAXPROC))); do ./test_klp-call_getpid & pids[$i]="$!" done From 6ce3e98184b625d2870991880bf9586ded7ea7f9 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:04 +0200 Subject: [PATCH 016/152] irqdomain: Fixed unbalanced fwnode get and put fwnode_handle_get(fwnode) is called when a domain is created with fwnode passed as a function parameter. fwnode_handle_put(domain->fwnode) is called when the domain is destroyed but during the creation a path exists that does not set domain->fwnode. If this path is taken, the fwnode get will never be put. To avoid the unbalanced get and put, set domain->fwnode unconditionally. Fixes: d59f6617eef0 ("genirq: Allow fwnode to carry name information only") Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20240614173232.1184015-4-herve.codina@bootlin.com --- kernel/irq/irqdomain.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 28709c14d894..7b4d580fc8e4 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -156,7 +156,6 @@ static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode, switch (fwid->type) { case IRQCHIP_FWNODE_NAMED: case IRQCHIP_FWNODE_NAMED_ID: - domain->fwnode = fwnode; domain->name = kstrdup(fwid->name, GFP_KERNEL); if (!domain->name) { kfree(domain); @@ -165,7 +164,6 @@ static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode, domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED; break; default: - domain->fwnode = fwnode; domain->name = fwid->name; break; } @@ -185,7 +183,6 @@ static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode, } domain->name = strreplace(name, '/', ':'); - domain->fwnode = fwnode; domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED; } @@ -201,8 +198,8 @@ static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode, domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED; } - fwnode_handle_get(fwnode); - fwnode_dev_initialized(fwnode, true); + domain->fwnode = fwnode_handle_get(fwnode); + fwnode_dev_initialized(domain->fwnode, true); /* Fill structure */ INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL); From 89b37541ca38954f8ac01c2ca25405b140cfc8eb Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:02 +0200 Subject: [PATCH 017/152] irqdomain: Introduce irq_domain_free() In preparation of the introduction of the irq domain instantiation, introduce irq_domain_free() to avoid code duplication on later modifications. This new function is an extraction of the current operations performed to free the irq domain. No functional change intended. Suggested-by: Thomas Gleixner Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-2-herve.codina@bootlin.com --- kernel/irq/irqdomain.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 7b4d580fc8e4..40b631bd2836 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -238,6 +238,15 @@ static void __irq_domain_publish(struct irq_domain *domain) pr_debug("Added domain %s\n", domain->name); } +static void irq_domain_free(struct irq_domain *domain) +{ + fwnode_dev_initialized(domain->fwnode, false); + fwnode_handle_put(domain->fwnode); + if (domain->flags & IRQ_DOMAIN_NAME_ALLOCATED) + kfree(domain->name); + kfree(domain); +} + /** * __irq_domain_add() - Allocate a new irq_domain data structure * @fwnode: firmware node for the interrupt controller @@ -293,12 +302,7 @@ void irq_domain_remove(struct irq_domain *domain) mutex_unlock(&irq_domain_mutex); pr_debug("Removed domain %s\n", domain->name); - - fwnode_dev_initialized(domain->fwnode, false); - fwnode_handle_put(domain->fwnode); - if (domain->flags & IRQ_DOMAIN_NAME_ALLOCATED) - kfree(domain->name); - kfree(domain); + irq_domain_free(domain); } EXPORT_SYMBOL_GPL(irq_domain_remove); From 299d623f5c9ab48e53255cf6b510627f1ef26dfe Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:03 +0200 Subject: [PATCH 018/152] irqdomain: Introduce irq_domain_instantiate() The existing irq_domain_add_*() functions used to instantiate an IRQ domain are wrappers built on top of __irq_domain_add() and describe the domain properties using a bunch of parameters. Adding more parameters and wrappers to hide new parameters in the existing code lead to more and more code without any relevant value and without any flexibility. Introduce irq_domain_instantiate() where the interrupt domain properties are given using a irq_domain_info structure instead of the bunch of parameters to allow flexibility and easy evolution. irq_domain_instantiate() performs the same operation as the one done by __irq_domain_add(). For compatibility reason with existing code, keep __irq_domain_add() but convert it to irq_domain_instantiate(). [ tglx: Fixed up struct initializer coding style ] Suggested-by: Thomas Gleixner Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-3-herve.codina@bootlin.com --- include/linux/irqdomain.h | 21 +++++++++++++++++++++ kernel/irq/irqdomain.c | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 21ecf582a0fe..ab8939c8724d 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -257,6 +257,27 @@ static inline struct fwnode_handle *irq_domain_alloc_fwnode(phys_addr_t *pa) } void irq_domain_free_fwnode(struct fwnode_handle *fwnode); +/** + * struct irq_domain_info - Domain information structure + * @fwnode: firmware node for the interrupt controller + * @size: Size of linear map; 0 for radix mapping only + * @hwirq_max: Maximum number of interrupts supported by controller + * @direct_max: Maximum value of direct maps; + * Use ~0 for no limit; 0 for no direct mapping + * @ops: Domain operation callbacks + * @host_data: Controller private data pointer + */ +struct irq_domain_info { + struct fwnode_handle *fwnode; + unsigned int size; + irq_hw_number_t hwirq_max; + int direct_max; + const struct irq_domain_ops *ops; + void *host_data; +}; + +struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info); + struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, unsigned int size, irq_hw_number_t hwirq_max, int direct_max, const struct irq_domain_ops *ops, diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 40b631bd2836..111052f363ea 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -247,6 +247,27 @@ static void irq_domain_free(struct irq_domain *domain) kfree(domain); } +/** + * irq_domain_instantiate() - Instantiate a new irq domain data structure + * @info: Domain information pointer pointing to the information for this domain + * + * Return: A pointer to the instantiated irq domain or an ERR_PTR value. + */ +struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info) +{ + struct irq_domain *domain; + + domain = __irq_domain_create(info->fwnode, info->size, info->hwirq_max, + info->direct_max, info->ops, info->host_data); + if (!domain) + return ERR_PTR(-ENOMEM); + + __irq_domain_publish(domain); + + return domain; +} +EXPORT_SYMBOL_GPL(irq_domain_instantiate); + /** * __irq_domain_add() - Allocate a new irq_domain data structure * @fwnode: firmware node for the interrupt controller @@ -265,14 +286,18 @@ struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, unsigned int s const struct irq_domain_ops *ops, void *host_data) { - struct irq_domain *domain; + struct irq_domain_info info = { + .fwnode = fwnode, + .size = size, + .hwirq_max = hwirq_max, + .direct_max = direct_max, + .ops = ops, + .host_data = host_data, + }; + struct irq_domain *d; - domain = __irq_domain_create(fwnode, size, hwirq_max, direct_max, - ops, host_data); - if (domain) - __irq_domain_publish(domain); - - return domain; + d = irq_domain_instantiate(&info); + return IS_ERR(d) ? NULL : d; } EXPORT_SYMBOL_GPL(__irq_domain_add); From 922ac2cf9fe444c4aff165b9f7e158a9b651647d Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:05 +0200 Subject: [PATCH 019/152] irqdomain: Constify parameter in is_fwnode_irqchip() The fwnode parameter has no reason to be a pointer to an un-const struct fwnode_handle. Indeed, struct fwnode_handle is not supposed to be modified by the function. Be consistent with other function performing the same kind of operation such as is_of_node(), is_acpi_device_node() or is_software_node(): constify the fwnode parameter. Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-5-herve.codina@bootlin.com --- include/linux/irqdomain.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index ab8939c8724d..a3b43e357009 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -314,7 +314,7 @@ static inline struct fwnode_handle *of_node_to_fwnode(struct device_node *node) extern const struct fwnode_operations irqchip_fwnode_ops; -static inline bool is_fwnode_irqchip(struct fwnode_handle *fwnode) +static inline bool is_fwnode_irqchip(const struct fwnode_handle *fwnode) { return fwnode && fwnode->ops == &irqchip_fwnode_ops; } From dbd56abffc6a43eb361e8033dce7a7d176f8e867 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:06 +0200 Subject: [PATCH 020/152] irqdomain: Use a dedicated function to set the domain name The interrupt domain name computation and setting is directly done in __irq_domain_create(). This leads to a quite long __irq_domain_create() function. In order to simplify __irq_domain_create() and isolate the domain name computation and setting, move the related operations to a dedicated function. Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-6-herve.codina@bootlin.com --- kernel/irq/irqdomain.c | 69 +++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 111052f363ea..a7be7763e0ff 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -128,27 +128,11 @@ void irq_domain_free_fwnode(struct fwnode_handle *fwnode) } EXPORT_SYMBOL_GPL(irq_domain_free_fwnode); -static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode, - unsigned int size, - irq_hw_number_t hwirq_max, - int direct_max, - const struct irq_domain_ops *ops, - void *host_data) +static int irq_domain_set_name(struct irq_domain *domain, + const struct fwnode_handle *fwnode) { - struct irqchip_fwid *fwid; - struct irq_domain *domain; - static atomic_t unknown_domains; - - if (WARN_ON((size && direct_max) || - (!IS_ENABLED(CONFIG_IRQ_DOMAIN_NOMAP) && direct_max) || - (direct_max && (direct_max != hwirq_max)))) - return NULL; - - domain = kzalloc_node(struct_size(domain, revmap, size), - GFP_KERNEL, of_node_to_nid(to_of_node(fwnode))); - if (!domain) - return NULL; + struct irqchip_fwid *fwid; if (is_fwnode_irqchip(fwnode)) { fwid = container_of(fwnode, struct irqchip_fwid, fwnode); @@ -157,10 +141,8 @@ static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode, case IRQCHIP_FWNODE_NAMED: case IRQCHIP_FWNODE_NAMED_ID: domain->name = kstrdup(fwid->name, GFP_KERNEL); - if (!domain->name) { - kfree(domain); - return NULL; - } + if (!domain->name) + return -ENOMEM; domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED; break; default: @@ -177,10 +159,8 @@ static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode, * the trick and is not as offensive as '\'... */ name = kasprintf(GFP_KERNEL, "%pfw", fwnode); - if (!name) { - kfree(domain); - return NULL; - } + if (!name) + return -ENOMEM; domain->name = strreplace(name, '/', ':'); domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED; @@ -191,13 +171,40 @@ static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode, pr_err("Invalid fwnode type for irqdomain\n"); domain->name = kasprintf(GFP_KERNEL, "unknown-%d", atomic_inc_return(&unknown_domains)); - if (!domain->name) { - kfree(domain); - return NULL; - } + if (!domain->name) + return -ENOMEM; domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED; } + return 0; +} + +static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode, + unsigned int size, + irq_hw_number_t hwirq_max, + int direct_max, + const struct irq_domain_ops *ops, + void *host_data) +{ + struct irq_domain *domain; + int err; + + if (WARN_ON((size && direct_max) || + (!IS_ENABLED(CONFIG_IRQ_DOMAIN_NOMAP) && direct_max) || + (direct_max && direct_max != hwirq_max))) + return NULL; + + domain = kzalloc_node(struct_size(domain, revmap, size), + GFP_KERNEL, of_node_to_nid(to_of_node(fwnode))); + if (!domain) + return NULL; + + err = irq_domain_set_name(domain, fwnode); + if (err) { + kfree(domain); + return NULL; + } + domain->fwnode = fwnode_handle_get(fwnode); fwnode_dev_initialized(domain->fwnode, true); From 24a4f4e48557dddf2bb722df7b01184efc92a6a7 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:07 +0200 Subject: [PATCH 021/152] irqdomain: Convert __irq_domain_create() to use struct irq_domain_info The existing __irq_domain_create() use a bunch of parameters to create an irq domain. With the introduction of irq_domain_info structure, these parameters are available in the information structure itself. Using directly this information structure allows future flexibility to add other parameters in a simple way without the need to change the __irq_domain_create() prototype. Convert __irq_domain_create() to use the information structure. [ tglx: Fixup struct initializer ] Suggested-by: Thomas Gleixner Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-7-herve.codina@bootlin.com --- kernel/irq/irqdomain.c | 48 +++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index a7be7763e0ff..0eda48f77aa9 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -179,45 +179,40 @@ static int irq_domain_set_name(struct irq_domain *domain, return 0; } -static struct irq_domain *__irq_domain_create(struct fwnode_handle *fwnode, - unsigned int size, - irq_hw_number_t hwirq_max, - int direct_max, - const struct irq_domain_ops *ops, - void *host_data) +static struct irq_domain *__irq_domain_create(const struct irq_domain_info *info) { struct irq_domain *domain; int err; - if (WARN_ON((size && direct_max) || - (!IS_ENABLED(CONFIG_IRQ_DOMAIN_NOMAP) && direct_max) || - (direct_max && direct_max != hwirq_max))) + if (WARN_ON((info->size && info->direct_max) || + (!IS_ENABLED(CONFIG_IRQ_DOMAIN_NOMAP) && info->direct_max) || + (info->direct_max && info->direct_max != info->hwirq_max))) return NULL; - domain = kzalloc_node(struct_size(domain, revmap, size), - GFP_KERNEL, of_node_to_nid(to_of_node(fwnode))); + domain = kzalloc_node(struct_size(domain, revmap, info->size), + GFP_KERNEL, of_node_to_nid(to_of_node(info->fwnode))); if (!domain) return NULL; - err = irq_domain_set_name(domain, fwnode); + err = irq_domain_set_name(domain, info->fwnode); if (err) { kfree(domain); return NULL; } - domain->fwnode = fwnode_handle_get(fwnode); + domain->fwnode = fwnode_handle_get(info->fwnode); fwnode_dev_initialized(domain->fwnode, true); /* Fill structure */ INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL); - domain->ops = ops; - domain->host_data = host_data; - domain->hwirq_max = hwirq_max; + domain->ops = info->ops; + domain->host_data = info->host_data; + domain->hwirq_max = info->hwirq_max; - if (direct_max) + if (info->direct_max) domain->flags |= IRQ_DOMAIN_FLAG_NO_MAP; - domain->revmap_size = size; + domain->revmap_size = info->size; /* * Hierarchical domains use the domain lock of the root domain @@ -264,8 +259,7 @@ struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info) { struct irq_domain *domain; - domain = __irq_domain_create(info->fwnode, info->size, info->hwirq_max, - info->direct_max, info->ops, info->host_data); + domain = __irq_domain_create(info); if (!domain) return ERR_PTR(-ENOMEM); @@ -1204,13 +1198,19 @@ struct irq_domain *irq_domain_create_hierarchy(struct irq_domain *parent, const struct irq_domain_ops *ops, void *host_data) { + struct irq_domain_info info = { + .fwnode = fwnode, + .size = size, + .hwirq_max = size, + .ops = ops, + .host_data = host_data, + }; struct irq_domain *domain; - if (size) - domain = __irq_domain_create(fwnode, size, size, 0, ops, host_data); - else - domain = __irq_domain_create(fwnode, 0, ~0, 0, ops, host_data); + if (!info.size) + info.hwirq_max = ~0U; + domain = __irq_domain_create(&info); if (domain) { if (parent) domain->root = parent->root; From 757398541c30a5e898169763b43f08dab71ea3bd Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:08 +0200 Subject: [PATCH 022/152] irqdomain: Handle additional domain flags in irq_domain_instantiate() In order to use irq_domain_instantiate() from several places such as irq_domain_create_hierarchy(), irq_domain_instantiate() needs to handle additional domain flags. Add the required infrastructure. Suggested-by: Thomas Gleixner Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-8-herve.codina@bootlin.com --- include/linux/irqdomain.h | 2 ++ kernel/irq/irqdomain.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index a3b43e357009..4683b66eded9 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -260,6 +260,7 @@ void irq_domain_free_fwnode(struct fwnode_handle *fwnode); /** * struct irq_domain_info - Domain information structure * @fwnode: firmware node for the interrupt controller + * @domain_flags: Additional flags to add to the domain flags * @size: Size of linear map; 0 for radix mapping only * @hwirq_max: Maximum number of interrupts supported by controller * @direct_max: Maximum value of direct maps; @@ -269,6 +270,7 @@ void irq_domain_free_fwnode(struct fwnode_handle *fwnode); */ struct irq_domain_info { struct fwnode_handle *fwnode; + unsigned int domain_flags; unsigned int size; irq_hw_number_t hwirq_max; int direct_max; diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 0eda48f77aa9..26ad1ea67998 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -263,6 +263,8 @@ struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info) if (!domain) return ERR_PTR(-ENOMEM); + domain->flags |= info->domain_flags; + __irq_domain_publish(domain); return domain; From 419e3778ff295c00aa158d9f2854a70b47ba1136 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:09 +0200 Subject: [PATCH 023/152] irqdomain: Handle domain hierarchy parent in irq_domain_instantiate() To use irq_domain_instantiate() from irq_domain_create_hierarchy(), irq_domain_instantiate() needs to handle the domain hierarchy parent. Add the required functionality. Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-9-herve.codina@bootlin.com --- include/linux/irqdomain.h | 6 ++++++ kernel/irq/irqdomain.c | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 4683b66eded9..e52fd5e5494c 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -276,6 +276,12 @@ struct irq_domain_info { int direct_max; const struct irq_domain_ops *ops; void *host_data; +#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY + /** + * @parent: Pointer to the parent irq domain used in a hierarchy domain + */ + struct irq_domain *parent; +#endif }; struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info); diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 26ad1ea67998..1269a8104e6c 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -265,6 +265,13 @@ struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info) domain->flags |= info->domain_flags; +#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY + if (info->parent) { + domain->root = info->parent->root; + domain->parent = info->parent; + } +#endif + __irq_domain_publish(domain); return domain; From b986055dd04141efd6d5dcdacd48d6b38cf320c8 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:10 +0200 Subject: [PATCH 024/152] irqdomain: Use irq_domain_instantiate() for hierarchy domain creation irq_domain_instantiate() handles all needs to be used in irq_domain_create_hierarchy() Avoid code duplication and use directly irq_domain_instantiate() for hierarchy domain creation. Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-10-herve.codina@bootlin.com --- kernel/irq/irqdomain.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 1269a8104e6c..8dc0007cdec1 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -1213,23 +1213,16 @@ struct irq_domain *irq_domain_create_hierarchy(struct irq_domain *parent, .hwirq_max = size, .ops = ops, .host_data = host_data, + .domain_flags = flags, + .parent = parent, }; - struct irq_domain *domain; + struct irq_domain *d; if (!info.size) info.hwirq_max = ~0U; - domain = __irq_domain_create(&info); - if (domain) { - if (parent) - domain->root = parent->root; - domain->parent = parent; - domain->flags |= flags; - - __irq_domain_publish(domain); - } - - return domain; + d = irq_domain_instantiate(&info); + return IS_ERR(d) ? NULL : d; } EXPORT_SYMBOL_GPL(irq_domain_create_hierarchy); From 80f6abe0d39bc6ccf353290067ff589653ff922c Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:11 +0200 Subject: [PATCH 025/152] irqdomain: Make __irq_domain_create() return an error code __irq_domain_create() can fail for several reasons. When it fails it returns a NULL pointer and so filters out the exact failure reason. The only user of __irq_domain_create() is irq_domain_instantiate() which can return a PTR_ERR value. On __irq_domain_create() failure, it uses an arbitrary error code. Rather than using this arbitrary error value, make __irq_domain_create() return is own error code and use that one. [ tglx: Remove the pointless ERR_CAST. domain is a valid return pointer ] Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-11-herve.codina@bootlin.com --- kernel/irq/irqdomain.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 8dc0007cdec1..fe7bba685580 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -187,17 +187,17 @@ static struct irq_domain *__irq_domain_create(const struct irq_domain_info *info if (WARN_ON((info->size && info->direct_max) || (!IS_ENABLED(CONFIG_IRQ_DOMAIN_NOMAP) && info->direct_max) || (info->direct_max && info->direct_max != info->hwirq_max))) - return NULL; + return ERR_PTR(-EINVAL); domain = kzalloc_node(struct_size(domain, revmap, info->size), GFP_KERNEL, of_node_to_nid(to_of_node(info->fwnode))); if (!domain) - return NULL; + return ERR_PTR(-ENOMEM); err = irq_domain_set_name(domain, info->fwnode); if (err) { kfree(domain); - return NULL; + return ERR_PTR(err); } domain->fwnode = fwnode_handle_get(info->fwnode); @@ -260,8 +260,8 @@ struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info) struct irq_domain *domain; domain = __irq_domain_create(info); - if (!domain) - return ERR_PTR(-ENOMEM); + if (IS_ERR(domain)) + return domain; domain->flags |= info->domain_flags; From 0b21add71bd9cfa2bd6677a0300e15fd4c4b84ed Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:12 +0200 Subject: [PATCH 026/152] irqdomain: Handle domain bus token in irq_domain_create() irq_domain_update_bus_token() is the only way to set the domain bus token. This is sub-optimal as irq_domain_update_bus_token() can be called only once the domain is created and needs to revert some operations, change the domain name and redo the operations. In order to avoid this revert/change/redo sequence, take the domain bus into account token during the domain creation. Suggested-by: Thomas Gleixner Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-12-herve.codina@bootlin.com --- include/linux/irqdomain.h | 2 ++ kernel/irq/irqdomain.c | 30 ++++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index e52fd5e5494c..52bed23e5c61 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -265,6 +265,7 @@ void irq_domain_free_fwnode(struct fwnode_handle *fwnode); * @hwirq_max: Maximum number of interrupts supported by controller * @direct_max: Maximum value of direct maps; * Use ~0 for no limit; 0 for no direct mapping + * @bus_token: Domain bus token * @ops: Domain operation callbacks * @host_data: Controller private data pointer */ @@ -274,6 +275,7 @@ struct irq_domain_info { unsigned int size; irq_hw_number_t hwirq_max; int direct_max; + enum irq_domain_bus_token bus_token; const struct irq_domain_ops *ops; void *host_data; #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index fe7bba685580..a21648c76336 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -129,7 +129,8 @@ void irq_domain_free_fwnode(struct fwnode_handle *fwnode) EXPORT_SYMBOL_GPL(irq_domain_free_fwnode); static int irq_domain_set_name(struct irq_domain *domain, - const struct fwnode_handle *fwnode) + const struct fwnode_handle *fwnode, + enum irq_domain_bus_token bus_token) { static atomic_t unknown_domains; struct irqchip_fwid *fwid; @@ -140,13 +141,23 @@ static int irq_domain_set_name(struct irq_domain *domain, switch (fwid->type) { case IRQCHIP_FWNODE_NAMED: case IRQCHIP_FWNODE_NAMED_ID: - domain->name = kstrdup(fwid->name, GFP_KERNEL); + domain->name = bus_token ? + kasprintf(GFP_KERNEL, "%s-%d", + fwid->name, bus_token) : + kstrdup(fwid->name, GFP_KERNEL); if (!domain->name) return -ENOMEM; domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED; break; default: domain->name = fwid->name; + if (bus_token) { + domain->name = kasprintf(GFP_KERNEL, "%s-%d", + fwid->name, bus_token); + if (!domain->name) + return -ENOMEM; + domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED; + } break; } } else if (is_of_node(fwnode) || is_acpi_device_node(fwnode) || @@ -158,7 +169,9 @@ static int irq_domain_set_name(struct irq_domain *domain, * unhappy about. Replace them with ':', which does * the trick and is not as offensive as '\'... */ - name = kasprintf(GFP_KERNEL, "%pfw", fwnode); + name = bus_token ? + kasprintf(GFP_KERNEL, "%pfw-%d", fwnode, bus_token) : + kasprintf(GFP_KERNEL, "%pfw", fwnode); if (!name) return -ENOMEM; @@ -169,8 +182,12 @@ static int irq_domain_set_name(struct irq_domain *domain, if (!domain->name) { if (fwnode) pr_err("Invalid fwnode type for irqdomain\n"); - domain->name = kasprintf(GFP_KERNEL, "unknown-%d", - atomic_inc_return(&unknown_domains)); + domain->name = bus_token ? + kasprintf(GFP_KERNEL, "unknown-%d-%d", + atomic_inc_return(&unknown_domains), + bus_token) : + kasprintf(GFP_KERNEL, "unknown-%d", + atomic_inc_return(&unknown_domains)); if (!domain->name) return -ENOMEM; domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED; @@ -194,7 +211,7 @@ static struct irq_domain *__irq_domain_create(const struct irq_domain_info *info if (!domain) return ERR_PTR(-ENOMEM); - err = irq_domain_set_name(domain, info->fwnode); + err = irq_domain_set_name(domain, info->fwnode, info->bus_token); if (err) { kfree(domain); return ERR_PTR(err); @@ -207,6 +224,7 @@ static struct irq_domain *__irq_domain_create(const struct irq_domain_info *info INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL); domain->ops = info->ops; domain->host_data = info->host_data; + domain->bus_token = info->bus_token; domain->hwirq_max = info->hwirq_max; if (info->direct_max) From 44b68de9b8e3dfde12308e8567548799d7ded0de Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:13 +0200 Subject: [PATCH 027/152] irqdomain: Introduce init() and exit() hooks The current API does not allow additional initialization before the domain is published. This can lead to a race condition between consumers and supplier as a domain can be available for consumers before being fully ready. Introduce the init() hook to allow additional initialization before plublishing the domain. Also introduce the exit() hook to revert operations done in init() on domain removal. Suggested-by: Thomas Gleixner Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-13-herve.codina@bootlin.com --- include/linux/irqdomain.h | 8 ++++++++ kernel/irq/irqdomain.c | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 52bed23e5c61..2c927edc4d43 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -141,6 +141,7 @@ struct irq_domain_chip_generic; * purposes related to the irq domain. * @parent: Pointer to parent irq_domain to support hierarchy irq_domains * @msi_parent_ops: Pointer to MSI parent domain methods for per device domain init + * @exit: Function called when the domain is destroyed * * Revmap data, used internally by the irq domain code: * @revmap_size: Size of the linear map table @revmap[] @@ -169,6 +170,7 @@ struct irq_domain { #ifdef CONFIG_GENERIC_MSI_IRQ const struct msi_parent_ops *msi_parent_ops; #endif + void (*exit)(struct irq_domain *d); /* reverse map data. The linear map gets appended to the irq_domain */ irq_hw_number_t hwirq_max; @@ -268,6 +270,10 @@ void irq_domain_free_fwnode(struct fwnode_handle *fwnode); * @bus_token: Domain bus token * @ops: Domain operation callbacks * @host_data: Controller private data pointer + * @init: Function called when the domain is created. + * Allow to do some additional domain initialisation. + * @exit: Function called when the domain is destroyed. + * Allow to do some additional cleanup operation. */ struct irq_domain_info { struct fwnode_handle *fwnode; @@ -284,6 +290,8 @@ struct irq_domain_info { */ struct irq_domain *parent; #endif + int (*init)(struct irq_domain *d); + void (*exit)(struct irq_domain *d); }; struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info); diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index a21648c76336..a0324d8e6dab 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -276,12 +276,14 @@ static void irq_domain_free(struct irq_domain *domain) struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info) { struct irq_domain *domain; + int err; domain = __irq_domain_create(info); if (IS_ERR(domain)) return domain; domain->flags |= info->domain_flags; + domain->exit = info->exit; #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY if (info->parent) { @@ -290,9 +292,19 @@ struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info) } #endif + if (info->init) { + err = info->init(domain); + if (err) + goto err_domain_free; + } + __irq_domain_publish(domain); return domain; + +err_domain_free: + irq_domain_free(domain); + return ERR_PTR(err); } EXPORT_SYMBOL_GPL(irq_domain_instantiate); @@ -339,6 +351,9 @@ EXPORT_SYMBOL_GPL(__irq_domain_add); */ void irq_domain_remove(struct irq_domain *domain) { + if (domain->exit) + domain->exit(domain); + mutex_lock(&irq_domain_mutex); debugfs_remove_domain_dir(domain); From e25f553a92973eaf59ff3a00fe7f61ab01b2877f Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:14 +0200 Subject: [PATCH 028/152] genirq/generic_chip: Introduce irq_domain_{alloc,remove}_generic_chips() The existing __irq_alloc_domain_generic_chips() uses a bunch of parameters to describe the generic chips that need to be allocated. Adding more parameters and wrappers to hide new parameters in the existing code leads to more and more code without any relevant values and without any flexibility. Introduce irq_domain_alloc_generic_chips() where the generic chips description is done using the irq_domain_chip_generic_info structure instead of the bunch of parameters to allow flexibility and easy evolution. Also introduce irq_domain_remove_generic_chips() to revert the operations done by irq_domain_alloc_generic_chips(). Suggested-by: Thomas Gleixner Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-14-herve.codina@bootlin.com --- include/linux/irq.h | 25 +++++++ kernel/irq/generic-chip.c | 147 +++++++++++++++++++++++++------------- 2 files changed, 121 insertions(+), 51 deletions(-) diff --git a/include/linux/irq.h b/include/linux/irq.h index a217e1029c1d..58264b236cbf 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -1117,6 +1117,27 @@ struct irq_domain_chip_generic { struct irq_chip_generic *gc[]; }; +/** + * struct irq_domain_chip_generic_info - Generic chip information structure + * @name: Name of the generic interrupt chip + * @handler: Interrupt handler used by the generic interrupt chip + * @irqs_per_chip: Number of interrupts each chip handles (max 32) + * @num_ct: Number of irq_chip_type instances associated with each + * chip + * @irq_flags_to_clear: IRQ_* bits to clear in the mapping function + * @irq_flags_to_set: IRQ_* bits to set in the mapping function + * @gc_flags: Generic chip specific setup flags + */ +struct irq_domain_chip_generic_info { + const char *name; + irq_flow_handler_t handler; + unsigned int irqs_per_chip; + unsigned int num_ct; + unsigned int irq_flags_to_clear; + unsigned int irq_flags_to_set; + enum irq_gc_flags gc_flags; +}; + /* Generic chip callback functions */ void irq_gc_noop(struct irq_data *d); void irq_gc_mask_disable_reg(struct irq_data *d); @@ -1153,6 +1174,10 @@ int devm_irq_setup_generic_chip(struct device *dev, struct irq_chip_generic *gc, struct irq_chip_generic *irq_get_domain_generic_chip(struct irq_domain *d, unsigned int hw_irq); +int irq_domain_alloc_generic_chips(struct irq_domain *d, + const struct irq_domain_chip_generic_info *info); +void irq_domain_remove_generic_chips(struct irq_domain *d); + int __irq_alloc_domain_generic_chips(struct irq_domain *d, int irqs_per_chip, int num_ct, const char *name, irq_flow_handler_t handler, diff --git a/kernel/irq/generic-chip.c b/kernel/irq/generic-chip.c index d39a40bc542b..d9696f5dcc38 100644 --- a/kernel/irq/generic-chip.c +++ b/kernel/irq/generic-chip.c @@ -275,6 +275,92 @@ irq_gc_init_mask_cache(struct irq_chip_generic *gc, enum irq_gc_flags flags) } } +/** + * irq_domain_alloc_generic_chips - Allocate generic chips for an irq domain + * @d: irq domain for which to allocate chips + * @info: Generic chip information + * + * Return: 0 on success, negative error code on failure + */ +int irq_domain_alloc_generic_chips(struct irq_domain *d, + const struct irq_domain_chip_generic_info *info) +{ + struct irq_domain_chip_generic *dgc; + struct irq_chip_generic *gc; + unsigned long flags; + int numchips, i; + size_t dgc_sz; + size_t gc_sz; + size_t sz; + void *tmp; + + if (d->gc) + return -EBUSY; + + numchips = DIV_ROUND_UP(d->revmap_size, info->irqs_per_chip); + if (!numchips) + return -EINVAL; + + /* Allocate a pointer, generic chip and chiptypes for each chip */ + gc_sz = struct_size(gc, chip_types, info->num_ct); + dgc_sz = struct_size(dgc, gc, numchips); + sz = dgc_sz + numchips * gc_sz; + + tmp = dgc = kzalloc(sz, GFP_KERNEL); + if (!dgc) + return -ENOMEM; + dgc->irqs_per_chip = info->irqs_per_chip; + dgc->num_chips = numchips; + dgc->irq_flags_to_set = info->irq_flags_to_set; + dgc->irq_flags_to_clear = info->irq_flags_to_clear; + dgc->gc_flags = info->gc_flags; + d->gc = dgc; + + /* Calc pointer to the first generic chip */ + tmp += dgc_sz; + for (i = 0; i < numchips; i++) { + /* Store the pointer to the generic chip */ + dgc->gc[i] = gc = tmp; + irq_init_generic_chip(gc, info->name, info->num_ct, + i * dgc->irqs_per_chip, NULL, + info->handler); + + gc->domain = d; + if (dgc->gc_flags & IRQ_GC_BE_IO) { + gc->reg_readl = &irq_readl_be; + gc->reg_writel = &irq_writel_be; + } + + raw_spin_lock_irqsave(&gc_lock, flags); + list_add_tail(&gc->list, &gc_list); + raw_spin_unlock_irqrestore(&gc_lock, flags); + /* Calc pointer to the next generic chip */ + tmp += gc_sz; + } + return 0; +} +EXPORT_SYMBOL_GPL(irq_domain_alloc_generic_chips); + +/** + * irq_domain_remove_generic_chips - Remove generic chips from an irq domain + * @d: irq domain for which generic chips are to be removed + */ +void irq_domain_remove_generic_chips(struct irq_domain *d) +{ + struct irq_domain_chip_generic *dgc = d->gc; + unsigned int i; + + if (!dgc) + return; + + for (i = 0; i < dgc->num_chips; i++) + irq_remove_generic_chip(dgc->gc[i], ~0U, 0, 0); + + d->gc = NULL; + kfree(dgc); +} +EXPORT_SYMBOL_GPL(irq_domain_remove_generic_chips); + /** * __irq_alloc_domain_generic_chips - Allocate generic chips for an irq domain * @d: irq domain for which to allocate chips @@ -292,58 +378,17 @@ int __irq_alloc_domain_generic_chips(struct irq_domain *d, int irqs_per_chip, unsigned int clr, unsigned int set, enum irq_gc_flags gcflags) { - struct irq_domain_chip_generic *dgc; - struct irq_chip_generic *gc; - unsigned long flags; - int numchips, i; - size_t dgc_sz; - size_t gc_sz; - size_t sz; - void *tmp; + struct irq_domain_chip_generic_info info = { + .irqs_per_chip = irqs_per_chip, + .num_ct = num_ct, + .name = name, + .handler = handler, + .irq_flags_to_clear = clr, + .irq_flags_to_set = set, + .gc_flags = gcflags, + }; - if (d->gc) - return -EBUSY; - - numchips = DIV_ROUND_UP(d->revmap_size, irqs_per_chip); - if (!numchips) - return -EINVAL; - - /* Allocate a pointer, generic chip and chiptypes for each chip */ - gc_sz = struct_size(gc, chip_types, num_ct); - dgc_sz = struct_size(dgc, gc, numchips); - sz = dgc_sz + numchips * gc_sz; - - tmp = dgc = kzalloc(sz, GFP_KERNEL); - if (!dgc) - return -ENOMEM; - dgc->irqs_per_chip = irqs_per_chip; - dgc->num_chips = numchips; - dgc->irq_flags_to_set = set; - dgc->irq_flags_to_clear = clr; - dgc->gc_flags = gcflags; - d->gc = dgc; - - /* Calc pointer to the first generic chip */ - tmp += dgc_sz; - for (i = 0; i < numchips; i++) { - /* Store the pointer to the generic chip */ - dgc->gc[i] = gc = tmp; - irq_init_generic_chip(gc, name, num_ct, i * irqs_per_chip, - NULL, handler); - - gc->domain = d; - if (gcflags & IRQ_GC_BE_IO) { - gc->reg_readl = &irq_readl_be; - gc->reg_writel = &irq_writel_be; - } - - raw_spin_lock_irqsave(&gc_lock, flags); - list_add_tail(&gc->list, &gc_list); - raw_spin_unlock_irqrestore(&gc_lock, flags); - /* Calc pointer to the next generic chip */ - tmp += gc_sz; - } - return 0; + return irq_domain_alloc_generic_chips(d, &info); } EXPORT_SYMBOL_GPL(__irq_alloc_domain_generic_chips); From fea922ee9f8ffd3c2a8e8dfbc68de42905a3982a Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:15 +0200 Subject: [PATCH 029/152] genirq/generic_chip: Introduce init() and exit() hooks Most of generic chip drivers need to perform some more additional initializations on the generic chips allocated before they can be fully ready. These additional initializations need to be performed before the IRQ domain is published to avoid a race condition between IRQ consumers and suppliers. Introduce the init() hook to perform these initializations at the right place just after the generic chip creation. Also introduce the exit() hook to allow reverting operations done by the init() hook just before the generic chip is destroyed. Suggested-by: Thomas Gleixner Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-15-herve.codina@bootlin.com --- include/linux/irq.h | 8 ++++++++ kernel/irq/generic-chip.c | 24 ++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/include/linux/irq.h b/include/linux/irq.h index 58264b236cbf..712bcee56efc 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -1106,6 +1106,7 @@ enum irq_gc_flags { * @irq_flags_to_set: IRQ* flags to set on irq setup * @irq_flags_to_clear: IRQ* flags to clear on irq setup * @gc_flags: Generic chip specific setup flags + * @exit: Function called on each chip when they are destroyed. * @gc: Array of pointers to generic interrupt chips */ struct irq_domain_chip_generic { @@ -1114,6 +1115,7 @@ struct irq_domain_chip_generic { unsigned int irq_flags_to_clear; unsigned int irq_flags_to_set; enum irq_gc_flags gc_flags; + void (*exit)(struct irq_chip_generic *gc); struct irq_chip_generic *gc[]; }; @@ -1127,6 +1129,10 @@ struct irq_domain_chip_generic { * @irq_flags_to_clear: IRQ_* bits to clear in the mapping function * @irq_flags_to_set: IRQ_* bits to set in the mapping function * @gc_flags: Generic chip specific setup flags + * @init: Function called on each chip when they are created. + * Allow to do some additional chip initialisation. + * @exit: Function called on each chip when they are destroyed. + * Allow to do some chip cleanup operation. */ struct irq_domain_chip_generic_info { const char *name; @@ -1136,6 +1142,8 @@ struct irq_domain_chip_generic_info { unsigned int irq_flags_to_clear; unsigned int irq_flags_to_set; enum irq_gc_flags gc_flags; + int (*init)(struct irq_chip_generic *gc); + void (*exit)(struct irq_chip_generic *gc); }; /* Generic chip callback functions */ diff --git a/kernel/irq/generic-chip.c b/kernel/irq/generic-chip.c index d9696f5dcc38..32ffcbb87fa1 100644 --- a/kernel/irq/generic-chip.c +++ b/kernel/irq/generic-chip.c @@ -293,6 +293,7 @@ int irq_domain_alloc_generic_chips(struct irq_domain *d, size_t gc_sz; size_t sz; void *tmp; + int ret; if (d->gc) return -EBUSY; @@ -314,6 +315,7 @@ int irq_domain_alloc_generic_chips(struct irq_domain *d, dgc->irq_flags_to_set = info->irq_flags_to_set; dgc->irq_flags_to_clear = info->irq_flags_to_clear; dgc->gc_flags = info->gc_flags; + dgc->exit = info->exit; d->gc = dgc; /* Calc pointer to the first generic chip */ @@ -331,6 +333,12 @@ int irq_domain_alloc_generic_chips(struct irq_domain *d, gc->reg_writel = &irq_writel_be; } + if (info->init) { + ret = info->init(gc); + if (ret) + goto err; + } + raw_spin_lock_irqsave(&gc_lock, flags); list_add_tail(&gc->list, &gc_list); raw_spin_unlock_irqrestore(&gc_lock, flags); @@ -338,6 +346,16 @@ int irq_domain_alloc_generic_chips(struct irq_domain *d, tmp += gc_sz; } return 0; + +err: + while (i--) { + if (dgc->exit) + dgc->exit(dgc->gc[i]); + irq_remove_generic_chip(dgc->gc[i], ~0U, 0, 0); + } + d->gc = NULL; + kfree(dgc); + return ret; } EXPORT_SYMBOL_GPL(irq_domain_alloc_generic_chips); @@ -353,9 +371,11 @@ void irq_domain_remove_generic_chips(struct irq_domain *d) if (!dgc) return; - for (i = 0; i < dgc->num_chips; i++) + for (i = 0; i < dgc->num_chips; i++) { + if (dgc->exit) + dgc->exit(dgc->gc[i]); irq_remove_generic_chip(dgc->gc[i], ~0U, 0, 0); - + } d->gc = NULL; kfree(dgc); } From e6f67ce32e8e6dcbadf42dc435fbc9002cabf1f9 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:16 +0200 Subject: [PATCH 030/152] irqdomain: Add support for generic irq chips creation before publishing a domain The current API functions create an irq_domain and also publish this newly created to domain. Once an irq_domain is published, consumers can request IRQ in order to use them. Some interrupt controller drivers have to perform some more operations with the created irq_domain in order to have it ready to be used. For instance: - Allocate generic irq chips with irq_alloc_domain_generic_chips() - Retrieve the generic irq chips with irq_get_domain_generic_chip() - Initialize retrieved chips: set register base address and offsets, set several hooks such as irq_mask, irq_unmask, ... With the newly introduced irq_domain_alloc_generic_chips(), an interrupt controller driver can use the irq_domain_chip_generic_info structure and set the init() hook to perform its generic chips initialization. In order to avoid a window where the domain is published but not yet ready to be used, handle the generic chip creation (i.e the irq_domain_alloc_generic_chips() call) before the domain is published. Suggested-by: Thomas Gleixner Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-16-herve.codina@bootlin.com --- include/linux/irqdomain.h | 9 +++++++++ kernel/irq/irqdomain.c | 14 +++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 2c927edc4d43..5540b22a755c 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -210,6 +210,9 @@ enum { /* Irq domain is a MSI device domain */ IRQ_DOMAIN_FLAG_MSI_DEVICE = (1 << 9), + /* Irq domain must destroy generic chips when removed */ + IRQ_DOMAIN_FLAG_DESTROY_GC = (1 << 10), + /* * Flags starting from IRQ_DOMAIN_FLAG_NONCORE are reserved * for implementation specific purposes and ignored by the @@ -259,6 +262,9 @@ static inline struct fwnode_handle *irq_domain_alloc_fwnode(phys_addr_t *pa) } void irq_domain_free_fwnode(struct fwnode_handle *fwnode); + +struct irq_domain_chip_generic_info; + /** * struct irq_domain_info - Domain information structure * @fwnode: firmware node for the interrupt controller @@ -270,6 +276,8 @@ void irq_domain_free_fwnode(struct fwnode_handle *fwnode); * @bus_token: Domain bus token * @ops: Domain operation callbacks * @host_data: Controller private data pointer + * @dgc_info: Geneneric chip information structure pointer used to + * create generic chips for the domain if not NULL. * @init: Function called when the domain is created. * Allow to do some additional domain initialisation. * @exit: Function called when the domain is destroyed. @@ -290,6 +298,7 @@ struct irq_domain_info { */ struct irq_domain *parent; #endif + struct irq_domain_chip_generic_info *dgc_info; int (*init)(struct irq_domain *d); void (*exit)(struct irq_domain *d); }; diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index a0324d8e6dab..4d2a40359eb2 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -292,16 +292,25 @@ struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info) } #endif + if (info->dgc_info) { + err = irq_domain_alloc_generic_chips(domain, info->dgc_info); + if (err) + goto err_domain_free; + } + if (info->init) { err = info->init(domain); if (err) - goto err_domain_free; + goto err_domain_gc_remove; } __irq_domain_publish(domain); return domain; +err_domain_gc_remove: + if (info->dgc_info) + irq_domain_remove_generic_chips(domain); err_domain_free: irq_domain_free(domain); return ERR_PTR(err); @@ -369,6 +378,9 @@ void irq_domain_remove(struct irq_domain *domain) mutex_unlock(&irq_domain_mutex); + if (domain->flags & IRQ_DOMAIN_FLAG_DESTROY_GC) + irq_domain_remove_generic_chips(domain); + pr_debug("Removed domain %s\n", domain->name); irq_domain_free(domain); } From 0c5b29a6dc7b463b6072da8cef43800008728ff3 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:17 +0200 Subject: [PATCH 031/152] irqdomain: Add a resource managed version of irq_domain_instantiate() Add a devres version of irq_domain_instantiate(). Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-17-herve.codina@bootlin.com --- include/linux/irqdomain.h | 2 ++ kernel/irq/devres.c | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 5540b22a755c..8820317582c4 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -304,6 +304,8 @@ struct irq_domain_info { }; struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info); +struct irq_domain *devm_irq_domain_instantiate(struct device *dev, + const struct irq_domain_info *info); struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, unsigned int size, irq_hw_number_t hwirq_max, int direct_max, diff --git a/kernel/irq/devres.c b/kernel/irq/devres.c index f6e5515ee077..b3e98668f4dd 100644 --- a/kernel/irq/devres.c +++ b/kernel/irq/devres.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include #include +#include #include #include #include @@ -282,3 +283,43 @@ int devm_irq_setup_generic_chip(struct device *dev, struct irq_chip_generic *gc, } EXPORT_SYMBOL_GPL(devm_irq_setup_generic_chip); #endif /* CONFIG_GENERIC_IRQ_CHIP */ + +#ifdef CONFIG_IRQ_DOMAIN +static void devm_irq_domain_remove(struct device *dev, void *res) +{ + struct irq_domain **domain = res; + + irq_domain_remove(*domain); +} + +/** + * devm_irq_domain_instantiate() - Instantiate a new irq domain data for a + * managed device. + * @dev: Device to instantiate the domain for + * @info: Domain information pointer pointing to the information for this + * domain + * + * Return: A pointer to the instantiated irq domain or an ERR_PTR value. + */ +struct irq_domain *devm_irq_domain_instantiate(struct device *dev, + const struct irq_domain_info *info) +{ + struct irq_domain *domain; + struct irq_domain **dr; + + dr = devres_alloc(devm_irq_domain_remove, sizeof(*dr), GFP_KERNEL); + if (!dr) + return ERR_PTR(-ENOMEM); + + domain = irq_domain_instantiate(info); + if (!IS_ERR(domain)) { + *dr = domain; + devres_add(dev, dr); + } else { + devres_free(dr); + } + + return domain; +} +EXPORT_SYMBOL_GPL(devm_irq_domain_instantiate); +#endif /* CONFIG_IRQ_DOMAIN */ From 7c53626cd11820a11f9cb2c54c02d47fc062a265 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:18 +0200 Subject: [PATCH 032/152] irqdomain: Convert __irq_domain_add() wrappers to irq_domain_instantiate() __irq_domain_add() wrappers use directly __irq_domain_add(). With the introduction of irq_domain_instantiate(), __irq_domain_add() becomes obsolete. In order to fully remove __irq_domain_add(), convert wrappers to irq_domain_instantiate() [ tglx: Fixup struct initializers ] Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-18-herve.codina@bootlin.com --- include/linux/irqdomain.h | 58 +++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 8820317582c4..33a968fbdda2 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -400,7 +400,17 @@ static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_no const struct irq_domain_ops *ops, void *host_data) { - return __irq_domain_add(of_node_to_fwnode(of_node), size, size, 0, ops, host_data); + struct irq_domain_info info = { + .fwnode = of_node_to_fwnode(of_node), + .size = size, + .hwirq_max = size, + .ops = ops, + .host_data = host_data, + }; + struct irq_domain *d; + + d = irq_domain_instantiate(&info); + return IS_ERR(d) ? NULL : d; } #ifdef CONFIG_IRQ_DOMAIN_NOMAP @@ -409,7 +419,17 @@ static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_nod const struct irq_domain_ops *ops, void *host_data) { - return __irq_domain_add(of_node_to_fwnode(of_node), 0, max_irq, max_irq, ops, host_data); + struct irq_domain_info info = { + .fwnode = of_node_to_fwnode(of_node), + .hwirq_max = max_irq, + .direct_max = max_irq, + .ops = ops, + .host_data = host_data, + }; + struct irq_domain *d; + + d = irq_domain_instantiate(&info); + return IS_ERR(d) ? NULL : d; } extern unsigned int irq_create_direct_mapping(struct irq_domain *host); @@ -419,7 +439,16 @@ static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node const struct irq_domain_ops *ops, void *host_data) { - return __irq_domain_add(of_node_to_fwnode(of_node), 0, ~0, 0, ops, host_data); + struct irq_domain_info info = { + .fwnode = of_node_to_fwnode(of_node), + .hwirq_max = ~0U, + .ops = ops, + .host_data = host_data, + }; + struct irq_domain *d; + + d = irq_domain_instantiate(&info); + return IS_ERR(d) ? NULL : d; } static inline struct irq_domain *irq_domain_create_linear(struct fwnode_handle *fwnode, @@ -427,14 +456,33 @@ static inline struct irq_domain *irq_domain_create_linear(struct fwnode_handle * const struct irq_domain_ops *ops, void *host_data) { - return __irq_domain_add(fwnode, size, size, 0, ops, host_data); + struct irq_domain_info info = { + .fwnode = fwnode, + .size = size, + .hwirq_max = size, + .ops = ops, + .host_data = host_data, + }; + struct irq_domain *d; + + d = irq_domain_instantiate(&info); + return IS_ERR(d) ? NULL : d; } static inline struct irq_domain *irq_domain_create_tree(struct fwnode_handle *fwnode, const struct irq_domain_ops *ops, void *host_data) { - return __irq_domain_add(fwnode, 0, ~0, 0, ops, host_data); + struct irq_domain_info info = { + .fwnode = fwnode, + .hwirq_max = ~0, + .ops = ops, + .host_data = host_data, + }; + struct irq_domain *d; + + d = irq_domain_instantiate(&info); + return IS_ERR(d) ? NULL : d; } extern void irq_domain_remove(struct irq_domain *host); From 2ada5ed6ecac06e32defe39b15b33e9a6b004413 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:19 +0200 Subject: [PATCH 033/152] irqdomain: Convert domain creation functions to irq_domain_instantiate() Domain creation functions use __irq_domain_add(). With the introduction of irq_domain_instantiate(), __irq_domain_add() becomes obsolete. In order to fully remove __irq_domain_add(), convert domain creation function to irq_domain_instantiate() Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-19-herve.codina@bootlin.com --- kernel/irq/irqdomain.c | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 4d2a40359eb2..c9b076c09eb4 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -442,10 +442,17 @@ struct irq_domain *irq_domain_create_simple(struct fwnode_handle *fwnode, const struct irq_domain_ops *ops, void *host_data) { + struct irq_domain_info info = { + .fwnode = fwnode, + .size = size, + .hwirq_max = size, + .ops = ops, + .host_data = host_data, + }; struct irq_domain *domain; - domain = __irq_domain_add(fwnode, size, size, 0, ops, host_data); - if (!domain) + domain = irq_domain_instantiate(&info); + if (IS_ERR(domain)) return NULL; if (first_irq > 0) { @@ -498,11 +505,20 @@ struct irq_domain *irq_domain_create_legacy(struct fwnode_handle *fwnode, const struct irq_domain_ops *ops, void *host_data) { + struct irq_domain_info info = { + .fwnode = fwnode, + .size = first_hwirq + size, + .hwirq_max = first_hwirq + size, + .ops = ops, + .host_data = host_data, + }; struct irq_domain *domain; - domain = __irq_domain_add(fwnode, first_hwirq + size, first_hwirq + size, 0, ops, host_data); - if (domain) - irq_domain_associate_many(domain, first_irq, first_hwirq, size); + domain = irq_domain_instantiate(&info); + if (IS_ERR(domain)) + return NULL; + + irq_domain_associate_many(domain, first_irq, first_hwirq, size); return domain; } From a701f8e93b8355a0817a3b60974e8211797e0733 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:20 +0200 Subject: [PATCH 034/152] _PATCH_19_23_um_virt_pci_Use_irq_domain_instantiate_ um_pci_init() uses __irq_domain_add(). With the introduction of irq_domain_instantiate(), __irq_domain_add() becomes obsolete. In order to fully remove __irq_domain_add(), use directly irq_domain_instantiate(). [ tglx: Fixup struct initializer ] Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-20-herve.codina@bootlin.com --- arch/um/drivers/virt-pci.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/arch/um/drivers/virt-pci.c b/arch/um/drivers/virt-pci.c index 7cb503469bbd..a0883d5445b5 100644 --- a/arch/um/drivers/virt-pci.c +++ b/arch/um/drivers/virt-pci.c @@ -986,6 +986,11 @@ static struct resource virt_platform_resource = { static int __init um_pci_init(void) { + struct irq_domain_info inner_domain_info = { + .size = MAX_MSI_VECTORS, + .hwirq_max = MAX_MSI_VECTORS, + .ops = &um_pci_inner_domain_ops, + }; int err, i; WARN_ON(logic_iomem_add_region(&virt_cfgspace_resource, @@ -1015,11 +1020,10 @@ static int __init um_pci_init(void) goto free; } - um_pci_inner_domain = __irq_domain_add(um_pci_fwnode, MAX_MSI_VECTORS, - MAX_MSI_VECTORS, 0, - &um_pci_inner_domain_ops, NULL); - if (!um_pci_inner_domain) { - err = -ENOMEM; + inner_domain_info.fwnode = um_pci_fwnode; + um_pci_inner_domain = irq_domain_instantiate(&inner_domain_info); + if (IS_ERR(um_pci_inner_domain)) { + err = PTR_ERR(um_pci_inner_domain); goto free; } @@ -1056,7 +1060,7 @@ static int __init um_pci_init(void) goto free; return 0; free: - if (um_pci_inner_domain) + if (!IS_ERR_OR_NULL(um_pci_inner_domain)) irq_domain_remove(um_pci_inner_domain); if (um_pci_fwnode) irq_domain_free_fwnode(um_pci_fwnode); From 0b4b172b760efabf8a77ea17644d333fbb444d39 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:21 +0200 Subject: [PATCH 035/152] irqdomain: Remove __irq_domain_add() __irq_domain_add() has been replaced by irq_domain_instanciate() and so, it is no more used. Simply remove it. Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-21-herve.codina@bootlin.com --- include/linux/irqdomain.h | 6 +----- kernel/irq/irqdomain.c | 33 --------------------------------- 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 33a968fbdda2..02cd486ac354 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -184,7 +184,7 @@ enum { /* Irq domain is hierarchical */ IRQ_DOMAIN_FLAG_HIERARCHY = (1 << 0), - /* Irq domain name was allocated in __irq_domain_add() */ + /* Irq domain name was allocated internally */ IRQ_DOMAIN_NAME_ALLOCATED = (1 << 1), /* Irq domain is an IPI domain with virq per cpu */ @@ -307,10 +307,6 @@ struct irq_domain *irq_domain_instantiate(const struct irq_domain_info *info); struct irq_domain *devm_irq_domain_instantiate(struct device *dev, const struct irq_domain_info *info); -struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, unsigned int size, - irq_hw_number_t hwirq_max, int direct_max, - const struct irq_domain_ops *ops, - void *host_data); struct irq_domain *irq_domain_create_simple(struct fwnode_handle *fwnode, unsigned int size, unsigned int first_irq, diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index c9b076c09eb4..91eaf6bfcbf0 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -317,39 +317,6 @@ err_domain_free: } EXPORT_SYMBOL_GPL(irq_domain_instantiate); -/** - * __irq_domain_add() - Allocate a new irq_domain data structure - * @fwnode: firmware node for the interrupt controller - * @size: Size of linear map; 0 for radix mapping only - * @hwirq_max: Maximum number of interrupts supported by controller - * @direct_max: Maximum value of direct maps; Use ~0 for no limit; 0 for no - * direct mapping - * @ops: domain callbacks - * @host_data: Controller private data pointer - * - * Allocates and initializes an irq_domain structure. - * Returns pointer to IRQ domain, or NULL on failure. - */ -struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, unsigned int size, - irq_hw_number_t hwirq_max, int direct_max, - const struct irq_domain_ops *ops, - void *host_data) -{ - struct irq_domain_info info = { - .fwnode = fwnode, - .size = size, - .hwirq_max = hwirq_max, - .direct_max = direct_max, - .ops = ops, - .host_data = host_data, - }; - struct irq_domain *d; - - d = irq_domain_instantiate(&info); - return IS_ERR(d) ? NULL : d; -} -EXPORT_SYMBOL_GPL(__irq_domain_add); - /** * irq_domain_remove() - Remove an irq domain. * @domain: domain to remove From 17972a5f1ba85ad8c4f32dfd00ff620a85e98416 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:22 +0200 Subject: [PATCH 036/152] dt-bindings: interrupt-controller: Add support for Microchip LAN966x OIC The Microchip LAN966x outband interrupt controller (OIC) maps the internal interrupt sources of the LAN966x device to an external interrupt. When the LAN966x device is used as a PCI device, the external interrupt is routed to the PCI interrupt. Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20240614173232.1184015-22-herve.codina@bootlin.com --- .../microchip,lan966x-oic.yaml | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 Documentation/devicetree/bindings/interrupt-controller/microchip,lan966x-oic.yaml diff --git a/Documentation/devicetree/bindings/interrupt-controller/microchip,lan966x-oic.yaml b/Documentation/devicetree/bindings/interrupt-controller/microchip,lan966x-oic.yaml new file mode 100644 index 000000000000..b2adc7174177 --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/microchip,lan966x-oic.yaml @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/interrupt-controller/microchip,lan966x-oic.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Microchip LAN966x outband interrupt controller + +maintainers: + - Herve Codina + +allOf: + - $ref: /schemas/interrupt-controller.yaml# + +description: | + The Microchip LAN966x outband interrupt controller (OIC) maps the internal + interrupt sources of the LAN966x device to an external interrupt. + When the LAN966x device is used as a PCI device, the external interrupt is + routed to the PCI interrupt. + +properties: + compatible: + const: microchip,lan966x-oic + + '#interrupt-cells': + const: 2 + + interrupt-controller: true + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + +required: + - compatible + - '#interrupt-cells' + - interrupt-controller + - interrupts + - reg + +additionalProperties: false + +examples: + - | + interrupt-controller@e00c0120 { + compatible = "microchip,lan966x-oic"; + reg = <0xe00c0120 0x190>; + #interrupt-cells = <2>; + interrupt-controller; + interrupts = <0>; + interrupt-parent = <&intc>; + }; +... From 3e3a7b35332924c8ade696c4b24735561ee6aea3 Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:23 +0200 Subject: [PATCH 037/152] irqchip: Add support for LAN966x OIC The Microchip LAN966x outband interrupt controller (OIC) maps the internal interrupt sources of the LAN966x device to an external interrupt. When the LAN966x device is used as a PCI device, the external interrupt is routed to the PCI interrupt. Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-23-herve.codina@bootlin.com --- drivers/irqchip/Kconfig | 12 ++ drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-lan966x-oic.c | 278 ++++++++++++++++++++++++++++++ 3 files changed, 291 insertions(+) create mode 100644 drivers/irqchip/irq-lan966x-oic.c diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 14464716bacb..348f34525d23 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -169,6 +169,18 @@ config IXP4XX_IRQ select IRQ_DOMAIN select SPARSE_IRQ +config LAN966X_OIC + tristate "Microchip LAN966x OIC Support" + select GENERIC_IRQ_CHIP + select IRQ_DOMAIN + help + Enable support for the LAN966x Outbound Interrupt Controller. + This controller is present on the Microchip LAN966x PCI device and + maps the internal interrupts sources to PCIe interrupt. + + To compile this driver as a module, choose M here: the module + will be called irq-lan966x-oic. + config MADERA_IRQ tristate diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index d9dc3d99aaa8..9f6f88274bec 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -104,6 +104,7 @@ obj-$(CONFIG_IMX_IRQSTEER) += irq-imx-irqsteer.o obj-$(CONFIG_IMX_INTMUX) += irq-imx-intmux.o obj-$(CONFIG_IMX_MU_MSI) += irq-imx-mu-msi.o obj-$(CONFIG_MADERA_IRQ) += irq-madera.o +obj-$(CONFIG_LAN966X_OIC) += irq-lan966x-oic.o obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o diff --git a/drivers/irqchip/irq-lan966x-oic.c b/drivers/irqchip/irq-lan966x-oic.c new file mode 100644 index 000000000000..41ac880e3b87 --- /dev/null +++ b/drivers/irqchip/irq-lan966x-oic.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Microchip LAN966x outbound interrupt controller + * + * Copyright (c) 2024 Technology Inc. and its subsidiaries. + * + * Authors: + * Horatiu Vultur + * Clément Léger + * Herve Codina + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct lan966x_oic_chip_regs { + int reg_off_ena_set; + int reg_off_ena_clr; + int reg_off_sticky; + int reg_off_ident; + int reg_off_map; +}; + +struct lan966x_oic_data { + void __iomem *regs; + int irq; +}; + +#define LAN966X_OIC_NR_IRQ 86 + +/* Interrupt sticky status */ +#define LAN966X_OIC_INTR_STICKY 0x30 +#define LAN966X_OIC_INTR_STICKY1 0x34 +#define LAN966X_OIC_INTR_STICKY2 0x38 + +/* Interrupt enable */ +#define LAN966X_OIC_INTR_ENA 0x48 +#define LAN966X_OIC_INTR_ENA1 0x4c +#define LAN966X_OIC_INTR_ENA2 0x50 + +/* Atomic clear of interrupt enable */ +#define LAN966X_OIC_INTR_ENA_CLR 0x54 +#define LAN966X_OIC_INTR_ENA_CLR1 0x58 +#define LAN966X_OIC_INTR_ENA_CLR2 0x5c + +/* Atomic set of interrupt */ +#define LAN966X_OIC_INTR_ENA_SET 0x60 +#define LAN966X_OIC_INTR_ENA_SET1 0x64 +#define LAN966X_OIC_INTR_ENA_SET2 0x68 + +/* Mapping of source to destination interrupts (_n = 0..8) */ +#define LAN966X_OIC_DST_INTR_MAP(_n) (0x78 + (_n) * 4) +#define LAN966X_OIC_DST_INTR_MAP1(_n) (0x9c + (_n) * 4) +#define LAN966X_OIC_DST_INTR_MAP2(_n) (0xc0 + (_n) * 4) + +/* Currently active interrupt sources per destination (_n = 0..8) */ +#define LAN966X_OIC_DST_INTR_IDENT(_n) (0xe4 + (_n) * 4) +#define LAN966X_OIC_DST_INTR_IDENT1(_n) (0x108 + (_n) * 4) +#define LAN966X_OIC_DST_INTR_IDENT2(_n) (0x12c + (_n) * 4) + +static unsigned int lan966x_oic_irq_startup(struct irq_data *data) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); + struct irq_chip_type *ct = irq_data_get_chip_type(data); + struct lan966x_oic_chip_regs *chip_regs = gc->private; + u32 map; + + irq_gc_lock(gc); + + /* Map the source interrupt to the destination */ + map = irq_reg_readl(gc, chip_regs->reg_off_map); + map |= data->mask; + irq_reg_writel(gc, map, chip_regs->reg_off_map); + + irq_gc_unlock(gc); + + ct->chip.irq_ack(data); + ct->chip.irq_unmask(data); + + return 0; +} + +static void lan966x_oic_irq_shutdown(struct irq_data *data) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); + struct irq_chip_type *ct = irq_data_get_chip_type(data); + struct lan966x_oic_chip_regs *chip_regs = gc->private; + u32 map; + + ct->chip.irq_mask(data); + + irq_gc_lock(gc); + + /* Unmap the interrupt */ + map = irq_reg_readl(gc, chip_regs->reg_off_map); + map &= ~data->mask; + irq_reg_writel(gc, map, chip_regs->reg_off_map); + + irq_gc_unlock(gc); +} + +static int lan966x_oic_irq_set_type(struct irq_data *data, + unsigned int flow_type) +{ + if (flow_type != IRQ_TYPE_LEVEL_HIGH) { + pr_err("lan966x oic doesn't support flow type %d\n", flow_type); + return -EINVAL; + } + + return 0; +} + +static void lan966x_oic_irq_handler_domain(struct irq_domain *d, u32 first_irq) +{ + struct irq_chip_generic *gc = irq_get_domain_generic_chip(d, first_irq); + struct lan966x_oic_chip_regs *chip_regs = gc->private; + unsigned long ident; + unsigned int hwirq; + + ident = irq_reg_readl(gc, chip_regs->reg_off_ident); + if (!ident) + return; + + for_each_set_bit(hwirq, &ident, 32) + generic_handle_domain_irq(d, hwirq + first_irq); +} + +static void lan966x_oic_irq_handler(struct irq_desc *desc) +{ + struct irq_domain *d = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + + chained_irq_enter(chip, desc); + lan966x_oic_irq_handler_domain(d, 0); + lan966x_oic_irq_handler_domain(d, 32); + lan966x_oic_irq_handler_domain(d, 64); + chained_irq_exit(chip, desc); +} + +static struct lan966x_oic_chip_regs lan966x_oic_chip_regs[3] = { + { + .reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET, + .reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR, + .reg_off_sticky = LAN966X_OIC_INTR_STICKY, + .reg_off_ident = LAN966X_OIC_DST_INTR_IDENT(0), + .reg_off_map = LAN966X_OIC_DST_INTR_MAP(0), + }, { + .reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET1, + .reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR1, + .reg_off_sticky = LAN966X_OIC_INTR_STICKY1, + .reg_off_ident = LAN966X_OIC_DST_INTR_IDENT1(0), + .reg_off_map = LAN966X_OIC_DST_INTR_MAP1(0), + }, { + .reg_off_ena_set = LAN966X_OIC_INTR_ENA_SET2, + .reg_off_ena_clr = LAN966X_OIC_INTR_ENA_CLR2, + .reg_off_sticky = LAN966X_OIC_INTR_STICKY2, + .reg_off_ident = LAN966X_OIC_DST_INTR_IDENT2(0), + .reg_off_map = LAN966X_OIC_DST_INTR_MAP2(0), + } +}; + +static int lan966x_oic_chip_init(struct irq_chip_generic *gc) +{ + struct lan966x_oic_data *lan966x_oic = gc->domain->host_data; + struct lan966x_oic_chip_regs *chip_regs; + + chip_regs = &lan966x_oic_chip_regs[gc->irq_base / 32]; + + gc->reg_base = lan966x_oic->regs; + gc->chip_types[0].regs.enable = chip_regs->reg_off_ena_set; + gc->chip_types[0].regs.disable = chip_regs->reg_off_ena_clr; + gc->chip_types[0].regs.ack = chip_regs->reg_off_sticky; + gc->chip_types[0].chip.irq_startup = lan966x_oic_irq_startup; + gc->chip_types[0].chip.irq_shutdown = lan966x_oic_irq_shutdown; + gc->chip_types[0].chip.irq_set_type = lan966x_oic_irq_set_type; + gc->chip_types[0].chip.irq_mask = irq_gc_mask_disable_reg; + gc->chip_types[0].chip.irq_unmask = irq_gc_unmask_enable_reg; + gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit; + gc->private = chip_regs; + + /* Disable all interrupts handled by this chip */ + irq_reg_writel(gc, ~0U, chip_regs->reg_off_ena_clr); + + return 0; +} + +static void lan966x_oic_chip_exit(struct irq_chip_generic *gc) +{ + /* Disable and ack all interrupts handled by this chip */ + irq_reg_writel(gc, ~0U, gc->chip_types[0].regs.disable); + irq_reg_writel(gc, ~0U, gc->chip_types[0].regs.ack); +} + +static int lan966x_oic_domain_init(struct irq_domain *d) +{ + struct lan966x_oic_data *lan966x_oic = d->host_data; + + irq_set_chained_handler_and_data(lan966x_oic->irq, lan966x_oic_irq_handler, d); + + return 0; +} + +static void lan966x_oic_domain_exit(struct irq_domain *d) +{ + struct lan966x_oic_data *lan966x_oic = d->host_data; + + irq_set_chained_handler_and_data(lan966x_oic->irq, NULL, NULL); +} + +static int lan966x_oic_probe(struct platform_device *pdev) +{ + struct irq_domain_chip_generic_info dgc_info = { + .name = "lan966x-oic", + .handler = handle_level_irq, + .irqs_per_chip = 32, + .num_ct = 1, + .init = lan966x_oic_chip_init, + .exit = lan966x_oic_chip_exit, + }; + struct irq_domain_info d_info = { + .fwnode = of_node_to_fwnode(pdev->dev.of_node), + .domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC, + .size = LAN966X_OIC_NR_IRQ, + .hwirq_max = LAN966X_OIC_NR_IRQ, + .ops = &irq_generic_chip_ops, + .dgc_info = &dgc_info, + .init = lan966x_oic_domain_init, + .exit = lan966x_oic_domain_exit, + }; + struct lan966x_oic_data *lan966x_oic; + struct device *dev = &pdev->dev; + struct irq_domain *domain; + + lan966x_oic = devm_kmalloc(dev, sizeof(*lan966x_oic), GFP_KERNEL); + if (!lan966x_oic) + return -ENOMEM; + + lan966x_oic->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(lan966x_oic->regs)) + return dev_err_probe(dev, PTR_ERR(lan966x_oic->regs), + "failed to map resource\n"); + + lan966x_oic->irq = platform_get_irq(pdev, 0); + if (lan966x_oic->irq < 0) + return dev_err_probe(dev, lan966x_oic->irq, "failed to get the IRQ\n"); + + d_info.host_data = lan966x_oic; + domain = devm_irq_domain_instantiate(dev, &d_info); + if (IS_ERR(domain)) + return dev_err_probe(dev, PTR_ERR(domain), + "failed to instantiate the IRQ domain\n"); + return 0; +} + +static const struct of_device_id lan966x_oic_of_match[] = { + { .compatible = "microchip,lan966x-oic" }, + {} /* sentinel */ +}; +MODULE_DEVICE_TABLE(of, lan966x_oic_of_match); + +static struct platform_driver lan966x_oic_driver = { + .probe = lan966x_oic_probe, + .driver = { + .name = "lan966x-oic", + .of_match_table = lan966x_oic_of_match, + }, +}; +module_platform_driver(lan966x_oic_driver); + +MODULE_AUTHOR("Herve Codina "); +MODULE_DESCRIPTION("Microchip LAN966x OIC driver"); +MODULE_LICENSE("GPL"); From 92584deade41ac8dda3f9b79ab545f50ba0e95db Mon Sep 17 00:00:00 2001 From: Herve Codina Date: Fri, 14 Jun 2024 19:32:24 +0200 Subject: [PATCH 038/152] MAINTAINERS: Add the Microchip LAN966x OIC driver entry After contributing the driver, add myself as the maintainer for the Microchip LAN966x OIC driver. Signed-off-by: Herve Codina Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614173232.1184015-24-herve.codina@bootlin.com --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 8754ac2c259d..c47d8b9114b8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14727,6 +14727,12 @@ L: netdev@vger.kernel.org S: Maintained F: drivers/net/ethernet/microchip/lan966x/* +MICROCHIP LAN966X OIC DRIVER +M: Herve Codina +S: Maintained +F: Documentation/devicetree/bindings/interrupt-controller/microchip,lan966x-oic.yaml +F: drivers/irqchip/irq-lan966x-oic.c + MICROCHIP LCDFB DRIVER M: Nicolas Ferre L: linux-fbdev@vger.kernel.org From 8cb2dbf94e44bcde4cff0223f2f900f8fb9083a4 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Mon, 17 Jun 2024 20:31:31 +0200 Subject: [PATCH 039/152] irqdomain: Make build work for CONFIG_GENERIC_IRQ_CHIP=n ld: kernel/irq/irqdomain.o: in function `irq_domain_instantiate': kernel/irq/irqdomain.c:296:(.text+0x10dd): undefined reference to `irq_domain_alloc_generic_chips' ld: kernel/irq/irqdomain.c:313:(.text+0x1218): undefined reference to `irq_domain_remove_generic_chips' ld: kernel/irq/irqdomain.o: in function `irq_domain_remove': kernel/irq/irqdomain.c:349:(.text+0x1ddf): undefined reference to `irq_domain_remove_generic_chips' Provide the required stubs. Fixes: e6f67ce32e8e ("irqdomain: Add support for generic irq chips creation before publishing a domain") Reported-by: Borislav Betkov Signed-off-by: Thomas Gleixner --- include/linux/irq.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/linux/irq.h b/include/linux/irq.h index 712bcee56efc..1f5dbf1f92c9 100644 --- a/include/linux/irq.h +++ b/include/linux/irq.h @@ -1182,9 +1182,19 @@ int devm_irq_setup_generic_chip(struct device *dev, struct irq_chip_generic *gc, struct irq_chip_generic *irq_get_domain_generic_chip(struct irq_domain *d, unsigned int hw_irq); +#ifdef CONFIG_GENERIC_IRQ_CHIP int irq_domain_alloc_generic_chips(struct irq_domain *d, const struct irq_domain_chip_generic_info *info); void irq_domain_remove_generic_chips(struct irq_domain *d); +#else +static inline int +irq_domain_alloc_generic_chips(struct irq_domain *d, + const struct irq_domain_chip_generic_info *info) +{ + return -EINVAL; +} +static inline void irq_domain_remove_generic_chips(struct irq_domain *d) { } +#endif /* CONFIG_GENERIC_IRQ_CHIP */ int __irq_alloc_domain_generic_chips(struct irq_domain *d, int irqs_per_chip, int num_ct, const char *name, From 61894818e3049e8b3864fecf52164b125bd77aae Mon Sep 17 00:00:00 2001 From: Marcos Paulo de Souza Date: Mon, 3 Jun 2024 14:26:19 -0300 Subject: [PATCH 040/152] selftests: livepatch: Test atomic replace against multiple modules Adapt the current test-livepatch.sh script to account the number of applied livepatches and ensure that an atomic replace livepatch disables all previously applied livepatches. Signed-off-by: Marcos Paulo de Souza Acked-by: Joe Lawrence Reviewed-by: Miroslav Benes Link: https://lore.kernel.org/r/20240603-lp-atomic-replace-v3-1-9f3b8ace5c9f@suse.com [mbenes@suse.cz: Fixed typo in a comment.] Reviewed-by: Petr Mladek Signed-off-by: Petr Mladek --- .../selftests/livepatch/test-livepatch.sh | 138 +++++++++++------- 1 file changed, 89 insertions(+), 49 deletions(-) diff --git a/tools/testing/selftests/livepatch/test-livepatch.sh b/tools/testing/selftests/livepatch/test-livepatch.sh index e3455a6b1158..65c9c058458d 100755 --- a/tools/testing/selftests/livepatch/test-livepatch.sh +++ b/tools/testing/selftests/livepatch/test-livepatch.sh @@ -4,7 +4,9 @@ . $(dirname $0)/functions.sh -MOD_LIVEPATCH=test_klp_livepatch +MOD_LIVEPATCH1=test_klp_livepatch +MOD_LIVEPATCH2=test_klp_syscall +MOD_LIVEPATCH3=test_klp_callbacks_demo MOD_REPLACE=test_klp_atomic_replace setup_config @@ -16,33 +18,33 @@ setup_config start_test "basic function patching" -load_lp $MOD_LIVEPATCH +load_lp $MOD_LIVEPATCH1 -if [[ "$(cat /proc/cmdline)" != "$MOD_LIVEPATCH: this has been live patched" ]] ; then +if [[ "$(cat /proc/cmdline)" != "$MOD_LIVEPATCH1: this has been live patched" ]] ; then echo -e "FAIL\n\n" die "livepatch kselftest(s) failed" fi -disable_lp $MOD_LIVEPATCH -unload_lp $MOD_LIVEPATCH +disable_lp $MOD_LIVEPATCH1 +unload_lp $MOD_LIVEPATCH1 -if [[ "$(cat /proc/cmdline)" == "$MOD_LIVEPATCH: this has been live patched" ]] ; then +if [[ "$(cat /proc/cmdline)" == "$MOD_LIVEPATCH1: this has been live patched" ]] ; then echo -e "FAIL\n\n" die "livepatch kselftest(s) failed" fi -check_result "% insmod test_modules/$MOD_LIVEPATCH.ko -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -livepatch: '$MOD_LIVEPATCH': patching complete -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled -livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -livepatch: '$MOD_LIVEPATCH': starting unpatching transition -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -livepatch: '$MOD_LIVEPATCH': unpatching complete -% rmmod $MOD_LIVEPATCH" +check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko +livepatch: enabling patch '$MOD_LIVEPATCH1' +livepatch: '$MOD_LIVEPATCH1': initializing patching transition +livepatch: '$MOD_LIVEPATCH1': starting patching transition +livepatch: '$MOD_LIVEPATCH1': completing patching transition +livepatch: '$MOD_LIVEPATCH1': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH1/enabled +livepatch: '$MOD_LIVEPATCH1': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH1': starting unpatching transition +livepatch: '$MOD_LIVEPATCH1': completing unpatching transition +livepatch: '$MOD_LIVEPATCH1': unpatching complete +% rmmod $MOD_LIVEPATCH1" # - load a livepatch that modifies the output from /proc/cmdline and @@ -53,7 +55,7 @@ livepatch: '$MOD_LIVEPATCH': unpatching complete start_test "multiple livepatches" -load_lp $MOD_LIVEPATCH +load_lp $MOD_LIVEPATCH1 grep 'live patched' /proc/cmdline > /dev/kmsg grep 'live patched' /proc/meminfo > /dev/kmsg @@ -69,26 +71,26 @@ unload_lp $MOD_REPLACE grep 'live patched' /proc/cmdline > /dev/kmsg grep 'live patched' /proc/meminfo > /dev/kmsg -disable_lp $MOD_LIVEPATCH -unload_lp $MOD_LIVEPATCH +disable_lp $MOD_LIVEPATCH1 +unload_lp $MOD_LIVEPATCH1 grep 'live patched' /proc/cmdline > /dev/kmsg grep 'live patched' /proc/meminfo > /dev/kmsg -check_result "% insmod test_modules/$MOD_LIVEPATCH.ko -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -livepatch: '$MOD_LIVEPATCH': patching complete -$MOD_LIVEPATCH: this has been live patched +check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko +livepatch: enabling patch '$MOD_LIVEPATCH1' +livepatch: '$MOD_LIVEPATCH1': initializing patching transition +livepatch: '$MOD_LIVEPATCH1': starting patching transition +livepatch: '$MOD_LIVEPATCH1': completing patching transition +livepatch: '$MOD_LIVEPATCH1': patching complete +$MOD_LIVEPATCH1: this has been live patched % insmod test_modules/$MOD_REPLACE.ko replace=0 livepatch: enabling patch '$MOD_REPLACE' livepatch: '$MOD_REPLACE': initializing patching transition livepatch: '$MOD_REPLACE': starting patching transition livepatch: '$MOD_REPLACE': completing patching transition livepatch: '$MOD_REPLACE': patching complete -$MOD_LIVEPATCH: this has been live patched +$MOD_LIVEPATCH1: this has been live patched $MOD_REPLACE: this has been live patched % echo 0 > /sys/kernel/livepatch/$MOD_REPLACE/enabled livepatch: '$MOD_REPLACE': initializing unpatching transition @@ -96,35 +98,57 @@ livepatch: '$MOD_REPLACE': starting unpatching transition livepatch: '$MOD_REPLACE': completing unpatching transition livepatch: '$MOD_REPLACE': unpatching complete % rmmod $MOD_REPLACE -$MOD_LIVEPATCH: this has been live patched -% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled -livepatch: '$MOD_LIVEPATCH': initializing unpatching transition -livepatch: '$MOD_LIVEPATCH': starting unpatching transition -livepatch: '$MOD_LIVEPATCH': completing unpatching transition -livepatch: '$MOD_LIVEPATCH': unpatching complete -% rmmod $MOD_LIVEPATCH" +$MOD_LIVEPATCH1: this has been live patched +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH1/enabled +livepatch: '$MOD_LIVEPATCH1': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH1': starting unpatching transition +livepatch: '$MOD_LIVEPATCH1': completing unpatching transition +livepatch: '$MOD_LIVEPATCH1': unpatching complete +% rmmod $MOD_LIVEPATCH1" # - load a livepatch that modifies the output from /proc/cmdline and # verify correct behavior -# - load an atomic replace livepatch and verify that only the second is active -# - remove the first livepatch and verify that the atomic replace livepatch -# is still active +# - load two additional livepatches and check the number of livepatch modules +# applied +# - load an atomic replace livepatch and check that the other three modules were +# disabled +# - remove all livepatches besides the atomic replace one and verify that the +# atomic replace livepatch is still active # - remove the atomic replace livepatch and verify that none are active start_test "atomic replace livepatch" -load_lp $MOD_LIVEPATCH +load_lp $MOD_LIVEPATCH1 grep 'live patched' /proc/cmdline > /dev/kmsg grep 'live patched' /proc/meminfo > /dev/kmsg +for mod in $MOD_LIVEPATCH2 $MOD_LIVEPATCH3; do + load_lp "$mod" +done + +mods=(/sys/kernel/livepatch/*) +nmods=${#mods[@]} +if [ "$nmods" -ne 3 ]; then + die "Expecting three modules listed, found $nmods" +fi + load_lp $MOD_REPLACE replace=1 grep 'live patched' /proc/cmdline > /dev/kmsg grep 'live patched' /proc/meminfo > /dev/kmsg -unload_lp $MOD_LIVEPATCH +mods=(/sys/kernel/livepatch/*) +nmods=${#mods[@]} +if [ "$nmods" -ne 1 ]; then + die "Expecting only one moduled listed, found $nmods" +fi + +# These modules were disabled by the atomic replace +for mod in $MOD_LIVEPATCH3 $MOD_LIVEPATCH2 $MOD_LIVEPATCH1; do + unload_lp "$mod" +done grep 'live patched' /proc/cmdline > /dev/kmsg grep 'live patched' /proc/meminfo > /dev/kmsg @@ -135,13 +159,27 @@ unload_lp $MOD_REPLACE grep 'live patched' /proc/cmdline > /dev/kmsg grep 'live patched' /proc/meminfo > /dev/kmsg -check_result "% insmod test_modules/$MOD_LIVEPATCH.ko -livepatch: enabling patch '$MOD_LIVEPATCH' -livepatch: '$MOD_LIVEPATCH': initializing patching transition -livepatch: '$MOD_LIVEPATCH': starting patching transition -livepatch: '$MOD_LIVEPATCH': completing patching transition -livepatch: '$MOD_LIVEPATCH': patching complete -$MOD_LIVEPATCH: this has been live patched +check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko +livepatch: enabling patch '$MOD_LIVEPATCH1' +livepatch: '$MOD_LIVEPATCH1': initializing patching transition +livepatch: '$MOD_LIVEPATCH1': starting patching transition +livepatch: '$MOD_LIVEPATCH1': completing patching transition +livepatch: '$MOD_LIVEPATCH1': patching complete +$MOD_LIVEPATCH1: this has been live patched +% insmod test_modules/$MOD_LIVEPATCH2.ko +livepatch: enabling patch '$MOD_LIVEPATCH2' +livepatch: '$MOD_LIVEPATCH2': initializing patching transition +livepatch: '$MOD_LIVEPATCH2': starting patching transition +livepatch: '$MOD_LIVEPATCH2': completing patching transition +livepatch: '$MOD_LIVEPATCH2': patching complete +% insmod test_modules/$MOD_LIVEPATCH3.ko +livepatch: enabling patch '$MOD_LIVEPATCH3' +livepatch: '$MOD_LIVEPATCH3': initializing patching transition +$MOD_LIVEPATCH3: pre_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH3': starting patching transition +livepatch: '$MOD_LIVEPATCH3': completing patching transition +$MOD_LIVEPATCH3: post_patch_callback: vmlinux +livepatch: '$MOD_LIVEPATCH3': patching complete % insmod test_modules/$MOD_REPLACE.ko replace=1 livepatch: enabling patch '$MOD_REPLACE' livepatch: '$MOD_REPLACE': initializing patching transition @@ -149,7 +187,9 @@ livepatch: '$MOD_REPLACE': starting patching transition livepatch: '$MOD_REPLACE': completing patching transition livepatch: '$MOD_REPLACE': patching complete $MOD_REPLACE: this has been live patched -% rmmod $MOD_LIVEPATCH +% rmmod $MOD_LIVEPATCH3 +% rmmod $MOD_LIVEPATCH2 +% rmmod $MOD_LIVEPATCH1 $MOD_REPLACE: this has been live patched % echo 0 > /sys/kernel/livepatch/$MOD_REPLACE/enabled livepatch: '$MOD_REPLACE': initializing unpatching transition From 722c9389c7fa91d1b6c665252f655b352b3a32b8 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Fri, 14 Jun 2024 23:19:55 +0800 Subject: [PATCH 041/152] irqchip/riscv-intc: Remove asmlinkage The two functions riscv_intc_aia_irq() and riscv_intc_irq() are only called by C functions. Signed-off-by: Jisheng Zhang Signed-off-by: Thomas Gleixner Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20240614151955.1949-1-jszhang@kernel.org --- drivers/irqchip/irq-riscv-intc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-riscv-intc.c b/drivers/irqchip/irq-riscv-intc.c index 9e71c4428814..983538a89e65 100644 --- a/drivers/irqchip/irq-riscv-intc.c +++ b/drivers/irqchip/irq-riscv-intc.c @@ -26,7 +26,7 @@ static unsigned int riscv_intc_nr_irqs __ro_after_init = BITS_PER_LONG; static unsigned int riscv_intc_custom_base __ro_after_init = BITS_PER_LONG; static unsigned int riscv_intc_custom_nr_irqs __ro_after_init; -static asmlinkage void riscv_intc_irq(struct pt_regs *regs) +static void riscv_intc_irq(struct pt_regs *regs) { unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG; @@ -34,7 +34,7 @@ static asmlinkage void riscv_intc_irq(struct pt_regs *regs) pr_warn_ratelimited("Failed to handle interrupt (cause: %ld)\n", cause); } -static asmlinkage void riscv_intc_aia_irq(struct pt_regs *regs) +static void riscv_intc_aia_irq(struct pt_regs *regs) { unsigned long topi; From 12c94f694e53b1bf105c56af4b800a32f1b0b10a Mon Sep 17 00:00:00 2001 From: Anna-Maria Behnsen Date: Fri, 14 Jun 2024 12:23:41 +0200 Subject: [PATCH 042/152] irqchip/imx-mu-msi: Fix codingstyle in imx_mu_msi_domains_init() Fixes the coding style of irq_domain_create_linear() call within imx_mu_msi_domains_init() for better code readability. Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614102403.13610-3-shivamurthy.shastri@linutronix.de --- drivers/irqchip/irq-imx-mu-msi.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/irq-imx-mu-msi.c b/drivers/irqchip/irq-imx-mu-msi.c index 90d41c1407ac..1dceda044db9 100644 --- a/drivers/irqchip/irq-imx-mu-msi.c +++ b/drivers/irqchip/irq-imx-mu-msi.c @@ -222,10 +222,8 @@ static int imx_mu_msi_domains_init(struct imx_mu_msi *msi_data, struct device *d struct irq_domain *parent; /* Initialize MSI domain parent */ - parent = irq_domain_create_linear(fwnodes, - IMX_MU_CHANS, - &imx_mu_msi_domain_ops, - msi_data); + parent = irq_domain_create_linear(fwnodes, IMX_MU_CHANS, + &imx_mu_msi_domain_ops, msi_data); if (!parent) { dev_err(dev, "failed to create IRQ domain\n"); return -ENOMEM; From 6dca724d61a1d10f772dcd06948c30ceca027069 Mon Sep 17 00:00:00 2001 From: Anna-Maria Behnsen Date: Fri, 14 Jun 2024 12:23:40 +0200 Subject: [PATCH 043/152] irqdomain: Fix formatting irq_find_matching_fwspec() kerneldoc comment Modify the comment formatting in irq_find_matching_fwspec function to enhance code readability and maintain consistency. Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614102403.13610-2-shivamurthy.shastri@linutronix.de --- kernel/irq/irqdomain.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index aadc8891cc16..8475b83c5519 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -438,7 +438,8 @@ struct irq_domain *irq_find_matching_fwspec(struct irq_fwspec *fwspec, struct fwnode_handle *fwnode = fwspec->fwnode; int rc; - /* We might want to match the legacy controller last since + /* + * We might want to match the legacy controller last since * it might potentially be set to match all interrupts in * the absence of a device node. This isn't a problem so far * yet though... From 492eee82574b163fbb3f099c74ce3b4322d0af28 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Thu, 6 Jun 2024 20:48:13 +0100 Subject: [PATCH 044/152] irqchip/renesas-rzg2l: Reorder function calls in rzg2l_irqc_irq_disable() The order of function calls in the disable operation should be the reverse of that in the enable operation. Thus, reorder the function calls to first disable the parent IRQ chip before disabling the TINT IRQ. Reported-by: Geert Uytterhoeven Signed-off-by: Lad Prabhakar Signed-off-by: Thomas Gleixner Tested-by: Claudiu Beznea # on RZ/G3S Link: https://lore.kernel.org/r/20240606194813.676823-1-prabhakar.mahadev-lad.rj@bp.renesas.com --- drivers/irqchip/irq-renesas-rzg2l.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-renesas-rzg2l.c b/drivers/irqchip/irq-renesas-rzg2l.c index 861a0e5a3e97..693ff285ca2c 100644 --- a/drivers/irqchip/irq-renesas-rzg2l.c +++ b/drivers/irqchip/irq-renesas-rzg2l.c @@ -271,8 +271,8 @@ static void rzg2l_tint_irq_endisable(struct irq_data *d, bool enable) static void rzg2l_irqc_irq_disable(struct irq_data *d) { - rzg2l_tint_irq_endisable(d, false); irq_chip_disable_parent(d); + rzg2l_tint_irq_endisable(d, false); } static void rzg2l_irqc_irq_enable(struct irq_data *d) From b1c387506d2738b655f0806d3ee3e6fc94ecb910 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sat, 8 Jun 2024 09:14:37 -0700 Subject: [PATCH 045/152] irqchip: Add missing MODULE_DESCRIPTION() macros On x86, make allmodconfig && make W=1 C=1 reports: WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/irqchip/irq-ts4800.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/irqchip/irq-meson-gpio.o Add the missing invocation of the MODULE_DESCRIPTION() macro to all files which have a MODULE_LICENSE(). This includes a 3rd file, irq-mvebu-pic.c, which did not produce a warning with the x86 allmodconfig, but which may cause this warning with other kernel configurations. Signed-off-by: Jeff Johnson Signed-off-by: Thomas Gleixner Reviewed-by: Andrew Lunn Link: https://lore.kernel.org/r/20240608-md-drivers-irqchip-v1-1-dd02c3229277@quicinc.com --- drivers/irqchip/irq-meson-gpio.c | 1 + drivers/irqchip/irq-mvebu-pic.c | 1 + drivers/irqchip/irq-ts4800.c | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/irqchip/irq-meson-gpio.c b/drivers/irqchip/irq-meson-gpio.c index 9a1791908598..27e30ce41db3 100644 --- a/drivers/irqchip/irq-meson-gpio.c +++ b/drivers/irqchip/irq-meson-gpio.c @@ -608,5 +608,6 @@ IRQCHIP_MATCH("amlogic,meson-gpio-intc", meson_gpio_irq_of_init) IRQCHIP_PLATFORM_DRIVER_END(meson_gpio_intc) MODULE_AUTHOR("Jerome Brunet "); +MODULE_DESCRIPTION("Meson GPIO Interrupt Multiplexer driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:meson-gpio-intc"); diff --git a/drivers/irqchip/irq-mvebu-pic.c b/drivers/irqchip/irq-mvebu-pic.c index d17d9c0e2880..08b0cc862adf 100644 --- a/drivers/irqchip/irq-mvebu-pic.c +++ b/drivers/irqchip/irq-mvebu-pic.c @@ -193,6 +193,7 @@ module_platform_driver(mvebu_pic_driver); MODULE_AUTHOR("Yehuda Yitschak "); MODULE_AUTHOR("Thomas Petazzoni "); +MODULE_DESCRIPTION("Marvell Armada 7K/8K PIC driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:mvebu_pic"); diff --git a/drivers/irqchip/irq-ts4800.c b/drivers/irqchip/irq-ts4800.c index 57f610dab6b8..b5dddb3c1568 100644 --- a/drivers/irqchip/irq-ts4800.c +++ b/drivers/irqchip/irq-ts4800.c @@ -163,5 +163,6 @@ static struct platform_driver ts4800_ic_driver = { module_platform_driver(ts4800_ic_driver); MODULE_AUTHOR("Damien Riegel "); +MODULE_DESCRIPTION("Multiplexed-IRQs driver for TS-4800's FPGA"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:ts4800_irqc"); From 50c43447f71c6d0eb9e320c4cc69822d396e52bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Beh=C3=BAn?= Date: Thu, 20 Jun 2024 11:52:33 +0200 Subject: [PATCH 046/152] irqchip/armada-370-xp: Use atomic_io_modify() instead of another spinlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the dedicated atomic_io_modify() instead of a open coded spin_lock() + readl() + writel() + spin_unlock() sequence. This allows to drop the irq_controller_lock spinlock from the driver. Signed-off-by: Marek Behún Signed-off-by: Thomas Gleixner Reviewed-by: Andrew Lunn --- drivers/irqchip/irq-armada-370-xp.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/irqchip/irq-armada-370-xp.c b/drivers/irqchip/irq-armada-370-xp.c index 4b021a67bdfe..676df716a3aa 100644 --- a/drivers/irqchip/irq-armada-370-xp.c +++ b/drivers/irqchip/irq-armada-370-xp.c @@ -461,24 +461,18 @@ static __init void armada_xp_ipi_init(struct device_node *node) set_smp_ipi_range(base_ipi, IPI_DOORBELL_END); } -static DEFINE_RAW_SPINLOCK(irq_controller_lock); - static int armada_xp_set_affinity(struct irq_data *d, const struct cpumask *mask_val, bool force) { irq_hw_number_t hwirq = irqd_to_hwirq(d); - unsigned long reg, mask; int cpu; /* Select a single core from the affinity mask which is online */ cpu = cpumask_any_and(mask_val, cpu_online_mask); - mask = 1UL << cpu_logical_map(cpu); - raw_spin_lock(&irq_controller_lock); - reg = readl(main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq)); - reg = (reg & (~ARMADA_370_XP_INT_SOURCE_CPU_MASK)) | mask; - writel(reg, main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq)); - raw_spin_unlock(&irq_controller_lock); + atomic_io_modify(main_int_base + ARMADA_370_XP_INT_SOURCE_CTL(hwirq), + ARMADA_370_XP_INT_SOURCE_CPU_MASK, + BIT(cpu_logical_map(cpu))); irq_data_update_effective_affinity(d, cpumask_of(cpu)); From 3cef738208e5c3cb7084e208caf9bbf684f24feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Fri, 21 Jun 2024 11:38:28 +0200 Subject: [PATCH 047/152] irqchip/armada-370-xp: Do not allow mapping IRQ 0 and 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IRQs 0 (IPI) and 1 (MSI) are handled internally by this driver, generic_handle_domain_irq() is never called for these IRQs. Disallow mapping these IRQs. [ Marek: changed commit message ] Signed-off-by: Pali Rohár Signed-off-by: Marek Behún Signed-off-by: Thomas Gleixner Reviewed-by: Andrew Lunn --- drivers/irqchip/irq-armada-370-xp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/irqchip/irq-armada-370-xp.c b/drivers/irqchip/irq-armada-370-xp.c index 676df716a3aa..526077df1c84 100644 --- a/drivers/irqchip/irq-armada-370-xp.c +++ b/drivers/irqchip/irq-armada-370-xp.c @@ -560,6 +560,10 @@ static struct irq_chip armada_370_xp_irq_chip = { static int armada_370_xp_mpic_irq_map(struct irq_domain *h, unsigned int virq, irq_hw_number_t hw) { + /* IRQs 0 and 1 cannot be mapped, they are handled internally */ + if (hw <= 1) + return -EINVAL; + armada_370_xp_irq_mask(irq_get_irq_data(virq)); if (!is_percpu_irq(hw)) writel(hw, per_cpu_int_base + From de796fc184179de86fb87f94178459b5b20b1b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Fri, 21 Jun 2024 11:38:29 +0200 Subject: [PATCH 048/152] irqchip/armada-370-xp: Only call ipi_resume() if IPI is available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IPI is available only on systems where the mpic controller does not have a parent interrupt defined (e.g. on Armada XP). If a parent interrupt is defined, inter-processor interrupts are handled by an interrupt controller higher in the hierarchy (most probably a parent GIC). Only call ipi_resume() on systems where IPI is available in the mpic controller. Signed-off-by: Pali Rohár Signed-off-by: Marek Behún Signed-off-by: Thomas Gleixner Reviewed-by: Andrew Lunn [ refactored a little and changed commit message ] --- drivers/irqchip/irq-armada-370-xp.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-armada-370-xp.c b/drivers/irqchip/irq-armada-370-xp.c index 526077df1c84..deb4c9b0080c 100644 --- a/drivers/irqchip/irq-armada-370-xp.c +++ b/drivers/irqchip/irq-armada-370-xp.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -156,6 +157,17 @@ static DEFINE_MUTEX(msi_used_lock); static phys_addr_t msi_doorbell_addr; #endif +static inline bool is_ipi_available(void) +{ + /* + * We distinguish IPI availability in the IC by the IC not having a + * parent irq defined. If a parent irq is defined, there is a parent + * interrupt controller (e.g. GIC) that takes care of inter-processor + * interrupts. + */ + return parent_irq <= 0; +} + static inline bool is_percpu_irq(irq_hw_number_t irq) { if (irq <= ARMADA_370_XP_MAX_PER_CPU_IRQS) @@ -521,7 +533,8 @@ static void armada_xp_mpic_reenable_percpu(void) armada_370_xp_irq_unmask(data); } - ipi_resume(); + if (is_ipi_available()) + ipi_resume(); armada_370_xp_msi_reenable_percpu(); } @@ -744,7 +757,8 @@ static void armada_370_xp_mpic_resume(void) if (doorbell_mask_reg & PCI_MSI_DOORBELL_MASK) writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS); - ipi_resume(); + if (is_ipi_available()) + ipi_resume(); } static struct syscore_ops armada_370_xp_mpic_syscore_ops = { From 9d80f6bd3ad8f2b49651d4685bb391399ecf80a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Fri, 21 Jun 2024 11:38:30 +0200 Subject: [PATCH 049/152] irqchip/armada-370-xp: Do not touch IPI registers on platforms without IPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On platforms where IPI is not available in the MPIC, the IPI registers instead represent an additional set of MSI interrupt registers (currently unused by the driver). Do not touch these registers on platforms where IPI is not available in the MPIC. [ Marek: refactored, changed commit message ] Signed-off-by: Pali Rohár Signed-off-by: Marek Behún Signed-off-by: Thomas Gleixner Reviewed-by: Andrew Lunn --- drivers/irqchip/irq-armada-370-xp.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-armada-370-xp.c b/drivers/irqchip/irq-armada-370-xp.c index deb4c9b0080c..94a81c551c0c 100644 --- a/drivers/irqchip/irq-armada-370-xp.c +++ b/drivers/irqchip/irq-armada-370-xp.c @@ -502,6 +502,9 @@ static void armada_xp_mpic_smp_cpu_init(void) for (i = 0; i < nr_irqs; i++) writel(i, per_cpu_int_base + ARMADA_370_XP_INT_SET_MASK_OFFS); + if (!is_ipi_available()) + return; + /* Disable all IPIs */ writel(0, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS); @@ -752,7 +755,7 @@ static void armada_370_xp_mpic_resume(void) /* Reconfigure doorbells for IPIs and MSIs */ writel(doorbell_mask_reg, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS); - if (doorbell_mask_reg & IPI_DOORBELL_MASK) + if (is_ipi_available() && (doorbell_mask_reg & IPI_DOORBELL_MASK)) writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS); if (doorbell_mask_reg & PCI_MSI_DOORBELL_MASK) writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS); @@ -803,13 +806,18 @@ static int __init armada_370_xp_mpic_of_init(struct device_node *node, BUG_ON(!armada_370_xp_mpic_domain); irq_domain_update_bus_token(armada_370_xp_mpic_domain, DOMAIN_BUS_WIRED); + /* + * Initialize parent_irq before calling any other functions, since it is + * used to distinguish between IPI and non-IPI platforms. + */ + parent_irq = irq_of_parse_and_map(node, 0); + /* Setup for the boot CPU */ armada_xp_mpic_perf_init(); armada_xp_mpic_smp_cpu_init(); armada_370_xp_msi_init(node, main_int_res.start); - parent_irq = irq_of_parse_and_map(node, 0); if (parent_irq <= 0) { irq_set_default_host(armada_370_xp_mpic_domain); set_handle_irq(armada_370_xp_handle_irq); From 986b6ad0c4c653940fab7e5decf0d847670bf407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pali=20Roh=C3=A1r?= Date: Fri, 21 Jun 2024 11:38:31 +0200 Subject: [PATCH 050/152] irqchip/armada-370-xp: Add support for 32 MSI interrupts on non-IPI platforms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The doorbell interrupts have the following layout on IPI vs no-IPI platforms: | 0...7 | 8...15 | 16...31 | ------------------+---------+----------+---------------------+ IPI platform | IPI | n/a | MSI | ------------------+---------+----------+---------------------+ non-IPI platform | MSI | ------------------+------------------------------------------+ Currently the driver only allows for the upper 16...31 interrupts for MSI domain (i.e. the MSI domain has only 16 interrupts). On platforms where IPI is not available, we can use whole 32 MSI interrupts. Implement support also for the lower 16 MSI interrupts on non-IPI platforms. [ Marek: refactored, changed commit message ] Signed-off-by: Pali Rohár Signed-off-by: Marek Behún Signed-off-by: Thomas Gleixner Reviewed-by: Andrew Lunn --- drivers/irqchip/irq-armada-370-xp.c | 77 +++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 14 deletions(-) diff --git a/drivers/irqchip/irq-armada-370-xp.c b/drivers/irqchip/irq-armada-370-xp.c index 94a81c551c0c..dce2b80bf439 100644 --- a/drivers/irqchip/irq-armada-370-xp.c +++ b/drivers/irqchip/irq-armada-370-xp.c @@ -13,6 +13,7 @@ * warranty of any kind, whether express or implied. */ +#include #include #include #include @@ -136,6 +137,7 @@ #define ARMADA_370_XP_MAX_PER_CPU_IRQS (28) +/* IPI and MSI interrupt definitions for IPI platforms */ #define IPI_DOORBELL_START (0) #define IPI_DOORBELL_END (8) #define IPI_DOORBELL_MASK 0xFF @@ -144,6 +146,14 @@ #define PCI_MSI_DOORBELL_END (32) #define PCI_MSI_DOORBELL_MASK 0xFFFF0000 +/* MSI interrupt definitions for non-IPI platforms */ +#define PCI_MSI_FULL_DOORBELL_START 0 +#define PCI_MSI_FULL_DOORBELL_NR 32 +#define PCI_MSI_FULL_DOORBELL_END 32 +#define PCI_MSI_FULL_DOORBELL_MASK GENMASK(31, 0) +#define PCI_MSI_FULL_DOORBELL_SRC0_MASK GENMASK(15, 0) +#define PCI_MSI_FULL_DOORBELL_SRC1_MASK GENMASK(31, 16) + static void __iomem *per_cpu_int_base; static void __iomem *main_int_base; static struct irq_domain *armada_370_xp_mpic_domain; @@ -152,7 +162,7 @@ static int parent_irq; #ifdef CONFIG_PCI_MSI static struct irq_domain *armada_370_xp_msi_domain; static struct irq_domain *armada_370_xp_msi_inner_domain; -static DECLARE_BITMAP(msi_used, PCI_MSI_DOORBELL_NR); +static DECLARE_BITMAP(msi_used, PCI_MSI_FULL_DOORBELL_NR); static DEFINE_MUTEX(msi_used_lock); static phys_addr_t msi_doorbell_addr; #endif @@ -168,6 +178,30 @@ static inline bool is_ipi_available(void) return parent_irq <= 0; } +static inline u32 msi_doorbell_mask(void) +{ + return is_ipi_available() ? PCI_MSI_DOORBELL_MASK : + PCI_MSI_FULL_DOORBELL_MASK; +} + +static inline unsigned int msi_doorbell_start(void) +{ + return is_ipi_available() ? PCI_MSI_DOORBELL_START : + PCI_MSI_FULL_DOORBELL_START; +} + +static inline unsigned int msi_doorbell_size(void) +{ + return is_ipi_available() ? PCI_MSI_DOORBELL_NR : + PCI_MSI_FULL_DOORBELL_NR; +} + +static inline unsigned int msi_doorbell_end(void) +{ + return is_ipi_available() ? PCI_MSI_DOORBELL_END : + PCI_MSI_FULL_DOORBELL_END; +} + static inline bool is_percpu_irq(irq_hw_number_t irq) { if (irq <= ARMADA_370_XP_MAX_PER_CPU_IRQS) @@ -225,7 +259,7 @@ static void armada_370_xp_compose_msi_msg(struct irq_data *data, struct msi_msg msg->address_lo = lower_32_bits(msi_doorbell_addr); msg->address_hi = upper_32_bits(msi_doorbell_addr); - msg->data = BIT(cpu + 8) | (data->hwirq + PCI_MSI_DOORBELL_START); + msg->data = BIT(cpu + 8) | (data->hwirq + msi_doorbell_start()); } static int armada_370_xp_msi_set_affinity(struct irq_data *irq_data, @@ -258,7 +292,7 @@ static int armada_370_xp_msi_alloc(struct irq_domain *domain, unsigned int virq, int hwirq, i; mutex_lock(&msi_used_lock); - hwirq = bitmap_find_free_region(msi_used, PCI_MSI_DOORBELL_NR, + hwirq = bitmap_find_free_region(msi_used, msi_doorbell_size(), order_base_2(nr_irqs)); mutex_unlock(&msi_used_lock); @@ -295,9 +329,10 @@ static void armada_370_xp_msi_reenable_percpu(void) u32 reg; /* Enable MSI doorbell mask and combined cpu local interrupt */ - reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS) - | PCI_MSI_DOORBELL_MASK; + reg = readl(per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS); + reg |= msi_doorbell_mask(); writel(reg, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS); + /* Unmask local doorbell interrupt */ writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS); } @@ -309,7 +344,7 @@ static int armada_370_xp_msi_init(struct device_node *node, ARMADA_370_XP_SW_TRIG_INT_OFFS; armada_370_xp_msi_inner_domain = - irq_domain_add_linear(NULL, PCI_MSI_DOORBELL_NR, + irq_domain_add_linear(NULL, msi_doorbell_size(), &armada_370_xp_msi_domain_ops, NULL); if (!armada_370_xp_msi_inner_domain) return -ENOMEM; @@ -325,6 +360,10 @@ static int armada_370_xp_msi_init(struct device_node *node, armada_370_xp_msi_reenable_percpu(); + /* Unmask low 16 MSI irqs on non-IPI platforms */ + if (!is_ipi_available()) + writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS); + return 0; } #else @@ -613,20 +652,20 @@ static void armada_370_xp_handle_msi_irq(struct pt_regs *regs, bool is_chained) u32 msimask, msinr; msimask = readl_relaxed(per_cpu_int_base + - ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS) - & PCI_MSI_DOORBELL_MASK; + ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS); + msimask &= msi_doorbell_mask(); writel(~msimask, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_CAUSE_OFFS); - for (msinr = PCI_MSI_DOORBELL_START; - msinr < PCI_MSI_DOORBELL_END; msinr++) { + for (msinr = msi_doorbell_start(); + msinr < msi_doorbell_end(); msinr++) { unsigned int irq; if (!(msimask & BIT(msinr))) continue; - irq = msinr - PCI_MSI_DOORBELL_START; + irq = msinr - msi_doorbell_start(); generic_handle_domain_irq(armada_370_xp_msi_inner_domain, irq); } @@ -655,7 +694,7 @@ static void armada_370_xp_mpic_handle_cascade_irq(struct irq_desc *desc) if (!(irqsrc & ARMADA_370_XP_INT_IRQ_FIQ_MASK(cpuid))) continue; - if (irqn == 1) { + if (irqn == 0 || irqn == 1) { armada_370_xp_handle_msi_irq(NULL, true); continue; } @@ -716,6 +755,7 @@ static int armada_370_xp_mpic_suspend(void) static void armada_370_xp_mpic_resume(void) { + bool src0, src1; int nirqs; irq_hw_number_t irq; @@ -755,9 +795,18 @@ static void armada_370_xp_mpic_resume(void) /* Reconfigure doorbells for IPIs and MSIs */ writel(doorbell_mask_reg, per_cpu_int_base + ARMADA_370_XP_IN_DRBEL_MSK_OFFS); - if (is_ipi_available() && (doorbell_mask_reg & IPI_DOORBELL_MASK)) + + if (is_ipi_available()) { + src0 = doorbell_mask_reg & IPI_DOORBELL_MASK; + src1 = doorbell_mask_reg & PCI_MSI_DOORBELL_MASK; + } else { + src0 = doorbell_mask_reg & PCI_MSI_FULL_DOORBELL_SRC0_MASK; + src1 = doorbell_mask_reg & PCI_MSI_FULL_DOORBELL_SRC1_MASK; + } + + if (src0) writel(0, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS); - if (doorbell_mask_reg & PCI_MSI_DOORBELL_MASK) + if (src1) writel(1, per_cpu_int_base + ARMADA_370_XP_INT_CLEAR_MASK_OFFS); if (is_ipi_available()) From 760d7e719499d64beea62bfcf53938fb233bb6e7 Mon Sep 17 00:00:00 2001 From: Tianyang Zhang Date: Tue, 4 Jun 2024 20:50:26 +0800 Subject: [PATCH 051/152] Loongarch: Support loongarch avec Introduce the advanced extended interrupt controllers. This feature will allow each core to have 256 independent interrupt vectors and MSI interrupts can be independently routed to any vector on any CPU. [ tglx: Fixed up coding style. Made on/offline functions void ] Co-developed-by: Jianmin Lv Signed-off-by: Jianmin Lv Co-developed-by: Liupu Wang Signed-off-by: Liupu Wang Signed-off-by: Tianyang Zhang Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240604125026.18745-1-zhangtianyang@loongson.cn --- arch/loongarch/Kconfig | 1 + arch/loongarch/include/asm/cpu-features.h | 1 + arch/loongarch/include/asm/cpu.h | 2 + arch/loongarch/include/asm/hw_irq.h | 10 + arch/loongarch/include/asm/irq.h | 12 +- arch/loongarch/include/asm/loongarch.h | 20 +- arch/loongarch/include/asm/smp.h | 2 + arch/loongarch/kernel/cpu-probe.c | 3 +- arch/loongarch/kernel/smp.c | 5 + drivers/irqchip/Makefile | 2 +- drivers/irqchip/irq-loongarch-avec.c | 419 ++++++++++++++++++++++ drivers/irqchip/irq-loongarch-cpu.c | 4 +- drivers/irqchip/irq-loongson-eiointc.c | 3 + drivers/irqchip/irq-loongson-pch-msi.c | 43 ++- 14 files changed, 517 insertions(+), 10 deletions(-) create mode 100644 drivers/irqchip/irq-loongarch-avec.c diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index e38139c576ee..a66e49b5a68c 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -83,6 +83,7 @@ config LOONGARCH select GENERIC_ENTRY select GENERIC_GETTIMEOFDAY select GENERIC_IOREMAP if !ARCH_IOREMAP + select GENERIC_IRQ_MATRIX_ALLOCATOR select GENERIC_IRQ_MULTI_HANDLER select GENERIC_IRQ_PROBE select GENERIC_IRQ_SHOW diff --git a/arch/loongarch/include/asm/cpu-features.h b/arch/loongarch/include/asm/cpu-features.h index 2eafe6a6aca8..16a716f88a5c 100644 --- a/arch/loongarch/include/asm/cpu-features.h +++ b/arch/loongarch/include/asm/cpu-features.h @@ -65,5 +65,6 @@ #define cpu_has_guestid cpu_opt(LOONGARCH_CPU_GUESTID) #define cpu_has_hypervisor cpu_opt(LOONGARCH_CPU_HYPERVISOR) #define cpu_has_ptw cpu_opt(LOONGARCH_CPU_PTW) +#define cpu_has_avecint cpu_opt(LOONGARCH_CPU_AVECINT) #endif /* __ASM_CPU_FEATURES_H */ diff --git a/arch/loongarch/include/asm/cpu.h b/arch/loongarch/include/asm/cpu.h index 48b9f7168bcc..843f9c4ec980 100644 --- a/arch/loongarch/include/asm/cpu.h +++ b/arch/loongarch/include/asm/cpu.h @@ -99,6 +99,7 @@ enum cpu_type_enum { #define CPU_FEATURE_GUESTID 24 /* CPU has GuestID feature */ #define CPU_FEATURE_HYPERVISOR 25 /* CPU has hypervisor (running in VM) */ #define CPU_FEATURE_PTW 26 /* CPU has hardware page table walker */ +#define CPU_FEATURE_AVECINT 27 /* CPU has avec interrupt */ #define LOONGARCH_CPU_CPUCFG BIT_ULL(CPU_FEATURE_CPUCFG) #define LOONGARCH_CPU_LAM BIT_ULL(CPU_FEATURE_LAM) @@ -127,5 +128,6 @@ enum cpu_type_enum { #define LOONGARCH_CPU_GUESTID BIT_ULL(CPU_FEATURE_GUESTID) #define LOONGARCH_CPU_HYPERVISOR BIT_ULL(CPU_FEATURE_HYPERVISOR) #define LOONGARCH_CPU_PTW BIT_ULL(CPU_FEATURE_PTW) +#define LOONGARCH_CPU_AVECINT BIT_ULL(CPU_FEATURE_AVECINT) #endif /* _ASM_CPU_H */ diff --git a/arch/loongarch/include/asm/hw_irq.h b/arch/loongarch/include/asm/hw_irq.h index af4f4e8fbd85..772692e765c0 100644 --- a/arch/loongarch/include/asm/hw_irq.h +++ b/arch/loongarch/include/asm/hw_irq.h @@ -9,6 +9,16 @@ extern atomic_t irq_err_count; +/* + * 256 vectors Map: + * + * 0 - 15: mapping legacy IPs, e.g. IP0-12. + * 16 - 255: mapping a vector for external IRQ. + * + */ +#define NR_VECTORS 256 +#define IRQ_MATRIX_BITS NR_VECTORS +#define NR_LEGACY_VECTORS 16 /* * interrupt-retrigger: NOP for now. This may not be appropriate for all * machines, we'll see ... diff --git a/arch/loongarch/include/asm/irq.h b/arch/loongarch/include/asm/irq.h index 480418bc5071..cf3b635a9b86 100644 --- a/arch/loongarch/include/asm/irq.h +++ b/arch/loongarch/include/asm/irq.h @@ -65,7 +65,7 @@ extern struct acpi_vector_group msi_group[MAX_IO_PICS]; #define LOONGSON_LPC_LAST_IRQ (LOONGSON_LPC_IRQ_BASE + 15) #define LOONGSON_CPU_IRQ_BASE 16 -#define LOONGSON_CPU_LAST_IRQ (LOONGSON_CPU_IRQ_BASE + 14) +#define LOONGSON_CPU_LAST_IRQ (LOONGSON_CPU_IRQ_BASE + 15) #define LOONGSON_PCH_IRQ_BASE 64 #define LOONGSON_PCH_ACPI_IRQ (LOONGSON_PCH_IRQ_BASE + 47) @@ -101,6 +101,16 @@ int pch_msi_acpi_init(struct irq_domain *parent, struct acpi_madt_msi_pic *acpi_pchmsi); int pch_pic_acpi_init(struct irq_domain *parent, struct acpi_madt_bio_pic *acpi_pchpic); + +#ifdef CONFIG_ACPI +int __init pch_msi_acpi_init_v2(struct irq_domain *parent, + struct acpi_madt_msi_pic *pch_msi_entry); +int __init loongarch_avec_acpi_init(struct irq_domain *parent); +void complete_irq_moving(void); +void loongarch_avec_offline_cpu(unsigned int cpu); +void loongarch_avec_online_cpu(unsigned int cpu); +#endif + int find_pch_pic(u32 gsi); struct fwnode_handle *get_pch_msi_handle(int pci_segment); diff --git a/arch/loongarch/include/asm/loongarch.h b/arch/loongarch/include/asm/loongarch.h index eb09adda54b7..16a910359977 100644 --- a/arch/loongarch/include/asm/loongarch.h +++ b/arch/loongarch/include/asm/loongarch.h @@ -72,7 +72,6 @@ #define CPUCFG1_RPLV BIT(23) #define CPUCFG1_HUGEPG BIT(24) #define CPUCFG1_CRC32 BIT(25) -#define CPUCFG1_MSGINT BIT(26) #define LOONGARCH_CPUCFG2 0x2 #define CPUCFG2_FP BIT(0) @@ -252,8 +251,8 @@ #define CSR_ESTAT_EXC_WIDTH 6 #define CSR_ESTAT_EXC (_ULCAST_(0x3f) << CSR_ESTAT_EXC_SHIFT) #define CSR_ESTAT_IS_SHIFT 0 -#define CSR_ESTAT_IS_WIDTH 14 -#define CSR_ESTAT_IS (_ULCAST_(0x3fff) << CSR_ESTAT_IS_SHIFT) +#define CSR_ESTAT_IS_WIDTH 15 +#define CSR_ESTAT_IS (_ULCAST_(0x7fff) << CSR_ESTAT_IS_SHIFT) #define LOONGARCH_CSR_ERA 0x6 /* ERA */ @@ -999,10 +998,18 @@ #define CSR_FWPC_SKIP_SHIFT 16 #define CSR_FWPC_SKIP (_ULCAST_(1) << CSR_FWPC_SKIP_SHIFT) +#define LOONGARCH_CSR_IRR0 0xa0 +#define LOONGARCH_CSR_IRR1 0xa1 +#define LOONGARCH_CSR_IRR2 0xa2 +#define LOONGARCH_CSR_IRR3 0xa3 +#define LOONGARCH_CSR_IRR_BASE LOONGARCH_CSR_IRR0 + +#define LOONGARCH_CSR_ILR 0xa4 + /* * CSR_ECFG IM */ -#define ECFG0_IM 0x00001fff +#define ECFG0_IM 0x00005fff #define ECFGB_SIP0 0 #define ECFGF_SIP0 (_ULCAST_(1) << ECFGB_SIP0) #define ECFGB_SIP1 1 @@ -1045,6 +1052,7 @@ #define IOCSRF_EIODECODE BIT_ULL(9) #define IOCSRF_FLATMODE BIT_ULL(10) #define IOCSRF_VM BIT_ULL(11) +#define IOCSRF_AVEC BIT_ULL(15) #define LOONGARCH_IOCSR_VENDOR 0x10 @@ -1055,6 +1063,7 @@ #define LOONGARCH_IOCSR_MISC_FUNC 0x420 #define IOCSR_MISC_FUNC_TIMER_RESET BIT_ULL(21) #define IOCSR_MISC_FUNC_EXT_IOI_EN BIT_ULL(48) +#define IOCSR_MISC_FUNC_AVEC_EN BIT_ULL(51) #define LOONGARCH_IOCSR_CPUTEMP 0x428 @@ -1375,9 +1384,10 @@ __BUILD_CSR_OP(tlbidx) #define INT_TI 11 /* Timer */ #define INT_IPI 12 #define INT_NMI 13 +#define INT_AVEC 14 /* ExcCodes corresponding to interrupts */ -#define EXCCODE_INT_NUM (INT_NMI + 1) +#define EXCCODE_INT_NUM (INT_AVEC + 1) #define EXCCODE_INT_START 64 #define EXCCODE_INT_END (EXCCODE_INT_START + EXCCODE_INT_NUM - 1) diff --git a/arch/loongarch/include/asm/smp.h b/arch/loongarch/include/asm/smp.h index 278700cfee88..2399004596a3 100644 --- a/arch/loongarch/include/asm/smp.h +++ b/arch/loongarch/include/asm/smp.h @@ -69,9 +69,11 @@ extern int __cpu_logical_map[NR_CPUS]; #define ACTION_BOOT_CPU 0 #define ACTION_RESCHEDULE 1 #define ACTION_CALL_FUNCTION 2 +#define ACTION_CLEAR_VECT 3 #define SMP_BOOT_CPU BIT(ACTION_BOOT_CPU) #define SMP_RESCHEDULE BIT(ACTION_RESCHEDULE) #define SMP_CALL_FUNCTION BIT(ACTION_CALL_FUNCTION) +#define SMP_CLEAR_VECT BIT(ACTION_CLEAR_VECT) struct secondary_data { unsigned long stack; diff --git a/arch/loongarch/kernel/cpu-probe.c b/arch/loongarch/kernel/cpu-probe.c index 55320813ee08..3b2e72e8f9bd 100644 --- a/arch/loongarch/kernel/cpu-probe.c +++ b/arch/loongarch/kernel/cpu-probe.c @@ -106,7 +106,6 @@ static void cpu_probe_common(struct cpuinfo_loongarch *c) elf_hwcap |= HWCAP_LOONGARCH_CRC32; } - config = read_cpucfg(LOONGARCH_CPUCFG2); if (config & CPUCFG2_LAM) { c->options |= LOONGARCH_CPU_LAM; @@ -176,6 +175,8 @@ static void cpu_probe_common(struct cpuinfo_loongarch *c) c->options |= LOONGARCH_CPU_EIODECODE; if (config & IOCSRF_VM) c->options |= LOONGARCH_CPU_HYPERVISOR; + if (config & IOCSRF_AVEC) + c->options |= LOONGARCH_CPU_AVECINT; config = csr_read32(LOONGARCH_CSR_ASID); config = (config & CSR_ASID_BIT) >> CSR_ASID_BIT_SHIFT; diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index 0dfe2388ef41..6dfedef306f3 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -234,6 +234,9 @@ static irqreturn_t loongson_ipi_interrupt(int irq, void *dev) per_cpu(irq_stat, cpu).ipi_irqs[IPI_CALL_FUNCTION]++; } + if (action & SMP_CLEAR_VECT) + complete_irq_moving(); + return IRQ_HANDLED; } @@ -388,6 +391,7 @@ int loongson_cpu_disable(void) irq_migrate_all_off_this_cpu(); clear_csr_ecfg(ECFG0_IM); local_irq_restore(flags); + loongarch_avec_offline_cpu(cpu); local_flush_tlb_all(); return 0; @@ -566,6 +570,7 @@ asmlinkage void start_secondary(void) * early is dangerous. */ WARN_ON_ONCE(!irqs_disabled()); + loongarch_avec_online_cpu(cpu); loongson_smp_finish(); cpu_startup_entry(CPUHP_AP_ONLINE_IDLE); diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 9f6f88274bec..1062e713cea4 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -109,7 +109,7 @@ obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o obj-$(CONFIG_TI_PRUSS_INTC) += irq-pruss-intc.o -obj-$(CONFIG_IRQ_LOONGARCH_CPU) += irq-loongarch-cpu.o +obj-$(CONFIG_IRQ_LOONGARCH_CPU) += irq-loongarch-cpu.o irq-loongarch-avec.o obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.o obj-$(CONFIG_LOONGSON_EIOINTC) += irq-loongson-eiointc.o obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o diff --git a/drivers/irqchip/irq-loongarch-avec.c b/drivers/irqchip/irq-loongarch-avec.c new file mode 100644 index 000000000000..4cd9079f2814 --- /dev/null +++ b/drivers/irqchip/irq-loongarch-avec.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Loongson Technologies, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define VECTORS_PER_REG 64 +#define ILR_INVALID_MASK 0x80000000UL +#define ILR_VECTOR_MASK 0xffUL +#define AVEC_MSG_OFFSET 0x100000 + +static phys_addr_t msi_base_v2; +static DEFINE_PER_CPU(struct irq_desc * [NR_VECTORS], irq_map); + +struct pending_list { + struct list_head head; +}; + +static DEFINE_PER_CPU(struct pending_list, pending_list); + +struct loongarch_avec_chip { + struct fwnode_handle *fwnode; + struct irq_domain *domain; + struct irq_matrix *vector_matrix; + raw_spinlock_t lock; +}; + +static struct loongarch_avec_chip loongarch_avec; + +struct loongarch_avec_data { + struct list_head entry; + unsigned int cpu; + unsigned int vec; + unsigned int prev_cpu; + unsigned int prev_vec; + unsigned int moving : 1, + managed : 1; +}; + +static struct cpumask intersect_mask; + +static int assign_irq_vector(struct irq_data *irqd, const struct cpumask *dest, + unsigned int *cpu) +{ + return irq_matrix_alloc(loongarch_avec.vector_matrix, dest, false, cpu); +} + +static inline void loongarch_avec_ack_irq(struct irq_data *d) +{ +} + +static inline void loongarch_avec_unmask_irq(struct irq_data *d) +{ +} + +static inline void loongarch_avec_mask_irq(struct irq_data *d) +{ +} + +static void loongarch_avec_sync(struct loongarch_avec_data *adata) +{ + struct pending_list *plist; + + if (cpu_online(adata->prev_cpu)) { + plist = per_cpu_ptr(&pending_list, adata->prev_cpu); + list_add_tail(&adata->entry, &plist->head); + adata->moving = true; + loongson_send_ipi_single(adata->prev_cpu, SMP_CLEAR_VECT); + } + adata->prev_cpu = adata->cpu; + adata->prev_vec = adata->vec; +} + +static int loongarch_avec_set_affinity(struct irq_data *data, const struct cpumask *dest, + bool force) +{ + struct loongarch_avec_data *adata; + unsigned int cpu, vector; + unsigned long flags; + int ret; + + raw_spin_lock_irqsave(&loongarch_avec.lock, flags); + adata = irq_data_get_irq_chip_data(data); + + if (adata->vec && cpu_online(adata->cpu) && cpumask_test_cpu(adata->cpu, dest)) { + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); + return 0; + } + if (adata->moving) + return -EBUSY; + + cpumask_and(&intersect_mask, dest, cpu_online_mask); + + ret = assign_irq_vector(data, &intersect_mask, &cpu); + if (ret < 0) { + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); + return ret; + } + vector = ret; + adata->cpu = cpu; + adata->vec = vector; + per_cpu_ptr(irq_map, adata->cpu)[adata->vec] = irq_data_to_desc(data); + loongarch_avec_sync(adata); + + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); + irq_data_update_effective_affinity(data, cpumask_of(cpu)); + + return IRQ_SET_MASK_OK; +} + +static void loongarch_avec_compose_msg(struct irq_data *d, + struct msi_msg *msg) +{ + struct loongarch_avec_data *avec_data; + + avec_data = irq_data_get_irq_chip_data(d); + + msg->address_hi = 0x0; + msg->address_lo = msi_base_v2 | ((avec_data->vec & 0xff) << 4) | + ((cpu_logical_map(avec_data->cpu & 0xffff)) << 12); + msg->data = 0x0; + +} + +static struct irq_chip loongarch_avec_controller = { + .name = "CORE_AVEC", + .irq_ack = loongarch_avec_ack_irq, + .irq_mask = loongarch_avec_mask_irq, + .irq_unmask = loongarch_avec_unmask_irq, + .irq_set_affinity = loongarch_avec_set_affinity, + .irq_compose_msi_msg = loongarch_avec_compose_msg, +}; + +void complete_irq_moving(void) +{ + struct pending_list *plist = this_cpu_ptr(&pending_list); + struct loongarch_avec_data *adata, *tmp; + int cpu, vector, bias; + u64 irr; + + raw_spin_lock(&loongarch_avec.lock); + + list_for_each_entry_safe(adata, tmp, &plist->head, entry) { + cpu = adata->prev_cpu; + vector = adata->prev_vec; + bias = vector / VECTORS_PER_REG; + switch (bias) { + case 0: + irr = csr_read64(LOONGARCH_CSR_IRR0); + case 1: + irr = csr_read64(LOONGARCH_CSR_IRR1); + case 2: + irr = csr_read64(LOONGARCH_CSR_IRR2); + case 3: + irr = csr_read64(LOONGARCH_CSR_IRR3); + } + + if (irr & (1UL << (vector % VECTORS_PER_REG))) { + loongson_send_ipi_single(cpu, SMP_CLEAR_VECT); + continue; + } + list_del(&adata->entry); + irq_matrix_free(loongarch_avec.vector_matrix, cpu, vector, adata->managed); + this_cpu_write(irq_map[vector], NULL); + adata->moving = 0; + } + raw_spin_unlock(&loongarch_avec.lock); +} + +static void loongarch_avec_dispatch(struct irq_desc *desc) +{ + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned long vector; + struct irq_desc *d; + + chained_irq_enter(chip, desc); + vector = csr_read64(LOONGARCH_CSR_ILR); + if (vector & ILR_INVALID_MASK) + return; + + vector &= ILR_VECTOR_MASK; + + d = this_cpu_read(irq_map[vector]); + if (d) { + generic_handle_irq_desc(d); + } else { + pr_warn("IRQ ERROR:Unexpected irq occur on cpu %d[vector %ld]\n", + smp_processor_id(), vector); + } + + chained_irq_exit(chip, desc); +} + +static int loongarch_avec_alloc(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + struct loongarch_avec_data *adata; + struct irq_data *irqd; + unsigned int cpu, vector, i, ret; + unsigned long flags; + + raw_spin_lock_irqsave(&loongarch_avec.lock, flags); + for (i = 0; i < nr_irqs; i++) { + irqd = irq_domain_get_irq_data(domain, virq + i); + adata = kzalloc(sizeof(*adata), GFP_KERNEL); + if (!adata) { + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); + return -ENOMEM; + } + ret = assign_irq_vector(irqd, cpu_online_mask, &cpu); + if (ret < 0) { + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); + return ret; + } + vector = ret; + adata->prev_cpu = adata->cpu = cpu; + adata->prev_vec = adata->vec = vector; + adata->managed = irqd_affinity_is_managed(irqd); + irq_domain_set_info(domain, virq + i, virq + i, &loongarch_avec_controller, + adata, handle_edge_irq, NULL, NULL); + adata->moving = 0; + irqd_set_single_target(irqd); + irqd_set_affinity_on_activate(irqd); + + per_cpu_ptr(irq_map, adata->cpu)[adata->vec] = irq_data_to_desc(irqd); + } + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); + + return 0; +} + +static void clear_free_vector(struct irq_data *irqd) +{ + struct loongarch_avec_data *adata = irq_data_get_irq_chip_data(irqd); + bool managed = irqd_affinity_is_managed(irqd); + + per_cpu(irq_map, adata->cpu)[adata->vec] = NULL; + irq_matrix_free(loongarch_avec.vector_matrix, adata->cpu, adata->vec, managed); + adata->cpu = 0; + adata->vec = 0; + if (!adata->moving) + return; + + per_cpu(irq_map, adata->prev_cpu)[adata->prev_vec] = 0; + irq_matrix_free(loongarch_avec.vector_matrix, adata->prev_cpu, + adata->prev_vec, adata->managed); + adata->prev_vec = 0; + adata->prev_cpu = 0; + adata->moving = 0; + list_del_init(&adata->entry); +} + +static void loongarch_avec_free(struct irq_domain *domain, unsigned int virq, + unsigned int nr_irqs) +{ + struct irq_data *d; + unsigned long flags; + unsigned int i; + + raw_spin_lock_irqsave(&loongarch_avec.lock, flags); + for (i = 0; i < nr_irqs; i++) { + d = irq_domain_get_irq_data(domain, virq + i); + if (d) { + clear_free_vector(d); + irq_domain_reset_irq_data(d); + + } + } + + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); +} + +static const struct irq_domain_ops loongarch_avec_domain_ops = { + .alloc = loongarch_avec_alloc, + .free = loongarch_avec_free, +}; + +static int __init irq_matrix_init(void) +{ + int i; + + loongarch_avec.vector_matrix = irq_alloc_matrix(NR_VECTORS, 0, NR_VECTORS - 1); + if (!loongarch_avec.vector_matrix) + return -ENOMEM; + for (i = 0; i < NR_LEGACY_VECTORS; i++) + irq_matrix_assign_system(loongarch_avec.vector_matrix, i, false); + + irq_matrix_online(loongarch_avec.vector_matrix); + + return 0; +} + +static int __init loongarch_avec_init(struct irq_domain *parent) +{ + struct pending_list *plist = per_cpu_ptr(&pending_list, 0); + int ret = 0, parent_irq; + unsigned long tmp; + + raw_spin_lock_init(&loongarch_avec.lock); + + loongarch_avec.fwnode = irq_domain_alloc_named_fwnode("CORE_AVEC"); + if (!loongarch_avec.fwnode) { + pr_err("Unable to allocate domain handle\n"); + ret = -ENOMEM; + goto out; + } + + loongarch_avec.domain = irq_domain_create_tree(loongarch_avec.fwnode, + &loongarch_avec_domain_ops, NULL); + if (!loongarch_avec.domain) { + pr_err("core-vec: cannot create IRQ domain\n"); + ret = -ENOMEM; + goto out_free_handle; + } + + parent_irq = irq_create_mapping(parent, INT_AVEC); + if (!parent_irq) { + pr_err("Failed to mapping hwirq\n"); + ret = -EINVAL; + goto out_remove_domain; + } + irq_set_chained_handler_and_data(parent_irq, loongarch_avec_dispatch, NULL); + + ret = irq_matrix_init(); + if (ret) { + pr_err("Failed to init irq matrix\n"); + goto out_free_matrix; + } + + INIT_LIST_HEAD(&plist->head); + tmp = iocsr_read64(LOONGARCH_IOCSR_MISC_FUNC); + tmp |= IOCSR_MISC_FUNC_AVEC_EN; + iocsr_write64(tmp, LOONGARCH_IOCSR_MISC_FUNC); + + return ret; + +out_free_matrix: + kfree(loongarch_avec.vector_matrix); +out_remove_domain: + irq_domain_remove(loongarch_avec.domain); +out_free_handle: + irq_domain_free_fwnode(loongarch_avec.fwnode); +out: + return ret; +} + +void loongarch_avec_offline_cpu(unsigned int cpu) +{ + struct pending_list *plist = per_cpu_ptr(&pending_list, cpu); + unsigned long flags; + + raw_spin_lock_irqsave(&loongarch_avec.lock, flags); + if (list_empty(&plist->head)) + irq_matrix_offline(loongarch_avec.vector_matrix); + else + pr_warn("cpu %d advanced extioi is busy\n", cpu); + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); +} + +void loongarch_avec_online_cpu(unsigned int cpu) +{ + struct pending_list *plist = per_cpu_ptr(&pending_list, cpu); + unsigned long flags; + + raw_spin_lock_irqsave(&loongarch_avec.lock, flags); + + irq_matrix_online(loongarch_avec.vector_matrix); + + INIT_LIST_HEAD(&plist->head); + + raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); +} + +static int __init pch_msi_parse_madt(union acpi_subtable_headers *header, + const unsigned long end) +{ + struct acpi_madt_msi_pic *pchmsi_entry = (struct acpi_madt_msi_pic *)header; + + msi_base_v2 = pchmsi_entry->msg_address - AVEC_MSG_OFFSET; + return pch_msi_acpi_init_v2(loongarch_avec.domain, pchmsi_entry); +} + +static inline int __init acpi_cascade_irqdomain_init(void) +{ + return acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 1); +} + +int __init loongarch_avec_acpi_init(struct irq_domain *parent) +{ + int ret = 0; + + ret = loongarch_avec_init(parent); + if (ret) { + pr_err("Failed to init irq domain\n"); + return ret; + } + + ret = acpi_cascade_irqdomain_init(); + if (ret) { + pr_err("Failed to cascade IRQ domain\n"); + return ret; + } + + return ret; +} diff --git a/drivers/irqchip/irq-loongarch-cpu.c b/drivers/irqchip/irq-loongarch-cpu.c index 9d8f2c406043..1ecac59925c6 100644 --- a/drivers/irqchip/irq-loongarch-cpu.c +++ b/drivers/irqchip/irq-loongarch-cpu.c @@ -138,7 +138,9 @@ static int __init acpi_cascade_irqdomain_init(void) if (r < 0) return r; - return 0; + if (cpu_has_avecint) + r = loongarch_avec_acpi_init(irq_domain); + return r; } static int __init cpuintc_acpi_init(union acpi_subtable_headers *header, diff --git a/drivers/irqchip/irq-loongson-eiointc.c b/drivers/irqchip/irq-loongson-eiointc.c index c7ddebf312ad..1f9a30488137 100644 --- a/drivers/irqchip/irq-loongson-eiointc.c +++ b/drivers/irqchip/irq-loongson-eiointc.c @@ -359,6 +359,9 @@ static int __init acpi_cascade_irqdomain_init(void) if (r < 0) return r; + if (cpu_has_avecint) + return 0; + r = acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 1); if (r < 0) return r; diff --git a/drivers/irqchip/irq-loongson-pch-msi.c b/drivers/irqchip/irq-loongson-pch-msi.c index dd4d699170f4..1926857f9a41 100644 --- a/drivers/irqchip/irq-loongson-pch-msi.c +++ b/drivers/irqchip/irq-loongson-pch-msi.c @@ -16,7 +16,6 @@ #include static int nr_pics; - struct pch_msi_data { struct mutex msi_map_lock; phys_addr_t doorbell; @@ -100,6 +99,17 @@ static struct irq_chip middle_irq_chip = { .irq_compose_msi_msg = pch_msi_compose_msi_msg, }; +static struct irq_chip pch_msi_irq_chip_v2 = { + .name = "MSI", + .irq_ack = irq_chip_ack_parent, +}; + +static struct msi_domain_info pch_msi_domain_info_v2 = { + .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | + MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX, + .chip = &pch_msi_irq_chip_v2, +}; + static int pch_msi_parent_domain_alloc(struct irq_domain *domain, unsigned int virq, int hwirq) { @@ -268,6 +278,9 @@ struct fwnode_handle *get_pch_msi_handle(int pci_segment) { int i; + if (cpu_has_avecint) + return pch_msi_handle[0]; + for (i = 0; i < MAX_IO_PICS; i++) { if (msi_group[i].pci_segment == pci_segment) return pch_msi_handle[i]; @@ -289,4 +302,32 @@ int __init pch_msi_acpi_init(struct irq_domain *parent, return ret; } + +int __init pch_msi_acpi_init_v2(struct irq_domain *parent, + struct acpi_madt_msi_pic *msi_entry) +{ + struct irq_domain *msi_domain; + + if (pch_msi_handle[0]) + return 0; + + pch_msi_handle[0] = irq_domain_alloc_named_fwnode("msipic-v2"); + if (!pch_msi_handle[0]) { + pr_err("Unable to allocate domain handle\n"); + kfree(pch_msi_handle[0]); + return -ENOMEM; + } + + msi_domain = pci_msi_create_irq_domain(pch_msi_handle[0], + &pch_msi_domain_info_v2, + parent); + if (!msi_domain) { + pr_err("Failed to create PCI MSI domain\n"); + kfree(pch_msi_handle[0]); + return -ENOMEM; + } + + pr_info("IRQ domain MSIPIC-V2 init done.\n"); + return 0; +} #endif From ef7080bd30bab81a1c4dd7c0afd942d2ab43081d Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 3 Jun 2024 12:56:52 +0000 Subject: [PATCH 052/152] irqchip/riscv-aplic: Simplify the initialization code The initialization code has an is_of_node() check and invokes to_of_node() for every of_property_*() invocation. to_of_node() has a is_of_node() check already, so simplify the code by invoking to_of_node() and checking that for NULL. If not NULL hand in the node pointer to of_property_*(). The same applies to of_property_*() which fails when invoked with a NULL node pointer. [ tglx: Massaged change log ] Signed-off-by: Jinjie Ruan Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/all/20240603125652.791601-1-ruanjinjie@huawei.com --- drivers/irqchip/irq-riscv-aplic-main.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/irqchip/irq-riscv-aplic-main.c b/drivers/irqchip/irq-riscv-aplic-main.c index 774a0c97fdab..28dd175b5764 100644 --- a/drivers/irqchip/irq-riscv-aplic-main.c +++ b/drivers/irqchip/irq-riscv-aplic-main.c @@ -127,6 +127,7 @@ static void aplic_init_hw_irqs(struct aplic_priv *priv) int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem *regs) { + struct device_node *np = to_of_node(dev->fwnode); struct of_phandle_args parent; int rc; @@ -134,7 +135,7 @@ int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem * * Currently, only OF fwnode is supported so extend this * function for ACPI support. */ - if (!is_of_node(dev->fwnode)) + if (!np) return -EINVAL; /* Save device pointer and register base */ @@ -142,8 +143,7 @@ int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem * priv->regs = regs; /* Find out number of interrupt sources */ - rc = of_property_read_u32(to_of_node(dev->fwnode), "riscv,num-sources", - &priv->nr_irqs); + rc = of_property_read_u32(np, "riscv,num-sources", &priv->nr_irqs); if (rc) { dev_err(dev, "failed to get number of interrupt sources\n"); return rc; @@ -155,8 +155,8 @@ int aplic_setup_priv(struct aplic_priv *priv, struct device *dev, void __iomem * * If "msi-parent" property is present then we ignore the * APLIC IDCs which forces the APLIC driver to use MSI mode. */ - if (!of_property_present(to_of_node(dev->fwnode), "msi-parent")) { - while (!of_irq_parse_one(to_of_node(dev->fwnode), priv->nr_idcs, &parent)) + if (!of_property_present(np, "msi-parent")) { + while (!of_irq_parse_one(np, priv->nr_idcs, &parent)) priv->nr_idcs++; } @@ -184,8 +184,7 @@ static int aplic_probe(struct platform_device *pdev) * If msi-parent property is present then setup APLIC MSI * mode otherwise setup APLIC direct mode. */ - if (is_of_node(dev->fwnode)) - msi_mode = of_property_present(to_of_node(dev->fwnode), "msi-parent"); + msi_mode = of_property_present(to_of_node(dev->fwnode), "msi-parent"); if (msi_mode) rc = aplic_msi_setup(dev, regs); else From 7cc4f309c933ec5d64eea31066fe86bbf9e48819 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Fri, 14 Jun 2024 23:34:49 +0800 Subject: [PATCH 053/152] irqchip/dw-apb-ictl: Support building as module The driver is now always built in. In some synaptics ARM64 SoCs it is used as a second level interrupt controller hanging off the ARM GIC and is therefore loadable during boot. Enable it to be built as a module and handle built-in usage correctly, so that it continues working on systems where it is the main interrupt controller. [ tglx: Massage changelog ] Signed-off-by: Jisheng Zhang Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240614153449.2083-1-jszhang@kernel.org --- drivers/irqchip/Kconfig | 2 +- drivers/irqchip/irq-dw-apb-ictl.c | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 348f34525d23..aaf84530386a 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -145,7 +145,7 @@ config DAVINCI_CP_INTC select IRQ_DOMAIN config DW_APB_ICTL - bool + tristate "DesignWare APB Interrupt Controller" select GENERIC_IRQ_CHIP select IRQ_DOMAIN_HIERARCHY diff --git a/drivers/irqchip/irq-dw-apb-ictl.c b/drivers/irqchip/irq-dw-apb-ictl.c index d5c1c750c8d2..5eda6c4689cf 100644 --- a/drivers/irqchip/irq-dw-apb-ictl.c +++ b/drivers/irqchip/irq-dw-apb-ictl.c @@ -122,7 +122,7 @@ static int __init dw_apb_ictl_init(struct device_node *np, int ret, nrirqs, parent_irq, i; u32 reg; - if (!parent) { + if (!parent && IS_BUILTIN(CONFIG_DW_APB_ICTL)) { /* Used as the primary interrupt controller */ parent_irq = 0; domain_ops = &dw_apb_ictl_irq_domain_ops; @@ -214,5 +214,12 @@ err_release: release_mem_region(r.start, resource_size(&r)); return ret; } -IRQCHIP_DECLARE(dw_apb_ictl, - "snps,dw-apb-ictl", dw_apb_ictl_init); +#if IS_BUILTIN(CONFIG_DW_APB_ICTL) +IRQCHIP_DECLARE(dw_apb_ictl, "snps,dw-apb-ictl", dw_apb_ictl_init); +#else +IRQCHIP_PLATFORM_DRIVER_BEGIN(dw_apb_ictl) +IRQCHIP_MATCH("snps,dw-apb-ictl", dw_apb_ictl_init) +IRQCHIP_PLATFORM_DRIVER_END(dw_apb_ictl) +MODULE_DESCRIPTION("DesignWare APB Interrupt Controller"); +MODULE_LICENSE("GPL v2"); +#endif From b20cf2dcbe8b77afb4fcbe7af9349dfca6b7f22a Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Thu, 20 Jun 2024 10:31:08 +0200 Subject: [PATCH 054/152] irqchip/stm32-exti: Add CONFIG_STM32MP_EXTI To guarantee bisect-ability during the split of stm32-exti in MCU and MPU code, introduce CONFIG_STM32MP_EXTI. Signed-off-by: Antonio Borneo Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240620083115.204362-2-antonio.borneo@foss.st.com --- drivers/irqchip/Kconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index aaf84530386a..bc5e19177280 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -404,6 +404,10 @@ config LS_SCFG_MSI config PARTITION_PERCPU bool +config STM32MP_EXTI + bool + select STM32_EXTI + config STM32_EXTI bool select IRQ_DOMAIN From 721cdbd68c588a0e883d8a7658bc6a56237eaa71 Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Thu, 20 Jun 2024 10:31:09 +0200 Subject: [PATCH 055/152] ARM: stm32: Use different EXTI driver on ARMv7m and ARMv7a Build the proper driver by selecting the appropriate config flag. Signed-off-by: Antonio Borneo Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240620083115.204362-3-antonio.borneo@foss.st.com --- arch/arm/mach-stm32/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm/mach-stm32/Kconfig b/arch/arm/mach-stm32/Kconfig index ae21a9f78f9c..a401a991fe08 100644 --- a/arch/arm/mach-stm32/Kconfig +++ b/arch/arm/mach-stm32/Kconfig @@ -11,7 +11,8 @@ menuconfig ARCH_STM32 select CLKSRC_STM32 select PINCTRL select RESET_CONTROLLER - select STM32_EXTI + select STM32MP_EXTI if ARCH_MULTI_V7 + select STM32_EXTI if ARM_SINGLE_ARMV7M select STM32_FIREWALL help Support for STMicroelectronics STM32 processors. From 00f07f97276d9c4c09526798d41cb825095d2319 Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Thu, 20 Jun 2024 10:31:10 +0200 Subject: [PATCH 056/152] arm64: Kconfig: Select STM32MP_EXTI on STM32 platforms Use the new config flag to build the correct driver that will be extracted from the old code. Signed-off-by: Antonio Borneo Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240620083115.204362-4-antonio.borneo@foss.st.com --- arch/arm64/Kconfig.platforms | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index a52618073de2..be8629858d74 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -309,7 +309,7 @@ config ARCH_STM32 select GPIOLIB select PINCTRL select PINCTRL_STM32MP257 - select STM32_EXTI + select STM32MP_EXTI select ARM_SMC_MBOX select ARM_SCMI_PROTOCOL select COMMON_CLK_SCMI From 350755e2e548ccbe941d045900b57233efa906cb Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Thu, 20 Jun 2024 10:31:11 +0200 Subject: [PATCH 057/152] irqchip/stm32-exti: Split MCU and MPU code Keep only the code for ARMv7m STM32 MCUs in in stm32-exti.c and split out the code for ARMv7a & ARMv8a STM32MPxxx MPUs into stm32mp-exti.c Signed-off-by: Antonio Borneo Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240620083115.204362-5-antonio.borneo@foss.st.com --- drivers/irqchip/Kconfig | 3 +- drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-stm32-exti.c | 670 +------------------------- drivers/irqchip/irq-stm32mp-exti.c | 744 +++++++++++++++++++++++++++++ 4 files changed, 751 insertions(+), 667 deletions(-) create mode 100644 drivers/irqchip/irq-stm32mp-exti.c diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index bc5e19177280..978639d10d75 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -406,7 +406,8 @@ config PARTITION_PERCPU config STM32MP_EXTI bool - select STM32_EXTI + select IRQ_DOMAIN + select GENERIC_IRQ_CHIP config STM32_EXTI bool diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 1062e713cea4..de091a9f7c88 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -84,6 +84,7 @@ obj-$(CONFIG_MVEBU_SEI) += irq-mvebu-sei.o obj-$(CONFIG_LS_EXTIRQ) += irq-ls-extirq.o obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o irq-aspeed-scu-ic.o +obj-$(CONFIG_STM32MP_EXTI) += irq-stm32mp-exti.o obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o obj-$(CONFIG_IRQ_UNIPHIER_AIDET) += irq-uniphier-aidet.o diff --git a/drivers/irqchip/irq-stm32-exti.c b/drivers/irqchip/irq-stm32-exti.c index 2cc9f3b7d669..7c6a0080c330 100644 --- a/drivers/irqchip/irq-stm32-exti.c +++ b/drivers/irqchip/irq-stm32-exti.c @@ -1,45 +1,22 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) Maxime Coquelin 2015 - * Copyright (C) STMicroelectronics 2017 + * Copyright (C) STMicroelectronics 2017-2024 * Author: Maxime Coquelin */ #include -#include -#include #include #include #include #include #include #include -#include -#include #include #include -#include -#include - -#include #define IRQS_PER_BANK 32 -#define HWSPNLCK_TIMEOUT 1000 /* usec */ - -#define EXTI_EnCIDCFGR(n) (0x180 + (n) * 4) -#define EXTI_HWCFGR1 0x3f0 - -/* Register: EXTI_EnCIDCFGR(n) */ -#define EXTI_CIDCFGR_CFEN_MASK BIT(0) -#define EXTI_CIDCFGR_CID_MASK GENMASK(6, 4) -#define EXTI_CIDCFGR_CID_SHIFT 4 - -/* Register: EXTI_HWCFGR1 */ -#define EXTI_HWCFGR1_CIDWIDTH_MASK GENMASK(27, 24) - -#define EXTI_CID1 1 - struct stm32_exti_bank { u32 imr_ofst; u32 emr_ofst; @@ -47,13 +24,8 @@ struct stm32_exti_bank { u32 ftsr_ofst; u32 swier_ofst; u32 rpr_ofst; - u32 fpr_ofst; - u32 trg_ofst; - u32 seccfgr_ofst; }; -#define UNDEF_REG ~0 - struct stm32_exti_drv_data { const struct stm32_exti_bank **exti_banks; const u8 *desc_irqs; @@ -63,7 +35,6 @@ struct stm32_exti_drv_data { struct stm32_exti_chip_data { struct stm32_exti_host_data *host_data; const struct stm32_exti_bank *reg_bank; - struct raw_spinlock rlock; u32 wake_active; u32 mask_cache; u32 rtsr_cache; @@ -76,8 +47,6 @@ struct stm32_exti_host_data { struct device *dev; struct stm32_exti_chip_data *chips_data; const struct stm32_exti_drv_data *drv_data; - struct hwspinlock *hwlock; - bool dt_has_irqs_desc; /* skip internal desc_irqs array and get it from DT */ }; static const struct stm32_exti_bank stm32f4xx_exti_b1 = { @@ -87,9 +56,6 @@ static const struct stm32_exti_bank stm32f4xx_exti_b1 = { .ftsr_ofst = 0x0C, .swier_ofst = 0x10, .rpr_ofst = 0x14, - .fpr_ofst = UNDEF_REG, - .trg_ofst = UNDEF_REG, - .seccfgr_ofst = UNDEF_REG, }; static const struct stm32_exti_bank *stm32f4xx_exti_banks[] = { @@ -108,9 +74,6 @@ static const struct stm32_exti_bank stm32h7xx_exti_b1 = { .ftsr_ofst = 0x04, .swier_ofst = 0x08, .rpr_ofst = 0x88, - .fpr_ofst = UNDEF_REG, - .trg_ofst = UNDEF_REG, - .seccfgr_ofst = UNDEF_REG, }; static const struct stm32_exti_bank stm32h7xx_exti_b2 = { @@ -120,9 +83,6 @@ static const struct stm32_exti_bank stm32h7xx_exti_b2 = { .ftsr_ofst = 0x24, .swier_ofst = 0x28, .rpr_ofst = 0x98, - .fpr_ofst = UNDEF_REG, - .trg_ofst = UNDEF_REG, - .seccfgr_ofst = UNDEF_REG, }; static const struct stm32_exti_bank stm32h7xx_exti_b3 = { @@ -132,9 +92,6 @@ static const struct stm32_exti_bank stm32h7xx_exti_b3 = { .ftsr_ofst = 0x44, .swier_ofst = 0x48, .rpr_ofst = 0xA8, - .fpr_ofst = UNDEF_REG, - .trg_ofst = UNDEF_REG, - .seccfgr_ofst = UNDEF_REG, }; static const struct stm32_exti_bank *stm32h7xx_exti_banks[] = { @@ -148,183 +105,12 @@ static const struct stm32_exti_drv_data stm32h7xx_drv_data = { .bank_nr = ARRAY_SIZE(stm32h7xx_exti_banks), }; -static const struct stm32_exti_bank stm32mp1_exti_b1 = { - .imr_ofst = 0x80, - .emr_ofst = UNDEF_REG, - .rtsr_ofst = 0x00, - .ftsr_ofst = 0x04, - .swier_ofst = 0x08, - .rpr_ofst = 0x0C, - .fpr_ofst = 0x10, - .trg_ofst = 0x3EC, - .seccfgr_ofst = 0x14, -}; - -static const struct stm32_exti_bank stm32mp1_exti_b2 = { - .imr_ofst = 0x90, - .emr_ofst = UNDEF_REG, - .rtsr_ofst = 0x20, - .ftsr_ofst = 0x24, - .swier_ofst = 0x28, - .rpr_ofst = 0x2C, - .fpr_ofst = 0x30, - .trg_ofst = 0x3E8, - .seccfgr_ofst = 0x34, -}; - -static const struct stm32_exti_bank stm32mp1_exti_b3 = { - .imr_ofst = 0xA0, - .emr_ofst = UNDEF_REG, - .rtsr_ofst = 0x40, - .ftsr_ofst = 0x44, - .swier_ofst = 0x48, - .rpr_ofst = 0x4C, - .fpr_ofst = 0x50, - .trg_ofst = 0x3E4, - .seccfgr_ofst = 0x54, -}; - -static const struct stm32_exti_bank *stm32mp1_exti_banks[] = { - &stm32mp1_exti_b1, - &stm32mp1_exti_b2, - &stm32mp1_exti_b3, -}; - -static struct irq_chip stm32_exti_h_chip; -static struct irq_chip stm32_exti_h_chip_direct; - -#define EXTI_INVALID_IRQ U8_MAX -#define STM32MP1_DESC_IRQ_SIZE (ARRAY_SIZE(stm32mp1_exti_banks) * IRQS_PER_BANK) - -/* - * Use some intentionally tricky logic here to initialize the whole array to - * EXTI_INVALID_IRQ, but then override certain fields, requiring us to indicate - * that we "know" that there are overrides in this structure, and we'll need to - * disable that warning from W=1 builds. - */ -__diag_push(); -__diag_ignore_all("-Woverride-init", - "logic to initialize all and then override some is OK"); - -static const u8 stm32mp1_desc_irq[] = { - /* default value */ - [0 ... (STM32MP1_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ, - - [0] = 6, - [1] = 7, - [2] = 8, - [3] = 9, - [4] = 10, - [5] = 23, - [6] = 64, - [7] = 65, - [8] = 66, - [9] = 67, - [10] = 40, - [11] = 42, - [12] = 76, - [13] = 77, - [14] = 121, - [15] = 127, - [16] = 1, - [19] = 3, - [21] = 31, - [22] = 33, - [23] = 72, - [24] = 95, - [25] = 107, - [26] = 37, - [27] = 38, - [28] = 39, - [29] = 71, - [30] = 52, - [31] = 53, - [32] = 82, - [33] = 83, - [46] = 151, - [47] = 93, - [48] = 138, - [50] = 139, - [52] = 140, - [53] = 141, - [54] = 135, - [61] = 100, - [65] = 144, - [68] = 143, - [70] = 62, - [73] = 129, -}; - -static const u8 stm32mp13_desc_irq[] = { - /* default value */ - [0 ... (STM32MP1_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ, - - [0] = 6, - [1] = 7, - [2] = 8, - [3] = 9, - [4] = 10, - [5] = 24, - [6] = 65, - [7] = 66, - [8] = 67, - [9] = 68, - [10] = 41, - [11] = 43, - [12] = 77, - [13] = 78, - [14] = 106, - [15] = 109, - [16] = 1, - [19] = 3, - [21] = 32, - [22] = 34, - [23] = 73, - [24] = 93, - [25] = 114, - [26] = 38, - [27] = 39, - [28] = 40, - [29] = 72, - [30] = 53, - [31] = 54, - [32] = 83, - [33] = 84, - [44] = 96, - [47] = 92, - [48] = 116, - [50] = 117, - [52] = 118, - [53] = 119, - [68] = 63, - [70] = 98, -}; - -__diag_pop(); - -static const struct stm32_exti_drv_data stm32mp1_drv_data = { - .exti_banks = stm32mp1_exti_banks, - .bank_nr = ARRAY_SIZE(stm32mp1_exti_banks), - .desc_irqs = stm32mp1_desc_irq, -}; - -static const struct stm32_exti_drv_data stm32mp13_drv_data = { - .exti_banks = stm32mp1_exti_banks, - .bank_nr = ARRAY_SIZE(stm32mp1_exti_banks), - .desc_irqs = stm32mp13_desc_irq, -}; - static unsigned long stm32_exti_pending(struct irq_chip_generic *gc) { struct stm32_exti_chip_data *chip_data = gc->private; const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; - unsigned long pending; - pending = irq_reg_readl(gc, stm32_bank->rpr_ofst); - if (stm32_bank->fpr_ofst != UNDEF_REG) - pending |= irq_reg_readl(gc, stm32_bank->fpr_ofst); - - return pending; + return irq_reg_readl(gc, stm32_bank->rpr_ofst); } static void stm32_irq_handler(struct irq_desc *desc) @@ -380,33 +166,21 @@ static int stm32_irq_set_type(struct irq_data *d, unsigned int type) struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); struct stm32_exti_chip_data *chip_data = gc->private; const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; - struct hwspinlock *hwlock = chip_data->host_data->hwlock; u32 rtsr, ftsr; int err; irq_gc_lock(gc); - if (hwlock) { - err = hwspin_lock_timeout_in_atomic(hwlock, HWSPNLCK_TIMEOUT); - if (err) { - pr_err("%s can't get hwspinlock (%d)\n", __func__, err); - goto unlock; - } - } - rtsr = irq_reg_readl(gc, stm32_bank->rtsr_ofst); ftsr = irq_reg_readl(gc, stm32_bank->ftsr_ofst); err = stm32_exti_set_type(d, type, &rtsr, &ftsr); if (err) - goto unspinlock; + goto unlock; irq_reg_writel(gc, rtsr, stm32_bank->rtsr_ofst); irq_reg_writel(gc, ftsr, stm32_bank->ftsr_ofst); -unspinlock: - if (hwlock) - hwspin_unlock_in_atomic(hwlock); unlock: irq_gc_unlock(gc); @@ -494,287 +268,10 @@ static void stm32_irq_ack(struct irq_data *d) irq_gc_lock(gc); irq_reg_writel(gc, d->mask, stm32_bank->rpr_ofst); - if (stm32_bank->fpr_ofst != UNDEF_REG) - irq_reg_writel(gc, d->mask, stm32_bank->fpr_ofst); irq_gc_unlock(gc); } -/* directly set the target bit without reading first. */ -static inline void stm32_exti_write_bit(struct irq_data *d, u32 reg) -{ - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - void __iomem *base = chip_data->host_data->base; - u32 val = BIT(d->hwirq % IRQS_PER_BANK); - - writel_relaxed(val, base + reg); -} - -static inline u32 stm32_exti_set_bit(struct irq_data *d, u32 reg) -{ - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - void __iomem *base = chip_data->host_data->base; - u32 val; - - val = readl_relaxed(base + reg); - val |= BIT(d->hwirq % IRQS_PER_BANK); - writel_relaxed(val, base + reg); - - return val; -} - -static inline u32 stm32_exti_clr_bit(struct irq_data *d, u32 reg) -{ - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - void __iomem *base = chip_data->host_data->base; - u32 val; - - val = readl_relaxed(base + reg); - val &= ~BIT(d->hwirq % IRQS_PER_BANK); - writel_relaxed(val, base + reg); - - return val; -} - -static void stm32_exti_h_eoi(struct irq_data *d) -{ - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; - - raw_spin_lock(&chip_data->rlock); - - stm32_exti_write_bit(d, stm32_bank->rpr_ofst); - if (stm32_bank->fpr_ofst != UNDEF_REG) - stm32_exti_write_bit(d, stm32_bank->fpr_ofst); - - raw_spin_unlock(&chip_data->rlock); - - if (d->parent_data->chip) - irq_chip_eoi_parent(d); -} - -static void stm32_exti_h_mask(struct irq_data *d) -{ - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; - - raw_spin_lock(&chip_data->rlock); - chip_data->mask_cache = stm32_exti_clr_bit(d, stm32_bank->imr_ofst); - raw_spin_unlock(&chip_data->rlock); - - if (d->parent_data->chip) - irq_chip_mask_parent(d); -} - -static void stm32_exti_h_unmask(struct irq_data *d) -{ - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; - - raw_spin_lock(&chip_data->rlock); - chip_data->mask_cache = stm32_exti_set_bit(d, stm32_bank->imr_ofst); - raw_spin_unlock(&chip_data->rlock); - - if (d->parent_data->chip) - irq_chip_unmask_parent(d); -} - -static int stm32_exti_h_set_type(struct irq_data *d, unsigned int type) -{ - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; - struct hwspinlock *hwlock = chip_data->host_data->hwlock; - void __iomem *base = chip_data->host_data->base; - u32 rtsr, ftsr; - int err; - - raw_spin_lock(&chip_data->rlock); - - if (hwlock) { - err = hwspin_lock_timeout_in_atomic(hwlock, HWSPNLCK_TIMEOUT); - if (err) { - pr_err("%s can't get hwspinlock (%d)\n", __func__, err); - goto unlock; - } - } - - rtsr = readl_relaxed(base + stm32_bank->rtsr_ofst); - ftsr = readl_relaxed(base + stm32_bank->ftsr_ofst); - - err = stm32_exti_set_type(d, type, &rtsr, &ftsr); - if (err) - goto unspinlock; - - writel_relaxed(rtsr, base + stm32_bank->rtsr_ofst); - writel_relaxed(ftsr, base + stm32_bank->ftsr_ofst); - -unspinlock: - if (hwlock) - hwspin_unlock_in_atomic(hwlock); -unlock: - raw_spin_unlock(&chip_data->rlock); - - return err; -} - -static int stm32_exti_h_set_wake(struct irq_data *d, unsigned int on) -{ - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - u32 mask = BIT(d->hwirq % IRQS_PER_BANK); - - raw_spin_lock(&chip_data->rlock); - - if (on) - chip_data->wake_active |= mask; - else - chip_data->wake_active &= ~mask; - - raw_spin_unlock(&chip_data->rlock); - - return 0; -} - -static int stm32_exti_h_set_affinity(struct irq_data *d, - const struct cpumask *dest, bool force) -{ - if (d->parent_data->chip) - return irq_chip_set_affinity_parent(d, dest, force); - - return IRQ_SET_MASK_OK_DONE; -} - -static int stm32_exti_h_suspend(struct device *dev) -{ - struct stm32_exti_host_data *host_data = dev_get_drvdata(dev); - struct stm32_exti_chip_data *chip_data; - int i; - - for (i = 0; i < host_data->drv_data->bank_nr; i++) { - chip_data = &host_data->chips_data[i]; - stm32_chip_suspend(chip_data, chip_data->wake_active); - } - - return 0; -} - -static int stm32_exti_h_resume(struct device *dev) -{ - struct stm32_exti_host_data *host_data = dev_get_drvdata(dev); - struct stm32_exti_chip_data *chip_data; - int i; - - for (i = 0; i < host_data->drv_data->bank_nr; i++) { - chip_data = &host_data->chips_data[i]; - stm32_chip_resume(chip_data, chip_data->mask_cache); - } - - return 0; -} - -static int stm32_exti_h_retrigger(struct irq_data *d) -{ - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; - void __iomem *base = chip_data->host_data->base; - u32 mask = BIT(d->hwirq % IRQS_PER_BANK); - - writel_relaxed(mask, base + stm32_bank->swier_ofst); - - return 0; -} - -static struct irq_chip stm32_exti_h_chip = { - .name = "stm32-exti-h", - .irq_eoi = stm32_exti_h_eoi, - .irq_mask = stm32_exti_h_mask, - .irq_unmask = stm32_exti_h_unmask, - .irq_retrigger = stm32_exti_h_retrigger, - .irq_set_type = stm32_exti_h_set_type, - .irq_set_wake = stm32_exti_h_set_wake, - .flags = IRQCHIP_MASK_ON_SUSPEND, - .irq_set_affinity = IS_ENABLED(CONFIG_SMP) ? stm32_exti_h_set_affinity : NULL, -}; - -static struct irq_chip stm32_exti_h_chip_direct = { - .name = "stm32-exti-h-direct", - .irq_eoi = irq_chip_eoi_parent, - .irq_ack = irq_chip_ack_parent, - .irq_mask = stm32_exti_h_mask, - .irq_unmask = stm32_exti_h_unmask, - .irq_retrigger = irq_chip_retrigger_hierarchy, - .irq_set_type = irq_chip_set_type_parent, - .irq_set_wake = stm32_exti_h_set_wake, - .flags = IRQCHIP_MASK_ON_SUSPEND, - .irq_set_affinity = IS_ENABLED(CONFIG_SMP) ? irq_chip_set_affinity_parent : NULL, -}; - -static int stm32_exti_h_domain_alloc(struct irq_domain *dm, - unsigned int virq, - unsigned int nr_irqs, void *data) -{ - struct stm32_exti_host_data *host_data = dm->host_data; - struct stm32_exti_chip_data *chip_data; - u8 desc_irq; - struct irq_fwspec *fwspec = data; - struct irq_fwspec p_fwspec; - irq_hw_number_t hwirq; - int bank; - u32 event_trg; - struct irq_chip *chip; - - hwirq = fwspec->param[0]; - if (hwirq >= host_data->drv_data->bank_nr * IRQS_PER_BANK) - return -EINVAL; - - bank = hwirq / IRQS_PER_BANK; - chip_data = &host_data->chips_data[bank]; - - /* Check if event is reserved (Secure) */ - if (chip_data->event_reserved & BIT(hwirq % IRQS_PER_BANK)) { - dev_err(host_data->dev, "event %lu is reserved, secure\n", hwirq); - return -EPERM; - } - - event_trg = readl_relaxed(host_data->base + chip_data->reg_bank->trg_ofst); - chip = (event_trg & BIT(hwirq % IRQS_PER_BANK)) ? - &stm32_exti_h_chip : &stm32_exti_h_chip_direct; - - irq_domain_set_hwirq_and_chip(dm, virq, hwirq, chip, chip_data); - - if (host_data->dt_has_irqs_desc) { - struct of_phandle_args out_irq; - int ret; - - ret = of_irq_parse_one(host_data->dev->of_node, hwirq, &out_irq); - if (ret) - return ret; - /* we only support one parent, so far */ - if (of_node_to_fwnode(out_irq.np) != dm->parent->fwnode) - return -EINVAL; - - of_phandle_args_to_fwspec(out_irq.np, out_irq.args, - out_irq.args_count, &p_fwspec); - - return irq_domain_alloc_irqs_parent(dm, virq, 1, &p_fwspec); - } - - if (!host_data->drv_data->desc_irqs) - return -EINVAL; - - desc_irq = host_data->drv_data->desc_irqs[hwirq]; - if (desc_irq != EXTI_INVALID_IRQ) { - p_fwspec.fwnode = dm->parent->fwnode; - p_fwspec.param_count = 3; - p_fwspec.param[0] = GIC_SPI; - p_fwspec.param[1] = desc_irq; - p_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH; - - return irq_domain_alloc_irqs_parent(dm, virq, 1, &p_fwspec); - } - - return 0; -} - static struct stm32_exti_host_data *stm32_exti_host_init(const struct stm32_exti_drv_data *dd, struct device_node *node) @@ -822,19 +319,12 @@ stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data, chip_data->host_data = h_data; chip_data->reg_bank = stm32_bank; - raw_spin_lock_init(&chip_data->rlock); - /* * This IP has no reset, so after hot reboot we should * clear registers to avoid residue */ writel_relaxed(0, base + stm32_bank->imr_ofst); - if (stm32_bank->emr_ofst != UNDEF_REG) - writel_relaxed(0, base + stm32_bank->emr_ofst); - - /* reserve Secure events */ - if (stm32_bank->seccfgr_ofst != UNDEF_REG) - chip_data->event_reserved = readl_relaxed(base + stm32_bank->seccfgr_ofst); + writel_relaxed(0, base + stm32_bank->emr_ofst); pr_info("%pOF: bank%d\n", node, bank_idx); @@ -914,158 +404,6 @@ out_unmap: return ret; } -static const struct irq_domain_ops stm32_exti_h_domain_ops = { - .alloc = stm32_exti_h_domain_alloc, - .free = irq_domain_free_irqs_common, - .xlate = irq_domain_xlate_twocell, -}; - -static void stm32_exti_check_rif(struct stm32_exti_host_data *host_data) -{ - unsigned int bank, i, event; - u32 cid, cidcfgr, hwcfgr1; - - /* quit on CID not supported */ - hwcfgr1 = readl_relaxed(host_data->base + EXTI_HWCFGR1); - if ((hwcfgr1 & EXTI_HWCFGR1_CIDWIDTH_MASK) == 0) - return; - - for (bank = 0; bank < host_data->drv_data->bank_nr; bank++) { - for (i = 0; i < IRQS_PER_BANK; i++) { - event = bank * IRQS_PER_BANK + i; - cidcfgr = readl_relaxed(host_data->base + EXTI_EnCIDCFGR(event)); - cid = (cidcfgr & EXTI_CIDCFGR_CID_MASK) >> EXTI_CIDCFGR_CID_SHIFT; - if ((cidcfgr & EXTI_CIDCFGR_CFEN_MASK) && cid != EXTI_CID1) - host_data->chips_data[bank].event_reserved |= BIT(i); - } - } -} - -static void stm32_exti_remove_irq(void *data) -{ - struct irq_domain *domain = data; - - irq_domain_remove(domain); -} - -static int stm32_exti_probe(struct platform_device *pdev) -{ - int ret, i; - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - struct irq_domain *parent_domain, *domain; - struct stm32_exti_host_data *host_data; - const struct stm32_exti_drv_data *drv_data; - - host_data = devm_kzalloc(dev, sizeof(*host_data), GFP_KERNEL); - if (!host_data) - return -ENOMEM; - - dev_set_drvdata(dev, host_data); - host_data->dev = dev; - - /* check for optional hwspinlock which may be not available yet */ - ret = of_hwspin_lock_get_id(np, 0); - if (ret == -EPROBE_DEFER) - /* hwspinlock framework not yet ready */ - return ret; - - if (ret >= 0) { - host_data->hwlock = devm_hwspin_lock_request_specific(dev, ret); - if (!host_data->hwlock) { - dev_err(dev, "Failed to request hwspinlock\n"); - return -EINVAL; - } - } else if (ret != -ENOENT) { - /* note: ENOENT is a valid case (means 'no hwspinlock') */ - dev_err(dev, "Failed to get hwspinlock\n"); - return ret; - } - - /* initialize host_data */ - drv_data = of_device_get_match_data(dev); - if (!drv_data) { - dev_err(dev, "no of match data\n"); - return -ENODEV; - } - host_data->drv_data = drv_data; - - host_data->chips_data = devm_kcalloc(dev, drv_data->bank_nr, - sizeof(*host_data->chips_data), - GFP_KERNEL); - if (!host_data->chips_data) - return -ENOMEM; - - host_data->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(host_data->base)) - return PTR_ERR(host_data->base); - - for (i = 0; i < drv_data->bank_nr; i++) - stm32_exti_chip_init(host_data, i, np); - - stm32_exti_check_rif(host_data); - - parent_domain = irq_find_host(of_irq_find_parent(np)); - if (!parent_domain) { - dev_err(dev, "GIC interrupt-parent not found\n"); - return -EINVAL; - } - - domain = irq_domain_add_hierarchy(parent_domain, 0, - drv_data->bank_nr * IRQS_PER_BANK, - np, &stm32_exti_h_domain_ops, - host_data); - - if (!domain) { - dev_err(dev, "Could not register exti domain\n"); - return -ENOMEM; - } - - ret = devm_add_action_or_reset(dev, stm32_exti_remove_irq, domain); - if (ret) - return ret; - - if (of_property_read_bool(np, "interrupts-extended")) - host_data->dt_has_irqs_desc = true; - - return 0; -} - -/* platform driver only for MP1 */ -static const struct of_device_id stm32_exti_ids[] = { - { .compatible = "st,stm32mp1-exti", .data = &stm32mp1_drv_data}, - { .compatible = "st,stm32mp13-exti", .data = &stm32mp13_drv_data}, - {}, -}; -MODULE_DEVICE_TABLE(of, stm32_exti_ids); - -static const struct dev_pm_ops stm32_exti_dev_pm_ops = { - NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32_exti_h_suspend, stm32_exti_h_resume) -}; - -static struct platform_driver stm32_exti_driver = { - .probe = stm32_exti_probe, - .driver = { - .name = "stm32_exti", - .of_match_table = stm32_exti_ids, - .pm = &stm32_exti_dev_pm_ops, - }, -}; - -static int __init stm32_exti_arch_init(void) -{ - return platform_driver_register(&stm32_exti_driver); -} - -static void __exit stm32_exti_arch_exit(void) -{ - return platform_driver_unregister(&stm32_exti_driver); -} - -arch_initcall(stm32_exti_arch_init); -module_exit(stm32_exti_arch_exit); - -/* no platform driver for F4 and H7 */ static int __init stm32f4_exti_of_init(struct device_node *np, struct device_node *parent) { diff --git a/drivers/irqchip/irq-stm32mp-exti.c b/drivers/irqchip/irq-stm32mp-exti.c new file mode 100644 index 000000000000..8a45ece2e198 --- /dev/null +++ b/drivers/irqchip/irq-stm32mp-exti.c @@ -0,0 +1,744 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Maxime Coquelin 2015 + * Copyright (C) STMicroelectronics 2017-2024 + * Author: Maxime Coquelin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define IRQS_PER_BANK 32 + +#define HWSPNLCK_TIMEOUT 1000 /* usec */ + +#define EXTI_EnCIDCFGR(n) (0x180 + (n) * 4) +#define EXTI_HWCFGR1 0x3f0 + +/* Register: EXTI_EnCIDCFGR(n) */ +#define EXTI_CIDCFGR_CFEN_MASK BIT(0) +#define EXTI_CIDCFGR_CID_MASK GENMASK(6, 4) +#define EXTI_CIDCFGR_CID_SHIFT 4 + +/* Register: EXTI_HWCFGR1 */ +#define EXTI_HWCFGR1_CIDWIDTH_MASK GENMASK(27, 24) + +#define EXTI_CID1 1 + +struct stm32_exti_bank { + u32 imr_ofst; + u32 rtsr_ofst; + u32 ftsr_ofst; + u32 swier_ofst; + u32 rpr_ofst; + u32 fpr_ofst; + u32 trg_ofst; + u32 seccfgr_ofst; +}; + +struct stm32_exti_drv_data { + const struct stm32_exti_bank **exti_banks; + const u8 *desc_irqs; + u32 bank_nr; +}; + +struct stm32_exti_chip_data { + struct stm32_exti_host_data *host_data; + const struct stm32_exti_bank *reg_bank; + struct raw_spinlock rlock; + u32 wake_active; + u32 mask_cache; + u32 rtsr_cache; + u32 ftsr_cache; + u32 event_reserved; +}; + +struct stm32_exti_host_data { + void __iomem *base; + struct device *dev; + struct stm32_exti_chip_data *chips_data; + const struct stm32_exti_drv_data *drv_data; + struct hwspinlock *hwlock; + bool dt_has_irqs_desc; /* skip internal desc_irqs array and get it from DT */ +}; + +static const struct stm32_exti_bank stm32mp1_exti_b1 = { + .imr_ofst = 0x80, + .rtsr_ofst = 0x00, + .ftsr_ofst = 0x04, + .swier_ofst = 0x08, + .rpr_ofst = 0x0C, + .fpr_ofst = 0x10, + .trg_ofst = 0x3EC, + .seccfgr_ofst = 0x14, +}; + +static const struct stm32_exti_bank stm32mp1_exti_b2 = { + .imr_ofst = 0x90, + .rtsr_ofst = 0x20, + .ftsr_ofst = 0x24, + .swier_ofst = 0x28, + .rpr_ofst = 0x2C, + .fpr_ofst = 0x30, + .trg_ofst = 0x3E8, + .seccfgr_ofst = 0x34, +}; + +static const struct stm32_exti_bank stm32mp1_exti_b3 = { + .imr_ofst = 0xA0, + .rtsr_ofst = 0x40, + .ftsr_ofst = 0x44, + .swier_ofst = 0x48, + .rpr_ofst = 0x4C, + .fpr_ofst = 0x50, + .trg_ofst = 0x3E4, + .seccfgr_ofst = 0x54, +}; + +static const struct stm32_exti_bank *stm32mp1_exti_banks[] = { + &stm32mp1_exti_b1, + &stm32mp1_exti_b2, + &stm32mp1_exti_b3, +}; + +static struct irq_chip stm32_exti_h_chip; +static struct irq_chip stm32_exti_h_chip_direct; + +#define EXTI_INVALID_IRQ U8_MAX +#define STM32MP1_DESC_IRQ_SIZE (ARRAY_SIZE(stm32mp1_exti_banks) * IRQS_PER_BANK) + +/* + * Use some intentionally tricky logic here to initialize the whole array to + * EXTI_INVALID_IRQ, but then override certain fields, requiring us to indicate + * that we "know" that there are overrides in this structure, and we'll need to + * disable that warning from W=1 builds. + */ +__diag_push(); +__diag_ignore_all("-Woverride-init", + "logic to initialize all and then override some is OK"); + +static const u8 stm32mp1_desc_irq[] = { + /* default value */ + [0 ... (STM32MP1_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ, + + [0] = 6, + [1] = 7, + [2] = 8, + [3] = 9, + [4] = 10, + [5] = 23, + [6] = 64, + [7] = 65, + [8] = 66, + [9] = 67, + [10] = 40, + [11] = 42, + [12] = 76, + [13] = 77, + [14] = 121, + [15] = 127, + [16] = 1, + [19] = 3, + [21] = 31, + [22] = 33, + [23] = 72, + [24] = 95, + [25] = 107, + [26] = 37, + [27] = 38, + [28] = 39, + [29] = 71, + [30] = 52, + [31] = 53, + [32] = 82, + [33] = 83, + [46] = 151, + [47] = 93, + [48] = 138, + [50] = 139, + [52] = 140, + [53] = 141, + [54] = 135, + [61] = 100, + [65] = 144, + [68] = 143, + [70] = 62, + [73] = 129, +}; + +static const u8 stm32mp13_desc_irq[] = { + /* default value */ + [0 ... (STM32MP1_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ, + + [0] = 6, + [1] = 7, + [2] = 8, + [3] = 9, + [4] = 10, + [5] = 24, + [6] = 65, + [7] = 66, + [8] = 67, + [9] = 68, + [10] = 41, + [11] = 43, + [12] = 77, + [13] = 78, + [14] = 106, + [15] = 109, + [16] = 1, + [19] = 3, + [21] = 32, + [22] = 34, + [23] = 73, + [24] = 93, + [25] = 114, + [26] = 38, + [27] = 39, + [28] = 40, + [29] = 72, + [30] = 53, + [31] = 54, + [32] = 83, + [33] = 84, + [44] = 96, + [47] = 92, + [48] = 116, + [50] = 117, + [52] = 118, + [53] = 119, + [68] = 63, + [70] = 98, +}; + +__diag_pop(); + +static const struct stm32_exti_drv_data stm32mp1_drv_data = { + .exti_banks = stm32mp1_exti_banks, + .bank_nr = ARRAY_SIZE(stm32mp1_exti_banks), + .desc_irqs = stm32mp1_desc_irq, +}; + +static const struct stm32_exti_drv_data stm32mp13_drv_data = { + .exti_banks = stm32mp1_exti_banks, + .bank_nr = ARRAY_SIZE(stm32mp1_exti_banks), + .desc_irqs = stm32mp13_desc_irq, +}; + +static int stm32_exti_set_type(struct irq_data *d, + unsigned int type, u32 *rtsr, u32 *ftsr) +{ + u32 mask = BIT(d->hwirq % IRQS_PER_BANK); + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + *rtsr |= mask; + *ftsr &= ~mask; + break; + case IRQ_TYPE_EDGE_FALLING: + *rtsr &= ~mask; + *ftsr |= mask; + break; + case IRQ_TYPE_EDGE_BOTH: + *rtsr |= mask; + *ftsr |= mask; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void stm32_chip_suspend(struct stm32_exti_chip_data *chip_data, + u32 wake_active) +{ + const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + void __iomem *base = chip_data->host_data->base; + + /* save rtsr, ftsr registers */ + chip_data->rtsr_cache = readl_relaxed(base + stm32_bank->rtsr_ofst); + chip_data->ftsr_cache = readl_relaxed(base + stm32_bank->ftsr_ofst); + + writel_relaxed(wake_active, base + stm32_bank->imr_ofst); +} + +static void stm32_chip_resume(struct stm32_exti_chip_data *chip_data, + u32 mask_cache) +{ + const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + void __iomem *base = chip_data->host_data->base; + + /* restore rtsr, ftsr, registers */ + writel_relaxed(chip_data->rtsr_cache, base + stm32_bank->rtsr_ofst); + writel_relaxed(chip_data->ftsr_cache, base + stm32_bank->ftsr_ofst); + + writel_relaxed(mask_cache, base + stm32_bank->imr_ofst); +} + +/* directly set the target bit without reading first. */ +static inline void stm32_exti_write_bit(struct irq_data *d, u32 reg) +{ + struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + void __iomem *base = chip_data->host_data->base; + u32 val = BIT(d->hwirq % IRQS_PER_BANK); + + writel_relaxed(val, base + reg); +} + +static inline u32 stm32_exti_set_bit(struct irq_data *d, u32 reg) +{ + struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + void __iomem *base = chip_data->host_data->base; + u32 val; + + val = readl_relaxed(base + reg); + val |= BIT(d->hwirq % IRQS_PER_BANK); + writel_relaxed(val, base + reg); + + return val; +} + +static inline u32 stm32_exti_clr_bit(struct irq_data *d, u32 reg) +{ + struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + void __iomem *base = chip_data->host_data->base; + u32 val; + + val = readl_relaxed(base + reg); + val &= ~BIT(d->hwirq % IRQS_PER_BANK); + writel_relaxed(val, base + reg); + + return val; +} + +static void stm32_exti_h_eoi(struct irq_data *d) +{ + struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + + raw_spin_lock(&chip_data->rlock); + + stm32_exti_write_bit(d, stm32_bank->rpr_ofst); + stm32_exti_write_bit(d, stm32_bank->fpr_ofst); + + raw_spin_unlock(&chip_data->rlock); + + if (d->parent_data->chip) + irq_chip_eoi_parent(d); +} + +static void stm32_exti_h_mask(struct irq_data *d) +{ + struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + + raw_spin_lock(&chip_data->rlock); + chip_data->mask_cache = stm32_exti_clr_bit(d, stm32_bank->imr_ofst); + raw_spin_unlock(&chip_data->rlock); + + if (d->parent_data->chip) + irq_chip_mask_parent(d); +} + +static void stm32_exti_h_unmask(struct irq_data *d) +{ + struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + + raw_spin_lock(&chip_data->rlock); + chip_data->mask_cache = stm32_exti_set_bit(d, stm32_bank->imr_ofst); + raw_spin_unlock(&chip_data->rlock); + + if (d->parent_data->chip) + irq_chip_unmask_parent(d); +} + +static int stm32_exti_h_set_type(struct irq_data *d, unsigned int type) +{ + struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + struct hwspinlock *hwlock = chip_data->host_data->hwlock; + void __iomem *base = chip_data->host_data->base; + u32 rtsr, ftsr; + int err; + + raw_spin_lock(&chip_data->rlock); + + if (hwlock) { + err = hwspin_lock_timeout_in_atomic(hwlock, HWSPNLCK_TIMEOUT); + if (err) { + pr_err("%s can't get hwspinlock (%d)\n", __func__, err); + goto unlock; + } + } + + rtsr = readl_relaxed(base + stm32_bank->rtsr_ofst); + ftsr = readl_relaxed(base + stm32_bank->ftsr_ofst); + + err = stm32_exti_set_type(d, type, &rtsr, &ftsr); + if (err) + goto unspinlock; + + writel_relaxed(rtsr, base + stm32_bank->rtsr_ofst); + writel_relaxed(ftsr, base + stm32_bank->ftsr_ofst); + +unspinlock: + if (hwlock) + hwspin_unlock_in_atomic(hwlock); +unlock: + raw_spin_unlock(&chip_data->rlock); + + return err; +} + +static int stm32_exti_h_set_wake(struct irq_data *d, unsigned int on) +{ + struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + u32 mask = BIT(d->hwirq % IRQS_PER_BANK); + + raw_spin_lock(&chip_data->rlock); + + if (on) + chip_data->wake_active |= mask; + else + chip_data->wake_active &= ~mask; + + raw_spin_unlock(&chip_data->rlock); + + return 0; +} + +static int stm32_exti_h_set_affinity(struct irq_data *d, + const struct cpumask *dest, bool force) +{ + if (d->parent_data->chip) + return irq_chip_set_affinity_parent(d, dest, force); + + return IRQ_SET_MASK_OK_DONE; +} + +static int stm32_exti_h_suspend(struct device *dev) +{ + struct stm32_exti_host_data *host_data = dev_get_drvdata(dev); + struct stm32_exti_chip_data *chip_data; + int i; + + for (i = 0; i < host_data->drv_data->bank_nr; i++) { + chip_data = &host_data->chips_data[i]; + stm32_chip_suspend(chip_data, chip_data->wake_active); + } + + return 0; +} + +static int stm32_exti_h_resume(struct device *dev) +{ + struct stm32_exti_host_data *host_data = dev_get_drvdata(dev); + struct stm32_exti_chip_data *chip_data; + int i; + + for (i = 0; i < host_data->drv_data->bank_nr; i++) { + chip_data = &host_data->chips_data[i]; + stm32_chip_resume(chip_data, chip_data->mask_cache); + } + + return 0; +} + +static int stm32_exti_h_retrigger(struct irq_data *d) +{ + struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + void __iomem *base = chip_data->host_data->base; + u32 mask = BIT(d->hwirq % IRQS_PER_BANK); + + writel_relaxed(mask, base + stm32_bank->swier_ofst); + + return 0; +} + +static struct irq_chip stm32_exti_h_chip = { + .name = "stm32-exti-h", + .irq_eoi = stm32_exti_h_eoi, + .irq_mask = stm32_exti_h_mask, + .irq_unmask = stm32_exti_h_unmask, + .irq_retrigger = stm32_exti_h_retrigger, + .irq_set_type = stm32_exti_h_set_type, + .irq_set_wake = stm32_exti_h_set_wake, + .flags = IRQCHIP_MASK_ON_SUSPEND, + .irq_set_affinity = IS_ENABLED(CONFIG_SMP) ? stm32_exti_h_set_affinity : NULL, +}; + +static struct irq_chip stm32_exti_h_chip_direct = { + .name = "stm32-exti-h-direct", + .irq_eoi = irq_chip_eoi_parent, + .irq_ack = irq_chip_ack_parent, + .irq_mask = stm32_exti_h_mask, + .irq_unmask = stm32_exti_h_unmask, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_type = irq_chip_set_type_parent, + .irq_set_wake = stm32_exti_h_set_wake, + .flags = IRQCHIP_MASK_ON_SUSPEND, + .irq_set_affinity = IS_ENABLED(CONFIG_SMP) ? irq_chip_set_affinity_parent : NULL, +}; + +static int stm32_exti_h_domain_alloc(struct irq_domain *dm, + unsigned int virq, + unsigned int nr_irqs, void *data) +{ + struct stm32_exti_host_data *host_data = dm->host_data; + struct stm32_exti_chip_data *chip_data; + u8 desc_irq; + struct irq_fwspec *fwspec = data; + struct irq_fwspec p_fwspec; + irq_hw_number_t hwirq; + int bank; + u32 event_trg; + struct irq_chip *chip; + + hwirq = fwspec->param[0]; + if (hwirq >= host_data->drv_data->bank_nr * IRQS_PER_BANK) + return -EINVAL; + + bank = hwirq / IRQS_PER_BANK; + chip_data = &host_data->chips_data[bank]; + + /* Check if event is reserved (Secure) */ + if (chip_data->event_reserved & BIT(hwirq % IRQS_PER_BANK)) { + dev_err(host_data->dev, "event %lu is reserved, secure\n", hwirq); + return -EPERM; + } + + event_trg = readl_relaxed(host_data->base + chip_data->reg_bank->trg_ofst); + chip = (event_trg & BIT(hwirq % IRQS_PER_BANK)) ? + &stm32_exti_h_chip : &stm32_exti_h_chip_direct; + + irq_domain_set_hwirq_and_chip(dm, virq, hwirq, chip, chip_data); + + if (host_data->dt_has_irqs_desc) { + struct of_phandle_args out_irq; + int ret; + + ret = of_irq_parse_one(host_data->dev->of_node, hwirq, &out_irq); + if (ret) + return ret; + /* we only support one parent, so far */ + if (of_node_to_fwnode(out_irq.np) != dm->parent->fwnode) + return -EINVAL; + + of_phandle_args_to_fwspec(out_irq.np, out_irq.args, + out_irq.args_count, &p_fwspec); + + return irq_domain_alloc_irqs_parent(dm, virq, 1, &p_fwspec); + } + + if (!host_data->drv_data->desc_irqs) + return -EINVAL; + + desc_irq = host_data->drv_data->desc_irqs[hwirq]; + if (desc_irq != EXTI_INVALID_IRQ) { + p_fwspec.fwnode = dm->parent->fwnode; + p_fwspec.param_count = 3; + p_fwspec.param[0] = GIC_SPI; + p_fwspec.param[1] = desc_irq; + p_fwspec.param[2] = IRQ_TYPE_LEVEL_HIGH; + + return irq_domain_alloc_irqs_parent(dm, virq, 1, &p_fwspec); + } + + return 0; +} + +static struct +stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data, + u32 bank_idx, + struct device_node *node) +{ + const struct stm32_exti_bank *stm32_bank; + struct stm32_exti_chip_data *chip_data; + void __iomem *base = h_data->base; + + stm32_bank = h_data->drv_data->exti_banks[bank_idx]; + chip_data = &h_data->chips_data[bank_idx]; + chip_data->host_data = h_data; + chip_data->reg_bank = stm32_bank; + + raw_spin_lock_init(&chip_data->rlock); + + /* + * This IP has no reset, so after hot reboot we should + * clear registers to avoid residue + */ + writel_relaxed(0, base + stm32_bank->imr_ofst); + + /* reserve Secure events */ + chip_data->event_reserved = readl_relaxed(base + stm32_bank->seccfgr_ofst); + + pr_info("%pOF: bank%d\n", node, bank_idx); + + return chip_data; +} + +static const struct irq_domain_ops stm32_exti_h_domain_ops = { + .alloc = stm32_exti_h_domain_alloc, + .free = irq_domain_free_irqs_common, + .xlate = irq_domain_xlate_twocell, +}; + +static void stm32_exti_check_rif(struct stm32_exti_host_data *host_data) +{ + unsigned int bank, i, event; + u32 cid, cidcfgr, hwcfgr1; + + /* quit on CID not supported */ + hwcfgr1 = readl_relaxed(host_data->base + EXTI_HWCFGR1); + if ((hwcfgr1 & EXTI_HWCFGR1_CIDWIDTH_MASK) == 0) + return; + + for (bank = 0; bank < host_data->drv_data->bank_nr; bank++) { + for (i = 0; i < IRQS_PER_BANK; i++) { + event = bank * IRQS_PER_BANK + i; + cidcfgr = readl_relaxed(host_data->base + EXTI_EnCIDCFGR(event)); + cid = (cidcfgr & EXTI_CIDCFGR_CID_MASK) >> EXTI_CIDCFGR_CID_SHIFT; + if ((cidcfgr & EXTI_CIDCFGR_CFEN_MASK) && cid != EXTI_CID1) + host_data->chips_data[bank].event_reserved |= BIT(i); + } + } +} + +static void stm32_exti_remove_irq(void *data) +{ + struct irq_domain *domain = data; + + irq_domain_remove(domain); +} + +static int stm32_exti_probe(struct platform_device *pdev) +{ + int ret, i; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct irq_domain *parent_domain, *domain; + struct stm32_exti_host_data *host_data; + const struct stm32_exti_drv_data *drv_data; + + host_data = devm_kzalloc(dev, sizeof(*host_data), GFP_KERNEL); + if (!host_data) + return -ENOMEM; + + dev_set_drvdata(dev, host_data); + host_data->dev = dev; + + /* check for optional hwspinlock which may be not available yet */ + ret = of_hwspin_lock_get_id(np, 0); + if (ret == -EPROBE_DEFER) + /* hwspinlock framework not yet ready */ + return ret; + + if (ret >= 0) { + host_data->hwlock = devm_hwspin_lock_request_specific(dev, ret); + if (!host_data->hwlock) { + dev_err(dev, "Failed to request hwspinlock\n"); + return -EINVAL; + } + } else if (ret != -ENOENT) { + /* note: ENOENT is a valid case (means 'no hwspinlock') */ + dev_err(dev, "Failed to get hwspinlock\n"); + return ret; + } + + /* initialize host_data */ + drv_data = of_device_get_match_data(dev); + if (!drv_data) { + dev_err(dev, "no of match data\n"); + return -ENODEV; + } + host_data->drv_data = drv_data; + + host_data->chips_data = devm_kcalloc(dev, drv_data->bank_nr, + sizeof(*host_data->chips_data), + GFP_KERNEL); + if (!host_data->chips_data) + return -ENOMEM; + + host_data->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(host_data->base)) + return PTR_ERR(host_data->base); + + for (i = 0; i < drv_data->bank_nr; i++) + stm32_exti_chip_init(host_data, i, np); + + stm32_exti_check_rif(host_data); + + parent_domain = irq_find_host(of_irq_find_parent(np)); + if (!parent_domain) { + dev_err(dev, "GIC interrupt-parent not found\n"); + return -EINVAL; + } + + domain = irq_domain_add_hierarchy(parent_domain, 0, + drv_data->bank_nr * IRQS_PER_BANK, + np, &stm32_exti_h_domain_ops, + host_data); + + if (!domain) { + dev_err(dev, "Could not register exti domain\n"); + return -ENOMEM; + } + + ret = devm_add_action_or_reset(dev, stm32_exti_remove_irq, domain); + if (ret) + return ret; + + if (of_property_read_bool(np, "interrupts-extended")) + host_data->dt_has_irqs_desc = true; + + return 0; +} + +static const struct of_device_id stm32_exti_ids[] = { + { .compatible = "st,stm32mp1-exti", .data = &stm32mp1_drv_data}, + { .compatible = "st,stm32mp13-exti", .data = &stm32mp13_drv_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_exti_ids); + +static const struct dev_pm_ops stm32_exti_dev_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32_exti_h_suspend, stm32_exti_h_resume) +}; + +static struct platform_driver stm32_exti_driver = { + .probe = stm32_exti_probe, + .driver = { + .name = "stm32_exti", + .of_match_table = stm32_exti_ids, + .pm = &stm32_exti_dev_pm_ops, + }, +}; + +static int __init stm32_exti_arch_init(void) +{ + return platform_driver_register(&stm32_exti_driver); +} + +static void __exit stm32_exti_arch_exit(void) +{ + return platform_driver_unregister(&stm32_exti_driver); +} + +arch_initcall(stm32_exti_arch_init); +module_exit(stm32_exti_arch_exit); From c9d269469d2b9a06559cdc84d12dd3fb4d552581 Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Thu, 20 Jun 2024 10:31:12 +0200 Subject: [PATCH 058/152] irqchip/stm32mp-exti: Rename internal symbols Rename all the internal symbols accordingly to the new name of the driver. Renaming done automatically through sed rules: s/stm32_exti_set_type/stm32mp_exti_convert_type/g s/stm32_exti_h_/stm32mp_exti_/g s/stm32_exti/stm32mp_exti/g s/stm32_bank/bank/g s/stm32_/stm32mp_/g s/STM32_/STM32MP_/g s/STM32MP1_/STM32MP_/g s/stm32mp1_exti_/stm32mp_exti_/g s/stm32-exti-h/stm32mp-exti/g Manually fix some indentation after the rename. [ tglx: Mop up more coding style issues while at it ] Signed-off-by: Antonio Borneo Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240620083115.204362-6-antonio.borneo@foss.st.com --- drivers/irqchip/irq-stm32mp-exti.c | 312 ++++++++++++++--------------- 1 file changed, 152 insertions(+), 160 deletions(-) diff --git a/drivers/irqchip/irq-stm32mp-exti.c b/drivers/irqchip/irq-stm32mp-exti.c index 8a45ece2e198..727859e073ea 100644 --- a/drivers/irqchip/irq-stm32mp-exti.c +++ b/drivers/irqchip/irq-stm32mp-exti.c @@ -38,7 +38,7 @@ #define EXTI_CID1 1 -struct stm32_exti_bank { +struct stm32mp_exti_bank { u32 imr_ofst; u32 rtsr_ofst; u32 ftsr_ofst; @@ -49,33 +49,34 @@ struct stm32_exti_bank { u32 seccfgr_ofst; }; -struct stm32_exti_drv_data { - const struct stm32_exti_bank **exti_banks; - const u8 *desc_irqs; - u32 bank_nr; +struct stm32mp_exti_drv_data { + const struct stm32mp_exti_bank **exti_banks; + const u8 *desc_irqs; + u32 bank_nr; }; -struct stm32_exti_chip_data { - struct stm32_exti_host_data *host_data; - const struct stm32_exti_bank *reg_bank; - struct raw_spinlock rlock; - u32 wake_active; - u32 mask_cache; - u32 rtsr_cache; - u32 ftsr_cache; - u32 event_reserved; +struct stm32mp_exti_chip_data { + struct stm32mp_exti_host_data *host_data; + const struct stm32mp_exti_bank *reg_bank; + struct raw_spinlock rlock; + u32 wake_active; + u32 mask_cache; + u32 rtsr_cache; + u32 ftsr_cache; + u32 event_reserved; }; -struct stm32_exti_host_data { - void __iomem *base; - struct device *dev; - struct stm32_exti_chip_data *chips_data; - const struct stm32_exti_drv_data *drv_data; - struct hwspinlock *hwlock; - bool dt_has_irqs_desc; /* skip internal desc_irqs array and get it from DT */ +struct stm32mp_exti_host_data { + void __iomem *base; + struct device *dev; + struct stm32mp_exti_chip_data *chips_data; + const struct stm32mp_exti_drv_data *drv_data; + struct hwspinlock *hwlock; + /* skip internal desc_irqs array and get it from DT */ + bool dt_has_irqs_desc; }; -static const struct stm32_exti_bank stm32mp1_exti_b1 = { +static const struct stm32mp_exti_bank stm32mp_exti_b1 = { .imr_ofst = 0x80, .rtsr_ofst = 0x00, .ftsr_ofst = 0x04, @@ -86,7 +87,7 @@ static const struct stm32_exti_bank stm32mp1_exti_b1 = { .seccfgr_ofst = 0x14, }; -static const struct stm32_exti_bank stm32mp1_exti_b2 = { +static const struct stm32mp_exti_bank stm32mp_exti_b2 = { .imr_ofst = 0x90, .rtsr_ofst = 0x20, .ftsr_ofst = 0x24, @@ -97,7 +98,7 @@ static const struct stm32_exti_bank stm32mp1_exti_b2 = { .seccfgr_ofst = 0x34, }; -static const struct stm32_exti_bank stm32mp1_exti_b3 = { +static const struct stm32mp_exti_bank stm32mp_exti_b3 = { .imr_ofst = 0xA0, .rtsr_ofst = 0x40, .ftsr_ofst = 0x44, @@ -108,17 +109,17 @@ static const struct stm32_exti_bank stm32mp1_exti_b3 = { .seccfgr_ofst = 0x54, }; -static const struct stm32_exti_bank *stm32mp1_exti_banks[] = { - &stm32mp1_exti_b1, - &stm32mp1_exti_b2, - &stm32mp1_exti_b3, +static const struct stm32mp_exti_bank *stm32mp_exti_banks[] = { + &stm32mp_exti_b1, + &stm32mp_exti_b2, + &stm32mp_exti_b3, }; -static struct irq_chip stm32_exti_h_chip; -static struct irq_chip stm32_exti_h_chip_direct; +static struct irq_chip stm32mp_exti_chip; +static struct irq_chip stm32mp_exti_chip_direct; #define EXTI_INVALID_IRQ U8_MAX -#define STM32MP1_DESC_IRQ_SIZE (ARRAY_SIZE(stm32mp1_exti_banks) * IRQS_PER_BANK) +#define STM32MP_DESC_IRQ_SIZE (ARRAY_SIZE(stm32mp_exti_banks) * IRQS_PER_BANK) /* * Use some intentionally tricky logic here to initialize the whole array to @@ -132,7 +133,7 @@ __diag_ignore_all("-Woverride-init", static const u8 stm32mp1_desc_irq[] = { /* default value */ - [0 ... (STM32MP1_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ, + [0 ... (STM32MP_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ, [0] = 6, [1] = 7, @@ -181,7 +182,7 @@ static const u8 stm32mp1_desc_irq[] = { static const u8 stm32mp13_desc_irq[] = { /* default value */ - [0 ... (STM32MP1_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ, + [0 ... (STM32MP_DESC_IRQ_SIZE - 1)] = EXTI_INVALID_IRQ, [0] = 6, [1] = 7, @@ -226,20 +227,19 @@ static const u8 stm32mp13_desc_irq[] = { __diag_pop(); -static const struct stm32_exti_drv_data stm32mp1_drv_data = { - .exti_banks = stm32mp1_exti_banks, - .bank_nr = ARRAY_SIZE(stm32mp1_exti_banks), +static const struct stm32mp_exti_drv_data stm32mp1_drv_data = { + .exti_banks = stm32mp_exti_banks, + .bank_nr = ARRAY_SIZE(stm32mp_exti_banks), .desc_irqs = stm32mp1_desc_irq, }; -static const struct stm32_exti_drv_data stm32mp13_drv_data = { - .exti_banks = stm32mp1_exti_banks, - .bank_nr = ARRAY_SIZE(stm32mp1_exti_banks), +static const struct stm32mp_exti_drv_data stm32mp13_drv_data = { + .exti_banks = stm32mp_exti_banks, + .bank_nr = ARRAY_SIZE(stm32mp_exti_banks), .desc_irqs = stm32mp13_desc_irq, }; -static int stm32_exti_set_type(struct irq_data *d, - unsigned int type, u32 *rtsr, u32 *ftsr) +static int stm32mp_exti_convert_type(struct irq_data *d, unsigned int type, u32 *rtsr, u32 *ftsr) { u32 mask = BIT(d->hwirq % IRQS_PER_BANK); @@ -263,45 +263,43 @@ static int stm32_exti_set_type(struct irq_data *d, return 0; } -static void stm32_chip_suspend(struct stm32_exti_chip_data *chip_data, - u32 wake_active) +static void stm32mp_chip_suspend(struct stm32mp_exti_chip_data *chip_data, u32 wake_active) { - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + const struct stm32mp_exti_bank *bank = chip_data->reg_bank; void __iomem *base = chip_data->host_data->base; /* save rtsr, ftsr registers */ - chip_data->rtsr_cache = readl_relaxed(base + stm32_bank->rtsr_ofst); - chip_data->ftsr_cache = readl_relaxed(base + stm32_bank->ftsr_ofst); + chip_data->rtsr_cache = readl_relaxed(base + bank->rtsr_ofst); + chip_data->ftsr_cache = readl_relaxed(base + bank->ftsr_ofst); - writel_relaxed(wake_active, base + stm32_bank->imr_ofst); + writel_relaxed(wake_active, base + bank->imr_ofst); } -static void stm32_chip_resume(struct stm32_exti_chip_data *chip_data, - u32 mask_cache) +static void stm32mp_chip_resume(struct stm32mp_exti_chip_data *chip_data, u32 mask_cache) { - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + const struct stm32mp_exti_bank *bank = chip_data->reg_bank; void __iomem *base = chip_data->host_data->base; /* restore rtsr, ftsr, registers */ - writel_relaxed(chip_data->rtsr_cache, base + stm32_bank->rtsr_ofst); - writel_relaxed(chip_data->ftsr_cache, base + stm32_bank->ftsr_ofst); + writel_relaxed(chip_data->rtsr_cache, base + bank->rtsr_ofst); + writel_relaxed(chip_data->ftsr_cache, base + bank->ftsr_ofst); - writel_relaxed(mask_cache, base + stm32_bank->imr_ofst); + writel_relaxed(mask_cache, base + bank->imr_ofst); } /* directly set the target bit without reading first. */ -static inline void stm32_exti_write_bit(struct irq_data *d, u32 reg) +static inline void stm32mp_exti_write_bit(struct irq_data *d, u32 reg) { - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); void __iomem *base = chip_data->host_data->base; u32 val = BIT(d->hwirq % IRQS_PER_BANK); writel_relaxed(val, base + reg); } -static inline u32 stm32_exti_set_bit(struct irq_data *d, u32 reg) +static inline u32 stm32mp_exti_set_bit(struct irq_data *d, u32 reg) { - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); void __iomem *base = chip_data->host_data->base; u32 val; @@ -312,9 +310,9 @@ static inline u32 stm32_exti_set_bit(struct irq_data *d, u32 reg) return val; } -static inline u32 stm32_exti_clr_bit(struct irq_data *d, u32 reg) +static inline u32 stm32mp_exti_clr_bit(struct irq_data *d, u32 reg) { - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); void __iomem *base = chip_data->host_data->base; u32 val; @@ -325,15 +323,15 @@ static inline u32 stm32_exti_clr_bit(struct irq_data *d, u32 reg) return val; } -static void stm32_exti_h_eoi(struct irq_data *d) +static void stm32mp_exti_eoi(struct irq_data *d) { - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + const struct stm32mp_exti_bank *bank = chip_data->reg_bank; raw_spin_lock(&chip_data->rlock); - stm32_exti_write_bit(d, stm32_bank->rpr_ofst); - stm32_exti_write_bit(d, stm32_bank->fpr_ofst); + stm32mp_exti_write_bit(d, bank->rpr_ofst); + stm32mp_exti_write_bit(d, bank->fpr_ofst); raw_spin_unlock(&chip_data->rlock); @@ -341,36 +339,36 @@ static void stm32_exti_h_eoi(struct irq_data *d) irq_chip_eoi_parent(d); } -static void stm32_exti_h_mask(struct irq_data *d) +static void stm32mp_exti_mask(struct irq_data *d) { - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + const struct stm32mp_exti_bank *bank = chip_data->reg_bank; raw_spin_lock(&chip_data->rlock); - chip_data->mask_cache = stm32_exti_clr_bit(d, stm32_bank->imr_ofst); + chip_data->mask_cache = stm32mp_exti_clr_bit(d, bank->imr_ofst); raw_spin_unlock(&chip_data->rlock); if (d->parent_data->chip) irq_chip_mask_parent(d); } -static void stm32_exti_h_unmask(struct irq_data *d) +static void stm32mp_exti_unmask(struct irq_data *d) { - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + const struct stm32mp_exti_bank *bank = chip_data->reg_bank; raw_spin_lock(&chip_data->rlock); - chip_data->mask_cache = stm32_exti_set_bit(d, stm32_bank->imr_ofst); + chip_data->mask_cache = stm32mp_exti_set_bit(d, bank->imr_ofst); raw_spin_unlock(&chip_data->rlock); if (d->parent_data->chip) irq_chip_unmask_parent(d); } -static int stm32_exti_h_set_type(struct irq_data *d, unsigned int type) +static int stm32mp_exti_set_type(struct irq_data *d, unsigned int type) { - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + const struct stm32mp_exti_bank *bank = chip_data->reg_bank; struct hwspinlock *hwlock = chip_data->host_data->hwlock; void __iomem *base = chip_data->host_data->base; u32 rtsr, ftsr; @@ -386,28 +384,25 @@ static int stm32_exti_h_set_type(struct irq_data *d, unsigned int type) } } - rtsr = readl_relaxed(base + stm32_bank->rtsr_ofst); - ftsr = readl_relaxed(base + stm32_bank->ftsr_ofst); + rtsr = readl_relaxed(base + bank->rtsr_ofst); + ftsr = readl_relaxed(base + bank->ftsr_ofst); - err = stm32_exti_set_type(d, type, &rtsr, &ftsr); - if (err) - goto unspinlock; + err = stm32mp_exti_convert_type(d, type, &rtsr, &ftsr); + if (!err) { + writel_relaxed(rtsr, base + bank->rtsr_ofst); + writel_relaxed(ftsr, base + bank->ftsr_ofst); + } - writel_relaxed(rtsr, base + stm32_bank->rtsr_ofst); - writel_relaxed(ftsr, base + stm32_bank->ftsr_ofst); - -unspinlock: if (hwlock) hwspin_unlock_in_atomic(hwlock); unlock: raw_spin_unlock(&chip_data->rlock); - return err; } -static int stm32_exti_h_set_wake(struct irq_data *d, unsigned int on) +static int stm32mp_exti_set_wake(struct irq_data *d, unsigned int on) { - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); u32 mask = BIT(d->hwirq % IRQS_PER_BANK); raw_spin_lock(&chip_data->rlock); @@ -422,8 +417,7 @@ static int stm32_exti_h_set_wake(struct irq_data *d, unsigned int on) return 0; } -static int stm32_exti_h_set_affinity(struct irq_data *d, - const struct cpumask *dest, bool force) +static int stm32mp_exti_set_affinity(struct irq_data *d, const struct cpumask *dest, bool force) { if (d->parent_data->chip) return irq_chip_set_affinity_parent(d, dest, force); @@ -431,84 +425,84 @@ static int stm32_exti_h_set_affinity(struct irq_data *d, return IRQ_SET_MASK_OK_DONE; } -static int stm32_exti_h_suspend(struct device *dev) +static int stm32mp_exti_suspend(struct device *dev) { - struct stm32_exti_host_data *host_data = dev_get_drvdata(dev); - struct stm32_exti_chip_data *chip_data; + struct stm32mp_exti_host_data *host_data = dev_get_drvdata(dev); + struct stm32mp_exti_chip_data *chip_data; int i; for (i = 0; i < host_data->drv_data->bank_nr; i++) { chip_data = &host_data->chips_data[i]; - stm32_chip_suspend(chip_data, chip_data->wake_active); + stm32mp_chip_suspend(chip_data, chip_data->wake_active); } return 0; } -static int stm32_exti_h_resume(struct device *dev) +static int stm32mp_exti_resume(struct device *dev) { - struct stm32_exti_host_data *host_data = dev_get_drvdata(dev); - struct stm32_exti_chip_data *chip_data; + struct stm32mp_exti_host_data *host_data = dev_get_drvdata(dev); + struct stm32mp_exti_chip_data *chip_data; int i; for (i = 0; i < host_data->drv_data->bank_nr; i++) { chip_data = &host_data->chips_data[i]; - stm32_chip_resume(chip_data, chip_data->mask_cache); + stm32mp_chip_resume(chip_data, chip_data->mask_cache); } return 0; } -static int stm32_exti_h_retrigger(struct irq_data *d) +static int stm32mp_exti_retrigger(struct irq_data *d) { - struct stm32_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); - const struct stm32_exti_bank *stm32_bank = chip_data->reg_bank; + struct stm32mp_exti_chip_data *chip_data = irq_data_get_irq_chip_data(d); + const struct stm32mp_exti_bank *bank = chip_data->reg_bank; void __iomem *base = chip_data->host_data->base; u32 mask = BIT(d->hwirq % IRQS_PER_BANK); - writel_relaxed(mask, base + stm32_bank->swier_ofst); + writel_relaxed(mask, base + bank->swier_ofst); return 0; } -static struct irq_chip stm32_exti_h_chip = { - .name = "stm32-exti-h", - .irq_eoi = stm32_exti_h_eoi, - .irq_mask = stm32_exti_h_mask, - .irq_unmask = stm32_exti_h_unmask, - .irq_retrigger = stm32_exti_h_retrigger, - .irq_set_type = stm32_exti_h_set_type, - .irq_set_wake = stm32_exti_h_set_wake, +static struct irq_chip stm32mp_exti_chip = { + .name = "stm32mp-exti", + .irq_eoi = stm32mp_exti_eoi, + .irq_mask = stm32mp_exti_mask, + .irq_unmask = stm32mp_exti_unmask, + .irq_retrigger = stm32mp_exti_retrigger, + .irq_set_type = stm32mp_exti_set_type, + .irq_set_wake = stm32mp_exti_set_wake, .flags = IRQCHIP_MASK_ON_SUSPEND, - .irq_set_affinity = IS_ENABLED(CONFIG_SMP) ? stm32_exti_h_set_affinity : NULL, + .irq_set_affinity = IS_ENABLED(CONFIG_SMP) ? stm32mp_exti_set_affinity : NULL, }; -static struct irq_chip stm32_exti_h_chip_direct = { - .name = "stm32-exti-h-direct", +static struct irq_chip stm32mp_exti_chip_direct = { + .name = "stm32mp-exti-direct", .irq_eoi = irq_chip_eoi_parent, .irq_ack = irq_chip_ack_parent, - .irq_mask = stm32_exti_h_mask, - .irq_unmask = stm32_exti_h_unmask, + .irq_mask = stm32mp_exti_mask, + .irq_unmask = stm32mp_exti_unmask, .irq_retrigger = irq_chip_retrigger_hierarchy, .irq_set_type = irq_chip_set_type_parent, - .irq_set_wake = stm32_exti_h_set_wake, + .irq_set_wake = stm32mp_exti_set_wake, .flags = IRQCHIP_MASK_ON_SUSPEND, .irq_set_affinity = IS_ENABLED(CONFIG_SMP) ? irq_chip_set_affinity_parent : NULL, }; -static int stm32_exti_h_domain_alloc(struct irq_domain *dm, +static int stm32mp_exti_domain_alloc(struct irq_domain *dm, unsigned int virq, unsigned int nr_irqs, void *data) { - struct stm32_exti_host_data *host_data = dm->host_data; - struct stm32_exti_chip_data *chip_data; - u8 desc_irq; + struct stm32mp_exti_host_data *host_data = dm->host_data; + struct stm32mp_exti_chip_data *chip_data; struct irq_fwspec *fwspec = data; struct irq_fwspec p_fwspec; irq_hw_number_t hwirq; - int bank; - u32 event_trg; struct irq_chip *chip; + u32 event_trg; + u8 desc_irq; + int bank; hwirq = fwspec->param[0]; if (hwirq >= host_data->drv_data->bank_nr * IRQS_PER_BANK) @@ -525,7 +519,7 @@ static int stm32_exti_h_domain_alloc(struct irq_domain *dm, event_trg = readl_relaxed(host_data->base + chip_data->reg_bank->trg_ofst); chip = (event_trg & BIT(hwirq % IRQS_PER_BANK)) ? - &stm32_exti_h_chip : &stm32_exti_h_chip_direct; + &stm32mp_exti_chip : &stm32mp_exti_chip_direct; irq_domain_set_hwirq_and_chip(dm, virq, hwirq, chip, chip_data); @@ -563,19 +557,17 @@ static int stm32_exti_h_domain_alloc(struct irq_domain *dm, return 0; } -static struct -stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data, - u32 bank_idx, - struct device_node *node) +static struct stm32mp_exti_chip_data *stm32mp_exti_chip_init(struct stm32mp_exti_host_data *h_data, + u32 bank_idx, struct device_node *node) { - const struct stm32_exti_bank *stm32_bank; - struct stm32_exti_chip_data *chip_data; + struct stm32mp_exti_chip_data *chip_data; + const struct stm32mp_exti_bank *bank; void __iomem *base = h_data->base; - stm32_bank = h_data->drv_data->exti_banks[bank_idx]; + bank = h_data->drv_data->exti_banks[bank_idx]; chip_data = &h_data->chips_data[bank_idx]; chip_data->host_data = h_data; - chip_data->reg_bank = stm32_bank; + chip_data->reg_bank = bank; raw_spin_lock_init(&chip_data->rlock); @@ -583,23 +575,23 @@ stm32_exti_chip_data *stm32_exti_chip_init(struct stm32_exti_host_data *h_data, * This IP has no reset, so after hot reboot we should * clear registers to avoid residue */ - writel_relaxed(0, base + stm32_bank->imr_ofst); + writel_relaxed(0, base + bank->imr_ofst); /* reserve Secure events */ - chip_data->event_reserved = readl_relaxed(base + stm32_bank->seccfgr_ofst); + chip_data->event_reserved = readl_relaxed(base + bank->seccfgr_ofst); pr_info("%pOF: bank%d\n", node, bank_idx); return chip_data; } -static const struct irq_domain_ops stm32_exti_h_domain_ops = { - .alloc = stm32_exti_h_domain_alloc, +static const struct irq_domain_ops stm32mp_exti_domain_ops = { + .alloc = stm32mp_exti_domain_alloc, .free = irq_domain_free_irqs_common, .xlate = irq_domain_xlate_twocell, }; -static void stm32_exti_check_rif(struct stm32_exti_host_data *host_data) +static void stm32mp_exti_check_rif(struct stm32mp_exti_host_data *host_data) { unsigned int bank, i, event; u32 cid, cidcfgr, hwcfgr1; @@ -620,21 +612,21 @@ static void stm32_exti_check_rif(struct stm32_exti_host_data *host_data) } } -static void stm32_exti_remove_irq(void *data) +static void stm32mp_exti_remove_irq(void *data) { struct irq_domain *domain = data; irq_domain_remove(domain); } -static int stm32_exti_probe(struct platform_device *pdev) +static int stm32mp_exti_probe(struct platform_device *pdev) { - int ret, i; + const struct stm32mp_exti_drv_data *drv_data; + struct irq_domain *parent_domain, *domain; + struct stm32mp_exti_host_data *host_data; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; - struct irq_domain *parent_domain, *domain; - struct stm32_exti_host_data *host_data; - const struct stm32_exti_drv_data *drv_data; + int ret, i; host_data = devm_kzalloc(dev, sizeof(*host_data), GFP_KERNEL); if (!host_data) @@ -680,9 +672,9 @@ static int stm32_exti_probe(struct platform_device *pdev) return PTR_ERR(host_data->base); for (i = 0; i < drv_data->bank_nr; i++) - stm32_exti_chip_init(host_data, i, np); + stm32mp_exti_chip_init(host_data, i, np); - stm32_exti_check_rif(host_data); + stm32mp_exti_check_rif(host_data); parent_domain = irq_find_host(of_irq_find_parent(np)); if (!parent_domain) { @@ -692,7 +684,7 @@ static int stm32_exti_probe(struct platform_device *pdev) domain = irq_domain_add_hierarchy(parent_domain, 0, drv_data->bank_nr * IRQS_PER_BANK, - np, &stm32_exti_h_domain_ops, + np, &stm32mp_exti_domain_ops, host_data); if (!domain) { @@ -700,7 +692,7 @@ static int stm32_exti_probe(struct platform_device *pdev) return -ENOMEM; } - ret = devm_add_action_or_reset(dev, stm32_exti_remove_irq, domain); + ret = devm_add_action_or_reset(dev, stm32mp_exti_remove_irq, domain); if (ret) return ret; @@ -710,35 +702,35 @@ static int stm32_exti_probe(struct platform_device *pdev) return 0; } -static const struct of_device_id stm32_exti_ids[] = { +static const struct of_device_id stm32mp_exti_ids[] = { { .compatible = "st,stm32mp1-exti", .data = &stm32mp1_drv_data}, { .compatible = "st,stm32mp13-exti", .data = &stm32mp13_drv_data}, {}, }; -MODULE_DEVICE_TABLE(of, stm32_exti_ids); +MODULE_DEVICE_TABLE(of, stm32mp_exti_ids); -static const struct dev_pm_ops stm32_exti_dev_pm_ops = { - NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32_exti_h_suspend, stm32_exti_h_resume) +static const struct dev_pm_ops stm32mp_exti_dev_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32mp_exti_suspend, stm32mp_exti_resume) }; -static struct platform_driver stm32_exti_driver = { - .probe = stm32_exti_probe, +static struct platform_driver stm32mp_exti_driver = { + .probe = stm32mp_exti_probe, .driver = { - .name = "stm32_exti", - .of_match_table = stm32_exti_ids, - .pm = &stm32_exti_dev_pm_ops, + .name = "stm32mp_exti", + .of_match_table = stm32mp_exti_ids, + .pm = &stm32mp_exti_dev_pm_ops, }, }; -static int __init stm32_exti_arch_init(void) +static int __init stm32mp_exti_arch_init(void) { - return platform_driver_register(&stm32_exti_driver); + return platform_driver_register(&stm32mp_exti_driver); } -static void __exit stm32_exti_arch_exit(void) +static void __exit stm32mp_exti_arch_exit(void) { - return platform_driver_unregister(&stm32_exti_driver); + return platform_driver_unregister(&stm32mp_exti_driver); } -arch_initcall(stm32_exti_arch_init); -module_exit(stm32_exti_arch_exit); +arch_initcall(stm32mp_exti_arch_init); +module_exit(stm32mp_exti_arch_exit); From 0be58e0553812fcbd37c0c2d89e2b5bc296f04ea Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Thu, 20 Jun 2024 10:31:13 +0200 Subject: [PATCH 059/152] irqchip/stm32mp-exti: Allow building as module Allow to build the driver as a module by adding the necessarily hooks in Kconfig and in the driver's code. Since all the probe dependencies linked to this driver have already been fixed, remove the not longer relevant 'arch_initcall'. Signed-off-by: Antonio Borneo Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240620083115.204362-7-antonio.borneo@foss.st.com --- drivers/irqchip/Kconfig | 8 ++++++-- drivers/irqchip/irq-stm32mp-exti.c | 15 ++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 978639d10d75..cbf49b6404b8 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -405,9 +405,13 @@ config PARTITION_PERCPU bool config STM32MP_EXTI - bool - select IRQ_DOMAIN + tristate "STM32MP extended interrupts and event controller" + depends on (ARCH_STM32 && !ARM_SINGLE_ARMV7M) || COMPILE_TEST + default y + select IRQ_DOMAIN_HIERARCHY select GENERIC_IRQ_CHIP + help + Support STM32MP EXTI (extended interrupts and event) controller. config STM32_EXTI bool diff --git a/drivers/irqchip/irq-stm32mp-exti.c b/drivers/irqchip/irq-stm32mp-exti.c index 727859e073ea..33e0cfdea654 100644 --- a/drivers/irqchip/irq-stm32mp-exti.c +++ b/drivers/irqchip/irq-stm32mp-exti.c @@ -722,15 +722,8 @@ static struct platform_driver stm32mp_exti_driver = { }, }; -static int __init stm32mp_exti_arch_init(void) -{ - return platform_driver_register(&stm32mp_exti_driver); -} +module_platform_driver(stm32mp_exti_driver); -static void __exit stm32mp_exti_arch_exit(void) -{ - return platform_driver_unregister(&stm32mp_exti_driver); -} - -arch_initcall(stm32mp_exti_arch_init); -module_exit(stm32mp_exti_arch_exit); +MODULE_AUTHOR("Maxime Coquelin "); +MODULE_DESCRIPTION("STM32MP EXTI driver"); +MODULE_LICENSE("GPL"); From 2751ee69c150fa3013924cadba6f47eb7215086f Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Thu, 20 Jun 2024 10:31:14 +0200 Subject: [PATCH 060/152] ARM: stm32: Allow build irq-stm32mp-exti driver as module Drop auto-selecting the driver, so it can be built either as a module or built-in. Signed-off-by: Antonio Borneo Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240620083115.204362-8-antonio.borneo@foss.st.com --- arch/arm/mach-stm32/Kconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm/mach-stm32/Kconfig b/arch/arm/mach-stm32/Kconfig index a401a991fe08..630b992f32b1 100644 --- a/arch/arm/mach-stm32/Kconfig +++ b/arch/arm/mach-stm32/Kconfig @@ -11,7 +11,6 @@ menuconfig ARCH_STM32 select CLKSRC_STM32 select PINCTRL select RESET_CONTROLLER - select STM32MP_EXTI if ARCH_MULTI_V7 select STM32_EXTI if ARM_SINGLE_ARMV7M select STM32_FIREWALL help From f2605e1715dd28e8943b557453fed3a40421d3b5 Mon Sep 17 00:00:00 2001 From: Antonio Borneo Date: Thu, 20 Jun 2024 10:31:15 +0200 Subject: [PATCH 061/152] arm64: Kconfig: Allow build irq-stm32mp-exti driver as module Drop auto-selecting the driver, so it can be built either as a module or built-in. Signed-off-by: Antonio Borneo Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240620083115.204362-9-antonio.borneo@foss.st.com --- arch/arm64/Kconfig.platforms | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index be8629858d74..a028ea312378 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -309,7 +309,6 @@ config ARCH_STM32 select GPIOLIB select PINCTRL select PINCTRL_STM32MP257 - select STM32MP_EXTI select ARM_SMC_MBOX select ARM_SCMI_PROTOCOL select COMMON_CLK_SCMI From 18db1b6dbbe576c283f69f3e925fbeb57de40855 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 25 Jun 2024 07:40:08 +0200 Subject: [PATCH 062/152] Revert "Loongarch: Support loongarch avec" This reverts commit 760d7e719499d64beea62bfcf53938fb233bb6e7. This results in build failures and has other issues according to Tianyang. Reported-by: kernel test robot Signed-off-by: Thomas Gleixner Cc: Tianyang Zhang Closes: https://lore.kernel.org/oe-kbuild-all/202406240451.ygBFNyJ3-lkp@intel.com/ --- arch/loongarch/Kconfig | 1 - arch/loongarch/include/asm/cpu-features.h | 1 - arch/loongarch/include/asm/cpu.h | 2 - arch/loongarch/include/asm/hw_irq.h | 10 - arch/loongarch/include/asm/irq.h | 12 +- arch/loongarch/include/asm/loongarch.h | 20 +- arch/loongarch/include/asm/smp.h | 2 - arch/loongarch/kernel/cpu-probe.c | 3 +- arch/loongarch/kernel/smp.c | 5 - drivers/irqchip/Makefile | 2 +- drivers/irqchip/irq-loongarch-avec.c | 419 ---------------------- drivers/irqchip/irq-loongarch-cpu.c | 4 +- drivers/irqchip/irq-loongson-eiointc.c | 3 - drivers/irqchip/irq-loongson-pch-msi.c | 43 +-- 14 files changed, 10 insertions(+), 517 deletions(-) delete mode 100644 drivers/irqchip/irq-loongarch-avec.c diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index a66e49b5a68c..e38139c576ee 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -83,7 +83,6 @@ config LOONGARCH select GENERIC_ENTRY select GENERIC_GETTIMEOFDAY select GENERIC_IOREMAP if !ARCH_IOREMAP - select GENERIC_IRQ_MATRIX_ALLOCATOR select GENERIC_IRQ_MULTI_HANDLER select GENERIC_IRQ_PROBE select GENERIC_IRQ_SHOW diff --git a/arch/loongarch/include/asm/cpu-features.h b/arch/loongarch/include/asm/cpu-features.h index 16a716f88a5c..2eafe6a6aca8 100644 --- a/arch/loongarch/include/asm/cpu-features.h +++ b/arch/loongarch/include/asm/cpu-features.h @@ -65,6 +65,5 @@ #define cpu_has_guestid cpu_opt(LOONGARCH_CPU_GUESTID) #define cpu_has_hypervisor cpu_opt(LOONGARCH_CPU_HYPERVISOR) #define cpu_has_ptw cpu_opt(LOONGARCH_CPU_PTW) -#define cpu_has_avecint cpu_opt(LOONGARCH_CPU_AVECINT) #endif /* __ASM_CPU_FEATURES_H */ diff --git a/arch/loongarch/include/asm/cpu.h b/arch/loongarch/include/asm/cpu.h index 843f9c4ec980..48b9f7168bcc 100644 --- a/arch/loongarch/include/asm/cpu.h +++ b/arch/loongarch/include/asm/cpu.h @@ -99,7 +99,6 @@ enum cpu_type_enum { #define CPU_FEATURE_GUESTID 24 /* CPU has GuestID feature */ #define CPU_FEATURE_HYPERVISOR 25 /* CPU has hypervisor (running in VM) */ #define CPU_FEATURE_PTW 26 /* CPU has hardware page table walker */ -#define CPU_FEATURE_AVECINT 27 /* CPU has avec interrupt */ #define LOONGARCH_CPU_CPUCFG BIT_ULL(CPU_FEATURE_CPUCFG) #define LOONGARCH_CPU_LAM BIT_ULL(CPU_FEATURE_LAM) @@ -128,6 +127,5 @@ enum cpu_type_enum { #define LOONGARCH_CPU_GUESTID BIT_ULL(CPU_FEATURE_GUESTID) #define LOONGARCH_CPU_HYPERVISOR BIT_ULL(CPU_FEATURE_HYPERVISOR) #define LOONGARCH_CPU_PTW BIT_ULL(CPU_FEATURE_PTW) -#define LOONGARCH_CPU_AVECINT BIT_ULL(CPU_FEATURE_AVECINT) #endif /* _ASM_CPU_H */ diff --git a/arch/loongarch/include/asm/hw_irq.h b/arch/loongarch/include/asm/hw_irq.h index 772692e765c0..af4f4e8fbd85 100644 --- a/arch/loongarch/include/asm/hw_irq.h +++ b/arch/loongarch/include/asm/hw_irq.h @@ -9,16 +9,6 @@ extern atomic_t irq_err_count; -/* - * 256 vectors Map: - * - * 0 - 15: mapping legacy IPs, e.g. IP0-12. - * 16 - 255: mapping a vector for external IRQ. - * - */ -#define NR_VECTORS 256 -#define IRQ_MATRIX_BITS NR_VECTORS -#define NR_LEGACY_VECTORS 16 /* * interrupt-retrigger: NOP for now. This may not be appropriate for all * machines, we'll see ... diff --git a/arch/loongarch/include/asm/irq.h b/arch/loongarch/include/asm/irq.h index cf3b635a9b86..480418bc5071 100644 --- a/arch/loongarch/include/asm/irq.h +++ b/arch/loongarch/include/asm/irq.h @@ -65,7 +65,7 @@ extern struct acpi_vector_group msi_group[MAX_IO_PICS]; #define LOONGSON_LPC_LAST_IRQ (LOONGSON_LPC_IRQ_BASE + 15) #define LOONGSON_CPU_IRQ_BASE 16 -#define LOONGSON_CPU_LAST_IRQ (LOONGSON_CPU_IRQ_BASE + 15) +#define LOONGSON_CPU_LAST_IRQ (LOONGSON_CPU_IRQ_BASE + 14) #define LOONGSON_PCH_IRQ_BASE 64 #define LOONGSON_PCH_ACPI_IRQ (LOONGSON_PCH_IRQ_BASE + 47) @@ -101,16 +101,6 @@ int pch_msi_acpi_init(struct irq_domain *parent, struct acpi_madt_msi_pic *acpi_pchmsi); int pch_pic_acpi_init(struct irq_domain *parent, struct acpi_madt_bio_pic *acpi_pchpic); - -#ifdef CONFIG_ACPI -int __init pch_msi_acpi_init_v2(struct irq_domain *parent, - struct acpi_madt_msi_pic *pch_msi_entry); -int __init loongarch_avec_acpi_init(struct irq_domain *parent); -void complete_irq_moving(void); -void loongarch_avec_offline_cpu(unsigned int cpu); -void loongarch_avec_online_cpu(unsigned int cpu); -#endif - int find_pch_pic(u32 gsi); struct fwnode_handle *get_pch_msi_handle(int pci_segment); diff --git a/arch/loongarch/include/asm/loongarch.h b/arch/loongarch/include/asm/loongarch.h index 16a910359977..eb09adda54b7 100644 --- a/arch/loongarch/include/asm/loongarch.h +++ b/arch/loongarch/include/asm/loongarch.h @@ -72,6 +72,7 @@ #define CPUCFG1_RPLV BIT(23) #define CPUCFG1_HUGEPG BIT(24) #define CPUCFG1_CRC32 BIT(25) +#define CPUCFG1_MSGINT BIT(26) #define LOONGARCH_CPUCFG2 0x2 #define CPUCFG2_FP BIT(0) @@ -251,8 +252,8 @@ #define CSR_ESTAT_EXC_WIDTH 6 #define CSR_ESTAT_EXC (_ULCAST_(0x3f) << CSR_ESTAT_EXC_SHIFT) #define CSR_ESTAT_IS_SHIFT 0 -#define CSR_ESTAT_IS_WIDTH 15 -#define CSR_ESTAT_IS (_ULCAST_(0x7fff) << CSR_ESTAT_IS_SHIFT) +#define CSR_ESTAT_IS_WIDTH 14 +#define CSR_ESTAT_IS (_ULCAST_(0x3fff) << CSR_ESTAT_IS_SHIFT) #define LOONGARCH_CSR_ERA 0x6 /* ERA */ @@ -998,18 +999,10 @@ #define CSR_FWPC_SKIP_SHIFT 16 #define CSR_FWPC_SKIP (_ULCAST_(1) << CSR_FWPC_SKIP_SHIFT) -#define LOONGARCH_CSR_IRR0 0xa0 -#define LOONGARCH_CSR_IRR1 0xa1 -#define LOONGARCH_CSR_IRR2 0xa2 -#define LOONGARCH_CSR_IRR3 0xa3 -#define LOONGARCH_CSR_IRR_BASE LOONGARCH_CSR_IRR0 - -#define LOONGARCH_CSR_ILR 0xa4 - /* * CSR_ECFG IM */ -#define ECFG0_IM 0x00005fff +#define ECFG0_IM 0x00001fff #define ECFGB_SIP0 0 #define ECFGF_SIP0 (_ULCAST_(1) << ECFGB_SIP0) #define ECFGB_SIP1 1 @@ -1052,7 +1045,6 @@ #define IOCSRF_EIODECODE BIT_ULL(9) #define IOCSRF_FLATMODE BIT_ULL(10) #define IOCSRF_VM BIT_ULL(11) -#define IOCSRF_AVEC BIT_ULL(15) #define LOONGARCH_IOCSR_VENDOR 0x10 @@ -1063,7 +1055,6 @@ #define LOONGARCH_IOCSR_MISC_FUNC 0x420 #define IOCSR_MISC_FUNC_TIMER_RESET BIT_ULL(21) #define IOCSR_MISC_FUNC_EXT_IOI_EN BIT_ULL(48) -#define IOCSR_MISC_FUNC_AVEC_EN BIT_ULL(51) #define LOONGARCH_IOCSR_CPUTEMP 0x428 @@ -1384,10 +1375,9 @@ __BUILD_CSR_OP(tlbidx) #define INT_TI 11 /* Timer */ #define INT_IPI 12 #define INT_NMI 13 -#define INT_AVEC 14 /* ExcCodes corresponding to interrupts */ -#define EXCCODE_INT_NUM (INT_AVEC + 1) +#define EXCCODE_INT_NUM (INT_NMI + 1) #define EXCCODE_INT_START 64 #define EXCCODE_INT_END (EXCCODE_INT_START + EXCCODE_INT_NUM - 1) diff --git a/arch/loongarch/include/asm/smp.h b/arch/loongarch/include/asm/smp.h index 2399004596a3..278700cfee88 100644 --- a/arch/loongarch/include/asm/smp.h +++ b/arch/loongarch/include/asm/smp.h @@ -69,11 +69,9 @@ extern int __cpu_logical_map[NR_CPUS]; #define ACTION_BOOT_CPU 0 #define ACTION_RESCHEDULE 1 #define ACTION_CALL_FUNCTION 2 -#define ACTION_CLEAR_VECT 3 #define SMP_BOOT_CPU BIT(ACTION_BOOT_CPU) #define SMP_RESCHEDULE BIT(ACTION_RESCHEDULE) #define SMP_CALL_FUNCTION BIT(ACTION_CALL_FUNCTION) -#define SMP_CLEAR_VECT BIT(ACTION_CLEAR_VECT) struct secondary_data { unsigned long stack; diff --git a/arch/loongarch/kernel/cpu-probe.c b/arch/loongarch/kernel/cpu-probe.c index 3b2e72e8f9bd..55320813ee08 100644 --- a/arch/loongarch/kernel/cpu-probe.c +++ b/arch/loongarch/kernel/cpu-probe.c @@ -106,6 +106,7 @@ static void cpu_probe_common(struct cpuinfo_loongarch *c) elf_hwcap |= HWCAP_LOONGARCH_CRC32; } + config = read_cpucfg(LOONGARCH_CPUCFG2); if (config & CPUCFG2_LAM) { c->options |= LOONGARCH_CPU_LAM; @@ -175,8 +176,6 @@ static void cpu_probe_common(struct cpuinfo_loongarch *c) c->options |= LOONGARCH_CPU_EIODECODE; if (config & IOCSRF_VM) c->options |= LOONGARCH_CPU_HYPERVISOR; - if (config & IOCSRF_AVEC) - c->options |= LOONGARCH_CPU_AVECINT; config = csr_read32(LOONGARCH_CSR_ASID); config = (config & CSR_ASID_BIT) >> CSR_ASID_BIT_SHIFT; diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index 6dfedef306f3..0dfe2388ef41 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -234,9 +234,6 @@ static irqreturn_t loongson_ipi_interrupt(int irq, void *dev) per_cpu(irq_stat, cpu).ipi_irqs[IPI_CALL_FUNCTION]++; } - if (action & SMP_CLEAR_VECT) - complete_irq_moving(); - return IRQ_HANDLED; } @@ -391,7 +388,6 @@ int loongson_cpu_disable(void) irq_migrate_all_off_this_cpu(); clear_csr_ecfg(ECFG0_IM); local_irq_restore(flags); - loongarch_avec_offline_cpu(cpu); local_flush_tlb_all(); return 0; @@ -570,7 +566,6 @@ asmlinkage void start_secondary(void) * early is dangerous. */ WARN_ON_ONCE(!irqs_disabled()); - loongarch_avec_online_cpu(cpu); loongson_smp_finish(); cpu_startup_entry(CPUHP_AP_ONLINE_IDLE); diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index de091a9f7c88..2df72b7b165b 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -110,7 +110,7 @@ obj-$(CONFIG_LS1X_IRQ) += irq-ls1x.o obj-$(CONFIG_TI_SCI_INTR_IRQCHIP) += irq-ti-sci-intr.o obj-$(CONFIG_TI_SCI_INTA_IRQCHIP) += irq-ti-sci-inta.o obj-$(CONFIG_TI_PRUSS_INTC) += irq-pruss-intc.o -obj-$(CONFIG_IRQ_LOONGARCH_CPU) += irq-loongarch-cpu.o irq-loongarch-avec.o +obj-$(CONFIG_IRQ_LOONGARCH_CPU) += irq-loongarch-cpu.o obj-$(CONFIG_LOONGSON_LIOINTC) += irq-loongson-liointc.o obj-$(CONFIG_LOONGSON_EIOINTC) += irq-loongson-eiointc.o obj-$(CONFIG_LOONGSON_HTPIC) += irq-loongson-htpic.o diff --git a/drivers/irqchip/irq-loongarch-avec.c b/drivers/irqchip/irq-loongarch-avec.c deleted file mode 100644 index 4cd9079f2814..000000000000 --- a/drivers/irqchip/irq-loongarch-avec.c +++ /dev/null @@ -1,419 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (C) 2020 Loongson Technologies, Inc. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define VECTORS_PER_REG 64 -#define ILR_INVALID_MASK 0x80000000UL -#define ILR_VECTOR_MASK 0xffUL -#define AVEC_MSG_OFFSET 0x100000 - -static phys_addr_t msi_base_v2; -static DEFINE_PER_CPU(struct irq_desc * [NR_VECTORS], irq_map); - -struct pending_list { - struct list_head head; -}; - -static DEFINE_PER_CPU(struct pending_list, pending_list); - -struct loongarch_avec_chip { - struct fwnode_handle *fwnode; - struct irq_domain *domain; - struct irq_matrix *vector_matrix; - raw_spinlock_t lock; -}; - -static struct loongarch_avec_chip loongarch_avec; - -struct loongarch_avec_data { - struct list_head entry; - unsigned int cpu; - unsigned int vec; - unsigned int prev_cpu; - unsigned int prev_vec; - unsigned int moving : 1, - managed : 1; -}; - -static struct cpumask intersect_mask; - -static int assign_irq_vector(struct irq_data *irqd, const struct cpumask *dest, - unsigned int *cpu) -{ - return irq_matrix_alloc(loongarch_avec.vector_matrix, dest, false, cpu); -} - -static inline void loongarch_avec_ack_irq(struct irq_data *d) -{ -} - -static inline void loongarch_avec_unmask_irq(struct irq_data *d) -{ -} - -static inline void loongarch_avec_mask_irq(struct irq_data *d) -{ -} - -static void loongarch_avec_sync(struct loongarch_avec_data *adata) -{ - struct pending_list *plist; - - if (cpu_online(adata->prev_cpu)) { - plist = per_cpu_ptr(&pending_list, adata->prev_cpu); - list_add_tail(&adata->entry, &plist->head); - adata->moving = true; - loongson_send_ipi_single(adata->prev_cpu, SMP_CLEAR_VECT); - } - adata->prev_cpu = adata->cpu; - adata->prev_vec = adata->vec; -} - -static int loongarch_avec_set_affinity(struct irq_data *data, const struct cpumask *dest, - bool force) -{ - struct loongarch_avec_data *adata; - unsigned int cpu, vector; - unsigned long flags; - int ret; - - raw_spin_lock_irqsave(&loongarch_avec.lock, flags); - adata = irq_data_get_irq_chip_data(data); - - if (adata->vec && cpu_online(adata->cpu) && cpumask_test_cpu(adata->cpu, dest)) { - raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); - return 0; - } - if (adata->moving) - return -EBUSY; - - cpumask_and(&intersect_mask, dest, cpu_online_mask); - - ret = assign_irq_vector(data, &intersect_mask, &cpu); - if (ret < 0) { - raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); - return ret; - } - vector = ret; - adata->cpu = cpu; - adata->vec = vector; - per_cpu_ptr(irq_map, adata->cpu)[adata->vec] = irq_data_to_desc(data); - loongarch_avec_sync(adata); - - raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); - irq_data_update_effective_affinity(data, cpumask_of(cpu)); - - return IRQ_SET_MASK_OK; -} - -static void loongarch_avec_compose_msg(struct irq_data *d, - struct msi_msg *msg) -{ - struct loongarch_avec_data *avec_data; - - avec_data = irq_data_get_irq_chip_data(d); - - msg->address_hi = 0x0; - msg->address_lo = msi_base_v2 | ((avec_data->vec & 0xff) << 4) | - ((cpu_logical_map(avec_data->cpu & 0xffff)) << 12); - msg->data = 0x0; - -} - -static struct irq_chip loongarch_avec_controller = { - .name = "CORE_AVEC", - .irq_ack = loongarch_avec_ack_irq, - .irq_mask = loongarch_avec_mask_irq, - .irq_unmask = loongarch_avec_unmask_irq, - .irq_set_affinity = loongarch_avec_set_affinity, - .irq_compose_msi_msg = loongarch_avec_compose_msg, -}; - -void complete_irq_moving(void) -{ - struct pending_list *plist = this_cpu_ptr(&pending_list); - struct loongarch_avec_data *adata, *tmp; - int cpu, vector, bias; - u64 irr; - - raw_spin_lock(&loongarch_avec.lock); - - list_for_each_entry_safe(adata, tmp, &plist->head, entry) { - cpu = adata->prev_cpu; - vector = adata->prev_vec; - bias = vector / VECTORS_PER_REG; - switch (bias) { - case 0: - irr = csr_read64(LOONGARCH_CSR_IRR0); - case 1: - irr = csr_read64(LOONGARCH_CSR_IRR1); - case 2: - irr = csr_read64(LOONGARCH_CSR_IRR2); - case 3: - irr = csr_read64(LOONGARCH_CSR_IRR3); - } - - if (irr & (1UL << (vector % VECTORS_PER_REG))) { - loongson_send_ipi_single(cpu, SMP_CLEAR_VECT); - continue; - } - list_del(&adata->entry); - irq_matrix_free(loongarch_avec.vector_matrix, cpu, vector, adata->managed); - this_cpu_write(irq_map[vector], NULL); - adata->moving = 0; - } - raw_spin_unlock(&loongarch_avec.lock); -} - -static void loongarch_avec_dispatch(struct irq_desc *desc) -{ - struct irq_chip *chip = irq_desc_get_chip(desc); - unsigned long vector; - struct irq_desc *d; - - chained_irq_enter(chip, desc); - vector = csr_read64(LOONGARCH_CSR_ILR); - if (vector & ILR_INVALID_MASK) - return; - - vector &= ILR_VECTOR_MASK; - - d = this_cpu_read(irq_map[vector]); - if (d) { - generic_handle_irq_desc(d); - } else { - pr_warn("IRQ ERROR:Unexpected irq occur on cpu %d[vector %ld]\n", - smp_processor_id(), vector); - } - - chained_irq_exit(chip, desc); -} - -static int loongarch_avec_alloc(struct irq_domain *domain, unsigned int virq, - unsigned int nr_irqs, void *arg) -{ - struct loongarch_avec_data *adata; - struct irq_data *irqd; - unsigned int cpu, vector, i, ret; - unsigned long flags; - - raw_spin_lock_irqsave(&loongarch_avec.lock, flags); - for (i = 0; i < nr_irqs; i++) { - irqd = irq_domain_get_irq_data(domain, virq + i); - adata = kzalloc(sizeof(*adata), GFP_KERNEL); - if (!adata) { - raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); - return -ENOMEM; - } - ret = assign_irq_vector(irqd, cpu_online_mask, &cpu); - if (ret < 0) { - raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); - return ret; - } - vector = ret; - adata->prev_cpu = adata->cpu = cpu; - adata->prev_vec = adata->vec = vector; - adata->managed = irqd_affinity_is_managed(irqd); - irq_domain_set_info(domain, virq + i, virq + i, &loongarch_avec_controller, - adata, handle_edge_irq, NULL, NULL); - adata->moving = 0; - irqd_set_single_target(irqd); - irqd_set_affinity_on_activate(irqd); - - per_cpu_ptr(irq_map, adata->cpu)[adata->vec] = irq_data_to_desc(irqd); - } - raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); - - return 0; -} - -static void clear_free_vector(struct irq_data *irqd) -{ - struct loongarch_avec_data *adata = irq_data_get_irq_chip_data(irqd); - bool managed = irqd_affinity_is_managed(irqd); - - per_cpu(irq_map, adata->cpu)[adata->vec] = NULL; - irq_matrix_free(loongarch_avec.vector_matrix, adata->cpu, adata->vec, managed); - adata->cpu = 0; - adata->vec = 0; - if (!adata->moving) - return; - - per_cpu(irq_map, adata->prev_cpu)[adata->prev_vec] = 0; - irq_matrix_free(loongarch_avec.vector_matrix, adata->prev_cpu, - adata->prev_vec, adata->managed); - adata->prev_vec = 0; - adata->prev_cpu = 0; - adata->moving = 0; - list_del_init(&adata->entry); -} - -static void loongarch_avec_free(struct irq_domain *domain, unsigned int virq, - unsigned int nr_irqs) -{ - struct irq_data *d; - unsigned long flags; - unsigned int i; - - raw_spin_lock_irqsave(&loongarch_avec.lock, flags); - for (i = 0; i < nr_irqs; i++) { - d = irq_domain_get_irq_data(domain, virq + i); - if (d) { - clear_free_vector(d); - irq_domain_reset_irq_data(d); - - } - } - - raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); -} - -static const struct irq_domain_ops loongarch_avec_domain_ops = { - .alloc = loongarch_avec_alloc, - .free = loongarch_avec_free, -}; - -static int __init irq_matrix_init(void) -{ - int i; - - loongarch_avec.vector_matrix = irq_alloc_matrix(NR_VECTORS, 0, NR_VECTORS - 1); - if (!loongarch_avec.vector_matrix) - return -ENOMEM; - for (i = 0; i < NR_LEGACY_VECTORS; i++) - irq_matrix_assign_system(loongarch_avec.vector_matrix, i, false); - - irq_matrix_online(loongarch_avec.vector_matrix); - - return 0; -} - -static int __init loongarch_avec_init(struct irq_domain *parent) -{ - struct pending_list *plist = per_cpu_ptr(&pending_list, 0); - int ret = 0, parent_irq; - unsigned long tmp; - - raw_spin_lock_init(&loongarch_avec.lock); - - loongarch_avec.fwnode = irq_domain_alloc_named_fwnode("CORE_AVEC"); - if (!loongarch_avec.fwnode) { - pr_err("Unable to allocate domain handle\n"); - ret = -ENOMEM; - goto out; - } - - loongarch_avec.domain = irq_domain_create_tree(loongarch_avec.fwnode, - &loongarch_avec_domain_ops, NULL); - if (!loongarch_avec.domain) { - pr_err("core-vec: cannot create IRQ domain\n"); - ret = -ENOMEM; - goto out_free_handle; - } - - parent_irq = irq_create_mapping(parent, INT_AVEC); - if (!parent_irq) { - pr_err("Failed to mapping hwirq\n"); - ret = -EINVAL; - goto out_remove_domain; - } - irq_set_chained_handler_and_data(parent_irq, loongarch_avec_dispatch, NULL); - - ret = irq_matrix_init(); - if (ret) { - pr_err("Failed to init irq matrix\n"); - goto out_free_matrix; - } - - INIT_LIST_HEAD(&plist->head); - tmp = iocsr_read64(LOONGARCH_IOCSR_MISC_FUNC); - tmp |= IOCSR_MISC_FUNC_AVEC_EN; - iocsr_write64(tmp, LOONGARCH_IOCSR_MISC_FUNC); - - return ret; - -out_free_matrix: - kfree(loongarch_avec.vector_matrix); -out_remove_domain: - irq_domain_remove(loongarch_avec.domain); -out_free_handle: - irq_domain_free_fwnode(loongarch_avec.fwnode); -out: - return ret; -} - -void loongarch_avec_offline_cpu(unsigned int cpu) -{ - struct pending_list *plist = per_cpu_ptr(&pending_list, cpu); - unsigned long flags; - - raw_spin_lock_irqsave(&loongarch_avec.lock, flags); - if (list_empty(&plist->head)) - irq_matrix_offline(loongarch_avec.vector_matrix); - else - pr_warn("cpu %d advanced extioi is busy\n", cpu); - raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); -} - -void loongarch_avec_online_cpu(unsigned int cpu) -{ - struct pending_list *plist = per_cpu_ptr(&pending_list, cpu); - unsigned long flags; - - raw_spin_lock_irqsave(&loongarch_avec.lock, flags); - - irq_matrix_online(loongarch_avec.vector_matrix); - - INIT_LIST_HEAD(&plist->head); - - raw_spin_unlock_irqrestore(&loongarch_avec.lock, flags); -} - -static int __init pch_msi_parse_madt(union acpi_subtable_headers *header, - const unsigned long end) -{ - struct acpi_madt_msi_pic *pchmsi_entry = (struct acpi_madt_msi_pic *)header; - - msi_base_v2 = pchmsi_entry->msg_address - AVEC_MSG_OFFSET; - return pch_msi_acpi_init_v2(loongarch_avec.domain, pchmsi_entry); -} - -static inline int __init acpi_cascade_irqdomain_init(void) -{ - return acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 1); -} - -int __init loongarch_avec_acpi_init(struct irq_domain *parent) -{ - int ret = 0; - - ret = loongarch_avec_init(parent); - if (ret) { - pr_err("Failed to init irq domain\n"); - return ret; - } - - ret = acpi_cascade_irqdomain_init(); - if (ret) { - pr_err("Failed to cascade IRQ domain\n"); - return ret; - } - - return ret; -} diff --git a/drivers/irqchip/irq-loongarch-cpu.c b/drivers/irqchip/irq-loongarch-cpu.c index 1ecac59925c6..9d8f2c406043 100644 --- a/drivers/irqchip/irq-loongarch-cpu.c +++ b/drivers/irqchip/irq-loongarch-cpu.c @@ -138,9 +138,7 @@ static int __init acpi_cascade_irqdomain_init(void) if (r < 0) return r; - if (cpu_has_avecint) - r = loongarch_avec_acpi_init(irq_domain); - return r; + return 0; } static int __init cpuintc_acpi_init(union acpi_subtable_headers *header, diff --git a/drivers/irqchip/irq-loongson-eiointc.c b/drivers/irqchip/irq-loongson-eiointc.c index 1f9a30488137..c7ddebf312ad 100644 --- a/drivers/irqchip/irq-loongson-eiointc.c +++ b/drivers/irqchip/irq-loongson-eiointc.c @@ -359,9 +359,6 @@ static int __init acpi_cascade_irqdomain_init(void) if (r < 0) return r; - if (cpu_has_avecint) - return 0; - r = acpi_table_parse_madt(ACPI_MADT_TYPE_MSI_PIC, pch_msi_parse_madt, 1); if (r < 0) return r; diff --git a/drivers/irqchip/irq-loongson-pch-msi.c b/drivers/irqchip/irq-loongson-pch-msi.c index 1926857f9a41..dd4d699170f4 100644 --- a/drivers/irqchip/irq-loongson-pch-msi.c +++ b/drivers/irqchip/irq-loongson-pch-msi.c @@ -16,6 +16,7 @@ #include static int nr_pics; + struct pch_msi_data { struct mutex msi_map_lock; phys_addr_t doorbell; @@ -99,17 +100,6 @@ static struct irq_chip middle_irq_chip = { .irq_compose_msi_msg = pch_msi_compose_msi_msg, }; -static struct irq_chip pch_msi_irq_chip_v2 = { - .name = "MSI", - .irq_ack = irq_chip_ack_parent, -}; - -static struct msi_domain_info pch_msi_domain_info_v2 = { - .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | - MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX, - .chip = &pch_msi_irq_chip_v2, -}; - static int pch_msi_parent_domain_alloc(struct irq_domain *domain, unsigned int virq, int hwirq) { @@ -278,9 +268,6 @@ struct fwnode_handle *get_pch_msi_handle(int pci_segment) { int i; - if (cpu_has_avecint) - return pch_msi_handle[0]; - for (i = 0; i < MAX_IO_PICS; i++) { if (msi_group[i].pci_segment == pci_segment) return pch_msi_handle[i]; @@ -302,32 +289,4 @@ int __init pch_msi_acpi_init(struct irq_domain *parent, return ret; } - -int __init pch_msi_acpi_init_v2(struct irq_domain *parent, - struct acpi_madt_msi_pic *msi_entry) -{ - struct irq_domain *msi_domain; - - if (pch_msi_handle[0]) - return 0; - - pch_msi_handle[0] = irq_domain_alloc_named_fwnode("msipic-v2"); - if (!pch_msi_handle[0]) { - pr_err("Unable to allocate domain handle\n"); - kfree(pch_msi_handle[0]); - return -ENOMEM; - } - - msi_domain = pci_msi_create_irq_domain(pch_msi_handle[0], - &pch_msi_domain_info_v2, - parent); - if (!msi_domain) { - pr_err("Failed to create PCI MSI domain\n"); - kfree(pch_msi_handle[0]); - return -ENOMEM; - } - - pr_info("IRQ domain MSIPIC-V2 init done.\n"); - return 0; -} #endif From be5e5f3a1120bada0cff1bc84c2a1805da308f6e Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 25 Jun 2024 21:30:48 +0200 Subject: [PATCH 063/152] Revert "irqchip/dw-apb-ictl: Support building as module" This reverts commit 7cc4f309c933ec5d64eea31066fe86bbf9e48819. Causes build fails. Reported-by: Mark Brown Reported-by: kernel test robot Signed-off-by: Thomas Gleixner Cc: Jisheng Zhang https://lore.kernel.org/oe-kbuild-all/202406250214.WZEjWnnU-lkp@intel.com/ --- drivers/irqchip/Kconfig | 2 +- drivers/irqchip/irq-dw-apb-ictl.c | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index cbf49b6404b8..344c484736af 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -145,7 +145,7 @@ config DAVINCI_CP_INTC select IRQ_DOMAIN config DW_APB_ICTL - tristate "DesignWare APB Interrupt Controller" + bool select GENERIC_IRQ_CHIP select IRQ_DOMAIN_HIERARCHY diff --git a/drivers/irqchip/irq-dw-apb-ictl.c b/drivers/irqchip/irq-dw-apb-ictl.c index 5eda6c4689cf..d5c1c750c8d2 100644 --- a/drivers/irqchip/irq-dw-apb-ictl.c +++ b/drivers/irqchip/irq-dw-apb-ictl.c @@ -122,7 +122,7 @@ static int __init dw_apb_ictl_init(struct device_node *np, int ret, nrirqs, parent_irq, i; u32 reg; - if (!parent && IS_BUILTIN(CONFIG_DW_APB_ICTL)) { + if (!parent) { /* Used as the primary interrupt controller */ parent_irq = 0; domain_ops = &dw_apb_ictl_irq_domain_ops; @@ -214,12 +214,5 @@ err_release: release_mem_region(r.start, resource_size(&r)); return ret; } -#if IS_BUILTIN(CONFIG_DW_APB_ICTL) -IRQCHIP_DECLARE(dw_apb_ictl, "snps,dw-apb-ictl", dw_apb_ictl_init); -#else -IRQCHIP_PLATFORM_DRIVER_BEGIN(dw_apb_ictl) -IRQCHIP_MATCH("snps,dw-apb-ictl", dw_apb_ictl_init) -IRQCHIP_PLATFORM_DRIVER_END(dw_apb_ictl) -MODULE_DESCRIPTION("DesignWare APB Interrupt Controller"); -MODULE_LICENSE("GPL v2"); -#endif +IRQCHIP_DECLARE(dw_apb_ictl, + "snps,dw-apb-ictl", dw_apb_ictl_init); From 3288757087cbb93b91019ba6b7de53a1908c9d48 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 22 Jun 2024 09:04:24 +0200 Subject: [PATCH 064/152] power: supply: ab8500: Fix error handling when calling iio_read_channel_processed() The ab8500_charger_get_[ac|vbus]_[current|voltage]() functions should return an error code on error. Up to now, an un-initialized value is returned. This makes the error handling of the callers un-reliable. Return the error code instead, to fix the issue. Fixes: 97ab78bac5d0 ("power: supply: ab8500_charger: Convert to IIO ADC") Signed-off-by: Christophe JAILLET Reviewed-by: Jonathan Cameron Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/f9f65642331c9e40aaebb888589db043db80b7eb.1719037737.git.christophe.jaillet@wanadoo.fr Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 9b34d1a60f66..4b0ad1b4b4c9 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -488,8 +488,10 @@ static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) /* Only measure voltage if the charger is connected */ if (di->ac.charger_connected) { ret = iio_read_channel_processed(di->adc_main_charger_v, &vch); - if (ret < 0) + if (ret < 0) { dev_err(di->dev, "%s ADC conv failed,\n", __func__); + return ret; + } } else { vch = 0; } @@ -540,8 +542,10 @@ static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) /* Only measure voltage if the charger is connected */ if (di->usb.charger_connected) { ret = iio_read_channel_processed(di->adc_vbus_v, &vch); - if (ret < 0) + if (ret < 0) { dev_err(di->dev, "%s ADC conv failed,\n", __func__); + return ret; + } } else { vch = 0; } @@ -563,8 +567,10 @@ static int ab8500_charger_get_usb_current(struct ab8500_charger *di) /* Only measure current if the charger is online */ if (di->usb.charger_online) { ret = iio_read_channel_processed(di->adc_usb_charger_c, &ich); - if (ret < 0) + if (ret < 0) { dev_err(di->dev, "%s ADC conv failed,\n", __func__); + return ret; + } } else { ich = 0; } @@ -586,8 +592,10 @@ static int ab8500_charger_get_ac_current(struct ab8500_charger *di) /* Only measure current if the charger is online */ if (di->ac.charger_online) { ret = iio_read_channel_processed(di->adc_main_charger_c, &ich); - if (ret < 0) + if (ret < 0) { dev_err(di->dev, "%s ADC conv failed,\n", __func__); + return ret; + } } else { ich = 0; } From dc6ce568afd3452ac682261ea0db570d28f7d82d Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 22 Jun 2024 09:04:25 +0200 Subject: [PATCH 065/152] power: supply: ab8500: Use iio_read_channel_processed_scale() Instead of rescaling current or voltage channels after the fact, use the dedicated scaling API. This should reduce any inaccuracies resulting from the scaling. This is also slightly more efficient as it saves a function call and a multiplication. Signed-off-by: Christophe JAILLET Reviewed-by: Jonathan Cameron Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/5668d73b92eb6318c7f094a9a8fa914c909485ca.1719037737.git.christophe.jaillet@wanadoo.fr Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 28 +++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 4b0ad1b4b4c9..2f06b93682ac 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -487,7 +487,9 @@ static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) /* Only measure voltage if the charger is connected */ if (di->ac.charger_connected) { - ret = iio_read_channel_processed(di->adc_main_charger_v, &vch); + /* Convert to microvolt, IIO returns millivolt */ + ret = iio_read_channel_processed_scale(di->adc_main_charger_v, + &vch, 1000); if (ret < 0) { dev_err(di->dev, "%s ADC conv failed,\n", __func__); return ret; @@ -495,8 +497,7 @@ static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) } else { vch = 0; } - /* Convert to microvolt, IIO returns millivolt */ - return vch * 1000; + return vch; } /** @@ -541,7 +542,9 @@ static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) /* Only measure voltage if the charger is connected */ if (di->usb.charger_connected) { - ret = iio_read_channel_processed(di->adc_vbus_v, &vch); + /* Convert to microvolt, IIO returns millivolt */ + ret = iio_read_channel_processed_scale(di->adc_vbus_v, + &vch, 1000); if (ret < 0) { dev_err(di->dev, "%s ADC conv failed,\n", __func__); return ret; @@ -549,8 +552,7 @@ static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) } else { vch = 0; } - /* Convert to microvolt, IIO returns millivolt */ - return vch * 1000; + return vch; } /** @@ -566,7 +568,9 @@ static int ab8500_charger_get_usb_current(struct ab8500_charger *di) /* Only measure current if the charger is online */ if (di->usb.charger_online) { - ret = iio_read_channel_processed(di->adc_usb_charger_c, &ich); + /* Return microamperes */ + ret = iio_read_channel_processed_scale(di->adc_usb_charger_c, + &ich, 1000); if (ret < 0) { dev_err(di->dev, "%s ADC conv failed,\n", __func__); return ret; @@ -574,8 +578,7 @@ static int ab8500_charger_get_usb_current(struct ab8500_charger *di) } else { ich = 0; } - /* Return microamperes */ - return ich * 1000; + return ich; } /** @@ -591,7 +594,9 @@ static int ab8500_charger_get_ac_current(struct ab8500_charger *di) /* Only measure current if the charger is online */ if (di->ac.charger_online) { - ret = iio_read_channel_processed(di->adc_main_charger_c, &ich); + /* Return microamperes */ + ret = iio_read_channel_processed_scale(di->adc_main_charger_c, + &ich, 1000); if (ret < 0) { dev_err(di->dev, "%s ADC conv failed,\n", __func__); return ret; @@ -599,8 +604,7 @@ static int ab8500_charger_get_ac_current(struct ab8500_charger *di) } else { ich = 0; } - /* Return microamperes */ - return ich * 1000; + return ich; } /** From f62b267adcac33c64a26ec55973dad92bc8a8358 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 22 Jun 2024 09:04:26 +0200 Subject: [PATCH 066/152] power: supply: ab8500: Clean some error messages There is an useless extra comma at the end of some error messages, remove them. Signed-off-by: Christophe JAILLET Reviewed-by: Jonathan Cameron Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/e7ac0fa83c6100cbe4e0efa90cf99291c2423b10.1719037737.git.christophe.jaillet@wanadoo.fr Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_charger.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 2f06b93682ac..93181ebfb324 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -491,7 +491,7 @@ static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) ret = iio_read_channel_processed_scale(di->adc_main_charger_v, &vch, 1000); if (ret < 0) { - dev_err(di->dev, "%s ADC conv failed,\n", __func__); + dev_err(di->dev, "%s ADC conv failed\n", __func__); return ret; } } else { @@ -546,7 +546,7 @@ static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) ret = iio_read_channel_processed_scale(di->adc_vbus_v, &vch, 1000); if (ret < 0) { - dev_err(di->dev, "%s ADC conv failed,\n", __func__); + dev_err(di->dev, "%s ADC conv failed\n", __func__); return ret; } } else { @@ -572,7 +572,7 @@ static int ab8500_charger_get_usb_current(struct ab8500_charger *di) ret = iio_read_channel_processed_scale(di->adc_usb_charger_c, &ich, 1000); if (ret < 0) { - dev_err(di->dev, "%s ADC conv failed,\n", __func__); + dev_err(di->dev, "%s ADC conv failed\n", __func__); return ret; } } else { @@ -598,7 +598,7 @@ static int ab8500_charger_get_ac_current(struct ab8500_charger *di) ret = iio_read_channel_processed_scale(di->adc_main_charger_c, &ich, 1000); if (ret < 0) { - dev_err(di->dev, "%s ADC conv failed,\n", __func__); + dev_err(di->dev, "%s ADC conv failed\n", __func__); return ret; } } else { From f8b6c1eb76f73ed721facd58d0cfb08513aad34c Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 23 Jun 2024 07:50:32 +0200 Subject: [PATCH 067/152] power: supply: ingenic: Fix some error handling paths in ingenic_battery_get_property() If iio_read_channel_processed() fails, 'val->intval' is not updated, but it is still *1000 just after. So, in case of error, the *1000 accumulate and 'val->intval' becomes erroneous. So instead of rescaling the value after the fact, use the dedicated scaling API. This way the result is updated only when needed. In case of error, the previous value is kept, unmodified. This should also reduce any inaccuracies resulting from the scaling. Finally, this is also slightly more efficient as it saves a function call and a multiplication. Fixes: fb24ccfbe1e0 ("power: supply: add Ingenic JZ47xx battery driver.") Signed-off-by: Christophe JAILLET Acked-by: Artur Rojek Link: https://lore.kernel.org/r/51e49c18574003db1e20c9299061a5ecd1661a3c.1719121781.git.christophe.jaillet@wanadoo.fr Signed-off-by: Sebastian Reichel --- drivers/power/supply/ingenic-battery.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/power/supply/ingenic-battery.c b/drivers/power/supply/ingenic-battery.c index 2e7fdfde47ec..0a40f425c277 100644 --- a/drivers/power/supply/ingenic-battery.c +++ b/drivers/power/supply/ingenic-battery.c @@ -31,8 +31,9 @@ static int ingenic_battery_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_HEALTH: - ret = iio_read_channel_processed(bat->channel, &val->intval); - val->intval *= 1000; + ret = iio_read_channel_processed_scale(bat->channel, + &val->intval, + 1000); if (val->intval < info->voltage_min_design_uv) val->intval = POWER_SUPPLY_HEALTH_DEAD; else if (val->intval > info->voltage_max_design_uv) @@ -41,8 +42,9 @@ static int ingenic_battery_get_property(struct power_supply *psy, val->intval = POWER_SUPPLY_HEALTH_GOOD; return ret; case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = iio_read_channel_processed(bat->channel, &val->intval); - val->intval *= 1000; + ret = iio_read_channel_processed_scale(bat->channel, + &val->intval, + 1000); return ret; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: val->intval = info->voltage_min_design_uv; From db9cc848128eb174b24a5dff82fc3e7589a3bf25 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Fri, 21 Jun 2024 01:23:51 +0300 Subject: [PATCH 068/152] power: supply: lenovo_yoga_c630_battery: add Lenovo C630 driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the Lenovo Yoga C630 WOS laptop the EC provides access to the adapter and battery status. Add the driver to read power supply status on the laptop. Reviewed-by: Ilpo Järvinen Signed-off-by: Dmitry Baryshkov Link: https://lore.kernel.org/r/20240621-yoga-ec-driver-v8-1-daae67d0233d@linaro.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 9 + drivers/power/supply/Makefile | 1 + .../power/supply/lenovo_yoga_c630_battery.c | 501 ++++++++++++++++++ 3 files changed, 511 insertions(+) create mode 100644 drivers/power/supply/lenovo_yoga_c630_battery.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 3e31375491d5..75b4f65c7d77 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -167,6 +167,15 @@ config BATTERY_LEGO_EV3 help Say Y here to enable support for the LEGO MINDSTORMS EV3 battery. +config BATTERY_LENOVO_YOGA_C630 + tristate "Lenovo Yoga C630 battery" + depends on EC_LENOVO_YOGA_C630 + help + This driver enables battery support on the Lenovo Yoga C630 laptop. + + To compile the driver as a module, choose M here: the module will be + called lenovo_yoga_c630_battery. + config BATTERY_PMU tristate "Apple PMU battery" depends on PPC32 && ADB_PMU diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 58b567278034..8ebbdcf92dac 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o obj-$(CONFIG_BATTERY_LEGO_EV3) += lego_ev3_battery.o +obj-$(CONFIG_BATTERY_LENOVO_YOGA_C630) += lenovo_yoga_c630_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o obj-$(CONFIG_BATTERY_QCOM_BATTMGR) += qcom_battmgr.o obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o diff --git a/drivers/power/supply/lenovo_yoga_c630_battery.c b/drivers/power/supply/lenovo_yoga_c630_battery.c new file mode 100644 index 000000000000..d4d422cc5353 --- /dev/null +++ b/drivers/power/supply/lenovo_yoga_c630_battery.c @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022-2024, Linaro Ltd + * Authors: + * Bjorn Andersson + * Dmitry Baryshkov + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct yoga_c630_psy { + struct yoga_c630_ec *ec; + struct device *dev; + struct fwnode_handle *fwnode; + struct notifier_block nb; + + /* guards all battery properties and registration of power supplies */ + struct mutex lock; + + struct power_supply *adp_psy; + struct power_supply *bat_psy; + + unsigned long last_status_update; + + bool adapter_online; + + bool unit_mA; + + bool bat_present; + unsigned int bat_status; + unsigned int design_capacity; + unsigned int design_voltage; + unsigned int full_charge_capacity; + + unsigned int capacity_now; + unsigned int voltage_now; + + int current_now; + int rate_now; +}; + +#define LENOVO_EC_CACHE_TIME (10 * HZ) + +#define LENOVO_EC_ADPT_STATUS 0xa3 +#define LENOVO_EC_ADPT_STATUS_PRESENT BIT(7) +#define LENOVO_EC_BAT_ATTRIBUTES 0xc0 +#define LENOVO_EC_BAT_ATTRIBUTES_UNIT_IS_MA BIT(1) +#define LENOVO_EC_BAT_STATUS 0xc1 +#define LENOVO_EC_BAT_STATUS_DISCHARGING BIT(0) +#define LENOVO_EC_BAT_STATUS_CHARGING BIT(1) +#define LENOVO_EC_BAT_REMAIN_CAPACITY 0xc2 +#define LENOVO_EC_BAT_VOLTAGE 0xc6 +#define LENOVO_EC_BAT_DESIGN_VOLTAGE 0xc8 +#define LENOVO_EC_BAT_DESIGN_CAPACITY 0xca +#define LENOVO_EC_BAT_FULL_CAPACITY 0xcc +#define LENOVO_EC_BAT_CURRENT 0xd2 +#define LENOVO_EC_BAT_FULL_FACTORY 0xd6 +#define LENOVO_EC_BAT_PRESENT 0xda +#define LENOVO_EC_BAT_PRESENT_IS_PRESENT BIT(0) +#define LENOVO_EC_BAT_FULL_REGISTER 0xdb +#define LENOVO_EC_BAT_FULL_REGISTER_IS_FACTORY BIT(0) + +static int yoga_c630_psy_update_bat_info(struct yoga_c630_psy *ecbat) +{ + struct yoga_c630_ec *ec = ecbat->ec; + int val; + + lockdep_assert_held(&ecbat->lock); + + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_PRESENT); + if (val < 0) + return val; + ecbat->bat_present = !!(val & LENOVO_EC_BAT_PRESENT_IS_PRESENT); + if (!ecbat->bat_present) + return val; + + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_ATTRIBUTES); + if (val < 0) + return val; + ecbat->unit_mA = val & LENOVO_EC_BAT_ATTRIBUTES_UNIT_IS_MA; + + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_DESIGN_CAPACITY); + if (val < 0) + return val; + ecbat->design_capacity = val * 1000; + + /* + * DSDT has delays after most of EC reads in these methods. + * Having no documentation for the EC we have to follow and sleep here. + */ + msleep(50); + + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_DESIGN_VOLTAGE); + if (val < 0) + return val; + ecbat->design_voltage = val; + + msleep(50); + + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_FULL_REGISTER); + if (val < 0) + return val; + val = yoga_c630_ec_read16(ec, + val & LENOVO_EC_BAT_FULL_REGISTER_IS_FACTORY ? + LENOVO_EC_BAT_FULL_FACTORY : + LENOVO_EC_BAT_FULL_CAPACITY); + if (val < 0) + return val; + + ecbat->full_charge_capacity = val * 1000; + + if (!ecbat->unit_mA) { + ecbat->design_capacity *= 10; + ecbat->full_charge_capacity *= 10; + } + + return 0; +} + +static int yoga_c630_psy_maybe_update_bat_status(struct yoga_c630_psy *ecbat) +{ + struct yoga_c630_ec *ec = ecbat->ec; + int current_mA; + int val; + + guard(mutex)(&ecbat->lock); + if (time_before(jiffies, ecbat->last_status_update + LENOVO_EC_CACHE_TIME)) + return 0; + + val = yoga_c630_ec_read8(ec, LENOVO_EC_BAT_STATUS); + if (val < 0) + return val; + ecbat->bat_status = val; + + msleep(50); + + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_REMAIN_CAPACITY); + if (val < 0) + return val; + ecbat->capacity_now = val * 1000; + + msleep(50); + + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_VOLTAGE); + if (val < 0) + return val; + ecbat->voltage_now = val * 1000; + + msleep(50); + + val = yoga_c630_ec_read16(ec, LENOVO_EC_BAT_CURRENT); + if (val < 0) + return val; + current_mA = sign_extend32(val, 15); + ecbat->current_now = current_mA * 1000; + ecbat->rate_now = current_mA * (ecbat->voltage_now / 1000); + + msleep(50); + + if (!ecbat->unit_mA) + ecbat->capacity_now *= 10; + + ecbat->last_status_update = jiffies; + + return 0; +} + +static int yoga_c630_psy_update_adapter_status(struct yoga_c630_psy *ecbat) +{ + struct yoga_c630_ec *ec = ecbat->ec; + int val; + + guard(mutex)(&ecbat->lock); + + val = yoga_c630_ec_read8(ec, LENOVO_EC_ADPT_STATUS); + if (val < 0) + return val; + + ecbat->adapter_online = !!(val & LENOVO_EC_ADPT_STATUS_PRESENT); + + return 0; +} + +static bool yoga_c630_psy_is_charged(struct yoga_c630_psy *ecbat) +{ + if (ecbat->bat_status != 0) + return false; + + if (ecbat->full_charge_capacity <= ecbat->capacity_now) + return true; + + if (ecbat->design_capacity <= ecbat->capacity_now) + return true; + + return false; +} + +static int yoga_c630_psy_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct yoga_c630_psy *ecbat = power_supply_get_drvdata(psy); + int rc = 0; + + if (!ecbat->bat_present && psp != POWER_SUPPLY_PROP_PRESENT) + return -ENODEV; + + rc = yoga_c630_psy_maybe_update_bat_status(ecbat); + if (rc) + return rc; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (ecbat->bat_status & LENOVO_EC_BAT_STATUS_DISCHARGING) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (ecbat->bat_status & LENOVO_EC_BAT_STATUS_CHARGING) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (yoga_c630_psy_is_charged(ecbat)) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = ecbat->bat_present; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = ecbat->design_voltage; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = ecbat->design_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = ecbat->full_charge_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_ENERGY_NOW: + val->intval = ecbat->capacity_now; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ecbat->current_now; + break; + case POWER_SUPPLY_PROP_POWER_NOW: + val->intval = ecbat->rate_now; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = ecbat->voltage_now; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "PABAS0241231"; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "Compal"; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + break; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static enum power_supply_property yoga_c630_psy_bat_mA_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SCOPE, +}; + +static enum power_supply_property yoga_c630_psy_bat_mWh_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SCOPE, +}; + +static const struct power_supply_desc yoga_c630_psy_bat_psy_desc_mA = { + .name = "yoga-c630-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = yoga_c630_psy_bat_mA_properties, + .num_properties = ARRAY_SIZE(yoga_c630_psy_bat_mA_properties), + .get_property = yoga_c630_psy_bat_get_property, +}; + +static const struct power_supply_desc yoga_c630_psy_bat_psy_desc_mWh = { + .name = "yoga-c630-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = yoga_c630_psy_bat_mWh_properties, + .num_properties = ARRAY_SIZE(yoga_c630_psy_bat_mWh_properties), + .get_property = yoga_c630_psy_bat_get_property, +}; + +static int yoga_c630_psy_adpt_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct yoga_c630_psy *ecbat = power_supply_get_drvdata(psy); + int ret = 0; + + ret = yoga_c630_psy_update_adapter_status(ecbat); + if (ret < 0) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = ecbat->adapter_online; + break; + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval = POWER_SUPPLY_USB_TYPE_C; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property yoga_c630_psy_adpt_properties[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_USB_TYPE, +}; + +static const enum power_supply_usb_type yoga_c630_psy_adpt_usb_type[] = { + POWER_SUPPLY_USB_TYPE_C, +}; + +static const struct power_supply_desc yoga_c630_psy_adpt_psy_desc = { + .name = "yoga-c630-adapter", + .type = POWER_SUPPLY_TYPE_USB, + .usb_types = yoga_c630_psy_adpt_usb_type, + .num_usb_types = ARRAY_SIZE(yoga_c630_psy_adpt_usb_type), + .properties = yoga_c630_psy_adpt_properties, + .num_properties = ARRAY_SIZE(yoga_c630_psy_adpt_properties), + .get_property = yoga_c630_psy_adpt_get_property, +}; + +static int yoga_c630_psy_register_bat_psy(struct yoga_c630_psy *ecbat) +{ + struct power_supply_config bat_cfg = {}; + + bat_cfg.drv_data = ecbat; + bat_cfg.fwnode = ecbat->fwnode; + ecbat->bat_psy = power_supply_register_no_ws(ecbat->dev, + ecbat->unit_mA ? + &yoga_c630_psy_bat_psy_desc_mA : + &yoga_c630_psy_bat_psy_desc_mWh, + &bat_cfg); + if (IS_ERR(ecbat->bat_psy)) { + dev_err(ecbat->dev, "failed to register battery supply\n"); + return PTR_ERR(ecbat->bat_psy); + } + + return 0; +} + +static void yoga_c630_ec_refresh_bat_info(struct yoga_c630_psy *ecbat) +{ + bool current_unit; + + guard(mutex)(&ecbat->lock); + + current_unit = ecbat->unit_mA; + + yoga_c630_psy_update_bat_info(ecbat); + + if (current_unit != ecbat->unit_mA) { + power_supply_unregister(ecbat->bat_psy); + yoga_c630_psy_register_bat_psy(ecbat); + } +} + +static int yoga_c630_psy_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct yoga_c630_psy *ecbat = container_of(nb, struct yoga_c630_psy, nb); + + switch (action) { + case LENOVO_EC_EVENT_BAT_INFO: + yoga_c630_ec_refresh_bat_info(ecbat); + break; + case LENOVO_EC_EVENT_BAT_ADPT_STATUS: + power_supply_changed(ecbat->adp_psy); + fallthrough; + case LENOVO_EC_EVENT_BAT_STATUS: + power_supply_changed(ecbat->bat_psy); + break; + } + + return NOTIFY_OK; +} + +static int yoga_c630_psy_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct yoga_c630_ec *ec = adev->dev.platform_data; + struct power_supply_config adp_cfg = {}; + struct device *dev = &adev->dev; + struct yoga_c630_psy *ecbat; + int ret; + + ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL); + if (!ecbat) + return -ENOMEM; + + ecbat->ec = ec; + ecbat->dev = dev; + mutex_init(&ecbat->lock); + ecbat->fwnode = adev->dev.parent->fwnode; + ecbat->nb.notifier_call = yoga_c630_psy_notify; + + auxiliary_set_drvdata(adev, ecbat); + + adp_cfg.drv_data = ecbat; + adp_cfg.fwnode = ecbat->fwnode; + adp_cfg.supplied_to = (char **)&yoga_c630_psy_bat_psy_desc_mA.name; + adp_cfg.num_supplicants = 1; + ecbat->adp_psy = devm_power_supply_register_no_ws(dev, &yoga_c630_psy_adpt_psy_desc, &adp_cfg); + if (IS_ERR(ecbat->adp_psy)) { + dev_err(dev, "failed to register AC adapter supply\n"); + return PTR_ERR(ecbat->adp_psy); + } + + scoped_guard(mutex, &ecbat->lock) { + ret = yoga_c630_psy_update_bat_info(ecbat); + if (ret) + goto err_unreg_bat; + + ret = yoga_c630_psy_register_bat_psy(ecbat); + if (ret) + goto err_unreg_bat; + } + + ret = yoga_c630_ec_register_notify(ecbat->ec, &ecbat->nb); + if (ret) + goto err_unreg_bat; + + return 0; + +err_unreg_bat: + power_supply_unregister(ecbat->bat_psy); + return ret; +} + +static void yoga_c630_psy_remove(struct auxiliary_device *adev) +{ + struct yoga_c630_psy *ecbat = auxiliary_get_drvdata(adev); + + yoga_c630_ec_unregister_notify(ecbat->ec, &ecbat->nb); + power_supply_unregister(ecbat->bat_psy); +} + +static const struct auxiliary_device_id yoga_c630_psy_id_table[] = { + { .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_PSY, }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, yoga_c630_psy_id_table); + +static struct auxiliary_driver yoga_c630_psy_driver = { + .name = YOGA_C630_DEV_PSY, + .id_table = yoga_c630_psy_id_table, + .probe = yoga_c630_psy_probe, + .remove = yoga_c630_psy_remove, +}; + +module_auxiliary_driver(yoga_c630_psy_driver); + +MODULE_DESCRIPTION("Lenovo Yoga C630 psy"); +MODULE_LICENSE("GPL"); From 5d55721d6e24c8e99cc86ee1fcb90d776ef47964 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 23 Jun 2024 08:45:33 +0200 Subject: [PATCH 069/152] power: supply: samsung-sdi-battery: Constify struct power_supply_vbat_ri_table 'struct power_supply_vbat_ri_table' are not modified in this driver. Constifying these structures moves some data to a read-only section, so increase overall security. In order to do it, some code also needs to be adjusted to this new const qualifier. On a x86_64, with allmodconfig: Before: ====== $ size drivers/power/supply/samsung-sdi-battery.o text data bss dec hex filename 955 7664 0 8619 21ab drivers/power/supply/samsung-sdi-battery.o After: ===== $ size drivers/power/supply/samsung-sdi-battery.o text data bss dec hex filename 4055 4584 0 8639 21bf drivers/power/supply/samsung-sdi-battery.o Signed-off-by: Christophe JAILLET Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/d01818abd880bf435d1106a9a6cc11a7a8a3e661.1719125040.git.christophe.jaillet@wanadoo.fr Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 2 +- drivers/power/supply/samsung-sdi-battery.c | 24 +++++++++++----------- include/linux/power_supply.h | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index fefe938c9342..022d0e4bf621 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1024,7 +1024,7 @@ EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple); int power_supply_vbat2ri(struct power_supply_battery_info *info, int vbat_uv, bool charging) { - struct power_supply_vbat_ri_table *vbat2ri; + const struct power_supply_vbat_ri_table *vbat2ri; int table_len; int i, high, low; diff --git a/drivers/power/supply/samsung-sdi-battery.c b/drivers/power/supply/samsung-sdi-battery.c index b33daab798b9..725fbe09379e 100644 --- a/drivers/power/supply/samsung-sdi-battery.c +++ b/drivers/power/supply/samsung-sdi-battery.c @@ -25,7 +25,7 @@ struct samsung_sdi_battery { * tables apply depending on whether we are charging or not. */ -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb_l1m7flu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb_l1m7flu[] = { { .vbat_uv = 4240000, .ri_uohm = 160000 }, { .vbat_uv = 4210000, .ri_uohm = 179000 }, { .vbat_uv = 4180000, .ri_uohm = 183000 }, @@ -53,7 +53,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb_l1m7flu { .vbat_uv = 3300000, .ri_uohm = 339000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb_l1m7flu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb_l1m7flu[] = { { .vbat_uv = 4302000, .ri_uohm = 230000 }, { .vbat_uv = 4276000, .ri_uohm = 345000 }, { .vbat_uv = 4227000, .ri_uohm = 345000 }, @@ -73,7 +73,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb_l1m7flu[] { .vbat_uv = 3590000, .ri_uohm = 164000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161la[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161la[] = { { .vbat_uv = 4240000, .ri_uohm = 160000 }, { .vbat_uv = 4210000, .ri_uohm = 179000 }, { .vbat_uv = 4180000, .ri_uohm = 183000 }, @@ -105,7 +105,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161la { .vbat_uv = 3300000, .ri_uohm = 339000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161la[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161la[] = { { .vbat_uv = 4345000, .ri_uohm = 230000 }, { .vbat_uv = 4329000, .ri_uohm = 238000 }, { .vbat_uv = 4314000, .ri_uohm = 225000 }, @@ -182,7 +182,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161la[] { .vbat_uv = 3590000, .ri_uohm = 164000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161lu[] = { { .vbat_uv = 4240000, .ri_uohm = 160000 }, { .vbat_uv = 4210000, .ri_uohm = 179000 }, { .vbat_uv = 4180000, .ri_uohm = 183000 }, @@ -214,7 +214,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb425161lu { .vbat_uv = 3300000, .ri_uohm = 339000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161lu[] = { { .vbat_uv = 4346000, .ri_uohm = 293000 }, { .vbat_uv = 4336000, .ri_uohm = 290000 }, { .vbat_uv = 4315000, .ri_uohm = 274000 }, @@ -244,7 +244,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb425161lu[] { .vbat_uv = 3590000, .ri_uohm = 164000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb485159lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb485159lu[] = { { .vbat_uv = 4240000, .ri_uohm = 160000 }, { .vbat_uv = 4210000, .ri_uohm = 179000 }, { .vbat_uv = 4180000, .ri_uohm = 183000 }, @@ -271,7 +271,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb485159lu { .vbat_uv = 3300000, .ri_uohm = 339000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb485159lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb485159lu[] = { { .vbat_uv = 4302000, .ri_uohm = 200000 }, { .vbat_uv = 4258000, .ri_uohm = 206000 }, { .vbat_uv = 4200000, .ri_uohm = 231000 }, @@ -291,7 +291,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb485159lu[] { .vbat_uv = 3590000, .ri_uohm = 164000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb535151vu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb535151vu[] = { { .vbat_uv = 4071000, .ri_uohm = 158000 }, { .vbat_uv = 4019000, .ri_uohm = 187000 }, { .vbat_uv = 3951000, .ri_uohm = 191000 }, @@ -311,7 +311,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb535151vu { .vbat_uv = 3280000, .ri_uohm = 250000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb535151vu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb535151vu[] = { { .vbat_uv = 4190000, .ri_uohm = 214000 }, { .vbat_uv = 4159000, .ri_uohm = 252000 }, { .vbat_uv = 4121000, .ri_uohm = 245000 }, @@ -331,7 +331,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb535151vu[] { .vbat_uv = 3510000, .ri_uohm = 228000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb585157lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb585157lu[] = { { .vbat_uv = 4194000, .ri_uohm = 121000 }, { .vbat_uv = 4169000, .ri_uohm = 188000 }, { .vbat_uv = 4136000, .ri_uohm = 173000 }, @@ -401,7 +401,7 @@ static struct power_supply_vbat_ri_table samsung_vbat2res_discharging_eb585157lu { .vbat_uv = 3161000, .ri_uohm = 452000 }, }; -static struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb585157lu[] = { +static const struct power_supply_vbat_ri_table samsung_vbat2res_charging_eb585157lu[] = { { .vbat_uv = 4360000, .ri_uohm = 128000 }, { .vbat_uv = 4325000, .ri_uohm = 130000 }, { .vbat_uv = 4316000, .ri_uohm = 148000 }, diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 65082ef75692..5061eeecf62e 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -755,9 +755,9 @@ struct power_supply_battery_info { int ocv_table_size[POWER_SUPPLY_OCV_TEMP_MAX]; struct power_supply_resistance_temp_table *resist_table; int resist_table_size; - struct power_supply_vbat_ri_table *vbat2ri_discharging; + const struct power_supply_vbat_ri_table *vbat2ri_discharging; int vbat2ri_discharging_size; - struct power_supply_vbat_ri_table *vbat2ri_charging; + const struct power_supply_vbat_ri_table *vbat2ri_charging; int vbat2ri_charging_size; int bti_resistance_ohm; int bti_resistance_tolerance; From 0b209ec85b2b73c38a09ba71dc05fbe4aee7be67 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 23 Jun 2024 10:04:45 +0200 Subject: [PATCH 070/152] power: supply: samsung-sdi-battery: Constify struct power_supply_maintenance_charge_table 'struct power_supply_maintenance_charge_table' is not modified in this driver. Constifying this structure moves some data to a read-only section, so increase overall security. In order to do it, some code also needs to be adjusted to this new const qualifier. On a x86_64, with allmodconfig: Before: ====== $ size drivers/power/supply/samsung-sdi-battery.o text data bss dec hex filename 4055 4584 0 8639 21bf drivers/power/supply/samsung-sdi-battery.o After: ===== $ size drivers/power/supply/samsung-sdi-battery.o text data bss dec hex filename 4087 4552 0 8639 21bf drivers/power/supply/samsung-sdi-battery.o Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/6caafd0ac2556a40405273b1a4badc508ea8e9b0.1719125040.git.christophe.jaillet@wanadoo.fr Signed-off-by: Sebastian Reichel --- drivers/power/supply/ab8500_chargalg.c | 2 +- drivers/power/supply/power_supply_core.c | 2 +- drivers/power/supply/samsung-sdi-battery.c | 2 +- include/linux/power_supply.h | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/power/supply/ab8500_chargalg.c b/drivers/power/supply/ab8500_chargalg.c index 55ab7a28056e..854491ad3ecd 100644 --- a/drivers/power/supply/ab8500_chargalg.c +++ b/drivers/power/supply/ab8500_chargalg.c @@ -1225,8 +1225,8 @@ static bool ab8500_chargalg_time_to_restart(struct ab8500_chargalg *di) */ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) { + const struct power_supply_maintenance_charge_table *mt; struct power_supply_battery_info *bi = di->bm->bi; - struct power_supply_maintenance_charge_table *mt; int charger_status; int ret; diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 022d0e4bf621..8f6025acd10a 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1072,7 +1072,7 @@ int power_supply_vbat2ri(struct power_supply_battery_info *info, } EXPORT_SYMBOL_GPL(power_supply_vbat2ri); -struct power_supply_maintenance_charge_table * +const struct power_supply_maintenance_charge_table * power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, int index) { diff --git a/drivers/power/supply/samsung-sdi-battery.c b/drivers/power/supply/samsung-sdi-battery.c index 725fbe09379e..b63fd2758c2f 100644 --- a/drivers/power/supply/samsung-sdi-battery.c +++ b/drivers/power/supply/samsung-sdi-battery.c @@ -613,7 +613,7 @@ static struct power_supply_battery_ocv_table samsung_ocv_cap_eb585157lu[] = { { .ocv = 3300000, .capacity = 0}, }; -static struct power_supply_maintenance_charge_table samsung_maint_charge_table[] = { +static const struct power_supply_maintenance_charge_table samsung_maint_charge_table[] = { { /* Maintenance charging phase A, 60 hours */ .charge_current_max_ua = 600000, diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 5061eeecf62e..72dc7e45c90c 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -736,7 +736,7 @@ struct power_supply_battery_info { int overvoltage_limit_uv; int constant_charge_current_max_ua; int constant_charge_voltage_max_uv; - struct power_supply_maintenance_charge_table *maintenance_charge; + const struct power_supply_maintenance_charge_table *maintenance_charge; int maintenance_charge_size; int alert_low_temp_charge_current_ua; int alert_low_temp_charge_voltage_uv; @@ -810,7 +810,7 @@ power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table int table_len, int temp); extern int power_supply_vbat2ri(struct power_supply_battery_info *info, int vbat_uv, bool charging); -extern struct power_supply_maintenance_charge_table * +extern const struct power_supply_maintenance_charge_table * power_supply_get_maintenance_charging_setting(struct power_supply_battery_info *info, int index); extern bool power_supply_battery_bti_in_range(struct power_supply_battery_info *info, int resistance); @@ -824,7 +824,7 @@ extern int power_supply_set_battery_charged(struct power_supply *psy); static inline bool power_supply_supports_maintenance_charging(struct power_supply_battery_info *info) { - struct power_supply_maintenance_charge_table *mt; + const struct power_supply_maintenance_charge_table *mt; mt = power_supply_get_maintenance_charging_setting(info, 0); From b498ddb6f283c1a3efff98851aa58d66307be620 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sat, 8 Jun 2024 21:02:54 -0700 Subject: [PATCH 071/152] power: reset: piix4: add missing MODULE_DESCRIPTION() macro make allmodconfig && make W=1 C=1 reports: WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/power/reset/piix4-poweroff.o Add the missing invocation of the MODULE_DESCRIPTION() macro. Signed-off-by: Jeff Johnson Link: https://lore.kernel.org/r/20240608-md-drivers-power-reset-v1-1-08dbc1a546a2@quicinc.com Signed-off-by: Sebastian Reichel --- drivers/power/reset/piix4-poweroff.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/reset/piix4-poweroff.c b/drivers/power/reset/piix4-poweroff.c index 7f308292d7e3..e6822c021000 100644 --- a/drivers/power/reset/piix4-poweroff.c +++ b/drivers/power/reset/piix4-poweroff.c @@ -106,4 +106,5 @@ static struct pci_driver piix4_poweroff_driver = { module_pci_driver(piix4_poweroff_driver); MODULE_AUTHOR("Paul Burton "); +MODULE_DESCRIPTION("Intel PIIX4 power-off driver"); MODULE_LICENSE("GPL"); From 02d51503324cb699ef248ece47cbdc2f2a61eb73 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 27 Mar 2024 18:46:47 +0100 Subject: [PATCH 072/152] pcmcia: bcm63xx: drop driver owner assignment Core in platform_driver_register() already sets the .owner, so driver does not need to. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Dominik Brodowski --- drivers/pcmcia/bcm63xx_pcmcia.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/pcmcia/bcm63xx_pcmcia.c b/drivers/pcmcia/bcm63xx_pcmcia.c index a5414441834a..5bda3e6d43d8 100644 --- a/drivers/pcmcia/bcm63xx_pcmcia.c +++ b/drivers/pcmcia/bcm63xx_pcmcia.c @@ -456,7 +456,6 @@ struct platform_driver bcm63xx_pcmcia_driver = { .remove_new = bcm63xx_drv_pcmcia_remove, .driver = { .name = "bcm63xx_pcmcia", - .owner = THIS_MODULE, }, }; From 24a025497e7e883bd2adef5d0ece1e9b9268009f Mon Sep 17 00:00:00 2001 From: Jules Irenge Date: Sun, 12 May 2024 23:31:21 +0100 Subject: [PATCH 073/152] pcmcia: Use resource_size function on resource object Cocinnele reports a warning WARNING: Suspicious code. resource_size is maybe missing with root The root cause is the function resource_size is not used when needed Use resource_size() on variable "root" of type resource Signed-off-by: Jules Irenge Signed-off-by: Dominik Brodowski --- drivers/pcmcia/yenta_socket.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pcmcia/yenta_socket.c b/drivers/pcmcia/yenta_socket.c index 1365eaa20ff4..ff169124929c 100644 --- a/drivers/pcmcia/yenta_socket.c +++ b/drivers/pcmcia/yenta_socket.c @@ -638,11 +638,11 @@ static int yenta_search_one_res(struct resource *root, struct resource *res, start = PCIBIOS_MIN_CARDBUS_IO; end = ~0U; } else { - unsigned long avail = root->end - root->start; + unsigned long avail = resource_size(root); int i; size = BRIDGE_MEM_MAX; - if (size > avail/8) { - size = (avail+1)/8; + if (size > (avail - 1) / 8) { + size = avail / 8; /* round size down to next power of 2 */ i = 0; while ((size /= 2) != 0) From 0630e3bc0e91b57288921df2927859b23184ca45 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Fri, 7 Jun 2024 15:02:58 -0700 Subject: [PATCH 074/152] pcmcia: add missing MODULE_DESCRIPTION() macros On x86, make allmodconfig && make W=1 C=1 reports: WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/pcmcia/pcmcia_rsrc.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/pcmcia/yenta_socket.o WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/pcmcia/i82092.o Add the missing invocation of the MODULE_DESCRIPTION() macro to all files which have a MODULE_LICENSE(). This includes files which did not produce a warning with the x86 allmodconfig since they may cause this warning with other configurations. Signed-off-by: Jeff Johnson [linux@dominikbrodowski.net: modify some descriptions] Signed-off-by: Dominik Brodowski --- drivers/pcmcia/i82092.c | 1 + drivers/pcmcia/i82365.c | 1 + drivers/pcmcia/max1600.c | 1 + drivers/pcmcia/rsrc_mgr.c | 1 + drivers/pcmcia/yenta_socket.c | 1 + 5 files changed, 5 insertions(+) diff --git a/drivers/pcmcia/i82092.c b/drivers/pcmcia/i82092.c index a335748bdef5..a947ffb2df55 100644 --- a/drivers/pcmcia/i82092.c +++ b/drivers/pcmcia/i82092.c @@ -23,6 +23,7 @@ #include "i82092aa.h" #include "i82365.h" +MODULE_DESCRIPTION("Driver for Intel I82092AA PCI-PCMCIA bridge"); MODULE_LICENSE("GPL"); /* PCI core routines */ diff --git a/drivers/pcmcia/i82365.c b/drivers/pcmcia/i82365.c index 891ccea2cccb..86a357837a7b 100644 --- a/drivers/pcmcia/i82365.c +++ b/drivers/pcmcia/i82365.c @@ -1342,5 +1342,6 @@ static void __exit exit_i82365(void) module_init(init_i82365); module_exit(exit_i82365); +MODULE_DESCRIPTION("Driver for Intel 82365 and compatible PC Card controllers"); MODULE_LICENSE("Dual MPL/GPL"); /*====================================================================*/ diff --git a/drivers/pcmcia/max1600.c b/drivers/pcmcia/max1600.c index 379875a5e7cd..7be9068f6191 100644 --- a/drivers/pcmcia/max1600.c +++ b/drivers/pcmcia/max1600.c @@ -119,4 +119,5 @@ int max1600_configure(struct max1600 *m, unsigned int vcc, unsigned int vpp) } EXPORT_SYMBOL_GPL(max1600_configure); +MODULE_DESCRIPTION("MAX1600 PCMCIA power switch library"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/pcmcia/rsrc_mgr.c b/drivers/pcmcia/rsrc_mgr.c index 252893216e50..3a1d2baa466f 100644 --- a/drivers/pcmcia/rsrc_mgr.c +++ b/drivers/pcmcia/rsrc_mgr.c @@ -66,5 +66,6 @@ EXPORT_SYMBOL(pccard_static_ops); MODULE_AUTHOR("David A. Hinds, Dominik Brodowski"); +MODULE_DESCRIPTION("PCMCIA resource management routines"); MODULE_LICENSE("GPL"); MODULE_ALIAS("rsrc_nonstatic"); diff --git a/drivers/pcmcia/yenta_socket.c b/drivers/pcmcia/yenta_socket.c index ff169124929c..020ea86c24ec 100644 --- a/drivers/pcmcia/yenta_socket.c +++ b/drivers/pcmcia/yenta_socket.c @@ -1452,4 +1452,5 @@ static struct pci_driver yenta_cardbus_driver = { module_pci_driver(yenta_cardbus_driver); +MODULE_DESCRIPTION("Driver for CardBus yenta-compatible bridges"); MODULE_LICENSE("GPL"); From 61842868de13aa7fd7391c626e889f4d6f1450bf Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Fri, 10 May 2024 10:57:22 +0200 Subject: [PATCH 075/152] module: create weak dependecies It has been seen that for some network mac drivers (i.e. lan78xx) the related module for the phy is loaded dynamically depending on the current hardware. In this case, the associated phy is read using mdio bus and then the associated phy module is loaded during runtime (kernel function phy_request_driver_module). However, no software dependency is defined, so the user tools will no be able to get this dependency. For example, if dracut is used and the hardware is present, lan78xx will be included but no phy module will be added, and in the next restart the device will not work from boot because no related phy will be found during initramfs stage. In order to solve this, we could define a normal 'pre' software dependency in lan78xx module with all the possible phy modules (there may be some), but proceeding in that way, all the possible phy modules would be loaded while only one is necessary. The idea is to create a new type of dependency, that we are going to call 'weak' to be used only by the user tools that need to detect this situation. In that way, for example, dracut could check the 'weak' dependency of the modules involved in order to install these dependencies in initramfs too. That is, for the commented lan78xx module, defining the 'weak' dependency with the possible phy modules list, only the necessary phy would be loaded on demand keeping the same behavior, but all the possible phy modules would be available from initramfs. The 'weak' dependency support has been included in kmod: https://github.com/kmod-project/kmod/commit/05828b4a6e9327a63ef94df544a042b5e9ce4fe7 But, take into account that this can only be used if depmod is new enough. If it isn't, depmod will have the same behavior as always (keeping backward compatibility) and the information for the 'weak' dependency will not be provided. Signed-off-by: Jose Ignacio Tornos Martinez Reviewed-by: Lucas De Marchi Signed-off-by: Luis Chamberlain --- include/linux/module.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/module.h b/include/linux/module.h index ffa1c603163c..ae19bf7e3131 100644 --- a/include/linux/module.h +++ b/include/linux/module.h @@ -173,6 +173,12 @@ extern void cleanup_module(void); */ #define MODULE_SOFTDEP(_softdep) MODULE_INFO(softdep, _softdep) +/* + * Weak module dependencies. See man modprobe.d for details. + * Example: MODULE_WEAKDEP("module-foo") + */ +#define MODULE_WEAKDEP(_weakdep) MODULE_INFO(weakdep, _weakdep) + /* * MODULE_FILE is used for generating modules.builtin * So, make it no-op when this is being built as a module From adb68ed26a3e92224e04502c768f1bb03b7c7aeb Mon Sep 17 00:00:00 2001 From: Yafang Shao Date: Tue, 25 Jun 2024 23:11:21 +0800 Subject: [PATCH 076/152] livepatch: Add "replace" sysfs attribute There are situations when it might make sense to combine livepatches with and without the atomic replace on the same system. For example, the livepatch without the atomic replace might provide a hotfix or extra tuning. Managing livepatches on such systems might be challenging. And the information which of the installed livepatches do not use the atomic replace would be useful. Add new sysfs interface 'replace'. It works as follows: $ cat /sys/kernel/livepatch/livepatch-non_replace/replace 0 $ cat /sys/kernel/livepatch/livepatch-replace/replace 1 [ commit log improved by Petr ] Signed-off-by: Yafang Shao Reviewed-by: Petr Mladek Acked-by: Miroslav Benes Link: https://lore.kernel.org/r/20240625151123.2750-2-laoar.shao@gmail.com Signed-off-by: Petr Mladek --- Documentation/ABI/testing/sysfs-kernel-livepatch | 8 ++++++++ kernel/livepatch/core.c | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch index a5df9b4910dc..3735d868013d 100644 --- a/Documentation/ABI/testing/sysfs-kernel-livepatch +++ b/Documentation/ABI/testing/sysfs-kernel-livepatch @@ -47,6 +47,14 @@ Description: disabled when the feature is used. See Documentation/livepatch/livepatch.rst for more information. +What: /sys/kernel/livepatch//replace +Date: Jun 2024 +KernelVersion: 6.11.0 +Contact: live-patching@vger.kernel.org +Description: + An attribute which indicates whether the patch supports + atomic-replace. + What: /sys/kernel/livepatch// Date: Nov 2014 KernelVersion: 3.19.0 diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index 52426665eecc..ad28617bfd75 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -346,6 +346,7 @@ int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs, * /sys/kernel/livepatch//enabled * /sys/kernel/livepatch//transition * /sys/kernel/livepatch//force + * /sys/kernel/livepatch//replace * /sys/kernel/livepatch// * /sys/kernel/livepatch///patched * /sys/kernel/livepatch/// @@ -443,13 +444,24 @@ static ssize_t force_store(struct kobject *kobj, struct kobj_attribute *attr, return count; } +static ssize_t replace_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct klp_patch *patch; + + patch = container_of(kobj, struct klp_patch, kobj); + return sysfs_emit(buf, "%d\n", patch->replace); +} + static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled); static struct kobj_attribute transition_kobj_attr = __ATTR_RO(transition); static struct kobj_attribute force_kobj_attr = __ATTR_WO(force); +static struct kobj_attribute replace_kobj_attr = __ATTR_RO(replace); static struct attribute *klp_patch_attrs[] = { &enabled_kobj_attr.attr, &transition_kobj_attr.attr, &force_kobj_attr.attr, + &replace_kobj_attr.attr, NULL }; ATTRIBUTE_GROUPS(klp_patch); From 40f9caa7b171d088b8244db280495402fd416c55 Mon Sep 17 00:00:00 2001 From: Yafang Shao Date: Tue, 25 Jun 2024 23:11:22 +0800 Subject: [PATCH 077/152] selftests/livepatch: Add selftests for "replace" sysfs attribute Add selftests for both atomic replace and non atomic replace livepatches. The result is as follows, TEST: sysfs test ... ok TEST: sysfs test object/patched ... ok TEST: sysfs test replace enabled ... ok TEST: sysfs test replace disabled ... ok Suggested-by: Marcos Paulo de Souza Signed-off-by: Yafang Shao Tested-by: Marcos Paulo de Souza Reviewed-by: Marcos Paulo de Souza Reviewed-by: Petr Mladek Acked-by: Miroslav Benes Link: https://lore.kernel.org/r/20240625151123.2750-3-laoar.shao@gmail.com Signed-off-by: Petr Mladek --- .../testing/selftests/livepatch/test-sysfs.sh | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tools/testing/selftests/livepatch/test-sysfs.sh b/tools/testing/selftests/livepatch/test-sysfs.sh index 6c646afa7395..05a14f5a7bfb 100755 --- a/tools/testing/selftests/livepatch/test-sysfs.sh +++ b/tools/testing/selftests/livepatch/test-sysfs.sh @@ -18,6 +18,7 @@ check_sysfs_rights "$MOD_LIVEPATCH" "" "drwxr-xr-x" check_sysfs_rights "$MOD_LIVEPATCH" "enabled" "-rw-r--r--" check_sysfs_value "$MOD_LIVEPATCH" "enabled" "1" check_sysfs_rights "$MOD_LIVEPATCH" "force" "--w-------" +check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--" check_sysfs_rights "$MOD_LIVEPATCH" "transition" "-r--r--r--" check_sysfs_value "$MOD_LIVEPATCH" "transition" "0" check_sysfs_rights "$MOD_LIVEPATCH" "vmlinux/patched" "-r--r--r--" @@ -83,4 +84,51 @@ test_klp_callbacks_demo: post_unpatch_callback: vmlinux livepatch: 'test_klp_callbacks_demo': unpatching complete % rmmod test_klp_callbacks_demo" +start_test "sysfs test replace enabled" + +MOD_LIVEPATCH=test_klp_atomic_replace +load_lp $MOD_LIVEPATCH replace=1 + +check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--" +check_sysfs_value "$MOD_LIVEPATCH" "replace" "1" + +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace=1 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" + +start_test "sysfs test replace disabled" + +load_lp $MOD_LIVEPATCH replace=0 + +check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--" +check_sysfs_value "$MOD_LIVEPATCH" "replace" "0" + +disable_lp $MOD_LIVEPATCH +unload_lp $MOD_LIVEPATCH + +check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace=0 +livepatch: enabling patch '$MOD_LIVEPATCH' +livepatch: '$MOD_LIVEPATCH': initializing patching transition +livepatch: '$MOD_LIVEPATCH': starting patching transition +livepatch: '$MOD_LIVEPATCH': completing patching transition +livepatch: '$MOD_LIVEPATCH': patching complete +% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled +livepatch: '$MOD_LIVEPATCH': initializing unpatching transition +livepatch: '$MOD_LIVEPATCH': starting unpatching transition +livepatch: '$MOD_LIVEPATCH': completing unpatching transition +livepatch: '$MOD_LIVEPATCH': unpatching complete +% rmmod $MOD_LIVEPATCH" + exit 0 From 920526928089b00be7881c7112a463fe8a63371b Mon Sep 17 00:00:00 2001 From: Yafang Shao Date: Tue, 25 Jun 2024 23:11:23 +0800 Subject: [PATCH 078/152] livepatch: Replace snprintf() with sysfs_emit() Let's use sysfs_emit() instead of snprintf(). Suggested-by: Miroslav Benes Signed-off-by: Yafang Shao Reviewed-by: Petr Mladek Acked-by: Miroslav Benes Link: https://lore.kernel.org/r/20240625151123.2750-4-laoar.shao@gmail.com Signed-off-by: Petr Mladek --- kernel/livepatch/core.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c index ad28617bfd75..3c21c31796db 100644 --- a/kernel/livepatch/core.c +++ b/kernel/livepatch/core.c @@ -402,7 +402,7 @@ static ssize_t enabled_show(struct kobject *kobj, struct klp_patch *patch; patch = container_of(kobj, struct klp_patch, kobj); - return snprintf(buf, PAGE_SIZE-1, "%d\n", patch->enabled); + return sysfs_emit(buf, "%d\n", patch->enabled); } static ssize_t transition_show(struct kobject *kobj, @@ -411,8 +411,7 @@ static ssize_t transition_show(struct kobject *kobj, struct klp_patch *patch; patch = container_of(kobj, struct klp_patch, kobj); - return snprintf(buf, PAGE_SIZE-1, "%d\n", - patch == klp_transition_patch); + return sysfs_emit(buf, "%d\n", patch == klp_transition_patch); } static ssize_t force_store(struct kobject *kobj, struct kobj_attribute *attr, From ad0aa2361c8c0d1f29db3d6da4a11be41813296d Mon Sep 17 00:00:00 2001 From: Dimitri Fedrau Date: Tue, 2 Jul 2024 11:03:07 +0200 Subject: [PATCH 079/152] dt-bindings: power: supply: add support for MAX17201/MAX17205 fuel gauge Adding documentation for MAXIMs MAX17201/MAX17205 fuel gauge. Reviewed-by: Conor Dooley Signed-off-by: Dimitri Fedrau Link: https://lore.kernel.org/r/20240702090308.8848-2-dima.fedrau@gmail.com Signed-off-by: Sebastian Reichel --- .../bindings/power/supply/maxim,max17201.yaml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/supply/maxim,max17201.yaml diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max17201.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max17201.yaml new file mode 100644 index 000000000000..fe3dd9bd5585 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/maxim,max17201.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/maxim,max17201.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Maxim MAX17201 fuel gauge + +maintainers: + - Dimitri Fedrau + +allOf: + - $ref: power-supply.yaml# + +properties: + compatible: + oneOf: + - const: maxim,max17201 + - items: + - enum: + - maxim,max17205 + - const: maxim,max17201 + + reg: + items: + - description: ModelGauge m5 registers + - description: Nonvolatile registers + + reg-names: + items: + - const: m5 + - const: nvmem + + interrupts: + maxItems: 1 + +required: + - compatible + - reg + - reg-names + +unevaluatedProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + fuel-gauge@36 { + compatible = "maxim,max17201"; + reg = <0x36>, <0xb>; + reg-names = "m5", "nvmem"; + interrupt-parent = <&gpio0>; + interrupts = <31 IRQ_TYPE_LEVEL_LOW>; + }; + }; From 479b6d04964b5abe66c9f5080fad3389b34e2a70 Mon Sep 17 00:00:00 2001 From: Dimitri Fedrau Date: Tue, 2 Jul 2024 11:03:08 +0200 Subject: [PATCH 080/152] power: supply: add support for MAX1720x standalone fuel gauge The MAX17201 monitors a single cell pack. The MAX17205 monitors and balances a 2S or 3S pack or monitors a multiple-series cell pack. Both devices use a I2C interface. Signed-off-by: Dimitri Fedrau Link: https://lore.kernel.org/r/20240702090308.8848-3-dima.fedrau@gmail.com Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 12 + drivers/power/supply/Makefile | 1 + drivers/power/supply/max1720x_battery.c | 337 ++++++++++++++++++++++++ 3 files changed, 350 insertions(+) create mode 100644 drivers/power/supply/max1720x_battery.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 75b4f65c7d77..4c2a30741755 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -411,6 +411,18 @@ config BATTERY_MAX17042 Driver can be build as a module (max17042_battery). +config BATTERY_MAX1720X + tristate "Maxim MAX17201/MAX17205 Fuel Gauge" + depends on I2C + select REGMAP_I2C + help + MAX1720x is a family of fuel-gauge systems for lithium-ion (Li+) + batteries in handheld and portable equipment. MAX17201 are + configured to operate with a single lithium cell, the MAX17205 + can operate with multiple cells. + + Say Y to include support for the MAX17201/MAX17205 Fuel Gauges. + config BATTERY_MAX1721X tristate "MAX17211/MAX17215 standalone gas-gauge" depends on W1 diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 8ebbdcf92dac..536e25a97fd5 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_CHARGER_DA9150) += da9150-charger.o obj-$(CONFIG_BATTERY_DA9150) += da9150-fg.o obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o +obj-$(CONFIG_BATTERY_MAX1720X) += max1720x_battery.o obj-$(CONFIG_BATTERY_MAX1721X) += max1721x_battery.o obj-$(CONFIG_BATTERY_RT5033) += rt5033_battery.o obj-$(CONFIG_CHARGER_RT5033) += rt5033_charger.o diff --git a/drivers/power/supply/max1720x_battery.c b/drivers/power/supply/max1720x_battery.c new file mode 100644 index 000000000000..edc262f0a62f --- /dev/null +++ b/drivers/power/supply/max1720x_battery.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Fuel gauge driver for Maxim 17201/17205 + * + * based on max1721x_battery.c + * + * Copyright (C) 2024 Liebherr-Electronics and Drives GmbH + */ + +#include +#include +#include +#include +#include + +#include + +/* Nonvolatile registers */ +#define MAX1720X_NRSENSE 0xCF /* RSense in 10^-5 Ohm */ + +/* ModelGauge m5 */ +#define MAX172XX_STATUS 0x00 /* Status */ +#define MAX172XX_STATUS_BAT_ABSENT BIT(3) /* Battery absent */ +#define MAX172XX_REPCAP 0x05 /* Average capacity */ +#define MAX172XX_REPSOC 0x06 /* Percentage of charge */ +#define MAX172XX_TEMP 0x08 /* Temperature */ +#define MAX172XX_CURRENT 0x0A /* Actual current */ +#define MAX172XX_AVG_CURRENT 0x0B /* Average current */ +#define MAX172XX_TTE 0x11 /* Time to empty */ +#define MAX172XX_AVG_TA 0x16 /* Average temperature */ +#define MAX172XX_CYCLES 0x17 +#define MAX172XX_DESIGN_CAP 0x18 /* Design capacity */ +#define MAX172XX_AVG_VCELL 0x19 +#define MAX172XX_TTF 0x20 /* Time to full */ +#define MAX172XX_DEV_NAME 0x21 /* Device name */ +#define MAX172XX_DEV_NAME_TYPE_MASK GENMASK(3, 0) +#define MAX172XX_DEV_NAME_TYPE_MAX17201 BIT(0) +#define MAX172XX_DEV_NAME_TYPE_MAX17205 (BIT(0) | BIT(2)) +#define MAX172XX_QR_TABLE10 0x22 +#define MAX172XX_BATT 0xDA /* Battery voltage */ +#define MAX172XX_ATAVCAP 0xDF + +static const char *const max1720x_manufacturer = "Maxim Integrated"; +static const char *const max17201_model = "MAX17201"; +static const char *const max17205_model = "MAX17205"; + +struct max1720x_device_info { + struct regmap *regmap; + int rsense; +}; + +/* + * Model Gauge M5 Algorithm output register + * Volatile data (must not be cached) + */ +static const struct regmap_range max1720x_volatile_allow[] = { + regmap_reg_range(MAX172XX_STATUS, MAX172XX_CYCLES), + regmap_reg_range(MAX172XX_AVG_VCELL, MAX172XX_TTF), + regmap_reg_range(MAX172XX_QR_TABLE10, MAX172XX_ATAVCAP), +}; + +static const struct regmap_range max1720x_readable_allow[] = { + regmap_reg_range(MAX172XX_STATUS, MAX172XX_ATAVCAP), +}; + +static const struct regmap_range max1720x_readable_deny[] = { + /* unused registers */ + regmap_reg_range(0x24, 0x26), + regmap_reg_range(0x30, 0x31), + regmap_reg_range(0x33, 0x34), + regmap_reg_range(0x37, 0x37), + regmap_reg_range(0x3B, 0x3C), + regmap_reg_range(0x40, 0x41), + regmap_reg_range(0x43, 0x44), + regmap_reg_range(0x47, 0x49), + regmap_reg_range(0x4B, 0x4C), + regmap_reg_range(0x4E, 0xAF), + regmap_reg_range(0xB1, 0xB3), + regmap_reg_range(0xB5, 0xB7), + regmap_reg_range(0xBF, 0xD0), + regmap_reg_range(0xDB, 0xDB), + regmap_reg_range(0xE0, 0xFF), +}; + +static const struct regmap_access_table max1720x_readable_regs = { + .yes_ranges = max1720x_readable_allow, + .n_yes_ranges = ARRAY_SIZE(max1720x_readable_allow), + .no_ranges = max1720x_readable_deny, + .n_no_ranges = ARRAY_SIZE(max1720x_readable_deny), +}; + +static const struct regmap_access_table max1720x_volatile_regs = { + .yes_ranges = max1720x_volatile_allow, + .n_yes_ranges = ARRAY_SIZE(max1720x_volatile_allow), + .no_ranges = max1720x_readable_deny, + .n_no_ranges = ARRAY_SIZE(max1720x_readable_deny), +}; + +static const struct regmap_config max1720x_regmap_cfg = { + .reg_bits = 8, + .val_bits = 16, + .max_register = MAX172XX_ATAVCAP, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + .rd_table = &max1720x_readable_regs, + .volatile_table = &max1720x_volatile_regs, + .cache_type = REGCACHE_RBTREE, +}; + +static const enum power_supply_property max1720x_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_AVG, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +/* Convert regs value to power_supply units */ + +static int max172xx_time_to_ps(unsigned int reg) +{ + return reg * 5625 / 1000; /* in sec. */ +} + +static int max172xx_percent_to_ps(unsigned int reg) +{ + return reg / 256; /* in percent from 0 to 100 */ +} + +static int max172xx_voltage_to_ps(unsigned int reg) +{ + return reg * 1250; /* in uV */ +} + +static int max172xx_capacity_to_ps(unsigned int reg) +{ + return reg * 500; /* in uAh */ +} + +/* + * Current and temperature is signed values, so unsigned regs + * value must be converted to signed type + */ + +static int max172xx_temperature_to_ps(unsigned int reg) +{ + int val = (int16_t)reg; + + return val * 10 / 256; /* in tenths of deg. C */ +} + +/* + * Calculating current registers resolution: + * + * RSense stored in 10^-5 Ohm, so mesaurment voltage must be + * in 10^-11 Volts for get current in uA. + * 16 bit current reg fullscale +/-51.2mV is 102400 uV. + * So: 102400 / 65535 * 10^5 = 156252 + */ +static int max172xx_current_to_voltage(unsigned int reg) +{ + int val = (int16_t)reg; + + return val * 156252; +} + +static int max1720x_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max1720x_device_info *info = power_supply_get_drvdata(psy); + unsigned int reg_val; + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + /* + * POWER_SUPPLY_PROP_PRESENT will always readable via + * sysfs interface. Value return 0 if battery not + * present or unaccesable via I2c. + */ + ret = regmap_read(info->regmap, MAX172XX_STATUS, ®_val); + if (ret < 0) { + val->intval = 0; + return 0; + } + + val->intval = !FIELD_GET(MAX172XX_STATUS_BAT_ABSENT, reg_val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = regmap_read(info->regmap, MAX172XX_REPSOC, ®_val); + val->intval = max172xx_percent_to_ps(reg_val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = regmap_read(info->regmap, MAX172XX_BATT, ®_val); + val->intval = max172xx_voltage_to_ps(reg_val); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + ret = regmap_read(info->regmap, MAX172XX_DESIGN_CAP, ®_val); + val->intval = max172xx_capacity_to_ps(reg_val); + break; + case POWER_SUPPLY_PROP_CHARGE_AVG: + ret = regmap_read(info->regmap, MAX172XX_REPCAP, ®_val); + val->intval = max172xx_capacity_to_ps(reg_val); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + ret = regmap_read(info->regmap, MAX172XX_TTE, ®_val); + val->intval = max172xx_time_to_ps(reg_val); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + ret = regmap_read(info->regmap, MAX172XX_TTF, ®_val); + val->intval = max172xx_time_to_ps(reg_val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = regmap_read(info->regmap, MAX172XX_TEMP, ®_val); + val->intval = max172xx_temperature_to_ps(reg_val); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = regmap_read(info->regmap, MAX172XX_CURRENT, ®_val); + val->intval = max172xx_current_to_voltage(reg_val) / info->rsense; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + ret = regmap_read(info->regmap, MAX172XX_AVG_CURRENT, ®_val); + val->intval = max172xx_current_to_voltage(reg_val) / info->rsense; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + ret = regmap_read(info->regmap, MAX172XX_DEV_NAME, ®_val); + reg_val = FIELD_GET(MAX172XX_DEV_NAME_TYPE_MASK, reg_val); + if (reg_val == MAX172XX_DEV_NAME_TYPE_MAX17201) + val->strval = max17201_model; + else if (reg_val == MAX172XX_DEV_NAME_TYPE_MAX17205) + val->strval = max17205_model; + else + return -ENODEV; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = max1720x_manufacturer; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int max1720x_probe_sense_resistor(struct i2c_client *client, + struct max1720x_device_info *info) +{ + struct device *dev = &client->dev; + struct i2c_client *ancillary; + int ret; + + ancillary = i2c_new_ancillary_device(client, "nvmem", 0xb); + if (IS_ERR(ancillary)) { + dev_err(dev, "Failed to initialize ancillary i2c device\n"); + return PTR_ERR(ancillary); + } + + ret = i2c_smbus_read_word_data(ancillary, MAX1720X_NRSENSE); + i2c_unregister_device(ancillary); + if (ret < 0) + return ret; + + info->rsense = ret; + if (!info->rsense) { + dev_warn(dev, "RSense not calibrated, set 10 mOhms!\n"); + info->rsense = 1000; /* in regs in 10^-5 */ + } + + return 0; +} + +static const struct power_supply_desc max1720x_bat_desc = { + .name = "max1720x", + .no_thermal = true, + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = max1720x_battery_props, + .num_properties = ARRAY_SIZE(max1720x_battery_props), + .get_property = max1720x_battery_get_property, +}; + +static int max1720x_probe(struct i2c_client *client) +{ + struct power_supply_config psy_cfg = {}; + struct device *dev = &client->dev; + struct max1720x_device_info *info; + struct power_supply *bat; + int ret; + + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + psy_cfg.drv_data = info; + psy_cfg.fwnode = dev_fwnode(dev); + info->regmap = devm_regmap_init_i2c(client, &max1720x_regmap_cfg); + if (IS_ERR(info->regmap)) + return dev_err_probe(dev, PTR_ERR(info->regmap), + "regmap initialization failed\n"); + + ret = max1720x_probe_sense_resistor(client, info); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read sense resistor value\n"); + + bat = devm_power_supply_register(dev, &max1720x_bat_desc, &psy_cfg); + if (IS_ERR(bat)) + return dev_err_probe(dev, PTR_ERR(bat), + "Failed to register power supply\n"); + + return 0; +} + +static const struct of_device_id max1720x_of_match[] = { + { .compatible = "maxim,max17201" }, + {} +}; +MODULE_DEVICE_TABLE(of, max1720x_of_match); + +static struct i2c_driver max1720x_i2c_driver = { + .driver = { + .name = "max1720x", + .of_match_table = max1720x_of_match, + }, + .probe = max1720x_probe, +}; +module_i2c_driver(max1720x_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dimitri Fedrau "); +MODULE_DESCRIPTION("Maxim MAX17201/MAX17205 Fuel Gauge IC driver"); From 29832adac01a1057e89579c977ef4776623ee463 Mon Sep 17 00:00:00 2001 From: Abel Vesa Date: Fri, 21 Jun 2024 10:16:35 +0300 Subject: [PATCH 081/152] power: supply: qcom_battmgr: Enable battery support on x1e80100 The x1e80100, being a compute platform, provides functionality for the exact same power supplies as sc8280xp. Add the compatible and assign the sc8280xp match data. Signed-off-by: Abel Vesa Reviewed-by: Johan Hovold Tested-by: Johan Hovold Link: https://lore.kernel.org/r/20240621-x1e80100-power-supply-qcom-battmgr-v1-1-40cb89a0c144@linaro.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/qcom_battmgr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index ec163d1bcd18..46f36dcb185c 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -1308,6 +1308,7 @@ static void qcom_battmgr_pdr_notify(void *priv, int state) static const struct of_device_id qcom_battmgr_of_variants[] = { { .compatible = "qcom,sc8180x-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP }, { .compatible = "qcom,sc8280xp-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP }, + { .compatible = "qcom,x1e80100-pmic-glink", .data = (void *)QCOM_BATTMGR_SC8280XP }, /* Unmatched devices falls back to QCOM_BATTMGR_SM8350 */ {} }; From be6299c6e55e971ffc060495708740a59aa0e45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 8 Jun 2024 21:19:37 +0200 Subject: [PATCH 082/152] power: supply: sysfs: use power_supply_property_is_writeable() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of open-coding the helper use it directly. Signed-off-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20240608-power-supply-extensions-v2-1-2dcd35b012ad@weissschuh.net Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_sysfs.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index b86e11bdc07e..3e63d165b2f7 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -379,8 +379,7 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj, int property = psy->desc->properties[i]; if (property == attrno) { - if (psy->desc->property_is_writeable && - psy->desc->property_is_writeable(psy, property) > 0) + if (power_supply_property_is_writeable(psy, property) > 0) mode |= S_IWUSR; return mode; From d1368f62a8dca89ce55ca1a7568ca273f50d60e4 Mon Sep 17 00:00:00 2001 From: "Jason-JH.Lin" Date: Wed, 24 Jan 2024 09:14:57 +0800 Subject: [PATCH 083/152] dt-bindings: mailbox: Add mediatek,gce-props.yaml Add mediatek,gce-props.yaml for common GCE properties that is used for both mailbox providers and consumers. We place the common property "mediatek,gce-events" in this binding currently. The property "mediatek,gce-events" is used for GCE event ID corresponding to a hardware event signal sent by the hardware or a software driver. If the mailbox providers or consumers want to manipulate the value of the event ID, they need to know the specific event ID. Signed-off-by: Jason-JH.Lin Reviewed-by: Conor Dooley Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Jassi Brar --- .../bindings/mailbox/mediatek,gce-props.yaml | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Documentation/devicetree/bindings/mailbox/mediatek,gce-props.yaml diff --git a/Documentation/devicetree/bindings/mailbox/mediatek,gce-props.yaml b/Documentation/devicetree/bindings/mailbox/mediatek,gce-props.yaml new file mode 100644 index 000000000000..c25eed4606fe --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/mediatek,gce-props.yaml @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mailbox/mediatek,gce-props.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MediaTek Global Command Engine Common Properties + +maintainers: + - Houlong Wei + +description: + The Global Command Engine (GCE) is an instruction based, multi-threaded, + single-core command dispatcher for MediaTek hardware. The Command Queue + (CMDQ) mailbox driver is a driver for GCE, implemented using the Linux + mailbox framework. It is used to receive messages from mailbox consumers + and configure GCE to execute the specified instruction set in the message. + We use mediatek,gce-mailbox.yaml to define the properties for CMDQ mailbox + driver. A device driver that uses the CMDQ driver to configure its hardware + registers is a mailbox consumer. The mailbox consumer can request a mailbox + channel corresponding to a GCE hardware thread to send a message, specifying + that the GCE thread to configure its hardware. The mailbox provider can also + reserve a mailbox channel to configure GCE hardware register by the specific + GCE thread. This binding defines the common GCE properties for both mailbox + provider and consumers. + +properties: + mediatek,gce-events: + description: + GCE has an event table in SRAM, consisting of 1024 event IDs (0~1023). + Each event ID has a boolean event value with the default value 0. + The property mediatek,gce-events is used to obtain the event IDs. + Some gce-events are hardware-bound and cannot be changed by software. + For instance, in MT8195, when VDO0_MUTEX is stream done, VDO_MUTEX will + send an event signal to GCE, setting the value of event ID 597 to 1. + Similarly, in MT8188, the value of event ID 574 will be set to 1 when + VOD0_MUTEX is stream done. + On the other hand, some gce-events are not hardware-bound and can be + changed by software. For example, in MT8188, we can set the value of + event ID 855, which is not bound to any hardware, to 1 when the driver + in the secure world completes a task. However, in MT8195, event ID 855 + is already bound to VDEC_LAT1, so we need to select another event ID to + achieve the same purpose. This event ID can be any ID that is not bound + to any hardware and is not yet used in any software driver. + To determine if the event ID is bound to the hardware or used by a + software driver, refer to the GCE header + include/dt-bindings/gce/-gce.h of each chip. + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 32 + +additionalProperties: true From db9344804cfa7ceca17aab71a3130736a7a2ac9d Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 20 Jun 2024 12:07:47 +0200 Subject: [PATCH 084/152] mailbox: mtk-cmdq: Stop requiring name for GCE clock The Global Command Engine mailbox has only one clock hence requiring clock-names is useless. Get the first (and only) clock instead, without name checks. Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Jassi Brar --- drivers/mailbox/mtk-cmdq-mailbox.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c index 4aa394e91109..66dbc8e846d8 100644 --- a/drivers/mailbox/mtk-cmdq-mailbox.c +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -629,7 +629,7 @@ static int cmdq_probe(struct platform_device *pdev) } } else { cmdq->clocks[alias_id].id = clk_name; - cmdq->clocks[alias_id].clk = devm_clk_get(&pdev->dev, clk_name); + cmdq->clocks[alias_id].clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(cmdq->clocks[alias_id].clk)) { return dev_err_probe(dev, PTR_ERR(cmdq->clocks[alias_id].clk), "failed to get gce clk\n"); From 0af932c5fc44d2709c9f87a15bcd6cd376901e23 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 20 Jun 2024 12:07:48 +0200 Subject: [PATCH 085/152] mailbox: mtk-cmdq: Move and partially refactor clocks probe Move the clocks probe to a new cmdq_get_clocks() function; while at it, partially refactor the code: Drop the clk_names[] array and assign clock names to the array of clk_bulk_data with devm_kasprintf() instead, slightly reduce the indentation for the multi-gce clock probe path and add a comment describing the reason why we get clocks of other GCE instance instead of just the clock from the one that it is getting probed. Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Jassi Brar --- drivers/mailbox/mtk-cmdq-mailbox.c | 79 +++++++++++++++++++----------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c index 66dbc8e846d8..a3f57151a221 100644 --- a/drivers/mailbox/mtk-cmdq-mailbox.c +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -578,16 +578,59 @@ static struct mbox_chan *cmdq_xlate(struct mbox_controller *mbox, return &mbox->chans[ind]; } +static int cmdq_get_clocks(struct device *dev, struct cmdq *cmdq) +{ + static const char * const gce_name = "gce"; + struct device_node *node, *parent = dev->of_node->parent; + struct clk_bulk_data *clks; + + if (cmdq->pdata->gce_num == 1) { + clks = &cmdq->clocks[0]; + + clks->id = gce_name; + clks->clk = devm_clk_get(dev, NULL); + if (IS_ERR(clks->clk)) + return dev_err_probe(dev, PTR_ERR(clks->clk), + "failed to get gce clock\n"); + + return 0; + } + + /* + * If there is more than one GCE, get the clocks for the others too, + * as the clock of the main GCE must be enabled for additional IPs + * to be reachable. + */ + for_each_child_of_node(parent, node) { + int alias_id = of_alias_get_id(node, gce_name); + + if (alias_id < 0 || alias_id >= cmdq->pdata->gce_num) + continue; + + clks = &cmdq->clocks[alias_id]; + + clks->id = devm_kasprintf(dev, GFP_KERNEL, "gce%d", alias_id); + if (!clks->id) { + of_node_put(node); + return -ENOMEM; + } + + clks->clk = of_clk_get(node, 0); + if (IS_ERR(clks->clk)) { + of_node_put(node); + return dev_err_probe(dev, PTR_ERR(clks->clk), + "failed to get gce%d clock\n", alias_id); + } + } + + return 0; +} + static int cmdq_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct cmdq *cmdq; int err, i; - struct device_node *phandle = dev->of_node; - struct device_node *node; - int alias_id = 0; - static const char * const clk_name = "gce"; - static const char * const clk_names[] = { "gce0", "gce1" }; cmdq = devm_kzalloc(dev, sizeof(*cmdq), GFP_KERNEL); if (!cmdq) @@ -612,29 +655,9 @@ static int cmdq_probe(struct platform_device *pdev) dev_dbg(dev, "cmdq device: addr:0x%p, va:0x%p, irq:%d\n", dev, cmdq->base, cmdq->irq); - if (cmdq->pdata->gce_num > 1) { - for_each_child_of_node(phandle->parent, node) { - alias_id = of_alias_get_id(node, clk_name); - if (alias_id >= 0 && alias_id < cmdq->pdata->gce_num) { - cmdq->clocks[alias_id].id = clk_names[alias_id]; - cmdq->clocks[alias_id].clk = of_clk_get(node, 0); - if (IS_ERR(cmdq->clocks[alias_id].clk)) { - of_node_put(node); - return dev_err_probe(dev, - PTR_ERR(cmdq->clocks[alias_id].clk), - "failed to get gce clk: %d\n", - alias_id); - } - } - } - } else { - cmdq->clocks[alias_id].id = clk_name; - cmdq->clocks[alias_id].clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(cmdq->clocks[alias_id].clk)) { - return dev_err_probe(dev, PTR_ERR(cmdq->clocks[alias_id].clk), - "failed to get gce clk\n"); - } - } + err = cmdq_get_clocks(dev, cmdq); + if (err) + return err; cmdq->mbox.dev = dev; cmdq->mbox.chans = devm_kcalloc(dev, cmdq->pdata->thread_nr, From aa1609f571caba0db102c611829d48adf226bb70 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 20 Jun 2024 12:07:49 +0200 Subject: [PATCH 086/152] mailbox: mtk-cmdq: Dynamically allocate clk_bulk_data structure Now that the clock probing code uses devm_kasprintf(), there is no more restriction on the number of GCEs: dynamically allocate the clk_bulk_data clocks array to improve flexibility and also to get a slight memory saving on platforms featuring only one CMDQ mailbox (and consequently only one Global Command Engine). Signed-off-by: AngeloGioacchino Del Regno Signed-off-by: Jassi Brar --- drivers/mailbox/mtk-cmdq-mailbox.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c index a3f57151a221..2a71a5400088 100644 --- a/drivers/mailbox/mtk-cmdq-mailbox.c +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -22,7 +22,6 @@ #define CMDQ_OP_CODE_MASK (0xff << CMDQ_OP_CODE_SHIFT) #define CMDQ_NUM_CMD(t) (t->cmd_buf_size / CMDQ_INST_SIZE) -#define CMDQ_GCE_NUM_MAX (2) #define CMDQ_CURR_IRQ_STATUS 0x10 #define CMDQ_SYNC_TOKEN_UPDATE 0x68 @@ -81,7 +80,7 @@ struct cmdq { u32 irq_mask; const struct gce_plat *pdata; struct cmdq_thread *thread; - struct clk_bulk_data clocks[CMDQ_GCE_NUM_MAX]; + struct clk_bulk_data *clocks; bool suspended; }; @@ -584,6 +583,11 @@ static int cmdq_get_clocks(struct device *dev, struct cmdq *cmdq) struct device_node *node, *parent = dev->of_node->parent; struct clk_bulk_data *clks; + cmdq->clocks = devm_kcalloc(dev, cmdq->pdata->gce_num, + sizeof(cmdq->clocks), GFP_KERNEL); + if (!cmdq->clocks) + return -ENOMEM; + if (cmdq->pdata->gce_num == 1) { clks = &cmdq->clocks[0]; From 0a02bc0a34cd53c7fe5bf4bae6efb56ad47677fa Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Tue, 11 Jun 2024 12:04:56 -0500 Subject: [PATCH 087/152] mailbox: omap: Fix mailbox interrupt sharing Multiple mailbox users can share one interrupt line. This flag was mistakenly dropped as part of the FIFO removal. Mark the IRQ as shared. Reported-by: Beleswar Padhi Fixes: 3f58c1f4206f ("mailbox: omap: Remove kernel FIFO message queuing") Signed-off-by: Andrew Davis Tested-by: Beleswar Padhi Signed-off-by: Jassi Brar --- drivers/mailbox/omap-mailbox.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mailbox/omap-mailbox.c b/drivers/mailbox/omap-mailbox.c index 46747559b438..7a87424657a1 100644 --- a/drivers/mailbox/omap-mailbox.c +++ b/drivers/mailbox/omap-mailbox.c @@ -230,7 +230,8 @@ static int omap_mbox_startup(struct omap_mbox *mbox) int ret = 0; ret = request_threaded_irq(mbox->irq, NULL, mbox_interrupt, - IRQF_ONESHOT, mbox->name, mbox); + IRQF_SHARED | IRQF_ONESHOT, mbox->name, + mbox); if (unlikely(ret)) { pr_err("failed to register mailbox interrupt:%d\n", ret); return ret; From b5ef17917f3a797a7b12d1edd51f676554e44a07 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 24 May 2024 15:56:32 +0800 Subject: [PATCH 088/152] mailbox: imx: fix TXDB_V2 channel race condition Two TXDB_V2 channels are used between Linux and System Manager(SM). Channel0 for normal TX, Channel 1 for notification completion. The TXDB_V2 trigger logic is using imx_mu_xcr_rmw which uses read/modify/update logic. Note: clear MUB GSR BITs, the MUA side GCR BITs will also got cleared per hardware design. Channel0 Linux read GCR->modify GCR->write GCR->M33 SM->read GSR----->clear GSR |-(1)-| Channel1 Linux start in time slot(1) read GCR->modify GCR->write GCR->M33 SM->read GSR->clear GSR So Channel1 read GCR will read back the GCR that Channel0 wrote, because M33 has not finish clear GSR, this means Channel1 GCR writing will trigger Channel1 and Channel0 interrupt both which is wrong. Channel0 will be freed(SCMI channel status set to FREE) in M33 SM when processing the 1st Channel0 interrupt. So when 2nd interrupt trigger (channel 0/1 trigger together), SM will see a freed Channel0, and report protocol error. To address the issue, not using read/modify/update logic, just use write, because write 0 to GCR will be ignored. And after write MUA GCR, wait the SM to clear MUB GSR by looping MUA GCR value. Fixes: 5bfe4067d350 ("mailbox: imx: support channel type tx doorbell v2") Reviewed-by: Ranjani Vaidyanathan Signed-off-by: Peng Fan Signed-off-by: Jassi Brar --- drivers/mailbox/imx-mailbox.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/mailbox/imx-mailbox.c b/drivers/mailbox/imx-mailbox.c index 933727f89431..d17efb1dd0cb 100644 --- a/drivers/mailbox/imx-mailbox.c +++ b/drivers/mailbox/imx-mailbox.c @@ -225,6 +225,8 @@ static int imx_mu_generic_tx(struct imx_mu_priv *priv, void *data) { u32 *arg = data; + u32 val; + int ret; switch (cp->type) { case IMX_MU_TYPE_TX: @@ -236,7 +238,13 @@ static int imx_mu_generic_tx(struct imx_mu_priv *priv, queue_work(system_bh_wq, &cp->txdb_work); break; case IMX_MU_TYPE_TXDB_V2: - imx_mu_xcr_rmw(priv, IMX_MU_GCR, IMX_MU_xCR_GIRn(priv->dcfg->type, cp->idx), 0); + imx_mu_write(priv, IMX_MU_xCR_GIRn(priv->dcfg->type, cp->idx), + priv->dcfg->xCR[IMX_MU_GCR]); + ret = readl_poll_timeout(priv->base + priv->dcfg->xCR[IMX_MU_GCR], val, + !(val & IMX_MU_xCR_GIRn(priv->dcfg->type, cp->idx)), + 0, 1000); + if (ret) + dev_warn_ratelimited(priv->dev, "channel type: %d failure\n", cp->type); break; default: dev_warn_ratelimited(priv->dev, "Send data on wrong channel type: %d\n", cp->type); From 63c31d4a9169fd4eebf08c259ba8688d15668ae5 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Thu, 23 May 2024 21:37:41 +0100 Subject: [PATCH 089/152] mailbox: bcm-pdc: remove unused struct 'pdc_dma_map' 'pdf_dma_map' has been unused since the original commit a24532f8d17b ("mailbox: Add Broadcom PDC mailbox driver"). Remove it. Signed-off-by: Dr. David Alan Gilbert Signed-off-by: Jassi Brar --- drivers/mailbox/bcm-pdc-mailbox.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/mailbox/bcm-pdc-mailbox.c b/drivers/mailbox/bcm-pdc-mailbox.c index 242e7504a628..a873672a9082 100644 --- a/drivers/mailbox/bcm-pdc-mailbox.c +++ b/drivers/mailbox/bcm-pdc-mailbox.c @@ -158,10 +158,6 @@ enum pdc_hw { PDC_HW /* PDC/MDE hardware (i.e. Northstar 2, Pegasus) */ }; -struct pdc_dma_map { - void *ctx; /* opaque context associated with frame */ -}; - /* dma descriptor */ struct dma64dd { u32 ctrl1; /* misc control bits */ From 203e038d23c43e37f43e675e6cc7ac0e38ef939d Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Mon, 24 Jun 2024 09:09:48 -0700 Subject: [PATCH 090/152] mailbox: mtk-cmdq: add missing MODULE_DESCRIPTION() macro make allmodconfig && make W=1 C=1 reports: WARNING: modpost: missing MODULE_DESCRIPTION() in drivers/mailbox/mtk-cmdq-mailbox.o Add the missing invocation of the MODULE_DESCRIPTION() macro. Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Jeff Johnson Signed-off-by: Jassi Brar --- drivers/mailbox/mtk-cmdq-mailbox.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c index 2a71a5400088..f1dfce9e27f5 100644 --- a/drivers/mailbox/mtk-cmdq-mailbox.c +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -817,4 +817,5 @@ static void __exit cmdq_drv_exit(void) subsys_initcall(cmdq_drv_init); module_exit(cmdq_drv_exit); +MODULE_DESCRIPTION("Mediatek Command Queue(CMDQ) Mailbox driver"); MODULE_LICENSE("GPL v2"); From af5da7b0944c3616fa6add186043637092d94200 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Wed, 5 Jun 2024 18:56:51 +0200 Subject: [PATCH 091/152] dt-bindings: remoteproc: qcom,sa8775p-pas: Document the SA8775p ADSP, CDSP and GPDSP Document the components used to boot the ADSP, CDSP0, CDSP1, GPDSP0 and GPDSP1 on the SA8775p SoC. Signed-off-by: Bartosz Golaszewski Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jassi Brar --- .../bindings/remoteproc/qcom,sa8775p-pas.yaml | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml new file mode 100644 index 000000000000..7fe401a06805 --- /dev/null +++ b/Documentation/devicetree/bindings/remoteproc/qcom,sa8775p-pas.yaml @@ -0,0 +1,160 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/remoteproc/qcom,sa8775p-pas.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm SA8775p Peripheral Authentication Service + +maintainers: + - Bartosz Golaszewski + +description: + Qualcomm SA8775p SoC Peripheral Authentication Service loads and boots firmware + on the Qualcomm DSP Hexagon cores. + +properties: + compatible: + enum: + - qcom,sa8775p-adsp-pas + - qcom,sa8775p-cdsp0-pas + - qcom,sa8775p-cdsp1-pas + - qcom,sa8775p-gpdsp0-pas + - qcom,sa8775p-gpdsp1-pas + + reg: + maxItems: 1 + + clocks: + items: + - description: XO clock + + clock-names: + items: + - const: xo + + qcom,qmp: + $ref: /schemas/types.yaml#/definitions/phandle + description: Reference to the AOSS side-channel message RAM. + + firmware-name: + $ref: /schemas/types.yaml#/definitions/string-array + items: + - description: Firmware name of the Hexagon core + + memory-region: + items: + - description: Memory region for main Firmware authentication + + interrupts: + maxItems: 5 + + interrupt-names: + maxItems: 5 + +required: + - compatible + - reg + - memory-region + +allOf: + - $ref: /schemas/remoteproc/qcom,pas-common.yaml# + + - if: + properties: + compatible: + enum: + - qcom,sa8775p-adsp-pas + then: + properties: + power-domains: + items: + - description: LCX power domain + - description: LMX power domain + power-domain-names: + items: + - const: lcx + - const: lmx + + - if: + properties: + compatible: + enum: + - qcom,sa8775p-cdsp0-pas + - qcom,sa8775p-cdsp1-pas + then: + properties: + power-domains: + items: + - description: CX power domain + - description: MXC power domain + - description: NSP0 power domain + power-domain-names: + items: + - const: cx + - const: mxc + - const: nsp + + - if: + properties: + compatible: + enum: + - qcom,sa8775p-gpdsp0-pas + - qcom,sa8775p-gpdsp1-pas + then: + properties: + power-domains: + items: + - description: CX power domain + - description: MXC power domain + power-domain-names: + items: + - const: cx + - const: mxc + +unevaluatedProperties: false + +examples: + - | + #include + #include + #include + #include + #include + + remoteproc@30000000 { + compatible = "qcom,sa8775p-adsp-pas"; + reg = <0x30000000 0x100>; + + interrupts-extended = <&pdc 6 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp_in 0 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp_in 2 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp_in 1 IRQ_TYPE_EDGE_RISING>, + <&smp2p_adsp_in 3 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "wdog", "fatal", "ready", "handover", "stop-ack"; + + clocks = <&rpmhcc RPMH_CXO_CLK>; + clock-names = "xo"; + + power-domains = <&rpmhpd RPMHPD_LCX>, <&rpmhpd RPMHPD_LMX>; + power-domain-names = "lcx", "lmx"; + + interconnects = <&lpass_ag_noc MASTER_LPASS_PROC 0 &mc_virt SLAVE_EBI1 0>; + + memory-region = <&pil_adsp_mem>; + + qcom,qmp = <&aoss_qmp>; + + qcom,smem-states = <&smp2p_adsp_out 0>; + qcom,smem-state-names = "stop"; + + glink-edge { + interrupts-extended = <&ipcc IPCC_CLIENT_LPASS + IPCC_MPROC_SIGNAL_GLINK_QMP + IRQ_TYPE_EDGE_RISING>; + mboxes = <&ipcc IPCC_CLIENT_LPASS IPCC_MPROC_SIGNAL_GLINK_QMP>; + + label = "lpass"; + qcom,remote-pid = <2>; + }; + }; From 6e7c4cc55dbc461c08e00f0ef16867fe6bf014aa Mon Sep 17 00:00:00 2001 From: Sibi Sankar Date: Wed, 12 Jun 2024 18:10:52 +0530 Subject: [PATCH 092/152] dt-bindings: mailbox: qcom: Add CPUCP mailbox controller bindings Add devicetree binding for CPUSS Control Processor (CPUCP) mailbox controller. Reviewed-by: Rob Herring Signed-off-by: Sibi Sankar Signed-off-by: Jassi Brar --- .../bindings/mailbox/qcom,cpucp-mbox.yaml | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Documentation/devicetree/bindings/mailbox/qcom,cpucp-mbox.yaml diff --git a/Documentation/devicetree/bindings/mailbox/qcom,cpucp-mbox.yaml b/Documentation/devicetree/bindings/mailbox/qcom,cpucp-mbox.yaml new file mode 100644 index 000000000000..f7342d04beec --- /dev/null +++ b/Documentation/devicetree/bindings/mailbox/qcom,cpucp-mbox.yaml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mailbox/qcom,cpucp-mbox.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Qualcomm Technologies, Inc. CPUCP Mailbox Controller + +maintainers: + - Sibi Sankar + +description: + The CPUSS Control Processor (CPUCP) mailbox controller enables communication + between AP and CPUCP by acting as a doorbell between them. + +properties: + compatible: + items: + - const: qcom,x1e80100-cpucp-mbox + + reg: + items: + - description: CPUCP rx register region + - description: CPUCP tx register region + + interrupts: + maxItems: 1 + + "#mbox-cells": + const: 1 + +required: + - compatible + - reg + - interrupts + - "#mbox-cells" + +additionalProperties: false + +examples: + - | + #include + + mailbox@17430000 { + compatible = "qcom,x1e80100-cpucp-mbox"; + reg = <0x17430000 0x10000>, <0x18830000 0x10000>; + interrupts = ; + #mbox-cells = <1>; + }; From 0e2a9a03106cd5fa0dbc9047675e7645c55e2669 Mon Sep 17 00:00:00 2001 From: Sibi Sankar Date: Wed, 12 Jun 2024 18:10:53 +0530 Subject: [PATCH 093/152] mailbox: Add support for QTI CPUCP mailbox controller Add support for CPUSS Control Processor (CPUCP) mailbox controller, this driver enables communication between AP and CPUCP by acting as a doorbell between them. Reviewed-by: Dmitry Baryshkov Signed-off-by: Sibi Sankar Reviewed-by: Bjorn Andersson Reviewed-by: Konrad Dybcio Signed-off-by: Jassi Brar --- MAINTAINERS | 7 ++ drivers/mailbox/Kconfig | 8 ++ drivers/mailbox/Makefile | 2 + drivers/mailbox/qcom-cpucp-mbox.c | 187 ++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 drivers/mailbox/qcom-cpucp-mbox.c diff --git a/MAINTAINERS b/MAINTAINERS index da5352dbd4f3..b28f4c628b7b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18461,6 +18461,13 @@ S: Maintained F: Documentation/devicetree/bindings/power/avs/qcom,cpr.yaml F: drivers/pmdomain/qcom/cpr.c +QUALCOMM CPUCP MAILBOX DRIVER +M: Sibi Sankar +L: linux-arm-msm@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/mailbox/qcom,cpucp-mbox.yaml +F: drivers/mailbox/qcom-cpucp-mbox.c + QUALCOMM CPUFREQ DRIVER MSM8996/APQ8096 M: Ilia Lin L: linux-pm@vger.kernel.org diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index 3b8842c4a340..d1f6c758b5e8 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -276,6 +276,14 @@ config SPRD_MBOX to send message between application processors and MCU. Say Y here if you want to build the Spreatrum mailbox controller driver. +config QCOM_CPUCP_MBOX + tristate "Qualcomm Technologies, Inc. CPUCP mailbox driver" + depends on ARCH_QCOM || (COMPILE_TEST && 64BIT) + help + Qualcomm Technologies, Inc. CPUSS Control Processor (CPUCP) mailbox + controller driver enables communication between AP and CPUCP. Say + Y here if you want to build this driver. + config QCOM_IPCC tristate "Qualcomm Technologies, Inc. IPCC driver" depends on ARCH_QCOM || COMPILE_TEST diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index 5cf2f54debaf..3c3c27d54c13 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -61,4 +61,6 @@ obj-$(CONFIG_SUN6I_MSGBOX) += sun6i-msgbox.o obj-$(CONFIG_SPRD_MBOX) += sprd-mailbox.o +obj-$(CONFIG_QCOM_CPUCP_MBOX) += qcom-cpucp-mbox.o + obj-$(CONFIG_QCOM_IPCC) += qcom-ipcc.o diff --git a/drivers/mailbox/qcom-cpucp-mbox.c b/drivers/mailbox/qcom-cpucp-mbox.c new file mode 100644 index 000000000000..e5437c294803 --- /dev/null +++ b/drivers/mailbox/qcom-cpucp-mbox.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define APSS_CPUCP_IPC_CHAN_SUPPORTED 3 +#define APSS_CPUCP_MBOX_CMD_OFF 0x4 + +/* Tx Registers */ +#define APSS_CPUCP_TX_MBOX_CMD(i) (0x100 + ((i) * 8)) + +/* Rx Registers */ +#define APSS_CPUCP_RX_MBOX_CMD(i) (0x100 + ((i) * 8)) +#define APSS_CPUCP_RX_MBOX_MAP 0x4000 +#define APSS_CPUCP_RX_MBOX_STAT 0x4400 +#define APSS_CPUCP_RX_MBOX_CLEAR 0x4800 +#define APSS_CPUCP_RX_MBOX_EN 0x4c00 +#define APSS_CPUCP_RX_MBOX_CMD_MASK GENMASK_ULL(63, 0) + +/** + * struct qcom_cpucp_mbox - Holder for the mailbox driver + * @chans: The mailbox channel + * @mbox: The mailbox controller + * @tx_base: Base address of the CPUCP tx registers + * @rx_base: Base address of the CPUCP rx registers + */ +struct qcom_cpucp_mbox { + struct mbox_chan chans[APSS_CPUCP_IPC_CHAN_SUPPORTED]; + struct mbox_controller mbox; + void __iomem *tx_base; + void __iomem *rx_base; +}; + +static inline int channel_number(struct mbox_chan *chan) +{ + return chan - chan->mbox->chans; +} + +static irqreturn_t qcom_cpucp_mbox_irq_fn(int irq, void *data) +{ + struct qcom_cpucp_mbox *cpucp = data; + u64 status; + int i; + + status = readq(cpucp->rx_base + APSS_CPUCP_RX_MBOX_STAT); + + for_each_set_bit(i, (unsigned long *)&status, APSS_CPUCP_IPC_CHAN_SUPPORTED) { + u32 val = readl(cpucp->rx_base + APSS_CPUCP_RX_MBOX_CMD(i) + APSS_CPUCP_MBOX_CMD_OFF); + struct mbox_chan *chan = &cpucp->chans[i]; + unsigned long flags; + + /* Provide mutual exclusion with changes to chan->cl */ + spin_lock_irqsave(&chan->lock, flags); + if (chan->cl) + mbox_chan_received_data(chan, &val); + writeq(BIT(i), cpucp->rx_base + APSS_CPUCP_RX_MBOX_CLEAR); + spin_unlock_irqrestore(&chan->lock, flags); + } + + return IRQ_HANDLED; +} + +static int qcom_cpucp_mbox_startup(struct mbox_chan *chan) +{ + struct qcom_cpucp_mbox *cpucp = container_of(chan->mbox, struct qcom_cpucp_mbox, mbox); + unsigned long chan_id = channel_number(chan); + u64 val; + + val = readq(cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN); + val |= BIT(chan_id); + writeq(val, cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN); + + return 0; +} + +static void qcom_cpucp_mbox_shutdown(struct mbox_chan *chan) +{ + struct qcom_cpucp_mbox *cpucp = container_of(chan->mbox, struct qcom_cpucp_mbox, mbox); + unsigned long chan_id = channel_number(chan); + u64 val; + + val = readq(cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN); + val &= ~BIT(chan_id); + writeq(val, cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN); +} + +static int qcom_cpucp_mbox_send_data(struct mbox_chan *chan, void *data) +{ + struct qcom_cpucp_mbox *cpucp = container_of(chan->mbox, struct qcom_cpucp_mbox, mbox); + unsigned long chan_id = channel_number(chan); + u32 *val = data; + + writel(*val, cpucp->tx_base + APSS_CPUCP_TX_MBOX_CMD(chan_id) + APSS_CPUCP_MBOX_CMD_OFF); + + return 0; +} + +static const struct mbox_chan_ops qcom_cpucp_mbox_chan_ops = { + .startup = qcom_cpucp_mbox_startup, + .send_data = qcom_cpucp_mbox_send_data, + .shutdown = qcom_cpucp_mbox_shutdown +}; + +static int qcom_cpucp_mbox_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct qcom_cpucp_mbox *cpucp; + struct mbox_controller *mbox; + int irq, ret; + + cpucp = devm_kzalloc(dev, sizeof(*cpucp), GFP_KERNEL); + if (!cpucp) + return -ENOMEM; + + cpucp->rx_base = devm_of_iomap(dev, dev->of_node, 0, NULL); + if (IS_ERR(cpucp->rx_base)) + return PTR_ERR(cpucp->rx_base); + + cpucp->tx_base = devm_of_iomap(dev, dev->of_node, 1, NULL); + if (IS_ERR(cpucp->tx_base)) + return PTR_ERR(cpucp->tx_base); + + writeq(0, cpucp->rx_base + APSS_CPUCP_RX_MBOX_EN); + writeq(0, cpucp->rx_base + APSS_CPUCP_RX_MBOX_CLEAR); + writeq(0, cpucp->rx_base + APSS_CPUCP_RX_MBOX_MAP); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, qcom_cpucp_mbox_irq_fn, + IRQF_TRIGGER_HIGH, "apss_cpucp_mbox", cpucp); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to register irq: %d\n", irq); + + writeq(APSS_CPUCP_RX_MBOX_CMD_MASK, cpucp->rx_base + APSS_CPUCP_RX_MBOX_MAP); + + mbox = &cpucp->mbox; + mbox->dev = dev; + mbox->num_chans = APSS_CPUCP_IPC_CHAN_SUPPORTED; + mbox->chans = cpucp->chans; + mbox->ops = &qcom_cpucp_mbox_chan_ops; + + ret = devm_mbox_controller_register(dev, mbox); + if (ret) + return dev_err_probe(dev, ret, "Failed to create mailbox\n"); + + return 0; +} + +static const struct of_device_id qcom_cpucp_mbox_of_match[] = { + { .compatible = "qcom,x1e80100-cpucp-mbox" }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_cpucp_mbox_of_match); + +static struct platform_driver qcom_cpucp_mbox_driver = { + .probe = qcom_cpucp_mbox_probe, + .driver = { + .name = "qcom_cpucp_mbox", + .of_match_table = qcom_cpucp_mbox_of_match, + }, +}; + +static int __init qcom_cpucp_mbox_init(void) +{ + return platform_driver_register(&qcom_cpucp_mbox_driver); +} +core_initcall(qcom_cpucp_mbox_init); + +static void __exit qcom_cpucp_mbox_exit(void) +{ + platform_driver_unregister(&qcom_cpucp_mbox_driver); +} +module_exit(qcom_cpucp_mbox_exit); + +MODULE_DESCRIPTION("QTI CPUCP MBOX Driver"); +MODULE_LICENSE("GPL"); From 7d2c2048a86477461f7bc75d064579ed349472bc Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 5 Jul 2024 10:31:53 +0100 Subject: [PATCH 094/152] irqchip/gic-v4: Always configure affinity on VPE activation There are currently two paths to set the initial affinity of a VPE: - at activation time on GICv4 without the stupid VMOVP list, and on GICv4.1 - at map time for GICv4 with VMOVP list The latter location may end-up modifying the affinity of VPE that is currently running, making the results unpredictible. Instead, unify the two paths, making sure to set the initial affinity only at activation time. Reported-by: Nianyao Tang Signed-off-by: Marc Zyngier Signed-off-by: Thomas Gleixner Tested-by: Nianyao Tang Link: https://lore.kernel.org/r/20240705093155.871070-2-maz@kernel.org --- drivers/irqchip/irq-gic-v3-its.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index af5297ef2293..387ac5a31d1d 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -1809,13 +1809,9 @@ static void its_map_vm(struct its_node *its, struct its_vm *vm) for (i = 0; i < vm->nr_vpes; i++) { struct its_vpe *vpe = vm->vpes[i]; - struct irq_data *d = irq_get_irq_data(vpe->irq); - /* Map the VPE to the first possible CPU */ - vpe->col_idx = cpumask_first(cpu_online_mask); its_send_vmapp(its, vpe, true); its_send_vinvall(its, vpe); - irq_data_update_effective_affinity(d, cpumask_of(vpe->col_idx)); } } @@ -4582,6 +4578,10 @@ static int its_vpe_irq_domain_activate(struct irq_domain *domain, struct its_vpe *vpe = irq_data_get_irq_chip_data(d); struct its_node *its; + /* Map the VPE to the first possible CPU */ + vpe->col_idx = cpumask_first(cpu_online_mask); + irq_data_update_effective_affinity(d, cpumask_of(vpe->col_idx)); + /* * If we use the list map, we issue VMAPP on demand... Unless * we're on a GICv4.1 and we eagerly map the VPE on all ITSs @@ -4590,9 +4590,6 @@ static int its_vpe_irq_domain_activate(struct irq_domain *domain, if (!gic_requires_eager_mapping()) return 0; - /* Map the VPE to the first possible CPU */ - vpe->col_idx = cpumask_first(cpu_online_mask); - list_for_each_entry(its, &its_nodes, entry) { if (!is_v4(its)) continue; @@ -4601,8 +4598,6 @@ static int its_vpe_irq_domain_activate(struct irq_domain *domain, its_send_vinvall(its, vpe); } - irq_data_update_effective_affinity(d, cpumask_of(vpe->col_idx)); - return 0; } From f0eb154c39471bf881422e8ac23e4c037289ece9 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 5 Jul 2024 10:31:54 +0100 Subject: [PATCH 095/152] irqchip/gic-v4: Substitute vmovp_lock for a per-VM lock vmovp_lock is abused in a number of cases to serialise updates to vlpi_count[] and deal with map/unmap of a VM to ITSs. Instead, provide a per-VM lock and revisit the use of vlpi_count[] so that it is always wrapped in this per-VM vmapp_lock. This reduces the potential contention on a concurrent VMOVP command, and paves the way for subsequent VPE locking that holding vmovp_lock actively prevents due to the lock ordering. Signed-off-by: Marc Zyngier Signed-off-by: Thomas Gleixner Tested-by: Nianyao Tang Link: https://lore.kernel.org/r/20240705093155.871070-3-maz@kernel.org --- drivers/irqchip/irq-gic-v3-its.c | 27 ++++++++++++--------------- include/linux/irqchip/arm-gic-v4.h | 8 ++++++++ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 387ac5a31d1d..215c7ab74e3a 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -1317,7 +1317,6 @@ static void its_send_vmovp(struct its_vpe *vpe) { struct its_cmd_desc desc = {}; struct its_node *its; - unsigned long flags; int col_id = vpe->col_idx; desc.its_vmovp_cmd.vpe = vpe; @@ -1329,6 +1328,12 @@ static void its_send_vmovp(struct its_vpe *vpe) return; } + /* + * Protect against concurrent updates of the mapping state on + * individual VMs. + */ + guard(raw_spinlock_irqsave)(&vpe->its_vm->vmapp_lock); + /* * Yet another marvel of the architecture. If using the * its_list "feature", we need to make sure that all ITSs @@ -1337,8 +1342,7 @@ static void its_send_vmovp(struct its_vpe *vpe) * * Wall <-- Head. */ - raw_spin_lock_irqsave(&vmovp_lock, flags); - + guard(raw_spinlock)(&vmovp_lock); desc.its_vmovp_cmd.seq_num = vmovp_seq_num++; desc.its_vmovp_cmd.its_list = get_its_list(vpe->its_vm); @@ -1353,8 +1357,6 @@ static void its_send_vmovp(struct its_vpe *vpe) desc.its_vmovp_cmd.col = &its->collections[col_id]; its_send_single_vcommand(its, its_build_vmovp_cmd, &desc); } - - raw_spin_unlock_irqrestore(&vmovp_lock, flags); } static void its_send_vinvall(struct its_node *its, struct its_vpe *vpe) @@ -1791,12 +1793,10 @@ static bool gic_requires_eager_mapping(void) static void its_map_vm(struct its_node *its, struct its_vm *vm) { - unsigned long flags; - if (gic_requires_eager_mapping()) return; - raw_spin_lock_irqsave(&vmovp_lock, flags); + guard(raw_spinlock_irqsave)(&vm->vmapp_lock); /* * If the VM wasn't mapped yet, iterate over the vpes and get @@ -1814,19 +1814,15 @@ static void its_map_vm(struct its_node *its, struct its_vm *vm) its_send_vinvall(its, vpe); } } - - raw_spin_unlock_irqrestore(&vmovp_lock, flags); } static void its_unmap_vm(struct its_node *its, struct its_vm *vm) { - unsigned long flags; - /* Not using the ITS list? Everything is always mapped. */ if (gic_requires_eager_mapping()) return; - raw_spin_lock_irqsave(&vmovp_lock, flags); + guard(raw_spinlock_irqsave)(&vm->vmapp_lock); if (!--vm->vlpi_count[its->list_nr]) { int i; @@ -1834,8 +1830,6 @@ static void its_unmap_vm(struct its_node *its, struct its_vm *vm) for (i = 0; i < vm->nr_vpes; i++) its_send_vmapp(its, vm->vpes[i], false); } - - raw_spin_unlock_irqrestore(&vmovp_lock, flags); } static int its_vlpi_map(struct irq_data *d, struct its_cmd_info *info) @@ -3942,6 +3936,8 @@ static void its_vpe_invall(struct its_vpe *vpe) { struct its_node *its; + guard(raw_spinlock_irqsave)(&vpe->its_vm->vmapp_lock); + list_for_each_entry(its, &its_nodes, entry) { if (!is_v4(its)) continue; @@ -4547,6 +4543,7 @@ static int its_vpe_irq_domain_alloc(struct irq_domain *domain, unsigned int virq vm->db_lpi_base = base; vm->nr_db_lpis = nr_ids; vm->vprop_page = vprop_page; + raw_spin_lock_init(&vm->vmapp_lock); if (gic_rdists->has_rvpeid) irqchip = &its_vpe_4_1_irq_chip; diff --git a/include/linux/irqchip/arm-gic-v4.h b/include/linux/irqchip/arm-gic-v4.h index 2c63375bbd43..ecabed6d3307 100644 --- a/include/linux/irqchip/arm-gic-v4.h +++ b/include/linux/irqchip/arm-gic-v4.h @@ -25,6 +25,14 @@ struct its_vm { irq_hw_number_t db_lpi_base; unsigned long *db_bitmap; int nr_db_lpis; + /* + * Ensures mutual exclusion between updates to vlpi_count[] + * and map/unmap when using the ITSList mechanism. + * + * The lock order for any sequence involving the ITSList is + * vmapp_lock -> vpe_lock ->vmovp_lock. + */ + raw_spinlock_t vmapp_lock; u32 vlpi_count[GICv4_ITS_LIST_MAX]; }; From a84a07fa3100d7ad46a3d6882af25a3df9c9e7e3 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Fri, 5 Jul 2024 10:31:55 +0100 Subject: [PATCH 096/152] irqchip/gic-v4: Make sure a VPE is locked when VMAPP is issued In order to make sure that vpe->col_idx is correctly sampled when a VMAPP command is issued, the vpe_lock must be held for the VPE. This is now possible since the introduction of the per-VM vmapp_lock, which can be taken before vpe_lock in the correct locking order. Signed-off-by: Marc Zyngier Signed-off-by: Thomas Gleixner Tested-by: Nianyao Tang Link: https://lore.kernel.org/r/20240705093155.871070-4-maz@kernel.org --- drivers/irqchip/irq-gic-v3-its.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 215c7ab74e3a..c23a64fcf9b3 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -1810,7 +1810,9 @@ static void its_map_vm(struct its_node *its, struct its_vm *vm) for (i = 0; i < vm->nr_vpes; i++) { struct its_vpe *vpe = vm->vpes[i]; - its_send_vmapp(its, vpe, true); + scoped_guard(raw_spinlock, &vpe->vpe_lock) + its_send_vmapp(its, vpe, true); + its_send_vinvall(its, vpe); } } @@ -1827,8 +1829,10 @@ static void its_unmap_vm(struct its_node *its, struct its_vm *vm) if (!--vm->vlpi_count[its->list_nr]) { int i; - for (i = 0; i < vm->nr_vpes; i++) + for (i = 0; i < vm->nr_vpes; i++) { + guard(raw_spinlock)(&vm->vpes[i]->vpe_lock); its_send_vmapp(its, vm->vpes[i], false); + } } } From 9a58480e5e532903f08b2a34f05076d3ec3a5c00 Mon Sep 17 00:00:00 2001 From: Stefan Wahren Date: Sun, 30 Jun 2024 17:36:46 +0200 Subject: [PATCH 097/152] irqchip/bcm2835: Enable SKIP_SET_WAKE and MASK_ON_SUSPEND The BCM2835 ARMCTRL interrupt controller doesn't provide any facility to configure the wakeup sources. That's the reason why the driver lacks the irq_set_wake() callback for the interrupt chip. But this prevent to properly enter power management states like "suspend to idle". Enable the flags IRQCHIP_SKIP_SET_WAKE and IRQCHIP_MASK_ON_SUSPEND so the interrupt suspend logic can handle the chip correctly. Signed-off-by: Stefan Wahren Signed-off-by: Thomas Gleixner Reviewed-by: Florian Fainelli --- drivers/irqchip/irq-bcm2835.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-bcm2835.c b/drivers/irqchip/irq-bcm2835.c index e94e2882286c..6c20604c2242 100644 --- a/drivers/irqchip/irq-bcm2835.c +++ b/drivers/irqchip/irq-bcm2835.c @@ -102,7 +102,9 @@ static void armctrl_unmask_irq(struct irq_data *d) static struct irq_chip armctrl_chip = { .name = "ARMCTRL-level", .irq_mask = armctrl_mask_irq, - .irq_unmask = armctrl_unmask_irq + .irq_unmask = armctrl_unmask_irq, + .flags = IRQCHIP_MASK_ON_SUSPEND | + IRQCHIP_SKIP_SET_WAKE, }; static int armctrl_xlate(struct irq_domain *d, struct device_node *ctrlr, From 10697eee6a6ff59207663f536dc8e8de7a4fd3e7 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 2 Jul 2024 11:24:14 +0200 Subject: [PATCH 098/152] irqchip/gic-v3: Pass #redistributor-regions to gic_of_setup_kvm_info() The caller of gic_of_setup_kvm_info() already queried DT for the value of the #redistributor-regions property. So just pass this value, instead of doing the DT look-up again in the callee. Signed-off-by: Geert Uytterhoeven Signed-off-by: Thomas Gleixner Acked-by: Marc Zyngier Link: https://lore.kernel.org/r/808286a3ac08f60585ae7e2c848e0f9b3cb79cf8.1719912215.git.geert+renesas@glider.be --- drivers/irqchip/irq-gic-v3.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index e4bc5f04226e..7c12d11513c6 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -2190,11 +2190,10 @@ out_put_node: of_node_put(parts_node); } -static void __init gic_of_setup_kvm_info(struct device_node *node) +static void __init gic_of_setup_kvm_info(struct device_node *node, u32 nr_redist_regions) { int ret; struct resource r; - u32 gicv_idx; gic_v3_kvm_info.type = GIC_V3; @@ -2202,12 +2201,8 @@ static void __init gic_of_setup_kvm_info(struct device_node *node) if (!gic_v3_kvm_info.maint_irq) return; - if (of_property_read_u32(node, "#redistributor-regions", - &gicv_idx)) - gicv_idx = 1; - - gicv_idx += 3; /* Also skip GICD, GICC, GICH */ - ret = of_address_to_resource(node, gicv_idx, &r); + /* Also skip GICD, GICC, GICH */ + ret = of_address_to_resource(node, nr_redist_regions + 3, &r); if (!ret) gic_v3_kvm_info.vcpu = r; @@ -2297,7 +2292,7 @@ static int __init gic_of_init(struct device_node *node, struct device_node *pare gic_populate_ppi_partitions(node); if (static_branch_likely(&supports_deactivate_key)) - gic_of_setup_kvm_info(node); + gic_of_setup_kvm_info(node, nr_redist_regions); return 0; out_unmap_rdist: From 33b1c47d1fc0b5f06a393bb915db85baacba18ea Mon Sep 17 00:00:00 2001 From: Shenwei Wang Date: Wed, 3 Jul 2024 11:32:50 -0500 Subject: [PATCH 099/152] irqchip/imx-irqsteer: Handle runtime power management correctly The power domain is automatically activated from clk_prepare(). However, on certain platforms like i.MX8QM and i.MX8QXP, the power-on handling invokes sleeping functions, which triggers the 'scheduling while atomic' bug in the context switch path during device probing: BUG: scheduling while atomic: kworker/u13:1/48/0x00000002 Call trace: __schedule_bug+0x54/0x6c __schedule+0x7f0/0xa94 schedule+0x5c/0xc4 schedule_preempt_disabled+0x24/0x40 __mutex_lock.constprop.0+0x2c0/0x540 __mutex_lock_slowpath+0x14/0x20 mutex_lock+0x48/0x54 clk_prepare_lock+0x44/0xa0 clk_prepare+0x20/0x44 imx_irqsteer_resume+0x28/0xe0 pm_generic_runtime_resume+0x2c/0x44 __genpd_runtime_resume+0x30/0x80 genpd_runtime_resume+0xc8/0x2c0 __rpm_callback+0x48/0x1d8 rpm_callback+0x6c/0x78 rpm_resume+0x490/0x6b4 __pm_runtime_resume+0x50/0x94 irq_chip_pm_get+0x2c/0xa0 __irq_do_set_handler+0x178/0x24c irq_set_chained_handler_and_data+0x60/0xa4 mxc_gpio_probe+0x160/0x4b0 Cure this by implementing the irq_bus_lock/sync_unlock() interrupt chip callbacks and handle power management in them as they are invoked from non-atomic context. [ tglx: Rewrote change log, added Fixes tag ] Fixes: 0136afa08967 ("irqchip: Add driver for imx-irqsteer controller") Signed-off-by: Shenwei Wang Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20240703163250.47887-1-shenwei.wang@nxp.com --- drivers/irqchip/irq-imx-irqsteer.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/drivers/irqchip/irq-imx-irqsteer.c b/drivers/irqchip/irq-imx-irqsteer.c index 20cf7a9e9ece..75a0e980ff35 100644 --- a/drivers/irqchip/irq-imx-irqsteer.c +++ b/drivers/irqchip/irq-imx-irqsteer.c @@ -36,6 +36,7 @@ struct irqsteer_data { int channel; struct irq_domain *domain; u32 *saved_reg; + struct device *dev; }; static int imx_irqsteer_get_reg_index(struct irqsteer_data *data, @@ -72,10 +73,26 @@ static void imx_irqsteer_irq_mask(struct irq_data *d) raw_spin_unlock_irqrestore(&data->lock, flags); } +static void imx_irqsteer_irq_bus_lock(struct irq_data *d) +{ + struct irqsteer_data *data = d->chip_data; + + pm_runtime_get_sync(data->dev); +} + +static void imx_irqsteer_irq_bus_sync_unlock(struct irq_data *d) +{ + struct irqsteer_data *data = d->chip_data; + + pm_runtime_put_autosuspend(data->dev); +} + static const struct irq_chip imx_irqsteer_irq_chip = { - .name = "irqsteer", - .irq_mask = imx_irqsteer_irq_mask, - .irq_unmask = imx_irqsteer_irq_unmask, + .name = "irqsteer", + .irq_mask = imx_irqsteer_irq_mask, + .irq_unmask = imx_irqsteer_irq_unmask, + .irq_bus_lock = imx_irqsteer_irq_bus_lock, + .irq_bus_sync_unlock = imx_irqsteer_irq_bus_sync_unlock, }; static int imx_irqsteer_irq_map(struct irq_domain *h, unsigned int irq, @@ -150,6 +167,7 @@ static int imx_irqsteer_probe(struct platform_device *pdev) if (!data) return -ENOMEM; + data->dev = &pdev->dev; data->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(data->regs)) { dev_err(&pdev->dev, "failed to initialize reg\n"); From c37927a203fa283950f6045602b9f71328ad786c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 11 Jul 2024 12:20:04 +0200 Subject: [PATCH 100/152] genirq: Set IRQF_COND_ONESHOT in request_irq() The callers of request_irq() don't care about IRQF_ONESHOT because they don't provide threaded handlers, but if they happen to share the IRQ with the ACPI SCI, which has a threaded handler and sets IRQF_ONESHOT, request_irq() will fail for them due to a flags mismatch. Address this by making request_irq() add IRQF_COND_ONESHOT to the flags passed to request_threaded_irq() for all of its callers. Fixes: 7a36b901a6eb ("ACPI: OSL: Use a threaded interrupt handler for SCI") Reported-by: Stefan Seyfried Signed-off-by: Rafael J. Wysocki Signed-off-by: Thomas Gleixner Tested-by: Stefan Seyfried Cc: stable@vger.kerel.org Link: https://lore.kernel.org/r/5800834.DvuYhMxLoT@rjwysocki.net Closes: https://lore.kernel.org/lkml/205bd84a-fe8e-4963-968e-0763285f35ba@message-id.googlemail.com --- include/linux/interrupt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h index 5c9bdd3ffccc..dac7466de5f3 100644 --- a/include/linux/interrupt.h +++ b/include/linux/interrupt.h @@ -168,7 +168,7 @@ static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) { - return request_threaded_irq(irq, handler, NULL, flags, name, dev); + return request_threaded_irq(irq, handler, NULL, flags | IRQF_COND_ONESHOT, name, dev); } extern int __must_check From b7b377332b96a38bc98928d7ec2674c77a95fcb3 Mon Sep 17 00:00:00 2001 From: "Jiri Slaby (SUSE)" Date: Fri, 12 Jul 2024 08:41:48 +0200 Subject: [PATCH 101/152] irqdomain: Fix the kernel-doc and plug it into Documentation There were several undocumented fields in structs irq_domain_ops and irq_domain_info. Document them. irq_domain_ops::revmap_size contained "[]" in the description, which is not allowed in sphinx. Remove that. Finally, plug the whole header (irqdomain.h) into genericirq.rst, so that the docs is autogenerated and hyperlinks to these structure are created. Signed-off-by: Jiri Slaby (SUSE) Signed-off-by: Thomas Gleixner Tested-by: Randy Dunlap Reviewed-by: Randy Dunlap Link: https://lore.kernel.org/r/20240712064148.157040-1-jirislaby@kernel.org --- Documentation/core-api/genericirq.rst | 2 ++ include/linux/irqdomain.h | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Documentation/core-api/genericirq.rst b/Documentation/core-api/genericirq.rst index 4a460639ab1c..7a27663f309f 100644 --- a/Documentation/core-api/genericirq.rst +++ b/Documentation/core-api/genericirq.rst @@ -410,6 +410,8 @@ which are used in the generic IRQ layer. .. kernel-doc:: include/linux/interrupt.h :internal: +.. kernel-doc:: include/linux/irqdomain.h + Public Functions Provided ========================= diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h index 02cd486ac354..de6105f68fec 100644 --- a/include/linux/irqdomain.h +++ b/include/linux/irqdomain.h @@ -74,11 +74,24 @@ void of_phandle_args_to_fwspec(struct device_node *np, const u32 *args, * struct irq_domain_ops - Methods for irq_domain objects * @match: Match an interrupt controller device node to a host, returns * 1 on a match + * @select: Match an interrupt controller fw specification. It is more generic + * than @match as it receives a complete struct irq_fwspec. Therefore, + * @select is preferred if provided. Returns 1 on a match. * @map: Create or update a mapping between a virtual irq number and a hw * irq number. This is called only once for a given mapping. * @unmap: Dispose of such a mapping * @xlate: Given a device tree node and interrupt specifier, decode * the hardware irq number and linux irq type value. + * @alloc: Allocate @nr_irqs interrupts starting from @virq. + * @free: Free @nr_irqs interrupts starting from @virq. + * @activate: Activate one interrupt in HW (@irqd). If @reserve is set, only + * reserve the vector. If unset, assign the vector (called from + * request_irq()). + * @deactivate: Disarm one interrupt (@irqd). + * @translate: Given @fwspec, decode the hardware irq number (@out_hwirq) and + * linux irq type value (@out_type). This is a generalised @xlate + * (over struct irq_fwspec) and is preferred if provided. + * @debug_show: For domains to show specific data for an interrupt in debugfs. * * Functions below are provided by the driver and called whenever a new mapping * is created or an old mapping is disposed. The driver can then proceed to @@ -131,6 +144,9 @@ struct irq_domain_chip_generic; * Optional elements: * @fwnode: Pointer to firmware node associated with the irq_domain. Pretty easy * to swap it for the of_node via the irq_domain_get_of_node accessor + * @bus_token: @fwnode's device_node might be used for several irq domains. But + * in connection with @bus_token, the pair shall be unique in a + * system. * @gc: Pointer to a list of generic chips. There is a helper function for * setting up one or more generic chips for interrupt controllers * drivers using the generic chip library which uses this pointer. @@ -144,7 +160,9 @@ struct irq_domain_chip_generic; * @exit: Function called when the domain is destroyed * * Revmap data, used internally by the irq domain code: - * @revmap_size: Size of the linear map table @revmap[] + * @hwirq_max: Top limit for the HW irq number. Especially to avoid + * conflicts/failures with reserved HW irqs. Can be ~0. + * @revmap_size: Size of the linear map table @revmap * @revmap_tree: Radix map tree for hwirqs that don't fit in the linear map * @revmap: Linear table of irq_data pointers */ From 7d189c77106ed6df09829f7a419e35ada67b2bd0 Mon Sep 17 00:00:00 2001 From: Shivamurthy Shastri Date: Wed, 26 Jun 2024 21:05:12 +0200 Subject: [PATCH 102/152] PCI/MSI: Provide MSI_FLAG_PCI_MSI_MASK_PARENT Most ARM(64) PCI/MSI domains mask and unmask in the parent domain after or before the PCI mask/unmask operation takes place. So there are more than a dozen of the same wrapper implementation all over the place. Don't make the same mistake with the new per device PCI/MSI domains and provide a new MSI feature flag, which lets the domain implementation enable this sequence in the PCI/MSI code. Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Acked-by: Bjorn Helgaas Link: https://lore.kernel.org/r/87ed8j34pj.ffs@tglx --- drivers/pci/msi/irqdomain.c | 20 ++++++++++++++++++++ include/linux/msi.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/drivers/pci/msi/irqdomain.c b/drivers/pci/msi/irqdomain.c index 03d2dd25790d..569125726b3e 100644 --- a/drivers/pci/msi/irqdomain.c +++ b/drivers/pci/msi/irqdomain.c @@ -148,17 +148,35 @@ static void pci_device_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *d arg->hwirq = desc->msi_index; } +static __always_inline void cond_mask_parent(struct irq_data *data) +{ + struct msi_domain_info *info = data->domain->host_data; + + if (unlikely(info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT)) + irq_chip_mask_parent(data); +} + +static __always_inline void cond_unmask_parent(struct irq_data *data) +{ + struct msi_domain_info *info = data->domain->host_data; + + if (unlikely(info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT)) + irq_chip_unmask_parent(data); +} + static void pci_irq_mask_msi(struct irq_data *data) { struct msi_desc *desc = irq_data_get_msi_desc(data); pci_msi_mask(desc, BIT(data->irq - desc->irq)); + cond_mask_parent(data); } static void pci_irq_unmask_msi(struct irq_data *data) { struct msi_desc *desc = irq_data_get_msi_desc(data); + cond_unmask_parent(data); pci_msi_unmask(desc, BIT(data->irq - desc->irq)); } @@ -195,10 +213,12 @@ static const struct msi_domain_template pci_msi_template = { static void pci_irq_mask_msix(struct irq_data *data) { pci_msix_mask(irq_data_get_msi_desc(data)); + cond_mask_parent(data); } static void pci_irq_unmask_msix(struct irq_data *data) { + cond_unmask_parent(data); pci_msix_unmask(irq_data_get_msi_desc(data)); } diff --git a/include/linux/msi.h b/include/linux/msi.h index dc27cf3903d5..04f33e7f6f8b 100644 --- a/include/linux/msi.h +++ b/include/linux/msi.h @@ -556,6 +556,8 @@ enum { MSI_FLAG_USE_DEV_FWNODE = (1 << 7), /* Set parent->dev into domain->pm_dev on device domain creation */ MSI_FLAG_PARENT_PM_DEV = (1 << 8), + /* Support for parent mask/unmask */ + MSI_FLAG_PCI_MSI_MASK_PARENT = (1 << 9), /* Mask for the generic functionality */ MSI_GENERIC_FLAGS_MASK = GENMASK(15, 0), From 72e257c6f058032daba1c4fe0c81003d545d0f81 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:34 +0200 Subject: [PATCH 103/152] irqchip: Provide irq-msi-lib All irqdomains which provide MSI parent domain functionality for per device MSI domains need to provide a select() callback for the irqdomain and a function to initialize the child domain. Most of these functions would just be copy&paste with minimal modifications, so provide a library function which implements the required functionality and is customizable via parent_domain::msi_parent_ops. The check for the supported bus tokens in msi_lib_init_dev_msi_info() is expanded step by step within the next patches. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142234.840975799@linutronix.de --- drivers/irqchip/Kconfig | 3 + drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-msi-lib.c | 112 ++++++++++++++++++++++++++++++++++ drivers/irqchip/irq-msi-lib.h | 19 ++++++ 4 files changed, 135 insertions(+) create mode 100644 drivers/irqchip/irq-msi-lib.c create mode 100644 drivers/irqchip/irq-msi-lib.h diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 14464716bacb..2bf8d940504c 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -74,6 +74,9 @@ config ARM_VIC_NR The maximum number of VICs available in the system, for power management. +config IRQ_MSI_LIB + bool + config ARMADA_370_XP_IRQ bool select GENERIC_IRQ_CHIP diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index d9dc3d99aaa8..72c7f6289411 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_ARCH_SPEAR3XX) += spear-shirq.o obj-$(CONFIG_ARM_GIC) += irq-gic.o irq-gic-common.o obj-$(CONFIG_ARM_GIC_PM) += irq-gic-pm.o obj-$(CONFIG_ARCH_REALVIEW) += irq-gic-realview.o +obj-$(CONFIG_IRQ_MSI_LIB) += irq-msi-lib.o obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-platform-msi.o irq-gic-v4.o diff --git a/drivers/irqchip/irq-msi-lib.c b/drivers/irqchip/irq-msi-lib.c new file mode 100644 index 000000000000..ec1a10f396b3 --- /dev/null +++ b/drivers/irqchip/irq-msi-lib.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2022 Linutronix GmbH +// Copyright (C) 2022 Intel + +#include + +#include "irq-msi-lib.h" + +/** + * msi_lib_init_dev_msi_info - Domain info setup for MSI domains + * @dev: The device for which the domain is created for + * @domain: The domain providing this callback + * @real_parent: The real parent domain of the domain to be initialized + * which might be a domain built on top of @domain or + * @domain itself + * @info: The domain info for the domain to be initialize + * + * This function is to be used for all types of MSI domains above the root + * parent domain and any intermediates. The topmost parent domain specific + * functionality is determined via @real_parent. + * + * All intermediate domains between the root and the device domain must + * have either msi_parent_ops.init_dev_msi_info = msi_parent_init_dev_msi_info + * or invoke it down the line. + */ +bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain, + struct irq_domain *real_parent, + struct msi_domain_info *info) +{ + const struct msi_parent_ops *pops = real_parent->msi_parent_ops; + + /* Parent ops available? */ + if (WARN_ON_ONCE(!pops)) + return false; + + /* + * MSI parent domain specific settings. For now there is only the + * root parent domain, e.g. NEXUS, acting as a MSI parent, but it is + * possible to stack MSI parents. See x86 vector -> irq remapping + */ + if (domain->bus_token == pops->bus_select_token) { + if (WARN_ON_ONCE(domain != real_parent)) + return false; + } else { + WARN_ON_ONCE(1); + return false; + } + + /* Is the target domain bus token supported? */ + switch(info->bus_token) { + default: + /* + * This should never be reached. See + * msi_lib_irq_domain_select() + */ + WARN_ON_ONCE(1); + return false; + } + + /* + * Mask out the domain specific MSI feature flags which are not + * supported by the real parent. + */ + info->flags &= pops->supported_flags; + /* Enforce the required flags */ + info->flags |= pops->required_flags; + + /* Chip updates for all child bus types */ + if (!info->chip->irq_eoi) + info->chip->irq_eoi = irq_chip_eoi_parent; + + /* + * The device MSI domain can never have a set affinity callback. It + * always has to rely on the parent domain to handle affinity + * settings. The device MSI domain just has to write the resulting + * MSI message into the hardware which is the whole purpose of the + * device MSI domain aside of mask/unmask which is provided e.g. by + * PCI/MSI device domains. + */ + info->chip->irq_set_affinity = msi_domain_set_affinity; + return true; +} +EXPORT_SYMBOL_GPL(msi_lib_init_dev_msi_info); + +/** + * msi_lib_irq_domain_select - Shared select function for NEXUS domains + * @d: Pointer to the irq domain on which select is invoked + * @fwspec: Firmware spec describing what is searched + * @bus_token: The bus token for which a matching irq domain is looked up + * + * Returns: %0 if @d is not what is being looked for + * + * %1 if @d is either the domain which is directly searched for or + * if @d is providing the parent MSI domain for the functionality + * requested with @bus_token. + */ +int msi_lib_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec, + enum irq_domain_bus_token bus_token) +{ + const struct msi_parent_ops *ops = d->msi_parent_ops; + u32 busmask = BIT(bus_token); + + if (fwspec->fwnode != d->fwnode || fwspec->param_count != 0) + return 0; + + /* Handle pure domain searches */ + if (bus_token == ops->bus_select_token) + return 1; + + return ops && !!(ops->bus_select_mask & busmask); +} +EXPORT_SYMBOL_GPL(msi_lib_irq_domain_select); diff --git a/drivers/irqchip/irq-msi-lib.h b/drivers/irqchip/irq-msi-lib.h new file mode 100644 index 000000000000..f0706cc28264 --- /dev/null +++ b/drivers/irqchip/irq-msi-lib.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2022 Linutronix GmbH +// Copyright (C) 2022 Intel + +#ifndef _DRIVERS_IRQCHIP_IRQ_MSI_LIB_H +#define _DRIVERS_IRQCHIP_IRQ_MSI_LIB_H + +#include +#include +#include + +int msi_lib_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec, + enum irq_domain_bus_token bus_token); + +bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain, + struct irq_domain *real_parent, + struct msi_domain_info *info); + +#endif /* _DRIVERS_IRQCHIP_IRQ_MSI_LIB_H */ From 48f71d56e2b87839052d2a2ec32fc97a79c3e264 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:36 +0200 Subject: [PATCH 104/152] irqchip/gic-v3-its: Provide MSI parent infrastructure To support per device MSI domains the ITS must provide MSI parent domain functionality. Provide the basic skeleton for this: - msi_parent_ops - child domain init callback - the MSI parent flag set in irqdomain::flags This does not make ITS a functional parent domain as there is no bit set in the bus_select_mask yet, but it provides the base to implement PCI and platform MSI support gradually on top. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142234.903076277@linutronix.de --- drivers/irqchip/Kconfig | 1 + drivers/irqchip/Makefile | 2 +- drivers/irqchip/irq-gic-common.h | 3 ++ drivers/irqchip/irq-gic-v3-its-msi-parent.c | 31 +++++++++++++++++++++ drivers/irqchip/irq-gic-v3-its.c | 5 ++++ 5 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 drivers/irqchip/irq-gic-v3-its-msi-parent.c diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 2bf8d940504c..b51863fa9b38 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -41,6 +41,7 @@ config ARM_GIC_V3 config ARM_GIC_V3_ITS bool select GENERIC_MSI_IRQ + select IRQ_MSI_LIB default ARM_GIC_V3 config ARM_GIC_V3_ITS_PCI diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 72c7f6289411..6e4f7715206d 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -32,7 +32,7 @@ obj-$(CONFIG_ARCH_REALVIEW) += irq-gic-realview.o obj-$(CONFIG_IRQ_MSI_LIB) += irq-msi-lib.o obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o -obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-platform-msi.o irq-gic-v4.o +obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-platform-msi.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o obj-$(CONFIG_ARM_GIC_V3_ITS_PCI) += irq-gic-v3-its-pci-msi.o obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o diff --git a/drivers/irqchip/irq-gic-common.h b/drivers/irqchip/irq-gic-common.h index f407cce9ecaa..eb4a220dd6ad 100644 --- a/drivers/irqchip/irq-gic-common.h +++ b/drivers/irqchip/irq-gic-common.h @@ -8,6 +8,7 @@ #include #include +#include #include struct gic_quirk { @@ -29,6 +30,8 @@ void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks, void gic_enable_of_quirks(const struct device_node *np, const struct gic_quirk *quirks, void *data); +extern const struct msi_parent_ops gic_v3_its_msi_parent_ops; + #define RDIST_FLAGS_PROPBASE_NEEDS_FLUSHING (1 << 0) #define RDIST_FLAGS_RD_TABLES_PREALLOCATED (1 << 1) #define RDIST_FLAGS_FORCE_NON_SHAREABLE (1 << 2) diff --git a/drivers/irqchip/irq-gic-v3-its-msi-parent.c b/drivers/irqchip/irq-gic-v3-its-msi-parent.c new file mode 100644 index 000000000000..cdc0844229b5 --- /dev/null +++ b/drivers/irqchip/irq-gic-v3-its-msi-parent.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2022 Linutronix GmbH +// Copyright (C) 2022 Intel + +#include "irq-gic-common.h" +#include "irq-msi-lib.h" + +#define ITS_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ + MSI_FLAG_USE_DEF_CHIP_OPS) + +#define ITS_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \ + MSI_FLAG_PCI_MSIX | \ + MSI_FLAG_MULTI_PCI_MSI | \ + MSI_FLAG_PCI_MSI_MASK_PARENT) + +static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain, + struct irq_domain *real_parent, struct msi_domain_info *info) +{ + if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info)) + return false; + + return true; +} + +const struct msi_parent_ops gic_v3_its_msi_parent_ops = { + .supported_flags = ITS_MSI_FLAGS_SUPPORTED, + .required_flags = ITS_MSI_FLAGS_REQUIRED, + .bus_select_token = DOMAIN_BUS_NEXUS, + .prefix = "ITS-", + .init_dev_msi_info = its_init_dev_msi_info, +}; diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 3c755d5dad6e..d770d6aedb29 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -38,6 +38,7 @@ #include #include "irq-gic-common.h" +#include "irq-msi-lib.h" #define ITS_FLAGS_CMDQ_NEEDS_FLUSHING (1ULL << 0) #define ITS_FLAGS_WORKAROUND_CAVIUM_22375 (1ULL << 1) @@ -3688,6 +3689,7 @@ static void its_irq_domain_free(struct irq_domain *domain, unsigned int virq, } static const struct irq_domain_ops its_domain_ops = { + .select = msi_lib_irq_domain_select, .alloc = its_irq_domain_alloc, .free = its_irq_domain_free, .activate = its_irq_domain_activate, @@ -4993,6 +4995,9 @@ static int its_init_domain(struct its_node *its) irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS); + inner_domain->msi_parent_ops = &gic_v3_its_msi_parent_ops; + inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT; + return 0; } From 8c41ccec839c622b2d1be769a95405e4e9a4cb20 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:38 +0200 Subject: [PATCH 105/152] irqchip/irq-msi-lib: Prepare for PCI MSI/MSIX Add the bus tokens for DOMAIN_BUS_PCI_DEVICE_MSI and DOMAIN_BUS_PCI_DEVICE_MSIX to the common child init function. Provide the match mask which can be used by parent domain implementation so the bitmask based child bus token match works. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142234.964056815@linutronix.de --- drivers/irqchip/irq-msi-lib.c | 11 ++++++++++- drivers/irqchip/irq-msi-lib.h | 6 ++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-msi-lib.c b/drivers/irqchip/irq-msi-lib.c index ec1a10f396b3..ef26962a31ee 100644 --- a/drivers/irqchip/irq-msi-lib.c +++ b/drivers/irqchip/irq-msi-lib.c @@ -28,6 +28,7 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain, struct msi_domain_info *info) { const struct msi_parent_ops *pops = real_parent->msi_parent_ops; + u32 required_flags; /* Parent ops available? */ if (WARN_ON_ONCE(!pops)) @@ -46,8 +47,16 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain, return false; } + required_flags = pops->required_flags; + /* Is the target domain bus token supported? */ switch(info->bus_token) { + case DOMAIN_BUS_PCI_DEVICE_MSI: + case DOMAIN_BUS_PCI_DEVICE_MSIX: + if (WARN_ON_ONCE(!IS_ENABLED(CONFIG_PCI_MSI))) + return false; + + break; default: /* * This should never be reached. See @@ -63,7 +72,7 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain, */ info->flags &= pops->supported_flags; /* Enforce the required flags */ - info->flags |= pops->required_flags; + info->flags |= required_flags; /* Chip updates for all child bus types */ if (!info->chip->irq_eoi) diff --git a/drivers/irqchip/irq-msi-lib.h b/drivers/irqchip/irq-msi-lib.h index f0706cc28264..525aa5284a99 100644 --- a/drivers/irqchip/irq-msi-lib.h +++ b/drivers/irqchip/irq-msi-lib.h @@ -9,6 +9,12 @@ #include #include +#ifdef CONFIG_PCI_MSI +#define MATCH_PCI_MSI BIT(DOMAIN_BUS_PCI_MSI) +#else +#define MATCH_PCI_MSI (0) +#endif + int msi_lib_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec, enum irq_domain_bus_token bus_token); From b5712bf89b4bbc5bcc9ebde8753ad222f1f68296 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:39 +0200 Subject: [PATCH 106/152] irqchip/gic-v3-its: Provide MSI parent for PCI/MSI[-X] The its_pci_msi_prepare() function from the ITS-PCI/MSI code provides the 'global' PCI/MSI domains. Move this function to the ITS-MSI parent code and amend the function to use the domain hardware size, which is the MSI[X] vector count, for allocating the ITS slots for the PCI device. Enable PCI matching in msi_parent_ops and provide the necessary update to the ITS specific child domain initialization function so that the prepare callback gets invoked on allocations. The latter might be optimized to do the allocation right at the point where the child domain is initialized, but keep it simple for now. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.024567623@linutronix.de --- drivers/irqchip/Makefile | 1 - drivers/irqchip/irq-gic-v3-its-msi-parent.c | 114 ++++++++++- drivers/irqchip/irq-gic-v3-its-pci-msi.c | 202 -------------------- 3 files changed, 111 insertions(+), 206 deletions(-) delete mode 100644 drivers/irqchip/irq-gic-v3-its-pci-msi.c diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 6e4f7715206d..c5316634637f 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -33,7 +33,6 @@ obj-$(CONFIG_IRQ_MSI_LIB) += irq-msi-lib.o obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-platform-msi.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o -obj-$(CONFIG_ARM_GIC_V3_ITS_PCI) += irq-gic-v3-its-pci-msi.o obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o diff --git a/drivers/irqchip/irq-gic-v3-its-msi-parent.c b/drivers/irqchip/irq-gic-v3-its-msi-parent.c index cdc0844229b5..33b04c3623f9 100644 --- a/drivers/irqchip/irq-gic-v3-its-msi-parent.c +++ b/drivers/irqchip/irq-gic-v3-its-msi-parent.c @@ -1,17 +1,100 @@ // SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2013-2015 ARM Limited, All Rights Reserved. +// Author: Marc Zyngier // Copyright (C) 2022 Linutronix GmbH // Copyright (C) 2022 Intel +#include + #include "irq-gic-common.h" #include "irq-msi-lib.h" #define ITS_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ - MSI_FLAG_USE_DEF_CHIP_OPS) + MSI_FLAG_USE_DEF_CHIP_OPS | \ + MSI_FLAG_PCI_MSI_MASK_PARENT) #define ITS_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \ MSI_FLAG_PCI_MSIX | \ - MSI_FLAG_MULTI_PCI_MSI | \ - MSI_FLAG_PCI_MSI_MASK_PARENT) + MSI_FLAG_MULTI_PCI_MSI) + +#ifdef CONFIG_PCI_MSI +static int its_pci_msi_vec_count(struct pci_dev *pdev, void *data) +{ + int msi, msix, *count = data; + + msi = max(pci_msi_vec_count(pdev), 0); + msix = max(pci_msix_vec_count(pdev), 0); + *count += max(msi, msix); + + return 0; +} + +static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data) +{ + struct pci_dev **alias_dev = data; + + *alias_dev = pdev; + + return 0; +} + +static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev, + int nvec, msi_alloc_info_t *info) +{ + struct pci_dev *pdev, *alias_dev; + struct msi_domain_info *msi_info; + int alias_count = 0, minnvec = 1; + + if (!dev_is_pci(dev)) + return -EINVAL; + + pdev = to_pci_dev(dev); + /* + * If pdev is downstream of any aliasing bridges, take an upper + * bound of how many other vectors could map to the same DevID. + * Also tell the ITS that the signalling will come from a proxy + * device, and that special allocation rules apply. + */ + pci_for_each_dma_alias(pdev, its_get_pci_alias, &alias_dev); + if (alias_dev != pdev) { + if (alias_dev->subordinate) + pci_walk_bus(alias_dev->subordinate, + its_pci_msi_vec_count, &alias_count); + info->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE; + } + + /* ITS specific DeviceID, as the core ITS ignores dev. */ + info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain, pdev); + + /* + * @domain->msi_domain_info->hwsize contains the size of the + * MSI[-X] domain, but vector allocation happens one by one. This + * needs some thought when MSI comes into play as the size of MSI + * might be unknown at domain creation time and therefore set to + * MSI_MAX_INDEX. + */ + msi_info = msi_get_domain_info(domain); + if (msi_info->hwsize > nvec) + nvec = msi_info->hwsize; + + /* + * Always allocate a power of 2, and special case device 0 for + * broken systems where the DevID is not wired (and all devices + * appear as DevID 0). For that reason, we generously allocate a + * minimum of 32 MSIs for DevID 0. If you want more because all + * your devices are aliasing to DevID 0, consider fixing your HW. + */ + nvec = max(nvec, alias_count); + if (!info->scratchpad[0].ul) + minnvec = 32; + nvec = max_t(int, minnvec, roundup_pow_of_two(nvec)); + + msi_info = msi_get_domain_info(domain->parent); + return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info); +} +#else /* CONFIG_PCI_MSI */ +#define its_pci_msi_prepare NULL +#endif /* !CONFIG_PCI_MSI */ static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain, struct irq_domain *real_parent, struct msi_domain_info *info) @@ -19,6 +102,30 @@ static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain, if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info)) return false; + switch(info->bus_token) { + case DOMAIN_BUS_PCI_DEVICE_MSI: + case DOMAIN_BUS_PCI_DEVICE_MSIX: + /* + * FIXME: This probably should be done after a (not yet + * existing) post domain creation callback once to make + * support for dynamic post-enable MSI-X allocations + * work without having to reevaluate the domain size + * over and over. It is known already at allocation + * time via info->hwsize. + * + * That should work perfectly fine for MSI/MSI-X but needs + * some thoughts for purely software managed MSI domains + * where the index space is only limited artificially via + * %MSI_MAX_INDEX. + */ + info->ops->msi_prepare = its_pci_msi_prepare; + break; + default: + /* Confused. How did the lib return true? */ + WARN_ON_ONCE(1); + return false; + } + return true; } @@ -26,6 +133,7 @@ const struct msi_parent_ops gic_v3_its_msi_parent_ops = { .supported_flags = ITS_MSI_FLAGS_SUPPORTED, .required_flags = ITS_MSI_FLAGS_REQUIRED, .bus_select_token = DOMAIN_BUS_NEXUS, + .bus_select_mask = MATCH_PCI_MSI, .prefix = "ITS-", .init_dev_msi_info = its_init_dev_msi_info, }; diff --git a/drivers/irqchip/irq-gic-v3-its-pci-msi.c b/drivers/irqchip/irq-gic-v3-its-pci-msi.c deleted file mode 100644 index 93f77a8196da..000000000000 --- a/drivers/irqchip/irq-gic-v3-its-pci-msi.c +++ /dev/null @@ -1,202 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2013-2015 ARM Limited, All Rights Reserved. - * Author: Marc Zyngier - */ - -#include -#include -#include -#include -#include -#include - -static void its_mask_msi_irq(struct irq_data *d) -{ - pci_msi_mask_irq(d); - irq_chip_mask_parent(d); -} - -static void its_unmask_msi_irq(struct irq_data *d) -{ - pci_msi_unmask_irq(d); - irq_chip_unmask_parent(d); -} - -static struct irq_chip its_msi_irq_chip = { - .name = "ITS-MSI", - .irq_unmask = its_unmask_msi_irq, - .irq_mask = its_mask_msi_irq, - .irq_eoi = irq_chip_eoi_parent, -}; - -static int its_pci_msi_vec_count(struct pci_dev *pdev, void *data) -{ - int msi, msix, *count = data; - - msi = max(pci_msi_vec_count(pdev), 0); - msix = max(pci_msix_vec_count(pdev), 0); - *count += max(msi, msix); - - return 0; -} - -static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data) -{ - struct pci_dev **alias_dev = data; - - *alias_dev = pdev; - - return 0; -} - -static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev, - int nvec, msi_alloc_info_t *info) -{ - struct pci_dev *pdev, *alias_dev; - struct msi_domain_info *msi_info; - int alias_count = 0, minnvec = 1; - - if (!dev_is_pci(dev)) - return -EINVAL; - - msi_info = msi_get_domain_info(domain->parent); - - pdev = to_pci_dev(dev); - /* - * If pdev is downstream of any aliasing bridges, take an upper - * bound of how many other vectors could map to the same DevID. - * Also tell the ITS that the signalling will come from a proxy - * device, and that special allocation rules apply. - */ - pci_for_each_dma_alias(pdev, its_get_pci_alias, &alias_dev); - if (alias_dev != pdev) { - if (alias_dev->subordinate) - pci_walk_bus(alias_dev->subordinate, - its_pci_msi_vec_count, &alias_count); - info->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE; - } - - /* ITS specific DeviceID, as the core ITS ignores dev. */ - info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain, pdev); - - /* - * Always allocate a power of 2, and special case device 0 for - * broken systems where the DevID is not wired (and all devices - * appear as DevID 0). For that reason, we generously allocate a - * minimum of 32 MSIs for DevID 0. If you want more because all - * your devices are aliasing to DevID 0, consider fixing your HW. - */ - nvec = max(nvec, alias_count); - if (!info->scratchpad[0].ul) - minnvec = 32; - nvec = max_t(int, minnvec, roundup_pow_of_two(nvec)); - return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info); -} - -static struct msi_domain_ops its_pci_msi_ops = { - .msi_prepare = its_pci_msi_prepare, -}; - -static struct msi_domain_info its_pci_msi_domain_info = { - .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | - MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX), - .ops = &its_pci_msi_ops, - .chip = &its_msi_irq_chip, -}; - -static struct of_device_id its_device_id[] = { - { .compatible = "arm,gic-v3-its", }, - {}, -}; - -static int __init its_pci_msi_init_one(struct fwnode_handle *handle, - const char *name) -{ - struct irq_domain *parent; - - parent = irq_find_matching_fwnode(handle, DOMAIN_BUS_NEXUS); - if (!parent || !msi_get_domain_info(parent)) { - pr_err("%s: Unable to locate ITS domain\n", name); - return -ENXIO; - } - - if (!pci_msi_create_irq_domain(handle, &its_pci_msi_domain_info, - parent)) { - pr_err("%s: Unable to create PCI domain\n", name); - return -ENOMEM; - } - - return 0; -} - -static int __init its_pci_of_msi_init(void) -{ - struct device_node *np; - - for (np = of_find_matching_node(NULL, its_device_id); np; - np = of_find_matching_node(np, its_device_id)) { - if (!of_device_is_available(np)) - continue; - if (!of_property_read_bool(np, "msi-controller")) - continue; - - if (its_pci_msi_init_one(of_node_to_fwnode(np), np->full_name)) - continue; - - pr_info("PCI/MSI: %pOF domain created\n", np); - } - - return 0; -} - -#ifdef CONFIG_ACPI - -static int __init -its_pci_msi_parse_madt(union acpi_subtable_headers *header, - const unsigned long end) -{ - struct acpi_madt_generic_translator *its_entry; - struct fwnode_handle *dom_handle; - const char *node_name; - int err = -ENXIO; - - its_entry = (struct acpi_madt_generic_translator *)header; - node_name = kasprintf(GFP_KERNEL, "ITS@0x%lx", - (long)its_entry->base_address); - dom_handle = iort_find_domain_token(its_entry->translation_id); - if (!dom_handle) { - pr_err("%s: Unable to locate ITS domain handle\n", node_name); - goto out; - } - - err = its_pci_msi_init_one(dom_handle, node_name); - if (!err) - pr_info("PCI/MSI: %s domain created\n", node_name); - -out: - kfree(node_name); - return err; -} - -static int __init its_pci_acpi_msi_init(void) -{ - acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR, - its_pci_msi_parse_madt, 0); - return 0; -} -#else -static int __init its_pci_acpi_msi_init(void) -{ - return 0; -} -#endif - -static int __init its_pci_msi_init(void) -{ - its_pci_of_msi_init(); - its_pci_acpi_msi_init(); - - return 0; -} -early_initcall(its_pci_msi_init); From 496436f4a514a3fb4bc7aecd41f0dd4b38e39b1f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:41 +0200 Subject: [PATCH 107/152] irqchip/irq-msi-lib: Prepare for DEVICE MSI to replace platform MSI Add the prerequisites for DEVICE MSI into the shared select() and child domain init function. These domains are really trivial and just provide a custom irq chip callback to write the MSI message. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.085171290@linutronix.de --- drivers/irqchip/irq-msi-lib.c | 15 +++++++++++++++ drivers/irqchip/irq-msi-lib.h | 2 ++ 2 files changed, 17 insertions(+) diff --git a/drivers/irqchip/irq-msi-lib.c b/drivers/irqchip/irq-msi-lib.c index ef26962a31ee..b98a2191c860 100644 --- a/drivers/irqchip/irq-msi-lib.c +++ b/drivers/irqchip/irq-msi-lib.c @@ -56,6 +56,21 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain, if (WARN_ON_ONCE(!IS_ENABLED(CONFIG_PCI_MSI))) return false; + break; + case DOMAIN_BUS_DEVICE_MSI: + /* + * Per device MSI should never have any MSI feature bits + * set. It's sole purpose is to create a dumb interrupt + * chip which has a device specific irq_write_msi_msg() + * callback. + */ + if (WARN_ON_ONCE(info->flags)) + return false; + + /* Core managed MSI descriptors */ + info->flags = MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | MSI_FLAG_FREE_MSI_DESCS; + /* Remove PCI specific flags */ + required_flags &= ~MSI_FLAG_PCI_MSI_MASK_PARENT; break; default: /* diff --git a/drivers/irqchip/irq-msi-lib.h b/drivers/irqchip/irq-msi-lib.h index 525aa5284a99..681ceabb7bc7 100644 --- a/drivers/irqchip/irq-msi-lib.h +++ b/drivers/irqchip/irq-msi-lib.h @@ -15,6 +15,8 @@ #define MATCH_PCI_MSI (0) #endif +#define MATCH_PLATFORM_MSI BIT(DOMAIN_BUS_PLATFORM_MSI) + int msi_lib_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec, enum irq_domain_bus_token bus_token); From fbfe7e13641efe811a273e1fd237929245376fe0 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:43 +0200 Subject: [PATCH 108/152] irqchip/mbigen: Prepare for real per device MSI The core infrastructure has everything in place to switch MBIGEN to per device MSI domains and avoid the convoluted construct of the existing platform-MSI layering violation. The new infrastructure provides a wired interrupt specific interface in the MSI core which converts the 'hardware interrupt number + trigger type' allocation which is required for wired interrupts in the regular irqdomain code to a normal MSI allocation. The hardware interrupt number and the trigger type are stored in the MSI descriptor device cookie by the core code so the MBIGEN specific code can retrieve them. The new per device domain is only instantiated when the irqdomain which is associated to the MBIGEN device provides MSI parent functionality. Up to that point it invokes the existing code. Once the parent is converted the code for the current platform-MSI mechanism is removed. The new domain shares the interrupt chip callbacks and the translation function. The only new functionality aside of filling out the msi_domain_template is a domain specific set_desc() callback, which will go away once all platform-MSI code has been converted. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.146579575@linutronix.de --- drivers/irqchip/irq-mbigen.c | 98 +++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 28 deletions(-) diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c index 58881d313979..db0fa80330d9 100644 --- a/drivers/irqchip/irq-mbigen.c +++ b/drivers/irqchip/irq-mbigen.c @@ -135,24 +135,14 @@ static int mbigen_set_type(struct irq_data *data, unsigned int type) return 0; } -static struct irq_chip mbigen_irq_chip = { - .name = "mbigen-v2", - .irq_mask = irq_chip_mask_parent, - .irq_unmask = irq_chip_unmask_parent, - .irq_eoi = mbigen_eoi_irq, - .irq_set_type = mbigen_set_type, - .irq_set_affinity = irq_chip_set_affinity_parent, -}; - -static void mbigen_write_msg(struct msi_desc *desc, struct msi_msg *msg) +static void mbigen_write_msi_msg(struct irq_data *d, struct msi_msg *msg) { - struct irq_data *d = irq_get_irq_data(desc->irq); void __iomem *base = d->chip_data; u32 val; if (!msg->address_lo && !msg->address_hi) return; - + base += get_mbigen_vec_reg(d->hwirq); val = readl_relaxed(base); @@ -165,10 +155,8 @@ static void mbigen_write_msg(struct msi_desc *desc, struct msi_msg *msg) writel_relaxed(val, base); } -static int mbigen_domain_translate(struct irq_domain *d, - struct irq_fwspec *fwspec, - unsigned long *hwirq, - unsigned int *type) +static int mbigen_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, + unsigned long *hwirq, unsigned int *type) { if (is_of_node(fwspec->fwnode) || is_acpi_device_node(fwspec->fwnode)) { if (fwspec->param_count != 2) @@ -192,6 +180,17 @@ static int mbigen_domain_translate(struct irq_domain *d, return -EINVAL; } +/* The following section will go away once ITS provides a MSI parent */ + +static struct irq_chip mbigen_irq_chip = { + .name = "mbigen-v2", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_eoi = mbigen_eoi_irq, + .irq_set_type = mbigen_set_type, + .irq_set_affinity = irq_chip_set_affinity_parent, +}; + static int mbigen_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, unsigned int nr_irqs, @@ -232,11 +231,63 @@ static const struct irq_domain_ops mbigen_domain_ops = { .free = mbigen_irq_domain_free, }; +static void mbigen_write_msg(struct msi_desc *desc, struct msi_msg *msg) +{ + mbigen_write_msi_msg(irq_get_irq_data(desc->irq), msg); +} + +/* End of to be removed section */ + +static void mbigen_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) +{ + arg->desc = desc; + arg->hwirq = (u32)desc->data.icookie.value; +} + +static const struct msi_domain_template mbigen_msi_template = { + .chip = { + .name = "mbigen-v2", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_eoi = mbigen_eoi_irq, + .irq_set_type = mbigen_set_type, + .irq_write_msi_msg = mbigen_write_msi_msg, + }, + + .ops = { + .set_desc = mbigen_domain_set_desc, + .msi_translate = mbigen_domain_translate, + }, + + .info = { + .bus_token = DOMAIN_BUS_WIRED_TO_MSI, + .flags = MSI_FLAG_USE_DEV_FWNODE, + }, +}; + +static bool mbigen_create_device_domain(struct device *dev, unsigned int size, + struct mbigen_device *mgn_chip) +{ + struct irq_domain *domain = dev->msi.domain; + + if (WARN_ON_ONCE(!domain)) + return false; + + if (irq_domain_is_msi_parent(domain)) { + return msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN, + &mbigen_msi_template, size, + NULL, mgn_chip->base); + } + + /* Remove once ITS provides MSI parent */ + return !!platform_msi_create_device_domain(dev, size, mbigen_write_msg, + &mbigen_domain_ops, mgn_chip); +} + static int mbigen_of_create_domain(struct platform_device *pdev, struct mbigen_device *mgn_chip) { struct platform_device *child; - struct irq_domain *domain; struct device_node *np; u32 num_pins; int ret = 0; @@ -258,11 +309,7 @@ static int mbigen_of_create_domain(struct platform_device *pdev, break; } - domain = platform_msi_create_device_domain(&child->dev, num_pins, - mbigen_write_msg, - &mbigen_domain_ops, - mgn_chip); - if (!domain) { + if (!mbigen_create_device_domain(&child->dev, num_pins, mgn_chip)) { ret = -ENOMEM; break; } @@ -284,7 +331,6 @@ MODULE_DEVICE_TABLE(acpi, mbigen_acpi_match); static int mbigen_acpi_create_domain(struct platform_device *pdev, struct mbigen_device *mgn_chip) { - struct irq_domain *domain; u32 num_pins = 0; int ret; @@ -315,11 +361,7 @@ static int mbigen_acpi_create_domain(struct platform_device *pdev, if (ret || num_pins == 0) return -EINVAL; - domain = platform_msi_create_device_domain(&pdev->dev, num_pins, - mbigen_write_msg, - &mbigen_domain_ops, - mgn_chip); - if (!domain) + if (!mbigen_create_device_domain(&pdev->dev, num_pins, mgn_chip)) return -ENOMEM; return 0; From 64a855324311ddad7a85fc0b25ce1f3914ed3425 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:44 +0200 Subject: [PATCH 109/152] irqchip/irq-msi-lib: Prepare for DOMAIN_BUS_WIRED_TO_MSI Add the new bus token to the accepted list of child domain tokens. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.207343466@linutronix.de --- drivers/irqchip/irq-msi-lib.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/irqchip/irq-msi-lib.c b/drivers/irqchip/irq-msi-lib.c index b98a2191c860..d20a0e6e0d36 100644 --- a/drivers/irqchip/irq-msi-lib.c +++ b/drivers/irqchip/irq-msi-lib.c @@ -69,6 +69,8 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain, /* Core managed MSI descriptors */ info->flags = MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | MSI_FLAG_FREE_MSI_DESCS; + fallthrough; + case DOMAIN_BUS_WIRED_TO_MSI: /* Remove PCI specific flags */ required_flags &= ~MSI_FLAG_PCI_MSI_MASK_PARENT; break; From 7f2baef05d6aaae7bd6d0c863eeec50ae5b2997f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:46 +0200 Subject: [PATCH 110/152] irqchip/gic-v3-its: Switch platform MSI to MSI parent Similar to the previous conversion of the PCI/MSI support lift the prepare() callback from the existing platform MSI code and enable platform MSI and the related device domain bus tokens in select and the child domain initialization code. All platform MSI users are automatically using the new per device MSI model now. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.271734124@linutronix.de --- drivers/irqchip/Makefile | 2 +- drivers/irqchip/irq-gic-v3-its-msi-parent.c | 73 +++++++- drivers/irqchip/irq-gic-v3-its-platform-msi.c | 163 ------------------ 3 files changed, 73 insertions(+), 165 deletions(-) delete mode 100644 drivers/irqchip/irq-gic-v3-its-platform-msi.c diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index c5316634637f..afc44f4709a6 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -32,7 +32,7 @@ obj-$(CONFIG_ARCH_REALVIEW) += irq-gic-realview.o obj-$(CONFIG_IRQ_MSI_LIB) += irq-msi-lib.o obj-$(CONFIG_ARM_GIC_V2M) += irq-gic-v2m.o obj-$(CONFIG_ARM_GIC_V3) += irq-gic-v3.o irq-gic-v3-mbi.o irq-gic-common.o -obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v3-its-platform-msi.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o +obj-$(CONFIG_ARM_GIC_V3_ITS) += irq-gic-v3-its.o irq-gic-v4.o irq-gic-v3-its-msi-parent.o obj-$(CONFIG_ARM_GIC_V3_ITS_FSL_MC) += irq-gic-v3-its-fsl-mc-msi.o obj-$(CONFIG_PARTITION_PERCPU) += irq-partition-percpu.o obj-$(CONFIG_HISILICON_IRQ_MBIGEN) += irq-mbigen.o diff --git a/drivers/irqchip/irq-gic-v3-its-msi-parent.c b/drivers/irqchip/irq-gic-v3-its-msi-parent.c index 33b04c3623f9..780e1bc9df54 100644 --- a/drivers/irqchip/irq-gic-v3-its-msi-parent.c +++ b/drivers/irqchip/irq-gic-v3-its-msi-parent.c @@ -4,6 +4,7 @@ // Copyright (C) 2022 Linutronix GmbH // Copyright (C) 2022 Intel +#include #include #include "irq-gic-common.h" @@ -96,6 +97,68 @@ static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev, #define its_pci_msi_prepare NULL #endif /* !CONFIG_PCI_MSI */ +static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev, + u32 *dev_id) +{ + int ret, index = 0; + + /* Suck the DeviceID out of the msi-parent property */ + do { + struct of_phandle_args args; + + ret = of_parse_phandle_with_args(dev->of_node, + "msi-parent", "#msi-cells", + index, &args); + if (args.np == irq_domain_get_of_node(domain)) { + if (WARN_ON(args.args_count != 1)) + return -EINVAL; + *dev_id = args.args[0]; + break; + } + index++; + } while (!ret); + + return ret; +} + +int __weak iort_pmsi_get_dev_id(struct device *dev, u32 *dev_id) +{ + return -1; +} + +static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, + int nvec, msi_alloc_info_t *info) +{ + struct msi_domain_info *msi_info; + u32 dev_id; + int ret; + + if (dev->of_node) + ret = of_pmsi_get_dev_id(domain, dev, &dev_id); + else + ret = iort_pmsi_get_dev_id(dev, &dev_id); + if (ret) + return ret; + + /* ITS specific DeviceID, as the core ITS ignores dev. */ + info->scratchpad[0].ul = dev_id; + + /* + * @domain->msi_domain_info->hwsize contains the size of the device + * domain, but vector allocation happens one by one. + */ + msi_info = msi_get_domain_info(domain); + if (msi_info->hwsize > nvec) + nvec = msi_info->hwsize; + + /* Allocate at least 32 MSIs, and always as a power of 2 */ + nvec = max_t(int, 32, roundup_pow_of_two(nvec)); + + msi_info = msi_get_domain_info(domain->parent); + return msi_info->ops->msi_prepare(domain->parent, + dev, nvec, info); +} + static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain, struct irq_domain *real_parent, struct msi_domain_info *info) { @@ -120,6 +183,14 @@ static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain, */ info->ops->msi_prepare = its_pci_msi_prepare; break; + case DOMAIN_BUS_DEVICE_MSI: + case DOMAIN_BUS_WIRED_TO_MSI: + /* + * FIXME: See the above PCI prepare comment. The domain + * size is also known at domain creation time. + */ + info->ops->msi_prepare = its_pmsi_prepare; + break; default: /* Confused. How did the lib return true? */ WARN_ON_ONCE(1); @@ -133,7 +204,7 @@ const struct msi_parent_ops gic_v3_its_msi_parent_ops = { .supported_flags = ITS_MSI_FLAGS_SUPPORTED, .required_flags = ITS_MSI_FLAGS_REQUIRED, .bus_select_token = DOMAIN_BUS_NEXUS, - .bus_select_mask = MATCH_PCI_MSI, + .bus_select_mask = MATCH_PCI_MSI | MATCH_PLATFORM_MSI, .prefix = "ITS-", .init_dev_msi_info = its_init_dev_msi_info, }; diff --git a/drivers/irqchip/irq-gic-v3-its-platform-msi.c b/drivers/irqchip/irq-gic-v3-its-platform-msi.c deleted file mode 100644 index daa6d5053bc3..000000000000 --- a/drivers/irqchip/irq-gic-v3-its-platform-msi.c +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2013-2015 ARM Limited, All Rights Reserved. - * Author: Marc Zyngier - */ - -#include -#include -#include -#include -#include - -static struct irq_chip its_pmsi_irq_chip = { - .name = "ITS-pMSI", -}; - -static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev, - u32 *dev_id) -{ - int ret, index = 0; - - /* Suck the DeviceID out of the msi-parent property */ - do { - struct of_phandle_args args; - - ret = of_parse_phandle_with_args(dev->of_node, - "msi-parent", "#msi-cells", - index, &args); - if (args.np == irq_domain_get_of_node(domain)) { - if (WARN_ON(args.args_count != 1)) - return -EINVAL; - *dev_id = args.args[0]; - break; - } - index++; - } while (!ret); - - return ret; -} - -int __weak iort_pmsi_get_dev_id(struct device *dev, u32 *dev_id) -{ - return -1; -} - -static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, - int nvec, msi_alloc_info_t *info) -{ - struct msi_domain_info *msi_info; - u32 dev_id; - int ret; - - msi_info = msi_get_domain_info(domain->parent); - - if (dev->of_node) - ret = of_pmsi_get_dev_id(domain, dev, &dev_id); - else - ret = iort_pmsi_get_dev_id(dev, &dev_id); - if (ret) - return ret; - - /* ITS specific DeviceID, as the core ITS ignores dev. */ - info->scratchpad[0].ul = dev_id; - - /* Allocate at least 32 MSIs, and always as a power of 2 */ - nvec = max_t(int, 32, roundup_pow_of_two(nvec)); - return msi_info->ops->msi_prepare(domain->parent, - dev, nvec, info); -} - -static struct msi_domain_ops its_pmsi_ops = { - .msi_prepare = its_pmsi_prepare, -}; - -static struct msi_domain_info its_pmsi_domain_info = { - .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS), - .ops = &its_pmsi_ops, - .chip = &its_pmsi_irq_chip, -}; - -static const struct of_device_id its_device_id[] = { - { .compatible = "arm,gic-v3-its", }, - {}, -}; - -static int __init its_pmsi_init_one(struct fwnode_handle *fwnode, - const char *name) -{ - struct irq_domain *parent; - - parent = irq_find_matching_fwnode(fwnode, DOMAIN_BUS_NEXUS); - if (!parent || !msi_get_domain_info(parent)) { - pr_err("%s: unable to locate ITS domain\n", name); - return -ENXIO; - } - - if (!platform_msi_create_irq_domain(fwnode, &its_pmsi_domain_info, - parent)) { - pr_err("%s: unable to create platform domain\n", name); - return -ENXIO; - } - - pr_info("Platform MSI: %s domain created\n", name); - return 0; -} - -#ifdef CONFIG_ACPI -static int __init -its_pmsi_parse_madt(union acpi_subtable_headers *header, - const unsigned long end) -{ - struct acpi_madt_generic_translator *its_entry; - struct fwnode_handle *domain_handle; - const char *node_name; - int err = -ENXIO; - - its_entry = (struct acpi_madt_generic_translator *)header; - node_name = kasprintf(GFP_KERNEL, "ITS@0x%lx", - (long)its_entry->base_address); - domain_handle = iort_find_domain_token(its_entry->translation_id); - if (!domain_handle) { - pr_err("%s: Unable to locate ITS domain handle\n", node_name); - goto out; - } - - err = its_pmsi_init_one(domain_handle, node_name); - -out: - kfree(node_name); - return err; -} - -static void __init its_pmsi_acpi_init(void) -{ - acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_TRANSLATOR, - its_pmsi_parse_madt, 0); -} -#else -static inline void its_pmsi_acpi_init(void) { } -#endif - -static void __init its_pmsi_of_init(void) -{ - struct device_node *np; - - for (np = of_find_matching_node(NULL, its_device_id); np; - np = of_find_matching_node(np, its_device_id)) { - if (!of_device_is_available(np)) - continue; - if (!of_property_read_bool(np, "msi-controller")) - continue; - - its_pmsi_init_one(of_node_to_fwnode(np), np->full_name); - } -} - -static int __init its_pmsi_init(void) -{ - its_pmsi_of_init(); - its_pmsi_acpi_init(); - return 0; -} -early_initcall(its_pmsi_init); From 752e021f5b9be0ad42752deaa241ae631f293f9f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:48 +0200 Subject: [PATCH 111/152] irqchip/mbigen: Remove platform_msi_create_device_domain() fallback Now that ITS provides the MSI parent domain, remove the unused fallback code. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.333333826@linutronix.de --- drivers/irqchip/irq-mbigen.c | 74 ++---------------------------------- 1 file changed, 4 insertions(+), 70 deletions(-) diff --git a/drivers/irqchip/irq-mbigen.c b/drivers/irqchip/irq-mbigen.c index db0fa80330d9..093fd42893a7 100644 --- a/drivers/irqchip/irq-mbigen.c +++ b/drivers/irqchip/irq-mbigen.c @@ -180,64 +180,6 @@ static int mbigen_domain_translate(struct irq_domain *d, struct irq_fwspec *fwsp return -EINVAL; } -/* The following section will go away once ITS provides a MSI parent */ - -static struct irq_chip mbigen_irq_chip = { - .name = "mbigen-v2", - .irq_mask = irq_chip_mask_parent, - .irq_unmask = irq_chip_unmask_parent, - .irq_eoi = mbigen_eoi_irq, - .irq_set_type = mbigen_set_type, - .irq_set_affinity = irq_chip_set_affinity_parent, -}; - -static int mbigen_irq_domain_alloc(struct irq_domain *domain, - unsigned int virq, - unsigned int nr_irqs, - void *args) -{ - struct irq_fwspec *fwspec = args; - irq_hw_number_t hwirq; - unsigned int type; - struct mbigen_device *mgn_chip; - int i, err; - - err = mbigen_domain_translate(domain, fwspec, &hwirq, &type); - if (err) - return err; - - err = platform_msi_device_domain_alloc(domain, virq, nr_irqs); - if (err) - return err; - - mgn_chip = platform_msi_get_host_data(domain); - - for (i = 0; i < nr_irqs; i++) - irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, - &mbigen_irq_chip, mgn_chip->base); - - return 0; -} - -static void mbigen_irq_domain_free(struct irq_domain *domain, unsigned int virq, - unsigned int nr_irqs) -{ - platform_msi_device_domain_free(domain, virq, nr_irqs); -} - -static const struct irq_domain_ops mbigen_domain_ops = { - .translate = mbigen_domain_translate, - .alloc = mbigen_irq_domain_alloc, - .free = mbigen_irq_domain_free, -}; - -static void mbigen_write_msg(struct msi_desc *desc, struct msi_msg *msg) -{ - mbigen_write_msi_msg(irq_get_irq_data(desc->irq), msg); -} - -/* End of to be removed section */ - static void mbigen_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) { arg->desc = desc; @@ -268,20 +210,12 @@ static const struct msi_domain_template mbigen_msi_template = { static bool mbigen_create_device_domain(struct device *dev, unsigned int size, struct mbigen_device *mgn_chip) { - struct irq_domain *domain = dev->msi.domain; - - if (WARN_ON_ONCE(!domain)) + if (WARN_ON_ONCE(!dev->msi.domain)) return false; - if (irq_domain_is_msi_parent(domain)) { - return msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN, - &mbigen_msi_template, size, - NULL, mgn_chip->base); - } - - /* Remove once ITS provides MSI parent */ - return !!platform_msi_create_device_domain(dev, size, mbigen_write_msg, - &mbigen_domain_ops, mgn_chip); + return msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN, + &mbigen_msi_template, size, + NULL, mgn_chip->base); } static int mbigen_of_create_domain(struct platform_device *pdev, From f6a9886a9e55a1e6bd78c7e505205d05ef50a71e Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:50 +0200 Subject: [PATCH 112/152] genirq/msi: Remove platform_msi_create_device_domain() No more users. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.395577449@linutronix.de --- include/linux/msi.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/linux/msi.h b/include/linux/msi.h index 04f33e7f6f8b..4ae036d0c7db 100644 --- a/include/linux/msi.h +++ b/include/linux/msi.h @@ -660,8 +660,6 @@ __platform_msi_create_device_domain(struct device *dev, const struct irq_domain_ops *ops, void *host_data); -#define platform_msi_create_device_domain(dev, nvec, write, ops, data) \ - __platform_msi_create_device_domain(dev, nvec, false, write, ops, data) #define platform_msi_create_device_tree_domain(dev, nvec, write, ops, data) \ __platform_msi_create_device_domain(dev, nvec, true, write, ops, data) From da8ec7956efb5dc2be87f5f3beb3c9ed74316baf Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:51 +0200 Subject: [PATCH 113/152] irqchip/gic_v3_mbi: Switch over to parent domain The MBI chip creates two MSI domains: - PCI/MSI - Platform device domain Both have the MBI domain as parent and differ slightly in the interrupt chip callbacks and the platform device domain supports level type signaling. Convert it over to the MSI parent domain mechanism by: - Providing the required templates - Implementing a custom init_dev_msi_info() callback which sets the chip callbacks and the level support flags depending on the domain bus token type of the per device domain. Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.455849114@linutronix.de --- drivers/irqchip/irq-gic-v3-mbi.c | 136 ++++++++++++------------------- 1 file changed, 50 insertions(+), 86 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3-mbi.c b/drivers/irqchip/irq-gic-v3-mbi.c index dbb8b1efda44..3fe870f8ee17 100644 --- a/drivers/irqchip/irq-gic-v3-mbi.c +++ b/drivers/irqchip/irq-gic-v3-mbi.c @@ -18,6 +18,8 @@ #include +#include "irq-msi-lib.h" + struct mbi_range { u32 spi_start; u32 nr_spis; @@ -138,6 +140,7 @@ static void mbi_irq_domain_free(struct irq_domain *domain, } static const struct irq_domain_ops mbi_domain_ops = { + .select = msi_lib_irq_domain_select, .alloc = mbi_irq_domain_alloc, .free = mbi_irq_domain_free, }; @@ -151,54 +154,6 @@ static void mbi_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) iommu_dma_compose_msi_msg(irq_data_get_msi_desc(data), msg); } -#ifdef CONFIG_PCI_MSI -/* PCI-specific irqchip */ -static void mbi_mask_msi_irq(struct irq_data *d) -{ - pci_msi_mask_irq(d); - irq_chip_mask_parent(d); -} - -static void mbi_unmask_msi_irq(struct irq_data *d) -{ - pci_msi_unmask_irq(d); - irq_chip_unmask_parent(d); -} - -static struct irq_chip mbi_msi_irq_chip = { - .name = "MSI", - .irq_mask = mbi_mask_msi_irq, - .irq_unmask = mbi_unmask_msi_irq, - .irq_eoi = irq_chip_eoi_parent, - .irq_compose_msi_msg = mbi_compose_msi_msg, -}; - -static struct msi_domain_info mbi_msi_domain_info = { - .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | - MSI_FLAG_PCI_MSIX | MSI_FLAG_MULTI_PCI_MSI), - .chip = &mbi_msi_irq_chip, -}; - -static int mbi_allocate_pci_domain(struct irq_domain *nexus_domain, - struct irq_domain **pci_domain) -{ - *pci_domain = pci_msi_create_irq_domain(nexus_domain->parent->fwnode, - &mbi_msi_domain_info, - nexus_domain); - if (!*pci_domain) - return -ENOMEM; - - return 0; -} -#else -static int mbi_allocate_pci_domain(struct irq_domain *nexus_domain, - struct irq_domain **pci_domain) -{ - *pci_domain = NULL; - return 0; -} -#endif - static void mbi_compose_mbi_msg(struct irq_data *data, struct msi_msg *msg) { mbi_compose_msi_msg(data, msg); @@ -210,28 +165,51 @@ static void mbi_compose_mbi_msg(struct irq_data *data, struct msi_msg *msg) iommu_dma_compose_msi_msg(irq_data_get_msi_desc(data), &msg[1]); } -/* Platform-MSI specific irqchip */ -static struct irq_chip mbi_pmsi_irq_chip = { - .name = "pMSI", - .irq_set_type = irq_chip_set_type_parent, - .irq_compose_msi_msg = mbi_compose_mbi_msg, - .flags = IRQCHIP_SUPPORTS_LEVEL_MSI, -}; - -static struct msi_domain_ops mbi_pmsi_ops = { -}; - -static struct msi_domain_info mbi_pmsi_domain_info = { - .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | - MSI_FLAG_LEVEL_CAPABLE), - .ops = &mbi_pmsi_ops, - .chip = &mbi_pmsi_irq_chip, -}; - -static int mbi_allocate_domains(struct irq_domain *parent) +static bool mbi_init_dev_msi_info(struct device *dev, struct irq_domain *domain, + struct irq_domain *real_parent, struct msi_domain_info *info) { - struct irq_domain *nexus_domain, *pci_domain, *plat_domain; - int err; + if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info)) + return false; + + switch (info->bus_token) { + case DOMAIN_BUS_PCI_DEVICE_MSI: + case DOMAIN_BUS_PCI_DEVICE_MSIX: + info->chip->irq_compose_msi_msg = mbi_compose_msi_msg; + return true; + + case DOMAIN_BUS_DEVICE_MSI: + info->chip->irq_compose_msi_msg = mbi_compose_mbi_msg; + info->chip->irq_set_type = irq_chip_set_type_parent; + info->chip->flags |= IRQCHIP_SUPPORTS_LEVEL_MSI; + info->flags |= MSI_FLAG_LEVEL_CAPABLE; + return true; + + default: + WARN_ON_ONCE(1); + return false; + } +} + +#define MBI_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ + MSI_FLAG_USE_DEF_CHIP_OPS | \ + MSI_FLAG_PCI_MSI_MASK_PARENT) + +#define MBI_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \ + MSI_FLAG_PCI_MSIX | \ + MSI_FLAG_MULTI_PCI_MSI) + +static const struct msi_parent_ops gic_v3_mbi_msi_parent_ops = { + .supported_flags = MBI_MSI_FLAGS_SUPPORTED, + .required_flags = MBI_MSI_FLAGS_REQUIRED, + .bus_select_token = DOMAIN_BUS_NEXUS, + .bus_select_mask = MATCH_PCI_MSI | MATCH_PLATFORM_MSI, + .prefix = "MBI-", + .init_dev_msi_info = mbi_init_dev_msi_info, +}; + +static int mbi_allocate_domain(struct irq_domain *parent) +{ + struct irq_domain *nexus_domain; nexus_domain = irq_domain_create_hierarchy(parent, 0, 0, parent->fwnode, &mbi_domain_ops, NULL); @@ -239,22 +217,8 @@ static int mbi_allocate_domains(struct irq_domain *parent) return -ENOMEM; irq_domain_update_bus_token(nexus_domain, DOMAIN_BUS_NEXUS); - - err = mbi_allocate_pci_domain(nexus_domain, &pci_domain); - - plat_domain = platform_msi_create_irq_domain(parent->fwnode, - &mbi_pmsi_domain_info, - nexus_domain); - - if (err || !plat_domain) { - if (plat_domain) - irq_domain_remove(plat_domain); - if (pci_domain) - irq_domain_remove(pci_domain); - irq_domain_remove(nexus_domain); - return -ENOMEM; - } - + nexus_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT; + nexus_domain->msi_parent_ops = &gic_v3_mbi_msi_parent_ops; return 0; } @@ -317,7 +281,7 @@ int __init mbi_init(struct fwnode_handle *fwnode, struct irq_domain *parent) pr_info("Using MBI frame %pa\n", &mbi_phys_base); - ret = mbi_allocate_domains(parent); + ret = mbi_allocate_domain(parent); if (ret) goto err_free_mbi; From 74e44454aafefd682706248eb3846e25a1a05c6d Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:53 +0200 Subject: [PATCH 114/152] irqchip/gic-v2m: Switch to device MSI All platform MSI users and the PCI/MSI code handle per device MSI domains when the irqdomain associated to the device provides MSI parent functionality. Remove the "global" PCI/MSI and platform domain related code and provide the MSI parent functionality by filling in msi_parent_ops. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.514419280@linutronix.de --- drivers/irqchip/Kconfig | 1 + drivers/irqchip/irq-gic-v2m.c | 81 +++++++++++------------------------ 2 files changed, 26 insertions(+), 56 deletions(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index b51863fa9b38..2104b8727b1a 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -26,6 +26,7 @@ config ARM_GIC_V2M bool depends on PCI select ARM_GIC + select IRQ_MSI_LIB select PCI_MSI config GIC_NON_BANKED diff --git a/drivers/irqchip/irq-gic-v2m.c b/drivers/irqchip/irq-gic-v2m.c index f2ff4387870d..51af63c046ed 100644 --- a/drivers/irqchip/irq-gic-v2m.c +++ b/drivers/irqchip/irq-gic-v2m.c @@ -26,6 +26,8 @@ #include #include +#include "irq-msi-lib.h" + /* * MSI_TYPER: * [31:26] Reserved @@ -72,31 +74,6 @@ struct v2m_data { u32 flags; /* v2m flags for specific implementation */ }; -static void gicv2m_mask_msi_irq(struct irq_data *d) -{ - pci_msi_mask_irq(d); - irq_chip_mask_parent(d); -} - -static void gicv2m_unmask_msi_irq(struct irq_data *d) -{ - pci_msi_unmask_irq(d); - irq_chip_unmask_parent(d); -} - -static struct irq_chip gicv2m_msi_irq_chip = { - .name = "MSI", - .irq_mask = gicv2m_mask_msi_irq, - .irq_unmask = gicv2m_unmask_msi_irq, - .irq_eoi = irq_chip_eoi_parent, -}; - -static struct msi_domain_info gicv2m_msi_domain_info = { - .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | - MSI_FLAG_PCI_MSIX | MSI_FLAG_MULTI_PCI_MSI), - .chip = &gicv2m_msi_irq_chip, -}; - static phys_addr_t gicv2m_get_msi_addr(struct v2m_data *v2m, int hwirq) { if (v2m->flags & GICV2M_GRAVITON_ADDRESS_ONLY) @@ -230,6 +207,7 @@ static void gicv2m_irq_domain_free(struct irq_domain *domain, } static const struct irq_domain_ops gicv2m_domain_ops = { + .select = msi_lib_irq_domain_select, .alloc = gicv2m_irq_domain_alloc, .free = gicv2m_irq_domain_free, }; @@ -250,19 +228,6 @@ static bool is_msi_spi_valid(u32 base, u32 num) return true; } -static struct irq_chip gicv2m_pmsi_irq_chip = { - .name = "pMSI", -}; - -static struct msi_domain_ops gicv2m_pmsi_ops = { -}; - -static struct msi_domain_info gicv2m_pmsi_domain_info = { - .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS), - .ops = &gicv2m_pmsi_ops, - .chip = &gicv2m_pmsi_irq_chip, -}; - static void __init gicv2m_teardown(void) { struct v2m_data *v2m, *tmp; @@ -278,9 +243,27 @@ static void __init gicv2m_teardown(void) } } + +#define GICV2M_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ + MSI_FLAG_USE_DEF_CHIP_OPS | \ + MSI_FLAG_PCI_MSI_MASK_PARENT) + +#define GICV2M_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \ + MSI_FLAG_PCI_MSIX | \ + MSI_FLAG_MULTI_PCI_MSI) + +static struct msi_parent_ops gicv2m_msi_parent_ops = { + .supported_flags = GICV2M_MSI_FLAGS_SUPPORTED, + .required_flags = GICV2M_MSI_FLAGS_REQUIRED, + .bus_select_token = DOMAIN_BUS_NEXUS, + .bus_select_mask = MATCH_PCI_MSI | MATCH_PLATFORM_MSI, + .prefix = "GICv2m-", + .init_dev_msi_info = msi_lib_init_dev_msi_info, +}; + static __init int gicv2m_allocate_domains(struct irq_domain *parent) { - struct irq_domain *inner_domain, *pci_domain, *plat_domain; + struct irq_domain *inner_domain; struct v2m_data *v2m; v2m = list_first_entry_or_null(&v2m_nodes, struct v2m_data, entry); @@ -295,22 +278,8 @@ static __init int gicv2m_allocate_domains(struct irq_domain *parent) } irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_NEXUS); - pci_domain = pci_msi_create_irq_domain(v2m->fwnode, - &gicv2m_msi_domain_info, - inner_domain); - plat_domain = platform_msi_create_irq_domain(v2m->fwnode, - &gicv2m_pmsi_domain_info, - inner_domain); - if (!pci_domain || !plat_domain) { - pr_err("Failed to create MSI domains\n"); - if (plat_domain) - irq_domain_remove(plat_domain); - if (pci_domain) - irq_domain_remove(pci_domain); - irq_domain_remove(inner_domain); - return -ENOMEM; - } - + inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT; + inner_domain->msi_parent_ops = &gicv2m_msi_parent_ops; return 0; } @@ -511,7 +480,7 @@ acpi_parse_madt_msi(union acpi_subtable_headers *header, pr_info("applying Amazon Graviton quirk\n"); res.end = res.start + SZ_8K - 1; flags |= GICV2M_GRAVITON_ADDRESS_ONLY; - gicv2m_msi_domain_info.flags &= ~MSI_FLAG_MULTI_PCI_MSI; + gicv2m_msi_parent_ops.supported_flags &= ~MSI_FLAG_MULTI_PCI_MSI; } if (m->flags & ACPI_MADT_OVERRIDE_SPI_VALUES) { From 7b2f8aa005bc141ac9144f2fcf2cfb63abd7a0b1 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:55 +0200 Subject: [PATCH 115/152] irqchip/imx-mu-msi: Switch to MSI parent All platform MSI users and the PCI/MSI code handle per device MSI domains when the irqdomain associated to the device provides MSI parent functionality. Remove the "global" platform domain related code and provide the MSI parent functionality by filling in msi_parent_ops. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.574932935@linutronix.de --- drivers/irqchip/Kconfig | 1 + drivers/irqchip/irq-imx-mu-msi.c | 48 ++++++++++++++------------------ drivers/irqchip/irq-msi-lib.c | 2 ++ 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 2104b8727b1a..e7a57b3dc4b1 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -492,6 +492,7 @@ config IMX_MU_MSI select IRQ_DOMAIN select IRQ_DOMAIN_HIERARCHY select GENERIC_MSI_IRQ + select IRQ_MSI_LIB help Provide a driver for the i.MX Messaging Unit block used as a CPU-to-CPU MSI controller. This requires a specially crafted DT diff --git a/drivers/irqchip/irq-imx-mu-msi.c b/drivers/irqchip/irq-imx-mu-msi.c index 1dceda044db9..4342a21de1eb 100644 --- a/drivers/irqchip/irq-imx-mu-msi.c +++ b/drivers/irqchip/irq-imx-mu-msi.c @@ -24,6 +24,8 @@ #include #include +#include "irq-msi-lib.h" + #define IMX_MU_CHANS 4 enum imx_mu_xcr { @@ -114,20 +116,6 @@ static void imx_mu_msi_parent_ack_irq(struct irq_data *data) imx_mu_read(msi_data, msi_data->cfg->xRR + data->hwirq * 4); } -static struct irq_chip imx_mu_msi_irq_chip = { - .name = "MU-MSI", - .irq_ack = irq_chip_ack_parent, -}; - -static struct msi_domain_ops imx_mu_msi_irq_ops = { -}; - -static struct msi_domain_info imx_mu_msi_domain_info = { - .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS), - .ops = &imx_mu_msi_irq_ops, - .chip = &imx_mu_msi_irq_chip, -}; - static void imx_mu_msi_parent_compose_msg(struct irq_data *data, struct msi_msg *msg) { @@ -195,6 +183,7 @@ static void imx_mu_msi_domain_irq_free(struct irq_domain *domain, } static const struct irq_domain_ops imx_mu_msi_domain_ops = { + .select = msi_lib_irq_domain_select, .alloc = imx_mu_msi_domain_irq_alloc, .free = imx_mu_msi_domain_irq_free, }; @@ -216,6 +205,21 @@ static void imx_mu_msi_irq_handler(struct irq_desc *desc) chained_irq_exit(chip, desc); } +#define IMX_MU_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ + MSI_FLAG_USE_DEF_CHIP_OPS | \ + MSI_FLAG_PARENT_PM_DEV) + +#define IMX_MU_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK) + +static const struct msi_parent_ops imx_mu_msi_parent_ops = { + .supported_flags = IMX_MU_MSI_FLAGS_SUPPORTED, + .required_flags = IMX_MU_MSI_FLAGS_REQUIRED, + .bus_select_token = DOMAIN_BUS_NEXUS, + .bus_select_mask = MATCH_PLATFORM_MSI, + .prefix = "MU-MSI-", + .init_dev_msi_info = msi_lib_init_dev_msi_info, +}; + static int imx_mu_msi_domains_init(struct imx_mu_msi *msi_data, struct device *dev) { struct fwnode_handle *fwnodes = dev_fwnode(dev); @@ -230,19 +234,9 @@ static int imx_mu_msi_domains_init(struct imx_mu_msi *msi_data, struct device *d } irq_domain_update_bus_token(parent, DOMAIN_BUS_NEXUS); - - msi_data->msi_domain = platform_msi_create_irq_domain(fwnodes, - &imx_mu_msi_domain_info, - parent); - - if (!msi_data->msi_domain) { - dev_err(dev, "failed to create MSI domain\n"); - irq_domain_remove(parent); - return -ENOMEM; - } - - irq_domain_set_pm_device(msi_data->msi_domain, dev); - + parent->dev = parent->pm_dev = dev; + parent->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT; + parent->msi_parent_ops = &imx_mu_msi_parent_ops; return 0; } diff --git a/drivers/irqchip/irq-msi-lib.c b/drivers/irqchip/irq-msi-lib.c index d20a0e6e0d36..b5b90003311a 100644 --- a/drivers/irqchip/irq-msi-lib.c +++ b/drivers/irqchip/irq-msi-lib.c @@ -94,6 +94,8 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain, /* Chip updates for all child bus types */ if (!info->chip->irq_eoi) info->chip->irq_eoi = irq_chip_eoi_parent; + if (!info->chip->irq_ack) + info->chip->irq_ack = irq_chip_ack_parent; /* * The device MSI domain can never have a set affinity callback. It From d929e4db22b61555a6b091450c26d1f7281ca576 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:56 +0200 Subject: [PATCH 116/152] irqchip/irq-mvebu-icu: Prepare for real per device MSI The core infrastructure has everything in place to switch ICU to per device MSI domains and avoid the convoluted construct of the existing platform-MSI layering violation. The new infrastructure provides a wired interrupt specific interface in the MSI core which converts the 'hardware interrupt number + trigger type' allocation which is required for wired interrupts in the regular irqdomain code to a normal MSI allocation. The hardware interrupt number and the trigger type are stored in the MSI descriptor device cookie by the core code so the ICU specific code can retrieve them. The new per device domain is only instantiated when the irqdomain which is associated to the ICU device provides MSI parent functionality. Up to that point it invokes the existing code. Once the parent is converted the code for the current platform-MSI mechanism is removed. The new domain shares the interrupt chip callbacks and the translation function. The only new functionality aside of filling out the msi_domain_templates is a domain specific set_desc() callback, which will go away once all platform-MSI code has been converted. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.635015886@linutronix.de --- drivers/irqchip/irq-mvebu-icu.c | 181 ++++++++++++++++++++++++++++++-- 1 file changed, 170 insertions(+), 11 deletions(-) diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c index 3c77acc7ec6a..2a210cd9482e 100644 --- a/drivers/irqchip/irq-mvebu-icu.c +++ b/drivers/irqchip/irq-mvebu-icu.c @@ -20,6 +20,8 @@ #include #include +#include "irq-msi-lib.h" + #include /* ICU registers */ @@ -60,14 +62,52 @@ struct mvebu_icu_msi_data { const struct mvebu_icu_subset_data *subset_data; }; -struct mvebu_icu_irq_data { - struct mvebu_icu *icu; - unsigned int icu_group; - unsigned int type; -}; - static DEFINE_STATIC_KEY_FALSE(legacy_bindings); +static int mvebu_icu_translate(struct irq_domain *d, struct irq_fwspec *fwspec, + unsigned long *hwirq, unsigned int *type) +{ + unsigned int param_count = static_branch_unlikely(&legacy_bindings) ? 3 : 2; + struct mvebu_icu_msi_data *msi_data = d->host_data; + struct mvebu_icu *icu = msi_data->icu; + + /* Check the count of the parameters in dt */ + if (WARN_ON(fwspec->param_count != param_count)) { + dev_err(icu->dev, "wrong ICU parameter count %d\n", + fwspec->param_count); + return -EINVAL; + } + + if (static_branch_unlikely(&legacy_bindings)) { + *hwirq = fwspec->param[1]; + *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; + if (fwspec->param[0] != ICU_GRP_NSR) { + dev_err(icu->dev, "wrong ICU group type %x\n", + fwspec->param[0]); + return -EINVAL; + } + } else { + *hwirq = fwspec->param[0]; + *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; + + /* + * The ICU receives level interrupts. While the NSR are also + * level interrupts, SEI are edge interrupts. Force the type + * here in this case. Please note that this makes the interrupt + * handling unreliable. + */ + if (msi_data->subset_data->icu_group == ICU_GRP_SEI) + *type = IRQ_TYPE_EDGE_RISING; + } + + if (*hwirq >= ICU_MAX_IRQS) { + dev_err(icu->dev, "invalid interrupt number %ld\n", *hwirq); + return -EINVAL; + } + + return 0; +} + static void mvebu_icu_init(struct mvebu_icu *icu, struct mvebu_icu_msi_data *msi_data, struct msi_msg *msg) @@ -89,6 +129,14 @@ static void mvebu_icu_init(struct mvebu_icu *icu, writel_relaxed(msg[1].address_lo, icu->base + subset->offset_clr_al); } +/* Start of area to be removed once all parent chips provide MSI parent */ + +struct mvebu_icu_irq_data { + struct mvebu_icu *icu; + unsigned int icu_group; + unsigned int type; +}; + static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg) { struct irq_data *d = irq_get_irq_data(desc->irq); @@ -269,6 +317,109 @@ static const struct irq_domain_ops mvebu_icu_domain_ops = { .free = mvebu_icu_irq_domain_free, }; +/* End of removal area */ + +static int mvebu_icu_msi_init(struct irq_domain *domain, struct msi_domain_info *info, + unsigned int virq, irq_hw_number_t hwirq, msi_alloc_info_t *arg) +{ + irq_domain_set_hwirq_and_chip(domain, virq, hwirq, info->chip, info->chip_data); + return irq_set_irqchip_state(virq, IRQCHIP_STATE_PENDING, false); +} + +static void mvebu_icu_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) +{ + arg->desc = desc; + arg->hwirq = (u32)desc->data.icookie.value; +} + +static void mvebu_icu_write_msi_msg(struct irq_data *d, struct msi_msg *msg) +{ + struct mvebu_icu_msi_data *msi_data = d->chip_data; + unsigned int icu_group = msi_data->subset_data->icu_group; + struct msi_desc *desc = irq_data_get_msi_desc(d); + struct mvebu_icu *icu = msi_data->icu; + unsigned int type; + u32 icu_int; + + if (msg->address_lo || msg->address_hi) { + /* One off initialization per domain */ + mvebu_icu_init(icu, msi_data, msg); + /* Configure the ICU with irq number & type */ + icu_int = msg->data | ICU_INT_ENABLE; + type = (unsigned int)(desc->data.icookie.value >> 32); + if (type & IRQ_TYPE_EDGE_RISING) + icu_int |= ICU_IS_EDGE; + icu_int |= icu_group << ICU_GROUP_SHIFT; + } else { + /* De-configure the ICU */ + icu_int = 0; + } + + writel_relaxed(icu_int, icu->base + ICU_INT_CFG(d->hwirq)); + + /* + * The SATA unit has 2 ports, and a dedicated ICU entry per + * port. The ahci sata driver supports only one irq interrupt + * per SATA unit. To solve this conflict, we configure the 2 + * SATA wired interrupts in the south bridge into 1 GIC + * interrupt in the north bridge. Even if only a single port + * is enabled, if sata node is enabled, both interrupts are + * configured (regardless of which port is actually in use). + */ + if (d->hwirq == ICU_SATA0_ICU_ID || d->hwirq == ICU_SATA1_ICU_ID) { + writel_relaxed(icu_int, icu->base + ICU_INT_CFG(ICU_SATA0_ICU_ID)); + writel_relaxed(icu_int, icu->base + ICU_INT_CFG(ICU_SATA1_ICU_ID)); + } +} + +static const struct msi_domain_template mvebu_icu_nsr_msi_template = { + .chip = { + .name = "ICU-NSR", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_eoi = irq_chip_eoi_parent, + .irq_set_type = irq_chip_set_type_parent, + .irq_write_msi_msg = mvebu_icu_write_msi_msg, + .flags = IRQCHIP_SUPPORTS_LEVEL_MSI, + }, + + .ops = { + .msi_translate = mvebu_icu_translate, + .msi_init = mvebu_icu_msi_init, + .set_desc = mvebu_icu_set_desc, + }, + + .info = { + .bus_token = DOMAIN_BUS_WIRED_TO_MSI, + .flags = MSI_FLAG_LEVEL_CAPABLE | + MSI_FLAG_USE_DEV_FWNODE, + }, +}; + +static const struct msi_domain_template mvebu_icu_sei_msi_template = { + .chip = { + .name = "ICU-SEI", + .irq_mask = irq_chip_mask_parent, + .irq_unmask = irq_chip_unmask_parent, + .irq_ack = irq_chip_ack_parent, + .irq_set_type = irq_chip_set_type_parent, + .irq_write_msi_msg = mvebu_icu_write_msi_msg, + .flags = IRQCHIP_SUPPORTS_LEVEL_MSI, + }, + + .ops = { + .msi_translate = mvebu_icu_translate, + .msi_init = mvebu_icu_msi_init, + .set_desc = mvebu_icu_set_desc, + }, + + .info = { + .bus_token = DOMAIN_BUS_WIRED_TO_MSI, + .flags = MSI_FLAG_LEVEL_CAPABLE | + MSI_FLAG_USE_DEV_FWNODE, + }, +}; + static const struct mvebu_icu_subset_data mvebu_icu_nsr_subset_data = { .icu_group = ICU_GRP_NSR, .offset_set_ah = ICU_SETSPI_NSR_AH, @@ -298,7 +449,6 @@ static const struct of_device_id mvebu_icu_subset_of_match[] = { static int mvebu_icu_subset_probe(struct platform_device *pdev) { struct mvebu_icu_msi_data *msi_data; - struct device_node *msi_parent_dn; struct device *dev = &pdev->dev; struct irq_domain *irq_domain; @@ -314,15 +464,24 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev) msi_data->subset_data = of_device_get_match_data(dev); } - dev->msi.domain = of_msi_get_domain(dev, dev->of_node, - DOMAIN_BUS_PLATFORM_MSI); + dev->msi.domain = of_msi_get_domain(dev, dev->of_node, DOMAIN_BUS_PLATFORM_MSI); if (!dev->msi.domain) return -EPROBE_DEFER; - msi_parent_dn = irq_domain_get_of_node(dev->msi.domain); - if (!msi_parent_dn) + if (!irq_domain_get_of_node(dev->msi.domain)) return -ENODEV; + if (irq_domain_is_msi_parent(dev->msi.domain)) { + bool sei = msi_data->subset_data->icu_group == ICU_GRP_SEI; + const struct msi_domain_template *tmpl; + + tmpl = sei ? &mvebu_icu_sei_msi_template : &mvebu_icu_nsr_msi_template; + + if (!msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN, tmpl, + ICU_MAX_IRQS, NULL, msi_data)) + return -ENOMEM; + } + irq_domain = platform_msi_create_device_tree_domain(dev, ICU_MAX_IRQS, mvebu_icu_write_msg, &mvebu_icu_domain_ops, From cdb238723018eb766040d7be5d879b4c81ad3d50 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:18:58 +0200 Subject: [PATCH 117/152] irqchip/mvebu-gicp: Switch to MSI parent All platform MSI users and the PCI/MSI code handle per device MSI domains when the irqdomain associated to the device provides MSI parent functionality. Remove the "global" platform domain related code and provide the MSI parent functionality by filling in msi_parent_ops. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.699780279@linutronix.de --- drivers/irqchip/Kconfig | 1 + drivers/irqchip/irq-mvebu-gicp.c | 44 ++++++++++++++------------------ 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index e7a57b3dc4b1..a0fa59928913 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -371,6 +371,7 @@ config MSCC_OCELOT_IRQ select GENERIC_IRQ_CHIP config MVEBU_GICP + select IRQ_MSI_LIB bool config MVEBU_ICU diff --git a/drivers/irqchip/irq-mvebu-gicp.c b/drivers/irqchip/irq-mvebu-gicp.c index c43a345061d5..2b6183919ea4 100644 --- a/drivers/irqchip/irq-mvebu-gicp.c +++ b/drivers/irqchip/irq-mvebu-gicp.c @@ -17,6 +17,8 @@ #include #include +#include "irq-msi-lib.h" + #include #define GICP_SETSPI_NSR_OFFSET 0x0 @@ -145,32 +147,32 @@ static void gicp_irq_domain_free(struct irq_domain *domain, } static const struct irq_domain_ops gicp_domain_ops = { + .select = msi_lib_irq_domain_select, .alloc = gicp_irq_domain_alloc, .free = gicp_irq_domain_free, }; -static struct irq_chip gicp_msi_irq_chip = { - .name = "GICP", - .irq_set_type = irq_chip_set_type_parent, - .flags = IRQCHIP_SUPPORTS_LEVEL_MSI, -}; +#define GICP_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ + MSI_FLAG_USE_DEF_CHIP_OPS) -static struct msi_domain_ops gicp_msi_ops = { -}; +#define GICP_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \ + MSI_FLAG_LEVEL_CAPABLE) -static struct msi_domain_info gicp_msi_domain_info = { - .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | - MSI_FLAG_LEVEL_CAPABLE), - .ops = &gicp_msi_ops, - .chip = &gicp_msi_irq_chip, +static const struct msi_parent_ops gicp_msi_parent_ops = { + .supported_flags = GICP_MSI_FLAGS_SUPPORTED, + .required_flags = GICP_MSI_FLAGS_REQUIRED, + .bus_select_token = DOMAIN_BUS_GENERIC_MSI, + .bus_select_mask = MATCH_PLATFORM_MSI, + .prefix = "GICP-", + .init_dev_msi_info = msi_lib_init_dev_msi_info, }; static int mvebu_gicp_probe(struct platform_device *pdev) { - struct mvebu_gicp *gicp; - struct irq_domain *inner_domain, *plat_domain, *parent_domain; + struct irq_domain *inner_domain, *parent_domain; struct device_node *node = pdev->dev.of_node; struct device_node *irq_parent_dn; + struct mvebu_gicp *gicp; int ret, i; gicp = devm_kzalloc(&pdev->dev, sizeof(*gicp), GFP_KERNEL); @@ -234,17 +236,9 @@ static int mvebu_gicp_probe(struct platform_device *pdev) if (!inner_domain) return -ENOMEM; - - plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node), - &gicp_msi_domain_info, - inner_domain); - if (!plat_domain) { - irq_domain_remove(inner_domain); - return -ENOMEM; - } - - platform_set_drvdata(pdev, gicp); - + irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_GENERIC_MSI); + inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT; + inner_domain->msi_parent_ops = &gicp_msi_parent_ops; return 0; } From e0b99c4c5917759c257a5c41d2c3e4d7c014578b Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:19:00 +0200 Subject: [PATCH 118/152] irqchip/mvebu-odmi: Switch to parent MSI All platform MSI users and the PCI/MSI code handle per device MSI domains when the irqdomain associated to the device provides MSI parent functionality. Remove the "global" platform domain related code and provide the MSI parent functionality by filling in msi_parent_ops. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.759892514@linutronix.de --- drivers/irqchip/Kconfig | 1 + drivers/irqchip/irq-mvebu-odmi.c | 37 ++++++++++++++++---------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index a0fa59928913..4def6dc76fa6 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -379,6 +379,7 @@ config MVEBU_ICU config MVEBU_ODMI bool + select IRQ_MSI_LIB select GENERIC_MSI_IRQ config MVEBU_PIC diff --git a/drivers/irqchip/irq-mvebu-odmi.c b/drivers/irqchip/irq-mvebu-odmi.c index 108091533e10..ff19bfd258dc 100644 --- a/drivers/irqchip/irq-mvebu-odmi.c +++ b/drivers/irqchip/irq-mvebu-odmi.c @@ -17,6 +17,9 @@ #include #include #include + +#include "irq-msi-lib.h" + #include #define GICP_ODMIN_SET 0x40 @@ -141,27 +144,29 @@ static void odmi_irq_domain_free(struct irq_domain *domain, } static const struct irq_domain_ops odmi_domain_ops = { + .select = msi_lib_irq_domain_select, .alloc = odmi_irq_domain_alloc, .free = odmi_irq_domain_free, }; -static struct irq_chip odmi_msi_irq_chip = { - .name = "ODMI", -}; +#define ODMI_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ + MSI_FLAG_USE_DEF_CHIP_OPS) -static struct msi_domain_ops odmi_msi_ops = { -}; +#define ODMI_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK) -static struct msi_domain_info odmi_msi_domain_info = { - .flags = (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS), - .ops = &odmi_msi_ops, - .chip = &odmi_msi_irq_chip, +static const struct msi_parent_ops odmi_msi_parent_ops = { + .supported_flags = ODMI_MSI_FLAGS_SUPPORTED, + .required_flags = ODMI_MSI_FLAGS_REQUIRED, + .bus_select_token = DOMAIN_BUS_GENERIC_MSI, + .bus_select_mask = MATCH_PLATFORM_MSI, + .prefix = "ODMI-", + .init_dev_msi_info = msi_lib_init_dev_msi_info, }; static int __init mvebu_odmi_init(struct device_node *node, struct device_node *parent) { - struct irq_domain *parent_domain, *inner_domain, *plat_domain; + struct irq_domain *parent_domain, *inner_domain; int ret, i; if (of_property_read_u32(node, "marvell,odmi-frames", &odmis_count)) @@ -208,18 +213,12 @@ static int __init mvebu_odmi_init(struct device_node *node, goto err_unmap; } - plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node), - &odmi_msi_domain_info, - inner_domain); - if (!plat_domain) { - ret = -ENOMEM; - goto err_remove_inner; - } + irq_domain_update_bus_token(inner_domain, DOMAIN_BUS_GENERIC_MSI); + inner_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT; + inner_domain->msi_parent_ops = &odmi_msi_parent_ops; return 0; -err_remove_inner: - irq_domain_remove(inner_domain); err_unmap: for (i = 0; i < odmis_count; i++) { struct odmi_data *odmi = &odmis[i]; From fbdf14e90ce445fedfb387413c3d8dc9d90db2a7 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:19:02 +0200 Subject: [PATCH 119/152] irqchip/irq-mvebu-sei: Switch to MSI parent All platform MSI users and the PCI/MSI code handle per device MSI domains when the irqdomain associated to the device provides MSI parent functionality. Remove the "global" platform domain related code and provide the MSI parent functionality by filling in msi_parent_ops. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.820275215@linutronix.de --- drivers/irqchip/irq-mvebu-sei.c | 52 +++++++++++++-------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/drivers/irqchip/irq-mvebu-sei.c b/drivers/irqchip/irq-mvebu-sei.c index a48dbe91b036..f8c70f2d100a 100644 --- a/drivers/irqchip/irq-mvebu-sei.c +++ b/drivers/irqchip/irq-mvebu-sei.c @@ -14,6 +14,8 @@ #include #include +#include "irq-msi-lib.h" + /* Cause register */ #define GICP_SECR(idx) (0x0 + ((idx) * 0x4)) /* Mask register */ @@ -190,6 +192,7 @@ static void mvebu_sei_domain_free(struct irq_domain *domain, unsigned int virq, } static const struct irq_domain_ops mvebu_sei_domain_ops = { + .select = msi_lib_irq_domain_select, .alloc = mvebu_sei_domain_alloc, .free = mvebu_sei_domain_free, }; @@ -307,21 +310,6 @@ static const struct irq_domain_ops mvebu_sei_cp_domain_ops = { .free = mvebu_sei_cp_domain_free, }; -static struct irq_chip mvebu_sei_msi_irq_chip = { - .name = "SEI pMSI", - .irq_ack = irq_chip_ack_parent, - .irq_set_type = irq_chip_set_type_parent, -}; - -static struct msi_domain_ops mvebu_sei_msi_ops = { -}; - -static struct msi_domain_info mvebu_sei_msi_domain_info = { - .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS, - .ops = &mvebu_sei_msi_ops, - .chip = &mvebu_sei_msi_irq_chip, -}; - static void mvebu_sei_handle_cascade_irq(struct irq_desc *desc) { struct mvebu_sei *sei = irq_desc_get_handler_data(desc); @@ -360,10 +348,23 @@ static void mvebu_sei_reset(struct mvebu_sei *sei) } } +#define SEI_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ + MSI_FLAG_USE_DEF_CHIP_OPS) + +#define SEI_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK) + +static const struct msi_parent_ops sei_msi_parent_ops = { + .supported_flags = SEI_MSI_FLAGS_SUPPORTED, + .required_flags = SEI_MSI_FLAGS_REQUIRED, + .bus_select_mask = MATCH_PLATFORM_MSI, + .bus_select_token = DOMAIN_BUS_GENERIC_MSI, + .prefix = "SEI-", + .init_dev_msi_info = msi_lib_init_dev_msi_info, +}; + static int mvebu_sei_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; - struct irq_domain *plat_domain; struct mvebu_sei *sei; u32 parent_irq; int ret; @@ -440,33 +441,20 @@ static int mvebu_sei_probe(struct platform_device *pdev) } irq_domain_update_bus_token(sei->cp_domain, DOMAIN_BUS_GENERIC_MSI); - - plat_domain = platform_msi_create_irq_domain(of_node_to_fwnode(node), - &mvebu_sei_msi_domain_info, - sei->cp_domain); - if (!plat_domain) { - pr_err("Failed to create CPs MSI domain\n"); - ret = -ENOMEM; - goto remove_cp_domain; - } + sei->cp_domain->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT; + sei->cp_domain->msi_parent_ops = &sei_msi_parent_ops; mvebu_sei_reset(sei); - irq_set_chained_handler_and_data(parent_irq, - mvebu_sei_handle_cascade_irq, - sei); - + irq_set_chained_handler_and_data(parent_irq, mvebu_sei_handle_cascade_irq, sei); return 0; -remove_cp_domain: - irq_domain_remove(sei->cp_domain); remove_ap_domain: irq_domain_remove(sei->ap_domain); remove_sei_domain: irq_domain_remove(sei->sei_domain); dispose_irq: irq_dispose_mapping(parent_irq); - return ret; } From ce44d1ff463dcf812b43c71a0e16859e3451bc1e Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:19:04 +0200 Subject: [PATCH 120/152] irqchip/irq-mvebu-icu: Remove platform MSI leftovers All related domains provide MSI parent functionality, so the fallback code to the original platform MSI implementation is not longer required. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142235.881677325@linutronix.de --- drivers/irqchip/irq-mvebu-icu.c | 212 +------------------------------- 1 file changed, 6 insertions(+), 206 deletions(-) diff --git a/drivers/irqchip/irq-mvebu-icu.c b/drivers/irqchip/irq-mvebu-icu.c index 2a210cd9482e..b337f6c05f18 100644 --- a/drivers/irqchip/irq-mvebu-icu.c +++ b/drivers/irqchip/irq-mvebu-icu.c @@ -129,196 +129,6 @@ static void mvebu_icu_init(struct mvebu_icu *icu, writel_relaxed(msg[1].address_lo, icu->base + subset->offset_clr_al); } -/* Start of area to be removed once all parent chips provide MSI parent */ - -struct mvebu_icu_irq_data { - struct mvebu_icu *icu; - unsigned int icu_group; - unsigned int type; -}; - -static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg) -{ - struct irq_data *d = irq_get_irq_data(desc->irq); - struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d->domain); - struct mvebu_icu_irq_data *icu_irqd = d->chip_data; - struct mvebu_icu *icu = icu_irqd->icu; - unsigned int icu_int; - - if (msg->address_lo || msg->address_hi) { - /* One off initialization per domain */ - mvebu_icu_init(icu, msi_data, msg); - /* Configure the ICU with irq number & type */ - icu_int = msg->data | ICU_INT_ENABLE; - if (icu_irqd->type & IRQ_TYPE_EDGE_RISING) - icu_int |= ICU_IS_EDGE; - icu_int |= icu_irqd->icu_group << ICU_GROUP_SHIFT; - } else { - /* De-configure the ICU */ - icu_int = 0; - } - - writel_relaxed(icu_int, icu->base + ICU_INT_CFG(d->hwirq)); - - /* - * The SATA unit has 2 ports, and a dedicated ICU entry per - * port. The ahci sata driver supports only one irq interrupt - * per SATA unit. To solve this conflict, we configure the 2 - * SATA wired interrupts in the south bridge into 1 GIC - * interrupt in the north bridge. Even if only a single port - * is enabled, if sata node is enabled, both interrupts are - * configured (regardless of which port is actually in use). - */ - if (d->hwirq == ICU_SATA0_ICU_ID || d->hwirq == ICU_SATA1_ICU_ID) { - writel_relaxed(icu_int, - icu->base + ICU_INT_CFG(ICU_SATA0_ICU_ID)); - writel_relaxed(icu_int, - icu->base + ICU_INT_CFG(ICU_SATA1_ICU_ID)); - } -} - -static struct irq_chip mvebu_icu_nsr_chip = { - .name = "ICU-NSR", - .irq_mask = irq_chip_mask_parent, - .irq_unmask = irq_chip_unmask_parent, - .irq_eoi = irq_chip_eoi_parent, - .irq_set_type = irq_chip_set_type_parent, - .irq_set_affinity = irq_chip_set_affinity_parent, -}; - -static struct irq_chip mvebu_icu_sei_chip = { - .name = "ICU-SEI", - .irq_ack = irq_chip_ack_parent, - .irq_mask = irq_chip_mask_parent, - .irq_unmask = irq_chip_unmask_parent, - .irq_set_type = irq_chip_set_type_parent, - .irq_set_affinity = irq_chip_set_affinity_parent, -}; - -static int -mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec, - unsigned long *hwirq, unsigned int *type) -{ - unsigned int param_count = static_branch_unlikely(&legacy_bindings) ? 3 : 2; - struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d); - struct mvebu_icu *icu = msi_data->icu; - - /* Check the count of the parameters in dt */ - if (WARN_ON(fwspec->param_count != param_count)) { - dev_err(icu->dev, "wrong ICU parameter count %d\n", - fwspec->param_count); - return -EINVAL; - } - - if (static_branch_unlikely(&legacy_bindings)) { - *hwirq = fwspec->param[1]; - *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; - if (fwspec->param[0] != ICU_GRP_NSR) { - dev_err(icu->dev, "wrong ICU group type %x\n", - fwspec->param[0]); - return -EINVAL; - } - } else { - *hwirq = fwspec->param[0]; - *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; - - /* - * The ICU receives level interrupts. While the NSR are also - * level interrupts, SEI are edge interrupts. Force the type - * here in this case. Please note that this makes the interrupt - * handling unreliable. - */ - if (msi_data->subset_data->icu_group == ICU_GRP_SEI) - *type = IRQ_TYPE_EDGE_RISING; - } - - if (*hwirq >= ICU_MAX_IRQS) { - dev_err(icu->dev, "invalid interrupt number %ld\n", *hwirq); - return -EINVAL; - } - - return 0; -} - -static int -mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, - unsigned int nr_irqs, void *args) -{ - int err; - unsigned long hwirq; - struct irq_fwspec *fwspec = args; - struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(domain); - struct mvebu_icu *icu = msi_data->icu; - struct mvebu_icu_irq_data *icu_irqd; - struct irq_chip *chip = &mvebu_icu_nsr_chip; - - icu_irqd = kmalloc(sizeof(*icu_irqd), GFP_KERNEL); - if (!icu_irqd) - return -ENOMEM; - - err = mvebu_icu_irq_domain_translate(domain, fwspec, &hwirq, - &icu_irqd->type); - if (err) { - dev_err(icu->dev, "failed to translate ICU parameters\n"); - goto free_irqd; - } - - if (static_branch_unlikely(&legacy_bindings)) - icu_irqd->icu_group = fwspec->param[0]; - else - icu_irqd->icu_group = msi_data->subset_data->icu_group; - icu_irqd->icu = icu; - - err = platform_msi_device_domain_alloc(domain, virq, nr_irqs); - if (err) { - dev_err(icu->dev, "failed to allocate ICU interrupt in parent domain\n"); - goto free_irqd; - } - - /* Make sure there is no interrupt left pending by the firmware */ - err = irq_set_irqchip_state(virq, IRQCHIP_STATE_PENDING, false); - if (err) - goto free_msi; - - if (icu_irqd->icu_group == ICU_GRP_SEI) - chip = &mvebu_icu_sei_chip; - - err = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, - chip, icu_irqd); - if (err) { - dev_err(icu->dev, "failed to set the data to IRQ domain\n"); - goto free_msi; - } - - return 0; - -free_msi: - platform_msi_device_domain_free(domain, virq, nr_irqs); -free_irqd: - kfree(icu_irqd); - return err; -} - -static void -mvebu_icu_irq_domain_free(struct irq_domain *domain, unsigned int virq, - unsigned int nr_irqs) -{ - struct irq_data *d = irq_get_irq_data(virq); - struct mvebu_icu_irq_data *icu_irqd = d->chip_data; - - kfree(icu_irqd); - - platform_msi_device_domain_free(domain, virq, nr_irqs); -} - -static const struct irq_domain_ops mvebu_icu_domain_ops = { - .translate = mvebu_icu_irq_domain_translate, - .alloc = mvebu_icu_irq_domain_alloc, - .free = mvebu_icu_irq_domain_free, -}; - -/* End of removal area */ - static int mvebu_icu_msi_init(struct irq_domain *domain, struct msi_domain_info *info, unsigned int virq, irq_hw_number_t hwirq, msi_alloc_info_t *arg) { @@ -448,9 +258,10 @@ static const struct of_device_id mvebu_icu_subset_of_match[] = { static int mvebu_icu_subset_probe(struct platform_device *pdev) { + const struct msi_domain_template *tmpl; struct mvebu_icu_msi_data *msi_data; struct device *dev = &pdev->dev; - struct irq_domain *irq_domain; + bool sei; msi_data = devm_kzalloc(dev, sizeof(*msi_data), GFP_KERNEL); if (!msi_data) @@ -471,22 +282,11 @@ static int mvebu_icu_subset_probe(struct platform_device *pdev) if (!irq_domain_get_of_node(dev->msi.domain)) return -ENODEV; - if (irq_domain_is_msi_parent(dev->msi.domain)) { - bool sei = msi_data->subset_data->icu_group == ICU_GRP_SEI; - const struct msi_domain_template *tmpl; + sei = msi_data->subset_data->icu_group == ICU_GRP_SEI; + tmpl = sei ? &mvebu_icu_sei_msi_template : &mvebu_icu_nsr_msi_template; - tmpl = sei ? &mvebu_icu_sei_msi_template : &mvebu_icu_nsr_msi_template; - - if (!msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN, tmpl, - ICU_MAX_IRQS, NULL, msi_data)) - return -ENOMEM; - } - - irq_domain = platform_msi_create_device_tree_domain(dev, ICU_MAX_IRQS, - mvebu_icu_write_msg, - &mvebu_icu_domain_ops, - msi_data); - if (!irq_domain) { + if (!msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN, tmpl, + ICU_MAX_IRQS, NULL, msi_data)) { dev_err(dev, "Failed to create ICU MSI domain\n"); return -ENOMEM; } From e9894248994ca8291838baf063f045eab28e5a0e Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:19:05 +0200 Subject: [PATCH 121/152] genirq/msi: Remove platform MSI leftovers No more users! Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Reviewed-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20240623142235.943295676@linutronix.de --- drivers/base/platform-msi.c | 350 +----------------------------------- include/linux/msi.h | 30 ---- kernel/irq/msi.c | 75 +------- 3 files changed, 5 insertions(+), 450 deletions(-) diff --git a/drivers/base/platform-msi.c b/drivers/base/platform-msi.c index 11f5fdf65b9e..0e60dd650b5e 100644 --- a/drivers/base/platform-msi.c +++ b/drivers/base/platform-msi.c @@ -4,346 +4,12 @@ * * Copyright (C) 2015 ARM Limited, All Rights Reserved. * Author: Marc Zyngier + * Copyright (C) 2022 Linutronix GmbH */ #include -#include -#include #include #include -#include - -/* Begin of removal area. Once everything is converted over. Cleanup the includes too! */ - -#define DEV_ID_SHIFT 21 -#define MAX_DEV_MSIS (1 << (32 - DEV_ID_SHIFT)) - -/* - * Internal data structure containing a (made up, but unique) devid - * and the callback to write the MSI message. - */ -struct platform_msi_priv_data { - struct device *dev; - void *host_data; - msi_alloc_info_t arg; - irq_write_msi_msg_t write_msg; - int devid; -}; - -/* The devid allocator */ -static DEFINE_IDA(platform_msi_devid_ida); - -#ifdef GENERIC_MSI_DOMAIN_OPS -/* - * Convert an msi_desc to a globaly unique identifier (per-device - * devid + msi_desc position in the msi_list). - */ -static irq_hw_number_t platform_msi_calc_hwirq(struct msi_desc *desc) -{ - u32 devid = desc->dev->msi.data->platform_data->devid; - - return (devid << (32 - DEV_ID_SHIFT)) | desc->msi_index; -} - -static void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) -{ - arg->desc = desc; - arg->hwirq = platform_msi_calc_hwirq(desc); -} - -static int platform_msi_init(struct irq_domain *domain, - struct msi_domain_info *info, - unsigned int virq, irq_hw_number_t hwirq, - msi_alloc_info_t *arg) -{ - return irq_domain_set_hwirq_and_chip(domain, virq, hwirq, - info->chip, info->chip_data); -} - -static void platform_msi_set_proxy_dev(msi_alloc_info_t *arg) -{ - arg->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE; -} -#else -#define platform_msi_set_desc NULL -#define platform_msi_init NULL -#define platform_msi_set_proxy_dev(x) do {} while(0) -#endif - -static void platform_msi_update_dom_ops(struct msi_domain_info *info) -{ - struct msi_domain_ops *ops = info->ops; - - BUG_ON(!ops); - - if (ops->msi_init == NULL) - ops->msi_init = platform_msi_init; - if (ops->set_desc == NULL) - ops->set_desc = platform_msi_set_desc; -} - -static void platform_msi_write_msg(struct irq_data *data, struct msi_msg *msg) -{ - struct msi_desc *desc = irq_data_get_msi_desc(data); - - desc->dev->msi.data->platform_data->write_msg(desc, msg); -} - -static void platform_msi_update_chip_ops(struct msi_domain_info *info) -{ - struct irq_chip *chip = info->chip; - - BUG_ON(!chip); - if (!chip->irq_mask) - chip->irq_mask = irq_chip_mask_parent; - if (!chip->irq_unmask) - chip->irq_unmask = irq_chip_unmask_parent; - if (!chip->irq_eoi) - chip->irq_eoi = irq_chip_eoi_parent; - if (!chip->irq_set_affinity) - chip->irq_set_affinity = msi_domain_set_affinity; - if (!chip->irq_write_msi_msg) - chip->irq_write_msi_msg = platform_msi_write_msg; - if (WARN_ON((info->flags & MSI_FLAG_LEVEL_CAPABLE) && - !(chip->flags & IRQCHIP_SUPPORTS_LEVEL_MSI))) - info->flags &= ~MSI_FLAG_LEVEL_CAPABLE; -} - -/** - * platform_msi_create_irq_domain - Create a platform MSI interrupt domain - * @fwnode: Optional fwnode of the interrupt controller - * @info: MSI domain info - * @parent: Parent irq domain - * - * Updates the domain and chip ops and creates a platform MSI - * interrupt domain. - * - * Returns: - * A domain pointer or NULL in case of failure. - */ -struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode, - struct msi_domain_info *info, - struct irq_domain *parent) -{ - struct irq_domain *domain; - - if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) - platform_msi_update_dom_ops(info); - if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) - platform_msi_update_chip_ops(info); - info->flags |= MSI_FLAG_DEV_SYSFS | MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | - MSI_FLAG_FREE_MSI_DESCS; - - domain = msi_create_irq_domain(fwnode, info, parent); - if (domain) - irq_domain_update_bus_token(domain, DOMAIN_BUS_PLATFORM_MSI); - - return domain; -} -EXPORT_SYMBOL_GPL(platform_msi_create_irq_domain); - -static int platform_msi_alloc_priv_data(struct device *dev, unsigned int nvec, - irq_write_msi_msg_t write_msi_msg) -{ - struct platform_msi_priv_data *datap; - int err; - - /* - * Limit the number of interrupts to 2048 per device. Should we - * need to bump this up, DEV_ID_SHIFT should be adjusted - * accordingly (which would impact the max number of MSI - * capable devices). - */ - if (!dev->msi.domain || !write_msi_msg || !nvec || nvec > MAX_DEV_MSIS) - return -EINVAL; - - if (dev->msi.domain->bus_token != DOMAIN_BUS_PLATFORM_MSI) { - dev_err(dev, "Incompatible msi_domain, giving up\n"); - return -EINVAL; - } - - err = msi_setup_device_data(dev); - if (err) - return err; - - /* Already initialized? */ - if (dev->msi.data->platform_data) - return -EBUSY; - - datap = kzalloc(sizeof(*datap), GFP_KERNEL); - if (!datap) - return -ENOMEM; - - datap->devid = ida_alloc_max(&platform_msi_devid_ida, - (1 << DEV_ID_SHIFT) - 1, GFP_KERNEL); - if (datap->devid < 0) { - err = datap->devid; - kfree(datap); - return err; - } - - datap->write_msg = write_msi_msg; - datap->dev = dev; - dev->msi.data->platform_data = datap; - return 0; -} - -static void platform_msi_free_priv_data(struct device *dev) -{ - struct platform_msi_priv_data *data = dev->msi.data->platform_data; - - dev->msi.data->platform_data = NULL; - ida_free(&platform_msi_devid_ida, data->devid); - kfree(data); -} - -/** - * platform_msi_domain_alloc_irqs - Allocate MSI interrupts for @dev - * @dev: The device for which to allocate interrupts - * @nvec: The number of interrupts to allocate - * @write_msi_msg: Callback to write an interrupt message for @dev - * - * Returns: - * Zero for success, or an error code in case of failure - */ -static int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec, - irq_write_msi_msg_t write_msi_msg) -{ - int err; - - err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); - if (err) - return err; - - err = msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, nvec - 1); - if (err) - platform_msi_free_priv_data(dev); - - return err; -} - -/** - * platform_msi_get_host_data - Query the private data associated with - * a platform-msi domain - * @domain: The platform-msi domain - * - * Return: The private data provided when calling - * platform_msi_create_device_domain(). - */ -void *platform_msi_get_host_data(struct irq_domain *domain) -{ - struct platform_msi_priv_data *data = domain->host_data; - - return data->host_data; -} - -static struct lock_class_key platform_device_msi_lock_class; - -/** - * __platform_msi_create_device_domain - Create a platform-msi device domain - * - * @dev: The device generating the MSIs - * @nvec: The number of MSIs that need to be allocated - * @is_tree: flag to indicate tree hierarchy - * @write_msi_msg: Callback to write an interrupt message for @dev - * @ops: The hierarchy domain operations to use - * @host_data: Private data associated to this domain - * - * Return: An irqdomain for @nvec interrupts on success, NULL in case of error. - * - * This is for interrupt domains which stack on a platform-msi domain - * created by platform_msi_create_irq_domain(). @dev->msi.domain points to - * that platform-msi domain which is the parent for the new domain. - */ -struct irq_domain * -__platform_msi_create_device_domain(struct device *dev, - unsigned int nvec, - bool is_tree, - irq_write_msi_msg_t write_msi_msg, - const struct irq_domain_ops *ops, - void *host_data) -{ - struct platform_msi_priv_data *data; - struct irq_domain *domain; - int err; - - err = platform_msi_alloc_priv_data(dev, nvec, write_msi_msg); - if (err) - return NULL; - - /* - * Use a separate lock class for the MSI descriptor mutex on - * platform MSI device domains because the descriptor mutex nests - * into the domain mutex. See alloc/free below. - */ - lockdep_set_class(&dev->msi.data->mutex, &platform_device_msi_lock_class); - - data = dev->msi.data->platform_data; - data->host_data = host_data; - domain = irq_domain_create_hierarchy(dev->msi.domain, 0, - is_tree ? 0 : nvec, - dev->fwnode, ops, data); - if (!domain) - goto free_priv; - - platform_msi_set_proxy_dev(&data->arg); - err = msi_domain_prepare_irqs(domain->parent, dev, nvec, &data->arg); - if (err) - goto free_domain; - - return domain; - -free_domain: - irq_domain_remove(domain); -free_priv: - platform_msi_free_priv_data(dev); - return NULL; -} - -/** - * platform_msi_device_domain_free - Free interrupts associated with a platform-msi - * device domain - * - * @domain: The platform-msi device domain - * @virq: The base irq from which to perform the free operation - * @nr_irqs: How many interrupts to free from @virq - */ -void platform_msi_device_domain_free(struct irq_domain *domain, unsigned int virq, - unsigned int nr_irqs) -{ - struct platform_msi_priv_data *data = domain->host_data; - - msi_lock_descs(data->dev); - msi_domain_depopulate_descs(data->dev, virq, nr_irqs); - irq_domain_free_irqs_common(domain, virq, nr_irqs); - msi_free_msi_descs_range(data->dev, virq, virq + nr_irqs - 1); - msi_unlock_descs(data->dev); -} - -/** - * platform_msi_device_domain_alloc - Allocate interrupts associated with - * a platform-msi device domain - * - * @domain: The platform-msi device domain - * @virq: The base irq from which to perform the allocate operation - * @nr_irqs: How many interrupts to allocate from @virq - * - * Return 0 on success, or an error code on failure. Must be called - * with irq_domain_mutex held (which can only be done as part of a - * top-level interrupt allocation). - */ -int platform_msi_device_domain_alloc(struct irq_domain *domain, unsigned int virq, - unsigned int nr_irqs) -{ - struct platform_msi_priv_data *data = domain->host_data; - struct device *dev = data->dev; - - return msi_domain_populate_irqs(domain->parent, dev, virq, nr_irqs, &data->arg); -} - -/* End of removal area */ - -/* Real per device domain interfaces */ /* * This indirection can go when platform_device_msi_init_and_alloc_irqs() @@ -357,7 +23,7 @@ static void platform_msi_write_msi_msg(struct irq_data *d, struct msi_msg *msg) cb(irq_data_get_msi_desc(d), msg); } -static void platform_msi_set_desc_byindex(msi_alloc_info_t *arg, struct msi_desc *desc) +static void platform_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) { arg->desc = desc; arg->hwirq = desc->msi_index; @@ -373,7 +39,7 @@ static const struct msi_domain_template platform_msi_template = { }, .ops = { - .set_desc = platform_msi_set_desc_byindex, + .set_desc = platform_msi_set_desc, }, .info = { @@ -408,10 +74,6 @@ int platform_device_msi_init_and_alloc_irqs(struct device *dev, unsigned int nve if (!domain || !write_msi_msg) return -EINVAL; - /* Migration support. Will go away once everything is converted */ - if (!irq_domain_is_msi_parent(domain)) - return platform_msi_domain_alloc_irqs(dev, nvec, write_msi_msg); - /* * @write_msi_msg is stored in the resulting msi_domain_info::data. * The underlying domain creation mechanism will assign that @@ -432,12 +94,6 @@ EXPORT_SYMBOL_GPL(platform_device_msi_init_and_alloc_irqs); */ void platform_device_msi_free_irqs_all(struct device *dev) { - struct irq_domain *domain = dev->msi.domain; - msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN); - - /* Migration support. Will go away once everything is converted */ - if (!irq_domain_is_msi_parent(domain)) - platform_msi_free_priv_data(dev); } EXPORT_SYMBOL_GPL(platform_device_msi_free_irqs_all); diff --git a/include/linux/msi.h b/include/linux/msi.h index 4ae036d0c7db..4c3462a6a97b 100644 --- a/include/linux/msi.h +++ b/include/linux/msi.h @@ -81,7 +81,6 @@ extern int pci_msi_ignore_mask; /* Helper functions */ struct msi_desc; struct pci_dev; -struct platform_msi_priv_data; struct device_attribute; struct irq_domain; struct irq_affinity_desc; @@ -231,14 +230,12 @@ struct msi_dev_domain { /** * msi_device_data - MSI per device data * @properties: MSI properties which are interesting to drivers - * @platform_data: Platform-MSI specific data * @mutex: Mutex protecting the MSI descriptor store * @__domains: Internal data for per device MSI domains * @__iter_idx: Index to search the next entry for iterators */ struct msi_device_data { unsigned long properties; - struct platform_msi_priv_data *platform_data; struct mutex mutex; struct msi_dev_domain __domains[MSI_MAX_DEVICE_IRQDOMAINS]; unsigned long __iter_idx; @@ -641,33 +638,6 @@ void msi_domain_free_irqs_all(struct device *dev, unsigned int domid); struct msi_domain_info *msi_get_domain_info(struct irq_domain *domain); -struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode, - struct msi_domain_info *info, - struct irq_domain *parent); - -/* When an MSI domain is used as an intermediate domain */ -int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev, - int nvec, msi_alloc_info_t *args); -int msi_domain_populate_irqs(struct irq_domain *domain, struct device *dev, - int virq, int nvec, msi_alloc_info_t *args); -void msi_domain_depopulate_descs(struct device *dev, int virq, int nvec); - -struct irq_domain * -__platform_msi_create_device_domain(struct device *dev, - unsigned int nvec, - bool is_tree, - irq_write_msi_msg_t write_msi_msg, - const struct irq_domain_ops *ops, - void *host_data); - -#define platform_msi_create_device_tree_domain(dev, nvec, write, ops, data) \ - __platform_msi_create_device_domain(dev, nvec, true, write, ops, data) - -int platform_msi_device_domain_alloc(struct irq_domain *domain, unsigned int virq, - unsigned int nr_irqs); -void platform_msi_device_domain_free(struct irq_domain *domain, unsigned int virq, - unsigned int nvec); -void *platform_msi_get_host_data(struct irq_domain *domain); /* Per device platform MSI */ int platform_device_msi_init_and_alloc_irqs(struct device *dev, unsigned int nvec, irq_write_msi_msg_t write_msi_msg); diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c index 2024f89baea4..8314b1d4a903 100644 --- a/kernel/irq/msi.c +++ b/kernel/irq/msi.c @@ -1088,8 +1088,8 @@ bool msi_match_device_irq_domain(struct device *dev, unsigned int domid, return ret; } -int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev, - int nvec, msi_alloc_info_t *arg) +static int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev, + int nvec, msi_alloc_info_t *arg) { struct msi_domain_info *info = domain->host_data; struct msi_domain_ops *ops = info->ops; @@ -1097,77 +1097,6 @@ int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev, return ops->msi_prepare(domain, dev, nvec, arg); } -int msi_domain_populate_irqs(struct irq_domain *domain, struct device *dev, - int virq_base, int nvec, msi_alloc_info_t *arg) -{ - struct msi_domain_info *info = domain->host_data; - struct msi_domain_ops *ops = info->ops; - struct msi_ctrl ctrl = { - .domid = MSI_DEFAULT_DOMAIN, - .first = virq_base, - .last = virq_base + nvec - 1, - }; - struct msi_desc *desc; - struct xarray *xa; - int ret, virq; - - msi_lock_descs(dev); - - if (!msi_ctrl_valid(dev, &ctrl)) { - ret = -EINVAL; - goto unlock; - } - - ret = msi_domain_add_simple_msi_descs(dev, &ctrl); - if (ret) - goto unlock; - - xa = &dev->msi.data->__domains[ctrl.domid].store; - - for (virq = virq_base; virq < virq_base + nvec; virq++) { - desc = xa_load(xa, virq); - desc->irq = virq; - - ops->set_desc(arg, desc); - ret = irq_domain_alloc_irqs_hierarchy(domain, virq, 1, arg); - if (ret) - goto fail; - - irq_set_msi_desc(virq, desc); - } - msi_unlock_descs(dev); - return 0; - -fail: - for (--virq; virq >= virq_base; virq--) { - msi_domain_depopulate_descs(dev, virq, 1); - irq_domain_free_irqs_common(domain, virq, 1); - } - msi_domain_free_descs(dev, &ctrl); -unlock: - msi_unlock_descs(dev); - return ret; -} - -void msi_domain_depopulate_descs(struct device *dev, int virq_base, int nvec) -{ - struct msi_ctrl ctrl = { - .domid = MSI_DEFAULT_DOMAIN, - .first = virq_base, - .last = virq_base + nvec - 1, - }; - struct msi_desc *desc; - struct xarray *xa; - unsigned long idx; - - if (!msi_ctrl_valid(dev, &ctrl)) - return; - - xa = &dev->msi.data->__domains[ctrl.domid].store; - xa_for_each_range(xa, idx, desc, ctrl.first, ctrl.last) - desc->irq = 0; -} - /* * Carefully check whether the device can use reservation mode. If * reservation mode is enabled then the early activation will assign a From 2fdda02a8749fdaff5621c96aaf24a61d2f8c5a2 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 23 Jun 2024 17:19:07 +0200 Subject: [PATCH 122/152] genirq/msi: Move msi_device_data to core Now that the platform MSI hack is gone, nothing needs to know about struct msi_device_data outside of the core code. Signed-off-by: Thomas Gleixner Signed-off-by: Anna-Maria Behnsen Signed-off-by: Shivamurthy Shastri Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240623142236.003295177@linutronix.de --- include/linux/msi.h | 18 ------------------ kernel/irq/msi.c | 20 ++++++++++++++++++-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/include/linux/msi.h b/include/linux/msi.h index 4c3462a6a97b..369367ecae5e 100644 --- a/include/linux/msi.h +++ b/include/linux/msi.h @@ -21,11 +21,7 @@ #include #include #include -#include -#include -#include #include -#include #include @@ -227,20 +223,6 @@ struct msi_dev_domain { struct irq_domain *domain; }; -/** - * msi_device_data - MSI per device data - * @properties: MSI properties which are interesting to drivers - * @mutex: Mutex protecting the MSI descriptor store - * @__domains: Internal data for per device MSI domains - * @__iter_idx: Index to search the next entry for iterators - */ -struct msi_device_data { - unsigned long properties; - struct mutex mutex; - struct msi_dev_domain __domains[MSI_MAX_DEVICE_IRQDOMAINS]; - unsigned long __iter_idx; -}; - int msi_setup_device_data(struct device *dev); void msi_lock_descs(struct device *dev); diff --git a/kernel/irq/msi.c b/kernel/irq/msi.c index 8314b1d4a903..5fa0547ece0c 100644 --- a/kernel/irq/msi.c +++ b/kernel/irq/msi.c @@ -8,17 +8,33 @@ * This file contains common code to support Message Signaled Interrupts for * PCI compatible and non PCI compatible devices. */ -#include #include #include #include #include +#include +#include #include #include -#include +#include +#include #include "internals.h" +/** + * struct msi_device_data - MSI per device data + * @properties: MSI properties which are interesting to drivers + * @mutex: Mutex protecting the MSI descriptor store + * @__domains: Internal data for per device MSI domains + * @__iter_idx: Index to search the next entry for iterators + */ +struct msi_device_data { + unsigned long properties; + struct mutex mutex; + struct msi_dev_domain __domains[MSI_MAX_DEVICE_IRQDOMAINS]; + unsigned long __iter_idx; +}; + /** * struct msi_ctrl - MSI internal management control structure * @domid: ID of the domain on which management operations should be done From 99d7fbf8f813eb77dd4ec148c4596455910b2fa1 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Wed, 17 Jul 2024 20:59:37 +0100 Subject: [PATCH 123/152] irqchip/gic-v3-its: Correctly honor the RID remapping Since 6adb35ff43a16 ("irqchip/gic-v3-its: Provide MSI parent for PCI/MSI[-X]"), the primary domain a PCI device allocates its interrupts from is the one that is directly attached to the device itself. By virtue of being a PCI device, it has no OF node. This domain is (through more layer than it is worth describing) passed to its_pci_msi_prepare(), which tries to compute the full RID that is presented to the ITS by the device. This is ultimately done by calling pci_msi_domain_get_msi_rid(), passing both the domain and the PCI device as arguments. The baked-in assumption is that either the domain that is passed to pci_msi_domain_get_msi_rid() describes an interrupt controller with either an OF node or an entry in an ACPI IORT table. In this case, it is *neither*. This domain is does not represent anything firmware-based, but just an allocation unit for the device. As a result, it fails to provide the full RID (which requires inspecting the msi-map/msi-mask properties in the DT), and stick to the BDF, which isn't very useful. Tragedy follows with a litany of devices that randomly die as they fail to see any MSI (because the RID is wrong) or fail to get an allocation (because they try to steal LPIs from their neighbour's pool). This will happen on any system where a single ITS is shared by multiple root ports and end-points with overlapping BDF numbers, and has the topology described in the device-tree. Simpler DT topologies will luckily work, and so will ACPI-based systems. Solve it by pointing pci_msi_domain_get_msi_rid() at the *parent* domain, which is the ITS, resulting in a correct mapping and a restored happiness in my personal zoo. Fixes: 6adb35ff43a16 ("irqchip/gic-v3-its: Provide MSI parent for PCI/MSI[-X]") Reported-by: Johan Hovold Signed-off-by: Marc Zyngier Signed-off-by: Thomas Gleixner Tested-by: Johan Hovold Link: https://lore.kernel.org/r/20240717195937.2240400-1-maz@kernel.org --- drivers/irqchip/irq-gic-v3-its-msi-parent.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v3-its-msi-parent.c b/drivers/irqchip/irq-gic-v3-its-msi-parent.c index 780e1bc9df54..2f3fc597331b 100644 --- a/drivers/irqchip/irq-gic-v3-its-msi-parent.c +++ b/drivers/irqchip/irq-gic-v3-its-msi-parent.c @@ -65,7 +65,7 @@ static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev, } /* ITS specific DeviceID, as the core ITS ignores dev. */ - info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain, pdev); + info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain->parent, pdev); /* * @domain->msi_domain_info->hwsize contains the size of the From c9b4f313f6b83ac80e9d51845d092c32513efdb4 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Thu, 18 Jul 2024 08:58:04 +0100 Subject: [PATCH 124/152] irqchip/gic-v3-its: Correctly fish out the DID for platform MSI Similarly to PCI where msi-map/msi-mask are used to compute the full RID (aka DID in ITS speak), use the msi-parent as the discovery mechanism, since there is no way a device can generally express its ID. However, since switching to a per-device MSI domain model, the domain passed to its_pmsi_prepare() is the wrong one, and points to the device's instead of the ITS'. Bad. Use the parent domain instead, which is the ITS domain. Fixes: 80b63cc1cc146 ("irqchip/gic-v3-its: Switch platform MSI to MSI parent") Signed-off-by: Marc Zyngier Signed-off-by: Thomas Gleixner Link: https://lore.kernel.org/r/20240718075804.2245733-1-maz@kernel.org --- drivers/irqchip/irq-gic-v3-its-msi-parent.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v3-its-msi-parent.c b/drivers/irqchip/irq-gic-v3-its-msi-parent.c index 2f3fc597331b..e150365fbe89 100644 --- a/drivers/irqchip/irq-gic-v3-its-msi-parent.c +++ b/drivers/irqchip/irq-gic-v3-its-msi-parent.c @@ -134,7 +134,7 @@ static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev, int ret; if (dev->of_node) - ret = of_pmsi_get_dev_id(domain, dev, &dev_id); + ret = of_pmsi_get_dev_id(domain->parent, dev, &dev_id); else ret = iort_pmsi_get_dev_id(dev, &dev_id); if (ret) From 8c4ff24d7cc84432a9f1055ef57e4c86706b38c7 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Thu, 13 Jun 2024 00:58:32 +0300 Subject: [PATCH 125/152] dt-bindings: i2c: qcom-cci: Document sm8550 compatible Add sm8550 compatible consistent with CAMSS CCI interfaces, the list of clocks is reduced by removing "slow_ahb_src" clock, which is derived from "cpas_ahb" clock. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Krzysztof Kozlowski Signed-off-by: Andi Shyti --- .../devicetree/bindings/i2c/qcom,i2c-cci.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml b/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml index daf4e71b8e7f..e5c4b20446b6 100644 --- a/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml +++ b/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml @@ -31,6 +31,7 @@ properties: - qcom,sm6350-cci - qcom,sm8250-cci - qcom,sm8450-cci + - qcom,sm8550-cci - const: qcom,msm8996-cci # CCI v2 "#address-cells": @@ -195,6 +196,23 @@ allOf: - const: cpas_ahb - const: cci + - if: + properties: + compatible: + contains: + enum: + - qcom,sm8550-cci + then: + properties: + clocks: + minItems: 3 + maxItems: 3 + clock-names: + items: + - const: camnoc_axi + - const: cpas_ahb + - const: cci + additionalProperties: false examples: From 064e911392177e8c39e395f748fb4ae8ffb18e92 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Thu, 13 Jun 2024 00:58:33 +0300 Subject: [PATCH 126/152] dt-bindings: i2c: qcom-cci: Document sm8650 compatible Add sm8650 compatible consistent with CAMSS CCI interfaces. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Krzysztof Kozlowski Signed-off-by: Andi Shyti --- Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml b/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml index e5c4b20446b6..c33ae7b63b84 100644 --- a/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml +++ b/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml @@ -32,6 +32,7 @@ properties: - qcom,sm8250-cci - qcom,sm8450-cci - qcom,sm8550-cci + - qcom,sm8650-cci - const: qcom,msm8996-cci # CCI v2 "#address-cells": @@ -202,6 +203,7 @@ allOf: contains: enum: - qcom,sm8550-cci + - qcom,sm8650-cci then: properties: clocks: From dc53dbe3e79008ca7c1bc5e5ae2c71b52161f96d Mon Sep 17 00:00:00 2001 From: Bastien Curutchet Date: Mon, 17 Jun 2024 14:08:16 +0200 Subject: [PATCH 127/152] dt-bindings: i2c: mux-gpio: Add 'settle-time-us' property I2C MUXes described by the i2c-gpio-mux sometimes need a significant amount of time to switch from a bus to another. When a new bus is selected, the first I2C transfer can fail if it occurs too early. There is no way to describe this transition delay that has to be waited before starting the first I2C transfer. Add a 'settle-time-us' property that indicates the delay to be respected before doing the first i2c transfer. Signed-off-by: Bastien Curutchet Reviewed-by: Krzysztof Kozlowski Reviewed-by: Andi Shyti Acked-by: Peter Rosin Signed-off-by: Andi Shyti --- Documentation/devicetree/bindings/i2c/i2c-mux-gpio.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/i2c/i2c-mux-gpio.yaml b/Documentation/devicetree/bindings/i2c/i2c-mux-gpio.yaml index f34cc7ad5a00..4a93d1f78f93 100644 --- a/Documentation/devicetree/bindings/i2c/i2c-mux-gpio.yaml +++ b/Documentation/devicetree/bindings/i2c/i2c-mux-gpio.yaml @@ -57,6 +57,9 @@ properties: last value used. $ref: /schemas/types.yaml#/definitions/uint32 + settle-time-us: + description: Delay to wait before doing any transfer when a new bus gets selected. + allOf: - $ref: i2c-mux.yaml From a618d86d5a1cf1ac6edbbe0e2b29649afb3decf0 Mon Sep 17 00:00:00 2001 From: Bastien Curutchet Date: Mon, 17 Jun 2024 14:08:17 +0200 Subject: [PATCH 128/152] i2c: mux: gpio: Re-order #include to match alphabetic order The #includes don't match alphabetic order. Re-order #includes to match the alphabetic order before adding a new one. Signed-off-by: Bastien Curutchet Reviewed-by: Andi Shyti Acked-by: Peter Rosin Signed-off-by: Andi Shyti --- drivers/i2c/muxes/i2c-mux-gpio.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/muxes/i2c-mux-gpio.c b/drivers/i2c/muxes/i2c-mux-gpio.c index d6bbb8b68333..c61e9d9ea695 100644 --- a/drivers/i2c/muxes/i2c-mux-gpio.c +++ b/drivers/i2c/muxes/i2c-mux-gpio.c @@ -5,16 +5,16 @@ * Peter Korsgaard */ -#include -#include -#include -#include -#include -#include -#include #include #include #include +#include +#include +#include +#include +#include +#include +#include struct gpiomux { struct i2c_mux_gpio_platform_data data; From 45b8ee7182d5ef8df6959297046f86dc128d6a06 Mon Sep 17 00:00:00 2001 From: Bastien Curutchet Date: Mon, 17 Jun 2024 14:08:18 +0200 Subject: [PATCH 129/152] i2c: mux: gpio: Add support for the 'settle-time-us' property Some hardware need some time to switch from a bus to another. This can cause the first transfers following the selection of a bus to fail. There is no way to configure this kind of waiting time in the driver. Add support for the 'settle-time-us' device-tree property. When set, the i2c_mux_gpio_select() applies a delay before returning, leaving enough time to the hardware to switch to the new bus. Signed-off-by: Bastien Curutchet Reviewed-by: Andi Shyti Acked-by: Peter Rosin Signed-off-by: Andi Shyti --- drivers/i2c/muxes/i2c-mux-gpio.c | 6 ++++++ include/linux/platform_data/i2c-mux-gpio.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/i2c/muxes/i2c-mux-gpio.c b/drivers/i2c/muxes/i2c-mux-gpio.c index c61e9d9ea695..944577bb09c1 100644 --- a/drivers/i2c/muxes/i2c-mux-gpio.c +++ b/drivers/i2c/muxes/i2c-mux-gpio.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -37,6 +38,9 @@ static int i2c_mux_gpio_select(struct i2c_mux_core *muxc, u32 chan) i2c_mux_gpio_set(mux, chan); + if (mux->data.settle_time) + fsleep(mux->data.settle_time); + return 0; } @@ -116,6 +120,8 @@ static int i2c_mux_gpio_probe_fw(struct gpiomux *mux, if (device_property_read_u32(dev, "idle-state", &mux->data.idle)) mux->data.idle = I2C_MUX_GPIO_NO_IDLE; + device_property_read_u32(dev, "settle-time-us", &mux->data.settle_time); + return 0; } diff --git a/include/linux/platform_data/i2c-mux-gpio.h b/include/linux/platform_data/i2c-mux-gpio.h index 816a4cd3ccb5..96843aab4d1e 100644 --- a/include/linux/platform_data/i2c-mux-gpio.h +++ b/include/linux/platform_data/i2c-mux-gpio.h @@ -19,6 +19,7 @@ * position * @n_values: Number of multiplexer positions (busses to instantiate) * @idle: Bitmask to write to MUX when idle or GPIO_I2CMUX_NO_IDLE if not used + * @settle_time: Delay to wait when a new bus is selected */ struct i2c_mux_gpio_platform_data { int parent; @@ -26,6 +27,7 @@ struct i2c_mux_gpio_platform_data { const unsigned *values; int n_values; unsigned idle; + u32 settle_time; }; #endif /* _LINUX_I2C_MUX_GPIO_H */ From 8f3075cc680c232590ca4e2ef39ebaf1e887a52e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 9 Jul 2024 19:35:35 +0200 Subject: [PATCH 130/152] i2c: smbus: remove i801 assumptions from SPD probing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The check and warning are very specific to the SPD usage of the i801 driver. That was fine as long as i801 was the only caller of i2c_register_spd(). Now that piix4 will be added as another user of that function, the check and warning are not accurate anymore. Instead of introducing a more complicated calling protocol only to print a warning, drop the warning. Even in cases where not all slots can be probed, then at least probe the 8 slots that can be. Signed-off-by: Thomas Weißschuh Reviewed-by: Heiner Kallweit Signed-off-by: Andi Shyti --- drivers/i2c/i2c-smbus.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/drivers/i2c/i2c-smbus.c b/drivers/i2c/i2c-smbus.c index 1cb137b9181d..7e4203df83ed 100644 --- a/drivers/i2c/i2c-smbus.c +++ b/drivers/i2c/i2c-smbus.c @@ -352,18 +352,11 @@ void i2c_register_spd(struct i2c_adapter *adap) return; /* - * If we're a child adapter on a muxed segment, then limit slots to 8, - * as this is the max number of SPD EEPROMs that can be addressed per bus. + * The max number of SPD EEPROMs that can be addressed per bus is 8. + * If more slots are present either muxed or multiple busses are + * necessary or the additional slots are ignored. */ - if (i2c_parent_is_i2c_adapter(adap)) { - slot_count = 8; - } else { - if (slot_count > 8) { - dev_warn(&adap->dev, - "More than 8 memory slots on a single bus, contact i801 maintainer to add missing mux config\n"); - return; - } - } + slot_count = min(slot_count, 8); /* * Memory types could be found at section 7.18.2 (Memory Device — Type), table 78 From cbf5009580c7c86e9822b3e675df1762ea0a5a0a Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 19 Jul 2024 12:02:23 +0200 Subject: [PATCH 131/152] mailbox: qcom-cpucp: fix 64BIT dependency This newly added driver fails compile testing on 32-bit architectures because it relies on 64-bit MMIO register access: drivers/mailbox/qcom-cpucp-mbox.c: In function 'qcom_cpucp_mbox_irq_fn': drivers/mailbox/qcom-cpucp-mbox.c:54:18: error: implicit declaration of function 'readq'; did you mean 'readb'? [-Wimplicit-function-declaration] 54 | status = readq(cpucp->rx_base + APSS_CPUCP_RX_MBOX_STAT); | ^~~~~ | readb drivers/mailbox/qcom-cpucp-mbox.c:65:17: error: implicit declaration of function 'writeq'; did you mean 'writeb'? [-Wimplicit-function-declaration] 65 | writeq(BIT(i), cpucp->rx_base + APSS_CPUCP_RX_MBOX_CLEAR); | ^~~~~~ | writeb Change the Kconfig dependency to disallow that configuration as well. Fixes: 0e2a9a03106c ("mailbox: Add support for QTI CPUCP mailbox controller") Signed-off-by: Arnd Bergmann Reviewed-by: Konrad Dybcio Signed-off-by: Jassi Brar --- drivers/mailbox/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig index d1f6c758b5e8..4eed97295927 100644 --- a/drivers/mailbox/Kconfig +++ b/drivers/mailbox/Kconfig @@ -278,7 +278,7 @@ config SPRD_MBOX config QCOM_CPUCP_MBOX tristate "Qualcomm Technologies, Inc. CPUCP mailbox driver" - depends on ARCH_QCOM || (COMPILE_TEST && 64BIT) + depends on (ARCH_QCOM || COMPILE_TEST) && 64BIT help Qualcomm Technologies, Inc. CPUSS Control Processor (CPUCP) mailbox controller driver enables communication between AP and CPUCP. Say From 0e3bb6ee88874fc052a6f7a8eb18463faea0a63d Mon Sep 17 00:00:00 2001 From: Ben Levinsky Date: Thu, 18 Jul 2024 13:40:57 -0700 Subject: [PATCH 132/152] mailbox: zynqmp-ipi: Make polling period configurable There are cases where remote that is acking mailbox message can take longer than the default tx_poll_period value. Therefore, enable this to be mutable. Added tx_poll_period field while inserting the module to set the poll period for ack after sending mailbox message. Signed-off-by: Ben Levinsky Acked-by: Michal Simek Signed-off-by: Jassi Brar --- drivers/mailbox/zynqmp-ipi-mailbox.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/mailbox/zynqmp-ipi-mailbox.c b/drivers/mailbox/zynqmp-ipi-mailbox.c index 4acf5612487c..521d08b9ab47 100644 --- a/drivers/mailbox/zynqmp-ipi-mailbox.c +++ b/drivers/mailbox/zynqmp-ipi-mailbox.c @@ -64,6 +64,13 @@ #define MAX_SGI 16 +/* + * Module parameters + */ +static int tx_poll_period = 5; +module_param_named(tx_poll_period, tx_poll_period, int, 0644); +MODULE_PARM_DESC(tx_poll_period, "Poll period waiting for ack after send."); + /** * struct zynqmp_ipi_mchan - Description of a Xilinx ZynqMP IPI mailbox channel * @is_opened: indicate if the IPI channel is opened @@ -537,7 +544,7 @@ static int zynqmp_ipi_mbox_probe(struct zynqmp_ipi_mbox *ipi_mbox, mbox->num_chans = 2; mbox->txdone_irq = false; mbox->txdone_poll = true; - mbox->txpoll_period = 5; + mbox->txpoll_period = tx_poll_period; mbox->of_xlate = zynqmp_ipi_of_xlate; chans = devm_kzalloc(mdev, 2 * sizeof(*chans), GFP_KERNEL); if (!chans) From a8bd68e4329f9a0ad1b878733e0f80be6a971649 Mon Sep 17 00:00:00 2001 From: "Jason-JH.Lin" Date: Thu, 18 Jul 2024 22:17:04 +0800 Subject: [PATCH 133/152] mailbox: mtk-cmdq: Move devm_mbox_controller_register() after devm_pm_runtime_enable() When mtk-cmdq unbinds, a WARN_ON message with condition pm_runtime_get_sync() < 0 occurs. According to the call tracei below: cmdq_mbox_shutdown mbox_free_channel mbox_controller_unregister __devm_mbox_controller_unregister ... The root cause can be deduced to be calling pm_runtime_get_sync() after calling pm_runtime_disable() as observed below: 1. CMDQ driver uses devm_mbox_controller_register() in cmdq_probe() to bind the cmdq device to the mbox_controller, so devm_mbox_controller_unregister() will automatically unregister the device bound to the mailbox controller when the device-managed resource is removed. That means devm_mbox_controller_unregister() and cmdq_mbox_shoutdown() will be called after cmdq_remove(). 2. CMDQ driver also uses devm_pm_runtime_enable() in cmdq_probe() after devm_mbox_controller_register(), so that devm_pm_runtime_disable() will be called after cmdq_remove(), but before devm_mbox_controller_unregister(). To fix this problem, cmdq_probe() needs to move devm_mbox_controller_register() after devm_pm_runtime_enable() to make devm_pm_runtime_disable() be called after devm_mbox_controller_unregister(). Fixes: 623a6143a845 ("mailbox: mediatek: Add Mediatek CMDQ driver") Signed-off-by: Jason-JH.Lin Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Jassi Brar --- drivers/mailbox/mtk-cmdq-mailbox.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/mailbox/mtk-cmdq-mailbox.c b/drivers/mailbox/mtk-cmdq-mailbox.c index f1dfce9e27f5..4bff73532085 100644 --- a/drivers/mailbox/mtk-cmdq-mailbox.c +++ b/drivers/mailbox/mtk-cmdq-mailbox.c @@ -689,12 +689,6 @@ static int cmdq_probe(struct platform_device *pdev) cmdq->mbox.chans[i].con_priv = (void *)&cmdq->thread[i]; } - err = devm_mbox_controller_register(dev, &cmdq->mbox); - if (err < 0) { - dev_err(dev, "failed to register mailbox: %d\n", err); - return err; - } - platform_set_drvdata(pdev, cmdq); WARN_ON(clk_bulk_prepare(cmdq->pdata->gce_num, cmdq->clocks)); @@ -722,6 +716,12 @@ static int cmdq_probe(struct platform_device *pdev) pm_runtime_set_autosuspend_delay(dev, CMDQ_MBOX_AUTOSUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); + err = devm_mbox_controller_register(dev, &cmdq->mbox); + if (err < 0) { + dev_err(dev, "failed to register mailbox: %d\n", err); + return err; + } + return 0; } From de4f2f52f93257433c9029ba2b4044cd9f029b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 9 Jul 2024 19:35:36 +0200 Subject: [PATCH 134/152] i2c: piix4: Register SPDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The piix4 I2C bus can carry SPDs, register them if present. Only look on bus 0, as this is where the SPDs seem to be located. Only the first 8 slots are supported. If the system has more, then these will not be visible. The AUX bus can not be probed as on some platforms it reports all devices present and all reads return "0". This would allow the ee1004 to be probed incorrectly. Signed-off-by: Thomas Weißschuh Reviewed-by: Guenter Roeck Tested-by: Guenter Roeck Signed-off-by: Andi Shyti --- drivers/i2c/busses/Kconfig | 1 + drivers/i2c/busses/i2c-piix4.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 3e32fb882101..a22f9125322a 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -196,6 +196,7 @@ config I2C_ISMT config I2C_PIIX4 tristate "Intel PIIX4 and compatible (ATI/AMD/Serverworks/Broadcom/SMSC)" depends on PCI && HAS_IOPORT + select I2C_SMBUS help If you say yes to this option, support will be included for the Intel PIIX4 family of mainboard I2C interfaces. Specifically, the following diff --git a/drivers/i2c/busses/i2c-piix4.c b/drivers/i2c/busses/i2c-piix4.c index 84aa18d1003b..4e32d57ae0bf 100644 --- a/drivers/i2c/busses/i2c-piix4.c +++ b/drivers/i2c/busses/i2c-piix4.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -982,6 +983,14 @@ static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba, return retval; } + /* + * The AUX bus can not be probed as on some platforms it reports all + * devices present and all reads return "0". + * This would allow the ee1004 to be probed incorrectly. + */ + if (port == 0) + i2c_register_spd(adap); + *padap = adap; return 0; } From d83763e44944d4d6dee38099c5d0a0984ac66385 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 16 Jul 2024 10:36:24 +0200 Subject: [PATCH 135/152] i2c: header: remove unneeded stuff regarding i2c_algorithm The forward declaration is not needed anymore. The sentence about "following structs" became obsolete when struct i2c_algorithm became a kdoc. The paragraph about return values can go because we have this information in kdoc already. Signed-off-by: Wolfram Sang --- include/linux/i2c.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/include/linux/i2c.h b/include/linux/i2c.h index e9cc14b1f9a1..1e34b486f604 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -30,7 +30,6 @@ extern const struct device_type i2c_client_type; /* --- General options ------------------------------------------------ */ struct i2c_msg; -struct i2c_algorithm; struct i2c_adapter; struct i2c_client; struct i2c_driver; @@ -533,8 +532,6 @@ i2c_register_board_info(int busnum, struct i2c_board_info const *info, * @reg_slave: deprecated, use @reg_target * @unreg_slave: deprecated, use @unreg_target * - * - * The following structs are for those who like to implement new bus drivers: * i2c_algorithm is the interface to a class of hardware solutions which can * be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584 * to name two of the most common. @@ -550,9 +547,6 @@ struct i2c_algorithm { * to NULL. If an adapter algorithm can do SMBus access, set * smbus_xfer. If set to NULL, the SMBus protocol is simulated * using common I2C messages. - * - * xfer should return the number of messages successfully - * processed, or a negative value on error */ union { int (*xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, From 385ac870bdd531348de123d6790626ccd7827f69 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Tue, 16 Jul 2024 10:36:25 +0200 Subject: [PATCH 136/152] i2c: header: improve kdoc for i2c_algorithm Reword the explanation of @xfer, the old one was confusing and mixing up terminology. Other than that, capitalize some words correctly and use full line length. Signed-off-by: Wolfram Sang --- include/linux/i2c.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 1e34b486f604..8caaa13834bf 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -511,16 +511,15 @@ i2c_register_board_info(int busnum, struct i2c_board_info const *info, #endif /* I2C_BOARDINFO */ /** - * struct i2c_algorithm - represent I2C transfer method - * @xfer: Issue a set of i2c transactions to the given I2C adapter - * defined by the msgs array, with num messages available to transfer via - * the adapter specified by adap. - * @xfer_atomic: same as @xfer. Yet, only using atomic context - * so e.g. PMICs can be accessed very late before shutdown. Optional. - * @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this + * struct i2c_algorithm - represent I2C transfer methods + * @xfer: Transfer a given number of messages defined by the msgs array via + * the specified adapter. + * @xfer_atomic: Same as @xfer. Yet, only using atomic context so e.g. PMICs + * can be accessed very late before shutdown. Optional. + * @smbus_xfer: Issue SMBus transactions to the given I2C adapter. If this * is not present, then the bus layer will try and convert the SMBus calls * into I2C transfers instead. - * @smbus_xfer_atomic: same as @smbus_xfer. Yet, only using atomic context + * @smbus_xfer_atomic: Same as @smbus_xfer. Yet, only using atomic context * so e.g. PMICs can be accessed very late before shutdown. Optional. * @functionality: Return the flags that this algorithm/adapter pair supports * from the ``I2C_FUNC_*`` flags. From 7697a0fe0154468f5df35c23ebd7aa48994c2cdc Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 20 Jul 2024 22:40:58 +0800 Subject: [PATCH 137/152] LoongArch: Define __ARCH_WANT_NEW_STAT in unistd.h Chromium sandbox apparently wants to deny statx [1] so it could properly inspect arguments after the sandboxed process later falls back to fstat. Because there's currently not a "fd-only" version of statx, so that the sandbox has no way to ensure the path argument is empty without being able to peek into the sandboxed process's memory. For architectures able to do newfstatat though, glibc falls back to newfstatat after getting -ENOSYS for statx, then the respective SIGSYS handler [2] takes care of inspecting the path argument, transforming allowed newfstatat's into fstat instead which is allowed and has the same type of return value. But, as LoongArch is the first architecture to not have fstat nor newfstatat, the LoongArch glibc does not attempt falling back at all when it gets -ENOSYS for statx -- and you see the problem there! Actually, back when the LoongArch port was under review, people were aware of the same problem with sandboxing clone3 [3], so clone was eventually kept. Unfortunately it seemed at that time no one had noticed statx, so besides restoring fstat/newfstatat to LoongArch uapi (and postponing the problem further), it seems inevitable that we would need to tackle seccomp deep argument inspection. However, this is obviously a decision that shouldn't be taken lightly, so we just restore fstat/newfstatat by defining __ARCH_WANT_NEW_STAT in unistd.h. This is the simplest solution for now, and so we hope the community will tackle the long-standing problem of seccomp deep argument inspection in the future [4][5]. Also add "newstat" to syscall_abis_64 in Makefile.syscalls due to upstream asm-generic changes. More infomation please reading this thread [6]. [1] https://chromium-review.googlesource.com/c/chromium/src/+/2823150 [2] https://chromium.googlesource.com/chromium/src/sandbox/+/c085b51940bd/linux/seccomp-bpf-helpers/sigsys_handlers.cc#355 [3] https://lore.kernel.org/linux-arch/20220511211231.GG7074@brightrain.aerifal.cx/ [4] https://lwn.net/Articles/799557/ [5] https://lpc.events/event/4/contributions/560/attachments/397/640/deep-arg-inspection.pdf [6] https://lore.kernel.org/loongarch/20240226-granit-seilschaft-eccc2433014d@brauner/T/#t Cc: stable@vger.kernel.org Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/unistd.h | 1 + arch/loongarch/kernel/Makefile.syscalls | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/loongarch/include/asm/unistd.h b/arch/loongarch/include/asm/unistd.h index fc0a481a7416..e2c0f3d86c7b 100644 --- a/arch/loongarch/include/asm/unistd.h +++ b/arch/loongarch/include/asm/unistd.h @@ -8,6 +8,7 @@ #include +#define __ARCH_WANT_NEW_STAT #define __ARCH_WANT_SYS_CLONE #define NR_syscalls (__NR_syscalls) diff --git a/arch/loongarch/kernel/Makefile.syscalls b/arch/loongarch/kernel/Makefile.syscalls index ab7d9baa2915..523bb411a3bc 100644 --- a/arch/loongarch/kernel/Makefile.syscalls +++ b/arch/loongarch/kernel/Makefile.syscalls @@ -1,4 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 -# No special ABIs on loongarch so far -syscall_abis_64 += +syscall_abis_64 += newstat From 12d3b559b85e75640b79995e1320588d6f0d5630 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 20 Jul 2024 22:40:58 +0800 Subject: [PATCH 138/152] LoongArch: Always enumerate MADT and setup logical-physical CPU mapping Some drivers want to use cpu_logical_map(), early_cpu_to_node() and some other CPU mapping APIs, even if we use "nr_cpus=1" to hard limit the CPU number. This is strongly required for the multi-bridges machines. Currently, we stop parsing the MADT if the nr_cpus limit is reached, but to achieve the above goal we should always enumerate the MADT table and setup logical-physical CPU mapping whether there is a nr_cpus limit. Rework the MADT enumeration: 1. Define a flag "cpu_enumerated" to distinguish the first enumeration (cpu_enumerated=0) and the physical hotplug case (cpu_enumerated=1) for set_processor_mask(). 2. If cpu_enumerated=0, stop parsing only when NR_CPUS limit is reached, so we can setup logical-physical CPU mapping; if cpu_enumerated=1, stop parsing when nr_cpu_ids limit is reached, so we can avoid some runtime bugs. Once logical-physical CPU mapping is setup, we will let cpu_enumerated=1. 3. Use find_first_zero_bit() instead of cpumask_next_zero() to find the next zero bit (free logical CPU id) in the cpu_present_mask, because cpumask_next_zero() will stop at nr_cpu_ids. 4. Only touch cpu_possible_mask if cpu_enumerated=0, this is in order to avoid some potential crashes, because cpu_possible_mask is marked as __ro_after_init. 5. In prefill_possible_map(), clear cpu_present_mask bits greater than nr_cpu_ids, in order to avoid a CPU be "present" but not "possible". Signed-off-by: Huacai Chen --- arch/loongarch/kernel/acpi.c | 22 ++++++++++++++++------ arch/loongarch/kernel/setup.c | 4 +++- arch/loongarch/kernel/smp.c | 7 +++---- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/arch/loongarch/kernel/acpi.c b/arch/loongarch/kernel/acpi.c index 5cf59c617126..929a497c987e 100644 --- a/arch/loongarch/kernel/acpi.c +++ b/arch/loongarch/kernel/acpi.c @@ -57,15 +57,22 @@ void __iomem *acpi_os_ioremap(acpi_physical_address phys, acpi_size size) return ioremap_cache(phys, size); } +static int cpu_enumerated = 0; + #ifdef CONFIG_SMP static int set_processor_mask(u32 id, u32 flags) { - + int nr_cpus; int cpu, cpuid = id; - if (num_processors >= nr_cpu_ids) { - pr_warn(PREFIX "nr_cpus/possible_cpus limit of %i reached." - " processor 0x%x ignored.\n", nr_cpu_ids, cpuid); + if (!cpu_enumerated) + nr_cpus = NR_CPUS; + else + nr_cpus = nr_cpu_ids; + + if (num_processors >= nr_cpus) { + pr_warn(PREFIX "nr_cpus limit of %i reached." + " processor 0x%x ignored.\n", nr_cpus, cpuid); return -ENODEV; @@ -73,11 +80,13 @@ static int set_processor_mask(u32 id, u32 flags) if (cpuid == loongson_sysconf.boot_cpu_id) cpu = 0; else - cpu = cpumask_next_zero(-1, cpu_present_mask); + cpu = find_first_zero_bit(cpumask_bits(cpu_present_mask), NR_CPUS); + + if (!cpu_enumerated) + set_cpu_possible(cpu, true); if (flags & ACPI_MADT_ENABLED) { num_processors++; - set_cpu_possible(cpu, true); set_cpu_present(cpu, true); __cpu_number_map[cpuid] = cpu; __cpu_logical_map[cpu] = cpuid; @@ -138,6 +147,7 @@ static void __init acpi_process_madt(void) acpi_table_parse_madt(ACPI_MADT_TYPE_EIO_PIC, acpi_parse_eio_master, MAX_IO_PICS); + cpu_enumerated = 1; loongson_sysconf.nr_cpus = num_processors; } diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c index 3d048f1be143..0f0740f0be27 100644 --- a/arch/loongarch/kernel/setup.c +++ b/arch/loongarch/kernel/setup.c @@ -576,8 +576,10 @@ static void __init prefill_possible_map(void) for (i = 0; i < possible; i++) set_cpu_possible(i, true); - for (; i < NR_CPUS; i++) + for (; i < NR_CPUS; i++) { + set_cpu_present(i, false); set_cpu_possible(i, false); + } set_nr_cpu_ids(possible); } diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index 1436d2465939..03b2b7669cf5 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -271,11 +271,10 @@ static void __init fdt_smp_setup(void) if (cpuid >= nr_cpu_ids) continue; - if (cpuid == loongson_sysconf.boot_cpu_id) { + if (cpuid == loongson_sysconf.boot_cpu_id) cpu = 0; - } else { - cpu = cpumask_next_zero(-1, cpu_present_mask); - } + else + cpu = find_first_zero_bit(cpumask_bits(cpu_present_mask), NR_CPUS); num_processors++; set_cpu_possible(cpu, true); From 08f417db702c5b05150b3851af7186fee96ddd46 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 20 Jul 2024 22:40:58 +0800 Subject: [PATCH 139/152] LoongArch: Add irq_work support via self IPIs Add irq_work support for LoongArch via self IPIs. This make it possible to run works in hardware interrupt context, which is a prerequisite for NOHZ_FULL. Implement: - arch_irq_work_raise() - arch_irq_work_has_interrupt() Reviewed-by: Guo Ren Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/hardirq.h | 3 ++- arch/loongarch/include/asm/irq_work.h | 10 ++++++++++ arch/loongarch/include/asm/smp.h | 2 ++ arch/loongarch/kernel/paravirt.c | 6 ++++++ arch/loongarch/kernel/smp.c | 14 ++++++++++++++ 5 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 arch/loongarch/include/asm/irq_work.h diff --git a/arch/loongarch/include/asm/hardirq.h b/arch/loongarch/include/asm/hardirq.h index d41138abcf26..1d7feb719515 100644 --- a/arch/loongarch/include/asm/hardirq.h +++ b/arch/loongarch/include/asm/hardirq.h @@ -12,11 +12,12 @@ extern void ack_bad_irq(unsigned int irq); #define ack_bad_irq ack_bad_irq -#define NR_IPI 2 +#define NR_IPI 3 enum ipi_msg_type { IPI_RESCHEDULE, IPI_CALL_FUNCTION, + IPI_IRQ_WORK, }; typedef struct { diff --git a/arch/loongarch/include/asm/irq_work.h b/arch/loongarch/include/asm/irq_work.h new file mode 100644 index 000000000000..d63076e9160d --- /dev/null +++ b/arch/loongarch/include/asm/irq_work.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_LOONGARCH_IRQ_WORK_H +#define _ASM_LOONGARCH_IRQ_WORK_H + +static inline bool arch_irq_work_has_interrupt(void) +{ + return IS_ENABLED(CONFIG_SMP); +} + +#endif /* _ASM_LOONGARCH_IRQ_WORK_H */ diff --git a/arch/loongarch/include/asm/smp.h b/arch/loongarch/include/asm/smp.h index 278700cfee88..50db503f44e3 100644 --- a/arch/loongarch/include/asm/smp.h +++ b/arch/loongarch/include/asm/smp.h @@ -69,9 +69,11 @@ extern int __cpu_logical_map[NR_CPUS]; #define ACTION_BOOT_CPU 0 #define ACTION_RESCHEDULE 1 #define ACTION_CALL_FUNCTION 2 +#define ACTION_IRQ_WORK 3 #define SMP_BOOT_CPU BIT(ACTION_BOOT_CPU) #define SMP_RESCHEDULE BIT(ACTION_RESCHEDULE) #define SMP_CALL_FUNCTION BIT(ACTION_CALL_FUNCTION) +#define SMP_IRQ_WORK BIT(ACTION_IRQ_WORK) struct secondary_data { unsigned long stack; diff --git a/arch/loongarch/kernel/paravirt.c b/arch/loongarch/kernel/paravirt.c index 1633ed4f692f..4272d2447445 100644 --- a/arch/loongarch/kernel/paravirt.c +++ b/arch/loongarch/kernel/paravirt.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -97,6 +98,11 @@ static irqreturn_t pv_ipi_interrupt(int irq, void *dev) info->ipi_irqs[IPI_CALL_FUNCTION]++; } + if (action & SMP_IRQ_WORK) { + irq_work_run(); + info->ipi_irqs[IPI_IRQ_WORK]++; + } + return IRQ_HANDLED; } diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index 03b2b7669cf5..ca405ab86aae 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,7 @@ static DEFINE_PER_CPU(int, cpu_state); static const char *ipi_types[NR_IPI] __tracepoint_string = { [IPI_RESCHEDULE] = "Rescheduling interrupts", [IPI_CALL_FUNCTION] = "Function call interrupts", + [IPI_IRQ_WORK] = "IRQ work interrupts", }; void show_ipi_list(struct seq_file *p, int prec) @@ -217,6 +219,13 @@ void arch_smp_send_reschedule(int cpu) } EXPORT_SYMBOL_GPL(arch_smp_send_reschedule); +#ifdef CONFIG_IRQ_WORK +void arch_irq_work_raise(void) +{ + mp_ops.send_ipi_single(smp_processor_id(), ACTION_IRQ_WORK); +} +#endif + static irqreturn_t loongson_ipi_interrupt(int irq, void *dev) { unsigned int action; @@ -234,6 +243,11 @@ static irqreturn_t loongson_ipi_interrupt(int irq, void *dev) per_cpu(irq_stat, cpu).ipi_irqs[IPI_CALL_FUNCTION]++; } + if (action & SMP_IRQ_WORK) { + irq_work_run(); + per_cpu(irq_stat, cpu).ipi_irqs[IPI_IRQ_WORK]++; + } + return IRQ_HANDLED; } From a0f7085f6a63f19f83f2644ce2da49a8d3cf7c0f Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Sat, 20 Jul 2024 22:40:58 +0800 Subject: [PATCH 140/152] LoongArch: Add RANDOMIZE_KSTACK_OFFSET support Add support of kernel stack offset randomization while handling syscall, the offset is defaultly limited by KSTACK_OFFSET_MAX(). In order to avoid triggering stack canaries (due to __builtin_alloca()) and slowing down the entry path, use __no_stack_protector attribute to disable stack protector for do_syscall() at function level. With this patch, the REPORT_STACK test show that: `loongarch64 bits of stack entropy: 7` Reviewed-by: Kees Cook Signed-off-by: Jinjie Ruan Signed-off-by: Huacai Chen --- arch/loongarch/Kconfig | 1 + arch/loongarch/kernel/syscall.c | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index ddc042895d01..fcf6451b4e38 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -106,6 +106,7 @@ config LOONGARCH select HAVE_ARCH_KFENCE select HAVE_ARCH_KGDB if PERF_EVENTS select HAVE_ARCH_MMAP_RND_BITS if MMU + select HAVE_ARCH_RANDOMIZE_KSTACK_OFFSET select HAVE_ARCH_SECCOMP select HAVE_ARCH_SECCOMP_FILTER select HAVE_ARCH_TRACEHOOK diff --git a/arch/loongarch/kernel/syscall.c b/arch/loongarch/kernel/syscall.c index ec17cd5163b7..ba5d0930a74f 100644 --- a/arch/loongarch/kernel/syscall.c +++ b/arch/loongarch/kernel/syscall.c @@ -9,11 +9,14 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -39,7 +42,7 @@ void *sys_call_table[__NR_syscalls] = { typedef long (*sys_call_fn)(unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long); -void noinstr do_syscall(struct pt_regs *regs) +void noinstr __no_stack_protector do_syscall(struct pt_regs *regs) { unsigned long nr; sys_call_fn syscall_fn; @@ -55,11 +58,28 @@ void noinstr do_syscall(struct pt_regs *regs) nr = syscall_enter_from_user_mode(regs, nr); + add_random_kstack_offset(); + if (nr < NR_syscalls) { syscall_fn = sys_call_table[nr]; regs->regs[4] = syscall_fn(regs->orig_a0, regs->regs[5], regs->regs[6], regs->regs[7], regs->regs[8], regs->regs[9]); } + /* + * This value will get limited by KSTACK_OFFSET_MAX(), which is 10 + * bits. The actual entropy will be further reduced by the compiler + * when applying stack alignment constraints: 16-bytes (i.e. 4-bits) + * aligned, which will remove the 4 low bits from any entropy chosen + * here. + * + * The resulting 6 bits of entropy is seen in SP[9:4]. + */ + choose_random_kstack_offset(drdtime()); + syscall_exit_to_user_mode(regs); } + +#ifdef CONFIG_RANDOMIZE_KSTACK_OFFSET +STACK_FRAME_NON_STANDARD(do_syscall); +#endif From b7a2750ef268e2723acb785c4903b0b78c3dcbc6 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 20 Jul 2024 22:40:59 +0800 Subject: [PATCH 141/152] LoongArch: Add ARCH_HAS_PTE_DEVMAP support In order for things like get_user_pages() to work on ZONE_DEVICE memory, we need a software PTE bit to identify device-backed PFNs. Hook this up along with the relevant helpers to join in with ARCH_HAS_PTE_DEVMAP. Signed-off-by: Huacai Chen --- arch/loongarch/Kconfig | 1 + arch/loongarch/include/asm/pgtable-bits.h | 6 ++++-- arch/loongarch/include/asm/pgtable.h | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index fcf6451b4e38..be5249ebd8fc 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -22,6 +22,7 @@ config LOONGARCH select ARCH_HAS_KERNEL_FPU_SUPPORT if CPU_HAS_FPU select ARCH_HAS_NMI_SAFE_THIS_CPU_OPS select ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE + select ARCH_HAS_PTE_DEVMAP select ARCH_HAS_PTE_SPECIAL select ARCH_HAS_TICK_BROADCAST if GENERIC_CLOCKEVENTS_BROADCAST select ARCH_INLINE_READ_LOCK if !PREEMPTION diff --git a/arch/loongarch/include/asm/pgtable-bits.h b/arch/loongarch/include/asm/pgtable-bits.h index 21319c1e045c..82cd3a9f094b 100644 --- a/arch/loongarch/include/asm/pgtable-bits.h +++ b/arch/loongarch/include/asm/pgtable-bits.h @@ -22,6 +22,7 @@ #define _PAGE_PFN_SHIFT 12 #define _PAGE_SWP_EXCLUSIVE_SHIFT 23 #define _PAGE_PFN_END_SHIFT 48 +#define _PAGE_DEVMAP_SHIFT 59 #define _PAGE_PRESENT_INVALID_SHIFT 60 #define _PAGE_NO_READ_SHIFT 61 #define _PAGE_NO_EXEC_SHIFT 62 @@ -35,6 +36,7 @@ #define _PAGE_MODIFIED (_ULCAST_(1) << _PAGE_MODIFIED_SHIFT) #define _PAGE_PROTNONE (_ULCAST_(1) << _PAGE_PROTNONE_SHIFT) #define _PAGE_SPECIAL (_ULCAST_(1) << _PAGE_SPECIAL_SHIFT) +#define _PAGE_DEVMAP (_ULCAST_(1) << _PAGE_DEVMAP_SHIFT) /* We borrow bit 23 to store the exclusive marker in swap PTEs. */ #define _PAGE_SWP_EXCLUSIVE (_ULCAST_(1) << _PAGE_SWP_EXCLUSIVE_SHIFT) @@ -74,8 +76,8 @@ #define __READABLE (_PAGE_VALID) #define __WRITEABLE (_PAGE_DIRTY | _PAGE_WRITE) -#define _PAGE_CHG_MASK (_PAGE_MODIFIED | _PAGE_SPECIAL | _PFN_MASK | _CACHE_MASK | _PAGE_PLV) -#define _HPAGE_CHG_MASK (_PAGE_MODIFIED | _PAGE_SPECIAL | _PFN_MASK | _CACHE_MASK | _PAGE_PLV | _PAGE_HUGE) +#define _PAGE_CHG_MASK (_PAGE_MODIFIED | _PAGE_SPECIAL | _PAGE_DEVMAP | _PFN_MASK | _CACHE_MASK | _PAGE_PLV) +#define _HPAGE_CHG_MASK (_PAGE_MODIFIED | _PAGE_SPECIAL | _PAGE_DEVMAP | _PFN_MASK | _CACHE_MASK | _PAGE_PLV | _PAGE_HUGE) #define PAGE_NONE __pgprot(_PAGE_PROTNONE | _PAGE_NO_READ | \ _PAGE_USER | _CACHE_CC) diff --git a/arch/loongarch/include/asm/pgtable.h b/arch/loongarch/include/asm/pgtable.h index af3acdf3481a..0e821be63268 100644 --- a/arch/loongarch/include/asm/pgtable.h +++ b/arch/loongarch/include/asm/pgtable.h @@ -424,6 +424,9 @@ static inline int pte_special(pte_t pte) { return pte_val(pte) & _PAGE_SPECIAL; static inline pte_t pte_mkspecial(pte_t pte) { pte_val(pte) |= _PAGE_SPECIAL; return pte; } #endif /* CONFIG_ARCH_HAS_PTE_SPECIAL */ +static inline int pte_devmap(pte_t pte) { return !!(pte_val(pte) & _PAGE_DEVMAP); } +static inline pte_t pte_mkdevmap(pte_t pte) { pte_val(pte) |= _PAGE_DEVMAP; return pte; } + #define pte_accessible pte_accessible static inline unsigned long pte_accessible(struct mm_struct *mm, pte_t a) { @@ -558,6 +561,17 @@ static inline pmd_t pmd_mkyoung(pmd_t pmd) return pmd; } +static inline int pmd_devmap(pmd_t pmd) +{ + return !!(pmd_val(pmd) & _PAGE_DEVMAP); +} + +static inline pmd_t pmd_mkdevmap(pmd_t pmd) +{ + pmd_val(pmd) |= _PAGE_DEVMAP; + return pmd; +} + static inline struct page *pmd_page(pmd_t pmd) { if (pmd_trans_huge(pmd)) @@ -613,6 +627,11 @@ static inline long pmd_protnone(pmd_t pmd) #define pmd_leaf(pmd) ((pmd_val(pmd) & _PAGE_HUGE) != 0) #define pud_leaf(pud) ((pud_val(pud) & _PAGE_HUGE) != 0) +#ifdef CONFIG_TRANSPARENT_HUGEPAGE +#define pud_devmap(pud) (0) +#define pgd_devmap(pgd) (0) +#endif /* CONFIG_TRANSPARENT_HUGEPAGE */ + /* * We provide our own get_unmapped area to cope with the virtual aliasing * constraints placed on us by the cache architecture. From 614d7e99752e02ff6f6d447a83d2929b9649b6cb Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 20 Jul 2024 22:40:59 +0800 Subject: [PATCH 142/152] LoongArch: Add ARCH_HAS_DEBUG_VM_PGTABLE support Add ARCH_HAS_DEBUG_VM_PGTABLE selection in Kconfig, in order to make corresponding vm debug features usable on LoongArch. Also update the corresponding arch-support.txt document. Signed-off-by: Huacai Chen --- Documentation/features/debug/debug-vm-pgtable/arch-support.txt | 2 +- arch/loongarch/Kconfig | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/features/debug/debug-vm-pgtable/arch-support.txt b/Documentation/features/debug/debug-vm-pgtable/arch-support.txt index bbf029f095cb..156687a7436d 100644 --- a/Documentation/features/debug/debug-vm-pgtable/arch-support.txt +++ b/Documentation/features/debug/debug-vm-pgtable/arch-support.txt @@ -12,7 +12,7 @@ | arm64: | ok | | csky: | TODO | | hexagon: | TODO | - | loongarch: | TODO | + | loongarch: | ok | | m68k: | TODO | | microblaze: | TODO | | mips: | TODO | diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index be5249ebd8fc..0693b621ebd4 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -16,6 +16,7 @@ config LOONGARCH select ARCH_HAS_ACPI_TABLE_UPGRADE if ACPI select ARCH_HAS_CPU_FINALIZE_INIT select ARCH_HAS_CURRENT_STACK_POINTER + select ARCH_HAS_DEBUG_VM_PGTABLE select ARCH_HAS_FAST_MULTIPLIER select ARCH_HAS_FORTIFY_SOURCE select ARCH_HAS_KCOV From 8e02c3b782ec64343f3cccc8dc5a8be2b379e80b Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 20 Jul 2024 22:40:59 +0800 Subject: [PATCH 143/152] LoongArch: Add writecombine support for DMW-based ioremap() Currently, only TLB-based ioremap() support writecombine, so add the counterpart for DMW-based ioremap() with help of DMW2. The base address (WRITECOMBINE_BASE) is configured as 0xa000000000000000. DMW3 is unused by kernel now, however firmware may leave garbage in them and interfere kernel's address mapping. So clear it as necessary. BTW, centralize the DMW configuration to macro SETUP_DMWINS. Signed-off-by: Jiaxun Yang Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/addrspace.h | 4 ++++ arch/loongarch/include/asm/io.h | 10 ++++++++-- arch/loongarch/include/asm/loongarch.h | 10 +++++++++- arch/loongarch/include/asm/stackframe.h | 11 +++++++++++ arch/loongarch/kernel/head.S | 11 ++--------- arch/loongarch/power/suspend_asm.S | 6 +----- drivers/firmware/efi/libstub/loongarch.c | 2 ++ 7 files changed, 37 insertions(+), 17 deletions(-) diff --git a/arch/loongarch/include/asm/addrspace.h b/arch/loongarch/include/asm/addrspace.h index 7bd47d65bf7a..fe198b473f84 100644 --- a/arch/loongarch/include/asm/addrspace.h +++ b/arch/loongarch/include/asm/addrspace.h @@ -37,6 +37,10 @@ extern unsigned long vm_map_base; #define UNCACHE_BASE CSR_DMW0_BASE #endif +#ifndef WRITECOMBINE_BASE +#define WRITECOMBINE_BASE CSR_DMW2_BASE +#endif + #define DMW_PABITS 48 #define TO_PHYS_MASK ((1ULL << DMW_PABITS) - 1) diff --git a/arch/loongarch/include/asm/io.h b/arch/loongarch/include/asm/io.h index c2f9979b2979..5e95a60df180 100644 --- a/arch/loongarch/include/asm/io.h +++ b/arch/loongarch/include/asm/io.h @@ -25,10 +25,16 @@ extern void __init early_iounmap(void __iomem *addr, unsigned long size); static inline void __iomem *ioremap_prot(phys_addr_t offset, unsigned long size, unsigned long prot_val) { - if (prot_val & _CACHE_CC) + switch (prot_val & _CACHE_MASK) { + case _CACHE_CC: return (void __iomem *)(unsigned long)(CACHE_BASE + offset); - else + case _CACHE_SUC: return (void __iomem *)(unsigned long)(UNCACHE_BASE + offset); + case _CACHE_WUC: + return (void __iomem *)(unsigned long)(WRITECOMBINE_BASE + offset); + default: + return NULL; + } } #define ioremap(offset, size) \ diff --git a/arch/loongarch/include/asm/loongarch.h b/arch/loongarch/include/asm/loongarch.h index eb09adda54b7..c430df595376 100644 --- a/arch/loongarch/include/asm/loongarch.h +++ b/arch/loongarch/include/asm/loongarch.h @@ -877,7 +877,7 @@ #define LOONGARCH_CSR_DMWIN2 0x182 /* 64 direct map win2: MEM */ #define LOONGARCH_CSR_DMWIN3 0x183 /* 64 direct map win3: MEM */ -/* Direct Map window 0/1 */ +/* Direct Map window 0/1/2/3 */ #define CSR_DMW0_PLV0 _CONST64_(1 << 0) #define CSR_DMW0_VSEG _CONST64_(0x8000) #define CSR_DMW0_BASE (CSR_DMW0_VSEG << DMW_PABITS) @@ -889,6 +889,14 @@ #define CSR_DMW1_BASE (CSR_DMW1_VSEG << DMW_PABITS) #define CSR_DMW1_INIT (CSR_DMW1_BASE | CSR_DMW1_MAT | CSR_DMW1_PLV0) +#define CSR_DMW2_PLV0 _CONST64_(1 << 0) +#define CSR_DMW2_MAT _CONST64_(2 << 4) +#define CSR_DMW2_VSEG _CONST64_(0xa000) +#define CSR_DMW2_BASE (CSR_DMW2_VSEG << DMW_PABITS) +#define CSR_DMW2_INIT (CSR_DMW2_BASE | CSR_DMW2_MAT | CSR_DMW2_PLV0) + +#define CSR_DMW3_INIT 0x0 + /* Performance Counter registers */ #define LOONGARCH_CSR_PERFCTRL0 0x200 /* 32 perf event 0 config */ #define LOONGARCH_CSR_PERFCNTR0 0x201 /* 64 perf event 0 count value */ diff --git a/arch/loongarch/include/asm/stackframe.h b/arch/loongarch/include/asm/stackframe.h index d9eafd3ee3d1..66736837085b 100644 --- a/arch/loongarch/include/asm/stackframe.h +++ b/arch/loongarch/include/asm/stackframe.h @@ -38,6 +38,17 @@ cfi_restore \reg \offset \docfi .endm + .macro SETUP_DMWINS temp + li.d \temp, CSR_DMW0_INIT # WUC, PLV0, 0x8000 xxxx xxxx xxxx + csrwr \temp, LOONGARCH_CSR_DMWIN0 + li.d \temp, CSR_DMW1_INIT # CAC, PLV0, 0x9000 xxxx xxxx xxxx + csrwr \temp, LOONGARCH_CSR_DMWIN1 + li.d \temp, CSR_DMW2_INIT # WUC, PLV0, 0xa000 xxxx xxxx xxxx + csrwr \temp, LOONGARCH_CSR_DMWIN2 + li.d \temp, CSR_DMW3_INIT # 0x0, unused + csrwr \temp, LOONGARCH_CSR_DMWIN3 + .endm + /* Jump to the runtime virtual address. */ .macro JUMP_VIRT_ADDR temp1 temp2 li.d \temp1, CACHE_BASE diff --git a/arch/loongarch/kernel/head.S b/arch/loongarch/kernel/head.S index 4677ea8fa8e9..506a99a5bbc7 100644 --- a/arch/loongarch/kernel/head.S +++ b/arch/loongarch/kernel/head.S @@ -44,11 +44,7 @@ SYM_DATA(kernel_fsize, .long _kernel_fsize); SYM_CODE_START(kernel_entry) # kernel entry point /* Config direct window and set PG */ - li.d t0, CSR_DMW0_INIT # UC, PLV0, 0x8000 xxxx xxxx xxxx - csrwr t0, LOONGARCH_CSR_DMWIN0 - li.d t0, CSR_DMW1_INIT # CA, PLV0, 0x9000 xxxx xxxx xxxx - csrwr t0, LOONGARCH_CSR_DMWIN1 - + SETUP_DMWINS t0 JUMP_VIRT_ADDR t0, t1 /* Enable PG */ @@ -124,11 +120,8 @@ SYM_CODE_END(kernel_entry) * function after setting up the stack and tp registers. */ SYM_CODE_START(smpboot_entry) - li.d t0, CSR_DMW0_INIT # UC, PLV0 - csrwr t0, LOONGARCH_CSR_DMWIN0 - li.d t0, CSR_DMW1_INIT # CA, PLV0 - csrwr t0, LOONGARCH_CSR_DMWIN1 + SETUP_DMWINS t0 JUMP_VIRT_ADDR t0, t1 #ifdef CONFIG_PAGE_SIZE_4KB diff --git a/arch/loongarch/power/suspend_asm.S b/arch/loongarch/power/suspend_asm.S index e2fc3b4e31f0..c28ad52b7baf 100644 --- a/arch/loongarch/power/suspend_asm.S +++ b/arch/loongarch/power/suspend_asm.S @@ -73,11 +73,7 @@ SYM_FUNC_START(loongarch_suspend_enter) * Reload all of the registers and return. */ SYM_INNER_LABEL(loongarch_wakeup_start, SYM_L_GLOBAL) - li.d t0, CSR_DMW0_INIT # UC, PLV0 - csrwr t0, LOONGARCH_CSR_DMWIN0 - li.d t0, CSR_DMW1_INIT # CA, PLV0 - csrwr t0, LOONGARCH_CSR_DMWIN1 - + SETUP_DMWINS t0 JUMP_VIRT_ADDR t0, t1 /* Enable PG */ diff --git a/drivers/firmware/efi/libstub/loongarch.c b/drivers/firmware/efi/libstub/loongarch.c index d0ef93551c44..3782d0a187d1 100644 --- a/drivers/firmware/efi/libstub/loongarch.c +++ b/drivers/firmware/efi/libstub/loongarch.c @@ -74,6 +74,8 @@ efi_status_t efi_boot_kernel(void *handle, efi_loaded_image_t *image, /* Config Direct Mapping */ csr_write64(CSR_DMW0_INIT, LOONGARCH_CSR_DMWIN0); csr_write64(CSR_DMW1_INIT, LOONGARCH_CSR_DMWIN1); + csr_write64(CSR_DMW2_INIT, LOONGARCH_CSR_DMWIN2); + csr_write64(CSR_DMW3_INIT, LOONGARCH_CSR_DMWIN3); real_kernel_entry = (void *)kernel_entry_address(kernel_addr, image); From f60d251b27f6599b3407950924ac9b2c7a94b714 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 20 Jul 2024 22:41:06 +0800 Subject: [PATCH 144/152] LoongArch: Add architectural preparation for CPUFreq Add architectural preparation for CPUFreq driver, including: Kconfig, register definition and platform device registration. Some of LoongArch processors support DVFS, their IOCSR.FEATURES has IOCSRF_FREQSCALE set. And they has a micro-core in the package called SMC (System Management Controller) to scale frequency, voltage, etc. Signed-off-by: Binbin Zhou Signed-off-by: Huacai Chen --- arch/loongarch/Kconfig | 1 + arch/loongarch/include/asm/loongarch.h | 3 +++ arch/loongarch/power/platform.c | 15 +++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 0693b621ebd4..9687b1d24126 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -702,6 +702,7 @@ config ARCH_HIBERNATION_POSSIBLE source "kernel/power/Kconfig" source "drivers/acpi/Kconfig" +source "drivers/cpufreq/Kconfig" endmenu diff --git a/arch/loongarch/include/asm/loongarch.h b/arch/loongarch/include/asm/loongarch.h index c430df595376..1501f0f8b06e 100644 --- a/arch/loongarch/include/asm/loongarch.h +++ b/arch/loongarch/include/asm/loongarch.h @@ -1061,11 +1061,14 @@ #define LOONGARCH_IOCSR_NODECNT 0x408 #define LOONGARCH_IOCSR_MISC_FUNC 0x420 +#define IOCSR_MISC_FUNC_SOFT_INT BIT_ULL(10) #define IOCSR_MISC_FUNC_TIMER_RESET BIT_ULL(21) #define IOCSR_MISC_FUNC_EXT_IOI_EN BIT_ULL(48) #define LOONGARCH_IOCSR_CPUTEMP 0x428 +#define LOONGARCH_IOCSR_SMCMBX 0x51c + /* PerCore CSR, only accessible by local cores */ #define LOONGARCH_IOCSR_IPI_STATUS 0x1000 #define LOONGARCH_IOCSR_IPI_EN 0x1004 diff --git a/arch/loongarch/power/platform.c b/arch/loongarch/power/platform.c index 3ea8e07aa225..a19353f7d1b0 100644 --- a/arch/loongarch/power/platform.c +++ b/arch/loongarch/power/platform.c @@ -34,6 +34,21 @@ void enable_pci_wakeup(void) acpi_write_bit_register(ACPI_BITREG_PCIEXP_WAKE_DISABLE, 0); } +static struct platform_device loongson3_cpufreq_device = { + .name = "loongson3_cpufreq", + .id = -1, +}; + +static int __init loongson_cpufreq_init(void) +{ + if (!cpu_has_scalefreq) + return -ENODEV; + + return platform_device_register(&loongson3_cpufreq_device); +} + +arch_initcall(loongson_cpufreq_init); + static int __init loongson3_acpi_suspend_init(void) { #ifdef CONFIG_ACPI From e523a5a65f34ca5bd67ccfdfdccf04689b58f0bf Mon Sep 17 00:00:00 2001 From: Jiaxun Yang Date: Sat, 20 Jul 2024 22:41:06 +0800 Subject: [PATCH 145/152] LoongArch: Add ACPI standard hardware register based S3 support Most LoongArch 64 machines are using custom "SADR" ACPI extension to perform ACPI S3 sleep. However the standard ACPI way to perform sleep is to write a value to ACPI PM1/SLEEP_CTL register, and this is never supported properly in kernel. Add standard S3 sleep by providing a default DoSuspend function which calls ACPI's acpi_enter_sleep_state() routine when SADR is not provided by the firmware. Also fix suspend assembly code so that ra is set properly before go into sleep routine. (Previously linked address of jirl was set to a0, some firmware do require return address in a0 but it's already set with la.pcrel before). Signed-off-by: Jiaxun Yang Signed-off-by: Huacai Chen --- arch/loongarch/power/platform.c | 22 +++++++++++++++++----- arch/loongarch/power/suspend_asm.S | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/arch/loongarch/power/platform.c b/arch/loongarch/power/platform.c index a19353f7d1b0..0909729dc2e1 100644 --- a/arch/loongarch/power/platform.c +++ b/arch/loongarch/power/platform.c @@ -49,22 +49,34 @@ static int __init loongson_cpufreq_init(void) arch_initcall(loongson_cpufreq_init); +static void default_suspend_addr(void) +{ + acpi_enter_sleep_state(ACPI_STATE_S3); +} + static int __init loongson3_acpi_suspend_init(void) { #ifdef CONFIG_ACPI acpi_status status; uint64_t suspend_addr = 0; - if (acpi_disabled || acpi_gbl_reduced_hardware) + if (acpi_disabled) + return 0; + + if (!acpi_gbl_reduced_hardware) + acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1); + + if (!acpi_sleep_state_supported(ACPI_STATE_S3)) return 0; - acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1); status = acpi_evaluate_integer(NULL, "\\SADR", NULL, &suspend_addr); if (ACPI_FAILURE(status) || !suspend_addr) { - pr_err("ACPI S3 is not support!\n"); - return -1; + pr_info("ACPI S3 supported with hardware register default\n"); + loongson_sysconf.suspend_addr = (u64)default_suspend_addr; + } else { + pr_info("ACPI S3 supported with Loongson ACPI SADR extension\n"); + loongson_sysconf.suspend_addr = (u64)phys_to_virt(PHYSADDR(suspend_addr)); } - loongson_sysconf.suspend_addr = (u64)phys_to_virt(PHYSADDR(suspend_addr)); #endif return 0; } diff --git a/arch/loongarch/power/suspend_asm.S b/arch/loongarch/power/suspend_asm.S index c28ad52b7baf..9fe28d5a0270 100644 --- a/arch/loongarch/power/suspend_asm.S +++ b/arch/loongarch/power/suspend_asm.S @@ -66,7 +66,7 @@ SYM_FUNC_START(loongarch_suspend_enter) la.pcrel a0, loongarch_wakeup_start la.pcrel t0, loongarch_suspend_addr ld.d t0, t0, 0 - jirl a0, t0, 0 /* Call BIOS's STR sleep routine */ + jirl ra, t0, 0 /* Call BIOS's STR sleep routine */ /* * This is where we return upon wakeup. From 67e6b115ddcf8f03108cedcf66261153904ce4fb Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 20 Jul 2024 22:41:06 +0800 Subject: [PATCH 146/152] LoongArch: Automatically disable KASLR for hibernation Hibernation assumes the memory layout after resume be the same as that before sleep, so it expects the kernel is loaded at the same position. To achieve this goal we automatically disable KASLR if user explicitly requests hibernation via the "resume=" command line. Since "nohibernate" and "noresume" have higher priorities than "resume=", we only disable KASLR if there is no "nohibernate" and "noresume". Signed-off-by: Huacai Chen --- arch/loongarch/kernel/relocate.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/arch/loongarch/kernel/relocate.c b/arch/loongarch/kernel/relocate.c index 1acfa704c8d0..6834d627ff20 100644 --- a/arch/loongarch/kernel/relocate.c +++ b/arch/loongarch/kernel/relocate.c @@ -123,6 +123,32 @@ static inline __init bool kaslr_disabled(void) if (str == boot_command_line || (str > boot_command_line && *(str - 1) == ' ')) return true; +#ifdef CONFIG_HIBERNATION + str = strstr(builtin_cmdline, "nohibernate"); + if (str == builtin_cmdline || (str > builtin_cmdline && *(str - 1) == ' ')) + return false; + + str = strstr(boot_command_line, "nohibernate"); + if (str == boot_command_line || (str > boot_command_line && *(str - 1) == ' ')) + return false; + + str = strstr(builtin_cmdline, "noresume"); + if (str == builtin_cmdline || (str > builtin_cmdline && *(str - 1) == ' ')) + return false; + + str = strstr(boot_command_line, "noresume"); + if (str == boot_command_line || (str > boot_command_line && *(str - 1) == ' ')) + return false; + + str = strstr(builtin_cmdline, "resume="); + if (str == builtin_cmdline || (str > builtin_cmdline && *(str - 1) == ' ')) + return true; + + str = strstr(boot_command_line, "resume="); + if (str == boot_command_line || (str > boot_command_line && *(str - 1) == ' ')) + return true; +#endif + return false; } From 0124fbb4c6dba23dbdf80c829be68adbccde2722 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 20 Jul 2024 22:41:07 +0800 Subject: [PATCH 147/152] LoongArch: Use correct API to map cmdline in relocate_kernel() fw_arg1 is in memory space rather than I/O space, so we should use early_memremap_ro() instead of early_ioremap() to map the cmdline. Moreover, we should unmap it after using. Suggested-by: Jiaxun Yang Reviewed-by: Jiaxun Yang Signed-off-by: Huacai Chen --- arch/loongarch/kernel/relocate.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/loongarch/kernel/relocate.c b/arch/loongarch/kernel/relocate.c index 6834d627ff20..d142061c61d4 100644 --- a/arch/loongarch/kernel/relocate.c +++ b/arch/loongarch/kernel/relocate.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -196,7 +197,7 @@ unsigned long __init relocate_kernel(void) unsigned long kernel_length; unsigned long random_offset = 0; void *location_new = _text; /* Default to original kernel start */ - char *cmdline = early_ioremap(fw_arg1, COMMAND_LINE_SIZE); /* Boot command line is passed in fw_arg1 */ + char *cmdline = early_memremap_ro(fw_arg1, COMMAND_LINE_SIZE); /* Boot command line is passed in fw_arg1 */ strscpy(boot_command_line, cmdline, COMMAND_LINE_SIZE); @@ -208,6 +209,7 @@ unsigned long __init relocate_kernel(void) random_offset = (unsigned long)location_new - (unsigned long)(_text); #endif reloc_offset = (unsigned long)_text - VMLINUX_LOAD_ADDRESS; + early_memunmap(cmdline, COMMAND_LINE_SIZE); if (random_offset) { kernel_length = (long)(_end) - (long)(_text); From 0ad158e4ef769c5f261cbf791e0005d69fc3b785 Mon Sep 17 00:00:00 2001 From: Xi Ruoyao Date: Sat, 20 Jul 2024 22:41:07 +0800 Subject: [PATCH 148/152] LoongArch: Remove a redundant checking in relocator With our linker script "relocated_addr >= VMLINUX_LOAD_ADDRESS" should be always true. Signed-off-by: Xi Ruoyao Signed-off-by: Huacai Chen --- arch/loongarch/kernel/relocate.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arch/loongarch/kernel/relocate.c b/arch/loongarch/kernel/relocate.c index d142061c61d4..e3836f0b9bd8 100644 --- a/arch/loongarch/kernel/relocate.c +++ b/arch/loongarch/kernel/relocate.c @@ -35,9 +35,7 @@ static inline void __init relocate_relative(void) if (rela->r_info != R_LARCH_RELATIVE) continue; - if (relocated_addr >= VMLINUX_LOAD_ADDRESS) - relocated_addr = (Elf64_Addr)RELOCATED(relocated_addr); - + relocated_addr = (Elf64_Addr)RELOCATED(relocated_addr); *(Elf64_Addr *)RELOCATED(addr) = relocated_addr; } } From e05d4cd9b895c503dcf19c0ed9ebb8d393b220ec Mon Sep 17 00:00:00 2001 From: Xi Ruoyao Date: Sat, 20 Jul 2024 22:41:07 +0800 Subject: [PATCH 149/152] LoongArch: Add support for relocating the kernel with RELR relocation RELR as a relocation packing format for relative relocations for reducing the size of relative relocation records. In a position independent executable there are often many relative relocation records, and our vmlinux is a PIE. The LLD linker (since 17.0.0) and the BFD linker (since 2.43) supports packing the relocations in the RELR format for LoongArch, with the flag -z pack-relative-relocs. Commits 5cf896fb6be3eff ("arm64: Add support for relocating the kernel with RELR relocations") and ccb2d173b983984bfa ("Makefile: use -z pack-relative-relocs") have already added the framework to use RELR. We just need to wire it up and process the RELR relocation records in relocate_relative() in addition to the RELA relocation records. A ".p2align 3" directive is added to la_abs macro or the BFD linker cannot pack the relocation records against the .la_abs section (the ". = ALIGN(8);" directive in vmlinux.lds.S is too late in the linking process). With defconfig and CONFIG_RELR vmlinux.efi is 2.1 MiB (6%) smaller, and vmlinuz.efi (using gzip compression) is 384 KiB (2.8%) smaller. Link: https://groups.google.com/d/topic/generic-abi/bX460iggiKg Link: https://reviews.llvm.org/D138135#4531389 Link: https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=d89ecf33ab6d Signed-off-by: Xi Ruoyao Signed-off-by: Huacai Chen --- arch/loongarch/Kconfig | 1 + arch/loongarch/include/asm/asmmacro.h | 1 + arch/loongarch/include/asm/setup.h | 5 +++++ arch/loongarch/kernel/relocate.c | 18 ++++++++++++++++++ arch/loongarch/kernel/vmlinux.lds.S | 8 ++++++++ 5 files changed, 33 insertions(+) diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 9687b1d24126..f5df69ad70a6 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -610,6 +610,7 @@ config ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION config RELOCATABLE bool "Relocatable kernel" + select ARCH_HAS_RELR help This builds the kernel as a Position Independent Executable (PIE), which retains all relocation metadata required, so as to relocate diff --git a/arch/loongarch/include/asm/asmmacro.h b/arch/loongarch/include/asm/asmmacro.h index 655db7d7a427..8d7f501b0a12 100644 --- a/arch/loongarch/include/asm/asmmacro.h +++ b/arch/loongarch/include/asm/asmmacro.h @@ -609,6 +609,7 @@ lu32i.d \reg, 0 lu52i.d \reg, \reg, 0 .pushsection ".la_abs", "aw", %progbits + .p2align 3 .dword 766b .dword \sym .popsection diff --git a/arch/loongarch/include/asm/setup.h b/arch/loongarch/include/asm/setup.h index ee52fb1e9963..3c2fb16b11b6 100644 --- a/arch/loongarch/include/asm/setup.h +++ b/arch/loongarch/include/asm/setup.h @@ -34,6 +34,11 @@ extern long __la_abs_end; extern long __rela_dyn_begin; extern long __rela_dyn_end; +#ifdef CONFIG_RELR +extern long __relr_dyn_begin; +extern long __relr_dyn_end; +#endif + extern unsigned long __init relocate_kernel(void); #endif diff --git a/arch/loongarch/kernel/relocate.c b/arch/loongarch/kernel/relocate.c index e3836f0b9bd8..50c469067f3a 100644 --- a/arch/loongarch/kernel/relocate.c +++ b/arch/loongarch/kernel/relocate.c @@ -38,6 +38,24 @@ static inline void __init relocate_relative(void) relocated_addr = (Elf64_Addr)RELOCATED(relocated_addr); *(Elf64_Addr *)RELOCATED(addr) = relocated_addr; } + +#ifdef CONFIG_RELR + u64 *addr = NULL; + u64 *relr = (u64 *)&__relr_dyn_begin; + u64 *relr_end = (u64 *)&__relr_dyn_end; + + for ( ; relr < relr_end; relr++) { + if ((*relr & 1) == 0) { + addr = (u64 *)(*relr + reloc_offset); + *addr++ += reloc_offset; + } else { + for (u64 *p = addr, r = *relr >> 1; r; p++, r >>= 1) + if (r & 1) + *p += reloc_offset; + addr += 63; + } + } +#endif } static inline void __init relocate_absolute(long random_offset) diff --git a/arch/loongarch/kernel/vmlinux.lds.S b/arch/loongarch/kernel/vmlinux.lds.S index 3c7595342730..08ea921cdec1 100644 --- a/arch/loongarch/kernel/vmlinux.lds.S +++ b/arch/loongarch/kernel/vmlinux.lds.S @@ -113,6 +113,14 @@ SECTIONS __rela_dyn_end = .; } +#ifdef CONFIG_RELR + .relr.dyn : ALIGN(8) { + __relr_dyn_begin = .; + *(.relr.dyn) + __relr_dyn_end = .; + } +#endif + .data.rel : { *(.data.rel*) } #ifdef CONFIG_RELOCATABLE From 0d3a6322021b8c98527842518eeb685f3a8761c8 Mon Sep 17 00:00:00 2001 From: WANG Rui Date: Sat, 20 Jul 2024 22:41:07 +0800 Subject: [PATCH 150/152] LoongArch: Use rustc option -Zdirect-access-external-data -Zdirect-access-external-data is a new Rust compiler option added in Rust 1.78, which we use to optimize the access of external data in the Linux kernel's Rust code. This patch modifies the Rust code in vmlinux to directly access externa data, using PC-REL instead of GOT. However, Rust code whithin modules is constrained by the PC-REL addressing range and is explicitly set to use an indirect method. Acked-by: Miguel Ojeda Signed-off-by: WANG Rui Signed-off-by: Huacai Chen --- arch/loongarch/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/loongarch/Makefile b/arch/loongarch/Makefile index 8674e7e24c4a..ae3f80622f4c 100644 --- a/arch/loongarch/Makefile +++ b/arch/loongarch/Makefile @@ -105,7 +105,8 @@ KBUILD_CFLAGS += -fno-jump-tables endif KBUILD_RUSTFLAGS += --target=loongarch64-unknown-none-softfloat -KBUILD_RUSTFLAGS_MODULE += -Crelocation-model=pic +KBUILD_RUSTFLAGS_KERNEL += -Zdirect-access-external-data=yes +KBUILD_RUSTFLAGS_MODULE += -Zdirect-access-external-data=no ifeq ($(CONFIG_RELOCATABLE),y) KBUILD_CFLAGS_KERNEL += -fPIE From 3892b11eac5aaaeefbf717f1953288b77759d9e2 Mon Sep 17 00:00:00 2001 From: Tiezhu Yang Date: Sat, 20 Jul 2024 22:41:07 +0800 Subject: [PATCH 151/152] LoongArch: Check TIF_LOAD_WATCH to enable user space watchpoint Currently, there are some places to set CSR.PRMD.PWE, the first one is in hw_breakpoint_thread_switch() to enable user space singlestep via checking TIF_SINGLESTEP, the second one is in hw_breakpoint_control() to enable user space watchpoint. For the latter case, it should also check TIF_LOAD_WATCH to make the logic correct and clear. Fixes: c8e57ab0995c ("LoongArch: Trigger user-space watchpoints correctly") Signed-off-by: Tiezhu Yang Signed-off-by: Huacai Chen --- arch/loongarch/kernel/hw_breakpoint.c | 2 +- arch/loongarch/kernel/ptrace.c | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c index 621ad7634df7..a6e4b605bfa8 100644 --- a/arch/loongarch/kernel/hw_breakpoint.c +++ b/arch/loongarch/kernel/hw_breakpoint.c @@ -221,7 +221,7 @@ static int hw_breakpoint_control(struct perf_event *bp, } enable = csr_read64(LOONGARCH_CSR_CRMD); csr_write64(CSR_CRMD_WE | enable, LOONGARCH_CSR_CRMD); - if (bp->hw.target) + if (bp->hw.target && test_tsk_thread_flag(bp->hw.target, TIF_LOAD_WATCH)) regs->csr_prmd |= CSR_PRMD_PWE; break; case HW_BREAKPOINT_UNINSTALL: diff --git a/arch/loongarch/kernel/ptrace.c b/arch/loongarch/kernel/ptrace.c index 200109de1971..19dc6eff45cc 100644 --- a/arch/loongarch/kernel/ptrace.c +++ b/arch/loongarch/kernel/ptrace.c @@ -589,6 +589,7 @@ static int ptrace_hbp_set_ctrl(unsigned int note_type, struct perf_event *bp; struct perf_event_attr attr; struct arch_hw_breakpoint_ctrl ctrl; + struct thread_info *ti = task_thread_info(tsk); bp = ptrace_hbp_get_initialised_bp(note_type, tsk, idx); if (IS_ERR(bp)) @@ -613,8 +614,10 @@ static int ptrace_hbp_set_ctrl(unsigned int note_type, if (err) return err; attr.disabled = 0; + set_ti_thread_flag(ti, TIF_LOAD_WATCH); } else { attr.disabled = 1; + clear_ti_thread_flag(ti, TIF_LOAD_WATCH); } return modify_user_hw_breakpoint(bp, &attr); From 998b17d4440b8559a8bf4926e86f493101995519 Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Sat, 20 Jul 2024 22:41:07 +0800 Subject: [PATCH 152/152] LoongArch: Make the users of larch_insn_gen_break() constant LoongArch defines UPROBE_SWBP_INSN as a function call and this breaks arch_uprobe_trampoline() which uses it to initialize a static variable. Add the new "__builtin_constant_p" helper, __emit_break(), and redefine the current users of larch_insn_gen_break() to use it. Fixes: ff474a78cef5 ("uprobe: Add uretprobe syscall to speed up return probe") Reported-by: Nathan Chancellor Closes: https://lore.kernel.org/all/20240614174822.GA1185149@thelio-3990X/ Suggested-by: Andrii Nakryiko Tested-by: Tiezhu Yang Signed-off-by: Oleg Nesterov Signed-off-by: Huacai Chen --- arch/loongarch/include/asm/inst.h | 3 +++ arch/loongarch/include/asm/uprobes.h | 4 ++-- arch/loongarch/kernel/kprobes.c | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/arch/loongarch/include/asm/inst.h b/arch/loongarch/include/asm/inst.h index c3993fd88aba..944482063f14 100644 --- a/arch/loongarch/include/asm/inst.h +++ b/arch/loongarch/include/asm/inst.h @@ -532,6 +532,9 @@ static inline void emit_##NAME(union loongarch_instruction *insn, \ DEF_EMIT_REG0I15_FORMAT(break, break_op) +/* like emit_break(imm) but returns a constant expression */ +#define __emit_break(imm) ((u32)((imm) | (break_op << 15))) + #define DEF_EMIT_REG0I26_FORMAT(NAME, OP) \ static inline void emit_##NAME(union loongarch_instruction *insn, \ int offset) \ diff --git a/arch/loongarch/include/asm/uprobes.h b/arch/loongarch/include/asm/uprobes.h index c8f59983f702..99a0d198927f 100644 --- a/arch/loongarch/include/asm/uprobes.h +++ b/arch/loongarch/include/asm/uprobes.h @@ -9,10 +9,10 @@ typedef u32 uprobe_opcode_t; #define MAX_UINSN_BYTES 8 #define UPROBE_XOL_SLOT_BYTES MAX_UINSN_BYTES -#define UPROBE_SWBP_INSN larch_insn_gen_break(BRK_UPROBE_BP) +#define UPROBE_SWBP_INSN __emit_break(BRK_UPROBE_BP) #define UPROBE_SWBP_INSN_SIZE LOONGARCH_INSN_SIZE -#define UPROBE_XOLBP_INSN larch_insn_gen_break(BRK_UPROBE_XOLBP) +#define UPROBE_XOLBP_INSN __emit_break(BRK_UPROBE_XOLBP) struct arch_uprobe { unsigned long resume_era; diff --git a/arch/loongarch/kernel/kprobes.c b/arch/loongarch/kernel/kprobes.c index 17b040bd6067..8ba391cfabb0 100644 --- a/arch/loongarch/kernel/kprobes.c +++ b/arch/loongarch/kernel/kprobes.c @@ -4,8 +4,8 @@ #include #include -#define KPROBE_BP_INSN larch_insn_gen_break(BRK_KPROBE_BP) -#define KPROBE_SSTEPBP_INSN larch_insn_gen_break(BRK_KPROBE_SSTEPBP) +#define KPROBE_BP_INSN __emit_break(BRK_KPROBE_BP) +#define KPROBE_SSTEPBP_INSN __emit_break(BRK_KPROBE_SSTEPBP) DEFINE_PER_CPU(struct kprobe *, current_kprobe); DEFINE_PER_CPU(struct kprobe_ctlblk, kprobe_ctlblk);