windows driver npu hardware 온도 읽어오기

wangki·2026년 5월 19일

windows_driver_npu

목록 보기
10/11

google coral tpu의 온도 값을 읽어오는 기능을 구현했다. 어떤 흐름으로 구현했는지에 대해서 포스팅할 예정이다.


구현 흐름

npu_driver에 ioctl handler, hardware register offset, init 을 해준다.
npu runtime dll에서는 온도 값을 읽는 IOCTL을 래핑 한다.
python gui 에서 npu runtime dll을 로드하여 호출해 준다.


linux driver 소스 참고

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);
}

IOCTL 핸들러

// 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로 보내주는 이 과정이 조금은 이해가 되는 것 같다.

https://github.com/wangki-kyu/npu_driver

0개의 댓글