util.c revision e7cbe64f7a72dae5cb44f100db60ca88f3313c65
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/cpr.h>
#include <sys/promimpl.h>
#include "cprboot.h"
static ihandle_t cb_rih;
static pnode_t chosen;
static int statefile_special;
static int reset_input = 0;
static char kbd_input[] = "keyboard input";
static char null_input[] = "\" /nulldev\" input";
/*
* Ask prom to open a disk file given either the OBP device path, or the
* device path representing the target drive/partition and the fs-relative
* path of the file. Handle file pathnames with or without leading '/'.
* if fs points to a null char, it indicates that we are opening a device.
*/
int
cpr_statefile_open(char *path, char *fs_dev)
{
int plen, dlen;
int handle;
char fs_pkg[OBP_MAXPATHLEN];
char fs_name[OBP_MAXDRVNAME];
/*
* instead of using specialstate, we use fs as the flag
*/
if (*fs_dev == '\0') { /* device open */
statefile_special = 1;
handle = prom_open(path);
/* IEEE1275 prom_open returns 0 on failure; we return -1 */
return (handle ? handle : -1);
}
/*
* No cif for $open-package, so we have to use interpret
*/
if (prom_getprop(chosen, "fs-package", fs_pkg) == -1) {
prom_printf("Missing fs-package name\n");
return (-1);
}
plen = prom_strlen(fs_pkg);
dlen = prom_strlen(fs_dev);
prom_interpret("$open-package swap l!", plen, (uintptr_t)fs_pkg,
dlen, (uintptr_t)fs_dev, (uintptr_t)&cb_rih);
if (cb_rih == OBP_BADNODE || cb_rih == 0) {
prom_printf("Can't open %s\n", fs_pkg);
return (-1);
}
if (volname) {
return (cpr_fs_volopen(volname));
}
/*
* Prepend '/' if it's not there already
*/
if (*path != '/') {
(void) prom_sprintf(fs_name, "/%s", path);
return (cpr_fs_open(fs_name));
} else
return (cpr_fs_open(path));
}
/*
* Mount root fs so we can read statefile, etc
*
* sets global
* cb_rih
*/
int
cb_mountroot()
{
chosen = prom_chosennode();
if (chosen == OBP_BADNODE) {
prom_printf("Missing chosen node\n");
return (ERR);
}
if (prom_getprop(chosen, "bootfs", (caddr_t)&cb_rih) == -1) {
prom_printf("Missing bootfs ihandle\n");
return (ERR);
}
return (0);
}
/*
* Unmount root
*/
int
cb_unmountroot()
{
(void) prom_close(cb_rih);
cb_rih = OBP_BADNODE;
return (0);
}
int
cpr_fs_volopen(char *path)
{
CB_VENTRY(cpr_fs_volopen);
if (cb_rih == OBP_BADNODE)
return (-1);
return (prom_volopen(cb_rih, path));
}
/*
* Ask prom to open a disk file.
*/
int
cpr_fs_open(char *path)
{
CB_VENTRY(cpr_fs_open);
if (cb_rih == OBP_BADNODE)
return (-1);
return (prom_fopen(cb_rih, path));
}
/*
* Direct read if using block special,
* otherwise use fs read
*/
int
cpr_read(int fd, caddr_t buf, size_t len)
{
if (!statefile_special || volname)
return (cpr_fs_read(fd, buf, len));
else
return (prom_read(fd, buf, len, 0, 0));
}
int
cpr_fs_read(int fd, caddr_t buf, int len)
{
if (cb_rih == OBP_BADNODE)
return (-1);
return (prom_fread(cb_rih, fd, buf, len));
}
int
cpr_fs_close(int fd)
{
CB_VPRINTF(("cpr_fs_close 0x%x\n", fd));
if (cb_rih == OBP_BADNODE)
return (-1);
prom_fclose(cb_rih, fd);
return (0);
}
int
cpr_fs_seek(int fd, offset_t off)
{
if (cb_rih == OBP_BADNODE)
return (-1);
return (prom_fseek(cb_rih, fd, off));
}
int
cpr_statefile_close(int fd)
{
if (statefile_special) {
statefile_special = 0;
return (prom_close(fd));
} else
return (cpr_fs_close(fd));
}
void
cb_spin(void)
{
static int spindex = 0;
static char *spin_pairs[] = { "|\b", "/\b", "-\b", "\\\b" };
const size_t nspin_pairs = sizeof (spin_pairs) / sizeof (spin_pairs[0]);
prom_printf(spin_pairs[spindex]);
spindex = (spindex + 1) % nspin_pairs;
}
/*
* translate vaddr to phys page number
*/
pfn_t
cpr_vatopfn(caddr_t vaddr)
{
physaddr_t paddr;
int valid, mode;
(void) prom_translate_virt(vaddr, &valid, &paddr, &mode);
if (valid != -1)
return (PFN_INVALID);
return (paddr >> MMU_PAGESHIFT);
}
/*
* unmap virt, then map virt to new phys;
* see remap definition below
*/
int
prom_remap(size_t size, caddr_t virt, physaddr_t phys)
{
ihandle_t immu;
cell_t ci[8];
int rv;
immu = prom_mmu_ihandle();
if (immu == (ihandle_t)-1)
return (ERR);
ci[0] = p1275_ptr2cell("call-method"); /* Service name */
ci[1] = (cell_t)5; /* #argument cells */
ci[2] = (cell_t)0; /* #result cells */
ci[3] = p1275_ptr2cell("remap"); /* Arg1: Method name */
ci[4] = p1275_ihandle2cell(immu); /* Arg2: memory ihandle */
ci[5] = p1275_size2cell(size); /* remap arg0 */
ci[6] = p1275_ptr2cell(virt); /* remap arg1 */
ci[7] = p1275_ull2cell_low(phys); /* remap arg2 */
promif_preprom();
rv = p1275_cif_handler(ci);
promif_postprom();
if (rv)
return (rv); /* Service "call-method" failed */
return (0);
}
/*
* install remap definition in /virtual-memory node;
* used for replacing a virt->phys mapping in one promif call;
* this needs to be atomic from the client's perspective to
* avoid faults while relocating client text.
*/
void
install_remap(void)
{
static char remap_def[] =
"\" /virtual-memory\" find-device "
": remap ( phys.lo virt size -- )"
" 2dup unmap ( phys.lo virt size )"
" 0 -rot -1 map ( ) ; "
"device-end";
prom_interpret(remap_def, 0, 0, 0, 0, 0);
}
/*
* allocate virt and phys space without any mapping;
* stores virt and phys addrs at *vap and *pap
*/
int
cb_alloc(size_t size, uint_t align, caddr_t *vap, physaddr_t *pap)
{
physaddr_t phys;
caddr_t virt;
virt = prom_allocate_virt(align, (size_t)align);
if (virt == (caddr_t)-1)
return (ERR);
if (prom_allocate_phys(size, align, &phys) == -1) {
prom_free_virt(size, virt);
return (ERR);
}
*vap = virt;
*pap = phys;
return (0);
}
static int
get_intprop(pnode_t node, caddr_t prop, void *dst)
{
int len, glen;
len = sizeof (uint_t);
glen = prom_getprop(node, prop, dst);
if (glen != len)
return (ERR);
return (0);
}
/*
* find cpu node for the boot processor
*
* sets globals:
* cb_mid
*/
static pnode_t
get_cpu_node(void)
{
static char *props[] = { "upa-portid", "portid", NULL };
pnode_t node;
char *str, *name, **propp;
uint_t cpu_id;
int err;
str = "get_cpu_node";
name = "cpu";
cb_mid = getmid();
for (node = prom_rootnode(); ; node = prom_nextnode(node)) {
node = prom_findnode_bydevtype(node, name);
if (node == OBP_NONODE) {
prom_printf("\n%s: cant find node for devtype \"%s\"\n",
str, name);
break;
}
cpu_id = (uint_t)-1;
for (propp = props; *propp; propp++) {
err = get_intprop(node, *propp, &cpu_id);
CB_VPRINTF((" cpu node 0x%x, "
"prop \"%s\", cpu_id %d\n",
node, *propp, (int)cpu_id));
if (err == 0)
break;
}
if (cpu_id == cb_mid)
return (node);
}
return (OBP_NONODE);
}
/*
* lookup prom properties
*
* sets globals:
* cb_dents
* cb_clock_freq
* cpu_delay
*/
int
cb_get_props(void)
{
uint_t clock_mhz;
pnode_t node;
struct cb_props *cbp;
static struct cb_props cpu_data[] = {
"#dtlb-entries", &cb_dents,
"clock-frequency", &cb_clock_freq,
NULL, NULL,
};
CB_VENTRY(cb_get_props);
node = get_cpu_node();
if (node == OBP_NONODE)
return (ERR);
for (cbp = cpu_data; cbp->prop; cbp++) {
if (get_intprop(node, cbp->prop, cbp->datap)) {
prom_printf("\n%s: getprop error, "
"node 0x%x, prop \"%s\"\n",
prog, node, cbp->prop);
return (ERR);
}
CB_VPRINTF((" \"%s\" = 0x%x\n",
cbp->prop, *cbp->datap));
}
/*
* setup cpu_delay for cb_usec_wait
*/
clock_mhz = (cb_clock_freq + 500000) / 1000000;
cpu_delay = clock_mhz - 7;
CB_VPRINTF((" clock_mhz %d, cpu_delay %d\n",
clock_mhz, cpu_delay));
return (0);
}
/*
* map-in data pages
* size should fit tte_bit.sz
* rw should be 0 or TTE_HWWR_INT
*/
void
cb_mapin(caddr_t vaddr, pfn_t ppn, uint_t size, uint_t rw, uint_t dtlb_index)
{
tte_t tte;
tte.tte_inthi = TTE_VALID_INT | TTE_SZ_INT(size) |
TTE_PFN_INTHI(ppn);
tte.tte_intlo = TTE_PFN_INTLO(ppn) | TTE_LCK_INT |
TTE_CP_INT | TTE_CV_INT | TTE_PRIV_INT | rw;
set_dtlb_entry(dtlb_index, vaddr, &tte);
}
static char *
prom_strstr(char *string, char *substr)
{
char *strp, *subp, *tmp, c;
if (substr == NULL || *substr == '\0')
return (string);
strp = string;
subp = substr;
c = *subp;
while (*strp) {
if (*strp++ == c) {
tmp = strp;
while ((c = *++subp) == *strp++ && c)
;
if (c == '\0')
return (tmp - 1);
strp = tmp;
subp = substr;
c = *subp;
}
}
return (NULL);
}
static void
cb_set_idev(char *istr)
{
if (reset_input) {
prom_interpret(istr, 0, 0, 0, 0, 0);
CB_VPRINTF(("\ncb_set_idev: reset with [%s]\n", istr));
}
}
/*
* workaround for USB keyboard:
* USB DMA activity has been known to corrupt kernel pages while cprboot
* is restoring them. to quiesce the USB chip, we craft a "null" device
* and temporarily use that as the prom's input device. this effectively
* disables the USB keyboard until the cpr module restores the original
* prom and a kernel driver re-inits and takes-over control of USB.
*
* may set globals:
* reset_input
*/
int
cb_usb_setup(void)
{
char sp[OBP_MAXPATHLEN];
static char cb_nulldev[] = {
"\" /\" select-dev "
"new-device "
"\" nulldev\" device-name "
": read 2drop -2 ; "
": open true ; "
": close ; "
": install-abort ; "
": remove-abort ; "
": write 2drop 0 ; "
": restore ; "
"finish-device "
"unselect-dev"
};
CB_VENTRY(cb_usb_setup);
bzero(sp, sizeof (sp));
prom_interpret("stdin @ ihandle>devname swap -rot move",
(uintptr_t)sp, 0, 0, 0, 0);
if (prom_strstr(sp, "usb") && prom_strstr(sp, "keyboard")) {
prom_interpret(cb_nulldev, 0, 0, 0, 0, 0);
reset_input = 1;
cb_set_idev(null_input);
}
return (0);
}
/*
* switch input to keyboard before entering the prom, and switch to the
* crafted nulldev after returning from the prom. this occurs only when
* stdinpath is a USB keyboard; entering the prom is usually done only
* for debugging purposes - see check_halt() and above DMA comment.
*/
void
cb_enter_mon(void)
{
cb_set_idev(kbd_input);
prom_enter_mon();
cb_set_idev(null_input);
}
/*
* similar to above before exiting to the prom
*/
void
cb_exit_to_mon(void)
{
cb_set_idev(kbd_input);
prom_exit_to_mon();
}