mirror of https://gitee.com/openkylin/linux.git
648 lines
12 KiB
C
648 lines
12 KiB
C
/*
|
|
* Copyright (C) 1996 Paul Mackerras.
|
|
*/
|
|
#include <linux/config.h>
|
|
#include <linux/string.h>
|
|
#include <asm/machdep.h>
|
|
#include <asm/io.h>
|
|
#include <asm/page.h>
|
|
#include <linux/adb.h>
|
|
#include <linux/pmu.h>
|
|
#include <linux/cuda.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/sysrq.h>
|
|
#include <linux/bitops.h>
|
|
#include <asm/xmon.h>
|
|
#include <asm/prom.h>
|
|
#include <asm/bootx.h>
|
|
#include <asm/machdep.h>
|
|
#include <asm/errno.h>
|
|
#include <asm/pmac_feature.h>
|
|
#include <asm/processor.h>
|
|
#include <asm/delay.h>
|
|
#include <asm/btext.h>
|
|
|
|
static volatile unsigned char *sccc, *sccd;
|
|
unsigned int TXRDY, RXRDY, DLAB;
|
|
static int xmon_expect(const char *str, unsigned int timeout);
|
|
|
|
static int use_serial;
|
|
static int use_screen;
|
|
static int via_modem;
|
|
static int xmon_use_sccb;
|
|
static struct device_node *channel_node;
|
|
|
|
#define TB_SPEED 25000000
|
|
|
|
static inline unsigned int readtb(void)
|
|
{
|
|
unsigned int ret;
|
|
|
|
asm volatile("mftb %0" : "=r" (ret) :);
|
|
return ret;
|
|
}
|
|
|
|
void buf_access(void)
|
|
{
|
|
if (DLAB)
|
|
sccd[3] &= ~DLAB; /* reset DLAB */
|
|
}
|
|
|
|
extern int adb_init(void);
|
|
|
|
#ifdef CONFIG_PPC_CHRP
|
|
/*
|
|
* This looks in the "ranges" property for the primary PCI host bridge
|
|
* to find the physical address of the start of PCI/ISA I/O space.
|
|
* It is basically a cut-down version of pci_process_bridge_OF_ranges.
|
|
*/
|
|
static unsigned long chrp_find_phys_io_base(void)
|
|
{
|
|
struct device_node *node;
|
|
unsigned int *ranges;
|
|
unsigned long base = CHRP_ISA_IO_BASE;
|
|
int rlen = 0;
|
|
int np;
|
|
|
|
node = find_devices("isa");
|
|
if (node != NULL) {
|
|
node = node->parent;
|
|
if (node == NULL || node->type == NULL
|
|
|| strcmp(node->type, "pci") != 0)
|
|
node = NULL;
|
|
}
|
|
if (node == NULL)
|
|
node = find_devices("pci");
|
|
if (node == NULL)
|
|
return base;
|
|
|
|
ranges = (unsigned int *) get_property(node, "ranges", &rlen);
|
|
np = prom_n_addr_cells(node) + 5;
|
|
while ((rlen -= np * sizeof(unsigned int)) >= 0) {
|
|
if ((ranges[0] >> 24) == 1 && ranges[2] == 0) {
|
|
/* I/O space starting at 0, grab the phys base */
|
|
base = ranges[np - 3];
|
|
break;
|
|
}
|
|
ranges += np;
|
|
}
|
|
return base;
|
|
}
|
|
#endif /* CONFIG_PPC_CHRP */
|
|
|
|
#ifdef CONFIG_MAGIC_SYSRQ
|
|
static void sysrq_handle_xmon(int key, struct pt_regs *regs,
|
|
struct tty_struct *tty)
|
|
{
|
|
xmon(regs);
|
|
}
|
|
|
|
static struct sysrq_key_op sysrq_xmon_op =
|
|
{
|
|
.handler = sysrq_handle_xmon,
|
|
.help_msg = "Xmon",
|
|
.action_msg = "Entering xmon",
|
|
};
|
|
#endif
|
|
|
|
void
|
|
xmon_map_scc(void)
|
|
{
|
|
#ifdef CONFIG_PPC_MULTIPLATFORM
|
|
volatile unsigned char *base;
|
|
|
|
if (_machine == _MACH_Pmac) {
|
|
struct device_node *np;
|
|
unsigned long addr;
|
|
#ifdef CONFIG_BOOTX_TEXT
|
|
if (!use_screen && !use_serial
|
|
&& !machine_is_compatible("iMac")) {
|
|
/* see if there is a keyboard in the device tree
|
|
with a parent of type "adb" */
|
|
for (np = find_devices("keyboard"); np; np = np->next)
|
|
if (np->parent && np->parent->type
|
|
&& strcmp(np->parent->type, "adb") == 0)
|
|
break;
|
|
|
|
/* needs to be hacked if xmon_printk is to be used
|
|
from within find_via_pmu() */
|
|
#ifdef CONFIG_ADB_PMU
|
|
if (np != NULL && boot_text_mapped && find_via_pmu())
|
|
use_screen = 1;
|
|
#endif
|
|
#ifdef CONFIG_ADB_CUDA
|
|
if (np != NULL && boot_text_mapped && find_via_cuda())
|
|
use_screen = 1;
|
|
#endif
|
|
}
|
|
if (!use_screen && (np = find_devices("escc")) != NULL) {
|
|
/*
|
|
* look for the device node for the serial port
|
|
* we're using and see if it says it has a modem
|
|
*/
|
|
char *name = xmon_use_sccb? "ch-b": "ch-a";
|
|
char *slots;
|
|
int l;
|
|
|
|
np = np->child;
|
|
while (np != NULL && strcmp(np->name, name) != 0)
|
|
np = np->sibling;
|
|
if (np != NULL) {
|
|
/* XXX should parse this properly */
|
|
channel_node = np;
|
|
slots = get_property(np, "slot-names", &l);
|
|
if (slots != NULL && l >= 10
|
|
&& strcmp(slots+4, "Modem") == 0)
|
|
via_modem = 1;
|
|
}
|
|
}
|
|
btext_drawstring("xmon uses ");
|
|
if (use_screen)
|
|
btext_drawstring("screen and keyboard\n");
|
|
else {
|
|
if (via_modem)
|
|
btext_drawstring("modem on ");
|
|
btext_drawstring(xmon_use_sccb? "printer": "modem");
|
|
btext_drawstring(" port\n");
|
|
}
|
|
|
|
#endif /* CONFIG_BOOTX_TEXT */
|
|
|
|
#ifdef CHRP_ESCC
|
|
addr = 0xc1013020;
|
|
#else
|
|
addr = 0xf3013020;
|
|
#endif
|
|
TXRDY = 4;
|
|
RXRDY = 1;
|
|
|
|
np = find_devices("mac-io");
|
|
if (np && np->n_addrs)
|
|
addr = np->addrs[0].address + 0x13020;
|
|
base = (volatile unsigned char *) ioremap(addr & PAGE_MASK, PAGE_SIZE);
|
|
sccc = base + (addr & ~PAGE_MASK);
|
|
sccd = sccc + 0x10;
|
|
|
|
} else {
|
|
base = (volatile unsigned char *) isa_io_base;
|
|
if (_machine == _MACH_chrp)
|
|
base = (volatile unsigned char *)
|
|
ioremap(chrp_find_phys_io_base(), 0x1000);
|
|
|
|
sccc = base + 0x3fd;
|
|
sccd = base + 0x3f8;
|
|
if (xmon_use_sccb) {
|
|
sccc -= 0x100;
|
|
sccd -= 0x100;
|
|
}
|
|
TXRDY = 0x20;
|
|
RXRDY = 1;
|
|
DLAB = 0x80;
|
|
}
|
|
#elif defined(CONFIG_GEMINI)
|
|
/* should already be mapped by the kernel boot */
|
|
sccc = (volatile unsigned char *) 0xffeffb0d;
|
|
sccd = (volatile unsigned char *) 0xffeffb08;
|
|
TXRDY = 0x20;
|
|
RXRDY = 1;
|
|
DLAB = 0x80;
|
|
#elif defined(CONFIG_405GP)
|
|
sccc = (volatile unsigned char *)0xef600305;
|
|
sccd = (volatile unsigned char *)0xef600300;
|
|
TXRDY = 0x20;
|
|
RXRDY = 1;
|
|
DLAB = 0x80;
|
|
#endif /* platform */
|
|
|
|
register_sysrq_key('x', &sysrq_xmon_op);
|
|
}
|
|
|
|
static int scc_initialized = 0;
|
|
|
|
void xmon_init_scc(void);
|
|
extern void cuda_poll(void);
|
|
|
|
static inline void do_poll_adb(void)
|
|
{
|
|
#ifdef CONFIG_ADB_PMU
|
|
if (sys_ctrler == SYS_CTRLER_PMU)
|
|
pmu_poll_adb();
|
|
#endif /* CONFIG_ADB_PMU */
|
|
#ifdef CONFIG_ADB_CUDA
|
|
if (sys_ctrler == SYS_CTRLER_CUDA)
|
|
cuda_poll();
|
|
#endif /* CONFIG_ADB_CUDA */
|
|
}
|
|
|
|
int
|
|
xmon_write(void *handle, void *ptr, int nb)
|
|
{
|
|
char *p = ptr;
|
|
int i, c, ct;
|
|
|
|
#ifdef CONFIG_SMP
|
|
static unsigned long xmon_write_lock;
|
|
int lock_wait = 1000000;
|
|
int locked;
|
|
|
|
while ((locked = test_and_set_bit(0, &xmon_write_lock)) != 0)
|
|
if (--lock_wait == 0)
|
|
break;
|
|
#endif
|
|
|
|
#ifdef CONFIG_BOOTX_TEXT
|
|
if (use_screen) {
|
|
/* write it on the screen */
|
|
for (i = 0; i < nb; ++i)
|
|
btext_drawchar(*p++);
|
|
goto out;
|
|
}
|
|
#endif
|
|
if (!scc_initialized)
|
|
xmon_init_scc();
|
|
ct = 0;
|
|
for (i = 0; i < nb; ++i) {
|
|
while ((*sccc & TXRDY) == 0)
|
|
do_poll_adb();
|
|
c = p[i];
|
|
if (c == '\n' && !ct) {
|
|
c = '\r';
|
|
ct = 1;
|
|
--i;
|
|
} else {
|
|
ct = 0;
|
|
}
|
|
buf_access();
|
|
*sccd = c;
|
|
eieio();
|
|
}
|
|
|
|
out:
|
|
#ifdef CONFIG_SMP
|
|
if (!locked)
|
|
clear_bit(0, &xmon_write_lock);
|
|
#endif
|
|
return nb;
|
|
}
|
|
|
|
int xmon_wants_key;
|
|
int xmon_adb_keycode;
|
|
|
|
#ifdef CONFIG_BOOTX_TEXT
|
|
static int xmon_adb_shiftstate;
|
|
|
|
static unsigned char xmon_keytab[128] =
|
|
"asdfhgzxcv\000bqwer" /* 0x00 - 0x0f */
|
|
"yt123465=97-80]o" /* 0x10 - 0x1f */
|
|
"u[ip\rlj'k;\\,/nm." /* 0x20 - 0x2f */
|
|
"\t `\177\0\033\0\0\0\0\0\0\0\0\0\0" /* 0x30 - 0x3f */
|
|
"\0.\0*\0+\0\0\0\0\0/\r\0-\0" /* 0x40 - 0x4f */
|
|
"\0\0000123456789\0\0\0"; /* 0x50 - 0x5f */
|
|
|
|
static unsigned char xmon_shift_keytab[128] =
|
|
"ASDFHGZXCV\000BQWER" /* 0x00 - 0x0f */
|
|
"YT!@#$^%+(&_*)}O" /* 0x10 - 0x1f */
|
|
"U{IP\rLJ\"K:|<?NM>" /* 0x20 - 0x2f */
|
|
"\t ~\177\0\033\0\0\0\0\0\0\0\0\0\0" /* 0x30 - 0x3f */
|
|
"\0.\0*\0+\0\0\0\0\0/\r\0-\0" /* 0x40 - 0x4f */
|
|
"\0\0000123456789\0\0\0"; /* 0x50 - 0x5f */
|
|
|
|
static int
|
|
xmon_get_adb_key(void)
|
|
{
|
|
int k, t, on;
|
|
|
|
xmon_wants_key = 1;
|
|
for (;;) {
|
|
xmon_adb_keycode = -1;
|
|
t = 0;
|
|
on = 0;
|
|
do {
|
|
if (--t < 0) {
|
|
on = 1 - on;
|
|
btext_drawchar(on? 0xdb: 0x20);
|
|
btext_drawchar('\b');
|
|
t = 200000;
|
|
}
|
|
do_poll_adb();
|
|
} while (xmon_adb_keycode == -1);
|
|
k = xmon_adb_keycode;
|
|
if (on)
|
|
btext_drawstring(" \b");
|
|
|
|
/* test for shift keys */
|
|
if ((k & 0x7f) == 0x38 || (k & 0x7f) == 0x7b) {
|
|
xmon_adb_shiftstate = (k & 0x80) == 0;
|
|
continue;
|
|
}
|
|
if (k >= 0x80)
|
|
continue; /* ignore up transitions */
|
|
k = (xmon_adb_shiftstate? xmon_shift_keytab: xmon_keytab)[k];
|
|
if (k != 0)
|
|
break;
|
|
}
|
|
xmon_wants_key = 0;
|
|
return k;
|
|
}
|
|
#endif /* CONFIG_BOOTX_TEXT */
|
|
|
|
int
|
|
xmon_read(void *handle, void *ptr, int nb)
|
|
{
|
|
char *p = ptr;
|
|
int i;
|
|
|
|
#ifdef CONFIG_BOOTX_TEXT
|
|
if (use_screen) {
|
|
for (i = 0; i < nb; ++i)
|
|
*p++ = xmon_get_adb_key();
|
|
return i;
|
|
}
|
|
#endif
|
|
if (!scc_initialized)
|
|
xmon_init_scc();
|
|
for (i = 0; i < nb; ++i) {
|
|
while ((*sccc & RXRDY) == 0)
|
|
do_poll_adb();
|
|
buf_access();
|
|
*p++ = *sccd;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
int
|
|
xmon_read_poll(void)
|
|
{
|
|
if ((*sccc & RXRDY) == 0) {
|
|
do_poll_adb();
|
|
return -1;
|
|
}
|
|
buf_access();
|
|
return *sccd;
|
|
}
|
|
|
|
static unsigned char scc_inittab[] = {
|
|
13, 0, /* set baud rate divisor */
|
|
12, 1,
|
|
14, 1, /* baud rate gen enable, src=rtxc */
|
|
11, 0x50, /* clocks = br gen */
|
|
5, 0xea, /* tx 8 bits, assert DTR & RTS */
|
|
4, 0x46, /* x16 clock, 1 stop */
|
|
3, 0xc1, /* rx enable, 8 bits */
|
|
};
|
|
|
|
void
|
|
xmon_init_scc(void)
|
|
{
|
|
if ( _machine == _MACH_chrp )
|
|
{
|
|
sccd[3] = 0x83; eieio(); /* LCR = 8N1 + DLAB */
|
|
sccd[0] = 12; eieio(); /* DLL = 9600 baud */
|
|
sccd[1] = 0; eieio();
|
|
sccd[2] = 0; eieio(); /* FCR = 0 */
|
|
sccd[3] = 3; eieio(); /* LCR = 8N1 */
|
|
sccd[1] = 0; eieio(); /* IER = 0 */
|
|
}
|
|
else if ( _machine == _MACH_Pmac )
|
|
{
|
|
int i, x;
|
|
|
|
if (channel_node != 0)
|
|
pmac_call_feature(
|
|
PMAC_FTR_SCC_ENABLE,
|
|
channel_node,
|
|
PMAC_SCC_ASYNC | PMAC_SCC_FLAG_XMON, 1);
|
|
printk(KERN_INFO "Serial port locked ON by debugger !\n");
|
|
if (via_modem && channel_node != 0) {
|
|
unsigned int t0;
|
|
|
|
pmac_call_feature(
|
|
PMAC_FTR_MODEM_ENABLE,
|
|
channel_node, 0, 1);
|
|
printk(KERN_INFO "Modem powered up by debugger !\n");
|
|
t0 = readtb();
|
|
while (readtb() - t0 < 3*TB_SPEED)
|
|
eieio();
|
|
}
|
|
/* use the B channel if requested */
|
|
if (xmon_use_sccb) {
|
|
sccc = (volatile unsigned char *)
|
|
((unsigned long)sccc & ~0x20);
|
|
sccd = sccc + 0x10;
|
|
}
|
|
for (i = 20000; i != 0; --i) {
|
|
x = *sccc; eieio();
|
|
}
|
|
*sccc = 9; eieio(); /* reset A or B side */
|
|
*sccc = ((unsigned long)sccc & 0x20)? 0x80: 0x40; eieio();
|
|
for (i = 0; i < sizeof(scc_inittab); ++i) {
|
|
*sccc = scc_inittab[i];
|
|
eieio();
|
|
}
|
|
}
|
|
scc_initialized = 1;
|
|
if (via_modem) {
|
|
for (;;) {
|
|
xmon_write(NULL, "ATE1V1\r", 7);
|
|
if (xmon_expect("OK", 5)) {
|
|
xmon_write(NULL, "ATA\r", 4);
|
|
if (xmon_expect("CONNECT", 40))
|
|
break;
|
|
}
|
|
xmon_write(NULL, "+++", 3);
|
|
xmon_expect("OK", 3);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
extern int (*prom_entry)(void *);
|
|
|
|
int
|
|
xmon_exit(void)
|
|
{
|
|
struct prom_args {
|
|
char *service;
|
|
} args;
|
|
|
|
for (;;) {
|
|
args.service = "exit";
|
|
(*prom_entry)(&args);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void *xmon_stdin;
|
|
void *xmon_stdout;
|
|
void *xmon_stderr;
|
|
|
|
void
|
|
xmon_init(int arg)
|
|
{
|
|
xmon_map_scc();
|
|
}
|
|
|
|
int
|
|
xmon_putc(int c, void *f)
|
|
{
|
|
char ch = c;
|
|
|
|
if (c == '\n')
|
|
xmon_putc('\r', f);
|
|
return xmon_write(f, &ch, 1) == 1? c: -1;
|
|
}
|
|
|
|
int
|
|
xmon_putchar(int c)
|
|
{
|
|
return xmon_putc(c, xmon_stdout);
|
|
}
|
|
|
|
int
|
|
xmon_fputs(char *str, void *f)
|
|
{
|
|
int n = strlen(str);
|
|
|
|
return xmon_write(f, str, n) == n? 0: -1;
|
|
}
|
|
|
|
int
|
|
xmon_readchar(void)
|
|
{
|
|
char ch;
|
|
|
|
for (;;) {
|
|
switch (xmon_read(xmon_stdin, &ch, 1)) {
|
|
case 1:
|
|
return ch;
|
|
case -1:
|
|
xmon_printf("read(stdin) returned -1\r\n", 0, 0);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static char line[256];
|
|
static char *lineptr;
|
|
static int lineleft;
|
|
|
|
int xmon_expect(const char *str, unsigned int timeout)
|
|
{
|
|
int c;
|
|
unsigned int t0;
|
|
|
|
timeout *= TB_SPEED;
|
|
t0 = readtb();
|
|
do {
|
|
lineptr = line;
|
|
for (;;) {
|
|
c = xmon_read_poll();
|
|
if (c == -1) {
|
|
if (readtb() - t0 > timeout)
|
|
return 0;
|
|
continue;
|
|
}
|
|
if (c == '\n')
|
|
break;
|
|
if (c != '\r' && lineptr < &line[sizeof(line) - 1])
|
|
*lineptr++ = c;
|
|
}
|
|
*lineptr = 0;
|
|
} while (strstr(line, str) == NULL);
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
xmon_getchar(void)
|
|
{
|
|
int c;
|
|
|
|
if (lineleft == 0) {
|
|
lineptr = line;
|
|
for (;;) {
|
|
c = xmon_readchar();
|
|
if (c == -1 || c == 4)
|
|
break;
|
|
if (c == '\r' || c == '\n') {
|
|
*lineptr++ = '\n';
|
|
xmon_putchar('\n');
|
|
break;
|
|
}
|
|
switch (c) {
|
|
case 0177:
|
|
case '\b':
|
|
if (lineptr > line) {
|
|
xmon_putchar('\b');
|
|
xmon_putchar(' ');
|
|
xmon_putchar('\b');
|
|
--lineptr;
|
|
}
|
|
break;
|
|
case 'U' & 0x1F:
|
|
while (lineptr > line) {
|
|
xmon_putchar('\b');
|
|
xmon_putchar(' ');
|
|
xmon_putchar('\b');
|
|
--lineptr;
|
|
}
|
|
break;
|
|
default:
|
|
if (lineptr >= &line[sizeof(line) - 1])
|
|
xmon_putchar('\a');
|
|
else {
|
|
xmon_putchar(c);
|
|
*lineptr++ = c;
|
|
}
|
|
}
|
|
}
|
|
lineleft = lineptr - line;
|
|
lineptr = line;
|
|
}
|
|
if (lineleft == 0)
|
|
return -1;
|
|
--lineleft;
|
|
return *lineptr++;
|
|
}
|
|
|
|
char *
|
|
xmon_fgets(char *str, int nb, void *f)
|
|
{
|
|
char *p;
|
|
int c;
|
|
|
|
for (p = str; p < str + nb - 1; ) {
|
|
c = xmon_getchar();
|
|
if (c == -1) {
|
|
if (p == str)
|
|
return NULL;
|
|
break;
|
|
}
|
|
*p++ = c;
|
|
if (c == '\n')
|
|
break;
|
|
}
|
|
*p = 0;
|
|
return str;
|
|
}
|
|
|
|
void
|
|
xmon_enter(void)
|
|
{
|
|
#ifdef CONFIG_ADB_PMU
|
|
if (_machine == _MACH_Pmac) {
|
|
pmu_suspend();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
xmon_leave(void)
|
|
{
|
|
#ifdef CONFIG_ADB_PMU
|
|
if (_machine == _MACH_Pmac) {
|
|
pmu_resume();
|
|
}
|
|
#endif
|
|
}
|