Broadcom/Avago/LSI HBA Card: Monitoring IOC Temperature

SleepyTami

Cadet
Joined
Aug 25, 2021
Messages
2
Hey Guys,

Just thought this simple app might help a few of you. Unfortunately, I don't have time to create a plugin for Truenas Core but it would be nice if someone else did.

Enjoy!!


Code:
/*
 * Broadcom/Avago/LSI HBA/RAID cards are made to be used in servers where there's plenty of airflow (minimum of 200 linear feet per minute (LFM)).
 * These conditions are not always attainable in Desktop PCs without generating a considerable amount of noise.
 * In this scenario, there's a need of constantly monitoring the IOC core temperature to dynamically regulate fan speed.
 *
 * Unfortunately, those idiotic devs at Broadcom couldn't be bothered to add this feature to "sas3ircu" so a DIY approach had to be followed.
 *
 * The IOC has internal sensors that measure the temperature of each core.
 *
 * This very simple PoC displays the IOC core temperature (MAX(Sensor[0->N-1])) for Broadcom/Avago/LSI HBA cards. No kernel mods required.
 *
 * Tested against:
 *
 * Broadcom/Avago/LSI 9305-16i 12Gb/s HBA
 *
 * Tested on:
 *
 * FreeBSD 13.0-RELEASE
 *
 * Compile with:
 *
 * gcc lsi_temp.c -o lsi_temp
 *
 * Output:
 *
 * ./lsi_temp /dev/mpr0
 * IOC Temp: 43 (C)
 * Board Temp: 0 (N/A)
 *
 * tami@ombra.org.uk
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <fcntl.h>

#define MPI2_IOCSTATUS_MASK (0x7FFF)
#define MPI2_IOCSTATUS_SUCCESS (0x0000)
#define MPRIO_READ_CFG_PAGE _IOWR('M', 201, struct mpr_cfg_page_req)
#define MPRIO_READ_CFG_HEADER _IOWR('M', 200, struct mpr_cfg_page_req)
#define MPI2_CONFIG_PAGETYPE_IO_UNIT (0x00)
#define MPI2_IOUNITPAGE7_PAGEVERSION (0x05)
#define MPI2_IOUNITPAGE7_IOC_TEMP_NOT_PRESENT (0x00)
#define MPI2_IOUNITPAGE7_IOC_TEMP_FAHRENHEIT (0x01)
#define MPI2_IOUNITPAGE7_IOC_TEMP_CELSIUS (0x02)

typedef struct _MPI2_CONFIG_PAGE_HEADER
{
  uint8_t PageVersion;
  uint8_t PageLength;
  uint8_t PageNumber;
  uint8_t PageType;
} MPI2_CONFIG_PAGE_HEADER, Mpi2ConfigPageHeader_t;

struct mpr_cfg_page_req
{
  MPI2_CONFIG_PAGE_HEADER header;
  uint32_t page_address;
  void *buf;
  int len;
  uint16_t ioc_status;
};

typedef struct _MPI2_CONFIG_PAGE_IO_UNIT_7
{
  MPI2_CONFIG_PAGE_HEADER Header;
  uint8_t CurrentPowerMode;
  uint8_t PreviousPowerMode;
  uint8_t PCIeWidth;
  uint8_t PCIeSpeed;
  uint32_t ProcessorState;
  uint32_t PowerManagementCapabilities;
  uint16_t IOCTemperature;
  uint8_t IOCTemperatureUnits;
  uint8_t IOCSpeed;
  uint16_t BoardTemperature;
  uint8_t BoardTemperatureUnits;
  uint8_t Reserved3;
  uint32_t BoardPowerRequirement;
  uint32_t PCISlotPowerAllocation;
  uint8_t Flags;
  uint8_t Reserved6;
  uint16_t Reserved7;
  uint32_t Reserved8;
} MPI2_CONFIG_PAGE_IO_UNIT_7, Mpi2IOUnitPage7_t;

/* Application specific methods */

char *utos (uint8_t unit)
{
  switch (unit)
  {
  case MPI2_IOUNITPAGE7_IOC_TEMP_FAHRENHEIT:
    return "F";
  case MPI2_IOUNITPAGE7_IOC_TEMP_CELSIUS:
    return "C";
  default:
    return "N/A";
  }
}

