Commit b2929a9c authored by Linus Walleij's avatar Linus Walleij
Browse files

Merge tag 'gpio-updates-for-v5.7-part1' of...

Merge tag 'gpio-updates-for-v5.7-part1' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux into devel

gpio updates for v5.7 part 1

- make irqs optional in gpio-pxa
- improve the logic behind the get() and set() callbacks in gpio-wcd934x
- add new kfifo helpers (acked by kfifo maintainer)
- rework the locking mechanism for lineevent kfifo
- implement a new ioctl() for watching changes on GPIO lines
parents 046e14af 33f0c47b
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -652,8 +652,8 @@ static int pxa_gpio_probe(struct platform_device *pdev)
	if (!pchip->irqdomain)
		return -ENOMEM;

	irq0 = platform_get_irq_byname(pdev, "gpio0");
	irq1 = platform_get_irq_byname(pdev, "gpio1");
	irq0 = platform_get_irq_byname_optional(pdev, "gpio0");
	irq1 = platform_get_irq_byname_optional(pdev, "gpio1");
	irq_mux = platform_get_irq_byname(pdev, "gpio_mux");
	if ((irq0 > 0 && irq1 <= 0) || (irq0 <= 0 && irq1 > 0)
		|| (irq_mux <= 0))
+6 −3
Original line number Diff line number Diff line
@@ -57,16 +57,19 @@ static int wcd_gpio_direction_output(struct gpio_chip *chip, unsigned int pin,
static int wcd_gpio_get(struct gpio_chip *chip, unsigned int pin)
{
	struct wcd_gpio_data *data = gpiochip_get_data(chip);
	int value;
	unsigned int value;

	regmap_read(data->map, WCD_REG_VAL_CTL_OFFSET, &value);

	return !!(value && WCD_PIN_MASK(pin));
	return !!(value & WCD_PIN_MASK(pin));
}

static void wcd_gpio_set(struct gpio_chip *chip, unsigned int pin, int val)
{
	wcd_gpio_direction_output(chip, pin, val);
	struct wcd_gpio_data *data = gpiochip_get_data(chip);

	regmap_update_bits(data->map, WCD_REG_VAL_CTL_OFFSET,
			   WCD_PIN_MASK(pin), val ? WCD_PIN_MASK(pin) : 0);
}

static int wcd_gpio_probe(struct platform_device *pdev)
+270 −80
Original line number Diff line number Diff line
@@ -546,6 +546,9 @@ static long linehandle_set_config(struct linehandle_state *lh,
			if (ret)
				return ret;
		}

		atomic_notifier_call_chain(&desc->gdev->notifier,
					   GPIOLINE_CHANGED_CONFIG, desc);
	}
	return 0;
}
@@ -787,8 +790,6 @@ out_free_lh:
 * @irq: the interrupt that trigger in response to events on this GPIO
 * @wait: wait queue that handles blocking reads of events
 * @events: KFIFO for the GPIO events
 * @read_lock: mutex lock to protect reads from colliding with adding
 * new events to the FIFO
 * @timestamp: cache for the timestamp storing it between hardirq
 * and IRQ thread, used to bring the timestamp close to the actual
 * event
@@ -801,7 +802,6 @@ struct lineevent_state {
	int irq;
	wait_queue_head_t wait;
	DECLARE_KFIFO(events, struct gpioevent_data, 16);
	struct mutex read_lock;
	u64 timestamp;
};

@@ -817,7 +817,7 @@ static __poll_t lineevent_poll(struct file *filep,

	poll_wait(filep, &le->wait, wait);

	if (!kfifo_is_empty(&le->events))
	if (!kfifo_is_empty_spinlocked_noirqsave(&le->events, &le->wait.lock))
		events = EPOLLIN | EPOLLRDNORM;

	return events;
@@ -830,43 +830,52 @@ static ssize_t lineevent_read(struct file *filep,
			      loff_t *f_ps)
{
	struct lineevent_state *le = filep->private_data;
	unsigned int copied;
	struct gpioevent_data event;
	ssize_t bytes_read = 0;
	int ret;

	if (count < sizeof(struct gpioevent_data))
	if (count < sizeof(event))
		return -EINVAL;

	do {
		spin_lock(&le->wait.lock);
		if (kfifo_is_empty(&le->events)) {
			if (filep->f_flags & O_NONBLOCK)
			if (bytes_read) {
				spin_unlock(&le->wait.lock);
				return bytes_read;
			}

			if (filep->f_flags & O_NONBLOCK) {
				spin_unlock(&le->wait.lock);
				return -EAGAIN;
			}

			ret = wait_event_interruptible(le->wait,
			ret = wait_event_interruptible_locked(le->wait,
					!kfifo_is_empty(&le->events));
			if (ret)
			if (ret) {
				spin_unlock(&le->wait.lock);
				return ret;
			}
		}

		if (mutex_lock_interruptible(&le->read_lock))
			return -ERESTARTSYS;
		ret = kfifo_to_user(&le->events, buf, count, &copied);
		mutex_unlock(&le->read_lock);

		if (ret)
			return ret;

		ret = kfifo_out(&le->events, &event, 1);
		spin_unlock(&le->wait.lock);
		if (ret != 1) {
			/*
		 * If we couldn't read anything from the fifo (a different
		 * thread might have been faster) we either return -EAGAIN if
		 * the file descriptor is non-blocking, otherwise we go back to
		 * sleep and wait for more data to arrive.
			 * This should never happen - we were holding the lock
			 * from the moment we learned the fifo is no longer
			 * empty until now.
			 */
		if (copied == 0 && (filep->f_flags & O_NONBLOCK))
			return -EAGAIN;
			ret = -EIO;
			break;
		}

	} while (copied == 0);
		if (copy_to_user(buf + bytes_read, &event, sizeof(event)))
			return -EFAULT;
		bytes_read += sizeof(event);
	} while (count >= bytes_read + sizeof(event));

	return copied;
	return bytes_read;
}

static int lineevent_release(struct inode *inode, struct file *filep)
@@ -968,9 +977,12 @@ static irqreturn_t lineevent_irq_thread(int irq, void *p)
		return IRQ_NONE;
	}

	ret = kfifo_put(&le->events, ge);
	ret = kfifo_in_spinlocked_noirqsave(&le->events, &ge,
					    1, &le->wait.lock);
	if (ret)
		wake_up_poll(&le->wait, EPOLLIN);
	else
		pr_debug_ratelimited("event FIFO is full - event dropped\n");

	return IRQ_HANDLED;
}
@@ -1083,7 +1095,6 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)

	INIT_KFIFO(le->events);
	init_waitqueue_head(&le->wait);
	mutex_init(&le->read_lock);

	/* Request a thread to read the events */
	ret = request_threaded_irq(le->irq,
@@ -1139,14 +1150,79 @@ out_free_le:
	return ret;
}

static void gpio_desc_to_lineinfo(struct gpio_desc *desc,
				  struct gpioline_info *info)
{
	struct gpio_chip *chip = desc->gdev->chip;
	unsigned long flags;

	spin_lock_irqsave(&gpio_lock, flags);

	if (desc->name) {
		strncpy(info->name, desc->name, sizeof(info->name));
		info->name[sizeof(info->name) - 1] = '\0';
	} else {
		info->name[0] = '\0';
	}

	if (desc->label) {
		strncpy(info->consumer, desc->label, sizeof(info->consumer));
		info->consumer[sizeof(info->consumer) - 1] = '\0';
	} else {
		info->consumer[0] = '\0';
	}

