/* $NetBSD: linux_hdmi.c,v 1.10 2022/07/10 13:56:44 riastradh Exp $ */
/*-
* Copyright (c) 2014 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Taylor R. Campbell.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: linux_hdmi.c,v 1.10 2022/07/10 13:56:44 riastradh Exp $");
#include <sys/types.h>
#include <sys/device.h>
#include <sys/errno.h>
#include <sys/systm.h>
#include <lib/libkern/libkern.h>
#include <linux/hdmi.h>
/* Infoframe headers */
static void
hdmi_infoframe_header_init(struct hdmi_infoframe_header *header,
enum hdmi_infoframe_type type, uint8_t vers, uint8_t length)
{
header->type = type;
header->version = vers;
header->length = length;
}
static int
hdmi_infoframe_header_check(const struct hdmi_infoframe_header *header,
enum hdmi_infoframe_type type, uint8_t vers, uint8_t length)
{
if (header->type != type ||
header->version != vers ||
header->length != length)
return -EINVAL;
return 0;
}
static ssize_t
hdmi_infoframe_header_pack(const struct hdmi_infoframe_header *header,
uint8_t length, void *buf, size_t size)
{
uint8_t *const p = buf;
KASSERT(length >= HDMI_INFOFRAME_HEADER_SIZE);
if (size < length)
return -ENOSPC;
p[0] = header->type;
p[1] = header->version;
p[2] = (length - HDMI_INFOFRAME_HEADER_SIZE);
p[3] = 0; /* checksum */
return HDMI_INFOFRAME_HEADER_SIZE;
}
static uint8_t
hdmi_infoframe_checksum(const void *buf, size_t length)
{
const uint8_t *p = buf;
uint8_t checksum = 0;
while (length--)
checksum += *p++;
return 256 - checksum;
}
static int
hdmi_infoframe_header_unpack(struct hdmi_infoframe_header *header,
const void *buf, size_t size)
{
const uint8_t *const p = buf;
if (size < HDMI_INFOFRAME_HEADER_SIZE)
return -EINVAL;
if (p[2] > size - HDMI_INFOFRAME_HEADER_SIZE)
return -EINVAL;
if (hdmi_infoframe_checksum(buf, p[2] + HDMI_INFOFRAME_HEADER_SIZE))
return -EINVAL;
hdmi_infoframe_header_init(header, p[0], p[1], p[2]);
return 0;
}
static void
hdmi_infoframe_set_checksum(void *buf, size_t length)
{
uint8_t *p = buf;
p[3] = hdmi_infoframe_checksum(buf, length);
}
/* Audio infoframes */
int
hdmi_audio_infoframe_init(struct hdmi_audio_infoframe *frame)
{
static const struct hdmi_audio_infoframe zero_frame;
*frame = zero_frame;
hdmi_infoframe_header_init(&frame->header, HDMI_INFOFRAME_TYPE_AUDIO,
1, HDMI_AUDIO_INFOFRAME_SIZE);
return 0;
}
ssize_t
hdmi_audio_infoframe_pack(const struct hdmi_audio_infoframe *frame, void *buf,
size_t size)
{
const size_t length = HDMI_INFOFRAME_HEADER_SIZE +
HDMI_AUDIO_INFOFRAME_SIZE;
uint8_t channels = 0;
uint8_t *p = buf;
int ret;
KASSERT(frame->header.length == HDMI_AUDIO_INFOFRAME_SIZE);
ret = hdmi_infoframe_header_pack(&frame->header, length, p, size);
if (ret < 0)
return ret;
KASSERT(ret == HDMI_INFOFRAME_HEADER_SIZE);
p += HDMI_INFOFRAME_HEADER_SIZE;
size -= HDMI_INFOFRAME_HEADER_SIZE;
if (frame->channels >= 2)
channels = frame->channels - 1;
p[0] = __SHIFTIN(frame->coding_type, __BITS(7,4));
p[0] |= __SHIFTIN(channels, __BITS(2,0));
p[1] = __SHIFTIN(frame->sample_frequency, __BITS(4,2));
p[1] |= __SHIFTIN(frame->sample_size, __BITS(1,0));
p[2] = __SHIFTIN(frame->coding_type_ext, __BITS(5,0));
p[3] = __SHIFTIN(frame->level_shift_value, __BITS(6,3));
p[4] = __SHIFTIN(frame->downmix_inhibit? 1 : 0, __BIT(7));
/* PB6 to PB10 are reserved */
p[5] = 0;
p[6] = 0;
p[7] = 0;
p[8] = 0;
p[9] = 0;
CTASSERT(HDMI_AUDIO_INFOFRAME_SIZE == 10);
hdmi_infoframe_set_checksum(buf, length);
return length;
}
static int
hdmi_audio_infoframe_unpack(struct hdmi_audio_infoframe *frame,
const void *buf, size_t size)
{
const uint8_t *p = buf;
int ret;
ret = hdmi_infoframe_header_unpack(&frame->header, p, size);
if (ret)
return ret;
if (frame->header.length != HDMI_AUDIO_INFOFRAME_SIZE)
return -EINVAL;
p += HDMI_INFOFRAME_HEADER_SIZE;
size -= HDMI_INFOFRAME_HEADER_SIZE;
frame->coding_type = __SHIFTOUT(p[0], __BITS(7,4));
frame->channels = __SHIFTOUT(p[0], __BITS(2,0));
frame->sample_frequency = __SHIFTOUT(p[1], __BITS(4,2));
frame->sample_size = __SHIFTOUT(p[1], __BITS(1,0));
frame->coding_type_ext = __SHIFTOUT(p[2], __BITS(5,0));
frame->level_shift_value = __SHIFTOUT(p[3], __BITS(6,3));
frame->downmix_inhibit = __SHIFTOUT(p[4], __BIT(7));
return 0;
}
/* AVI infoframes */
int
hdmi_avi_infoframe_init(struct hdmi_avi_infoframe *frame)
{
static const struct hdmi_avi_infoframe zero_frame;
*frame = zero_frame;
hdmi_infoframe_header_init(&frame->header, HDMI_INFOFRAME_TYPE_AVI, 2,
HDMI_AVI_INFOFRAME_SIZE);
return 0;
}
int
hdmi_avi_infoframe_check(const struct hdmi_avi_infoframe *frame)
{
int ret;
ret = hdmi_infoframe_header_check(&frame->header,
HDMI_INFOFRAME_TYPE_AVI, 2, HDMI_AVI_INFOFRAME_SIZE);
if (ret)
return ret;
return 0;
}
ssize_t
hdmi_avi_infoframe_pack(const struct hdmi_avi_infoframe *frame, void *buf,
size_t size)
{
const size_t length = HDMI_INFOFRAME_HEADER_SIZE +
HDMI_AVI_INFOFRAME_SIZE;
uint8_t *p = buf;
int ret;
KASSERT(frame->header.length == HDMI_AVI_INFOFRAME_SIZE);
ret = hdmi_infoframe_header_pack(&frame->header, length, p, size);
if (ret < 0)
return ret;
KASSERT(ret == HDMI_INFOFRAME_HEADER_SIZE);
p += HDMI_INFOFRAME_HEADER_SIZE;
size -= HDMI_INFOFRAME_HEADER_SIZE;
p[0] = __SHIFTIN(frame->colorspace, __BITS(6,5));
p[0] |= __SHIFTIN(frame->active_aspect & 0xf? 1 : 0, __BIT(4));
p[0] |= __SHIFTIN(frame->top_bar || frame->bottom_bar, __BIT(3));
p[0] |= __SHIFTIN(frame->left_bar || frame->right_bar, __BIT(2));
p[0] |= __SHIFTIN(frame->scan_mode, __BITS(1,0));
p[1] = __SHIFTIN(frame->colorimetry, __BITS(7,6));
p[1] |= __SHIFTIN(frame->picture_aspect, __BITS(5,4));
p[1] |= __SHIFTIN(frame->active_aspect, __BITS(3,0));
p[2] = __SHIFTIN(frame->itc? 1 : 0, __BIT(7));
p[2] |= __SHIFTIN(frame->extended_colorimetry, __BITS(6,4));
p[2] |= __SHIFTIN(frame->quantization_range, __BITS(3,2));
p[2] |= __SHIFTIN(frame->nups, __BITS(1,0));
p[3] = frame->video_code;
p[4] = __SHIFTIN(frame->ycc_quantization_range, __BITS(7,6));
p[4] |= __SHIFTIN(frame->content_type, __BITS(5,4));
p[4] |= __SHIFTIN(frame->pixel_repeat, __BITS(3,0));
le16enc(&p[5], frame->top_bar);
le16enc(&p[7], frame->bottom_bar);
le16enc(&p[9], frame->left_bar);
le16enc(&p[11], frame->right_bar);
CTASSERT(HDMI_AVI_INFOFRAME_SIZE == 13);
hdmi_infoframe_set_checksum(buf, length);
return length;
}
static int
hdmi_avi_infoframe_unpack(struct hdmi_avi_infoframe *frame, const void *buf,
size_t size)
{
const uint8_t *p = buf;
int ret;
ret = hdmi_infoframe_header_unpack(&frame->header, p, size);
if (ret)
return ret;
if (frame->header.length != HDMI_AVI_INFOFRAME_SIZE)
return -EINVAL;
p += HDMI_INFOFRAME_HEADER_SIZE;
size -= HDMI_INFOFRAME_HEADER_SIZE;
frame->colorspace = __SHIFTOUT(p[0], __BITS(6,5));
frame->scan_mode = __SHIFTOUT(p[0], __BITS(1,0));
frame->colorimetry = __SHIFTOUT(p[1], __BITS(7,6));
frame->picture_aspect = __SHIFTOUT(p[1], __BITS(5,4));
if (p[0] & __BIT(4))
frame->active_aspect = __SHIFTOUT(p[1], __BITS(3,0));
frame->itc = __SHIFTOUT(p[2], __BIT(7));
frame->extended_colorimetry = __SHIFTOUT(p[2], __BITS(6,4));
frame->quantization_range = __SHIFTOUT(p[2], __BITS(3,2));
frame->nups = __SHIFTOUT(p[2], __BITS(1,0));
frame->video_code = p[3];
frame->ycc_quantization_range = __SHIFTOUT(p[4], __BITS(7,6));
frame->content_type = __SHIFTOUT(p[4], __BITS(5,4));
frame->pixel_repeat = __SHIFTOUT(p[4], __BITS(3,0));
if (p[0] & __BIT(3)) {
frame->top_bar = le16dec(&p[5]);
frame->bottom_bar = le16dec(&p[7]);
}
if (p[0] & __BIT(2)) {
frame->left_bar = le16dec(&p[9]);
frame->right_bar = le16dec(&p[11]);
}
return 0;
}
/* DRM infoframes */
int
hdmi_drm_infoframe_init(struct hdmi_drm_infoframe *frame)
{
static const struct hdmi_drm_infoframe zero_frame;
*frame = zero_frame;
hdmi_infoframe_header_init(&frame->header, HDMI_INFOFRAME_TYPE_DRM,
1, HDMI_DRM_INFOFRAME_SIZE);
return 0;
}
int
hdmi_drm_infoframe_check(const struct hdmi_drm_infoframe *frame)
{
int ret;
ret = hdmi_infoframe_header_check(&frame->header,
HDMI_INFOFRAME_TYPE_DRM, 1, HDMI_DRM_INFOFRAME_SIZE);
if (ret)
return ret;
return 0;
}
__strong_alias(linux_hdmi_drm_infoframe_pack_only,linux_hdmi_drm_infoframe_pack) /* XXX */
ssize_t
hdmi_drm_infoframe_pack(const struct hdmi_drm_infoframe *frame,
void *buf, size_t size)
{
const size_t length = HDMI_INFOFRAME_HEADER_SIZE +
HDMI_DRM_INFOFRAME_SIZE;
uint8_t *p = buf;
unsigned i;
int ret;
KASSERT(frame->header.length == HDMI_DRM_INFOFRAME_SIZE);
ret = hdmi_infoframe_header_pack(&frame->header, length, p, size);
if (ret < 0)
return ret;
KASSERT(ret == HDMI_INFOFRAME_HEADER_SIZE);
p += HDMI_INFOFRAME_HEADER_SIZE;
size -= HDMI_INFOFRAME_HEADER_SIZE;
p[0] = frame->eotf;
p[1] = frame->metadata_type;
for (i = 0; i < __arraycount(frame->display_primaries); i++) {
le16enc(&p[2 + 4*i], frame->display_primaries[i].x);
le16enc(&p[2 + 4*i + 2], frame->display_primaries[i].y);
}
le16enc(&p[14], frame->white_point.x);
le16enc(&p[16], frame->white_point.y);
le16enc(&p[18], frame->min_display_mastering_luminance);
le16enc(&p[20], frame->max_display_mastering_luminance);
le16enc(&p[22], frame->max_cll);
le16enc(&p[24], frame->max_fall);
CTASSERT(HDMI_DRM_INFOFRAME_SIZE == 26);
hdmi_infoframe_set_checksum(buf, length);
return length;
}
static int
hdmi_drm_infoframe_unpack(struct hdmi_drm_infoframe *frame, const void *buf,
size_t size)
{
const uint8_t *p = buf;
unsigned i;
int ret;
ret = hdmi_infoframe_header_unpack(&frame->header, p, size);
if (ret)
return ret;
if (frame->header.length != HDMI_DRM_INFOFRAME_SIZE)
return -EINVAL;
p += HDMI_INFOFRAME_HEADER_SIZE;
size -= HDMI_INFOFRAME_HEADER_SIZE;
frame->eotf = p[0];
frame->metadata_type = p[1];
for (i = 0; i < __arraycount(frame->display_primaries); i++) {
frame->display_primaries[i].x = le16dec(&p[2 + 4*i]);
frame->display_primaries[i].y = le16dec(&p[2 + 4*i + 2]);
}
frame->white_point.x = le16dec(&p[14]);
frame->white_point.y = le16dec(&p[16]);
frame->min_display_mastering_luminance = le16dec(&p[18]);
frame->max_display_mastering_luminance = le16dec(&p[20]);
frame->max_cll = le16dec(&p[22]);
frame->max_fall = le16dec(&p[24]);
return 0;
}
/* SPD infoframes */
int
hdmi_spd_infoframe_init(struct hdmi_spd_infoframe *frame, const char *vendor,
const char *product)
{
static const struct hdmi_spd_infoframe zero_frame;
*frame = zero_frame;
hdmi_infoframe_header_init(&frame->header, HDMI_INFOFRAME_TYPE_SPD,
1, HDMI_SPD_INFOFRAME_SIZE);
strncpy(frame->vendor, vendor, sizeof(frame->vendor));
strncpy(frame->product, product, sizeof(frame->product));
return 0;
}
int
hdmi_spd_infoframe_check(const struct hdmi_spd_infoframe *frame)
{
int ret;
ret = hdmi_infoframe_header_check(&frame->header,
HDMI_INFOFRAME_TYPE_SPD, 1, HDMI_SPD_INFOFRAME_SIZE);
if (ret)
return ret;
return 0;
}
ssize_t
hdmi_spd_infoframe_pack(const struct hdmi_spd_infoframe *frame, void *buf,
size_t size)
{
const size_t length = HDMI_INFOFRAME_HEADER_SIZE +
HDMI_SPD_INFOFRAME_SIZE;
uint8_t *p = buf;
int ret;
KASSERT(frame->header.length == HDMI_SPD_INFOFRAME_SIZE);
ret = hdmi_infoframe_header_pack(&frame->header, length, p, size);
if (ret < 0)
return ret;
KASSERT(ret == HDMI_INFOFRAME_HEADER_SIZE);
p += HDMI_INFOFRAME_HEADER_SIZE;
size -= HDMI_INFOFRAME_HEADER_SIZE;
memcpy(&p[0], frame->vendor, 8);
memcpy(&p[8], frame->product, 16);
p[24] = frame->sdi;
CTASSERT(HDMI_SPD_INFOFRAME_SIZE == 25);
hdmi_infoframe_set_checksum(buf, length);
return length;
}
static int
hdmi_spd_infoframe_unpack(struct hdmi_spd_infoframe *frame, const void *buf,
size_t size)
{
const uint8_t *p = buf;
int ret;
ret = hdmi_infoframe_header_unpack(&frame->header, p, size);
if (ret)
return ret;
if (frame->header.length != HDMI_SPD_INFOFRAME_SIZE)
return -EINVAL;
p += HDMI_INFOFRAME_HEADER_SIZE;
size -= HDMI_INFOFRAME_HEADER_SIZE;
memcpy(frame->vendor, &p[0], 8);
memcpy(frame->product, &p[8], 16);
frame->sdi = p[24];
return 0;
}
/* Vendor infoframes */
int
hdmi_vendor_infoframe_init(struct hdmi_vendor_infoframe *frame)
{
static const struct hdmi_vendor_infoframe zero_frame;
*frame = zero_frame;
hdmi_infoframe_header_init(&frame->header, HDMI_INFOFRAME_TYPE_VENDOR,
1, 0 /* depends on s3d_struct */);
frame->oui = HDMI_IEEE_OUI;
frame->s3d_struct = HDMI_3D_STRUCTURE_INVALID;
return 0;
}
static size_t
hdmi_vendor_infoframe_length(const struct hdmi_vendor_infoframe *frame)
{
if (frame->vic) {
return 5;
} else if (frame->s3d_struct != HDMI_3D_STRUCTURE_INVALID) {
if (frame->s3d_struct < HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF)
return 5;
else
return 6;
} else {
return 4;
}
}
int
hdmi_vendor_infoframe_check(const struct hdmi_vendor_infoframe *frame)
{
if (frame->header.type != HDMI_INFOFRAME_TYPE_VENDOR ||
frame->header.version != 1)
return -EINVAL;
/* frame->header.length not used when packing */
/* At most one may be supplied. */
if (frame->vic != 0 && frame->s3d_struct != HDMI_3D_STRUCTURE_INVALID)
return -EINVAL;
return 0;
}
ssize_t
hdmi_vendor_infoframe_pack(const struct hdmi_vendor_infoframe *frame,
void *buf, size_t size)
{
uint8_t *p = buf;
size_t length;
int ret;
/* At most one may be supplied. */
if (frame->vic != 0 && frame->s3d_struct != HDMI_3D_STRUCTURE_INVALID)
return -EINVAL;
length = HDMI_INFOFRAME_HEADER_SIZE;
length += hdmi_vendor_infoframe_length(frame);
ret = hdmi_infoframe_header_pack(&frame->header, length, p, size);
if (ret < 0)
return ret;
KASSERT(ret == HDMI_INFOFRAME_HEADER_SIZE);
p += HDMI_INFOFRAME_HEADER_SIZE;
size -= HDMI_INFOFRAME_HEADER_SIZE;
p[0] = 0x03;
p[1] = 0x0c;
p[2] = 0x00;
if (frame->vic) {
p[3] = __SHIFTIN(0x1, __BITS(6,5));
p[4] = frame->vic;
} else if (frame->s3d_struct != HDMI_3D_STRUCTURE_INVALID) {
p[3] = __SHIFTIN(0x2, __BITS(6,5));
p[4] = __SHIFTIN(frame->s3d_struct, __BITS(7,4));
if (frame->s3d_struct >= HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF)
p[5] = __SHIFTIN(frame->s3d_ext_data, __BITS(7,4));
} else {
p[3] = __SHIFTIN(0x0, __BITS(6,5));
}
hdmi_infoframe_set_checksum(buf, length);
return length;
}
static int
hdmi_vendor_infoframe_unpack(struct hdmi_vendor_infoframe *frame,
const void *buf, size_t size)
{
const uint8_t *p = buf;
int ret;
ret = hdmi_infoframe_header_unpack(&frame->header, p, size);
if (ret)
return ret;
if (frame->header.length < 4)
return -EINVAL;
p += HDMI_INFOFRAME_HEADER_SIZE;
size -= HDMI_INFOFRAME_HEADER_SIZE;
if (p[0] != 0x03 || p[1] != 0x0c || p[2] != 0x00)
return -EINVAL;
switch (__SHIFTOUT(p[3], __BITS(6,5))) {
case 0x0:
if (frame->header.length != 4)
return -EINVAL;
break;
case 0x1:
if (frame->header.length != 5)
return -EINVAL;
frame->vic = p[4];
break;
case 0x2:
if (frame->header.length < 5)
return -EINVAL;
frame->s3d_struct = __SHIFTOUT(p[4], __BITS(7,4));
if (frame->s3d_struct < HDMI_3D_STRUCTURE_SIDE_BY_SIDE_HALF) {
if (frame->header.length != 5)
return -EINVAL;
} else {
if (frame->header.length != 6)
return -EINVAL;
frame->s3d_ext_data = __SHIFTOUT(p[5], __BITS(7,4));
}
break;
default:
return -EINVAL;
}
return 0;
}
/* union infoframe */
__strong_alias(linux_hdmi_infoframe_pack_only,linux_hdmi_infoframe_pack) /* XXX */
ssize_t
hdmi_infoframe_pack(const union hdmi_infoframe *frame, void *buf, size_t size)
{
switch (frame->any.type) {
case HDMI_INFOFRAME_TYPE_VENDOR:
return hdmi_vendor_infoframe_pack(&frame->vendor.hdmi, buf,
size);
case HDMI_INFOFRAME_TYPE_AVI:
return hdmi_avi_infoframe_pack(&frame->avi, buf, size);
case HDMI_INFOFRAME_TYPE_SPD:
return hdmi_spd_infoframe_pack(&frame->spd, buf, size);
case HDMI_INFOFRAME_TYPE_AUDIO:
return hdmi_audio_infoframe_pack(&frame->audio, buf, size);
case HDMI_INFOFRAME_TYPE_DRM:
return hdmi_drm_infoframe_pack(&frame->drm, buf, size);
default:
return -EINVAL;
}
}
int
hdmi_infoframe_unpack(union hdmi_infoframe *frame, const void *buf,
size_t size)
{
int ret;
memset(frame, 0, sizeof(*frame));
ret = hdmi_infoframe_header_unpack(&frame->any, buf, size);
if (ret)
return ret;
switch (frame->any.type) {
case HDMI_INFOFRAME_TYPE_VENDOR:
return hdmi_vendor_infoframe_unpack(&frame->vendor.hdmi, buf,
size);
case HDMI_INFOFRAME_TYPE_AVI:
return hdmi_avi_infoframe_unpack(&frame->avi, buf, size);
case HDMI_INFOFRAME_TYPE_SPD:
return hdmi_spd_infoframe_unpack(&frame->spd, buf, size);
case HDMI_INFOFRAME_TYPE_AUDIO:
return hdmi_audio_infoframe_unpack(&frame->audio, buf, size);
case HDMI_INFOFRAME_TYPE_DRM:
return hdmi_drm_infoframe_unpack(&frame->drm, buf, size);
default:
return -EINVAL;
}
}
void
hdmi_infoframe_log(const char *level, struct device *device,
const union hdmi_infoframe *frame)
{
hexdump(printf, device_xname(device), frame, sizeof(*frame));
}