root/sys/scsi/ses.c
/*      $OpenBSD: ses.c,v 1.64 2021/10/24 16:57:30 mpi Exp $ */

/*
 * Copyright (c) 2005 David Gwynne <dlg@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "bio.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/scsiio.h>
#include <sys/malloc.h>
#include <sys/pool.h>
#include <sys/rwlock.h>
#include <sys/queue.h>
#include <sys/sensors.h>

#if NBIO > 0
#include <dev/biovar.h>
#endif /* NBIO > 0 */

#include <scsi/scsi_all.h>
#include <scsi/scsiconf.h>

#include <scsi/ses.h>

#ifdef SES_DEBUG
#define DPRINTF(x...)           do { if (sesdebug) printf(x); } while (0)
#define DPRINTFN(n, x...)       do { if (sesdebug > (n)) printf(x); } while (0)
int     sesdebug = 2;
#else
#define DPRINTF(x...)           /* x */
#define DPRINTFN(n,x...)        /* n: x */
#endif /* SES_DEBUG */

int     ses_match(struct device *, void *, void *);
void    ses_attach(struct device *, struct device *, void *);
int     ses_detach(struct device *, int);

struct ses_sensor {
        struct ksensor           se_sensor;
        u_int8_t                 se_type;
        struct ses_status       *se_stat;

        TAILQ_ENTRY(ses_sensor) se_entry;
};

#if NBIO > 0
struct ses_slot {
        struct ses_status       *sl_stat;

        TAILQ_ENTRY(ses_slot)    sl_entry;
};
#endif /* NBIO > 0 */

struct ses_softc {
        struct device            sc_dev;
        struct scsi_link        *sc_link;
        struct rwlock            sc_lock;

        enum {
                SES_ENC_STD,
                SES_ENC_DELL
        }                        sc_enctype;

        u_char                  *sc_buf;
        ssize_t                  sc_buflen;

#if NBIO > 0
        TAILQ_HEAD(, ses_slot)   sc_slots;
#endif /* NBIO > 0 */
        TAILQ_HEAD(, ses_sensor) sc_sensors;
        struct ksensordev        sc_sensordev;
        struct sensor_task      *sc_sensortask;
};

const struct cfattach ses_ca = {
        sizeof(struct ses_softc), ses_match, ses_attach, ses_detach
};

struct cfdriver ses_cd = {
        NULL, "ses", DV_DULL
};

#define DEVNAME(s)      ((s)->sc_dev.dv_xname)

#define SES_BUFLEN      2048 /* XXX Is this enough? */

int     ses_read_config(struct ses_softc *);
int     ses_read_status(struct ses_softc *);
int     ses_make_sensors(struct ses_softc *, struct ses_type_desc *, int);
void    ses_refresh_sensors(void *);

#if NBIO > 0
int     ses_ioctl(struct device *, u_long, caddr_t);
int     ses_write_config(struct ses_softc *);
int     ses_bio_blink(struct ses_softc *, struct bioc_blink *);
#endif /* NBIO > 0 */

void    ses_psu2sensor(struct ses_softc *, struct ses_sensor *);
void    ses_cool2sensor(struct ses_softc *, struct ses_sensor *);
void    ses_temp2sensor(struct ses_softc *, struct ses_sensor *);

#ifdef SES_DEBUG
void     ses_dump_enc_desc(struct ses_enc_desc *);
char    *ses_dump_enc_string(u_char *, ssize_t);
#endif /* SES_DEBUG */

int
ses_match(struct device *parent, void *match, void *aux)
{
        struct scsi_attach_args         *sa = aux;
        struct scsi_inquiry_data        *inq = &sa->sa_sc_link->inqdata;

        if ((inq->device & SID_TYPE) == T_ENCLOSURE &&
            SID_ANSII_REV(inq) >= SCSI_REV_2)
                return 2;

        /* Match on Dell enclosures. */
        if ((inq->device & SID_TYPE) == T_PROCESSOR &&
            SID_ANSII_REV(inq) == SCSI_REV_SPC)
                return 3;

        return 0;
}

