#include <linux/interrupt.h>
#include <linux/sched.h>

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

//------------------------------------------------------------------------------
static void add_packet_to_fifo(struct can_t *can, int channel, CAN_data *packet);
/* Записываем принятый пакет в очереди данного канала */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
static inline void do_gettimeofday(struct __kernel_old_timeval* tv)
{
    struct timespec64 ts;
    ktime_get_real_ts64(&ts);
    tv->tv_sec = ts.tv_sec;
    tv->tv_usec = ts.tv_nsec / 1000;
}
#endif

//-----------------------------------------------------------------------------
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 18)
irqreturn_t can_irq(int irq, void *dev_id)
#else
irqreturn_t can_irq(int irq, void *dev_id, struct pt_regs *notused)
#endif
{
	/* Реализация функции обслуживания прерывания */
	int                   channel, port;
	unsigned char         mode;
	struct can_t         *can;
	struct can_rx_fifo_t *rx_fifo;
	struct can_tx_fifo_t *tx_fifo;
	CAN_data              packet;
	irqreturn_t           ret = IRQ_NONE;
	union CAN_interrupt_t interrupt;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)
	struct __kernel_old_timeval timestamp;
#else
	struct timeval timestamp;
#endif
	unsigned long         flags;
	union CAN_status_t    status;
	unsigned char         res_intr;

	can = (struct can_t *)dev_id;

	/* Захватываем спин и запрещаем локальные прерывания */
	spin_lock_irqsave(&can->irq_lock, flags);
	while (1)
	{
		res_intr = 0;
		for (channel = 0; channel < can200_num_ports(can); channel++)
		{
			port = can->wPorts[channel];

			interrupt.byte = get_interrupt(port);
			mode = get_work_mode(can->wPorts[channel]);
			if (BasicCAN == mode)
			{
				interrupt.byte = interrupt.byte & 0x1f;
			}
			res_intr = res_intr | interrupt.byte;

			if (0 != interrupt.byte)
			{
				/* Прерывание этого канала */
				if (0 != interrupt.bit.ei)
				{
					/* Ошибка */
					/* Время */
					do_gettimeofday(&timestamp);

					status.byte = get_status(port);

					packet.type = ErrorCAN;
					packet.timestamp = timestamp;
					packet.data[0] = mode;
					packet.data[1] = status.byte;
					get_error_code(port, &packet.data[2]);
					get_lost_capture(port, &packet.data[3]);

					add_packet_to_fifo(can, channel, &packet);

					/* Сброс контроллера? */
					if (0 != status.bit.bs)
					{
						/* Захватываем спин и запрещаем локальные прерывания */
						spin_lock_irqsave(&can->tx_fifo_lock[channel], flags);
	
						can->bus_mode[channel] = BUS_OFF;

						/* Освобождаем клиентов заблокированнных на выдаче */
						write_replay_error_all(can, channel, CAN_TRANSMIT_ERROR);

						if (1 == can->stop_trans[channel])
						{
							/* Остановка выдачи */
							can->stopping[channel] = 1;
						}

						/* Освобождаем спин и разрешаем локальные прерывания */
						spin_unlock_irqrestore(&can->tx_fifo_lock[channel], flags);
	
						/* Будим всех ждущих выдачи пакета */
						wake_up(&can->tx_queue[channel]);
					}

					/* Будим всех ждущих принятия пакета */
					wake_up_interruptible(&can->rx_queue[channel]);

					ret = IRQ_HANDLED;
				}
				if (0 != interrupt.bit.ri)
				{
					/* Принят пакет */
					/* Время */
					do_gettimeofday(&timestamp);

					/* Читаем пакет */
					read_message(port, &packet);

					/* Разрешаем прием следующего кадра */
					set_command(port, 0x04);

					packet.timestamp = timestamp;
	
					add_packet_to_fifo(can, channel, &packet);

					/* Будим всех ждущих принятия пакета */
					wake_up_interruptible(&can->rx_queue[channel]);

					ret = IRQ_HANDLED;
				}
				if (0 != interrupt.bit.ti)
				{
					status.byte = get_status(port);

					tx_fifo = NULL;

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

					if (NULL != can->tx_fifo[channel])
					{
						if (1 != status.bit.tcs)
						{
							/* Ошибка выдачи */
							can->tx_fifo[channel]->tx_counter = can->tx_fifo[channel]->tx_counter - 1;
						}
						else
						{
							can->tx_fifo[channel]->tx_counter = can->tx_counter;
							/* Уменьшаем длину оставшегося фрейма */
							can->tx_fifo[channel]->len = can->tx_fifo[channel]->len - sizeof(CAN_data);
							/* Увеличаваем смешение до следующего пакета */
							can->tx_fifo[channel]->offset = can->tx_fifo[channel]->offset + sizeof(CAN_data);
							if (0 == can->tx_fifo[channel]->len)
							{
								/* Последний пакет в данном фрейме */
								tx_fifo = can->tx_fifo[channel];
								/* Следующий буфер выдачи */
								can->tx_fifo[channel] = tx_fifo->next;
							}
						}
					}
					if (1 == can->stop_trans[channel])
					{
						/* Остановка выдачи */
						can->stopping[channel] = 1;
					}
					else
					{
						/* Выдаем следующий пакет */
						transmit_message(can, channel);
					}
					/* Будим всех ждущих выдачи пакета */
					if ((NULL == can->tx_fifo[channel]) || (1 == can->stop_trans[channel]))
					{
						wake_up(&can->tx_queue[channel]);
					}

					if (NULL != tx_fifo)
					{
						write_replay_status_and_free_mem(tx_fifo, CAN_TRANSMIT_COMPLETE);
					}

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

					ret = IRQ_HANDLED;
				}
				if (0 != interrupt.bit.doi)
				{
					/* Ошибка (переполнение) */
					/* Захватываем спин и запрещаем локальные прерывания */
					spin_lock_irqsave(&can->rx_fifo_lock[channel], flags);
					rx_fifo = can->rx_fifo[channel];
					while (NULL != rx_fifo)
					{
						rx_fifo->overrun = rx_fifo->overrun + 1;
						/* Переходим к следующей очереди */
						rx_fifo = rx_fifo->next;
					}
					/* Освобождаем спин и разрешаем локальные прерывания */
					spin_unlock_irqrestore(&can->rx_fifo_lock[channel], flags);

					set_command(port, 0x08);

					ret = IRQ_HANDLED;
				}
			}

		}
		if (0 == res_intr)
		{
			break;
		}
	}
	/* Освобождаем спин и разрешаем локальные прерывания */
	spin_unlock_irqrestore(&can->irq_lock, flags);

	return IRQ_RETVAL(ret);
}

//------------------------------------------------------------------------------
static void add_packet_to_fifo(struct can_t *can, int channel, CAN_data *packet)
{
	/* Записываем принятый пакет в очереди данного канала */
	unsigned long         flags;
	struct can_rx_fifo_t *rx_fifo;

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

	rx_fifo = can->rx_fifo[channel];
	while (NULL != rx_fifo)
	{
		if (atomic_read(&rx_fifo->len) < CAN_FIFO_SIZE)
		{
			/* Есть место для записи следующего пакета */
			memcpy(&rx_fifo->data[rx_fifo->last], packet, sizeof(CAN_data));
			rx_fifo->last = (rx_fifo->last + 1) % CAN_FIFO_SIZE;
			atomic_inc(&rx_fifo->len);
		}
		else
		{
			/* Нет места в приемном буфере */
			rx_fifo->overrun = rx_fifo->overrun + 1;
		}
		/* Переходим к следующей очереди */
		rx_fifo = rx_fifo->next;
	}

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