blob: 28ff62c22b81486a9d2ad482beb003ec3ba565fb [file] [log] [blame]
/*
* Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** @file
*
* Cache-block management operations (Zicbom)
*
* We support explicit cache management operations on I/O buffers.
* These are guaranteed to be aligned on their own size and at least
* as large as a (reasonable) cacheline, and therefore cannot cross a
* cacheline boundary.
*/
#include <stdint.h>
#include <ipxe/hart.h>
#include <ipxe/xthead.h>
#include <ipxe/zicbom.h>
/** Minimum supported cacheline size
*
* We assume that cache management operations will ignore the least
* significant address bits, and so we are safe to assume a cacheline
* size that is smaller than the size actually used by the CPU.
*
* Cache clean and invalidate loops could be made faster by detecting
* the actual cacheline size.
*/
#define CACHE_STRIDE 32
/** A cache management extension */
struct cache_extension {
/**
* Clean data cache (i.e. write cached content back to memory)
*
* @v first First byte
* @v last Last byte
*/
void ( * clean ) ( const void *first, const void *last );
/**
* Invalidate data cache (i.e. discard any cached content)
*
* @v first First byte
* @v last Last byte
*/
void ( * invalidate ) ( void *first, void *last );
};
/** Define an operation to clean the data cache */
#define CACHE_CLEAN( extension, insn ) \
static void extension ## _clean ( const void *first, \
const void *last ) { \
\
__asm__ __volatile__ ( ".option arch, +" #extension "\n\t" \
"\n1:\n\t" \
insn "\n\t" \
"addi %0, %0, %2\n\t" \
"bltu %0, %1, 1b\n\t" \
: "+r" ( first ) \
: "r" ( last ), "i" ( CACHE_STRIDE ) ); \
}
/** Define an operation to invalidate the data cache */
#define CACHE_INVALIDATE( extension, insn ) \
static void extension ## _invalidate ( void *first, \
void *last ) { \
\
__asm__ __volatile__ ( ".option arch, +" #extension "\n\t" \
"\n1:\n\t" \
insn "\n\t" \
"addi %0, %0, %2\n\t" \
"bltu %0, %1, 1b\n\t" \
: "+r" ( first ) \
: "r" ( last ), "i" ( CACHE_STRIDE ) \
: "memory" ); \
}
/** Define a cache management extension */
#define CACHE_EXTENSION( extension, clean_insn, invalidate_insn ) \
CACHE_CLEAN ( extension, clean_insn ); \
CACHE_INVALIDATE ( extension, invalidate_insn ); \
static struct cache_extension extension = { \
.clean = extension ## _clean, \
.invalidate = extension ## _invalidate, \
};
/** The standard Zicbom extension */
CACHE_EXTENSION ( zicbom, "cbo.clean (%0)", "cbo.inval (%0)" );
/** The T-Head cache management extension */
CACHE_EXTENSION ( xtheadcmo, "th.dcache.cva %0", "th.dcache.iva %0" );
/**
* Clean data cache (with fully coherent memory)
*
* @v first First byte
* @v last Last byte
*/
static void cache_coherent_clean ( const void *first __unused,
const void *last __unused ) {
/* Nothing to do */
}
/**
* Invalidate data cache (with fully coherent memory)
*
* @v first First byte
* @v last Last byte
*/
static void cache_coherent_invalidate ( void *first __unused,
void *last __unused ) {
/* Nothing to do */
}
/** Dummy cache management extension for fully coherent memory */
static struct cache_extension cache_coherent = {
.clean = cache_coherent_clean,
.invalidate = cache_coherent_invalidate,
};
static void cache_auto_detect ( void );
static void cache_auto_clean ( const void *first, const void *last );
static void cache_auto_invalidate ( void *first, void *last );
/** The autodetect cache management extension */
static struct cache_extension cache_auto = {
.clean = cache_auto_clean,
.invalidate = cache_auto_invalidate,
};
/** Active cache management extension */
static struct cache_extension *cache_extension = &cache_auto;
/**
* Clean data cache (i.e. write cached content back to memory)
*
* @v start Start address
* @v len Length
*/
void cache_clean ( const void *start, size_t len ) {
const void *first;
const void *last;
/* Do nothing for zero-length buffers */
if ( ! len )
return;
/* Construct address range */
first = ( ( const void * )
( ( ( intptr_t ) start ) & ~( CACHE_STRIDE - 1 ) ) );
last = ( start + len - 1 );
/* Clean cache lines */
cache_extension->clean ( first, last );
}
/**
* Invalidate data cache (i.e. discard any cached content)
*
* @v start Start address
* @v len Length
*/
void cache_invalidate ( void *start, size_t len ) {
void *first;
void *last;
/* Do nothing for zero-length buffers */
if ( ! len )
return;
/* Construct address range */
first = ( ( void * )
( ( ( intptr_t ) start ) & ~( CACHE_STRIDE - 1 ) ) );
last = ( start + len - 1 );
/* Invalidate cache lines */
cache_extension->invalidate ( first, last );
}
/**
* Autodetect and clean data cache
*
* @v first First byte
* @v last Last byte
*/
static void cache_auto_clean ( const void *first, const void *last ) {
/* Detect cache extension */
cache_auto_detect();
/* Clean data cache */
cache_extension->clean ( first, last );
}
/**
* Autodetect and invalidate data cache
*
* @v first First byte
* @v last Last byte
*/
static void cache_auto_invalidate ( void *first, void *last ) {
/* Detect cache extension */
cache_auto_detect();
/* Clean data cache */
cache_extension->invalidate ( first, last );
}
/**
* Autodetect cache
*
*/
static void cache_auto_detect ( void ) {
int rc;
/* Check for standard Zicbom extension */
if ( ( rc = hart_supported ( "_zicbom" ) ) == 0 ) {
DBGC ( &cache_extension, "CACHE detected Zicbom\n" );
cache_extension = &zicbom;
return;
}
/* Check for T-Head cache management extension */
if ( xthead_supported ( THEAD_SXSTATUS_THEADISAEE ) ) {
DBGC ( &cache_extension, "CACHE detected XTheadCmo\n" );
cache_extension = &xtheadcmo;
return;
}
/* Assume coherent memory if no supported extension detected */
DBGC ( &cache_extension, "CACHE assuming coherent memory\n" );
cache_extension = &cache_coherent;
}