void
ses_attach(struct device *parent, struct device *self, void *aux)
{
        char                             vendor[33];
        struct ses_softc                *sc = (struct ses_softc *)self;
        struct scsi_attach_args         *sa = aux;
        struct ses_sensor               *sensor;
#if NBIO > 0
        struct ses_slot                 *slot;
#endif /* NBIO > 0 */

        sc->sc_link = sa->sa_sc_link;
        sa->sa_sc_link->device_softc = sc;
        rw_init(&sc->sc_lock, DEVNAME(sc));

        scsi_strvis(vendor, sc->sc_link->inqdata.vendor,
            sizeof(sc->sc_link->inqdata.vendor));
        if (strncasecmp(vendor, "Dell", sizeof(vendor)) == 0)
                sc->sc_enctype = SES_ENC_DELL;
        else
                sc->sc_enctype = SES_ENC_STD;

        printf("\n");

        if (ses_read_config(sc) != 0) {
                printf("%s: unable to read enclosure configuration\n",
                    DEVNAME(sc));
                return;
        }

        if (!TAILQ_EMPTY(&sc->sc_sensors)) {
                sc->sc_sensortask = sensor_task_register(sc,
                    ses_refresh_sensors, 10);
                if (sc->sc_sensortask == NULL) {
                        printf("%s: unable to register update task\n",
                            DEVNAME(sc));
                        while (!TAILQ_EMPTY(&sc->sc_sensors)) {
                                sensor = TAILQ_FIRST(&sc->sc_sensors);
                                TAILQ_REMOVE(&sc->sc_sensors, sensor,
                                    se_entry);
                                free(sensor, M_DEVBUF, sizeof(*sensor));
                        }
                } else {
                        TAILQ_FOREACH(sensor, &sc->sc_sensors, se_entry)
                                sensor_attach(&sc->sc_sensordev,
                                    &sensor->se_sensor);
                        sensordev_install(&sc->sc_sensordev);
                }
        }

#if NBIO > 0
        if (!TAILQ_EMPTY(&sc->sc_slots) &&
            bio_register(self, ses_ioctl) != 0) {
                printf("%s: unable to register ioctl\n", DEVNAME(sc));
                while (!TAILQ_EMPTY(&sc->sc_slots)) {
                        slot = TAILQ_FIRST(&sc->sc_slots);
                        TAILQ_REMOVE(&sc->sc_slots, slot, sl_entry);
                        free(slot, M_DEVBUF, sizeof(*slot));
                }
        }
#endif /* NBIO > 0 */

        if (TAILQ_EMPTY(&sc->sc_sensors)
#if NBIO > 0
            && TAILQ_EMPTY(&sc->sc_slots)
#endif /* NBIO > 0 */
            ) {
                dma_free(sc->sc_buf, sc->sc_buflen);
                sc->sc_buf = NULL;
        }
}

int
ses_detach(struct device *self, int flags)
{
        struct ses_softc                *sc = (struct ses_softc *)self;
        struct ses_sensor               *sensor;
#if NBIO > 0
        struct ses_slot                 *slot;
#endif /* NBIO > 0 */

        rw_enter_write(&sc->sc_lock);

#if NBIO > 0
        if (!TAILQ_EMPTY(&sc->sc_slots)) {
                bio_unregister(self);
                while (!TAILQ_EMPTY(&sc->sc_slots)) {
                        slot = TAILQ_FIRST(&sc->sc_slots);
                        TAILQ_REMOVE(&sc->sc_slots, slot, sl_entry);
                        free(slot, M_DEVBUF, sizeof(*slot));
                }
        }
#endif /* NBIO > 0 */

        if (!TAILQ_EMPTY(&sc->sc_sensors)) {
                sensordev_deinstall(&sc->sc_sensordev);
                sensor_task_unregister(sc->sc_sensortask);

                while (!TAILQ_EMPTY(&sc->sc_sensors)) {
                        sensor = TAILQ_FIRST(&sc->sc_sensors);
                        sensor_detach(&sc->sc_sensordev, &sensor->se_sensor);
                        TAILQ_REMOVE(&sc->sc_sensors, sensor, se_entry);
                        free(sensor, M_DEVBUF, sizeof(*sensor));
                }
        }

        if (sc->sc_buf != NULL)
                dma_free(sc->sc_buf, sc->sc_buflen);

        rw_exit_write(&sc->sc_lock);

        return 0;
}

int
ses_read_config(struct ses_softc *sc)
{
        struct ses_scsi_diag            *cmd;
        struct ses_config_hdr           *cfg;
        struct ses_type_desc            *tdh, *tdlist;
#ifdef SES_DEBUG
        struct ses_enc_desc             *desc;
#endif /* SES_DEBUG */
        struct ses_enc_hdr              *enc;
        struct scsi_xfer                *xs;
        u_char                          *buf, *p;
        int                              error = 0, i;
        int                              flags = 0, ntypes = 0, nelems = 0;

        buf = dma_alloc(SES_BUFLEN, PR_NOWAIT | PR_ZERO);
        if (buf == NULL)
                return 1;

        if (cold)
                SET(flags, SCSI_AUTOCONF);
        xs = scsi_xs_get(sc->sc_link, flags | SCSI_DATA_IN | SCSI_SILENT);
        if (xs == NULL) {
                error = 1;
                goto done;
        }
        xs->cmdlen = sizeof(*cmd);
        xs->data = buf;
        xs->datalen = SES_BUFLEN;
        xs->retries = 2;
        xs->timeout = 3000;

        cmd = (struct ses_scsi_diag *)&xs->cmd;
        cmd->opcode = RECEIVE_DIAGNOSTIC;
        SET(cmd->flags, SES_DIAG_PCV);
        cmd->pgcode = SES_PAGE_CONFIG;
        cmd->length = htobe16(SES_BUFLEN);

        error = scsi_xs_sync(xs);
        scsi_xs_put(xs);

        if (error) {
                error = 1;
                goto done;
        }

        cfg = (struct ses_config_hdr *)buf;
        if (cfg->pgcode != SES_PAGE_CONFIG || betoh16(cfg->length) >
            SES_BUFLEN) {
                error = 1;
                goto done;
        }

        DPRINTF("%s: config: n_subenc: %d length: %d\n", DEVNAME(sc),
            cfg->n_subenc, betoh16(cfg->length));

        p = buf + SES_CFG_HDRLEN;
        for (i = 0; i <= cfg->n_subenc; i++) {
                enc = (struct ses_enc_hdr *)p;
#ifdef SES_DEBUG
                DPRINTF("%s: enclosure %d enc_id: 0x%02x n_types: %d\n",
                    DEVNAME(sc), i, enc->enc_id, enc->n_types);
                desc = (struct ses_enc_desc *)(p + SES_ENC_HDRLEN);
                ses_dump_enc_desc(desc);
#endif /* SES_DEBUG */

                ntypes += enc->n_types;

                p += SES_ENC_HDRLEN + enc->vendor_len;
        }

        tdlist = (struct ses_type_desc *)p; /* Stash this for later. */

        for (i = 0; i < ntypes; i++) {
                tdh = (struct ses_type_desc *)p;
                DPRINTF("%s: td %d subenc_id: %d type 0x%02x n_elem: %d\n",
                    DEVNAME(sc), i, tdh->subenc_id, tdh->type, tdh->n_elem);

                nelems += tdh->n_elem;

                p += SES_TYPE_DESCLEN;
        }

#ifdef SES_DEBUG
        for (i = 0; i < ntypes; i++) {
                DPRINTF("%s: td %d '%s'\n", DEVNAME(sc), i,
                    ses_dump_enc_string(p, tdlist[i].desc_len));

                p += tdlist[i].desc_len;
        }
#endif /* SES_DEBUG */

        sc->sc_buflen = SES_STAT_LEN(ntypes, nelems);
        sc->sc_buf = dma_alloc(sc->sc_buflen, PR_NOWAIT);
        if (sc->sc_buf == NULL) {
                error = 1;
                goto done;
        }

        /* Get the status page and then use it to generate a list of sensors. */
        if (ses_make_sensors(sc, tdlist, ntypes) != 0) {
                dma_free(sc->sc_buf, sc->sc_buflen);
                error = 1;
                goto done;
        }

done:
        if (buf)
                dma_free(buf, SES_BUFLEN);
        return error;
}

int
ses_read_status(struct ses_softc *sc)
{
        struct ses_scsi_diag            *cmd;
        struct scsi_xfer                *xs;
        int                              error, flags = 0;

        if (cold)
                SET(flags, SCSI_AUTOCONF);
        xs = scsi_xs_get(sc->sc_link, flags | SCSI_DATA_IN | SCSI_SILENT);
        if (xs == NULL)
                return 1;
        xs->cmdlen = sizeof(*cmd);
        xs->data = sc->sc_buf;
        xs->datalen = sc->sc_buflen;
        xs->retries = 2;
        xs->timeout = 3000;

        cmd = (struct ses_scsi_diag *)&xs->cmd;
        cmd->opcode = RECEIVE_DIAGNOSTIC;
        SET(cmd->flags, SES_DIAG_PCV);
        cmd->pgcode = SES_PAGE_STATUS;
        cmd->length = htobe16(sc->sc_buflen);

        error = scsi_xs_sync(xs);
        scsi_xs_put(xs);

        if (error != 0)
                return 1;

        return 0;
}

