summaryrefslogtreecommitdiffstats
path: root/src/usr/i2c
diff options
context:
space:
mode:
authorNick Bofferding <bofferdn@us.ibm.com>2019-02-20 22:11:41 -0600
committerDaniel M. Crowell <dcrowell@us.ibm.com>2019-03-08 10:12:18 -0600
commit09a3da7596822d721dee3e69a13adfdd67759588 (patch)
tree4f782633fc309aaecbd08b2465abad20dd47adac /src/usr/i2c
parent6fa8d04529309619414c75a1e975f3f41d46fcd0 (diff)
downloadtalos-hostboot-09a3da7596822d721dee3e69a13adfdd67759588.tar.gz
talos-hostboot-09a3da7596822d721dee3e69a13adfdd67759588.zip
UCD Flash Update: Support I2C SMBUS operations for UCD flash update
- Adds I2C SMBUS operations for UCD flash update - Creates UCD component ID + trace name - Creates stub for UCD flash update entry point Change-Id: Id75cdd137b5a4924998c04bdbdce9218610a4906 RTC: 201992 Reviewed-on: http://rchgit01.rchland.ibm.com/gerrit1/72229 Reviewed-by: Ilya Smirnov <ismirno@us.ibm.com> Tested-by: Jenkins Server <pfd-jenkins+hostboot@us.ibm.com> Tested-by: Jenkins OP Build CI <op-jenkins+hostboot@us.ibm.com> Tested-by: FSP CI Jenkins <fsp-CI-jenkins+hostboot@us.ibm.com> Tested-by: Jenkins OP HW <op-hw-jenkins+hostboot@us.ibm.com> Reviewed-by: Matthew Raybuck <matthew.raybuck@ibm.com> Reviewed-by: Daniel M. Crowell <dcrowell@us.ibm.com>
Diffstat (limited to 'src/usr/i2c')
-rwxr-xr-xsrc/usr/i2c/i2c.C762
-rwxr-xr-xsrc/usr/i2c/i2c.H262
-rw-r--r--src/usr/i2c/i2c_common.H31
3 files changed, 1028 insertions, 27 deletions
diff --git a/src/usr/i2c/i2c.C b/src/usr/i2c/i2c.C
index b61dc8b20..6854d58ac 100755
--- a/src/usr/i2c/i2c.C
+++ b/src/usr/i2c/i2c.C
@@ -58,6 +58,7 @@
#include <i2c/eepromif.H>
#include <i2c/tpmddif.H>
#include <hwas/common/hwas.H> // HwasState
+#include <algorithm>
#ifdef CONFIG_NVDIMM
#include <isteps/nvdimm/nvdimmif.H>
@@ -105,6 +106,102 @@ const TARGETING::ATTR_I2C_BUS_SPEED_ARRAY_type g_var = {{NULL}};
namespace I2C
{
+namespace SMBUS
+{
+
+uint8_t calculatePec(
+ const uint8_t* const i_pData,
+ const size_t i_size)
+{
+ uint8_t pec = 0;
+
+ for (size_t index=0; index<i_size; ++index)
+ {
+ pec ^= i_pData[index];
+ pec = pec ^ (pec<<1) ^ (pec<<2) ^ ((pec&128)?9:0) ^ ((pec&64)?7:0);
+ }
+
+ return pec;
+}
+
+BlockWrite::BlockWrite(
+ const uint8_t i_address,
+ const uint8_t i_commandCode,
+ const uint8_t i_byteCount,
+ const void* const i_pDataBytes,
+ const bool i_usePec)
+
+ : writeAddr(i_address),
+ commandCode(i_commandCode),
+ byteCount(i_byteCount)
+{
+ memcpy(dataBytes,i_pDataBytes,i_byteCount);
+ memset(dataBytes+i_byteCount,0x00,sizeof(dataBytes)-i_byteCount);
+ messageSize = offsetof(I2C::SMBUS::BlockWrite,dataBytes)
+ + byteCount - sizeof(writeAddr);
+ if(i_usePec)
+ {
+ ++messageSize;
+ const auto pec = I2C::SMBUS::calculatePec(
+ reinterpret_cast<uint8_t*>(&writeAddr),
+ messageSize);
+ *(reinterpret_cast<uint8_t*>(&writeAddr)+messageSize)=pec;
+ }
+}
+
+WriteByteOrWord::WriteByteOrWord(
+ const uint8_t i_address,
+ const uint8_t i_commandCode,
+ const uint8_t i_byteCount,
+ const void* const i_pDataBytes,
+ const bool i_usePec)
+ : writeAddr(i_address),
+ commandCode(i_commandCode),
+ byteCount(i_byteCount)
+{
+ assert(((byteCount==1) || (byteCount==2)),
+ "Invalid byte count %d for write byte or write word",
+ byteCount);
+ memcpy(dataBytes,i_pDataBytes,byteCount);
+ memset(dataBytes+byteCount,0x00,sizeof(dataBytes)-byteCount);
+ messageSize = offsetof(I2C::SMBUS::WriteByteOrWord,dataBytes)
+ - offsetof(I2C::SMBUS::WriteByteOrWord,commandCode)
+ + byteCount;
+ if(i_usePec)
+ {
+ // Currently message size does not reflect the address. If we
+ // are adding a PEC byte, that will up the amount of data we need
+ // to transmit. Leverage the preincrement to calculate the PEC over
+ // the right number of bytes.
+ const auto pec = I2C::SMBUS::calculatePec(
+ reinterpret_cast<uint8_t*>(&writeAddr),
+ ++messageSize);
+ *(dataBytes+byteCount)=pec;
+ }
+}
+
+SendByte::SendByte(const uint8_t i_address,
+ const void* const i_pDataByte,
+ const bool i_usePec)
+ : writeAddr(i_address),
+ dataByte(*reinterpret_cast<const uint8_t*>(i_pDataByte))
+{
+ messageSize = offsetof(I2C::SMBUS::SendByte,pec)
+ - offsetof(I2C::SMBUS::SendByte,dataByte);
+ if(i_usePec)
+ {
+ // Currently message size does not reflect the address. If we
+ // are adding a PEC byte, that will up the amount of data we need
+ // to transmit. Leverage the preincrement to calculate the PEC over
+ // the right number of bytes.
+ pec = I2C::SMBUS::calculatePec(
+ reinterpret_cast<uint8_t*>(&writeAddr),
+ ++messageSize);
+ }
+}
+
+} // End SMBUS namespace
+
// Register the generic I2C perform Op with the routing code for Procs.
DEVICE_REGISTER_ROUTE( DeviceFW::WILDCARD,
DeviceFW::I2C,
@@ -159,17 +256,20 @@ void dumpMiscArgsData(const I2C::misc_args_t & i_args)
l_muxPath = i_args.i2cMuxPath->toString();
}
- TRACFCOMP( g_trac_i2c,"dumpMiscArgsData: "
- "port(%d), engine(%d), devAddr(0x%X), skip_mode_step(%d), "
- "with_stop(%d), read_not_write(%d), bus_speed(%d)",
- i_args.port, i_args.engine, i_args.devAddr,
+ TRACFCOMP( g_trac_i2c,"dumpMiscArgsData: subop(0x%016llX), "
+ "engine(%d), port(%d), devAddr(0x%X), skip_mode_step(%d), "
+ "with_stop(%d), read_not_write(%d), with_address(%d), "
+ "with_start(%d)",
+ i_args.subop,i_args.engine, i_args.port, i_args.devAddr,
i_args.skip_mode_setup, i_args.with_stop,
- i_args.read_not_write, i_args.bus_speed );
+ i_args.read_not_write, i_args.with_address,
+ i_args.with_start);
- TRACFCOMP( g_trac_i2c,"dumpMiscArgsData cont.: "
- "bit_rate_divisor(%d), polling_interval_ns(%d), "
+ TRACFCOMP( g_trac_i2c,"dumpMiscArgsData cont.: read_continue(%d), "
+ "bus_speed(%d), bit_rate_divisor(%d), polling_interval_ns(%d), "
"timeout_count(%d), offset_length(%d), offset_buffer(%p/0x%X)",
- i_args.bit_rate_divisor, i_args.polling_interval_ns,
+ i_args.read_continue,i_args.bus_speed,i_args.bit_rate_divisor,
+ i_args.polling_interval_ns,
i_args.timeout_count, i_args.offset_length,
i_args.offset_buffer,
(0x0 == i_args.offset_buffer ? 0 : *(i_args.offset_buffer) ) );
@@ -208,6 +308,11 @@ errlHndl_t i2cPerformOp( DeviceFW::OperationType i_opType,
// Address, Port, Engine, Device Addr.
// Other args set below
misc_args_t args;
+
+ // Read in the sub-operation
+ const auto subop =
+ static_cast<DeviceFW::I2C_SUBOP>(va_arg(i_args,uint64_t));
+
args.port = va_arg( i_args, uint64_t );
args.engine = va_arg( i_args, uint64_t );
args.devAddr = va_arg( i_args, uint64_t );
@@ -287,17 +392,42 @@ errlHndl_t i2cPerformOp( DeviceFW::OperationType i_opType,
// Else this is not a page operation, call the normal common function
else
{
- args.offset_length = va_arg( i_args, uint64_t);
- uint8_t* temp = reinterpret_cast<uint8_t*>(va_arg(i_args, uint64_t));
- args.i2cMuxBusSelector = va_arg( i_args, uint64_t);
- args.i2cMuxPath = reinterpret_cast<const TARGETING::EntityPath*>(va_arg(i_args, uint64_t));
-
- if ( args.offset_length != 0 )
+ if( (subop==DeviceFW::I2C_SMBUS_BLOCK)
+ || (subop==DeviceFW::I2C_SMBUS_BYTE)
+ || (subop==DeviceFW::I2C_SMBUS_WORD))
+ {
+ args.smbus.commandCode =
+ static_cast<decltype(args.smbus.commandCode)>(
+ va_arg(i_args,uint64_t));
+ args.smbus.usePec = true; // All implementations use PEC
+ args.i2cMuxBusSelector = va_arg(i_args,uint64_t);
+ args.i2cMuxPath = reinterpret_cast<const TARGETING::EntityPath*>(
+ va_arg(i_args, uint64_t));
+ }
+ else if(subop==DeviceFW::I2C_SMBUS_SEND_OR_RECV)
+ {
+ args.smbus.usePec = true; // All implementations use PEC
+ args.i2cMuxBusSelector = va_arg(i_args,uint64_t);
+ args.i2cMuxPath = reinterpret_cast<const TARGETING::EntityPath*>(
+ va_arg(i_args, uint64_t));
+ }
+ else // Standard ops
{
- args.offset_buffer = temp;
+ args.offset_length = va_arg( i_args, uint64_t);
+ uint8_t* temp = reinterpret_cast<uint8_t*>(
+ va_arg(i_args, uint64_t));
+ args.i2cMuxBusSelector = va_arg( i_args, uint64_t);
+ args.i2cMuxPath = reinterpret_cast<const TARGETING::EntityPath*>(
+ va_arg(i_args, uint64_t));
+ if ( args.offset_length != 0 )
+ {
+ args.offset_buffer = temp;
+ }
}
+ args.subop=subop;
+
err = i2cCommonOp( i_opType,
i_target,
io_buffer,
@@ -1240,9 +1370,594 @@ errlHndl_t i2cCommonOp( DeviceFW::OperationType i_opType,
/*******************************************************/
/***********************************************/
+ /* I2C SMBUS Send Byte */
+ /***********************************************/
+ if( (i_opType == DeviceFW::WRITE )
+ && (i_args.subop == DeviceFW::I2C_SMBUS_SEND_OR_RECV))
+ {
+ TRACUCOMP(g_trac_i2c, INFO_MRK
+ "I2C SMBUS Send Byte, "
+ "Use PEC = %d.",
+ i_args.smbus.usePec);
+
+ // If requested length is anything other than 1 byte, throw an
+ // error.
+ if(io_buflen != sizeof(uint8_t))
+ {
+ /*@
+ * @errortype
+ * @reasoncode I2C_INVALID_SEND_BYTE_LENGTH
+ * @severity ERRL_SEV_UNRECOVERABLE
+ * @moduleid I2C_PERFORM_OP
+ * @userdata1 Size of request
+ * @devdesc Invalid input buffer length for send byte request
+ * @custdesc Unexpected firmware error
+ */
+ err = new ERRORLOG::ErrlEntry(
+ ERRORLOG::ERRL_SEV_UNRECOVERABLE,
+ I2C_PERFORM_OP,
+ I2C_INVALID_SEND_BYTE_LENGTH,
+ io_buflen,
+ 0,
+ ERRORLOG::ErrlEntry::ADD_SW_CALLOUT);
+
+ err->collectTrace(I2C_COMP_NAME);
+
+ io_buflen = 0;
+
+ break;
+ }
+
+ // Write SMBUS Send Byte command to device.
+ i_args.read_not_write = false;
+ i_args.with_stop = true;
+ i_args.skip_mode_setup = false;
+ i_args.with_address = true;
+ i_args.with_start = true;
+
+ I2C::SMBUS::SendByte sendByte(i_args.devAddr,
+ io_buffer,
+ i_args.smbus.usePec);
+ do {
+
+ size_t writeSize = sendByte.messageSize;
+ const auto writeSizeExp = writeSize;
+ err = i2cWrite(i_target,
+ &sendByte.dataByte,
+ writeSize,
+ i_args);
+ if(err)
+ {
+ break;
+ }
+
+ assert(writeSize == writeSizeExp,
+ "Write size mismatch; expected %d but got %d",
+ writeSizeExp,writeSize);
+
+ io_buflen = sizeof(sendByte.dataByte);
+
+ } while(0);
+
+ if(err)
+ {
+ io_buflen = 0;
+ }
+ }
+ /***********************************************/
+ /* I2C SMBUS Write Byte / Write Word */
+ /***********************************************/
+ else if( (i_opType == DeviceFW::WRITE )
+ && ( (i_args.subop == DeviceFW::I2C_SMBUS_BYTE)
+ || (i_args.subop == DeviceFW::I2C_SMBUS_WORD)))
+ {
+ // Note: The SMBUS spec calls a 2 byte value a "word"
+
+ TRACUCOMP(g_trac_i2c, INFO_MRK
+ "I2C SMBUS Write %s: Command code = 0x%02X, "
+ "Use PEC = %d.",
+ i_args.subop == DeviceFW::I2C_SMBUS_BYTE ?
+ "Byte" : "Word",
+ i_args.smbus.commandCode,
+ i_args.smbus.usePec);
+
+ // If requested length is != 1 byte for a write byte transaction,
+ // or != 2 bytes for a write word transaction, throw an error.
+ if( ( (i_args.subop == DeviceFW::I2C_SMBUS_BYTE)
+ && (io_buflen != sizeof(uint8_t)))
+ || ( (i_args.subop == DeviceFW::I2C_SMBUS_WORD)
+ && (io_buflen != sizeof(uint16_t))))
+ {
+ /*@
+ * @errortype
+ * @reasoncode I2C_INVALID_WRITE_BYTE_OR_WORD_LENGTH
+ * @severity ERRL_SEV_UNRECOVERABLE
+ * @moduleid I2C_PERFORM_OP
+ * @userdata1 Size of request
+ * @userdata2 Sub-op
+ * @devdesc Invalid input buffer length for write byte or
+ * write word request
+ * @custdesc Unexpected firmware error
+ */
+ err = new ERRORLOG::ErrlEntry(
+ ERRORLOG::ERRL_SEV_UNRECOVERABLE,
+ I2C_PERFORM_OP,
+ I2C_INVALID_WRITE_BYTE_OR_WORD_LENGTH,
+ io_buflen,
+ i_args.subop,
+ ERRORLOG::ErrlEntry::ADD_SW_CALLOUT);
+
+ err->collectTrace(I2C_COMP_NAME);
+
+ io_buflen = 0;
+
+ break;
+ }
+
+ // Write SMBUS Write Byte or Write Word command to device.
+ i_args.read_not_write = false;
+ i_args.with_stop = true;
+ i_args.skip_mode_setup = false;
+ i_args.with_address = true;
+ i_args.with_start = true;
+
+ uint8_t bytes = (i_args.subop == DeviceFW::I2C_SMBUS_BYTE) ?
+ sizeof(uint8_t) : sizeof(uint16_t);
+ I2C::SMBUS::WriteByteOrWord writeByteOrWord(i_args.devAddr,
+ i_args.smbus.commandCode,
+ bytes,
+ io_buffer,
+ i_args.smbus.usePec);
+ do {
+
+ size_t writeSize = writeByteOrWord.messageSize;
+ const auto writeSizeExp = writeSize;
+ err = i2cWrite(i_target,
+ &writeByteOrWord.commandCode,
+ writeSize,
+ i_args);
+ if(err)
+ {
+ break;
+ }
+
+ assert(writeSize == writeSizeExp,
+ "Write size mismatch; expected %d but got %d",
+ writeSizeExp,writeSize);
+
+ io_buflen = writeByteOrWord.byteCount;
+
+ } while(0);
+
+ if(err)
+ {
+ io_buflen = 0;
+ }
+ }
+ /***********************************************/
+ /* I2C SMBUS Block Write */
+ /***********************************************/
+ else if( (i_opType == DeviceFW::WRITE )
+ && (i_args.subop == DeviceFW::I2C_SMBUS_BLOCK))
+ {
+ TRACUCOMP(g_trac_i2c, INFO_MRK
+ "I2C SMBUS Block Write: Command code = 0x%02X, "
+ "Use PEC = %d.",
+ i_args.smbus.commandCode,
+ i_args.smbus.usePec);
+
+ // If requested length is for < 1 byte or > 255 bytes for a block
+ // write transaction, throw an error.
+ if( (!io_buflen)
+ || (io_buflen > UINT8_MAX) )
+ {
+ /*@
+ * @errortype
+ * @reasoncode I2C_INVALID_BLOCK_WRITE_LENGTH
+ * @severity ERRL_SEV_UNRECOVERABLE
+ * @moduleid I2C_PERFORM_OP
+ * @userdata1 Size of request
+ * @devdesc Invalid input buffer length for block write
+ * request
+ * @custdesc Unexpected firmware error
+ */
+ err = new ERRORLOG::ErrlEntry(
+ ERRORLOG::ERRL_SEV_UNRECOVERABLE,
+ I2C_PERFORM_OP,
+ I2C_INVALID_BLOCK_WRITE_LENGTH,
+ io_buflen,
+ 0,
+ ERRORLOG::ErrlEntry::ADD_SW_CALLOUT );
+
+ err->collectTrace(I2C_COMP_NAME);
+
+ io_buflen = 0;
+
+ break;
+ }
+
+ // Write SMBUS block write command to device.
+ i_args.read_not_write = false;
+ i_args.with_stop = true;
+ i_args.skip_mode_setup = false;
+ i_args.with_address = true;
+ i_args.with_start = true;
+
+ I2C::SMBUS::BlockWrite blockWrite(i_args.devAddr,
+ i_args.smbus.commandCode,
+ io_buflen,
+ io_buffer,
+ i_args.smbus.usePec);
+ do {
+
+ size_t writeSize = blockWrite.messageSize;
+ const auto writeSizeExp = writeSize;
+ err = i2cWrite(i_target,
+ &blockWrite.commandCode,
+ writeSize,
+ i_args);
+ if(err)
+ {
+ break;
+ }
+
+ assert(writeSize == writeSizeExp,
+ "Write size mismatch; expected %d but got %d",
+ writeSizeExp,writeSize);
+
+ io_buflen = blockWrite.byteCount;
+
+ } while(0);
+
+ if(err)
+ {
+ io_buflen = 0;
+ }
+ }
+ /***********************************************/
+ /* I2C SMBUS Read Word or Byte */
+ /***********************************************/
+ else if( (i_opType == DeviceFW::READ )
+ && ( (i_args.subop == DeviceFW::I2C_SMBUS_BYTE)
+ || (i_args.subop == DeviceFW::I2C_SMBUS_WORD)))
+ {
+ // Note: The SMBUS spec calls a 2 byte value a "word"
+
+ TRACUCOMP(g_trac_i2c, INFO_MRK
+ "I2C SMBUS Read %s: Command code = 0x%02X, "
+ "Use PEC = %d",
+ i_args.subop == DeviceFW::I2C_SMBUS_BYTE ?
+ "Byte" : "Word",
+ i_args.smbus.commandCode,
+ i_args.smbus.usePec);
+
+ // If requested length is != 1 byte for a read byte transaction,
+ // or != 2 bytes for a read word transaction, throw an error.
+ if( ( (i_args.subop == DeviceFW::I2C_SMBUS_BYTE)
+ && (io_buflen != sizeof(uint8_t)))
+ || ( (i_args.subop == DeviceFW::I2C_SMBUS_WORD)
+ && (io_buflen != sizeof(uint16_t))))
+ {
+ /*@
+ * @errortype
+ * @reasoncode I2C_INVALID_READ_BYTE_OR_WORD_LENGTH
+ * @severity ERRL_SEV_UNRECOVERABLE
+ * @moduleid I2C_PERFORM_OP
+ * @userdata1 Size of request
+ * @userdata2 Sub-op
+ * @devdesc Invalid input buffer length for read byte or
+ * read word request
+ * @custdesc Unexpected firmware error
+ */
+ err = new ERRORLOG::ErrlEntry(
+ ERRORLOG::ERRL_SEV_UNRECOVERABLE,
+ I2C_PERFORM_OP,
+ I2C_INVALID_READ_BYTE_OR_WORD_LENGTH,
+ io_buflen,
+ i_args.subop,
+ ERRORLOG::ErrlEntry::ADD_SW_CALLOUT);
+
+ err->collectTrace(I2C_COMP_NAME);
+
+ io_buflen = 0;
+
+ break;
+ }
+
+ // Write SMBUS Read Byte|Word command to device. Inhibit stop
+ // because the protocol requires a chained read operation without
+ // a stop in between.
+ i_args.read_not_write = false;
+ i_args.with_stop = false;
+ i_args.skip_mode_setup = false;
+ i_args.with_address = true;
+ i_args.with_start = true;
+
+ uint8_t bytes = ( i_args.subop
+ == DeviceFW::I2C_SMBUS_BYTE) ?
+ sizeof(uint8_t) : sizeof(uint16_t);
+
+ I2C::SMBUS::ReadByteOrWord readByteOrWord(i_args.devAddr,
+ i_args.smbus.commandCode,
+ bytes);
+ do {
+
+ size_t commandCodeSize = sizeof(readByteOrWord.commandCode);
+ const auto commandCodeSizeExp = commandCodeSize;
+ err = i2cWrite(i_target,
+ &readByteOrWord.commandCode,
+ commandCodeSize,
+ i_args);
+ if(err)
+ {
+ break;
+ }
+
+ assert(commandCodeSize == commandCodeSizeExp,
+ "Command code write size mismatch; expected %d but got %d",
+ commandCodeSizeExp,commandCodeSize);
+
+ // Now read the required number of data bytes (1 or 2).
+ // If there is no PEC byte, complete the transaction with a stop
+ // and inform the engine there is no subsequent read. If the PEC
+ // byte is supported, withhold the stop and inform the engine there
+ // is an additional read transaction coming. Since this is a
+ // continuation of the original operation but as a read, repeat the
+ // start bit and address.
+ i_args.read_not_write = true;
+ i_args.skip_mode_setup = true;
+ i_args.with_stop = i_args.smbus.usePec ? false : true;
+ i_args.read_continue = i_args.smbus.usePec ? true : false;
+
+ size_t dataBytesSize = readByteOrWord.byteCount;
+ const auto dataBytesSizeExp = dataBytesSize;
+ err = i2cRead(i_target,
+ readByteOrWord.dataBytes,
+ dataBytesSize,
+ i_args);
+ if(err)
+ {
+ break;
+ }
+
+ assert(dataBytesSize == dataBytesSizeExp,
+ "Data bytes read size mismatch; expected %d but got %d",
+ dataBytesSizeExp,dataBytesSize);
+
+ if(i_args.smbus.usePec)
+ {
+ // Read the PEC byte with stop at the end and inform engine
+ // the chained reads are complete
+ i_args.with_stop = true;
+ i_args.read_continue = false;
+ i_args.with_address = false;
+ i_args.with_start = false;
+
+ size_t pecSize=sizeof(readByteOrWord.pec);
+ const auto pecSizeExp=pecSize;
+ err = i2cRead(i_target,
+ &readByteOrWord.pec,
+ pecSize,
+ i_args);
+ if(err)
+ {
+ break;
+ }
+
+ assert(pecSize == pecSizeExp,
+ "PEC byte read size mismatch; expected %d but got %d",
+ pecSizeExp,pecSize);
+
+ const size_t pecBytes =
+ offsetof(I2C::SMBUS::ReadByteOrWord,dataBytes)
+ + readByteOrWord.byteCount;
+ const auto expectedPec = I2C::SMBUS::calculatePec(
+ reinterpret_cast<uint8_t*>(&readByteOrWord),pecBytes);
+ if(readByteOrWord.pec != expectedPec)
+ {
+ TRACFCOMP(g_trac_i2c, ERR_MRK
+ "Bad PEC byte detected; expected 0x%02X "
+ "but received 0x%02X. # PEC bytes = %d",
+ expectedPec,readByteOrWord.pec,pecBytes);
+
+ // @TODO RTC 201990 support bad PEC handling
+ // Right now just ignore it
+ }
+ }
+
+ // Copy the amount of data returned by the remote device, or
+ // the user requested amount, whichever is smaller
+ io_buflen = readByteOrWord.byteCount;
+ memcpy(io_buffer,readByteOrWord.dataBytes,io_buflen);
+
+ } while(0);
+
+ if(err)
+ {
+ io_buflen = 0;
+ }
+ }
+ /***********************************************/
+ /* I2C SMBUS Block Read */
+ /***********************************************/
+ else if( (i_opType == DeviceFW::READ )
+ && (i_args.subop == DeviceFW::I2C_SMBUS_BLOCK))
+ {
+ TRACUCOMP(g_trac_i2c, INFO_MRK
+ "I2C SMBUS Block Read: Command code = 0x%02X, "
+ "Use PEC = %d.",
+ i_args.smbus.commandCode,
+ i_args.smbus.usePec);
+
+ // If requested length is < 1 byte or > 255 bytes for a block
+ // read transaction, throw an error.
+ if( (!io_buflen)
+ || (io_buflen > UINT8_MAX) )
+ {
+ /*@
+ * @errortype
+ * @reasoncode I2C_INVALID_BLOCK_READ_LENGTH
+ * @severity ERRL_SEV_UNRECOVERABLE
+ * @moduleid I2C_PERFORM_OP
+ * @userdata1 Size of request
+ * @devdesc Invalid input buffer length for block read
+ * request
+ * @custdesc Unexpected firmware error
+ */
+ err = new ERRORLOG::ErrlEntry(
+ ERRORLOG::ERRL_SEV_UNRECOVERABLE,
+ I2C_PERFORM_OP,
+ I2C_INVALID_BLOCK_READ_LENGTH,
+ io_buflen,
+ 0,
+ ERRORLOG::ErrlEntry::ADD_SW_CALLOUT );
+
+ err->collectTrace(I2C_COMP_NAME);
+
+ io_buflen = 0;
+
+ break;
+ }
+
+ // Write SMBUS block read command to device. Inhibit stop
+ // because the protocol requires a chained read operation without
+ // a stop in between.
+ i_args.read_not_write = false;
+ i_args.with_stop = false;
+ i_args.skip_mode_setup = false;
+ i_args.with_address = true;
+ i_args.with_start = true;
+
+ I2C::SMBUS::BlockRead blockRead(i_args.devAddr,
+ i_args.smbus.commandCode);
+
+ do {
+
+ size_t commandCodeSize = sizeof(i_args.smbus.commandCode);
+ const auto commandCodeSizeExp = commandCodeSize;
+ err = i2cWrite(i_target,
+ &i_args.smbus.commandCode,
+ commandCodeSize,
+ i_args);
+ if(err)
+ {
+ break;
+ }
+
+ assert(commandCodeSize == commandCodeSizeExp,
+ "Command code write size mismatch; expected %d but got %d",
+ commandCodeSizeExp,commandCodeSize);
+
+ // Read the block count byte, which indicates how many
+ // bytes (up to 255) the remote device will return as part of the
+ // logical response. Does -not- include PEC byte.
+ // Since it's a continuation of an existing command, send a repeated
+ // start and address, but again do not send the stop because
+ // the read must continue later. Also set read-continue in the
+ // engine so that we can break the full set of reads into multiple
+ // transactions and don't setup the engine again.
+ i_args.read_not_write = true;
+ i_args.read_continue = true;
+ i_args.skip_mode_setup = true;
+
+ size_t blockCountSize = sizeof(blockRead.blockCount);
+ const auto blockCountSizeExp = blockCountSize;
+ err = i2cRead(i_target,
+ &blockRead.blockCount,
+ blockCountSize,
+ i_args);
+ if(err)
+ {
+ break;
+ }
+
+ assert(blockCountSize == blockCountSizeExp,
+ "Block count read size mismatch; expected %d but got %d",
+ blockCountSizeExp,blockCountSize);
+
+ // Now read the specified number of data bytes.
+ // If there is no PEC byte, complete the transaction with a stop
+ // and inform the engine there is no subsequent read. If the PEC
+ // byte is supported, withhold the stop inform the engine there is
+ // an additional read transaction coming. Since this is a
+ // continuation of the read, withhold the start bit and the address.
+ // nor is an address.
+ i_args.with_start = false;
+ i_args.with_address = false;
+ i_args.with_stop = i_args.smbus.usePec ? false : true;
+ i_args.read_continue = i_args.smbus.usePec ? true : false;
+
+ size_t dataBytesSize = blockRead.blockCount;
+ const auto dataBytesSizeExp = dataBytesSize;
+ err = i2cRead(i_target,
+ blockRead.dataBytes,
+ dataBytesSize,
+ i_args);
+ if(err)
+ {
+ break;
+ }
+
+ assert(dataBytesSize == dataBytesSizeExp,
+ "Data bytes read size mismatch; expected %d but got %d",
+ dataBytesSizeExp,dataBytesSize);
+
+ if(i_args.smbus.usePec)
+ {
+ // Read the PEC byte with stop at the end and inform engine
+ // the chained reads are complete
+ i_args.with_stop = true;
+ i_args.read_continue = false;
+
+ size_t pecSize=sizeof(blockRead.pec);
+ const auto pecSizeExp=pecSize;
+ err = i2cRead(i_target,
+ &blockRead.pec,
+ pecSize,
+ i_args);
+ if(err)
+ {
+ break;
+ }
+
+ assert(pecSize == pecSizeExp,
+ "PEC byte read size mismatch; expected %d but got %d",
+ pecSizeExp,pecSize);
+
+ const size_t pecBytes =
+ offsetof(I2C::SMBUS::BlockRead,dataBytes)
+ + blockRead.blockCount;
+ const auto expectedPec = I2C::SMBUS::calculatePec(
+ reinterpret_cast<uint8_t*>(&blockRead),pecBytes);
+ if(blockRead.pec != expectedPec)
+ {
+ TRACFCOMP(g_trac_i2c, ERR_MRK
+ "Bad PEC byte detected; expected 0x%02X "
+ "but received 0x%02X. # PEC bytes = %d",
+ expectedPec,blockRead.pec,pecBytes);
+
+ // @TODO RTC 201990 support bad PEC handling
+ // Right now just ignore it
+ }
+ }
+
+ // Copy the amount of data returned by the remote device, or
+ // the user requested amount, whichever is smaller
+ io_buflen = std::min(io_buflen,
+ static_cast<size_t>(blockRead.blockCount));
+ memcpy(io_buffer,blockRead.dataBytes,io_buflen);
+
+ } while(0);
+
+ if(err)
+ {
+ io_buflen = 0;
+ }
+ }
+ /***********************************************/
/* I2C Read with Offset */
/***********************************************/
- if( i_opType == DeviceFW::READ &&
+ else if( i_opType == DeviceFW::READ &&
i_args.offset_length != 0 )
{
// First WRITE offset to device without a stop
@@ -2027,9 +2742,11 @@ errlHndl_t i2cSetup ( TARGETING::Target * i_target,
errlHndl_t err = NULL;
TRACUCOMP( g_trac_i2c,
- ENTER_MRK"i2cSetup(): buf_len=%d, r_nw=%d, w_stop=%d, sms=%d",
- i_buflen, i_args.read_not_write, i_args.with_stop,
- i_args.skip_mode_setup);
+ ENTER_MRK"i2cSetup(): buf_len=%d, r_nw=%d, w_start=%d, "
+ "w_address=%d, w_stop=%d, read_continue=%d, sms=%d",
+ i_buflen, i_args.read_not_write, i_args.with_start,
+ i_args.with_address, i_args.with_stop,
+ i_args.read_continue,i_args.skip_mode_setup);
// Define the registers that we'll use
mode_reg_t mode;
@@ -2104,13 +2821,16 @@ errlHndl_t i2cSetup ( TARGETING::Target * i_target,
// Write Command Register:
// - with start
+ // - with continue
// - with stop
+ // - with address
// - RnW
// - length
cmd.value = 0x0ull;
- cmd.with_start = 1;
+ cmd.with_start = (i_args.with_start ? 1 : 0);
+ cmd.read_continue = (i_args.read_continue ? 1 : 0);
cmd.with_stop = (i_args.with_stop ? 1 : 0);
- cmd.with_addr = 1;
+ cmd.with_addr = (i_args.with_address ? 1 : 0);
// cmd.device_addr is 7 bits
// devAddr though is a uint64_t
diff --git a/src/usr/i2c/i2c.H b/src/usr/i2c/i2c.H
index baf95df32..5b69b302e 100755
--- a/src/usr/i2c/i2c.H
+++ b/src/usr/i2c/i2c.H
@@ -63,6 +63,15 @@ enum
PAGE_ONE_ADDR = 0x6E,
};
+/**
+ * @brief Value of the rightmost (7th) bit of an I2C address, which on the
+ * wire indicates whether the operation is a read or a write
+ */
+enum I2C_OP_DIRECTION : uint8_t
+{
+ WRITE = 0x00, ///< Write operation
+ READ = 0x01, ///< Read operation
+};
/**
* @brief FIFO size (width) in bytes. This dictates how many bytes
@@ -959,6 +968,259 @@ void setLogicalFsiEnginePort(size_t &io_logical_engine,
void addHwCalloutsI2c(errlHndl_t i_err,
TARGETING::Target * i_target,
const misc_args_t & i_args);
+
+namespace SMBUS
+{
+
+/**
+ * @brief Calculates a packet error code (PEC) over the specified number of
+ * bytes at the specified address.
+ *
+ * @par Detailed Description:
+ * Calculates a packet error code (PEC) over the specified number of bytes
+ * at the specified address. Specifically, it applies a CRC-8 algorithm, a
+ * very common/simple CRC detailed @
+ * https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks
+ *
+ * @param[in] i_pData Pointer to the start of the data; must not be nullptr
+ * @param[in] i_size Number of bytes to consider when computing the PEC
+ *
+ * return uint8_t Computed PEC byte
+ */
+uint8_t calculatePec(
+ const uint8_t* const i_pData,
+ const size_t i_size);
+
+/**
+ * @brief Structure which tracks the Send Byte
+ * transaction to assist in calculating the PEC byte at the end (if
+ * applicable)
+ */
+struct SendByte
+{
+ // The following fields are known prior to the transaction:
+
+ // The remote device's address which starts the transaction;
+ // RW bit will be 0
+ uint8_t writeAddr;
+
+ // Data byte to send
+ uint8_t dataByte;
+
+ // PEC byte
+ uint8_t pec;
+
+ // Size of message to send, excluding address byte, including PEC byte if
+ // applicable
+ size_t messageSize;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] i_address Address of the remote device
+ * @param[in] i_pDataByte Pointer to byte to send. Must not be nullptr.
+ * @param[in] i_usePec Whether to suffix transaction with PEC byte or not
+ */
+ SendByte( uint8_t i_address,
+ const void* i_pDataByte,
+ bool i_usePec);
+} PACKED;
+
+/**
+ * @brief Structure which tracks the Write Byte or Write Word
+ * transaction to assist in calculating the PEC byte at the end (if
+ * applicable)
+ */
+struct WriteByteOrWord
+{
+ // The following fields are known prior to the transaction:
+
+ // The remote device's address which starts the transaction;
+ // RW bit will be 0
+ uint8_t writeAddr;
+
+ // The PMBUS command code to send as part of the write
+ uint8_t commandCode;
+
+ // Data bytes the originator intends to send.
+ // 1 for Write byte
+ // 2 for Write word
+ // One more byte is added in case there is a PEC byte.
+ uint8_t dataBytes[sizeof(uint16_t)+sizeof(uint8_t)];
+
+ // How many data bytes (excluding PEC) the originator intends to send
+ uint8_t byteCount;
+
+ // Size of message to send, excluding address byte, including PEC byte if
+ // applicable
+ size_t messageSize;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] i_address Address of the remote device
+ * @param[in] i_commandCode PMBUS command code to execute
+ * @param[in] i_byteCount Number of data bytes to send (1 or 2)
+ * @param[in] i_pDataBytes Pointer to byte stream to send. Must not be
+ * nullptr
+ * @param[in] i_usePec Whether to suffix transaction with PEC byte or not
+ */
+ WriteByteOrWord( uint8_t i_address,
+ uint8_t i_commandCode,
+ uint8_t i_byteCount,
+ const void* i_pDataBytes,
+ bool i_usePec);
+} PACKED;
+
+/**
+ * @brief Structure which tracks the block write transaction to assist in
+ * calculating the PEC byte at the end
+ */
+struct BlockWrite
+{
+ // The following fields are known prior to the transaction:
+
+ // The remote device's address which starts the transaction;
+ // RW bit will be 0
+ uint8_t writeAddr;
+
+ // The PMBUS command code to send as part of the write
+ uint8_t commandCode;
+
+ // How many data bytes (excluding PEC) the originator intends to send
+ uint8_t byteCount;
+
+ // Data bytes the originator intends to send. Max 255. One more byte is
+ // added in case there is a PEC byte
+ uint8_t dataBytes[UINT8_MAX+sizeof(uint8_t)];
+
+ // Size of message to send, excluding address byte, including PEC byte if
+ // applicable
+ size_t messageSize;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] i_address Address of the remote device
+ * @param[in] i_commandCode PMBUS command code to execute
+ * @param[in] i_byteCount Number of data bytes to send
+ * @param[in] i_pDataBytes Pointer to byte stream to send. Must not be
+ * nullptr
+ * @param[in] i_usePec Whether to suffix transaction with PEC byte or not
+ */
+ BlockWrite( uint8_t i_address,
+ uint8_t i_commandCode,
+ uint8_t i_byteCount,
+ const void* i_pDataBytes,
+ bool i_usePec);
+
+} PACKED;
+
+/**
+ * @brief Structure which tracks the read byte|word transaction to assist in
+ * calculating the PEC byte at the end
+ */
+struct ReadByteOrWord
+{
+ // The following fields are known prior to the transaction:
+
+ // The remote device's address which starts the transaction;
+ // RW bit will be 0
+ uint8_t writeAddr;
+
+ // The PMBUS command code to send as part of the write
+ uint8_t commandCode;
+
+ // The remote device's address (sent after repeated start);
+ // RW bit will be 1
+ uint8_t readAddr;
+
+ // The following fields are filled in during the transaction:
+
+ // Data bytes (1 if read byte, 2 if read word) returned by the remote device
+ uint8_t dataBytes[sizeof(uint16_t)];
+
+ // PEC byte returned by the remote device (if supported)
+ uint8_t pec;
+
+ // # data byte requested (1 or 2)
+ uint8_t byteCount;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] i_address Address of the remote device
+ * @param[in] i_commandCode PMBUS command code to execute
+ * @param[in] i_byteCount Number of bytes to read (1 or 2)
+ */
+ ReadByteOrWord(uint8_t i_address,
+ uint8_t i_commandCode,
+ uint8_t i_byteCount)
+ : writeAddr(i_address),
+ commandCode(i_commandCode),
+ readAddr(i_address | I2C_OP_DIRECTION::READ),
+ pec(0),
+ byteCount(i_byteCount)
+ {
+ assert(((byteCount==1) || (byteCount==2)),
+ "Invalid byte count %d for read byte or read word", byteCount);
+ memset(dataBytes,0x00,sizeof(dataBytes));
+ }
+
+} PACKED;
+
+/**
+ * @brief Structure which tracks the block read transaction to assist in
+ * calculating the PEC byte at the end
+ */
+struct BlockRead
+{
+ // The following fields are known prior to the transaction:
+
+ // The remote device's address which starts the transaction;
+ // RW bit will be 0
+ uint8_t writeAddr;
+
+ // The PMBUS command code to send as part of the write
+ uint8_t commandCode;
+
+ // The remote device's address (sent after repeated start);
+ // RW bit will be 1
+ uint8_t readAddr;
+
+ // The following fields are filled in during the transaction:
+
+ // How many data bytes (excluding PEC) the remote device intends
+ // to return.
+ uint8_t blockCount;
+
+ // Data bytes (blockCount of them) returned by the remote device
+ uint8_t dataBytes[UINT8_MAX];
+
+ // PEC byte returned by the remote device (if supported)
+ uint8_t pec;
+
+ /**
+ * @brief Constructor
+ *
+ * @param[in] i_address Address of the remote device
+ * @param[in] i_commandCode PMBUS command code to execute
+ */
+ BlockRead(const uint8_t i_address,
+ const uint8_t i_commandCode)
+ : writeAddr(i_address),
+ commandCode(i_commandCode),
+ readAddr(i_address | I2C_OP_DIRECTION::READ),
+ blockCount(0),
+ pec(0)
+ {
+ memset(dataBytes,0x00,sizeof(dataBytes));
+ }
+
+} PACKED;
+
+} // End SMBUS namespace
+
}; // end I2C namespace
#endif // __I2C_H
diff --git a/src/usr/i2c/i2c_common.H b/src/usr/i2c/i2c_common.H
index 61c5472c9..689d4fc8d 100644
--- a/src/usr/i2c/i2c_common.H
+++ b/src/usr/i2c/i2c_common.H
@@ -25,7 +25,7 @@
#ifndef __I2C_COMMON_H
#define __I2C_COMMON_H
-
+#include <devicefw/driverif.H>
/**
* @file i2c_common.H
*
@@ -40,20 +40,32 @@
namespace I2C
{
+struct Smbus_t
+{
+ uint8_t commandCode; // Command for read/write block operations
+ bool usePec; // Whether to use PEC byte or not
+};
+
/**
* @brief Structure used to pass important variables between functions
*/
struct misc_args_t
{
+ DeviceFW::I2C_SUBOP subop; ///< Sub-operation to perform
uint8_t port;
uint8_t engine;
uint64_t devAddr;
bool skip_mode_setup;
bool with_stop;
bool read_not_write;
- uint64_t bus_speed; // in kbits/sec (ie 400KHz)
- uint16_t bit_rate_divisor; // uint16_t to match size in mode register
- uint64_t polling_interval_ns; // in nanoseconds
+ bool with_address; ///< Send address
+ bool with_start; ///< Send start bit
+ bool read_continue; ///< Allow one logical read across multiple I2C
+ ///< ops. Set when next read will be tied to
+ ///< current read
+ uint64_t bus_speed; ///< in kbits/sec (ie 400KHz)
+ uint16_t bit_rate_divisor; ///< uint16_t to match size in mode register
+ uint64_t polling_interval_ns; ///< in nanoseconds
uint64_t timeout_count;
uint64_t offset_length;
uint8_t* offset_buffer;
@@ -62,12 +74,18 @@ struct misc_args_t
TARGETING::I2cSwitches switches;
- misc_args_t():port(0xFF),
+ Smbus_t smbus;
+
+ misc_args_t():subop(DeviceFW::I2C_STANDARD),
+ port(0xFF),
engine(0xFF),
devAddr(0xFFFFFFFF),
skip_mode_setup(false),
with_stop(true),
read_not_write(true),
+ with_address(true),
+ with_start(true),
+ read_continue(false),
bus_speed(0),
bit_rate_divisor(0),
polling_interval_ns(0),
@@ -75,7 +93,8 @@ struct misc_args_t
offset_length(0),
offset_buffer(nullptr),
i2cMuxBusSelector(I2C_MUX::NOT_APPLICABLE),
- i2cMuxPath(nullptr)
+ i2cMuxPath(nullptr),
+ smbus( {0,true} )
{
memset(&switches, 0x0, sizeof(switches));
};
OpenPOWER on IntegriCloud