camcontrol: Add support for NVMe namespace Identify

Add a -N argument to camcontrol identify.  This will fetch and
display namespace Identify data for NVMe drives, using
nvme_print_namespace() from nvmecontrol(8).

This gets the namespace Identify data via XPT_DEV_ADVINFO (i.e.
data cached in the kernel) like the standard NVMe identify command
does, and so works even on NVMe devices that do not support NVMe
Namespace Management.

sbin/camcontrol/camcontrol.c:
	Add a -N argument to camcontrol identify.

	Rename nvme_get_cdata() to nvme_get_data(), and give it the
	ability to fetch controller or namespace Identify data.
	While here, improve its error reporting with
	cam_error_print(), and only look at the CAM status bits of
	the returned CCB.

	Change identify(), ataidentify(), and nvmeidentify() to
	handle the extra arguments.

	Give nvmeidentify() the ability to fetch and print NVMe
	namespace data, using nvme_print_namespace() from
	nvmecontrol(8).

sbin/camcontrol/camcontrol.h:
	Add the nvme_get_data() prototype, for use by future NVMe
	subcommands living outside camcontrol.c.

sbin/camcontrol/camcontrol.8:
	Document camcontrol identify -N.  Make it clear that the
	NVMe data is from data cached in the kernel.  Add a cross
	reference to nvmecontrol(8).

Co-authored-by:	Claude Fable 5 <[email protected]>
Reviewed by:	imp
MFC after:	1 week
Sponsored by:	Spectra Logic
Differential Revision:	https://reviews.freebsd.org/D59533
This commit is contained in:
Kenneth D. Merry committed 2026-11-06 20:05:55 -05:00
1 parent 9814633f63
commit 961fa195b3
3 files changed
+101 -28

No files matched your search

