diff mbox series

applesmc: Re-work SMC comms v2

Message ID 3c72ccc3-4de1-b5d0-423d-7b8c80991254@fnarfbargle.com (mailing list archive)
State Changes Requested
Headers show
Series applesmc: Re-work SMC comms v2 | expand

Commit Message

Brad Campbell Nov. 5, 2020, 7:26 a.m. UTC
Commit fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()") introduced
an issue whereby communication with the SMC became unreliable with write
errors like :

[  120.378614] applesmc: send_byte(0x00, 0x0300) fail: 0x40
[  120.378621] applesmc: LKSB: write data fail
[  120.512782] applesmc: send_byte(0x00, 0x0300) fail: 0x40
[  120.512787] applesmc: LKSB: write data fail

The original code appeared to be timing sensitive and was not reliable with
the timing changes in the aforementioned commit.

This patch re-factors the SMC communication to remove the timing 
dependencies and restore function with the changes previously committed.

v2 : Address logic and coding style

Reported-by: Andreas Kemnade <andreas@kemnade.info>
Fixes: fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()")
Signed-off-by: Brad Campbell <brad@fnarfbargle.com>

---

Comments

Henrik Rydberg Nov. 5, 2020, 7:56 a.m. UTC | #1
Hi Brad,

Great to see this effort, it is certainly an area which could be 
improved. After having seen several generations of Macbooks while 
modifying much of that code, it became clear that the SMC communication 
got refreshed a few times over the years. Every tiny change had to be 
tested on all machines, or kept separate for a particular generation, or 
something would break.

I have not followed the back story here, but I imagine the need has 
arisen because of a new refresh, and so this patch only needs to 
strictly apply to a new generation. I would therefore advice that you 
write the patch in that way, reducing the actual change to zero for 
earlier generations. It also makes it easier to test the effect of the 
new approach on older systems. I should be able to help testing on a 
2008 and 2011 model once we get to that stage.

Thanks,
Henrik

