mirror of
https://github.com/SCST-project/scst.git
synced 2026-08-27 19:37:17 +00:00
Assigning CPU affinity to threads and connections
git-svn-id: http://svn.code.sf.net/p/scst/svn/trunk@2317 d57e44dd-8a1f-0410-8b47-8ef2f437770f
This commit is contained in:
@@ -1,3 +1,16 @@
|
||||
Summary of changes between versions 2.0.0 and 2.1.0
|
||||
---------------------------------------------------
|
||||
|
||||
- Assigning CPU affinity to threads and connections
|
||||
|
||||
- Splitting read/write CDBs for pass-through devices with sg_tablesize
|
||||
and max_sectors limitations added to scst_disk.
|
||||
|
||||
- Support for thin provisioning commands added in scst_vdisk.
|
||||
|
||||
- Bug fixes and other minor improvements.
|
||||
|
||||
|
||||
Summary of changes between versions 1.0.1 and 1.0.2
|
||||
---------------------------------------------------
|
||||
|
||||
|
||||
+15
-8
@@ -515,6 +515,10 @@ Every target should have at least the following entries:
|
||||
initiators security groups, so you can assign the addressing method
|
||||
on per-initiator basis.
|
||||
|
||||
- cpu_mask - defines CPU affinity mask for threads serving this target.
|
||||
For threads serving LUNs it is used only for devices with
|
||||
threads_pool_type "per_initiator".
|
||||
|
||||
- io_grouping_type - defines how I/O from sessions to this target are
|
||||
grouped together. This I/O grouping is very important for
|
||||
performance. By setting this attribute in a right value, you can
|
||||
@@ -668,7 +672,8 @@ commands by looking inside this file.
|
||||
- "del GROUP_NAME" - deletes a new security group.
|
||||
|
||||
Each security group's subdirectory contains 2 subdirectories: initiators
|
||||
and luns.
|
||||
and luns as well as the following attributes: addr_method, cpu_mask and
|
||||
io_grouping_type. See above description of them.
|
||||
|
||||
Each "initiators" subdirectory contains list of added to this groups
|
||||
initiator as well as as well as file "mgmt". This file has the following
|
||||
@@ -1487,13 +1492,15 @@ IMPORTANT: If you use on initiator some versions of Windows (at least W2K)
|
||||
for VDISK FILEIO devices above.
|
||||
|
||||
|
||||
9. In some cases, for instance working with SSD devices, which consume 100%
|
||||
of a single CPU load for data transfers in their internal threads, to
|
||||
maximize IOPS it can be needed to assign for those threads dedicated
|
||||
CPUs using Linux CPU affinity facilities. No IRQ processing should be
|
||||
done on those CPUs. Check that using /proc/interrupts. See taskset
|
||||
command and Documentation/IRQ-affinity.txt in your kernel's source tree
|
||||
for how to assign IRQ affinity to tasks and IRQs.
|
||||
9. In some cases, for instance working with SSD devices, which consume
|
||||
100% of a single CPU load for data transfers in their internal threads,
|
||||
to maximize IOPS it can be needed to assign for those threads dedicated
|
||||
CPUs. Consider using cpu_mask attribute for devices with
|
||||
threads_pool_type "per_initiator" or Linux CPU affinity facilities for
|
||||
other threads_pool_types. No IRQ processing should be done on those
|
||||
CPUs. Check that using /proc/interrupts. See taskset command and
|
||||
Documentation/IRQ-affinity.txt in your kernel's source tree for how to
|
||||
assign IRQ affinity to tasks and IRQs.
|
||||
|
||||
The reason for that is that processing of coming commands in SIRQ
|
||||
context might be done on the same CPUs as SSD devices' threads doing data
|
||||
|
||||
+15
-8
@@ -383,6 +383,10 @@ Every target should have at least the following entries:
|
||||
initiators security groups, so you can assign the addressing method
|
||||
on per-initiator basis.
|
||||
|
||||
- cpu_mask - defines CPU affinity mask for threads serving this target.
|
||||
For threads serving LUNs it is used only for devices with
|
||||
threads_pool_type "per_initiator".
|
||||
|
||||
- io_grouping_type - defines how I/O from sessions to this target are
|
||||
grouped together. This I/O grouping is very important for
|
||||
performance. By setting this attribute in a right value, you can
|
||||
@@ -536,7 +540,8 @@ commands by looking inside this file.
|
||||
- "del GROUP_NAME" - deletes a new security group.
|
||||
|
||||
Each security group's subdirectory contains 2 subdirectories: initiators
|
||||
and luns.
|
||||
and luns as well as the following attributes: addr_method, cpu_mask and
|
||||
io_grouping_type. See above description of them.
|
||||
|
||||
Each "initiators" subdirectory contains list of added to this groups
|
||||
initiator as well as as well as file "mgmt". This file has the following
|
||||
@@ -1306,13 +1311,15 @@ IMPORTANT: If you use on initiator some versions of Windows (at least W2K)
|
||||
See also important notes about setting block sizes >512 bytes
|
||||
for VDISK FILEIO devices above.
|
||||
|
||||
9. In some cases, for instance working with SSD devices, which consume 100%
|
||||
of a single CPU load for data transfers in their internal threads, to
|
||||
maximize IOPS it can be needed to assign for those threads dedicated
|
||||
CPUs using Linux CPU affinity facilities. No IRQ processing should be
|
||||
done on those CPUs. Check that using /proc/interrupts. See taskset
|
||||
command and Documentation/IRQ-affinity.txt in your kernel's source tree
|
||||
for how to assign IRQ affinity to tasks and IRQs.
|
||||
9. In some cases, for instance working with SSD devices, which consume
|
||||
100% of a single CPU load for data transfers in their internal threads,
|
||||
to maximize IOPS it can be needed to assign for those threads dedicated
|
||||
CPUs. Consider using cpu_mask attribute for devices with
|
||||
threads_pool_type "per_initiator" or Linux CPU affinity facilities for
|
||||
other threads_pool_types. No IRQ processing should be done on those
|
||||
CPUs. Check that using /proc/interrupts. See taskset command and
|
||||
Documentation/IRQ-affinity.txt in your kernel's source tree for how to
|
||||
assign IRQ affinity to tasks and IRQs.
|
||||
|
||||
The reason for that is that processing of coming commands in SIRQ
|
||||
context might be done on the same CPUs as SSD devices' threads doing data
|
||||
|
||||
@@ -335,6 +335,11 @@ enum scst_exec_context {
|
||||
*/
|
||||
#define SCST_AEN_SCSI 0
|
||||
|
||||
/*
|
||||
* Notifies that CPU affinity mask on the corresponding session changed
|
||||
*/
|
||||
#define SCST_AEN_CPU_MASK_CHANGED 1
|
||||
|
||||
/*************************************************************
|
||||
** Allowed return/status codes for report_aen() callback and
|
||||
** scst_set_aen_delivery_status() function
|
||||
@@ -2390,6 +2395,9 @@ struct scst_acg {
|
||||
/* Type of I/O initiators groupping */
|
||||
int acg_io_grouping_type;
|
||||
|
||||
/* CPU affinity for threads in this ACG */
|
||||
struct cpumask acg_cpu_mask;
|
||||
|
||||
unsigned int tgt_acg:1;
|
||||
|
||||
/* sysfs release completion */
|
||||
@@ -3887,6 +3895,7 @@ struct scst_sysfs_work_item {
|
||||
bool is_tgt_kobj;
|
||||
int io_grouping_type;
|
||||
bool enable;
|
||||
struct cpumask cpu_mask;
|
||||
};
|
||||
};
|
||||
struct {
|
||||
|
||||
+3
-2
@@ -1325,7 +1325,7 @@ void scst_set_initial_UA(struct scst_session *sess, int key, int asc, int ascq)
|
||||
}
|
||||
EXPORT_SYMBOL(scst_set_initial_UA);
|
||||
|
||||
static struct scst_aen *scst_alloc_aen(struct scst_session *sess,
|
||||
struct scst_aen *scst_alloc_aen(struct scst_session *sess,
|
||||
uint64_t unpacked_lun)
|
||||
{
|
||||
struct scst_aen *aen;
|
||||
@@ -1351,7 +1351,7 @@ out:
|
||||
return aen;
|
||||
}
|
||||
|
||||
static void scst_free_aen(struct scst_aen *aen)
|
||||
void scst_free_aen(struct scst_aen *aen)
|
||||
{
|
||||
TRACE_ENTRY();
|
||||
|
||||
@@ -2814,6 +2814,7 @@ struct scst_acg *scst_alloc_add_acg(struct scst_tgt *tgt,
|
||||
INIT_LIST_HEAD(&acg->acg_dev_list);
|
||||
INIT_LIST_HEAD(&acg->acg_sess_list);
|
||||
INIT_LIST_HEAD(&acg->acn_list);
|
||||
cpumask_copy(&acg->acg_cpu_mask, &default_cpu_mask);
|
||||
acg->acg_name = kstrdup(acg_name, GFP_KERNEL);
|
||||
if (acg->acg_name == NULL) {
|
||||
PRINT_ERROR("%s", "Allocation of acg_name failed");
|
||||
|
||||
@@ -158,6 +158,8 @@ static int suspend_count;
|
||||
|
||||
static int scst_virt_dev_last_id; /* protected by scst_mutex */
|
||||
|
||||
struct cpumask default_cpu_mask;
|
||||
|
||||
static unsigned int scst_max_cmd_mem;
|
||||
unsigned int scst_max_dev_cmd_mem;
|
||||
|
||||
@@ -1588,9 +1590,15 @@ int scst_add_threads(struct scst_cmd_threads *cmd_threads,
|
||||
cmd_threads, "%s%d", nm, n++);
|
||||
} else if (tgt_dev != NULL) {
|
||||
char nm[11]; /* to limit the name's len */
|
||||
int rc;
|
||||
strlcpy(nm, tgt_dev->dev->virt_name, ARRAY_SIZE(nm));
|
||||
thr->cmd_thread = kthread_create(scst_cmd_thread,
|
||||
cmd_threads, "%s%d_%d", nm, tgt_dev_num, n++);
|
||||
rc = set_cpus_allowed_ptr(thr->cmd_thread,
|
||||
&tgt_dev->sess->acg->acg_cpu_mask);
|
||||
if (rc != 0)
|
||||
PRINT_ERROR("Setting CPU affinity failed: "
|
||||
"%d", rc);
|
||||
} else
|
||||
thr->cmd_thread = kthread_create(scst_cmd_thread,
|
||||
cmd_threads, "scstd%d", n++);
|
||||
@@ -2182,6 +2190,7 @@ static int __init init_scst(void)
|
||||
init_waitqueue_head(&scst_dev_cmd_waitQ);
|
||||
mutex_init(&scst_suspend_mutex);
|
||||
INIT_LIST_HEAD(&scst_cmd_threads_list);
|
||||
cpus_setall(default_cpu_mask);
|
||||
|
||||
scst_init_threads(&scst_main_cmd_threads);
|
||||
|
||||
|
||||
@@ -193,6 +193,8 @@ extern spinlock_t scst_mgmt_lock;
|
||||
extern struct list_head scst_sess_init_list;
|
||||
extern struct list_head scst_sess_shut_list;
|
||||
|
||||
extern struct cpumask default_cpu_mask;
|
||||
|
||||
struct scst_cmd_thread_t {
|
||||
struct task_struct *cmd_thread;
|
||||
struct list_head thread_list_entry;
|
||||
@@ -550,6 +552,10 @@ void scst_process_reset(struct scst_device *dev,
|
||||
bool scst_is_ua_global(const uint8_t *sense, int len);
|
||||
void scst_requeue_ua(struct scst_cmd *cmd);
|
||||
|
||||
struct scst_aen *scst_alloc_aen(struct scst_session *sess,
|
||||
uint64_t unpacked_lun);
|
||||
void scst_free_aen(struct scst_aen *aen);
|
||||
|
||||
void scst_gen_aen_or_ua(struct scst_tgt_dev *tgt_dev,
|
||||
int key, int asc, int ascq);
|
||||
|
||||
|
||||
+228
-1
@@ -123,6 +123,12 @@ static ssize_t scst_tgt_io_grouping_type_show(struct kobject *kobj,
|
||||
static ssize_t scst_tgt_io_grouping_type_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
static ssize_t scst_tgt_cpu_mask_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf);
|
||||
static ssize_t scst_tgt_cpu_mask_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
static ssize_t scst_ini_group_mgmt_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf);
|
||||
@@ -156,6 +162,12 @@ static ssize_t scst_acg_io_grouping_type_show(struct kobject *kobj,
|
||||
static ssize_t scst_acg_io_grouping_type_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
static ssize_t scst_acg_cpu_mask_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
char *buf);
|
||||
static ssize_t scst_acg_cpu_mask_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
static ssize_t scst_acn_file_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf);
|
||||
|
||||
@@ -964,6 +976,11 @@ static struct kobj_attribute scst_tgt_io_grouping_type =
|
||||
scst_tgt_io_grouping_type_show,
|
||||
scst_tgt_io_grouping_type_store);
|
||||
|
||||
static struct kobj_attribute scst_tgt_cpu_mask =
|
||||
__ATTR(cpu_mask, S_IRUGO | S_IWUSR,
|
||||
scst_tgt_cpu_mask_show,
|
||||
scst_tgt_cpu_mask_store);
|
||||
|
||||
static struct kobj_attribute scst_rel_tgt_id =
|
||||
__ATTR(rel_tgt_id, S_IRUGO | S_IWUSR, scst_rel_tgt_id_show,
|
||||
scst_rel_tgt_id_store);
|
||||
@@ -977,6 +994,11 @@ static struct kobj_attribute scst_acg_io_grouping_type =
|
||||
scst_acg_io_grouping_type_show,
|
||||
scst_acg_io_grouping_type_store);
|
||||
|
||||
static struct kobj_attribute scst_acg_cpu_mask =
|
||||
__ATTR(cpu_mask, S_IRUGO | S_IWUSR,
|
||||
scst_acg_cpu_mask_show,
|
||||
scst_acg_cpu_mask_store);
|
||||
|
||||
static ssize_t scst_tgt_enable_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
@@ -1183,6 +1205,13 @@ int scst_tgt_sysfs_create(struct scst_tgt *tgt)
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
res = sysfs_create_file(&tgt->tgt_kobj, &scst_tgt_cpu_mask.attr);
|
||||
if (res != 0) {
|
||||
PRINT_ERROR("Can't add attribute %s for tgt %s",
|
||||
scst_tgt_cpu_mask.attr.name, tgt->tgt_name);
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
pattr = tgt->tgtt->tgt_attrs;
|
||||
if (pattr != NULL) {
|
||||
while (*pattr != NULL) {
|
||||
@@ -3143,6 +3172,168 @@ out:
|
||||
return res;
|
||||
}
|
||||
|
||||
static ssize_t __scst_acg_cpu_mask_show(struct scst_acg *acg, char *buf)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = cpumask_scnprintf(buf, SCST_SYSFS_BLOCK_SIZE,
|
||||
&acg->acg_cpu_mask);
|
||||
if (!cpus_equal(acg->acg_cpu_mask, default_cpu_mask))
|
||||
res += sprintf(&buf[res], "\n%s\n", SCST_SYSFS_KEY_MARK);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int __scst_acg_process_cpu_mask_store(struct scst_tgt *tgt,
|
||||
struct scst_acg *acg, struct cpumask *cpu_mask)
|
||||
{
|
||||
int res = 0;
|
||||
struct scst_session *sess;
|
||||
|
||||
TRACE_DBG("tgt %p, acg %p", tgt, acg);
|
||||
|
||||
if (mutex_lock_interruptible(&scst_mutex) != 0) {
|
||||
res = -EINTR;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Check if tgt and acg not already freed while we were coming here */
|
||||
if (scst_check_tgt_acg_ptrs(tgt, acg) != 0)
|
||||
goto out_unlock;
|
||||
|
||||
cpumask_copy(&acg->acg_cpu_mask, cpu_mask);
|
||||
|
||||
list_for_each_entry(sess, &acg->acg_sess_list, acg_sess_list_entry) {
|
||||
int i;
|
||||
for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) {
|
||||
struct scst_tgt_dev *tgt_dev;
|
||||
struct list_head *head = &sess->sess_tgt_dev_list[i];
|
||||
list_for_each_entry(tgt_dev, head,
|
||||
sess_tgt_dev_list_entry) {
|
||||
struct scst_cmd_thread_t *thr;
|
||||
if (tgt_dev->active_cmd_threads != &tgt_dev->tgt_dev_cmd_threads)
|
||||
continue;
|
||||
list_for_each_entry(thr,
|
||||
&tgt_dev->active_cmd_threads->threads_list,
|
||||
thread_list_entry) {
|
||||
int rc;
|
||||
rc = set_cpus_allowed_ptr(thr->cmd_thread, cpu_mask);
|
||||
if (rc != 0)
|
||||
PRINT_ERROR("Setting CPU "
|
||||
"affinity failed: %d", rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tgt->tgtt->report_aen != NULL) {
|
||||
struct scst_aen *aen;
|
||||
int rc;
|
||||
|
||||
aen = scst_alloc_aen(sess, 0);
|
||||
if (aen == NULL) {
|
||||
PRINT_ERROR("Unable to notify target driver %s "
|
||||
"about cpu_mask change", tgt->tgt_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
aen->event_fn = SCST_AEN_CPU_MASK_CHANGED;
|
||||
|
||||
TRACE_DBG("Calling target's %s report_aen(%p)",
|
||||
tgt->tgtt->name, aen);
|
||||
rc = tgt->tgtt->report_aen(aen);
|
||||
TRACE_DBG("Target's %s report_aen(%p) returned %d",
|
||||
tgt->tgtt->name, aen, rc);
|
||||
if (rc != SCST_AEN_RES_SUCCESS)
|
||||
scst_free_aen(aen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&scst_mutex);
|
||||
|
||||
out:
|
||||
return res;
|
||||
}
|
||||
|
||||
static int __scst_acg_cpu_mask_store_work_fn(struct scst_sysfs_work_item *work)
|
||||
{
|
||||
return __scst_acg_process_cpu_mask_store(work->tgt, work->acg,
|
||||
&work->cpu_mask);
|
||||
}
|
||||
|
||||
static ssize_t __scst_acg_cpu_mask_store(struct scst_acg *acg,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int res;
|
||||
struct scst_sysfs_work_item *work;
|
||||
|
||||
/* cpumask might be too big for stack */
|
||||
|
||||
res = scst_alloc_sysfs_work(__scst_acg_cpu_mask_store_work_fn,
|
||||
false, &work);
|
||||
if (res != 0)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* We can't use cpumask_parse_user() here, because it expects
|
||||
* buffer in the user space.
|
||||
*/
|
||||
res = __bitmap_parse(buf, count, 0, cpumask_bits(&work->cpu_mask),
|
||||
nr_cpumask_bits);
|
||||
if (res != 0) {
|
||||
PRINT_ERROR("__bitmap_parse() failed: %d", res);
|
||||
goto out_release;
|
||||
}
|
||||
|
||||
if (cpus_equal(acg->acg_cpu_mask, work->cpu_mask))
|
||||
goto out;
|
||||
|
||||
work->tgt = acg->tgt;
|
||||
work->acg = acg;
|
||||
|
||||
res = scst_sysfs_queue_wait_work(work);
|
||||
|
||||
out:
|
||||
return res;
|
||||
|
||||
out_release:
|
||||
scst_sysfs_work_release(&work->sysfs_work_kref);
|
||||
goto out;
|
||||
}
|
||||
|
||||
static ssize_t scst_tgt_cpu_mask_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
struct scst_acg *acg;
|
||||
struct scst_tgt *tgt;
|
||||
|
||||
tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
|
||||
acg = tgt->default_acg;
|
||||
|
||||
return __scst_acg_cpu_mask_show(acg, buf);
|
||||
}
|
||||
|
||||
static ssize_t scst_tgt_cpu_mask_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int res;
|
||||
struct scst_acg *acg;
|
||||
struct scst_tgt *tgt;
|
||||
|
||||
tgt = container_of(kobj, struct scst_tgt, tgt_kobj);
|
||||
acg = tgt->default_acg;
|
||||
|
||||
res = __scst_acg_cpu_mask_store(acg, buf, count);
|
||||
if (res != 0)
|
||||
goto out;
|
||||
|
||||
res = count;
|
||||
|
||||
out:
|
||||
TRACE_EXIT_RES(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called with scst_mutex held.
|
||||
*
|
||||
@@ -3241,6 +3432,13 @@ int scst_acg_sysfs_create(struct scst_tgt *tgt,
|
||||
goto out_del;
|
||||
}
|
||||
|
||||
res = sysfs_create_file(&acg->acg_kobj, &scst_acg_cpu_mask.attr);
|
||||
if (res != 0) {
|
||||
PRINT_ERROR("Can't add tgt attr %s for tgt %s",
|
||||
scst_acg_cpu_mask.attr.name, tgt->tgt_name);
|
||||
goto out_del;
|
||||
}
|
||||
|
||||
out:
|
||||
TRACE_EXIT_RES(res);
|
||||
return res;
|
||||
@@ -3303,6 +3501,35 @@ out:
|
||||
return res;
|
||||
}
|
||||
|
||||
static ssize_t scst_acg_cpu_mask_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
struct scst_acg *acg;
|
||||
|
||||
acg = container_of(kobj, struct scst_acg, acg_kobj);
|
||||
|
||||
return __scst_acg_cpu_mask_show(acg, buf);
|
||||
}
|
||||
|
||||
static ssize_t scst_acg_cpu_mask_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
int res;
|
||||
struct scst_acg *acg;
|
||||
|
||||
acg = container_of(kobj, struct scst_acg, acg_kobj);
|
||||
|
||||
res = __scst_acg_cpu_mask_store(acg, buf, count);
|
||||
if (res != 0)
|
||||
goto out;
|
||||
|
||||
res = count;
|
||||
|
||||
out:
|
||||
TRACE_EXIT_RES(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
static ssize_t scst_ini_group_mgmt_show(struct kobject *kobj,
|
||||
struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
@@ -5199,7 +5426,7 @@ int __init scst_sysfs_init(void)
|
||||
NULL, "scst_uid");
|
||||
if (IS_ERR(sysfs_work_thread)) {
|
||||
res = PTR_ERR(sysfs_work_thread);
|
||||
PRINT_ERROR("kthread_create() for user interface thread "
|
||||
PRINT_ERROR("kthread_run() for user interface thread "
|
||||
"failed: %d", res);
|
||||
sysfs_work_thread = NULL;
|
||||
goto out;
|
||||
|
||||
Reference in New Issue
Block a user