Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net/ncsi: Fix netlink major/minor version numbers

The netlink interface for major and minor version numbers doesn't actually
return the major and minor version numbers.

It reports a u32 that contains the (major, minor, update, alpha1)
components as the major version number, and then alpha2 as the minor
version number.

For whatever reason, the u32 byte order was reversed (ntohl): maybe it was
assumed that the encoded value was a single big-endian u32, and alpha2 was
the minor version.

The correct way to get the supported NC-SI version from the network
controller is to parse the Get Version ID response as described in 8.4.44
of the NC-SI spec[1].

Get Version ID Response Packet Format

Bits
+--------+--------+--------+--------+
Bytes | 31..24 | 23..16 | 15..8 | 7..0 |
+-------+--------+--------+--------+--------+
| 0..15 | NC-SI Header |
+-------+--------+--------+--------+--------+
| 16..19| Response code | Reason code |
+-------+--------+--------+--------+--------+
|20..23 | Major | Minor | Update | Alpha1 |
+-------+--------+--------+--------+--------+
|24..27 | reserved | Alpha2 |
+-------+--------+--------+--------+--------+
| .... other stuff .... |

The major, minor, and update fields are all binary-coded decimal (BCD)
encoded [2]. The spec provides examples below the Get Version ID response
format in section 8.4.44.1, but for practical purposes, this is an example
from a live network card:

root@bmc:~# ncsi-util 0x15
NC-SI Command Response:
cmd: GET_VERSION_ID(0x15)
Response: COMMAND_COMPLETED(0x0000) Reason: NO_ERROR(0x0000)
Payload length = 40

20: 0xf1 0xf1 0xf0 0x00 <<<<<<<<< (major, minor, update, alpha1)
24: 0x00 0x00 0x00 0x00 <<<<<<<<< (_, _, _, alpha2)

28: 0x6d 0x6c 0x78 0x30
32: 0x2e 0x31 0x00 0x00
36: 0x00 0x00 0x00 0x00
40: 0x16 0x1d 0x07 0xd2
44: 0x10 0x1d 0x15 0xb3
48: 0x00 0x17 0x15 0xb3
52: 0x00 0x00 0x81 0x19

This should be parsed as "1.1.0".

"f" in the upper-nibble means to ignore it, contributing zero.

If both nibbles are "f", I think the whole field is supposed to be ignored.
Major and minor are "required", meaning they're not supposed to be "ff",
but the update field is "optional" so I think it can be ff. I think the
simplest thing to do is just set the major and minor to zero instead of
juggling some conditional logic or something.

bcd2bin() from "include/linux/bcd.h" seems to assume both nibbles are 0-9,
so I've provided a custom BCD decoding function.

Alpha1 and alpha2 are ISO/IEC 8859-1 encoded, which just means ASCII
characters as far as I can tell, although the full encoding table for
non-alphabetic characters is slightly different (I think).

I imagine the alpha fields are just supposed to be alphabetic characters,
but I haven't seen any network cards actually report a non-zero value for
either.

If people wrote software against this netlink behavior, and were parsing
the major and minor versions themselves from the u32, then this would
definitely break their code.

[1] https://www.dmtf.org/sites/default/files/standards/documents/DSP0222_1.0.0.pdf
[2] https://en.wikipedia.org/wiki/Binary-coded_decimal
[2] https://en.wikipedia.org/wiki/ISO/IEC_8859-1

Signed-off-by: Peter Delevoryas <peter@pjd.dev>
Fixes: 138635cc27c9 ("net/ncsi: NCSI response packet handler")
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Peter Delevoryas and committed by
David S. Miller
3084b58b c797ce16

