#include <sys/types.h>
#include <sys/memlist.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/pci_impl.h>
#include <sys/debug.h>
int pci_memlist_debug;
#define dprintf if (pci_memlist_debug) printf
void
pci_memlist_dump(struct memlist *listp)
{
dprintf("memlist 0x%p content: ", (void *)listp);
while (listp) {
dprintf("(0x%lx, 0x%lx) ",
listp->ml_address, listp->ml_size);
listp = listp->ml_next;
}
dprintf("\n");
}
struct memlist *
pci_memlist_alloc()
{
return ((struct memlist *)kmem_zalloc(sizeof (struct memlist),
KM_SLEEP));
}
void
pci_memlist_free(struct memlist *buf)
{
kmem_free(buf, sizeof (struct memlist));
}
void
pci_memlist_free_all(struct memlist **list)
{
struct memlist *next, *buf;
next = *list;
while (next) {
buf = next;
next = buf->ml_next;
kmem_free(buf, sizeof (struct memlist));
}
*list = 0;
}
void
pci_memlist_insert(struct memlist **listp, uint64_t addr, uint64_t size)
{
int merge_left, merge_right;
struct memlist *entry;
struct memlist *prev = 0, *next;
next = *listp;
while (next && next->ml_address <= addr) {
if (next->ml_address <= addr &&
next->ml_address + next->ml_size >= addr + size) {
return;
}
if (next->ml_address == addr) {
break;
}
prev = next;
next = prev->ml_next;
}
merge_left = (prev && addr == prev->ml_address + prev->ml_size);
merge_right = (next && addr + size == next->ml_address);
if (merge_left && merge_right) {
prev->ml_size += size + next->ml_size;
prev->ml_next = next->ml_next;
pci_memlist_free(next);
return;
}
if (merge_left) {
prev->ml_size += size;
return;
}
if (merge_right) {
next->ml_address = addr;
next->ml_size += size;
return;
}
entry = pci_memlist_alloc();
entry->ml_address = addr;
entry->ml_size = size;
if (prev == 0) {
entry->ml_next = *listp;
*listp = entry;
} else {
entry->ml_next = next;
prev->ml_next = entry;
}
}
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define IN_RANGE(a, b, e) ((a) >= (b) && (a) <= (e))
int
pci_memlist_remove(struct memlist **listp, uint64_t addr, uint64_t size)
{
struct memlist *prev = 0;
struct memlist *chunk;
uint64_t rem_begin, rem_end;
uint64_t chunk_begin, chunk_end;
int begin_in_chunk, end_in_chunk;
if (size == 0)
return (0);
rem_begin = addr;
rem_end = addr + size - 1;
chunk = *listp;
while (chunk) {
chunk_begin = chunk->ml_address;
chunk_end = chunk->ml_address + chunk->ml_size - 1;
begin_in_chunk = IN_RANGE(rem_begin, chunk_begin, chunk_end);
end_in_chunk = IN_RANGE(rem_end, chunk_begin, chunk_end);
if (rem_begin <= chunk_begin && rem_end >= chunk_end) {
struct memlist *delete_chunk;
delete_chunk = chunk;
if (prev == 0)
chunk = *listp = chunk->ml_next;
else
chunk = prev->ml_next = chunk->ml_next;
pci_memlist_free(delete_chunk);
continue;
} else if (begin_in_chunk && end_in_chunk &&
chunk_begin != rem_begin && chunk_end != rem_end) {
struct memlist *new;
new = pci_memlist_alloc();
new->ml_address = rem_end + 1;
new->ml_size = chunk_end - new->ml_address + 1;
chunk->ml_size = rem_begin - chunk_begin;
new->ml_next = chunk->ml_next;
chunk->ml_next = new;
break;
} else if (begin_in_chunk || end_in_chunk) {
chunk->ml_size -= MIN(chunk_end, rem_end) -
MAX(chunk_begin, rem_begin) + 1;
if (rem_begin <= chunk_begin) {
chunk->ml_address = rem_end + 1;
break;
}
}
prev = chunk;
chunk = chunk->ml_next;
}
return (0);
}
uint64_t
pci_memlist_find(struct memlist **listp, uint64_t size, int align)
{
uint64_t delta, total_size;
uint64_t paddr;
struct memlist *prev = 0, *next;
next = *listp;
while (next) {
delta = next->ml_address & ((align != 0) ? (align - 1) : 0);
if (delta != 0)
total_size = size + align - delta;
else
total_size = size;
if (next->ml_size >= total_size)
break;
prev = next;
next = prev->ml_next;
}
if (next == 0)
return (0);
paddr = next->ml_address;
if (delta)
paddr += align - delta;
(void) pci_memlist_remove(listp, paddr, size);
return (paddr);
}
uint64_t
pci_memlist_find_with_startaddr(struct memlist **listp, uint64_t address,
uint64_t size, int align)
{
uint64_t delta, total_size;
uint64_t paddr;
struct memlist *next;
next = *listp;
while (next && (next->ml_address != address)) {
next = next->ml_next;
}
if (next == 0)
return (0);
delta = next->ml_address & ((align != 0) ? (align - 1) : 0);
if (delta != 0)
total_size = size + align - delta;
else
total_size = size;
if (next->ml_size < total_size)
return (0);
paddr = next->ml_address;
if (delta)
paddr += align - delta;
(void) pci_memlist_remove(listp, paddr, size);
return (paddr);
}
void
pci_memlist_subsume(struct memlist **src, struct memlist **dest)
{
struct memlist *head, *prev;
head = *src;
while (head) {
pci_memlist_insert(dest, head->ml_address, head->ml_size);
prev = head;
head = head->ml_next;
pci_memlist_free(prev);
}
*src = 0;
}
void
pci_memlist_merge(struct memlist **src, struct memlist **dest)
{
struct memlist *p;
p = *src;
while (p) {
pci_memlist_insert(dest, p->ml_address, p->ml_size);
p = p->ml_next;
}
}
struct memlist *
pci_memlist_dup(struct memlist *listp)
{
struct memlist *head = 0, *prev = 0;
while (listp) {
struct memlist *entry = pci_memlist_alloc();
entry->ml_address = listp->ml_address;
entry->ml_size = listp->ml_size;
entry->ml_next = 0;
if (prev)
prev->ml_next = entry;
else
head = entry;
prev = entry;
listp = listp->ml_next;
}
return (head);
}
int
pci_memlist_count(struct memlist *listp)
{
int count = 0;
while (listp) {
count++;
listp = listp->ml_next;
}
return (count);
}