blob: fde0208ec01e075f6c74f00396ae3055d40afbe6 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* SImple Tiler Allocator (SiTA): 2D and 1D allocation(reservation) algorithm
*
* Authors: Ravi Ramachandra <r.ramachandra@ti.com>,
* Lajos Molnar <molnar@ti.com>
* Andy Gross <andy.gross@ti.com>
*
* Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com/
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/bitmap.h>
#include <linux/slab.h>
#include "tcm.h"
static unsigned long mask[8];
/*
* pos position in bitmap
* w width in slots
* h height in slots
* map ptr to bitmap
* stride slots in a row
*/
static void free_slots(unsigned long pos, u16 w, u16 h,
unsigned long *map, u16 stride)
{
int i;
for (i = 0; i < h; i++, pos += stride)
bitmap_clear(map, pos, w);
}
/*
* w width in slots
* pos ptr to position
* map ptr to bitmap
* num_bits number of bits in bitmap
*/
static int r2l_b2t_1d(u16 w, unsigned long *pos, unsigned long *map,
size_t num_bits)
{
unsigned long search_count = 0;
unsigned long bit;
bool area_found = false;
*pos = num_bits - w;
while (search_count < num_bits) {
bit = find_next_bit(map, num_bits, *pos);
if (bit - *pos >= w) {
/* found a long enough free area */
bitmap_set(map, *pos, w);
area_found = true;
break;
}
search_count = num_bits - bit + w;
*pos = bit - w;
}
return (area_found) ? 0 : -ENOMEM;
}
/*
* w = width in slots
* h = height in slots
* a = align in slots (mask, 2^n-1, 0 is unaligned)
* offset = offset in bytes from 4KiB
* pos = position in bitmap for buffer
* map = bitmap ptr
* num_bits = size of bitmap
* stride = bits in one row of container
*/
static int l2r_t2b(u16 w, u16 h, u16 a, s16 offset,
unsigned long *pos, unsigned long slot_bytes,
unsigned long *map, size_t num_bits, size_t slot_stride)
{
int i;
unsigned long index;
bool area_free = false;
unsigned long slots_per_band = PAGE_SIZE / slot_bytes;
unsigned long bit_offset = (offset > 0) ? offset / slot_bytes : 0;
unsigned long curr_bit = bit_offset;
/* reset alignment to 1 if we are matching a specific offset */
/* adjust alignment - 1 to get to the format expected in bitmaps */
a = (offset > 0) ? 0 : a - 1;
/* FIXME Return error if slots_per_band > stride */
while (curr_bit < num_bits) {
*pos = bitmap_find_next_zero_area(map, num_bits, curr_bit, w,
a);
/* skip forward if we are not at right offset */
if (bit_offset > 0 && (*pos % slots_per_band != bit_offset)) {
curr_bit = ALIGN(*pos, slots_per_band) + bit_offset;
continue;
}
/* skip forward to next row if we overlap end of row */
if ((*pos % slot_stride) + w > slot_stride) {
curr_bit = ALIGN(*pos, slot_stride) + bit_offset;
continue;
}
/* TODO: Handle overlapping 4K boundaries */
/* break out of look if we will go past end of container */
if ((*pos + slot_stride * h) > num_bits)
break;
/* generate mask that represents out matching pattern */
bitmap_clear(mask, 0, slot_stride);
bitmap_set(mask, (*pos % BITS_PER_LONG), w);
/* assume the area is free until we find an overlap */
area_free = true;
/* check subsequent rows to see if complete area is free */
for (i = 1; i < h; i++) {
index = *pos / BITS_PER_LONG + i * 8;
if (bitmap_intersects(&map[index], mask,
(*pos % BITS_PER_LONG) + w)) {
area_free = false;
break;
}
}
if (area_free)
break;
/* go forward past this match */
if (bit_offset > 0)
curr_bit = ALIGN(*pos, slots_per_band) + bit_offset;
else
curr_bit = *pos + a + 1;
}
if (area_free) {
/* set area as in-use. iterate over rows */
for (i = 0, index = *pos; i < h; i++, index += slot_stride)
bitmap_set(map, index, w);
}
return (area_free) ? 0 : -ENOMEM;
}
static s32 sita_reserve_1d(struct tcm *tcm, u32 num_slots,
struct tcm_area *area)
{
unsigned long pos;
int ret;
spin_lock(&(tcm->lock));
ret = r2l_b2t_1d(num_slots, &pos, tcm->bitmap, tcm->map_size);
if (!ret) {
area->p0.x = pos % tcm->width;
area->p0.y = pos / tcm->width;
area->p1.x = (pos + num_slots - 1) % tcm->width;
area->p1.y = (pos + num_slots - 1) / tcm->width;
}
spin_unlock(&(tcm->lock));
return ret;
}
static s32 sita_reserve_2d(struct tcm *tcm, u16 h, u16 w, u16 align,
s16 offset, u16 slot_bytes,
struct tcm_area *area)
{
unsigned long pos;
int ret;
spin_lock(&(tcm->lock));
ret = l2r_t2b(w, h, align, offset, &pos, slot_bytes, tcm->bitmap,
tcm->map_size, tcm->width);
if (!ret) {
area->p0.x = pos % tcm->width;
area->p0.y = pos / tcm->width;
area->p1.x = area->p0.x + w - 1;
area->p1.y = area->p0.y + h - 1;
}
spin_unlock(&(tcm->lock));
return ret;
}
static void sita_deinit(struct tcm *tcm)
{
kfree(tcm);
}
static s32 sita_free(struct tcm *tcm, struct tcm_area *area)
{
unsigned long pos;
u16 w, h;
pos = area->p0.x + area->p0.y * tcm->width;
if (area->is2d) {
w = area->p1.x - area->p0.x + 1;
h = area->p1.y - area->p0.y + 1;
} else {
w = area->p1.x + area->p1.y * tcm->width - pos + 1;
h = 1;
}
spin_lock(&(tcm->lock));
free_slots(pos, w, h, tcm->bitmap, tcm->width);
spin_unlock(&(tcm->lock));
return 0;
}
struct tcm *sita_init(u16 width, u16 height)
{
struct tcm *tcm;
size_t map_size = BITS_TO_LONGS(width*height) * sizeof(unsigned long);
if (width == 0 || height == 0)
return NULL;
tcm = kzalloc(sizeof(*tcm) + map_size, GFP_KERNEL);
if (!tcm)
goto error;
/* 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;
spin_lock_init(&tcm->lock);
tcm->bitmap = (unsigned long *)(tcm + 1);
bitmap_clear(tcm->bitmap, 0, width*height);
tcm->map_size = width*height;
return tcm;
error:
return NULL;
}