1 // SPDX-License-Identifier: GPL-2.0
2 
3 /* Driver for ETAS GmbH ES58X USB CAN(-FD) Bus Interfaces.
4  *
5  * File es58x_devlink.c: report the product information using devlink.
6  *
7  * Copyright (c) 2022 Vincent Mailhol <mailhol.vincent@wanadoo.fr>
8  */
9 
10 #include <linux/ctype.h>
11 #include <linux/device.h>
12 #include <linux/usb.h>
13 #include <net/devlink.h>
14 
15 #include "es58x_core.h"
16 
17 /* USB descriptor index containing the product information string. */
18 #define ES58X_PROD_INFO_IDX 6
19 
20 /**
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
26  *	bootloader version.
27  *
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".
34  *
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.
38  *
39  * Return: zero on success, -EINVAL if @prefix contains an invalid
40  *	value and -EBADMSG if @prod_info could not be parsed.
41  */
42 static int es58x_parse_sw_version(struct es58x_device *es58x_dev,
43 				  const char *prod_info, const char *prefix)
44 {
45 	struct es58x_sw_version *version;
46 	int major, minor, revision;
47 
48 	if (!strcmp(prefix, "FW"))
49 		version = &es58x_dev->firmware_version;
50 	else if (!strcmp(prefix, "BL"))
51 		version = &es58x_dev->bootloader_version;
52 	else
53 		return -EINVAL;
54 
55 	/* Go to prefix */
56 	prod_info = strstr(prod_info, prefix);
57 	if (!prod_info)
58 		return -EBADMSG;
59 	/* Go to beginning of the version number */
60 	while (!isdigit(*prod_info)) {
61 		prod_info++;
62 		if (!*prod_info)
63 			return -EBADMSG;
64 	}
65 
66 	if (sscanf(prod_info, "%2u.%2u.%2u", &major, &minor, &revision) != 3)
67 		return -EBADMSG;
68 
69 	version->major = major;
70 	version->minor = minor;
71 	version->revision = revision;
72 
73 	return 0;
74 }
75 
76 /**
77  * es58x_parse_hw_rev() - Extract hardware revision number.
78  * @es58x_dev: ES58X device.
79  * @prod_info: USB custom string returned by the device.
80  *
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
85  * and 'x' a digit.
86  *
87  * Parse @prod_info and store the hardware revision number in
88  * &es58x_dev.hardware_revision.
89  *
90  * Return: zero on success, -EBADMSG if @prod_info could not be
91  *	parsed.
92  */
93 static int es58x_parse_hw_rev(struct es58x_device *es58x_dev,
94 			      const char *prod_info)
95 {
96 	char letter;
97 	int major, minor;
98 
99 	/* The only occurrence of 'H' is in the hardware revision prefix. */
100 	prod_info = strchr(prod_info, 'H');
101 	if (!prod_info)
102 		return -EBADMSG;
103 	/* Go to beginning of the hardware revision */
104 	prod_info = strchr(prod_info, ':');
105 	if (!prod_info)
106 		return -EBADMSG;
107 	prod_info++;
108 
109 	if (sscanf(prod_info, "%c%3u/%3u", &letter, &major, &minor) != 3)
110 		return -EBADMSG;
111 
112 	es58x_dev->hardware_revision.letter = letter;
113 	es58x_dev->hardware_revision.major = major;
114 	es58x_dev->hardware_revision.minor = minor;
115 
116 	return 0;
117 }
118 
119 /**
120  * es58x_parse_product_info() - Parse the ES58x product information
121  *	string.
122  * @es58x_dev: ES58X device.
123  *
124  * Retrieve the product information string and parse it to extract the
125  * firmware version, the bootloader version and the hardware
126  * revision.
127  *
128  * If the function fails, simply emit a log message and continue
129  * because product information is not critical for the driver to
130  * operate.
131  */
132 void es58x_parse_product_info(struct es58x_device *es58x_dev)
133 {
134 	char *prod_info;
135 
136 	prod_info = usb_cache_string(es58x_dev->udev, ES58X_PROD_INFO_IDX);
137 	if (!prod_info) {
138 		dev_warn(es58x_dev->dev,
139 			 "could not retrieve the product info string\n");
140 		return;
141 	}
142 
143 	if (es58x_parse_sw_version(es58x_dev, prod_info, "FW") ||
144 	    es58x_parse_sw_version(es58x_dev, prod_info, "BL") ||
145 	    es58x_parse_hw_rev(es58x_dev, prod_info))
146 		dev_info(es58x_dev->dev,
147 			 "could not parse product info: '%s'\n", prod_info);
148 
149 	kfree(prod_info);
150 }
151 
152 /**
153  * es58x_sw_version_is_set() - Check if the version is a valid number.
154  * @sw_ver: Version number of either the firmware or the bootloader.
155  *
156  * If &es58x_sw_version.major, &es58x_sw_version.minor and
157  * &es58x_sw_version.revision are all zero, the product string could
158  * not be parsed and the version number is invalid.
159  */
160 static inline bool es58x_sw_version_is_set(struct es58x_sw_version *sw_ver)
161 {
162 	return sw_ver->major || sw_ver->minor || sw_ver->revision;
163 }
164 
165 /**
166  * es58x_hw_revision_is_set() - Check if the revision is a valid number.
167  * @hw_rev: Revision number of the hardware.
168  *
169  * If &es58x_hw_revision.letter is the null character, the product
170  * string could not be parsed and the hardware revision number is
171  * invalid.
172  */
173 static inline bool es58x_hw_revision_is_set(struct es58x_hw_revision *hw_rev)
174 {
175 	return hw_rev->letter != '\0';
176 }
177 
178 /**
179  * es58x_devlink_info_get() - Report the product information.
180  * @devlink: Devlink.
181  * @req: skb wrapper where to put requested information.
182  * @extack: Unused.
183  *
184  * Report the firmware version, the bootloader version, the hardware
185  * revision and the serial number through netlink.
186  *
187  * Return: zero on success, errno when any error occurs.
188  */
189 static int es58x_devlink_info_get(struct devlink *devlink,
190 				  struct devlink_info_req *req,
191 				  struct netlink_ext_ack *extack)
192 {
193 	struct es58x_device *es58x_dev = devlink_priv(devlink);
194 	struct es58x_sw_version *fw_ver = &es58x_dev->firmware_version;
195 	struct es58x_sw_version *bl_ver = &es58x_dev->bootloader_version;
196 	struct es58x_hw_revision *hw_rev = &es58x_dev->hardware_revision;
197 	char buf[max(sizeof("xx.xx.xx"), sizeof("axxx/xxx"))];
198 	int ret = 0;
199 
200 	if (es58x_sw_version_is_set(fw_ver)) {
201 		snprintf(buf, sizeof(buf), "%02u.%02u.%02u",
202 			 fw_ver->major, fw_ver->minor, fw_ver->revision);
203 		ret = devlink_info_version_running_put(req,
204 						       DEVLINK_INFO_VERSION_GENERIC_FW,
205 						       buf);
206 		if (ret)
207 			return ret;
208 	}
209 
210 	if (es58x_sw_version_is_set(bl_ver)) {
211 		snprintf(buf, sizeof(buf), "%02u.%02u.%02u",
212 			 bl_ver->major, bl_ver->minor, bl_ver->revision);
213 		ret = devlink_info_version_running_put(req,
214 						       DEVLINK_INFO_VERSION_GENERIC_FW_BOOTLOADER,
215 						       buf);
216 		if (ret)
217 			return ret;
218 	}
219 
220 	if (es58x_hw_revision_is_set(hw_rev)) {
221 		snprintf(buf, sizeof(buf), "%c%03u/%03u",
222 			 hw_rev->letter, hw_rev->major, hw_rev->minor);
223 		ret = devlink_info_version_fixed_put(req,
224 						     DEVLINK_INFO_VERSION_GENERIC_BOARD_REV,
225 						     buf);
226 		if (ret)
227 			return ret;
228 	}
229 
230 	return devlink_info_serial_number_put(req, es58x_dev->udev->serial);
231 }
232 
233 const struct devlink_ops es58x_dl_ops = {
234 	.info_get = es58x_devlink_info_get,
235 };
236