File Index
Files and directories
Sign in
FILE

es58x_devlink.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/can/usb/etas_es58x/es58x_devlink.c C
  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 */
 42static 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 */
 93static 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, 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.
131 */
132void es58x_parse_product_info(struct es58x_device *es58x_dev)
133{
134	static const struct es58x_sw_version sw_version_not_set = {
135		.major = -1,
136		.minor = -1,
137		.revision = -1,
138	};
139	static const struct es58x_hw_revision hw_revision_not_set = {
140		.letter = '\0',
141		.major = -1,
142		.minor = -1,
143	};
144	char *prod_info;
145
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;
149
150	prod_info = usb_cache_string(es58x_dev->udev, ES58X_PROD_INFO_IDX);
151	if (!prod_info) {
152		dev_warn(es58x_dev->dev,
153			 "could not retrieve the product info string\n");
154		return;
155	}
156
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);
162
163	kfree(prod_info);
164}
165
166/**
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.
169 *
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.
173 *
174 * Return: @true if the software version is valid, @false otherwise.
175 */
176static inline bool es58x_sw_version_is_valid(struct es58x_sw_version *sw_ver)
177{
178	return sw_ver->major < 100 && sw_ver->minor < 100 &&
179		sw_ver->revision < 100;
180}
181
182/**
183 * es58x_hw_revision_is_valid() - Check if the revision is a valid number.
184 * @hw_rev: Revision number of the hardware.
185 *
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.
190 *
191 * Return: @true if the hardware revision is valid, @false otherwise.
192 */
193static inline bool es58x_hw_revision_is_valid(struct es58x_hw_revision *hw_rev)
194{
195	return isalnum(hw_rev->letter) && hw_rev->major < 1000 &&
196		hw_rev->minor < 1000;
197}
198
199/**
200 * es58x_devlink_info_get() - Report the product information.
201 * @devlink: Devlink.
202 * @req: skb wrapper where to put requested information.
203 * @extack: Unused.
204 *
205 * Report the firmware version, the bootloader version, the hardware
206 * revision and the serial number through netlink.
207 *
208 * Return: zero on success, errno when any error occurs.
209 */
210static int es58x_devlink_info_get(struct devlink *devlink,
211				  struct devlink_info_req *req,
212				  struct netlink_ext_ack *extack)
213{
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"))];
219	int ret = 0;
220
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,
226						       buf);
227		if (ret)
228			return ret;
229	}
230
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,
236						       buf);
237		if (ret)
238			return ret;
239	}
240
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,
246						     buf);
247		if (ret)
248			return ret;
249	}
250
251	if (es58x_dev->udev->serial)
252		ret = devlink_info_serial_number_put(req,
253						     es58x_dev->udev->serial);
254
255	return ret;
256}
257
258const struct devlink_ops es58x_dl_ops = {
259	.info_get = es58x_devlink_info_get,
260};
~/ Read-only file preview.