int main (int argc, char **argv)
{
  int fd = 0, ret = 0;
  struct mpr_cfg_page_req req;
  Mpi2IOUnitPage7_t pg7;

  if (argc != 2)
  {
    printf("%s: </dev/mpr0>\n", argv[0]);
    return 1;
  }

  if(geteuid() != 0)
  {
    printf("Error: Not root!!\n");
    return 1;
  }

  fd = open(argv[1], O_RDWR);

  if(fd == 0)
  {
    printf("Error: Could not open device file!!\n");
    return 1;
  }

  /* Initialize structs */

  memset(&req, 0x00, sizeof(struct mpr_cfg_page_req));
  memset(&pg7, 0x00, sizeof(Mpi2IOUnitPage7_t));

  /* Get config page 7 header */

  req.header.PageType = MPI2_CONFIG_PAGETYPE_IO_UNIT;
  req.header.PageNumber = 7;
  req.header.PageVersion = MPI2_IOUNITPAGE7_PAGEVERSION;
  req.buf = (Mpi2IOUnitPage7_t *) & pg7;
  req.len = sizeof (Mpi2IOUnitPage7_t);

  if(req.buf == NULL)
  {
    printf("Error: Out of memory!!\n");
    close(fd);
    return 1;
  }

  pg7.Header.PageType = MPI2_CONFIG_PAGETYPE_IO_UNIT;
  pg7.Header.PageNumber = 7;
  pg7.Header.PageVersion = MPI2_IOUNITPAGE7_PAGEVERSION;

  ret = ioctl(fd, MPRIO_READ_CFG_HEADER, &req);

  if(ret != 0)
  {
    printf("Error: MPRIO_READ_CFG_HEADER: IOCTL request failed with error num %d!!\n", ret);
    close (fd);
    return 1;
  }

  if((req.ioc_status & MPI2_IOCSTATUS_MASK) != MPI2_IOCSTATUS_SUCCESS)
  {
    printf("Error: MPRIO_READ_CFG_HEADER: Reply ioc_status != 0x0000 (%x)!!\n", req.ioc_status);
    close(fd);
    return 1;
  }

  /* Get config page 7 content */

  memcpy(req.buf, &req.header, sizeof(MPI2_CONFIG_PAGE_HEADER));

  ret = ioctl(fd, MPRIO_READ_CFG_PAGE, &req);

  if(ret != 0)
  {
    printf("Error: MPRIO_READ_CFG_PAGE: IOCTL request failed with error num %d!!\n", ret);
    close(fd);
    return 1;
  }

  if((req.ioc_status & MPI2_IOCSTATUS_MASK) != MPI2_IOCSTATUS_SUCCESS)
  {
    printf("Error: MPRIO_READ_CFG_PAGE: Reply ioc_status != 0x0000 (%x)!!\n", req.ioc_status);
    close(fd);
    return 1;
  }

  /* Display info */

  printf("IOC Temp: %hu (%s)\n", pg7.IOCTemperature, utos(pg7.IOCTemperatureUnits));
  printf("Board Temp: %hu (%s)\n", pg7.BoardTemperature, utos(pg7.BoardTemperatureUnits));

  /* Exit */

  close(fd);

  return 0;
}
 

Ericloewe

Server Wrangler
Moderator
Joined
Feb 15, 2014
Messages
20,194
Very cool. Any idea if this is easily portable to SAS2 cards?
 

SleepyTami

Cadet
Joined
Aug 25, 2021
Messages
2
The mps driver also implements IOCTL commands MPSIO_READ_CFG_PAGE and MPSIO_READ_PAGE so this code should work out of the box on SAS2 cards that have an I/O Controller. Keep in mind page 9 support was added to mpi2_cnfg.h in 2017 by Broadcom so any IT fw released before that date might not support this.
 

dak180

Patron
Joined
Nov 22, 2017
Messages
310
Keep in mind page 9 support was added to mpi2_cnfg.h in 2017 by Broadcom so any IT fw released before that date might not support this.
Code:
Read configuration has been initiated for controller 0
------------------------------------------------------------------------
Controller information
------------------------------------------------------------------------
  Controller type                         : SAS2008
  BIOS version                            : 7.39.02.00
  Firmware version                        : 20.00.07.00

Code:
lsi_temp /dev/mps0
IOC Temp: 0 (N/A)
Board Temp: 0 (N/A)

Is there any tweak that would get this working? I would love to be able to monitor my HBA temps directly.
 

jgreco

Resident Grinch
Joined
May 29, 2011
Messages
18,680
Code:
Read configuration has been initiated for controller 0
------------------------------------------------------------------------
Controller information
------------------------------------------------------------------------
  Controller type                         : SAS2008
  BIOS version                            : 7.39.02.00
  Firmware version                        : 20.00.07.00

Code:
lsi_temp /dev/mps0
IOC Temp: 0 (N/A)
Board Temp: 0 (N/A)

Is there any tweak that would get this working? I would love to be able to monitor my HBA temps directly.

No, there is no tweak that can get this working, because the 2008 does not have a temperature sensor. I believe it was determined at one point that the 2308 *might*. It may be simpler just to query the board with "mpsutil show cfgpage page 7" than to compile a C program to extract this info.
 

dak180

Patron
Joined
Nov 22, 2017
Messages
310
Just thought this simple app might help a few of you. Unfortunately, I don't have time to create a plugin for Truenas Core but it would be nice if someone else did.
Just for clarity's sake any chance you could add a license declaration (mit, bsd)?
 
Top