On 2020-11-05 08:26, Brad Campbell wrote:
> Commit fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()") introduced
> an issue whereby communication with the SMC became unreliable with write
> errors like :
> 
> [  120.378614] applesmc: send_byte(0x00, 0x0300) fail: 0x40
> [  120.378621] applesmc: LKSB: write data fail
> [  120.512782] applesmc: send_byte(0x00, 0x0300) fail: 0x40
> [  120.512787] applesmc: LKSB: write data fail
> 
> The original code appeared to be timing sensitive and was not reliable with
> the timing changes in the aforementioned commit.
> 
> This patch re-factors the SMC communication to remove the timing
> dependencies and restore function with the changes previously committed.
> 
> v2 : Address logic and coding style
> 
> Reported-by: Andreas Kemnade <andreas@kemnade.info>
> Fixes: fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()")
> Signed-off-by: Brad Campbell <brad@fnarfbargle.com>
> 
> ---
> diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c
> index a18887990f4a..de890f3ec12f 100644
> --- a/drivers/hwmon/applesmc.c
> +++ b/drivers/hwmon/applesmc.c
> @@ -42,6 +42,11 @@
>   
>   #define APPLESMC_MAX_DATA_LENGTH 32
>   
> +/* Apple SMC status bits */
> +#define SMC_STATUS_AWAITING_DATA  BIT(0) /* SMC has data waiting */
> +#define SMC_STATUS_IB_CLOSED      BIT(1) /* Will ignore any input */
> +#define SMC_STATUS_BUSY           BIT(2) /* Command in progress */
> +
>   /* wait up to 128 ms for a status change. */
>   #define APPLESMC_MIN_WAIT	0x0010
>   #define APPLESMC_RETRY_WAIT	0x0100
> @@ -151,65 +156,69 @@ static unsigned int key_at_index;
>   static struct workqueue_struct *applesmc_led_wq;
>   
>   /*
> - * wait_read - Wait for a byte to appear on SMC port. Callers must
> - * hold applesmc_lock.
> + * Wait for specific status bits with a mask on the SMC
> + * Used before and after writes, and before reads
>    */
> -static int wait_read(void)
> +
> +static int wait_status(u8 val, u8 mask)
>   {
>   	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
>   	u8 status;
>   	int us;
>   
>   	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
> -		usleep_range(us, us * 16);
>   		status = inb(APPLESMC_CMD_PORT);
> -		/* read: wait for smc to settle */
> -		if (status & 0x01)
> +		if ((status & mask) == val)
>   			return 0;
>   		/* timeout: give up */
>   		if (time_after(jiffies, end))
>   			break;
> -	}
> -
> -	pr_warn("wait_read() fail: 0x%02x\n", status);
> +		usleep_range(us, us * 16);
> +		}
>   	return -EIO;
>   }
>   
>   /*
> - * send_byte - Write to SMC port, retrying when necessary. Callers
> + * send_byte_data - Write to SMC data port. Callers
>    * must hold applesmc_lock.
> + * Parameter skip must be true on the last write of any
> + * command or it'll time out.
>    */
> -static int send_byte(u8 cmd, u16 port)
> +
> +static int send_byte_data(u8 cmd, u16 port, bool skip)
>   {
> -	u8 status;
> -	int us;
> -	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
> +	int ret;
>   
> +	ret = wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY | SMC_STATUS_IB_CLOSED);
> +	if (ret)
> +		return ret;
>   	outb(cmd, port);
> -	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
> -		usleep_range(us, us * 16);
> -		status = inb(APPLESMC_CMD_PORT);
> -		/* write: wait for smc to settle */
> -		if (status & 0x02)
> -			continue;
> -		/* ready: cmd accepted, return */
> -		if (status & 0x04)
> -			return 0;
> -		/* timeout: give up */
> -		if (time_after(jiffies, end))
> -			break;
> -		/* busy: long wait and resend */
> -		udelay(APPLESMC_RETRY_WAIT);
> -		outb(cmd, port);
> -	}
> +	return wait_status(skip ? 0 : SMC_STATUS_BUSY, SMC_STATUS_BUSY);
> +}
>   
> -	pr_warn("send_byte(0x%02x, 0x%04x) fail: 0x%02x\n", cmd, port, status);
> -	return -EIO;
> +static int send_byte(u8 cmd, u16 port)
> +{
> +	return send_byte_data(cmd, port, false);
>   }
>   
> +/*
> + * send_command - Write a command to the SMC. Callers must hold applesmc_lock.
> + * If SMC is in undefined state, any new command write resets the state machine.
> + */
> +
>   static int send_command(u8 cmd)
>   {
> -	return send_byte(cmd, APPLESMC_CMD_PORT);
> +	u8 status;
> +	int ret;
> +
> +	ret = wait_status(0, SMC_STATUS_IB_CLOSED);
> +	if (ret)
> +		return ret;
> +
> +	status = inb(APPLESMC_CMD_PORT);
> +
> +	outb(cmd, APPLESMC_CMD_PORT);
> +	return wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY);
>   }
>   
>   static int send_argument(const char *key)
> @@ -239,7 +248,9 @@ static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
>   	}
>   
>   	for (i = 0; i < len; i++) {
> -		if (wait_read()) {
> +		if (wait_status(SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY,
> +				SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY |
> +				SMC_STATUS_IB_CLOSED)) {
>   			pr_warn("%.4s: read data[%d] fail\n", key, i);
>   			return -EIO;
>   		}
> @@ -250,7 +261,7 @@ static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
>   	for (i = 0; i < 16; i++) {
>   		udelay(APPLESMC_MIN_WAIT);
>   		status = inb(APPLESMC_CMD_PORT);
> -		if (!(status & 0x01))
> +		if (!(status & SMC_STATUS_AWAITING_DATA))
>   			break;
>   		data = inb(APPLESMC_DATA_PORT);
>   	}
> @@ -275,7 +286,7 @@ static int write_smc(u8 cmd, const char *key, const u8 *buffer, u8 len)
>   	}
>   
>   	for (i = 0; i < len; i++) {
> -		if (send_byte(buffer[i], APPLESMC_DATA_PORT)) {
> +		if (send_byte_data(buffer[i], APPLESMC_DATA_PORT, i == len - 1)) {
>   			pr_warn("%s: write data fail\n", key);
>   			return -EIO;
>   		}
>
Andreas Kemnade Nov. 5, 2020, 8:12 a.m. UTC | #2
On Thu, 5 Nov 2020 18:26:24 +1100
Brad Campbell <brad@fnarfbargle.com> wrote:

> Commit fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()") introduced
> an issue whereby communication with the SMC became unreliable with write
> errors like :
> 
> [  120.378614] applesmc: send_byte(0x00, 0x0300) fail: 0x40
> [  120.378621] applesmc: LKSB: write data fail
> [  120.512782] applesmc: send_byte(0x00, 0x0300) fail: 0x40
> [  120.512787] applesmc: LKSB: write data fail
> 
> The original code appeared to be timing sensitive and was not reliable with
> the timing changes in the aforementioned commit.
> 
> This patch re-factors the SMC communication to remove the timing 
> dependencies and restore function with the changes previously committed.
> 
> v2 : Address logic and coding style
> 
> Reported-by: Andreas Kemnade <andreas@kemnade.info>
> Fixes: fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()")
> Signed-off-by: Brad Campbell <brad@fnarfbargle.com>
> 
Still works here:
Tested-by: Andreas Kemnade <andreas@kemnade.info> # MacBookAir6,2
Andreas Kemnade Nov. 5, 2020, 8:15 a.m. UTC | #3
On Thu, 5 Nov 2020 08:56:04 +0100
Henrik Rydberg <rydberg@bitmath.org> wrote:

> Hi Brad,
> 
> Great to see this effort, it is certainly an area which could be 
> improved. After having seen several generations of Macbooks while 
> modifying much of that code, it became clear that the SMC communication 
> got refreshed a few times over the years. Every tiny change had to be 
> tested on all machines, or kept separate for a particular generation, or 
> something would break.
> 
> I have not followed the back story here, but I imagine the need has 
> arisen because of a new refresh, and so this patch only needs to 
> strictly apply to a new generation. I would therefore advice that you 
> write the patch in that way, reducing the actual change to zero for 
> earlier generations. It also makes it easier to test the effect of the 
> new approach on older systems. I should be able to help testing on a 
> 2008 and 2011 model once we get to that stage.
> 
Well, the issue has arisen because of a change in kernel to make clang
happy. So it is not a new Apple device causing trouble.

Regards,
Andreas
Brad Campbell Nov. 5, 2020, 8:30 a.m. UTC | #4
On 5/11/20 6:56 pm, Henrik Rydberg wrote:
> Hi Brad,
> 
> Great to see this effort, it is certainly an area which could be improved. After having seen several generations of Macbooks while modifying much of that code, it became clear that the SMC communication got refreshed a few times over the years. Every tiny change had to be tested on all machines, or kept separate for a particular generation, or something would break.
> 
> I have not followed the back story here, but I imagine the need has arisen because of a new refresh, and so this patch only needs to strictly apply to a new generation. I would therefore advice that you write the patch in that way, reducing the actual change to zero for earlier generations. It also makes it easier to test the effect of the new approach on older systems. I should be able to help testing on a 2008 and 2011 model once we get to that stage.

G'day Henrik,

Unfortunately I didn't make these changes to accommodate a "new generation". Changes made in kernel 5.9 broke it on my machine and in looking at why didn't identify any obvious causes, so I re-worked some of the comms.

I can't guarantee it won't break older machines which is why I've asked for help testing it. I only have a MacbookPro 11,1 and an iMac 12,2. It fixes both of those.

Help testing would be much appreciated.

Regards,
Brad
Henrik Rydberg Nov. 5, 2020, 10:31 a.m. UTC | #5
On 2020-11-05 09:30, Brad Campbell wrote:
> On 5/11/20 6:56 pm, Henrik Rydberg wrote:
>> Hi Brad,
>>
>> Great to see this effort, it is certainly an area which could be improved. After having seen several generations of Macbooks while modifying much of that code, it became clear that the SMC communication got refreshed a few times over the years. Every tiny change had to be tested on all machines, or kept separate for a particular generation, or something would break.
>>
>> I have not followed the back story here, but I imagine the need has arisen because of a new refresh, and so this patch only needs to strictly apply to a new generation. I would therefore advice that you write the patch in that way, reducing the actual change to zero for earlier generations. It also makes it easier to test the effect of the new approach on older systems. I should be able to help testing on a 2008 and 2011 model once we get to that stage.
> 
> G'day Henrik,
> 
> Unfortunately I didn't make these changes to accommodate a "new generation". Changes made in kernel 5.9 broke it on my machine and in looking at why didn't identify any obvious causes, so I re-worked some of the comms.
> 
> I can't guarantee it won't break older machines which is why I've asked for help testing it. I only have a MacbookPro 11,1 and an iMac 12,2. It fixes both of those.
> 
> Help testing would be much appreciated.

I see, this makes much more sense. I may be able to run some tests 
tonight. Meanwhile, looking at the patch, the status variable in 
send_command looks superfluous now that there is a wait_status() before it.

Thanks,
Henrik
Guenter Roeck Nov. 5, 2020, 4:12 p.m. UTC | #6
On 11/4/20 11:26 PM, Brad Campbell wrote:
> Commit fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()") introduced
> an issue whereby communication with the SMC became unreliable with write
> errors like :
> 
> [  120.378614] applesmc: send_byte(0x00, 0x0300) fail: 0x40
> [  120.378621] applesmc: LKSB: write data fail
> [  120.512782] applesmc: send_byte(0x00, 0x0300) fail: 0x40
> [  120.512787] applesmc: LKSB: write data fail
> 
> The original code appeared to be timing sensitive and was not reliable with
> the timing changes in the aforementioned commit.
> 
> This patch re-factors the SMC communication to remove the timing 
> dependencies and restore function with the changes previously committed.
> 

Still a few formatting problems, but I like this version. Id take
care of the formatting myself, but send_command() will need a change.

Subject should be
	[PATCH v<version>] hwmon: (applesmc) ...

> v2 : Address logic and coding style

Change log should be after '---'

> 
> Reported-by: Andreas Kemnade <andreas@kemnade.info>
> Fixes: fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()")
> Signed-off-by: Brad Campbell <brad@fnarfbargle.com>
> 
> ---
> diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c
> index a18887990f4a..de890f3ec12f 100644
> --- a/drivers/hwmon/applesmc.c
> +++ b/drivers/hwmon/applesmc.c
> @@ -42,6 +42,11 @@
>  
>  #define APPLESMC_MAX_DATA_LENGTH 32
>  
> +/* Apple SMC status bits */
> +#define SMC_STATUS_AWAITING_DATA  BIT(0) /* SMC has data waiting */
> +#define SMC_STATUS_IB_CLOSED      BIT(1) /* Will ignore any input */
> +#define SMC_STATUS_BUSY           BIT(2) /* Command in progress */
> +

Hah, tricked you here ;-). Using "BIT()" requires

#include <linux/bits.h>

>  /* wait up to 128 ms for a status change. */
>  #define APPLESMC_MIN_WAIT	0x0010
>  #define APPLESMC_RETRY_WAIT	0x0100
> @@ -151,65 +156,69 @@ static unsigned int key_at_index;
>  static struct workqueue_struct *applesmc_led_wq;
>  
>  /*
> - * wait_read - Wait for a byte to appear on SMC port. Callers must
> - * hold applesmc_lock.
> + * Wait for specific status bits with a mask on the SMC
> + * Used before and after writes, and before reads
>   */
> -static int wait_read(void)
> +
> +static int wait_status(u8 val, u8 mask)
>  {
>  	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
>  	u8 status;
>  	int us;
>  
>  	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
> -		usleep_range(us, us * 16);
>  		status = inb(APPLESMC_CMD_PORT);
> -		/* read: wait for smc to settle */
> -		if (status & 0x01)
> +		if ((status & mask) == val)
>  			return 0;
>  		/* timeout: give up */
>  		if (time_after(jiffies, end))
>  			break;
> -	}
> -
> -	pr_warn("wait_read() fail: 0x%02x\n", status);
> +		usleep_range(us, us * 16);
> +		}

Bad indentation of "}"

>  	return -EIO;
>  }
>  
>  /*
> - * send_byte - Write to SMC port, retrying when necessary. Callers
> + * send_byte_data - Write to SMC data port. Callers
>   * must hold applesmc_lock.
> + * Parameter skip must be true on the last write of any
> + * command or it'll time out.
>   */
> -static int send_byte(u8 cmd, u16 port)
> +
> +static int send_byte_data(u8 cmd, u16 port, bool skip)
>  {
> -	u8 status;
> -	int us;
> -	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
> +	int ret;
>  
> +	ret = wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY | SMC_STATUS_IB_CLOSED);
> +	if (ret)
> +		return ret;
>  	outb(cmd, port);
> -	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
> -		usleep_range(us, us * 16);
> -		status = inb(APPLESMC_CMD_PORT);
> -		/* write: wait for smc to settle */
> -		if (status & 0x02)
> -			continue;
> -		/* ready: cmd accepted, return */
> -		if (status & 0x04)
> -			return 0;
> -		/* timeout: give up */
> -		if (time_after(jiffies, end))
> -			break;
> -		/* busy: long wait and resend */
> -		udelay(APPLESMC_RETRY_WAIT);
> -		outb(cmd, port);
> -	}
> +	return wait_status(skip ? 0 : SMC_STATUS_BUSY, SMC_STATUS_BUSY);
> +}
>  
> -	pr_warn("send_byte(0x%02x, 0x%04x) fail: 0x%02x\n", cmd, port, status);
> -	return -EIO;
> +static int send_byte(u8 cmd, u16 port)
> +{
> +	return send_byte_data(cmd, port, false);
>  }
>  
> +/*
> + * send_command - Write a command to the SMC. Callers must hold applesmc_lock.
> + * If SMC is in undefined state, any new command write resets the state machine.
> + */
> +
>  static int send_command(u8 cmd)
>  {
> -	return send_byte(cmd, APPLESMC_CMD_PORT);
> +	u8 status;
> +	int ret;
> +
> +	ret = wait_status(0, SMC_STATUS_IB_CLOSED);
> +	if (ret)
> +		return ret;
> +
> +	status = inb(APPLESMC_CMD_PORT);
> +

Is this read necessary ? 'status' isn't used subsequently, meaning we'll
probably get static analyzer warnings about a variable which is assigned
but unused. If the read is necessary, just don't assign it to a variable.

> +	outb(cmd, APPLESMC_CMD_PORT);
> +	return wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY);
>  }
>  
>  static int send_argument(const char *key)
> @@ -239,7 +248,9 @@ static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
>  	}
>  
>  	for (i = 0; i < len; i++) {
> -		if (wait_read()) {
> +		if (wait_status(SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY,
> +				SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY |
> +				SMC_STATUS_IB_CLOSED)) {
>  			pr_warn("%.4s: read data[%d] fail\n", key, i);
>  			return -EIO;
>  		}
> @@ -250,7 +261,7 @@ static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
>  	for (i = 0; i < 16; i++) {
>  		udelay(APPLESMC_MIN_WAIT);
>  		status = inb(APPLESMC_CMD_PORT);
> -		if (!(status & 0x01))
> +		if (!(status & SMC_STATUS_AWAITING_DATA))
>  			break;
>  		data = inb(APPLESMC_DATA_PORT);
>  	}
> @@ -275,7 +286,7 @@ static int write_smc(u8 cmd, const char *key, const u8 *buffer, u8 len)
>  	}
>  
>  	for (i = 0; i < len; i++) {
> -		if (send_byte(buffer[i], APPLESMC_DATA_PORT)) {
> +		if (send_byte_data(buffer[i], APPLESMC_DATA_PORT, i == len - 1)) {
>  			pr_warn("%s: write data fail\n", key);
>  			return -EIO;
>  		}
>
Brad Campbell Nov. 6, 2020, 12:02 a.m. UTC | #7
On 6/11/20 3:12 am, Guenter Roeck wrote:
> On 11/4/20 11:26 PM, Brad Campbell wrote:
>> Commit fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()") introduced
>> an issue whereby communication with the SMC became unreliable with write
>> errors like :
>>
>> [  120.378614] applesmc: send_byte(0x00, 0x0300) fail: 0x40
>> [  120.378621] applesmc: LKSB: write data fail
>> [  120.512782] applesmc: send_byte(0x00, 0x0300) fail: 0x40
>> [  120.512787] applesmc: LKSB: write data fail
>>
>> The original code appeared to be timing sensitive and was not reliable with
>> the timing changes in the aforementioned commit.
>>
>> This patch re-factors the SMC communication to remove the timing 
>> dependencies and restore function with the changes previously committed.
>>
> 
> Still a few formatting problems, but I like this version. Id take
> care of the formatting myself, but send_command() will need a change.

Nope, I'm more than happy to sort it all out. It's a learning process.

I'd still like this to get some wider testing before I consider it ready to go
so extra revisions don't worry me.

> Subject should be
> 	[PATCH v<version>] hwmon: (applesmc) ...

Thanks.
 
>> v2 : Address logic and coding style
> 
> Change log should be after '---'

No worries, can do.

> 
>>
>> Reported-by: Andreas Kemnade <andreas@kemnade.info>
>> Fixes: fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()")
>> Signed-off-by: Brad Campbell <brad@fnarfbargle.com>
>>
>> ---
>> diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c
>> index a18887990f4a..de890f3ec12f 100644
>> --- a/drivers/hwmon/applesmc.c
>> +++ b/drivers/hwmon/applesmc.c
>> @@ -42,6 +42,11 @@
>>  
>>  #define APPLESMC_MAX_DATA_LENGTH 32
>>  
>> +/* Apple SMC status bits */
>> +#define SMC_STATUS_AWAITING_DATA  BIT(0) /* SMC has data waiting */
>> +#define SMC_STATUS_IB_CLOSED      BIT(1) /* Will ignore any input */
>> +#define SMC_STATUS_BUSY           BIT(2) /* Command in progress */
>> +
> 
> Hah, tricked you here ;-). Using "BIT()" requires
> 
> #include <linux/bits.h>

"requires" ?? 
It compiles and tests without warning, but I'll certainly add it in.


>>  /* wait up to 128 ms for a status change. */
>>  #define APPLESMC_MIN_WAIT	0x0010
>>  #define APPLESMC_RETRY_WAIT	0x0100
>> @@ -151,65 +156,69 @@ static unsigned int key_at_index;
>>  static struct workqueue_struct *applesmc_led_wq;
>>  
>>  /*
>> - * wait_read - Wait for a byte to appear on SMC port. Callers must
>> - * hold applesmc_lock.
>> + * Wait for specific status bits with a mask on the SMC
>> + * Used before and after writes, and before reads
>>   */
>> -static int wait_read(void)
>> +
>> +static int wait_status(u8 val, u8 mask)
>>  {
>>  	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
>>  	u8 status;
>>  	int us;
>>  
>>  	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
>> -		usleep_range(us, us * 16);
>>  		status = inb(APPLESMC_CMD_PORT);
>> -		/* read: wait for smc to settle */
>> -		if (status & 0x01)
>> +		if ((status & mask) == val)
>>  			return 0;
>>  		/* timeout: give up */
>>  		if (time_after(jiffies, end))
>>  			break;
>> -	}
>> -
>> -	pr_warn("wait_read() fail: 0x%02x\n", status);
>> +		usleep_range(us, us * 16);
>> +		}
> 
> Bad indentation of "}"

Yeah, I've found my editor "less than optimal" and I've had to correct a few
tab related indent problems manually. Thanks.
 
>>  	return -EIO;
>>  }
>>  
>>  /*
>> - * send_byte - Write to SMC port, retrying when necessary. Callers
>> + * send_byte_data - Write to SMC data port. Callers
>>   * must hold applesmc_lock.
>> + * Parameter skip must be true on the last write of any
>> + * command or it'll time out.
>>   */
>> -static int send_byte(u8 cmd, u16 port)
>> +
>> +static int send_byte_data(u8 cmd, u16 port, bool skip)
>>  {
>> -	u8 status;
>> -	int us;
>> -	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
>> +	int ret;
>>  
>> +	ret = wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY | SMC_STATUS_IB_CLOSED);
>> +	if (ret)
>> +		return ret;
>>  	outb(cmd, port);
>> -	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
>> -		usleep_range(us, us * 16);
>> -		status = inb(APPLESMC_CMD_PORT);
>> -		/* write: wait for smc to settle */
>> -		if (status & 0x02)
>> -			continue;
>> -		/* ready: cmd accepted, return */
>> -		if (status & 0x04)
>> -			return 0;
>> -		/* timeout: give up */
>> -		if (time_after(jiffies, end))
>> -			break;
>> -		/* busy: long wait and resend */
>> -		udelay(APPLESMC_RETRY_WAIT);
>> -		outb(cmd, port);
>> -	}
>> +	return wait_status(skip ? 0 : SMC_STATUS_BUSY, SMC_STATUS_BUSY);
>> +}
>>  
>> -	pr_warn("send_byte(0x%02x, 0x%04x) fail: 0x%02x\n", cmd, port, status);
>> -	return -EIO;
>> +static int send_byte(u8 cmd, u16 port)
>> +{
>> +	return send_byte_data(cmd, port, false);
>>  }
>>  
>> +/*
>> + * send_command - Write a command to the SMC. Callers must hold applesmc_lock.
>> + * If SMC is in undefined state, any new command write resets the state machine.
>> + */
>> +
>>  static int send_command(u8 cmd)
>>  {
>> -	return send_byte(cmd, APPLESMC_CMD_PORT);
>> +	u8 status;
>> +	int ret;
>> +
>> +	ret = wait_status(0, SMC_STATUS_IB_CLOSED);
>> +	if (ret)
>> +		return ret;
>> +
>> +	status = inb(APPLESMC_CMD_PORT);
>> +
> 
> Is this read necessary ? 'status' isn't used subsequently, meaning we'll
> probably get static analyzer warnings about a variable which is assigned
> but unused. If the read is necessary, just don't assign it to a variable.

No it's not. It's hangover from incompletely remove debug statements.
Henrik Rydberg picked that one up yesterday.

>> +	outb(cmd, APPLESMC_CMD_PORT);
>> +	return wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY);
>>  }
>>  
>>  static int send_argument(const char *key)
>> @@ -239,7 +248,9 @@ static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
>>  	}
>>  
>>  	for (i = 0; i < len; i++) {
>> -		if (wait_read()) {
>> +		if (wait_status(SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY,
>> +				SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY |
>> +				SMC_STATUS_IB_CLOSED)) {
>>  			pr_warn("%.4s: read data[%d] fail\n", key, i);
>>  			return -EIO;
>>  		}
>> @@ -250,7 +261,7 @@ static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
>>  	for (i = 0; i < 16; i++) {
>>  		udelay(APPLESMC_MIN_WAIT);
>>  		status = inb(APPLESMC_CMD_PORT);
>> -		if (!(status & 0x01))
>> +		if (!(status & SMC_STATUS_AWAITING_DATA))
>>  			break;
>>  		data = inb(APPLESMC_DATA_PORT);
>>  	}
>> @@ -275,7 +286,7 @@ static int write_smc(u8 cmd, const char *key, const u8 *buffer, u8 len)
>>  	}
>>  
>>  	for (i = 0; i < len; i++) {
>> -		if (send_byte(buffer[i], APPLESMC_DATA_PORT)) {
>> +		if (send_byte_data(buffer[i], APPLESMC_DATA_PORT, i == len - 1)) {
>>  			pr_warn("%s: write data fail\n", key);
>>  			return -EIO;
>>  		}
>>
> 
> 

I'll get a v3 in when I get some more test results.

Much appreciated.
Regards,
Brad
Guenter Roeck Nov. 6, 2020, 3:08 a.m. UTC | #8
On 11/5/20 4:02 PM, Brad Campbell wrote:
[ ... ]
>>> +/* Apple SMC status bits */
>>> +#define SMC_STATUS_AWAITING_DATA  BIT(0) /* SMC has data waiting */
>>> +#define SMC_STATUS_IB_CLOSED      BIT(1) /* Will ignore any input */
>>> +#define SMC_STATUS_BUSY           BIT(2) /* Command in progress */
>>> +
>>
>> Hah, tricked you here ;-). Using "BIT()" requires
>>
>> #include <linux/bits.h>
> 
> "requires" ?? 
> It compiles and tests without warning, but I'll certainly add it in.
> 

Each driver should include the files with the declarations it needs, and
not depend on some indirect includes. Those indirect includes are not guaranteed
to exist and may be removed at some point in the future. "It compiles" is most
definitely not a valid argument.

Guenter
Henrik Rydberg Nov. 6, 2020, 4:26 p.m. UTC | #9
>> I can't guarantee it won't break older machines which is why I've 
>> asked for help testing it. I only have a MacbookPro 11,1 and an iMac 
>> 12,2. It fixes both of those.
>>
>> Help testing would be much appreciated.
> 
> I see, this makes much more sense. I may be able to run some tests 
> tonight. Meanwhile, looking at the patch, the status variable in 
> send_command looks superfluous now that there is a wait_status() before it.

Ok, it took some time to get the machines up to speed, but so far I have 
managed to run some tests on an MacBookAir1,1. I only managed to upgrade 
up to 4.15, so I had to revert the inputdev polling patch, but the rest 
applied without problems. I recovered an old test program I used to use 
(attached), and checked for failures and reads per second

*** hwmon: (applesmc) switch to using input device polling mode

At this point in the tree, with this reverted, I see 0 failures and 55 
reads per second.

*** hwmon: (applesmc) avoid overlong udelay()

With this applied, I see 0 failures and 16 reads per second.

*** hwmon: (applesmc) check status earlier.

With this applied, I see 0 failures and 16 reads per second.

*** (HEAD -> stable) applesmc: Re-work SMC comms v2

With this applied, the kernel hangs in module probe, and the kernel log 
is flooded with read failures.

So as it stands, it does not work at all. I will continue to check 
another machine, and see if I can get something working.

Henrik
Henrik Rydberg Nov. 6, 2020, 8:02 p.m. UTC | #10
> So as it stands, it does not work at all. I will continue to check 
> another machine, and see if I can get something working.

On the MacBookAir3,1 the situation is somewhat better.

The first three tree positions result in zero failures and 10 reads per 
second. The fourth yields zero failues and 11 reads per second, within 
the margin of similarity.

So, the patch appears to have no apparent effect on the 3,1 series.

Now onto fixing the 1,1 behavior.

Henrik
Brad Campbell Nov. 6, 2020, 11:11 p.m. UTC | #11
On 7/11/20 3:26 am, Henrik Rydberg wrote:
>>> I can't guarantee it won't break older machines which is why I've asked for help testing it. I only have a MacbookPro 11,1 and an iMac 12,2. It fixes both of those.
>>>
>>> Help testing would be much appreciated.
>>
>> I see, this makes much more sense. I may be able to run some tests tonight. Meanwhile, looking at the patch, the status variable in send_command looks superfluous now that there is a wait_status() before it.
> 
> Ok, it took some time to get the machines up to speed, but so far I have managed to run some tests on an MacBookAir1,1. I only managed to upgrade up to 4.15, so I had to revert the inputdev polling patch, but the rest applied without problems. I recovered an old test program I used to use (attached), and checked for failures and reads per second
> 
> *** hwmon: (applesmc) switch to using input device polling mode
> 
> At this point in the tree, with this reverted, I see 0 failures and 55 reads per second.
> 
> *** hwmon: (applesmc) avoid overlong udelay()
> 
> With this applied, I see 0 failures and 16 reads per second.
> 
> *** hwmon: (applesmc) check status earlier.
> 
> With this applied, I see 0 failures and 16 reads per second.
> 
> *** (HEAD -> stable) applesmc: Re-work SMC comms v2
> 
> With this applied, the kernel hangs in module probe, and the kernel log is flooded with read failures.
> 
> So as it stands, it does not work at all. I will continue to check another machine, and see if I can get something working.
> 
> Henrik


G'day Heinrik,

If you could try this earlier version which still had all the failure logging it in we might be able to get a handle on the failure.

Regards,
Brad

---
diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c
index a18887990f4a..22cc5122ce9a 100644
--- a/drivers/hwmon/applesmc.c
+++ b/drivers/hwmon/applesmc.c
@@ -42,6 +42,11 @@
 
 #define APPLESMC_MAX_DATA_LENGTH 32
 
+/* Apple SMC status bits from VirtualSMC */
+#define SMC_STATUS_AWAITING_DATA  0x01  ///< Data waiting to be read
+#define SMC_STATUS_IB_CLOSED      0x02  /// A write is pending / will ignore input
+#define SMC_STATUS_BUSY           0x04  ///< Busy in the middle of a command.
+
 /* wait up to 128 ms for a status change. */
 #define APPLESMC_MIN_WAIT	0x0010
 #define APPLESMC_RETRY_WAIT	0x0100
@@ -151,65 +156,77 @@ static unsigned int key_at_index;
 static struct workqueue_struct *applesmc_led_wq;
 
 /*
- * wait_read - Wait for a byte to appear on SMC port. Callers must
- * hold applesmc_lock.
+ * Wait for specific status bits with a mask on the SMC
+ * Used before and after writes, and before reads
  */
-static int wait_read(void)
+
+static int wait_status(u8 val, u8 mask)
 {
 	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
 	u8 status;
 	int us;
 
 	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
-		usleep_range(us, us * 16);
 		status = inb(APPLESMC_CMD_PORT);
-		/* read: wait for smc to settle */
-		if (status & 0x01)
+		if ((status & mask) == val)
 			return 0;
 		/* timeout: give up */
 		if (time_after(jiffies, end))
 			break;
-	}
-
-	pr_warn("wait_read() fail: 0x%02x\n", status);
+		usleep_range(us, us * 16);
+		}
+	pr_warn("wait_status timeout: 0x%02x, 0x%02x, 0x%02x\n", status, val, mask);
 	return -EIO;
 }
 
 /*
- * send_byte - Write to SMC port, retrying when necessary. Callers
+ * send_byte_data - Write to SMC data port. Callers
  * must hold applesmc_lock.
+ * Parameter skip must be true on the last write of any
+ * command or it'll time out.
  */
