/* * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* * Copyright (c) 2012 Intel Corporation. All rights reserved. */ #include #include "drmP.h" #include "drm_linux_list.h" #include "drm_sun_idr.h" static inline int fr_isfull(struct idr_free_id_range *range) { return range->min_unused_id >= range->end; } static struct idr_free_id_range * fr_new(int start) { struct idr_free_id_range *this; this = kmem_zalloc(sizeof(struct idr_free_id_range), KM_SLEEP); this->start = start; this->end = 0x7fffffff; this->min_unused_id = start; return this; } static struct idr_free_id_range * fr_destroy(struct idr_free_id_range *range) { struct idr_free_id_range *ret; struct idr_free_id *id; while (range->free_ids) { id = range->free_ids; range->free_ids = range->free_ids->next; kmem_free(id, sizeof(struct idr_free_id)); } ret = range->next; kmem_free(range, sizeof(struct idr_free_id_range)); return ret; } static struct idr_free_id_range * fr_get(struct idr_free_id_range *list, int start) { struct idr_free_id_range *entry = list; while (entry && (entry->start != start)) entry = entry->next; return entry; } static struct idr_free_id_range * fr_insert(struct idr_free_id_range *list, int start) { struct idr_free_id_range *prev = list; struct idr_free_id_range *n; struct idr_free_id *id, **pid; while (prev->next && prev->next->start < start) prev = prev->next; n = fr_new(start); /* link the new range */ n->next = prev->next; prev->next = n; /* change the end of the ranges */ prev->end = start; if (n->next) n->end = n->next->start; if (fr_isfull(prev)) { /* change the min_unused_id of the ranges */ n->min_unused_id = prev->min_unused_id; prev->min_unused_id = start; /* link free id to the new range */ pid = &prev->free_ids; while (*pid) { if ((*pid)->id < start) { pid = &((*pid)->next); continue; } id = *pid; /* remove from the prev range */ (*pid) = id->next; /* link to the new range */ id->next = n->free_ids; n->free_ids = id; } } return n; } static int idr_compare(const void *a, const void *b) { const struct idr_used_id *pa = a; const struct idr_used_id *pb = b; if (pa->id < pb->id) return (-1); else if (pa->id > pb->id) return (1); else return (0); } static int idr_get_free_id_in_range(struct idr_free_id_range* range) { struct idr_free_id *idp; int id; if (range->free_ids) { idp = range->free_ids; range->free_ids = idp->next; id = idp->id; kmem_free(idp, sizeof(struct idr_free_id)); return (id); } if (!fr_isfull(range)) { id = range->min_unused_id; range->min_unused_id++; return (id); } return (-1); } void idr_init(struct idr *idrp) { avl_create(&idrp->used_ids, idr_compare, sizeof(struct idr_used_id), offsetof(struct idr_used_id, link)); idrp->free_id_ranges = fr_new(0); mutex_init(&idrp->lock, NULL, MUTEX_DRIVER, NULL); } int idr_get_new_above(struct idr *idrp, void *obj, int start, int *newid) { struct idr_used_id *used; struct idr_free_id_range *range; int id; if (start < 0) return (-EINVAL); mutex_enter(&idrp->lock); range = fr_get(idrp->free_id_ranges, start); if (!range) range = fr_insert(idrp->free_id_ranges, start); while (range) { id = idr_get_free_id_in_range(range); if (id >= 0) goto got_id; range = range->next; } mutex_exit(&idrp->lock); return (-1); got_id: used = kmem_alloc(sizeof(struct idr_used_id), KM_NOSLEEP); if (!used) { mutex_exit(&idrp->lock); return (-ENOMEM); } used->id = id; used->obj = obj; avl_add(&idrp->used_ids, used); *newid = id; mutex_exit(&idrp->lock); return (0); } static struct idr_used_id * idr_find_used_id(struct idr *idrp, uint32_t id) { struct idr_used_id match; struct idr_used_id *ret; match.id = id; ret = avl_find(&idrp->used_ids, &match, NULL); if (ret) { return (ret); } return (NULL); } void * idr_find(struct idr *idrp, uint32_t id) { struct idr_used_id *ret; mutex_enter(&idrp->lock); ret = idr_find_used_id(idrp, id); if (ret) { mutex_exit(&idrp->lock); return (ret->obj); } mutex_exit(&idrp->lock); return (NULL); } int idr_remove(struct idr *idrp, uint32_t id) { struct idr_free_id_range *range; struct idr_used_id *ide; struct idr_free_id *fid; mutex_enter(&idrp->lock); ide = idr_find_used_id(idrp, id); if (!ide) { mutex_exit(&idrp->lock); return (-EINVAL); } fid = kmem_alloc(sizeof(struct idr_free_id), KM_NOSLEEP); if (!fid) { mutex_exit(&idrp->lock); return (-ENOMEM); } fid->id = id; range = idrp->free_id_ranges; while (range->end <= id) range = range->next; fid->next = range->free_ids; range->free_ids = fid; avl_remove(&idrp->used_ids, ide); kmem_free(ide, sizeof (struct idr_used_id)); mutex_exit(&idrp->lock); return (0); } void idr_remove_all(struct idr *idrp) { idr_destroy(idrp); idr_init(idrp); } void * idr_replace(struct idr *idrp, void *obj, uint32_t id) { struct idr_used_id *ide; void *ret; mutex_enter(&idrp->lock); ide = idr_find_used_id(idrp, id); if (!ide) { mutex_exit(&idrp->lock); return (void*)(-EINVAL); } ret = ide->obj; ide->obj = obj; mutex_exit(&idrp->lock); return ret; } int idr_for_each(struct idr *idrp, int (*fn)(int id, void *p, void *data), void *data) { struct idr_used_id *ide; int ret = 0; ide = avl_first(&idrp->used_ids); while (ide) { ret = fn(ide->id, ide->obj, data); if (ret) break; /* idr node has been removed by fn */ ide = AVL_NEXT(&idrp->used_ids, ide); } return ret; } int /* LINTED */ idr_pre_get(struct idr *idrp, int flag) { return (-1); } void idr_destroy(struct idr *idrp) { struct idr_free_id_range *range; struct idr_used_id *ide; void *cookie = NULL; while (ide = avl_destroy_nodes(&idrp->used_ids, &cookie)) kmem_free(ide, sizeof (struct idr_used_id)); avl_destroy(&idrp->used_ids); range = idrp->free_id_ranges; while (range) range = fr_destroy(range); idrp->free_id_ranges = NULL; mutex_destroy(&idrp->lock); } uint32_t fr_id = 0; uint32_t fr_time = 0; int /* LINTED */ idr_list_pre_get(struct idr_list *head, int flag) { return (-1); } void idr_list_init(struct idr_list *head) { struct idr_list *entry; /* HASH for accelerate */ entry = kmem_zalloc(DRM_GEM_OBJIDR_HASHNODE * sizeof (struct idr_list), KM_SLEEP); head->next = entry; for (int i = 0; i < DRM_GEM_OBJIDR_HASHNODE; i++) { INIT_LIST_HEAD(&entry[i]); } } int idr_list_get_new_above(struct idr_list *head, void *obj, int *handlep) { struct idr_list *entry, *node, *temp; int key, id; void *obj_temp; ASSERT(fr_id <= 0x7fffffff); id = ++fr_id; if (id == 0x7fffffff) { fr_time++; id = fr_id = 1; } if (fr_time) { /* find available id */ do { obj_temp = idr_list_find(head, id); } while ((obj_temp != NULL) && (++id < 0x7fffffff)); if (id < 0x7fffffff) { fr_id = id; } else { fr_id = 0; return (-EAGAIN); } } entry = kmem_zalloc(sizeof (*entry), KM_NOSLEEP); if (entry == NULL) return (-1); ASSERT(id <= 0x7fffffff); key = id % DRM_GEM_OBJIDR_HASHNODE; entry->obj = obj; entry->handle = id; /* list add */ node = &head->next[key]; temp = node->next; entry->prev = node; entry->next = node->next; temp->prev = entry; node->next = entry; *handlep = id; return (0); } void * idr_list_find(struct idr_list *head, uint32_t name) { struct idr_list *entry; int key; key = name % DRM_GEM_OBJIDR_HASHNODE; list_for_each(entry, &head->next[key]) { if (entry->handle == name) return (entry->obj); } return (NULL); } #define list_del_idr_list_node(ptr) \ do { \ struct idr_list *n_node = (ptr)->next; \ struct idr_list *p_node = (ptr)->prev; \ n_node->prev = (ptr)->prev; \ p_node->next = (ptr)->next; \ (ptr)->prev = NULL; \ (ptr)->next = NULL; \ } while (*"\0") int idr_list_remove(struct idr_list *head, uint32_t name) { struct idr_list *entry, *temp; int key; key = name % DRM_GEM_OBJIDR_HASHNODE; list_for_each_safe(entry, temp, &head->next[key]) { if (entry->handle == name) { list_del_idr_list_node(entry); kmem_free(entry, sizeof (*entry)); return (0); } } DRM_ERROR("Failed to remove the object %d", name); return (-1); } void idr_list_free(struct idr_list *head) { struct idr_list *entry, *temp; for (int key = 0; key < DRM_GEM_OBJIDR_HASHNODE; key++) { list_for_each_safe(entry, temp, &head->next[key]) { list_del_idr_list_node(entry); kmem_free(entry, sizeof (*entry)); } } kmem_free(head->next, DRM_GEM_OBJIDR_HASHNODE * sizeof (struct idr_list)); head->next = NULL; } int idr_list_empty(struct idr_list *head) { int empty; for (int key = 0; key < DRM_GEM_OBJIDR_HASHNODE; key++) { empty = list_empty(&(head)->next[key]); if (!empty) return (empty); } return (1); }