	/*
	 * Userspace only need to know that the kernel is using this GPIO so
	 * it can't use it.
	 */
	info->flags = 0;
	if (test_bit(FLAG_REQUESTED, &desc->flags) ||
	    test_bit(FLAG_IS_HOGGED, &desc->flags) ||
	    test_bit(FLAG_USED_AS_IRQ, &desc->flags) ||
	    test_bit(FLAG_EXPORT, &desc->flags) ||
	    test_bit(FLAG_SYSFS, &desc->flags) ||
	    !pinctrl_gpio_can_use_line(chip->base + info->line_offset))
		info->flags |= GPIOLINE_FLAG_KERNEL;
	if (test_bit(FLAG_IS_OUT, &desc->flags))
		info->flags |= GPIOLINE_FLAG_IS_OUT;
	if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
		info->flags |= GPIOLINE_FLAG_ACTIVE_LOW;
	if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
		info->flags |= (GPIOLINE_FLAG_OPEN_DRAIN |
				GPIOLINE_FLAG_IS_OUT);
	if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
		info->flags |= (GPIOLINE_FLAG_OPEN_SOURCE |
				GPIOLINE_FLAG_IS_OUT);
	if (test_bit(FLAG_BIAS_DISABLE, &desc->flags))
		info->flags |= GPIOLINE_FLAG_BIAS_DISABLE;
	if (test_bit(FLAG_PULL_DOWN, &desc->flags))
		info->flags |= GPIOLINE_FLAG_BIAS_PULL_DOWN;
	if (test_bit(FLAG_PULL_UP, &desc->flags))
		info->flags |= GPIOLINE_FLAG_BIAS_PULL_UP;

	spin_unlock_irqrestore(&gpio_lock, flags);
}

struct gpio_chardev_data {
	struct gpio_device *gdev;
	wait_queue_head_t wait;
	DECLARE_KFIFO(events, struct gpioline_info_changed, 32);
	struct notifier_block lineinfo_changed_nb;
	unsigned long *watched_lines;
};

/*
 * gpio_ioctl() - ioctl handler for the GPIO chardev
 */
static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct gpio_device *gdev = filp->private_data;
	struct gpio_chardev_data *priv = filp->private_data;
	struct gpio_device *gdev = priv->gdev;
	struct gpio_chip *chip = gdev->chip;
	void __user *ip = (void __user *)arg;
	struct gpio_desc *desc;
	__u32 offset;

	/* We fail any subsequent ioctl():s when the chip is gone */
	if (!chip)
@@ -1168,9 +1244,9 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
		if (copy_to_user(ip, &chipinfo, sizeof(chipinfo)))
			return -EFAULT;
		return 0;
	} else if (cmd == GPIO_GET_LINEINFO_IOCTL) {
	} else if (cmd == GPIO_GET_LINEINFO_IOCTL ||
		   cmd == GPIO_GET_LINEINFO_WATCH_IOCTL) {
		struct gpioline_info lineinfo;
		struct gpio_desc *desc;

		if (copy_from_user(&lineinfo, ip, sizeof(lineinfo)))
			return -EFAULT;
@@ -1179,57 +1255,29 @@ static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
		if (IS_ERR(desc))
			return PTR_ERR(desc);

		if (desc->name) {
			strncpy(lineinfo.name, desc->name,
				sizeof(lineinfo.name));
			lineinfo.name[sizeof(lineinfo.name)-1] = '\0';
		} else {
			lineinfo.name[0] = '\0';
		}
		if (desc->label) {
			strncpy(lineinfo.consumer, desc->label,
				sizeof(lineinfo.consumer));
			lineinfo.consumer[sizeof(lineinfo.consumer)-1] = '\0';
		} else {
			lineinfo.consumer[0] = '\0';
		}

		/*
		 * Userspace only need to know that the kernel is using
		 * this GPIO so it can't use it.
		 */
		lineinfo.flags = 0;
		if (test_bit(FLAG_REQUESTED, &desc->flags) ||
		    test_bit(FLAG_IS_HOGGED, &desc->flags) ||
		    test_bit(FLAG_USED_AS_IRQ, &desc->flags) ||
		    test_bit(FLAG_EXPORT, &desc->flags) ||
		    test_bit(FLAG_SYSFS, &desc->flags) ||
		    !pinctrl_gpio_can_use_line(chip->base + lineinfo.line_offset))
			lineinfo.flags |= GPIOLINE_FLAG_KERNEL;
		if (test_bit(FLAG_IS_OUT, &desc->flags))
			lineinfo.flags |= GPIOLINE_FLAG_IS_OUT;
		if (test_bit(FLAG_ACTIVE_LOW, &desc->flags))
			lineinfo.flags |= GPIOLINE_FLAG_ACTIVE_LOW;
		if (test_bit(FLAG_OPEN_DRAIN, &desc->flags))
			lineinfo.flags |= (GPIOLINE_FLAG_OPEN_DRAIN |
					   GPIOLINE_FLAG_IS_OUT);
		if (test_bit(FLAG_OPEN_SOURCE, &desc->flags))
			lineinfo.flags |= (GPIOLINE_FLAG_OPEN_SOURCE |
					   GPIOLINE_FLAG_IS_OUT);
		if (test_bit(FLAG_BIAS_DISABLE, &desc->flags))
			lineinfo.flags |= GPIOLINE_FLAG_BIAS_DISABLE;
		if (test_bit(FLAG_PULL_DOWN, &desc->flags))
			lineinfo.flags |= GPIOLINE_FLAG_BIAS_PULL_DOWN;
		if (test_bit(FLAG_PULL_UP, &desc->flags))
			lineinfo.flags |= GPIOLINE_FLAG_BIAS_PULL_UP;
		gpio_desc_to_lineinfo(desc, &lineinfo);

		if (copy_to_user(ip, &lineinfo, sizeof(lineinfo)))
			return -EFAULT;

		if (cmd == GPIO_GET_LINEINFO_WATCH_IOCTL)
			set_bit(desc_to_gpio(desc), priv->watched_lines);

		return 0;
	} else if (cmd == GPIO_GET_LINEHANDLE_IOCTL) {
		return linehandle_create(gdev, ip);
	} else if (cmd == GPIO_GET_LINEEVENT_IOCTL) {
		return lineevent_create(gdev, ip);
	} else if (cmd == GPIO_GET_LINEINFO_UNWATCH_IOCTL) {
		if (copy_from_user(&offset, ip, sizeof(offset)))
			return -EFAULT;

		desc = gpiochip_get_desc(chip, offset);
		if (IS_ERR(desc))
			return PTR_ERR(desc);

		clear_bit(desc_to_gpio(desc), &desc->flags);
		return 0;
	}
	return -EINVAL;
}
@@ -1242,6 +1290,101 @@ static long gpio_ioctl_compat(struct file *filp, unsigned int cmd,
}
#endif

