Commit 5425094a authored by Marc Kleine-Budde's avatar Marc Kleine-Budde

Merge patch series "can: etas_es58x: report firmware, bootloader and hardware version"

Vincent Mailhol <mailhol.vincent@wanadoo.fr> says:

The goal of this series is to report the firmware version, the
bootloader version and the hardware revision of ETAS ES58x devices.

These are already reported in the kernel log but this isn't best
practice. Remove the kernel log and instead export all these through
devlink. The devlink core automatically exports the firmware and the
bootloader version to ethtool, so no need to implement the
ethtool_ops::get_drvinfo() callback anymore.

Patch one and two implement the core support for devlink (at device
level) and devlink port (at the network interface level).

Patch three export usb_cache_string() and patch four add a new info
attribute to devlink.h. Both are prerequisites for patch five.

Patch five is the actual goal: it parses the product information from
a custom usb string returned by the device and expose them through
devlink.

Patch six removes the product information from the kernel log.

Finally, patch seven add a devlink documentation page with list all
the information attributes reported by the driver.

* Sample outputs following this series *

| $ devlink dev info
| usb/1-9:1.1:
|   serial_number 0108954
|   versions:
|       fixed:
|         board.rev B012/000
|       running:
|         fw 04.00.01
|         fw.bootloader 02.00.00

| $ devlink port show can0
| usb/1-9:1.1/0: type eth netdev can0 flavour physical port 0 splittable false

| $ ethtool -i can0
| driver: etas_es58x
| version: 6.1.0-rc7+
| firmware-version: 04.00.01 02.00.00
| expansion-rom-version:
| bus-info: 1-9:1.1
| supports-statistics: no
| supports-test: no
| supports-eeprom-access: no
| supports-register-dump: no
| supports-priv-flags: no

* Changelog *

v4 -> v5: https://lore.kernel.org/all/20221130174658.29282-1-mailhol.vincent@wanadoo.fr

  * [PATH 2/7] add devlink port support. This extends devlink to the
    network interface.

  * thanks to devlink port, 'ethtool -i' is now able to retrieve the
    firmware version from devlink. No need to implement the
    ethtool_ops::get_drvinfo() callback anymore: remove one patch from
    the series.

  * [PATCH 4/7] A new patch to add a new info attribute for the
    bootloader version in devlink.h. This patch was initially sent as
    a standalone patch here:
      https://lore.kernel.org/netdev/20221129031406.3849872-1-mailhol.vincent@wanadoo.fr
    Merging it to this series so that it is both added and used at the
    same time.

  * [PATCH 5/7] use the newly info attribute defined in patch 4/7 to
    report the bootloader version instead of the custom string "bl".

  * [PATCH 5/7] because the series does not implement
    ethtool_ops::get_drvinfo() anymore, the two helper functions
    es58x_sw_version_is_set() and es58x_hw_revision_is_set() are only
    used in devlink.c. Move them from es58x_core.h to es58x_devlink.c.

  * [PATCH 5/7] small rework of the helper function
    es58x_hw_revision_is_set(): it is OK to only check the letter (if
    the letter is '\0', it will not be possible to print the next
    numbers).

  * [PATCH 5/7 and 6/7] add reviewed-by Andrew Lunn tag.

  * [PATCH 7/7] Now, 'ethtool -i' reports both the firmware version
    and the bootloader version (this is how the core export the
    information from devlink to ethtool). Update the documentation to
    reflect this fact.

  * Reoder the patches.

v3 -> v4: https://lore.kernel.org/all/20221126162211.93322-1-mailhol.vincent@wanadoo.fr

  * major rework to use devlink instead of sysfs following Andrew's
    comment.

  * split the series in 6 patches.

  * [PATCH 1/6] add Acked-by: Greg Kroah-Hartman

