soc: qcom: hab: add hab statistics support
This allows user to read back hab runtime information. Change-Id: Id266dd17b9c9d38f0e93aa600510ae1c6b12cca5 Signed-off-by: Yong Ding <yongding@codeaurora.org>
This commit is contained in:
parent
7adc4e13a4
commit
4faca7cde5
10 changed files with 387 additions and 22 deletions
|
@ -9,7 +9,8 @@ msm_hab-objs = \
|
|||
hab_mem_linux.o \
|
||||
hab_pipe.o \
|
||||
hab_parser.o \
|
||||
khab_test.o
|
||||
khab_test.o \
|
||||
hab_stat.o
|
||||
|
||||
ifdef CONFIG_GHS_VMM
|
||||
msm_hab_hyp-objs = \
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
.openlock = __SPIN_LOCK_UNLOCKED(&hab_devices[__num__].openlock)\
|
||||
}
|
||||
|
||||
static const char hab_info_str[] = "Change: 16239527 Revision: #65";
|
||||
static const char hab_info_str[] = "Change: 16764735 Revision: #76";
|
||||
|
||||
/*
|
||||
* The following has to match habmm definitions, order does not matter if
|
||||
|
@ -283,7 +283,7 @@ struct virtual_channel *frontend_open(struct uhab_context *ctx,
|
|||
pr_err("vchan alloc failed\n");
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
} else
|
||||
}
|
||||
|
||||
/* Send Init sequence */
|
||||
hab_open_request_init(&request, HAB_PAYLOAD_TYPE_INIT, pchan,
|
||||
|
@ -667,6 +667,7 @@ int hab_vchan_open(struct uhab_context *ctx,
|
|||
}
|
||||
} else {
|
||||
pr_err("failed to find device, mmid %d\n", mmid);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1368,6 +1369,9 @@ static int __init hab_init(void)
|
|||
} else
|
||||
set_dma_ops(hab_driver.dev, &hab_dma_ops);
|
||||
}
|
||||
|
||||
hab_stat_init(&hab_driver);
|
||||
|
||||
return result;
|
||||
|
||||
err:
|
||||
|
@ -1387,6 +1391,7 @@ static void __exit hab_exit(void)
|
|||
dev_t dev;
|
||||
|
||||
hab_hypervisor_unregister();
|
||||
hab_stat_deinit(&hab_driver);
|
||||
hab_ctx_put(hab_driver.kctx);
|
||||
dev = MKDEV(MAJOR(hab_driver.major), 0);
|
||||
device_destroy(hab_driver.class, dev);
|
||||
|
|
|
@ -43,6 +43,8 @@
|
|||
#include <linux/dma-mapping.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
enum hab_payload_type {
|
||||
HAB_PAYLOAD_TYPE_MSG = 0x0,
|
||||
|
@ -338,6 +340,8 @@ struct hab_driver {
|
|||
int b_loopback;
|
||||
|
||||
void *hyp_priv; /* hypervisor plug-in storage */
|
||||
|
||||
void *hab_vmm_handle;
|
||||
};
|
||||
|
||||
struct virtual_channel {
|
||||
|
@ -412,6 +416,7 @@ int hab_vchan_recv(struct uhab_context *ctx,
|
|||
void hab_vchan_stop(struct virtual_channel *vchan);
|
||||
void hab_vchans_stop(struct physical_channel *pchan);
|
||||
void hab_vchan_stop_notify(struct virtual_channel *vchan);
|
||||
void hab_vchans_empty_wait(int vmid);
|
||||
|
||||
int hab_mem_export(struct uhab_context *ctx,
|
||||
struct hab_export *param, int kernel);
|
||||
|
@ -456,7 +461,7 @@ int habmm_imp_hyp_unmap(void *imp_ctx, struct export_desc *exp, int kernel);
|
|||
|
||||
int habmem_imp_hyp_mmap(struct file *flip, struct vm_area_struct *vma);
|
||||
|
||||
|
||||
int habmm_imp_hyp_map_check(void *imp_ctx, struct export_desc *exp);
|
||||
|
||||
void hab_msg_free(struct hab_message *message);
|
||||
int hab_msg_dequeue(struct virtual_channel *vchan,
|
||||
|
@ -563,6 +568,15 @@ int hab_open_cancel_notify(struct hab_open_request *request);
|
|||
int hab_open_receive_cancel(struct physical_channel *pchan,
|
||||
size_t sizebytes);
|
||||
|
||||
int hab_stat_init(struct hab_driver *drv);
|
||||
int hab_stat_deinit(struct hab_driver *drv);
|
||||
int hab_stat_show_vchan(struct hab_driver *drv, char *buf, int sz);
|
||||
int hab_stat_show_ctx(struct hab_driver *drv, char *buf, int sz);
|
||||
int hab_stat_show_expimp(struct hab_driver *drv, int pid, char *buf, int sz);
|
||||
|
||||
int hab_stat_init_sub(struct hab_driver *drv);
|
||||
int hab_stat_deinit_sub(struct hab_driver *drv);
|
||||
|
||||
/* Global singleton HAB instance */
|
||||
extern struct hab_driver hab_driver;
|
||||
|
||||
|
|
|
@ -751,3 +751,22 @@ int habmem_imp_hyp_mmap(struct file *filp, struct vm_area_struct *vma)
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int habmm_imp_hyp_map_check(void *imp_ctx, struct export_desc *exp)
|
||||
{
|
||||
struct importer_context *priv = imp_ctx;
|
||||
struct pages_list *pglist;
|
||||
int found = 0;
|
||||
|
||||
read_lock(&priv->implist_lock);
|
||||
list_for_each_entry(pglist, &priv->imp_list, list) {
|
||||
if (pglist->export_id == exp->export_id &&
|
||||
pglist->vcid == exp->vcid_remote) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
read_unlock(&priv->implist_lock);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
|
|
@ -104,8 +104,8 @@ static struct export_desc *habmem_add_export(struct virtual_channel *vchan,
|
|||
exp->vchan = vchan;
|
||||
exp->vcid_local = vchan->id;
|
||||
exp->vcid_remote = vchan->otherend_id;
|
||||
exp->domid_local = -1; /* dom id, provided on the importer */
|
||||
exp->domid_remote = vchan->pchan->dom_id;
|
||||
exp->domid_local = vchan->pchan->vmid_local;
|
||||
exp->domid_remote = vchan->pchan->vmid_remote;
|
||||
exp->ctx = vchan->ctx;
|
||||
exp->pchan = vchan->pchan;
|
||||
|
||||
|
@ -124,7 +124,8 @@ void habmem_remove_export(struct export_desc *exp)
|
|||
struct uhab_context *ctx;
|
||||
|
||||
if (!exp || !exp->ctx || !exp->pchan) {
|
||||
pr_err("failed to find valid info in exp %pK\n", exp);
|
||||
pr_err("failed to find valid info in exp %pK ctx %pK pchan %pK\n",
|
||||
exp, exp->ctx, exp->pchan);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -49,5 +49,5 @@ struct qvm_channel {
|
|||
|
||||
void *qnx_hyp_rx_dispatch(void *data);
|
||||
void hab_pipe_reset(struct physical_channel *pchan);
|
||||
|
||||
void habhyp_notify(void *commdev);
|
||||
#endif /* __HAB_QNX_H */
|
||||
|
|
167
drivers/soc/qcom/hab/hab_stat.c
Normal file
167
drivers/soc/qcom/hab/hab_stat.c
Normal file
|
@ -0,0 +1,167 @@
|
|||
/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
#include "hab.h"
|
||||
#include "hab_grantable.h"
|
||||
|
||||
#define MAX_LINE_SIZE 128
|
||||
|
||||
int hab_stat_init(struct hab_driver *driver)
|
||||
{
|
||||
return hab_stat_init_sub(driver);
|
||||
}
|
||||
|
||||
int hab_stat_deinit(struct hab_driver *driver)
|
||||
{
|
||||
return hab_stat_deinit_sub(driver);
|
||||
}
|
||||
|
||||
/*
|
||||
* If all goes well the return value is the formated print and concatenated
|
||||
* original dest string.
|
||||
*/
|
||||
static int hab_stat_buffer_print(char *dest,
|
||||
int dest_size, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
char line[MAX_LINE_SIZE];
|
||||
int ret;
|
||||
|
||||
va_start(args, fmt);
|
||||
ret = vsnprintf(line, sizeof(line), fmt, args);
|
||||
va_end(args);
|
||||
if (ret > 0)
|
||||
ret = strlcat(dest, line, dest_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int hab_stat_show_vchan(struct hab_driver *driver,
|
||||
char *buf, int size)
|
||||
{
|
||||
int i, ret = 0;
|
||||
|
||||
ret = strlcpy(buf, "", size);
|
||||
for (i = 0; i < driver->ndevices; i++) {
|
||||
struct hab_device *dev = &driver->devp[i];
|
||||
struct physical_channel *pchan;
|
||||
struct virtual_channel *vc;
|
||||
|
||||
spin_lock_bh(&dev->pchan_lock);
|
||||
list_for_each_entry(pchan, &dev->pchannels, node) {
|
||||
if (!pchan->vcnt)
|
||||
continue;
|
||||
|
||||
ret = hab_stat_buffer_print(buf, size,
|
||||
"mmid %s role %d local %d remote %d vcnt %d:\n",
|
||||
pchan->name, pchan->is_be, pchan->vmid_local,
|
||||
pchan->vmid_remote, pchan->vcnt);
|
||||
|
||||
read_lock(&pchan->vchans_lock);
|
||||
list_for_each_entry(vc, &pchan->vchannels, pnode) {
|
||||
ret = hab_stat_buffer_print(buf, size,
|
||||
"%08X ", vc->id);
|
||||
}
|
||||
ret = hab_stat_buffer_print(buf, size, "\n");
|
||||
read_unlock(&pchan->vchans_lock);
|
||||
}
|
||||
spin_unlock_bh(&dev->pchan_lock);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int hab_stat_show_ctx(struct hab_driver *driver,
|
||||
char *buf, int size)
|
||||
{
|
||||
int ret = 0;
|
||||
struct uhab_context *ctx;
|
||||
|
||||
ret = strlcpy(buf, "", size);
|
||||
|
||||
spin_lock_bh(&hab_driver.drvlock);
|
||||
ret = hab_stat_buffer_print(buf, size,
|
||||
"Total contexts %d\n",
|
||||
driver->ctx_cnt);
|
||||
list_for_each_entry(ctx, &hab_driver.uctx_list, node) {
|
||||
ret = hab_stat_buffer_print(buf, size,
|
||||
"ctx %d K %d close %d vc %d exp %d imp %d open %d\n",
|
||||
ctx->owner, ctx->kernel, ctx->closing,
|
||||
ctx->vcnt, ctx->export_total,
|
||||
ctx->import_total, ctx->pending_cnt);
|
||||
}
|
||||
spin_unlock_bh(&hab_driver.drvlock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_pft_tbl_total_size(struct compressed_pfns *pfn_table)
|
||||
{
|
||||
int i, total_size = 0;
|
||||
|
||||
for (i = 0; i < pfn_table->nregions; i++)
|
||||
total_size += pfn_table->region[i].size * PAGE_SIZE;
|
||||
|
||||
return total_size;
|
||||
}
|
||||
|
||||
static int print_ctx_total_expimp(struct uhab_context *ctx,
|
||||
char *buf, int size)
|
||||
{
|
||||
struct compressed_pfns *pfn_table;
|
||||
int exp_total = 0, imp_total = 0;
|
||||
int exp_cnt = 0, imp_cnt = 0;
|
||||
struct export_desc *exp;
|
||||
|
||||
read_lock(&ctx->exp_lock);
|
||||
list_for_each_entry(exp, &ctx->exp_whse, node) {
|
||||
pfn_table = (struct compressed_pfns *)exp->payload;
|
||||
exp_total += get_pft_tbl_total_size(pfn_table);
|
||||
exp_cnt++;
|
||||
}
|
||||
read_unlock(&ctx->exp_lock);
|
||||
|
||||
spin_lock_bh(&ctx->imp_lock);
|
||||
list_for_each_entry(exp, &ctx->imp_whse, node) {
|
||||
if (habmm_imp_hyp_map_check(ctx->import_ctx, exp)) {
|
||||
pfn_table = (struct compressed_pfns *)exp->payload;
|
||||
imp_total += get_pft_tbl_total_size(pfn_table);
|
||||
imp_cnt++;
|
||||
}
|
||||
}
|
||||
spin_unlock_bh(&ctx->imp_lock);
|
||||
|
||||
if (exp_cnt || exp_total || imp_cnt || imp_total)
|
||||
return hab_stat_buffer_print(buf, size,
|
||||
"ctx %d exp %d size %d imp %d size %d\n",
|
||||
ctx->owner, exp_cnt, exp_total,
|
||||
imp_cnt, imp_total);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hab_stat_show_expimp(struct hab_driver *driver,
|
||||
int pid, char *buf, int size)
|
||||
{
|
||||
struct uhab_context *ctx;
|
||||
int ret;
|
||||
|
||||
ret = strlcpy(buf, "", size);
|
||||
|
||||
spin_lock_bh(&hab_driver.drvlock);
|
||||
list_for_each_entry(ctx, &hab_driver.uctx_list, node) {
|
||||
if (pid == ctx->owner)
|
||||
ret = print_ctx_total_expimp(ctx, buf, size);
|
||||
}
|
||||
spin_unlock_bh(&hab_driver.drvlock);
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -194,6 +194,55 @@ void hab_vchan_stop_notify(struct virtual_channel *vchan)
|
|||
hab_vchan_stop(vchan);
|
||||
}
|
||||
|
||||
static int hab_vchans_per_pchan_empty(struct physical_channel *pchan)
|
||||
{
|
||||
int empty;
|
||||
|
||||
read_lock(&pchan->vchans_lock);
|
||||
empty = list_empty(&pchan->vchannels);
|
||||
read_unlock(&pchan->vchans_lock);
|
||||
|
||||
return empty;
|
||||
}
|
||||
|
||||
static int hab_vchans_empty(int vmid)
|
||||
{
|
||||
int i, empty = 1;
|
||||
struct physical_channel *pchan;
|
||||
struct hab_device *hab_dev;
|
||||
|
||||
for (i = 0; i < hab_driver.ndevices; i++) {
|
||||
hab_dev = &hab_driver.devp[i];
|
||||
|
||||
spin_lock_bh(&hab_dev->pchan_lock);
|
||||
list_for_each_entry(pchan, &hab_dev->pchannels, node) {
|
||||
if (pchan->vmid_remote == vmid) {
|
||||
if (!hab_vchans_per_pchan_empty(pchan)) {
|
||||
empty = 0;
|
||||
spin_unlock_bh(&hab_dev->pchan_lock);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
spin_unlock_bh(&hab_dev->pchan_lock);
|
||||
}
|
||||
|
||||
return empty;
|
||||
}
|
||||
|
||||
/*
|
||||
* block until all vchans of a given GVM are explicitly closed
|
||||
* with habmm_socket_close() by hab clients themselves
|
||||
*/
|
||||
void hab_vchans_empty_wait(int vmid)
|
||||
{
|
||||
pr_info("waiting for GVM%d's sockets closure\n", vmid);
|
||||
|
||||
while (!hab_vchans_empty(vmid))
|
||||
schedule();
|
||||
|
||||
pr_info("all of GVM%d's sockets are closed\n", vmid);
|
||||
}
|
||||
|
||||
int hab_vchan_find_domid(struct virtual_channel *vchan)
|
||||
{
|
||||
|
|
|
@ -11,13 +11,12 @@
|
|||
*
|
||||
*/
|
||||
#include "hab.h"
|
||||
#include "khab_test.h"
|
||||
#include "hab_pipe.h"
|
||||
#ifdef CONFIG_MSM_GVM_QUIN
|
||||
#include "hab_qvm.h"
|
||||
#endif
|
||||
#if !defined CONFIG_GHS_VMM && defined(CONFIG_MSM_GVM_QUIN)
|
||||
#include <asm/cacheflush.h>
|
||||
#include <linux/list.h>
|
||||
#include "hab_pipe.h"
|
||||
#include "hab_qvm.h"
|
||||
#include "khab_test.h"
|
||||
|
||||
static char g_perf_test_result[256];
|
||||
|
||||
|
@ -32,10 +31,8 @@ enum hab_perf_test_type {
|
|||
static int hab_shmm_throughput_test(void)
|
||||
{
|
||||
struct hab_device *habDev;
|
||||
#ifdef CONFIG_MSM_GVM_QUIN
|
||||
struct qvm_channel *dev;
|
||||
#endif
|
||||
struct hab_shared_buf *sh_buf = NULL;
|
||||
struct hab_shared_buf *sh_buf;
|
||||
struct physical_channel *pchan;
|
||||
struct timeval tv1, tv2;
|
||||
int i, counter;
|
||||
|
@ -56,7 +53,6 @@ static int hab_shmm_throughput_test(void)
|
|||
|
||||
pchan = list_first_entry(&(habDev->pchannels),
|
||||
struct physical_channel, node);
|
||||
#ifdef CONFIG_MSM_GVM_QUIN
|
||||
dev = pchan->hyp_data;
|
||||
if (!dev) {
|
||||
ret = -EPERM;
|
||||
|
@ -64,7 +60,6 @@ static int hab_shmm_throughput_test(void)
|
|||
}
|
||||
|
||||
sh_buf = dev->pipe_ep->tx_info.sh_buf;
|
||||
#endif
|
||||
|
||||
/* pChannel is of 128k, we use 64k to test */
|
||||
size = 0x10000;
|
||||
|
@ -268,3 +263,112 @@ static int get_hab_perf_result(char *buffer, struct kernel_param *kp)
|
|||
return strlcpy(buffer, g_perf_test_result,
|
||||
strlen(g_perf_test_result)+1);
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct kobject *hab_kobject;
|
||||
|
||||
static int vchan_stat;
|
||||
static int context_stat;
|
||||
static int pid_stat;
|
||||
|
||||
static ssize_t vchan_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return hab_stat_show_vchan(&hab_driver, buf, PAGE_SIZE);
|
||||
}
|
||||
|
||||
static ssize_t vchan_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf, size_t count)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sscanf(buf, "%du", &vchan_stat);
|
||||
if (ret < 1) {
|
||||
pr_err("failed to read anything from input %d", ret);
|
||||
return 0;
|
||||
} else
|
||||
return vchan_stat;
|
||||
}
|
||||
|
||||
static ssize_t ctx_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return hab_stat_show_ctx(&hab_driver, buf, PAGE_SIZE);
|
||||
}
|
||||
|
||||
static ssize_t ctx_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf, size_t count)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sscanf(buf, "%du", &context_stat);
|
||||
if (ret < 1) {
|
||||
pr_err("failed to read anything from input %d", ret);
|
||||
return 0;
|
||||
} else
|
||||
return context_stat;
|
||||
}
|
||||
|
||||
static ssize_t expimp_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return hab_stat_show_expimp(&hab_driver, pid_stat, buf, PAGE_SIZE);
|
||||
}
|
||||
|
||||
static ssize_t expimp_store(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf, size_t count)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sscanf(buf, "%du", &pid_stat);
|
||||
if (ret < 1) {
|
||||
pr_err("failed to read anything from input %d", ret);
|
||||
return 0;
|
||||
} else
|
||||
return pid_stat;
|
||||
}
|
||||
|
||||
static struct kobj_attribute vchan_attribute = __ATTR(vchan_stat, 0660,
|
||||
vchan_show,
|
||||
vchan_store);
|
||||
|
||||
static struct kobj_attribute ctx_attribute = __ATTR(context_stat, 0660,
|
||||
ctx_show,
|
||||
ctx_store);
|
||||
|
||||
static struct kobj_attribute expimp_attribute = __ATTR(pid_stat, 0660,
|
||||
expimp_show,
|
||||
expimp_store);
|
||||
|
||||
int hab_stat_init_sub(struct hab_driver *driver)
|
||||
{
|
||||
int result;
|
||||
|
||||
hab_kobject = kobject_create_and_add("hab", kernel_kobj);
|
||||
if (!hab_kobject)
|
||||
return -ENOMEM;
|
||||
|
||||
result = sysfs_create_file(hab_kobject, &vchan_attribute.attr);
|
||||
if (result)
|
||||
pr_debug("cannot add vchan in /sys/kernel/hab %d\n", result);
|
||||
|
||||
result = sysfs_create_file(hab_kobject, &ctx_attribute.attr);
|
||||
if (result)
|
||||
pr_debug("cannot add ctx in /sys/kernel/hab %d\n", result);
|
||||
|
||||
result = sysfs_create_file(hab_kobject, &expimp_attribute.attr);
|
||||
if (result)
|
||||
pr_debug("cannot add expimp in /sys/kernel/hab %d\n", result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int hab_stat_deinit_sub(struct hab_driver *driver)
|
||||
{
|
||||
sysfs_remove_file(hab_kobject, &vchan_attribute.attr);
|
||||
sysfs_remove_file(hab_kobject, &ctx_attribute.attr);
|
||||
sysfs_remove_file(hab_kobject, &expimp_attribute.attr);
|
||||
kobject_put(hab_kobject);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include "hab.h"
|
||||
#include "hab_qvm.h"
|
||||
|
||||
static inline void habhyp_notify(void *commdev)
|
||||
inline void habhyp_notify(void *commdev)
|
||||
{
|
||||
struct qvm_channel *dev = (struct qvm_channel *)commdev;
|
||||
|
||||
|
@ -70,9 +70,14 @@ int physical_channel_send(struct physical_channel *pchan,
|
|||
struct habmm_xing_vm_stat *pstat =
|
||||
(struct habmm_xing_vm_stat *)payload;
|
||||
|
||||
do_gettimeofday(&tv);
|
||||
pstat->tx_sec = tv.tv_sec;
|
||||
pstat->tx_usec = tv.tv_usec;
|
||||
if (pstat) {
|
||||
do_gettimeofday(&tv);
|
||||
pstat->tx_sec = tv.tv_sec;
|
||||
pstat->tx_usec = tv.tv_usec;
|
||||
} else {
|
||||
spin_unlock_bh(&dev->io_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (sizebytes) {
|
||||
|
|
Loading…
Add table
Reference in a new issue