-/* $OpenBSD: tpm.c,v 1.12 2021/09/11 23:22:38 deraadt Exp $ */
+/* $OpenBSD: tpm.c,v 1.13 2021/10/23 16:39:03 dv Exp $ */
/*
* Minimal interface to Trusted Platform Module chips implementing the
#define TPM_READ_TMO 120000 /* 2 minutes */
#define TPM_BURST_TMO 2000 /* 2sec */
+#define TPM2_START_METHOD_TIS 6
+#define TPM2_START_METHOD_CRB 7
+
+#define TPM_CRB_LOC_STATE 0x0
+#define TPM_CRB_LOC_CTRL 0x8
+#define TPM_LOC_STS 0xC
+#define TPM_CRB_INTF_ID 0x30
+#define TPM_CRB_CTRL_EXT 0x38
+#define TPM_CRB_CTRL_REQ 0x40
+#define TPM_CRB_CTRL_STS 0x44
+#define TPM_CRB_CTRL_CANCEL 0x48
+#define TPM_CRB_CTRL_START 0x4C
+#define TPM_CRB_CTRL_CMD_SIZE 0x58
+#define TPM_CRB_CTRL_CMD_LADDR 0x5C
+#define TPM_CRB_CTRL_CMD_HADDR 0x60
+#define TPM_CRB_CTRL_RSP_SIZE 0x64
+#define TPM_CRB_CTRL_RSP_LADDR 0x68
+#define TPM_CRB_CTRL_RSP_HADDR 0x6c
+#define TPM_CRB_DATA_BUFFER 0x80
+
+#define TPM_CRB_LOC_STATE_ESTB (1 << 0)
+#define TPM_CRB_LOC_STATE_ASSIGNED (1 << 1)
+#define TPM_CRB_LOC_ACTIVE_MASK 0x009c
+#define TPM_CRB_LOC_VALID (1 << 7)
+
+#define TPM_CRB_LOC_REQUEST (1 << 0)
+#define TPM_CRB_LOC_RELEASE (1 << 1)
+
+#define TPM_CRB_CTRL_REQ_GO_READY (1 << 0)
+#define TPM_CRB_CTRL_REQ_GO_IDLE (1 << 1)
+
+#define TPM_CRB_CTRL_STS_ERR_BIT (1 << 0)
+#define TPM_CRB_CTRL_STS_IDLE_BIT (1 << 1)
+
+#define TPM_CRB_CTRL_CANCEL_CMD 0x1
+#define TPM_CRB_CTRL_CANCEL_CLEAR 0x0
+
+#define TPM_CRB_CTRL_START_CMD (1 << 0)
+#define TPM_CRB_INT_ENABLED_BIT (1 << 31)
+
+#define TPM2_RC_SUCCESS 0x0000
+#define TPM2_RC_INITIALIZE 0x0100
+#define TPM2_RC_FAILURE 0x0101
+#define TPM2_RC_DISABLED 0x0120
+#define TPM2_RC_RETRY 0x0922
+
struct tpm_softc {
struct device sc_dev;
bus_space_tag_t sc_bt;
bus_space_handle_t sc_bh;
+ bus_size_t sc_bbase;
struct acpi_softc *sc_acpi;
struct aml_node *sc_devnode;
uint32_t sc_devid;
uint32_t sc_rev;
+
int sc_tpm20;
+ int sc_tpm_mode;
+#define TPM_TIS 0
+#define TPM_CRB 1
+ bus_size_t sc_cmd_off;
+ bus_size_t sc_rsp_off;
+ size_t sc_cmd_sz;
+ size_t sc_rsp_sz;
int sc_enabled;
};
int tpm_activate(struct device *, int);
int tpm_probe(bus_space_tag_t, bus_space_handle_t);
-int tpm_init(struct tpm_softc *);
-int tpm_read(struct tpm_softc *, void *, int, size_t *, int);
-int tpm_write(struct tpm_softc *, void *, int);
+int tpm_init_tis(struct tpm_softc *);
+int tpm_init_crb(struct tpm_softc *);
+int tpm_read_tis(struct tpm_softc *, void *, int, size_t *, int);
+int tpm_read_crb(struct tpm_softc *, void *, int);
+int tpm_write_tis(struct tpm_softc *, void *, int);
+int tpm_write_crb(struct tpm_softc *, void *, int);
int tpm_suspend(struct tpm_softc *);
int tpm_resume(struct tpm_softc *);
-int tpm_waitfor(struct tpm_softc *, uint8_t, int);
-int tpm_request_locality(struct tpm_softc *, int);
-void tpm_release_locality(struct tpm_softc *);
-int tpm_getburst(struct tpm_softc *);
+int tpm_waitfor(struct tpm_softc *, bus_space_handle_t, uint32_t, uint32_t, int);
+int tpm_waitfor_status(struct tpm_softc *, uint8_t, int);
+int tpm_request_locality_tis(struct tpm_softc *, int);
+int tpm_request_locality_crb(struct tpm_softc *, int);
+void tpm_release_locality_tis(struct tpm_softc *);
+void tpm_release_locality_crb(struct tpm_softc *);
+int tpm_getburst_tis(struct tpm_softc *);
+int tpm_getburst_crb(struct tpm_softc *);
uint8_t tpm_status(struct tpm_softc *);
+uint32_t tpm2_start_method(struct acpi_softc *);
+
struct cfattach tpm_ca = {
sizeof(struct tpm_softc),
tpm_match,
struct tpm_softc *sc = (struct tpm_softc *)self;
struct acpi_attach_args *aaa = aux;
int64_t sta;
+ uint32_t start_method;
sc->sc_acpi = (struct acpi_softc *)parent;
sc->sc_devnode = aaa->aaa_node;
sc->sc_enabled = 0;
+ sc->sc_tpm_mode = TPM_TIS;
printf(" %s", sc->sc_devnode->name);
if (strcmp(aaa->aaa_dev, "MSFT0101") == 0 ||
- strcmp(aaa->aaa_cdev, "MSFT0101") == 0)
+ strcmp(aaa->aaa_cdev, "MSFT0101") == 0) {
sc->sc_tpm20 = 1;
+ /* Identify if using 1.2 TIS or 2.0's CRB methods */
+ start_method = tpm2_start_method(sc->sc_acpi);
+ switch (start_method) {
+ case TPM2_START_METHOD_TIS:
+ /* Already default */
+ break;
+ case TPM2_START_METHOD_CRB:
+ sc->sc_tpm_mode = TPM_CRB;
+ break;
+ default:
+ printf(": unsupported TPM2 start method\n");
+ return;
+ }
+ }
+
+ printf(" %s (%s)", sc->sc_tpm20 ? "2.0" : "1.2",
+ sc->sc_tpm_mode == TPM_TIS ? "TIS" : "CRB");
sta = acpi_getsta(sc->sc_acpi, sc->sc_devnode);
if ((sta & (STA_PRESENT | STA_ENABLED | STA_DEV_OK)) !=
}
printf(" addr 0x%llx/0x%llx", aaa->aaa_addr[0], aaa->aaa_size[0]);
+ sc->sc_bbase = aaa->aaa_addr[0];
sc->sc_bt = aaa->aaa_bst[0];
if (bus_space_map(sc->sc_bt, aaa->aaa_addr[0], aaa->aaa_size[0],
return;
}
- if (!tpm_probe(sc->sc_bt, sc->sc_bh)) {
- printf(": probe failed\n");
- return;
- }
+ if (sc->sc_tpm_mode == TPM_TIS) {
+ if (!tpm_probe(sc->sc_bt, sc->sc_bh)) {
+ printf(": probe failed\n");
+ return;
+ }
- if (tpm_init(sc) != 0) {
- printf(": init failed\n");
- return;
+ if (tpm_init_tis(sc) != 0) {
+ printf(": init failed\n");
+ return;
+ }
+ } else {
+ if (tpm_init_crb(sc) != 0) {
+ printf(": init failed\n");
+ return;
+ }
}
printf("\n");
* Tell the chip to save its state so the BIOS can then restore it upon
* resume.
*/
- tpm_write(sc, command, commandlen);
- tpm_read(sc, command, commandlen, NULL, TPM_HDRSIZE);
-
+ if (sc->sc_tpm_mode == TPM_TIS) {
+ tpm_write_tis(sc, command, commandlen);
+ memset(command, 0, commandlen);
+ tpm_read_tis(sc, command, commandlen, NULL, TPM_HDRSIZE);
+ } else {
+ tpm_write_crb(sc, command, commandlen);
+ memset(command, 0, commandlen);
+ tpm_read_crb(sc, command, commandlen);
+ }
return 0;
}
return 0;
}
+uint32_t
+tpm2_start_method(struct acpi_softc *sc)
+{
+ struct acpi_q *entry;
+ struct acpi_tpm2 *p_tpm2 = NULL;
+
+ SIMPLEQ_FOREACH(entry, &sc->sc_tables, q_next) {
+ if (memcmp(entry->q_table, TPM2_SIG,
+ sizeof(TPM2_SIG) - 1) == 0) {
+ p_tpm2 = entry->q_table;
+ break;
+ }
+ }
+
+ if (!p_tpm2) {
+ DPRINTF((", no TPM2 table"));
+ return 0;
+ }
+
+ return p_tpm2->start_method;
+}
+
int
tpm_probe(bus_space_tag_t bt, bus_space_handle_t bh)
{
}
int
-tpm_init(struct tpm_softc *sc)
+tpm_init_tis(struct tpm_softc *sc)
{
uint32_t r, intmask;
int i;
intmask &= ~TPM_GLOBAL_INT_ENABLE;
bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE, intmask);
- if (tpm_request_locality(sc, 0)) {
+ if (tpm_request_locality_tis(sc, 0)) {
printf(", requesting locality failed\n");
return 1;
}
}
int
-tpm_request_locality(struct tpm_softc *sc, int l)
+tpm_init_crb(struct tpm_softc *sc)
+{
+ uint32_t intmask;
+ int i;
+
+ if (tpm_request_locality_crb(sc, 0)) {
+ printf(", request locality failed\n");
+ return 1;
+ }
+
+ /* ack and disable all interrupts, we'll be using polling only */
+ intmask = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE);
+ intmask &= ~TPM_CRB_INT_ENABLED_BIT;
+ bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_INTERRUPT_ENABLE, intmask);
+
+ /* Identify command and response registers and sizes */
+ sc->sc_cmd_off = bus_space_read_4(sc->sc_bt, sc->sc_bh,
+ TPM_CRB_CTRL_CMD_LADDR);
+ sc->sc_cmd_off |= ((uint64_t) bus_space_read_4(sc->sc_bt, sc->sc_bh,
+ TPM_CRB_CTRL_CMD_HADDR) << 32);
+ sc->sc_cmd_sz = bus_space_read_4(sc->sc_bt, sc->sc_bh,
+ TPM_CRB_CTRL_CMD_SIZE);
+
+ sc->sc_rsp_off = bus_space_read_4(sc->sc_bt, sc->sc_bh,
+ TPM_CRB_CTRL_RSP_LADDR);
+ sc->sc_rsp_off |= ((uint64_t) bus_space_read_4(sc->sc_bt, sc->sc_bh,
+ TPM_CRB_CTRL_RSP_HADDR) << 32);
+ sc->sc_rsp_sz = bus_space_read_4(sc->sc_bt, sc->sc_bh,
+ TPM_CRB_CTRL_RSP_SIZE);
+
+ DPRINTF((", cmd @ 0x%lx, %ld, rsp @ 0x%lx, %ld", sc->sc_cmd_off,
+ sc->sc_cmd_sz, sc->sc_rsp_off, sc->sc_rsp_sz));
+
+ sc->sc_cmd_off = sc->sc_cmd_off - sc->sc_bbase;
+ sc->sc_rsp_off = sc->sc_rsp_off - sc->sc_bbase;
+
+ tpm_release_locality_crb(sc);
+
+ /* If it's a unified buffer, the sizes must be the same. */
+ if (sc->sc_cmd_off == sc->sc_rsp_off) {
+ if (sc->sc_cmd_sz != sc->sc_rsp_sz) {
+ printf(", invalid buffer sizes\n");
+ return 1;
+ }
+ }
+
+ for (i = 0; tpm_devs[i].devid; i++)
+ if (tpm_devs[i].devid == sc->sc_devid)
+ break;
+
+ if (tpm_devs[i].devid)
+ printf(", %s rev 0x%x", tpm_devs[i].name, sc->sc_rev);
+ else
+ printf(", device 0x%08x rev 0x%x", sc->sc_devid, sc->sc_rev);
+
+ return 0;
+}
+
+int
+tpm_request_locality_tis(struct tpm_softc *sc, int l)
{
uint32_t r;
int to;
return 0;
}
+int
+tpm_request_locality_crb(struct tpm_softc *sc, int l)
+{
+ uint32_t r, mask;
+ int to;
+
+ if (l != 0)
+ return EINVAL;
+
+ r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_CTRL);
+ bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_CTRL,
+ r | TPM_CRB_LOC_REQUEST);
+
+ to = TPM_ACCESS_TMO * 200;
+ mask = TPM_CRB_LOC_STATE_ASSIGNED | TPM_CRB_LOC_VALID;
+
+ r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_STATE);
+ while ((r & mask) != mask && to--) {
+ DELAY(10);
+ r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_STATE);
+ }
+
+ if ((r & mask) != mask) {
+ printf(", CRB loc FAILED");
+ return EBUSY;
+ }
+
+ return 0;
+}
+
void
-tpm_release_locality(struct tpm_softc *sc)
+tpm_release_locality_tis(struct tpm_softc *sc)
{
if ((bus_space_read_1(sc->sc_bt, sc->sc_bh, TPM_ACCESS) &
(TPM_ACCESS_REQUEST_PENDING|TPM_ACCESS_VALID)) ==
}
}
+void
+tpm_release_locality_crb(struct tpm_softc *sc)
+{
+ uint32_t r;
+
+ r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_CTRL);
+ bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_LOC_CTRL,
+ r | TPM_CRB_LOC_RELEASE);
+}
+
int
tpm_getburst(struct tpm_softc *sc)
{
}
int
-tpm_waitfor(struct tpm_softc *sc, uint8_t mask, int msecs)
+tpm_waitfor(struct tpm_softc *sc, bus_size_t offset, uint32_t mask,
+ uint32_t val, int msecs)
+{
+ int usecs;
+ uint32_t r;
+
+ usecs = msecs * 1000;
+
+ r = bus_space_read_4(sc->sc_bt, sc->sc_bh, offset);
+ if ((r & mask) == val)
+ return 0;
+
+ while (usecs > 0) {
+ r = bus_space_read_4(sc->sc_bt, sc->sc_bh, offset);
+ if ((r & mask) == val)
+ return 0;
+ DELAY(1);
+ usecs--;
+ }
+
+ DPRINTF(("%s: %s: timed out, status 0x%x != 0x%x\n",
+ sc->sc_dev.dv_xname, __func__, r, mask));
+ return ETIMEDOUT;
+}
+
+int
+tpm_waitfor_status(struct tpm_softc *sc, uint8_t mask, int msecs)
{
int usecs;
uint8_t status;
}
int
-tpm_read(struct tpm_softc *sc, void *buf, int len, size_t *count,
+tpm_read_tis(struct tpm_softc *sc, void *buf, int len, size_t *count,
int flags)
{
uint8_t *p = buf;
cnt = 0;
while (len > 0) {
- if ((rv = tpm_waitfor(sc, TPM_STS_DATA_AVAIL | TPM_STS_VALID,
- TPM_READ_TMO)))
+ if ((rv = tpm_waitfor_status(sc,
+ TPM_STS_DATA_AVAIL | TPM_STS_VALID, TPM_READ_TMO)))
return rv;
bcnt = tpm_getburst(sc);
}
int
-tpm_write(struct tpm_softc *sc, void *buf, int len)
+tpm_read_crb(struct tpm_softc *sc, void *buf, int len)
+{
+ uint8_t *p = buf;
+ uint32_t sz = 0, mask, rc;
+ size_t count = 0;
+ int r;
+
+ DPRINTF(("%s: %s %d:", sc->sc_dev.dv_xname, __func__, len));
+
+ if (len < TPM_HDRSIZE) {
+ printf("%s: %s buf len too small\n", sc->sc_dev.dv_xname,
+ __func__);
+ return EINVAL;
+ }
+
+ while (count < TPM_HDRSIZE) {
+ *p = bus_space_read_1(sc->sc_bt, sc->sc_bh,
+ sc->sc_rsp_off + count);
+ DPRINTF((" %02x", *p));
+ count++;
+ p++;
+ }
+ DPRINTF(("\n"));
+
+ /* Response length is bytes 2-5 in the response header. */
+ p = buf;
+ sz = be32toh(*(uint32_t *) (p + 2));
+ if (sz < TPM_HDRSIZE || sz > sc->sc_rsp_sz) {
+ printf("%s: invalid response size %d\n",
+ sc->sc_dev.dv_xname, sz);
+ return EIO;
+ }
+ if (sz > len)
+ printf("%s: response size too large, truncated to %d\n",
+ sc->sc_dev.dv_xname, len);
+
+ /* Response code is bytes 6-9. */
+ rc = be32toh(*(uint32_t *) (p + 6));
+ if (rc != TPM2_RC_SUCCESS) {
+ printf("%s: command failed (0x%04x)\n", sc->sc_dev.dv_xname,
+ rc);
+ /* Nothing we can do on failure. Still try to idle the tpm. */
+ }
+
+ /* Tell the device to go idle. */
+ r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ);
+ bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ,
+ r | TPM_CRB_CTRL_REQ_GO_IDLE);
+
+ mask = TPM_CRB_CTRL_STS_IDLE_BIT;
+ if (tpm_waitfor(sc, TPM_CRB_CTRL_STS, mask, mask, 200)) {
+ printf("%s: failed to transition to idle state after read\n",
+ sc->sc_dev.dv_xname);
+ }
+
+ tpm_release_locality_crb(sc);
+
+ DPRINTF(("%s: %s completed\n", sc->sc_dev.dv_xname, __func__));
+ return 0;
+}
+
+int
+tpm_write_tis(struct tpm_softc *sc, void *buf, int len)
{
uint8_t *p = buf;
uint8_t status;
size_t count = 0;
int rv, r;
- if ((rv = tpm_request_locality(sc, 0)) != 0)
+ if ((rv = tpm_request_locality_tis(sc, 0)) != 0)
return rv;
DPRINTF(("%s: %s %d:", sc->sc_dev.dv_xname, __func__, len));
/* abort! */
bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_STS,
TPM_STS_CMD_READY);
- if ((rv = tpm_waitfor(sc, TPM_STS_CMD_READY, TPM_READ_TMO))) {
+ if ((rv = tpm_waitfor_status(sc, TPM_STS_CMD_READY,
+ TPM_READ_TMO))) {
DPRINTF(("%s: failed waiting for ready after abort "
"(0x%x)\n", sc->sc_dev.dv_xname, rv));
return rv;
bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_DATA, *p++);
count++;
}
- if ((rv = tpm_waitfor(sc, TPM_STS_VALID | TPM_STS_DATA_EXPECT,
+ if ((rv = tpm_waitfor_status(sc, TPM_STS_VALID | TPM_STS_DATA_EXPECT,
TPM_READ_TMO))) {
DPRINTF(("%s: %s: failed waiting for next byte (%d)\n",
sc->sc_dev.dv_xname, __func__, rv));
bus_space_write_1(sc->sc_bt, sc->sc_bh, TPM_DATA, *p);
count++;
- if ((rv = tpm_waitfor(sc, TPM_STS_VALID, TPM_READ_TMO))) {
+ if ((rv = tpm_waitfor_status(sc, TPM_STS_VALID, TPM_READ_TMO))) {
DPRINTF(("%s: %s: failed after last byte (%d)\n",
sc->sc_dev.dv_xname, __func__, rv));
return rv;
return 0;
}
+
+int
+tpm_write_crb(struct tpm_softc *sc, void *buf, int len)
+{
+ uint8_t *p = buf;
+ size_t count = 0;
+ uint32_t r, mask;
+
+ if (len > sc->sc_cmd_sz) {
+ printf("%s: requested write length larger than cmd buffer\n",
+ sc->sc_dev.dv_xname);
+ return EINVAL;
+ }
+
+ if (bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_STS)
+ & TPM_CRB_CTRL_STS_ERR_BIT) {
+ printf("%s: device error bit set\n", sc->sc_dev.dv_xname);
+ return EIO;
+ }
+
+ if (tpm_request_locality_crb(sc, 0)) {
+ printf("%s: failed to acquire locality\n", sc->sc_dev.dv_xname);
+ return EIO;
+ }
+
+ /* Clear cancellation bit */
+ bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_CANCEL,
+ TPM_CRB_CTRL_CANCEL_CLEAR);
+
+ /* Toggle to idle state (if needed) and then to ready */
+ r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_STS);
+ if(!(r & TPM_CRB_CTRL_STS_IDLE_BIT)) {
+ printf("%s: asking device to idle\n", sc->sc_dev.dv_xname);
+ r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ);
+ bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ,
+ r | TPM_CRB_CTRL_REQ_GO_IDLE);
+
+ mask = TPM_CRB_CTRL_STS_IDLE_BIT;
+ if (tpm_waitfor(sc, TPM_CRB_CTRL_STS, mask, mask, 200)) {
+ printf("%s: failed to transition to idle state before "
+ "write\n", sc->sc_dev.dv_xname);
+ return EIO;
+ }
+ }
+ r = bus_space_read_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ);
+ bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_REQ,
+ r | TPM_CRB_CTRL_REQ_GO_READY);
+ mask = TPM_CRB_CTRL_REQ_GO_READY;
+ if (tpm_waitfor(sc, TPM_CRB_CTRL_STS, mask, !mask, 200)) {
+ printf("%s: failed to transition to ready state\n",
+ sc->sc_dev.dv_xname);
+ return EIO;
+ }
+
+ /* Write the command */
+ DPRINTF(("%s: %s %d:", sc->sc_dev.dv_xname, __func__, len));
+ while (count < len) {
+ DPRINTF((" %02x", (uint8_t)(*p)));
+ bus_space_write_1(sc->sc_bt, sc->sc_bh, sc->sc_cmd_off + count,
+ *p++);
+ count++;
+ }
+ DPRINTF(("\n"));
+ bus_space_barrier(sc->sc_bt, sc->sc_bh, sc->sc_cmd_off, len,
+ BUS_SPACE_BARRIER_WRITE);
+ DPRINTF(("%s: %s wrote %lu bytes\n", sc->sc_dev.dv_xname, __func__,
+ count));
+
+ /* Send the Start Command request */
+ bus_space_write_4(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_START,
+ TPM_CRB_CTRL_START_CMD);
+ bus_space_barrier(sc->sc_bt, sc->sc_bh, TPM_CRB_CTRL_START, 4,
+ BUS_SPACE_BARRIER_WRITE);
+
+ /* Check if command was processed */
+ mask = ~0;
+ if (tpm_waitfor(sc, TPM_CRB_CTRL_START, mask, ~mask, 200)) {
+ printf("%s: timeout waiting for device to process command\n",
+ sc->sc_dev.dv_xname);
+ return EIO;
+ }
+
+ return 0;
+}