mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-26 01:44:48 +00:00
RX/TX split: rxLoop reads PDUs, txLoop writes responses via respCh. Handlers refactored to void + enqueueResponse pattern. IOCCSZ fix enables inline write data (100K IOPS vs 15K before). R2T deadlock fix via completeWaiters. Shutdown cleans up pendingCapsules buffers. Bench: ParseFioMetric accepts plain/quoted numbers for aggregated medians. Profiling actions: pprof_capture, vmstat_capture, iostat_capture. 196 NVMe tests, 92 testrunner actions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
266 lines
7.6 KiB
Go
266 lines
7.6 KiB
Go
package nvme
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"math/bits"
|
|
)
|
|
|
|
const identifySize = 4096
|
|
|
|
// handleIdentify dispatches Identify commands by CNS type.
|
|
func (c *Controller) handleIdentify(req *Request) {
|
|
cns := uint8(req.capsule.D10 & 0xFF)
|
|
|
|
switch cns {
|
|
case cnsIdentifyController:
|
|
c.identifyController(req)
|
|
case cnsIdentifyNamespace:
|
|
c.identifyNamespace(req)
|
|
case cnsActiveNSList:
|
|
c.identifyActiveNSList(req)
|
|
case cnsNSDescriptorList:
|
|
c.identifyNSDescriptors(req)
|
|
default:
|
|
req.resp.Status = uint16(StatusInvalidField)
|
|
c.enqueueResponse(&response{resp: req.resp})
|
|
}
|
|
}
|
|
|
|
// identifyController returns the 4096-byte Identify Controller data structure.
|
|
func (c *Controller) identifyController(req *Request) {
|
|
buf := make([]byte, identifySize)
|
|
|
|
sub := c.subsystem
|
|
if sub == nil {
|
|
req.resp.Status = uint16(StatusInvalidField)
|
|
c.enqueueResponse(&response{resp: req.resp})
|
|
return
|
|
}
|
|
|
|
// VID (PCI Vendor ID) - use 0 for software target
|
|
// SSVID - 0
|
|
|
|
// Serial Number (offset 4, 20 bytes, space-padded ASCII)
|
|
copyPadded(buf[4:24], "SWF00001")
|
|
|
|
// Model Number (offset 24, 40 bytes, space-padded ASCII)
|
|
copyPadded(buf[24:64], "SeaweedFS BlockVol")
|
|
|
|
// Firmware Revision (offset 64, 8 bytes, space-padded ASCII)
|
|
copyPadded(buf[64:72], "0001")
|
|
|
|
// RAB (Recommended Arbitration Burst) - offset 72
|
|
buf[72] = 6
|
|
|
|
// IEEE OUI - offset 73-75 (3 bytes, 0 for software)
|
|
|
|
// CMIC (Controller Multi-Path I/O Capabilities) - offset 76
|
|
// bit 3: ANA reporting supported
|
|
buf[76] = 0x08
|
|
|
|
// MDTS (Maximum Data Transfer Size) - offset 77
|
|
// 2^MDTS * 4096 = max transfer. MDTS=3 → 32KB
|
|
buf[77] = 3
|
|
|
|
// CNTLID (Controller ID) - offset 78-79
|
|
binary.LittleEndian.PutUint16(buf[78:], c.cntlID)
|
|
|
|
// Version - offset 80-83
|
|
binary.LittleEndian.PutUint32(buf[80:], nvmeVersion14)
|
|
|
|
// OACS (Optional Admin Command Support) - offset 256-257
|
|
// 0 = no optional admin commands
|
|
binary.LittleEndian.PutUint16(buf[256:], 0)
|
|
|
|
// ACRTD (Abort Command Limit) - offset 258
|
|
buf[258] = 3
|
|
|
|
// AERTL (Async Event Request Limit) - offset 259
|
|
buf[259] = 3
|
|
|
|
// FRMW (Firmware Updates) - offset 260
|
|
buf[260] = 0x02 // slot 1 read-only
|
|
|
|
// LPA (Log Page Attributes) - offset 261
|
|
buf[261] = 0
|
|
|
|
// ELPE (Error Log Page Entries) - offset 262
|
|
buf[262] = 0 // 1 entry (0-based)
|
|
|
|
// KAS (Keep Alive Support) - offset 320-321
|
|
// Granularity in 100ms units. Non-zero is mandatory for fabrics controllers.
|
|
binary.LittleEndian.PutUint16(buf[320:], 10) // 1 second granularity
|
|
|
|
// ANACAP (ANA Capabilities) - offset 341
|
|
// bit 3: reports Optimized state
|
|
buf[341] = 0x08
|
|
|
|
// ANAGRPMAX (Max ANA Group ID) - offset 344-347
|
|
binary.LittleEndian.PutUint32(buf[344:], 1)
|
|
|
|
// NANAGRPID (Number of ANA Group IDs) - offset 348-351
|
|
binary.LittleEndian.PutUint32(buf[348:], 1)
|
|
|
|
// SQES (Submission Queue Entry Size) - offset 512
|
|
// min=6 (2^6=64 bytes), max=6
|
|
buf[512] = 0x66
|
|
|
|
// CQES (Completion Queue Entry Size) - offset 513
|
|
// min=4 (2^4=16 bytes), max=4
|
|
buf[513] = 0x44
|
|
|
|
// MAXCMD - offset 514-515
|
|
binary.LittleEndian.PutUint16(buf[514:], 64)
|
|
|
|
// NN (Number of Namespaces) - offset 516-519
|
|
binary.LittleEndian.PutUint32(buf[516:], 1)
|
|
|
|
// ONCS (Optional NVM Command Support) - offset 520-521
|
|
// bit 3: WriteZeros, bit 2: DatasetMgmt (Trim)
|
|
binary.LittleEndian.PutUint16(buf[520:], 0x0C)
|
|
|
|
// VWC (Volatile Write Cache) - offset 525
|
|
// bit 0: volatile write cache present → Flush required
|
|
buf[525] = 0x01
|
|
|
|
// SGLS (SGL Support) - offset 536-539
|
|
// bit 0: SGLs supported (required for NVMe/TCP)
|
|
binary.LittleEndian.PutUint32(buf[536:], 0x01)
|
|
|
|
// MNAN (Maximum Number of Allowed Namespaces) - offset 540-543
|
|
// Must be non-zero for NVMe 1.4+ controllers; kernel validates this.
|
|
binary.LittleEndian.PutUint32(buf[540:], 1)
|
|
|
|
// SubNQN (Subsystem NQN) - offset 768, 256 bytes, NUL-terminated
|
|
// Must NOT be space-padded — kernel uses strcmp() to match against Connect NQN.
|
|
copy(buf[768:1024], sub.NQN) // buf is already zeroed → NUL-terminated
|
|
|
|
// IOCCSZ (I/O Queue Command Capsule Supported Size) - offset 1792-1795
|
|
// In 16-byte units: (SQE + max inline data) / 16
|
|
// Allows host to send write data inline in the command capsule,
|
|
// avoiding R2T round-trip for small writes (e.g. 4K).
|
|
ioccsz := (64 + c.maxDataLen) / 16
|
|
binary.LittleEndian.PutUint32(buf[1792:], ioccsz)
|
|
|
|
// IORCSZ (I/O Queue Response Capsule Supported Size) - offset 1796-1799
|
|
// In 16-byte units: 16/16 = 1
|
|
binary.LittleEndian.PutUint32(buf[1796:], 1)
|
|
|
|
// ICDOFF (In Capsule Data Offset) - offset 1800-1801
|
|
// 0 means inline data immediately follows SQE in capsule
|
|
binary.LittleEndian.PutUint16(buf[1800:], 0)
|
|
|
|
// FCATT (Fabrics Controller Attributes) - offset 1802
|
|
// bit 0: 0 = I/O controller (not discovery)
|
|
buf[1802] = 0
|
|
|
|
// MSDBD (Maximum SGL Data Block Descriptors) - offset 1803
|
|
buf[1803] = 1
|
|
|
|
// OFCS (Optional Fabric Commands Supported) - offset 1804-1805
|
|
// bit 0: Disconnect command supported
|
|
binary.LittleEndian.PutUint16(buf[1804:], 0x01)
|
|
|
|
req.c2hData = buf
|
|
c.enqueueResponse(&response{resp: req.resp, c2hData: req.c2hData})
|
|
}
|
|
|
|
// identifyNamespace returns the 4096-byte Identify Namespace data for NSID=1.
|
|
func (c *Controller) identifyNamespace(req *Request) {
|
|
sub := c.subsystem
|
|
if sub == nil {
|
|
req.resp.Status = uint16(StatusInvalidField)
|
|
c.enqueueResponse(&response{resp: req.resp})
|
|
return
|
|
}
|
|
|
|
dev := sub.Dev
|
|
blockSize := dev.BlockSize()
|
|
nsze := dev.VolumeSize() / uint64(blockSize)
|
|
|
|
buf := make([]byte, identifySize)
|
|
|
|
// NSZE (Namespace Size in blocks) - offset 0-7
|
|
binary.LittleEndian.PutUint64(buf[0:], nsze)
|
|
|
|
// NCAP (Namespace Capacity) - offset 8-15
|
|
binary.LittleEndian.PutUint64(buf[8:], nsze)
|
|
|
|
// NUSE (Namespace Utilization) - offset 16-23
|
|
binary.LittleEndian.PutUint64(buf[16:], nsze)
|
|
|
|
// NSFEAT (Namespace Features) - offset 24
|
|
// bit 0: thin provisioning (supports Trim)
|
|
buf[24] = 0x01
|
|
|
|
// NLBAF (Number of LBA Formats minus 1) - offset 25
|
|
buf[25] = 0 // one format
|
|
|
|
// FLBAS (Formatted LBA Size) - offset 26
|
|
// bits 3:0 = LBA format index (0)
|
|
buf[26] = 0
|
|
|
|
// MC (Metadata Capabilities) - offset 27
|
|
buf[27] = 0
|
|
|
|
// DLFEAT (Deallocate Logical Block Features) - offset 28
|
|
// bit 2: Deallocated blocks return zeros on read
|
|
buf[28] = 0x04
|
|
|
|
// NGUID (Namespace Globally Unique Identifier) - offset 104-119 (16 bytes)
|
|
copy(buf[104:120], sub.NGUID[:])
|
|
|
|
// LBAF[0] (LBA Format 0) - offset 128-131
|
|
// bits 23:16 = LBADS (log2 of block size)
|
|
lbads := uint8(bits.TrailingZeros32(blockSize))
|
|
binary.LittleEndian.PutUint32(buf[128:], uint32(lbads)<<16)
|
|
|
|
// ANAGRPID (ANA Group Identifier) - offset 92-95
|
|
binary.LittleEndian.PutUint32(buf[92:], 1)
|
|
|
|
req.c2hData = buf
|
|
c.enqueueResponse(&response{resp: req.resp, c2hData: req.c2hData})
|
|
}
|
|
|
|
// identifyActiveNSList returns the list of active namespace IDs (just NSID=1).
|
|
func (c *Controller) identifyActiveNSList(req *Request) {
|
|
buf := make([]byte, identifySize)
|
|
// Single namespace: NSID=1
|
|
binary.LittleEndian.PutUint32(buf[0:], 1)
|
|
|
|
req.c2hData = buf
|
|
c.enqueueResponse(&response{resp: req.resp, c2hData: req.c2hData})
|
|
}
|
|
|
|
// identifyNSDescriptors returns namespace descriptor list for NSID=1.
|
|
func (c *Controller) identifyNSDescriptors(req *Request) {
|
|
sub := c.subsystem
|
|
if sub == nil {
|
|
req.resp.Status = uint16(StatusInvalidField)
|
|
c.enqueueResponse(&response{resp: req.resp})
|
|
return
|
|
}
|
|
|
|
buf := make([]byte, identifySize)
|
|
off := 0
|
|
|
|
// NGUID descriptor (type=0x02, length=16)
|
|
buf[off] = 0x02 // NIDT: NGUID
|
|
off++
|
|
buf[off] = 16 // NIDL: 16 bytes
|
|
off++
|
|
off += 2 // reserved
|
|
copy(buf[off:off+16], sub.NGUID[:])
|
|
|
|
req.c2hData = buf
|
|
c.enqueueResponse(&response{resp: req.resp, c2hData: req.c2hData})
|
|
}
|
|
|
|
// copyPadded copies src into dst, padding remaining bytes with spaces.
|
|
func copyPadded(dst []byte, src string) {
|
|
n := copy(dst, src)
|
|
for i := n; i < len(dst); i++ {
|
|
dst[i] = ' '
|
|
}
|
|
}
|