sb/intel/common: Fix SMBus block commands

Clear LAST_BYTE flag at beginning of block commands.

For reads, slave device determines in the message format how
many bytes it has to transfer out, host firmware only dictates
the maximum buffer length. Return SMBUS_ERROR if only
partial message was received.

For writes, return SMBUS_ERROR if length > 32.

For writes, fix off-by-one error reading memory one
byte past the buffer end.

Change-Id: If9e5d24255a0a03039b52d2863bc278d0eefc311
Signed-off-by: Arthur Heymans <arthur@aheymans.xyz>
Signed-off-by: Kyösti Mälkki <kyosti.malkki@gmail.com>
Reviewed-on: https://review.coreboot.org/20879
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Nico Huber <nico.h@gmx.de>
This commit is contained in:
Arthur Heymans 2017-08-04 14:28:50 +02:00 committed by Kyösti Mälkki
parent 24798a1544
commit 1b04aa2591
1 changed files with 43 additions and 15 deletions

View File

@ -17,6 +17,7 @@
#include <arch/io.h> #include <arch/io.h>
#include <device/smbus_def.h> #include <device/smbus_def.h>
#include <stdlib.h>
#include "smbus.h" #include "smbus.h"
@ -193,14 +194,17 @@ int do_smbus_write_byte(unsigned int smbus_base, u8 device,
} }
int do_smbus_block_read(unsigned int smbus_base, u8 device, u8 cmd, int do_smbus_block_read(unsigned int smbus_base, u8 device, u8 cmd,
unsigned int bytes, u8 *buf) unsigned int max_bytes, u8 *buf)
{ {
u8 status; u8 status;
int slave_bytes;
int bytes_read = 0; int bytes_read = 0;
unsigned int loops = SMBUS_TIMEOUT; unsigned int loops = SMBUS_TIMEOUT;
if (smbus_wait_until_ready(smbus_base) < 0) if (smbus_wait_until_ready(smbus_base) < 0)
return SMBUS_WAIT_UNTIL_READY_TIMEOUT; return SMBUS_WAIT_UNTIL_READY_TIMEOUT;
max_bytes = MIN(32, max_bytes);
/* Set up transaction */ /* Set up transaction */
/* Disable interrupts */ /* Disable interrupts */
outb(inb(smbus_base + SMBHSTCTL) & ~SMBHSTCNT_INTREN, outb(inb(smbus_base + SMBHSTCTL) & ~SMBHSTCNT_INTREN,
@ -210,11 +214,15 @@ int do_smbus_block_read(unsigned int smbus_base, u8 device, u8 cmd,
/* Set the command/address... */ /* Set the command/address... */
outb(cmd & 0xff, smbus_base + SMBHSTCMD); outb(cmd & 0xff, smbus_base + SMBHSTCMD);
/* Set up for a block data read */ /* Set up for a block data read */
outb((inb(smbus_base + SMBHSTCTL) & 0xe3) | I801_BLOCK_DATA, outb((inb(smbus_base + SMBHSTCTL) & 0xc3) | I801_BLOCK_DATA,
(smbus_base + SMBHSTCTL)); (smbus_base + SMBHSTCTL));
/* Clear any lingering errors, so the transaction will run */ /* Clear any lingering errors, so the transaction will run */
outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT); outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT);
/* Reset number of bytes to transfer so we notice later it
* was really updated with the transaction. */
outb(0, smbus_base + SMBHSTDAT0);
/* Start the command */ /* Start the command */
outb((inb(smbus_base + SMBHSTCTL) | SMBHSTCNT_START), outb((inb(smbus_base + SMBHSTCTL) | SMBHSTCNT_START),
smbus_base + SMBHSTCTL); smbus_base + SMBHSTCTL);
@ -233,28 +241,39 @@ int do_smbus_block_read(unsigned int smbus_base, u8 device, u8 cmd,
return SMBUS_ERROR; return SMBUS_ERROR;
if (status & SMBHSTSTS_BYTE_DONE) { /* Byte done */ if (status & SMBHSTSTS_BYTE_DONE) { /* Byte done */
if (bytes_read < max_bytes) {
*buf = inb(smbus_base + SMBBLKDAT); *buf = inb(smbus_base + SMBBLKDAT);
buf++; buf++;
bytes_read++; bytes_read++;
if (--bytes == 1) {
/* indicate that next byte is the last one */
outb(inb(smbus_base + SMBHSTCTL)
| SMBHSTCNT_LAST_BYTE,
smbus_base + SMBHSTCTL);
} }
/* Engine internally completes the transaction
* and clears HOST_BUSY flag once the byte count
* from slave is reached.
*/
outb(status, smbus_base + SMBHSTSTAT); outb(status, smbus_base + SMBHSTSTAT);
} }
} while ((status & SMBHSTSTS_HOST_BUSY) && loops); } while ((status & SMBHSTSTS_HOST_BUSY) && loops);
/* Post-check we received complete message. */
slave_bytes = inb(smbus_base + SMBHSTDAT0);
if (bytes_read < slave_bytes)
return SMBUS_ERROR;
return bytes_read; return bytes_read;
} }
int do_smbus_block_write(unsigned int smbus_base, u8 device, u8 cmd, int do_smbus_block_write(unsigned int smbus_base, u8 device, u8 cmd,
unsigned int bytes, const u8 *buf) const unsigned int bytes, const u8 *buf)
{ {
u8 status; u8 status;
int bytes_sent = 0;
unsigned int loops = SMBUS_TIMEOUT; unsigned int loops = SMBUS_TIMEOUT;
if (bytes > 32)
return SMBUS_ERROR;
if (smbus_wait_until_ready(smbus_base) < 0) if (smbus_wait_until_ready(smbus_base) < 0)
return SMBUS_WAIT_UNTIL_READY_TIMEOUT; return SMBUS_WAIT_UNTIL_READY_TIMEOUT;
@ -267,7 +286,7 @@ int do_smbus_block_write(unsigned int smbus_base, u8 device, u8 cmd,
/* Set the command/address... */ /* Set the command/address... */
outb(cmd & 0xff, smbus_base + SMBHSTCMD); outb(cmd & 0xff, smbus_base + SMBHSTCMD);
/* Set up for a block data write */ /* Set up for a block data write */
outb((inb(smbus_base + SMBHSTCTL) & 0xe3) | I801_BLOCK_DATA, outb((inb(smbus_base + SMBHSTCTL) & 0xc3) | I801_BLOCK_DATA,
(smbus_base + SMBHSTCTL)); (smbus_base + SMBHSTCTL));
/* Clear any lingering errors, so the transaction will run */ /* Clear any lingering errors, so the transaction will run */
outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT); outb(inb(smbus_base + SMBHSTSTAT), smbus_base + SMBHSTSTAT);
@ -275,8 +294,10 @@ int do_smbus_block_write(unsigned int smbus_base, u8 device, u8 cmd,
/* set number of bytes to transfer */ /* set number of bytes to transfer */
outb(bytes, smbus_base + SMBHSTDAT0); outb(bytes, smbus_base + SMBHSTDAT0);
/* Send first byte from buffer, bytes_sent increments after
* hardware acknowledges it.
*/
outb(*buf++, smbus_base + SMBBLKDAT); outb(*buf++, smbus_base + SMBBLKDAT);
bytes--;
/* Start the command */ /* Start the command */
outb((inb(smbus_base + SMBHSTCTL) | SMBHSTCNT_START), outb((inb(smbus_base + SMBHSTCTL) | SMBHSTCNT_START),
@ -296,12 +317,19 @@ int do_smbus_block_write(unsigned int smbus_base, u8 device, u8 cmd,
return SMBUS_ERROR; return SMBUS_ERROR;
if (status & SMBHSTSTS_BYTE_DONE) { if (status & SMBHSTSTS_BYTE_DONE) {
bytes_sent++;
if (bytes_sent < bytes)
outb(*buf++, smbus_base + SMBBLKDAT); outb(*buf++, smbus_base + SMBBLKDAT);
/* Engine internally completes the transaction
* and clears HOST_BUSY flag once the byte count
* has been reached.
*/
outb(status, smbus_base + SMBHSTSTAT); outb(status, smbus_base + SMBHSTSTAT);
} }
} while ((status & SMBHSTSTS_HOST_BUSY) && loops); } while ((status & SMBHSTSTS_HOST_BUSY) && loops);
return 0; return bytes_sent;
} }
/* Only since ICH5 */ /* Only since ICH5 */