static struct gpio_chardev_data *
to_gpio_chardev_data(struct notifier_block *nb)
{
	return container_of(nb, struct gpio_chardev_data, lineinfo_changed_nb);
}

static int lineinfo_changed_notify(struct notifier_block *nb,
				   unsigned long action, void *data)
{
	struct gpio_chardev_data *priv = to_gpio_chardev_data(nb);
	struct gpioline_info_changed chg;
	struct gpio_desc *desc = data;
	int ret;

	if (!test_bit(desc_to_gpio(desc), priv->watched_lines))
		return NOTIFY_DONE;

	memset(&chg, 0, sizeof(chg));
	chg.info.line_offset = gpio_chip_hwgpio(desc);
	chg.event_type = action;
	chg.timestamp = ktime_get_ns();
	gpio_desc_to_lineinfo(desc, &chg.info);

	ret = kfifo_in_spinlocked(&priv->events, &chg, 1, &priv->wait.lock);
	if (ret)
		wake_up_poll(&priv->wait, EPOLLIN);
	else
		pr_debug_ratelimited("lineinfo event FIFO is full - event dropped\n");

	return NOTIFY_OK;
}

static __poll_t lineinfo_watch_poll(struct file *filep,
				    struct poll_table_struct *pollt)
{
	struct gpio_chardev_data *priv = filep->private_data;
	__poll_t events = 0;

	poll_wait(filep, &priv->wait, pollt);

	if (!kfifo_is_empty_spinlocked_noirqsave(&priv->events,
						 &priv->wait.lock))
		events = EPOLLIN | EPOLLRDNORM;

	return events;
}