-static int send_byte(u8 cmd, u16 port)
+
+static int send_byte_data(u8 cmd, u16 port, bool skip)
 {
-	u8 status;
-	int us;
-	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
+	u8 wstat = SMC_STATUS_BUSY;
 
+	if (skip)
+		wstat = 0;
+	if (wait_status(SMC_STATUS_BUSY,
+	SMC_STATUS_BUSY | SMC_STATUS_IB_CLOSED))
+		goto fail;
 	outb(cmd, port);
-	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
-		usleep_range(us, us * 16);
-		status = inb(APPLESMC_CMD_PORT);
-		/* write: wait for smc to settle */
-		if (status & 0x02)
-			continue;
-		/* ready: cmd accepted, return */
-		if (status & 0x04)
-			return 0;
-		/* timeout: give up */
-		if (time_after(jiffies, end))
-			break;
-		/* busy: long wait and resend */
-		udelay(APPLESMC_RETRY_WAIT);
-		outb(cmd, port);
-	}
-
-	pr_warn("send_byte(0x%02x, 0x%04x) fail: 0x%02x\n", cmd, port, status);
+	if (!wait_status(wstat,
+	SMC_STATUS_BUSY))
+		return 0;
+fail:
+	pr_warn("send_byte_data(0x%02x, 0x%04x) fail\n", cmd, APPLESMC_CMD_PORT);
 	return -EIO;
 }
 
