add idl4k kernel firmware version 1.13.0.105

This commit is contained in:
Jaroslav Kysela
2015-03-26 17:22:37 +01:00
parent 5194d2792e
commit e9070cdc77
31064 changed files with 12769984 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
#
# 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
obj-$(CONFIG_PCI) += cvmx-helper-errata.o cvmx-helper-jtag.o

View File

@@ -0,0 +1,690 @@
/***********************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 <linux/module.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);
}
void *cvmx_bootmem_alloc_named_range(uint64_t size, uint64_t min_addr,
uint64_t max_addr, uint64_t align,
char *name)
{
int64_t addr;
addr = cvmx_bootmem_phy_named_block_alloc(size, min_addr, max_addr,
align, name, 0);
if (addr >= 0)
return cvmx_phys_to_ptr(addr);
else
return NULL;
}
void *cvmx_bootmem_alloc_named_address(uint64_t size, uint64_t address,
char *name)
{
return cvmx_bootmem_alloc_named_range(size, address, address + size,
0, name);
}
void *cvmx_bootmem_alloc_named(uint64_t size, uint64_t alignment, char *name)
{
return cvmx_bootmem_alloc_named_range(size, 0, 0, alignment, name);
}
EXPORT_SYMBOL(cvmx_bootmem_alloc_named);
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);
}
EXPORT_SYMBOL(cvmx_bootmem_find_named_block);
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 */
}
int64_t cvmx_bootmem_phy_named_block_alloc(uint64_t size, uint64_t min_addr,
uint64_t max_addr,
uint64_t alignment,
char *name,
uint32_t flags)
{
int64_t addr_allocated;
struct cvmx_bootmem_named_block_desc *named_block_desc_ptr;
#ifdef DEBUG
cvmx_dprintf("cvmx_bootmem_phy_named_block_alloc: size: 0x%llx, min: "
"0x%llx, max: 0x%llx, align: 0x%llx, name: %s\n",
(unsigned long long)size,
(unsigned long long)min_addr,
(unsigned long long)max_addr,
(unsigned long long)alignment,
name);
#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 -1;
}
/*
* Take lock here, as name lookup/block alloc/name add need to
* be atomic.
*/
if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING))
cvmx_spinlock_lock((cvmx_spinlock_t *)&(cvmx_bootmem_desc->lock));
/* Get pointer to first available named block descriptor */
named_block_desc_ptr =
cvmx_bootmem_phy_named_block_find(NULL,
flags | CVMX_BOOTMEM_FLAG_NO_LOCKING);
/*
* Check to see if name already in use, return error if name
* not available or no more room for blocks.
*/
if (cvmx_bootmem_phy_named_block_find(name,
flags | CVMX_BOOTMEM_FLAG_NO_LOCKING) || !named_block_desc_ptr) {
if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING))
cvmx_spinlock_unlock((cvmx_spinlock_t *)&(cvmx_bootmem_desc->lock));
return -1;
}
/*
* Round size up to mult of minimum alignment bytes We need
* the actual size allocated to allow for blocks to be
* coallesced when they are freed. The alloc routine does the
* same rounding up on all allocations.
*/
size = __ALIGN_MASK(size, (CVMX_BOOTMEM_ALIGNMENT_SIZE - 1));
addr_allocated = cvmx_bootmem_phy_alloc(size, min_addr, max_addr,
alignment,
flags | CVMX_BOOTMEM_FLAG_NO_LOCKING);
if (addr_allocated >= 0) {
named_block_desc_ptr->base_addr = addr_allocated;
named_block_desc_ptr->size = size;
strncpy(named_block_desc_ptr->name, name,
cvmx_bootmem_desc->named_block_name_len);
named_block_desc_ptr->name[cvmx_bootmem_desc->named_block_name_len - 1] = 0;
}
if (!(flags & CVMX_BOOTMEM_FLAG_NO_LOCKING))
cvmx_spinlock_unlock((cvmx_spinlock_t *)&(cvmx_bootmem_desc->lock));
return addr_allocated;
}

View File

@@ -0,0 +1,73 @@
/***********************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**************************************/
/**
*
* Fixes and workaround for Octeon chip errata. This file
* contains functions called by cvmx-helper to workaround known
* chip errata. For the most part, code doesn't need to call
* these functions directly.
*
*/
#include <linux/module.h>
#include <asm/octeon/octeon.h>
#include <asm/octeon/cvmx-helper-jtag.h>
/**
* Due to errata G-720, the 2nd order CDR circuit on CN52XX pass
* 1 doesn't work properly. The following code disables 2nd order
* CDR for the specified QLM.
*
* @qlm: QLM to disable 2nd order CDR for.
*/
void __cvmx_helper_errata_qlm_disable_2nd_order_cdr(int qlm)
{
int lane;
cvmx_helper_qlm_jtag_init();
/* We need to load all four lanes of the QLM, a total of 1072 bits */
for (lane = 0; lane < 4; lane++) {
/*
* Each lane has 268 bits. We need to set
* cfg_cdr_incx<67:64> = 3 and cfg_cdr_secord<77> =
* 1. All other bits are zero. Bits go in LSB first,
* so start off with the zeros for bits <63:0>.
*/
cvmx_helper_qlm_jtag_shift_zeros(qlm, 63 - 0 + 1);
/* cfg_cdr_incx<67:64>=3 */
cvmx_helper_qlm_jtag_shift(qlm, 67 - 64 + 1, 3);
/* Zeros for bits <76:68> */
cvmx_helper_qlm_jtag_shift_zeros(qlm, 76 - 68 + 1);
/* cfg_cdr_secord<77>=1 */
cvmx_helper_qlm_jtag_shift(qlm, 77 - 77 + 1, 1);
/* Zeros for bits <267:78> */
cvmx_helper_qlm_jtag_shift_zeros(qlm, 267 - 78 + 1);
}
cvmx_helper_qlm_jtag_update(qlm);
}
EXPORT_SYMBOL(__cvmx_helper_errata_qlm_disable_2nd_order_cdr);

