Re: [PATCH v6 part1 4/8] usb: assign default peer ports for root hubs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, 2014-03-04 at 10:58 -0500, Alan Stern wrote:
> On Mon, 3 Mar 2014, Dan Williams wrote:
> 
> > Ok, so the root issue is that the peering code needs to see
> > hcd->primary_hcd = NULL to know that there is no longer a peer.  I
> > update usb_remove_hcd() to clear out ->shared_hcd and ->primary_hcd
> > under the peer lock before we allow the root hub to be freed.
> 
> Even if there is a peer hcd, the peer's root hub may not exist.  More
> locking is needed than you have here.
> 
> > I add some description of the new locking scheme to the changelog as
> > well.
> > 
> > 8<----------
> > Subject: usb: assign default peer ports for root hubs
> > 
> > From: Dan Williams <dan.j.williams@xxxxxxxxx>
> > 
> > Assume that the peer of a superspeed port is the port with the same id
> > on the shared_hcd root hub.  This identification scheme is required of
> > external hubs by the USB3 spec [1].  However, for root hubs, tier mismatch
> > may be in effect [2].  Tier mismatch can only be enumerated via platform
> > firmware.  For now, simply perform the nominal association.
> > 
> > Once the root hub is marked as unregistered we block attempts to walk
> > the ->shared_hcd pointer to find a root-peer port.
> > 
> > A new lock 'usb_port_peer_mutex' is introduced to synchronize port
> > device add/remove with peer lookups.  We hold the lock for the duration
> > of registration allowing default peers (and later firmware identified
> > peers) to be discovered.  We also hold the lock whenever hub devices are
> > validated (hdev->maxchild set to non-zero) and invalidated
> > (hdev->maxchild set to zero).  Marking a hub valid and registering its
> > ports is a locked operation and conversely invalidating a hub and
> > removing its ports is another locked operation.  This prevents a port
> > from associating with an invalid peer.  Finally, we hold the lock when
> > breaking the "shared hcd" relationship at usb_remove_hcd() time.
> 
> More generally, I would say that the new mutex protects the
> hcd->shared_hcd, hcd->self.root_hub, and port_dev->child pointers, in 
> addition to hdev->maxchild.

Yes, much more succinct.

> 
> > [1]: usb 3.1 section 10.3.3
> > [2]: xhci 1.1 appendix D
> > 
> > Cc: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>
> > [alan: usb_port_peer_mutex locking scheme]
> > Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
> > ---
> >  drivers/usb/core/hcd.c  |   21 ++++++++++++-
> >  drivers/usb/core/hub.c  |   37 ++++++++++++++--------
> >  drivers/usb/core/hub.h  |    2 +
> >  drivers/usb/core/port.c |   78 ++++++++++++++++++++++++++++++++++++++++++++---
> >  4 files changed, 118 insertions(+), 20 deletions(-)
> > 
> > diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
> > index 2518c3250750..259990a982f3 100644
> > --- a/drivers/usb/core/hcd.c
> > +++ b/drivers/usb/core/hcd.c
> > @@ -2766,6 +2766,8 @@ EXPORT_SYMBOL_GPL(usb_add_hcd);
> >   */
> >  void usb_remove_hcd(struct usb_hcd *hcd)
> >  {
> > +	/* only routine outside of hub.c that needs this mutex */
> > +	extern struct mutex usb_port_peer_mutex;
> 
> To be thorough, you also should hold the mutex when initially setting 
> hcd->self.root_hub.  And in usb_create_shared_hcd, when the shared_hcd 
> pointers are initialized.

Ok, I added this.

> 
> Also, don't forget to clear hcd->self.root_hub (under protection
> of the mutex) in the failure pathway of usb_add_hcd.

I made a new usb_put_invalidate_rhdev() to handle cases where we can set
hcd->self.root_hub to NULL.

