windows npu driver descriptor ring / status block 을 dma-coherent 메모리로 교체

wangki·2026년 5월 17일

windows_driver_npu

목록 보기
7/15

convert scores가 계속 쓰레기로 나오는 문제, 이번에는 input도 아니고 weight도 아닌 descriptor ring / status block의 cache coherency 를 의심해봤다. libedgetpu (정상 동작)와 우리 driver (비정상)의 메모리 할당 방식이 다른 걸 발견했고, 우리쪽을 libedgetpu 쪽에 맞춰 봤다.


libedgetpu 가 queue / status block 메모리 잡는 방식

먼저 정상 동작하는 libedgetpu가 instruction queue ring 과 status block 을 어떻게 잡는지 확인했다.소스 경로는 driver/kernel/kernel_coherent_allocator.cc 와 windows 전용 driver/kernel/windows/kernel_coherent_allocator_windows.cc 을 참고하면 된다.

 // kernel_coherent_allocator.cc:42~
  ioctl_buffer.enable = 1;
  ioctl_buffer.size   = size_bytes;   // 16KB (queue 4KB + status 4KB + padding)
  ioctl(fd, GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR, &ioctl_buffer);
  dma_address_ = ioctl_buffer.dma_address;   // ← coral.sys 가 dma_alloc_coherent 로 받은 PA

  // windows 전용
  apex_memmap_ioctl.dev_dma_addr = dma_address;
  ioctl(fd, GASKET_IOCTL_MAP_UMDMA_VIEW, &apex_memmap_ioctl);
  return (char*)apex_memmap_ioctl.virtaddr;   // user-mode UC view

요약하면 이렇다

  • libedgetpu user-mode는 coral.sys 한테 IOCTL 로 위임
  • coral.sys (kernel) 가 DMA-coherent (uncached) 메모리를 잡음 (linux의 dma_alloc_coherent 등가)
  • 잡은 메모리를 user-mode 에 UC view로 mmap 해서 돌려줌

chip이 ring descriptor를 fetch 하거나 status block에 DMA write 할 때, CPU cache 와 sync 가 hardware 차원에서 보장 된다.


우리 driver의 문제

반면 우리 npu driver의 Device.c를 보면 두 군데가 그냥 cacheable pool 로 잡혀 있었다.

  // Device.c:593 (변경 전)
  deviceContext->DescRingBase = ExAllocatePoolWithTag(NonPagedPoolNx, PAGE_SIZE, 'DRNG');

  // Device.c:620 (변경 전)
  deviceContext->StatusBlockBase = ExAllocatePoolWithTag(NonPagedPoolNx, PAGE_SIZE, 'SBLK');

NonPagedPoolNx는 nonpaged (page-out 안됨) 이긴 한데 cache attribute 가 cacheable 이다. PCIe device가 직접 DMA write 한 데이터를 CPU가 cache 에서 stale 값 읽을 가능성이 있다.

가설은 두 가지이다.

  1. status block stale read - chip 이 inference 끝나고 completed_head_pointer를 status block 에 write 해도, 우리 driver가 cache 에서 stale 0을 읽어서 completion 감지 못함
  2. descriptor ring stale fetch - 우리가 cache 에만 descriptor 16b 를 write 하고 PCIe로 chip이 PA fetch 했을 때 stale garbage 를 가져감 -> 잘못된 DMA

증상이랑 부합한다.


수정 - alloc / free 교체

 1. DescRing 할당

  // Device.c:593 (변경 후)
  {
      PHYSICAL_ADDRESS lo, hi, none;
      lo.QuadPart   = 0;
      hi.QuadPart   = 0xFFFFFFFFLL;   // < 4GB. chip MMU 32-bit PA 제약
      none.QuadPart = 0;
      deviceContext->DescRingBase = MmAllocateContiguousMemorySpecifyCache(
          PAGE_SIZE, lo, hi, none, MmNonCached);
  }
  if (deviceContext->DescRingBase == NULL) {
      DbgPrint("[%s] Failed to allocate descriptor ring\n", __FUNCTION__);
      return STATUS_INSUFFICIENT_RESOURCES;
  }
  RtlZeroMemory(deviceContext->DescRingBase, PAGE_SIZE);

  hi.QuadPart = 0xFFFFFFFFLL 가 중요하다. chip MMU 가 32-bit PA 만 받기 때문에 4GB 미만 영역에서 잡아야 한다.

  2. StatusBlock 할당

  // Device.c:620 (변경 후)
  {
      PHYSICAL_ADDRESS lo, hi, none;
      lo.QuadPart   = 0;
      hi.QuadPart   = 0xFFFFFFFFLL;
      none.QuadPart = 0;
      deviceContext->StatusBlockBase = MmAllocateContiguousMemorySpecifyCache(
          PAGE_SIZE, lo, hi, none, MmNonCached);
  }
  if (deviceContext->StatusBlockBase == NULL) {
      DbgPrint("[%s] Failed to allocate status block\n", __FUNCTION__);
      MmFreeContiguousMemory(deviceContext->DescRingBase);   // ← 이것도 교체
      deviceContext->DescRingBase = NULL;
      return STATUS_INSUFFICIENT_RESOURCES;
  }

  3. Cleanup path

  ExFreePoolWithTag 였던 free 도 전부 MmFreeContiguousMemory 로 교체. tag 인자가 없어서 더 깔끔하다.

  // Device.c:1149 (변경 후)
  if (deviceContext->DescRingBase != NULL) {
      MmFreeContiguousMemory(deviceContext->DescRingBase);
      deviceContext->DescRingBase = NULL;
  }
  if (deviceContext->StatusBlockBase != NULL) {
      MmFreeContiguousMemory(deviceContext->StatusBlockBase);
      deviceContext->StatusBlockBase = NULL;
  }

결과

여전히 쓰레기 값이 나온다..
또 다음 가설을 설정 후, 테스트해야겠다..
화이팅

0개의 댓글