| /* |
| * 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; |
| } |
| |