;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; Copyright (C) KolibriOS team 2004-2015. All rights reserved. ;; ;; Distributed under terms of the GNU General Public License ;; ;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;driver sceletone format PE DLL native entry START API_VERSION equ 0 ;debug SRV_GETVERSION equ 0 __DEBUG__ = 1 __DEBUG_LEVEL__ = 1 DRIVER_VERSION = 1 DBG_INFO = 1 ; Opcodes for NVM commands NVM_CMD_FLUSH = 0x00 NVM_CMD_WRITE = 0x01 NVM_CMD_READ = 0x02 NVM_CMD_WRITE_UNCORRECTABLE = 0x04 NVM_CMD_COMPARE = 0x05 NVM_CMD_WRITE_ZEROES = 0x08 NVM_CMD_DATASET_MANAGEMENT = 0x09 NVM_CMD_VERIFY = 0x0C NVM_CMD_RESERVATION_REG = 0x0D NVM_CMD_RESERVATION_REPORT = 0x0E NVM_CMD_RESERVATION_ACQUIRE = 0x11 NVM_CMD_RESERVATION_RELEASE = 0x15 NVM_CMD_COPY = 0x19 ; Opcodes for admin commands ADM_CMD_DEL_IO_SUBMISSION_QUEUE = 0x00 ADM_CMD_CRE_IO_SUBMISSION_QUEUE = 0x01 ADM_CMD_GET_LOG_PAGE = 0x02 ADM_CMD_DEL_IO_COMPLETION_QUEUE = 0x04 ADM_CMD_CRE_IO_COMPLETION_QUEUE = 0x05 ADM_CMD_IDENTIFY = 0x06 ADM_CMD_ABORT = 0x08 ADM_CMD_SET_FEATURES = 0x09 ADM_CMD_GET_FEATURES = 0x0A ; fuse (fused operation): In a fused operation, a complex command is created by 'fusing' together ; two simpler commands. This field specifies whether this command is part ; of a fused operation, and if so, which command it is in the sequence: ; 00b -> Normal operation ; 01b -> Fused operation, first command ; 10b -> Fused operation, second command ; 11b -> Reserved NO_FUSE = 0 FUSE_OP_FIRST_CMD = 1 shl 8 FUSE_OP_SECOND_CMD = 2 shl 8 ; sel (PRP or SGL for data transfer): This field specifies whether PRPs or SGLs are used for any ; data transfer associated with the command. PRPs shall be ; used for all Admin commands for NVMe over PCIe implementations. ; SGLs shall be used for all Admin and I/O commands for NVMe over ; Fabrics implementations (i.e., field set to 01b): ; 00b -> PRPs are used for this transfer ; 01b -> SGLs are used for this transfer, MPTR will contain address of ; a single contiguous physical buffer that is byte aligned ; 10b -> SGLs are used for this transfer. MPTR will contain address of ; an SGL segment containing exactly one SGL descriptor that is ; QWORD aligned ; 11b -> Reserved SEL_PRP = 0 SEL_SGL = 1 shl 14 ; scope is all attached namespaces or all namespaces in NVM subsystem NSID_BROADCAST = 0xFFFFFFFF section ".flat" code readable writable executable include "../proc32.inc" include "../struct.inc" include "../macros.inc" include "../fdo.inc" include "../pci.inc" include "../peimport.inc" struc ns_identify { .nsze dd ? .ncap dd ? .nuse dd ? } ; Submission Queue Entry (64 bytes) struc cq_entry { .cdw0 dd ? .nsid dd ? .reserved dq ? .mptr dq ? .prp1 dq ? .prp2 dq ? .cdw10 dd ? .cdw11 dd ? .cdw12 dd ? .cdw13 dd ? .cdw14 dd ? .cdw15 dd ? } SUBMISSION_QUEUE_ENTRY_SIZE = 64 struc nvme_dev { .pci_bus dd ? .pci_devfn dd ? .bar0 dd ? .serial db 20 .model db 40 } proc START c, reason:dword cmp [reason], DRV_ENTRY jne .exit .entry: push esi DEBUGF DBG_INFO,"Detecting NVMe hardware...\n" call detect pop esi test eax, eax jz .exit invoke RegService, my_service, service_proc ret .exit: xor eax, eax ret endp proc service_proc stdcall, ioctl:dword mov ebx, [ioctl] mov eax, [ebx+IOCTL.io_code] cmp eax, SRV_GETVERSION jne @F mov eax, [ebx+IOCTL.output] cmp [ebx+IOCTL.out_size], 4 jne .fail mov dword [eax], API_VERSION xor eax, eax ret @@: .fail: or eax, -1 ret endp proc nvme_identify stdcall, p_dev:dword, nsid:dword, cns:byte, cntid:word sub esp, SUBMISSION_QUEUE_ENTRY_SIZE push SUBMISSION_QUEUE_ENTRY_SIZE push 0 push esp call memset pop esp add esp, 8 mov eax, dword [nsid] mov [esp + cq_entry.nsid], eax mov eax, dword [prp1] mov [esp + cq_entry.prp1], eax mov eax, dword [prp2] mov [esp + cq_entry.prp2], eax ; TODO: setting CID to 1 for now but later on keep a list of unique list of identifiers mov [esp + cq_entry.cdw0], ADM_CMD_IDENTIFY or (1 shl 16) ; the Controller or Namespace structure (CNS) specifies the information to be returned to the host mov eax, dword [cntid] shl eax, 16 or eax, dword [cns] mov [esp + cq_entry.cdw10], eax add esp, SUBMISSION_QUEUE_ENTRY_SIZE endp proc detect push ebx invoke GetPCIList mov edx, eax .check_dev: mov ecx, [eax+PCIDEV.class] and ecx, 0x00ffff00 ; retrieve class/subclass code only cmp ecx, 0x00010800 ; Mass Storage Controller - Non-Volatile Memory Controller je .check_cap .next_dev: mov eax, [eax + PCIDEV.fd] cmp eax, edx jne .check_dev ; no more PCI devices to enumerate? xor eax, eax pop ebx ret .check_cap: push eax movzx ebx, [eax + PCIDEV.bus] mov [pcidev_bus], ebx movzx ebx, [eax + PCIDEV.devfn] mov [pcidev_devfn], ebx invoke PciRead16, [pcidev_bus], [pcidev_devfn], PCI_header00.status test ax, 0x10 ; check capabilities list bit jnz .got_cap pop eax .got_cap: DEBUGF DBG_INFO,"Found NVMe device with capabilities\n" invoke PciRead8, [pcidev_bus], [pcidev_devfn], PCI_header00.cap_ptr and eax, 11111100b mov edi, eax @@: invoke PciRead32, [pcidev_bus], [pcidev_devfn], edi mov ecx, eax ;and ecx, 0xff mov eax, ecx movzx edi, ah test edi, edi jnz @b ; read BAR0 invoke PciRead32, [pcidev_bus], [pcidev_devfn], PCI_header00.base_addr_0 mov [bar0], eax stdcall nvme_identify, 0, 0, 0, 0 ; return successfully pop eax xor eax, eax inc eax pop ebx ret endp ; uninitialized data pcidev_bus dd ? pcidev_devfn dd ? bar0 dd ? ;all initialized data place here align 4 my_service db "NVMe Service",0 ;max 16 chars include zero include_debug_strings align 4 data fixups end data