v2 -> v3: https://lore.kernel.org/all/20221113040108.68249-1-mailhol.vincent@wanadoo.fr

  * patch 2/3: do not spam the kernel log anymore with the product
    number. Instead parse the product information string, extract the
    firmware version, the bootloadar version and the hardware revision
    and export them through sysfs.

  * patch 2/3: rework the parsing in order not to need additional
    fields in struct es58x_parameters.

  * patch 3/3: only populate ethtool_drvinfo::fw_version because since
    commit edaf5df2 ("ethtool: ethtool_get_drvinfo: populate
    drvinfo fields even if callback exits"), there is no need to
    populate ethtool_drvinfo::driver and ethtool_drvinfo::bus_info in
    the driver.

v1 -> v2: https://lore.kernel.org/all/20221104171604.24052-1-mailhol.vincent@wanadoo.fr

  * was a single patch. It is now a series of three patches.
  * add a first new patch to export  usb_cache_string().
  * add a second new patch to apply usb_cache_string() to existing code.
  * add missing check on product info string to prevent a buffer overflow.
  * add comma on the last entry of struct es58x_parameters.

v1: https://lore.kernel.org/all/20221104073659.414147-1-mailhol.vincent@wanadoo.fr

Link: https://lore.kernel.org/all/20221130174658.29282-1-mailhol.vincent@wanadoo.frSigned-off-by: default avatarMarc Kleine-Budde <mkl@pengutronix.de>
parents 74d95352 9f63f96a
...@@ -198,6 +198,11 @@ fw.bundle_id ...@@ -198,6 +198,11 @@ fw.bundle_id
Unique identifier of the entire firmware bundle. Unique identifier of the entire firmware bundle.
fw.bootloader
-------------
Version of the bootloader.
Future work Future work
=========== ===========
......
.. SPDX-License-Identifier: GPL-2.0
==========================
etas_es58x devlink support
==========================
This document describes the devlink features implemented by the
``etas_es58x`` device driver.
Info versions
=============
The ``etas_es58x`` driver reports the following versions
.. list-table:: devlink info versions implemented
:widths: 5 5 90
* - Name
- Type
- Description
* - ``fw``
- running
- Version of the firmware running on the device. Also available
through ``ethtool -i`` as the first member of the
``firmware-version``.
* - ``fw.bootloader``
- running
- Version of the bootloader running on the device. Also available
through ``ethtool -i`` as the second member of the
``firmware-version``.
* - ``board.rev``
- fixed
- The hardware revision of the device.
* - ``serial_number``
- fixed
- The USB serial number. Also available through ``lsusb -v``.
...@@ -7682,6 +7682,7 @@ ETAS ES58X CAN/USB DRIVER ...@@ -7682,6 +7682,7 @@ ETAS ES58X CAN/USB DRIVER
M: Vincent Mailhol <mailhol.vincent@wanadoo.fr> M: Vincent Mailhol <mailhol.vincent@wanadoo.fr>
L: linux-can@vger.kernel.org L: linux-can@vger.kernel.org
S: Maintained S: Maintained
F: Documentation/networking/devlink/etas_es58x.rst
F: drivers/net/can/usb/etas_es58x/ F: drivers/net/can/usb/etas_es58x/
ETHERNET BRIDGE ETHERNET BRIDGE
......
...@@ -30,6 +30,7 @@ config CAN_ESD_USB ...@@ -30,6 +30,7 @@ config CAN_ESD_USB
config CAN_ETAS_ES58X config CAN_ETAS_ES58X
tristate "ETAS ES58X CAN/USB interfaces" tristate "ETAS ES58X CAN/USB interfaces"
select CRC16 select CRC16
select NET_DEVLINK
help help
This driver supports the ES581.4, ES582.1 and ES584.1 interfaces This driver supports the ES581.4, ES582.1 and ES584.1 interfaces
from ETAS GmbH (https://www.etas.com/en/products/es58x.php). from ETAS GmbH (https://www.etas.com/en/products/es58x.php).
......
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CAN_ETAS_ES58X) += etas_es58x.o obj-$(CONFIG_CAN_ETAS_ES58X) += etas_es58x.o
etas_es58x-y = es58x_core.o es581_4.o es58x_fd.o etas_es58x-y = es58x_core.o es58x_devlink.o es581_4.o es58x_fd.o
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/usb.h> #include <linux/usb.h>
#include <net/devlink.h>
#include "es58x_core.h" #include "es58x_core.h"
...@@ -2038,10 +2039,16 @@ static int es58x_set_mode(struct net_device *netdev, enum can_mode mode) ...@@ -2038,10 +2039,16 @@ static int es58x_set_mode(struct net_device *netdev, enum can_mode mode)
* @es58x_dev: ES58X device. * @es58x_dev: ES58X device.
* @priv: ES58X private parameters related to the network device. * @priv: ES58X private parameters related to the network device.
* @channel_idx: Index of the network device. * @channel_idx: Index of the network device.
*
* Return: zero on success, errno if devlink port could not be
* properly registered.
*/ */
static void es58x_init_priv(struct es58x_device *es58x_dev, static int es58x_init_priv(struct es58x_device *es58x_dev,
struct es58x_priv *priv, int channel_idx) struct es58x_priv *priv, int channel_idx)
{ {
struct devlink_port_attrs attrs = {
.flavour = DEVLINK_PORT_FLAVOUR_PHYSICAL,
};
const struct es58x_parameters *param = es58x_dev->param; const struct es58x_parameters *param = es58x_dev->param;
struct can_priv *can = &priv->can; struct can_priv *can = &priv->can;
...@@ -2060,6 +2067,10 @@ static void es58x_init_priv(struct es58x_device *es58x_dev, ...@@ -2060,6 +2067,10 @@ static void es58x_init_priv(struct es58x_device *es58x_dev,
can->state = CAN_STATE_STOPPED; can->state = CAN_STATE_STOPPED;
can->ctrlmode_supported = param->ctrlmode_supported; can->ctrlmode_supported = param->ctrlmode_supported;
can->do_set_mode = es58x_set_mode; can->do_set_mode = es58x_set_mode;
devlink_port_attrs_set(&priv->devlink_port, &attrs);
return devlink_port_register(priv_to_devlink(es58x_dev),
&priv->devlink_port, channel_idx);
} }
/** /**
...@@ -2083,7 +2094,10 @@ static int es58x_init_netdev(struct es58x_device *es58x_dev, int channel_idx) ...@@ -2083,7 +2094,10 @@ static int es58x_init_netdev(struct es58x_device *es58x_dev, int channel_idx)
} }
SET_NETDEV_DEV(netdev, dev); SET_NETDEV_DEV(netdev, dev);
es58x_dev->netdev[channel_idx] = netdev; es58x_dev->netdev[channel_idx] = netdev;
es58x_init_priv(es58x_dev, es58x_priv(netdev), channel_idx); ret = es58x_init_priv(es58x_dev, es58x_priv(netdev), channel_idx);
if (ret)
goto free_candev;
SET_NETDEV_DEVLINK_PORT(netdev, &es58x_priv(netdev)->devlink_port);
netdev->netdev_ops = &es58x_netdev_ops; netdev->netdev_ops = &es58x_netdev_ops;
netdev->ethtool_ops = &es58x_ethtool_ops; netdev->ethtool_ops = &es58x_ethtool_ops;
...@@ -2091,16 +2105,20 @@ static int es58x_init_netdev(struct es58x_device *es58x_dev, int channel_idx) ...@@ -2091,16 +2105,20 @@ static int es58x_init_netdev(struct es58x_device *es58x_dev, int channel_idx)
netdev->dev_port = channel_idx; netdev->dev_port = channel_idx;
ret = register_candev(netdev); ret = register_candev(netdev);
if (ret) { if (ret)
es58x_dev->netdev[channel_idx] = NULL; goto devlink_port_unregister;
free_candev(netdev);
return ret;
}
netdev_queue_set_dql_min_limit(netdev_get_tx_queue(netdev, 0), netdev_queue_set_dql_min_limit(netdev_get_tx_queue(netdev, 0),
es58x_dev->param->dql_min_limit); es58x_dev->param->dql_min_limit);
return ret; return ret;
devlink_port_unregister:
devlink_port_unregister(&es58x_priv(netdev)->devlink_port);
free_candev:
es58x_dev->netdev[channel_idx] = NULL;
free_candev(netdev);
return ret;
} }
/** /**
...@@ -2117,53 +2135,12 @@ static void es58x_free_netdevs(struct es58x_device *es58x_dev) ...@@ -2117,53 +2135,12 @@ static void es58x_free_netdevs(struct es58x_device *es58x_dev)
if (!netdev) if (!netdev)
continue; continue;
unregister_candev(netdev); unregister_candev(netdev);
devlink_port_unregister(&es58x_priv(netdev)->devlink_port);
es58x_dev->netdev[i] = NULL; es58x_dev->netdev[i] = NULL;
free_candev(netdev); free_candev(netdev);
} }
} }
/**
* es58x_get_product_info() - Get the product information and print them.
* @es58x_dev: ES58X device.
*
* Do a synchronous call to get the product information.
*
* Return: zero on success, errno when any error occurs.
*/
static int es58x_get_product_info(struct es58x_device *es58x_dev)
{
struct usb_device *udev = es58x_dev->udev;
const int es58x_prod_info_idx = 6;
/* Empirical tests show a prod_info length of maximum 83,
* below should be more than enough.
*/
const size_t prod_info_len = 127;
char *prod_info;
int ret;
prod_info = kmalloc(prod_info_len, GFP_KERNEL);
if (!prod_info)
return -ENOMEM;
ret = usb_string(udev, es58x_prod_info_idx, prod_info, prod_info_len);
if (ret < 0) {
dev_err(es58x_dev->dev,
"%s: Could not read the product info: %pe\n",
__func__, ERR_PTR(ret));
goto out_free;
}
if (ret >= prod_info_len - 1) {
dev_warn(es58x_dev->dev,
"%s: Buffer is too small, result might be truncated\n",
__func__);
}
dev_info(es58x_dev->dev, "Product info: %s\n", prod_info);
out_free:
kfree(prod_info);
return ret < 0 ? ret : 0;
}
/** /**
* es58x_init_es58x_dev() - Initialize the ES58X device. * es58x_init_es58x_dev() - Initialize the ES58X device.
* @intf: USB interface. * @intf: USB interface.
...@@ -2177,6 +2154,7 @@ static struct es58x_device *es58x_init_es58x_dev(struct usb_interface *intf, ...@@ -2177,6 +2154,7 @@ static struct es58x_device *es58x_init_es58x_dev(struct usb_interface *intf,
{ {
struct device *dev = &intf->dev; struct device *dev = &intf->dev;
struct es58x_device *es58x_dev; struct es58x_device *es58x_dev;
struct devlink *devlink;
const struct es58x_parameters *param; const struct es58x_parameters *param;
const struct es58x_operators *ops; const struct es58x_operators *ops;
struct usb_device *udev = interface_to_usbdev(intf); struct usb_device *udev = interface_to_usbdev(intf);
...@@ -2199,11 +2177,12 @@ static struct es58x_device *es58x_init_es58x_dev(struct usb_interface *intf, ...@@ -2199,11 +2177,12 @@ static struct es58x_device *es58x_init_es58x_dev(struct usb_interface *intf,
ops = &es581_4_ops; ops = &es581_4_ops;
} }
es58x_dev = devm_kzalloc(dev, es58x_sizeof_es58x_device(param), devlink = devlink_alloc(&es58x_dl_ops, es58x_sizeof_es58x_device(param),
GFP_KERNEL); dev);
if (!es58x_dev) if (!devlink)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
es58x_dev = devlink_priv(devlink);
es58x_dev->param = param; es58x_dev->param = param;
es58x_dev->ops = ops; es58x_dev->ops = ops;
es58x_dev->dev = dev; es58x_dev->dev = dev;
...@@ -2240,25 +2219,24 @@ static int es58x_probe(struct usb_interface *intf, ...@@ -2240,25 +2219,24 @@ static int es58x_probe(struct usb_interface *intf,
const struct usb_device_id *id) const struct usb_device_id *id)
{ {
struct es58x_device *es58x_dev; struct es58x_device *es58x_dev;
int ch_idx, ret; int ch_idx;
es58x_dev = es58x_init_es58x_dev(intf, id->driver_info); es58x_dev = es58x_init_es58x_dev(intf, id->driver_info);
if (IS_ERR(es58x_dev)) if (IS_ERR(es58x_dev))
return PTR_ERR(es58x_dev); return PTR_ERR(es58x_dev);
ret = es58x_get_product_info(es58x_dev); es58x_parse_product_info(es58x_dev);
if (ret) devlink_register(priv_to_devlink(es58x_dev));
return ret;
for (ch_idx = 0; ch_idx < es58x_dev->num_can_ch; ch_idx++) { for (ch_idx = 0; ch_idx < es58x_dev->num_can_ch; ch_idx++) {
ret = es58x_init_netdev(es58x_dev, ch_idx); int ret = es58x_init_netdev(es58x_dev, ch_idx);
if (ret) { if (ret) {
es58x_free_netdevs(es58x_dev); es58x_free_netdevs(es58x_dev);
return ret; return ret;
} }
} }
return ret; return 0;
} }
/** /**
...@@ -2275,8 +2253,10 @@ static void es58x_disconnect(struct usb_interface *intf) ...@@ -2275,8 +2253,10 @@ static void es58x_disconnect(struct usb_interface *intf)
dev_info(&intf->dev, "Disconnecting %s %s\n", dev_info(&intf->dev, "Disconnecting %s %s\n",
es58x_dev->udev->manufacturer, es58x_dev->udev->product); es58x_dev->udev->manufacturer, es58x_dev->udev->product);
devlink_unregister(priv_to_devlink(es58x_dev));
es58x_free_netdevs(es58x_dev); es58x_free_netdevs(es58x_dev);
es58x_free_urbs(es58x_dev); es58x_free_urbs(es58x_dev);
devlink_free(priv_to_devlink(es58x_dev));
usb_set_intfdata(intf, NULL); usb_set_intfdata(intf, NULL);
} }
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/types.h> #include <linux/types.h>
#include <linux/usb.h> #include <linux/usb.h>
#include <net/devlink.h>
#include "es581_4.h" #include "es581_4.h"
#include "es58x_fd.h" #include "es58x_fd.h"
...@@ -230,6 +231,7 @@ union es58x_urb_cmd { ...@@ -230,6 +231,7 @@ union es58x_urb_cmd {
* @can: struct can_priv must be the first member (Socket CAN relies * @can: struct can_priv must be the first member (Socket CAN relies
* on the fact that function netdev_priv() returns a pointer to * on the fact that function netdev_priv() returns a pointer to
* a struct can_priv). * a struct can_priv).
* @devlink_port: devlink instance for the network interface.
* @es58x_dev: pointer to the corresponding ES58X device. * @es58x_dev: pointer to the corresponding ES58X device.
* @tx_urb: Used as a buffer to concatenate the TX messages and to do * @tx_urb: Used as a buffer to concatenate the TX messages and to do
* a bulk send. Please refer to es58x_start_xmit() for more * a bulk send. Please refer to es58x_start_xmit() for more
...@@ -255,6 +257,7 @@ union es58x_urb_cmd { ...@@ -255,6 +257,7 @@ union es58x_urb_cmd {
*/ */
struct es58x_priv { struct es58x_priv {
struct can_priv can; struct can_priv can;
struct devlink_port devlink_port;
struct es58x_device *es58x_dev; struct es58x_device *es58x_dev;
struct urb *tx_urb; struct urb *tx_urb;
...@@ -356,6 +359,39 @@ struct es58x_operators { ...@@ -356,6 +359,39 @@ struct es58x_operators {
int (*get_timestamp)(struct es58x_device *es58x_dev); int (*get_timestamp)(struct es58x_device *es58x_dev);
}; };
/**
* struct es58x_sw_version - Version number of the firmware or the
* bootloader.
* @major: Version major number, represented on two digits.
* @minor: Version minor number, represented on two digits.
* @revision: Version revision number, represented on two digits.
*
* The firmware and the bootloader share the same format: "xx.xx.xx"
* where 'x' is a digit. Both can be retrieved from the product
* information string.
*/
struct es58x_sw_version {
u8 major;
u8 minor;
u8 revision;
};
/**
* struct es58x_hw_revision - Hardware revision number.
* @letter: Revision letter.
* @major: Version major number, represented on three digits.
* @minor: Version minor number, represented on three digits.
*
* The hardware revision uses its own format: "axxx/xxx" where 'a' is
* a letter and 'x' a digit. It can be retrieved from the product
* information string.
*/
struct es58x_hw_revision {
char letter;
u16 major;
u16 minor;
};
/** /**
* struct es58x_device - All information specific to an ES58X device. * struct es58x_device - All information specific to an ES58X device.
* @dev: Device information. * @dev: Device information.
...@@ -373,6 +409,9 @@ struct es58x_operators { ...@@ -373,6 +409,9 @@ struct es58x_operators {
* queue wake/stop logic should prevent this URB from getting * queue wake/stop logic should prevent this URB from getting
* empty. Please refer to es58x_get_tx_urb() for more details. * empty. Please refer to es58x_get_tx_urb() for more details.
* @tx_urbs_idle_cnt: number of urbs in @tx_urbs_idle. * @tx_urbs_idle_cnt: number of urbs in @tx_urbs_idle.
* @firmware_version: The firmware version number.
* @bootloader_version: The bootloader version number.
* @hardware_revision: The hardware revision number.
* @ktime_req_ns: kernel timestamp when es58x_set_realtime_diff_ns() * @ktime_req_ns: kernel timestamp when es58x_set_realtime_diff_ns()
* was called. * was called.
* @realtime_diff_ns: difference in nanoseconds between the clocks of * @realtime_diff_ns: difference in nanoseconds between the clocks of
...@@ -408,6 +447,10 @@ struct es58x_device { ...@@ -408,6 +447,10 @@ struct es58x_device {
struct usb_anchor tx_urbs_idle; struct usb_anchor tx_urbs_idle;
atomic_t tx_urbs_idle_cnt; atomic_t tx_urbs_idle_cnt;
struct es58x_sw_version firmware_version;
struct es58x_sw_version bootloader_version;
struct es58x_hw_revision hardware_revision;
u64 ktime_req_ns; u64 ktime_req_ns;
s64 realtime_diff_ns; s64 realtime_diff_ns;
...@@ -674,6 +717,7 @@ static inline enum es58x_flag es58x_get_flags(const struct sk_buff *skb) ...@@ -674,6 +717,7 @@ static inline enum es58x_flag es58x_get_flags(const struct sk_buff *skb)
return es58x_flags; return es58x_flags;
} }
/* es58x_core.c. */
int es58x_can_get_echo_skb(struct net_device *netdev, u32 packet_idx, int es58x_can_get_echo_skb(struct net_device *netdev, u32 packet_idx,
u64 *tstamps, unsigned int pkts); u64 *tstamps, unsigned int pkts);
int es58x_tx_ack_msg(struct net_device *netdev, u16 tx_free_entries, int es58x_tx_ack_msg(struct net_device *netdev, u16 tx_free_entries,
...@@ -691,9 +735,15 @@ int es58x_rx_cmd_ret_u32(struct net_device *netdev, ...@@ -691,9 +735,15 @@ int es58x_rx_cmd_ret_u32(struct net_device *netdev,
int es58x_send_msg(struct es58x_device *es58x_dev, u8 cmd_type, u8 cmd_id, int es58x_send_msg(struct es58x_device *es58x_dev, u8 cmd_type, u8 cmd_id,
const void *msg, u16 cmd_len, int channel_idx); const void *msg, u16 cmd_len, int channel_idx);
/* es58x_devlink.c. */
void es58x_parse_product_info(struct es58x_device *es58x_dev);
extern const struct devlink_ops es58x_dl_ops;
/* es581_4.c. */
extern const struct es58x_parameters es581_4_param; extern const struct es58x_parameters es581_4_param;
extern const struct es58x_operators es581_4_ops; extern const struct es58x_operators es581_4_ops;
/* es58x_fd.c. */
extern const struct es58x_parameters es58x_fd_param; extern const struct es58x_parameters es58x_fd_param;
extern const struct es58x_operators es58x_fd_ops; extern const struct es58x_operators es58x_fd_ops;
......
// SPDX-License-Identifier: GPL-2.0
/* Driver for ETAS GmbH ES58X USB CAN(-FD) Bus Interfaces.
*
* File es58x_devlink.c: report the product information using devlink.
*
* Copyright (c) 2022 Vincent Mailhol <mailhol.vincent@wanadoo.fr>
*/
#include <linux/ctype.h>
#include <linux/device.h>
#include <linux/usb.h>
#include <net/devlink.h>
#include "es58x_core.h"
/* USB descriptor index containing the product information string. */
#define ES58X_PROD_INFO_IDX 6
/**
* es58x_parse_sw_version() - Extract boot loader or firmware version.
* @es58x_dev: ES58X device.
* @prod_info: USB custom string returned by the device.
* @prefix: Select which information should be parsed. Set it to "FW"
* to parse the firmware version or to "BL" to parse the
* bootloader version.
*
* The @prod_info string contains the firmware and the bootloader
* version number all prefixed by a magic string and concatenated with
* other numbers. Depending on the device, the firmware (bootloader)
* format is either "FW_Vxx.xx.xx" ("BL_Vxx.xx.xx") or "FW:xx.xx.xx"
* ("BL:xx.xx.xx") where 'x' represents a digit. @prod_info must
* contains the common part of those prefixes: "FW" or "BL".
*
* Parse @prod_info and store the version number in
* &es58x_dev.firmware_version or &es58x_dev.bootloader_version
* according to @prefix value.
*
* Return: zero on success, -EINVAL if @prefix contains an invalid
* value and -EBADMSG if @prod_info could not be parsed.
*/
static int es58x_parse_sw_version(struct es58x_device *es58x_dev,
const char *prod_info, const char *prefix)
{
struct es58x_sw_version *version;
int major, minor, revision;
if (!strcmp(prefix, "FW"))
version = &es58x_dev->firmware_version;
else if (!strcmp(prefix, "BL"))
version = &es58x_dev->bootloader_version;
else
return -EINVAL;
/* Go to prefix */
prod_info = strstr(prod_info, prefix);
if (!prod_info)
return -EBADMSG;
/* Go to beginning of the version number */
while (!isdigit(*prod_info)) {
prod_info++;
if (!*prod_info)
return -EBADMSG;
}
if (sscanf(prod_info, "%2u.%2u.%2u", &major, &minor, &revision) != 3)
return -EBADMSG;
version->major = major;
version->minor = minor;
version->revision = revision;
return 0;
}
/**
* es58x_parse_hw_rev() - Extract hardware revision number.
* @es58x_dev: ES58X device.
* @prod_info: USB custom string returned by the device.
*
* @prod_info contains the hardware revision prefixed by a magic
* string and conquenated together with other numbers. Depending on
* the device, the hardware revision format is either
* "HW_VER:axxx/xxx" or "HR:axxx/xxx" where 'a' represents a letter
* and 'x' a digit.
*
* Parse @prod_info and store the hardware revision number in
* &es58x_dev.hardware_revision.
*
* Return: zero on success, -EBADMSG if @prod_info could not be
* parsed.
*/
static int es58x_parse_hw_rev(struct es58x_device *es58x_dev,
const char *prod_info)
{
char letter;
int major, minor;
/* The only occurrence of 'H' is in the hardware revision prefix. */
prod_info = strchr(prod_info, 'H');
if (!prod_info)
return -EBADMSG;
/* Go to beginning of the hardware revision */
prod_info = strchr(prod_info, ':');
if (!prod_info)
return -EBADMSG;
prod_info++;
if (sscanf(prod_info, "%c%3u/%3u", &letter, &major, &minor) != 3)
return -EBADMSG;
es58x_dev->hardware_revision.letter = letter;
es58x_dev->hardware_revision.major = major;
es58x_dev->hardware_revision.minor = minor;
return 0;
}
/**
* es58x_parse_product_info() - Parse the ES58x product information
* string.
* @es58x_dev: ES58X device.
*
* Retrieve the product information string and parse it to extract the
* firmware version, the bootloader version and the hardware
* revision.
*
* If the function fails, simply emit a log message and continue
* because product information is not critical for the driver to
* operate.
*/
void es58x_parse_product_info(struct es58x_device *es58x_dev)
{
char *prod_info;
prod_info = usb_cache_string(es58x_dev->udev, ES58X_PROD_INFO_IDX);
if (!prod_info) {
dev_warn(es58x_dev->dev,
"could not retrieve the product info string\n");
return;
}
if (es58x_parse_sw_version(es58x_dev, prod_info, "FW") ||
es58x_parse_sw_version(es58x_dev, prod_info, "BL") ||
es58x_parse_hw_rev(es58x_dev, prod_info))
dev_info(es58x_dev->dev,
"could not parse product info: '%s'\n", prod_info);
kfree(prod_info);
}
/**
* es58x_sw_version_is_set() - Check if the version is a valid number.
* @sw_ver: Version number of either the firmware or the bootloader.
*
* If &es58x_sw_version.major, &es58x_sw_version.minor and
* &es58x_sw_version.revision are all zero, the product string could
* not be parsed and the version number is invalid.
*/
static inline bool es58x_sw_version_is_set(struct es58x_sw_version *sw_ver)
{
return sw_ver->major || sw_ver->minor || sw_ver->revision;
}
/**
* es58x_hw_revision_is_set() - Check if the revision is a valid number.
* @hw_rev: Revision number of the hardware.
*
* If &es58x_hw_revision.letter is the null character, the product
* string could not be parsed and the hardware revision number is
* invalid.
*/
static inline bool es58x_hw_revision_is_set(struct es58x_hw_revision *hw_rev)
{
return hw_rev->letter != '\0';
}
/**
* es58x_devlink_info_get() - Report the product information.
* @devlink: Devlink.
* @req: skb wrapper where to put requested information.
* @extack: Unused.
*
* Report the firmware version, the bootloader version, the hardware
* revision and the serial number through netlink.
*
* Return: zero on success, errno when any error occurs.
*/
static int es58x_devlink_info_get(struct devlink *devlink,
struct devlink_info_req *req,
struct netlink_ext_ack *extack)
{
struct es58x_device *es58x_dev = devlink_priv(devlink);
struct es58x_sw_version *fw_ver = &es58x_dev->firmware_version;
struct es58x_sw_version *bl_ver = &es58x_dev->bootloader_version;
struct es58x_hw_revision *hw_rev = &es58x_dev->hardware_revision;
char buf[max(sizeof("xx.xx.xx"), sizeof("axxx/xxx"))];
int ret = 0;
if (es58x_sw_version_is_set(fw_ver)) {
snprintf(buf, sizeof(buf), "%02u.%02u.%02u",
fw_ver->major, fw_ver->minor, fw_ver->revision);
ret = devlink_info_version_running_put(req,
DEVLINK_INFO_VERSION_GENERIC_FW,
buf);
if (ret)
return ret;
}
if (es58x_sw_version_is_set(bl_ver)) {
snprintf(buf, sizeof(buf), "%02u.%02u.%02u",
bl_ver->major, bl_ver->minor, bl_ver->revision);
ret = devlink_info_version_running_put(req,
DEVLINK_INFO_VERSION_GENERIC_FW_BOOTLOADER,
buf);
if (ret)
return ret;
}
if (es58x_hw_revision_is_set(hw_rev)) {
snprintf(buf, sizeof(buf), "%c%03u/%03u",
hw_rev->letter, hw_rev->major, hw_rev->minor);
ret = devlink_info_version_fixed_put(req,
DEVLINK_INFO_VERSION_GENERIC_BOARD_REV,
buf);
if (ret)
return ret;
}
return devlink_info_serial_number_put(req, es58x_dev->udev->serial);
}
const struct devlink_ops es58x_dl_ops = {
.info_get = es58x_devlink_info_get,
};
...@@ -1037,6 +1037,7 @@ char *usb_cache_string(struct usb_device *udev, int index) ...@@ -1037,6 +1037,7 @@ char *usb_cache_string(struct usb_device *udev, int index)
} }
return smallbuf; return smallbuf;
} }
EXPORT_SYMBOL_GPL(usb_cache_string);
/* /*
* usb_get_device_descriptor - (re)reads the device descriptor (usbcore) * usb_get_device_descriptor - (re)reads the device descriptor (usbcore)
......
...@@ -47,7 +47,6 @@ extern int usb_get_device_descriptor(struct usb_device *dev, ...@@ -47,7 +47,6 @@ extern int usb_get_device_descriptor(struct usb_device *dev,
extern int usb_set_isoch_delay(struct usb_device *dev); extern int usb_set_isoch_delay(struct usb_device *dev);
extern int usb_get_bos_descriptor(struct usb_device *dev); extern int usb_get_bos_descriptor(struct usb_device *dev);
extern void usb_release_bos_descriptor(struct usb_device *dev); extern void usb_release_bos_descriptor(struct usb_device *dev);
extern char *usb_cache_string(struct usb_device *udev, int index);
extern int usb_set_configuration(struct usb_device *dev, int configuration); extern int usb_set_configuration(struct usb_device *dev, int configuration);
extern int usb_choose_configuration(struct usb_device *udev); extern int usb_choose_configuration(struct usb_device *udev);
extern int usb_generic_driver_probe(struct usb_device *udev); extern int usb_generic_driver_probe(struct usb_device *udev);
......
...@@ -1829,6 +1829,7 @@ static inline int usb_get_ptm_status(struct usb_device *dev, void *data) ...@@ -1829,6 +1829,7 @@ static inline int usb_get_ptm_status(struct usb_device *dev, void *data)
extern int usb_string(struct usb_device *dev, int index, extern int usb_string(struct usb_device *dev, int index,
char *buf, size_t size); char *buf, size_t size);
extern char *usb_cache_string(struct usb_device *udev, int index);
/* wrappers that also update important state inside usbcore */ /* wrappers that also update important state inside usbcore */
extern int usb_clear_halt(struct usb_device *dev, int pipe); extern int usb_clear_halt(struct usb_device *dev, int pipe);
......
...@@ -621,6 +621,8 @@ enum devlink_param_generic_id { ...@@ -621,6 +621,8 @@ enum devlink_param_generic_id {
#define DEVLINK_INFO_VERSION_GENERIC_FW_ROCE "fw.roce" #define DEVLINK_INFO_VERSION_GENERIC_FW_ROCE "fw.roce"
/* Firmware bundle identifier */ /* Firmware bundle identifier */
#define DEVLINK_INFO_VERSION_GENERIC_FW_BUNDLE_ID "fw.bundle_id" #define DEVLINK_INFO_VERSION_GENERIC_FW_BUNDLE_ID "fw.bundle_id"
/* Bootloader */
#define DEVLINK_INFO_VERSION_GENERIC_FW_BOOTLOADER "fw.bootloader"
/** /**
* struct devlink_flash_update_params - Flash Update parameters * struct devlink_flash_update_params - Flash Update parameters
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment