Release 4.12 block/sed-opal.c
/*
* Copyright © 2016 Intel Corporation
*
* Authors:
* Scott Bauer <scott.bauer@intel.com>
* Rafael Antognolli <rafael.antognolli@intel.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":OPAL: " fmt
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/genhd.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <uapi/linux/sed-opal.h>
#include <linux/sed-opal.h>
#include <linux/string.h>
#include <linux/kdev_t.h>
#include "opal_proto.h"
#define IO_BUFFER_LENGTH 2048
#define MAX_TOKS 64
struct opal_step {
int (*fn)(struct opal_dev *dev, void *data);
void *data;
};
typedef int (cont_fn)(struct opal_dev *dev);
enum opal_atom_width {
OPAL_WIDTH_TINY,
OPAL_WIDTH_SHORT,
OPAL_WIDTH_MEDIUM,
OPAL_WIDTH_LONG,
OPAL_WIDTH_TOKEN
};
/*
* On the parsed response, we don't store again the toks that are already
* stored in the response buffer. Instead, for each token, we just store a
* pointer to the position in the buffer where the token starts, and the size
* of the token in bytes.
*/
struct opal_resp_tok {
const u8 *pos;
size_t len;
enum opal_response_token type;
enum opal_atom_width width;
union {
u64 u;
s64 s;
} stored;
};
/*
* From the response header it's not possible to know how many tokens there are
* on the payload. So we hardcode that the maximum will be MAX_TOKS, and later
* if we start dealing with messages that have more than that, we can increase
* this number. This is done to avoid having to make two passes through the
* response, the first one counting how many tokens we have and the second one
* actually storing the positions.
*/
struct parsed_resp {
int num;
struct opal_resp_tok toks[MAX_TOKS];
};
struct opal_dev {
bool supported;
void *data;
sec_send_recv *send_recv;
const struct opal_step *steps;
struct mutex dev_lock;
u16 comid;
u32 hsn;
u32 tsn;
u64 align;
u64 lowest_lba;
size_t pos;
u8 cmd[IO_BUFFER_LENGTH];
u8 resp[IO_BUFFER_LENGTH];
struct parsed_resp parsed;
size_t prev_d_len;
void *prev_data;
struct list_head unlk_lst;
};
static const u8 opaluid[][OPAL_UID_LENGTH] = {
/* users */
[OPAL_SMUID_UID] =
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff },
[OPAL_THISSP_UID] =
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 },
[OPAL_ADMINSP_UID] =
{ 0x00, 0x00, 0x02, 0x05, 0x00, 0x00, 0x00, 0x01 },
[OPAL_LOCKINGSP_UID] =
{ 0x00, 0x00, 0x02, 0x05, 0x00, 0x00, 0x00, 0x02 },
[OPAL_ENTERPRISE_LOCKINGSP_UID] =
{ 0x00, 0x00, 0x02, 0x05, 0x00, 0x01, 0x00, 0x01 },
[OPAL_ANYBODY_UID] =
{ 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01 },
[OPAL_SID_UID] =
{ 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x06 },
[OPAL_ADMIN1_UID] =
{ 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x00, 0x01 },
[OPAL_USER1_UID] =
{ 0x00, 0x00, 0x00, 0x09, 0x00, 0x03, 0x00, 0x01 },
[OPAL_USER2_UID] =
{ 0x00, 0x00, 0x00, 0x09, 0x00, 0x03, 0x00, 0x02 },
[OPAL_PSID_UID] =
{ 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0xff, 0x01 },
[OPAL_ENTERPRISE_BANDMASTER0_UID] =
{ 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x80, 0x01 },
[OPAL_ENTERPRISE_ERASEMASTER_UID] =
{ 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x84, 0x01 },
/* tables */
[OPAL_LOCKINGRANGE_GLOBAL] =
{ 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x01 },
[OPAL_LOCKINGRANGE_ACE_RDLOCKED] =
{ 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0xE0, 0x01 },
[OPAL_LOCKINGRANGE_ACE_WRLOCKED] =
{ 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0xE8, 0x01 },
[OPAL_MBRCONTROL] =
{ 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x00, 0x01 },
[OPAL_MBR] =
{ 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00 },
[OPAL_AUTHORITY_TABLE] =
{ 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00},
[OPAL_C_PIN_TABLE] =
{ 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00},
[OPAL_LOCKING_INFO_TABLE] =
{ 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x01 },
[OPAL_ENTERPRISE_LOCKING_INFO_TABLE] =
{ 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00 },
/* C_PIN_TABLE object ID's */
[OPAL_C_PIN_MSID] =
{ 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x84, 0x02},
[OPAL_C_PIN_SID] =
{ 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01},
[OPAL_C_PIN_ADMIN1] =
{ 0x00, 0x00, 0x00, 0x0B, 0x00, 0x01, 0x00, 0x01},
/* half UID's (only first 4 bytes used) */
[OPAL_HALF_UID_AUTHORITY_OBJ_REF] =
{ 0x00, 0x00, 0x0C, 0x05, 0xff, 0xff, 0xff, 0xff },
[OPAL_HALF_UID_BOOLEAN_ACE] =
{ 0x00, 0x00, 0x04, 0x0E, 0xff, 0xff, 0xff, 0xff },
/* special value for omitted optional parameter */
[OPAL_UID_HEXFF] =
{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
};
/*
* TCG Storage SSC Methods.
* Derived from: TCG_Storage_Architecture_Core_Spec_v2.01_r1.00
* Section: 6.3 Assigned UIDs
*/
static const u8 opalmethod[][OPAL_UID_LENGTH] = {
[OPAL_PROPERTIES] =
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01 },
[OPAL_STARTSESSION] =
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x02 },
[OPAL_REVERT] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x02, 0x02 },
[OPAL_ACTIVATE] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x02, 0x03 },
[OPAL_EGET] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06 },
[OPAL_ESET] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07 },
[OPAL_NEXT] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08 },
[OPAL_EAUTHENTICATE] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0c },
[OPAL_GETACL] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0d },
[OPAL_GENKEY] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x10 },
[OPAL_REVERTSP] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x11 },
[OPAL_GET] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x16 },
[OPAL_SET] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x17 },
[OPAL_AUTHENTICATE] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1c },
[OPAL_RANDOM] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x01 },
[OPAL_ERASE] =
{ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x08, 0x03 },
};
static int end_opal_session_error(struct opal_dev *dev);
struct opal_suspend_data {
struct opal_lock_unlock unlk;
u8 lr;
struct list_head node;
};
/*
* Derived from:
* TCG_Storage_Architecture_Core_Spec_v2.01_r1.00
* Section: 5.1.5 Method Status Codes
*/
static const char * const opal_errors[] = {
"Success",
"Not Authorized",
"Unknown Error",
"SP Busy",
"SP Failed",
"SP Disabled",
"SP Frozen",
"No Sessions Available",
"Uniqueness Conflict",
"Insufficient Space",
"Insufficient Rows",
"Invalid Function",
"Invalid Parameter",
"Invalid Reference",
"Unknown Error",
"TPER Malfunction",
"Transaction Failure",
"Response Overflow",
"Authority Locked Out",
};
static const char *opal_error_to_human(int error)
{
if (error == 0x3f)
return "Failed";
if (error >= ARRAY_SIZE(opal_errors) || error < 0)
return "Unknown Error";
return opal_errors[error];
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 42 | 100.00% | 1 | 100.00% |
Total | 42 | 100.00% | 1 | 100.00% |
static void print_buffer(const u8 *ptr, u32 length)
{
#ifdef DEBUG
print_hex_dump_bytes("OPAL: ", DUMP_PREFIX_OFFSET, ptr, length);
pr_debug("\n");
#endif
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 35 | 100.00% | 1 | 100.00% |
Total | 35 | 100.00% | 1 | 100.00% |
static bool check_tper(const void *data)
{
const struct d0_tper_features *tper = data;
u8 flags = tper->supported_features;
if (!(flags & TPER_SYNC_SUPPORTED)) {
pr_debug("TPer sync not supported. flags = %d\n",
tper->supported_features);
return false;
}
return true;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 52 | 100.00% | 2 | 100.00% |
Total | 52 | 100.00% | 2 | 100.00% |
static bool check_sum(const void *data)
{
const struct d0_single_user_mode *sum = data;
u32 nlo = be32_to_cpu(sum->num_locking_objects);
if (nlo == 0) {
pr_debug("Need at least one locking object.\n");
return false;
}
pr_debug("Number of locking objects: %d\n", nlo);
return true;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 55 | 100.00% | 2 | 100.00% |
Total | 55 | 100.00% | 2 | 100.00% |
static u16 get_comid_v100(const void *data)
{
const struct d0_opal_v100 *v100 = data;
return be16_to_cpu(v100->baseComID);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 27 | 100.00% | 1 | 100.00% |
Total | 27 | 100.00% | 1 | 100.00% |
static u16 get_comid_v200(const void *data)
{
const struct d0_opal_v200 *v200 = data;
return be16_to_cpu(v200->baseComID);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 27 | 100.00% | 1 | 100.00% |
Total | 27 | 100.00% | 1 | 100.00% |
static int opal_send_cmd(struct opal_dev *dev)
{
return dev->send_recv(dev->data, dev->comid, TCG_SECP_01,
dev->cmd, IO_BUFFER_LENGTH,
true);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 33 | 94.29% | 1 | 50.00% |
Christoph Hellwig | 2 | 5.71% | 1 | 50.00% |
Total | 35 | 100.00% | 2 | 100.00% |
static int opal_recv_cmd(struct opal_dev *dev)
{
return dev->send_recv(dev->data, dev->comid, TCG_SECP_01,
dev->resp, IO_BUFFER_LENGTH,
false);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 33 | 94.29% | 1 | 50.00% |
Christoph Hellwig | 2 | 5.71% | 1 | 50.00% |
Total | 35 | 100.00% | 2 | 100.00% |
static int opal_recv_check(struct opal_dev *dev)
{
size_t buflen = IO_BUFFER_LENGTH;
void *buffer = dev->resp;
struct opal_header *hdr = buffer;
int ret;
do {
pr_debug("Sent OPAL command: outstanding=%d, minTransfer=%d\n",
hdr->cp.outstandingData,
hdr->cp.minTransfer);
if (hdr->cp.outstandingData == 0 ||
hdr->cp.minTransfer != 0)
return 0;
memset(buffer, 0, buflen);
ret = opal_recv_cmd(dev);
} while (!ret);
return ret;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 100 | 100.00% | 1 | 100.00% |
Total | 100 | 100.00% | 1 | 100.00% |
static int opal_send_recv(struct opal_dev *dev, cont_fn *cont)
{
int ret;
ret = opal_send_cmd(dev);
if (ret)
return ret;
ret = opal_recv_cmd(dev);
if (ret)
return ret;
ret = opal_recv_check(dev);
if (ret)
return ret;
return cont(dev);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 66 | 100.00% | 1 | 100.00% |
Total | 66 | 100.00% | 1 | 100.00% |
static void check_geometry(struct opal_dev *dev, const void *data)
{
const struct d0_geometry_features *geo = data;
dev->align = geo->alignment_granularity;
dev->lowest_lba = geo->lowest_aligned_lba;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 40 | 100.00% | 1 | 100.00% |
Total | 40 | 100.00% | 1 | 100.00% |
static int next(struct opal_dev *dev)
{
const struct opal_step *step;
int state = 0, error = 0;
do {
step = &dev->steps[state];
if (!step->fn)
break;
error = step->fn(dev, step->data);
if (error) {
pr_debug("Error on step function: %d with error %d: %s\n",
state, error,
opal_error_to_human(error));
/* For each OPAL command we do a discovery0 then we
* start some sort of session.
* If we haven't passed state 1 then there was an error
* on discovery0 or during the attempt to start a
* session. Therefore we shouldn't attempt to terminate
* a session, as one has not yet been created.
*/
if (state > 1) {
end_opal_session_error(dev);
return error;
}
}
state++;
} while (!error);
return error;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 88 | 80.73% | 3 | 75.00% |
Jon Derrick | 21 | 19.27% | 1 | 25.00% |
Total | 109 | 100.00% | 4 | 100.00% |
static int opal_discovery0_end(struct opal_dev *dev)
{
bool found_com_id = false, supported = true, single_user = false;
const struct d0_header *hdr = (struct d0_header *)dev->resp;
const u8 *epos = dev->resp, *cpos = dev->resp;
u16 comid = 0;
u32 hlen = be32_to_cpu(hdr->length);
print_buffer(dev->resp, hlen);
if (hlen > IO_BUFFER_LENGTH - sizeof(*hdr)) {
pr_debug("Discovery length overflows buffer (%zu+%u)/%u\n",
sizeof(*hdr), hlen, IO_BUFFER_LENGTH);
return -EFAULT;
}
epos += hlen; /* end of buffer */
cpos += sizeof(*hdr); /* current position on buffer */
while (cpos < epos && supported) {
const struct d0_features *body =
(const struct d0_features *)cpos;
switch (be16_to_cpu(body->code)) {
case FC_TPER:
supported = check_tper(body->features);
break;
case FC_SINGLEUSER:
single_user = check_sum(body->features);
break;
case FC_GEOMETRY:
check_geometry(dev, body);
break;
case FC_LOCKING:
case FC_ENTERPRISE:
case FC_DATASTORE:
/* some ignored properties */
pr_debug("Found OPAL feature description: %d\n",
be16_to_cpu(body->code));
break;
case FC_OPALV100:
comid = get_comid_v100(body->features);
found_com_id = true;
break;
case FC_OPALV200:
comid = get_comid_v200(body->features);
found_com_id = true;
break;
case 0xbfff ... 0xffff:
/* vendor specific, just ignore */
break;
default:
pr_debug("OPAL Unknown feature: %d\n",
be16_to_cpu(body->code));
}
cpos += body->length + 4;
}
if (!supported) {
pr_debug("This device is not Opal enabled. Not Supported!\n");
return -EOPNOTSUPP;
}
if (!single_user)
pr_debug("Device doesn't support single user mode\n");
if (!found_com_id) {
pr_debug("Could not find OPAL comid for device. Returning early\n");
return -EOPNOTSUPP;;
}
dev->comid = comid;
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 291 | 86.87% | 2 | 50.00% |
Jon Derrick | 41 | 12.24% | 1 | 25.00% |
Christoph Hellwig | 3 | 0.90% | 1 | 25.00% |
Total | 335 | 100.00% | 4 | 100.00% |
static int opal_discovery0(struct opal_dev *dev, void *data)
{
int ret;
memset(dev->resp, 0, IO_BUFFER_LENGTH);
dev->comid = OPAL_DISCOVERY_COMID;
ret = opal_recv_cmd(dev);
if (ret)
return ret;
return opal_discovery0_end(dev);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 51 | 92.73% | 1 | 50.00% |
Jon Derrick | 4 | 7.27% | 1 | 50.00% |
Total | 55 | 100.00% | 2 | 100.00% |
static void add_token_u8(int *err, struct opal_dev *cmd, u8 tok)
{
if (*err)
return;
if (cmd->pos >= IO_BUFFER_LENGTH - 1) {
pr_debug("Error adding u8: end of buffer.\n");
*err = -ERANGE;
return;
}
cmd->cmd[cmd->pos++] = tok;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 60 | 100.00% | 2 | 100.00% |
Total | 60 | 100.00% | 2 | 100.00% |
static void add_short_atom_header(struct opal_dev *cmd, bool bytestring,
bool has_sign, int len)
{
u8 atom;
int err = 0;
atom = SHORT_ATOM_ID;
atom |= bytestring ? SHORT_ATOM_BYTESTRING : 0;
atom |= has_sign ? SHORT_ATOM_SIGNED : 0;
atom |= len & SHORT_ATOM_LEN_MASK;
add_token_u8(&err, cmd, atom);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 64 | 100.00% | 1 | 100.00% |
Total | 64 | 100.00% | 1 | 100.00% |
static void add_medium_atom_header(struct opal_dev *cmd, bool bytestring,
bool has_sign, int len)
{
u8 header0;
header0 = MEDIUM_ATOM_ID;
header0 |= bytestring ? MEDIUM_ATOM_BYTESTRING : 0;
header0 |= has_sign ? MEDIUM_ATOM_SIGNED : 0;
header0 |= (len >> 8) & MEDIUM_ATOM_LEN_MASK;
cmd->cmd[cmd->pos++] = header0;
cmd->cmd[cmd->pos++] = len;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 77 | 100.00% | 1 | 100.00% |
Total | 77 | 100.00% | 1 | 100.00% |
static void add_token_u64(int *err, struct opal_dev *cmd, u64 number)
{
size_t len;
int msb;
u8 n;
if (!(number & ~TINY_ATOM_DATA_MASK)) {
add_token_u8(err, cmd, number);
return;
}
msb = fls(number);
len = DIV_ROUND_UP(msb, 4);
if (cmd->pos >= IO_BUFFER_LENGTH - len - 1) {
pr_debug("Error adding u64: end of buffer.\n");
*err = -ERANGE;
return;
}
add_short_atom_header(cmd, false, false, len);
while (len--) {
n = number >> (len * 8);
add_token_u8(err, cmd, n);
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 128 | 100.00% | 2 | 100.00% |
Total | 128 | 100.00% | 2 | 100.00% |
static void add_token_bytestring(int *err, struct opal_dev *cmd,
const u8 *bytestring, size_t len)
{
size_t header_len = 1;
bool is_short_atom = true;
if (*err)
return;
if (len & ~SHORT_ATOM_LEN_MASK) {
header_len = 2;
is_short_atom = false;
}
if (len >= IO_BUFFER_LENGTH - cmd->pos - header_len) {
pr_debug("Error adding bytestring: end of buffer.\n");
*err = -ERANGE;
return;
}
if (is_short_atom)
add_short_atom_header(cmd, true, false, len);
else
add_medium_atom_header(cmd, true, false, len);
memcpy(&cmd->cmd[cmd->pos], bytestring, len);
cmd->pos += len;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 132 | 100.00% | 2 | 100.00% |
Total | 132 | 100.00% | 2 | 100.00% |
static int build_locking_range(u8 *buffer, size_t length, u8 lr)
{
if (length > OPAL_UID_LENGTH) {
pr_debug("Can't build locking range. Length OOB\n");
return -ERANGE;
}
memcpy(buffer, opaluid[OPAL_LOCKINGRANGE_GLOBAL], OPAL_UID_LENGTH);
if (lr == 0)
return 0;
buffer[5] = LOCKING_RANGE_NON_GLOBAL;
buffer[7] = lr;
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 71 | 100.00% | 2 | 100.00% |
Total | 71 | 100.00% | 2 | 100.00% |
static int build_locking_user(u8 *buffer, size_t length, u8 lr)
{
if (length > OPAL_UID_LENGTH) {
pr_debug("Can't build locking range user, Length OOB\n");
return -ERANGE;
}
memcpy(buffer, opaluid[OPAL_USER1_UID], OPAL_UID_LENGTH);
buffer[7] = lr + 1;
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Scott Bauer | 57 | 100.00% | 2 | 100.00% |
Total | 57 | 100.00% | 2 | 100.00% |
static void set_comid(struct opal_dev *cmd, u16 comid)
{
struct opal_header *hdr = (struct opal_header *)cmd->cmd;
hdr->cp.extendedComID[0] = comid >> 8;
hdr->cp.extendedComID[1] = comid;
hdr->cp.extendedComID[2] = 0;
hdr->cp.extendedComID