#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/sched.h>

#include "can.h"
#include "sja1000.h"
#include "can_ioctl.h"
#include "can_irq.h"
#include "can_util.h"
#include "compat_access_ok.h"


#define WRAPPER(f)															\
			/* Захватываем спин и запрещаем локальные прерывания */			\
			spin_lock_irqsave(&can[dev].irq_lock, flags);					\
			if (ResetMode == test_reset_request(port))						\
			{																\
				/* Выполняем функцию */										\
				f;															\
																			\
				/* Освобождаем спин и разрешаем локальные прерывания */		\
				spin_unlock_irqrestore(&can[dev].irq_lock, flags);			\
			}																\
			else															\
			{																\
				/* Освобождаем спин и разрешаем локальные прерывания */		\
				spin_unlock_irqrestore(&can[dev].irq_lock, flags);			\
																			\
				/* Останавливаем выдачу */									\
				stop_trans(&can[dev], channel);								\
																			\
				/* Захватываем спин и запрещаем локальные прерывания */		\
				spin_lock_irqsave(&can[dev].irq_lock, flags);				\
																			\
				/* Сбрасываем канал */										\
				reset_request(port, ResetMode);								\
																			\
				/* Выполняем функцию */										\
				f;															\
																			\
				/* Переводим канал в рабочий режим */						\
				reset_request(port, NormalMode);							\
																			\
				can[dev].bus_mode[channel] = BUS_ON;						\
																			\
				/* Освобождаем спин и разрешаем локальные прерывания */		\
				spin_unlock_irqrestore(&can[dev].irq_lock, flags);			\
																			\
				start_trans(&can[dev], channel);							\
			}

