#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include "ringbuf.h"

int ringbuf_init(struct ringbuf *desc, unsigned long buf_size)
{
	memset(desc, 0, sizeof(*desc));
	if (!(desc->buf = vmalloc(buf_size * sizeof(*desc->buf)))) {
		return -ENOMEM;
	}
	desc->buf_size = buf_size;

	return 0;
}

void ringbuf_reset(struct ringbuf *desc)
{
	unsigned long save_flags;

	local_irq_save(save_flags);
	desc->head_pos = desc->tail_pos = 0;
	local_irq_restore(save_flags);
}
	
void ringbuf_destroy(struct ringbuf *desc)
{
	if (desc->buf)
		vfree(desc->buf);
	memset(desc, 0, sizeof(*desc));
}

int ringbuf_read_user (struct ringbuf * desc, struct ringbuf_entry * entries, 
			unsigned long count)
{
	unsigned long burst, save_flags;
	unsigned long ncpy = 0;

	do {
		local_irq_save(save_flags);
		if (desc->head_pos == desc->tail_pos || count == ncpy) {
			local_irq_restore(save_flags);
			return ncpy;
		}

		burst = desc->buf_size - desc->tail_pos;
		burst = min(burst, count - ncpy);
		if (desc->tail_pos < desc->head_pos)
			burst = min(burst, desc->head_pos - desc->tail_pos);

		copy_to_user(entries + ncpy, desc->buf + desc->tail_pos,
				burst*sizeof(*entries));

		desc->tail_pos += burst;
		if (desc->tail_pos == desc->buf_size)
			desc->tail_pos = 0;
		ncpy += burst;
	} while (1);
}

static unsigned long ringbuf_available_slots(struct ringbuf * desc)
{
	unsigned long head = desc->head_pos;
	unsigned long tail = desc->tail_pos;

	if (head < tail)
		return tail - head - 1;

	return tail + desc->buf_size - head - 1;
}

int ringbuf_write (struct ringbuf * desc, struct ringbuf_entry * entries,
			unsigned long count)
{
	unsigned long avail, burst, save_flags;
	unsigned long ncpy = 0;

	local_irq_save(save_flags);
	if ((avail = ringbuf_available_slots(desc)) < count) {
		desc->lost_overflow += count;
		local_irq_restore(save_flags);

		return -ENOSPC;
	}
	do {
		if (count == ncpy) {
			local_irq_restore(save_flags);
			return ncpy;
		}

		burst = desc->buf_size - desc->head_pos;
		burst = min(burst, count - ncpy);
		if (desc->head_pos < desc->tail_pos)
			burst = min(burst, desc->tail_pos - desc->head_pos - 1);

		memcpy (desc->buf + desc->head_pos, entries + ncpy,
				burst*sizeof(*entries));

		desc->head_pos += burst;
		if (desc->head_pos == desc->buf_size)
			desc->head_pos = 0;
		ncpy += burst;
	} while (1);
}

