|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | + |
| 3 | +/* Copyright (c) 2025 Vincent Mailhol <mailhol@kernel.org> */ |
| 4 | + |
| 5 | +#include <linux/array_size.h> |
| 6 | +#include <linux/errno.h> |
| 7 | +#include <linux/init.h> |
| 8 | +#include <linux/kernel.h> |
| 9 | +#include <linux/module.h> |
| 10 | +#include <linux/netdevice.h> |
| 11 | +#include <linux/units.h> |
| 12 | +#include <linux/string_choices.h> |
| 13 | + |
| 14 | +#include <linux/can.h> |
| 15 | +#include <linux/can/bittiming.h> |
| 16 | +#include <linux/can/dev.h> |
| 17 | +#include <linux/can/skb.h> |
| 18 | + |
| 19 | +struct dummy_can { |
| 20 | + struct can_priv can; |
| 21 | + struct net_device *dev; |
| 22 | +}; |
| 23 | + |
| 24 | +static struct dummy_can *dummy_can; |
| 25 | + |
| 26 | +static const struct can_bittiming_const dummy_can_bittiming_const = { |
| 27 | + .name = "dummy_can CC", |
| 28 | + .tseg1_min = 2, |
| 29 | + .tseg1_max = 256, |
| 30 | + .tseg2_min = 2, |
| 31 | + .tseg2_max = 128, |
| 32 | + .sjw_max = 128, |
| 33 | + .brp_min = 1, |
| 34 | + .brp_max = 512, |
| 35 | + .brp_inc = 1 |
| 36 | +}; |
| 37 | + |
| 38 | +static const struct can_bittiming_const dummy_can_fd_databittiming_const = { |
| 39 | + .name = "dummy_can FD", |
| 40 | + .tseg1_min = 2, |
| 41 | + .tseg1_max = 256, |
| 42 | + .tseg2_min = 2, |
| 43 | + .tseg2_max = 128, |
| 44 | + .sjw_max = 128, |
| 45 | + .brp_min = 1, |
| 46 | + .brp_max = 512, |
| 47 | + .brp_inc = 1 |
| 48 | +}; |
| 49 | + |
| 50 | +static const struct can_tdc_const dummy_can_fd_tdc_const = { |
| 51 | + .tdcv_min = 0, |
| 52 | + .tdcv_max = 0, /* Manual mode not supported. */ |
| 53 | + .tdco_min = 0, |
| 54 | + .tdco_max = 127, |
| 55 | + .tdcf_min = 0, |
| 56 | + .tdcf_max = 127 |
| 57 | +}; |
| 58 | + |
| 59 | +static const struct can_bittiming_const dummy_can_xl_databittiming_const = { |
| 60 | + .name = "dummy_can XL", |
| 61 | + .tseg1_min = 2, |
| 62 | + .tseg1_max = 256, |
| 63 | + .tseg2_min = 2, |
| 64 | + .tseg2_max = 128, |
| 65 | + .sjw_max = 128, |
| 66 | + .brp_min = 1, |
| 67 | + .brp_max = 512, |
| 68 | + .brp_inc = 1 |
| 69 | +}; |
| 70 | + |
| 71 | +static const struct can_tdc_const dummy_can_xl_tdc_const = { |
| 72 | + .tdcv_min = 0, |
| 73 | + .tdcv_max = 0, /* Manual mode not supported. */ |
| 74 | + .tdco_min = 0, |
| 75 | + .tdco_max = 127, |
| 76 | + .tdcf_min = 0, |
| 77 | + .tdcf_max = 127 |
| 78 | +}; |
| 79 | + |
| 80 | +static const struct can_pwm_const dummy_can_pwm_const = { |
| 81 | + .pwms_min = 1, |
| 82 | + .pwms_max = 8, |
| 83 | + .pwml_min = 2, |
| 84 | + .pwml_max = 24, |
| 85 | + .pwmo_min = 0, |
| 86 | + .pwmo_max = 16, |
| 87 | +}; |
| 88 | + |
| 89 | +static void dummy_can_print_bittiming(struct net_device *dev, |
| 90 | + struct can_bittiming *bt) |
| 91 | +{ |
| 92 | + netdev_dbg(dev, "\tbitrate: %u\n", bt->bitrate); |
| 93 | + netdev_dbg(dev, "\tsample_point: %u\n", bt->sample_point); |
| 94 | + netdev_dbg(dev, "\ttq: %u\n", bt->tq); |
| 95 | + netdev_dbg(dev, "\tprop_seg: %u\n", bt->prop_seg); |
| 96 | + netdev_dbg(dev, "\tphase_seg1: %u\n", bt->phase_seg1); |
| 97 | + netdev_dbg(dev, "\tphase_seg2: %u\n", bt->phase_seg2); |
| 98 | + netdev_dbg(dev, "\tsjw: %u\n", bt->sjw); |
| 99 | + netdev_dbg(dev, "\tbrp: %u\n", bt->brp); |
| 100 | +} |
| 101 | + |
| 102 | +static void dummy_can_print_tdc(struct net_device *dev, struct can_tdc *tdc) |
| 103 | +{ |
| 104 | + netdev_dbg(dev, "\t\ttdcv: %u\n", tdc->tdcv); |
| 105 | + netdev_dbg(dev, "\t\ttdco: %u\n", tdc->tdco); |
| 106 | + netdev_dbg(dev, "\t\ttdcf: %u\n", tdc->tdcf); |
| 107 | +} |
| 108 | + |
| 109 | +static void dummy_can_print_pwm(struct net_device *dev, struct can_pwm *pwm, |
| 110 | + struct can_bittiming *dbt) |
| 111 | +{ |
| 112 | + netdev_dbg(dev, "\t\tpwms: %u\n", pwm->pwms); |
| 113 | + netdev_dbg(dev, "\t\tpwml: %u\n", pwm->pwml); |
| 114 | + netdev_dbg(dev, "\t\tpwmo: %u\n", pwm->pwmo); |
| 115 | +} |
| 116 | + |
| 117 | +static void dummy_can_print_ctrlmode(struct net_device *dev) |
| 118 | +{ |
| 119 | + struct dummy_can *priv = netdev_priv(dev); |
| 120 | + struct can_priv *can_priv = &priv->can; |
| 121 | + unsigned long supported = can_priv->ctrlmode_supported; |
| 122 | + u32 enabled = can_priv->ctrlmode; |
| 123 | + |
| 124 | + netdev_dbg(dev, "Control modes:\n"); |
| 125 | + netdev_dbg(dev, "\tsupported: 0x%08x\n", (u32)supported); |
| 126 | + netdev_dbg(dev, "\tenabled: 0x%08x\n", enabled); |
| 127 | + |
| 128 | + if (supported) { |
| 129 | + int idx; |
| 130 | + |
| 131 | + netdev_dbg(dev, "\tlist:"); |
| 132 | + for_each_set_bit(idx, &supported, BITS_PER_TYPE(u32)) |
| 133 | + netdev_dbg(dev, "\t\t%s: %s\n", |
| 134 | + can_get_ctrlmode_str(BIT(idx)), |
| 135 | + enabled & BIT(idx) ? "on" : "off"); |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +static void dummy_can_print_bittiming_info(struct net_device *dev) |
| 140 | +{ |
| 141 | + struct dummy_can *priv = netdev_priv(dev); |
| 142 | + struct can_priv *can_priv = &priv->can; |
| 143 | + |
| 144 | + netdev_dbg(dev, "Clock frequency: %u\n", can_priv->clock.freq); |
| 145 | + netdev_dbg(dev, "Maximum bitrate: %u\n", can_priv->bitrate_max); |
| 146 | + netdev_dbg(dev, "MTU: %u\n", dev->mtu); |
| 147 | + netdev_dbg(dev, "\n"); |
| 148 | + |
| 149 | + dummy_can_print_ctrlmode(dev); |
| 150 | + netdev_dbg(dev, "\n"); |
| 151 | + |
| 152 | + netdev_dbg(dev, "Classical CAN nominal bittiming:\n"); |
| 153 | + dummy_can_print_bittiming(dev, &can_priv->bittiming); |
| 154 | + netdev_dbg(dev, "\n"); |
| 155 | + |
| 156 | + if (can_priv->ctrlmode & CAN_CTRLMODE_FD) { |
| 157 | + netdev_dbg(dev, "CAN FD databittiming:\n"); |
| 158 | + dummy_can_print_bittiming(dev, &can_priv->fd.data_bittiming); |
| 159 | + if (can_fd_tdc_is_enabled(can_priv)) { |
| 160 | + netdev_dbg(dev, "\tCAN FD TDC:\n"); |
| 161 | + dummy_can_print_tdc(dev, &can_priv->fd.tdc); |
| 162 | + } |
| 163 | + } |
| 164 | + netdev_dbg(dev, "\n"); |
| 165 | + |
| 166 | + if (can_priv->ctrlmode & CAN_CTRLMODE_XL) { |
| 167 | + netdev_dbg(dev, "CAN XL databittiming:\n"); |
| 168 | + dummy_can_print_bittiming(dev, &can_priv->xl.data_bittiming); |
| 169 | + if (can_xl_tdc_is_enabled(can_priv)) { |
| 170 | + netdev_dbg(dev, "\tCAN XL TDC:\n"); |
| 171 | + dummy_can_print_tdc(dev, &can_priv->xl.tdc); |
| 172 | + } |
| 173 | + if (can_priv->ctrlmode & CAN_CTRLMODE_XL_TMS) { |
| 174 | + netdev_dbg(dev, "\tCAN XL PWM:\n"); |
| 175 | + dummy_can_print_pwm(dev, &can_priv->xl.pwm, |
| 176 | + &can_priv->xl.data_bittiming); |
| 177 | + } |
| 178 | + } |
| 179 | + netdev_dbg(dev, "\n"); |
| 180 | +} |
| 181 | + |
| 182 | +static int dummy_can_netdev_open(struct net_device *dev) |
| 183 | +{ |
| 184 | + int ret; |
| 185 | + struct can_priv *priv = netdev_priv(dev); |
| 186 | + |
| 187 | + dummy_can_print_bittiming_info(dev); |
| 188 | + netdev_dbg(dev, "error-signalling is %s\n", |
| 189 | + str_enabled_disabled(!can_dev_in_xl_only_mode(priv))); |
| 190 | + |
| 191 | + ret = open_candev(dev); |
| 192 | + if (ret) |
| 193 | + return ret; |
| 194 | + netif_start_queue(dev); |
| 195 | + netdev_dbg(dev, "dummy-can is up\n"); |
| 196 | + |
| 197 | + return 0; |
| 198 | +} |
| 199 | + |
| 200 | +static int dummy_can_netdev_close(struct net_device *dev) |
| 201 | +{ |
| 202 | + netif_stop_queue(dev); |
| 203 | + close_candev(dev); |
| 204 | + netdev_dbg(dev, "dummy-can is down\n"); |
| 205 | + |
| 206 | + return 0; |
| 207 | +} |
| 208 | + |
| 209 | +static netdev_tx_t dummy_can_start_xmit(struct sk_buff *skb, |
| 210 | + struct net_device *dev) |
| 211 | +{ |
| 212 | + if (can_dev_dropped_skb(dev, skb)) |
| 213 | + return NETDEV_TX_OK; |
| 214 | + |
| 215 | + can_put_echo_skb(skb, dev, 0, 0); |
| 216 | + dev->stats.tx_packets++; |
| 217 | + dev->stats.tx_bytes += can_get_echo_skb(dev, 0, NULL); |
| 218 | + |
| 219 | + return NETDEV_TX_OK; |
| 220 | +} |
| 221 | + |
| 222 | +static const struct net_device_ops dummy_can_netdev_ops = { |
| 223 | + .ndo_open = dummy_can_netdev_open, |
| 224 | + .ndo_stop = dummy_can_netdev_close, |
| 225 | + .ndo_start_xmit = dummy_can_start_xmit, |
| 226 | +}; |
| 227 | + |
| 228 | +static const struct ethtool_ops dummy_can_ethtool_ops = { |
| 229 | + .get_ts_info = ethtool_op_get_ts_info, |
| 230 | +}; |
| 231 | + |
| 232 | +static int __init dummy_can_init(void) |
| 233 | +{ |
| 234 | + struct net_device *dev; |
| 235 | + struct dummy_can *priv; |
| 236 | + int ret; |
| 237 | + |
| 238 | + dev = alloc_candev(sizeof(*priv), 1); |
| 239 | + if (!dev) |
| 240 | + return -ENOMEM; |
| 241 | + |
| 242 | + dev->netdev_ops = &dummy_can_netdev_ops; |
| 243 | + dev->ethtool_ops = &dummy_can_ethtool_ops; |
| 244 | + priv = netdev_priv(dev); |
| 245 | + priv->can.bittiming_const = &dummy_can_bittiming_const; |
| 246 | + priv->can.bitrate_max = 20 * MEGA /* BPS */; |
| 247 | + priv->can.clock.freq = 160 * MEGA /* Hz */; |
| 248 | + priv->can.fd.data_bittiming_const = &dummy_can_fd_databittiming_const; |
| 249 | + priv->can.fd.tdc_const = &dummy_can_fd_tdc_const; |
| 250 | + priv->can.xl.data_bittiming_const = &dummy_can_xl_databittiming_const; |
| 251 | + priv->can.xl.tdc_const = &dummy_can_xl_tdc_const; |
| 252 | + priv->can.xl.pwm_const = &dummy_can_pwm_const; |
| 253 | + priv->can.ctrlmode_supported = CAN_CTRLMODE_LISTENONLY | |
| 254 | + CAN_CTRLMODE_FD | CAN_CTRLMODE_TDC_AUTO | |
| 255 | + CAN_CTRLMODE_RESTRICTED | CAN_CTRLMODE_XL | |
| 256 | + CAN_CTRLMODE_XL_TDC_AUTO | CAN_CTRLMODE_XL_TMS; |
| 257 | + priv->dev = dev; |
| 258 | + |
| 259 | + ret = register_candev(priv->dev); |
| 260 | + if (ret) { |
| 261 | + free_candev(priv->dev); |
| 262 | + return ret; |
| 263 | + } |
| 264 | + |
| 265 | + dummy_can = priv; |
| 266 | + netdev_dbg(dev, "dummy-can ready\n"); |
| 267 | + |
| 268 | + return 0; |
| 269 | +} |
| 270 | + |
| 271 | +static void __exit dummy_can_exit(void) |
| 272 | +{ |
| 273 | + struct net_device *dev = dummy_can->dev; |
| 274 | + |
| 275 | + netdev_dbg(dev, "dummy-can bye bye\n"); |
| 276 | + unregister_candev(dev); |
| 277 | + free_candev(dev); |
| 278 | +} |
| 279 | + |
| 280 | +module_init(dummy_can_init); |
| 281 | +module_exit(dummy_can_exit); |
| 282 | + |
| 283 | +MODULE_DESCRIPTION("A dummy CAN driver, mainly to test the netlink interface"); |
| 284 | +MODULE_LICENSE("GPL"); |
| 285 | +MODULE_AUTHOR("Vincent Mailhol <mailhol@kernel.org>"); |
0 commit comments