// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 *
 * Description: Bluetooth virtual network device used in
 * the NewIP over Bluetooth communication scenario.
 *
 * Author: Yang Yanjun <yangyanjun@huawei.com>
 *
 * Data: 2023-03-14
 */

#define pr_fmt(fmt) "newip-bt: [%s:%d] " fmt, __func__, __LINE__

#include "btdev.h"

#define ndev_name(vnet)  bt_virnet_get_ndev_name(vnet)  /* btn1/2/3/4/... */
#define cdev_name(vnet)  bt_virnet_get_cdev_name(vnet)  /* dev/btdev1/2/3/4/... */

/* /sys/module/btdev/parameters/btdev_debug */
bool g_btdev_debug;
module_param_named(btdev_debug, g_btdev_debug, bool, 0644);

#define btdev_dbg(fmt, ...) \
do { \
	if (g_btdev_debug) \
		pr_crit(fmt, ##__VA_ARGS__); \
} while (0)

#define btdev_dbg_err(fmt, ...) pr_err(fmt, ##__VA_ARGS__)

static struct bt_drv *g_bt_drv;

static int bt_seq_show(struct seq_file *m, void *v)
{
	struct bt_virnet *vnet = NULL;

	if (unlikely(!g_bt_drv)) {
		btdev_dbg_err("invalid bt_drv");
		return -EINVAL;
	}

	seq_printf(m, "Total device: %d (bitmap: 0x%X) Ring size: %d\n",
		   bt_get_total_device(g_bt_drv), g_bt_drv->bitmap,
		   BT_RING_BUFFER_SIZE);

	list_for_each_entry(vnet, &g_bt_drv->devices_table->head, virnet_entry) {
		seq_printf(m, "dev: %12s, interface: %7s, state: %12s, MTU: %4d\n",
			   cdev_name(vnet), ndev_name(vnet),
			   bt_virnet_get_state_rep(vnet), vnet->ndev->mtu);
		seq_printf(m, "ring head: %4d, ring tail: %4d, packets num: %4d\n",
			   vnet->tx_ring->head, vnet->tx_ring->tail,
			   bt_virnet_get_ring_packets(vnet));
	}

	return OK;
}

static int bt_proc_open(struct inode *inode, struct file *file)
{
	if (unlikely(!inode) || unlikely(!file)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}

	return single_open(file, bt_seq_show, PDE_DATA(inode));
}

static struct proc_ops g_bt_proc_fops = {
	.proc_open = bt_proc_open,
	.proc_read = seq_read,
	.proc_lseek = seq_lseek,
	.proc_release = single_release};

static int __bt_virnet_open(struct file *filp, struct bt_virnet *vnet)
{
	struct net_device *ndev;

	if ((filp->f_flags & O_ACCMODE) == O_RDONLY) {
		/* Check whether xx_open_limit is equal to 0 after subtracting 1.
		 * If so, return true
		 */
		if (unlikely(!atomic_dec_and_test(&vnet->io_file->read_open_limit)))
			goto read_twice_already;
	} else if ((filp->f_flags & O_ACCMODE) == O_WRONLY) {
		if (unlikely(!atomic_dec_and_test(&vnet->io_file->write_open_limit)))
			goto write_twice_already;
	} else if ((filp->f_flags & O_ACCMODE) == O_RDWR) {
		if (unlikely(!atomic_dec_and_test(&vnet->io_file->read_open_limit)))
			goto read_twice_already;
		if (unlikely(!atomic_dec_and_test(&vnet->io_file->write_open_limit)))
			goto write_twice_already;
	}

	/* Set xx_open_limit to 0 when the file is first opened */
	rtnl_lock();
	ndev = vnet->ndev;
	if (unlikely(!(ndev->flags & IFF_UP))) {
		int ret = dev_change_flags(ndev, ndev->flags | IFF_UP, NULL);

		if (unlikely(ret < 0)) {
			rtnl_unlock();
			btdev_dbg_err("%s dev change flags failed, ret=%d", cdev_name(vnet), ret);
			return -EBUSY;
		}
	}
	rtnl_unlock();

	set_state(vnet, BT_VIRNET_STATE_CONNECTED);
	filp->private_data = vnet;
	btdev_dbg("%s has been opened", cdev_name(vnet));
	return OK;

	/* If the file is not opened for the first time, an error occurs
	 * and xx_open_limit is restored to the open state. (set to 0)
	 */
read_twice_already:
	atomic_inc(&vnet->io_file->read_open_limit);
	btdev_dbg_err("%s has been opened for read twice already", cdev_name(vnet));
	return -EBUSY;

write_twice_already:
	atomic_inc(&vnet->io_file->write_open_limit);
	btdev_dbg_err("%s has been opened for write twice already", cdev_name(vnet));
	return -EBUSY;
}

static int bt_io_file_open(struct inode *node, struct file *filp)
{
	struct bt_virnet *vnet = NULL;

	if (unlikely(!node) || unlikely(!filp)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}

	list_for_each_entry(vnet, &g_bt_drv->devices_table->head, virnet_entry) {
		if (bt_virnet_get_cdev(vnet) == node->i_cdev)
			return __bt_virnet_open(filp, vnet);
	}
	return -EIO;
}

static int bt_io_file_release(struct inode *node, struct file *filp)
{
	struct bt_virnet *vnet = NULL;

	if (unlikely(!filp) || unlikely(!filp->private_data)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}

	vnet = filp->private_data;
	btdev_dbg("%s has been released", cdev_name(vnet));

	/* Set xx_open_limit to 1 when the file is closed */
	if ((filp->f_flags & O_ACCMODE) == O_RDONLY) {
		atomic_inc(&vnet->io_file->read_open_limit);
	} else if ((filp->f_flags & O_ACCMODE) == O_WRONLY) {
		atomic_inc(&vnet->io_file->write_open_limit);
	} else if ((filp->f_flags & O_ACCMODE) == O_RDWR) {
		atomic_inc(&vnet->io_file->read_open_limit);
		atomic_inc(&vnet->io_file->write_open_limit);
	}

	set_state(vnet, BT_VIRNET_STATE_DISCONNECTED);

	return OK;
}

static ssize_t bt_io_file_read(struct file *filp,
			       char __user *buffer,
			       size_t size, loff_t *off)
{
	struct bt_virnet *vnet = NULL;
	ssize_t out_sz;
	struct sk_buff *skb = NULL;

	if (unlikely(!filp) || unlikely(!buffer) || unlikely(!filp->private_data)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}

	vnet = filp->private_data;
	while (unlikely(bt_ring_is_empty(vnet->tx_ring))) {
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;

		if (wait_event_interruptible(vnet->rx_queue, !bt_ring_is_empty(vnet->tx_ring)))
			return -ERESTARTSYS;
	}

	skb = bt_ring_current(vnet->tx_ring);
	if (unlikely(!skb)) {
		btdev_dbg_err("%s invalid skb", cdev_name(vnet));
		return -EINVAL;
	}
	out_sz = skb->len > MACADDR_LEN ? (skb->len - MACADDR_LEN) : 0;
	if (unlikely(out_sz > size) || unlikely(out_sz == 0)) {
		/* Obtain the skb pointer from the ring buf and ask whether the user-state buf
		 * length can store data in the skb. If the user-state buf length is not enough,
		 * the skb cannot be released at this time, because the skb is still unchained
		 * on the ring buf.
		 */
		btdev_dbg_err("%s usr-buf too small, skb-len=%ld, usr-buf-len=%ld, skb-len=%u",
			      cdev_name(vnet), (long)out_sz, (long)size, skb->len);
		return -EINVAL;
	}

	bt_ring_consume(vnet->tx_ring);
	if (copy_to_user(buffer, skb->data + MACADDR_LEN, out_sz)) {
		/* The skb pointer is obtained from the ring buf and the skb has been unchained
		 * from the ring buf. In this case, the skb needs to be released when the skb data
		 * fails to be copied to the user mode.
		 */
		btdev_dbg_err("%s copy to user failed", cdev_name(vnet));
		dev_kfree_skb(skb);
		return -EIO;
	}
	dev_kfree_skb(skb);

	btdev_dbg("read %ld data from %s", (long)out_sz, cdev_name(vnet));
	if (unlikely(netif_queue_stopped(vnet->ndev))) {
		btdev_dbg("consume data: wake the queue");
		netif_wake_queue(vnet->ndev);
	}

	return out_sz;
}

static ssize_t bt_io_file_write(struct file *filp,
				const char __user *buffer,
				size_t size, loff_t *off)
{
	struct bt_virnet *vnet = NULL;
	struct sk_buff *skb = NULL;
	int ret;
	int len;
	ssize_t in_sz;

	if (unlikely(!filp) || unlikely(!buffer) || unlikely(!filp->private_data)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}

	vnet = filp->private_data;
	in_sz = size + MACADDR_LEN;

	/* Ethernet head length: DMAC(6B) + SMAC(6B) + eth-type(2B) */
	skb = netdev_alloc_skb(bt_virnet_get_ndev(vnet), in_sz + NEWIP_TYPE_SIZE);
	if (unlikely(!skb))
		return -ENOMEM;

	skb_reserve(skb, NEWIP_TYPE_SIZE);
	skb_put(skb, in_sz);

	memset(skb->data, 0, MACADDR_LEN);
	if (copy_from_user(skb->data + MACADDR_LEN, buffer, size)) {
		btdev_dbg_err("%s copy from user failed", cdev_name(vnet));
		dev_kfree_skb(skb);
		return -EIO;
	}

	len = skb->len;
	skb->dev = bt_virnet_get_ndev(vnet);
	skb->protocol = eth_type_trans(skb, bt_virnet_get_ndev(vnet));
	ret = netif_rx_ni(skb);

	if (ret == NET_RX_SUCCESS) {
		btdev_dbg("write %lu bytes data to %s", size, cdev_name(vnet));
		vnet->ndev->stats.rx_packets++;
		vnet->ndev->stats.rx_bytes += len;
	} else {
		btdev_dbg_err("failed to write %lu bytes data to %s", size, cdev_name(vnet));
		vnet->ndev->stats.rx_errors++;
		vnet->ndev->stats.rx_dropped++;
	}

	return size;
}

static int bt_virnet_change_mtu(struct net_device *dev, int mtu)
{
	if (unlikely(!dev) || unlikely(mtu < 0) || unlikely(mtu > BT_MAX_MTU)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}
	btdev_dbg("change %s mtu %u to %u", dev->name, dev->mtu, mtu);
	dev->mtu = mtu;
	return OK;
}

static int bt_set_mtu(struct net_device *dev, int mtu)
{
	int err = OK;

	if (unlikely(mtu < 0) || unlikely(mtu > BT_MAX_MTU)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}

	rtnl_lock();
	err = dev_set_mtu(dev, mtu);
	rtnl_unlock();
	if (err < 0)
		btdev_dbg_err("failed to set %s mtu to %d, err=%d", dev->name, mtu, err);
	else
		btdev_dbg("set %s mtu to %d", dev->name, mtu);

	return err;
}

static int bt_cmd_enable_virnet(struct bt_virnet *vnet, unsigned long arg)
{
	int ret;

	if (unlikely(vnet->state != BT_VIRNET_STATE_DISABLED)) {
		btdev_dbg_err("%s enable can only be set at disabled state", cdev_name(vnet));
		return -EINVAL; // enable failed
	}

	rtnl_lock();
	ret = dev_change_flags(vnet->ndev, vnet->ndev->flags | IFF_UP, NULL);
	rtnl_unlock();
	if (unlikely(ret < 0)) {
		btdev_dbg_err("%s dev change flags failed, ret=%d", cdev_name(vnet), ret);
		return -EIO;
	}

	btdev_dbg("%s has been enabled", cdev_name(vnet));
	set_state(vnet, BT_VIRNET_STATE_CONNECTED);
	return OK;
}

static int bt_cmd_disable_virnet(struct bt_virnet *vnet, unsigned long arg)
{
	int ret;

	if (unlikely(vnet->state != BT_VIRNET_STATE_CONNECTED)) {
		btdev_dbg_err("%s disable can only be set at connected state", cdev_name(vnet));
		return -EINVAL;
	}

	rtnl_lock();
	ret = dev_change_flags(vnet->ndev, vnet->ndev->flags & ~IFF_UP, NULL);
	rtnl_unlock();
	if (unlikely(ret < 0)) {
		btdev_dbg_err("%s dev change flags failed, ret=%d", cdev_name(vnet), ret);
		return -EIO;
	}

	btdev_dbg("%s has been disabled", cdev_name(vnet));
	set_state(vnet, BT_VIRNET_STATE_DISABLED);
	return OK;
}

static int bt_cmd_change_mtu(struct bt_virnet *vnet, unsigned long arg)
{
	int mtu;
	int ret;

	if (unlikely(get_user(mtu, (int __user *)arg))) {
		btdev_dbg_err("%s get user failed", ndev_name(vnet));
		return -EIO;
	}

	ret = bt_set_mtu(vnet->ndev, mtu);
	if (unlikely(ret < 0)) {
		btdev_dbg_err("%s changed mtu to %d failed", ndev_name(vnet), mtu);
		return -EIO;
	}

	btdev_dbg("%s changed mtu to %d", ndev_name(vnet), mtu);
	return OK;
}

static int bt_cmd_peek_packet(struct bt_virnet *vnet, unsigned long arg)
{
	u32 len;
	struct sk_buff *skb;

	if (unlikely(bt_ring_is_empty(vnet->tx_ring))) {
		btdev_dbg_err("%s ring is empty", ndev_name(vnet));
		return -EAGAIN;
	}

	/* The user state retrieves the data length from the ring buf, rather than
	 * unchain the skb from the ring buf, so there is no need to release the skb
	 */
	skb = bt_ring_current(vnet->tx_ring);
	if (unlikely(!skb)) {
		btdev_dbg_err("%s invalid skb", ndev_name(vnet));
		return -EINVAL;
	}

	len = skb->len - MACADDR_LEN;
	if (unlikely(put_user(len, (int __user *)arg))) {
		btdev_dbg_err("%s put_user failed", ndev_name(vnet));
		return -EIO;
	}

	btdev_dbg("%s get packet len is %u", ndev_name(vnet), len);
	return OK;
}

static long bt_io_file_ioctl(struct file *filep,
			     unsigned int cmd,
			     unsigned long arg)
{
	long ret;
	struct bt_virnet *vnet = NULL;

	if (unlikely(!filep) || unlikely(!filep->private_data)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}
	vnet = filep->private_data;
	switch (cmd) {
	case BT_IOC_CHANGE_MTU:
		ret = bt_cmd_change_mtu(vnet, arg);
		break;
	case BT_IOC_ENABLE:
		ret = bt_cmd_enable_virnet(vnet, arg);
		break;
	case BT_IOC_DISABLE:
		ret = bt_cmd_disable_virnet(vnet, arg);
		break;
	case BT_IOC_PEEK_PACKET:
		ret = bt_cmd_peek_packet(vnet, arg);
		break;
	default:
		btdev_dbg_err("not a valid cmd(%u)", cmd);
		return -ENOIOCTLCMD;
	}

	return ret;
}

static unsigned int bt_io_file_poll(struct file *filp, poll_table *wait)
{
	struct bt_virnet *vnet = NULL;
	unsigned int mask = 0;

	if (unlikely(!filp) || unlikely(!wait) || unlikely(!filp->private_data)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}
	vnet = filp->private_data;
	poll_wait(filp, &vnet->rx_queue, wait);

	if (!bt_ring_is_empty(vnet->tx_ring)) // readable
		mask |= POLLIN | POLLRDNORM;

	if (!bt_ring_is_full(vnet->tx_ring)) // writable
		mask |= POLLOUT | POLLWRNORM;

	return mask;
}

static const struct file_operations g_bt_io_file_ops = {
	.owner = THIS_MODULE,
	.open = bt_io_file_open,
	.release = bt_io_file_release,
	.read = bt_io_file_read,
	.write = bt_io_file_write,
	.poll = bt_io_file_poll,
	.unlocked_ioctl = bt_io_file_ioctl,
	.compat_ioctl = bt_io_file_ioctl};

static int bt_mng_file_open(struct inode *node, struct file *filp)
{
	if (unlikely(!filp)) {
		btdev_dbg_err("bt mng file open: invalid filp");
		return -EINVAL;
	}

	/* Check whether open_limit is equal to 0 after subtracting 1. If so, return true */
	if (unlikely(!atomic_dec_and_test(&g_bt_drv->mng_file->open_limit))) {
		/* If the file is not opened for the first time, an error occurs
		 * and open_limit is restored to the open state. (set to 0)
		 */
		atomic_inc(&g_bt_drv->mng_file->open_limit);
		btdev_dbg_err("file %s has been opened already",
			      g_bt_drv->mng_file->bt_cdev->dev_filename);
		return -EBUSY;
	}

	/* open_limit becomes 0 after the file is first opened */
	filp->private_data = g_bt_drv;

	btdev_dbg("%s has been opened", g_bt_drv->mng_file->bt_cdev->dev_filename);
	return OK;
}

static int bt_mng_file_release(struct inode *node, struct file *filp)
{
	struct bt_drv *drv = NULL;

	if (unlikely(!filp) || unlikely(!filp->private_data)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}
	drv = filp->private_data;

	/* Set open_limit to 1 when the file is closed */
	atomic_inc(&drv->mng_file->open_limit);

	btdev_dbg("%s has been released", g_bt_drv->mng_file->bt_cdev->dev_filename);
	return OK;
}

static int bt_cmd_create_virnet(struct bt_drv *bt_mng, unsigned long arg)
{
	int id;
	int ret;
	struct bt_virnet *vnet = NULL;
	struct bt_uioc_args vp;
	unsigned long size;

	mutex_lock(&bt_mng->bitmap_lock);
	id = bt_get_unused_id(bt_mng->bitmap);
	if ((unlikely(bt_mng->devices_table->num >= BT_VIRNET_MAX_NUM)) ||
	    (unlikely(id < 0))) {
		btdev_dbg_err("reach the limit of max virnets");
		goto virnet_create_failed;
	}
	vnet = bt_virnet_create(bt_mng, id);
	if (unlikely(!vnet)) {
		btdev_dbg_err("bt virnet create failed");
		goto virnet_create_failed;
	}

	ret = bt_table_add_device(bt_mng->devices_table, vnet);
	if (unlikely(ret < 0)) {
		btdev_dbg_err("bt table add device failed: ret=%d", ret);
		goto add_device_failed;
	}

	bt_set_bit(&bt_mng->bitmap, id);
	mutex_unlock(&bt_mng->bitmap_lock);

	memcpy(vp.ifa_name, ndev_name(vnet), sizeof(vp.ifa_name));
	memcpy(vp.cfile_name, cdev_name(vnet), sizeof(vp.cfile_name));

	mdelay(DELAY_100_MS);

	size = copy_to_user((void __user *)arg, &vp, sizeof(struct bt_uioc_args));
	if (unlikely(size)) {
		btdev_dbg_err("copy_to_user failed: left size=%lu", size);
		goto copy_to_user_failed;
	}

	btdev_dbg("%s has been created", ndev_name(vnet));
	return OK;

copy_to_user_failed:
	mutex_lock(&bt_mng->bitmap_lock);
	bt_table_remove_device(bt_mng->devices_table, vnet);
	bt_clear_bit(&bt_mng->bitmap, id);

add_device_failed:
	bt_virnet_destroy(vnet);

virnet_create_failed:
	mutex_unlock(&bt_mng->bitmap_lock);
	return -EIO;
}

static int bt_cmd_delete_virnet(struct bt_drv *bt_mng, unsigned long arg)
{
	int err;
	struct bt_virnet *vnet = NULL;
	struct bt_uioc_args vp;
	unsigned long size;
	dev_t number;

	size = copy_from_user(&vp, (void __user *)arg,
			      sizeof(struct bt_uioc_args));
	if (unlikely(size)) {
		btdev_dbg_err("copy_from_user failed: left size=%lu", size);
		return -EIO;
	}

	vnet = bt_table_find(bt_mng->devices_table, vp.ifa_name);
	if (unlikely(!vnet)) {
		btdev_dbg_err("virnet: %s cannot be found in bt table", vp.ifa_name);
		return -EIO; // not found
	}

	btdev_dbg("%s has been deleted", ndev_name(vnet));
	mutex_lock(&bt_mng->bitmap_lock);
	err = bt_virnet_get_cdev_number(vnet, &number);
	if (likely(!err))
		bt_clear_bit(&bt_mng->bitmap, (u32)MINOR(number));
	bt_table_remove_device(bt_mng->devices_table, vnet);
	bt_virnet_destroy(vnet);
	mutex_unlock(&bt_mng->bitmap_lock);
	return OK;
}

static int bt_cmd_query_all_virnets(struct bt_drv *bt_mng, unsigned long arg)
{
	if (unlikely(put_user(bt_mng->bitmap, (u32 *)arg))) {
		btdev_dbg_err("put_user failed");
		return -EIO;
	}
	return OK;
}

static int bt_cmd_delete_all_virnets(struct bt_drv *bt_mng, unsigned long arg)
{
	return bt_table_delete_all(bt_mng);
}

static long bt_mng_file_ioctl(struct file *filep,
			      unsigned int cmd,
			      unsigned long arg)
{
	int ret;
	struct bt_drv *bt_mng = NULL;

	if (unlikely(!filep) || unlikely(!filep->private_data)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}
	bt_mng = filep->private_data;

	switch (cmd) {
	case BT_IOC_CREATE:
		ret = bt_cmd_create_virnet(bt_mng, arg);
		break;
	case BT_IOC_DELETE:
		ret = bt_cmd_delete_virnet(bt_mng, arg);
		break;
	case BT_IOC_QUERY_ALL:
		ret = bt_cmd_query_all_virnets(bt_mng, arg);
		break;
	case BT_IOC_DELETE_ALL:
		ret = bt_cmd_delete_all_virnets(bt_mng, arg);
		break;
	default:
		btdev_dbg_err("not a valid cmd(%u)", cmd);
		return -ENOIOCTLCMD;
	}
	return ret;
}

static const struct file_operations g_bt_mng_file_ops = {
	.owner = THIS_MODULE,
	.open = bt_mng_file_open,
	.release = bt_mng_file_release,
	.unlocked_ioctl = bt_mng_file_ioctl,
	.compat_ioctl = bt_mng_file_ioctl};

static netdev_tx_t bt_virnet_xmit(struct sk_buff *skb,
				  struct net_device *dev)
{
	int ret;
	struct bt_virnet *vnet = NULL;

	if (unlikely(!skb) || unlikely(!dev)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}

	vnet = bt_table_find(g_bt_drv->devices_table, dev->name);
	if (unlikely(!vnet)) {
		btdev_dbg_err("bt_table_find %s failed", ndev_name(vnet));
		return -EINVAL;
	}

	ret = bt_virnet_produce_data(vnet, (void *)skb);
	if (unlikely(ret < 0)) {
		btdev_dbg("%s produce data failed: ring is full, need to stop queue",
			  ndev_name(vnet));
		netif_stop_queue(vnet->ndev);
		return NETDEV_TX_BUSY;
	}

	vnet->ndev->stats.tx_packets++;
	vnet->ndev->stats.tx_bytes += skb->len;

	btdev_dbg("%s send success, skb-len=%u", ndev_name(vnet), skb->len);
	return NETDEV_TX_OK;
}

static const struct net_device_ops g_bt_virnet_ops = {
	.ndo_start_xmit = bt_virnet_xmit,
	.ndo_change_mtu = bt_virnet_change_mtu};

static struct bt_table *bt_table_init(void)
{
	struct bt_table *tbl = kmalloc(sizeof(*tbl), GFP_KERNEL);

	if (unlikely(!tbl)) {
		btdev_dbg_err("alloc failed");
		return NULL;
	}

	INIT_LIST_HEAD(&tbl->head);
	mutex_init(&tbl->tbl_lock);
	tbl->num = 0;
	return tbl;
}

static int bt_table_add_device(struct bt_table *tbl, struct bt_virnet *vn)
{
	struct bt_virnet *vnet = NULL;

	if (unlikely(!tbl)) {
		btdev_dbg_err("invalid parameter");
		return -EINVAL;
	}

	vnet = bt_table_find(tbl, ndev_name(vn));
	if (unlikely(vnet)) {
		btdev_dbg_err("found duplicated device %s", ndev_name(vn));
		return -ENOIOCTLCMD; // duplicated
	}

	btdev_dbg("%s has been added", ndev_name(vn));
	mutex_lock(&tbl->tbl_lock);
	list_add_tail(&vn->virnet_entry, &tbl->head);
	if (tbl->num < UINT32_MAX)
		++tbl->num;
	mutex_unlock(&tbl->tbl_lock);

	return OK;
}

static void bt_table_remove_device(struct bt_table *tbl, struct bt_virnet *vn)
{
	if (unlikely(!tbl))
		return;

	btdev_dbg("%s has been removed", ndev_name(vn));
	mutex_lock(&tbl->tbl_lock);
	list_del(&vn->virnet_entry);
	if (tbl->num)
		--tbl->num;
	mutex_unlock(&tbl->tbl_lock);
}

static struct bt_virnet *bt_table_find(struct bt_table *tbl, const char *ifa_name)
{
	struct bt_virnet *vnet = NULL;

	if (unlikely(!tbl) || unlikely(!ifa_name)) {
		btdev_dbg_err("invalid parameter");
		return NULL;
	}

	list_for_each_entry(vnet, &tbl->head, virnet_entry) {
		if (!strcmp(ndev_name(vnet), ifa_name))
			return vnet;
	}

	return NULL;
}

static void __bt_table_delete_all(struct bt_drv *drv)
{
	dev_t number;
	struct bt_virnet *vnet = NULL;
	struct bt_virnet *tmp_vnet = NULL;

	if (unlikely(!g_bt_drv->devices_table))
		return;

	list_for_each_entry_safe(vnet,
				 tmp_vnet,
				 &drv->devices_table->head,
				 virnet_entry) {
		int err = bt_virnet_get_cdev_number(vnet, &number);

		if (likely(!err))
			bt_clear_bit(&drv->bitmap, (u32)MINOR(number));
		list_del(&vnet->virnet_entry);
		btdev_dbg("%s has been deleted", ndev_name(vnet));
		bt_virnet_destroy(vnet);
	}
	drv->devices_table->num = 0;
}

static int bt_table_delete_all(struct bt_drv *drv)
{
	if (unlikely(!drv->devices_table))
		return -EINVAL;

	mutex_lock(&drv->bitmap_lock);
	mutex_lock(&drv->devices_table->tbl_lock);

	__bt_table_delete_all(drv);

	mutex_unlock(&drv->devices_table->tbl_lock);
	mutex_unlock(&drv->bitmap_lock);
	return OK;
}

static void bt_table_destroy(struct bt_drv *drv)
{
	__bt_table_delete_all(drv);
	kfree(drv->devices_table);
	drv->devices_table = NULL;
}

static struct bt_ring *__bt_ring_create(int size)
{
	struct bt_ring *ring;

	if (unlikely(size < 0))
		return NULL;

	ring = kmalloc(sizeof(*ring), GFP_KERNEL);
	if (unlikely(!ring)) {
		btdev_dbg_err("ring alloc failed");
		return NULL;
	}

	ring->head = 0;
	ring->tail = 0;
	ring->data = kmalloc_array(size, sizeof(void *), GFP_KERNEL);
	if (unlikely(!ring->data)) {
		btdev_dbg_err("ring data allocfailed");
		kfree(ring);
		return NULL;
	}
	ring->size = size;

	return ring;
}

static struct bt_ring *bt_ring_create(void)
{
	return __bt_ring_create(BT_RING_BUFFER_SIZE);
}

static int bt_ring_is_empty(const struct bt_ring *ring)
{
	if (unlikely(!ring))
		return TRUE;

	return ring->head == ring->tail;
}

static int bt_ring_is_full(const struct bt_ring *ring)
{
	if (unlikely(!ring))
		return TRUE;

	return (ring->head + 1) % ring->size == ring->tail;
}

static void bt_ring_produce(struct bt_ring *ring, void *data)
{
	smp_mb(); // Make sure the read and write order is correct
	if (likely(ring->head < ring->size)) {
		ring->data[ring->head] = data;
		ring->head = (ring->head + 1) % ring->size;
	}
	smp_wmb(); // Make sure the write order is correct
}

static void *bt_ring_current(struct bt_ring *ring)
{
	void *data = NULL;

	if (unlikely(!ring) || unlikely(ring->tail > ring->size))
		return data;

	data = ring->data[ring->tail];
	return data;
}

static void bt_ring_consume(struct bt_ring *ring)
{
	if (unlikely(!ring))
		return;

	smp_rmb(); // Make sure the read order is correct
	ring->tail = (ring->tail + 1) % ring->size;
	smp_mb(); // Make sure the read and write order is correct
}

static void bt_ring_destroy(struct bt_ring *ring)
{
	if (unlikely(!ring))
		return;

	kfree(ring->data);
	kfree(ring);
}

static int bt_virnet_produce_data(struct bt_virnet *dev, void *data)
{
	if (unlikely(bt_ring_is_full(dev->tx_ring))) {
		btdev_dbg("ring is full");
		return -ENFILE;
	}

	/* There is a memory barrier inside the function */
	bt_ring_produce(dev->tx_ring, data);
	wake_up(&dev->rx_queue);
	return OK;
}

/**
 * register all the region
 */
static int bt_cdev_region_init(int major, int count)
{
	return register_chrdev_region(MKDEV(major, 0), count, "bt");
}

static struct class *bt_dev_class_create(void)
{
	struct class *cls = class_create(THIS_MODULE, "bt");

	if (IS_ERR(cls)) {
		btdev_dbg_err("create struct class failed");
		return NULL;
	}
	return cls;
}

static void bt_dev_class_destroy(struct class *cls)
{
	if (unlikely(!cls))
		return;

	class_destroy(cls);
}

static void bt_cdev_device_destroy(struct bt_cdev *dev)
{
	device_destroy(dev->bt_class, dev->cdev->dev);
}

static int bt_cdev_device_create(struct bt_cdev *dev,
				 struct class *cls,
				 u32 id)
{
	struct device *device = NULL;
	dev_t devno = MKDEV(BT_DEV_MAJOR, id);
	int ret;

	if (unlikely(!cls)) {
		btdev_dbg_err("not a valid class");
		return -EINVAL;
	}

	dev->bt_class = cls;
	device = device_create(cls, NULL, devno, NULL, "%s%u", BT_DEV_NAME_PREFIX, id);
	if (IS_ERR(device)) {
		btdev_dbg_err("create device failed, id=%d", id);
		return -EIO;
	}
	ret = snprintf(dev->dev_filename, sizeof(dev->dev_filename),
		       "%s%u", BT_DEV_PATH_PREFIX, id);
	if (ret < 0) {
		btdev_dbg_err("snprintf failed, id=%d", id);
		bt_cdev_device_destroy(dev);
		return -EFAULT;
	}

	btdev_dbg("%s has been created", dev->dev_filename);
	return OK;
}

static struct bt_cdev *bt_cdev_create(const struct file_operations *ops,
				      u32 id)
{
	int ret;
	int minor = id;
	struct bt_cdev *dev = NULL;
	struct cdev *chrdev = NULL;

	dev = kmalloc(sizeof(*dev), GFP_KERNEL);
	if (unlikely(!dev)) {
		btdev_dbg_err("dev alloc failed, id=%d", id);
		goto dev_alloc_failed;
	}

	chrdev = cdev_alloc();
	if (unlikely(!chrdev)) {
		btdev_dbg_err("cdev alloc failed, id=%d", id);
		goto cdev_alloc_failed;
	}

	cdev_init(chrdev, ops);
	dev->cdev = chrdev;

	ret = cdev_add(chrdev, MKDEV(BT_DEV_MAJOR, minor), 1);
	if (unlikely(ret < 0)) {
		btdev_dbg_err("cdev add failed, id=%d", id);
		goto cdev_add_failed;
	}

	if (unlikely(bt_cdev_device_create(dev, g_bt_drv->bt_class, minor) < 0)) {
		btdev_dbg_err("bt cdev device create failed, id=%d", id);
		goto cdev_device_create_failed;
	}

	return dev;

cdev_device_create_failed:
cdev_add_failed:
	cdev_del(chrdev);

cdev_alloc_failed:
	kfree(dev);

dev_alloc_failed:
	return NULL;
}

/**
 * delete one char device
 */
static void bt_cdev_delete(struct bt_cdev *bt_cdev)
{
	dev_t devno;

	if (likely(bt_cdev)) {
		devno = bt_cdev->cdev->dev;

		/* BT_DEV_PATH_PREFIX + ID --> /dev/btdev1 */
		unregister_chrdev(MAJOR(devno), bt_cdev->dev_filename + strlen(BT_DEV_PATH_PREFIX));
		bt_cdev_device_destroy(bt_cdev);

		cdev_del(bt_cdev->cdev);
	} else {
		btdev_dbg_err("cdev is null");
	}
}

/**
 * create and add data char device
 */
static struct bt_io_file *bt_create_io_file(u32 id)
{
	struct bt_io_file *file = kmalloc(sizeof(*file), GFP_KERNEL);

	if (unlikely(!file)) {
		btdev_dbg_err("file alloc failed, id=%d", id);
		return NULL;
	}
	file->bt_cdev = bt_cdev_create(&g_bt_io_file_ops, id);
	if (unlikely(!file->bt_cdev)) {
		btdev_dbg_err("create cdev failed, id=%d", id);
		kfree(file);
		return NULL;
	}
	atomic_set(&file->read_open_limit, 1);
	atomic_set(&file->write_open_limit, 1);
	return file;
}

static struct bt_io_file **bt_create_io_files(void)
{
	int i;
	struct bt_io_file **all_files = kmalloc(BT_VIRNET_MAX_NUM * sizeof(struct bt_io_file *),
						GFP_KERNEL);

	if (unlikely(!all_files)) {
		btdev_dbg_err("all_files alloc failed");
		return NULL;
	}
	for (i = 0; i < BT_VIRNET_MAX_NUM; ++i)
		all_files[i] = bt_create_io_file(i + 1);

	return all_files;
}

static void bt_delete_io_file(struct bt_io_file *file)
{
	if (unlikely(!file))
		return;

	bt_cdev_delete(file->bt_cdev);
	kfree(file);
}

static void bt_delete_io_files(struct bt_drv *bt_mng)
{
	int i;

	for (i = 0; i < BT_VIRNET_MAX_NUM; ++i)
		bt_delete_io_file(bt_mng->io_files[i]);

	kfree(bt_mng->io_files);
	bt_mng->io_files = NULL;
}

/**
 * create and add management char device
 */
static struct bt_mng_file *bt_create_mng_file(int id)
{
	struct bt_mng_file *file = kmalloc(sizeof(*file), GFP_KERNEL);

	if (unlikely(!file)) {
		btdev_dbg_err("file alloc failed");
		return NULL;
	}

	file->bt_cdev = bt_cdev_create(&g_bt_mng_file_ops, id);
	if (unlikely(!file->bt_cdev)) {
		btdev_dbg_err("create cdev failed");
		kfree(file);
		return NULL;
	}

	atomic_set(&file->open_limit, 1);

	btdev_dbg("mng file has been created");
	return file;
}

static void bt_delete_mng_file(struct bt_mng_file *file)
{
	if (unlikely(!file))
		return;

	bt_cdev_delete(file->bt_cdev);
	kfree(file);
}

/**
 * unregister the region
 */
static void bt_cdev_region_destroy(int major, int count)
{
	return unregister_chrdev_region(MKDEV(major, 0), count);
}

/**
 * create one net device
 */
static struct net_device *bt_net_device_create(u32 id)
{
	struct net_device *ndev = NULL;
	int err;
	char ifa_name[IFNAMSIZ];

	if (unlikely(id < 0) || unlikely(id > BT_VIRNET_MAX_NUM)) {
		btdev_dbg_err("invalid id");
		return NULL;
	}
	err = snprintf(ifa_name, sizeof(ifa_name), "%s%d", BT_VIRNET_NAME_PREFIX, id);
	if (err < 0) {
		btdev_dbg_err("snprintf failed, id=%d", id);
		return NULL;
	}
	ndev = alloc_netdev(0, ifa_name, NET_NAME_UNKNOWN, ether_setup);
	if (unlikely(!ndev)) {
		btdev_dbg_err("%s ndev alloc failed", ifa_name);
		return NULL;
	}

	ndev->netdev_ops = &g_bt_virnet_ops;
	ndev->flags |= IFF_NOARP;
	ndev->flags &= ~IFF_BROADCAST & ~IFF_MULTICAST;
	ndev->min_mtu = 1;
	ndev->max_mtu = ETH_MAX_MTU;

	err = register_netdev(ndev);
	if (unlikely(err)) {
		btdev_dbg_err("%s register netdev failed", ifa_name);
		free_netdev(ndev);
		return NULL;
	}

	btdev_dbg("%s has been created", ifa_name);
	return ndev;
}

/**
 * destroy one net device
 */
static void bt_net_device_destroy(struct net_device *dev)
{
	btdev_dbg("%s has been destroyed", dev->name);
	unregister_netdev(dev);
	free_netdev(dev);
}

static struct bt_io_file *bt_get_io_file(struct bt_drv *drv, int id)
{
	if (id >= 1 && id <= BT_VIRNET_MAX_NUM)
		return drv->io_files[id - 1];

	return NULL;
}

/**
 * create an virtual net_device
 */
static struct bt_virnet *bt_virnet_create(struct bt_drv *bt_mng, u32 id)
{
	struct bt_virnet *vnet = kmalloc(sizeof(*vnet), GFP_KERNEL);

	if (unlikely(!vnet)) {
		btdev_dbg_err("invalid parameter");
		goto out_of_memory;
	}

	vnet->tx_ring = bt_ring_create();
	if (unlikely(!vnet->tx_ring)) {
		btdev_dbg_err("create ring failed");
		goto bt_ring_create_failed;
	}

	vnet->ndev = bt_net_device_create(id);
	if (unlikely(!vnet->ndev)) {
		btdev_dbg_err("create net device failed");
		goto net_device_create_failed;
	}

	vnet->io_file = bt_get_io_file(bt_mng, id);
	if (unlikely(!vnet->io_file)) {
		btdev_dbg_err("create cdev failed");
		goto get_io_file_failed;
	}

	init_waitqueue_head(&vnet->rx_queue);

	set_state(vnet, BT_VIRNET_STATE_CREATED);
	btdev_dbg("%s has been created", cdev_name(vnet));
	return vnet;

get_io_file_failed:
	bt_net_device_destroy(vnet->ndev);

net_device_create_failed:
	bt_ring_destroy(vnet->tx_ring);

bt_ring_create_failed:
	kfree(vnet);

out_of_memory:
	return NULL;
}

static void bt_virnet_destroy(struct bt_virnet *vnet)
{
	btdev_dbg("%s has been destroyed", ndev_name(vnet));
	bt_ring_destroy(vnet->tx_ring);
	bt_net_device_destroy(vnet->ndev);

	set_state(vnet, BT_VIRNET_STATE_DELETED);

	kfree(vnet);
}

static void __exit bt_module_release(void)
{
	if (likely(g_bt_drv)) {
		bt_table_destroy(g_bt_drv);
		bt_delete_io_files(g_bt_drv);
		bt_delete_mng_file(g_bt_drv->mng_file);
		bt_dev_class_destroy(g_bt_drv->bt_class);

		kfree(g_bt_drv);
		g_bt_drv = NULL;
	}

	bt_cdev_region_destroy(BT_DEV_MAJOR, BT_VIRNET_MAX_NUM);
	remove_proc_entry("bt_info_proc", NULL);
	btdev_dbg("success");
}

static int __bt_module_base_init(void)
{
	int ret = 0;

	g_bt_drv = kmalloc(sizeof(*g_bt_drv), GFP_KERNEL);
	if (unlikely(!g_bt_drv)) {
		btdev_dbg_err("bt_drv alloc failed");
		ret = -ENOMEM;
		goto btdrv_alloc_failed;
	}

	if (unlikely(bt_cdev_region_init(BT_DEV_MAJOR, BT_VIRNET_MAX_NUM) < 0)) {
		btdev_dbg_err("bt cdev region init failed");
		ret = -EFAULT;
		goto cdev_region_fail;
	}

	g_bt_drv->devices_table = bt_table_init();
	if (unlikely(!g_bt_drv->devices_table)) {
		btdev_dbg_err("bt table init failed");
		ret = -ENOMEM;
		goto table_init_fail;
	}

	g_bt_drv->bt_class = bt_dev_class_create();
	if (unlikely(!g_bt_drv->bt_class)) {
		btdev_dbg_err("class create failed");
		ret = -ENOMEM;
		goto class_create_fail;
	}

	g_bt_drv->io_files = bt_create_io_files();
	if (unlikely(!g_bt_drv->io_files)) {
		btdev_dbg_err("bt create io files failed");
		ret = -ENOMEM;
		goto io_files_create_fail;
	}

	mutex_init(&g_bt_drv->bitmap_lock);
	g_bt_drv->bitmap = 0;
	return ret;

io_files_create_fail:
	bt_dev_class_destroy(g_bt_drv->bt_class);

class_create_fail:
	bt_table_destroy(g_bt_drv);

table_init_fail:
	bt_cdev_region_destroy(BT_DEV_MAJOR, BT_VIRNET_MAX_NUM);

cdev_region_fail:
	kfree(g_bt_drv);
	g_bt_drv = NULL;

btdrv_alloc_failed:
	return ret;
}

static int __bt_module_dev_create(void)
{
	int mid = 0;
	struct proc_dir_entry *entry = NULL;
	int ret = 0;

	mutex_lock(&g_bt_drv->bitmap_lock);
	g_bt_drv->mng_file = bt_create_mng_file(mid);
	if (unlikely(!g_bt_drv->mng_file)) {
		btdev_dbg_err("bt create mng file failed");
		ret = -ENOMEM;
		mutex_unlock(&g_bt_drv->bitmap_lock);
		goto mng_file_create_fail;
	}
	bt_set_bit(&g_bt_drv->bitmap, mid);
	mutex_unlock(&g_bt_drv->bitmap_lock);

	entry = proc_create_data("bt_info_proc", 0, NULL, &g_bt_proc_fops, NULL);
	if (unlikely(!entry)) {
		btdev_dbg_err("create proc data failed");
		ret = -ENOMEM;
		goto proc_create_fail;
	}

	return ret;
proc_create_fail:
	bt_delete_mng_file(g_bt_drv->mng_file);

mng_file_create_fail:
	bt_delete_io_files(g_bt_drv);
	bt_dev_class_destroy(g_bt_drv->bt_class);
	bt_table_destroy(g_bt_drv);
	bt_cdev_region_destroy(BT_DEV_MAJOR, BT_VIRNET_MAX_NUM);
	kfree(g_bt_drv);
	g_bt_drv = NULL;

	return ret;
}

/**
 *  module init function
 */
static int __init bt_module_init(void)
{
	int ret;

	ret = __bt_module_base_init();
	if (ret < 0)
		return ret;

	return __bt_module_dev_create();
}

module_init(bt_module_init);
module_exit(bt_module_release);
MODULE_LICENSE("GPL");