mirror of
https://github.com/torvalds/linux.git
synced 2026-05-28 09:04:39 +02:00
Arm FF-A fixes for v7.1
The updates fix several robustness issues in the FF-A bus and core driver, mostly around notification handling, RxTx buffer sizing, firmware-provided bounds, and cleanup paths. The notification fixes make per-CPU notification work truly per-CPU, avoid running per-vCPU notification handling from IPI context, keep RX buffer release serialized under rx_lock, validate framework notification payload layout before copying from the shared RX buffer, and avoid dereferencing notifier entries after they may have been unregistered. The remaining fixes reject FF-A drivers without an ID table at registration time, avoid freeing an unallocated RX buffer after allocation failure, unregister the FF-A v1.0 bus notifier on teardown, bound register-based PARTITION_INFO_GET descriptor copies, align the stored RxTx buffer size with the size mapped to firmware, and fix sched-recv callback partition lookup on the circular partition list. -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEunHlEgbzHrJD3ZPhAEG6vDF+4pgFAmn7maMACgkQAEG6vDF+ 4pgGAw/+OClw1JVGF0i6nTBGxuQAmZQGtuM4HZEnxloFsxPLWFqoRGsIJ4MMLUnb xCClPqi7LoNCZKlSD3WxXcuVmAEO9J7gto8NzHzY+Mdn4ByhMfvlu6DZtLYLLwy2 33HCoW++VW1iXomZ3yBnyZmA8U6jMNTs7z5pE420iArxy4HfA2BfrRpBx+L4dA5n wEj4unrab9GHo43rFAkQsTb2rwGh40H3w41mD7TnHzwwsvTbFgIfNiHo0kneTGBa P8eXFj3b5tNBv9sOccjqZQKeANDmwmm2ncxtWs6dFgOi3QXI+UyQQvylnG8C96DM tSjNmwCcw7wWvjpK0wmVHrL0jh9pkpPHNJ6qIxlNKPkd+96L8vWo7uNbel76qLjU vP1y1RCjdtHeJs9xYW9BYUNoNTDOWKXWGivzIe4P3PevD4q+imqTMBPe6lpF0+50 4bIa//A2cm6iuPGL9ySgwJ9jEoXY5QAZD5TjGg3pcW2UFyL5F1Wg8yFQBHuU8XP2 N7nlmntzLRRNn8pjjAJckQWKUlH+7hiPoQJMiBGuIWLevlkjt2SZCMbG2RL7Ymbd 9+wlb8dUlNTMfdrBLZZMym1cp07gh/myDo0iSaK7WY5tvYJj0O/lI7DXf69adgH2 GFFd30JAo+CMXJuTadOZf5+3aUYzwCl1l4I8axT1gqyNC8A4/Bs= =iFfw -----END PGP SIGNATURE----- gpgsig -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEo6/YBQwIrVS28WGKmmx57+YAGNkFAmn8gG4ACgkQmmx57+YA GNnsPQ/+Ij20fAnnPF0eyvYj1vPn4LGWHZneD8DzPcms77QF5oQZnkbC/HIecAsC 6SqSSPtwuYVDQuPtEoALFh5RtZt0UF5lQgd8Xgk9Mi+cVLEVJkb8OJkNKXofC45Z FaW7apHcgZOMadmWATJHhniLnlYCodDP3LQq8n2CvfmL/RAXdvPrDnba6eiwj9nP iONVEsyFRHhpZAQvz/JgpsApRj2K6+XT9SR2yVAUQk+CNkA+QbvMI8QAA5YCiR2H GFFfi1AAzM41m5PQcWqFKUtgr3d8QIlDIvlaoGa7fofmxSSBgxXpC/BGLe+HlUrW ZGBuicbBg4eZIw2AX0zbBJxH/Zpg1Z7EuDM5EGEmyrMjyi35pDEouoFdTmIT1lr7 MwC2odJlcS1KYYyHQDleVHKrcUwnwa4Pw1lgganhnnADb4Rv1ziqHhoTP1qxNbbu qqXXD1+go9WYxh9CiLA2HbjiTGzNLm9DMOoIc+/cEwLAD8H3Xa7oK4Yplee6P9d7 utaTR3NwT7B5XQ2mbh7LZjBKz4W1DfMN+XrmI3y19bL30ufmOi8K6uKYrbLxRUAc gH9nO4BVCUXgCqTimfI0ORsA6ZoCXcNNpc491ANX15S9vmbN4rRKc6hNfriYEycw k5XIzT5H9v7Qu3OvkM4cTwP4iv0l6UFAB6u1+17I+2kG9/CJJZs= =wjLn -----END PGP SIGNATURE----- Merge tag 'ffa-fixes-7.1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux into arm/fixes Arm FF-A fixes for v7.1 The updates fix several robustness issues in the FF-A bus and core driver, mostly around notification handling, RxTx buffer sizing, firmware-provided bounds, and cleanup paths. The notification fixes make per-CPU notification work truly per-CPU, avoid running per-vCPU notification handling from IPI context, keep RX buffer release serialized under rx_lock, validate framework notification payload layout before copying from the shared RX buffer, and avoid dereferencing notifier entries after they may have been unregistered. The remaining fixes reject FF-A drivers without an ID table at registration time, avoid freeing an unallocated RX buffer after allocation failure, unregister the FF-A v1.0 bus notifier on teardown, bound register-based PARTITION_INFO_GET descriptor copies, align the stored RxTx buffer size with the size mapped to firmware, and fix sched-recv callback partition lookup on the circular partition list. * tag 'ffa-fixes-7.1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux: firmware: arm_ffa: Fix sched-recv callback partition lookup firmware: arm_ffa: Snapshot notifier callbacks under lock firmware: arm_ffa: Align RxTx buffer size before mapping firmware: arm_ffa: Validate framework notification message layout firmware: arm_ffa: Keep framework RX release under lock firmware: arm_ffa: Bound PARTITION_INFO_GET_REGS copies firmware: arm_ffa: Unregister bus notifier on teardown for FF-A v1.0 firmware: arm_ffa: Fix per-vcpu self notifications handling in workqueue firmware: arm_ffa: Avoid collapsing NPI work from different CPUs firmware: arm_ffa: Skip free_pages on RX buffer alloc failure firmware: arm_ffa: Check for NULL FF-A ID table while driver registration Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
commit
d1f29ea139
|
|
@ -26,6 +26,8 @@ static int ffa_device_match(struct device *dev, const struct device_driver *drv)
|
|||
|
||||
id_table = to_ffa_driver(drv)->id_table;
|
||||
ffa_dev = to_ffa_dev(dev);
|
||||
if (!id_table)
|
||||
return 0;
|
||||
|
||||
while (!uuid_is_null(&id_table->uuid)) {
|
||||
/*
|
||||
|
|
@ -123,7 +125,7 @@ int ffa_driver_register(struct ffa_driver *driver, struct module *owner,
|
|||
{
|
||||
int ret;
|
||||
|
||||
if (!driver->probe)
|
||||
if (!driver->probe || !driver->id_table)
|
||||
return -EINVAL;
|
||||
|
||||
driver->driver.bus = &ffa_bus_type;
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ static inline int ffa_to_linux_errno(int errno)
|
|||
|
||||
struct ffa_pcpu_irq {
|
||||
struct ffa_drv_info *info;
|
||||
struct work_struct notif_pcpu_work;
|
||||
};
|
||||
|
||||
struct ffa_drv_info {
|
||||
|
|
@ -100,13 +101,13 @@ struct ffa_drv_info {
|
|||
bool mem_ops_native;
|
||||
bool msg_direct_req2_supp;
|
||||
bool bitmap_created;
|
||||
bool bus_notifier_registered;
|
||||
bool notif_enabled;
|
||||
unsigned int sched_recv_irq;
|
||||
unsigned int notif_pend_irq;
|
||||
unsigned int cpuhp_state;
|
||||
struct ffa_pcpu_irq __percpu *irq_pcpu;
|
||||
struct workqueue_struct *notif_pcpu_wq;
|
||||
struct work_struct notif_pcpu_work;
|
||||
struct work_struct sched_recv_irq_work;
|
||||
struct xarray partition_info;
|
||||
DECLARE_HASHTABLE(notifier_hash, ilog2(FFA_MAX_NOTIFICATIONS));
|
||||
|
|
@ -322,6 +323,12 @@ __ffa_partition_info_get(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
|
|||
#define PART_INFO_ID_MASK GENMASK(15, 0)
|
||||
#define PART_INFO_EXEC_CXT_MASK GENMASK(31, 16)
|
||||
#define PART_INFO_PROPS_MASK GENMASK(63, 32)
|
||||
#define FFA_PART_INFO_GET_REGS_FIRST_REG 3
|
||||
#define FFA_PART_INFO_GET_REGS_REGS_PER_DESC 3
|
||||
#define FFA_PART_INFO_GET_REGS_MAX_DESC \
|
||||
(((sizeof(ffa_value_t) / sizeof_field(ffa_value_t, a0)) - \
|
||||
FFA_PART_INFO_GET_REGS_FIRST_REG) / \
|
||||
FFA_PART_INFO_GET_REGS_REGS_PER_DESC)
|
||||
#define PART_INFO_ID(x) ((u16)(FIELD_GET(PART_INFO_ID_MASK, (x))))
|
||||
#define PART_INFO_EXEC_CXT(x) ((u16)(FIELD_GET(PART_INFO_EXEC_CXT_MASK, (x))))
|
||||
#define PART_INFO_PROPERTIES(x) ((u32)(FIELD_GET(PART_INFO_PROPS_MASK, (x))))
|
||||
|
|
@ -329,15 +336,13 @@ static int
|
|||
__ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
|
||||
struct ffa_partition_info *buffer, int num_parts)
|
||||
{
|
||||
u16 buf_sz, start_idx, cur_idx, count = 0, prev_idx = 0, tag = 0;
|
||||
u16 buf_sz, start_idx = 0, cur_idx, count = 0, tag = 0;
|
||||
struct ffa_partition_info *buf = buffer;
|
||||
ffa_value_t partition_info;
|
||||
|
||||
do {
|
||||
__le64 *regs;
|
||||
int idx;
|
||||
|
||||
start_idx = prev_idx ? prev_idx + 1 : 0;
|
||||
int idx, nr_desc, buf_idx;
|
||||
|
||||
invoke_ffa_fn((ffa_value_t){
|
||||
.a0 = FFA_PARTITION_INFO_GET_REGS,
|
||||
|
|
@ -353,15 +358,28 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
|
|||
count = PARTITION_COUNT(partition_info.a2);
|
||||
if (!buffer || !num_parts) /* count only */
|
||||
return count;
|
||||
if (count > num_parts)
|
||||
return -EINVAL;
|
||||
|
||||
cur_idx = CURRENT_INDEX(partition_info.a2);
|
||||
if (cur_idx < start_idx || cur_idx >= count)
|
||||
return -EINVAL;
|
||||
|
||||
nr_desc = cur_idx - start_idx + 1;
|
||||
if (nr_desc > FFA_PART_INFO_GET_REGS_MAX_DESC)
|
||||
return -EINVAL;
|
||||
|
||||
buf_idx = buf - buffer;
|
||||
if (buf_idx + nr_desc > num_parts)
|
||||
return -EINVAL;
|
||||
|
||||
tag = UUID_INFO_TAG(partition_info.a2);
|
||||
buf_sz = PARTITION_INFO_SZ(partition_info.a2);
|
||||
if (buf_sz > sizeof(*buffer))
|
||||
buf_sz = sizeof(*buffer);
|
||||
|
||||
regs = (void *)&partition_info.a3;
|
||||
for (idx = 0; idx < cur_idx - start_idx + 1; idx++, buf++) {
|
||||
for (idx = 0; idx < nr_desc; idx++, buf++) {
|
||||
union {
|
||||
uuid_t uuid;
|
||||
u64 regs[2];
|
||||
|
|
@ -379,7 +397,7 @@ __ffa_partition_info_get_regs(u32 uuid0, u32 uuid1, u32 uuid2, u32 uuid3,
|
|||
uuid_copy(&buf->uuid, &uuid_regs.uuid);
|
||||
regs += 3;
|
||||
}
|
||||
prev_idx = cur_idx;
|
||||
start_idx = cur_idx + 1;
|
||||
|
||||
} while (cur_idx < (count - 1));
|
||||
|
||||
|
|
@ -1189,7 +1207,7 @@ static int
|
|||
ffa_sched_recv_cb_update(struct ffa_device *dev, ffa_sched_recv_cb callback,
|
||||
void *cb_data, bool is_registration)
|
||||
{
|
||||
struct ffa_dev_part_info *partition = NULL, *tmp;
|
||||
struct ffa_dev_part_info *partition = NULL;
|
||||
struct list_head *phead;
|
||||
bool cb_valid;
|
||||
|
||||
|
|
@ -1202,11 +1220,11 @@ ffa_sched_recv_cb_update(struct ffa_device *dev, ffa_sched_recv_cb callback,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
list_for_each_entry_safe(partition, tmp, phead, node)
|
||||
list_for_each_entry(partition, phead, node)
|
||||
if (partition->dev == dev)
|
||||
break;
|
||||
|
||||
if (!partition) {
|
||||
if (&partition->node == phead) {
|
||||
pr_err("%s: No such partition ID 0x%x\n", __func__, dev->vm_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
@ -1445,20 +1463,25 @@ static int ffa_notify_send(struct ffa_device *dev, int notify_id,
|
|||
|
||||
static void handle_notif_callbacks(u64 bitmap, enum notify_type type)
|
||||
{
|
||||
ffa_notifier_cb cb;
|
||||
void *cb_data;
|
||||
int notify_id;
|
||||
struct notifier_cb_info *cb_info = NULL;
|
||||
|
||||
for (notify_id = 0; notify_id <= FFA_MAX_NOTIFICATIONS && bitmap;
|
||||
notify_id++, bitmap >>= 1) {
|
||||
if (!(bitmap & 1))
|
||||
continue;
|
||||
|
||||
read_lock(&drv_info->notify_lock);
|
||||
cb_info = notifier_hnode_get_by_type(notify_id, type);
|
||||
read_unlock(&drv_info->notify_lock);
|
||||
scoped_guard(read_lock, &drv_info->notify_lock) {
|
||||
struct notifier_cb_info *cb_info;
|
||||
|
||||
if (cb_info && cb_info->cb)
|
||||
cb_info->cb(notify_id, cb_info->cb_data);
|
||||
cb_info = notifier_hnode_get_by_type(notify_id, type);
|
||||
cb = cb_info ? cb_info->cb : NULL;
|
||||
cb_data = cb_info ? cb_info->cb_data : NULL;
|
||||
}
|
||||
|
||||
if (cb)
|
||||
cb(notify_id, cb_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1466,39 +1489,56 @@ static void handle_fwk_notif_callbacks(u32 bitmap)
|
|||
{
|
||||
void *buf;
|
||||
uuid_t uuid;
|
||||
void *fwk_cb_data;
|
||||
int notify_id = 0, target;
|
||||
ffa_fwk_notifier_cb fwk_cb;
|
||||
struct ffa_indirect_msg_hdr *msg;
|
||||
struct notifier_cb_info *cb_info = NULL;
|
||||
size_t min_offset = offsetof(struct ffa_indirect_msg_hdr, uuid);
|
||||
|
||||
/* Only one framework notification defined and supported for now */
|
||||
if (!(bitmap & FRAMEWORK_NOTIFY_RX_BUFFER_FULL))
|
||||
return;
|
||||
|
||||
mutex_lock(&drv_info->rx_lock);
|
||||
scoped_guard(mutex, &drv_info->rx_lock) {
|
||||
u32 offset, size;
|
||||
|
||||
msg = drv_info->rx_buffer;
|
||||
buf = kmemdup((void *)msg + msg->offset, msg->size, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
mutex_unlock(&drv_info->rx_lock);
|
||||
return;
|
||||
msg = drv_info->rx_buffer;
|
||||
offset = msg->offset;
|
||||
size = msg->size;
|
||||
|
||||
if (!size || (offset != min_offset && offset < sizeof(*msg)) ||
|
||||
offset > drv_info->rxtx_bufsz ||
|
||||
size > drv_info->rxtx_bufsz - offset) {
|
||||
pr_err("invalid framework notification message\n");
|
||||
ffa_rx_release();
|
||||
return;
|
||||
}
|
||||
|
||||
buf = kmemdup((void *)msg + offset, size, GFP_KERNEL);
|
||||
if (!buf) {
|
||||
ffa_rx_release();
|
||||
return;
|
||||
}
|
||||
|
||||
target = SENDER_ID(msg->send_recv_id);
|
||||
if (offset >= sizeof(*msg))
|
||||
uuid_copy(&uuid, &msg->uuid);
|
||||
else
|
||||
uuid_copy(&uuid, &uuid_null);
|
||||
ffa_rx_release();
|
||||
}
|
||||
|
||||
target = SENDER_ID(msg->send_recv_id);
|
||||
if (msg->offset >= sizeof(*msg))
|
||||
uuid_copy(&uuid, &msg->uuid);
|
||||
else
|
||||
uuid_copy(&uuid, &uuid_null);
|
||||
scoped_guard(read_lock, &drv_info->notify_lock) {
|
||||
struct notifier_cb_info *cb_info;
|
||||
|
||||
mutex_unlock(&drv_info->rx_lock);
|
||||
cb_info = notifier_hnode_get_by_vmid_uuid(notify_id, target,
|
||||
&uuid);
|
||||
fwk_cb = cb_info ? cb_info->fwk_cb : NULL;
|
||||
fwk_cb_data = cb_info ? cb_info->cb_data : NULL;
|
||||
}
|
||||
|
||||
ffa_rx_release();
|
||||
|
||||
read_lock(&drv_info->notify_lock);
|
||||
cb_info = notifier_hnode_get_by_vmid_uuid(notify_id, target, &uuid);
|
||||
read_unlock(&drv_info->notify_lock);
|
||||
|
||||
if (cb_info && cb_info->fwk_cb)
|
||||
cb_info->fwk_cb(notify_id, cb_info->cb_data, buf);
|
||||
if (fwk_cb)
|
||||
fwk_cb(notify_id, fwk_cb_data, buf);
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
|
|
@ -1539,10 +1579,11 @@ ffa_self_notif_handle(u16 vcpu, bool is_per_vcpu, void *cb_data)
|
|||
|
||||
static void notif_pcpu_irq_work_fn(struct work_struct *work)
|
||||
{
|
||||
struct ffa_drv_info *info = container_of(work, struct ffa_drv_info,
|
||||
struct ffa_pcpu_irq *pcpu = container_of(work, struct ffa_pcpu_irq,
|
||||
notif_pcpu_work);
|
||||
struct ffa_drv_info *info = pcpu->info;
|
||||
|
||||
ffa_self_notif_handle(smp_processor_id(), true, info);
|
||||
notif_get_and_handle(info);
|
||||
}
|
||||
|
||||
static const struct ffa_info_ops ffa_drv_info_ops = {
|
||||
|
|
@ -1629,6 +1670,15 @@ static struct notifier_block ffa_bus_nb = {
|
|||
.notifier_call = ffa_bus_notifier,
|
||||
};
|
||||
|
||||
static void ffa_bus_notifier_unregister(void)
|
||||
{
|
||||
if (!drv_info->bus_notifier_registered)
|
||||
return;
|
||||
|
||||
bus_unregister_notifier(&ffa_bus_type, &ffa_bus_nb);
|
||||
drv_info->bus_notifier_registered = false;
|
||||
}
|
||||
|
||||
static int ffa_xa_add_partition_info(struct ffa_device *dev)
|
||||
{
|
||||
struct ffa_dev_part_info *info;
|
||||
|
|
@ -1712,6 +1762,8 @@ static void ffa_partitions_cleanup(void)
|
|||
struct list_head *phead;
|
||||
unsigned long idx;
|
||||
|
||||
ffa_bus_notifier_unregister();
|
||||
|
||||
/* Clean up/free all registered devices */
|
||||
ffa_devices_unregister();
|
||||
|
||||
|
|
@ -1739,11 +1791,14 @@ static int ffa_setup_partitions(void)
|
|||
ret = bus_register_notifier(&ffa_bus_type, &ffa_bus_nb);
|
||||
if (ret)
|
||||
pr_err("Failed to register FF-A bus notifiers\n");
|
||||
else
|
||||
drv_info->bus_notifier_registered = true;
|
||||
}
|
||||
|
||||
count = ffa_partition_probe(&uuid_null, &pbuf);
|
||||
if (count <= 0) {
|
||||
pr_info("%s: No partitions found, error %d\n", __func__, count);
|
||||
ffa_bus_notifier_unregister();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
|
@ -1811,7 +1866,7 @@ static irqreturn_t notif_pend_irq_handler(int irq, void *irq_data)
|
|||
struct ffa_drv_info *info = pcpu->info;
|
||||
|
||||
queue_work_on(smp_processor_id(), info->notif_pcpu_wq,
|
||||
&info->notif_pcpu_work);
|
||||
&pcpu->notif_pcpu_work);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
|
@ -1928,8 +1983,11 @@ static int ffa_init_pcpu_irq(void)
|
|||
if (!irq_pcpu)
|
||||
return -ENOMEM;
|
||||
|
||||
for_each_present_cpu(cpu)
|
||||
for_each_present_cpu(cpu) {
|
||||
per_cpu_ptr(irq_pcpu, cpu)->info = drv_info;
|
||||
INIT_WORK(&per_cpu_ptr(irq_pcpu, cpu)->notif_pcpu_work,
|
||||
notif_pcpu_irq_work_fn);
|
||||
}
|
||||
|
||||
drv_info->irq_pcpu = irq_pcpu;
|
||||
|
||||
|
|
@ -1958,7 +2016,6 @@ static int ffa_init_pcpu_irq(void)
|
|||
}
|
||||
|
||||
INIT_WORK(&drv_info->sched_recv_irq_work, ffa_sched_recv_irq_work_fn);
|
||||
INIT_WORK(&drv_info->notif_pcpu_work, notif_pcpu_irq_work_fn);
|
||||
drv_info->notif_pcpu_wq = create_workqueue("ffa_pcpu_irq_notification");
|
||||
if (!drv_info->notif_pcpu_wq)
|
||||
return -EINVAL;
|
||||
|
|
@ -2063,11 +2120,12 @@ static int __init ffa_init(void)
|
|||
rxtx_bufsz = SZ_4K;
|
||||
}
|
||||
|
||||
rxtx_bufsz = PAGE_ALIGN(rxtx_bufsz);
|
||||
drv_info->rxtx_bufsz = rxtx_bufsz;
|
||||
drv_info->rx_buffer = alloc_pages_exact(rxtx_bufsz, GFP_KERNEL);
|
||||
if (!drv_info->rx_buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto free_pages;
|
||||
goto free_drv_info;
|
||||
}
|
||||
|
||||
drv_info->tx_buffer = alloc_pages_exact(rxtx_bufsz, GFP_KERNEL);
|
||||
|
|
@ -2078,7 +2136,7 @@ static int __init ffa_init(void)
|
|||
|
||||
ret = ffa_rxtx_map(virt_to_phys(drv_info->tx_buffer),
|
||||
virt_to_phys(drv_info->rx_buffer),
|
||||
PAGE_ALIGN(rxtx_bufsz) / FFA_PAGE_SIZE);
|
||||
rxtx_bufsz / FFA_PAGE_SIZE);
|
||||
if (ret) {
|
||||
pr_err("failed to register FFA RxTx buffers\n");
|
||||
goto free_pages;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user