google coral tpu의 온도 값을 읽어오는 기능을 구현했다. 어떤 흐름으로 구현했는지에 대해서 포스팅할 예정이다.
npu_driver에 ioctl handler, hardware register offset, init 을 해준다.
npu runtime dll에서는 온도 값을 읽는 IOCTL을 래핑 한다.
python gui 에서 npu runtime dll을 로드하여 호출해 준다.
gasket-driver 소스를 참고하였다.
// linux
static int adc_to_millic(int adc) {
return (662 - adc) * 250 + 550;
}
static int millic_to_adc(int millic) {
return (550 - millic) / 250 + 662;
}
하드웨어로부터 읽은 값을 온도 값으로 변환해 주는 공식인 것 같다.
우리 쪽 소스에도 동일하게 추가했다.
// windows
static inline INT32 apex_adc_to_millic(UINT32 adc) {
return (662 - (INT32)adc) * 250 + 550;
}
static inline UINT32 apex_millic_to_adc(INT32 millic) {
return (UINT32)((550 - millic) / 250 + 662);
}
// linux
case ATTR_TEMP:
value = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
APEX_BAR2_REG_OMC0_DC);
value = (value >> 16) & ((1 << 10) - 1); // ★ bits [25:16]
ret = scnprintf(buf, PAGE_SIZE, "%i\n", adc_to_millic(value));
break;
gasket driver의 IOCTL 핸들러 부분이다. 32비트 레지스터 값을 읽은 뒤 온도 부분 10비트만 파싱 하는 내용이다.
// windows
#define APEX_OMC_DC_ADC_SHIFT 16
#define APEX_OMC_DC_ADC_MASK 0x3FF // 10-bit
#define APEX_OMC_DC_ADC_FROM_REG(r) (((UINT32)(r) >> APEX_OMC_DC_ADC_SHIFT) & APEX_OMC_DC_ADC_MASK)
값을 16비트 우측으로 shift 시킨 뒤, 10비트를 가져오는 방법을 매크로로 표현했다.
gasket에서 사용한 초기화 방식을 그대로 모방하였다.
// linux
static void enable_thermal_sensing(struct gasket_dev *gasket_dev) {
// Enable thermal sensor clocks
gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
APEX_BAR2_REG_OMC0_D0, 0x1, 1, 7);
// Enable thermal sensor (ENAD ENVR ENBG)
gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
APEX_BAR2_REG_OMC0_D8, 0x7, 3, 0);
// 100us settle
schedule_timeout(usecs_to_jiffies(100));
// Enable OMC thermal sensor controller
gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
APEX_BAR2_REG_OMC0_DC, 0x1, 1, 0);
}
npu driver의 preparehardware 쪽에 추가하였다.
// windows
{
PVOID bar2 = deviceContext->Bar2BaseAddress;
apex_rmw_register_32(bar2, APEX_REG_OMC0_D0, 1, APEX_OMC_D0_CLK_EN_WIDTH, APEX_OMC_D0_CLK_EN_SHIFT);
apex_rmw_register_32(bar2, APEX_REG_OMC0_D8, APEX_OMC_D8_SENSOR_EN_VAL, APEX_OMC_D8_SENSOR_EN_WIDTH, APEX_OMC_D8_SENSOR_EN_SHIFT);
KeStallExecutionProcessor(APEX_OMC_SENSOR_SETTLE_US);
apex_rmw_register_32(bar2, APEX_REG_OMC0_DC, 1, APEX_OMC_DC_CTRL_EN_WIDTH, APEX_OMC_DC_CTRL_EN_SHIFT);
DbgPrint("[thermal] sensor enabled\n");
}
// windows npu driver
case IOCTL_GET_TEMPERATURE:
{
PDEVICE_CONTEXT pDC = DeviceGetContext(device);
PVOID bar2 = pDC->Bar2BaseAddress;
if (bar2 == NULL) {
status = STATUS_DEVICE_NOT_READY;
break;
}
WDFMEMORY outMem;
IOCTL_GET_TEMPERATURE_OUT* pOut = NULL;
status = WdfRequestRetrieveOutputMemory(Request, &outMem);
if (!NT_SUCCESS(status)) break;
pOut = (IOCTL_GET_TEMPERATURE_OUT*)WdfMemoryGetBuffer(outMem, NULL);
if (pOut == NULL) { status = STATUS_INVALID_PARAMETER; break; }
UINT32 reg = apex_read_register_32(bar2, APEX_REG_OMC0_DC);
UINT32 adc = APEX_OMC_DC_ADC_FROM_REG(reg);
pOut->raw_adc = adc;
pOut->millic = apex_adc_to_millic(adc);
DbgPrint("[thermal] reg=0x%x adc=%u millic=%d\n", reg, adc, pOut->millic); // ← 임시
bytesReturned = sizeof(IOCTL_GET_TEMPERATURE_OUT);
status = STATUS_SUCCESS;
break;
}
32bit 레지스터 값을 읽는다. 읽은 값을 비트 연산하여 온도 값을 읽어준 뒤, 공식에 넣어서 user buffer space에 넘겨주는 코드이다.

하단에 온도가 표시되도록 하였다.
추가로 데모 영상은 아래에서 확인할 수 있다.
user space에서 hardware를 컨트롤하기 위해서 runtime을 통해 요청하고 driver에서는 실제 hardware의 register를 읽어서 다시 userspace로 보내주는 이 과정이 조금은 이해가 되는 것 같다.