+36 -8
+5 -2
net/ncsi/internal.h
··· 105 105 106 106 107 107 struct ncsi_channel_version { 108 - u32 version; /* Supported BCD encoded NCSI version */ 109 - u32 alpha2; /* Supported BCD encoded NCSI version */ 108 + u8 major; /* NCSI version major */ 109 + u8 minor; /* NCSI version minor */ 110 + u8 update; /* NCSI version update */ 111 + char alpha1; /* NCSI version alpha1 */ 112 + char alpha2; /* NCSI version alpha2 */ 110 113 u8 fw_name[12]; /* Firmware name string */ 111 114 u32 fw_version; /* Firmware version */ 112 115 u16 pci_ids[4]; /* PCI identification */
+2 -2
net/ncsi/ncsi-netlink.c
··· 71 71 if (nc == nc->package->preferred_channel) 72 72 nla_put_flag(skb, NCSI_CHANNEL_ATTR_FORCED); 73 73 74 - nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MAJOR, nc->version.version); 75 - nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MINOR, nc->version.alpha2); 74 + nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MAJOR, nc->version.major); 75 + nla_put_u32(skb, NCSI_CHANNEL_ATTR_VERSION_MINOR, nc->version.minor); 76 76 nla_put_string(skb, NCSI_CHANNEL_ATTR_VERSION_STR, nc->version.fw_name); 77 77 78 78 vid_nest = nla_nest_start_noflag(skb, NCSI_CHANNEL_ATTR_VLAN_LIST);
+5 -2
net/ncsi/ncsi-pkt.h
··· 197 197 /* Get Version ID */ 198 198 struct ncsi_rsp_gvi_pkt { 199 199 struct ncsi_rsp_pkt_hdr rsp; /* Response header */ 200 - __be32 ncsi_version; /* NCSI version */ 200 + unsigned char major; /* NCSI version major */ 201 + unsigned char minor; /* NCSI version minor */ 202 + unsigned char update; /* NCSI version update */ 203 + unsigned char alpha1; /* NCSI version alpha1 */ 201 204 unsigned char reserved[3]; /* Reserved */ 202 - unsigned char alpha2; /* NCSI version */ 205 + unsigned char alpha2; /* NCSI version alpha2 */ 203 206 unsigned char fw_name[12]; /* f/w name string */ 204 207 __be32 fw_version; /* f/w version */ 205 208 __be16 pci_ids[4]; /* PCI IDs */
+24 -2
net/ncsi/ncsi-rsp.c
··· 19 19 #include "ncsi-pkt.h" 20 20 #include "ncsi-netlink.h" 21 21 22 + /* Nibbles within [0xA, 0xF] add zero "0" to the returned value. 23 + * Optional fields (encoded as 0xFF) will default to zero. 24 + */ 25 + static u8 decode_bcd_u8(u8 x) 26 + { 27 + int lo = x & 0xF; 28 + int hi = x >> 4; 29 + 30 + lo = lo < 0xA ? lo : 0; 31 + hi = hi < 0xA ? hi : 0; 32 + return lo + hi * 10; 33 + } 34 + 22 35 static int ncsi_validate_rsp_pkt(struct ncsi_request *nr, 23 36 unsigned short payload) 24 37 { ··· 768 755 if (!nc) 769 756 return -ENODEV; 770 757 771 - /* Update to channel's version info */ 758 + /* Update channel's version info 759 + * 760 + * Major, minor, and update fields are supposed to be 761 + * unsigned integers encoded as packed BCD. 762 + * 763 + * Alpha1 and alpha2 are ISO/IEC 8859-1 characters. 764 + */ 772 765 ncv = &nc->version; 773 - ncv->version = ntohl(rsp->ncsi_version); 766 + ncv->major = decode_bcd_u8(rsp->major); 767 + ncv->minor = decode_bcd_u8(rsp->minor); 768 + ncv->update = decode_bcd_u8(rsp->update); 769 + ncv->alpha1 = rsp->alpha1; 774 770 ncv->alpha2 = rsp->alpha2; 775 771 memcpy(ncv->fw_name, rsp->fw_name, 12); 776 772 ncv->fw_version = ntohl(rsp->fw_version);