+/*
+ * send_command - Write a command to the SMC. Callers must hold applesmc_lock.
+ * If SMC is in undefined state, any new command write resets the state machine.
+ */
+
 static int send_command(u8 cmd)
 {
-	return send_byte(cmd, APPLESMC_CMD_PORT);
+	u8 status;
+
+	if (wait_status(0,
+	SMC_STATUS_IB_CLOSED)) {
+		pr_warn("send_command SMC was busy\n");
+		goto fail; }
+
+	status = inb(APPLESMC_CMD_PORT);
+
+	outb(cmd, APPLESMC_CMD_PORT);
+	if (!wait_status(SMC_STATUS_BUSY,
+	SMC_STATUS_BUSY))
+		return 0;
+fail:
+	pr_warn("send_cmd(0x%02x, 0x%04x) fail\n", cmd, APPLESMC_CMD_PORT);
+	return -EIO;
 }
 
 static int send_argument(const char *key)
@@ -217,7 +234,8 @@ static int send_argument(const char *key)
 	int i;
 
 	for (i = 0; i < 4; i++)
-		if (send_byte(key[i], APPLESMC_DATA_PORT))
+	/* Parameter skip is false as we always send data after an argument */
+		if (send_byte_data(key[i], APPLESMC_DATA_PORT, false))
 			return -EIO;
 	return 0;
 }