> 
> > @@ -2829,6 +2830,24 @@ void usb_remove_hcd(struct usb_hcd *hcd)
> >  		usb_put_phy(hcd->phy);
> >  		hcd->phy = NULL;
> >  	}
> > +
> > +	/*
> > +	 * Before we free this device, flush in-flight peering attempts
> > +	 * and disable peer lookups
> > +	 */
> > +	mutex_lock(&usb_port_peer_mutex);
> > +	if (hcd->shared_hcd) {
> > +		struct usb_hcd *peer_hcd = hcd->shared_hcd;
> > +
> > +		hcd->shared_hcd = NULL;
> > +		hcd->primary_hcd = NULL;
> > +		peer_hcd->shared_hcd = NULL;
> > +		if (peer_hcd->primary_hcd == hcd)
> > +			peer_hcd->primary_hcd = NULL;
> > +	}
> > +	mutex_unlock(&usb_port_peer_mutex);
> 
> This belongs in hcd_release, not here, corresponding to the fact that 
> these pointers are initialized in usb_create_shared_hcd.
> 

Yes, I overlooked hcd_release.

> > +
> > +	usb_put_dev(hcd->self.root_hub);
> 
> Before doing this, you need to set hcd->self.root_hub to NULL, under 
> the protection of the mutex.  Unfortunately, it's not possible to move 
> the usb_put_dev call into hcd_release -- this is because each USB 
> device (including the root hub) holds a reference to the hcd.
> 
> > diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
> > index 531a591a7b1e..cb42352b97e6 100644
> > --- a/drivers/usb/core/port.c
> > +++ b/drivers/usb/core/port.c
> > @@ -151,9 +151,68 @@ static struct device_driver usb_port_driver = {
> >  	.owner = THIS_MODULE,
> >  };
> >  
> > +/*
> > + * Set the default peer port for root hubs.  Assumes the primary_hcd is
> > + * registered first
> 
> I realized last night that the second sentence is wrong.  We have to be 
> able to go both ways, from the secondary hcd to the primary and vice 
> versa.  This is because userspace can unbind and rebind the hub driver 
> to the primary root hub at any time.
> 

Fixed.

> > + */
> > +static struct usb_port *find_default_peer(struct usb_hub *hub, int port1)
> > +{
> > +	struct usb_device *hdev = hub->hdev;
> > +	struct usb_port *peer = NULL;
> > +
> > +	if (!hdev->parent) {
> > +		struct usb_hub *peer_hub;
> > +		struct usb_device *peer_hdev;
> > +		struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
> > +		struct usb_hcd *peer_hcd = hcd->primary_hcd;
> 
> So this should be hcd->shared_hcd.
> 
> > +
> > +		if (!peer_hcd || hcd == peer_hcd)
> > +			return NULL;
> > +
> > +		peer_hdev = peer_hcd->self.root_hub;
> > +		peer_hub = usb_hub_to_struct_hub(peer_hdev);
> > +		if (!peer_hub || port1 > peer_hdev->maxchild)
> > +			return NULL;
> > +
> > +		peer = peer_hub->ports[port1 - 1];
> > +	}
> > +
> > +	return peer;
> > +}
> 

Please have a look at the following.  I'm sure Greg will be happy that
we are killing these bugs before they become fodder for -stable.  Thanks
for the review!  The other change I made was to convert
find_default_peer to find_and_link_peer per your other comments about
centralizing peering on patch 6.

8<---------
Subject: usb: assign default peer ports for root hubs

From: Dan Williams <dan.j.williams@xxxxxxxxx>

Assume that the peer of a superspeed port is the port with the same id
on the shared_hcd root hub.  This identification scheme is required of
external hubs by the USB3 spec [1].  However, for root hubs, tier mismatch
may be in effect [2].  Tier mismatch can only be enumerated via platform
firmware.  For now, simply perform the nominal association.

A new lock 'usb_port_peer_mutex' is introduced to synchronize port
device add/remove with peer lookups.  It protects peering against
changes to hcd->shared_hcd, hcd->self.root_hub, hdev->maxchild, and
port_dev->child pointers.

[1]: usb 3.1 section 10.3.3
[2]: xhci 1.1 appendix D

Cc: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>
[alan: usb_port_peer_mutex locking scheme]
Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
---
 drivers/usb/core/hcd.c  |   43 +++++++++++++++++++++++-----
 drivers/usb/core/hub.c  |   42 ++++++++++++++++++---------
 drivers/usb/core/hub.h  |    2 +
 drivers/usb/core/port.c |   73 ++++++++++++++++++++++++++++++++++++++++++++---
 drivers/usb/core/usb.h  |    1 +
 5 files changed, 134 insertions(+), 27 deletions(-)

diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 2518c3250750..af9f053b9626 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -2437,11 +2437,13 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver,
 		mutex_init(hcd->bandwidth_mutex);
 		dev_set_drvdata(dev, hcd);
 	} else {
+		mutex_lock(&usb_port_peer_mutex);
 		hcd->bandwidth_mutex = primary_hcd->bandwidth_mutex;
 		hcd->primary_hcd = primary_hcd;
 		primary_hcd->primary_hcd = primary_hcd;
 		hcd->shared_hcd = primary_hcd;
 		primary_hcd->shared_hcd = hcd;
+		mutex_unlock(&usb_port_peer_mutex);
 	}
 
 	kref_init(&hcd->kref);
@@ -2493,18 +2495,25 @@ EXPORT_SYMBOL_GPL(usb_create_hcd);
  * deallocated.
  *
  * Make sure to only deallocate the bandwidth_mutex when the primary HCD is
- * freed.  When hcd_release() is called for the non-primary HCD, set the
- * primary_hcd's shared_hcd pointer to null (since the non-primary HCD will be
- * freed shortly).
+ * freed.  When hcd_release() is called for either hcd in a peer set
+ * invalidate the peer's ->shared_hcd and ->primary_hcd pointers to
+ * block new peering attempts
  */
-static void hcd_release (struct kref *kref)
+static void hcd_release(struct kref *kref)
 {
 	struct usb_hcd *hcd = container_of (kref, struct usb_hcd, kref);
 
+	mutex_lock(&usb_port_peer_mutex);
 	if (usb_hcd_is_primary_hcd(hcd))
 		kfree(hcd->bandwidth_mutex);
-	else
-		hcd->shared_hcd->shared_hcd = NULL;
+	if (hcd->shared_hcd) {
+		struct usb_hcd *peer = hcd->shared_hcd;
+
+		peer->shared_hcd = NULL;
+		if (peer->primary_hcd == hcd)
+			peer->primary_hcd = NULL;
+	}
+	mutex_unlock(&usb_port_peer_mutex);
 	kfree(hcd);
 }
 
@@ -2572,6 +2581,21 @@ static int usb_hcd_request_irqs(struct usb_hcd *hcd,
 	return 0;
 }
 