static ssize_t lineinfo_watch_read(struct file *filep, char __user *buf,
				   size_t count, loff_t *off)
{
	struct gpio_chardev_data *priv = filep->private_data;
	struct gpioline_info_changed event;
	ssize_t bytes_read = 0;
	int ret;

	if (count < sizeof(event))
		return -EINVAL;

	do {
		spin_lock(&priv->wait.lock);
		if (kfifo_is_empty(&priv->events)) {
			if (bytes_read) {
				spin_unlock(&priv->wait.lock);
				return bytes_read;
			}

			if (filep->f_flags & O_NONBLOCK) {
				spin_unlock(&priv->wait.lock);
				return -EAGAIN;
			}

			ret = wait_event_interruptible_locked(priv->wait,
					!kfifo_is_empty(&priv->events));
			if (ret) {
				spin_unlock(&priv->wait.lock);
				return ret;
			}
		}

		ret = kfifo_out(&priv->events, &event, 1);
		spin_unlock(&priv->wait.lock);
		if (ret != 1) {
			ret = -EIO;
			break;
			/* We should never get here. See lineevent_read(). */
		}

		if (copy_to_user(buf + bytes_read, &event, sizeof(event)))
			return -EFAULT;
		bytes_read += sizeof(event);
	} while (count >= bytes_read + sizeof(event));

	return bytes_read;
}

/**
 * gpio_chrdev_open() - open the chardev for ioctl operations
 * @inode: inode for this chardev
@@ -1252,14 +1395,48 @@ static int gpio_chrdev_open(struct inode *inode, struct file *filp)
{
	struct gpio_device *gdev = container_of(inode->i_cdev,
					      struct gpio_device, chrdev);
	struct gpio_chardev_data *priv;
	int ret = -ENOMEM;

	/* Fail on open if the backing gpiochip is gone */
	if (!gdev->chip)
		return -ENODEV;

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->watched_lines = bitmap_zalloc(gdev->chip->ngpio, GFP_KERNEL);
	if (!priv->watched_lines)
		goto out_free_priv;

	init_waitqueue_head(&priv->wait);
	INIT_KFIFO(priv->events);
	priv->gdev = gdev;

	priv->lineinfo_changed_nb.notifier_call = lineinfo_changed_notify;
	ret = atomic_notifier_chain_register(&gdev->notifier,
					     &priv->lineinfo_changed_nb);
	if (ret)
		goto out_free_bitmap;

	get_device(&gdev->dev);
	filp->private_data = gdev;
	filp->private_data = priv;

	ret = nonseekable_open(inode, filp);
	if (ret)
		goto out_unregister_notifier;

	return nonseekable_open(inode, filp);
	return ret;

out_unregister_notifier:
	atomic_notifier_chain_unregister(&gdev->notifier,
					 &priv->lineinfo_changed_nb);
out_free_bitmap:
	bitmap_free(priv->watched_lines);
out_free_priv:
	kfree(priv);
	return ret;
}

/**
@@ -1270,17 +1447,23 @@ static int gpio_chrdev_open(struct inode *inode, struct file *filp)
 */
static int gpio_chrdev_release(struct inode *inode, struct file *filp)
{
	struct gpio_device *gdev = container_of(inode->i_cdev,
					      struct gpio_device, chrdev);
	struct gpio_chardev_data *priv = filp->private_data;
	struct gpio_device *gdev = priv->gdev;

	bitmap_free(priv->watched_lines);
	atomic_notifier_chain_unregister(&gdev->notifier,
					 &priv->lineinfo_changed_nb);
	put_device(&gdev->dev);
	kfree(priv);

	return 0;
}


static const struct file_operations gpio_fileops = {
	.release = gpio_chrdev_release,
	.open = gpio_chrdev_open,
	.poll = lineinfo_watch_poll,
	.read = lineinfo_watch_read,
	.owner = THIS_MODULE,
	.llseek = no_llseek,
	.unlocked_ioctl = gpio_ioctl,
@@ -1491,6 +1674,8 @@ int gpiochip_add_data_with_key(struct gpio_chip *chip, void *data,

	spin_unlock_irqrestore(&gpio_lock, flags);

	ATOMIC_INIT_NOTIFIER_HEAD(&gdev->notifier);

#ifdef CONFIG_PINCTRL
	INIT_LIST_HEAD(&gdev->pin_ranges);
#endif
@@ -2823,6 +3008,8 @@ static int gpiod_request_commit(struct gpio_desc *desc, const char *label)
	}
done:
	spin_unlock_irqrestore(&gpio_lock, flags);
	atomic_notifier_call_chain(&desc->gdev->notifier,
				   GPIOLINE_CHANGED_REQUESTED, desc);
	return ret;
}

