1 // SPDX-License-Identifier: GPL-2.0
3 /* Driver for ETAS GmbH ES58X USB CAN(-FD) Bus Interfaces.
5 * File es58x_devlink.c: report the product information using devlink.
7 * Copyright (c) 2022 Vincent Mailhol <mailhol.vincent@wanadoo.fr>
10 #include <linux/ctype.h>
11 #include <linux/device.h>
12 #include <linux/usb.h>
13 #include <net/devlink.h>
15 #include "es58x_core.h"
17 /* USB descriptor index containing the product information string. */
18 #define ES58X_PROD_INFO_IDX 6
21 * es58x_parse_sw_version() - Extract boot loader or firmware version.
22 * @es58x_dev: ES58X device.
23 * @prod_info: USB custom string returned by the device.
24 * @prefix: Select which information should be parsed. Set it to "FW"
25 * to parse the firmware version or to "BL" to parse the
28 * The @prod_info string contains the firmware and the bootloader
29 * version number all prefixed by a magic string and concatenated with
30 * other numbers. Depending on the device, the firmware (bootloader)
31 * format is either "FW_Vxx.xx.xx" ("BL_Vxx.xx.xx") or "FW:xx.xx.xx"
32 * ("BL:xx.xx.xx") where 'x' represents a digit. @prod_info must
33 * contains the common part of those prefixes: "FW" or "BL".
35 * Parse @prod_info and store the version number in
36 * &es58x_dev.firmware_version or &es58x_dev.bootloader_version
37 * according to @prefix value.
39 * Return: zero on success, -EINVAL if @prefix contains an invalid
40 * value and -EBADMSG if @prod_info could not be parsed.
42 static int es58x_parse_sw_version(struct es58x_device
*es58x_dev
,
43 const char *prod_info
, const char *prefix
)
45 struct es58x_sw_version
*version
;
46 int major
, minor
, revision
;
48 if (!strcmp(prefix
, "FW"))
49 version
= &es58x_dev
->firmware_version
;
50 else if (!strcmp(prefix
, "BL"))
51 version
= &es58x_dev
->bootloader_version
;
56 prod_info
= strstr(prod_info
, prefix
);
59 /* Go to beginning of the version number */
60 while (!isdigit(*prod_info
)) {
66 if (sscanf(prod_info
, "%2u.%2u.%2u", &major
, &minor
, &revision
) != 3)
69 version
->major
= major
;
70 version
->minor
= minor
;
71 version
->revision
= revision
;
77 * es58x_parse_hw_rev() - Extract hardware revision number.
78 * @es58x_dev: ES58X device.
79 * @prod_info: USB custom string returned by the device.
81 * @prod_info contains the hardware revision prefixed by a magic
82 * string and conquenated together with other numbers. Depending on
83 * the device, the hardware revision format is either
84 * "HW_VER:axxx/xxx" or "HR:axxx/xxx" where 'a' represents a letter
87 * Parse @prod_info and store the hardware revision number in
88 * &es58x_dev.hardware_revision.
90 * Return: zero on success, -EBADMSG if @prod_info could not be
93 static int es58x_parse_hw_rev(struct es58x_device
*es58x_dev
,
94 const char *prod_info
)
99 /* The only occurrence of 'H' is in the hardware revision prefix. */
100 prod_info
= strchr(prod_info
, 'H');
103 /* Go to beginning of the hardware revision */
104 prod_info
= strchr(prod_info
, ':');
109 if (sscanf(prod_info
, "%c%3u/%3u", &letter
, &major
, &minor
) != 3)
112 es58x_dev
->hardware_revision
.letter
= letter
;
113 es58x_dev
->hardware_revision
.major
= major
;
114 es58x_dev
->hardware_revision
.minor
= minor
;
120 * es58x_parse_product_info() - Parse the ES58x product information
122 * @es58x_dev: ES58X device.
124 * Retrieve the product information string and parse it to extract the
125 * firmware version, the bootloader version and the hardware
128 * If the function fails, set the version or revision to an invalid
129 * value and emit an informal message. Continue probing because the
130 * product information is not critical for the driver to operate.
132 void es58x_parse_product_info(struct es58x_device
*es58x_dev
)
134 static const struct es58x_sw_version sw_version_not_set
= {
139 static const struct es58x_hw_revision hw_revision_not_set
= {
146 es58x_dev
->firmware_version
= sw_version_not_set
;
147 es58x_dev
->bootloader_version
= sw_version_not_set
;
148 es58x_dev
->hardware_revision
= hw_revision_not_set
;
150 prod_info
= usb_cache_string(es58x_dev
->udev
, ES58X_PROD_INFO_IDX
);
152 dev_warn(es58x_dev
->dev
,
153 "could not retrieve the product info string\n");
157 if (es58x_parse_sw_version(es58x_dev
, prod_info
, "FW") ||
158 es58x_parse_sw_version(es58x_dev
, prod_info
, "BL") ||
159 es58x_parse_hw_rev(es58x_dev
, prod_info
))
160 dev_info(es58x_dev
->dev
,
161 "could not parse product info: '%s'\n", prod_info
);
167 * es58x_sw_version_is_valid() - Check if the version is a valid number.
168 * @sw_ver: Version number of either the firmware or the bootloader.
170 * If any of the software version sub-numbers do not fit on two
171 * digits, the version is invalid, most probably because the product
172 * string could not be parsed.
174 * Return: @true if the software version is valid, @false otherwise.
176 static inline bool es58x_sw_version_is_valid(struct es58x_sw_version
*sw_ver
)
178 return sw_ver
->major
< 100 && sw_ver
->minor
< 100 &&
179 sw_ver
->revision
< 100;
183 * es58x_hw_revision_is_valid() - Check if the revision is a valid number.
184 * @hw_rev: Revision number of the hardware.
186 * If &es58x_hw_revision.letter is not a alphanumeric character or if
187 * any of the hardware revision sub-numbers do not fit on three
188 * digits, the revision is invalid, most probably because the product
189 * string could not be parsed.
191 * Return: @true if the hardware revision is valid, @false otherwise.
193 static inline bool es58x_hw_revision_is_valid(struct es58x_hw_revision
*hw_rev
)
195 return isalnum(hw_rev
->letter
) && hw_rev
->major
< 1000 &&
196 hw_rev
->minor
< 1000;
200 * es58x_devlink_info_get() - Report the product information.
202 * @req: skb wrapper where to put requested information.
205 * Report the firmware version, the bootloader version, the hardware
206 * revision and the serial number through netlink.
208 * Return: zero on success, errno when any error occurs.
210 static int es58x_devlink_info_get(struct devlink
*devlink
,
211 struct devlink_info_req
*req
,
212 struct netlink_ext_ack
*extack
)
214 struct es58x_device
*es58x_dev
= devlink_priv(devlink
);
215 struct es58x_sw_version
*fw_ver
= &es58x_dev
->firmware_version
;
216 struct es58x_sw_version
*bl_ver
= &es58x_dev
->bootloader_version
;
217 struct es58x_hw_revision
*hw_rev
= &es58x_dev
->hardware_revision
;
218 char buf
[MAX(sizeof("xx.xx.xx"), sizeof("axxx/xxx"))];
221 if (es58x_sw_version_is_valid(fw_ver
)) {
222 snprintf(buf
, sizeof(buf
), "%02u.%02u.%02u",
223 fw_ver
->major
, fw_ver
->minor
, fw_ver
->revision
);
224 ret
= devlink_info_version_running_put(req
,
225 DEVLINK_INFO_VERSION_GENERIC_FW
,
231 if (es58x_sw_version_is_valid(bl_ver
)) {
232 snprintf(buf
, sizeof(buf
), "%02u.%02u.%02u",
233 bl_ver
->major
, bl_ver
->minor
, bl_ver
->revision
);
234 ret
= devlink_info_version_running_put(req
,
235 DEVLINK_INFO_VERSION_GENERIC_FW_BOOTLOADER
,
241 if (es58x_hw_revision_is_valid(hw_rev
)) {
242 snprintf(buf
, sizeof(buf
), "%c%03u/%03u",
243 hw_rev
->letter
, hw_rev
->major
, hw_rev
->minor
);
244 ret
= devlink_info_version_fixed_put(req
,
245 DEVLINK_INFO_VERSION_GENERIC_BOARD_REV
,
251 return devlink_info_serial_number_put(req
, es58x_dev
->udev
->serial
);
254 const struct devlink_ops es58x_dl_ops
= {
255 .info_get
= es58x_devlink_info_get
,