/* This file contains the cbuf functions. Cbuf is a memory allocator, currently not used for anything in kernel land other than ports.*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ALLOCATE_CHUNK (PAGE_SIZE * 16) #define CBUF_REGION_SIZE (4*1024*1024) #define CBUF_BITMAP_SIZE (CBUF_REGION_SIZE / CBUF_LEN) static cbuf *cbuf_free_list; static sem_id free_list_sem; static cbuf *cbuf_free_noblock_list; static spinlock noblock_spin; static spinlock cbuf_lowlevel_spinlock; static region_id cbuf_region_id; static cbuf *cbuf_region; static region_id cbuf_bitmap_region_id; static uint8 *cbuf_bitmap; /* Declarations we need that aren't in header files */ uint16 ones_sum16(uint32, const void *, int); static void initialize_cbuf(cbuf *buf) { buf->len = sizeof(buf->dat); buf->data = buf->dat; buf->flags = 0; buf->total_len = 0; } static void *_cbuf_alloc(size_t *size) { int state; void *buf; int i; int start; size_t len_found = 0; // dprintf("cbuf_alloc: asked to allocate size %d\n", *size); state = disable_interrupts(); acquire_spinlock(&cbuf_lowlevel_spinlock); // scan through the allocation bitmap, looking for the first free block // XXX not optimal start = -1; for(i=0; i= *size) { // we're done break; } } else if(start >= 0) { // we've found a start of a run before, so we're done now break; } } if(start < 0) { // couldn't find any memory buf = NULL; *size = 0; } else { buf = &cbuf_region[start]; *size = len_found; } release_spinlock(&cbuf_lowlevel_spinlock); restore_interrupts(state); return buf; } static cbuf *allocate_cbuf_mem(size_t size) { void *_buf; cbuf *buf = NULL; cbuf *last_buf = NULL; cbuf *head_buf = NULL; int i; int count; size_t found_size; size = PAGE_ALIGN(size); while(size > 0) { found_size = size; _buf = _cbuf_alloc(&found_size); if(!_buf) { // couldn't allocate, lets bail with what we have break; } size -= found_size; count = found_size / CBUF_LEN; // dprintf("allocate_cbuf_mem: returned %d of memory, %d left\n", found_size, size); buf = (cbuf *)_buf; if(!head_buf) head_buf = buf; for(i=0; inext = buf; if(buf == head_buf) { buf->flags |= CBUF_FLAG_CHAIN_HEAD; buf->total_len = (size / CBUF_LEN) * sizeof(buf->dat); } last_buf = buf; buf++; } } if(buf) { buf->next = NULL; buf->flags |= CBUF_FLAG_CHAIN_TAIL; } return head_buf; } static void _clear_chain(cbuf *head, cbuf **tail) { cbuf *buf; buf = head; *tail = NULL; while(buf) { initialize_cbuf(buf); // doesn't touch the next ptr *tail = buf; buf = buf->next; } return; } void cbuf_free_chain_noblock(cbuf *buf) { cbuf *head, *last; int state; if(buf == NULL) return; head = buf; _clear_chain(head, &last); state = disable_interrupts(); acquire_spinlock(&noblock_spin); last->next = cbuf_free_noblock_list; cbuf_free_noblock_list = head; release_spinlock(&noblock_spin); restore_interrupts(state); } void cbuf_free_chain(cbuf *buf) { cbuf *head, *last; if(buf == NULL) return; head = buf; _clear_chain(head, &last); acquire_sem(free_list_sem); last->next = cbuf_free_list; cbuf_free_list = head; release_sem(free_list_sem); } cbuf *cbuf_get_chain(size_t len) { cbuf *chain = NULL; cbuf *tail = NULL; cbuf *temp; size_t chain_len = 0; if(len == 0) panic("cbuf_get_chain: passed size 0\n"); acquire_sem(free_list_sem); while(chain_len < len) { if(cbuf_free_list == NULL) { // we need to allocate some more cbufs release_sem(free_list_sem); temp = allocate_cbuf_mem(ALLOCATE_CHUNK); if(!temp) { // no more ram if(chain) cbuf_free_chain(chain); return NULL; } cbuf_free_chain(temp); acquire_sem(free_list_sem); continue; } temp = cbuf_free_list; cbuf_free_list = cbuf_free_list->next; temp->next = chain; if(chain == NULL) tail = temp; chain = temp; chain_len += chain->len; } release_sem(free_list_sem); // now we have a chain, fixup the first and last entry chain->total_len = len; chain->flags |= CBUF_FLAG_CHAIN_HEAD; tail->len -= chain_len - len; tail->flags |= CBUF_FLAG_CHAIN_TAIL; return chain; } cbuf *cbuf_get_chain_noblock(size_t len) { cbuf *chain = NULL; cbuf *tail = NULL; cbuf *temp; size_t chain_len = 0; int state; state = disable_interrupts(); acquire_spinlock(&noblock_spin); while(chain_len < len) { if(cbuf_free_noblock_list == NULL) { dprintf("cbuf_get_chain_noblock: not enough cbufs\n"); release_spinlock(&noblock_spin); restore_interrupts(state); if(chain != NULL) cbuf_free_chain_noblock(chain); return NULL; } temp = cbuf_free_noblock_list; cbuf_free_noblock_list = cbuf_free_noblock_list->next; temp->next = chain; if(chain == NULL) tail = temp; chain = temp; chain_len += chain->len; } release_spinlock(&noblock_spin); restore_interrupts(state); // now we have a chain, fixup the first and last entry chain->total_len = len; chain->flags |= CBUF_FLAG_CHAIN_HEAD; tail->len -= chain_len - len; tail->flags |= CBUF_FLAG_CHAIN_TAIL; return chain; } int cbuf_memcpy_to_chain(cbuf *chain, size_t offset, const void *_src, size_t len) { cbuf *buf; char *src = (char *)_src; int buf_offset; if((chain->flags & CBUF_FLAG_CHAIN_HEAD) == 0) { dprintf("cbuf_memcpy_to_chain: chain at %p not head\n", chain); return EINVAL; } if(len + offset > chain->total_len) { dprintf("cbuf_memcpy_to_chain: len + offset > size of cbuf chain\n"); return EINVAL; } // find the starting cbuf in the chain to copy to buf = chain; buf_offset = 0; while(offset > 0) { if(buf == NULL) { dprintf("cbuf_memcpy_to_chain: end of chain reached too early!\n"); return ERR_GENERAL; } if(offset < buf->len) { // this is the one buf_offset = offset; break; } offset -= buf->len; buf = buf->next; } while(len > 0) { int to_copy; if(buf == NULL) { dprintf("cbuf_memcpy_to_chain: end of chain reached too early!\n"); return ERR_GENERAL; } to_copy = min(len, buf->len - buf_offset); memcpy((char *)buf->data + buf_offset, src, to_copy); buf_offset = 0; len -= to_copy; src += to_copy; buf = buf->next; } return B_NO_ERROR; } int cbuf_user_memcpy_to_chain(cbuf *chain, size_t offset, const void *_src, size_t len) { cbuf *buf; char *src = (char *)_src; int buf_offset; int err; if((chain->flags & CBUF_FLAG_CHAIN_HEAD) == 0) { dprintf("cbuf_memcpy_to_chain: chain at %p not head\n", chain); return EINVAL; } if(len + offset > chain->total_len) { dprintf("cbuf_memcpy_to_chain: len + offset > size of cbuf chain\n"); return EINVAL; } // find the starting cbuf in the chain to copy to buf = chain; buf_offset = 0; while(offset > 0) { if(buf == NULL) { dprintf("cbuf_memcpy_to_chain: end of chain reached too early!\n"); return ERR_GENERAL; } if(offset < buf->len) { // this is the one buf_offset = offset; break; } offset -= buf->len; buf = buf->next; } err = B_NO_ERROR; while(len > 0) { int to_copy; if(buf == NULL) { dprintf("cbuf_memcpy_to_chain: end of chain reached too early!\n"); return ERR_GENERAL; } to_copy = min(len, buf->len - buf_offset); if ((err = user_memcpy((char *)buf->data + buf_offset, src, to_copy) < 0)) break; // memory exception buf_offset = 0; len -= to_copy; src += to_copy; buf = buf->next; } return err; } int cbuf_memcpy_from_chain(void *_dest, cbuf *chain, size_t offset, size_t len) { cbuf *buf; char *dest = (char *)_dest; int buf_offset; if((chain->flags & CBUF_FLAG_CHAIN_HEAD) == 0) { dprintf("cbuf_memcpy_from_chain: chain at %p not head\n", chain); return EINVAL; } if(len + offset > chain->total_len) { dprintf("cbuf_memcpy_from_chain: len + offset > size of cbuf chain\n"); return EINVAL; } // find the starting cbuf in the chain to copy from buf = chain; buf_offset = 0; while(offset > 0) { if(buf == NULL) { dprintf("cbuf_memcpy_from_chain: end of chain reached too early!\n"); return ERR_GENERAL; } if(offset < buf->len) { // this is the one buf_offset = offset; break; } offset -= buf->len; buf = buf->next; } while(len > 0) { int to_copy; if(buf == NULL) { dprintf("cbuf_memcpy_from_chain: end of chain reached too early!\n"); return ERR_GENERAL; } to_copy = min(len, buf->len - buf_offset); memcpy(dest, (char *)buf->data + buf_offset, to_copy); buf_offset = 0; len -= to_copy; dest += to_copy; buf = buf->next; } return B_NO_ERROR; } int cbuf_user_memcpy_from_chain(void *_dest, cbuf *chain, size_t offset, size_t len) { cbuf *buf; char *dest = (char *)_dest; int buf_offset; int err; if((chain->flags & CBUF_FLAG_CHAIN_HEAD) == 0) { dprintf("cbuf_memcpy_from_chain: chain at %p not head\n", chain); return EINVAL; } if(len + offset > chain->total_len) { dprintf("cbuf_memcpy_from_chain: len + offset > size of cbuf chain\n"); return EINVAL; } // find the starting cbuf in the chain to copy from buf = chain; buf_offset = 0; while(offset > 0) { if(buf == NULL) { dprintf("cbuf_memcpy_from_chain: end of chain reached too early!\n"); return ERR_GENERAL; } if(offset < buf->len) { // this is the one buf_offset = offset; break; } offset -= buf->len; buf = buf->next; } err = B_NO_ERROR; while(len > 0) { int to_copy; if(buf == NULL) { dprintf("cbuf_memcpy_from_chain: end of chain reached too early!\n"); return ERR_GENERAL; } to_copy = min(len, buf->len - buf_offset); if ((err = user_memcpy(dest, (char *)buf->data + buf_offset, to_copy) < 0)) break; buf_offset = 0; len -= to_copy; dest += to_copy; buf = buf->next; } return err; } cbuf *cbuf_duplicate_chain(cbuf *chain, size_t offset, size_t len) { cbuf *buf; cbuf *newbuf; cbuf *destbuf; int dest_buf_offset; int buf_offset; if(!chain) return NULL; if((chain->flags & CBUF_FLAG_CHAIN_HEAD) == 0) return NULL; if(offset >= chain->total_len) return NULL; len = min(len, chain->total_len - offset); newbuf = cbuf_get_chain(len); if(!newbuf) return NULL; // find the starting cbuf in the chain to copy from buf = chain; buf_offset = 0; while(offset > 0) { if(buf == NULL) { cbuf_free_chain(newbuf); dprintf("cbuf_duplicate_chain: end of chain reached too early!\n"); return NULL; } if(offset < buf->len) { // this is the one buf_offset = offset; break; } offset -= buf->len; buf = buf->next; } destbuf = newbuf; dest_buf_offset = 0; while(len > 0) { size_t to_copy; if(buf == NULL) { cbuf_free_chain(newbuf); dprintf("cbuf_duplicate_chain: end of source chain reached too early!\n"); return NULL; } if(destbuf == NULL) { cbuf_free_chain(newbuf); dprintf("cbuf_duplicate_chain: end of destination chain reached too early!\n"); return NULL; } to_copy = min(destbuf->len - dest_buf_offset, buf->len - buf_offset); to_copy = min(to_copy, len); memcpy((char *)destbuf->data + dest_buf_offset, (char *)buf->data + buf_offset, to_copy); len -= to_copy; if(to_copy + buf_offset == buf->len) { buf = buf->next; buf_offset = 0; } else { buf_offset += to_copy; } if(to_copy + dest_buf_offset == destbuf->len) { destbuf = destbuf->next; dest_buf_offset = 0; } else { dest_buf_offset += to_copy; } } return newbuf; } cbuf *cbuf_merge_chains(cbuf *chain1, cbuf *chain2) { cbuf *buf; if(!chain1 && !chain2) return NULL; if(!chain1) return chain2; if(!chain2) return chain1; if((chain1->flags & CBUF_FLAG_CHAIN_HEAD) == 0) { dprintf("cbuf_merge_chain: chain at %p not head\n", chain1); return NULL; } if((chain2->flags & CBUF_FLAG_CHAIN_HEAD) == 0) { dprintf("cbuf_merge_chain: chain at %p not head\n", chain2); return NULL; } // walk to the end of the first chain and tag the second one on buf = chain1; while(buf->next) buf = buf->next; buf->next = chain2; // modify the flags on the chain headers buf->flags &= ~CBUF_FLAG_CHAIN_TAIL; chain1->total_len += chain2->total_len; chain2->flags &= ~CBUF_FLAG_CHAIN_HEAD; return chain1; } size_t cbuf_get_len(cbuf *buf) { if(!buf) return EINVAL; if(buf->flags & CBUF_FLAG_CHAIN_HEAD) { return buf->total_len; } else { int len = 0; while(buf) { len += buf->len; buf = buf->next; } return len; } } void *cbuf_get_ptr(cbuf *buf, size_t offset) { while(buf) { if(buf->len > offset) return (void *)((int)buf->data + offset); if(buf->len > offset) return NULL; offset -= buf->len; buf = buf->next; } return NULL; } int cbuf_is_contig_region(cbuf *buf, size_t start, size_t end) { while(buf) { if(buf->len > start) { if(buf->len - start >= end) return 1; else return 0; } start -= buf->len; end -= buf->len; buf = buf->next; } return 0; } uint16 cbuf_ones_cksum16(cbuf *buf, size_t offset, size_t len) { uint16 sum = 0; int swapped = 0; if(!buf) return 0; if((buf->flags & CBUF_FLAG_CHAIN_HEAD) == 0) return 0; // find the start ptr while(buf) { if(buf->len > offset) break; if(buf->len > offset) return 0; offset -= buf->len; buf = buf->next; } // start checksumming while(buf && len > 0) { void *ptr = (void *)((addr)buf->data + offset); size_t plen = min(len, buf->len - offset); sum = ones_sum16(sum, ptr, plen); len -= plen; buf = buf->next; // if the pointer was odd, or the length was odd, but not both, // the checksum was swapped if((buf && len > 0) && (((offset % 2) && ((plen % 2) == 0)) || (((offset % 2) == 0) && (plen % 2)))) { swapped ^= 1; sum = ((sum & 0xff) << 8) | ((sum >> 8) & 0xff); } offset = 0; } if (swapped) sum = ((sum & 0xff) << 8) | ((sum >> 8) & 0xff); return ~sum; } int cbuf_truncate_head(cbuf *buf, size_t trunc_bytes) { cbuf *head = buf; if(!buf || (buf->flags & CBUF_FLAG_CHAIN_HEAD) == 0) return EINVAL; while(buf && trunc_bytes > 0) { int to_trunc; to_trunc = min(trunc_bytes, buf->len); buf->len -= to_trunc; buf->data = (void *)((int)buf->data + to_trunc); trunc_bytes -= to_trunc; head->total_len -= to_trunc; buf = buf->next; } return 0; } int cbuf_truncate_tail(cbuf *buf, size_t trunc_bytes) { size_t offset; size_t buf_offset; cbuf *head = buf; if(!buf || (buf->flags & CBUF_FLAG_CHAIN_HEAD) == 0) return EINVAL; offset = buf->total_len - trunc_bytes; buf_offset = 0; while(buf && offset > 0) { if(offset < buf->len) { // this is the one buf_offset = offset; break; } offset -= buf->len; buf = buf->next; } if(!buf) return ERR_GENERAL; head->total_len -= buf->len - buf_offset; buf->len -= buf->len - buf_offset; // clear out the rest of the buffers in this chain buf = buf->next; while(buf) { head->total_len -= buf->len; buf->len = 0; buf = buf->next; } return B_NO_ERROR; } static int dbg_dump_cbuf_freelists(int argc, char **argv) { cbuf *buf; dprintf("cbuf_free_list:\n"); for(buf = cbuf_free_list; buf; buf = buf->next) dprintf("%p ", buf); dprintf("\n"); dprintf("cbuf_free_noblock_list:\n"); for(buf = cbuf_free_noblock_list; buf; buf = buf->next) dprintf("%p ", buf); dprintf("\n"); return 0; } void cbuf_test() { cbuf *buf, *buf2; char temp[1024]; unsigned int i; dprintf("starting cbuffer test\n"); buf = cbuf_get_chain(32); if(!buf) panic("cbuf_test: failed allocation of 32\n"); buf2 = cbuf_get_chain(3*1024*1024); if(!buf2) panic("cbuf_test: failed allocation of 3mb\n"); buf = cbuf_merge_chains(buf2, buf); cbuf_free_chain(buf); dprintf("allocating too much...\n"); buf = cbuf_get_chain(128*1024*1024); if(buf) panic("cbuf_test: should have failed to allocate 128mb\n"); dprintf("touching memory allocated by cbuf\n"); buf = cbuf_get_chain(7*1024*1024); if(!buf) panic("cbuf_test: failed allocation of 7mb\n"); for(i=0; i < sizeof(temp); i++) temp[i] = i; for(i=0; i<7*1024*1024 / sizeof(temp); i++) { if(i % 128 == 0) dprintf("%Lud\n", (long long)(i*sizeof(temp))); cbuf_memcpy_to_chain(buf, i*sizeof(temp), temp, sizeof(temp)); } cbuf_free_chain(buf); dprintf("finished cbuffer test\n"); } int cbuf_init() { cbuf *buf; int i; cbuf_free_list = NULL; cbuf_free_noblock_list = NULL; noblock_spin = 0; cbuf_lowlevel_spinlock = 0; // add the debug command add_debugger_command("cbuf_freelist", &dbg_dump_cbuf_freelists, "Dumps the cbuf free lists"); free_list_sem = create_sem(1, "cbuf_free_list_sem"); if(free_list_sem < 0) { panic("cbuf_init: error creating cbuf_free_list_sem\n"); return ENOMEM; } cbuf_region_id = vm_create_anonymous_region(vm_get_kernel_aspace_id(), "cbuf region", (void **)&cbuf_region, REGION_ADDR_ANY_ADDRESS, CBUF_REGION_SIZE, REGION_WIRING_LAZY, LOCK_RW|LOCK_KERNEL); if(cbuf_region_id < 0) { panic("cbuf_init: error creating cbuf region\n"); return ENOMEM; } cbuf_bitmap_region_id = vm_create_anonymous_region(vm_get_kernel_aspace_id(), "cbuf bitmap region", (void **)&cbuf_bitmap, REGION_ADDR_ANY_ADDRESS, CBUF_BITMAP_SIZE / 8, REGION_WIRING_WIRED, LOCK_RW|LOCK_KERNEL); if(cbuf_region_id < 0) { panic("cbuf_init: error creating cbuf bitmap region\n"); return ENOMEM; } // initialize the bitmap for(i=0; i