diff mbox series

[v1,1/1] platform/x86: ideapad-laptop: Make the scope_guard() clear of its scope

Message ID 20240829165105.1609180-1-andriy.shevchenko@linux.intel.com (mailing list archive)
State Accepted, archived
Headers show
Series [v1,1/1] platform/x86: ideapad-laptop: Make the scope_guard() clear of its scope | expand

Commit Message

Andy Shevchenko Aug. 29, 2024, 4:50 p.m. UTC
First of all, it's a bit counterintuitive to have something like

	int err;
	...
	scoped_guard(...)
		err = foo(...);
	if (err)
		return err;

Second, with a particular kernel configuration and compiler version in
one of such cases the objtool is not happy:

  ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section

I'm not an expert on all this, but the theory is that compiler and
linker in this case can't understand that 'result' variable will be
always initialized as long as no error has been returned. Assigning
'result' to a dummy value helps with this. Note, that fixing the
scoped_guard() scope (as per above) does not make issue gone.

That said, assign dummy value and make the scope_guard() clear of its scope.
For the sake of consistency do it in the entire file.

Fixes: 7cc06e729460 ("platform/x86: ideapad-laptop: add a mutex to synchronize VPC commands")
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202408290219.BrPO8twi-lkp@intel.com/
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
---

This has been Cc'ed to objtool and clang maintainers to have a look and
tell if this can be addressed in a better way.

 drivers/platform/x86/ideapad-laptop.c | 48 +++++++++++++++------------
 1 file changed, 27 insertions(+), 21 deletions(-)

Comments

Gergo Koteles Sept. 3, 2024, 3 p.m. UTC | #1
Hi Andy,

Thank you for addressing this.

On Thu, 2024-08-29 at 19:50 +0300, Andy Shevchenko wrote:
> First of all, it's a bit counterintuitive to have something like
> 
> 	int err;
> 	...
> 	scoped_guard(...)
> 		err = foo(...);
> 	if (err)
> 		return err;
> 
> Second, with a particular kernel configuration and compiler version in
> one of such cases the objtool is not happy:
> 
>   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
> 
> I'm not an expert on all this, but the theory is that compiler and
> linker in this case can't understand that 'result' variable will be
> always initialized as long as no error has been returned. Assigning
> 'result' to a dummy value helps with this. Note, that fixing the
> scoped_guard() scope (as per above) does not make issue gone.
> 
> That said, assign dummy value and make the scope_guard() clear of its scope.
> For the sake of consistency do it in the entire file.
> 

Interestingly, if I open a scope manually and use the plain guard, the
warning disappears.

	...
	unsigned long result;
	int err;

	{
		guard(mutex)(&priv->vpc_mutex);
		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN,
&result);
		if (err)
			return err;
	}
	...

This looks a bit strange, but is probably easier for the compiler than
the for loop of scoped_guard.

But I don't know how well this style fits into the kernel.

Best regards,
Gergo Koteles
Andy Shevchenko Sept. 3, 2024, 3:14 p.m. UTC | #2
On Tue, Sep 03, 2024 at 05:00:51PM +0200, Gergo Koteles wrote:
> On Thu, 2024-08-29 at 19:50 +0300, Andy Shevchenko wrote:
> > First of all, it's a bit counterintuitive to have something like
> > 
> > 	int err;
> > 	...
> > 	scoped_guard(...)
> > 		err = foo(...);
> > 	if (err)
> > 		return err;
> > 
> > Second, with a particular kernel configuration and compiler version in
> > one of such cases the objtool is not happy:
> > 
> >   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
> > 
> > I'm not an expert on all this, but the theory is that compiler and
> > linker in this case can't understand that 'result' variable will be
> > always initialized as long as no error has been returned. Assigning
> > 'result' to a dummy value helps with this. Note, that fixing the
> > scoped_guard() scope (as per above) does not make issue gone.
> > 
> > That said, assign dummy value and make the scope_guard() clear of its scope.
> > For the sake of consistency do it in the entire file.
> > 
> 
> Interestingly, if I open a scope manually and use the plain guard, the
> warning disappears.

Yes, that's what I also have, but I avoid that approach because in that case
the printing will be done inside the lock, widening the critical section for
no benefits.

> 	...
> 	unsigned long result;
> 	int err;
> 
> 	{
> 		guard(mutex)(&priv->vpc_mutex);
> 		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN,
> &result);
> 		if (err)
> 			return err;
> 	}
> 	...
> 
> This looks a bit strange, but is probably easier for the compiler than
> the for loop of scoped_guard.
> 
> But I don't know how well this style fits into the kernel.
Gergo Koteles Sept. 3, 2024, 3:29 p.m. UTC | #3
On Tue, 2024-09-03 at 18:14 +0300, Andy Shevchenko wrote:
> On Tue, Sep 03, 2024 at 05:00:51PM +0200, Gergo Koteles wrote:
> > On Thu, 2024-08-29 at 19:50 +0300, Andy Shevchenko wrote:
> > > First of all, it's a bit counterintuitive to have something like
> > > 
> > > 	int err;
> > > 	...
> > > 	scoped_guard(...)
> > > 		err = foo(...);
> > > 	if (err)
> > > 		return err;
> > > 
> > > Second, with a particular kernel configuration and compiler version in
> > > one of such cases the objtool is not happy:
> > > 
> > >   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
> > > 
> > > I'm not an expert on all this, but the theory is that compiler and
> > > linker in this case can't understand that 'result' variable will be
> > > always initialized as long as no error has been returned. Assigning
> > > 'result' to a dummy value helps with this. Note, that fixing the
> > > scoped_guard() scope (as per above) does not make issue gone.
> > > 
> > > That said, assign dummy value and make the scope_guard() clear of its scope.
> > > For the sake of consistency do it in the entire file.
> > > 
> > 
> > Interestingly, if I open a scope manually and use the plain guard, the
> > warning disappears.
> 
> Yes, that's what I also have, but I avoid that approach because in that case
> the printing will be done inside the lock, widening the critical section for
> no benefits.
> 

This is intended to be an inner block scope within the function, it
does not expand the critical section.


> > 	...
> > 	unsigned long result;
> > 	int err;
> > 
> > 	{
> > 		guard(mutex)(&priv->vpc_mutex);
> > 		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN,
> > &result);
> > 		if (err)
> > 			return err;
> > 	}
> > 	...
> > 
> > This looks a bit strange, but is probably easier for the compiler than
> > the for loop of scoped_guard.
> > 
> > But I don't know how well this style fits into the kernel.
>
Andy Shevchenko Sept. 3, 2024, 3:40 p.m. UTC | #4
On Tue, Sep 03, 2024 at 05:29:02PM +0200, Gergo Koteles wrote:
> On Tue, 2024-09-03 at 18:14 +0300, Andy Shevchenko wrote:
> > On Tue, Sep 03, 2024 at 05:00:51PM +0200, Gergo Koteles wrote:
> > > On Thu, 2024-08-29 at 19:50 +0300, Andy Shevchenko wrote:
> > > > First of all, it's a bit counterintuitive to have something like
> > > > 
> > > > 	int err;
> > > > 	...
> > > > 	scoped_guard(...)
> > > > 		err = foo(...);
> > > > 	if (err)
> > > > 		return err;
> > > > 
> > > > Second, with a particular kernel configuration and compiler version in
> > > > one of such cases the objtool is not happy:
> > > > 
> > > >   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
> > > > 
> > > > I'm not an expert on all this, but the theory is that compiler and
> > > > linker in this case can't understand that 'result' variable will be
> > > > always initialized as long as no error has been returned. Assigning
> > > > 'result' to a dummy value helps with this. Note, that fixing the
> > > > scoped_guard() scope (as per above) does not make issue gone.
> > > > 
> > > > That said, assign dummy value and make the scope_guard() clear of its scope.
> > > > For the sake of consistency do it in the entire file.
> > > > 
> > > 
> > > Interestingly, if I open a scope manually and use the plain guard, the
> > > warning disappears.
> > 
> > Yes, that's what I also have, but I avoid that approach because in that case
> > the printing will be done inside the lock, widening the critical section for
> > no benefits.
> > 
> 
> This is intended to be an inner block scope within the function, it
> does not expand the critical section.

I'm not sure I understand.

scoped_guard() has a marked scope (with {} or just a line coupled with it).
The guard() has a scope starting at it till the end of the function. In the
latter case the sysfs_emit() becomes part of the critical section.

> > > 	unsigned long result;
> > > 	int err;
> > > 
> > > 	{
> > > 		guard(mutex)(&priv->vpc_mutex);
> > > 		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN,
> > > &result);
> > > 		if (err)
> > > 			return err;
> > > 	}

But looking again into the code above now I got what you meant.
You have added a nested scope inside the function, like

	do {
		...
	} while (0);

Yes, this is strange and not what we want to have either. So I prefer to hear
what objtool / clang people may comment on this.

Sorry that I missed this.

> > > This looks a bit strange, but is probably easier for the compiler than
> > > the for loop of scoped_guard.
> > > 
> > > But I don't know how well this style fits into the kernel.
Nathan Chancellor Sept. 4, 2024, 1:22 a.m. UTC | #5
Hi Andy,

On Tue, Sep 03, 2024 at 06:40:16PM +0300, Andy Shevchenko wrote:
> On Tue, Sep 03, 2024 at 05:29:02PM +0200, Gergo Koteles wrote:
> > On Tue, 2024-09-03 at 18:14 +0300, Andy Shevchenko wrote:
> > > On Tue, Sep 03, 2024 at 05:00:51PM +0200, Gergo Koteles wrote:
> > > > On Thu, 2024-08-29 at 19:50 +0300, Andy Shevchenko wrote:
> > > > > First of all, it's a bit counterintuitive to have something like
> > > > > 
> > > > > 	int err;
> > > > > 	...
> > > > > 	scoped_guard(...)
> > > > > 		err = foo(...);
> > > > > 	if (err)
> > > > > 		return err;
> > > > > 
> > > > > Second, with a particular kernel configuration and compiler version in
> > > > > one of such cases the objtool is not happy:
> > > > > 
> > > > >   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
> > > > > 
> > > > > I'm not an expert on all this, but the theory is that compiler and
> > > > > linker in this case can't understand that 'result' variable will be
> > > > > always initialized as long as no error has been returned. Assigning
> > > > > 'result' to a dummy value helps with this. Note, that fixing the
> > > > > scoped_guard() scope (as per above) does not make issue gone.
> > > > > 
> > > > > That said, assign dummy value and make the scope_guard() clear of its scope.
> > > > > For the sake of consistency do it in the entire file.
> > > > > 
> > > > 
> > > > Interestingly, if I open a scope manually and use the plain guard, the
> > > > warning disappears.
> > > 
> > > Yes, that's what I also have, but I avoid that approach because in that case
> > > the printing will be done inside the lock, widening the critical section for
> > > no benefits.
> > > 
> > 
> > This is intended to be an inner block scope within the function, it
> > does not expand the critical section.
> 
> I'm not sure I understand.
> 
> scoped_guard() has a marked scope (with {} or just a line coupled with it).
> The guard() has a scope starting at it till the end of the function. In the
> latter case the sysfs_emit() becomes part of the critical section.
> 
> > > > 	unsigned long result;
> > > > 	int err;
> > > > 
> > > > 	{
> > > > 		guard(mutex)(&priv->vpc_mutex);
> > > > 		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN,
> > > > &result);
> > > > 		if (err)
> > > > 			return err;
> > > > 	}
> 
> But looking again into the code above now I got what you meant.
> You have added a nested scope inside the function, like
> 
> 	do {
> 		...
> 	} while (0);
> 
> Yes, this is strange and not what we want to have either. So I prefer to hear
> what objtool / clang people may comment on this.

So this does not appear to happen when CONFIG_KCOV is disabled with the
configuration from the original report. I have spent some time looking
at the disassembly but I am a little out of my element there. If I
remember correctly, the "unexpected end of section" warning from objtool
can appear when optimizations play fast and loose with the presence of
potential undefined behavior (or cannot prove that there is no undefined
behavior through inlining or analysis). In this case, I wonder if KCOV
prevents LLVM from realizing that the for loop that scoped_guard()
results in will run at least once, meaning that err and result would be
potentially used uninitialized? That could explain why this change
resolves the warning, as it ensures that no undefined behavior could
happen regardless of whether or not the loop runs?

Josh and Peter may have more insight.

Cheers,
Nathan
Josh Poimboeuf Sept. 4, 2024, 4:52 a.m. UTC | #6
On Thu, Aug 29, 2024 at 07:50:32PM +0300, Andy Shevchenko wrote:
> First of all, it's a bit counterintuitive to have something like
> 
> 	int err;
> 	...
> 	scoped_guard(...)
> 		err = foo(...);
> 	if (err)
> 		return err;
> 
> Second, with a particular kernel configuration and compiler version in
> one of such cases the objtool is not happy:
> 
>   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
>
> I'm not an expert on all this, but the theory is that compiler and
> linker in this case can't understand that 'result' variable will be
> always initialized as long as no error has been returned. Assigning
> 'result' to a dummy value helps with this. Note, that fixing the
> scoped_guard() scope (as per above) does not make issue gone.

I'm not sure I buy that, we should look closer to understand what the
issue is.  Can you share the config and/or toolchain version(s) need to
trigger the warning?
Andy Shevchenko Sept. 4, 2024, 10:26 a.m. UTC | #7
On Tue, Sep 03, 2024 at 09:52:01PM -0700, Josh Poimboeuf wrote:
> On Thu, Aug 29, 2024 at 07:50:32PM +0300, Andy Shevchenko wrote:
> > First of all, it's a bit counterintuitive to have something like
> > 
> > 	int err;
> > 	...
> > 	scoped_guard(...)
> > 		err = foo(...);
> > 	if (err)
> > 		return err;
> > 
> > Second, with a particular kernel configuration and compiler version in
> > one of such cases the objtool is not happy:
> > 
> >   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
> >
> > I'm not an expert on all this, but the theory is that compiler and
> > linker in this case can't understand that 'result' variable will be
> > always initialized as long as no error has been returned. Assigning
> > 'result' to a dummy value helps with this. Note, that fixing the
> > scoped_guard() scope (as per above) does not make issue gone.
> 
> I'm not sure I buy that, we should look closer to understand what the
> issue is.  Can you share the config and/or toolchain version(s) need to
> trigger the warning?

.config is from the original report [1], toolchain is
Debian clang version 18.1.8 (9)
	Target: x86_64-pc-linux-gnu
	Thread model: posix
	InstalledDir: /usr/bin

(Just whatever Debian unstable provides)

[1]: https://lore.kernel.org/oe-kbuild-all/202408290219.BrPO8twi-lkp@intel.com/
Andy Shevchenko Sept. 4, 2024, 10:28 a.m. UTC | #8
On Tue, Sep 03, 2024 at 06:22:42PM -0700, Nathan Chancellor wrote:
> On Tue, Sep 03, 2024 at 06:40:16PM +0300, Andy Shevchenko wrote:
> > On Tue, Sep 03, 2024 at 05:29:02PM +0200, Gergo Koteles wrote:
> > > On Tue, 2024-09-03 at 18:14 +0300, Andy Shevchenko wrote:
> > > > On Tue, Sep 03, 2024 at 05:00:51PM +0200, Gergo Koteles wrote:
> > > > > On Thu, 2024-08-29 at 19:50 +0300, Andy Shevchenko wrote:
> > > > > > First of all, it's a bit counterintuitive to have something like
> > > > > > 
> > > > > > 	int err;
> > > > > > 	...
> > > > > > 	scoped_guard(...)
> > > > > > 		err = foo(...);
> > > > > > 	if (err)
> > > > > > 		return err;
> > > > > > 
> > > > > > Second, with a particular kernel configuration and compiler version in
> > > > > > one of such cases the objtool is not happy:
> > > > > > 
> > > > > >   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
> > > > > > 
> > > > > > I'm not an expert on all this, but the theory is that compiler and
> > > > > > linker in this case can't understand that 'result' variable will be
> > > > > > always initialized as long as no error has been returned. Assigning
> > > > > > 'result' to a dummy value helps with this. Note, that fixing the
> > > > > > scoped_guard() scope (as per above) does not make issue gone.
> > > > > > 
> > > > > > That said, assign dummy value and make the scope_guard() clear of its scope.
> > > > > > For the sake of consistency do it in the entire file.
> > > > > > 
> > > > > 
> > > > > Interestingly, if I open a scope manually and use the plain guard, the
> > > > > warning disappears.
> > > > 
> > > > Yes, that's what I also have, but I avoid that approach because in that case
> > > > the printing will be done inside the lock, widening the critical section for
> > > > no benefits.
> > > > 
> > > 
> > > This is intended to be an inner block scope within the function, it
> > > does not expand the critical section.
> > 
> > I'm not sure I understand.
> > 
> > scoped_guard() has a marked scope (with {} or just a line coupled with it).
> > The guard() has a scope starting at it till the end of the function. In the
> > latter case the sysfs_emit() becomes part of the critical section.
> > 
> > > > > 	unsigned long result;
> > > > > 	int err;
> > > > > 
> > > > > 	{
> > > > > 		guard(mutex)(&priv->vpc_mutex);
> > > > > 		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN,
> > > > > &result);
> > > > > 		if (err)
> > > > > 			return err;
> > > > > 	}
> > 
> > But looking again into the code above now I got what you meant.
> > You have added a nested scope inside the function, like
> > 
> > 	do {
> > 		...
> > 	} while (0);
> > 
> > Yes, this is strange and not what we want to have either. So I prefer to hear
> > what objtool / clang people may comment on this.
> 
> So this does not appear to happen when CONFIG_KCOV is disabled with the
> configuration from the original report. I have spent some time looking
> at the disassembly but I am a little out of my element there. If I
> remember correctly, the "unexpected end of section" warning from objtool
> can appear when optimizations play fast and loose with the presence of
> potential undefined behavior (or cannot prove that there is no undefined
> behavior through inlining or analysis). In this case, I wonder if KCOV
> prevents LLVM from realizing that the for loop that scoped_guard()
> results in will run at least once, meaning that err and result would be
> potentially used uninitialized? That could explain why this change
> resolves the warning, as it ensures that no undefined behavior could
> happen regardless of whether or not the loop runs?
> 
> Josh and Peter may have more insight.

Thanks for looking into this. Josh already keeps an eye on this.
Ilpo Järvinen Sept. 4, 2024, 1:37 p.m. UTC | #9
On Tue, 3 Sep 2024, Gergo Koteles wrote:

> On Tue, 2024-09-03 at 18:14 +0300, Andy Shevchenko wrote:
> > On Tue, Sep 03, 2024 at 05:00:51PM +0200, Gergo Koteles wrote:
> > > On Thu, 2024-08-29 at 19:50 +0300, Andy Shevchenko wrote:
> > > > First of all, it's a bit counterintuitive to have something like
> > > > 
> > > > 	int err;
> > > > 	...
> > > > 	scoped_guard(...)
> > > > 		err = foo(...);
> > > > 	if (err)
> > > > 		return err;
> > > > 
> > > > Second, with a particular kernel configuration and compiler version in
> > > > one of such cases the objtool is not happy:
> > > > 
> > > >   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
> > > > 
> > > > I'm not an expert on all this, but the theory is that compiler and
> > > > linker in this case can't understand that 'result' variable will be
> > > > always initialized as long as no error has been returned. Assigning
> > > > 'result' to a dummy value helps with this. Note, that fixing the
> > > > scoped_guard() scope (as per above) does not make issue gone.
> > > > 
> > > > That said, assign dummy value and make the scope_guard() clear of its scope.
> > > > For the sake of consistency do it in the entire file.
> > > > 
> > > 
> > > Interestingly, if I open a scope manually and use the plain guard, the
> > > warning disappears.
> > 
> > Yes, that's what I also have, but I avoid that approach because in that case
> > the printing will be done inside the lock, widening the critical section for
> > no benefits.
> > 
> 
> This is intended to be an inner block scope within the function, it
> does not expand the critical section.
> 
> 
> > > 	...
> > > 	unsigned long result;
> > > 	int err;
> > > 
> > > 	{
> > > 		guard(mutex)(&priv->vpc_mutex);
> > > 		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN,
> > > &result);
> > > 		if (err)
> > > 			return err;
> > > 	}
> > > 	...
> > > 
> > > This looks a bit strange, but is probably easier for the compiler than
> > > the for loop of scoped_guard.
> > > 
> > > But I don't know how well this style fits into the kernel.

It's ugly enough that I'd prefer the initialization of results variable as 
done in Andy's patch.
Hans de Goede Sept. 4, 2024, 6:14 p.m. UTC | #10
Hi,

On 8/29/24 6:50 PM, Andy Shevchenko wrote:
> First of all, it's a bit counterintuitive to have something like
> 
> 	int err;
> 	...
> 	scoped_guard(...)
> 		err = foo(...);
> 	if (err)
> 		return err;
> 
> Second, with a particular kernel configuration and compiler version in
> one of such cases the objtool is not happy:
> 
>   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
> 
> I'm not an expert on all this, but the theory is that compiler and
> linker in this case can't understand that 'result' variable will be
> always initialized as long as no error has been returned. Assigning
> 'result' to a dummy value helps with this. Note, that fixing the
> scoped_guard() scope (as per above) does not make issue gone.
> 
> That said, assign dummy value and make the scope_guard() clear of its scope.
> For the sake of consistency do it in the entire file.
> 
> Fixes: 7cc06e729460 ("platform/x86: ideapad-laptop: add a mutex to synchronize VPC commands")
> Reported-by: kernel test robot <lkp@intel.com>
> Closes: https://lore.kernel.org/oe-kbuild-all/202408290219.BrPO8twi-lkp@intel.com/
> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>

Thank you for your patch, I've applied this patch to my review-hans 
branch:
https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git/log/?h=review-hans

Note it will show up in my review-hans branch once I've pushed my
local branch there, which might take a while.

Once I've run some tests on this branch the patches there will be
added to the platform-drivers-x86/for-next branch and eventually
will be included in the pdx86 pull-request to Linus for the next
merge-window.

Regards,

Hans




> ---
> 
> This has been Cc'ed to objtool and clang maintainers to have a look and
> tell if this can be addressed in a better way.
> 
>  drivers/platform/x86/ideapad-laptop.c | 48 +++++++++++++++------------
>  1 file changed, 27 insertions(+), 21 deletions(-)
> 
> diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
> index 35c75bcff195..c64dfc56651d 100644
> --- a/drivers/platform/x86/ideapad-laptop.c
> +++ b/drivers/platform/x86/ideapad-laptop.c
> @@ -554,13 +554,14 @@ static ssize_t camera_power_show(struct device *dev,
>  				 char *buf)
>  {
>  	struct ideapad_private *priv = dev_get_drvdata(dev);
> -	unsigned long result;
> +	unsigned long result = 0;
>  	int err;
>  
> -	scoped_guard(mutex, &priv->vpc_mutex)
> +	scoped_guard(mutex, &priv->vpc_mutex) {
>  		err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result);
> -	if (err)
> -		return err;
> +		if (err)
> +			return err;
> +	}
>  
>  	return sysfs_emit(buf, "%d\n", !!result);
>  }
> @@ -577,10 +578,11 @@ static ssize_t camera_power_store(struct device *dev,
>  	if (err)
>  		return err;
>  
> -	scoped_guard(mutex, &priv->vpc_mutex)
> +	scoped_guard(mutex, &priv->vpc_mutex) {
>  		err = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state);
> -	if (err)
> -		return err;
> +		if (err)
> +			return err;
> +	}
>  
>  	return count;
>  }
> @@ -628,13 +630,14 @@ static ssize_t fan_mode_show(struct device *dev,
>  			     char *buf)
>  {
>  	struct ideapad_private *priv = dev_get_drvdata(dev);
> -	unsigned long result;
> +	unsigned long result = 0;
>  	int err;
>  
> -	scoped_guard(mutex, &priv->vpc_mutex)
> +	scoped_guard(mutex, &priv->vpc_mutex) {
>  		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result);
> -	if (err)
> -		return err;
> +		if (err)
> +			return err;
> +	}
>  
>  	return sysfs_emit(buf, "%lu\n", result);
>  }
> @@ -654,10 +657,11 @@ static ssize_t fan_mode_store(struct device *dev,
>  	if (state > 4 || state == 3)
>  		return -EINVAL;
>  
> -	scoped_guard(mutex, &priv->vpc_mutex)
> +	scoped_guard(mutex, &priv->vpc_mutex) {
>  		err = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state);
> -	if (err)
> -		return err;
> +		if (err)
> +			return err;
> +	}
>  
>  	return count;
>  }
> @@ -737,13 +741,14 @@ static ssize_t touchpad_show(struct device *dev,
>  			     char *buf)
>  {
>  	struct ideapad_private *priv = dev_get_drvdata(dev);
> -	unsigned long result;
> +	unsigned long result = 0;
>  	int err;
>  
> -	scoped_guard(mutex, &priv->vpc_mutex)
> +	scoped_guard(mutex, &priv->vpc_mutex) {
>  		err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result);
> -	if (err)
> -		return err;
> +		if (err)
> +			return err;
> +	}
>  
>  	priv->r_touchpad_val = result;
>  
> @@ -762,10 +767,11 @@ static ssize_t touchpad_store(struct device *dev,
>  	if (err)
>  		return err;
>  
> -	scoped_guard(mutex, &priv->vpc_mutex)
> +	scoped_guard(mutex, &priv->vpc_mutex) {
>  		err = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state);
> -	if (err)
> -		return err;
> +		if (err)
> +			return err;
> +	}
>  
>  	priv->r_touchpad_val = state;
>
Andy Shevchenko Sept. 4, 2024, 8:18 p.m. UTC | #11
Wed, Sep 04, 2024 at 08:14:53PM +0200, Hans de Goede kirjoitti:
> Hi,
> 
> On 8/29/24 6:50 PM, Andy Shevchenko wrote:
> > First of all, it's a bit counterintuitive to have something like
> > 
> > 	int err;
> > 	...
> > 	scoped_guard(...)
> > 		err = foo(...);
> > 	if (err)
> > 		return err;
> > 
> > Second, with a particular kernel configuration and compiler version in
> > one of such cases the objtool is not happy:
> > 
> >   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
> > 
> > I'm not an expert on all this, but the theory is that compiler and
> > linker in this case can't understand that 'result' variable will be
> > always initialized as long as no error has been returned. Assigning
> > 'result' to a dummy value helps with this. Note, that fixing the
> > scoped_guard() scope (as per above) does not make issue gone.
> > 
> > That said, assign dummy value and make the scope_guard() clear of its scope.
> > For the sake of consistency do it in the entire file.
> > 
> > Fixes: 7cc06e729460 ("platform/x86: ideapad-laptop: add a mutex to synchronize VPC commands")
> > Reported-by: kernel test robot <lkp@intel.com>
> > Closes: https://lore.kernel.org/oe-kbuild-all/202408290219.BrPO8twi-lkp@intel.com/
> > Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>

> Thank you for your patch, I've applied this patch to my review-hans 
> branch:
> https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git/log/?h=review-hans

Have you had a chance to go through the discussion?

TL;DR: please defer this. There is still no clear understanding of the root
cause and the culprit.
Hans de Goede Sept. 5, 2024, 8:33 a.m. UTC | #12
Hi Andy,

On 9/4/24 10:18 PM, Andy Shevchenko wrote:
> Wed, Sep 04, 2024 at 08:14:53PM +0200, Hans de Goede kirjoitti:
>> Hi,
>>
>> On 8/29/24 6:50 PM, Andy Shevchenko wrote:
>>> First of all, it's a bit counterintuitive to have something like
>>>
>>> 	int err;
>>> 	...
>>> 	scoped_guard(...)
>>> 		err = foo(...);
>>> 	if (err)
>>> 		return err;
>>>
>>> Second, with a particular kernel configuration and compiler version in
>>> one of such cases the objtool is not happy:
>>>
>>>   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
>>>
>>> I'm not an expert on all this, but the theory is that compiler and
>>> linker in this case can't understand that 'result' variable will be
>>> always initialized as long as no error has been returned. Assigning
>>> 'result' to a dummy value helps with this. Note, that fixing the
>>> scoped_guard() scope (as per above) does not make issue gone.
>>>
>>> That said, assign dummy value and make the scope_guard() clear of its scope.
>>> For the sake of consistency do it in the entire file.
>>>
>>> Fixes: 7cc06e729460 ("platform/x86: ideapad-laptop: add a mutex to synchronize VPC commands")
>>> Reported-by: kernel test robot <lkp@intel.com>
>>> Closes: https://lore.kernel.org/oe-kbuild-all/202408290219.BrPO8twi-lkp@intel.com/
>>> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> 
>> Thank you for your patch, I've applied this patch to my review-hans 
>> branch:
>> https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git/log/?h=review-hans
> 
> Have you had a chance to go through the discussion?

Yes I did read the entire discussion.

> TL;DR: please defer this. There is still no clear understanding of the root
> cause and the culprit.

My gist from the discussion was that this was good to have regardless of
the root cause.

IMHO the old construction where the scoped-guard only guards the function-call
and not the "if (ret)" on the return value of the guarded call was quite ugly /
convoluted / hard to read and this patch is an improvement regardless.

Regards,

Hans
Andy Shevchenko Sept. 5, 2024, 8:36 a.m. UTC | #13
On Thu, Sep 05, 2024 at 10:33:22AM +0200, Hans de Goede wrote:
> On 9/4/24 10:18 PM, Andy Shevchenko wrote:
> > Wed, Sep 04, 2024 at 08:14:53PM +0200, Hans de Goede kirjoitti:
> >> On 8/29/24 6:50 PM, Andy Shevchenko wrote:

> >>> First of all, it's a bit counterintuitive to have something like
> >>>
> >>> 	int err;
> >>> 	...
> >>> 	scoped_guard(...)
> >>> 		err = foo(...);
> >>> 	if (err)
> >>> 		return err;
> >>>
> >>> Second, with a particular kernel configuration and compiler version in
> >>> one of such cases the objtool is not happy:
> >>>
> >>>   ideapad-laptop.o: warning: objtool: .text.fan_mode_show: unexpected end of section
> >>>
> >>> I'm not an expert on all this, but the theory is that compiler and
> >>> linker in this case can't understand that 'result' variable will be
> >>> always initialized as long as no error has been returned. Assigning
> >>> 'result' to a dummy value helps with this. Note, that fixing the
> >>> scoped_guard() scope (as per above) does not make issue gone.
> >>>
> >>> That said, assign dummy value and make the scope_guard() clear of its scope.
> >>> For the sake of consistency do it in the entire file.
> >>>
> >>> Fixes: 7cc06e729460 ("platform/x86: ideapad-laptop: add a mutex to synchronize VPC commands")
> >>> Reported-by: kernel test robot <lkp@intel.com>
> >>> Closes: https://lore.kernel.org/oe-kbuild-all/202408290219.BrPO8twi-lkp@intel.com/
> >>> Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> > 
> >> Thank you for your patch, I've applied this patch to my review-hans 
> >> branch:
> >> https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git/log/?h=review-hans
> > 
> > Have you had a chance to go through the discussion?
> 
> Yes I did read the entire discussion.
> 
> > TL;DR: please defer this. There is still no clear understanding of the root
> > cause and the culprit.
> 
> My gist from the discussion was that this was good to have regardless of
> the root cause.
> 
> IMHO the old construction where the scoped-guard only guards the function-call
> and not the "if (ret)" on the return value of the guarded call was quite ugly /
> convoluted / hard to read and this patch is an improvement regardless.

Okay, if you think it's good to go, you are welcome!

Thanks!
Josh Poimboeuf Sept. 6, 2024, 3:16 a.m. UTC | #14
On Wed, Sep 04, 2024 at 01:26:11PM +0300, Andy Shevchenko wrote:
> On Tue, Sep 03, 2024 at 09:52:01PM -0700, Josh Poimboeuf wrote:
> > I'm not sure I buy that, we should look closer to understand what the
> > issue is.  Can you share the config and/or toolchain version(s) need to
> > trigger the warning?
> 
> .config is from the original report [1], toolchain is
> Debian clang version 18.1.8 (9)
> 	Target: x86_64-pc-linux-gnu
> 	Thread model: posix
> 	InstalledDir: /usr/bin
> 
> (Just whatever Debian unstable provides)
> 
> [1]: https://lore.kernel.org/oe-kbuild-all/202408290219.BrPO8twi-lkp@intel.com/

The warning is due to a (minor?) Clang bug, almost like it tried to
optimize but didn't quite finish.

Here's the disassembly:

0000000000000010 <fan_mode_show>:
  10:	e8 00 00 00 00       	call   15 <fan_mode_show+0x5>	11: R_X86_64_PLT32	__fentry__-0x4
  15:	55                   	push   %rbp
  16:	48 89 e5             	mov    %rsp,%rbp
  19:	41 57                	push   %r15
  1b:	41 56                	push   %r14
  1d:	53                   	push   %rbx
  1e:	50                   	push   %rax
  1f:	48 89 d3             	mov    %rdx,%rbx
  22:	49 89 fe             	mov    %rdi,%r14
  25:	e8 00 00 00 00       	call   2a <fan_mode_show+0x1a>	26: R_X86_64_PLT32	__sanitizer_cov_trace_pc-0x4
  2a:	4d 8b 76 78          	mov    0x78(%r14),%r14
  2e:	31 f6                	xor    %esi,%esi
  30:	49 8d 7e 08          	lea    0x8(%r14),%rdi
  34:	e8 00 00 00 00       	call   39 <fan_mode_show+0x29>	35: R_X86_64_PLT32	mutex_lock_nested-0x4
  39:	4d 89 f7             	mov    %r14,%r15
  3c:	49 83 c7 08          	add    $0x8,%r15
  40:	74 5b                	je     9d <fan_mode_show+0x8d>
  42:	49 8b 06             	mov    (%r14),%rax
  45:	48 8d 55 e0          	lea    -0x20(%rbp),%rdx
  49:	be 2b 00 00 00       	mov    $0x2b,%esi
  4e:	48 8b 78 08          	mov    0x8(%rax),%rdi
  52:	e8 00 00 00 00       	call   57 <fan_mode_show+0x47>	53: R_X86_64_PLT32	.text.read_ec_data-0x4
  57:	4c 89 ff             	mov    %r15,%rdi
  5a:	41 89 c6             	mov    %eax,%r14d
  5d:	e8 00 00 00 00       	call   62 <fan_mode_show+0x52>	5e: R_X86_64_PLT32	mutex_unlock-0x4
  62:	45 85 f6             	test   %r14d,%r14d
  65:	74 07                	je     6e <fan_mode_show+0x5e>
  67:	e8 00 00 00 00       	call   6c <fan_mode_show+0x5c>	68: R_X86_64_PLT32	__sanitizer_cov_trace_pc-0x4
  6c:	eb 1b                	jmp    89 <fan_mode_show+0x79>
  6e:	e8 00 00 00 00       	call   73 <fan_mode_show+0x63>	6f: R_X86_64_PLT32	__sanitizer_cov_trace_pc-0x4
  73:	48 8b 55 e0          	mov    -0x20(%rbp),%rdx
  77:	48 89 df             	mov    %rbx,%rdi
  7a:	48 c7 c6 00 00 00 00 	mov    $0x0,%rsi	7d: R_X86_64_32S	.rodata.str1.1+0x508
  81:	e8 00 00 00 00       	call   86 <fan_mode_show+0x76>	82: R_X86_64_PLT32	sysfs_emit-0x4
  86:	41 89 c6             	mov    %eax,%r14d
  89:	49 63 c6             	movslq %r14d,%rax
  8c:	48 83 c4 08          	add    $0x8,%rsp
  90:	5b                   	pop    %rbx
  91:	41 5e                	pop    %r14
  93:	41 5f                	pop    %r15
  95:	5d                   	pop    %rbp
  96:	31 ff                	xor    %edi,%edi
  98:	31 d2                	xor    %edx,%edx
  9a:	31 f6                	xor    %esi,%esi
  9c:	c3                   	ret
  9d:	e8 00 00 00 00       	call   a2 <__param_ctrl_ps2_aux_port+0x2>	9e: R_X86_64_PLT32	__sanitizer_cov_trace_pc-0x4
  <end of function>


And the interesting bit:

  30:	49 8d 7e 08          	lea    0x8(%r14),%rdi		# rdi = &priv->vpc_mutex
  34:	e8 00 00 00 00       	call   mutex_lock_nested
  39:	4d 89 f7             	mov    %r14,%r15		# r15 = r14 = priv
  3c:	49 83 c7 08          	add    $0x8,%r15		# r15 = &priv->vpc_mutex
  40:	74 5b                	je     9d <fan_mode_show+0x8d>	# if &priv->vpc_mutex == NULL, goto 9d
  ...
  9d:	e8 00 00 00 00       	call   a2 <__param_ctrl_ps2_aux_port+0x2>	9e: R_X86_64_PLT32	__sanitizer_cov_trace_pc-0x4
  <oof>


If '&priv->vpc_mutex' is NULL, it jumps to 9d, where it calls
__sanitizer_cov_trace_pc().  After that returns, it runs off the rails.

Apparently Clang decided somehow (LTO?) that '&priv->vpc_mutex' can
never be NULL, but it didn't quite finish the optimization.  Maybe some
bad interaction between LTO and KCOV?

Here's the triggering code:

> static ssize_t fan_mode_show(struct device *dev,
> 			     struct device_attribute *attr,
> 			     char *buf)
> {
> 	struct ideapad_private *priv = dev_get_drvdata(dev);
> 	unsigned long result;
> 	int err;
> 
> 	scoped_guard(mutex, &priv->vpc_mutex)
> 		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result);

Here's the pre-processed code for the scoped_guard() invocation and its
DEFINE_GUARD() dependency, edited for readability:

> typedef struct mutex * class_mutex_t;
> 
> static inline void class_mutex_destructor(struct mutex **p)
> {
> 	struct mutex *_T = *p;
> 	if (_T)
> 		mutex_unlock(_T);
> }
> 
> static inline struct mutex *class_mutex_constructor(struct mutex *_T)
> {
> 	struct mutex *t = ({ mutex_lock_nested(_T, 0); _T; });
> 	return t;
> }
> 
> static inline void *class_mutex_lock_ptr(struct mutex **_T)
> {
> 	return *_T;
> }
> 
> 	for (struct mutex *scope = class_mutex_constructor(&priv->vpc_mutex), *done = ((void *)0);
> 	     class_mutex_lock_ptr(&scope) && !done;
> 	     done = (void *)1) {
> 
> 		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result);
> 	}

The fake "for loop" is needed to be able to initialize the scope
variable inline.  But basically it's doing this:

> 	if (class_mutex_constructor(&priv->vpc_mutex))
> 		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result);

In this case, mutex is an unconditional guard, so the constructor just
returns the original value of '&priv->vpc_mutex'.  So if the original
'&priv->vpc_mutex' is never NULL, the condition would always be true.
Nathan Chancellor Sept. 13, 2024, 11:33 p.m. UTC | #15
On Thu, Sep 05, 2024 at 08:16:01PM -0700, Josh Poimboeuf wrote:
> On Wed, Sep 04, 2024 at 01:26:11PM +0300, Andy Shevchenko wrote:
> > On Tue, Sep 03, 2024 at 09:52:01PM -0700, Josh Poimboeuf wrote:
> > > I'm not sure I buy that, we should look closer to understand what the
> > > issue is.  Can you share the config and/or toolchain version(s) need to
> > > trigger the warning?
> > 
> > .config is from the original report [1], toolchain is
> > Debian clang version 18.1.8 (9)
> > 	Target: x86_64-pc-linux-gnu
> > 	Thread model: posix
> > 	InstalledDir: /usr/bin
> > 
> > (Just whatever Debian unstable provides)
> > 
> > [1]: https://lore.kernel.org/oe-kbuild-all/202408290219.BrPO8twi-lkp@intel.com/
> 
> The warning is due to a (minor?) Clang bug, almost like it tried to
> optimize but didn't quite finish.
...
> In this case, mutex is an unconditional guard, so the constructor just
> returns the original value of '&priv->vpc_mutex'.  So if the original
> '&priv->vpc_mutex' is never NULL, the condition would always be true.

Thanks a lot for that Josh. I have a somewhat trivial reproducer for the
clang folks to take a look at. I should have some time on Monday to get
that reported upstream and I will see if I can find anyone to take a
look at it.

For what it is worth, I don't think the workaround for this is too bad
and it seems like it only shows up with KCOV.

Cheers,
Nathan
diff mbox series

Patch

diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c
index 35c75bcff195..c64dfc56651d 100644
--- a/drivers/platform/x86/ideapad-laptop.c
+++ b/drivers/platform/x86/ideapad-laptop.c
@@ -554,13 +554,14 @@  static ssize_t camera_power_show(struct device *dev,
 				 char *buf)
 {
 	struct ideapad_private *priv = dev_get_drvdata(dev);
-	unsigned long result;
+	unsigned long result = 0;
 	int err;
 
-	scoped_guard(mutex, &priv->vpc_mutex)
+	scoped_guard(mutex, &priv->vpc_mutex) {
 		err = read_ec_data(priv->adev->handle, VPCCMD_R_CAMERA, &result);
-	if (err)
-		return err;
+		if (err)
+			return err;
+	}
 
 	return sysfs_emit(buf, "%d\n", !!result);
 }
@@ -577,10 +578,11 @@  static ssize_t camera_power_store(struct device *dev,
 	if (err)
 		return err;
 
-	scoped_guard(mutex, &priv->vpc_mutex)
+	scoped_guard(mutex, &priv->vpc_mutex) {
 		err = write_ec_cmd(priv->adev->handle, VPCCMD_W_CAMERA, state);
-	if (err)
-		return err;
+		if (err)
+			return err;
+	}
 
 	return count;
 }
@@ -628,13 +630,14 @@  static ssize_t fan_mode_show(struct device *dev,
 			     char *buf)
 {
 	struct ideapad_private *priv = dev_get_drvdata(dev);
-	unsigned long result;
+	unsigned long result = 0;
 	int err;
 
-	scoped_guard(mutex, &priv->vpc_mutex)
+	scoped_guard(mutex, &priv->vpc_mutex) {
 		err = read_ec_data(priv->adev->handle, VPCCMD_R_FAN, &result);
-	if (err)
-		return err;
+		if (err)
+			return err;
+	}
 
 	return sysfs_emit(buf, "%lu\n", result);
 }
@@ -654,10 +657,11 @@  static ssize_t fan_mode_store(struct device *dev,
 	if (state > 4 || state == 3)
 		return -EINVAL;
 
-	scoped_guard(mutex, &priv->vpc_mutex)
+	scoped_guard(mutex, &priv->vpc_mutex) {
 		err = write_ec_cmd(priv->adev->handle, VPCCMD_W_FAN, state);
-	if (err)
-		return err;
+		if (err)
+			return err;
+	}
 
 	return count;
 }
@@ -737,13 +741,14 @@  static ssize_t touchpad_show(struct device *dev,
 			     char *buf)
 {
 	struct ideapad_private *priv = dev_get_drvdata(dev);
-	unsigned long result;
+	unsigned long result = 0;
 	int err;
 
-	scoped_guard(mutex, &priv->vpc_mutex)
+	scoped_guard(mutex, &priv->vpc_mutex) {
 		err = read_ec_data(priv->adev->handle, VPCCMD_R_TOUCHPAD, &result);
-	if (err)
-		return err;
+		if (err)
+			return err;
+	}
 
 	priv->r_touchpad_val = result;
 
@@ -762,10 +767,11 @@  static ssize_t touchpad_store(struct device *dev,
 	if (err)
 		return err;
 
-	scoped_guard(mutex, &priv->vpc_mutex)
+	scoped_guard(mutex, &priv->vpc_mutex) {
 		err = write_ec_cmd(priv->adev->handle, VPCCMD_W_TOUCHPAD, state);
-	if (err)
-		return err;
+		if (err)
+			return err;
+	}
 
 	priv->r_touchpad_val = state;