gpio: mvebu: convert to noirq suspend/resume to prevent interrupt storm on resume
PATCH 1/1 gpio: mvebu: convert to noirq suspend/resume to prevent interrupt storm on resume
The driver uses the legacy .suspend/.resume callbacks, but sets IRQCHIP_MASK_ON_SUSPEND on the irq_chip. During resume, the PM core runs dpm_resume_noirq() first, which calls irq_pm_resume() to unmask interrupts, and only then runs dpm_resume() which invokes the driver's .resume callback to restore GPIO registers (GPIO_IN_POL, GPIO_IO_CONF, mask registers).
This ordering means interrupts are unmasked while the hardware is still in its reset state, potentially with incorrect polarities, causing spurious level-triggered interrupts before local IRQs are re-enabled.
Convert the driver from legacy .suspend/.resume callbacks to noirq callbacks via dev_pm_ops. The noirq phase runs before resume_device_irqs() on resume and after suspend_device_irqs() on suspend, ensuring GPIO registers are restored before interrupts are unmasked.
--- drivers/gpio/gpio-mvebu.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-)
drivers/gpio/gpio-mvebu.c +9 -6
@@ -979,9 +979,9 @@ static const struct of_device_id mvebu_gpio_of_match[] = { }, }; -static int mvebu_gpio_suspend (struct platform_device *pdev,pm_message_t state) +static int mvebu_gpio_suspend (struct device *dev) { -struct mvebu_gpio_chip *mvchip =platform_get_drvdata (pdev); +struct mvebu_gpio_chip *mvchip =dev_get_drvdata (dev);int i; regmap_read(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
@@ -1031,9 +1031,9 @@ static int mvebu_gpio_suspend(struct platform_device *pdev, pm_message_t state)return 0 ; } -static int mvebu_gpio_resume (struct platform_device *pdev) +static int mvebu_gpio_resume (struct device *dev) { -struct mvebu_gpio_chip *mvchip =platform_get_drvdata (pdev); +struct mvebu_gpio_chip *mvchip =dev_get_drvdata (dev);int i; regmap_write(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
@@ -1338,13 +1338,16 @@ static int mvebu_gpio_probe(struct platform_device *pdev)return 0 ; } +static const struct dev_pm_ops mvebu_gpio_pm_ops = { +SET_NOIRQ_SYSTEM_SLEEP_PM_OPS (mvebu_gpio_suspend, mvebu_gpio_resume) +}; +static struct platform_driver mvebu_gpio_driver = { .driver = { .name ="mvebu-gpio" , .of_match_table = mvebu_gpio_of_match, + .pm =pm_sleep_ptr (&mvebu_gpio_pm_ops), }, .probe = mvebu_gpio_probe, - .suspend = mvebu_gpio_suspend, - .resume = mvebu_gpio_resume, };builtin_platform_driver (mvebu_gpio_driver);
Signature · 1 line
2.55.0
Bartosz Golaszewski
maintainerQuoted 27 lines · Rosen Penev
The driver uses the legacy .suspend/.resume callbacks, but sets
IRQCHIP_MASK_ON_SUSPENDon theirq_chip. During resume, the PM core runsdpm_resume_noirq()first, which callsirq_pm_resume()to unmask interrupts, and only then runsdpm_resume()which invokes the driver's .resume callback to restore GPIO registers (GPIO_IN_POL,GPIO_IO_CONF, mask registers).This ordering means interrupts are unmasked while the hardware is still in its reset state, potentially with incorrect polarities, causing spurious level-triggered interrupts before local IRQs are re-enabled.
Convert the driver from legacy .suspend/.resume callbacks to noirq callbacks via
dev_pm_ops. The noirq phase runs beforeresume_device_irqs()on resume and aftersuspend_device_irqs()on suspend, ensuring GPIO registers are restored before interrupts are unmasked.Assisted-by opencode:big-pickleSigned-off-by Rosen Penev <[email protected]>--- drivers/gpio/gpio-mvebu.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-)drivers/gpio/gpio-mvebu.c +0
-0
drivers/gpio/gpio-mvebu.c static const struct of_device_id mvebu_gpio_of_match[] = { · line 982
Quoted 5 lines
drivers/gpio/gpio-mvebu.c +1
-1@@ -979,9 +979,9 @@ static const struct of_device_id mvebu_gpio_of_match[] = { }, }; -static int mvebu_gpio_suspend (struct platform_device *pdev,pm_message_t state) +static int mvebu_gpio_suspend (struct device *dev)
Needs __maybe_unused for SET_NOIRQ_SYSTEM_SLEEP_PM_OPS().
Bart
Quoted 32 lines · Bartosz Golaszewski
Quoted 30 lines · Rosen Penev
The driver uses the legacy .suspend/.resume callbacks, but sets
IRQCHIP_MASK_ON_SUSPENDon theirq_chip. During resume, the PM core runsdpm_resume_noirq()first, which callsirq_pm_resume()to unmask interrupts, and only then runsdpm_resume()which invokes the driver's .resume callback to restore GPIO registers (GPIO_IN_POL,GPIO_IO_CONF, mask registers).This ordering means interrupts are unmasked while the hardware is still in its reset state, potentially with incorrect polarities, causing spurious level-triggered interrupts before local IRQs are re-enabled.
Convert the driver from legacy .suspend/.resume callbacks to noirq callbacks via
dev_pm_ops. The noirq phase runs beforeresume_device_irqs()on resume and aftersuspend_device_irqs()on suspend, ensuring GPIO registers are restored before interrupts are unmasked.Assisted-by opencode:big-pickleSigned-off-by Rosen Penev <[email protected]>--- drivers/gpio/gpio-mvebu.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-)drivers/gpio/gpio-mvebu.c +0
-0@@ -979,9 +979,9 @@ static const struct of_device_id mvebu_gpio_of_match[] = { }, };
Quoted 4 lines
drivers/gpio/gpio-mvebu.c static const struct of_device_id mvebu_gpio_of_match[] = { · line 982
Quoted 2 lines
drivers/gpio/gpio-mvebu.c +1
-1-static int mvebu_gpio_suspend (struct platform_device *pdev,pm_message_t state) +static int mvebu_gpio_suspend (struct device *dev)Needs
__maybe_unusedforSET_NOIRQ_SYSTEM_SLEEP_PM_OPS().
I see __maybe_unused and #ifdef for code like this. Which is prefered?
Quoted 2 lines
Bart
Bartosz Golaszewski
maintainerQuoted 33 lines · Rosen Penev
Quoted 32 lines · Bartosz Golaszewski
Quoted 30 lines · Rosen Penev
The driver uses the legacy .suspend/.resume callbacks, but sets
IRQCHIP_MASK_ON_SUSPENDon theirq_chip. During resume, the PM core runsdpm_resume_noirq()first, which callsirq_pm_resume()to unmask interrupts, and only then runsdpm_resume()which invokes the driver's .resume callback to restore GPIO registers (GPIO_IN_POL,GPIO_IO_CONF, mask registers).This ordering means interrupts are unmasked while the hardware is still in its reset state, potentially with incorrect polarities, causing spurious level-triggered interrupts before local IRQs are re-enabled.
Convert the driver from legacy .suspend/.resume callbacks to noirq callbacks via
dev_pm_ops. The noirq phase runs beforeresume_device_irqs()on resume and aftersuspend_device_irqs()on suspend, ensuring GPIO registers are restored before interrupts are unmasked.Assisted-by opencode:big-pickleSigned-off-by Rosen Penev <[email protected]>--- drivers/gpio/gpio-mvebu.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-)drivers/gpio/gpio-mvebu.c +0
-0@@ -979,9 +979,9 @@ static const struct of_device_id mvebu_gpio_of_match[] = { }, };
Quoted 5 lines
Quoted 4 lines
drivers/gpio/gpio-mvebu.c static const struct of_device_id mvebu_gpio_of_match[] = { · line 982
Quoted 2 lines
drivers/gpio/gpio-mvebu.c +1
-1-static int mvebu_gpio_suspend (struct platform_device *pdev,pm_message_t state) +static int mvebu_gpio_suspend (struct device *dev)Needs
__maybe_unusedforSET_NOIRQ_SYSTEM_SLEEP_PM_OPS().I see
__maybe_unusedand #ifdef for code like this. Which is prefered?
__maybe_unused is cleaner. Please add some newlines between quoted parts of your emails and your responses for better readability. I'm finding myself looking for where your response starts.
Bart
kernel test robot
build botkernel test robot noticed the following build warnings:
Hi Rosen,
kernel test robot noticed the following build warnings:
[auto build test WARNING on brgl/gpio/for-next]
[also build test WARNING on linusw-pinctrl/devel linusw-pinctrl/for-next linus/master v7.2-rc6 next-20260803] [If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Rosen-Penev/gpio-mvebu-convert-to-noirq-suspend-resume-to-prevent-interrupt-storm-on-resume/20260804-155102 base: https://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux.git gpio/for-next patch link: https://lore.kernel.org/r/20260707233511.1272686-1-rosenp%40gmail.com patch subject: [PATCH] gpio: mvebu: convert to noirq suspend/resume to prevent interrupt storm on resume config: alpha-allmodconfig (https://download.01.org/0day-ci/archive/20260804/[email protected]/config) compiler: alpha-linux-gcc (GCC) 16.1.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260804/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All warnings (new ones prefixed by >>):
>> drivers/gpio/gpio-mvebu.c:1034:12: warning: 'mvebu_gpio_resume' defined but not used [-Wunused-function]
1034 | static int mvebu_gpio_resume(struct device *dev)
| ^~~~~~~~~~~~~~~~~
>> drivers/gpio/gpio-mvebu.c:982:12: warning: 'mvebu_gpio_suspend' defined but not used [-Wunused-function] 982 | static int mvebu_gpio_suspend(struct device *dev)
| ^~~~~~~~~~~~~~~~~~
vim +/mvebu_gpio_resume +1034 drivers/gpio/gpio-mvebu.c
981
> 982 static int mvebu_gpio_suspend(struct device *dev)
983 {
984 struct mvebu_gpio_chip *mvchip = dev_get_drvdata(dev);
985 int i;
986
987 regmap_read(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
988 &mvchip->out_reg);
989 regmap_read(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
990 &mvchip->io_conf_reg);
991 regmap_read(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset,
992 &mvchip->blink_en_reg);
993 regmap_read(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset,
994 &mvchip->in_pol_reg);
995
996 switch (mvchip->soc_variant) {
997 case MVEBU_GPIO_SOC_VARIANT_ORION:
998 case MVEBU_GPIO_SOC_VARIANT_A8K:
999 regmap_read(mvchip->regs, GPIO_EDGE_MASK_OFF + mvchip->offset,
1000 &mvchip->edge_mask_regs[0]);
1001 regmap_read(mvchip->regs, GPIO_LEVEL_MASK_OFF + mvchip->offset,
1002 &mvchip->level_mask_regs[0]);
1003 break;
1004 case MVEBU_GPIO_SOC_VARIANT_MV78200:
1005 for (i = 0; i < 2; i++) {
1006 regmap_read(mvchip->regs,
1007 GPIO_EDGE_MASK_MV78200_OFF(i),
1008 &mvchip->edge_mask_regs[i]);
1009 regmap_read(mvchip->regs,
1010 GPIO_LEVEL_MASK_MV78200_OFF(i),
1011 &mvchip->level_mask_regs[i]);
1012 }
1013 break;
1014 case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
1015 for (i = 0; i < 4; i++) {
1016 regmap_read(mvchip->regs,
1017 GPIO_EDGE_MASK_ARMADAXP_OFF(i),
1018 &mvchip->edge_mask_regs[i]);
1019 regmap_read(mvchip->regs,
1020 GPIO_LEVEL_MASK_ARMADAXP_OFF(i),
1021 &mvchip->level_mask_regs[i]);
1022 }
1023 break;
1024 default:
1025 BUG();
1026 }
1027
1028 if (IS_REACHABLE(CONFIG_PWM) && mvchip->mvpwm)
1029 mvebu_pwm_suspend(mvchip);
1030
1031 return 0;
1032 }
1033
> 1034 static int mvebu_gpio_resume(struct device *dev) 1035 {
1036 struct mvebu_gpio_chip *mvchip = dev_get_drvdata(dev);
1037 int i;
1038
1039 regmap_write(mvchip->regs, GPIO_OUT_OFF + mvchip->offset,
1040 mvchip->out_reg);
1041 regmap_write(mvchip->regs, GPIO_IO_CONF_OFF + mvchip->offset,
1042 mvchip->io_conf_reg);
1043 regmap_write(mvchip->regs, GPIO_BLINK_EN_OFF + mvchip->offset,
1044 mvchip->blink_en_reg);
1045 regmap_write(mvchip->regs, GPIO_IN_POL_OFF + mvchip->offset,
1046 mvchip->in_pol_reg);
1047
1048 switch (mvchip->soc_variant) {
1049 case MVEBU_GPIO_SOC_VARIANT_ORION:
1050 case MVEBU_GPIO_SOC_VARIANT_A8K:
1051 regmap_write(mvchip->regs, GPIO_EDGE_MASK_OFF + mvchip->offset,
1052 mvchip->edge_mask_regs[0]);
1053 regmap_write(mvchip->regs, GPIO_LEVEL_MASK_OFF + mvchip->offset,
1054 mvchip->level_mask_regs[0]);
1055 break;
1056 case MVEBU_GPIO_SOC_VARIANT_MV78200:
1057 for (i = 0; i < 2; i++) {
1058 regmap_write(mvchip->regs,
1059 GPIO_EDGE_MASK_MV78200_OFF(i),
1060 mvchip->edge_mask_regs[i]);
1061 regmap_write(mvchip->regs,
1062 GPIO_LEVEL_MASK_MV78200_OFF(i),
1063 mvchip->level_mask_regs[i]);
1064 }
1065 break;
1066 case MVEBU_GPIO_SOC_VARIANT_ARMADAXP:
1067 for (i = 0; i < 4; i++) {
1068 regmap_write(mvchip->regs,
1069 GPIO_EDGE_MASK_ARMADAXP_OFF(i),
1070 mvchip->edge_mask_regs[i]);
1071 regmap_write(mvchip->regs,
1072 GPIO_LEVEL_MASK_ARMADAXP_OFF(i),
1073 mvchip->level_mask_regs[i]);
1074 }
1075 break;
1076 default:
1077 BUG();
1078 }
1079
1080 if (IS_REACHABLE(CONFIG_PWM) && mvchip->mvpwm)
1081 mvebu_pwm_resume(mvchip);
1082
1083 return 0;
1084 }
1085
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki