#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/machsystm.h>
#include <sys/machparam.h>
#include <sys/cmn_err.h>
#include <sys/stat.h>
#include <sys/mach_descrip.h>
#include <sys/memnode.h>
#include <sys/mdesc.h>
#include <sys/mpo.h>
#include <vm/page.h>
#include <vm/vm_dep.h>
#include <vm/hat_sfmmu.h>
#include <sys/promif.h>
int sun4v_mpo_enable = 1;
int sun4v_mpo_debug = 0;
char sun4v_mpo_status[256] = "";
static struct cpu_md mpo_cpu[NCPU];
#define MAX_MD_LGROUPS 32
static struct lgrp_md mpo_lgroup[MAX_MD_LGROUPS];
static int n_lgrpnodes = 0;
static int n_locality_groups = 0;
static int max_locality_groups = 0;
static int szc_mask0 = 0;
#define SMALL_MBLOCKS_COUNT 8
static struct mblock_md *mpo_mblock;
static struct mblock_md small_mpo_mblocks[SMALL_MBLOCKS_COUNT];
static int n_mblocks = 0;
static mem_stripe_t *mem_stripes;
static mem_stripe_t small_mem_stripes[SMALL_MBLOCKS_COUNT * MAX_MEM_NODES];
static int n_mem_stripes = 0;
static pfn_t mnode_stride;
static int stripe_shift;
static pfn_t mnode_pages;
static uint64_t home_mask = 0;
static pfn_t home_mask_pfn = 0;
static int home_mask_shift = 0;
static uint_t home_mask_pfn_shift = 0;
static int lower_latency = 0;
static int higher_latency = 0;
static pfn_t base_ra_to_pa_pfn = 0;
static int mpo_genid;
static mpo_config_t mpo_config;
typedef enum { U_ADD, U_ADD_ALL, U_DEL } update_t;
static int valid_pages(md_t *md, mde_cookie_t cpu0);
static int unique_home_mem_lg_count(uint64_t mem_lg_homeset);
static int fix_interleave(void);
static int mblock_alloc(mpo_config_t *, update_t, int nmblocks);
static void mblock_install(mpo_config_t *);
static void mblock_free(mpo_config_t *);
static void mblock_update(mpo_config_t *, md_t, mde_cookie_t *mblocknodes);
static void mblock_update_add(mpo_config_t *);
static void mblock_update_del(mpo_config_t *, mpo_config_t *, pfn_t, pfn_t);
static void mstripe_update(mpo_config_t *);
static void mnode_update(mpo_config_t *, pfn_t, pfn_t, update_t);
#if defined(DEBUG) && !defined(lint)
#define VALIDATE_SLICE(base, end) { \
ASSERT(IS_P2ALIGNED(ptob(base), TTEBYTES(TTE256M))); \
ASSERT(IS_P2ALIGNED(ptob(end - base + 1), TTEBYTES(TTE256M))); \
}
#define MPO_DEBUG(args...) if (sun4v_mpo_debug) printf(args)
#else
#define VALIDATE_SLICE(base, end)
#define MPO_DEBUG(...)
#endif
#define MPO_STATUS(args...) { \
(void) snprintf(sun4v_mpo_status, sizeof (sun4v_mpo_status), args); \
MPO_DEBUG(sun4v_mpo_status); \
}
#define mpo_rd_lock kpreempt_disable
#define mpo_rd_unlock kpreempt_enable
static void
mpo_wr_lock()
{
mutex_enter(&cpu_lock);
pause_cpus(NULL, NULL);
mutex_exit(&cpu_lock);
}
static void
mpo_wr_unlock()
{
mutex_enter(&cpu_lock);
start_cpus();
mutex_exit(&cpu_lock);
}
static int64_t
get_int(md_t md, mde_cookie_t node, char *propname, uint64_t *val)
{
int err = md_get_prop_val(md, node, propname, val);
return (err);
}
static int
mblock_cmp(const void *a, const void *b)
{
struct mblock_md *m1 = (struct mblock_md *)a;
struct mblock_md *m2 = (struct mblock_md *)b;
if (m1->base < m2->base)
return (-1);
else if (m1->base == m2->base)
return (0);
else
return (1);
}
static void
mblock_sort(struct mblock_md *mblocks, int n)
{
extern void qsort(void *, size_t, size_t,
int (*)(const void *, const void *));
qsort(mblocks, n, sizeof (mblocks[0]), mblock_cmp);
}
static void
mpo_update_tunables(void)
{
int i, ncpu_min;
ncpu_min = NCPU;
for (i = 0; i < n_lgrpnodes; i++) {
int ncpu = mpo_lgroup[i].ncpu;
if (ncpu != 0 && ncpu < ncpu_min)
ncpu_min = ncpu;
}
lgrp_expand_proc_thresh = ncpu_min * lgrp_loadavg_max_effect / 2;
lgrp_expand_proc_diff = lgrp_expand_proc_thresh / 2;
lgrp_loadavg_tolerance = lgrp_loadavg_max_effect;
}
static mde_cookie_t
cpuid_to_cpunode(md_t *md, int cpuid)
{
mde_cookie_t rootnode, foundnode, *cpunodes;
uint64_t cpuid_prop;
int n_cpunodes, i;
if (md == NULL)
return (MDE_INVAL_ELEM_COOKIE);
rootnode = md_root_node(md);
if (rootnode == MDE_INVAL_ELEM_COOKIE)
return (MDE_INVAL_ELEM_COOKIE);
n_cpunodes = md_alloc_scan_dag(md, rootnode, PROP_LG_CPU,
"fwd", &cpunodes);
if (n_cpunodes <= 0 || n_cpunodes > NCPU)
goto cpuid_fail;
for (i = 0; i < n_cpunodes; i++) {
if (md_get_prop_val(md, cpunodes[i], PROP_LG_CPU_ID,
&cpuid_prop))
break;
if (cpuid_prop == (uint64_t)cpuid) {
foundnode = cpunodes[i];
md_free_scan_dag(md, &cpunodes);
return (foundnode);
}
}
cpuid_fail:
if (n_cpunodes > 0)
md_free_scan_dag(md, &cpunodes);
return (MDE_INVAL_ELEM_COOKIE);
}
static int
mpo_cpu_to_lgroup(md_t *md, mde_cookie_t cpunode)
{
mde_cookie_t *nodes;
uint64_t latency, lowest_latency;
uint64_t address_match, lowest_address_match;
int n_lgroups, j, result = 0;
n_lgroups = md_alloc_scan_dag(md, cpunode, PROP_LG_MEM_LG,
"fwd", &nodes);
lowest_latency = ~(0UL);
for (j = 0; j < n_lgroups; j++) {
result = get_int(md, nodes[j], PROP_LG_LATENCY,
&latency);
result |= get_int(md, nodes[j], PROP_LG_MATCH,
&address_match);
if (result != 0) {
j = -1;
goto to_lgrp_done;
}
if (latency < lowest_latency) {
lowest_latency = latency;
lowest_address_match = address_match;
}
}
for (j = 0; j < n_lgrpnodes; j++) {
if ((mpo_lgroup[j].latency == lowest_latency) &&
(mpo_lgroup[j].addr_match == lowest_address_match))
break;
}
if (j == n_lgrpnodes)
j = -1;
to_lgrp_done:
if (n_lgroups > 0)
md_free_scan_dag(md, &nodes);
return (j);
}
void
mpo_cpu_add(md_t *md, int cpuid)
{
mde_cookie_t cpunode;
int i;
if (n_lgrpnodes <= 0)
return;
if (md == NULL)
goto add_fail;
cpunode = cpuid_to_cpunode(md, cpuid);
if (cpunode == MDE_INVAL_ELEM_COOKIE)
goto add_fail;
i = mpo_cpu_to_lgroup(md, cpunode);
if (i == -1)
goto add_fail;
mpo_cpu[cpuid].lgrp_index = i;
mpo_cpu[cpuid].home = mpo_lgroup[i].addr_match >> home_mask_shift;
mpo_lgroup[i].ncpu++;
mpo_update_tunables();
return;
add_fail:
panic("mpo_cpu_add: Cannot read MD");
}
void
mpo_cpu_remove(int cpuid)
{
int i;
if (n_lgrpnodes <= 0)
return;
i = mpo_cpu[cpuid].lgrp_index;
mpo_lgroup[i].ncpu--;
mpo_cpu[cpuid].home = 0;
mpo_cpu[cpuid].lgrp_index = -1;
mpo_update_tunables();
}
static mde_cookie_t
md_get_root(md_t *md)
{
mde_cookie_t root = MDE_INVAL_ELEM_COOKIE;
int n_nodes;
n_nodes = md_node_count(md);
if (n_nodes <= 0) {
MPO_STATUS("md_get_root: No nodes in node count\n");
return (root);
}
root = md_root_node(md);
if (root == MDE_INVAL_ELEM_COOKIE) {
MPO_STATUS("md_get_root: Root node is missing\n");
return (root);
}
MPO_DEBUG("md_get_root: Node Count: %d\n", n_nodes);
MPO_DEBUG("md_get_root: md: %p\n", md);
MPO_DEBUG("md_get_root: root: %lx\n", root);
done:
return (root);
}
static int
lgrp_update(md_t *md, mde_cookie_t root)
{
int i, j, result;
int ret_val = 0;
int sub_page_fix;
mde_cookie_t *nodes, *lgrpnodes;
n_lgrpnodes = md_alloc_scan_dag(md, root, PROP_LG_MEM_LG,
"fwd", &lgrpnodes);
if (n_lgrpnodes <= 0 || n_lgrpnodes >= MAX_MD_LGROUPS) {
MPO_STATUS("lgrp_update: No Lgroups\n");
ret_val = -1;
goto fail;
}
MPO_DEBUG("lgrp_update: mem_lgs: %d\n", n_lgrpnodes);
for (i = 0; i < n_lgrpnodes; i++) {
mpo_lgroup[i].node = lgrpnodes[i];
mpo_lgroup[i].id = i;
mpo_lgroup[i].ncpu = 0;
result = get_int(md, lgrpnodes[i], PROP_LG_MASK,
&mpo_lgroup[i].addr_mask);
result |= get_int(md, lgrpnodes[i], PROP_LG_MATCH,
&mpo_lgroup[i].addr_match);
if (result < 0) {
mpo_lgroup[i].addr_mask = 0;
mpo_lgroup[i].addr_match = 0;
}
result = get_int(md, lgrpnodes[i], PROP_LG_LATENCY,
&mpo_lgroup[i].latency);
if (result < 0)
mpo_lgroup[i].latency = 0;
}
sub_page_fix = fix_interleave();
if (n_lgrpnodes == 0) {
ret_val = -1;
goto fail;
}
for (i = 0; i < n_lgrpnodes; i++) {
if (mpo_lgroup[0].addr_mask != mpo_lgroup[i].addr_mask) {
MPO_STATUS("lgrp_update: "
"addr_mask values are not the same\n");
ret_val = -1;
goto fail;
}
}
if (sub_page_fix == 0) {
for (i = 0; i < n_lgrpnodes; i++) {
j = md_alloc_scan_dag(md, mpo_lgroup[i].node,
PROP_LG_MBLOCK, "fwd", &nodes);
md_free_scan_dag(md, &nodes);
if (j != n_mblocks) {
MPO_STATUS("lgrp_update: "
"sub-page interleave is being fixed\n");
ret_val = -1;
goto fail;
}
}
}
fail:
if (n_lgrpnodes > 0) {
md_free_scan_dag(md, &lgrpnodes);
for (i = 0; i < n_lgrpnodes; i++)
mpo_lgroup[i].node = MDE_INVAL_ELEM_COOKIE;
}
return (ret_val);
}
static int
lgrp_traverse(md_t *md)
{
mde_cookie_t root, *cpunodes, *mblocknodes;
int o;
uint64_t i, k, stripe, stride;
uint64_t mem_lg_homeset = 0;
int ret_val = 0;
int result = 0;
int n_cpunodes = 0;
mpo_config_t new_config;
if ((root = md_get_root(md)) == MDE_INVAL_ELEM_COOKIE) {
ret_val = -1;
goto fail;
}
n_mblocks = md_alloc_scan_dag(md, root, PROP_LG_MBLOCK, "fwd",
&mblocknodes);
if (n_mblocks <= 0) {
MPO_STATUS("lgrp_traverse: No mblock nodes detected in Machine "
"Descriptor\n");
ret_val = -1;
goto fail;
}
if (mblock_alloc(&new_config, U_ADD_ALL, n_mblocks) < 0) {
ret_val = -1;
goto fail;
}
mblock_update(&new_config, md, mblocknodes);
mblock_install(&new_config);
if (&page_next_pfn_for_color_cpu == NULL) {
MPO_STATUS("lgrp_traverse: No page coloring support\n");
ret_val = -1;
goto fail;
}
if (sun4v_mpo_enable == 0) {
MPO_STATUS("lgrp_traverse: MPO feature is not enabled\n");
ret_val = -1;
goto fail;
}
n_cpunodes = md_alloc_scan_dag(md, root, PROP_LG_CPU, "fwd", &cpunodes);
if (n_cpunodes <= 0 || n_cpunodes > NCPU) {
MPO_STATUS("lgrp_traverse: No CPU nodes detected "
"in MD\n");
ret_val = -1;
goto fail;
}
MPO_DEBUG("lgrp_traverse: cpus: %d\n", n_cpunodes);
if ((ret_val = lgrp_update(md, root)) == -1)
goto fail;
home_mask = mpo_lgroup[0].addr_mask;
home_mask_pfn = btop(home_mask);
home_mask_shift = lowbit(home_mask) - 1;
home_mask_pfn_shift = home_mask_shift - PAGESHIFT;
mnode_pages = btop(1ULL << home_mask_shift);
max_locality_groups =
1 << highbit(home_mask_pfn >> home_mask_pfn_shift);
stripe_shift = highbit(max_locality_groups) - 1;
stripe = ptob(mnode_pages);
stride = max_locality_groups * stripe;
mnode_stride = btop(stride);
if (max_locality_groups - 1 != home_mask_pfn >> home_mask_pfn_shift) {
MPO_STATUS("lgrp_traverse: "
"home mask bits are not contiguous\n");
ret_val = -1;
goto fail;
}
for (i = 0; i < n_lgrpnodes; i++) {
HOMESET_ADD(mem_lg_homeset,
mpo_lgroup[i].addr_match >> home_mask_shift);
}
n_locality_groups = unique_home_mem_lg_count(mem_lg_homeset);
if (n_locality_groups == 1) {
MPO_STATUS("lgrp_traverse: n_locality_groups == 1\n");
ret_val = -1;
goto fail;
}
lower_latency = mpo_lgroup[0].latency;
higher_latency = mpo_lgroup[0].latency;
for (i = 1; i < n_lgrpnodes; i++) {
if (mpo_lgroup[i].latency < lower_latency) {
lower_latency = mpo_lgroup[i].latency;
}
if (mpo_lgroup[i].latency > higher_latency) {
higher_latency = mpo_lgroup[i].latency;
}
}
lower_latency /= 10000;
higher_latency /= 10000;
for (i = 0; i < NCPU; i++) {
mpo_cpu[i].home = 0;
mpo_cpu[i].lgrp_index = -1;
}
for (i = 0; i < n_cpunodes; i++) {
result = get_int(md, cpunodes[i], PROP_LG_CPU_ID, &k);
if (result < 0) {
MPO_STATUS("lgrp_traverse: PROP_LG_CPU_ID missing\n");
ret_val = -1;
goto fail;
}
o = mpo_cpu_to_lgroup(md, cpunodes[i]);
if (o == -1) {
ret_val = -1;
goto fail;
}
mpo_cpu[k].lgrp_index = o;
mpo_cpu[k].home = mpo_lgroup[o].addr_match >> home_mask_shift;
mpo_lgroup[o].ncpu++;
}
if (valid_pages(md, cpunodes[0]) == 0) {
ret_val = -1;
goto fail;
}
fail:
if (n_cpunodes > 0)
md_free_scan_dag(md, &cpunodes);
if (n_mblocks > 0)
md_free_scan_dag(md, &mblocknodes);
else
panic("lgrp_traverse: No memory blocks found");
if (ret_val == 0) {
MPO_STATUS("MPO feature is enabled.\n");
} else
sun4v_mpo_enable = 0;
return (ret_val);
}
static int
unique_home_mem_lg_count(uint64_t mem_lg_homeset)
{
int homeid;
int count = 0;
for (homeid = 0; homeid < NLGRPS_MAX; homeid++) {
if (MEM_LG_ISMEMBER(mem_lg_homeset, homeid)) {
count++;
}
}
MPO_DEBUG("unique_home_mem_lg_count: homeset %lx\n",
mem_lg_homeset);
MPO_DEBUG("unique_home_mem_lg_count: count: %d\n", count);
if (count == 0)
count = 1;
return (count);
}
void
plat_lgrp_init(void)
{
md_t *md;
int rc;
md = md_get_handle();
if (md == NULL) {
panic("cannot access machine descriptor\n");
} else {
rc = lgrp_traverse(md);
(void) md_fini_handle(md);
}
if (rc == -1) {
home_mask_pfn = 0;
max_locality_groups = 1;
n_locality_groups = 1;
return;
}
mem_node_pfn_shift = 0;
mem_node_physalign = 0;
tsb_lgrp_affinity = 1;
lgrp_mem_free_thresh = 1;
lgrp_mem_policy_root = LGRP_MEM_POLICY_NEXT;
lgrp_load_thresh = UINT32_MAX;
mpo_update_tunables();
}
static void
mpo_mem_node_add_slice(pfn_t basepfn, pfn_t endpfn)
{
#if defined(DEBUG) && !defined(lint)
static int slice_count = 0;
slice_count++;
MPO_DEBUG("mem_add_slice(%d): basepfn: %lx endpfn: %lx\n",
slice_count, basepfn, endpfn);
#endif
mem_node_add_slice(basepfn, endpfn);
}
static void
mpo_mem_node_del_slice(pfn_t basepfn, pfn_t endpfn)
{
#if defined(DEBUG) && !defined(lint)
static int slice_count = 0;
slice_count++;
MPO_DEBUG("mem_del_slice(%d): basepfn: %lx endpfn: %lx\n",
slice_count, basepfn, endpfn);
#endif
mem_node_del_slice(basepfn, endpfn);
}
static void
mpo_plat_assign_lgrphand_to_mem_node(lgrp_handle_t plathand, int mnode)
{
MPO_DEBUG("plat_assign_to_mem_nodes: lgroup home %ld, "
"mnode index: %d\n", plathand, mnode);
plat_assign_lgrphand_to_mem_node(plathand, mnode);
}
void
plat_build_mem_nodes(prom_memlist_t *list, size_t nelems)
{
int elem;
uint64_t base, len;
max_mem_nodes = max_locality_groups;
mstripe_update(&mpo_config);
if (n_locality_groups <= 1) {
mpo_plat_assign_lgrphand_to_mem_node(LGRP_DEFAULT_HANDLE, 0);
for (elem = 0; elem < nelems; list++, elem++) {
base = list->addr;
len = list->size;
mpo_mem_node_add_slice(btop(base),
btop(base + len - 1));
}
mem_node_pfn_shift = 0;
mem_node_physalign = 0;
} else
mnode_update(&mpo_config, 0, 0, U_ADD_ALL);
if (max_mem_nodes > 1) {
interleaved_mnodes = 1;
}
}
lgrp_handle_t
plat_lgrp_cpu_to_hand(processorid_t id)
{
lgrp_handle_t lgrphand;
mpo_rd_lock();
if (n_locality_groups > 1) {
lgrphand = (lgrp_handle_t)mpo_cpu[(int)id].home;
} else {
lgrphand = (lgrp_handle_t)LGRP_DEFAULT_HANDLE;
}
mpo_rd_unlock();
return (lgrphand);
}
int
plat_lgrp_latency(lgrp_handle_t from, lgrp_handle_t to)
{
if (lgrp_optimizations() && (from != to ||
from == LGRP_DEFAULT_HANDLE || to == LGRP_DEFAULT_HANDLE)) {
return ((int)higher_latency);
} else {
return ((int)lower_latency);
}
}
int
plat_pfn_to_mem_node(pfn_t pfn)
{
int i, mnode;
pfn_t ra_to_pa_pfn;
struct mblock_md *mb;
if (n_locality_groups <= 1)
return (0);
mpo_rd_lock();
mb = &mpo_mblock[0];
for (i = 0; i < n_mblocks; i++) {
if (pfn >= mb->base_pfn && pfn <= mb->end_pfn) {
ra_to_pa_pfn = btop(mb->ra_to_pa);
mnode = (((pfn + ra_to_pa_pfn) & home_mask_pfn) >>
home_mask_pfn_shift);
ASSERT(mnode < max_mem_nodes);
mpo_rd_unlock();
return (mnode);
}
mb++;
}
panic("plat_pfn_to_mem_node() failed to find mblock: pfn=%lx\n", pfn);
return (pfn);
}
pfn_t
plat_rapfn_to_papfn(pfn_t pfn)
{
int i;
pfn_t ra_to_pa_pfn;
struct mblock_md *mb;
ASSERT(n_mblocks > 0);
if (n_mblocks == 1)
return (pfn + base_ra_to_pa_pfn);
mpo_rd_lock();
for (mb = &mpo_mblock[0], i = 0; i < n_mblocks; i++, mb++) {
if (pfn <= mb->end_pfn && pfn >= mb->base_pfn) {
ra_to_pa_pfn = btop(mb->ra_to_pa);
mpo_rd_unlock();
return (pfn + ra_to_pa_pfn);
}
}
panic("plat_rapfn_to_papfn() failed to find mblock: pfn=%lx\n", pfn);
return (pfn);
}
pfn_t
plat_mem_node_iterator_init(pfn_t pfn, int mnode, uchar_t szc,
mem_node_iterator_t *it, int init)
{
int i;
pgcnt_t szcpgcnt = PNUM_SIZE(szc);
struct mblock_md *mblock;
pfn_t base, end;
mem_stripe_t *ms;
uint64_t szcpagesize;
ASSERT(it != NULL);
ASSERT(mnode >= 0 && mnode < max_mem_nodes);
ASSERT(n_mblocks > 0);
ASSERT(P2PHASE(pfn, szcpgcnt) == 0);
mpo_rd_lock();
if (init || (it->mi_genid != mpo_genid)) {
it->mi_genid = mpo_genid;
it->mi_last_mblock = 0;
it->mi_init = 1;
}
if (n_locality_groups == 1 && n_mblocks == 1) {
if (P2PHASE(base_ra_to_pa_pfn, szcpgcnt)) {
pfn = (pfn_t)-1;
goto done;
}
it->mi_mnode = mnode;
it->mi_ra_to_pa = base_ra_to_pa_pfn;
it->mi_mnode_pfn_mask = 0;
it->mi_mnode_pfn_shift = 0;
it->mi_mnode_mask = 0;
it->mi_mblock_base = mem_node_config[mnode].physbase;
it->mi_mblock_end = mem_node_config[mnode].physmax;
if (pfn < it->mi_mblock_base)
pfn = P2ROUNDUP(it->mi_mblock_base, szcpgcnt);
if ((pfn + szcpgcnt - 1) > it->mi_mblock_end)
pfn = (pfn_t)-1;
goto done;
}
if (init == 1) {
i = 0;
} else {
ASSERT(it->mi_last_mblock < n_mblocks);
i = it->mi_last_mblock;
ASSERT(pfn >
mem_stripes[i * max_locality_groups + mnode].physmax);
if (++i == n_mblocks) {
pfn = (pfn_t)-1;
goto done;
}
}
szcpagesize = szcpgcnt << PAGESHIFT;
for (; i < n_mblocks; i++) {
if (P2PHASE(mpo_mblock[i].ra_to_pa, szcpagesize))
continue;
ms = &mem_stripes[i * max_locality_groups + mnode];
if (ms->exists && (pfn + szcpgcnt - 1) <= ms->physmax &&
(P2ROUNDUP(ms->physbase, szcpgcnt) + szcpgcnt - 1) <=
ms->physmax)
break;
}
if (i == n_mblocks) {
it->mi_last_mblock = i - 1;
pfn = (pfn_t)-1;
goto done;
}
it->mi_last_mblock = i;
mblock = &mpo_mblock[i];
base = ms->physbase;
end = ms->physmax;
it->mi_mnode = mnode;
it->mi_ra_to_pa = btop(mblock->ra_to_pa);
it->mi_mblock_base = base;
it->mi_mblock_end = end;
it->mi_mnode_pfn_mask = home_mask_pfn;
it->mi_mnode_pfn_shift = home_mask_pfn_shift;
it->mi_mnode_mask = max_locality_groups - 1;
if (pfn < base) {
pfn = P2ROUNDUP(base, szcpgcnt);
ASSERT(pfn + szcpgcnt - 1 <= end);
}
ASSERT((pfn + szcpgcnt - 1) <= mpo_mblock[i].end_pfn);
done:
mpo_rd_unlock();
return (pfn);
}
void
plat_mem_node_intersect_range(pfn_t test_base, pgcnt_t test_len,
int mnode, pgcnt_t *npages_out)
{
pfn_t offset, len, hole, base, end, test_end, frag;
pfn_t nearest;
mem_stripe_t *ms;
int i, npages;
*npages_out = 0;
if (!mem_node_config[mnode].exists || test_len == 0)
return;
base = mem_node_config[mnode].physbase;
end = mem_node_config[mnode].physmax;
test_end = test_base + test_len - 1;
if (end < test_base || base > test_end)
return;
if (n_locality_groups == 1) {
*npages_out = MIN(test_end, end) - MAX(test_base, base) + 1;
return;
}
hole = mnode_stride - mnode_pages;
npages = 0;
mpo_rd_lock();
for (i = mnode; i < n_mem_stripes; i += max_locality_groups) {
ms = &mem_stripes[i];
if (ms->exists &&
test_base <= (end = ms->physmax) &&
test_end >= (base = ms->physbase)) {
offset = ms->offset;
if (test_base > base) {
len = P2ROUNDUP(test_base - (base - offset),
mnode_stride);
nearest = base - offset + len;
if (nearest - test_base > hole) {
offset = test_base -
(nearest - mnode_stride);
base = test_base;
} else {
offset = 0;
base = nearest;
if (base > end)
continue;
}
}
if (test_end < end)
end = test_end;
end++;
len = P2ROUNDUP(end - (base - offset), mnode_stride);
nearest = (base - offset) + len;
if (nearest - end <= hole) {
frag = 0;
} else {
frag = nearest - hole - end;
}
len = (len >> stripe_shift) - offset - frag;
npages += len;
}
}
*npages_out = npages;
mpo_rd_unlock();
}
#define MNODE(pa) \
((btop(pa) & home_mask_pfn) >> home_mask_pfn_shift)
static int
valid_pages(md_t *md, mde_cookie_t cpu0)
{
int i, max_szc;
uint64_t last_page_base, szc_mask;
uint64_t max_page_len, max_coalesce_len;
struct mblock_md *mb = mpo_mblock;
if (cpu0 == 0)
szc_mask = szc_mask0;
else {
if (md_get_prop_val(md, cpu0, "mmu-page-size-list", &szc_mask))
szc_mask = 0;
szc_mask |= (1 << TTE4M);
szc_mask0 = szc_mask;
}
max_szc = highbit(szc_mask) - 1;
if (max_szc > TTE256M)
max_szc = TTE256M;
max_page_len = TTEBYTES(max_szc);
max_coalesce_len = TTEBYTES(TTE256M);
ASSERT(max_coalesce_len >= max_page_len);
if (ptob(mnode_pages) < max_coalesce_len) {
MPO_STATUS("Page too large; MPO disabled: page = %lx, "
"mnode slice = %lx\n", max_coalesce_len, ptob(mnode_pages));
return (0);
}
for (i = 0; i < n_mblocks; i++) {
uint64_t base = mb->base;
uint64_t end = mb->base + mb->size - 1;
uint64_t ra_to_pa = mb->ra_to_pa;
if (mb->size < max_page_len) {
if (MNODE(base + ra_to_pa) != MNODE(end + ra_to_pa)) {
MPO_STATUS("Small mblock spans mnodes; "
"MPO disabled: base = %lx, end = %lx, "
"ra2pa = %lx\n", base, end, ra_to_pa);
return (0);
}
} else {
uint64_t pa_base = base + ra_to_pa;
if ((base & (max_coalesce_len - 1)) !=
(pa_base & (max_coalesce_len - 1))) {
MPO_STATUS("bad page alignment; MPO disabled: "
"ra = %lx, pa = %lx, pagelen = %lx\n",
base, pa_base, max_coalesce_len);
return (0);
}
}
last_page_base = P2ALIGN(end, max_coalesce_len);
if (i + 1 < n_mblocks &&
last_page_base + max_coalesce_len > mb[1].base &&
MNODE(last_page_base + ra_to_pa) !=
MNODE(mb[1].base + mb[1].ra_to_pa)) {
MPO_STATUS("Large page spans mblocks; MPO disabled: "
"end = %lx, ra2pa = %lx, base = %lx, ra2pa = %lx, "
"pagelen = %lx\n", end, ra_to_pa, mb[1].base,
mb[1].ra_to_pa, max_coalesce_len);
return (0);
}
mb++;
}
return (1);
}
static int
fix_interleave(void)
{
int i, j;
uint64_t mask = 0;
j = 0;
for (i = 0; i < n_lgrpnodes; i++) {
if ((mpo_lgroup[i].addr_mask & PAGEOFFSET) != 0) {
mask = mpo_lgroup[i].addr_mask;
} else {
mpo_lgroup[j++] = mpo_lgroup[i];
}
}
n_lgrpnodes = j;
if (mask != 0)
MPO_STATUS("sub-page interleave %lx found; "
"removing lgroup.\n", mask);
return (mask != 0);
}
static int
mblock_alloc(mpo_config_t *mc, update_t utype, int nmblocks)
{
mblock_md_t *mb = NULL;
mem_stripe_t *ms = NULL;
int nstripes = MAX_MEM_NODES * nmblocks;
size_t mblocksz = nmblocks * sizeof (struct mblock_md);
size_t mstripesz = nstripes * sizeof (mem_stripe_t);
size_t allocsz = mmu_ptob(mmu_btopr(mblocksz + mstripesz));
if (utype == U_ADD || utype == U_DEL) {
mb = (struct mblock_md *)kmem_zalloc(allocsz, KM_SLEEP);
ms = (mem_stripe_t *)(mb + nmblocks);
mc->mc_alloc_sz = allocsz;
} else if (nmblocks <= SMALL_MBLOCKS_COUNT) {
mb = &small_mpo_mblocks[0];
ms = &small_mem_stripes[0];
mc->mc_alloc_sz = 0;
} else {
if (allocsz > MPOBUF_SIZE) {
MPO_STATUS("mblock_alloc: Insufficient space "
"for mblock structures \n");
return (-1);
}
mb = (struct mblock_md *)
prom_alloc((caddr_t)MPOBUF_BASE, allocsz, PAGESIZE);
if (mb != (struct mblock_md *)MPOBUF_BASE) {
MPO_STATUS("mblock_alloc: Cannot allocate space "
"for mblocks \n");
return (-1);
}
mpo_heap32_buf = (caddr_t)MPOBUF_BASE;
mpo_heap32_bufsz = MPOBUF_SIZE;
ms = (mem_stripe_t *)(mb + nmblocks);
mc->mc_alloc_sz = 0;
}
mc->mc_mblocks = mb;
mc->mc_stripes = ms;
mc->mc_nmblocks = nmblocks;
mc->mc_nstripes = nstripes;
MPO_DEBUG("mblock_alloc: mblocks: %d\n", nmblocks);
return (0);
}
static void
mblock_free(mpo_config_t *mc)
{
if (mc->mc_alloc_sz > 0) {
ASSERT(mc->mc_mblocks != mpo_mblock);
kmem_free((caddr_t)mc->mc_mblocks, mc->mc_alloc_sz);
}
bzero(mc, sizeof (*mc));
}
static void
mblock_install(mpo_config_t *mc)
{
mpo_mblock = mc->mc_mblocks;
n_mblocks = mc->mc_nmblocks;
mem_stripes = mc->mc_stripes;
n_mem_stripes = mc->mc_nstripes;
base_ra_to_pa_pfn = btop(mc->mc_mblocks[0].ra_to_pa);
mpo_config = *mc;
}
static void
mblock_update(mpo_config_t *mc, md_t md, mde_cookie_t *mblocknodes)
{
uint64_t i, j;
int result = 0;
mblock_md_t *mblock = mc->mc_mblocks;
for (i = 0, j = 0; j < mc->mc_nmblocks; j++) {
result = get_int(md, mblocknodes[j], PROP_LG_BASE,
&mblock[i].base);
if (result < 0) {
MPO_STATUS("mblock_update: "
"PROP_LG_BASE is missing\n");
mc->mc_nmblocks = 0;
return;
}
result = get_int(md, mblocknodes[j], PROP_LG_SIZE,
&mblock[i].size);
if (result < 0) {
MPO_STATUS("mblock_update: "
"PROP_LG_SIZE is missing\n");
mc->mc_nmblocks = 0;
return;
}
result = get_int(md, mblocknodes[j],
PROP_LG_RA_PA_OFFSET, &mblock[i].ra_to_pa);
if (result < 0)
mblock[i].ra_to_pa = 0;
MPO_DEBUG("mblock[%ld]: base = %lx, size = %lx, "
"ra_to_pa = %lx\n", i,
mblock[i].base,
mblock[i].size,
mblock[i].ra_to_pa);
if (mblock[i].base > mblock[i].base + mblock[i].size) {
MPO_STATUS("mblock_update: "
"PROP_LG_BASE+PROP_LG_SIZE is invalid: "
"base = %lx, size = %lx\n",
mblock[i].base, mblock[i].size);
mc->mc_nmblocks = 0;
return;
}
if (mblock[i].size != 0) {
uint64_t base = mblock[i].base;
uint64_t end = base + mblock[i].size;
ASSERT(end > base);
mblock[i].base_pfn = btop(base);
mblock[i].end_pfn = btop(end - 1);
i++;
}
}
if (i == 0) {
MPO_STATUS("mblock_update: "
"No non-empty mblock nodes were found "
"in the Machine Descriptor\n");
mc->mc_nmblocks = 0;
return;
}
ASSERT(i <= mc->mc_nmblocks);
mc->mc_nmblocks = i;
mblock_sort(mblock, mc->mc_nmblocks);
}
static void
mblock_update_add(mpo_config_t *mc)
{
md_t *md;
mde_cookie_t root, *mblocknodes;
int nmblocks = 0;
if ((md = md_get_handle()) == NULL) {
MPO_STATUS("Cannot access Machine Descriptor\n");
goto error;
}
if ((root = md_get_root(md)) == MDE_INVAL_ELEM_COOKIE)
goto error;
nmblocks = md_alloc_scan_dag(md, root, PROP_LG_MBLOCK, "fwd",
&mblocknodes);
if (nmblocks <= 0) {
MPO_STATUS("No mblock nodes detected in Machine Descriptor\n");
goto error;
}
if (mblock_alloc(mc, U_ADD, nmblocks) < 0)
goto error;
mblock_update(mc, md, mblocknodes);
md_free_scan_dag(md, &mblocknodes);
(void) md_fini_handle(md);
return;
error:
panic("mblock_update_add: cannot process mblocks from MD.\n");
}
static void
mblock_update_del(mpo_config_t *mc_new, mpo_config_t *mc_old, pfn_t ubase,
pfn_t uend)
{
int i, j;
pfn_t base, end;
mblock_md_t *mblock;
int nmblocks = mc_old->mc_nmblocks;
MPO_DEBUG("mblock_update_del(0x%lx, 0x%lx)\n", ubase, uend);
if (mblock_alloc(mc_new, U_DEL, nmblocks + 1) < 0)
return;
mblock = mc_new->mc_mblocks;
bcopy(mc_old->mc_mblocks, mblock, nmblocks * sizeof (mblock_md_t));
for (i = 0; i < nmblocks; i++) {
base = btop(mblock[i].base);
end = base + btop(mblock[i].size) - 1;
if (base == ubase && end == uend) {
for (j = i; j < nmblocks - 1; j++)
mblock[j] = mblock[j + 1];
nmblocks--;
bzero(&mblock[nmblocks], sizeof (*mblock));
break;
} else if (base < ubase && end > uend) {
for (j = nmblocks - 1; j >= i; j--)
mblock[j + 1] = mblock[j];
mblock[i].size = ptob(ubase - base);
mblock[i].end_pfn = ubase - 1;
mblock[i + 1].base = ptob(uend + 1);
mblock[i + 1].size = ptob(end - uend);
mblock[i + 1].base_pfn = uend + 1;
nmblocks++;
break;
} else if (base == ubase) {
MPO_DEBUG("mblock_update_del: shrink>"
" i=%d base=0x%lx end=0x%lx", i, base, end);
mblock[i].base = ptob(uend + 1);
mblock[i].size -= ptob(uend - ubase + 1);
base = uend + 1;
mblock[i].base_pfn = base;
mblock[i].end_pfn = end;
MPO_DEBUG(" nbase=0x%lx nend=0x%lx\n", base, end);
break;
} else if (end == uend) {
MPO_DEBUG("mblock_update_del: shrink<"
" i=%d base=0x%lx end=0x%lx", i, base, end);
mblock[i].size -= ptob(uend - ubase + 1);
end = ubase - 1;
mblock[i].base_pfn = base;
mblock[i].end_pfn = end;
MPO_DEBUG(" nbase=0x%lx nend=0x%lx\n", base, end);
break;
}
}
mc_new->mc_nmblocks = nmblocks;
ASSERT(end > base);
}
static void
mstripe_update(mpo_config_t *mc)
{
lgrp_handle_t lgrphand, lgrp_start;
int i, mnode;
uint64_t offset, stripe_end, base, end, ra_to_pa, stride;
uint64_t stripe, frag, remove;
mem_stripe_t *ms;
mblock_md_t *mblock = mc->mc_mblocks;
int nmblocks = mc->mc_nmblocks;
int mstripesz = MAX_MEM_NODES * nmblocks * sizeof (mem_stripe_t);
if (n_locality_groups <= 1) {
ASSERT(n_locality_groups == 1);
ASSERT(max_locality_groups == 1 && max_mem_nodes == 1);
if (nmblocks == 1) {
mc->mc_nstripes = 0;
} else {
mc->mc_nstripes = nmblocks;
bzero(mc->mc_stripes, mstripesz);
for (i = 0; i < nmblocks; i++) {
mc->mc_stripes[i].exists = 1;
mc->mc_stripes[i].physbase = mblock[i].base_pfn;
mc->mc_stripes[i].physmax = mblock[i].end_pfn;
}
}
return;
}
bzero(mc->mc_stripes, mstripesz);
mc->mc_nstripes = max_locality_groups * nmblocks;
stripe = ptob(mnode_pages);
stride = max_locality_groups * stripe;
for (i = 0; i < nmblocks; i++) {
base = mblock[i].base;
end = base + mblock[i].size;
ra_to_pa = mblock[i].ra_to_pa;
offset = (base + ra_to_pa) & (stripe - 1);
stripe_end = base - offset + stripe;
lgrp_start = (((base + ra_to_pa) & home_mask) >>
home_mask_shift);
lgrphand = lgrp_start;
do {
if (base >= end)
break;
mnode = lgrphand;
ASSERT(mnode < max_mem_nodes);
frag = (end - (base - offset)) & (stride - 1);
if (frag == 0) {
remove = stride - stripe;
} else if (frag < stripe) {
remove = 0;
} else {
remove = frag - stripe;
}
ms = &mc->mc_stripes[i * max_locality_groups + mnode];
ms->physbase = btop(base);
ms->physmax = btop(end - 1 - remove);
ms->offset = btop(offset);
ms->exists = 1;
base = stripe_end;
stripe_end += stripe;
offset = 0;
lgrphand = (((base + ra_to_pa) & home_mask) >>
home_mask_shift);
} while (lgrphand != lgrp_start);
}
}
#define INTERSECT(a, b, c, d) \
if (((a) >= (c) && (a) <= (d)) || \
((c) >= (a) && (c) <= (b))) { \
(c) = MAX((a), (c)); \
(d) = MIN((b), (d)); \
} else { \
ASSERT((a) >= (d) || (b) <= (c)); \
continue; \
} \
static void
mnode_update(mpo_config_t *mc, pfn_t ubase, pfn_t uend, update_t utype)
{
int i, j, mnode, found;
pfn_t base, end;
mem_stripe_t *ms;
MPO_DEBUG("mnode_udpate: basepfn: %lx endpfn: %lx\n", ubase, uend);
if (n_locality_groups <= 1 && mc->mc_nmblocks == 1) {
if (utype == U_ADD)
mpo_mem_node_add_slice(ubase, uend);
else if (utype == U_DEL)
mpo_mem_node_del_slice(ubase, uend);
else
panic("mnode update: %d: invalid\n", utype);
return;
}
found = 0;
for (i = 0; i < mc->mc_nmblocks; i++) {
for (mnode = 0; mnode < max_locality_groups; mnode++) {
j = i * max_locality_groups + mnode;
ms = &mc->mc_stripes[j];
if (!ms->exists)
continue;
base = ms->physbase;
end = ms->physmax;
switch (utype) {
case U_ADD:
INTERSECT(ubase, uend, base, end);
case U_ADD_ALL:
if (n_locality_groups > 1)
mpo_plat_assign_lgrphand_to_mem_node(
mnode, mnode);
mpo_mem_node_add_slice(base, end);
break;
case U_DEL:
INTERSECT(ubase, uend, base, end);
mpo_mem_node_del_slice(base, end);
break;
default:
panic("mnode_update: %d: invalid\n", utype);
break;
}
found++;
}
}
if (!found)
panic("mnode_update: mstripe not found");
#ifdef DEBUG
if (utype == U_ADD_ALL || utype == U_DEL)
return;
found = 0;
for (i = 0; i < max_mem_nodes; i++) {
if (!mem_node_config[i].exists)
continue;
if (ubase >= mem_node_config[i].physbase &&
ubase <= mem_node_config[i].physmax)
found |= 1;
if (uend >= mem_node_config[i].physbase &&
uend <= mem_node_config[i].physmax)
found |= 2;
}
ASSERT(found == 3);
{
pfn_t minpfn, maxpfn;
mem_node_max_range(&minpfn, &maxpfn);
ASSERT(minpfn <= ubase);
ASSERT(maxpfn >= uend);
}
#endif
}
void
plat_slice_add(pfn_t base, pfn_t end)
{
mpo_config_t old_config = mpo_config;
mpo_config_t new_config;
VALIDATE_SLICE(base, end);
mblock_update_add(&new_config);
mstripe_update(&new_config);
mpo_wr_lock();
mblock_install(&new_config);
mnode_update(&new_config, base, end, U_ADD);
mpo_genid++;
mpo_wr_unlock();
mblock_free(&old_config);
}
void
plat_slice_del(pfn_t base, pfn_t end)
{
mpo_config_t old_config = mpo_config;
mpo_config_t new_config;
VALIDATE_SLICE(base, end);
mblock_update_del(&new_config, &old_config, base, end);
mstripe_update(&new_config);
mpo_wr_lock();
mnode_update(&old_config, base, end, U_DEL);
mblock_install(&new_config);
mpo_genid++;
mpo_wr_unlock();
mblock_free(&old_config);
}