Hi Greg,
nobody has protested. For me it works. SMP I can't test.
The change set will reduce the scope of the BKL to improve
SMP scalability and fixes races in usbfs with respect to module unload.
It does so by providing generic functions to bind and unbind
drivers to devices and use the BKL where needed.
This is against the current 2.5 tree. Once the new module code
goes into the mainline, the BKL uses can simply go away and
the usage counts will still be properly managed.
The second attached cset contains documentation for the
added functions.
Please apply.
Regards
Oliver
You can import this changeset into BK by piping this whole message to:
'| bk receive [path to repository]' or apply the patch as usual.
===================================================================
[EMAIL PROTECTED], 2002-07-02 23:14:07+02:00, [EMAIL PROTECTED]
- BKL against module unload only where needed
diff -Nru a/drivers/usb/core/devices.c b/drivers/usb/core/devices.c
--- a/drivers/usb/core/devices.c Thu Jul 4 00:33:50 2002
+++ b/drivers/usb/core/devices.c Thu Jul 4 00:33:50 2002
@@ -239,6 +239,7 @@
if (start > end)
return start;
+ lock_kernel(); /* driver might be unloaded */
start += sprintf(start, format_iface,
desc->bInterfaceNumber,
desc->bAlternateSetting,
@@ -248,6 +249,7 @@
desc->bInterfaceSubClass,
desc->bInterfaceProtocol,
iface->driver ? iface->driver->name : "(none)");
+ unlock_kernel();
return start;
}
diff -Nru a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
--- a/drivers/usb/core/devio.c Thu Jul 4 00:33:50 2002
+++ b/drivers/usb/core/devio.c Thu Jul 4 00:33:50 2002
@@ -722,14 +722,11 @@
if (test_bit(i, &ps->ifclaimed))
continue;
- if (intf->driver) {
- const struct usb_device_id *id;
- down(&intf->driver->serialize);
- intf->driver->disconnect(ps->dev, intf->private_data);
- id = usb_match_id(ps->dev,intf,intf->driver->id_table);
- intf->driver->probe(ps->dev, i, id);
- up(&intf->driver->serialize);
+ lock_kernel();
+ if (intf->driver && ps->dev) {
+ usb_bind_driver(intf->driver,ps->dev, i);
}
+ unlock_kernel();
}
return 0;
@@ -1092,16 +1089,17 @@
/* disconnect kernel driver from interface, leaving it unbound. */
case USBDEVFS_DISCONNECT:
+ /* this function is voodoo. without locking it is a maybe thing */
+ lock_kernel();
driver = ifp->driver;
if (driver) {
- down (&driver->serialize);
dbg ("disconnect '%s' from dev %d interface %d",
driver->name, ps->dev->devnum, ctrl.ifno);
- driver->disconnect (ps->dev, ifp->private_data);
+ usb_unbind_driver(ps->dev, ifp);
usb_driver_release_interface (driver, ifp);
- up (&driver->serialize);
} else
retval = -EINVAL;
+ unlock_kernel();
break;
/* let kernel drivers try to (re)bind to the interface */
@@ -1111,18 +1109,28 @@
/* talk directly to the interface's driver */
default:
+ lock_kernel(); /* against module unload */
driver = ifp->driver;
- if (driver == 0 || driver->ioctl == 0)
- retval = -ENOSYS;
- else {
- if (ifp->driver->owner)
+ if (driver == 0 || driver->ioctl == 0) {
+ unlock_kernel();
+ retval = -ENOSYS;
+ } else {
+ if (ifp->driver->owner) {
__MOD_INC_USE_COUNT(ifp->driver->owner);
+ unlock_kernel();
+ }
/* ifno might usefully be passed ... */
retval = driver->ioctl (ps->dev, ctrl.ioctl_code, buf);
/* size = min_t(int, size, retval)? */
- if (ifp->driver->owner)
+ if (ifp->driver->owner) {
__MOD_DEC_USE_COUNT(ifp->driver->owner);
+ } else {
+ unlock_kernel();
+ }
}
+
+ if (retval == -ENOIOCTLCMD)
+ retval = -ENOTTY;
}
/* cleanup and return */
@@ -1139,7 +1147,7 @@
static int usbdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
struct dev_state *ps = (struct dev_state *)file->private_data;
- int ret = -ENOIOCTLCMD;
+ int ret = -ENOTTY;
if (!(file->f_mode & FMODE_WRITE))
return -EPERM;
diff -Nru a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
--- a/drivers/usb/core/hub.c Thu Jul 4 00:33:50 2002
+++ b/drivers/usb/core/hub.c Thu Jul 4 00:33:50 2002
@@ -1050,8 +1050,6 @@
static int usb_hub_thread(void *__hub)
{
- lock_kernel();
-
/*
* This thread doesn't need any user-level access,
* so get rid of all our resources
@@ -1071,8 +1069,6 @@
} while (!signal_pending(current));
dbg("usb_hub_thread exiting");
-
- unlock_kernel();
complete_and_exit(&khubd_exited, 0);
}
diff -Nru a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
--- a/drivers/usb/core/usb.c Thu Jul 4 00:33:50 2002
+++ b/drivers/usb/core/usb.c Thu Jul 4 00:33:50 2002
@@ -33,6 +33,7 @@
#include <linux/devfs_fs_kernel.h>
#include <linux/spinlock.h>
#include <linux/errno.h>
+#include <linux/smp_lock.h>
#ifdef CONFIG_USB_DEBUG
#define DEBUG
@@ -254,6 +255,96 @@
up (&usb_bus_list_lock);
}
+/**
+ * usb_unbind_driver - disconnects a driver from a device
+ * @driver: Driver to be disconnected
+ * @device: usb device to be disconnected
+ * @priv: private pointer passed to device driver
+ * Context: BKL held
+ *
+ * Handles module usage count correctly
+ */
+
+void usb_unbind_driver(struct usb_device *device, struct usb_interface *intf)
+{
+ struct usb_driver *driver;
+ void *priv;
+
+
+ driver = intf->driver;
+ priv = intf->private_data;
+
+ if (!driver)
+ return;
+
+ /* as soon as we increase the module use count we drop the BKL
+ before that we must not sleep */
+ if (driver->owner) {
+ __MOD_INC_USE_COUNT(driver->owner);
+ unlock_kernel();
+ }
+ down(&driver->serialize); /* if we sleep here on an umanaged driver
+ the holder of the lock guards against
+ module unload */
+
+ driver->disconnect(device, priv);
+
+ up(&driver->serialize);
+ if (driver->owner) {
+ lock_kernel();
+ __MOD_DEC_USE_COUNT(driver->owner);
+ }
+}
+
+/**
+ * usb_bind_driver - connect a driver to a device's interface
+ */
+
+void *usb_bind_driver(struct usb_driver *driver, struct usb_device *dev, unsigned int
+ifnum)
+{
+ int i;
+ void *private;
+ const struct usb_device_id *id;
+ struct usb_interface *interface;
+
+ if (driver->owner) {
+ __MOD_INC_USE_COUNT(driver->owner);
+ unlock_kernel();
+ }
+
+ interface = &dev->actconfig->interface[ifnum];
+
+ id = driver->id_table;
+ /* new style driver? */
+ if (id) {
+ for (i = 0; i < interface->num_altsetting; i++) {
+ interface->act_altsetting = i;
+ id = usb_match_id(dev, interface, id);
+ if (id) {
+ down(&driver->serialize);
+ private = driver->probe(dev,ifnum,id);
+ up(&driver->serialize);
+ if (private != NULL)
+ break;
+ }
+ }
+
+ /* if driver not bound, leave defaults unchanged */
+ if (private == NULL)
+ interface->act_altsetting = 0;
+ } else { /* "old style" driver */
+ down(&driver->serialize);
+ private = driver->probe(dev, ifnum, NULL);
+ up(&driver->serialize);
+ }
+ if (driver->owner) {
+ lock_kernel();
+ __MOD_DEC_USE_COUNT(driver->owner);
+ }
+
+ return private;
+}
+
/*
* This function is part of a depth-first search down the device tree,
* removing any instances of a device driver.
@@ -278,13 +369,7 @@
struct usb_interface *interface = &dev->actconfig->interface[i];
if (interface->driver == driver) {
- if (driver->owner)
- __MOD_INC_USE_COUNT(driver->owner);
- down(&driver->serialize);
- driver->disconnect(dev, interface->private_data);
- up(&driver->serialize);
- if (driver->owner)
- __MOD_DEC_USE_COUNT(driver->owner);
+ usb_unbind_driver(dev, interface);
/* if driver->disconnect didn't release the interface */
if (interface->driver)
usb_driver_release_interface(driver, interface);
@@ -300,7 +385,7 @@
/**
* usb_deregister - unregister a USB driver
* @driver: USB operations of the driver to unregister
- * Context: !in_interrupt ()
+ * Context: !in_interrupt (), must be called with BKL held
*
* Unlinks the specified driver from the internal USB driver list.
*/
@@ -666,9 +751,7 @@
struct list_head *tmp;
struct usb_interface *interface;
void *private;
- const struct usb_device_id *id;
struct usb_driver *driver;
- int i;
if ((!dev) || (ifnum >= dev->actconfig->bNumInterfaces)) {
err("bad find_interface_driver params");
@@ -683,37 +766,12 @@
goto out_err;
private = NULL;
+ lock_kernel();
for (tmp = usb_driver_list.next; tmp != &usb_driver_list;) {
driver = list_entry(tmp, struct usb_driver, driver_list);
tmp = tmp->next;
- if (driver->owner)
- __MOD_INC_USE_COUNT(driver->owner);
- id = driver->id_table;
- /* new style driver? */
- if (id) {
- for (i = 0; i < interface->num_altsetting; i++) {
- interface->act_altsetting = i;
- id = usb_match_id(dev, interface, id);
- if (id) {
- down(&driver->serialize);
- private = driver->probe(dev,ifnum,id);
- up(&driver->serialize);
- if (private != NULL)
- break;
- }
- }
-
- /* if driver not bound, leave defaults unchanged */
- if (private == NULL)
- interface->act_altsetting = 0;
- } else { /* "old style" driver */
- down(&driver->serialize);
- private = driver->probe(dev, ifnum, NULL);
- up(&driver->serialize);
- }
- if (driver->owner)
- __MOD_DEC_USE_COUNT(driver->owner);
+ private = usb_bind_driver(driver, dev, ifnum);
/* probe() may have changed the config on us */
interface = dev->actconfig->interface + ifnum;
@@ -721,9 +779,11 @@
if (private) {
usb_driver_claim_interface(driver, interface, private);
up(&dev->serialize);
+ unlock_kernel();
return 0;
}
}
+ unlock_kernel();
out_err:
up(&dev->serialize);
@@ -1135,27 +1195,22 @@
info("USB disconnect on device %d", dev->devnum);
+ lock_kernel();
if (dev->actconfig) {
for (i = 0; i < dev->actconfig->bNumInterfaces; i++) {
struct usb_interface *interface =
&dev->actconfig->interface[i];
struct usb_driver *driver = interface->driver;
if (driver) {
- if (driver->owner)
- __MOD_INC_USE_COUNT(driver->owner);
- down(&driver->serialize);
- driver->disconnect(dev, interface->private_data);
- up(&driver->serialize);
+ usb_unbind_driver(dev, interface);
/* if driver->disconnect didn't release the interface
*/
if (interface->driver)
usb_driver_release_interface(driver,
interface);
- /* we don't need the driver any longer */
- if (driver->owner)
- __MOD_DEC_USE_COUNT(driver->owner);
}
/* remove our device node for this interface */
put_device(&interface->dev);
}
}
+ unlock_kernel();
/* Free up all the children.. */
for (i = 0; i < USB_MAXCHILDREN; i++) {
@@ -1484,6 +1539,8 @@
EXPORT_SYMBOL(usb_reset_device);
EXPORT_SYMBOL(usb_connect);
EXPORT_SYMBOL(usb_disconnect);
+EXPORT_SYMBOL(usb_bind_driver);
+EXPORT_SYMBOL(usb_unbind_driver);
EXPORT_SYMBOL(__usb_get_extra_descriptor);
diff -Nru a/include/linux/usb.h b/include/linux/usb.h
--- a/include/linux/usb.h Thu Jul 4 00:33:50 2002
+++ b/include/linux/usb.h Thu Jul 4 00:33:50 2002
@@ -251,7 +251,7 @@
int act_altsetting; /* active alternate setting */
int num_altsetting; /* number of alternate settings */
int max_altsetting; /* total memory allocated */
-
+
struct usb_driver *driver; /* driver */
struct device dev; /* interface specific device info */
void *private_data;
@@ -429,6 +429,10 @@
/* for when layers above USB add new non-USB drivers */
extern void usb_scan_devices(void);
+
+/* for probe/disconnect with correct module usage counting */
+void *usb_bind_driver(struct usb_driver *driver, struct usb_device *dev, unsigned int
+ifnum);
+void usb_unbind_driver(struct usb_device *device, struct usb_interface *intf);
/* mostly for devices emulating SCSI over USB */
extern int usb_reset_device(struct usb_device *dev);
===================================================================
This BitKeeper patch contains the following changesets:
1.646
## Wrapped with gzip_uu ##
begin 664 bkpatch2827
M'XL(`,Y[(ST``[59>V_;.!+_V_H4[!;H)6D<DY0HV<XFUVL2W`:;-D73`E?<
M'@P]:%N(+/KT2)H[Y[O?#"G9\BNOS05-9(N<X;SG-^Q;\CV76;^EDOA&9M9;
M\IO*"_@J4Y7*@[&:R"1.RY\'*AO!XE>E8+&#KSN&HA-<MXM,RAP^)-R"/5_\
M(AP36,K[+79@S]\4=U/9;WT]^_OWB[]]M:RC(W(R]M.1O)(%.3JR@NL/42F3
M@^M,^6,\;C9?GG%*&6-<4%NX3,QXU[:=&7-I$,''B'N]8>#VK%$F1Q\,>:@F
MR^2<NMQECNUR,1-<V,(Z)>S`=5Q">8=Z'<H)M_O,Z5/O/>5]2HE1[\,F0Y#W
M@K2I]9'\>9E/K)"TR<??+X@_\N,T+\A$164B29DFRH^(2I,[<CN6F22IE)&,
MK-^)Z-D];GU9F,]J/_/'LJA/K>,-\D<9:IUWRCSHC,O@(%SHX=`><V<V<[@]
M"YGO#H5G.TX4".&)AZRUQ#-4F5PPYM2CG`-W3F>,VPY_5";X79<)*(4S"UTN
MI>ARYH6!;]-GRC1GW)2)]KH]]U&9(GD3JU6IO!GK.EX7@FW(Y-#F+."2!LQ^
MGE0-UDVY((Q[]I/D"F6^P5[<XVSF=\%8GA!=X=.@.^P]7[(Y\Z9LCDO%9IO%
M:9B4D>QHKMK@XZ9DP@&#NX[CS2+.[:@;"3%T(6/9@X)M8;HDD:#4U05GNQ:;
M*]"?L.9J-7K,@"[SJ&`V[<W`$EU/ER>(EJ7JQ/N</J4Z\=>M3L/XYTI5ROQ0
M0B$RKKXD[>Q6_X/"\N4!&[^@3)USAQ%FM1(57@^N99;*9&?WD'3VJF/()!Z-
M"Q+4DLF(['6`2E"DPG=-NNTQH)X:`4_.\R?Y7ZU[W_8P6-'[MGBA]QW29NXK
M^G_B7X-]\V"8PU])1C*561R2::8"4"/.0Y6F,BS(6"934/&!F,G);5R,2:S"
M(B$@2P1BC^;[S6N992HCF2S*+%WP0FI#/!W?Y7'H)Z1Q<MT[03PD_7<I\P)"
M8;$#@M74S*<$JWI1J)YZ7!#/.O=L1FRKM1*R\"(>DITX+8;MXRIVW[TCTQR^
MR9M=\E_8T`(I!D&<[email protected]?K5UG\3`#4[A&.'K(7[.:,\AW"+FIP6I4HSC
MG`S+-"QBE1+X?*-4I-2!MJ<J"X(LP!$D+G#5!X??048!&;R#=%K3Y13.\.#X
M4\8H)MIY]6RUJE-1D3)MJK*0?CC5'!CE%:6S31$&]8^MG8[)OQDK@:C`EPGB
M:.(N$;45ZA]T065\J`24S&95!+2/3?#AV]H9JP+!.XC*&XB\(](^^WQY]>,*
M7]X3F4!6:!KMXN&T]EG[6-U"KB!#%*@';FEMX7R/HG/;F(0;O1]F!VEN:\K&
M^5M8XVZA=U=A6.MA%#F_//EV<?+I='=-Q6_??FA?.96O]+,%88GYV=RSL;!J
MF/=X67T&S'RTJ#:0I>`]UN,]*F9,<-XS)=5[64FEI.V\9D%5-U)C_@@<>NMG
M40[UR>#?Q^J35O`EU8E184/\P=/#\K#181H#/^ZP9V#P1QW6@-TNM1D,9)0#
MA(3L-0@(//,BCV%Y:3OB%9WF1X@NZN:'_6@(8R^92*BA$=17:%JK'1&\J@>(
MQYRJC?`2=`00@5EO*PA,?C48.)],!U@%#L;'B(1<TJ-69V_/(GNMM;H,>BW$
MQ=)?O1YF:H+?-'1#R@]FH4].S89"(>A:T,)@JG=I@CXV@(IXV\XI\.D3_.L7
MDDP5%!5@._7S'*P,-!6U.18I3A3L^%GT=>(`U$`V^/XWA!&`+.I>D/LC24)5
M0I$"XV9P8G)G86_XP[I1<;2A-^5%5H:%7JA.W3//?=)8T@(.$8;L85_>M:#B
M-BF-7?;,$PJO/FP/%80O<'BK;CRDV=9A#;?,WU8&&41^X2.=+M=OS%ZLSP87
M'2(_[(,YR17T='C>2N`09M+/L7/+A3EJ8]RB,=54+X()+>S5@82PQ?V^7I^4
MT%-359`\D7*J6_^B8S:Z3ZLU&'RZ/!V<?SX9?+\Z&YQ<?O_\;67;X::>#KVH
MA35OYUV]&0!;["?Q?R0T=M0(S@-!S/[email protected]\5/P:U3'`_8Z5`"5&:LD
M`KNJH?Z&)Y)1B46UA@GSW6MP8>X5<,<\0'=JYZ,O=K6IR^E&B;>:9ZT)&WN=
MGCUHKWOK'@YKI.IRHL[!;IVDD"5UBOXE)_/XU,%.3+#OK4+*K1&[%.N---@'
M<^7Q*`7;8]^/AVDYT;&OORW%.<0M?`<Q(8K6F`UP5QP=+B7-<DZ9C]K@KQ]V
M6N#JL"/R#H1J'_MA`=(.XQ'`OWKQGUK#?QDI(G*T`(C1H/"#!%6$.$WE+>AX
ME]05ZJ_S;(DC(RLVA)T8&-!#$I-?%_YI'\,!`S\I<ED4`+%A^?U[0P-XO;$-
MQ&MLPQ*A\9R6"JTWP6M5L.N.`=8U(7R,#/)KBM/:GGAZM:[$"X5U-].\M4GV
M:Z[;LZ$ZLF;UYHA\_GYQL6OR+X#:=%T!4OP%^U8)7P4B%IX`2E6T3Q+I`T**
MY-`O07^(P%"W[,A,(\TSCAIG/&0ZBE%0(66<('Z!HF$<^$M]ON;]D)$>,I%)
MC'TCC`[";4:Z?_6J`;%MV@*99R&:]Y1W&0ZDO.N9<6*]\RT'#LYE-M4SB'DT
MN^Z;.#7IFI73@NSL[IMN`:T=9O$$7*.'\WEO/G7='DZ(KH?W-^=N5ZQ?X\!J
MCQ*.,S-SM8@+"Z_6K;I(+6RMI^!Z4EJ?'CWN;KP"@BG&]C;)`M.-#1,C3CF5
MO9YD,)@W<=X',M'=<J#3=0%QG_WCR^77;X.K'Y\^7E[LK.@'^];7EXZN;J\V
M7'=N!NPOO6Q=1>Q;[U<7<!VF*V$N+-WNR]"Z(&WVBE`]DF'B9SY>=QA@/D?K
M&Q"ZN1U>@>@;M'[)T,4%7FZ<F\<?UKEC,^+H'K]Q7C`Y5*'6#8"VNH_Y?W;V
@P]<%R8>+_Q`,QS*\SLO)48\%;K<K`NM_O$5**'L<````
`
end
You can import this changeset into BK by piping this whole message to:
'| bk receive [path to repository]' or apply the patch as usual.
===================================================================
[EMAIL PROTECTED], 2002-07-04 00:24:10+02:00, [EMAIL PROTECTED]
- update documentation
diff -Nru a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
--- a/drivers/usb/core/usb.c Thu Jul 4 00:25:01 2002
+++ b/drivers/usb/core/usb.c Thu Jul 4 00:25:01 2002
@@ -257,9 +257,8 @@
/**
* usb_unbind_driver - disconnects a driver from a device
- * @driver: Driver to be disconnected
* @device: usb device to be disconnected
- * @priv: private pointer passed to device driver
+ * @intf: interface of the device to be disconnected
* Context: BKL held
*
* Handles module usage count correctly
@@ -298,8 +297,14 @@
/**
* usb_bind_driver - connect a driver to a device's interface
+ * @driver: device driver to be bound to a devices interface
+ * @dev: device to be bound
+ * @ifnum: index number of the interface to be used
+ *
+ * Does a save binding of a driver to a device's interface
+ * Returns a pointer to the drivers private description of the binding
*/
-
+
void *usb_bind_driver(struct usb_driver *driver, struct usb_device *dev, unsigned int ifnum)
{
int i;
@@ -311,7 +316,7 @@
__MOD_INC_USE_COUNT(driver->owner);
unlock_kernel();
}
-
+
interface = &dev->actconfig->interface[ifnum];
id = driver->id_table;
@@ -341,7 +346,7 @@
lock_kernel();
__MOD_DEC_USE_COUNT(driver->owner);
}
-
+
return private;
}
@@ -364,10 +369,10 @@
if (!dev->actconfig)
return;
-
+
for (i = 0; i < dev->actconfig->bNumInterfaces; i++) {
struct usb_interface *interface = &dev->actconfig->interface[i];
-
+
if (interface->driver == driver) {
usb_unbind_driver(dev, interface);
/* if driver->disconnect didn't release the interface */
===================================================================
This BitKeeper patch contains the following changesets:
1.647
## Wrapped with gzip_uu ##
begin 664 bkpatch2758
M'XL(`+UY(ST``[56T6[:,!1]QE]AJ0^35@&VX]@A$A-MF;:JDU8Q]:TOB7V!
M"(A1XK!.RL?/<:"T58HTMB'(]8ESKT_N.;:XP`\E%''/K+,=%.@"?S6E=1!R
MD\-@:3:PSO+J:6"*A9N<&>,FA\WM89LQ3%=]6P"4;K!FR#USGUBUQ&ZJC'MT
M$#S?L;^V$/=FG[\\?+N:(30>XYMEDB_@!U@\'J-T-=$5K`>KPB3+9KGZ>;IF
MA%!*64B"4-"P9E$0\)H*DFHWU$R.YJD8H9;0I(OZZUJ,2,(8I9S(.AP%(X:F
MF`X$EYBP(9%#PC$A,>,Q)9>$Q83@$Z7Q)<-]@J[QW[_`#5*XCZNM3BQ@;52U
M@=PF-C,YNL."R%"B^V/34/\//PB1A*!/'42O,WL'L'5J@E7#;)&;`MYR#KAK
M5<U#J7A*0.LHDCR(3G7FW;)-_P/&&*>D)DQRV4E*%TWE<EB5:?,;J",C3D94
MU,Q1XK42#"",&)4J30(2GF3TLJ9R;(Z%7W)R53CU#NUZ@VZSGM]"M"A@,6DK
M*;,YT35!"14T8K(F)!#2NS8ZS[/T7WKV2FO0^-!;M4R*H8;Y"GYMDNU`86NP
M70+>=V^=E=;9V<O^'?>+G_[KW'G?V>TS7'X;44S1>W2\KMT^Z%;V3!^>/(Y.
M^[`YFQAS>V,4C817V0E_IL[N<)+__W!J=\P;.;M?\@Q!ITP0IZ@+S(7;-N"/
MO4F6VWF,W16*>:(`F[EWFH9=YI#S7>I`5BJ3YZ`L:'0;N(8)G]NRBP\/MW"?
MDYHJU\TXV4^7QT7:9-C%KY?Q*2VI>5YM&E8:GK`;IJ[LGMB1:9M4E=#D-&E3
MXQ9)<)GL7"V7F^6+)BMY0>Q`YL,;-C.P59$WV5OC)PX[;B\`WKKH58-2%=FV
MT>S`:+\4F@;$][8-CPY3[K$/#>8MY@<LI,<^-%@2CWUX//X/4$M0J[+:C*,D
.$B(((_0;%^[&^W((````
`
end