/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Memory allocation routines.
 *
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "driver.h"

#ifdef __QNXNTO__
#include <fcntl.h>
#include <sys/mman.h>
#endif

/*
 *  page allocation
 */
#ifdef __QNXNTO__
char *snd_malloc_pages(unsigned long size, char **xname, int dma)
{
	static int count = 0;
	char *buf = NULL,
	     *name = NULL;
	int shm_fd = 0;

	/* Note that xname is only valid when creating a shared 
	 * object. Otherwise, ignore it. Setting it will cause
	 * a SIGSEGV.
	 */

	snd_printk( "snd_malloc_pages start\n" );

	// map below 16Mb, can't create as a shared object
	// Therefore, ISA cards cannot support mmap functionality at this time.
	if (dma) {	  
		snd_printk( "isadma\n" );
	 	buf =  mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_NOCACHE, 
				MAP_ANON | MAP_PHYS | MAP_NOX64K | MAP_BELOW16M, 
				NOFD, 0);

	} else {

		name = snd_malloc( PATH_MAX );
		if (!name) {
			snd_printk("snd_malloc_pages: name malloc - %s\n", strerror(errno));
			return NULL;
		}

		sprintf( name, "/snd%.10d", count );

		snd_printk( "shm_open -- %s\n", name );
		/* Create a new memory object */
		shm_fd = shm_open(name, O_RDWR | O_CREAT, 0777);
		if (shm_fd == -1) {
			snd_printk("shm_open failed : %s\n", strerror(errno));
			return NULL;
		}

		snd_printk( "shm_ctl\n" );
		if ( shm_ctl( shm_fd, SHMCTL_ANON | SHMCTL_PHYS | SHMCTL_GLOBAL, 0, (_uint64) size ) != 0 ) {
        	        snd_printk("shm_ctl failed : %s\n", strerror(errno));
	                return NULL;
	        } 	
		snd_printk( "mmap\n" );
 		buf =  mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_NOCACHE, 
					MAP_SHARED, 
					shm_fd, 0);

		snd_printk( "shmem: name = %s, fd = %d, size = %ld, buf = 0x%X\n", name, shm_fd, size, (int) buf );
		*xname = name;
	}

#if 0
    if (buf) {
		int off = 0;
		while( off < size ) {
			snd_printdd("Phys @ 0x%x:0x%x\n",buf, virt_to_bus(buf+off));
			off += 0x1000;
		}
	}
#endif
	snd_printk( "snd_malloc_pages done\n" );
	count++;
	return (buf == MAP_FAILED ? NULL : buf);
}

void snd_free_pages(char *xname, char *ptr, unsigned long size)
{
	snd_printk( "snd_free_pages: xname == %s\n", xname );
	if (xname) {
		shm_unlink (xname);
		snd_free_str (xname);
	}
	munmap(ptr, size);
}

#else  // !__QNXNTO__

#define SEG_TABLE_SIZE 12

struct __st {
        __segment seg;
        void *ptr;
} __segment_table[SEG_TABLE_SIZE];
        

char *
snd_malloc_pages(unsigned long size, char **xname, int dma)
{
        __segment               seg;
        int                     shm_fd;
        struct _seginfo         info;
        void                    *mem;
        unsigned                flags = _PMF_NOCACHE | _PMF_DMA_SAFE;
        int			i;

        snd_printd("snd_malloc_pages: size %d, dma: %d\n", size, dma);

        if ((shm_fd = shm_open("Physical", O_RDWR, 0)) == -1) {
                perror("shm_open");
                return (NULL);
        }

        if (!dma)
                flags |= _PMF_DMA_HIGH;

        if ((int)(seg = qnx_segment_alloc_flags(size, flags)) < 0) {
                perror("qnx_segment_alloc_flags");
                return (NULL);
        }

        if ((int)qnx_segment_info(0, 0, seg, &info) < 0) {
                perror("qnx_segment_info");
                qnx_segment_free(seg);
                return (NULL);
        }

        if ((mem = mmap(0, size, PROT_READ | PROT_WRITE | PROT_NOCACHE,
                        MAP_SHARED, shm_fd, info.addr)) == (void *)(-1)) {
                qnx_segment_free(seg);
                return (NULL);
        }
        close(shm_fd);

        for (i = 0; i < SEG_TABLE_SIZE; i++) {
                if (__segment_table[i].ptr == 0)
                        break;
        }
        if (i == SEG_TABLE_SIZE) 
                fprintf(stderr, "Warning, segment table is full\n");
        else {
                __segment_table[i].seg = seg;
                __segment_table[i].ptr = mem;
        }
        
        return ((void *)mem);

}

void 
snd_free_pages(char *xname, char *ptr, unsigned long size)
{
        int 	i;

        for (i = 0; i < SEG_TABLE_SIZE; i++) {
                if (__segment_table[i].ptr == ptr)
                        break;
        }
        if (i == SEG_TABLE_SIZE) 
                fprintf(stderr, "Warning, unable to locate entry in segment table\n");

        else {
                qnx_segment_free(__segment_table[i].seg);
                __segment_table[i].ptr = NULL;
                __segment_table[i].seg = 0;
        }
	
	munmap(ptr, size);
}

#endif

/*
 *  ok.. new allocation routines..
 */

#ifdef __QNXNTO__



void *snd_malloc(unsigned long size)
{
	if (!size)
		return NULL;
	return malloc(size);
}

void *snd_calloc(unsigned long size)
{
	void *result;

	if ((result = snd_malloc(size)) == NULL)
		return NULL;
	memset(result, 0, size);
	return result;
}

void snd_free(void *obj, unsigned long size)
{
	if (!size)
		return;
	if (!obj) {
		snd_printk("snd_free - NULL?\n");
		return;
	}
	free(obj);
}

char *snd_malloc_strdup(char *string)
{
	int len;
	char *res;

	if (!string)
		return NULL;
	len = strlen(string) + 1;
	if ((res = snd_malloc(len)) == NULL)
		return NULL;
	memcpy(res, string, len);
	return res;
}

void snd_free_str(char *string)
{
	if (string)
		snd_free(string, strlen(string) + 1);
}
#else

static sem_t _m_sem;
static char inited = 0;

void *snd_malloc(unsigned long size)
{
        void *ptr;

        if (!inited) {
                sem_init(&_m_sem, 1, 1);
                inited = 1;
        }

        if (!size)
                return NULL;

        sem_wait(&_m_sem);
        ptr = malloc(size);
        sem_post(&_m_sem);

        return ptr;
}

void *snd_calloc(unsigned long size)
{
        void *result;

        if ((result = snd_malloc(size)) == NULL)
                return NULL;
        memset(result, 0, size);
        return result;
}

void snd_free(void *obj, unsigned long size)
{
        if (!inited) {
                sem_init(&_m_sem, 1, 1);
                inited = 1;
        }

        if (!size)
                return;
        if (!obj) {
                snd_printk("snd_free - NULL?\n");
                return;
        }
        sem_wait(&_m_sem);
        free(obj);
        sem_post(&_m_sem);

}

char *snd_malloc_strdup(char *string)
{
        int len;
        char *res;

        if (!string)
                return NULL;
        len = strlen(string) + 1;
        if ((res = snd_malloc(len)) == NULL)
                return NULL;
        memcpy(res, string, len);
        return res;
}

void snd_free_str(char *string)
{
        if (string)
                snd_free(string, strlen(string) + 1);
}
#endif


/*
 *  DMA allocation routines
 */

int snd_dma_malloc(snd_card_t * card, snd_dma_t *dma, char *owner)
{
	unsigned char *buf = NULL;
	long size;

    if (card == NULL || dma == NULL)
        return -EINVAL;

	if (dma->type == SND_DMA_TYPE_HARDWARE)
		return -EINVAL;
	if (dma->buf) {		/* allocated */
		return 0;	/* ok... dma is allocated */
	}
	size = dma->rsize;
	while (buf == NULL && size >= __PAGESIZE) {
		buf = snd_malloc_pages(size, &dma->shm_name, dma->type == SND_DMA_TYPE_ISA || dma->type == SND_DMA_TYPE_PCI_16MB ? 1 : 0);
		if (!buf)
			size >>= 1;
	}
	if (buf) {
		dma->size = size;
		dma->buf = buf;
		dma->owner = owner;
	}
	return buf == NULL ? -ENOMEM : 0;
}

void snd_dma_free(snd_card_t * card, snd_dma_t *dma)
{
   if (card == NULL || dma == NULL)
        return;

	if (dma->type == SND_DMA_TYPE_HARDWARE)
		return;

	snd_printd("snd_dma_free: dmanum = %i, mmaped = %i\n", dma->dma, dma->mmaped);

	dma->mmap_free = 0;
	if (dma->buf) {
		snd_free_pages(dma->shm_name, dma->buf, dma->size);
		dma->buf = NULL;
		dma->size = 0;
	}
}



#ifdef __QNXNTO__

void *snd_device_mmap(unsigned long addr, unsigned long size)
{
	return( mmap_device_memory (0, size, PROT_READ | PROT_WRITE |
		PROT_NOCACHE, 0, addr));
}

int snd_device_munmap(void *addr, unsigned long size)
{
	return(munmap_device_memory( addr, size ));
}

#else

void *snd_device_mmap(unsigned long addr, unsigned long size)
{
	int     fd;
	void *ptr;

	if ((fd= shm_open ("Physical", O_RDWR, 0777)) < 0)
		return (NULL);

	if ((int) (ptr= mmap (NULL, size, PROT_READ | PROT_WRITE | PROT_NOCACHE,
		MAP_SHARED, fd, addr)) == -1)
		return (NULL);

	close (fd);

	return (ptr);
}

int snd_device_munmap(void *addr, unsigned long size)
{
	return(munmap( addr, size ));
}

#endif
