// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2025 Vincent Mailhol */ #include #include #include #include #include #include #include #include #include #include #include #include struct dummy_can { struct can_priv can; struct net_device *dev; }; static struct dummy_can *dummy_can; static const struct can_bittiming_const dummy_can_bittiming_const = { .name = "dummy_can CC", .tseg1_min = 2, .tseg1_max = 256, .tseg2_min = 2, .tseg2_max = 128, .sjw_max = 128, .brp_min = 1, .brp_max = 512, .brp_inc = 1 }; static const struct can_bittiming_const dummy_can_fd_databittiming_const = { .name = "dummy_can FD", .tseg1_min = 2, .tseg1_max = 256, .tseg2_min = 2, .tseg2_max = 128, .sjw_max = 128, .brp_min = 1, .brp_max = 512, .brp_inc = 1 }; static const struct can_tdc_const dummy_can_fd_tdc_const = { .tdcv_min = 0, .tdcv_max = 0, /* Manual mode not supported. */ .tdco_min = 0, .tdco_max = 127, .tdcf_min = 0, .tdcf_max = 127 }; static const struct can_bittiming_const dummy_can_xl_databittiming_const = { .name = "dummy_can XL", .tseg1_min = 2, .tseg1_max = 256, .tseg2_min = 2, .tseg2_max = 128, .sjw_max = 128, .brp_min = 1, .brp_max = 512, .brp_inc = 1 }; static const struct can_tdc_const dummy_can_xl_tdc_const = { .tdcv_min = 0, .tdcv_max = 0, /* Manual mode not supported. */ .tdco_min = 0, .tdco_max = 127, .tdcf_min = 0, .tdcf_max = 127 }; static const struct can_pwm_const dummy_can_pwm_const = { .pwms_min = 1, .pwms_max = 8, .pwml_min = 2, .pwml_max = 24, .pwmo_min = 0, .pwmo_max = 16, }; static void dummy_can_print_bittiming(struct net_device *dev, struct can_bittiming *bt) { netdev_dbg(dev, "\tbitrate: %u\n", bt->bitrate); netdev_dbg(dev, "\tsample_point: %u\n", bt->sample_point); netdev_dbg(dev, "\ttq: %u\n", bt->tq); netdev_dbg(dev, "\tprop_seg: %u\n", bt->prop_seg); netdev_dbg(dev, "\tphase_seg1: %u\n", bt->phase_seg1); netdev_dbg(dev, "\tphase_seg2: %u\n", bt->phase_seg2); netdev_dbg(dev, "\tsjw: %u\n", bt->sjw); netdev_dbg(dev, "\tbrp: %u\n", bt->brp); } static void dummy_can_print_tdc(struct net_device *dev, struct can_tdc *tdc) { netdev_dbg(dev, "\t\ttdcv: %u\n", tdc->tdcv); netdev_dbg(dev, "\t\ttdco: %u\n", tdc->tdco); netdev_dbg(dev, "\t\ttdcf: %u\n", tdc->tdcf); } static void dummy_can_print_pwm(struct net_device *dev, struct can_pwm *pwm, struct can_bittiming *dbt) { netdev_dbg(dev, "\t\tpwms: %u\n", pwm->pwms); netdev_dbg(dev, "\t\tpwml: %u\n", pwm->pwml); netdev_dbg(dev, "\t\tpwmo: %u\n", pwm->pwmo); } static void dummy_can_print_ctrlmode(struct net_device *dev) { struct dummy_can *priv = netdev_priv(dev); struct can_priv *can_priv = &priv->can; unsigned long supported = can_priv->ctrlmode_supported; u32 enabled = can_priv->ctrlmode; netdev_dbg(dev, "Control modes:\n"); netdev_dbg(dev, "\tsupported: 0x%08x\n", (u32)supported); netdev_dbg(dev, "\tenabled: 0x%08x\n", enabled); if (supported) { int idx; netdev_dbg(dev, "\tlist:"); for_each_set_bit(idx, &supported, BITS_PER_TYPE(u32)) netdev_dbg(dev, "\t\t%s: %s\n", can_get_ctrlmode_str(BIT(idx)), enabled & BIT(idx) ? "on" : "off"); } } static void dummy_can_print_bittiming_info(struct net_device *dev) { struct dummy_can *priv = netdev_priv(dev); struct can_priv *can_priv = &priv->can; netdev_dbg(dev, "Clock frequency: %u\n", can_priv->clock.freq); netdev_dbg(dev, "Maximum bitrate: %u\n", can_priv->bitrate_max); netdev_dbg(dev, "MTU: %u\n", dev->mtu); netdev_dbg(dev, "\n"); dummy_can_print_ctrlmode(dev); netdev_dbg(dev, "\n"); netdev_dbg(dev, "Classical CAN nominal bittiming:\n"); dummy_can_print_bittiming(dev, &can_priv->bittiming); netdev_dbg(dev, "\n"); if (can_priv->ctrlmode & CAN_CTRLMODE_FD) { netdev_dbg(dev, "CAN FD databittiming:\n"); dummy_can_print_bittiming(dev, &can_priv->fd.data_bittiming); if (can_fd_tdc_is_enabled(can_priv)) { netdev_dbg(dev, "\tCAN FD TDC:\n"); dummy_can_print_tdc(dev, &can_priv->fd.tdc); } } netdev_dbg(dev, "\n"); if (can_priv->ctrlmode & CAN_CTRLMODE_XL) { netdev_dbg(dev, "CAN XL databittiming:\n"); dummy_can_print_bittiming(dev, &can_priv->xl.data_bittiming); if (can_xl_tdc_is_enabled(can_priv)) { netdev_dbg(dev, "\tCAN XL TDC:\n"); dummy_can_print_tdc(dev, &can_priv->xl.tdc); } if (can_priv->ctrlmode & CAN_CTRLMODE_XL_TMS) { netdev_dbg(dev, "\tCAN XL PWM:\n"); dummy_can_print_pwm(dev, &can_priv->xl.pwm, &can_priv->xl.data_bittiming); } } netdev_dbg(dev, "\n"); } static int dummy_can_netdev_open(struct net_device *dev) { int ret; struct can_priv *priv = netdev_priv(dev); dummy_can_print_bittiming_info(dev); netdev_dbg(dev, "error-signalling is %s\n", str_enabled_disabled(!can_dev_in_xl_only_mode(priv))); ret = open_candev(dev); if (ret) return ret; netif_start_queue(dev); netdev_dbg(dev, "dummy-can is up\n"); return 0; } static int dummy_can_netdev_close(struct net_device *dev) { netif_stop_queue(dev); close_candev(dev); netdev_dbg(dev, "dummy-can is down\n"); return 0; } static netdev_tx_t dummy_can_start_xmit(struct sk_buff *skb, struct net_device *dev) { if (can_dev_dropped_skb(dev, skb)) return NETDEV_TX_OK; can_put_echo_skb(skb, dev, 0, 0); dev->stats.tx_packets++; dev->stats.tx_bytes += can_get_echo_skb(dev, 0, NULL); return NETDEV_TX_OK; } static const struct net_device_ops dummy_can_netdev_ops = { .ndo_open = dummy_can_netdev_open, .ndo_stop = dummy_can_netdev_close, .ndo_start_xmit = dummy_can_start_xmit, }; static const struct ethtool_ops dummy_can_ethtool_ops = { .get_ts_info = ethtool_op_get_ts_info, }; static int __init dummy_can_init(void) { struct net_device *dev; struct dummy_can *priv; int ret; dev = alloc_candev(sizeof(*priv), 1); if (!dev) return -ENOMEM; dev->netdev_ops = &dummy_can_netdev_ops; dev->ethtool_ops = &dummy_can_ethtool_ops; priv = netdev_priv(dev); priv->can.bittiming_const = &dummy_can_bittiming_const; priv->can.bitrate_max = 20 * MEGA /* BPS */; priv->can.clock.freq = 160 * MEGA /* Hz */; priv->can.fd.data_bittiming_const = &dummy_can_fd_databittiming_const; priv->can.fd.tdc_const = &dummy_can_fd_tdc_const; priv->can.xl.data_bittiming_const = &dummy_can_xl_databittiming_const; priv->can.xl.tdc_const = &dummy_can_xl_tdc_const; priv->can.xl.pwm_const = &dummy_can_pwm_const; priv->can.ctrlmode_supported = CAN_CTRLMODE_LISTENONLY | CAN_CTRLMODE_FD | CAN_CTRLMODE_TDC_AUTO | CAN_CTRLMODE_RESTRICTED | CAN_CTRLMODE_XL | CAN_CTRLMODE_XL_TDC_AUTO | CAN_CTRLMODE_XL_TMS; priv->dev = dev; ret = register_candev(priv->dev); if (ret) { free_candev(priv->dev); return ret; } dummy_can = priv; netdev_dbg(dev, "dummy-can ready\n"); return 0; } static void __exit dummy_can_exit(void) { struct net_device *dev = dummy_can->dev; netdev_dbg(dev, "dummy-can bye bye\n"); unregister_candev(dev); free_candev(dev); } module_init(dummy_can_init); module_exit(dummy_can_exit); MODULE_DESCRIPTION("A dummy CAN driver, mainly to test the netlink interface"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Vincent Mailhol ");