From patchwork Sat Mar 9 04:04:48 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ramalingam C X-Patchwork-Id: 10845749 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id B9C0913B5 for ; Sat, 9 Mar 2019 04:03:08 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id A12F2300D2 for ; Sat, 9 Mar 2019 04:03:08 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 92DF6300E5; Sat, 9 Mar 2019 04:03:08 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=unavailable version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id A5229300F6 for ; Sat, 9 Mar 2019 04:03:07 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id E30D56E435; Sat, 9 Mar 2019 04:02:58 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from mga18.intel.com (mga18.intel.com [134.134.136.126]) by gabe.freedesktop.org (Postfix) with ESMTPS id BAA156E42E; Sat, 9 Mar 2019 04:02:55 +0000 (UTC) X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from orsmga006.jf.intel.com ([10.7.209.51]) by orsmga106.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 08 Mar 2019 20:02:55 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.58,458,1544515200"; d="scan'208";a="124618742" Received: from dut-ram.iind.intel.com ([10.223.34.157]) by orsmga006.jf.intel.com with ESMTP; 08 Mar 2019 20:02:53 -0800 From: Ramalingam C To: intel-gfx@lists.freedesktop.org, dri-devel@lists.freedesktop.org, daniel.vetter@ffwll.ch, maarten.lankhorst@linux.intel.com Subject: [PATCH v2 4/8] drm/i915: HDCP SRM parsing and revocation check Date: Sat, 9 Mar 2019 09:34:48 +0530 Message-Id: <20190309040452.23699-5-ramalingam.c@intel.com> X-Mailer: git-send-email 2.19.1 In-Reply-To: <20190309040452.23699-1-ramalingam.c@intel.com> References: <20190309040452.23699-1-ramalingam.c@intel.com> MIME-Version: 1.0 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP Implements the SRM table parsing for HDCP 1.4 and 2.2. And also revocation check is added at authentication of HDCP1.4 and 2.2 Signed-off-by: Ramalingam C --- drivers/gpu/drm/i915/i915_drv.c | 1 + drivers/gpu/drm/i915/i915_drv.h | 6 + drivers/gpu/drm/i915/intel_drv.h | 2 + drivers/gpu/drm/i915/intel_hdcp.c | 308 ++++++++++++++++++++++++++++-- include/drm/drm_hdcp.h | 32 ++++ 5 files changed, 334 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index 0d743907e7bc..6c396ec0f552 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -873,6 +873,7 @@ static int i915_driver_init_early(struct drm_i915_private *dev_priv) mutex_init(&dev_priv->wm.wm_mutex); mutex_init(&dev_priv->pps_mutex); mutex_init(&dev_priv->hdcp_comp_mutex); + mutex_init(&dev_priv->srm_mutex); i915_memcpy_init_early(dev_priv); intel_runtime_pm_init_early(dev_priv); diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h index c4ffe19ec698..c6c7cb1ae1b4 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -2054,6 +2054,12 @@ struct drm_i915_private { /* Mutex to protect the above hdcp component related values. */ struct mutex hdcp_comp_mutex; + unsigned int revocated_ksv_cnt; + u8 *revocated_ksv_list; + + /* Mutex to protect the data about revocated ksvs */ + struct mutex srm_mutex; + /* * NOTE: This is the dri1/ums dungeon, don't add stuff here. Your patch * will be rejected. Instead look for a better place. diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index 186fa035f491..7550f4ed5a98 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -2181,6 +2181,8 @@ void intel_hdcp_component_init(struct drm_i915_private *dev_priv); void intel_hdcp_component_fini(struct drm_i915_private *dev_priv); void intel_hdcp_cleanup(struct intel_connector *connector); void intel_hdcp_handle_cp_irq(struct intel_connector *connector); +ssize_t intel_hdcp_srm_update(struct drm_i915_private *dev_priv, char *buf, + size_t count); /* intel_psr.c */ #define CAN_PSR(dev_priv) (HAS_PSR(dev_priv) && dev_priv->psr.sink_support) diff --git a/drivers/gpu/drm/i915/intel_hdcp.c b/drivers/gpu/drm/i915/intel_hdcp.c index ff1bbbee2e52..b1fa81666490 100644 --- a/drivers/gpu/drm/i915/intel_hdcp.c +++ b/drivers/gpu/drm/i915/intel_hdcp.c @@ -273,6 +273,62 @@ u32 intel_hdcp_get_repeater_ctl(struct intel_digital_port *intel_dig_port) return -EINVAL; } +static inline void intel_hdcp_print_ksv(u8 *ksv) +{ + DRM_DEBUG_KMS("\t%#04x, %#04x, %#04x, %#04x, %#04x\n", *ksv, + *(ksv + 1), *(ksv + 2), *(ksv + 3), *(ksv + 4)); +} + +static inline +struct intel_connector *intel_hdcp_to_connector(struct intel_hdcp *hdcp) +{ + return container_of(hdcp, struct intel_connector, hdcp); +} + +/* Check if any of the KSV is revocated by DCP LLC through SRM table */ +static inline +bool intel_hdcp_ksvs_revocated(struct intel_hdcp *hdcp, u8 *ksvs, u32 ksv_count) +{ + struct intel_connector *connector = intel_hdcp_to_connector(hdcp); + struct intel_digital_port *intel_dig_port = conn_to_dig_port(connector); + struct drm_i915_private *dev_priv = + intel_dig_port->base.base.dev->dev_private; + u32 rev_ksv_cnt, cnt, i, j; + u8 *rev_ksv_list; + + mutex_lock(&dev_priv->srm_mutex); + rev_ksv_cnt = dev_priv->revocated_ksv_cnt; + rev_ksv_list = dev_priv->revocated_ksv_list; + + /* If the Revocated ksv list is empty */ + if (!rev_ksv_cnt || !rev_ksv_list) { + mutex_unlock(&dev_priv->srm_mutex); + return false; + } + + for (cnt = 0; cnt < ksv_count; cnt++) { + rev_ksv_list = dev_priv->revocated_ksv_list; + for (i = 0; i < rev_ksv_cnt; i++) { + for (j = 0; j < DRM_HDCP_KSV_LEN; j++) + if (*(ksvs + j) != *(rev_ksv_list + j)) { + break; + } else if (j == (DRM_HDCP_KSV_LEN - 1)) { + DRM_DEBUG_KMS("Revocated KSV is "); + intel_hdcp_print_ksv(ksvs); + mutex_unlock(&dev_priv->srm_mutex); + return true; + } + /* Move the offset to next KSV in the revocated list */ + rev_ksv_list += DRM_HDCP_KSV_LEN; + } + + /* Iterate to next ksv_offset */ + ksvs += DRM_HDCP_KSV_LEN; + } + mutex_unlock(&dev_priv->srm_mutex); + return false; +} + static int intel_hdcp_validate_v_prime(struct intel_digital_port *intel_dig_port, const struct intel_hdcp_shim *shim, @@ -490,9 +546,10 @@ int intel_hdcp_validate_v_prime(struct intel_digital_port *intel_dig_port, /* Implements Part 2 of the HDCP authorization procedure */ static -int intel_hdcp_auth_downstream(struct intel_digital_port *intel_dig_port, - const struct intel_hdcp_shim *shim) +int intel_hdcp_auth_downstream(struct intel_hdcp *hdcp, + struct intel_digital_port *intel_dig_port) { + const struct intel_hdcp_shim *shim = hdcp->shim; u8 bstatus[2], num_downstream, *ksv_fifo; int ret, i, tries = 3; @@ -531,6 +588,11 @@ int intel_hdcp_auth_downstream(struct intel_digital_port *intel_dig_port, if (ret) goto err; + if (intel_hdcp_ksvs_revocated(hdcp, ksv_fifo, num_downstream)) { + DRM_ERROR("Revocated Ksv(s) in ksv_fifo\n"); + return -EPERM; + } + /* * When V prime mismatches, DP Spec mandates re-read of * V prime atleast twice. @@ -557,9 +619,11 @@ int intel_hdcp_auth_downstream(struct intel_digital_port *intel_dig_port, } /* Implements Part 1 of the HDCP authorization procedure */ -static int intel_hdcp_auth(struct intel_digital_port *intel_dig_port, - const struct intel_hdcp_shim *shim) +static int intel_hdcp_auth(struct intel_connector *connector) { + struct intel_digital_port *intel_dig_port = conn_to_dig_port(connector); + struct intel_hdcp *hdcp = &connector->hdcp; + const struct intel_hdcp_shim *shim = hdcp->shim; struct drm_i915_private *dev_priv; enum port port; unsigned long r0_prime_gen_start; @@ -625,6 +689,11 @@ static int intel_hdcp_auth(struct intel_digital_port *intel_dig_port, if (ret < 0) return ret; + if (intel_hdcp_ksvs_revocated(hdcp, bksv.shim, 1)) { + DRM_ERROR("BKSV is revocated\n"); + return -EPERM; + } + I915_WRITE(PORT_HDCP_BKSVLO(port), bksv.reg[0]); I915_WRITE(PORT_HDCP_BKSVHI(port), bksv.reg[1]); @@ -698,7 +767,7 @@ static int intel_hdcp_auth(struct intel_digital_port *intel_dig_port, */ if (repeater_present) - return intel_hdcp_auth_downstream(intel_dig_port, shim); + return intel_hdcp_auth_downstream(hdcp, intel_dig_port); DRM_DEBUG_KMS("HDCP is enabled (no repeater present)\n"); return 0; @@ -735,7 +804,6 @@ static int _intel_hdcp_disable(struct intel_connector *connector) static int _intel_hdcp_enable(struct intel_connector *connector) { - struct intel_hdcp *hdcp = &connector->hdcp; struct drm_i915_private *dev_priv = connector->base.dev->dev_private; int i, ret, tries = 3; @@ -760,9 +828,9 @@ static int _intel_hdcp_enable(struct intel_connector *connector) /* Incase of authentication failures, HDCP spec expects reauth. */ for (i = 0; i < tries; i++) { - ret = intel_hdcp_auth(conn_to_dig_port(connector), hdcp->shim); + ret = intel_hdcp_auth(connector); if (!ret) { - hdcp->hdcp_encrypted = true; + connector->hdcp.hdcp_encrypted = true; return 0; } @@ -776,12 +844,6 @@ static int _intel_hdcp_enable(struct intel_connector *connector) return ret; } -static inline -struct intel_connector *intel_hdcp_to_connector(struct intel_hdcp *hdcp) -{ - return container_of(hdcp, struct intel_connector, hdcp); -} - /* Implements Part 3 of the HDCP authorization procedure */ static int intel_hdcp_check_link(struct intel_connector *connector) { @@ -1193,6 +1255,12 @@ static int hdcp2_authentication_key_exchange(struct intel_connector *connector) hdcp->is_repeater = HDCP_2_2_RX_REPEATER(msgs.send_cert.rx_caps[2]); + if (intel_hdcp_ksvs_revocated(hdcp, + msgs.send_cert.cert_rx.receiver_id, 1)) { + DRM_ERROR("Receiver ID is revocated\n"); + return -EPERM; + } + /* * Here msgs.no_stored_km will hold msgs corresponding to the km * stored also. @@ -1351,7 +1419,7 @@ int hdcp2_authenticate_repeater_topology(struct intel_connector *connector) } msgs; const struct intel_hdcp_shim *shim = hdcp->shim; u8 *rx_info; - u32 seq_num_v; + u32 seq_num_v, device_cnt; int ret; ret = shim->read_2_2_msg(intel_dig_port, HDCP_2_2_REP_SEND_RECVID_LIST, @@ -1376,6 +1444,14 @@ int hdcp2_authenticate_repeater_topology(struct intel_connector *connector) return -EINVAL; } + device_cnt = HDCP_2_2_DEV_COUNT_HI(rx_info[0]) << 4 || + HDCP_2_2_DEV_COUNT_LO(rx_info[1]); + if (intel_hdcp_ksvs_revocated(hdcp, msgs.recvid_list.receiver_ids, + device_cnt)) { + DRM_ERROR("Revoked receiver ID(s) is in list\n"); + return -EPERM; + } + ret = hdcp2_verify_rep_topology_prepare_ack(connector, &msgs.recvid_list, &msgs.rep_ack); @@ -1818,6 +1894,208 @@ int intel_hdcp_init(struct intel_connector *connector, return 0; } +static u32 intel_hdcp_get_revocated_ksv_count(u8 *buf, u32 vrls_length) +{ + u32 parsed_bytes = 0, ksv_count = 0, vrl_ksv_cnt, vrl_sz; + + do { + vrl_ksv_cnt = *buf; + ksv_count += vrl_ksv_cnt; + + vrl_sz = (vrl_ksv_cnt * DRM_HDCP_KSV_LEN) + 1; + buf += vrl_sz; + parsed_bytes += vrl_sz; + } while (parsed_bytes < vrls_length); + + return ksv_count; +} + +static u32 intel_hdcp_get_revocated_ksvs(u8 *ksv_list, const u8 *buf, + u32 vrls_length) +{ + u32 parsed_bytes = 0, ksv_count = 0; + u32 vrl_ksv_cnt, vrl_ksv_sz, vrl_idx = 0; + + do { + vrl_ksv_cnt = *buf; + vrl_ksv_sz = vrl_ksv_cnt * DRM_HDCP_KSV_LEN; + + buf++; + + DRM_DEBUG_KMS("vrl: %d, Revoked KSVs: %d\n", vrl_idx++, + vrl_ksv_cnt); + memcpy(ksv_list, buf, vrl_ksv_sz); + + ksv_count += vrl_ksv_cnt; + ksv_list += vrl_ksv_sz; + buf += vrl_ksv_sz; + + parsed_bytes += (vrl_ksv_sz + 1); + } while (parsed_bytes < vrls_length); + + return ksv_count; +} + +static int intel_hdcp_parse_srm(struct drm_i915_private *dev_priv, char *buf, + size_t count) +{ + struct hdcp_srm_header *header; + u32 vrl_length, ksv_count; + + if (count < (sizeof(struct hdcp_srm_header) + + DRM_HDCP_1_4_VRL_LENGTH_SIZE + DRM_HDCP_1_4_DCP_SIG_SIZE)) { + DRM_ERROR("Invalid blob length\n"); + return -EINVAL; + } + + header = (struct hdcp_srm_header *)buf; + mutex_lock(&dev_priv->srm_mutex); + DRM_DEBUG_KMS("SRM ID: 0x%x, SRM Ver: 0x%x, SRM Gen No: 0x%x\n", + header->spec_indicator.srm_id, + __swab16(header->srm_version), header->srm_gen_no); + + WARN_ON(header->spec_indicator.reserved_hi || + header->spec_indicator.reserved_lo); + + if (header->spec_indicator.srm_id != DRM_HDCP_1_4_SRM_ID) { + DRM_ERROR("Invalid srm_id\n"); + mutex_unlock(&dev_priv->srm_mutex); + return -EINVAL; + } + + buf = buf + sizeof(*header); + vrl_length = (*buf << 16 | *(buf + 1) << 8 | *(buf + 2)); + if (count < (sizeof(struct hdcp_srm_header) + vrl_length) || + vrl_length < (DRM_HDCP_1_4_VRL_LENGTH_SIZE + + DRM_HDCP_1_4_DCP_SIG_SIZE)) { + DRM_ERROR("Invalid blob length or vrl length\n"); + mutex_unlock(&dev_priv->srm_mutex); + return -EINVAL; + } + + /* Length of the all vrls combined */ + vrl_length -= (DRM_HDCP_1_4_VRL_LENGTH_SIZE + + DRM_HDCP_1_4_DCP_SIG_SIZE); + + if (!vrl_length) { + DRM_DEBUG_KMS("No vrl found\n"); + mutex_unlock(&dev_priv->srm_mutex); + return -EINVAL; + } + + buf += DRM_HDCP_1_4_VRL_LENGTH_SIZE; + ksv_count = intel_hdcp_get_revocated_ksv_count(buf, vrl_length); + if (!ksv_count) { + DRM_DEBUG_KMS("Revocated KSV count is 0\n"); + mutex_unlock(&dev_priv->srm_mutex); + return count; + } + + kfree(dev_priv->revocated_ksv_list); + dev_priv->revocated_ksv_list = kzalloc(ksv_count * DRM_HDCP_KSV_LEN, + GFP_KERNEL); + if (!dev_priv->revocated_ksv_list) { + DRM_ERROR("Out of Memory\n"); + mutex_unlock(&dev_priv->srm_mutex); + return -ENOMEM; + } + + if (intel_hdcp_get_revocated_ksvs(dev_priv->revocated_ksv_list, + buf, vrl_length) != ksv_count) { + dev_priv->revocated_ksv_cnt = 0; + kfree(dev_priv->revocated_ksv_list); + mutex_unlock(&dev_priv->srm_mutex); + return -EINVAL; + } + + dev_priv->revocated_ksv_cnt = ksv_count; + mutex_unlock(&dev_priv->srm_mutex); + return count; +} + +static int intel_hdcp2_parse_srm(struct drm_i915_private *dev_priv, char *buf, + size_t count) +{ + struct hdcp2_srm_header *header; + u32 vrl_length, ksv_count, ksv_sz; + + mutex_lock(&dev_priv->srm_mutex); + if (count < (sizeof(struct hdcp2_srm_header) + + DRM_HDCP_2_VRL_LENGTH_SIZE + DRM_HDCP_2_DCP_SIG_SIZE)) { + DRM_ERROR("Invalid blob length\n"); + mutex_unlock(&dev_priv->srm_mutex); + return -EINVAL; + } + + header = (struct hdcp2_srm_header *)buf; + DRM_DEBUG_KMS("SRM ID: 0x%x, SRM Ver: 0x%x, SRM Gen No: 0x%x\n", + header->spec_indicator.srm_id, + __swab16(header->srm_version), header->srm_gen_no); + + WARN_ON(header->spec_indicator.reserved); + buf = buf + sizeof(*header); + vrl_length = (*buf << 16 | *(buf + 1) << 8 | *(buf + 2)); + + if (count < (sizeof(struct hdcp2_srm_header) + vrl_length) || + vrl_length < (DRM_HDCP_2_VRL_LENGTH_SIZE + + DRM_HDCP_2_DCP_SIG_SIZE)) { + DRM_ERROR("Invalid blob length or vrl length\n"); + mutex_unlock(&dev_priv->srm_mutex); + return -EINVAL; + } + + /* Length of the all vrls combined */ + vrl_length -= (DRM_HDCP_2_VRL_LENGTH_SIZE + + DRM_HDCP_2_DCP_SIG_SIZE); + + if (!vrl_length) { + DRM_DEBUG_KMS("No vrl found\n"); + mutex_unlock(&dev_priv->srm_mutex); + return -EINVAL; + } + + buf += DRM_HDCP_2_VRL_LENGTH_SIZE; + ksv_count = (*buf << 2) | DRM_HDCP_2_KSV_COUNT_2_LSBITS(*(buf + 1)); + if (!ksv_count) { + DRM_DEBUG_KMS("Revocated KSV count is 0\n"); + mutex_unlock(&dev_priv->srm_mutex); + return count; + } + + kfree(dev_priv->revocated_ksv_list); + dev_priv->revocated_ksv_list = kzalloc(ksv_count * DRM_HDCP_KSV_LEN, + GFP_KERNEL); + if (!dev_priv->revocated_ksv_list) { + DRM_ERROR("Out of Memory\n"); + mutex_unlock(&dev_priv->srm_mutex); + return -ENOMEM; + } + + ksv_sz = ksv_count * DRM_HDCP_KSV_LEN; + buf += DRM_HDCP_2_NO_OF_DEV_PLUS_RESERVED_SZ; + + DRM_DEBUG_KMS("Revoked KSVs: %d\n", ksv_count); + memcpy(dev_priv->revocated_ksv_list, buf, ksv_sz); + + dev_priv->revocated_ksv_cnt = ksv_count; + mutex_unlock(&dev_priv->srm_mutex); + return count; +} + +ssize_t intel_hdcp_srm_update(struct drm_i915_private *dev_priv, char *buf, + size_t count) +{ + u8 srm_id; + + srm_id = *((u8 *)buf); + if (srm_id == 0x80) + return (ssize_t)intel_hdcp_parse_srm(dev_priv, buf, count); + else if (srm_id == 0x91) + return (ssize_t)intel_hdcp2_parse_srm(dev_priv, buf, count); + + return (ssize_t)-EINVAL; +} + int intel_hdcp_enable(struct intel_connector *connector, u8 content_type) { struct intel_hdcp *hdcp = &connector->hdcp; diff --git a/include/drm/drm_hdcp.h b/include/drm/drm_hdcp.h index f243408ecf26..652aaf5d658e 100644 --- a/include/drm/drm_hdcp.h +++ b/include/drm/drm_hdcp.h @@ -265,4 +265,36 @@ void drm_hdcp2_u32_to_seq_num(u8 seq_num[HDCP_2_2_SEQ_NUM_LEN], u32 val) seq_num[2] = val; } +#define DRM_HDCP_1_4_SRM_ID 0x8 +#define DRM_HDCP_1_4_VRL_LENGTH_SIZE 3 +#define DRM_HDCP_1_4_DCP_SIG_SIZE 40 + +struct hdcp_srm_header { + struct { + u8 reserved_hi:4; + u8 srm_id:4; + u8 reserved_lo; + } spec_indicator; + u16 srm_version; + u8 srm_gen_no; +} __packed; + +#define DRM_HDCP_2_SRM_ID 0x9 +#define DRM_HDCP_2_INDICATOR 0x1 +#define DRM_HDCP_2_VRL_LENGTH_SIZE 3 +#define DRM_HDCP_2_DCP_SIG_SIZE 384 +#define DRM_HDCP_2_NO_OF_DEV_PLUS_RESERVED_SZ 4 + +#define DRM_HDCP_2_KSV_COUNT_2_LSBITS(byte) (((byte) & 0xC) >> 6) + +struct hdcp2_srm_header { + struct { + u8 hdcp2_indicator:4; + u8 srm_id:4; + u8 reserved; + } spec_indicator; + u16 srm_version; + u8 srm_gen_no; +} __packed; + #endif