//-----------------------------------------------------------------------------
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)
long can_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
#else
int can_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) */
{
	/* Реализация функции ioctl */
	int                      dev;
	int                      channel;
	int                      err = 0;
	unsigned char            mode;
	unsigned char            status;
	unsigned char            counter;
    unsigned int             speed;
	struct b_filter_t        b_filter;
	struct p_filter_t        p_filter;
	int                      ret = 0;
	int                      port;
	struct CAN_info_t        info;
	int                      over_counter;
	struct can_rx_fifo_t    *rx_fifo;
	unsigned long            flags;
	union CAN_interrupt_t    interrupt;
	struct private_data_t   *private_data;
	struct transmit_status_t sts;
	int                      command;
	struct can_tx_fifo_t    *tx_fifo;
	int                      version;
	int                      num_channel;

	private_data = (struct private_data_t *)(filp->private_data);
	dev = private_data->num_dev;
	channel = private_data->num_channel;
	port = can[dev].wPorts[channel];
	rx_fifo = private_data->rx_fifo;

	if (_IOC_TYPE(cmd) != CAN200_IOC_MAGIC)
	{
		return -ENOTTY;
	}

	if (_IOC_DIR(cmd) & _IOC_READ)
	{
		err = !access_ok((void *)arg, _IOC_SIZE(cmd));
	}
	else
	{
		if (_IOC_DIR(cmd) & _IOC_WRITE)
		{
			err = !access_ok((void *)arg, _IOC_SIZE(cmd));
		}
	}
	if (err)
	{
		return -EFAULT;
	}

	mutex_lock(&can[dev].ioctl_mutex[channel]);

	switch (cmd)
	{
	case CAN200_HARDRESET:
		{
			if (CAN200PC == can[dev].type)
			{
				/* Останавливаем выдачу */
				stop_trans(&can[dev], channel);

				/* Захватываем спин и запрещаем локальные прерывания */
				spin_lock_irqsave(&can[dev].irq_lock, flags);
				
				/* Переводим канал в состояние сброса */
				reset_request(port, ResetMode);

				/* Сброс */
				hard_reset(port);
				
				/* Освобождаем клиентов заблокированных на приеме */
				wake_up_interruptible(&can[dev].rx_queue[channel]);
				
				/* Освобождаем клиентов заблокированных на выдаче */
				/* Захватываем спин и запрещаем локальные прерывания */
				spin_lock_irqsave(&can[dev].tx_fifo_lock[channel], flags);
				write_replay_error_all(&can[dev], channel, CAN_TRANSMIT_ERROR);
				/* Освобождаем спин и разрешаем локальные прерывания */
				spin_unlock_irqrestore(&can[dev].tx_fifo_lock[channel], flags);
				
				/* Настраиваем перрывания */
				interrupt.byte = 0;
				interrupt.bit.ri = 1;
				interrupt.bit.ti = 1;
				interrupt.bit.doi = 1;
				interrupt.bit.ei = 1;

				p_filter.mode = 1;
				p_filter.mask[0] = 0xff;
				p_filter.mask[1] = 0xff;
				p_filter.mask[2] = 0xff;
				p_filter.mask[3] = 0xff;
				p_filter.filter[0] = 0xff;
				p_filter.filter[1] = 0xff;
				p_filter.filter[2] = 0xff;
				p_filter.filter[3] = 0xff;

				set_work_mode(can[dev].wPorts[channel], PeliCAN);
				set_CAN_speed(can[dev].wPorts[channel], CAN_SPEED_1000);
				/* Фильтр */
				p_set_filter(can[dev].wPorts[channel], &p_filter);
				/* Разрешаем прием/выдачу */
				set_driver_mode(can[dev].wPorts[channel]);
				set_interrupt(can[dev].wPorts[channel], interrupt.byte); /* Разрешаем прерывания */
				/* Читаем прерывания для сброса */
				get_interrupt(can[dev].wPorts[channel]);
				/* Канал остается в состоянии сброса */

				/* Освобождаем спин и разрешаем локальные прерывания */
				spin_unlock_irqrestore(&can[dev].irq_lock, flags);
			}
			break; 
		}
	case CAN200_GETWORKMODE:
		{
			mode = get_work_mode(port);
			if (0 != put_user(mode, (unsigned char *)arg))
			{
				ret = -EINVAL;
			}
			break;
		}
	case CAN200_SETWORKMODE:
		{
			if (0 != get_user(mode, (unsigned char *)arg))
			{
				ret = -EINVAL;
				break;
			}

			/* Устанавливаем требуемый режим работы */
            WRAPPER(set_work_mode(port, mode))

			break;
		}
	case CAN200_GETCONFIG:
		{
			can_get_info(&info, dev, channel);
			if (0 != copy_to_user((struct can_info_t *)arg, &info, sizeof(struct CAN_info_t)))
			{
				ret = -EINVAL;
			}
			break;
		}
	case CAN200_GETNUMCHANNEL:
		{
			num_channel = can200_num_ports(&can[dev]);

			if (0 != put_user(num_channel, (int *)arg))
			{
				ret = -EINVAL;
			}

			break;
		}
	case CAN200_SETDRIVERMODE:
		{
			/* Переводим канал в рабочий режим */
			WRAPPER(reset_request(port, NormalMode))

			break;
		}
	case CAN200_SETCANSPEED:
		{
            if (0 != get_user(speed, (unsigned int *)arg))
			{
				ret = -EINVAL;
				break;
			}

			/* Устанавливаем скорость */
			WRAPPER(set_CAN_speed(port, speed))

			break;
		}
	case CAN200_GETCANSPEED:
		{
			/* Получаем текущую скорость обмена */
			WRAPPER(speed = get_CAN_speed(port))

            if (0 != put_user(speed, (unsigned int *)arg))
			{
				ret = -EINVAL;
			}
            break;
		}
	case CAN200_GETSTATUS:
		{
			/* Захватываем спин и запрещаем локальные прерывания */
			spin_lock_irqsave(&can[dev].irq_lock, flags);

			status = get_status(port);

			/* Освобождаем спин и разрешаем локальные прерывания */
			spin_unlock_irqrestore(&can[dev].irq_lock, flags);

			if (0 != put_user(status, (unsigned char *)arg))
			{
				ret = -EINVAL;
			}

			break;
		}
	case CAN200_B_SETFILTER:
		{
			if (0 != copy_from_user(&b_filter, (struct b_filter_t *)arg, sizeof(struct b_filter_t)))
			{
				ret = -EINVAL;
				break;
			}

			WRAPPER(ret = b_set_filter(port, &b_filter))
			if (0 != ret)
			{
				ret = -EPERM;
			}

			break;
		}
	case CAN200_B_GETFILTER:
		{
			/* Получаем фильтр */
			WRAPPER(ret = b_get_filter(port, &b_filter))
			if (0 != ret)
			{
				ret = -EPERM;
			}
			else
			{
				if (0 != copy_to_user((struct b_filter_t *)arg, &b_filter, sizeof(struct b_filter_t)))
				{
					ret = -EINVAL;
				}
			}
			break;
		}
	case CAN200_P_SETFILTER:
		{
			if (0 != copy_from_user(&p_filter, (struct p_filter_t *)arg, sizeof(struct p_filter_t)))
			{
				ret = -EINVAL;
				break;
			}
			
			/* Устанавливаем фильтр */
			WRAPPER(ret = p_set_filter(port, &p_filter))
			if (0 != ret)
			{
				ret = -EPERM;
			}
				
			break;
		}
	case CAN200_P_GETFILTER:
		{
			/* Получаем фильтр */
			WRAPPER(ret = p_get_filter(port, &p_filter))
			if (0 != ret)
			{
				ret = -EPERM;
			}
			else
			{
				if (0 != copy_to_user((struct p_filter_t *)arg, &p_filter, sizeof(struct p_filter_t)))
				{
					ret = -EINVAL;
				}
			}

			break;
		}
	case CAN200_P_GETRXERRORCOUNTER:
		{
			/* Захватываем спин и запрещаем локальные прерывания */
			spin_lock_irqsave(&can[dev].irq_lock, flags);

			ret = get_rx_error_counter(port, &counter);

			/* Освобождаем спин и разрешаем локальные прерывания */
			spin_unlock_irqrestore(&can[dev].irq_lock, flags);

			if (0 != ret)
			{
				ret = -EPERM;
			}
			else
			{
				if (0 != put_user(counter, (unsigned char *)arg))
				{
					ret = -EINVAL;
				}
			}
			break;
		}
	case CAN200_P_SETRXERRORCOUNTER:
		{
			if (0 != get_user(counter, (unsigned char *)arg))
			{
				ret = -EINVAL;
				break;
			}
			
			/* Устанавливаем счетчик ошибок приема */
			WRAPPER(ret = set_rx_error_counter(port, counter))
			if (0 != ret)
			{
				ret = -EPERM;
			}
				
			break;
		}
	case CAN200_P_GETTXERRORCOUNTER:
		{
			/* Захватываем спин и запрещаем локальные прерывания */
			spin_lock_irqsave(&can[dev].irq_lock, flags);

			ret = get_tx_error_counter(port, &counter);

			/* Освобождаем спин и разрешаем локальные прерывания */
			spin_unlock_irqrestore(&can[dev].irq_lock, flags);

			if (0 != ret)
			{
				ret = EPERM;
			}
			else
			{
				if (0 != put_user(counter, (unsigned char *)arg))
				{
					ret = -EINVAL;
				}
			}
			break;
		}
	case CAN200_P_SETTXERRORCOUNTER:
		{
			if (0 != get_user(counter, (unsigned char *)arg))
			{
				ret = -EINVAL;
				break;
			}

			/* Устанавливаем счетчик ошибок выдачи */
			WRAPPER(ret = set_tx_error_counter(port, counter))
			if (0 != ret)
			{
				ret = EPERM;
			}
				
			break;
		}
	case CAN200_P_GETERRORWARNINGLIMIT:
		{
			/* Захватываем спин и запрещаем локальные прерывания */
			spin_lock_irqsave(&can[dev].irq_lock, flags);

			ret = get_error_warning_limit(port, &counter);

			/* Освобождаем спин и разрешаем локальные прерывания */
			spin_unlock_irqrestore(&can[dev].irq_lock, flags);

			if (0 != ret)
			{
				ret = -EPERM;
			}
			else
			{
				if (0 != put_user(counter, (unsigned char *)arg))
				{
					ret = -EINVAL;
				}
			}
			break;
		}
	case CAN200_P_SETERRORWARNINGLIMIT:
		{
			if (0 != get_user(counter, (unsigned char *)arg))
			{
				ret = -EINVAL;
				break;
			}

			/* Устанавливаем счетчик предела ошибок */
			WRAPPER(ret = set_error_warning_limit(port, counter))
			if (0 != ret)
			{
				ret = -EPERM;
			}
				
			break;
		}
	case CAN200_GETOVERCOUNTER:
		{
			over_counter = rx_fifo->overrun;
			if (0 != put_user(over_counter, (int *)arg))
			{
				ret = -EINVAL;
			}
			break;
		}
	case CAN200_SETBUSON:
		{
			if (BUS_OFF == can[dev].bus_mode[channel])
			{
				/* Останавливаем выдачу */
				stop_trans(&can[dev], channel);

				/* Захватываем спин и запрещаем локальные прерывания */
				spin_lock_irqsave(&can[dev].irq_lock, flags);
				
				if (NormalMode == test_reset_request(port))
				{
					/* Сбрасываем канал */
					reset_request(port, ResetMode);
				
					/* Переводим канал в рабочий режим */
					reset_request(port, NormalMode);
				}

				can[dev].bus_mode[channel] = BUS_ON;
				
				/* Освобождаем спин и разрешаем локальные прерывания */
				spin_unlock_irqrestore(&can[dev].irq_lock, flags);

				start_trans(&can[dev], channel);
			}
			break;
		}
	case CAN200_GETTRANSMITSTATUS:
		{
			sts.status = private_data->status;
			sts.len = private_data->len;
			if (0 != copy_to_user((struct transmit_status_t *)arg, &sts, sizeof(struct transmit_status_t)))
			{
				ret = -EINVAL;
			}
			break;
		}
	case CAN200_P_GETEARBITRATIONLOSTCAPTURE:
		{
			/* Захватываем спин и запрещаем локальные прерывания */
			spin_lock_irqsave(&can[dev].irq_lock, flags);

			ret = get_lost_capture(port, &counter);

			/* Освобождаем спин и разрешаем локальные прерывания */
			spin_unlock_irqrestore(&can[dev].irq_lock, flags);

			if (0 != ret)
			{
				ret = -EPERM;
			}
			else
			{
				if (0 != put_user(counter, (unsigned char *)arg))
				{
					ret = -EINVAL;
				}
			}
			break;
		}
	case CAN200_P_GETERRORCODE:
		{
			/* Захватываем спин и запрещаем локальные прерывания */
			spin_lock_irqsave(&can[dev].irq_lock, flags);

			ret = get_error_code(port, &counter);

			/* Освобождаем спин и разрешаем локальные прерывания */
			spin_unlock_irqrestore(&can[dev].irq_lock, flags);

			if (0 != ret)
			{
				ret = -EPERM;
			}
			else
			{
				if (0 != put_user(counter, (unsigned char *)arg))
				{
					ret = -EINVAL;
				}
			}
			break;
		}
	case CAN200_DROPTRANSMIT:
		{
			struct can_tx_fifo_t *tx_fifo_tmp;
			if (0 != get_user(command, (int *)arg))
			{
				ret = -EINVAL;
				break;
			}

			/* Останавливаем выдачу */
			stop_trans(&can[dev], channel);

			/* Захватываем спин и запрещаем локальные прерывания */
			spin_lock_irqsave(&can[dev].irq_lock, flags);

			if (CAN_DROP_ALL == command)
			{
				/* Отброс всех пакетов */
				/* Освобождаем клиентов заблокированнных на выдаче */
				write_replay_error_all(&can[dev], channel, CAN_TRANSMIT_DROP);
			}
			else
			{
				/* Удаляем клиента из очереди выдачи */
				tx_fifo = can->tx_fifo[channel];
				while (NULL != tx_fifo)
				{
					if (tx_fifo == private_data->tx_fifo)
					{
						tx_fifo_tmp = tx_fifo;
						/* Удаляем данную запись из очереди */
						tx_fifo = remove_tx_client_from_list(&can[dev], channel, tx_fifo);
						/* Отвечаем клиенту */
						write_replay_status_and_free_mem(tx_fifo_tmp, CAN_TRANSMIT_DROP);
					}
					else
					{
						tx_fifo = tx_fifo->next;
					}
				}
			}

			/* Освобождаем спин и разрешаем локальные прерывания */
			spin_unlock_irqrestore(&can[dev].irq_lock, flags);

			start_trans(&can[dev], channel);

			break;
		}
	case CAN200_GETDRIVERVERSION:
		{
			version = (VERSION << 16) + (SUBVERSION << 8) + SUBSUBVERSION;

			if (0 != put_user(version, (int *)arg))
			{
				ret = -EINVAL;
			}

			break;
		}
	default:
		{
			ret = -ENOTTY;
			break;
		}
	}

	mutex_unlock(&can[dev].ioctl_mutex[channel]);

	return ret;
}