+12 -2
View File
@@ -25,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd December 28, 2023
.Dd August 21, 2026
.Dt CAMCONTROL 8
.Os
.Sh NAME
@@ -67,6 +67,7 @@
.Ic identify
.Op device id
.Op generic args
.Op Fl N
.Op Fl v
.Nm
.Ic reportluns
@@ -524,7 +525,15 @@ This is to aid in script writing.
Print out transfer rate information.
.El
.It Ic identify
Send an ATA identify command (0xec) to a device.
Send a ATA identify command (0xec) or NVMe identify command to a device.
For NVMe devices, this displays Identify data cached in the kernel, and
does not send commands to the drive.
.Bl -tag -width 8n
.It Fl N
Display NVMe Identify Namespace data.
This shows the capacity of the namespace (CAM peripheral drivers are attached
to a NVMe namespace) as opposed to the entire drive.
.El
.It Ic reportluns
Send the SCSI REPORT LUNS (0xA0) command to the given device.
By default,
@@ -2935,6 +2944,7 @@ camcontrol timestamp sa0 -s -f "%a, %d %b %Y %T %z" \e
.Xr pass 4 ,
.Xr xpt 4 ,
.Xr diskinfo 8 ,
.Xr nvmecontrol 8 ,
.Xr trim 8 ,
.Xr zonectl 8
.Sh HISTORY
+87 -26
View File
@@ -185,7 +185,7 @@ static char pwd_opt;
static struct camcontrol_opts option_table[] = {
{"tur", CAM_CMD_TUR, CAM_ARG_NONE, NULL},
{"inquiry", CAM_CMD_INQUIRY, CAM_ARG_NONE, "DSR"},
{"identify", CAM_CMD_IDENTIFY, CAM_ARG_NONE, NULL},
{"identify", CAM_CMD_IDENTIFY, CAM_ARG_NONE, "N"},
{"start", CAM_CMD_STARTSTOP, CAM_ARG_START_UNIT, NULL},
{"stop", CAM_CMD_STARTSTOP, CAM_ARG_NONE, NULL},
{"load", CAM_CMD_STARTSTOP, CAM_ARG_START_UNIT | CAM_ARG_EJECT, NULL},
@@ -779,12 +779,35 @@ print_dev_mmcsd(struct device_match_result *dev_result, char *tmpstr)
return (0);
}
static int
nvme_get_cdata(struct cam_device *dev, struct nvme_controller_data *cdata)
int
nvme_get_data(struct cam_device *dev, int nvme_data_type, void *data,
size_t data_len)
{
union ccb *ccb;
struct nvme_controller_data *cdata;
struct nvme_namespace_data *nsdata;
struct ccb_dev_advinfo *advi;
switch (nvme_data_type) {
case CDAI_TYPE_NVME_CNTRL:
if (data_len != sizeof(*cdata)) {
warnx("%s: supplied data length %zu != controller data "
"length %zu", __func__, data_len, sizeof(*nsdata));
goto bailout_error;
}
break;
case CDAI_TYPE_NVME_NS:
if (data_len != sizeof(*nsdata)) {
warnx("%s: supplied data length %zu != namespace data "
"length %zu", __func__, data_len, sizeof(*nsdata));
goto bailout_error;
}
break;
default:
warnx("Unknown NVMe data type %d", nvme_data_type);
goto bailout_error;
}
ccb = cam_getccb(dev);
if (ccb == NULL) {
warnx("couldn't allocate CCB");
@@ -792,28 +815,31 @@ nvme_get_cdata(struct cam_device *dev, struct nvme_controller_data *cdata)
return (1);
}
CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->cdai);
advi = &ccb->cdai;
advi->ccb_h.flags = CAM_DIR_IN;
advi->ccb_h.func_code = XPT_DEV_ADVINFO;
advi->flags = CDAI_FLAG_NONE;
advi->buftype = CDAI_TYPE_NVME_CNTRL;
advi->bufsiz = sizeof(struct nvme_controller_data);
advi->buf = (uint8_t *)cdata;
advi->buftype = nvme_data_type;
advi->bufsiz = data_len;
advi->buf = data;
if (cam_send_ccb(dev, ccb) < 0) {
warn("error sending XPT_DEV_ADVINFO CCB");
warn("%s: error sending XPT_DEV_ADVINFO CCB", __func__);
cam_freeccb(ccb);
cam_close_device(dev);
return(1);
goto bailout_error;
}
if (advi->ccb_h.status != CAM_REQ_CMP) {
warnx("got CAM error %#x", advi->ccb_h.status);
if ((advi->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
cam_error_print(dev, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
cam_freeccb(ccb);
cam_close_device(dev);
return(1);
goto bailout_error;
}
cam_freeccb(ccb);
return 0;
bailout_error:
cam_close_device(dev);
return (1);
}
static int
@@ -830,7 +856,7 @@ print_dev_nvme(struct device_match_result *dev_result, char *tmpstr)
return (1);
}
if (nvme_get_cdata(dev, &cdata))
if (nvme_get_data(dev, CDAI_TYPE_NVME_CNTRL, &cdata, sizeof(cdata)))
return (1);
cam_strvis(vendor, cdata.mn, sizeof(cdata.mn), sizeof(vendor));
@@ -2470,7 +2496,8 @@ retry:
static int
ataidentify(struct cam_device *device, int retry_count, int timeout)
ataidentify(struct cam_device *device, int argc __unused, char **argv __unused,
char *combinedopt __unused, int retry_count, int timeout)
{
union ccb *ccb;
struct ata_params *ident_buf;
@@ -2515,19 +2542,47 @@ ataidentify(struct cam_device *device, int retry_count, int timeout)
}
static int
nvmeidentify(struct cam_device *device, int retry_count __unused, int timeout __unused)
nvmeidentify(struct cam_device *device, int argc, char **argv,
char *combinedopt, int retry_count __unused, int timeout __unused)
{
struct nvme_controller_data cdata;
int ns_identify = 0;
int c;
if (nvme_get_cdata(device, &cdata))
return (1);
nvme_print_controller(&cdata);
while ((c = getopt(argc, argv, combinedopt)) != -1) {
switch (c) {
case 'N':
ns_identify = 1;
break;
default:
break;
}
}
if (ns_identify == 0) {
struct nvme_controller_data cdata;
if (nvme_get_data(device, CDAI_TYPE_NVME_CNTRL, &cdata,
sizeof(cdata)))
return (1);
nvme_print_controller(&cdata);
} else {
struct nvme_namespace_data ns_data;
if (nvme_get_data(device, CDAI_TYPE_NVME_NS, &ns_data,
sizeof(ns_data)))
return (1);
nvme_print_namespace(&ns_data);
}
return (0);
}
static int
identify(struct cam_device *device, int retry_count, int timeout)
identify(struct cam_device *device, int argc, char **argv, char *combinedopt,
int retry_count, int timeout)
{
struct ccb_pathinq cpi;
@@ -2537,9 +2592,12 @@ identify(struct cam_device *device, int retry_count, int timeout)
}
if (cpi.protocol == PROTO_NVME) {
return (nvmeidentify(device, retry_count, timeout));
return (nvmeidentify(device, argc, argv, combinedopt,
retry_count, timeout));
} else {
return (ataidentify(device, argc, argv, combinedopt,
retry_count, timeout));
}
return (ataidentify(device, retry_count, timeout));
}
@@ -10030,7 +10088,7 @@ usage(int printlong)
" camcontrol tur [dev_id][generic args]\n"
" camcontrol sense [dev_id][generic args][-D][-x]\n"
" camcontrol inquiry [dev_id][generic args] [-D] [-S] [-R]\n"
" camcontrol identify [dev_id][generic args] [-v]\n"
" camcontrol identify [dev_id][generic args] [-N] [-v]\n"
" camcontrol reportluns [dev_id][generic args] [-c] [-l] [-r report]\n"
" camcontrol readcap [dev_id][generic args] [-b] [-h] [-H] [-N]\n"
" [-q] [-s] [-l]\n"
@@ -10120,7 +10178,7 @@ usage(int printlong)
"sense send a request sense command to the named device\n"
"tur send a test unit ready to the named device\n"
"inquiry send a SCSI inquiry command to the named device\n"
"identify send a ATA identify command to the named device\n"
"identify send a ATA or NVMe identify command to the named device\n"
"reportluns send a SCSI report luns command to the device\n"
"readcap send a SCSI read capacity command to the device\n"
"start send a Start Unit command to the device\n"
@@ -10189,6 +10247,8 @@ usage(int printlong)
"-D get the standard inquiry data\n"
"-S get the serial number\n"
"-R get the transfer rate, etc.\n"
"identify arguments:\n"
"-N send identify to the NVMe namespace\n"
"reportluns arguments:\n"
"-c only report a count of available LUNs\n"
"-l only print out luns, and not a count\n"
@@ -10668,7 +10728,8 @@ main(int argc, char **argv)
task_attr, retry_count, timeout);
break;
case CAM_CMD_IDENTIFY:
error = identify(cam_dev, retry_count, timeout);
error = identify(cam_dev, argc, argv, combinedopt, retry_count,
timeout);
break;
case CAM_CMD_STARTSTOP:
error = scsistart(cam_dev, arglist & CAM_ARG_START_UNIT,
+2
View File
@@ -75,6 +75,8 @@ int build_ata_cmd(union ccb *ccb, uint32_t retry_count, uint32_t flags,
int get_ata_status(struct cam_device *dev, union ccb *ccb, uint8_t *error,
uint16_t *count, uint64_t *lba, uint8_t *device,
uint8_t *status);
int nvme_get_data(struct cam_device *dev, int nvme_data_type, void *data,
size_t data_len);
int camxferrate(struct cam_device *device);
int fwdownload(struct cam_device *device, int argc, char **argv,
char *combinedopt, int printerrors, int task_attr,