+/*
+ * Before we free this root hub, flush in-flight peering attempts
+ * and disable peer lookups
+ */
+static void usb_put_invalidate_rhdev(struct usb_hcd *hcd)
+{
+	struct usb_device *rhdev;
+
+	mutex_lock(&usb_port_peer_mutex);
+	rhdev = hcd->self.root_hub;
+	hcd->self.root_hub = NULL;
+	mutex_unlock(&usb_port_peer_mutex);
+	usb_put_dev(rhdev);
+}
+
 /**
  * usb_add_hcd - finish generic HCD structure initialization and register
  * @hcd: the usb_hcd structure to initialize
@@ -2632,7 +2656,9 @@ int usb_add_hcd(struct usb_hcd *hcd,
 		retval = -ENOMEM;
 		goto err_allocate_root_hub;
 	}
+	mutex_lock(&usb_port_peer_mutex);
 	hcd->self.root_hub = rhdev;
+	mutex_unlock(&usb_port_peer_mutex);
 
 	switch (hcd->speed) {
 	case HCD_USB11:
@@ -2741,7 +2767,7 @@ err_hcd_driver_start:
 err_request_irq:
 err_hcd_driver_setup:
 err_set_rh_speed:
-	usb_put_dev(hcd->self.root_hub);
+	usb_put_invalidate_rhdev(hcd);
 err_allocate_root_hub:
 	usb_deregister_bus(&hcd->self);
 err_register_bus:
@@ -2821,7 +2847,6 @@ void usb_remove_hcd(struct usb_hcd *hcd)
 			free_irq(hcd->irq, hcd);
 	}
 
-	usb_put_dev(hcd->self.root_hub);
 	usb_deregister_bus(&hcd->self);
 	hcd_buffer_destroy(hcd);
 	if (hcd->remove_phy && hcd->phy) {
@@ -2829,6 +2854,8 @@ void usb_remove_hcd(struct usb_hcd *hcd)
 		usb_put_phy(hcd->phy);
 		hcd->phy = NULL;
 	}
+
+	usb_put_invalidate_rhdev(hcd);
 }
 EXPORT_SYMBOL_GPL(usb_remove_hcd);
 
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 67de9b63a98d..c025edb90e1f 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -55,6 +55,9 @@ static DECLARE_WAIT_QUEUE_HEAD(khubd_wait);
 
 static struct task_struct *khubd_task;
 
+/* synchronize hub-port add/remove and peering operations */
+DEFINE_MUTEX(usb_port_peer_mutex);
+
 /* cycle leds on hubs that aren't blinking for attention */
 static bool blinkenlights = 0;
 module_param (blinkenlights, bool, S_IRUGO);
@@ -1303,6 +1306,7 @@ static int hub_configure(struct usb_hub *hub,
 	char *message = "out of memory";
 	unsigned unit_load;
 	unsigned full_load;
+	unsigned maxchild;
 
 	hub->buffer = kmalloc(sizeof(*hub->buffer), GFP_KERNEL);
 	if (!hub->buffer) {
@@ -1341,12 +1345,11 @@ static int hub_configure(struct usb_hub *hub,
 		goto fail;
 	}
 
-	hdev->maxchild = hub->descriptor->bNbrPorts;
-	dev_info (hub_dev, "%d port%s detected\n", hdev->maxchild,
-		(hdev->maxchild == 1) ? "" : "s");
+	maxchild = hub->descriptor->bNbrPorts;
+	dev_info(hub_dev, "%d port%s detected\n", maxchild,
+			(maxchild == 1) ? "" : "s");
 
-	hub->ports = kzalloc(hdev->maxchild * sizeof(struct usb_port *),
-			     GFP_KERNEL);
+	hub->ports = kzalloc(maxchild * sizeof(struct usb_port *), GFP_KERNEL);
 	if (!hub->ports) {
 		ret = -ENOMEM;
 		goto fail;
@@ -1367,11 +1370,11 @@ static int hub_configure(struct usb_hub *hub,
 		int	i;
 		char	portstr[USB_MAXCHILDREN + 1];
 
-		for (i = 0; i < hdev->maxchild; i++)
+		for (i = 0; i < maxchild; i++)
 			portstr[i] = hub->descriptor->u.hs.DeviceRemovable
 				    [((i + 1) / 8)] & (1 << ((i + 1) % 8))
 				? 'F' : 'R';
-		portstr[hdev->maxchild] = 0;
+		portstr[maxchild] = 0;
 		dev_dbg(hub_dev, "compound device; port removable status: %s\n", portstr);
 	} else
 		dev_dbg(hub_dev, "standalone hub\n");
@@ -1483,7 +1486,7 @@ static int hub_configure(struct usb_hub *hub,
 		if (hcd->power_budget > 0)
 			hdev->bus_mA = hcd->power_budget;
 		else
-			hdev->bus_mA = full_load * hdev->maxchild;
+			hdev->bus_mA = full_load * maxchild;
 		if (hdev->bus_mA >= full_load)
 			hub->mA_per_port = full_load;
 		else {
@@ -1498,7 +1501,7 @@ static int hub_configure(struct usb_hub *hub,
 			hub->descriptor->bHubContrCurrent);
 		hub->limited_power = 1;
 
-		if (remaining < hdev->maxchild * unit_load)
+		if (remaining < maxchild * unit_load)
 			dev_warn(hub_dev,
 					"insufficient power available "
 					"to use all downstream ports\n");
@@ -1566,15 +1569,19 @@ static int hub_configure(struct usb_hub *hub,
 	if (hub->has_indicators && blinkenlights)
 		hub->indicator[0] = INDICATOR_CYCLE;
 
-	for (i = 0; i < hdev->maxchild; i++) {
+	mutex_lock(&usb_port_peer_mutex);
+	for (i = 0; i < maxchild; i++) {
 		ret = usb_hub_create_port_device(hub, i + 1);
 		if (ret < 0) {
 			dev_err(hub->intfdev,
 				"couldn't create port%d device.\n", i + 1);
-			hdev->maxchild = i;
-			goto fail_keep_maxchild;
+			break;
 		}
 	}
+	hdev->maxchild = i;
+	mutex_unlock(&usb_port_peer_mutex);
+	if (ret < 0)
+		goto fail;
 
 	usb_hub_adjust_deviceremovable(hdev, hub->descriptor);
 
@@ -1582,8 +1589,6 @@ static int hub_configure(struct usb_hub *hub,
 	return 0;
 
 fail:
-	hdev->maxchild = 0;
-fail_keep_maxchild:
 	dev_err (hub_dev, "config failed, %s (err %d)\n",
 			message, ret);
 	/* hub_disconnect() frees urb and descriptor */
@@ -1619,6 +1624,8 @@ static void hub_disconnect(struct usb_interface *intf)
 	hub->error = 0;
 	hub_quiesce(hub, HUB_DISCONNECT);
 
+	mutex_lock(&usb_port_peer_mutex);
+
 	/* Avoid races with recursively_mark_NOTATTACHED() */
 	spin_lock_irq(&device_state_lock);
 	port1 = hdev->maxchild;
@@ -1629,6 +1636,8 @@ static void hub_disconnect(struct usb_interface *intf)
 	for (; port1 > 0; --port1)
 		usb_hub_remove_port_device(hub, port1);
 
+	mutex_unlock(&usb_port_peer_mutex);
+
 	if (hub->hdev->speed == USB_SPEED_HIGH)
 		highspeed_hubs--;
 
@@ -4574,6 +4583,8 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
 		 */
 		status = 0;
 
+		mutex_lock(&usb_port_peer_mutex);
+
 		/* We mustn't add new devices if the parent hub has
 		 * been disconnected; we would race with the
 		 * recursively_mark_NOTATTACHED() routine.
@@ -4584,14 +4595,17 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
 		else
 			port_dev->child = udev;
 		spin_unlock_irq(&device_state_lock);
+		mutex_unlock(&usb_port_peer_mutex);
 
 		/* Run it through the hoops (find a driver, etc) */
 		if (!status) {
 			status = usb_new_device(udev);
 			if (status) {
+				mutex_lock(&usb_port_peer_mutex);
 				spin_lock_irq(&device_state_lock);
 				port_dev->child = NULL;
 				spin_unlock_irq(&device_state_lock);
+				mutex_unlock(&usb_port_peer_mutex);
 			}
 		}
 
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index baf5b48b79f7..d51feb68165b 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -81,6 +81,7 @@ struct usb_hub {
  * @child: usb device attached to the port
  * @dev: generic device interface
  * @port_owner: port's owner
+ * @peer: related usb2 and usb3 ports (share the same connector)
  * @connect_type: port's connect type
  * @portnum: port index num based one
  * @power_is_on: port's power state
@@ -90,6 +91,7 @@ struct usb_port {
 	struct usb_device *child;
 	struct device dev;
 	struct dev_state *port_owner;
+	struct usb_port *peer;
 	enum usb_port_connect_type connect_type;
 	u8 portnum;
 	unsigned power_is_on:1;
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 531a591a7b1e..0d36610af156 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -151,9 +151,66 @@ static struct device_driver usb_port_driver = {
 	.owner = THIS_MODULE,
 };
 
+static void link_peers(struct usb_port *left, struct usb_port *right)
+{
+	if (left->peer == right && right->peer == left)
+		return;
+
+	if (left->peer || right->peer) {
+		struct usb_port *lpeer = left->peer;
+		struct usb_port *rpeer = right->peer;
+
+		WARN(1, "failed to peer %s and %s (%s -> %p) (%s -> %p)\n",
+			dev_name(&left->dev), dev_name(&right->dev),
+			dev_name(&left->dev), lpeer,
+			dev_name(&right->dev), rpeer);
+		return;
+	}
+
+	left->peer = right;
+	right->peer = left;
+}
+
+static void unlink_peers(struct usb_port *left, struct usb_port *right)
+{
+	WARN(right->peer != left || left->peer != right,
+			"%s and %s are not peers?\n",
+			dev_name(&left->dev), dev_name(&right->dev));
+
+	right->peer = NULL;
+	left->peer = NULL;
+}
+
+/* set the default peer port for root hubs */
+static void find_and_link_peer(struct usb_hub *hub, int port1)
+{
+	struct usb_port *port_dev = hub->ports[port1 - 1], *peer;
+	struct usb_device *hdev = hub->hdev;
+
+	if (!hdev->parent) {
+		struct usb_hub *peer_hub;
+		struct usb_device *peer_hdev;
+		struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
+		struct usb_hcd *peer_hcd = hcd->shared_hcd;
+
+		if (!peer_hcd)
+			return;
+
+		peer_hdev = peer_hcd->self.root_hub;
+		peer_hub = usb_hub_to_struct_hub(peer_hdev);
+		if (!peer_hub || port1 > peer_hdev->maxchild)
+			return;
+
+		peer = peer_hub->ports[port1 - 1];
+
+		if (peer)
+			link_peers(port_dev, peer);
+	}
+}
+
 int usb_hub_create_port_device(struct usb_hub *hub, int port1)
 {
-	struct usb_port *port_dev = NULL;
+	struct usb_port *port_dev;
 	int retval;
 
 	port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL);
@@ -175,6 +232,8 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
 	if (retval)
 		goto error_register;
 
+	find_and_link_peer(hub, port1);
+
 	pm_runtime_set_active(&port_dev->dev);
 
 	/*
@@ -197,9 +256,13 @@ exit:
 	return retval;
 }
 
-void usb_hub_remove_port_device(struct usb_hub *hub,
-				       int port1)
+void usb_hub_remove_port_device(struct usb_hub *hub, int port1)
 {
-	device_unregister(&hub->ports[port1 - 1]->dev);
-}
+	struct usb_port *port_dev = hub->ports[port1 - 1];
+	struct usb_port *peer;
 
+	peer = port_dev->peer;
+	if (peer)
+		unlink_peers(port_dev, peer);
+	device_unregister(&port_dev->dev);
+}
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 28f272884c0d..af89070d771d 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -123,6 +123,7 @@ static inline int usb_set_usb2_hardware_lpm(struct usb_device *udev, int enable)
 #endif
 
 extern struct bus_type usb_bus_type;
+extern struct mutex usb_port_peer_mutex;
 extern struct device_type usb_device_type;
 extern struct device_type usb_if_device_type;
 extern struct device_type usb_ep_device_type;


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux