Merge branch 'lorenzo/pci/endpoint'
- use usleep() instead of mdelay() in endpoint test (Jia-Ju Bai) - add configfs entries for pci_epf_driver device IDs (Kishon Vijay Abraham I) - clean up pci_endpoint_test driver (Gustavo Pimentel) * lorenzo/pci/endpoint: PCI: endpoint: Create configfs entry for each pci_epf_device_id table entry misc: pci_endpoint_test: Use pci_irq_vector function PCI: endpoint: functions/pci-epf-test: Replace lower into upper case characters misc: pci_endpoint_test: Replace lower into upper case characters PCI: endpoint: Replace mdelay with usleep_range() in pci_epf_test_write()
This commit is contained in:
commit
bf79d7d099
|
@ -203,7 +203,7 @@ static bool pci_endpoint_test_msi_irq(struct pci_endpoint_test *test,
|
||||||
if (!val)
|
if (!val)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (test->last_irq - pdev->irq == msi_num - 1)
|
if (pci_irq_vector(pdev, msi_num - 1) == test->last_irq)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -233,7 +233,7 @@ static bool pci_endpoint_test_copy(struct pci_endpoint_test *test, size_t size)
|
||||||
orig_src_addr = dma_alloc_coherent(dev, size + alignment,
|
orig_src_addr = dma_alloc_coherent(dev, size + alignment,
|
||||||
&orig_src_phys_addr, GFP_KERNEL);
|
&orig_src_phys_addr, GFP_KERNEL);
|
||||||
if (!orig_src_addr) {
|
if (!orig_src_addr) {
|
||||||
dev_err(dev, "failed to allocate source buffer\n");
|
dev_err(dev, "Failed to allocate source buffer\n");
|
||||||
ret = false;
|
ret = false;
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
@ -259,7 +259,7 @@ static bool pci_endpoint_test_copy(struct pci_endpoint_test *test, size_t size)
|
||||||
orig_dst_addr = dma_alloc_coherent(dev, size + alignment,
|
orig_dst_addr = dma_alloc_coherent(dev, size + alignment,
|
||||||
&orig_dst_phys_addr, GFP_KERNEL);
|
&orig_dst_phys_addr, GFP_KERNEL);
|
||||||
if (!orig_dst_addr) {
|
if (!orig_dst_addr) {
|
||||||
dev_err(dev, "failed to allocate destination address\n");
|
dev_err(dev, "Failed to allocate destination address\n");
|
||||||
ret = false;
|
ret = false;
|
||||||
goto err_orig_src_addr;
|
goto err_orig_src_addr;
|
||||||
}
|
}
|
||||||
|
@ -321,7 +321,7 @@ static bool pci_endpoint_test_write(struct pci_endpoint_test *test, size_t size)
|
||||||
orig_addr = dma_alloc_coherent(dev, size + alignment, &orig_phys_addr,
|
orig_addr = dma_alloc_coherent(dev, size + alignment, &orig_phys_addr,
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
if (!orig_addr) {
|
if (!orig_addr) {
|
||||||
dev_err(dev, "failed to allocate address\n");
|
dev_err(dev, "Failed to allocate address\n");
|
||||||
ret = false;
|
ret = false;
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
@ -382,7 +382,7 @@ static bool pci_endpoint_test_read(struct pci_endpoint_test *test, size_t size)
|
||||||
orig_addr = dma_alloc_coherent(dev, size + alignment, &orig_phys_addr,
|
orig_addr = dma_alloc_coherent(dev, size + alignment, &orig_phys_addr,
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
if (!orig_addr) {
|
if (!orig_addr) {
|
||||||
dev_err(dev, "failed to allocate destination address\n");
|
dev_err(dev, "Failed to allocate destination address\n");
|
||||||
ret = false;
|
ret = false;
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
@ -513,31 +513,31 @@ static int pci_endpoint_test_probe(struct pci_dev *pdev,
|
||||||
if (!no_msi) {
|
if (!no_msi) {
|
||||||
irq = pci_alloc_irq_vectors(pdev, 1, 32, PCI_IRQ_MSI);
|
irq = pci_alloc_irq_vectors(pdev, 1, 32, PCI_IRQ_MSI);
|
||||||
if (irq < 0)
|
if (irq < 0)
|
||||||
dev_err(dev, "failed to get MSI interrupts\n");
|
dev_err(dev, "Failed to get MSI interrupts\n");
|
||||||
test->num_irqs = irq;
|
test->num_irqs = irq;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = devm_request_irq(dev, pdev->irq, pci_endpoint_test_irqhandler,
|
err = devm_request_irq(dev, pdev->irq, pci_endpoint_test_irqhandler,
|
||||||
IRQF_SHARED, DRV_MODULE_NAME, test);
|
IRQF_SHARED, DRV_MODULE_NAME, test);
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(dev, "failed to request IRQ %d\n", pdev->irq);
|
dev_err(dev, "Failed to request IRQ %d\n", pdev->irq);
|
||||||
goto err_disable_msi;
|
goto err_disable_msi;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 1; i < irq; i++) {
|
for (i = 1; i < irq; i++) {
|
||||||
err = devm_request_irq(dev, pdev->irq + i,
|
err = devm_request_irq(dev, pci_irq_vector(pdev, i),
|
||||||
pci_endpoint_test_irqhandler,
|
pci_endpoint_test_irqhandler,
|
||||||
IRQF_SHARED, DRV_MODULE_NAME, test);
|
IRQF_SHARED, DRV_MODULE_NAME, test);
|
||||||
if (err)
|
if (err)
|
||||||
dev_err(dev, "failed to request IRQ %d for MSI %d\n",
|
dev_err(dev, "failed to request IRQ %d for MSI %d\n",
|
||||||
pdev->irq + i, i + 1);
|
pci_irq_vector(pdev, i), i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (bar = BAR_0; bar <= BAR_5; bar++) {
|
for (bar = BAR_0; bar <= BAR_5; bar++) {
|
||||||
if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) {
|
if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) {
|
||||||
base = pci_ioremap_bar(pdev, bar);
|
base = pci_ioremap_bar(pdev, bar);
|
||||||
if (!base) {
|
if (!base) {
|
||||||
dev_err(dev, "failed to read BAR%d\n", bar);
|
dev_err(dev, "Failed to read BAR%d\n", bar);
|
||||||
WARN_ON(bar == test_reg_bar);
|
WARN_ON(bar == test_reg_bar);
|
||||||
}
|
}
|
||||||
test->bar[bar] = base;
|
test->bar[bar] = base;
|
||||||
|
@ -557,7 +557,7 @@ static int pci_endpoint_test_probe(struct pci_dev *pdev,
|
||||||
id = ida_simple_get(&pci_endpoint_test_ida, 0, 0, GFP_KERNEL);
|
id = ida_simple_get(&pci_endpoint_test_ida, 0, 0, GFP_KERNEL);
|
||||||
if (id < 0) {
|
if (id < 0) {
|
||||||
err = id;
|
err = id;
|
||||||
dev_err(dev, "unable to get id\n");
|
dev_err(dev, "Unable to get id\n");
|
||||||
goto err_iounmap;
|
goto err_iounmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,7 +573,7 @@ static int pci_endpoint_test_probe(struct pci_dev *pdev,
|
||||||
|
|
||||||
err = misc_register(misc_device);
|
err = misc_register(misc_device);
|
||||||
if (err) {
|
if (err) {
|
||||||
dev_err(dev, "failed to register device\n");
|
dev_err(dev, "Failed to register device\n");
|
||||||
goto err_kfree_name;
|
goto err_kfree_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -592,7 +592,7 @@ static int pci_endpoint_test_probe(struct pci_dev *pdev,
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < irq; i++)
|
for (i = 0; i < irq; i++)
|
||||||
devm_free_irq(dev, pdev->irq + i, test);
|
devm_free_irq(&pdev->dev, pci_irq_vector(pdev, i), test);
|
||||||
|
|
||||||
err_disable_msi:
|
err_disable_msi:
|
||||||
pci_disable_msi(pdev);
|
pci_disable_msi(pdev);
|
||||||
|
@ -625,7 +625,7 @@ static void pci_endpoint_test_remove(struct pci_dev *pdev)
|
||||||
pci_iounmap(pdev, test->bar[bar]);
|
pci_iounmap(pdev, test->bar[bar]);
|
||||||
}
|
}
|
||||||
for (i = 0; i < test->num_irqs; i++)
|
for (i = 0; i < test->num_irqs; i++)
|
||||||
devm_free_irq(&pdev->dev, pdev->irq + i, test);
|
devm_free_irq(&pdev->dev, pci_irq_vector(pdev, i), test);
|
||||||
pci_disable_msi(pdev);
|
pci_disable_msi(pdev);
|
||||||
pci_release_regions(pdev);
|
pci_release_regions(pdev);
|
||||||
pci_disable_device(pdev);
|
pci_disable_device(pdev);
|
||||||
|
|
|
@ -87,7 +87,7 @@ static int pci_epf_test_copy(struct pci_epf_test *epf_test)
|
||||||
|
|
||||||
src_addr = pci_epc_mem_alloc_addr(epc, &src_phys_addr, reg->size);
|
src_addr = pci_epc_mem_alloc_addr(epc, &src_phys_addr, reg->size);
|
||||||
if (!src_addr) {
|
if (!src_addr) {
|
||||||
dev_err(dev, "failed to allocate source address\n");
|
dev_err(dev, "Failed to allocate source address\n");
|
||||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto err;
|
goto err;
|
||||||
|
@ -96,14 +96,14 @@ static int pci_epf_test_copy(struct pci_epf_test *epf_test)
|
||||||
ret = pci_epc_map_addr(epc, epf->func_no, src_phys_addr, reg->src_addr,
|
ret = pci_epc_map_addr(epc, epf->func_no, src_phys_addr, reg->src_addr,
|
||||||
reg->size);
|
reg->size);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(dev, "failed to map source address\n");
|
dev_err(dev, "Failed to map source address\n");
|
||||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||||
goto err_src_addr;
|
goto err_src_addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
dst_addr = pci_epc_mem_alloc_addr(epc, &dst_phys_addr, reg->size);
|
dst_addr = pci_epc_mem_alloc_addr(epc, &dst_phys_addr, reg->size);
|
||||||
if (!dst_addr) {
|
if (!dst_addr) {
|
||||||
dev_err(dev, "failed to allocate destination address\n");
|
dev_err(dev, "Failed to allocate destination address\n");
|
||||||
reg->status = STATUS_DST_ADDR_INVALID;
|
reg->status = STATUS_DST_ADDR_INVALID;
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto err_src_map_addr;
|
goto err_src_map_addr;
|
||||||
|
@ -112,7 +112,7 @@ static int pci_epf_test_copy(struct pci_epf_test *epf_test)
|
||||||
ret = pci_epc_map_addr(epc, epf->func_no, dst_phys_addr, reg->dst_addr,
|
ret = pci_epc_map_addr(epc, epf->func_no, dst_phys_addr, reg->dst_addr,
|
||||||
reg->size);
|
reg->size);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(dev, "failed to map destination address\n");
|
dev_err(dev, "Failed to map destination address\n");
|
||||||
reg->status = STATUS_DST_ADDR_INVALID;
|
reg->status = STATUS_DST_ADDR_INVALID;
|
||||||
goto err_dst_addr;
|
goto err_dst_addr;
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@ static int pci_epf_test_read(struct pci_epf_test *epf_test)
|
||||||
|
|
||||||
src_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size);
|
src_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size);
|
||||||
if (!src_addr) {
|
if (!src_addr) {
|
||||||
dev_err(dev, "failed to allocate address\n");
|
dev_err(dev, "Failed to allocate address\n");
|
||||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto err;
|
goto err;
|
||||||
|
@ -158,7 +158,7 @@ static int pci_epf_test_read(struct pci_epf_test *epf_test)
|
||||||
ret = pci_epc_map_addr(epc, epf->func_no, phys_addr, reg->src_addr,
|
ret = pci_epc_map_addr(epc, epf->func_no, phys_addr, reg->src_addr,
|
||||||
reg->size);
|
reg->size);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(dev, "failed to map address\n");
|
dev_err(dev, "Failed to map address\n");
|
||||||
reg->status = STATUS_SRC_ADDR_INVALID;
|
reg->status = STATUS_SRC_ADDR_INVALID;
|
||||||
goto err_addr;
|
goto err_addr;
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ static int pci_epf_test_write(struct pci_epf_test *epf_test)
|
||||||
|
|
||||||
dst_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size);
|
dst_addr = pci_epc_mem_alloc_addr(epc, &phys_addr, reg->size);
|
||||||
if (!dst_addr) {
|
if (!dst_addr) {
|
||||||
dev_err(dev, "failed to allocate address\n");
|
dev_err(dev, "Failed to allocate address\n");
|
||||||
reg->status = STATUS_DST_ADDR_INVALID;
|
reg->status = STATUS_DST_ADDR_INVALID;
|
||||||
ret = -ENOMEM;
|
ret = -ENOMEM;
|
||||||
goto err;
|
goto err;
|
||||||
|
@ -210,7 +210,7 @@ static int pci_epf_test_write(struct pci_epf_test *epf_test)
|
||||||
ret = pci_epc_map_addr(epc, epf->func_no, phys_addr, reg->dst_addr,
|
ret = pci_epc_map_addr(epc, epf->func_no, phys_addr, reg->dst_addr,
|
||||||
reg->size);
|
reg->size);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(dev, "failed to map address\n");
|
dev_err(dev, "Failed to map address\n");
|
||||||
reg->status = STATUS_DST_ADDR_INVALID;
|
reg->status = STATUS_DST_ADDR_INVALID;
|
||||||
goto err_addr;
|
goto err_addr;
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ static int pci_epf_test_write(struct pci_epf_test *epf_test)
|
||||||
* wait 1ms inorder for the write to complete. Without this delay L3
|
* wait 1ms inorder for the write to complete. Without this delay L3
|
||||||
* error in observed in the host system.
|
* error in observed in the host system.
|
||||||
*/
|
*/
|
||||||
mdelay(1);
|
usleep_range(1000, 2000);
|
||||||
|
|
||||||
kfree(buf);
|
kfree(buf);
|
||||||
|
|
||||||
|
@ -379,7 +379,7 @@ static int pci_epf_test_set_bar(struct pci_epf *epf)
|
||||||
ret = pci_epc_set_bar(epc, epf->func_no, epf_bar);
|
ret = pci_epc_set_bar(epc, epf->func_no, epf_bar);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
pci_epf_free_space(epf, epf_test->reg[bar], bar);
|
pci_epf_free_space(epf, epf_test->reg[bar], bar);
|
||||||
dev_err(dev, "failed to set BAR%d\n", bar);
|
dev_err(dev, "Failed to set BAR%d\n", bar);
|
||||||
if (bar == test_reg_bar)
|
if (bar == test_reg_bar)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -406,7 +406,7 @@ static int pci_epf_test_alloc_space(struct pci_epf *epf)
|
||||||
base = pci_epf_alloc_space(epf, sizeof(struct pci_epf_test_reg),
|
base = pci_epf_alloc_space(epf, sizeof(struct pci_epf_test_reg),
|
||||||
test_reg_bar);
|
test_reg_bar);
|
||||||
if (!base) {
|
if (!base) {
|
||||||
dev_err(dev, "failed to allocated register space\n");
|
dev_err(dev, "Failed to allocated register space\n");
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
epf_test->reg[test_reg_bar] = base;
|
epf_test->reg[test_reg_bar] = base;
|
||||||
|
@ -416,7 +416,7 @@ static int pci_epf_test_alloc_space(struct pci_epf *epf)
|
||||||
continue;
|
continue;
|
||||||
base = pci_epf_alloc_space(epf, bar_size[bar], bar);
|
base = pci_epf_alloc_space(epf, bar_size[bar], bar);
|
||||||
if (!base)
|
if (!base)
|
||||||
dev_err(dev, "failed to allocate space for BAR%d\n",
|
dev_err(dev, "Failed to allocate space for BAR%d\n",
|
||||||
bar);
|
bar);
|
||||||
epf_test->reg[bar] = base;
|
epf_test->reg[bar] = base;
|
||||||
}
|
}
|
||||||
|
@ -444,7 +444,7 @@ static int pci_epf_test_bind(struct pci_epf *epf)
|
||||||
|
|
||||||
ret = pci_epc_write_header(epc, epf->func_no, header);
|
ret = pci_epc_write_header(epc, epf->func_no, header);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(dev, "configuration header write failed\n");
|
dev_err(dev, "Configuration header write failed\n");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -526,7 +526,7 @@ static int __init pci_epf_test_init(void)
|
||||||
WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
|
WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
|
||||||
ret = pci_epf_register_driver(&test_driver);
|
ret = pci_epf_register_driver(&test_driver);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
pr_err("failed to register pci epf test driver --> %d\n", ret);
|
pr_err("Failed to register pci epf test driver --> %d\n", ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
#include <linux/pci-epf.h>
|
#include <linux/pci-epf.h>
|
||||||
#include <linux/pci-ep-cfs.h>
|
#include <linux/pci-ep-cfs.h>
|
||||||
|
|
||||||
|
static DEFINE_MUTEX(pci_epf_mutex);
|
||||||
|
|
||||||
static struct bus_type pci_epf_bus_type;
|
static struct bus_type pci_epf_bus_type;
|
||||||
static const struct device_type pci_epf_type;
|
static const struct device_type pci_epf_type;
|
||||||
|
|
||||||
|
@ -143,7 +145,13 @@ EXPORT_SYMBOL_GPL(pci_epf_alloc_space);
|
||||||
*/
|
*/
|
||||||
void pci_epf_unregister_driver(struct pci_epf_driver *driver)
|
void pci_epf_unregister_driver(struct pci_epf_driver *driver)
|
||||||
{
|
{
|
||||||
pci_ep_cfs_remove_epf_group(driver->group);
|
struct config_group *group;
|
||||||
|
|
||||||
|
mutex_lock(&pci_epf_mutex);
|
||||||
|
list_for_each_entry(group, &driver->epf_group, group_entry)
|
||||||
|
pci_ep_cfs_remove_epf_group(group);
|
||||||
|
list_del(&driver->epf_group);
|
||||||
|
mutex_unlock(&pci_epf_mutex);
|
||||||
driver_unregister(&driver->driver);
|
driver_unregister(&driver->driver);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(pci_epf_unregister_driver);
|
EXPORT_SYMBOL_GPL(pci_epf_unregister_driver);
|
||||||
|
@ -159,6 +167,8 @@ int __pci_epf_register_driver(struct pci_epf_driver *driver,
|
||||||
struct module *owner)
|
struct module *owner)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
struct config_group *group;
|
||||||
|
const struct pci_epf_device_id *id;
|
||||||
|
|
||||||
if (!driver->ops)
|
if (!driver->ops)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
@ -173,7 +183,16 @@ int __pci_epf_register_driver(struct pci_epf_driver *driver,
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
driver->group = pci_ep_cfs_add_epf_group(driver->driver.name);
|
INIT_LIST_HEAD(&driver->epf_group);
|
||||||
|
|
||||||
|
id = driver->id_table;
|
||||||
|
while (id->name[0]) {
|
||||||
|
group = pci_ep_cfs_add_epf_group(id->name);
|
||||||
|
mutex_lock(&pci_epf_mutex);
|
||||||
|
list_add_tail(&group->group_entry, &driver->epf_group);
|
||||||
|
mutex_unlock(&pci_epf_mutex);
|
||||||
|
id++;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ struct pci_epf_ops {
|
||||||
* @driver: PCI EPF driver
|
* @driver: PCI EPF driver
|
||||||
* @ops: set of function pointers for performing EPF operations
|
* @ops: set of function pointers for performing EPF operations
|
||||||
* @owner: the owner of the module that registers the PCI EPF driver
|
* @owner: the owner of the module that registers the PCI EPF driver
|
||||||
* @group: configfs group corresponding to the PCI EPF driver
|
* @epf_group: list of configfs group corresponding to the PCI EPF driver
|
||||||
* @id_table: identifies EPF devices for probing
|
* @id_table: identifies EPF devices for probing
|
||||||
*/
|
*/
|
||||||
struct pci_epf_driver {
|
struct pci_epf_driver {
|
||||||
|
@ -82,7 +82,7 @@ struct pci_epf_driver {
|
||||||
struct device_driver driver;
|
struct device_driver driver;
|
||||||
struct pci_epf_ops *ops;
|
struct pci_epf_ops *ops;
|
||||||
struct module *owner;
|
struct module *owner;
|
||||||
struct config_group *group;
|
struct list_head epf_group;
|
||||||
const struct pci_epf_device_id *id_table;
|
const struct pci_epf_device_id *id_table;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user