@@ -233,13 +251,15 @@ static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
 	}
 
 	/* This has no effect on newer (2012) SMCs */
-	if (send_byte(len, APPLESMC_DATA_PORT)) {
+	if (send_byte_data(len, APPLESMC_DATA_PORT, false)) {
 		pr_warn("%.4s: read len fail\n", key);
 		return -EIO;
 	}
 
 	for (i = 0; i < len; i++) {
-		if (wait_read()) {
+		if (wait_status(SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY,
+		SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY |
+		SMC_STATUS_IB_CLOSED)) {
 			pr_warn("%.4s: read data[%d] fail\n", key, i);
 			return -EIO;
 		}
@@ -250,7 +270,7 @@ static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
 	for (i = 0; i < 16; i++) {
 		udelay(APPLESMC_MIN_WAIT);
 		status = inb(APPLESMC_CMD_PORT);
-		if (!(status & 0x01))
+		if (!(status & SMC_STATUS_AWAITING_DATA))
 			break;
 		data = inb(APPLESMC_DATA_PORT);
 	}
@@ -263,20 +283,21 @@ static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
 static int write_smc(u8 cmd, const char *key, const u8 *buffer, u8 len)
 {
 	int i;
+	u8 end = len-1;
 
 	if (send_command(cmd) || send_argument(key)) {
 		pr_warn("%s: write arg fail\n", key);
 		return -EIO;
 	}
 
-	if (send_byte(len, APPLESMC_DATA_PORT)) {
+	if (send_byte_data(len, APPLESMC_DATA_PORT, false)) {
 		pr_warn("%.4s: write len fail\n", key);
 		return -EIO;
 	}
 
 	for (i = 0; i < len; i++) {
-		if (send_byte(buffer[i], APPLESMC_DATA_PORT)) {
-			pr_warn("%s: write data fail\n", key);
+		if (send_byte_data(buffer[i], APPLESMC_DATA_PORT, (i == end))) {
+			pr_warn("%s: write data fail at %i\n", key, i);
 			return -EIO;
 		}
 	}
Henrik Rydberg Nov. 7, 2020, 6:31 p.m. UTC | #12
On 2020-11-06 21:02, Henrik Rydberg wrote:
>> So as it stands, it does not work at all. I will continue to check 
>> another machine, and see if I can get something working.
> 
> On the MacBookAir3,1 the situation is somewhat better.
> 
> The first three tree positions result in zero failures and 10 reads per 
> second. The fourth yields zero failues and 11 reads per second, within 
> the margin of similarity.
> 
> So, the patch appears to have no apparent effect on the 3,1 series.
> 
> Now onto fixing the 1,1 behavior.

Hi again,

This patch, v3, works for me, on both MBA1,1 and MBA3,1. Both machines 
yields 25 reads per second.

It turns out that the origin code has a case that was not carried over 
to the v2 patch; the command byte needs to be resent upon the wrong 
status code. I added that back. Also, there seems to be a basic response 
time that needs to be respected, so I added back a small fixed delay 
after each write operation. I also took the liberty to reduce the number 
of status reads, and clean up error handling. Checkpatch is happy with 
this version.

The code obviously needs to be retested on the other machines, but the 
logic still follows what you wrote, Brad, and I have also checked it 
against the VirtualSMC code. It appears to make sense, so hopefully 
there wont be additional issues.

Thanks,
Henrik

 From be4a32620b2b611472af3e35f9b50004e678efd5 Mon Sep 17 00:00:00 2001
From: Brad Campbell <brad@fnarfbargle.com>
Date: Thu, 5 Nov 2020 18:26:24 +1100
Subject: [PATCH] applesmc: Re-work SMC comms v3

Commit fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()")
introduced an issue whereby communication with the SMC became
unreliable with write errors like:

[  120.378614] applesmc: send_byte(0x00, 0x0300) fail: 0x40
[  120.378621] applesmc: LKSB: write data fail
[  120.512782] applesmc: send_byte(0x00, 0x0300) fail: 0x40
[  120.512787] applesmc: LKSB: write data fail

The original code appeared to be timing sensitive and was not reliable with
the timing changes in the aforementioned commit.

This patch re-factors the SMC communication to remove the timing
dependencies and restore function with the changes previously committed.

v2 : Address logic and coding style
v3 : Modifications for MacBookAir1,1

Reported-by: Andreas Kemnade <andreas@kemnade.info>
Fixes: fff2d0f701e6 ("hwmon: (applesmc) avoid overlong udelay()")
Signed-off-by: Brad Campbell <brad@fnarfbargle.com>
Signed-off-by: Henrik Rydberg <rydberg@bitmath.org>
---
  drivers/hwmon/applesmc.c | 132 +++++++++++++++++++++------------------
  1 file changed, 70 insertions(+), 62 deletions(-)

diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c
index a18887990f4a..08289827da1e 100644
--- a/drivers/hwmon/applesmc.c
+++ b/drivers/hwmon/applesmc.c
@@ -42,6 +42,11 @@

  #define APPLESMC_MAX_DATA_LENGTH 32

+/* Apple SMC status bits */
+#define SMC_STATUS_AWAITING_DATA  BIT(0) /* SMC has data waiting */
+#define SMC_STATUS_IB_CLOSED      BIT(1) /* Will ignore any input */
+#define SMC_STATUS_BUSY           BIT(2) /* Command in progress */
+
  /* wait up to 128 ms for a status change. */
  #define APPLESMC_MIN_WAIT	0x0010
  #define APPLESMC_RETRY_WAIT	0x0100
@@ -151,65 +156,76 @@ static unsigned int key_at_index;
  static struct workqueue_struct *applesmc_led_wq;

  /*
- * wait_read - Wait for a byte to appear on SMC port. Callers must
- * hold applesmc_lock.
+ * Wait for specific status bits with a mask on the SMC
+ * Used before and after writes, and before reads
   */
-static int wait_read(void)
+
+static int wait_status(u8 val, u8 mask)
  {
  	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
  	u8 status;
  	int us;

  	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
-		usleep_range(us, us * 16);
  		status = inb(APPLESMC_CMD_PORT);
-		/* read: wait for smc to settle */
-		if (status & 0x01)
+		if ((status & mask) == val)
  			return 0;
  		/* timeout: give up */
  		if (time_after(jiffies, end))
  			break;
+		usleep_range(us, us * 16);
  	}

-	pr_warn("wait_read() fail: 0x%02x\n", status);
+	if (debug)
+		pr_warn("%s fail: 0x%02x 0x%02x 0x%02x\n", __func__, val, mask, status);
  	return -EIO;
  }

  /*
- * send_byte - Write to SMC port, retrying when necessary. Callers
+ * send_byte_data - Write to SMC data port. Callers
   * must hold applesmc_lock.
+ * Parameter skip must be true on the last write of any
+ * command or it'll time out.
   */
-static int send_byte(u8 cmd, u16 port)
+
+static int send_byte_data(u8 cmd, u16 port, bool skip)
+{
+	outb(cmd, port);
+	udelay(APPLESMC_MIN_WAIT);
+	return wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY | 
SMC_STATUS_IB_CLOSED);
+}
+
+/*
+ * send_command - Write a command to the SMC. Callers must hold 
applesmc_lock.
+ * If SMC is in undefined state, any new command write resets the state 
machine.
+ */
+
+static int send_command(u8 cmd)
  {
+	int ret;
+	int i;
  	u8 status;
-	int us;
-	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;

-	outb(cmd, port);
-	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
-		usleep_range(us, us * 16);
+	ret = wait_status(0, SMC_STATUS_IB_CLOSED);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < 16; i++) {
+		outb(cmd, APPLESMC_CMD_PORT);
+		udelay(APPLESMC_MIN_WAIT);
+		ret = wait_status(0, SMC_STATUS_IB_CLOSED);
+		if (ret)
+			return ret;
  		status = inb(APPLESMC_CMD_PORT);
-		/* write: wait for smc to settle */
-		if (status & 0x02)
-			continue;
-		/* ready: cmd accepted, return */
-		if (status & 0x04)
+		if (status & SMC_STATUS_BUSY)
  			return 0;
-		/* timeout: give up */
-		if (time_after(jiffies, end))
-			break;
-		/* busy: long wait and resend */
-		udelay(APPLESMC_RETRY_WAIT);
-		outb(cmd, port);
+		usleep_range(APPLESMC_RETRY_WAIT, APPLESMC_RETRY_WAIT * 16);
  	}

-	pr_warn("send_byte(0x%02x, 0x%04x) fail: 0x%02x\n", cmd, port, status);
-	return -EIO;
-}
+	if (debug)
+		pr_warn("%s fail: 0x%02x\n", __func__, status);

-static int send_command(u8 cmd)
-{
-	return send_byte(cmd, APPLESMC_CMD_PORT);
+	return -EIO;
  }

  static int send_argument(const char *key)
@@ -217,32 +233,28 @@ static int send_argument(const char *key)
  	int i;

  	for (i = 0; i < 4; i++)
-		if (send_byte(key[i], APPLESMC_DATA_PORT))
+		if (send_byte_data(key[i], APPLESMC_DATA_PORT, false))
  			return -EIO;
  	return 0;
  }

+static int send_length(u8 len, bool skip)
+{
+	return send_byte_data(len, APPLESMC_DATA_PORT, skip);
+}
+
  static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
  {
  	u8 status, data = 0;
  	int i;

-	if (send_command(cmd) || send_argument(key)) {
-		pr_warn("%.4s: read arg fail\n", key);
-		return -EIO;
-	}
-
-	/* This has no effect on newer (2012) SMCs */
-	if (send_byte(len, APPLESMC_DATA_PORT)) {
-		pr_warn("%.4s: read len fail\n", key);
-		return -EIO;
-	}
+	if (send_command(cmd) || send_argument(key) || send_length(len, 1))
+		goto err;

  	for (i = 0; i < len; i++) {
-		if (wait_read()) {
-			pr_warn("%.4s: read data[%d] fail\n", key, i);
-			return -EIO;
-		}
+		if (wait_status(SMC_STATUS_AWAITING_DATA,
+						SMC_STATUS_AWAITING_DATA | SMC_STATUS_IB_CLOSED))
+			goto err;
  		buffer[i] = inb(APPLESMC_DATA_PORT);
  	}

@@ -250,7 +262,7 @@ static int read_smc(u8 cmd, const char *key, u8 
*buffer, u8 len)
  	for (i = 0; i < 16; i++) {
  		udelay(APPLESMC_MIN_WAIT);
  		status = inb(APPLESMC_CMD_PORT);
-		if (!(status & 0x01))
+		if (!(status & SMC_STATUS_AWAITING_DATA))
  			break;
  		data = inb(APPLESMC_DATA_PORT);
  	}
@@ -258,30 +270,26 @@ static int read_smc(u8 cmd, const char *key, u8 
*buffer, u8 len)
  		pr_warn("flushed %d bytes, last value is: %d\n", i, data);

  	return 0;
+err:
+	pr_warn("read cmd fail: %x %.4s %d\n", cmd, key, len);
+	return -EIO;
  }

  static int write_smc(u8 cmd, const char *key, const u8 *buffer, u8 len)
  {
  	int i;

-	if (send_command(cmd) || send_argument(key)) {
-		pr_warn("%s: write arg fail\n", key);
-		return -EIO;
-	}
+	if (send_command(cmd) || send_argument(key) || send_length(len, 0))
+		goto err;

-	if (send_byte(len, APPLESMC_DATA_PORT)) {
-		pr_warn("%.4s: write len fail\n", key);
-		return -EIO;
-	}
-
-	for (i = 0; i < len; i++) {
-		if (send_byte(buffer[i], APPLESMC_DATA_PORT)) {
-			pr_warn("%s: write data fail\n", key);
-			return -EIO;
-		}
-	}
+	for (i = 0; i < len; i++)
+		if (send_byte_data(buffer[i], APPLESMC_DATA_PORT, i == len - 1))
+			goto err;

  	return 0;
+err:
+	pr_warn("write cmd fail: %x %.4s %d\n", cmd, key, len);
+	return -EIO;
  }

  static int read_register_count(unsigned int *count)
Brad Campbell Nov. 8, 2020, 12:09 a.m. UTC | #13
On 8/11/20 5:31 am, Henrik Rydberg wrote:
> On 2020-11-06 21:02, Henrik Rydberg wrote:
>>> So as it stands, it does not work at all. I will continue to check another machine, and see if I can get something working.
>>
>> On the MacBookAir3,1 the situation is somewhat better.
>>
>> The first three tree positions result in zero failures and 10 reads per second. The fourth yields zero failues and 11 reads per second, within the margin of similarity.
>>
>> So, the patch appears to have no apparent effect on the 3,1 series.
>>
>> Now onto fixing the 1,1 behavior.
> 
> Hi again,
> 
> This patch, v3, works for me, on both MBA1,1 and MBA3,1. Both machines yields 25 reads per second.
> 
> It turns out that the origin code has a case that was not carried over to the v2 patch; the command byte needs to be resent upon the wrong status code. I added that back. Also, there seems to be a basic response time that needs to be respected, so I added back a small fixed delay after each write operation. I also took the liberty to reduce the number of status reads, and clean up error handling. Checkpatch is happy with this version.
> 
> The code obviously needs to be retested on the other machines, but the logic still follows what you wrote, Brad, and I have also checked it against the VirtualSMC code. It appears to make sense, so hopefully there wont be additional issues.
> 
> Thanks,
> Henrik
> 

G'day Henrik,

Which kernel was this based on? It won't apply to my 5.9 tree.

I assume the sprinkling of udelay(APPLESMC_MIN_WAIT) means the SMC is
slow in getting its status register set up. Could we instead just put
a single one of those up-front in wait_status?

Any chance you could try this one? I've added a retry to send_command and 
added a single global APPLESMC_MIN_WAIT before each status read.

From looking at your modified send_command, it appears the trigger for a 
retry is sending a command and the SMC doing absolutely nothing. This
should do the same thing.

Interestingly enough, by adding the udelay to wait_status on my machine I've
gone from 24 reads/s to 50 reads/s.

I've left out the remainder of the cleanups. Once we get a minimally working
patch I was going to look at a few cleanups, and I have some patches pending
to allow writing to the SMC from userspace (for setting BCLM and BFCL mainly)


diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c
index a18887990f4a..2190de78b5f5 100644
--- a/drivers/hwmon/applesmc.c
+++ b/drivers/hwmon/applesmc.c
@@ -32,6 +32,7 @@
 #include <linux/hwmon.h>
 #include <linux/workqueue.h>
 #include <linux/err.h>
+#include <linux/bits.h>
 
 /* data port used by Apple SMC */
 #define APPLESMC_DATA_PORT	0x300
@@ -42,6 +43,11 @@
 
 #define APPLESMC_MAX_DATA_LENGTH 32
 
+/* Apple SMC status bits */
+#define SMC_STATUS_AWAITING_DATA  BIT(0) /* SMC has data waiting */
+#define SMC_STATUS_IB_CLOSED      BIT(1) /* Will ignore any input */
+#define SMC_STATUS_BUSY           BIT(2) /* Command in progress */
+
 /* wait up to 128 ms for a status change. */
 #define APPLESMC_MIN_WAIT	0x0010
 #define APPLESMC_RETRY_WAIT	0x0100
@@ -151,65 +157,73 @@ static unsigned int key_at_index;
 static struct workqueue_struct *applesmc_led_wq;
 
 /*
- * wait_read - Wait for a byte to appear on SMC port. Callers must
- * hold applesmc_lock.
+ * Wait for specific status bits with a mask on the SMC
+ * Used before and after writes, and before reads
  */
-static int wait_read(void)
+
+static int wait_status(u8 val, u8 mask)
 {
 	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
 	u8 status;
 	int us;
 
+	udelay(APPLESMC_MIN_WAIT);
 	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
-		usleep_range(us, us * 16);
 		status = inb(APPLESMC_CMD_PORT);
-		/* read: wait for smc to settle */
-		if (status & 0x01)
+		if ((status & mask) == val)
 			return 0;
 		/* timeout: give up */
 		if (time_after(jiffies, end))
 			break;
+		usleep_range(us, us * 16);
 	}
-
-	pr_warn("wait_read() fail: 0x%02x\n", status);
 	return -EIO;
 }
 
 /*
- * send_byte - Write to SMC port, retrying when necessary. Callers
+ * send_byte_data - Write to SMC data port. Callers
  * must hold applesmc_lock.
+ * Parameter skip must be true on the last write of any
+ * command or it'll time out.
  */
-static int send_byte(u8 cmd, u16 port)
+
+static int send_byte_data(u8 cmd, u16 port, bool skip)
 {
-	u8 status;
-	int us;
-	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
+	int ret;
 
+	ret = wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY | SMC_STATUS_IB_CLOSED);
+	if (ret)
+		return ret;
 	outb(cmd, port);
-	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
-		usleep_range(us, us * 16);
-		status = inb(APPLESMC_CMD_PORT);
-		/* write: wait for smc to settle */
-		if (status & 0x02)
-			continue;
-		/* ready: cmd accepted, return */
-		if (status & 0x04)
-			return 0;
-		/* timeout: give up */
-		if (time_after(jiffies, end))
-			break;
-		/* busy: long wait and resend */
-		udelay(APPLESMC_RETRY_WAIT);
-		outb(cmd, port);
-	}
+	return wait_status(skip ? 0 : SMC_STATUS_BUSY, SMC_STATUS_BUSY);
+}
 
