/* * Copyright (C) 2006 Jan Kiszka * * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include "rtcan_dev.h" #include "rtcan_raw.h" #define RTCAN_DEV_NAME "rtcan%d" #define RTCAN_DRV_NAME "VIRT" #define RTCAN_MAX_VIRT_DEVS 8 #define VIRT_TX_BUFS 1 static char *virt_ctlr_name = ""; static char *virt_board_name = ""; MODULE_AUTHOR("Jan Kiszka "); MODULE_DESCRIPTION("Virtual RT-Socket-CAN driver"); MODULE_LICENSE("GPL"); static unsigned int devices = 2; module_param(devices, uint, 0400); MODULE_PARM_DESC(devices, "Number of devices on the virtual bus"); static struct rtcan_device *rtcan_virt_devs[RTCAN_MAX_VIRT_DEVS]; static int rtcan_virt_start_xmit(struct rtcan_device *tx_dev, can_frame_t *tx_frame) { int i; struct rtcan_device *rx_dev; struct rtcan_skb skb; struct rtcan_rb_frame *rx_frame = &skb.rb_frame; rtdm_lockctx_t lock_ctx; /* we can transmit immediately again */ rtdm_sem_up(&tx_dev->tx_sem); tx_dev->tx_count++; skb.rb_frame_size = EMPTY_RB_FRAME_SIZE; rx_frame->can_dlc = tx_frame->can_dlc; rx_frame->can_id = tx_frame->can_id; if (!(tx_frame->can_id & CAN_RTR_FLAG)) { memcpy(rx_frame->data, tx_frame->data, tx_frame->can_dlc); skb.rb_frame_size += tx_frame->can_dlc; } rtdm_lock_get_irqsave(&rtcan_recv_list_lock, lock_ctx); rtdm_lock_get(&rtcan_socket_lock); /* Deliver to all other devices on the virtual bus */ for (i = 0; i < devices; i++) { rx_dev = rtcan_virt_devs[i]; if (rx_dev->state == CAN_STATE_ACTIVE) { if (tx_dev != rx_dev) { rx_frame->can_ifindex = rx_dev->ifindex; rtcan_rcv(rx_dev, &skb); } else if (rtcan_loopback_pending(tx_dev)) rtcan_loopback(tx_dev); } } rtdm_lock_put(&rtcan_socket_lock); rtdm_lock_put_irqrestore(&rtcan_recv_list_lock, lock_ctx); return 0; } static int rtcan_virt_set_mode(struct rtcan_device *dev, can_mode_t mode, rtdm_lockctx_t *lock_ctx) { int err = 0; switch (mode) { case CAN_MODE_STOP: dev->state = CAN_STATE_STOPPED; /* Wake up waiting senders */ rtdm_sem_destroy(&dev->tx_sem); break; case CAN_MODE_START: rtdm_sem_init(&dev->tx_sem, VIRT_TX_BUFS); dev->state = CAN_STATE_ACTIVE; break; default: err = -EOPNOTSUPP; } return err; } static int __init rtcan_virt_init_one(int idx) { struct rtcan_device *dev; int err; if ((dev = rtcan_dev_alloc(0, 0)) == NULL) return -ENOMEM; dev->ctrl_name = virt_ctlr_name; dev->board_name = virt_board_name; rtcan_virt_set_mode(dev, CAN_MODE_STOP, NULL); strncpy(dev->name, RTCAN_DEV_NAME, IFNAMSIZ); dev->hard_start_xmit = rtcan_virt_start_xmit; dev->do_set_mode = rtcan_virt_set_mode; /* Register RTDM device */ err = rtcan_dev_register(dev); if (err) { printk(KERN_ERR "ERROR %d while trying to register RTCAN device!\n", err); goto error_out; } /* Remember initialized devices */ rtcan_virt_devs[idx] = dev; printk("%s: %s driver loaded\n", dev->name, RTCAN_DRV_NAME); return 0; error_out: rtcan_dev_free(dev); return err; } /** Init module */ static int __init rtcan_virt_init(void) { int i, err = 0; if (!rtdm_available()) return -ENOSYS; for (i = 0; i < devices; i++) { err = rtcan_virt_init_one(i); if (err) { while (--i >= 0) { struct rtcan_device *dev = rtcan_virt_devs[i]; rtcan_dev_unregister(dev); rtcan_dev_free(dev); } break; } } return err; } /** Cleanup module */ static void __exit rtcan_virt_exit(void) { int i; struct rtcan_device *dev; for (i = 0; i < devices; i++) { dev = rtcan_virt_devs[i]; printk("Unloading %s device %s\n", RTCAN_DRV_NAME, dev->name); rtcan_virt_set_mode(dev, CAN_MODE_STOP, NULL); rtcan_dev_unregister(dev); rtcan_dev_free(dev); } } module_init(rtcan_virt_init); module_exit(rtcan_virt_exit);