You've already forked linux-apfs
mirror of
https://github.com/linux-apfs/linux-apfs.git
synced 2026-05-01 15:00:59 -07:00
Merge branches 'upstream', 'upstream-fixes' and 'debugfs' into for-linus
This commit is contained in:
@@ -31,21 +31,6 @@ config HID
|
|||||||
|
|
||||||
If unsure, say Y.
|
If unsure, say Y.
|
||||||
|
|
||||||
config HID_DEBUG
|
|
||||||
bool "HID debugging support"
|
|
||||||
default y
|
|
||||||
depends on HID
|
|
||||||
---help---
|
|
||||||
This option lets the HID layer output diagnostics about its internal
|
|
||||||
state, resolve HID usages, dump HID fields, etc. Individual HID drivers
|
|
||||||
use this debugging facility to output information about individual HID
|
|
||||||
devices, etc.
|
|
||||||
|
|
||||||
This feature is useful for those who are either debugging the HID parser
|
|
||||||
or any HID hardware device.
|
|
||||||
|
|
||||||
If unsure, say Y.
|
|
||||||
|
|
||||||
config HIDRAW
|
config HIDRAW
|
||||||
bool "/dev/hidraw raw HID device support"
|
bool "/dev/hidraw raw HID device support"
|
||||||
depends on HID
|
depends on HID
|
||||||
|
|||||||
@@ -3,9 +3,12 @@
|
|||||||
#
|
#
|
||||||
hid-objs := hid-core.o hid-input.o
|
hid-objs := hid-core.o hid-input.o
|
||||||
|
|
||||||
|
ifdef CONFIG_DEBUG_FS
|
||||||
|
hid-objs += hid-debug.o
|
||||||
|
endif
|
||||||
|
|
||||||
obj-$(CONFIG_HID) += hid.o
|
obj-$(CONFIG_HID) += hid.o
|
||||||
|
|
||||||
hid-$(CONFIG_HID_DEBUG) += hid-debug.o
|
|
||||||
hid-$(CONFIG_HIDRAW) += hidraw.o
|
hid-$(CONFIG_HIDRAW) += hidraw.o
|
||||||
|
|
||||||
hid-logitech-objs := hid-lg.o
|
hid-logitech-objs := hid-lg.o
|
||||||
|
|||||||
+43
-13
@@ -44,12 +44,10 @@
|
|||||||
#define DRIVER_DESC "HID core driver"
|
#define DRIVER_DESC "HID core driver"
|
||||||
#define DRIVER_LICENSE "GPL"
|
#define DRIVER_LICENSE "GPL"
|
||||||
|
|
||||||
#ifdef CONFIG_HID_DEBUG
|
|
||||||
int hid_debug = 0;
|
int hid_debug = 0;
|
||||||
module_param_named(debug, hid_debug, int, 0600);
|
module_param_named(debug, hid_debug, int, 0600);
|
||||||
MODULE_PARM_DESC(debug, "HID debugging (0=off, 1=probing info, 2=continuous data dumping)");
|
MODULE_PARM_DESC(debug, "toggle HID debugging messages");
|
||||||
EXPORT_SYMBOL_GPL(hid_debug);
|
EXPORT_SYMBOL_GPL(hid_debug);
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Register a new report for a device.
|
* Register a new report for a device.
|
||||||
@@ -861,7 +859,7 @@ static void hid_process_event(struct hid_device *hid, struct hid_field *field,
|
|||||||
struct hid_driver *hdrv = hid->driver;
|
struct hid_driver *hdrv = hid->driver;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
hid_dump_input(usage, value);
|
hid_dump_input(hid, usage, value);
|
||||||
|
|
||||||
if (hdrv && hdrv->event && hid_match_usage(hid, usage)) {
|
if (hdrv && hdrv->event && hid_match_usage(hid, usage)) {
|
||||||
ret = hdrv->event(hid, field, usage, value);
|
ret = hdrv->event(hid, field, usage, value);
|
||||||
@@ -983,11 +981,10 @@ int hid_set_field(struct hid_field *field, unsigned offset, __s32 value)
|
|||||||
{
|
{
|
||||||
unsigned size = field->report_size;
|
unsigned size = field->report_size;
|
||||||
|
|
||||||
hid_dump_input(field->usage + offset, value);
|
hid_dump_input(field->report->device, field->usage + offset, value);
|
||||||
|
|
||||||
if (offset >= field->report_count) {
|
if (offset >= field->report_count) {
|
||||||
dbg_hid("offset (%d) exceeds report_count (%d)\n", offset, field->report_count);
|
dbg_hid("offset (%d) exceeds report_count (%d)\n", offset, field->report_count);
|
||||||
hid_dump_field(field, 8);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (field->logical_minimum < 0) {
|
if (field->logical_minimum < 0) {
|
||||||
@@ -1078,6 +1075,7 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i
|
|||||||
struct hid_report_enum *report_enum;
|
struct hid_report_enum *report_enum;
|
||||||
struct hid_driver *hdrv;
|
struct hid_driver *hdrv;
|
||||||
struct hid_report *report;
|
struct hid_report *report;
|
||||||
|
char *buf;
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
@@ -1091,18 +1089,38 @@ int hid_input_report(struct hid_device *hid, int type, u8 *data, int size, int i
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg_hid("report (size %u) (%snumbered)\n", size, report_enum->numbered ? "" : "un");
|
buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE,
|
||||||
|
interrupt ? GFP_ATOMIC : GFP_KERNEL);
|
||||||
|
|
||||||
|
if (!buf) {
|
||||||
|
report = hid_get_report(report_enum, data);
|
||||||
|
goto nomem;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(buf, HID_DEBUG_BUFSIZE - 1,
|
||||||
|
"\nreport (size %u) (%snumbered)\n", size, report_enum->numbered ? "" : "un");
|
||||||
|
hid_debug_event(hid, buf);
|
||||||
|
|
||||||
report = hid_get_report(report_enum, data);
|
report = hid_get_report(report_enum, data);
|
||||||
if (!report)
|
if (!report) {
|
||||||
|
kfree(buf);
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* dump the report */
|
/* dump the report */
|
||||||
dbg_hid("report %d (size %u) = ", report->id, size);
|
snprintf(buf, HID_DEBUG_BUFSIZE - 1,
|
||||||
for (i = 0; i < size; i++)
|
"report %d (size %u) = ", report->id, size);
|
||||||
dbg_hid_line(" %02x", data[i]);
|
hid_debug_event(hid, buf);
|
||||||
dbg_hid_line("\n");
|
for (i = 0; i < size; i++) {
|
||||||
|
snprintf(buf, HID_DEBUG_BUFSIZE - 1,
|
||||||
|
" %02x", data[i]);
|
||||||
|
hid_debug_event(hid, buf);
|
||||||
|
}
|
||||||
|
hid_debug_event(hid, "\n");
|
||||||
|
|
||||||
|
kfree(buf);
|
||||||
|
|
||||||
|
nomem:
|
||||||
if (hdrv && hdrv->raw_event && hid_match_report(hid, report)) {
|
if (hdrv && hdrv->raw_event && hid_match_report(hid, report)) {
|
||||||
ret = hdrv->raw_event(hid, report, data, size);
|
ret = hdrv->raw_event(hid, report, data, size);
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
@@ -1323,7 +1341,6 @@ static const struct hid_device_id hid_blacklist[] = {
|
|||||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) },
|
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) },
|
||||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) },
|
{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) },
|
||||||
|
|
||||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, 0x030c) },
|
|
||||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_BT) },
|
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_BT) },
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
@@ -1730,6 +1747,8 @@ int hid_add_device(struct hid_device *hdev)
|
|||||||
if (!ret)
|
if (!ret)
|
||||||
hdev->status |= HID_STAT_ADDED;
|
hdev->status |= HID_STAT_ADDED;
|
||||||
|
|
||||||
|
hid_debug_register(hdev, dev_name(&hdev->dev));
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(hid_add_device);
|
EXPORT_SYMBOL_GPL(hid_add_device);
|
||||||
@@ -1766,6 +1785,9 @@ struct hid_device *hid_allocate_device(void)
|
|||||||
for (i = 0; i < HID_REPORT_TYPES; i++)
|
for (i = 0; i < HID_REPORT_TYPES; i++)
|
||||||
INIT_LIST_HEAD(&hdev->report_enum[i].report_list);
|
INIT_LIST_HEAD(&hdev->report_enum[i].report_list);
|
||||||
|
|
||||||
|
init_waitqueue_head(&hdev->debug_wait);
|
||||||
|
INIT_LIST_HEAD(&hdev->debug_list);
|
||||||
|
|
||||||
return hdev;
|
return hdev;
|
||||||
err:
|
err:
|
||||||
put_device(&hdev->dev);
|
put_device(&hdev->dev);
|
||||||
@@ -1777,6 +1799,7 @@ static void hid_remove_device(struct hid_device *hdev)
|
|||||||
{
|
{
|
||||||
if (hdev->status & HID_STAT_ADDED) {
|
if (hdev->status & HID_STAT_ADDED) {
|
||||||
device_del(&hdev->dev);
|
device_del(&hdev->dev);
|
||||||
|
hid_debug_unregister(hdev);
|
||||||
hdev->status &= ~HID_STAT_ADDED;
|
hdev->status &= ~HID_STAT_ADDED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1852,6 +1875,10 @@ static int __init hid_init(void)
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (hid_debug)
|
||||||
|
printk(KERN_WARNING "HID: hid_debug is now used solely for parser and driver debugging.\n"
|
||||||
|
"HID: debugfs is now used for inspecting the device (report descriptor, reports)\n");
|
||||||
|
|
||||||
ret = bus_register(&hid_bus_type);
|
ret = bus_register(&hid_bus_type);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
printk(KERN_ERR "HID: can't register hid bus\n");
|
printk(KERN_ERR "HID: can't register hid bus\n");
|
||||||
@@ -1862,6 +1889,8 @@ static int __init hid_init(void)
|
|||||||
if (ret)
|
if (ret)
|
||||||
goto err_bus;
|
goto err_bus;
|
||||||
|
|
||||||
|
hid_debug_init();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
err_bus:
|
err_bus:
|
||||||
bus_unregister(&hid_bus_type);
|
bus_unregister(&hid_bus_type);
|
||||||
@@ -1871,6 +1900,7 @@ err:
|
|||||||
|
|
||||||
static void __exit hid_exit(void)
|
static void __exit hid_exit(void)
|
||||||
{
|
{
|
||||||
|
hid_debug_exit();
|
||||||
hidraw_exit();
|
hidraw_exit();
|
||||||
bus_unregister(&hid_bus_type);
|
bus_unregister(&hid_bus_type);
|
||||||
}
|
}
|
||||||
|
|||||||
+363
-76
File diff suppressed because it is too large
Load Diff
+1
-12
@@ -159,17 +159,12 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
|
|||||||
|
|
||||||
field->hidinput = hidinput;
|
field->hidinput = hidinput;
|
||||||
|
|
||||||
dbg_hid("Mapping: ");
|
|
||||||
hid_resolv_usage(usage->hid);
|
|
||||||
dbg_hid_line(" ---> ");
|
|
||||||
|
|
||||||
if (field->flags & HID_MAIN_ITEM_CONSTANT)
|
if (field->flags & HID_MAIN_ITEM_CONSTANT)
|
||||||
goto ignore;
|
goto ignore;
|
||||||
|
|
||||||
/* only LED usages are supported in output fields */
|
/* only LED usages are supported in output fields */
|
||||||
if (field->report_type == HID_OUTPUT_REPORT &&
|
if (field->report_type == HID_OUTPUT_REPORT &&
|
||||||
(usage->hid & HID_USAGE_PAGE) != HID_UP_LED) {
|
(usage->hid & HID_USAGE_PAGE) != HID_UP_LED) {
|
||||||
dbg_hid_line(" [non-LED output field] ");
|
|
||||||
goto ignore;
|
goto ignore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,15 +556,9 @@ mapped:
|
|||||||
set_bit(MSC_SCAN, input->mscbit);
|
set_bit(MSC_SCAN, input->mscbit);
|
||||||
}
|
}
|
||||||
|
|
||||||
hid_resolv_event(usage->type, usage->code);
|
|
||||||
|
|
||||||
dbg_hid_line("\n");
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
ignore:
|
ignore:
|
||||||
dbg_hid_line("IGNORED\n");
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value)
|
void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value)
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
* Copyright (c) 1999 Andreas Gal
|
* Copyright (c) 1999 Andreas Gal
|
||||||
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
|
* Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
|
||||||
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
|
* Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
|
||||||
* Copyright (c) 2006-2008 Jiri Kosina
|
|
||||||
* Copyright (c) 2007-2008 Oliver Neukum
|
* Copyright (c) 2007-2008 Oliver Neukum
|
||||||
|
* Copyright (c) 2006-2009 Jiri Kosina
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -886,11 +886,6 @@ static int usbhid_parse(struct hid_device *hid)
|
|||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg_hid("report descriptor (size %u, read %d) = ", rsize, n);
|
|
||||||
for (n = 0; n < rsize; n++)
|
|
||||||
dbg_hid_line(" %02x", (unsigned char) rdesc[n]);
|
|
||||||
dbg_hid_line("\n");
|
|
||||||
|
|
||||||
ret = hid_parse_report(hid, rdesc, rsize);
|
ret = hid_parse_report(hid, rdesc, rsize);
|
||||||
kfree(rdesc);
|
kfree(rdesc);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
@@ -1004,7 +999,6 @@ static int usbhid_start(struct hid_device *hid)
|
|||||||
usbhid->urbctrl->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);
|
usbhid->urbctrl->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);
|
||||||
|
|
||||||
usbhid_init_reports(hid);
|
usbhid_init_reports(hid);
|
||||||
hid_dump_device(hid);
|
|
||||||
|
|
||||||
set_bit(HID_STARTED, &usbhid->iofl);
|
set_bit(HID_STARTED, &usbhid->iofl);
|
||||||
|
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ int usbhid_quirks_init(char **quirks_param)
|
|||||||
u32 quirks;
|
u32 quirks;
|
||||||
int n = 0, m;
|
int n = 0, m;
|
||||||
|
|
||||||
for (; quirks_param[n] && n < MAX_USBHID_BOOT_QUIRKS; n++) {
|
for (; n < MAX_USBHID_BOOT_QUIRKS && quirks_param[n]; n++) {
|
||||||
|
|
||||||
m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",
|
m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",
|
||||||
&idVendor, &idProduct, &quirks);
|
&idVendor, &idProduct, &quirks);
|
||||||
|
|||||||
+34
-14
@@ -2,7 +2,7 @@
|
|||||||
#define __HID_DEBUG_H
|
#define __HID_DEBUG_H
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2007 Jiri Kosina
|
* Copyright (c) 2007-2009 Jiri Kosina
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -22,24 +22,44 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef CONFIG_HID_DEBUG
|
#define HID_DEBUG_BUFSIZE 512
|
||||||
|
|
||||||
void hid_dump_input(struct hid_usage *, __s32);
|
#ifdef CONFIG_DEBUG_FS
|
||||||
void hid_dump_device(struct hid_device *);
|
|
||||||
void hid_dump_field(struct hid_field *, int);
|
void hid_dump_input(struct hid_device *, struct hid_usage *, __s32);
|
||||||
void hid_resolv_usage(unsigned);
|
void hid_dump_device(struct hid_device *, struct seq_file *);
|
||||||
void hid_resolv_event(__u8, __u16);
|
void hid_dump_field(struct hid_field *, int, struct seq_file *);
|
||||||
|
char *hid_resolv_usage(unsigned, struct seq_file *);
|
||||||
|
void hid_debug_register(struct hid_device *, const char *);
|
||||||
|
void hid_debug_unregister(struct hid_device *);
|
||||||
|
void hid_debug_init(void);
|
||||||
|
void hid_debug_exit(void);
|
||||||
|
void hid_debug_event(struct hid_device *, char *);
|
||||||
|
|
||||||
|
|
||||||
|
struct hid_debug_list {
|
||||||
|
char *hid_debug_buf;
|
||||||
|
int head;
|
||||||
|
int tail;
|
||||||
|
struct fasync_struct *fasync;
|
||||||
|
struct hid_device *hdev;
|
||||||
|
struct list_head node;
|
||||||
|
struct mutex read_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
#define hid_dump_input(a,b) do { } while (0)
|
#define hid_dump_input(a,b,c) do { } while (0)
|
||||||
#define hid_dump_device(c) do { } while (0)
|
#define hid_dump_device(a,b) do { } while (0)
|
||||||
#define hid_dump_field(a,b) do { } while (0)
|
#define hid_dump_field(a,b,c) do { } while (0)
|
||||||
#define hid_resolv_usage(a) do { } while (0)
|
#define hid_resolv_usage(a,b) do { } while (0)
|
||||||
#define hid_resolv_event(a,b) do { } while (0)
|
#define hid_debug_register(a, b) do { } while (0)
|
||||||
|
#define hid_debug_unregister(a) do { } while (0)
|
||||||
|
#define hid_debug_init() do { } while (0)
|
||||||
|
#define hid_debug_exit() do { } while (0)
|
||||||
|
#define hid_debug_event(a,b) do { } while (0)
|
||||||
|
|
||||||
#endif /* CONFIG_HID_DEBUG */
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
+8
-14
@@ -500,6 +500,14 @@ struct hid_device { /* device report descriptor */
|
|||||||
|
|
||||||
/* handler for raw output data, used by hidraw */
|
/* handler for raw output data, used by hidraw */
|
||||||
int (*hid_output_raw_report) (struct hid_device *, __u8 *, size_t);
|
int (*hid_output_raw_report) (struct hid_device *, __u8 *, size_t);
|
||||||
|
|
||||||
|
/* debugging support via debugfs */
|
||||||
|
unsigned short debug;
|
||||||
|
struct dentry *debug_dir;
|
||||||
|
struct dentry *debug_rdesc;
|
||||||
|
struct dentry *debug_events;
|
||||||
|
struct list_head debug_list;
|
||||||
|
wait_queue_head_t debug_wait;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void *hid_get_drvdata(struct hid_device *hdev)
|
static inline void *hid_get_drvdata(struct hid_device *hdev)
|
||||||
@@ -657,9 +665,7 @@ struct hid_ll_driver {
|
|||||||
|
|
||||||
/* HID core API */
|
/* HID core API */
|
||||||
|
|
||||||
#ifdef CONFIG_HID_DEBUG
|
|
||||||
extern int hid_debug;
|
extern int hid_debug;
|
||||||
#endif
|
|
||||||
|
|
||||||
extern int hid_add_device(struct hid_device *);
|
extern int hid_add_device(struct hid_device *);
|
||||||
extern void hid_destroy_device(struct hid_device *);
|
extern void hid_destroy_device(struct hid_device *);
|
||||||
@@ -815,21 +821,9 @@ int hid_pidff_init(struct hid_device *hid);
|
|||||||
#define hid_pidff_init NULL
|
#define hid_pidff_init NULL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_HID_DEBUG
|
|
||||||
#define dbg_hid(format, arg...) if (hid_debug) \
|
#define dbg_hid(format, arg...) if (hid_debug) \
|
||||||
printk(KERN_DEBUG "%s: " format ,\
|
printk(KERN_DEBUG "%s: " format ,\
|
||||||
__FILE__ , ## arg)
|
__FILE__ , ## arg)
|
||||||
#define dbg_hid_line(format, arg...) if (hid_debug) \
|
|
||||||
printk(format, ## arg)
|
|
||||||
#else
|
|
||||||
static inline int __attribute__((format(printf, 1, 2)))
|
|
||||||
dbg_hid(const char *fmt, ...)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#define dbg_hid_line dbg_hid
|
|
||||||
#endif /* HID_DEBUG */
|
|
||||||
|
|
||||||
#define err_hid(format, arg...) printk(KERN_ERR "%s: " format "\n" , \
|
#define err_hid(format, arg...) printk(KERN_ERR "%s: " format "\n" , \
|
||||||
__FILE__ , ## arg)
|
__FILE__ , ## arg)
|
||||||
#endif /* HID_FF */
|
#endif /* HID_FF */
|
||||||
|
|||||||
Reference in New Issue
Block a user