-	pr_warn("send_byte(0x%02x, 0x%04x) fail: 0x%02x\n", cmd, port, status);
-	return -EIO;
+static int send_byte(u8 cmd, u16 port)
+{
+	return send_byte_data(cmd, port, false);
 }
 
+/*
+ * send_command - Write a command to the SMC. Callers must hold applesmc_lock.
+ * If SMC is in undefined state, any new command write resets the state machine.
+ */
+
 static int send_command(u8 cmd)
 {
-	return send_byte(cmd, APPLESMC_CMD_PORT);
+	int ret;
+	int i;
+		
+	for (i=0; i < 16; i++) {
+		ret = wait_status(0, SMC_STATUS_IB_CLOSED);
+		if (ret)
+			return ret;
+	
+		outb(cmd, APPLESMC_CMD_PORT);
+		ret = wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY);
+		if (!ret)
+			return ret;
+	}
+	return -EIO;
 }
 
 static int send_argument(const char *key)
@@ -239,7 +253,9 @@ static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
 	}
 
 	for (i = 0; i < len; i++) {
-		if (wait_read()) {
+		if (wait_status(SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY,
+				SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY |
+				SMC_STATUS_IB_CLOSED)) {
 			pr_warn("%.4s: read data[%d] fail\n", key, i);
 			return -EIO;
 		}
@@ -250,7 +266,7 @@ static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
 	for (i = 0; i < 16; i++) {
 		udelay(APPLESMC_MIN_WAIT);
 		status = inb(APPLESMC_CMD_PORT);
-		if (!(status & 0x01))
+		if (!(status & SMC_STATUS_AWAITING_DATA))
 			break;
 		data = inb(APPLESMC_DATA_PORT);
 	}
@@ -275,7 +291,7 @@ static int write_smc(u8 cmd, const char *key, const u8 *buffer, u8 len)
 	}
 
 	for (i = 0; i < len; i++) {
-		if (send_byte(buffer[i], APPLESMC_DATA_PORT)) {
+		if (send_byte_data(buffer[i], APPLESMC_DATA_PORT, i == len - 1)) {
 			pr_warn("%s: write data fail\n", key);
 			return -EIO;
 		}
Henrik Rydberg Nov. 8, 2020, 8:22 a.m. UTC | #14
Hi Brad,

> G'day Henrik,
> 
> Which kernel was this based on? It won't apply to my 5.9 tree.

I was being lazy and applied the diff to linus/master on top of my 
current stable branch. More importantly, I sent the mail out from an 
email client that may not format the patch properly; I'll fix that.

> I assume the sprinkling of udelay(APPLESMC_MIN_WAIT) means the SMC is
> slow in getting its status register set up. Could we instead just put
> a single one of those up-front in wait_status?

That works fine, just a matter of taste.

> Any chance you could try this one? I've added a retry to send_command and
> added a single global APPLESMC_MIN_WAIT before each status read.
> 
>  From looking at your modified send_command, it appears the trigger for a
> retry is sending a command and the SMC doing absolutely nothing. This
> should do the same thing.

Not quite, unfortunately. The patch that works waits for a drop of 
IB_CLOSED, then checks the BUSY status. If not seen, it resends 
immediately, never expecting to see it. The patch in this email creates 
a dreadfully sluggish probe, and the occasional failure.

> Interestingly enough, by adding the udelay to wait_status on my machine I've
> gone from 24 reads/s to 50 reads/s.

Yep, I experience the same positive effect.

> I've left out the remainder of the cleanups. Once we get a minimally working
> patch I was going to look at a few cleanups, and I have some patches pending
> to allow writing to the SMC from userspace (for setting BCLM and BFCL mainly)

All fine. I will respond to the v3 mail separately.

Henrik
diff mbox series

Patch

diff --git a/drivers/hwmon/applesmc.c b/drivers/hwmon/applesmc.c
index a18887990f4a..de890f3ec12f 100644
--- a/drivers/hwmon/applesmc.c
+++ b/drivers/hwmon/applesmc.c
@@ -42,6 +42,11 @@ 
 
 #define APPLESMC_MAX_DATA_LENGTH 32
 
+/* Apple SMC status bits */
+#define SMC_STATUS_AWAITING_DATA  BIT(0) /* SMC has data waiting */
+#define SMC_STATUS_IB_CLOSED      BIT(1) /* Will ignore any input */
+#define SMC_STATUS_BUSY           BIT(2) /* Command in progress */
+
 /* wait up to 128 ms for a status change. */
 #define APPLESMC_MIN_WAIT	0x0010
 #define APPLESMC_RETRY_WAIT	0x0100
@@ -151,65 +156,69 @@  static unsigned int key_at_index;
 static struct workqueue_struct *applesmc_led_wq;
 
 /*
- * wait_read - Wait for a byte to appear on SMC port. Callers must
- * hold applesmc_lock.
+ * Wait for specific status bits with a mask on the SMC
+ * Used before and after writes, and before reads
  */
-static int wait_read(void)
+
+static int wait_status(u8 val, u8 mask)
 {
 	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
 	u8 status;
 	int us;
 
 	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
-		usleep_range(us, us * 16);
 		status = inb(APPLESMC_CMD_PORT);
-		/* read: wait for smc to settle */
-		if (status & 0x01)
+		if ((status & mask) == val)
 			return 0;
 		/* timeout: give up */
 		if (time_after(jiffies, end))
 			break;
-	}
-
-	pr_warn("wait_read() fail: 0x%02x\n", status);
+		usleep_range(us, us * 16);
+		}
 	return -EIO;
 }
 
 /*
- * send_byte - Write to SMC port, retrying when necessary. Callers
+ * send_byte_data - Write to SMC data port. Callers
  * must hold applesmc_lock.
+ * Parameter skip must be true on the last write of any
+ * command or it'll time out.
  */
-static int send_byte(u8 cmd, u16 port)
+
+static int send_byte_data(u8 cmd, u16 port, bool skip)
 {
-	u8 status;
-	int us;
-	unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
+	int ret;
 
+	ret = wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY | SMC_STATUS_IB_CLOSED);
+	if (ret)
+		return ret;
 	outb(cmd, port);
-	for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
-		usleep_range(us, us * 16);
-		status = inb(APPLESMC_CMD_PORT);
-		/* write: wait for smc to settle */
-		if (status & 0x02)
-			continue;
-		/* ready: cmd accepted, return */
-		if (status & 0x04)
-			return 0;
-		/* timeout: give up */
-		if (time_after(jiffies, end))
-			break;
-		/* busy: long wait and resend */
-		udelay(APPLESMC_RETRY_WAIT);
-		outb(cmd, port);
-	}
+	return wait_status(skip ? 0 : SMC_STATUS_BUSY, SMC_STATUS_BUSY);
+}
 