int
ses_make_sensors(struct ses_softc *sc, struct ses_type_desc *types, int ntypes)
{
        struct ses_status               *status;
        struct ses_sensor               *sensor;
        char                            *fmt;
#if NBIO > 0
        struct ses_slot                 *slot;
#endif /* NBIO > 0 */
        enum sensor_type                 stype;
        int                              i, j;

        if (ses_read_status(sc) != 0)
                return 1;

        strlcpy(sc->sc_sensordev.xname, DEVNAME(sc),
            sizeof(sc->sc_sensordev.xname));

        TAILQ_INIT(&sc->sc_sensors);
#if NBIO > 0
        TAILQ_INIT(&sc->sc_slots);
#endif /* NBIO > 0 */

        status = (struct ses_status *)(sc->sc_buf + SES_STAT_HDRLEN);
        for (i = 0; i < ntypes; i++) {
                /* Ignore the overall status element for this type. */
                DPRINTFN(1, "%s: %3d:-   0x%02x 0x%02x%02x%02x type: 0x%02x\n",
                     DEVNAME(sc), i, status->com, status->f1, status->f2,
                    status->f3, types[i].type);

                for (j = 0; j < types[i].n_elem; j++) {
                        /* Move to the current status element. */
                        status++;

                        DPRINTFN(1, "%s: %3d:%-3d 0x%02x 0x%02x%02x%02x\n",
                            DEVNAME(sc), i, j, status->com, status->f1,
                            status->f2, status->f3);

                        if (SES_STAT_CODE(status->com) == SES_STAT_CODE_NOTINST)
                                continue;

                        switch (types[i].type) {
#if NBIO > 0
                        case SES_T_DEVICE:
                                slot = malloc(sizeof(*slot), M_DEVBUF,
                                    M_NOWAIT | M_ZERO);
                                if (slot == NULL)
                                        goto error;

                                slot->sl_stat = status;

                                TAILQ_INSERT_TAIL(&sc->sc_slots, slot,
                                    sl_entry);

                                continue;
#endif /* NBIO > 0 */

                        case SES_T_POWERSUPPLY:
                                stype = SENSOR_INDICATOR;
                                fmt = "PSU";
                                break;

                        case SES_T_COOLING:
                                stype = SENSOR_PERCENT;
                                fmt = "Fan";
                                break;

                        case SES_T_TEMP:
                                stype = SENSOR_TEMP;
                                fmt = "";
                                break;

                        default:
                                continue;
                        }

                        sensor = malloc(sizeof(*sensor), M_DEVBUF,
                            M_NOWAIT | M_ZERO);
                        if (sensor == NULL)
                                goto error;

                        sensor->se_type = types[i].type;
                        sensor->se_stat = status;
                        sensor->se_sensor.type = stype;
                        strlcpy(sensor->se_sensor.desc, fmt,
                            sizeof(sensor->se_sensor.desc));

                        TAILQ_INSERT_TAIL(&sc->sc_sensors, sensor, se_entry);
                }

                /* Move to the overall status element of the next type. */
                status++;
        }

        return 0;
error:
#if NBIO > 0
        while (!TAILQ_EMPTY(&sc->sc_slots)) {
                slot = TAILQ_FIRST(&sc->sc_slots);
                TAILQ_REMOVE(&sc->sc_slots, slot, sl_entry);
                free(slot, M_DEVBUF, sizeof(*slot));
        }
#endif /* NBIO > 0 */
        while (!TAILQ_EMPTY(&sc->sc_sensors)) {
                sensor = TAILQ_FIRST(&sc->sc_sensors);
                TAILQ_REMOVE(&sc->sc_sensors, sensor, se_entry);
                free(sensor, M_DEVBUF, sizeof(*sensor));
        }
        return 1;
}

