@@ -15,7 +15,7 @@ allow dom0_t xen_t:xen {
};
allow dom0_t xen_t:xen2 {
resource_op psr_cmt_op psr_cat_op pmu_ctrl get_symbol
- get_cpu_levelling_caps get_cpu_featureset livepatch_op
+ get_cpu_levelling_caps get_cpu_featureset livepatch_op loglvl
};
# Allow dom0 to use all XENVER_ subops that have checks.
@@ -467,6 +467,11 @@ long do_sysctl(XEN_GUEST_HANDLE_PARAM(xen_sysctl_t) u_sysctl)
copyback = 1;
break;
+ case XEN_SYSCTL_loglvl_op:
+ ret = console_loglvl_op(&op->u.loglvl);
+ copyback = 1;
+ break;
+
default:
ret = arch_do_sysctl(op, u_sysctl);
copyback = 0;
@@ -144,7 +144,7 @@ struct log_level {
unsigned int num;
};
-static struct log_level __initdata log_levels[] = {
+static struct log_level log_levels[] = {
{ "none", 0 },
{ "error", 1 },
{ "warning", 2 },
@@ -152,6 +152,9 @@ static struct log_level __initdata log_levels[] = {
{ "debug", 4 },
{ "all", 4 },
};
+#define LOG_LEVEL_MIN 0
+#define LOG_LEVEL_MAX 4
+#define LOG_LEVEL_STRLEN_MAX 7 /* "warning" is the longest one */
#define ___parse_loglvl(s, ps, lvlstr, lvlnum) \
if ( !strncmp((s), (lvlstr), strlen(lvlstr)) ) { \
@@ -188,7 +191,41 @@ static void __init parse_guest_loglvl(char *s)
_parse_loglvl(s, &xenlog_guest_lower_thresh, &xenlog_guest_upper_thresh);
}
-static char * __init loglvl_str(int lvl)
+/*
+ * A variant used to parse log level during runtime. Returns -1 if it
+ * fails to parse.
+ */
+static int parse_log_level(const char *s)
+{
+ unsigned int i;
+
+ for ( i = 0; i < ARRAY_SIZE(log_levels); i++ )
+ {
+ if ( !strcmp(log_levels[i].str, s) )
+ return log_levels[i].num;
+ }
+
+ return -1;
+}
+
+static const char *loglvl_to_str(int lvl)
+{
+ unsigned int i;
+
+ if ( lvl < LOG_LEVEL_MIN || lvl > LOG_LEVEL_MAX )
+ return "???";
+
+ /* Multiple log levels can use the same number. Return the most
+ * comprehensive log level string.
+ */
+ for ( i = ARRAY_SIZE(log_levels) - 1; i >= 0; i-- )
+ {
+ if ( log_levels[i].num == lvl )
+ return log_levels[i].str;
+ }
+}
+
+static char *loglvl_str(int lvl)
{
switch ( lvl )
{
@@ -201,6 +238,250 @@ static char * __init loglvl_str(int lvl)
return "???";
}
+static int *__read_mostly upper_thresh_adj = &xenlog_upper_thresh;
+static int *__read_mostly lower_thresh_adj = &xenlog_lower_thresh;
+static const char *__read_mostly thresh_adj = "standard";
+
+static void do_toggle_guest(unsigned char key, struct cpu_user_regs *regs)
+{
+ if ( upper_thresh_adj == &xenlog_upper_thresh )
+ {
+ upper_thresh_adj = &xenlog_guest_upper_thresh;
+ lower_thresh_adj = &xenlog_guest_lower_thresh;
+ thresh_adj = "guest";
+ }
+ else
+ {
+ upper_thresh_adj = &xenlog_upper_thresh;
+ lower_thresh_adj = &xenlog_lower_thresh;
+ thresh_adj = "standard";
+ }
+ printk("'%c' pressed -> %s log level adjustments enabled\n",
+ key, thresh_adj);
+}
+
+static void do_adj_thresh(unsigned char key)
+{
+ if ( *upper_thresh_adj < *lower_thresh_adj )
+ *upper_thresh_adj = *lower_thresh_adj;
+ printk("'%c' pressed -> %s log level: %s (rate limited %s)\n",
+ key, thresh_adj, loglvl_str(*lower_thresh_adj),
+ loglvl_str(*upper_thresh_adj));
+}
+
+static void do_inc_thresh(unsigned char key, struct cpu_user_regs *regs)
+{
+ if ( *lower_thresh_adj == LOG_LEVEL_MAX )
+ return;
+ ++*lower_thresh_adj;
+ do_adj_thresh(key);
+}
+
+static void do_dec_thresh(unsigned char key, struct cpu_user_regs *regs)
+{
+ if ( *lower_thresh_adj == LOG_LEVEL_MIN )
+ return;
+ --*lower_thresh_adj;
+ do_adj_thresh(key);
+}
+
+static void __putstr(const char *);
+static void printk_start_of_line(const char *);
+
+static void do_loglvl_op(const int lower_thresh, const int upper_thresh,
+ int *lower, int *upper, const char *which)
+{
+ if ( lower_thresh < 0 && upper_thresh < 0 )
+ return;
+
+ if ( lower_thresh >= 0 )
+ *lower = lower_thresh;
+
+ if ( upper_thresh >= 0 )
+ *upper = upper_thresh;
+
+ if ( *upper < *lower )
+ {
+ if ( upper_thresh < 0 )
+ *upper = *lower;
+ else
+ *lower = *upper;
+ }
+
+ if ( printk_ratelimit() )
+ {
+ spin_lock_irq(&console_lock);
+ printk_start_of_line("(XEN) ");
+ __putstr(which);
+ __putstr(" log level: ");
+ __putstr(loglvl_str(*lower));
+ __putstr(" (rate limited ");
+ __putstr(loglvl_str(*upper));
+ __putstr(")\n");
+ spin_unlock_irq(&console_lock);
+ }
+}
+
+int console_loglvl_op(struct xen_sysctl_loglvl_op *op)
+{
+ int ret;
+
+ switch ( op->cmd )
+ {
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+
+ case XEN_SYSCTL_LOGLVL_set:
+ {
+ char *buf;
+ unsigned int buf_size = 0;
+ int host_lower_thresh, host_upper_thresh;
+ int guest_lower_thresh, guest_upper_thresh;
+
+ buf_size = op->host.lower_thresh_len;
+ if ( buf_size < op->host.upper_thresh_len )
+ buf_size = op->host.upper_thresh_len;
+ if ( buf_size < op->guest.lower_thresh_len )
+ buf_size = op->guest.lower_thresh_len;
+ if ( buf_size < op->guest.upper_thresh_len )
+ buf_size = op->guest.upper_thresh_len;
+
+ buf = xmalloc_array(char, buf_size);
+ if ( !buf )
+ {
+ ret = -ENOMEM;
+ goto set_done;
+ }
+
+#define parse(hg, lu) \
+ hg##_##lu##_thresh = -1; \
+ if ( op->hg.lu ##_thresh_len ) \
+ { \
+ if ( copy_from_guest(buf, op->hg.lu ##_thresh, \
+ op->hg.lu ##_thresh_len) ) \
+ { \
+ ret = -EFAULT; \
+ goto set_done; \
+ } \
+ \
+ buf[buf_size-1] = 0; \
+ \
+ ret = parse_log_level(buf); \
+ if ( ret == -1 ) \
+ { \
+ ret = -EINVAL; \
+ goto set_done; \
+ } \
+ \
+ hg##_##lu##_thresh = ret; \
+ }
+
+ parse(host, lower);
+ parse(host, upper);
+ parse(guest, lower);
+ parse(guest, upper);
+
+#undef parse
+
+ if ( (host_lower_thresh >= 0 && host_upper_thresh >= 0 &&
+ host_lower_thresh > host_upper_thresh) ||
+ (guest_lower_thresh >= 0 && guest_upper_thresh >= 0 &&
+ guest_lower_thresh > guest_upper_thresh) )
+ {
+ ret = -EINVAL;
+ goto set_done;
+ }
+
+ do_loglvl_op(host_lower_thresh, host_upper_thresh,
+ &xenlog_lower_thresh, &xenlog_upper_thresh,
+ "standard");
+
+ do_loglvl_op(guest_lower_thresh, guest_upper_thresh,
+ &xenlog_guest_lower_thresh, &xenlog_guest_upper_thresh,
+ "guest");
+
+ ret = 0;
+ set_done:
+ xfree(buf);
+ break;
+ }
+ case XEN_SYSCTL_LOGLVL_get:
+ {
+ unsigned int host_lower_thresh_len =
+ strlen(loglvl_to_str(xenlog_lower_thresh)) + 1;
+ unsigned int host_upper_thresh_len =
+ strlen(loglvl_to_str(xenlog_upper_thresh)) + 1;
+ unsigned int guest_lower_thresh_len =
+ strlen(loglvl_to_str(xenlog_guest_lower_thresh)) + 1;
+ unsigned int guest_upper_thresh_len =
+ strlen(loglvl_to_str(xenlog_guest_upper_thresh)) + 1;
+ char scratch[LOG_LEVEL_STRLEN_MAX+1];
+
+ if ( (op->host.lower_thresh_len &&
+ op->host.lower_thresh_len < host_lower_thresh_len) ||
+ (op->host.upper_thresh_len &&
+ op->host.upper_thresh_len < host_upper_thresh_len) ||
+ (op->guest.lower_thresh_len &&
+ op->guest.lower_thresh_len < guest_lower_thresh_len) ||
+ (op->guest.upper_thresh_len
+ && op->guest.upper_thresh_len < guest_upper_thresh_len)
+ )
+ {
+ ret = -ENOBUFS;
+ goto get_done;
+ }
+
+ ret = -EFAULT;
+
+ if ( op->host.lower_thresh_len )
+ {
+ snprintf(scratch, sizeof(scratch), "%s",
+ loglvl_to_str(xenlog_lower_thresh));
+ if ( copy_to_guest(op->host.lower_thresh, scratch,
+ strlen(scratch)+1) )
+ goto get_done;
+ }
+
+ if ( op->host.upper_thresh_len )
+ {
+ snprintf(scratch, sizeof(scratch), "%s",
+ loglvl_to_str(xenlog_upper_thresh));
+ if ( copy_to_guest(op->host.upper_thresh, scratch,
+ strlen(scratch)+1) )
+ goto get_done;
+ }
+
+ if ( op->guest.lower_thresh_len )
+ {
+ snprintf(scratch, sizeof(scratch), "%s",
+ loglvl_to_str(xenlog_guest_lower_thresh));
+ if ( copy_to_guest(op->guest.lower_thresh, scratch,
+ strlen(scratch)+1) )
+ goto get_done;
+ }
+
+ if ( op->guest.upper_thresh_len )
+ {
+ snprintf(scratch, sizeof(scratch), "%s",
+ loglvl_to_str(xenlog_guest_upper_thresh));
+ if ( copy_to_guest(op->guest.upper_thresh, scratch,
+ strlen(scratch)+1) )
+ goto get_done;
+ }
+
+ ret = 0;
+ get_done:
+ op->host.lower_thresh_len = host_lower_thresh_len;
+ op->host.upper_thresh_len = host_upper_thresh_len;
+ op->guest.lower_thresh_len = guest_lower_thresh_len;
+ op->guest.upper_thresh_len = guest_upper_thresh_len;
+ break;
+ }
+ }
+
+ return ret;
+}
/*
* ********************************************************
* *************** ACCESS TO CONSOLE RING *****************
@@ -829,6 +1110,12 @@ void __init console_endboot(void)
register_keyhandler('w', dump_console_ring_key,
"synchronously dump console ring buffer (dmesg)", 0);
+ register_irq_keyhandler('+', &do_inc_thresh,
+ "increase log level threshold", 0);
+ register_irq_keyhandler('-', &do_dec_thresh,
+ "decrease log level threshold", 0);
+ register_irq_keyhandler('G', &do_toggle_guest,
+ "toggle host/guest log level adjustment", 0);
/* Serial input is directed to DOM0 by default. */
switch_serial_input();
@@ -1031,6 +1031,45 @@ struct xen_sysctl_livepatch_op {
typedef struct xen_sysctl_livepatch_op xen_sysctl_livepatch_op_t;
DEFINE_XEN_GUEST_HANDLE(xen_sysctl_livepatch_op_t);
+/* XEN_SYSCTL_loglvl_op */
+#define XEN_SYSCTL_LOGLVL_get 0
+#define XEN_SYSCTL_LOGLVL_set 1
+struct xen_sysctl_loglvl_op {
+ uint32_t cmd; /* XEN_SYSCTL_LOGLVL_* */
+ struct xen_sysctl_loglvl_thresh {
+ /*
+ * IN/OUT variables.
+ *
+ * SET command:
+ *
+ * {lower,upper}_thresh: string representation of lower or
+ * upper threshold of log level.
+ *
+ * {lower,upper}_thresh_len: len == 0 indicates toolstack is
+ * not interested in setting the threshold. len should always
+ * count the trailing 0.
+ *
+ * GET command:
+ *
+ * {lower,upper}_thresh: points to buffer for hypervisor to
+ * fill in.
+ *
+ * {lower,upper}_thresh_len: len == 0 indicates toolstack is
+ * not interested in getting the threshold. For any len != 0,
+ * if len is too small, hypervisor returns -ENOBUFS. The
+ * actual length of the string representation of threshold
+ * (including trailing 0) is always copied back to to these
+ * fields.
+ */
+ XEN_GUEST_HANDLE_64(char) lower_thresh;
+ XEN_GUEST_HANDLE_64(char) upper_thresh;
+ uint32_t lower_thresh_len;
+ uint32_t upper_thresh_len;
+ } host, guest;
+};
+typedef struct xen_sysctl_loglvl_op xen_sysctl_loglvl_op_t;
+DEFINE_XEN_GUEST_HANDLE(xen_sysctl_loglvl_op_t);
+
struct xen_sysctl {
uint32_t cmd;
#define XEN_SYSCTL_readconsole 1
@@ -1059,6 +1098,7 @@ struct xen_sysctl {
#define XEN_SYSCTL_get_cpu_levelling_caps 25
#define XEN_SYSCTL_get_cpu_featureset 26
#define XEN_SYSCTL_livepatch_op 27
+#define XEN_SYSCTL_loglvl_op 28
uint32_t interface_version; /* XEN_SYSCTL_INTERFACE_VERSION */
union {
struct xen_sysctl_readconsole readconsole;
@@ -1087,6 +1127,7 @@ struct xen_sysctl {
struct xen_sysctl_cpu_levelling_caps cpu_levelling_caps;
struct xen_sysctl_cpu_featureset cpu_featureset;
struct xen_sysctl_livepatch_op livepatch;
+ struct xen_sysctl_loglvl_op loglvl;
uint8_t pad[128];
} u;
};
@@ -12,6 +12,8 @@
struct xen_sysctl_readconsole;
long read_console_ring(struct xen_sysctl_readconsole *op);
+struct xen_sysctl_loglvl_op;
+int console_loglvl_op(struct xen_sysctl_loglvl_op *);
void console_init_preirq(void);
void console_init_ring(void);
@@ -823,6 +823,9 @@ static int flask_sysctl(int cmd)
return avc_current_has_perm(SECINITSID_XEN, SECCLASS_XEN2,
XEN2__LIVEPATCH_OP, NULL);
+ case XEN_SYSCTL_loglvl_op:
+ return domain_has_xen(current->domain, XEN2__LOGLVL);
+
default:
return avc_unknown_permission("sysctl", cmd);
}
@@ -99,6 +99,8 @@ class xen2
get_cpu_featureset
# XEN_SYSCTL_livepatch_op
livepatch_op
+# XEN_SYSCTL_loglvl_op
+ loglvl
}
# Classes domain and domain2 consist of operations that a domain performs on