| /* |
| * Copyright (c) 2019 Alexander Wachter |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <net/can.h> |
| #include <net/net_pkt.h> |
| |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(net_can, CONFIG_CAN_NET_LOG_LEVEL); |
| |
| struct mcast_filter_mapping { |
| const struct in6_addr *addr; |
| int filter_id; |
| }; |
| |
| struct net_can_context { |
| const struct device *can_dev; |
| struct net_if *iface; |
| int recv_filter_id; |
| struct mcast_filter_mapping mcast_mapping[NET_IF_MAX_IPV6_MADDR]; |
| #ifdef CONFIG_NET_L2_CANBUS_ETH_TRANSLATOR |
| int eth_bridge_filter_id; |
| int all_mcast_filter_id; |
| #endif |
| }; |
| |
| static struct net_if_mcast_monitor mcast_monitor; |
| |
| struct mcast_filter_mapping *can_get_mcast_filter(struct net_can_context *ctx, |
| const struct in6_addr *addr) |
| { |
| struct mcast_filter_mapping *map = ctx->mcast_mapping; |
| |
| for (int i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) { |
| if (map[i].addr == addr) { |
| return &map[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static inline uint8_t can_get_frame_datalength(struct zcan_frame *frame) |
| { |
| /* TODO: Needs update when CAN FD support is added */ |
| return frame->dlc; |
| } |
| |
| static inline uint16_t can_get_lladdr_src(struct zcan_frame *frame) |
| { |
| return (frame->id >> CAN_NET_IF_ADDR_SRC_POS) & |
| CAN_NET_IF_ADDR_MASK; |
| } |
| |
| static inline uint16_t can_get_lladdr_dest(struct zcan_frame *frame) |
| { |
| uint16_t addr = (frame->id >> CAN_NET_IF_ADDR_DEST_POS) & |
| CAN_NET_IF_ADDR_MASK; |
| |
| if (frame->id & CAN_NET_IF_ADDR_MCAST_MASK) { |
| addr |= CAN_NET_IF_IS_MCAST_BIT; |
| } |
| |
| return addr; |
| } |
| |
| static inline void can_set_lladdr(struct net_pkt *pkt, struct zcan_frame *frame) |
| { |
| struct net_buf *buf = pkt->buffer; |
| |
| /* Put the destination at the beginning of the pkt. |
| * The net_canbus_lladdr has a size if 14 bits. To convert it to |
| * network byte order, we treat it as 16 bits here. |
| */ |
| net_pkt_lladdr_dst(pkt)->addr = buf->data; |
| net_pkt_lladdr_dst(pkt)->len = sizeof(struct net_canbus_lladdr); |
| net_pkt_lladdr_dst(pkt)->type = NET_LINK_CANBUS; |
| net_buf_add_be16(buf, can_get_lladdr_dest(frame)); |
| net_buf_pull(buf, sizeof(uint16_t)); |
| |
| /* Do the same as above for the source address */ |
| net_pkt_lladdr_src(pkt)->addr = buf->data; |
| net_pkt_lladdr_src(pkt)->len = sizeof(struct net_canbus_lladdr); |
| net_pkt_lladdr_src(pkt)->type = NET_LINK_CANBUS; |
| net_buf_add_be16(buf, can_get_lladdr_src(frame)); |
| net_buf_pull(buf, sizeof(uint16_t)); |
| } |
| |
| static int net_can_send(const struct device *dev, |
| const struct zcan_frame *frame, |
| can_tx_callback_t cb, void *cb_arg, k_timeout_t timeout) |
| { |
| struct net_can_context *ctx = dev->data; |
| |
| NET_ASSERT(frame->id_type == CAN_EXTENDED_IDENTIFIER); |
| return can_send(ctx->can_dev, frame, timeout, cb, cb_arg); |
| } |
| |
| static void net_can_recv(struct zcan_frame *frame, void *arg) |
| { |
| struct net_can_context *ctx = (struct net_can_context *)arg; |
| size_t pkt_size = 2 * sizeof(struct net_canbus_lladdr) + |
| can_get_frame_datalength(frame); |
| struct net_pkt *pkt; |
| int ret; |
| |
| NET_DBG("Frame with ID 0x%x received", frame->id); |
| pkt = net_pkt_rx_alloc_with_buffer(ctx->iface, pkt_size, AF_UNSPEC, 0, |
| K_NO_WAIT); |
| if (!pkt) { |
| LOG_ERR("Failed to obtain net_pkt with size of %d", pkt_size); |
| goto drop; |
| } |
| |
| pkt->canbus_rx_ctx = NULL; |
| |
| can_set_lladdr(pkt, frame); |
| net_pkt_cursor_init(pkt); |
| ret = net_pkt_write(pkt, frame->data, can_get_frame_datalength(frame)); |
| if (ret) { |
| LOG_ERR("Failed to append frame data to net_pkt"); |
| goto drop; |
| } |
| |
| ret = net_recv_data(ctx->iface, pkt); |
| if (ret < 0) { |
| LOG_ERR("Packet dropped by NET stack"); |
| goto drop; |
| } |
| |
| return; |
| |
| drop: |
| NET_INFO("pkt dropped"); |
| |
| if (pkt) { |
| net_pkt_unref(pkt); |
| } |
| } |
| |
| static inline int attach_mcast_filter(struct net_can_context *ctx, |
| const struct in6_addr *addr) |
| { |
| static struct zcan_filter filter = { |
| .id_type = CAN_EXTENDED_IDENTIFIER, |
| .rtr = CAN_DATAFRAME, |
| .rtr_mask = 1, |
| .id_mask = CAN_NET_IF_ADDR_MCAST_MASK | |
| CAN_NET_IF_ADDR_DEST_MASK |
| }; |
| const uint16_t group = |
| sys_be16_to_cpu(UNALIGNED_GET((&addr->s6_addr16[7]))); |
| int filter_id; |
| |
| filter.id = CAN_NET_IF_ADDR_MCAST_MASK | |
| ((group & CAN_NET_IF_ADDR_MASK) << |
| CAN_NET_IF_ADDR_DEST_POS); |
| |
| filter_id = can_attach_isr(ctx->can_dev, net_can_recv, |
| ctx, &filter); |
| if (filter_id == CAN_NET_FILTER_NOT_SET) { |
| return CAN_NET_FILTER_NOT_SET; |
| } |
| |
| NET_DBG("Attached mcast filter. Group 0x%04x. Filter:%d", |
| group, filter_id); |
| |
| return filter_id; |
| } |
| |
| static void mcast_cb(struct net_if *iface, const struct in6_addr *addr, |
| bool is_joined) |
| { |
| const struct device *dev = net_if_get_device(iface); |
| struct net_can_context *ctx = dev->data; |
| struct mcast_filter_mapping *filter_mapping; |
| int filter_id; |
| |
| if (is_joined) { |
| filter_mapping = can_get_mcast_filter(ctx, NULL); |
| if (!filter_mapping) { |
| NET_ERR("Can't get a free filter_mapping"); |
| } |
| |
| filter_id = attach_mcast_filter(ctx, addr); |
| if (filter_id < 0) { |
| NET_ERR("Can't attach mcast filter"); |
| return; |
| } |
| |
| filter_mapping->addr = addr; |
| filter_mapping->filter_id = filter_id; |
| } else { |
| filter_mapping = can_get_mcast_filter(ctx, addr); |
| if (!filter_mapping) { |
| NET_ERR("No filter mapping found"); |
| return; |
| } |
| |
| can_detach(ctx->can_dev, filter_mapping->filter_id); |
| filter_mapping->addr = NULL; |
| } |
| } |
| |
| static void net_can_iface_init(struct net_if *iface) |
| { |
| const struct device *dev = net_if_get_device(iface); |
| struct net_can_context *ctx = dev->data; |
| |
| ctx->iface = iface; |
| |
| NET_DBG("Init CAN network interface %p dev %p", iface, dev); |
| |
| net_6locan_init(iface); |
| |
| net_if_mcast_mon_register(&mcast_monitor, iface, mcast_cb); |
| } |
| |
| static int can_attach_filter(const struct device *dev, can_rx_callback_t cb, |
| void *cb_arg, |
| const struct zcan_filter *filter) |
| { |
| struct net_can_context *ctx = dev->data; |
| |
| return can_attach_isr(ctx->can_dev, cb, cb_arg, filter); |
| } |
| |
| static void can_detach_filter(const struct device *dev, int filter_id) |
| { |
| struct net_can_context *ctx = dev->data; |
| |
| if (filter_id >= 0) { |
| can_detach(ctx->can_dev, filter_id); |
| } |
| } |
| |
| static inline int can_attach_unicast_filter(struct net_can_context *ctx) |
| { |
| struct zcan_filter filter = { |
| .id_type = CAN_EXTENDED_IDENTIFIER, |
| .rtr = CAN_DATAFRAME, |
| .rtr_mask = 1, |
| .id_mask = CAN_NET_IF_ADDR_DEST_MASK |
| }; |
| const uint8_t *link_addr = net_if_get_link_addr(ctx->iface)->addr; |
| const uint16_t dest = sys_be16_to_cpu(UNALIGNED_GET((uint16_t *) link_addr)); |
| int filter_id; |
| |
| filter.id = (dest << CAN_NET_IF_ADDR_DEST_POS); |
| |
| filter_id = can_attach_isr(ctx->can_dev, net_can_recv, |
| ctx, &filter); |
| if (filter_id == CAN_NET_FILTER_NOT_SET) { |
| NET_ERR("Can't attach FF filter"); |
| return CAN_NET_FILTER_NOT_SET; |
| } |
| |
| NET_DBG("Attached FF filter %d", filter_id); |
| |
| return filter_id; |
| } |
| |
| #ifdef CONFIG_NET_L2_CANBUS_ETH_TRANSLATOR |
| static inline int can_attach_eth_bridge_filter(struct net_can_context *ctx) |
| { |
| const struct zcan_filter filter = { |
| .id_type = CAN_EXTENDED_IDENTIFIER, |
| .rtr = CAN_DATAFRAME, |
| .rtr_mask = 1, |
| .id_mask = CAN_NET_IF_ADDR_DEST_MASK, |
| .id = (NET_CAN_ETH_TRANSLATOR_ADDR << |
| CAN_NET_IF_ADDR_DEST_POS) |
| }; |
| |
| int filter_id; |
| |
| filter_id = can_attach_isr(ctx->can_dev, net_can_recv, |
| ctx, &filter); |
| if (filter_id == CAN_NET_FILTER_NOT_SET) { |
| NET_ERR("Can't attach ETH bridge filter"); |
| return CAN_NET_FILTER_NOT_SET; |
| } |
| |
| NET_DBG("Attached ETH bridge filter %d", filter_id); |
| |
| return filter_id; |
| } |
| |
| static inline int can_attach_all_mcast_filter(struct net_can_context *ctx) |
| { |
| const struct zcan_filter filter = { |
| .id_type = CAN_EXTENDED_IDENTIFIER, |
| .rtr = CAN_DATAFRAME, |
| .rtr_mask = 1, |
| .id_mask = CAN_NET_IF_ADDR_MCAST_MASK, |
| .id = CAN_NET_IF_ADDR_MCAST_MASK |
| }; |
| |
| int filter_id; |
| |
| filter_id = can_attach_isr(ctx->can_dev, net_can_recv, |
| ctx, &filter); |
| if (filter_id == CAN_NET_FILTER_NOT_SET) { |
| NET_ERR("Can't attach all mcast filter"); |
| return CAN_NET_FILTER_NOT_SET; |
| } |
| |
| NET_DBG("Attached all mcast filter %d", filter_id); |
| |
| return filter_id; |
| } |
| #endif /*CONFIG_NET_L2_CANBUS_ETH_TRANSLATOR*/ |
| |
| static int can_enable(const struct device *dev, bool enable) |
| { |
| struct net_can_context *ctx = dev->data; |
| |
| if (enable) { |
| if (ctx->recv_filter_id == CAN_NET_FILTER_NOT_SET) { |
| ctx->recv_filter_id = can_attach_unicast_filter(ctx); |
| if (ctx->recv_filter_id < 0) { |
| return -EIO; |
| } |
| } |
| |
| #ifdef CONFIG_NET_L2_CANBUS_ETH_TRANSLATOR |
| if (ctx->eth_bridge_filter_id == CAN_NET_FILTER_NOT_SET) { |
| ctx->eth_bridge_filter_id = can_attach_eth_bridge_filter(ctx); |
| if (ctx->eth_bridge_filter_id < 0) { |
| can_detach(ctx->can_dev, ctx->recv_filter_id); |
| return -EIO; |
| } |
| } |
| |
| if (ctx->all_mcast_filter_id == CAN_NET_FILTER_NOT_SET) { |
| ctx->all_mcast_filter_id = can_attach_all_mcast_filter(ctx); |
| if (ctx->all_mcast_filter_id < 0) { |
| can_detach(ctx->can_dev, ctx->recv_filter_id); |
| can_detach(ctx->can_dev, ctx->eth_bridge_filter_id); |
| return -EIO; |
| } |
| } |
| #endif /*CONFIG_NET_L2_CANBUS_ETH_TRANSLATOR*/ |
| } else { |
| if (ctx->recv_filter_id != CAN_NET_FILTER_NOT_SET) { |
| can_detach(ctx->can_dev, ctx->recv_filter_id); |
| } |
| |
| #ifdef CONFIG_NET_L2_CANBUS_ETH_TRANSLATOR |
| if (ctx->eth_bridge_filter_id != CAN_NET_FILTER_NOT_SET) { |
| can_detach(ctx->can_dev, ctx->eth_bridge_filter_id); |
| } |
| |
| if (ctx->all_mcast_filter_id != CAN_NET_FILTER_NOT_SET) { |
| can_detach(ctx->can_dev, ctx->all_mcast_filter_id); |
| } |
| #endif /*CONFIG_NET_L2_CANBUS_ETH_TRANSLATOR*/ |
| } |
| |
| return 0; |
| } |
| |
| static struct net_can_api net_can_api_inst = { |
| .iface_api.init = net_can_iface_init, |
| |
| .send = net_can_send, |
| .attach_filter = can_attach_filter, |
| .detach_filter = can_detach_filter, |
| .enable = can_enable, |
| }; |
| |
| static int net_can_init(const struct device *dev) |
| { |
| const struct device *can_dev; |
| struct net_can_context *ctx = dev->data; |
| |
| can_dev = device_get_binding(DT_CHOSEN_ZEPHYR_CAN_PRIMARY_LABEL); |
| |
| ctx->recv_filter_id = CAN_NET_FILTER_NOT_SET; |
| #ifdef CONFIG_NET_L2_CANBUS_ETH_TRANSLATOR |
| ctx->eth_bridge_filter_id = CAN_NET_FILTER_NOT_SET; |
| ctx->all_mcast_filter_id = CAN_NET_FILTER_NOT_SET; |
| #endif /*CONFIG_NET_L2_CANBUS_ETH_TRANSLATOR*/ |
| |
| if (!can_dev) { |
| NET_ERR("Can't get binding to CAN device %s", |
| DT_CHOSEN_ZEPHYR_CAN_PRIMARY_LABEL); |
| return -EIO; |
| } |
| |
| NET_DBG("Init net CAN device %p (%s) for dev %p (%s)", |
| dev, dev->name, can_dev, can_dev->name); |
| |
| ctx->can_dev = can_dev; |
| |
| return 0; |
| } |
| |
| static struct net_can_context net_can_context_1; |
| |
| NET_DEVICE_INIT(net_can_1, CONFIG_CAN_NET_NAME, net_can_init, |
| NULL, &net_can_context_1, NULL, |
| CONFIG_CAN_NET_INIT_PRIORITY, |
| &net_can_api_inst, |
| CANBUS_L2, NET_L2_GET_CTX_TYPE(CANBUS_L2), NET_CAN_MTU); |