123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703 |
- /*
- * tcm-sita.c
- *
- * SImple Tiler Allocator (SiTA): 2D and 1D allocation(reservation) algorithm
- *
- * Authors: Ravi Ramachandra <r.ramachandra@ti.com>,
- * Lajos Molnar <molnar@ti.com>
- *
- * Copyright (C) 2009-2010 Texas Instruments, Inc.
- *
- * This package is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
- *
- */
- #include <linux/slab.h>
- #include <linux/spinlock.h>
- #include "tcm-sita.h"
- #define ALIGN_DOWN(value, align) ((value) & ~((align) - 1))
- /* Individual selection criteria for different scan areas */
- static s32 CR_L2R_T2B = CR_BIAS_HORIZONTAL;
- static s32 CR_R2L_T2B = CR_DIAGONAL_BALANCE;
- /*********************************************
- * TCM API - Sita Implementation
- *********************************************/
- static s32 sita_reserve_2d(struct tcm *tcm, u16 h, u16 w, u8 align,
- struct tcm_area *area);
- static s32 sita_reserve_1d(struct tcm *tcm, u32 slots, struct tcm_area *area);
- static s32 sita_free(struct tcm *tcm, struct tcm_area *area);
- static void sita_deinit(struct tcm *tcm);
- /*********************************************
- * Main Scanner functions
- *********************************************/
- static s32 scan_areas_and_find_fit(struct tcm *tcm, u16 w, u16 h, u16 align,
- struct tcm_area *area);
- static s32 scan_l2r_t2b(struct tcm *tcm, u16 w, u16 h, u16 align,
- struct tcm_area *field, struct tcm_area *area);
- static s32 scan_r2l_t2b(struct tcm *tcm, u16 w, u16 h, u16 align,
- struct tcm_area *field, struct tcm_area *area);
- static s32 scan_r2l_b2t_one_dim(struct tcm *tcm, u32 num_slots,
- struct tcm_area *field, struct tcm_area *area);
- /*********************************************
- * Support Infrastructure Methods
- *********************************************/
- static s32 is_area_free(struct tcm_area ***map, u16 x0, u16 y0, u16 w, u16 h);
- static s32 update_candidate(struct tcm *tcm, u16 x0, u16 y0, u16 w, u16 h,
- struct tcm_area *field, s32 criteria,
- struct score *best);
- static void get_nearness_factor(struct tcm_area *field,
- struct tcm_area *candidate,
- struct nearness_factor *nf);
- static void get_neighbor_stats(struct tcm *tcm, struct tcm_area *area,
- struct neighbor_stats *stat);
- static void fill_area(struct tcm *tcm,
- struct tcm_area *area, struct tcm_area *parent);
- /*********************************************/
- /*********************************************
- * Utility Methods
- *********************************************/
- struct tcm *sita_init(u16 width, u16 height, struct tcm_pt *attr)
- {
- struct tcm *tcm;
- struct sita_pvt *pvt;
- struct tcm_area area = {0};
- s32 i;
- if (width == 0 || height == 0)
- return NULL;
- tcm = kmalloc(sizeof(*tcm), GFP_KERNEL);
- pvt = kmalloc(sizeof(*pvt), GFP_KERNEL);
- if (!tcm || !pvt)
- goto error;
- memset(tcm, 0, sizeof(*tcm));
- memset(pvt, 0, sizeof(*pvt));
- /* Updating the pointers to SiTA implementation APIs */
- tcm->height = height;
- tcm->width = width;
- tcm->reserve_2d = sita_reserve_2d;
- tcm->reserve_1d = sita_reserve_1d;
- tcm->free = sita_free;
- tcm->deinit = sita_deinit;
- tcm->pvt = (void *)pvt;
- spin_lock_init(&(pvt->lock));
- /* Creating tam map */
- pvt->map = kmalloc(sizeof(*pvt->map) * tcm->width, GFP_KERNEL);
- if (!pvt->map)
- goto error;
- for (i = 0; i < tcm->width; i++) {
- pvt->map[i] =
- kmalloc(sizeof(**pvt->map) * tcm->height,
- GFP_KERNEL);
- if (pvt->map[i] == NULL) {
- while (i--)
- kfree(pvt->map[i]);
- kfree(pvt->map);
- goto error;
- }
- }
- if (attr && attr->x <= tcm->width && attr->y <= tcm->height) {
- pvt->div_pt.x = attr->x;
- pvt->div_pt.y = attr->y;
- } else {
- /* Defaulting to 3:1 ratio on width for 2D area split */
- /* Defaulting to 3:1 ratio on height for 2D and 1D split */
- pvt->div_pt.x = (tcm->width * 3) / 4;
- pvt->div_pt.y = (tcm->height * 3) / 4;
- }
- spin_lock(&(pvt->lock));
- assign(&area, 0, 0, width - 1, height - 1);
- fill_area(tcm, &area, NULL);
- spin_unlock(&(pvt->lock));
- return tcm;
- error:
- kfree(tcm);
- kfree(pvt);
- return NULL;
- }
- static void sita_deinit(struct tcm *tcm)
- {
- struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
- struct tcm_area area = {0};
- s32 i;
- area.p1.x = tcm->width - 1;
- area.p1.y = tcm->height - 1;
- spin_lock(&(pvt->lock));
- fill_area(tcm, &area, NULL);
- spin_unlock(&(pvt->lock));
- for (i = 0; i < tcm->height; i++)
- kfree(pvt->map[i]);
- kfree(pvt->map);
- kfree(pvt);
- }
- /**
- * Reserve a 1D area in the container
- *
- * @param num_slots size of 1D area
- * @param area pointer to the area that will be populated with the
- * reserved area
- *
- * @return 0 on success, non-0 error value on failure.
- */
- static s32 sita_reserve_1d(struct tcm *tcm, u32 num_slots,
- struct tcm_area *area)
- {
- s32 ret;
- struct tcm_area field = {0};
- struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
- spin_lock(&(pvt->lock));
- /* Scanning entire container */
- assign(&field, tcm->width - 1, tcm->height - 1, 0, 0);
- ret = scan_r2l_b2t_one_dim(tcm, num_slots, &field, area);
- if (!ret)
- /* update map */
- fill_area(tcm, area, area);
- spin_unlock(&(pvt->lock));
- return ret;
- }
- /**
- * Reserve a 2D area in the container
- *
- * @param w width
- * @param h height
- * @param area pointer to the area that will be populated with the reserved
- * area
- *
- * @return 0 on success, non-0 error value on failure.
- */
- static s32 sita_reserve_2d(struct tcm *tcm, u16 h, u16 w, u8 align,
- struct tcm_area *area)
- {
- s32 ret;
- struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
- /* not supporting more than 64 as alignment */
- if (align > 64)
- return -EINVAL;
- /* we prefer 1, 32 and 64 as alignment */
- align = align <= 1 ? 1 : align <= 32 ? 32 : 64;
- spin_lock(&(pvt->lock));
- ret = scan_areas_and_find_fit(tcm, w, h, align, area);
- if (!ret)
- /* update map */
- fill_area(tcm, area, area);
- spin_unlock(&(pvt->lock));
- return ret;
- }
- /**
- * Unreserve a previously allocated 2D or 1D area
- * @param area area to be freed
- * @return 0 - success
- */
- static s32 sita_free(struct tcm *tcm, struct tcm_area *area)
- {
- struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
- spin_lock(&(pvt->lock));
- /* check that this is in fact an existing area */
- WARN_ON(pvt->map[area->p0.x][area->p0.y] != area ||
- pvt->map[area->p1.x][area->p1.y] != area);
- /* Clear the contents of the associated tiles in the map */
- fill_area(tcm, area, NULL);
- spin_unlock(&(pvt->lock));
- return 0;
- }
- /**
- * Note: In general the cordinates in the scan field area relevant to the can
- * sweep directions. The scan origin (e.g. top-left corner) will always be
- * the p0 member of the field. Therfore, for a scan from top-left p0.x <= p1.x
- * and p0.y <= p1.y; whereas, for a scan from bottom-right p1.x <= p0.x and p1.y
- * <= p0.y
- */
- /**
- * Raster scan horizontally right to left from top to bottom to find a place for
- * a 2D area of given size inside a scan field.
- *
- * @param w width of desired area
- * @param h height of desired area
- * @param align desired area alignment
- * @param area pointer to the area that will be set to the best position
- * @param field area to scan (inclusive)
- *
- * @return 0 on success, non-0 error value on failure.
- */
- static s32 scan_r2l_t2b(struct tcm *tcm, u16 w, u16 h, u16 align,
- struct tcm_area *field, struct tcm_area *area)
- {
- s32 x, y;
- s16 start_x, end_x, start_y, end_y, found_x = -1;
- struct tcm_area ***map = ((struct sita_pvt *)tcm->pvt)->map;
- struct score best = {{0}, {0}, {0}, 0};
- start_x = field->p0.x;
- end_x = field->p1.x;
- start_y = field->p0.y;
- end_y = field->p1.y;
- /* check scan area co-ordinates */
- if (field->p0.x < field->p1.x ||
- field->p1.y < field->p0.y)
- return -EINVAL;
- /* check if allocation would fit in scan area */
- if (w > LEN(start_x, end_x) || h > LEN(end_y, start_y))
- return -ENOSPC;
- /* adjust start_x and end_y, as allocation would not fit beyond */
- start_x = ALIGN_DOWN(start_x - w + 1, align); /* - 1 to be inclusive */
- end_y = end_y - h + 1;
- /* check if allocation would still fit in scan area */
- if (start_x < end_x)
- return -ENOSPC;
- /* scan field top-to-bottom, right-to-left */
- for (y = start_y; y <= end_y; y++) {
- for (x = start_x; x >= end_x; x -= align) {
- if (is_area_free(map, x, y, w, h)) {
- found_x = x;
- /* update best candidate */
- if (update_candidate(tcm, x, y, w, h, field,
- CR_R2L_T2B, &best))
- goto done;
- /* change upper x bound */
- end_x = x + 1;
- break;
- } else if (map[x][y] && map[x][y]->is2d) {
- /* step over 2D areas */
- x = ALIGN(map[x][y]->p0.x - w + 1, align);
- }
- }
- /* break if you find a free area shouldering the scan field */
- if (found_x == start_x)
- break;
- }
- if (!best.a.tcm)
- return -ENOSPC;
- done:
- assign(area, best.a.p0.x, best.a.p0.y, best.a.p1.x, best.a.p1.y);
- return 0;
- }
- /**
- * Raster scan horizontally left to right from top to bottom to find a place for
- * a 2D area of given size inside a scan field.
- *
- * @param w width of desired area
- * @param h height of desired area
- * @param align desired area alignment
- * @param area pointer to the area that will be set to the best position
- * @param field area to scan (inclusive)
- *
- * @return 0 on success, non-0 error value on failure.
- */
- static s32 scan_l2r_t2b(struct tcm *tcm, u16 w, u16 h, u16 align,
- struct tcm_area *field, struct tcm_area *area)
- {
- s32 x, y;
- s16 start_x, end_x, start_y, end_y, found_x = -1;
- struct tcm_area ***map = ((struct sita_pvt *)tcm->pvt)->map;
- struct score best = {{0}, {0}, {0}, 0};
- start_x = field->p0.x;
- end_x = field->p1.x;
- start_y = field->p0.y;
- end_y = field->p1.y;
- /* check scan area co-ordinates */
- if (field->p1.x < field->p0.x ||
- field->p1.y < field->p0.y)
- return -EINVAL;
- /* check if allocation would fit in scan area */
- if (w > LEN(end_x, start_x) || h > LEN(end_y, start_y))
- return -ENOSPC;
- start_x = ALIGN(start_x, align);
- /* check if allocation would still fit in scan area */
- if (w > LEN(end_x, start_x))
- return -ENOSPC;
- /* adjust end_x and end_y, as allocation would not fit beyond */
- end_x = end_x - w + 1; /* + 1 to be inclusive */
- end_y = end_y - h + 1;
- /* scan field top-to-bottom, left-to-right */
- for (y = start_y; y <= end_y; y++) {
- for (x = start_x; x <= end_x; x += align) {
- if (is_area_free(map, x, y, w, h)) {
- found_x = x;
- /* update best candidate */
- if (update_candidate(tcm, x, y, w, h, field,
- CR_L2R_T2B, &best))
- goto done;
- /* change upper x bound */
- end_x = x - 1;
- break;
- } else if (map[x][y] && map[x][y]->is2d) {
- /* step over 2D areas */
- x = ALIGN_DOWN(map[x][y]->p1.x, align);
- }
- }
- /* break if you find a free area shouldering the scan field */
- if (found_x == start_x)
- break;
- }
- if (!best.a.tcm)
- return -ENOSPC;
- done:
- assign(area, best.a.p0.x, best.a.p0.y, best.a.p1.x, best.a.p1.y);
- return 0;
- }
- /**
- * Raster scan horizontally right to left from bottom to top to find a place
- * for a 1D area of given size inside a scan field.
- *
- * @param num_slots size of desired area
- * @param align desired area alignment
- * @param area pointer to the area that will be set to the best
- * position
- * @param field area to scan (inclusive)
- *
- * @return 0 on success, non-0 error value on failure.
- */
- static s32 scan_r2l_b2t_one_dim(struct tcm *tcm, u32 num_slots,
- struct tcm_area *field, struct tcm_area *area)
- {
- s32 found = 0;
- s16 x, y;
- struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
- struct tcm_area *p;
- /* check scan area co-ordinates */
- if (field->p0.y < field->p1.y)
- return -EINVAL;
- /**
- * Currently we only support full width 1D scan field, which makes sense
- * since 1D slot-ordering spans the full container width.
- */
- if (tcm->width != field->p0.x - field->p1.x + 1)
- return -EINVAL;
- /* check if allocation would fit in scan area */
- if (num_slots > tcm->width * LEN(field->p0.y, field->p1.y))
- return -ENOSPC;
- x = field->p0.x;
- y = field->p0.y;
- /* find num_slots consecutive free slots to the left */
- while (found < num_slots) {
- if (y < 0)
- return -ENOSPC;
- /* remember bottom-right corner */
- if (found == 0) {
- area->p1.x = x;
- area->p1.y = y;
- }
- /* skip busy regions */
- p = pvt->map[x][y];
- if (p) {
- /* move to left of 2D areas, top left of 1D */
- x = p->p0.x;
- if (!p->is2d)
- y = p->p0.y;
- /* start over */
- found = 0;
- } else {
- /* count consecutive free slots */
- found++;
- if (found == num_slots)
- break;
- }
- /* move to the left */
- if (x == 0)
- y--;
- x = (x ? : tcm->width) - 1;
- }
- /* set top-left corner */
- area->p0.x = x;
- area->p0.y = y;
- return 0;
- }
- /**
- * Find a place for a 2D area of given size inside a scan field based on its
- * alignment needs.
- *
- * @param w width of desired area
- * @param h height of desired area
- * @param align desired area alignment
- * @param area pointer to the area that will be set to the best position
- *
- * @return 0 on success, non-0 error value on failure.
- */
- static s32 scan_areas_and_find_fit(struct tcm *tcm, u16 w, u16 h, u16 align,
- struct tcm_area *area)
- {
- s32 ret = 0;
- struct tcm_area field = {0};
- u16 boundary_x, boundary_y;
- struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
- if (align > 1) {
- /* prefer top-left corner */
- boundary_x = pvt->div_pt.x - 1;
- boundary_y = pvt->div_pt.y - 1;
- /* expand width and height if needed */
- if (w > pvt->div_pt.x)
- boundary_x = tcm->width - 1;
- if (h > pvt->div_pt.y)
- boundary_y = tcm->height - 1;
- assign(&field, 0, 0, boundary_x, boundary_y);
- ret = scan_l2r_t2b(tcm, w, h, align, &field, area);
- /* scan whole container if failed, but do not scan 2x */
- if (ret != 0 && (boundary_x != tcm->width - 1 ||
- boundary_y != tcm->height - 1)) {
- /* scan the entire container if nothing found */
- assign(&field, 0, 0, tcm->width - 1, tcm->height - 1);
- ret = scan_l2r_t2b(tcm, w, h, align, &field, area);
- }
- } else if (align == 1) {
- /* prefer top-right corner */
- boundary_x = pvt->div_pt.x;
- boundary_y = pvt->div_pt.y - 1;
- /* expand width and height if needed */
- if (w > (tcm->width - pvt->div_pt.x))
- boundary_x = 0;
- if (h > pvt->div_pt.y)
- boundary_y = tcm->height - 1;
- assign(&field, tcm->width - 1, 0, boundary_x, boundary_y);
- ret = scan_r2l_t2b(tcm, w, h, align, &field, area);
- /* scan whole container if failed, but do not scan 2x */
- if (ret != 0 && (boundary_x != 0 ||
- boundary_y != tcm->height - 1)) {
- /* scan the entire container if nothing found */
- assign(&field, tcm->width - 1, 0, 0, tcm->height - 1);
- ret = scan_r2l_t2b(tcm, w, h, align, &field,
- area);
- }
- }
- return ret;
- }
- /* check if an entire area is free */
- static s32 is_area_free(struct tcm_area ***map, u16 x0, u16 y0, u16 w, u16 h)
- {
- u16 x = 0, y = 0;
- for (y = y0; y < y0 + h; y++) {
- for (x = x0; x < x0 + w; x++) {
- if (map[x][y])
- return false;
- }
- }
- return true;
- }
- /* fills an area with a parent tcm_area */
- static void fill_area(struct tcm *tcm, struct tcm_area *area,
- struct tcm_area *parent)
- {
- s32 x, y;
- struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
- struct tcm_area a, a_;
- /* set area's tcm; otherwise, enumerator considers it invalid */
- area->tcm = tcm;
- tcm_for_each_slice(a, *area, a_) {
- for (x = a.p0.x; x <= a.p1.x; ++x)
- for (y = a.p0.y; y <= a.p1.y; ++y)
- pvt->map[x][y] = parent;
- }
- }
- /**
- * Compares a candidate area to the current best area, and if it is a better
- * fit, it updates the best to this one.
- *
- * @param x0, y0, w, h top, left, width, height of candidate area
- * @param field scan field
- * @param criteria scan criteria
- * @param best best candidate and its scores
- *
- * @return 1 (true) if the candidate area is known to be the final best, so no
- * more searching should be performed
- */
- static s32 update_candidate(struct tcm *tcm, u16 x0, u16 y0, u16 w, u16 h,
- struct tcm_area *field, s32 criteria,
- struct score *best)
- {
- struct score me; /* score for area */
- /*
- * NOTE: For horizontal bias we always give the first found, because our
- * scan is horizontal-raster-based and the first candidate will always
- * have the horizontal bias.
- */
- bool first = criteria & CR_BIAS_HORIZONTAL;
- assign(&me.a, x0, y0, x0 + w - 1, y0 + h - 1);
- /* calculate score for current candidate */
- if (!first) {
- get_neighbor_stats(tcm, &me.a, &me.n);
- me.neighs = me.n.edge + me.n.busy;
- get_nearness_factor(field, &me.a, &me.f);
- }
- /* the 1st candidate is always the best */
- if (!best->a.tcm)
- goto better;
- BUG_ON(first);
- /* diagonal balance check */
- if ((criteria & CR_DIAGONAL_BALANCE) &&
- best->neighs <= me.neighs &&
- (best->neighs < me.neighs ||
- /* this implies that neighs and occupied match */
- best->n.busy < me.n.busy ||
- (best->n.busy == me.n.busy &&
- /* check the nearness factor */
- best->f.x + best->f.y > me.f.x + me.f.y)))
- goto better;
- /* not better, keep going */
- return 0;
- better:
- /* save current area as best */
- memcpy(best, &me, sizeof(me));
- best->a.tcm = tcm;
- return first;
- }
- /**
- * Calculate the nearness factor of an area in a search field. The nearness
- * factor is smaller if the area is closer to the search origin.
- */
- static void get_nearness_factor(struct tcm_area *field, struct tcm_area *area,
- struct nearness_factor *nf)
- {
- /**
- * Using signed math as field coordinates may be reversed if
- * search direction is right-to-left or bottom-to-top.
- */
- nf->x = (s32)(area->p0.x - field->p0.x) * 1000 /
- (field->p1.x - field->p0.x);
- nf->y = (s32)(area->p0.y - field->p0.y) * 1000 /
- (field->p1.y - field->p0.y);
- }
- /* get neighbor statistics */
- static void get_neighbor_stats(struct tcm *tcm, struct tcm_area *area,
- struct neighbor_stats *stat)
- {
- s16 x = 0, y = 0;
- struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
- /* Clearing any exisiting values */
- memset(stat, 0, sizeof(*stat));
- /* process top & bottom edges */
- for (x = area->p0.x; x <= area->p1.x; x++) {
- if (area->p0.y == 0)
- stat->edge++;
- else if (pvt->map[x][area->p0.y - 1])
- stat->busy++;
- if (area->p1.y == tcm->height - 1)
- stat->edge++;
- else if (pvt->map[x][area->p1.y + 1])
- stat->busy++;
- }
- /* process left & right edges */
- for (y = area->p0.y; y <= area->p1.y; ++y) {
- if (area->p0.x == 0)
- stat->edge++;
- else if (pvt->map[area->p0.x - 1][y])
- stat->busy++;
- if (area->p1.x == tcm->width - 1)
- stat->edge++;
- else if (pvt->map[area->p1.x + 1][y])
- stat->busy++;
- }
- }
|