vnpu 개발기 #13 - 버퍼 객체 mmap과 크기 상한

BO를 사용자 공간에 mmap해서 CPU로 직접 읽고 쓸 수 있게 했다. 드라이버 코드는 한 줄도 없다. accel 장치 파일의 mmap은 이미 DRM 공통 함수(drm_gem_mmap)로 이어져 있고, BO는 shmem 헬퍼의 mmap을 쓴다. mmap에는 12편의 mmap 오프셋을 넘긴다. 커널은 이 오프셋으로 BO를 찾고, 이 fd에 권한이 있는지 본 뒤 매핑한다. 새 BO는 0으로 차 있었고, 쓴 값은 munmap했다가 다시 매핑해도 그대로였다. BO보다 긴 매핑, BO 중간부터의 매핑, MAP_PRIVATE는 EINVAL이다. 오프셋 0과 닫은 BO의 오프셋도 찾을 BO가 없어서 EINVAL이다. 다른 fd의 오프셋은 EACCES다. 오프셋 공간은 장치에 하나라 BO는 찾지만, 그 fd에는 권한이 없다. ...

September 29, 2026 · 2 min · Donggeun Yoo

vnpu 개발기 #12 - 버퍼 객체 정보 읽기

핸들을 주면 BO의 크기와 mmap 오프셋을 돌려주는 BO_INFO ioctl을 넣었다. 11편에서는 1바이트로 만든 BO가 정말 4096바이트가 됐는지 볼 방법이 없었는데, 이제 크기를 읽어 확인할 수 있다. 오프셋은 다음에 BO를 mmap할 때 쓴다. struct drm_vnpu_bo_info { __u32 handle; __u32 pad; __u64 size; __u64 mmap_offset; }; pad는 size를 8바이트 경계에 두려고 채운 칸이고, 0이 아니면 거절한다. ivpu는 BO의 flags와 장치 쪽 주소도 돌려주는데, vnpu에는 정의한 flags도 장치 쪽 주소도 아직 없어서 뺐다. 핸들러는 이 fd의 핸들 표에서 BO를 찾아 크기와 오프셋을 채운다. 못 찾으면 ENOENT다. ...

September 29, 2026 · 1 min · Donggeun Yoo

vnpu 개발기 #11 - 버퍼 객체 만들기

사용자 프로그램이 장치에 넘길 메모리를 만들 수 있게 했다. 이 메모리 덩어리를 버퍼 객체(BO, buffer object)라고 한다. ivpu에서도 작업을 제출할 때 필요한 BO들을 핸들 배열로 넘긴다. BO는 DRM의 메모리 관리자인 GEM(Graphics Execution Manager)이 관리한다. GEM은 BO 안에 뭐가 들었는지는 모르고, 사용자 공간에는 BO를 핸들이라는 번호로 보여 준다. 핸들 표는 장치 파일을 open할 때마다 따로 생긴다. BO 뒤의 메모리는 shmem 헬퍼로 만든다. shmem은 커널 안의 공유 메모리 파일 시스템이고, 사용자 공간에는 tmpfs로 보인다. 헬퍼는 BO마다 그 크기의 shmem 파일을 하나 만들어 둔다. ivpu의 BO도 이 헬퍼 위에 있다. ...

September 29, 2026 · 2 min · Donggeun Yoo

vnpu 개발기 #10 - 장치 리셋

장치가 리셋될 때 IRQ_STATUS를 0으로 되돌리게 했다. 9편에서 만든 이 레지스터는 게스트를 재부팅해도 값이 그대로 남아 있었다. make run은 QEMU를 -no-reboot로 띄우는데, 이러면 게스트가 재부팅할 때 QEMU가 그냥 꺼진다. 그래서 이 옵션만 빼고 QEMU를 직접 띄웠다. 드라이버를 올리면 핸들러가 비트를 지우니까, 드라이버 없이 RAISE에 0x5를 쓰고 재부팅했다. a는 5편과 같이 BAR 0의 시작 주소다. ~ # devmem $((a+8)) 32 0x5 ~ # devmem $((a+4)) 32 0x00000005 ~ # reboot -f [ 14.562993] reboot: Restarting system [ 14.563494] reboot: machine restart 다시 부팅한 뒤 STATUS를 읽었다. ...

September 28, 2026 · 1 min · Donggeun Yoo

vnpu 개발기 #9 - 인터럽트

장치가 드라이버에게 인터럽트를 보낼 수 있게 하는 작업을 시작했다. 앞으로 IPC나 job 완료 알림은 전부 인터럽트로 온다. 인터럽트는 MSI로 보낸다. MSI는 인터럽트 전용 선 대신, 장치가 정해진 주소에 정해진 값을 메모리 쓰기하는 방식이다. QEMU에서도 MSI를 보내는 코드는 결국 메모리 쓰기 한 번이다. 어떤 주소에 어떤 값을 쓸지는 운영체제가 정해서, 장치의 설정 공간에 있는 MSI capability에 적어 준다. 그래서 첫 단계는 설정 공간에 MSI capability를 넣는 것이다. QEMU에서는 realize에서 msi_init 한 번이면 되고, 장치가 없어질 때는 msi_uninit으로 뺀다. 벡터는 하나, 64비트 주소로 보낼 수 있게 했다. ...

September 28, 2026 · 3 min · Donggeun Yoo

vnpu 개발기 #8 - 첫 ioctl, GET_PARAM