View File

@@ -0,0 +1,144 @@
/***********************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**************************************/
/**
*
* Helper utilities for qlm_jtag.
*
*/
#include <asm/octeon/octeon.h>
#include <asm/octeon/cvmx-helper-jtag.h>
/**
* Initialize the internal QLM JTAG logic to allow programming
* of the JTAG chain by the cvmx_helper_qlm_jtag_*() functions.
* These functions should only be used at the direction of Cavium
* Networks. Programming incorrect values into the JTAG chain
* can cause chip damage.
*/
void cvmx_helper_qlm_jtag_init(void)
{
union cvmx_ciu_qlm_jtgc jtgc;
uint32_t clock_div = 0;
uint32_t divisor = cvmx_sysinfo_get()->cpu_clock_hz / (25 * 1000000);
divisor = (divisor - 1) >> 2;
/* Convert the divisor into a power of 2 shift */
while (divisor) {
clock_div++;
divisor = divisor >> 1;
}
/*
* Clock divider for QLM JTAG operations. eclk is divided by
* 2^(CLK_DIV + 2)
*/
jtgc.u64 = 0;
jtgc.s.clk_div = clock_div;
jtgc.s.mux_sel = 0;
if (OCTEON_IS_MODEL(OCTEON_CN52XX))
jtgc.s.bypass = 0x3;
else
jtgc.s.bypass = 0xf;
cvmx_write_csr(CVMX_CIU_QLM_JTGC, jtgc.u64);
cvmx_read_csr(CVMX_CIU_QLM_JTGC);
}
/**
* Write up to 32bits into the QLM jtag chain. Bits are shifted
* into the MSB and out the LSB, so you should shift in the low
* order bits followed by the high order bits. The JTAG chain is
* 4 * 268 bits long, or 1072.
*
* @qlm: QLM to shift value into
* @bits: Number of bits to shift in (1-32).
* @data: Data to shift in. Bit 0 enters the chain first, followed by
* bit 1, etc.
*
* Returns The low order bits of the JTAG chain that shifted out of the
* circle.
*/
uint32_t cvmx_helper_qlm_jtag_shift(int qlm, int bits, uint32_t data)
{
union cvmx_ciu_qlm_jtgd jtgd;
jtgd.u64 = 0;
jtgd.s.shift = 1;
jtgd.s.shft_cnt = bits - 1;
jtgd.s.shft_reg = data;
if (!OCTEON_IS_MODEL(OCTEON_CN56XX_PASS1_X))
jtgd.s.select = 1 << qlm;
cvmx_write_csr(CVMX_CIU_QLM_JTGD, jtgd.u64);
do {
jtgd.u64 = cvmx_read_csr(CVMX_CIU_QLM_JTGD);
} while (jtgd.s.shift);
return jtgd.s.shft_reg >> (32 - bits);
}
/**
* Shift long sequences of zeros into the QLM JTAG chain. It is
* common to need to shift more than 32 bits of zeros into the
* chain. This function is a convience wrapper around
* cvmx_helper_qlm_jtag_shift() to shift more than 32 bits of
* zeros at a time.
*
* @qlm: QLM to shift zeros into
* @bits:
*/
void cvmx_helper_qlm_jtag_shift_zeros(int qlm, int bits)
{
while (bits > 0) {
int n = bits;
if (n > 32)
n = 32;
cvmx_helper_qlm_jtag_shift(qlm, n, 0);
bits -= n;
}
}
/**
* Program the QLM JTAG chain into all lanes of the QLM. You must
* have already shifted in 268*4, or 1072 bits into the JTAG
* chain. Updating invalid values can possibly cause chip damage.
*
* @qlm: QLM to program
*/
void cvmx_helper_qlm_jtag_update(int qlm)
{
union cvmx_ciu_qlm_jtgd jtgd;
/* Update the new data */
jtgd.u64 = 0;
jtgd.s.update = 1;
if (!OCTEON_IS_MODEL(OCTEON_CN56XX_PASS1_X))
jtgd.s.select = 1 << qlm;
cvmx_write_csr(CVMX_CIU_QLM_JTGD, jtgd.u64);
do {
jtgd.u64 = cvmx_read_csr(CVMX_CIU_QLM_JTGD);
} while (jtgd.s.update);
}

View File

@@ -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);
}

View File

@@ -0,0 +1,118 @@
/***********************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 <linux/module.h>
#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);
}
EXPORT_SYMBOL(cvmx_sysinfo_get);
/**
* 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;
}

View File

@@ -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;
}