1use std::ffi::{c_char, c_uchar, c_uint, c_ulonglong, c_ushort};
27#[cfg(any(feature = "ioctl-nix", feature = "ioctl-rustix"))]
28use std::fs::File;
29use std::os::fd::AsFd;
30use std::{error, fmt, io};
31
32const AMZ_EBS_MN: &str = "Amazon Elastic Block Store";
33const AMZ_INST_STORE_MN: &str = "Amazon EC2 NVMe Instance Storage";
34const AMZ_VENDOR_ID: c_ushort = 0x1D0F;
35
36const NVME_ADMIN_IDENTIFY: u8 = 0x06;
37const NVME_IOCTL_ADMIN_CMD_NUM: u8 = 0x41;
38
39#[derive(Debug)]
41pub enum Error {
42 DeviceNameNotFound,
44 Io(io::Error),
46 UnparseableDeviceName(String),
48 UnrecognizedVendorId(u16),
50 UnrecognizedModel(String),
52 #[cfg(feature = "ioctl-nix")]
54 NixErrno(nix::errno::Errno),
55 #[cfg(feature = "ioctl-rustix")]
57 RustixErrno(rustix::io::Errno),
58}
59
60impl error::Error for Error {}
61
62impl From<io::Error> for Error {
63 fn from(e: io::Error) -> Self {
64 Self::Io(e)
65 }
66}
67
68#[cfg(feature = "ioctl-nix")]
69impl From<nix::errno::Errno> for Error {
70 fn from(e: nix::errno::Errno) -> Self {
71 Self::NixErrno(e)
72 }
73}
74
75#[cfg(feature = "ioctl-rustix")]
76impl From<rustix::io::Errno> for Error {
77 fn from(e: rustix::io::Errno) -> Self {
78 Self::RustixErrno(e)
79 }
80}
81
82impl std::fmt::Display for Error {
83 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
84 match self {
85 Self::DeviceNameNotFound => write!(f, "device name not found"),
86 Self::Io(e) => write!(f, "{}", e),
87 Self::UnparseableDeviceName(name) => write!(f, "unparseable device name: {}", name),
88 Self::UnrecognizedVendorId(id) => write!(f, "unrecognized vendor id: {}", id),
89 Self::UnrecognizedModel(model) => write!(f, "unrecognized model: {}", model),
90 #[cfg(feature = "ioctl-nix")]
91 Self::NixErrno(e) => write!(f, "{}", e),
92 #[cfg(feature = "ioctl-rustix")]
93 Self::RustixErrno(e) => write!(f, "{}", e),
94 }
95 }
96}
97
98type Result<T> = std::result::Result<T, Error>;
99
100#[repr(C)]
101#[derive(Debug, Clone, Copy)]
102struct NvmeIdPsd {
103 mp: c_ushort,
104 rsvd2: c_uchar,
105 flags: c_uchar,
106 enlat: c_uint,
107 exlat: c_uint,
108 rrt: c_uchar,
109 rrl: c_uchar,
110 rwt: c_uchar,
111 rwl: c_uchar,
112 idlp: c_ushort,
113 ips: c_uchar,
114 rsvd19: c_uchar,
115 actp: c_ushort,
116 apws: c_uchar,
117 rsvd23: [c_uchar; 9],
118}
119
120impl Default for NvmeIdPsd {
121 fn default() -> Self {
122 Self {
123 mp: 0,
124 rsvd2: 0,
125 flags: 0,
126 enlat: 0,
127 exlat: 0,
128 rrt: 0,
129 rrl: 0,
130 rwt: 0,
131 rwl: 0,
132 idlp: 0,
133 ips: 0,
134 rsvd19: 0,
135 actp: 0,
136 apws: 0,
137 rsvd23: [0; 9],
138 }
139 }
140}
141
142#[repr(C)]
143#[derive(Debug, Clone, Copy)]
144struct NvmeVuIdCtrlField {
145 bdev: [c_uchar; 32],
146 reserved0: [c_uchar; 992],
147}
148
149impl Default for NvmeVuIdCtrlField {
150 fn default() -> Self {
151 Self {
152 bdev: [0; 32],
153 reserved0: [0; 992],
154 }
155 }
156}
157
158#[repr(C)]
159#[derive(Debug, Clone, Copy)]
160struct NvmeIdCtrl {
161 vid: c_ushort,
162 ssvid: c_ushort,
163 sn: [c_char; 20],
164 mn: [c_char; 40],
165 fr: [c_char; 8],
166 rab: c_uchar,
167 ieee: [c_uchar; 3],
168 cmic: c_uchar,
169 mdts: c_uchar,
170 cntlid: c_ushort,
171 ver: c_uint,
172 rtd3r: c_uint,
173 rtd3e: c_uint,
174 oaes: c_uint,
175 ctratt: c_uint,
176 rrls: c_ushort,
177 rsvd102: [c_uchar; 9],
178 cntrltype: c_uchar,
179 fguid: [c_uchar; 16],
180 crdt1: c_ushort,
181 crdt2: c_ushort,
182 crdt3: c_ushort,
183 rsvd134: [c_uchar; 119],
184 nvmsr: c_uchar,
185 vwci: c_uchar,
186 mec: c_uchar,
187 oacs: c_ushort,
188 acl: c_uchar,
189 aerl: c_uchar,
190 frmw: c_uchar,
191 lpa: c_uchar,
192 elpe: c_uchar,
193 npss: c_uchar,
194 avscc: c_uchar,
195 apsta: c_uchar,
196 wctemp: c_ushort,
197 cctemp: c_ushort,
198 mtfa: c_ushort,
199 hmpre: c_uint,
200 hmmin: c_uint,
201 tnvmcap: [c_uchar; 16],
202 unvmcap: [c_uchar; 16],
203 rpmbs: c_uint,
204 edstt: c_ushort,
205 dsto: c_uchar,
206 fwug: c_uchar,
207 kas: c_ushort,
208 hctma: c_ushort,
209 mntmt: c_ushort,
210 mxtmt: c_ushort,
211 sanicap: c_uint,
212 hmminds: c_uint,
213 hmmaxd: c_ushort,
214 nsetidmax: c_ushort,
215 endgidmax: c_ushort,
216 anatt: c_uchar,
217 anacap: c_uchar,
218 anagrpmax: c_uint,
219 nanagrpid: c_uint,
220 pels: c_uint,
221 domainid: c_ushort,
222 rsvd358: [c_uchar; 10],
223 megcap: [c_uchar; 16],
224 tmpthha: c_uchar,
225 rsvd385: [c_uchar; 127],
226 sqes: c_uchar,
227 cqes: c_uchar,
228 maxcmd: c_ushort,
229 nn: c_uint,
230 oncs: c_ushort,
231 fuses: c_ushort,
232 fna: c_uchar,
233 vwc: c_uchar,
234 awun: c_ushort,
235 awupf: c_ushort,
236 icsvscc: c_uchar,
237 nwpc: c_uchar,
238 acwu: c_ushort,
239 ocfs: c_ushort,
240 sgls: c_uint,
241 mnan: c_uint,
242 maxdna: [c_uchar; 16],
243 maxcna: c_uint,
244 oaqd: c_uint,
245 rsvd568: [c_uchar; 200],
246 subnqn: [c_char; 256],
247 rsvd1024: [c_uchar; 768],
248 ioccsz: c_uint,
249 iorcsz: c_uint,
250 icdoff: c_ushort,
251 fcatt: c_uchar,
252 msdbd: c_uchar,
253 ofcs: c_ushort,
254 dctype: c_uchar,
255 rsvd1807: [c_uchar; 241],
256 psd: [NvmeIdPsd; 32],
257 vs: NvmeVuIdCtrlField,
258}
259
260impl Default for NvmeIdCtrl {
261 fn default() -> Self {
262 Self {
263 vid: 0,
264 ssvid: 0,
265 sn: [0; 20],
266 mn: [0; 40],
267 fr: [0; 8],
268 rab: 0,
269 ieee: [0; 3],
270 cmic: 0,
271 mdts: 0,
272 cntlid: 0,
273 ver: 0,
274 rtd3r: 0,
275 rtd3e: 0,
276 oaes: 0,
277 ctratt: 0,
278 rrls: 0,
279 rsvd102: [0; 9],
280 cntrltype: 0,
281 fguid: [0; 16],
282 crdt1: 0,
283 crdt2: 0,
284 crdt3: 0,
285 rsvd134: [0; 119],
286 nvmsr: 0,
287 vwci: 0,
288 mec: 0,
289 oacs: 0,
290 acl: 0,
291 aerl: 0,
292 frmw: 0,
293 lpa: 0,
294 elpe: 0,
295 npss: 0,
296 avscc: 0,
297 apsta: 0,
298 wctemp: 0,
299 cctemp: 0,
300 mtfa: 0,
301 hmpre: 0,
302 hmmin: 0,
303 tnvmcap: [0; 16],
304 unvmcap: [0; 16],
305 rpmbs: 0,
306 edstt: 0,
307 dsto: 0,
308 fwug: 0,
309 kas: 0,
310 hctma: 0,
311 mntmt: 0,
312 mxtmt: 0,
313 sanicap: 0,
314 hmminds: 0,
315 hmmaxd: 0,
316 nsetidmax: 0,
317 endgidmax: 0,
318 anatt: 0,
319 anacap: 0,
320 anagrpmax: 0,
321 nanagrpid: 0,
322 pels: 0,
323 domainid: 0,
324 rsvd358: [0; 10],
325 megcap: [0; 16],
326 tmpthha: 0,
327 rsvd385: [0; 127],
328 sqes: 0,
329 cqes: 0,
330 maxcmd: 0,
331 nn: 0,
332 oncs: 0,
333 fuses: 0,
334 fna: 0,
335 vwc: 0,
336 awun: 0,
337 awupf: 0,
338 icsvscc: 0,
339 nwpc: 0,
340 acwu: 0,
341 ocfs: 0,
342 sgls: 0,
343 mnan: 0,
344 maxdna: [0; 16],
345 maxcna: 0,
346 oaqd: 0,
347 rsvd568: [0; 200],
348 subnqn: [0; 256],
349 rsvd1024: [0; 768],
350 ioccsz: 0,
351 iorcsz: 0,
352 icdoff: 0,
353 fcatt: 0,
354 msdbd: 0,
355 ofcs: 0,
356 dctype: 0,
357 rsvd1807: [0; 241],
358 psd: [NvmeIdPsd::default(); 32],
359 vs: NvmeVuIdCtrlField::default(),
360 }
361 }
362}
363
364#[repr(C)]
365#[derive(Debug, Default, Clone, Copy)]
366struct NvmePassthruCmd {
367 opcode: c_uchar,
368 flags: c_uchar,
369 rsvd1: c_ushort,
370 nsid: c_uint,
371 cdw2: c_uint,
372 cdw3: c_uint,
373 metadata: c_ulonglong,
374 addr: c_ulonglong,
375 metadata_len: c_uint,
376 data_len: c_uint,
377 cdw10: c_uint,
378 cdw11: c_uint,
379 cdw12: c_uint,
380 cdw13: c_uint,
381 cdw14: c_uint,
382 cdw15: c_uint,
383 timeout_ms: c_uint,
384 result: c_uint,
385}
386
387type NvmeAdminCmd = NvmePassthruCmd;
388
389#[cfg(feature = "ioctl-nix")]
390mod ioctl_nix {
391 use std::os::fd::{AsFd, AsRawFd};
392
393 use nix::ioctl_readwrite;
394
395 use super::*;
396
397 pub(super) fn nvme_identify_ctrl<F: AsFd>(fd: F) -> Result<NvmeIdCtrl> {
398 ioctl_readwrite!(
399 nvme_identify_ctrl_inner,
400 b'N',
401 NVME_IOCTL_ADMIN_CMD_NUM,
402 NvmeAdminCmd
403 );
404 let mut out = NvmeIdCtrl::default();
405 let out_ptr = &mut out as *mut _;
406 let mut nvme_admin_cmd = NvmeAdminCmd {
407 addr: out_ptr as c_ulonglong,
408 cdw10: 1,
409 data_len: std::mem::size_of::<NvmeIdCtrl>() as c_uint,
410 opcode: NVME_ADMIN_IDENTIFY,
411 ..Default::default()
412 };
413 let nvme_admin_cmd_ptr = &mut nvme_admin_cmd as *mut _;
414 unsafe { nvme_identify_ctrl_inner(fd.as_fd().as_raw_fd(), nvme_admin_cmd_ptr) }?;
415 Ok(out)
416 }
417}
418
419#[cfg(feature = "ioctl-rustix")]
420mod ioctl_rustix {
421 use std::ffi::c_void;
422 use std::os::fd::AsFd;
423
424 use rustix::io;
425 use rustix::ioctl::{ioctl, opcode, Direction, Ioctl, IoctlOutput, Opcode};
426
427 use super::*;
428
429 unsafe impl Ioctl for NvmeAdminCmd {
430 type Output = NvmeIdCtrl;
431
432 const IS_MUTATING: bool = false;
433
434 fn opcode(&self) -> Opcode {
435 opcode::from_components(
436 Direction::ReadWrite,
437 b'N',
438 NVME_IOCTL_ADMIN_CMD_NUM,
439 std::mem::size_of::<NvmeAdminCmd>(),
440 )
441 }
442
443 fn as_ptr(&mut self) -> *mut c_void {
444 self as *const _ as *mut _
445 }
446
447 unsafe fn output_from_ptr(ret: IoctlOutput, ptr: *mut c_void) -> io::Result<Self::Output> {
448 if ret != 0 {
449 return Err(io::Errno::from_raw_os_error(ret));
450 }
451 let sellf = ptr.cast::<NvmeAdminCmd>().read();
452 let data_ptr = sellf.addr as *const NvmeIdCtrl;
453 let output = data_ptr.cast::<NvmeIdCtrl>().read();
454 Ok(output)
455 }
456 }
457
458 pub(super) fn nvme_identify_ctrl<F: AsFd>(fd: F) -> Result<NvmeIdCtrl> {
459 let mut data = NvmeIdCtrl::default();
460 let nvme_admin_cmd = NvmeAdminCmd {
461 addr: &mut data as *mut _ as c_ulonglong,
462 cdw10: 1,
463 data_len: std::mem::size_of::<NvmeIdCtrl>() as c_uint,
464 opcode: NVME_ADMIN_IDENTIFY,
465 ..Default::default()
466 };
467 let output = unsafe { ioctl(fd, nvme_admin_cmd) }?;
468 Ok(output)
469 }
470}
471
472pub struct Names {
474 pub device_name: Option<String>,
476 pub virtual_name: Option<String>,
478
479 _internal: (),
482}
483
484impl fmt::Debug for Names {
485 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
486 f.debug_struct("Names")
487 .field("device_name", &self.device_name)
488 .field("virtual_name", &self.virtual_name)
489 .finish()
490 }
491}
492
493impl TryFrom<&[c_uchar]> for Names {
494 type Error = Error;
495
496 fn try_from(chars: &[u8]) -> Result<Self> {
497 const COLON: u8 = 0x3a;
498 const SPACE: u8 = 0x20;
499 const NULL: u8 = 0x0;
500
501 let mut field1 = String::new();
512 let mut field2 = String::new();
513
514 let mut has_delim = false;
515
516 for c in chars.iter() {
517 if *c == NULL || *c == SPACE {
518 break;
519 }
520 if *c == COLON {
521 has_delim = true;
522 continue;
523 }
524 if !has_delim {
525 field1.push(*c as char);
526 } else {
527 field2.push(*c as char);
528 }
529 }
530
531 if field1.starts_with("/dev/") {
532 field1 = field1[5..].into();
533 }
534
535 if field2.starts_with("/dev/") {
536 field2 = field2[5..].into();
537 }
538
539 let device_name = if has_delim {
540 if field2 == "none" {
541 None
542 } else {
543 Some(&field2)
544 }
545 } else {
546 Some(&field1)
547 };
548
549 let virtual_name = if has_delim { Some(&field1) } else { None };
550
551 if device_name.is_none() && virtual_name.is_none() {
552 return Err(Error::UnparseableDeviceName(
553 chars.iter().map(|c| *c as char).collect(),
554 ));
555 }
556
557 Ok(Self {
558 device_name: device_name.cloned(),
559 virtual_name: virtual_name.cloned(),
560 _internal: (),
561 })
562 }
563}
564
565#[derive(Debug)]
567pub enum Model {
568 AmazonElasticBlockStore,
570 AmazonInstanceStore,
572}
573
574#[derive(Debug)]
576pub struct VendorId(pub u16);
577
578#[derive(Debug)]
580pub struct Nvme {
581 pub model: Model,
583 pub names: Names,
585 pub vendor_id: VendorId,
587}
588
589impl Nvme {
590 pub fn name(&self) -> &str {
593 self.names
594 .device_name
595 .as_ref()
596 .unwrap_or_else(|| self.names.virtual_name.as_ref().unwrap())
597 }
598
599 fn from_fd<F, IoctlFn>(fd: F, f: IoctlFn) -> Result<Self>
600 where
601 F: AsFd,
602 IoctlFn: FnOnce(F) -> Result<NvmeIdCtrl>,
603 {
604 let ctrl = f(fd)?;
605 if ctrl.vid != AMZ_VENDOR_ID {
606 return Err(Error::UnrecognizedVendorId(ctrl.vid));
607 }
608 let mut model_str = String::from_iter(ctrl.mn.map(|c| c as u8 as char));
609 model_str.truncate(model_str.trim_end().len());
610 let model = match model_str.as_str() {
611 AMZ_EBS_MN => Model::AmazonElasticBlockStore,
612 AMZ_INST_STORE_MN => Model::AmazonInstanceStore,
613 _ => return Err(Error::UnrecognizedModel(model_str)),
614 };
615 let names = ctrl.vs.bdev.as_slice().try_into()?;
616 Ok(Self {
617 model,
618 names,
619 vendor_id: VendorId(ctrl.vid),
620 })
621 }
622}
623
624#[cfg(any(feature = "ioctl-nix", feature = "ioctl-rustix"))]
625impl TryFrom<File> for Nvme {
626 type Error = Error;
627
628 #[cfg(feature = "ioctl-nix")]
629 fn try_from(f: File) -> Result<Self> {
630 Self::from_fd(f.as_fd(), ioctl_nix::nvme_identify_ctrl)
631 }
632
633 #[cfg(feature = "ioctl-rustix")]
634 fn try_from(f: File) -> Result<Self> {
635 Self::from_fd(f.as_fd(), ioctl_rustix::nvme_identify_ctrl)
636 }
637}
638
639#[cfg(all(feature = "ioctl-nix", feature = "ioctl-rustix"))]
640compile_error!("The features ioctl-nix and ioctl-rustix are mutually exclusive");