void
ses_refresh_sensors(void *arg)
{
        struct ses_softc                *sc = (struct ses_softc *)arg;
        struct ses_sensor               *sensor;
        int                              ret = 0;

        rw_enter_write(&sc->sc_lock);

        if (ses_read_status(sc) != 0) {
                rw_exit_write(&sc->sc_lock);
                return;
        }

        TAILQ_FOREACH(sensor, &sc->sc_sensors, se_entry) {
                DPRINTFN(10, "%s: %s 0x%02x 0x%02x%02x%02x\n", DEVNAME(sc),
                    sensor->se_sensor.desc, sensor->se_stat->com,
                    sensor->se_stat->f1, sensor->se_stat->f2,
                    sensor->se_stat->f3);

                switch (SES_STAT_CODE(sensor->se_stat->com)) {
                case SES_STAT_CODE_OK:
                        sensor->se_sensor.status = SENSOR_S_OK;
                        break;

                case SES_STAT_CODE_CRIT:
                case SES_STAT_CODE_UNREC:
                        sensor->se_sensor.status = SENSOR_S_CRIT;
                        break;

                case SES_STAT_CODE_NONCRIT:
                        sensor->se_sensor.status = SENSOR_S_WARN;
                        break;

                case SES_STAT_CODE_NOTINST:
                case SES_STAT_CODE_UNKNOWN:
                case SES_STAT_CODE_NOTAVAIL:
                        sensor->se_sensor.status = SENSOR_S_UNKNOWN;
                        break;
                }

                switch (sensor->se_type) {
                case SES_T_POWERSUPPLY:
                        ses_psu2sensor(sc, sensor);
                        break;

                case SES_T_COOLING:
                        ses_cool2sensor(sc, sensor);
                        break;

                case SES_T_TEMP:
                        ses_temp2sensor(sc, sensor);
                        break;

                default:
                        ret = 1;
                        break;
                }
        }

        rw_exit_write(&sc->sc_lock);

        if (ret)
                printf("%s: error in sensor data\n", DEVNAME(sc));
}

#if NBIO > 0
int
ses_ioctl(struct device *dev, u_long cmd, caddr_t addr)
{
        struct ses_softc                *sc = (struct ses_softc *)dev;
        int                              error = 0;

        switch (cmd) {
        case BIOCBLINK:
                error = ses_bio_blink(sc, (struct bioc_blink *)addr);
                break;

        default:
                error = EINVAL;
                break;
        }

        return error;
}

int
ses_write_config(struct ses_softc *sc)
{
        struct ses_scsi_diag            *cmd;
        struct scsi_xfer                *xs;
        int                              error, flags = 0;

        if (cold)
                SET(flags, SCSI_AUTOCONF);

        xs = scsi_xs_get(sc->sc_link, flags | SCSI_DATA_OUT | SCSI_SILENT);
        if (xs == NULL)
                return 1;
        xs->cmdlen = sizeof(*cmd);
        xs->data = sc->sc_buf;
        xs->datalen = sc->sc_buflen;
        xs->retries = 2;
        xs->timeout = 3000;

        cmd = (struct ses_scsi_diag *)&xs->cmd;
        cmd->opcode = SEND_DIAGNOSTIC;
        SET(cmd->flags, SES_DIAG_PF);
        cmd->length = htobe16(sc->sc_buflen);

        error = scsi_xs_sync(xs);
        scsi_xs_put(xs);

        if (error != 0)
                return 1;

        return 0;
}

int
ses_bio_blink(struct ses_softc *sc, struct bioc_blink *blink)
{
        struct ses_slot                 *slot;

        rw_enter_write(&sc->sc_lock);

        if (ses_read_status(sc) != 0) {
                rw_exit_write(&sc->sc_lock);
                return EIO;
        }

        TAILQ_FOREACH(slot, &sc->sc_slots, sl_entry) {
                if (slot->sl_stat->f1 == blink->bb_target)
                        break;
        }

        if (slot == NULL) {
                rw_exit_write(&sc->sc_lock);
                return EINVAL;
        }

        DPRINTFN(3, "%s: 0x%02x 0x%02x 0x%02x 0x%02x\n", DEVNAME(sc),
            slot->sl_stat->com, slot->sl_stat->f1, slot->sl_stat->f2,
            slot->sl_stat->f3);

        slot->sl_stat->com = SES_STAT_SELECT;
        slot->sl_stat->f2 &= SES_C_DEV_F2MASK;
        slot->sl_stat->f3 &= SES_C_DEV_F3MASK;

        switch (blink->bb_status) {
        case BIOC_SBUNBLINK:
                slot->sl_stat->f2 &= ~SES_C_DEV_IDENT;
                break;

        case BIOC_SBBLINK:
                SET(slot->sl_stat->f2, SES_C_DEV_IDENT);
                break;

        default:
                rw_exit_write(&sc->sc_lock);
                return EINVAL;
        }

        DPRINTFN(3, "%s: 0x%02x 0x%02x 0x%02x 0x%02x\n", DEVNAME(sc),
            slot->sl_stat->com, slot->sl_stat->f1, slot->sl_stat->f2,
            slot->sl_stat->f3);

        if (ses_write_config(sc) != 0) {
                rw_exit_write(&sc->sc_lock);
                return EIO;
        }

        rw_exit_write(&sc->sc_lock);

        return 0;
}
#endif /* NBIO > 0 */

