diff options
author | David Daney <ddaney@caviumnetworks.com> | 2008-12-24 02:22:14 +0300 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2009-01-11 12:57:20 +0300 |
commit | 58f07778ce9d32c22cecb1d8ef348001f0e705c9 (patch) | |
tree | 8b4462addd9058cbdf8f6085169f48c318fe7478 /arch/mips/cavium-octeon | |
parent | 54293ec3074a5fe61abd297502f68b2529a3dab3 (diff) | |
download | linux-58f07778ce9d32c22cecb1d8ef348001f0e705c9.tar.xz |
MIPS: Add Cavium OCTEON processor support files to arch/mips/cavium-octeon/executive and asm/octeon.
These files are used to coordinate resource sharing between all of
the programs running on the OCTEON SOC. The OCTEON processor has many
CPU cores (current parts have up to 16, but more are possible). It
also has a variety of on-chip hardware blocks for things like network
acceleration, encryption and RAID.
One typical configuration is to run Linux on several of the CPU cores,
and other dedicated applications on the other cores.
Resource allocation between the various programs running on the system
(Linux kernel and other dedicated applications) needs to be
coordinated. The code we use to do this we call the 'executive'. All
of this resource allocation and sharing code is gathered together in
the executive directory.
Included in the patch set are the following files:
cvmx-bootmem.c and cvmx-sysinfo.c -- Coordinate memory allocation.
All memory used by the Linux kernel is obtained here at boot time.
cvmx-l2c.c -- Coordinates operations on the shared level 2 cache.
octeon-model.c -- Probes chip capabilities and version.
The corresponding headers are in asm/octeon.
Signed-off-by: Tomaso Paoletti <tpaoletti@caviumnetworks.com>
Signed-off-by: David Daney <ddaney@caviumnetworks.com>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
create mode 100644 arch/mips/cavium-octeon/executive/Makefile
create mode 100644 arch/mips/cavium-octeon/executive/cvmx-bootmem.c
create mode 100644 arch/mips/cavium-octeon/executive/cvmx-l2c.c
create mode 100644 arch/mips/cavium-octeon/executive/cvmx-sysinfo.c
create mode 100644 arch/mips/cavium-octeon/executive/octeon-model.c
create mode 100644 arch/mips/include/asm/octeon/cvmx-asm.h
create mode 100644 arch/mips/include/asm/octeon/cvmx-bootinfo.h
create mode 100644 arch/mips/include/asm/octeon/cvmx-bootmem.h
create mode 100644 arch/mips/include/asm/octeon/cvmx-l2c.h
create mode 100644 arch/mips/include/asm/octeon/cvmx-packet.h
create mode 100644 arch/mips/include/asm/octeon/cvmx-spinlock.h
create mode 100644 arch/mips/include/asm/octeon/cvmx-sysinfo.h
create mode 100644 arch/mips/include/asm/octeon/cvmx.h
create mode 100644 arch/mips/include/asm/octeon/octeon-feature.h
create mode 100644 arch/mips/include/asm/octeon/octeon-model.h
Diffstat (limited to 'arch/mips/cavium-octeon')
-rw-r--r-- | arch/mips/cavium-octeon/executive/Makefile | 13 | ||||
-rw-r--r-- | arch/mips/cavium-octeon/executive/cvmx-bootmem.c | 586 | ||||
-rw-r--r-- | arch/mips/cavium-octeon/executive/cvmx-l2c.c | 734 | ||||
-rw-r--r-- | arch/mips/cavium-octeon/executive/cvmx-sysinfo.c | 116 | ||||
-rw-r--r-- | arch/mips/cavium-octeon/executive/octeon-model.c | 358 |
5 files changed, 1807 insertions, 0 deletions
diff --git a/arch/mips/cavium-octeon/executive/Makefile b/arch/mips/cavium-octeon/executive/Makefile new file mode 100644 index 000000000000..80d6cb26766b --- /dev/null +++ b/arch/mips/cavium-octeon/executive/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for the Cavium Octeon specific kernel interface routines +# under Linux. +# +# This file is subject to the terms and conditions of the GNU General Public +# License. See the file "COPYING" in the main directory of this archive +# for more details. +# +# Copyright (C) 2005-2008 Cavium Networks +# + +obj-y += cvmx-bootmem.o cvmx-l2c.o cvmx-sysinfo.o octeon-model.o + diff --git a/arch/mips/cavium-octeon/executive/cvmx-bootmem.c b/arch/mips/cavium-octeon/executive/cvmx-bootmem.c new file mode 100644 index 000000000000..4f5a08b37ccd --- /dev/null +++ b/arch/mips/cavium-octeon/executive/cvmx-bootmem.c @@ -0,0 +1,586 @@ +/***********************license start*************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2008 Cavium Networks + * + * This file 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 file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information + ***********************license end**************************************/ + +/* + * Simple allocate only memory allocator. Used to allocate memory at + * application start time. + */ + +#include <linux/kernel.h> + +#include <asm/octeon/cvmx.h> +#include <asm/octeon/cvmx-spinlock.h> +#include <asm/octeon/cvmx-bootmem.h> + +/*#define DEBUG */ + + +static struct cvmx_bootmem_desc *cvmx_bootmem_desc; + +/* See header file for descriptions of functions */ + +/* + * Wrapper functions are provided for reading/writing the size and + * next block values as these may not be directly addressible (in 32 + * bit applications, for instance.) Offsets of data elements in + * bootmem list, must match cvmx_bootmem_block_header_t. + */ +#define NEXT_OFFSET 0 +#define SIZE_OFFSET 8 + +static void cvmx_bootmem_phy_set_size(uint64_t addr, uint64_t size) +{ + cvmx_write64_uint64((addr + SIZE_OFFSET) | (1ull << 63), size); +} + +static void cvmx_bootmem_phy_set_next(uint64_t addr, uint64_t next) +{ + cvmx_write64_uint64((addr + NEXT_OFFSET) | (1ull << 63), next); +} + +static uint64_t cvmx_bootmem_phy_get_size(uint64_t addr) +{ + return cvmx_read64_uint64((addr + SIZE_OFFSET) | (1ull << 63)); +} + +static uint64_t cvmx_bootmem_phy_get_next(uint64_t addr) +{ + return cvmx_read64_uint64((addr + NEXT_OFFSET) | (1ull << 63)); +} + +void *cvmx_bootmem_alloc_range(uint64_t size, uint64_t alignment, + uint64_t min_addr, uint64_t max_addr) +{ + int64_t address; + address = + cvmx_bootmem_phy_alloc(size, min_addr, max_addr, alignment, 0); + + if (address > 0) + return cvmx_phys_to_ptr(address); + else + return NULL; +} + +void *cvmx_bootmem_alloc_address(uint64_t size, uint64_t address, + uint64_t alignment) +{ + return cvmx_bootmem_alloc_range(size, alignment, address, + address + size); +} + +void *cvmx_bootmem_alloc(uint64_t size, uint64_t alignment) +{ + return cvmx_bootmem_alloc_range(size, alignment, 0, 0); +} + +int cvmx_bootmem_free_named(char *name) +{ + return cvmx_bootmem_phy_named_block_free(name, 0); +} + +struct cvmx_bootmem_named_block_desc *cvmx_bootmem_find_named_block(char *name) +{ + return cvmx_bootmem_phy_named_block_find(name, 0); +} + +void cvmx_bootmem_lock(void) +{ + cvmx_spinlock_lock((cvmx_spinlock_t *) &(cvmx_bootmem_desc->lock)); +} + +void cvmx_bootmem_unlock(void) +{ + cvmx_spinlock_unlock((cvmx_spinlock_t *) &(cvmx_bootmem_desc->lock)); +} + +int cvmx_bootmem_init(void *mem_desc_ptr) +{ + /* Here we set the global pointer to the bootmem descriptor + * block. This pointer will be used directly, so we will set + * it up to be directly usable by the application. It is set + * up as follows for the various runtime/ABI combinations: + * + * Linux 64 bit: Set XKPHYS bit + * Linux 32 bit: use mmap to create mapping, use virtual address + * CVMX 64 bit: use physical address directly + * CVMX 32 bit: use physical address directly + * + * Note that the CVMX environment assumes the use of 1-1 TLB + * mappings so that the physical addresses can be used + * directly + */ + if (!cvmx_bootmem_desc) { +#if defined(CVMX_ABI_64) + /* Set XKPHYS bit */ + cvmx_bootmem_desc = cvmx_phys_to_ptr(CAST64(mem_desc_ptr)); +#else + cvmx_bootmem_desc = (struct cvmx_bootmem_desc *) mem_desc_ptr; +#endif + } + + return 0; +} + +/* + * The cvmx_bootmem_phy* functions below return 64 bit physical + * addresses, and expose more features that the cvmx_bootmem_functions + * above. These are required for full memory space access in 32 bit + * applications, as well as for using some advance features. Most + * applications should not need to use these. + */ + +int64_t cvmx_bootmem_phy_alloc(uint64_t req_size, uint64_t address_min, + uint64_t address_max, uint64_t alignment, + uint32_t flags) +{ + + uint64_t head_addr; + uint64_t ent_addr; + /* points to previous list entry, NULL current entry is head of list */ + uint64_t prev_addr = 0; + uint64_t new_ent_addr = 0; + uint64_t desired_min_addr; + +#ifdef DEBUG + cvmx_dprintf("cvmx_bootmem_phy_alloc: req_size: 0x%llx, " + "min_addr: 0x%llx, max_addr: 0x%llx, align: 0x%llx\n", + (unsigned long long)req_size, + (unsigned long long)address_min, + (unsigned long long)address_max, + (unsigned long long)alignment); +#endif + + if (cvmx_bootmem_desc->major_version > 3) { + cvmx_dprintf("ERROR: Incompatible bootmem descriptor " + "version: %d.%d at addr: %p\n", + (int)cvmx_bootmem_desc->major_version, + (int)cvmx_bootmem_desc->minor_version, + cvmx_bootmem_desc); + goto error_out; + } + + /* + * Do a variety of checks to validate the arguments. The + * allocator code will later assume that these checks have + * been made. We validate that the requested constraints are + * not self-contradictory before we look through the list of + * available memory. + */ + + /* 0 is not a valid req_size for this allocator */ + if (!req_size) + goto error_out; + + /* Round req_size up to mult of minimum alignment bytes */ + req_size = (req_size + (CVMX_BOOTMEM_ALIGNMENT_SIZE - 1)) & + ~(CVMX_BOOTMEM_ALIGNMENT_SIZE - 1); + + /* + * Convert !0 address_min and 0 address_max to special case of + * range that specifies an exact memory block to allocate. Do + * this before other checks and adjustments so that this + * tranformation will be validated. + */ + if (address_min && !address_max) + address_max = address_min + req_size; + else if (!address_min && !address_max) + address_max = ~0ull; /* If no limits given, use max limits */ + + + /* + * Enforce minimum alignment (this also keeps the minimum free block + * req_size the same as the alignment req_size. + */ + if (alignment < CVMX_BOOTMEM_ALIGNMENT_SIZE) + alignment = CVMX_BOOTMEM_ALIGNMENT_SIZE; + + /* + * Adjust address minimum based on requested alignment (round + * up to meet alignment). Do this here so we can reject + * impossible requests up front. (NOP for address_min == 0) + */ + if (alignment) + address_min = __ALIGN_MASK(address_min, (alignment - 1)); + + /* + * Reject inconsistent args. We have adjusted these, so this + * may fail due to our internal changes even if this check + * would pass for the values the user supplied. + */ + if (req_size > address_max - address_min) + goto error_out; + + /* Walk through the list entries - first fit found is returned */ + + if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) + cvmx_bootmem_lock(); + head_addr = cvmx_bootmem_desc->head_addr; + ent_addr = head_addr; + for (; ent_addr; + prev_addr = ent_addr, + ent_addr = cvmx_bootmem_phy_get_next(ent_addr)) { + uint64_t usable_base, usable_max; + uint64_t ent_size = cvmx_bootmem_phy_get_size(ent_addr); + + if (cvmx_bootmem_phy_get_next(ent_addr) + && ent_addr > cvmx_bootmem_phy_get_next(ent_addr)) { + cvmx_dprintf("Internal bootmem_alloc() error: ent: " + "0x%llx, next: 0x%llx\n", + (unsigned long long)ent_addr, + (unsigned long long) + cvmx_bootmem_phy_get_next(ent_addr)); + goto error_out; + } + + /* + * Determine if this is an entry that can satisify the + * request Check to make sure entry is large enough to + * satisfy request. + */ + usable_base = + __ALIGN_MASK(max(address_min, ent_addr), alignment - 1); + usable_max = min(address_max, ent_addr + ent_size); + /* + * We should be able to allocate block at address + * usable_base. + */ + + desired_min_addr = usable_base; + /* + * Determine if request can be satisfied from the + * current entry. + */ + if (!((ent_addr + ent_size) > usable_base + && ent_addr < address_max + && req_size <= usable_max - usable_base)) + continue; + /* + * We have found an entry that has room to satisfy the + * request, so allocate it from this entry. If end + * CVMX_BOOTMEM_FLAG_END_ALLOC set, then allocate from + * the end of this block rather than the beginning. + */ + if (flags & CVMX_BOOTMEM_FLAG_END_ALLOC) { + desired_min_addr = usable_max - req_size; + /* + * Align desired address down to required + * alignment. + */ + desired_min_addr &= ~(alignment - 1); + } + + /* Match at start of entry */ + if (desired_min_addr == ent_addr) { + if (req_size < ent_size) { + /* + * big enough to create a new block + * from top portion of block. + */ + new_ent_addr = ent_addr + req_size; + cvmx_bootmem_phy_set_next(new_ent_addr, + cvmx_bootmem_phy_get_next(ent_addr)); + cvmx_bootmem_phy_set_size(new_ent_addr, + ent_size - + req_size); + + /* + * Adjust next pointer as following + * code uses this. + */ + cvmx_bootmem_phy_set_next(ent_addr, + new_ent_addr); + } + + /* + * adjust prev ptr or head to remove this + * entry from list. + */ + if (prev_addr) + cvmx_bootmem_phy_set_next(prev_addr, + cvmx_bootmem_phy_get_next(ent_addr)); + else + /* + * head of list being returned, so + * update head ptr. + */ + cvmx_bootmem_desc->head_addr = + cvmx_bootmem_phy_get_next(ent_addr); + + if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) + cvmx_bootmem_unlock(); + return desired_min_addr; + } + /* + * block returned doesn't start at beginning of entry, + * so we know that we will be splitting a block off + * the front of this one. Create a new block from the + * beginning, add to list, and go to top of loop + * again. + * + * create new block from high portion of + * block, so that top block starts at desired + * addr. + */ + new_ent_addr = desired_min_addr; + cvmx_bootmem_phy_set_next(new_ent_addr, + cvmx_bootmem_phy_get_next + (ent_addr)); + cvmx_bootmem_phy_set_size(new_ent_addr, + cvmx_bootmem_phy_get_size + (ent_addr) - + (desired_min_addr - + ent_addr)); + cvmx_bootmem_phy_set_size(ent_addr, + desired_min_addr - ent_addr); + cvmx_bootmem_phy_set_next(ent_addr, new_ent_addr); + /* Loop again to handle actual alloc from new block */ + } +error_out: + /* We didn't find anything, so return error */ + if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) + cvmx_bootmem_unlock(); + return -1; +} + +int __cvmx_bootmem_phy_free(uint64_t phy_addr, uint64_t size, uint32_t flags) +{ + uint64_t cur_addr; + uint64_t prev_addr = 0; /* zero is invalid */ + int retval = 0; + +#ifdef DEBUG + cvmx_dprintf("__cvmx_bootmem_phy_free addr: 0x%llx, size: 0x%llx\n", + (unsigned long long)phy_addr, (unsigned long long)size); +#endif + if (cvmx_bootmem_desc->major_version > 3) { + cvmx_dprintf("ERROR: Incompatible bootmem descriptor " + "version: %d.%d at addr: %p\n", + (int)cvmx_bootmem_desc->major_version, + (int)cvmx_bootmem_desc->minor_version, + cvmx_bootmem_desc); + return 0; + } + + /* 0 is not a valid size for this allocator */ + if (!size) + return 0; + + if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) + cvmx_bootmem_lock(); + cur_addr = cvmx_bootmem_desc->head_addr; + if (cur_addr == 0 || phy_addr < cur_addr) { + /* add at front of list - special case with changing head ptr */ + if (cur_addr && phy_addr + size > cur_addr) + goto bootmem_free_done; /* error, overlapping section */ + else if (phy_addr + size == cur_addr) { + /* Add to front of existing first block */ + cvmx_bootmem_phy_set_next(phy_addr, + cvmx_bootmem_phy_get_next + (cur_addr)); + cvmx_bootmem_phy_set_size(phy_addr, + cvmx_bootmem_phy_get_size + (cur_addr) + size); + cvmx_bootmem_desc->head_addr = phy_addr; + + } else { + /* New block before first block. OK if cur_addr is 0 */ + cvmx_bootmem_phy_set_next(phy_addr, cur_addr); + cvmx_bootmem_phy_set_size(phy_addr, size); + cvmx_bootmem_desc->head_addr = phy_addr; + } + retval = 1; + goto bootmem_free_done; + } + + /* Find place in list to add block */ + while (cur_addr && phy_addr > cur_addr) { + prev_addr = cur_addr; + cur_addr = cvmx_bootmem_phy_get_next(cur_addr); + } + + if (!cur_addr) { + /* + * We have reached the end of the list, add on to end, + * checking to see if we need to combine with last + * block + */ + if (prev_addr + cvmx_bootmem_phy_get_size(prev_addr) == + phy_addr) { + cvmx_bootmem_phy_set_size(prev_addr, + cvmx_bootmem_phy_get_size + (prev_addr) + size); + } else { + cvmx_bootmem_phy_set_next(prev_addr, phy_addr); + cvmx_bootmem_phy_set_size(phy_addr, size); + cvmx_bootmem_phy_set_next(phy_addr, 0); + } + retval = 1; + goto bootmem_free_done; + } else { + /* + * insert between prev and cur nodes, checking for + * merge with either/both. + */ + if (prev_addr + cvmx_bootmem_phy_get_size(prev_addr) == + phy_addr) { + /* Merge with previous */ + cvmx_bootmem_phy_set_size(prev_addr, + cvmx_bootmem_phy_get_size + (prev_addr) + size); + if (phy_addr + size == cur_addr) { + /* Also merge with current */ + cvmx_bootmem_phy_set_size(prev_addr, + cvmx_bootmem_phy_get_size(cur_addr) + + cvmx_bootmem_phy_get_size(prev_addr)); + cvmx_bootmem_phy_set_next(prev_addr, + cvmx_bootmem_phy_get_next(cur_addr)); + } + retval = 1; + goto bootmem_free_done; + } else if (phy_addr + size == cur_addr) { + /* Merge with current */ + cvmx_bootmem_phy_set_size(phy_addr, + cvmx_bootmem_phy_get_size + (cur_addr) + size); + cvmx_bootmem_phy_set_next(phy_addr, + cvmx_bootmem_phy_get_next + (cur_addr)); + cvmx_bootmem_phy_set_next(prev_addr, phy_addr); + retval = 1; + goto bootmem_free_done; + } + + /* It is a standalone block, add in between prev and cur */ + cvmx_bootmem_phy_set_size(phy_addr, size); + cvmx_bootmem_phy_set_next(phy_addr, cur_addr); + cvmx_bootmem_phy_set_next(prev_addr, phy_addr); + + } + retval = 1; + +bootmem_free_done: + if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) + cvmx_bootmem_unlock(); + return retval; + +} + +struct cvmx_bootmem_named_block_desc * + cvmx_bootmem_phy_named_block_find(char *name, uint32_t flags) +{ + unsigned int i; + struct cvmx_bootmem_named_block_desc *named_block_array_ptr; + +#ifdef DEBUG + cvmx_dprintf("cvmx_bootmem_phy_named_block_find: %s\n", name); +#endif + /* + * Lock the structure to make sure that it is not being + * changed while we are examining it. + */ + if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) + cvmx_bootmem_lock(); + + /* Use XKPHYS for 64 bit linux */ + named_block_array_ptr = (struct cvmx_bootmem_named_block_desc *) + cvmx_phys_to_ptr(cvmx_bootmem_desc->named_block_array_addr); + +#ifdef DEBUG + cvmx_dprintf + ("cvmx_bootmem_phy_named_block_find: named_block_array_ptr: %p\n", + named_block_array_ptr); +#endif + if (cvmx_bootmem_desc->major_version == 3) { + for (i = 0; + i < cvmx_bootmem_desc->named_block_num_blocks; i++) { + if ((name && named_block_array_ptr[i].size + && !strncmp(name, named_block_array_ptr[i].name, + cvmx_bootmem_desc->named_block_name_len + - 1)) + || (!name && !named_block_array_ptr[i].size)) { + if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) + cvmx_bootmem_unlock(); + + return &(named_block_array_ptr[i]); + } + } + } else { + cvmx_dprintf("ERROR: Incompatible bootmem descriptor " + "version: %d.%d at addr: %p\n", + (int)cvmx_bootmem_desc->major_version, + (int)cvmx_bootmem_desc->minor_version, + cvmx_bootmem_desc); + } + if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING)) + cvmx_bootmem_unlock(); + + return NULL; +} + +int cvmx_bootmem_phy_named_block_free(char *name, uint32_t flags) +{ + struct cvmx_bootmem_named_block_desc *named_block_ptr; + + if (cvmx_bootmem_desc->major_version != 3) { + cvmx_dprintf("ERROR: Incompatible bootmem descriptor version: " + "%d.%d at addr: %p\n", + (int)cvmx_bootmem_desc->major_version, + (int)cvmx_bootmem_desc->minor_version, + cvmx_bootmem_desc); + return 0; + } +#ifdef DEBUG + cvmx_dprintf("cvmx_bootmem_phy_named_block_free: %s\n", name); +#endif + + /* + * Take lock here, as name lookup/block free/name free need to + * be atomic. + */ + cvmx_bootmem_lock(); + + named_block_ptr = + cvmx_bootmem_phy_named_block_find(name, + CVMX_BOOTMEM_FLAG_NO_LOCKING); + if (named_block_ptr) { +#ifdef DEBUG + cvmx_dprintf("cvmx_bootmem_phy_named_block_free: " + "%s, base: 0x%llx, size: 0x%llx\n", + name, + (unsigned long long)named_block_ptr->base_addr, + (unsigned long long)named_block_ptr->size); +#endif + __cvmx_bootmem_phy_free(named_block_ptr->base_addr, + named_block_ptr->size, + CVMX_BOOTMEM_FLAG_NO_LOCKING); + named_block_ptr->size = 0; + /* Set size to zero to indicate block not used. */ + } + + cvmx_bootmem_unlock(); + return named_block_ptr != NULL; /* 0 on failure, 1 on success */ +} diff --git a/arch/mips/cavium-octeon/executive/cvmx-l2c.c b/arch/mips/cavium-octeon/executive/cvmx-l2c.c new file mode 100644 index 000000000000..6abe56f1e097 --- /dev/null +++ b/arch/mips/cavium-octeon/executive/cvmx-l2c.c @@ -0,0 +1,734 @@ +/***********************license start*************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2008 Cavium Networks + * + * This file 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 file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information + ***********************license end**************************************/ + +/* + * Implementation of the Level 2 Cache (L2C) control, measurement, and + * debugging facilities. + */ + +#include <asm/octeon/cvmx.h> +#include <asm/octeon/cvmx-l2c.h> +#include <asm/octeon/cvmx-spinlock.h> + +/* + * This spinlock is used internally to ensure that only one core is + * performing certain L2 operations at a time. + * + * NOTE: This only protects calls from within a single application - + * if multiple applications or operating systems are running, then it + * is up to the user program to coordinate between them. + */ +static cvmx_spinlock_t cvmx_l2c_spinlock; + +static inline int l2_size_half(void) +{ + uint64_t val = cvmx_read_csr(CVMX_L2D_FUS3); + return !!(val & (1ull << 34)); +} + +int cvmx_l2c_get_core_way_partition(uint32_t core) +{ + uint32_t field; + + /* Validate the core number */ + if (core >= cvmx_octeon_num_cores()) + return -1; + + /* + * Use the lower two bits of the coreNumber to determine the + * bit offset of the UMSK[] field in the L2C_SPAR register. + */ + field = (core & 0x3) * 8; + + /* + * Return the UMSK[] field from the appropriate L2C_SPAR + * register based on the coreNumber. + */ + + switch (core & 0xC) { + case 0x0: + return (cvmx_read_csr(CVMX_L2C_SPAR0) & (0xFF << field)) >> + field; + case 0x4: + return (cvmx_read_csr(CVMX_L2C_SPAR1) & (0xFF << field)) >> + field; + case 0x8: + return (cvmx_read_csr(CVMX_L2C_SPAR2) & (0xFF << field)) >> + field; + case 0xC: + return (cvmx_read_csr(CVMX_L2C_SPAR3) & (0xFF << field)) >> + field; + } + return 0; +} + +int cvmx_l2c_set_core_way_partition(uint32_t core, uint32_t mask) +{ + uint32_t field; + uint32_t valid_mask; + + valid_mask = (0x1 << cvmx_l2c_get_num_assoc()) - 1; + + mask &= valid_mask; + + /* A UMSK setting which blocks all L2C Ways is an error. */ + if (mask == valid_mask) + return -1; + + /* Validate the core number */ + if (core >= cvmx_octeon_num_cores()) + return -1; + + /* Check to make sure current mask & new mask don't block all ways */ + if (((mask | cvmx_l2c_get_core_way_partition(core)) & valid_mask) == + valid_mask) + return -1; + + /* Use the lower two bits of core to determine the bit offset of the + * UMSK[] field in the L2C_SPAR register. + */ + field = (core & 0x3) * 8; + + /* Assign the new mask setting to the UMSK[] field in the appropriate + * L2C_SPAR register based on the core_num. + * + */ + switch (core & 0xC) { + case 0x0: + cvmx_write_csr(CVMX_L2C_SPAR0, + (cvmx_read_csr(CVMX_L2C_SPAR0) & + ~(0xFF << field)) | mask << field); + break; + case 0x4: + cvmx_write_csr(CVMX_L2C_SPAR1, + (cvmx_read_csr(CVMX_L2C_SPAR1) & + ~(0xFF << field)) | mask << field); + break; + case 0x8: + cvmx_write_csr(CVMX_L2C_SPAR2, + (cvmx_read_csr(CVMX_L2C_SPAR2) & + ~(0xFF << field)) | mask << field); + break; + case 0xC: + cvmx_write_csr(CVMX_L2C_SPAR3, + (cvmx_read_csr(CVMX_L2C_SPAR3) & + ~(0xFF << field)) | mask << field); + break; + } + return 0; +} + +int cvmx_l2c_set_hw_way_partition(uint32_t mask) +{ + uint32_t valid_mask; + + valid_mask = 0xff; + + if (OCTEON_IS_MODEL(OCTEON_CN58XX) || OCTEON_IS_MODEL(OCTEON_CN38XX)) { + if (l2_size_half()) + valid_mask = 0xf; + } else if (l2_size_half()) + valid_mask = 0x3; + + mask &= valid_mask; + + /* A UMSK setting which blocks all L2C Ways is an error. */ + if (mask == valid_mask) + return -1; + /* Check to make sure current mask & new mask don't block all ways */ + if (((mask | cvmx_l2c_get_hw_way_partition()) & valid_mask) == + valid_mask) + return -1; + + cvmx_write_csr(CVMX_L2C_SPAR4, + (cvmx_read_csr(CVMX_L2C_SPAR4) & ~0xFF) | mask); + return 0; +} + +int cvmx_l2c_get_hw_way_partition(void) +{ + return cvmx_read_csr(CVMX_L2C_SPAR4) & (0xFF); +} + +void cvmx_l2c_config_perf(uint32_t counter, enum cvmx_l2c_event event, + uint32_t clear_on_read) +{ + union cvmx_l2c_pfctl pfctl; + + pfctl.u64 = cvmx_read_csr(CVMX_L2C_PFCTL); + + switch (counter) { + case 0: + pfctl.s.cnt0sel = event; + pfctl.s.cnt0ena = 1; + if (!cvmx_octeon_is_pass1()) + pfctl.s.cnt0rdclr = clear_on_read; + break; + case 1: + pfctl.s.cnt1sel = event; + pfctl.s.cnt1ena = 1; + if (!cvmx_octeon_is_pass1()) + pfctl.s.cnt1rdclr = clear_on_read; + break; + case 2: + pfctl.s.cnt2sel = event; + pfctl.s.cnt2ena = 1; + if (!cvmx_octeon_is_pass1()) + pfctl.s.cnt2rdclr = clear_on_read; + break; + case 3: + default: + pfctl.s.cnt3sel = event; + pfctl.s.cnt3ena = 1; + if (!cvmx_octeon_is_pass1()) + pfctl.s.cnt3rdclr = clear_on_read; + break; + } + + cvmx_write_csr(CVMX_L2C_PFCTL, pfctl.u64); +} + +uint64_t cvmx_l2c_read_perf(uint32_t counter) +{ + switch (counter) { + case 0: + return cvmx_read_csr(CVMX_L2C_PFC0); + case 1: + return cvmx_read_csr(CVMX_L2C_PFC1); + case 2: + return cvmx_read_csr(CVMX_L2C_PFC2); + case 3: + default: + return cvmx_read_csr(CVMX_L2C_PFC3); + } +} + +/** + * @INTERNAL + * Helper function use to fault in cache lines for L2 cache locking + * + * @addr: Address of base of memory region to read into L2 cache + * @len: Length (in bytes) of region to fault in + */ +static void fault_in(uint64_t addr, int len) +{ + volatile char *ptr; + volatile char dummy; + /* + * Adjust addr and length so we get all cache lines even for + * small ranges spanning two cache lines + */ + len += addr & CVMX_CACHE_LINE_MASK; + addr &= ~CVMX_CACHE_LINE_MASK; + ptr = (volatile char *)cvmx_phys_to_ptr(addr); + /* + * Invalidate L1 cache to make sure all loads result in data + * being in L2. + */ + CVMX_DCACHE_INVALIDATE; + while (len > 0) { + dummy += *ptr; + len -= CVMX_CACHE_LINE_SIZE; + ptr += CVMX_CACHE_LINE_SIZE; + } +} + +int cvmx_l2c_lock_line(uint64_t addr) +{ + int retval = 0; + union cvmx_l2c_dbg l2cdbg; + union cvmx_l2c_lckbase lckbase; + union cvmx_l2c_lckoff lckoff; + union cvmx_l2t_err l2t_err; + l2cdbg.u64 = 0; + lckbase.u64 = 0; + lckoff.u64 = 0; + + cvmx_spinlock_lock(&cvmx_l2c_spinlock); + + /* Clear l2t error bits if set */ + l2t_err.u64 = cvmx_read_csr(CVMX_L2T_ERR); + l2t_err.s.lckerr = 1; + l2t_err.s.lckerr2 = 1; + cvmx_write_csr(CVMX_L2T_ERR, l2t_err.u64); + + addr &= ~CVMX_CACHE_LINE_MASK; + + /* Set this core as debug core */ + l2cdbg.s.ppnum = cvmx_get_core_num(); + CVMX_SYNC; + cvmx_write_csr(CVMX_L2C_DBG, l2cdbg.u64); + cvmx_read_csr(CVMX_L2C_DBG); + + lckoff.s.lck_offset = 0; /* Only lock 1 line at a time */ + cvmx_write_csr(CVMX_L2C_LCKOFF, lckoff.u64); + cvmx_read_csr(CVMX_L2C_LCKOFF); + + if (((union cvmx_l2c_cfg) (cvmx_read_csr(CVMX_L2C_CFG))).s.idxalias) { + int alias_shift = + CVMX_L2C_IDX_ADDR_SHIFT + 2 * CVMX_L2_SET_BITS - 1; + uint64_t addr_tmp = + addr ^ (addr & ((1 << alias_shift) - 1)) >> + CVMX_L2_SET_BITS; + lckbase.s.lck_base = addr_tmp >> 7; + } else { + lckbase.s.lck_base = addr >> 7; + } + + lckbase.s.lck_ena = 1; + cvmx_write_csr(CVMX_L2C_LCKBASE, lckbase.u64); + cvmx_read_csr(CVMX_L2C_LCKBASE); /* Make sure it gets there */ + + fault_in(addr, CVMX_CACHE_LINE_SIZE); + + lckbase.s.lck_ena = 0; + cvmx_write_csr(CVMX_L2C_LCKBASE, lckbase.u64); + cvmx_read_csr(CVMX_L2C_LCKBASE); /* Make sure it gets there */ + + /* Stop being debug core */ + cvmx_write_csr(CVMX_L2C_DBG, 0); + cvmx_read_csr(CVMX_L2C_DBG); + + l2t_err.u64 = cvmx_read_csr(CVMX_L2T_ERR); + if (l2t_err.s.lckerr || l2t_err.s.lckerr2) + retval = 1; /* We were unable to lock the line */ + + cvmx_spinlock_unlock(&cvmx_l2c_spinlock); + + return retval; +} + +int cvmx_l2c_lock_mem_region(uint64_t start, uint64_t len) +{ + int retval = 0; + + /* Round start/end to cache line boundaries */ + len += start & CVMX_CACHE_LINE_MASK; + start &= ~CVMX_CACHE_LINE_MASK; + len = (len + CVMX_CACHE_LINE_MASK) & ~CVMX_CACHE_LINE_MASK; + + while (len) { + retval += cvmx_l2c_lock_line(start); + start += CVMX_CACHE_LINE_SIZE; + len -= CVMX_CACHE_LINE_SIZE; + } + + return retval; +} + +void cvmx_l2c_flush(void) +{ + uint64_t assoc, set; + uint64_t n_assoc, n_set; + union cvmx_l2c_dbg l2cdbg; + + cvmx_spinlock_lock(&cvmx_l2c_spinlock); + + l2cdbg.u64 = 0; + if (!OCTEON_IS_MODEL(OCTEON_CN30XX)) + l2cdbg.s.ppnum = cvmx_get_core_num(); + l2cdbg.s.finv = 1; + n_set = CVMX_L2_SETS; + n_assoc = l2_size_half() ? (CVMX_L2_ASSOC / 2) : CVMX_L2_ASSOC; + for (set = 0; set < n_set; set++) { + for (assoc = 0; assoc < n_assoc; assoc++) { + l2cdbg.s.set = assoc; + /* Enter debug mode, and make sure all other + ** writes complete before we enter debug + ** mode */ + CVMX_SYNCW; + cvmx_write_csr(CVMX_L2C_DBG, l2cdbg.u64); + cvmx_read_csr(CVMX_L2C_DBG); + + CVMX_PREPARE_FOR_STORE(CVMX_ADD_SEG + (CVMX_MIPS_SPACE_XKPHYS, + set * CVMX_CACHE_LINE_SIZE), 0); + CVMX_SYNCW; /* Push STF out to L2 */ + /* Exit debug mode */ + CVMX_SYNC; + cvmx_write_csr(CVMX_L2C_DBG, 0); + cvmx_read_csr(CVMX_L2C_DBG); + } + } + + cvmx_spinlock_unlock(&cvmx_l2c_spinlock); +} + +int cvmx_l2c_unlock_line(uint64_t address) +{ + int assoc; + union cvmx_l2c_tag tag; + union cvmx_l2c_dbg l2cdbg; + uint32_t tag_addr; + + uint32_t index = cvmx_l2c_address_to_index(address); + + cvmx_spinlock_lock(&cvmx_l2c_spinlock); + /* Compute portion of address that is stored in tag */ + tag_addr = + ((address >> CVMX_L2C_TAG_ADDR_ALIAS_SHIFT) & + ((1 << CVMX_L2C_TAG_ADDR_ALIAS_SHIFT) - 1)); + for (assoc = 0; assoc < CVMX_L2_ASSOC; assoc++) { + tag = cvmx_get_l2c_tag(assoc, index); + + if (tag.s.V && (tag.s.addr == tag_addr)) { + l2cdbg.u64 = 0; + l2cdbg.s.ppnum = cvmx_get_core_num(); + l2cdbg.s.set = assoc; + l2cdbg.s.finv = 1; + + CVMX_SYNC; + /* Enter debug mode */ + cvmx_write_csr(CVMX_L2C_DBG, l2cdbg.u64); + cvmx_read_csr(CVMX_L2C_DBG); + + CVMX_PREPARE_FOR_STORE(CVMX_ADD_SEG + (CVMX_MIPS_SPACE_XKPHYS, + address), 0); + CVMX_SYNC; + /* Exit debug mode */ + cvmx_write_csr(CVMX_L2C_DBG, 0); + cvmx_read_csr(CVMX_L2C_DBG); + cvmx_spinlock_unlock(&cvmx_l2c_spinlock); + return tag.s.L; + } + } + cvmx_spinlock_unlock(&cvmx_l2c_spinlock); + return 0; +} + +int cvmx_l2c_unlock_mem_region(uint64_t start, uint64_t len) +{ + int num_unlocked = 0; + /* Round start/end to cache line boundaries */ + len += start & CVMX_CACHE_LINE_MASK; + start &= ~CVMX_CACHE_LINE_MASK; + len = (len + CVMX_CACHE_LINE_MASK) & ~CVMX_CACHE_LINE_MASK; + while (len > 0) { + num_unlocked += cvmx_l2c_unlock_line(start); + start += CVMX_CACHE_LINE_SIZE; + len -= CVMX_CACHE_LINE_SIZE; + } + + return num_unlocked; +} + +/* + * Internal l2c tag types. These are converted to a generic structure + * that can be used on all chips. + */ +union __cvmx_l2c_tag { + uint64_t u64; + struct cvmx_l2c_tag_cn50xx { + uint64_t reserved:40; + uint64_t V:1; /* Line valid */ + uint64_t D:1; /* Line dirty */ + uint64_t L:1; /* Line locked */ + uint64_t U:1; /* Use, LRU eviction */ + uint64_t addr:20; /* Phys mem addr (33..14) */ + } cn50xx; + struct cvmx_l2c_tag_cn30xx { + uint64_t reserved:41; + uint64_t V:1; /* Line valid */ + uint64_t D:1; /* Line dirty */ + uint64_t L:1; /* Line locked */ + uint64_t U:1; /* Use, LRU eviction */ + uint64_t addr:19; /* Phys mem addr (33..15) */ + } cn30xx; + struct cvmx_l2c_tag_cn31xx { + uint64_t reserved:42; + uint64_t V:1; /* Line valid */ + uint64_t D:1; /* Line dirty */ + uint64_t L:1; /* Line locked */ + uint64_t U:1; /* Use, LRU eviction */ + uint64_t addr:18; /* Phys mem addr (33..16) */ + } cn31xx; + struct cvmx_l2c_tag_cn38xx { + uint64_t reserved:43; + uint64_t V:1; /* Line valid */ + uint64_t D:1; /* Line dirty */ + uint64_t L:1; /* Line locked */ + uint64_t U:1; /* Use, LRU eviction */ + uint64_t addr:17; /* Phys mem addr (33..17) */ + } cn38xx; + struct cvmx_l2c_tag_cn58xx { + uint64_t reserved:44; + uint64_t V:1; /* Line valid */ + uint64_t D:1; /* Line dirty */ + uint64_t L:1; /* Line locked */ + uint64_t U:1; /* Use, LRU eviction */ + uint64_t addr:16; /* Phys mem addr (33..18) */ + } cn58xx; + struct cvmx_l2c_tag_cn58xx cn56xx; /* 2048 sets */ + struct cvmx_l2c_tag_cn31xx cn52xx; /* 512 sets */ +}; + +/** + * @INTERNAL + * Function to read a L2C tag. This code make the current core + * the 'debug core' for the L2. This code must only be executed by + * 1 core at a time. + * + * @assoc: Association (way) of the tag to dump + * @index: Index of the cacheline + * + * Returns The Octeon model specific tag structure. This is + * translated by a wrapper function to a generic form that is + * easier for applications to use. + */ +static union __cvmx_l2c_tag __read_l2_tag(uint64_t assoc, uint64_t index) +{ + + uint64_t debug_tag_addr = (((1ULL << 63) | (index << 7)) + 96); + uint64_t core = cvmx_get_core_num(); + union __cvmx_l2c_tag tag_val; + uint64_t dbg_addr = CVMX_L2C_DBG; + unsigned long flags; + + union cvmx_l2c_dbg debug_val; + debug_val.u64 = 0; + /* + * For low core count parts, the core number is always small enough + * to stay in the correct field and not set any reserved bits. + */ + debug_val.s.ppnum = core; + debug_val.s.l2t = 1; + debug_val.s.set = assoc; + /* + * Make sure core is quiet (no prefetches, etc.) before + * entering debug mode. + */ + CVMX_SYNC; + /* Flush L1 to make sure debug load misses L1 */ + CVMX_DCACHE_INVALIDATE; + + local_irq_save(flags); + + /* + * The following must be done in assembly as when in debug + * mode all data loads from L2 return special debug data, not + * normal memory contents. Also, interrupts must be + * disabled, since if an interrupt occurs while in debug mode + * the ISR will get debug data from all its memory reads + * instead of the contents of memory + */ + + asm volatile (".set push \n" + " .set mips64 \n" + " .set noreorder \n" + /* Enter debug mode, wait for store */ + " sd %[dbg_val], 0(%[dbg_addr]) \n" + " ld $0, 0(%[dbg_addr]) \n" + /* Read L2C tag data */ + " ld %[tag_val], 0(%[tag_addr]) \n" + /* Exit debug mode, wait for store */ + " sd $0, 0(%[dbg_addr]) \n" + " ld $0, 0(%[dbg_addr]) \n" + /* Invalidate dcache to discard debug data */ + " cache 9, 0($0) \n" + " .set pop" : + [tag_val] "=r"(tag_val.u64) : [dbg_addr] "r"(dbg_addr), + [dbg_val] "r"(debug_val.u64), + [tag_addr] "r"(debug_tag_addr) : "memory"); + + local_irq_restore(flags); + return tag_val; + +} + +union cvmx_l2c_tag cvmx_l2c_get_tag(uint32_t association, uint32_t index) +{ + union __cvmx_l2c_tag tmp_tag; + union cvmx_l2c_tag tag; + tag.u64 = 0; + + if ((int)association >= cvmx_l2c_get_num_assoc()) { + cvmx_dprintf + ("ERROR: cvmx_get_l2c_tag association out of range\n"); + return tag; + } + if ((int)index >= cvmx_l2c_get_num_sets()) { + cvmx_dprintf("ERROR: cvmx_get_l2c_tag " + "index out of range (arg: %d, max: %d\n", + index, cvmx_l2c_get_num_sets()); + return tag; + } + /* __read_l2_tag is intended for internal use only */ + tmp_tag = __read_l2_tag(association, index); + + /* + * Convert all tag structure types to generic version, as it + * can represent all models. + */ + if (OCTEON_IS_MODEL(OCTEON_CN58XX) || OCTEON_IS_MODEL(OCTEON_CN56XX)) { + tag.s.V = tmp_tag.cn58xx.V; + tag.s.D = tmp_tag.cn58xx.D; + tag.s.L = tmp_tag.cn58xx.L; + tag.s.U = tmp_tag.cn58xx.U; + tag.s.addr = tmp_tag.cn58xx.addr; + } else if (OCTEON_IS_MODEL(OCTEON_CN38XX)) { + tag.s.V = tmp_tag.cn38xx.V; + tag.s.D = tmp_tag.cn38xx.D; + tag.s.L = tmp_tag.cn38xx.L; + tag.s.U = tmp_tag.cn38xx.U; + tag.s.addr = tmp_tag.cn38xx.addr; + } else if (OCTEON_IS_MODEL(OCTEON_CN31XX) + || OCTEON_IS_MODEL(OCTEON_CN52XX)) { + tag.s.V = tmp_tag.cn31xx.V; + tag.s.D = tmp_tag.cn31xx.D; + tag.s.L = tmp_tag.cn31xx.L; + tag.s.U = tmp_tag.cn31xx.U; + tag.s.addr = tmp_tag.cn31xx.addr; + } else if (OCTEON_IS_MODEL(OCTEON_CN30XX)) { + tag.s.V = tmp_tag.cn30xx.V; + tag.s.D = tmp_tag.cn30xx.D; + tag.s.L = tmp_tag.cn30xx.L; + tag.s.U = tmp_tag.cn30xx.U; + tag.s.addr = tmp_tag.cn30xx.addr; + } else if (OCTEON_IS_MODEL(OCTEON_CN50XX)) { + tag.s.V = tmp_tag.cn50xx.V; + tag.s.D = tmp_tag.cn50xx.D; + tag.s.L = tmp_tag.cn50xx.L; + tag.s.U = tmp_tag.cn50xx.U; + tag.s.addr = tmp_tag.cn50xx.addr; + } else { + cvmx_dprintf("Unsupported OCTEON Model in %s\n", __func__); + } + + return tag; +} + +uint32_t cvmx_l2c_address_to_index(uint64_t addr) +{ + uint64_t idx = addr >> CVMX_L2C_IDX_ADDR_SHIFT; + union cvmx_l2c_cfg l2c_cfg; + l2c_cfg.u64 = cvmx_read_csr(CVMX_L2C_CFG); + + if (l2c_cfg.s.idxalias) { + idx ^= + ((addr & CVMX_L2C_ALIAS_MASK) >> + CVMX_L2C_TAG_ADDR_ALIAS_SHIFT); + } + idx &= CVMX_L2C_IDX_MASK; + return idx; +} + +int cvmx_l2c_get_cache_size_bytes(void) +{ + return cvmx_l2c_get_num_sets() * cvmx_l2c_get_num_assoc() * + CVMX_CACHE_LINE_SIZE; +} + +/** + * Return log base 2 of the number of sets in the L2 cache + * Returns + */ +int cvmx_l2c_get_set_bits(void) +{ + int l2_set_bits; + if (OCTEON_IS_MODEL(OCTEON_CN56XX) || OCTEON_IS_MODEL(OCTEON_CN58XX)) + l2_set_bits = 11; /* 2048 sets */ + else if (OCTEON_IS_MODEL(OCTEON_CN38XX)) + l2_set_bits = 10; /* 1024 sets */ + else if (OCTEON_IS_MODEL(OCTEON_CN31XX) + || OCTEON_IS_MODEL(OCTEON_CN52XX)) + l2_set_bits = 9; /* 512 sets */ + else if (OCTEON_IS_MODEL(OCTEON_CN30XX)) + l2_set_bits = 8; /* 256 sets */ + else if (OCTEON_IS_MODEL(OCTEON_CN50XX)) + l2_set_bits = 7; /* 128 sets */ + else { + cvmx_dprintf("Unsupported OCTEON Model in %s\n", __func__); + l2_set_bits = 11; /* 2048 sets */ + } + return l2_set_bits; + +} + +/* Return the number of sets in the L2 Cache */ +int cvmx_l2c_get_num_sets(void) +{ + return 1 << cvmx_l2c_get_set_bits(); +} + +/* Return the number of associations in the L2 Cache */ +int cvmx_l2c_get_num_assoc(void) +{ + int l2_assoc; + if (OCTEON_IS_MODEL(OCTEON_CN56XX) || + OCTEON_IS_MODEL(OCTEON_CN52XX) || + OCTEON_IS_MODEL(OCTEON_CN58XX) || + OCTEON_IS_MODEL(OCTEON_CN50XX) || OCTEON_IS_MODEL(OCTEON_CN38XX)) + l2_assoc = 8; + else if (OCTEON_IS_MODEL(OCTEON_CN31XX) || + OCTEON_IS_MODEL(OCTEON_CN30XX)) + l2_assoc = 4; + else { + cvmx_dprintf("Unsupported OCTEON Model in %s\n", __func__); + l2_assoc = 8; + } + + /* Check to see if part of the cache is disabled */ + if (cvmx_fuse_read(265)) + l2_assoc = l2_assoc >> 2; + else if (cvmx_fuse_read(264)) + l2_assoc = l2_assoc >> 1; + + return l2_assoc; +} + +/** + * Flush a line from the L2 cache + * This should only be called from one core at a time, as this routine + * sets the core to the 'debug' core in order to flush the line. + * + * @assoc: Association (or way) to flush + * @index: Index to flush + */ +void cvmx_l2c_flush_line(uint32_t assoc, uint32_t index) +{ + union cvmx_l2c_dbg l2cdbg; + + l2cdbg.u64 = 0; + l2cdbg.s.ppnum = cvmx_get_core_num(); + l2cdbg.s.finv = 1; + + l2cdbg.s.set = assoc; + /* + * Enter debug mode, and make sure all other writes complete + * before we enter debug mode. + */ + asm volatile ("sync" : : : "memory"); + cvmx_write_csr(CVMX_L2C_DBG, l2cdbg.u64); + cvmx_read_csr(CVMX_L2C_DBG); + + CVMX_PREPARE_FOR_STORE(((1ULL << 63) + (index) * 128), 0); + /* Exit debug mode */ + asm volatile ("sync" : : : "memory"); + cvmx_write_csr(CVMX_L2C_DBG, 0); + cvmx_read_csr(CVMX_L2C_DBG); +} diff --git a/arch/mips/cavium-octeon/executive/cvmx-sysinfo.c b/arch/mips/cavium-octeon/executive/cvmx-sysinfo.c new file mode 100644 index 000000000000..4812370706a1 --- /dev/null +++ b/arch/mips/cavium-octeon/executive/cvmx-sysinfo.c @@ -0,0 +1,116 @@ +/***********************license start*************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2008 Cavium Networks + * + * This file 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 file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information + ***********************license end**************************************/ + +/* + * This module provides system/board/application information obtained + * by the bootloader. + */ + +#include <asm/octeon/cvmx.h> +#include <asm/octeon/cvmx-spinlock.h> +#include <asm/octeon/cvmx-sysinfo.h> + +/** + * This structure defines the private state maintained by sysinfo module. + * + */ +static struct { + struct cvmx_sysinfo sysinfo; /* system information */ + cvmx_spinlock_t lock; /* mutex spinlock */ + +} state = { + .lock = CVMX_SPINLOCK_UNLOCKED_INITIALIZER +}; + + +/* + * Global variables that define the min/max of the memory region set + * up for 32 bit userspace access. + */ +uint64_t linux_mem32_min; +uint64_t linux_mem32_max; +uint64_t linux_mem32_wired; +uint64_t linux_mem32_offset; + +/** + * This function returns the application information as obtained + * by the bootloader. This provides the core mask of the cores + * running the same application image, as well as the physical + * memory regions available to the core. + * + * Returns Pointer to the boot information structure + * + */ +struct cvmx_sysinfo *cvmx_sysinfo_get(void) +{ + return &(state.sysinfo); +} + +/** + * This function is used in non-simple executive environments (such as + * Linux kernel, u-boot, etc.) to configure the minimal fields that + * are required to use simple executive files directly. + * + * Locking (if required) must be handled outside of this + * function + * + * @phy_mem_desc_ptr: + * Pointer to global physical memory descriptor + * (bootmem descriptor) @board_type: Octeon board + * type enumeration + * + * @board_rev_major: + * Board major revision + * @board_rev_minor: + * Board minor revision + * @cpu_clock_hz: + * CPU clock freqency in hertz + * + * Returns 0: Failure + * 1: success + */ +int cvmx_sysinfo_minimal_initialize(void *phy_mem_desc_ptr, + uint16_t board_type, + uint8_t board_rev_major, + uint8_t board_rev_minor, + uint32_t cpu_clock_hz) +{ + + /* The sysinfo structure was already initialized */ + if (state.sysinfo.board_type) + return 0; + + memset(&(state.sysinfo), 0x0, sizeof(state.sysinfo)); + state.sysinfo.phy_mem_desc_ptr = phy_mem_desc_ptr; + state.sysinfo.board_type = board_type; + state.sysinfo.board_rev_major = board_rev_major; + state.sysinfo.board_rev_minor = board_rev_minor; + state.sysinfo.cpu_clock_hz = cpu_clock_hz; + + return 1; +} + diff --git a/arch/mips/cavium-octeon/executive/octeon-model.c b/arch/mips/cavium-octeon/executive/octeon-model.c new file mode 100644 index 000000000000..9afc3794ed1b --- /dev/null +++ b/arch/mips/cavium-octeon/executive/octeon-model.c @@ -0,0 +1,358 @@ +/***********************license start*************** + * Author: Cavium Networks + * + * Contact: support@caviumnetworks.com + * This file is part of the OCTEON SDK + * + * Copyright (c) 2003-2008 Cavium Networks + * + * This file 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 file is distributed in the hope that it will be useful, but + * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or + * NONINFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this file; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * or visit http://www.gnu.org/licenses/. + * + * This file may also be available under a different license from Cavium. + * Contact Cavium Networks for more information + ***********************license end**************************************/ + +/* + * File defining functions for working with different Octeon + * models. + */ +#include <asm/octeon/octeon.h> + +/** + * Given the chip processor ID from COP0, this function returns a + * string representing the chip model number. The string is of the + * form CNXXXXpX.X-FREQ-SUFFIX. + * - XXXX = The chip model number + * - X.X = Chip pass number + * - FREQ = Current frequency in Mhz + * - SUFFIX = NSP, EXP, SCP, SSP, or CP + * + * @chip_id: Chip ID + * + * Returns Model string + */ +const char *octeon_model_get_string(uint32_t chip_id) +{ + static char buffer[32]; + return octeon_model_get_string_buffer(chip_id, buffer); +} + +/* + * Version of octeon_model_get_string() that takes buffer as argument, + * as running early in u-boot static/global variables don't work when + * running from flash. + */ +const char *octeon_model_get_string_buffer(uint32_t chip_id, char *buffer) +{ + const char *family; + const char *core_model; + char pass[4]; + int clock_mhz; + const char *suffix; + union cvmx_l2d_fus3 fus3; + int num_cores; + union cvmx_mio_fus_dat2 fus_dat2; + union cvmx_mio_fus_dat3 fus_dat3; + char fuse_model[10]; + uint32_t fuse_data = 0; + + fus3.u64 = cvmx_read_csr(CVMX_L2D_FUS3); + fus_dat2.u64 = cvmx_read_csr(CVMX_MIO_FUS_DAT2); + fus_dat3.u64 = cvmx_read_csr(CVMX_MIO_FUS_DAT3); + + num_cores = cvmx_octeon_num_cores(); + + /* Make sure the non existant devices look disabled */ + switch ((chip_id >> 8) & 0xff) { + case 6: /* CN50XX */ + case 2: /* CN30XX */ + fus_dat3.s.nodfa_dte = 1; + fus_dat3.s.nozip = 1; + break; + case 4: /* CN57XX or CN56XX */ + fus_dat3.s.nodfa_dte = 1; + break; + default: + break; + } + + /* Make a guess at the suffix */ + /* NSP = everything */ + /* EXP = No crypto */ + /* SCP = No DFA, No zip */ + /* CP = No DFA, No crypto, No zip */ + if (fus_dat3.s.nodfa_dte) { + if (fus_dat2.s.nocrypto) + suffix = "CP"; + else + suffix = "SCP"; + } else if (fus_dat2.s.nocrypto) + suffix = "EXP"; + else + suffix = "NSP"; + + /* + * Assume pass number is encoded using <5:3><2:0>. Exceptions + * will be fixed later. + */ + sprintf(pass, "%u.%u", ((chip_id >> 3) & 7) + 1, chip_id & 7); + + /* + * Use the number of cores to determine the last 2 digits of + * the model number. There are some exceptions that are fixed + * later. + */ + switch (num_cores) { + case 16: + core_model = "60"; + break; + case 15: + core_model = "58"; + break; + case 14: + core_model = "55"; + break; + case 13: + core_model = "52"; + break; + case 12: + core_model = "50"; + break; + case 11: + core_model = "48"; + break; + case 10: + core_model = "45"; + break; + case 9: + core_model = "42"; + break; + case 8: + core_model = "40"; + break; + case 7: + core_model = "38"; + break; + case 6: + core_model = "34"; + break; + case 5: + core_model = "32"; + break; + case 4: + core_model = "30"; + break; + case 3: + core_model = "25"; + break; + case 2: + core_model = "20"; + break; + case 1: + core_model = "10"; + break; + default: + core_model = "XX"; + break; + } + + /* Now figure out the family, the first two digits */ + switch ((chip_id >> 8) & 0xff) { + case 0: /* CN38XX, CN37XX or CN36XX */ + if (fus3.cn38xx.crip_512k) { + /* + * For some unknown reason, the 16 core one is + * called 37 instead of 36. + */ + if (num_cores >= 16) + family = "37"; + else + family = "36"; + } else + family = "38"; + /* + * This series of chips didn't follow the standard + * pass numbering. + */ + switch (chip_id & 0xf) { + case 0: + strcpy(pass, "1.X"); + break; + case 1: + strcpy(pass, "2.X"); + break; + case 3: + strcpy(pass, "3.X"); + break; + default: + strcpy(pass, "X.X"); + break; + } + break; + case 1: /* CN31XX or CN3020 */ + if ((chip_id & 0x10) || fus3.cn31xx.crip_128k) + family = "30"; + else + family = "31"; + /* + * This series of chips didn't follow the standard + * pass numbering. + */ + switch (chip_id & 0xf) { + case 0: + strcpy(pass, "1.0"); + break; + case 2: + strcpy(pass, "1.1"); + break; + default: + strcpy(pass, "X.X"); + break; + } + break; + case 2: /* CN3010 or CN3005 */ + family = "30"; + /* A chip with half cache is an 05 */ + if (fus3.cn30xx.crip_64k) + core_model = "05"; + /* + * This series of chips didn't follow the standard + * pass numbering. + */ + switch (chip_id & 0xf) { + case 0: + strcpy(pass, "1.0"); + break; + case 2: + strcpy(pass, "1.1"); + break; + default: + strcpy(pass, "X.X"); + break; + } + break; + case 3: /* CN58XX */ + family = "58"; + /* Special case. 4 core, no crypto */ + if ((num_cores == 4) && fus_dat2.cn38xx.nocrypto) + core_model = "29"; + + /* Pass 1 uses different encodings for pass numbers */ + if ((chip_id & 0xFF) < 0x8) { + switch (chip_id & 0x3) { + case 0: + strcpy(pass, "1.0"); + break; + case 1: + strcpy(pass, "1.1"); + break; + case 3: + strcpy(pass, "1.2"); + break; + default: + strcpy(pass, "1.X"); + break; + } + } + break; + case 4: /* CN57XX, CN56XX, CN55XX, CN54XX */ + if (fus_dat2.cn56xx.raid_en) { + if (fus3.cn56xx.crip_1024k) + family = "55"; + else + family = "57"; + if (fus_dat2.cn56xx.nocrypto) + suffix = "SP"; + else + suffix = "SSP"; + } else { + if (fus_dat2.cn56xx.nocrypto) + suffix = "CP"; + else { + suffix = "NSP"; + if (fus_dat3.s.nozip) + suffix = "SCP"; + } + if (fus3.cn56xx.crip_1024k) + family = "54"; + else + family = "56"; + } + break; + case 6: /* CN50XX */ + family = "50"; + break; + case 7: /* CN52XX */ + if (fus3.cn52xx.crip_256k) + family = "51"; + else + family = "52"; + break; + default: + family = "XX"; + core_model = "XX"; + strcpy(pass, "X.X"); + suffix = "XXX"; + break; + } + + clock_mhz = octeon_get_clock_rate() / 1000000; + + if (family[0] != '3') { + /* Check for model in fuses, overrides normal decode */ + /* This is _not_ valid for Octeon CN3XXX models */ + fuse_data |= cvmx_fuse_read_byte(51); + fuse_data = fuse_data << 8; + fuse_data |= cvmx_fuse_read_byte(50); + fuse_data = fuse_data << 8; + fuse_data |= cvmx_fuse_read_byte(49); + fuse_data = fuse_data << 8; + fuse_data |= cvmx_fuse_read_byte(48); + if (fuse_data & 0x7ffff) { + int model = fuse_data & 0x3fff; + int suffix = (fuse_data >> 14) & 0x1f; + if (suffix && model) { + /* + * Have both number and suffix in + * fuses, so both + */ + sprintf(fuse_model, "%d%c", + model, 'A' + suffix - 1); + core_model = ""; + family = fuse_model; + } else if (suffix && !model) { + /* + * Only have suffix, so add suffix to + * 'normal' model number. + */ + sprintf(fuse_model, "%s%c", core_model, + 'A' + suffix - 1); + core_model = fuse_model; + } else { + /* + * Don't have suffix, so just use + * model from fuses. + */ + sprintf(fuse_model, "%d", model); + core_model = ""; + family = fuse_model; + } + } + } + sprintf(buffer, "CN%s%sp%s-%d-%s", + family, core_model, pass, clock_mhz, suffix); + return buffer; +} |