blob: 8c4144332ae816b9e3ef9af397ceaf4a414c9ab2 [file] [log] [blame]
/*
* cache.c
*
* Include routines to caching the reading and writing the sectors.
* we use the "Least Recently Used" algorithm as our cache algorithm
* that discards the least recently used items. By building a static
* double list in an array to track the least recently used item.
* implimentation file.
*
* Copyright (C) knightray@gmail.com
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include "pubstruct.h"
#include "debug.h"
#include "cache.h"
#ifndef DBG_CACHE
#undef DBG
#define DBG nulldbg
#endif
/* Private interface declaration. */
static BOOL
_find_in_list(
IN tcache_t * pcache,
IN uint32 sec,
OUT int16 * pret
);
static int16
_hash_function(
IN tcache_t * pcache,
IN uint32 sec
);
static void
_del_from_list(
IN tcache_t * pcache,
IN int16 sec_index
);
static void
_insert_into_list(
IN tcache_t * pcache,
IN int16 sec_index
);
static int16
_do_cache_miss(
IN tcache_t * pcache,
IN int16 secindex,
IN uint32 sec,
IN BOOL is_read /* Marvell fixed */
);
static void
_do_cache_hint(
IN tcache_t * pcache,
IN int16 secindex
);
/*----------------------------------------------------------------------------------------------------*/
tcache_t *
cache_init(
IN tdev_handle_t hdev,
IN uint32 seccnt,
IN uint32 sector_size)
{
tcache_t * pcache;
int16 seci;
pcache = (tcache_t *)Malloc(sizeof(tcache_t));
pcache->seccnt = seccnt;
pcache->hdev = hdev;
pcache->sector_size = sector_size;
if (seccnt > 0) {
pcache->secbufs = (sec_buf_t *)Malloc(sizeof(sec_buf_t) * seccnt);
if (pcache->secbufs == NULL) {
Free(pcache);
return NULL;
}
Memset(pcache->secbufs, 0, sizeof(sec_buf_t) * seccnt);
for (seci = 0; seci < pcache->seccnt; seci++) {
pcache->secbufs[seci].secbuf = (ubyte *)Malloc(pcache->sector_size);
pcache->secbufs[seci].sec = 0;
}
pcache->head = SECBUF_EOF;
pcache->tail = SECBUF_EOF;
pcache->use_cache = 1;
}
else {
pcache->use_cache = 0;
}
return pcache;
}
int32
cache_readsector(
IN tcache_t * pcache,
IN int32 addr,
OUT ubyte * ptr)
{
int16 secindex;
int32 ret;
ret = CACHE_OK;
if (!pcache->use_cache) {
return HAI_readsector(pcache->hdev, addr, ptr);
}
if (!_find_in_list(pcache, addr, &secindex)) {
secindex = _do_cache_miss(pcache, secindex, addr, TRUE);
if (secindex < 0)
ret = secindex;
}
else {
_do_cache_hint(pcache, secindex);
}
if (ret >= 0) {
Memcpy(ptr, pcache->secbufs[secindex].secbuf, pcache->sector_size);
}
return ret;
}
int32
cache_writesector(
IN tcache_t * pcache,
IN int32 addr,
IN ubyte * ptr)
{
int16 secindex;
int32 ret;
ret = CACHE_OK;
if (!pcache->use_cache) {
/* Marvell fixed: was HAI_readsector */
return HAI_writesector(pcache->hdev, addr, ptr);
}
if (!_find_in_list(pcache, addr, &secindex)) {
secindex = _do_cache_miss(pcache, secindex, addr, FALSE);
if (secindex < 0)
ret = secindex;
}
else {
_do_cache_hint(pcache, secindex);
}
if (ret >= 0) {
Memcpy(pcache->secbufs[secindex].secbuf, ptr, pcache->sector_size);
pcache->secbufs[secindex].is_dirty = 1;
}
return ret;
}
int32
cache_flush(
IN tcache_t * pcache)
{
int32 ret;
int16 seci;
ret = CACHE_OK;
if (pcache->use_cache) {
for (seci = 0; seci < pcache->seccnt; seci++) {
if (pcache->secbufs[seci].is_dirty) {
DBG("%s:write sector %d\n", __FUNCTION__, seci);
if (HAI_writesector(pcache->hdev, pcache->secbufs[seci].sec,
pcache->secbufs[seci].secbuf) != HAI_OK) {
ret = ERR_CACHE_HARDWARE_FAIL;
break;
}
}
}
}
return ret;
}
int32
cache_destroy(
IN tcache_t * pcache)
{
int32 ret;
int16 seci;
ret = CACHE_OK;
if (pcache->use_cache) {
ret = cache_flush(pcache);
for (seci = 0; seci < pcache->seccnt; seci++) {
Free(pcache->secbufs[seci].secbuf);
}
Free(pcache->secbufs);
}
Free(pcache);
return ret;
}
/*----------------------------------------------------------------------------------------------------*/
int16
_do_cache_miss(
IN tcache_t * pcache,
IN int16 secindex,
IN uint32 sec,
IN BOOL is_read) /* Marvell fixed: prevent sector read on write miss */
{
int16 ret_sec_index;
ret_sec_index = secindex;
if (secindex == pcache->seccnt) {
/* The cache list is full, we have to get the last element to swap to disk. */
DBG("%s:cache is full, del %d from cache.\n", __FUNCTION__, pcache->tail);
if (pcache->secbufs[pcache->tail].is_dirty) {
if (HAI_writesector(pcache->hdev, pcache->secbufs[pcache->tail].sec,
pcache->secbufs[pcache->tail].secbuf) != HAI_OK) {
ret_sec_index = ERR_CACHE_HARDWARE_FAIL;
}
}
secindex = pcache->tail;
_del_from_list(pcache, pcache->tail);
}
if (ret_sec_index >= 0 && (!is_read || (HAI_readsector(pcache->hdev, sec,
pcache->secbufs[secindex].secbuf) == HAI_OK))) {
pcache->secbufs[secindex].sec = sec;
pcache->secbufs[secindex].is_dirty = 0;
_insert_into_list(pcache, secindex);
ret_sec_index = secindex;
}
else {
ret_sec_index = ERR_CACHE_HARDWARE_FAIL;
}
return ret_sec_index;
}
void
_do_cache_hint(
IN tcache_t * pcache,
IN int16 secindex)
{
_del_from_list(pcache, secindex);
_insert_into_list(pcache, secindex);
}
void
_insert_into_list(
IN tcache_t * pcache,
IN int16 sec_index)
{
if (pcache->head == SECBUF_EOF) {
/* list is empty. */
pcache->secbufs[sec_index].pre = SECBUF_EOF;
pcache->secbufs[sec_index].next = SECBUF_EOF;
pcache->head = sec_index;
pcache->tail = sec_index;
}
else {
/* insert into the head of the list. */
pcache->secbufs[sec_index].pre = SECBUF_EOF;
pcache->secbufs[sec_index].next = pcache->head;
pcache->secbufs[pcache->head].pre = sec_index;
pcache->head = sec_index;
}
}
void
_del_from_list(
IN tcache_t * pcache,
IN int16 sec_index)
{
//ASSERT(pcache->secbufs[sec_index].sec != 0);
DBG("%s:sec_index = %d\n", __FUNCTION__, sec_index);
if (pcache->secbufs[sec_index].pre == SECBUF_EOF) {
/* the element is the first one. */
pcache->head = pcache->secbufs[sec_index].next;
}
else {
pcache->secbufs[pcache->secbufs[sec_index].pre].next = pcache->secbufs[sec_index].next;
}
if (pcache->secbufs[sec_index].next != SECBUF_EOF) {
/* modify the next element's pre field only when next element is not EOF. */
pcache->secbufs[pcache->secbufs[sec_index].next].pre = pcache->secbufs[sec_index].pre;
}
else {
pcache->tail = pcache->secbufs[sec_index].pre;
}
}
int16
_hash_function(
IN tcache_t * pcache,
IN uint32 sec)
{
return sec % pcache->seccnt;
}
BOOL
_find_in_list(
IN tcache_t * pcache,
IN uint32 sec,
OUT int16 * pret)
{
int16 start_index;
BOOL is_found;
int16 cur_index;
start_index = _hash_function(pcache, sec);
cur_index = start_index;
while (1) {
DBG("%s:cur_index = %d\n", __FUNCTION__, cur_index);
if (pcache->secbufs[cur_index].sec == sec) {
is_found = TRUE;
*pret = cur_index;
break;
}
else if (pcache->secbufs[cur_index].sec == 0) {
is_found = FALSE;
*pret = cur_index;
break;
}
cur_index++;
cur_index = cur_index % pcache->seccnt;
if (cur_index == start_index) {
is_found = FALSE;
*pret = pcache->seccnt;
break;
}
}
return is_found;
}