void
ses_psu2sensor(struct ses_softc *sc, struct ses_sensor *s)
{
        s->se_sensor.value = SES_S_PSU_OFF(s->se_stat) ? 0 : 1;
}

void
ses_cool2sensor(struct ses_softc *sc, struct ses_sensor *s)
{
        switch (sc->sc_enctype) {
        case SES_ENC_STD:
                switch (SES_S_COOL_CODE(s->se_stat)) {
                case SES_S_COOL_C_STOPPED:
                        s->se_sensor.value = 0;
                        break;
                case SES_S_COOL_C_LOW1:
                case SES_S_COOL_C_LOW2:
                case SES_S_COOL_C_LOW3:
                        s->se_sensor.value = 33333;
                        break;
                case SES_S_COOL_C_INTER:
                case SES_S_COOL_C_HI3:
                case SES_S_COOL_C_HI2:
                        s->se_sensor.value = 66666;
                        break;
                case SES_S_COOL_C_HI1:
                        s->se_sensor.value = 100000;
                        break;
                }
                break;

        /* Dell only use the first three codes to represent speed */
        case SES_ENC_DELL:
                switch (SES_S_COOL_CODE(s->se_stat)) {
                case SES_S_COOL_C_STOPPED:
                        s->se_sensor.value = 0;
                        break;
                case SES_S_COOL_C_LOW1:
                        s->se_sensor.value = 33333;
                        break;
                case SES_S_COOL_C_LOW2:
                        s->se_sensor.value = 66666;
                        break;
                case SES_S_COOL_C_LOW3:
                case SES_S_COOL_C_INTER:
                case SES_S_COOL_C_HI3:
                case SES_S_COOL_C_HI2:
                case SES_S_COOL_C_HI1:
                        s->se_sensor.value = 100000;
                        break;
                }
                break;
        }
}

void
ses_temp2sensor(struct ses_softc *sc, struct ses_sensor *s)
{
        s->se_sensor.value = (int64_t)SES_S_TEMP(s->se_stat);
        s->se_sensor.value += SES_S_TEMP_OFFSET;
        s->se_sensor.value *= 1000000;          /* Convert to micro degrees. */
        s->se_sensor.value += 273150000;        /* Convert to kelvin. */
}

#ifdef SES_DEBUG
void
ses_dump_enc_desc(struct ses_enc_desc *desc)
{
        char                            str[32];

#if 0
        /* XXX not a string. wwn? */
        memset(str, 0, sizeof(str));
        memcpy(str, desc->logical_id, sizeof(desc->logical_id));
        DPRINTF("logical_id: %s", str);
#endif /* 0 */

        memset(str, 0, sizeof(str));
        memcpy(str, desc->vendor_id, sizeof(desc->vendor_id));
        DPRINTF(" vendor_id: %s", str);

        memset(str, 0, sizeof(str));
        memcpy(str, desc->prod_id, sizeof(desc->prod_id));
        DPRINTF(" prod_id: %s", str);

        memset(str, 0, sizeof(str));
        memcpy(str, desc->prod_rev, sizeof(desc->prod_rev));
        DPRINTF(" prod_rev: %s\n", str);
}

char *
ses_dump_enc_string(u_char *buf, ssize_t len)
{
        static char                     str[256];

        memset(str, 0, sizeof(str));
        if (len > 0)
                memcpy(str, buf, len);

        return str;
}
#endif /* SES_DEBUG */