@@ -2920,6 +3107,9 @@ static bool gpiod_free_commit(struct gpio_desc *desc)
	}

	spin_unlock_irqrestore(&gpio_lock, flags);
	atomic_notifier_call_chain(&desc->gdev->notifier,
				   GPIOLINE_CHANGED_RELEASED, desc);

	return ret;
}

+1 −0
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ struct gpio_device {
	const char		*label;
	void			*data;
	struct list_head        list;
	struct atomic_notifier_head notifier;

#ifdef CONFIG_PINCTRL
	/*
+73 −0
Original line number Diff line number Diff line
@@ -246,6 +246,37 @@ __kfifo_int_must_check_helper(int val)
	__tmpq->kfifo.in == __tmpq->kfifo.out; \
})

/**
 * kfifo_is_empty_spinlocked - returns true if the fifo is empty using
 * a spinlock for locking
 * @fifo: address of the fifo to be used
 * @lock: spinlock to be used for locking
 */
#define kfifo_is_empty_spinlocked(fifo, lock) \
({ \
	unsigned long __flags; \
	bool __ret; \
	spin_lock_irqsave(lock, __flags); \
	__ret = kfifo_is_empty(fifo); \
	spin_unlock_irqrestore(lock, __flags); \
	__ret; \
})

/**
 * kfifo_is_empty_spinlocked_noirqsave  - returns true if the fifo is empty
 * using a spinlock for locking, doesn't disable interrupts
 * @fifo: address of the fifo to be used
 * @lock: spinlock to be used for locking
 */
#define kfifo_is_empty_spinlocked_noirqsave(fifo, lock) \
({ \
	bool __ret; \
	spin_lock(lock); \
	__ret = kfifo_is_empty(fifo); \
	spin_unlock(lock); \
	__ret; \
})

/**
 * kfifo_is_full - returns true if the fifo is full
 * @fifo: address of the fifo to be used
@@ -517,6 +548,26 @@ __kfifo_uint_must_check_helper( \
	__ret; \
})

/**
 * kfifo_in_spinlocked_noirqsave - put data into fifo using a spinlock for
 * locking, don't disable interrupts
 * @fifo: address of the fifo to be used
 * @buf: the data to be added
 * @n: number of elements to be added
 * @lock: pointer to the spinlock to use for locking
 *
 * This is a variant of kfifo_in_spinlocked() but uses spin_lock/unlock()
 * for locking and doesn't disable interrupts.
 */
#define kfifo_in_spinlocked_noirqsave(fifo, buf, n, lock) \
({ \
	unsigned int __ret; \
	spin_lock(lock); \
	__ret = kfifo_in(fifo, buf, n); \
	spin_unlock(lock); \
	__ret; \
})

/* alias for kfifo_in_spinlocked, will be removed in a future release */
#define kfifo_in_locked(fifo, buf, n, lock) \
		kfifo_in_spinlocked(fifo, buf, n, lock)
@@ -569,6 +620,28 @@ __kfifo_uint_must_check_helper( \
}) \
)

/**
 * kfifo_out_spinlocked_noirqsave - get data from the fifo using a spinlock
 * for locking, don't disable interrupts
 * @fifo: address of the fifo to be used
 * @buf: pointer to the storage buffer
 * @n: max. number of elements to get
 * @lock: pointer to the spinlock to use for locking
 *
 * This is a variant of kfifo_out_spinlocked() which uses spin_lock/unlock()
 * for locking and doesn't disable interrupts.
 */
#define kfifo_out_spinlocked_noirqsave(fifo, buf, n, lock) \
__kfifo_uint_must_check_helper( \
({ \
	unsigned int __ret; \
	spin_lock(lock); \
	__ret = kfifo_out(fifo, buf, n); \
	spin_unlock(lock); \
	__ret; \
}) \
)

/* alias for kfifo_out_spinlocked, will be removed in a future release */
#define kfifo_out_locked(fifo, buf, n, lock) \
		kfifo_out_spinlocked(fifo, buf, n, lock)
Loading