사용자 공간 프로그램이 ioctl로 장치 정보를 물어볼 수 있게 했다. 첫 항목은 PCI 디바이스 ID 하나다. ivpu도 첫 ioctl이 GET_PARAM이고, 첫 항목이 디바이스 ID다. ioctl 번호와 주고받는 구조체는 드라이버와 사용자 공간이 똑같이 알아야 해서, 둘이 같이 include하는 uapi 헤더에 뒀다. struct drm_vnpu_param { __u32 param; __u32 pad; __u64 value; }; value가 64비트라 구조체 크기를 64비트 배수로 맞추려고 pad를 명시했고, pad가 0이 아니면 드라이버가 거절한다. 커널 문서(Documentation/process/botching-up-ioctls.rst)에 있는 규칙이다. 드라이버 쪽은 ioctl 표에 함수 하나를 등록하는 게 전부다. DRM의 drm_ioctl이 번호로 표를 찾고, 구조체를 커널로 복사해 드라이버 함수를 부른 뒤, 결과를 다시 사용자 공간으로 복사해 준다. ...

September 28, 2026 · 2 min · Donggeun Yoo

vnpu 개발기 #7 - accel 장치로 등록하기

드라이버를 accel 장치로 등록해서 /dev/accel/accel0을 만들었다. 사용자 공간 프로그램이 NPU에 말을 걸 통로다. 앞으로 버퍼 할당이나 job 제출은 이 파일의 ioctl로 들어오게 된다. NPU 같은 계산 가속기는 리눅스 accel 서브시스템에 붙는다. 뼈대는 GPU용 DRM 코어를 그대로 쓰고, 장치 파일만 /dev/dri가 아니라 /dev/accel 아래 생긴다. 커널 문서(Documentation/accel/introduction.rst)에 따르면, 그래픽 쪽 사용자 공간 소프트웨어가 가속기를 GPU로 착각하지 않게 하려는 것이다. 드라이버 쪽에서 할 일은 두 가지다. drm_driver에 DRIVER_COMPUTE_ACCEL 플래그를 켜고, 파일 연산은 DEFINE_DRM_ACCEL_FOPS로 만든다. probe에서는 ID 확인이 끝난 뒤 devm_drm_dev_alloc으로 DRM 장치를 할당하고, drm_dev_register로 등록한다. 여기서 장치 파일이 생긴다. ...

September 28, 2026 · 2 min · Donggeun Yoo

vnpu 개발기 #6 - 드라이버에서 ID 읽기

드라이버가 probe에서 ID 레지스터를 읽게 했다. ID가 0x564e5055가 아니면 이 장치를 잡지 않는다. 레지스터를 읽기 전에 두 가지가 필요하다. pcim_enable_device로 장치를 켜고, pcim_iomap_region으로 BAR 0을 커널 주소에 매핑해야 드라이버가 readl로 읽을 수 있다. pcim_iomap_region은 매핑과 함께 BAR 0 범위를 “vnpu” 이름으로 확보한다. 둘 다 이름의 m이 managed라는 뜻이라, 드라이버가 떨어질 때 커널이 알아서 되돌린다. unbind했다가 다시 bind해서 정말 풀리는지 봤다. [ 13.877076] vnpu 0000:00:04.0: ID 0x564e5055 ~ # grep vnpu /proc/iomem febf1000-febf1fff : vnpu ~ # echo 0000:00:04.0 > /sys/bus/pci/drivers/vnpu/unbind ~ # grep -c vnpu /proc/iomem 0 ~ # echo 0000:00:04.0 > /sys/bus/pci/drivers/vnpu/bind [ 13.893207] vnpu 0000:00:04.0: ID 0x564e5055 풀어 주는 코드를 따로 안 썼는데, unbind하자 범위가 풀렸고 다시 bind해도 문제없이 잡혔다. ...

September 28, 2026 · 1 min · Donggeun Yoo

vnpu 개발기 #5 - 첫 레지스터

BAR 0 창에 첫 레지스터를 넣었다. 오프셋 0x00을 읽으면 0x564e5055가 나오는 ID 레지스터다. 드라이버가 장치를 잡고 가장 먼저 할 일은 이 창 너머에 정말 vnpu가 있는지 확인하는 것이다. 창 연결이 어딘가 잘못돼 있으면 이 값이 안 나오니까, 이 값이 나오면 BAR 배치부터 매핑까지 제대로 됐다는 뜻이 된다. 값은 16진수로 56 4e 50 55, ASCII로 “VNPU"다. 게스트가 창을 읽으면 KVM이 그 접근을 QEMU로 넘기고, QEMU는 그 창의 MemoryRegionOps에 등록된 read 콜백을 부른다. 콜백은 창 시작 기준 오프셋을 받아서, 0x00이면 ID를 돌려준다. 레지스터는 전부 32비트라서 4바이트 접근만 받게 했다. ...

September 28, 2026 · 2 min · Donggeun Yoo

vnpu 개발기 #4 - BAR 0 달기

장치에 드라이버가 읽고 쓸 메모리 창을 달았다. 지금까지 장치는 PCI 설정 공간, 그러니까 자기소개만 있었다. PCI 장치는 설정 공간의 BAR(Base Address Register)로 “이만한 크기의 메모리 창이 필요하다"고 알린다. 부팅할 때 이 창이 물리 주소 공간 어딘가에 배치되고, 그 뒤로 CPU가 그 주소를 읽고 쓰면 RAM이 아니라 장치로 간다. 이게 MMIO이고, 레지스터 접근은 전부 이 창을 거친다. QEMU에서는 이 창을 MemoryRegion으로 만들고 pci_register_bar로 BAR에 연결한다. 크기는 4 KiB로 잡았다. CPU가 메모리를 페이지 단위로 매핑하니 이보다 작게 잡을 이유가 없었다. 읽고 쓸 때 부를 콜백은 아직 안 넣었다. QEMU는 콜백이 없는 창을 어떤 접근도 받지 않는 영역으로 처리한다. ...

September 28, 2026 · 1 min · Donggeun Yoo