Skip to content

Commit

Permalink
applesmc: Re-work SMC comms v1
Browse files Browse the repository at this point in the history
Commit fff2d0f ("hwmon: (applesmc) avoid overlong udelay()") introduced
an issue whereby communication with the SMC became unreliable with write
errors :

[  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.

Reported-by: Andreas Kemnade <andreas@kemnade.info>
Signed-off-by: Brad Campbell <brad@fnarfbargle.com>
  • Loading branch information
Brad Campbell authored and intel-lab-lkp committed Nov 5, 2020
1 parent ce15cd2 commit db5e973
Showing 1 changed file with 61 additions and 40 deletions.
101 changes: 61 additions & 40 deletions drivers/hwmon/applesmc.c
Expand Up @@ -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
Expand Down Expand Up @@ -151,73 +156,86 @@ 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)
{
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;
}
Expand All @@ -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;
}
Expand All @@ -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);
}
Expand All @@ -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;
}
}
Expand Down

0 comments on commit db5e973

Please sign in to comment.