summaryrefslogtreecommitdiffstats
path: root/drivers/net/netconsole.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/netconsole.c')
-rw-r--r--drivers/net/netconsole.c393
1 files changed, 328 insertions, 65 deletions
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c
index 86ab4a42769a..4289ccd3e41b 100644
--- a/drivers/net/netconsole.c
+++ b/drivers/net/netconsole.c
@@ -45,12 +45,12 @@ MODULE_DESCRIPTION("Console driver for network interfaces");
MODULE_LICENSE("GPL");
#define MAX_PARAM_LENGTH 256
-#define MAX_USERDATA_ENTRY_LENGTH 256
-#define MAX_USERDATA_VALUE_LENGTH 200
+#define MAX_EXTRADATA_ENTRY_LEN 256
+#define MAX_EXTRADATA_VALUE_LEN 200
/* The number 3 comes from userdata entry format characters (' ', '=', '\n') */
-#define MAX_USERDATA_NAME_LENGTH (MAX_USERDATA_ENTRY_LENGTH - \
- MAX_USERDATA_VALUE_LENGTH - 3)
-#define MAX_USERDATA_ITEMS 16
+#define MAX_EXTRADATA_NAME_LEN (MAX_EXTRADATA_ENTRY_LEN - \
+ MAX_EXTRADATA_VALUE_LEN - 3)
+#define MAX_EXTRADATA_ITEMS 16
#define MAX_PRINT_CHUNK 1000
static char config[MAX_PARAM_LENGTH];
@@ -97,13 +97,27 @@ struct netconsole_target_stats {
struct u64_stats_sync syncp;
};
+/* Features enabled in sysdata. Contrary to userdata, this data is populated by
+ * the kernel. The fields are designed as bitwise flags, allowing multiple
+ * features to be set in sysdata_fields.
+ */
+enum sysdata_feature {
+ /* Populate the CPU that sends the message */
+ SYSDATA_CPU_NR = BIT(0),
+ /* Populate the task name (as in current->comm) in sysdata */
+ SYSDATA_TASKNAME = BIT(1),
+ /* Kernel release/version as part of sysdata */
+ SYSDATA_RELEASE = BIT(2),
+};
+
/**
* struct netconsole_target - Represents a configured netconsole target.
* @list: Links this target into the target_list.
* @group: Links us into the configfs subsystem hierarchy.
* @userdata_group: Links to the userdata configfs hierarchy
- * @userdata_complete: Cached, formatted string of append
- * @userdata_length: String length of userdata_complete
+ * @extradata_complete: Cached, formatted string of append
+ * @userdata_length: String length of usedata in extradata_complete.
+ * @sysdata_fields: Sysdata features enabled.
* @stats: Packet send stats for the target. Used for debugging.
* @enabled: On / off knob to enable / disable target.
* Visible from userspace (read-write).
@@ -123,20 +137,25 @@ struct netconsole_target_stats {
* remote_ip (read-write)
* local_mac (read-only)
* remote_mac (read-write)
+ * @buf: The buffer used to send the full msg to the network stack
*/
struct netconsole_target {
struct list_head list;
#ifdef CONFIG_NETCONSOLE_DYNAMIC
struct config_group group;
struct config_group userdata_group;
- char userdata_complete[MAX_USERDATA_ENTRY_LENGTH * MAX_USERDATA_ITEMS];
+ char extradata_complete[MAX_EXTRADATA_ENTRY_LEN * MAX_EXTRADATA_ITEMS];
size_t userdata_length;
+ /* bit-wise with sysdata_feature bits */
+ u32 sysdata_fields;
#endif
struct netconsole_target_stats stats;
bool enabled;
bool extended;
bool release;
struct netpoll np;
+ /* protected by target_list_lock */
+ char buf[MAX_PRINT_CHUNK];
};
#ifdef CONFIG_NETCONSOLE_DYNAMIC
@@ -396,6 +415,46 @@ static ssize_t transmit_errors_show(struct config_item *item, char *buf)
return sysfs_emit(buf, "%llu\n", xmit_drop_count + enomem_count);
}
+/* configfs helper to display if cpu_nr sysdata feature is enabled */
+static ssize_t sysdata_cpu_nr_enabled_show(struct config_item *item, char *buf)
+{
+ struct netconsole_target *nt = to_target(item->ci_parent);
+ bool cpu_nr_enabled;
+
+ mutex_lock(&dynamic_netconsole_mutex);
+ cpu_nr_enabled = !!(nt->sysdata_fields & SYSDATA_CPU_NR);
+ mutex_unlock(&dynamic_netconsole_mutex);
+
+ return sysfs_emit(buf, "%d\n", cpu_nr_enabled);
+}
+
+/* configfs helper to display if taskname sysdata feature is enabled */
+static ssize_t sysdata_taskname_enabled_show(struct config_item *item,
+ char *buf)
+{
+ struct netconsole_target *nt = to_target(item->ci_parent);
+ bool taskname_enabled;
+
+ mutex_lock(&dynamic_netconsole_mutex);
+ taskname_enabled = !!(nt->sysdata_fields & SYSDATA_TASKNAME);
+ mutex_unlock(&dynamic_netconsole_mutex);
+
+ return sysfs_emit(buf, "%d\n", taskname_enabled);
+}
+
+static ssize_t sysdata_release_enabled_show(struct config_item *item,
+ char *buf)
+{
+ struct netconsole_target *nt = to_target(item->ci_parent);
+ bool release_enabled;
+
+ mutex_lock(&dynamic_netconsole_mutex);
+ release_enabled = !!(nt->sysdata_fields & SYSDATA_TASKNAME);
+ mutex_unlock(&dynamic_netconsole_mutex);
+
+ return sysfs_emit(buf, "%d\n", release_enabled);
+}
+
/*
* This one is special -- targets created through the configfs interface
* are not enabled (and the corresponding netpoll activated) by default.
@@ -659,6 +718,28 @@ out_unlock:
return ret;
}
+/* Count number of entries we have in extradata.
+ * This is important because the extradata_complete only supports
+ * MAX_EXTRADATA_ITEMS entries. Before enabling any new {user,sys}data
+ * feature, number of entries needs to checked for available space.
+ */
+static size_t count_extradata_entries(struct netconsole_target *nt)
+{
+ size_t entries;
+
+ /* Userdata entries */
+ entries = list_count_nodes(&nt->userdata_group.cg_children);
+ /* Plus sysdata entries */
+ if (nt->sysdata_fields & SYSDATA_CPU_NR)
+ entries += 1;
+ if (nt->sysdata_fields & SYSDATA_TASKNAME)
+ entries += 1;
+ if (nt->sysdata_fields & SYSDATA_RELEASE)
+ entries += 1;
+
+ return entries;
+}
+
static ssize_t remote_mac_store(struct config_item *item, const char *buf,
size_t count)
{
@@ -675,7 +756,7 @@ static ssize_t remote_mac_store(struct config_item *item, const char *buf,
if (!mac_pton(buf, remote_mac))
goto out_unlock;
- if (buf[3 * ETH_ALEN - 1] && buf[3 * ETH_ALEN - 1] != '\n')
+ if (buf[MAC_ADDR_STR_LEN] && buf[MAC_ADDR_STR_LEN] != '\n')
goto out_unlock;
memcpy(nt->np.remote_mac, remote_mac, ETH_ALEN);
@@ -687,7 +768,7 @@ out_unlock:
struct userdatum {
struct config_item item;
- char value[MAX_USERDATA_VALUE_LENGTH];
+ char value[MAX_EXTRADATA_VALUE_LEN];
};
static struct userdatum *to_userdatum(struct config_item *item)
@@ -724,13 +805,13 @@ static void update_userdata(struct netconsole_target *nt)
/* Clear the current string in case the last userdatum was deleted */
nt->userdata_length = 0;
- nt->userdata_complete[0] = 0;
+ nt->extradata_complete[0] = 0;
list_for_each(entry, &nt->userdata_group.cg_children) {
struct userdatum *udm_item;
struct config_item *item;
- if (WARN_ON_ONCE(child_count >= MAX_USERDATA_ITEMS))
+ if (WARN_ON_ONCE(child_count >= MAX_EXTRADATA_ITEMS))
break;
child_count++;
@@ -738,19 +819,19 @@ static void update_userdata(struct netconsole_target *nt)
udm_item = to_userdatum(item);
/* Skip userdata with no value set */
- if (strnlen(udm_item->value, MAX_USERDATA_VALUE_LENGTH) == 0)
+ if (strnlen(udm_item->value, MAX_EXTRADATA_VALUE_LEN) == 0)
continue;
- /* This doesn't overflow userdata_complete since it will write
- * one entry length (1/MAX_USERDATA_ITEMS long), entry count is
+ /* This doesn't overflow extradata_complete since it will write
+ * one entry length (1/MAX_EXTRADATA_ITEMS long), entry count is
* checked to not exceed MAX items with child_count above
*/
- complete_idx += scnprintf(&nt->userdata_complete[complete_idx],
- MAX_USERDATA_ENTRY_LENGTH, " %s=%s\n",
+ complete_idx += scnprintf(&nt->extradata_complete[complete_idx],
+ MAX_EXTRADATA_ENTRY_LEN, " %s=%s\n",
item->ci_name, udm_item->value);
}
- nt->userdata_length = strnlen(nt->userdata_complete,
- sizeof(nt->userdata_complete));
+ nt->userdata_length = strnlen(nt->extradata_complete,
+ sizeof(nt->extradata_complete));
}
static ssize_t userdatum_value_store(struct config_item *item, const char *buf,
@@ -761,7 +842,7 @@ static ssize_t userdatum_value_store(struct config_item *item, const char *buf,
struct userdata *ud;
ssize_t ret;
- if (count > MAX_USERDATA_VALUE_LENGTH)
+ if (count > MAX_EXTRADATA_VALUE_LEN)
return -EMSGSIZE;
mutex_lock(&dynamic_netconsole_mutex);
@@ -780,7 +861,132 @@ out_unlock:
return ret;
}
+/* disable_sysdata_feature - Disable sysdata feature and clean sysdata
+ * @nt: target that is disabling the feature
+ * @feature: feature being disabled
+ */
+static void disable_sysdata_feature(struct netconsole_target *nt,
+ enum sysdata_feature feature)
+{
+ nt->sysdata_fields &= ~feature;
+ nt->extradata_complete[nt->userdata_length] = 0;
+}
+
+static ssize_t sysdata_release_enabled_store(struct config_item *item,
+ const char *buf, size_t count)
+{
+ struct netconsole_target *nt = to_target(item->ci_parent);
+ bool release_enabled, curr;
+ ssize_t ret;
+
+ ret = kstrtobool(buf, &release_enabled);
+ if (ret)
+ return ret;
+
+ mutex_lock(&dynamic_netconsole_mutex);
+ curr = !!(nt->sysdata_fields & SYSDATA_RELEASE);
+ if (release_enabled == curr)
+ goto unlock_ok;
+
+ if (release_enabled &&
+ count_extradata_entries(nt) >= MAX_EXTRADATA_ITEMS) {
+ ret = -ENOSPC;
+ goto unlock;
+ }
+
+ if (release_enabled)
+ nt->sysdata_fields |= SYSDATA_RELEASE;
+ else
+ disable_sysdata_feature(nt, SYSDATA_RELEASE);
+
+unlock_ok:
+ ret = strnlen(buf, count);
+unlock:
+ mutex_unlock(&dynamic_netconsole_mutex);
+ return ret;
+}
+
+static ssize_t sysdata_taskname_enabled_store(struct config_item *item,
+ const char *buf, size_t count)
+{
+ struct netconsole_target *nt = to_target(item->ci_parent);
+ bool taskname_enabled, curr;
+ ssize_t ret;
+
+ ret = kstrtobool(buf, &taskname_enabled);
+ if (ret)
+ return ret;
+
+ mutex_lock(&dynamic_netconsole_mutex);
+ curr = !!(nt->sysdata_fields & SYSDATA_TASKNAME);
+ if (taskname_enabled == curr)
+ goto unlock_ok;
+
+ if (taskname_enabled &&
+ count_extradata_entries(nt) >= MAX_EXTRADATA_ITEMS) {
+ ret = -ENOSPC;
+ goto unlock;
+ }
+
+ if (taskname_enabled)
+ nt->sysdata_fields |= SYSDATA_TASKNAME;
+ else
+ disable_sysdata_feature(nt, SYSDATA_TASKNAME);
+
+unlock_ok:
+ ret = strnlen(buf, count);
+unlock:
+ mutex_unlock(&dynamic_netconsole_mutex);
+ return ret;
+}
+
+/* configfs helper to sysdata cpu_nr feature */
+static ssize_t sysdata_cpu_nr_enabled_store(struct config_item *item,
+ const char *buf, size_t count)
+{
+ struct netconsole_target *nt = to_target(item->ci_parent);
+ bool cpu_nr_enabled, curr;
+ ssize_t ret;
+
+ ret = kstrtobool(buf, &cpu_nr_enabled);
+ if (ret)
+ return ret;
+
+ mutex_lock(&dynamic_netconsole_mutex);
+ curr = !!(nt->sysdata_fields & SYSDATA_CPU_NR);
+ if (cpu_nr_enabled == curr)
+ /* no change requested */
+ goto unlock_ok;
+
+ if (cpu_nr_enabled &&
+ count_extradata_entries(nt) >= MAX_EXTRADATA_ITEMS) {
+ /* user wants the new feature, but there is no space in the
+ * buffer.
+ */
+ ret = -ENOSPC;
+ goto unlock;
+ }
+
+ if (cpu_nr_enabled)
+ nt->sysdata_fields |= SYSDATA_CPU_NR;
+ else
+ /* This is special because extradata_complete might have
+ * remaining data from previous sysdata, and it needs to be
+ * cleaned.
+ */
+ disable_sysdata_feature(nt, SYSDATA_CPU_NR);
+
+unlock_ok:
+ ret = strnlen(buf, count);
+unlock:
+ mutex_unlock(&dynamic_netconsole_mutex);
+ return ret;
+}
+
CONFIGFS_ATTR(userdatum_, value);
+CONFIGFS_ATTR(sysdata_, cpu_nr_enabled);
+CONFIGFS_ATTR(sysdata_, taskname_enabled);
+CONFIGFS_ATTR(sysdata_, release_enabled);
static struct configfs_attribute *userdatum_attrs[] = {
&userdatum_attr_value,
@@ -808,15 +1014,13 @@ static struct config_item *userdatum_make_item(struct config_group *group,
struct netconsole_target *nt;
struct userdatum *udm;
struct userdata *ud;
- size_t child_count;
- if (strlen(name) > MAX_USERDATA_NAME_LENGTH)
+ if (strlen(name) > MAX_EXTRADATA_NAME_LEN)
return ERR_PTR(-ENAMETOOLONG);
ud = to_userdata(&group->cg_item);
nt = userdata_to_target(ud);
- child_count = list_count_nodes(&nt->userdata_group.cg_children);
- if (child_count >= MAX_USERDATA_ITEMS)
+ if (count_extradata_entries(nt) >= MAX_EXTRADATA_ITEMS)
return ERR_PTR(-ENOSPC);
udm = kzalloc(sizeof(*udm), GFP_KERNEL);
@@ -842,6 +1046,9 @@ static void userdatum_drop(struct config_group *group, struct config_item *item)
}
static struct configfs_attribute *userdata_attrs[] = {
+ &sysdata_attr_cpu_nr_enabled,
+ &sysdata_attr_taskname_enabled,
+ &sysdata_attr_release_enabled,
NULL,
};
@@ -1017,6 +1224,63 @@ static void populate_configfs_item(struct netconsole_target *nt,
init_target_config_group(nt, target_name);
}
+static int sysdata_append_cpu_nr(struct netconsole_target *nt, int offset)
+{
+ /* Append cpu=%d at extradata_complete after userdata str */
+ return scnprintf(&nt->extradata_complete[offset],
+ MAX_EXTRADATA_ENTRY_LEN, " cpu=%u\n",
+ raw_smp_processor_id());
+}
+
+static int sysdata_append_taskname(struct netconsole_target *nt, int offset)
+{
+ return scnprintf(&nt->extradata_complete[offset],
+ MAX_EXTRADATA_ENTRY_LEN, " taskname=%s\n",
+ current->comm);
+}
+
+static int sysdata_append_release(struct netconsole_target *nt, int offset)
+{
+ return scnprintf(&nt->extradata_complete[offset],
+ MAX_EXTRADATA_ENTRY_LEN, " release=%s\n",
+ init_utsname()->release);
+}
+
+/*
+ * prepare_extradata - append sysdata at extradata_complete in runtime
+ * @nt: target to send message to
+ */
+static int prepare_extradata(struct netconsole_target *nt)
+{
+ u32 fields = SYSDATA_CPU_NR | SYSDATA_TASKNAME;
+ int extradata_len;
+
+ /* userdata was appended when configfs write helper was called
+ * by update_userdata().
+ */
+ extradata_len = nt->userdata_length;
+
+ if (!(nt->sysdata_fields & fields))
+ goto out;
+
+ if (nt->sysdata_fields & SYSDATA_CPU_NR)
+ extradata_len += sysdata_append_cpu_nr(nt, extradata_len);
+ if (nt->sysdata_fields & SYSDATA_TASKNAME)
+ extradata_len += sysdata_append_taskname(nt, extradata_len);
+ if (nt->sysdata_fields & SYSDATA_RELEASE)
+ extradata_len += sysdata_append_release(nt, extradata_len);
+
+ WARN_ON_ONCE(extradata_len >
+ MAX_EXTRADATA_ENTRY_LEN * MAX_EXTRADATA_ITEMS);
+
+out:
+ return extradata_len;
+}
+#else /* CONFIG_NETCONSOLE_DYNAMIC not set */
+static int prepare_extradata(struct netconsole_target *nt)
+{
+ return 0;
+}
#endif /* CONFIG_NETCONSOLE_DYNAMIC */
/* Handle network interface device notifications */
@@ -1117,29 +1381,28 @@ static void send_msg_no_fragmentation(struct netconsole_target *nt,
int msg_len,
int release_len)
{
- static char buf[MAX_PRINT_CHUNK]; /* protected by target_list_lock */
- const char *userdata = NULL;
+ const char *extradata = NULL;
const char *release;
#ifdef CONFIG_NETCONSOLE_DYNAMIC
- userdata = nt->userdata_complete;
+ extradata = nt->extradata_complete;
#endif
if (release_len) {
release = init_utsname()->release;
- scnprintf(buf, MAX_PRINT_CHUNK, "%s,%s", release, msg);
+ scnprintf(nt->buf, MAX_PRINT_CHUNK, "%s,%s", release, msg);
msg_len += release_len;
} else {
- memcpy(buf, msg, msg_len);
+ memcpy(nt->buf, msg, msg_len);
}
- if (userdata)
- msg_len += scnprintf(&buf[msg_len],
+ if (extradata)
+ msg_len += scnprintf(&nt->buf[msg_len],
MAX_PRINT_CHUNK - msg_len,
- "%s", userdata);
+ "%s", extradata);
- send_udp(nt, buf, msg_len);
+ send_udp(nt, nt->buf, msg_len);
}
static void append_release(char *buf)
@@ -1150,28 +1413,27 @@ static void append_release(char *buf)
scnprintf(buf, MAX_PRINT_CHUNK, "%s,", release);
}
-static void send_fragmented_body(struct netconsole_target *nt, char *buf,
+static void send_fragmented_body(struct netconsole_target *nt,
const char *msgbody, int header_len,
- int msgbody_len)
+ int msgbody_len, int extradata_len)
{
- const char *userdata = NULL;
+ int sent_extradata, preceding_bytes;
+ const char *extradata = NULL;
int body_len, offset = 0;
- int userdata_len = 0;
#ifdef CONFIG_NETCONSOLE_DYNAMIC
- userdata = nt->userdata_complete;
- userdata_len = nt->userdata_length;
+ extradata = nt->extradata_complete;
#endif
/* body_len represents the number of bytes that will be sent. This is
* bigger than MAX_PRINT_CHUNK, thus, it will be split in multiple
* packets
*/
- body_len = msgbody_len + userdata_len;
+ body_len = msgbody_len + extradata_len;
/* In each iteration of the while loop below, we send a packet
* containing the header and a portion of the body. The body is
- * composed of two parts: msgbody and userdata. We keep track of how
+ * composed of two parts: msgbody and extradata. We keep track of how
* many bytes have been sent so far using the offset variable, which
* ranges from 0 to the total length of the body.
*/
@@ -1181,7 +1443,7 @@ static void send_fragmented_body(struct netconsole_target *nt, char *buf,
int this_offset = 0;
int this_chunk = 0;
- this_header += scnprintf(buf + this_header,
+ this_header += scnprintf(nt->buf + this_header,
MAX_PRINT_CHUNK - this_header,
",ncfrag=%d/%d;", offset,
body_len);
@@ -1192,47 +1454,48 @@ static void send_fragmented_body(struct netconsole_target *nt, char *buf,
MAX_PRINT_CHUNK - this_header);
if (WARN_ON_ONCE(this_chunk <= 0))
return;
- memcpy(buf + this_header, msgbody + offset, this_chunk);
+ memcpy(nt->buf + this_header, msgbody + offset,
+ this_chunk);
this_offset += this_chunk;
}
/* msgbody was finally written, either in the previous
* messages and/or in the current buf. Time to write
- * the userdata.
+ * the extradata.
*/
msgbody_written |= offset + this_offset >= msgbody_len;
- /* Msg body is fully written and there is pending userdata to
- * write, append userdata in this chunk
+ /* Msg body is fully written and there is pending extradata to
+ * write, append extradata in this chunk
*/
if (msgbody_written && offset + this_offset < body_len) {
/* Track how much user data was already sent. First
* time here, sent_userdata is zero
*/
- int sent_userdata = (offset + this_offset) - msgbody_len;
+ sent_extradata = (offset + this_offset) - msgbody_len;
/* offset of bytes used in current buf */
- int preceding_bytes = this_chunk + this_header;
+ preceding_bytes = this_chunk + this_header;
- if (WARN_ON_ONCE(sent_userdata < 0))
+ if (WARN_ON_ONCE(sent_extradata < 0))
return;
- this_chunk = min(userdata_len - sent_userdata,
+ this_chunk = min(extradata_len - sent_extradata,
MAX_PRINT_CHUNK - preceding_bytes);
if (WARN_ON_ONCE(this_chunk < 0))
/* this_chunk could be zero if all the previous
* message used all the buffer. This is not a
- * problem, userdata will be sent in the next
+ * problem, extradata will be sent in the next
* iteration
*/
return;
- memcpy(buf + this_header + this_offset,
- userdata + sent_userdata,
+ memcpy(nt->buf + this_header + this_offset,
+ extradata + sent_extradata,
this_chunk);
this_offset += this_chunk;
}
- send_udp(nt, buf, this_header + this_offset);
+ send_udp(nt, nt->buf, this_header + this_offset);
offset += this_offset;
}
}
@@ -1240,9 +1503,9 @@ static void send_fragmented_body(struct netconsole_target *nt, char *buf,
static void send_msg_fragmented(struct netconsole_target *nt,
const char *msg,
int msg_len,
- int release_len)
+ int release_len,
+ int extradata_len)
{
- static char buf[MAX_PRINT_CHUNK]; /* protected by target_list_lock */
int header_len, msgbody_len;
const char *msgbody;
@@ -1260,16 +1523,17 @@ static void send_msg_fragmented(struct netconsole_target *nt,
* "ncfrag=<byte-offset>/<total-bytes>"
*/
if (release_len)
- append_release(buf);
+ append_release(nt->buf);
/* Copy the header into the buffer */
- memcpy(buf + release_len, msg, header_len);
+ memcpy(nt->buf + release_len, msg, header_len);
header_len += release_len;
/* for now on, the header will be persisted, and the msgbody
* will be replaced
*/
- send_fragmented_body(nt, buf, msgbody, header_len, msgbody_len);
+ send_fragmented_body(nt, msgbody, header_len, msgbody_len,
+ extradata_len);
}
/**
@@ -1285,20 +1549,19 @@ static void send_msg_fragmented(struct netconsole_target *nt,
static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg,
int msg_len)
{
- int userdata_len = 0;
int release_len = 0;
+ int extradata_len;
-#ifdef CONFIG_NETCONSOLE_DYNAMIC
- userdata_len = nt->userdata_length;
-#endif
+ extradata_len = prepare_extradata(nt);
if (nt->release)
release_len = strlen(init_utsname()->release) + 1;
- if (msg_len + release_len + userdata_len <= MAX_PRINT_CHUNK)
+ if (msg_len + release_len + extradata_len <= MAX_PRINT_CHUNK)
return send_msg_no_fragmentation(nt, msg, msg_len, release_len);
- return send_msg_fragmented(nt, msg, msg_len, release_len);
+ return send_msg_fragmented(nt, msg, msg_len, release_len,
+ extradata_len);
}
static void write_ext_msg(struct console *con, const char *msg,