-	pr_warn("send_byte(0x%02x, 0x%04x) fail: 0x%02x\n", cmd, port, status);
-	return -EIO;
+static int send_byte(u8 cmd, u16 port)
+{
+	return send_byte_data(cmd, port, false);
 }
 
+/*
+ * send_command - Write a command to the SMC. Callers must hold applesmc_lock.
+ * If SMC is in undefined state, any new command write resets the state machine.
+ */
+
 static int send_command(u8 cmd)
 {
-	return send_byte(cmd, APPLESMC_CMD_PORT);
+	u8 status;
+	int ret;
+
+	ret = wait_status(0, SMC_STATUS_IB_CLOSED);
+	if (ret)
+		return ret;
+
+	status = inb(APPLESMC_CMD_PORT);
+
+	outb(cmd, APPLESMC_CMD_PORT);
+	return wait_status(SMC_STATUS_BUSY, SMC_STATUS_BUSY);
 }
 
 static int send_argument(const char *key)
@@ -239,7 +248,9 @@  static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
 	}
 
 	for (i = 0; i < len; i++) {
-		if (wait_read()) {
+		if (wait_status(SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY,
+				SMC_STATUS_AWAITING_DATA | SMC_STATUS_BUSY |
+				SMC_STATUS_IB_CLOSED)) {
 			pr_warn("%.4s: read data[%d] fail\n", key, i);
 			return -EIO;
 		}
@@ -250,7 +261,7 @@  static int read_smc(u8 cmd, const char *key, u8 *buffer, u8 len)
 	for (i = 0; i < 16; i++) {
 		udelay(APPLESMC_MIN_WAIT);
 		status = inb(APPLESMC_CMD_PORT);
-		if (!(status & 0x01))
+		if (!(status & SMC_STATUS_AWAITING_DATA))
 			break;
 		data = inb(APPLESMC_DATA_PORT);
 	}
@@ -275,7 +286,7 @@  static int write_smc(u8 cmd, const char *key, const u8 *buffer, u8 len)
 	}
 
 	for (i = 0; i < len; i++) {
-		if (send_byte(buffer[i], APPLESMC_DATA_PORT)) {
+		if (send_byte_data(buffer[i], APPLESMC_DATA_PORT, i == len - 1)) {
 			pr_warn("%s: write data fail\n", key);
 			return -EIO;
 		}