diff options
author | Dan Williams <dcbw@redhat.com> | 2006-03-16 13:46:29 -0500 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2006-03-23 16:13:58 -0500 |
commit | 9e75af30d529d54fc650586776c100d0665c0c93 (patch) | |
tree | 978006aea21e2ccfd133f9131b3b98e0b19d5325 /drivers/net/wireless/airo.c | |
parent | 15db2763202b9479f3d30ea61a283be4fc48559d (diff) |
[PATCH] wireless/airo: cache wireless scans
Observed problems when multiple processes request scans and subsequently
scan results. This causes a scan result request to hit card registers
before the scan is complete, returning an incomplete scan list and
possibly making the card very angry. Instead, cache the results of a
wireless scan and serve result requests from the cache, rather than
hitting the hardware for them.
Signed-off-by: Dan Williams <dcbw@redhat.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'drivers/net/wireless/airo.c')
-rw-r--r-- | drivers/net/wireless/airo.c | 231 |
1 files changed, 172 insertions, 59 deletions
diff --git a/drivers/net/wireless/airo.c b/drivers/net/wireless/airo.c index 6f591abacfdc..108d9fed8f07 100644 --- a/drivers/net/wireless/airo.c +++ b/drivers/net/wireless/airo.c | |||
@@ -770,6 +770,11 @@ typedef struct { | |||
770 | } BSSListRid; | 770 | } BSSListRid; |
771 | 771 | ||
772 | typedef struct { | 772 | typedef struct { |
773 | BSSListRid bss; | ||
774 | struct list_head list; | ||
775 | } BSSListElement; | ||
776 | |||
777 | typedef struct { | ||
773 | u8 rssipct; | 778 | u8 rssipct; |
774 | u8 rssidBm; | 779 | u8 rssidBm; |
775 | } tdsRssiEntry; | 780 | } tdsRssiEntry; |
@@ -1120,6 +1125,8 @@ static int decapsulate(struct airo_info *ai, MICBuffer *mic, etherHead *pPacket, | |||
1120 | static u8 airo_rssi_to_dbm (tdsRssiEntry *rssi_rid, u8 rssi); | 1125 | static u8 airo_rssi_to_dbm (tdsRssiEntry *rssi_rid, u8 rssi); |
1121 | static u8 airo_dbm_to_pct (tdsRssiEntry *rssi_rid, u8 dbm); | 1126 | static u8 airo_dbm_to_pct (tdsRssiEntry *rssi_rid, u8 dbm); |
1122 | 1127 | ||
1128 | static void airo_networks_free(struct airo_info *ai); | ||
1129 | |||
1123 | struct airo_info { | 1130 | struct airo_info { |
1124 | struct net_device_stats stats; | 1131 | struct net_device_stats stats; |
1125 | struct net_device *dev; | 1132 | struct net_device *dev; |
@@ -1151,7 +1158,7 @@ struct airo_info { | |||
1151 | #define FLAG_COMMIT 13 | 1158 | #define FLAG_COMMIT 13 |
1152 | #define FLAG_RESET 14 | 1159 | #define FLAG_RESET 14 |
1153 | #define FLAG_FLASHING 15 | 1160 | #define FLAG_FLASHING 15 |
1154 | #define JOB_MASK 0x1ff0000 | 1161 | #define JOB_MASK 0x2ff0000 |
1155 | #define JOB_DIE 16 | 1162 | #define JOB_DIE 16 |
1156 | #define JOB_XMIT 17 | 1163 | #define JOB_XMIT 17 |
1157 | #define JOB_XMIT11 18 | 1164 | #define JOB_XMIT11 18 |
@@ -1161,6 +1168,7 @@ struct airo_info { | |||
1161 | #define JOB_EVENT 22 | 1168 | #define JOB_EVENT 22 |
1162 | #define JOB_AUTOWEP 23 | 1169 | #define JOB_AUTOWEP 23 |
1163 | #define JOB_WSTATS 24 | 1170 | #define JOB_WSTATS 24 |
1171 | #define JOB_SCAN_RESULTS 25 | ||
1164 | int (*bap_read)(struct airo_info*, u16 *pu16Dst, int bytelen, | 1172 | int (*bap_read)(struct airo_info*, u16 *pu16Dst, int bytelen, |
1165 | int whichbap); | 1173 | int whichbap); |
1166 | unsigned short *flash; | 1174 | unsigned short *flash; |
@@ -1177,7 +1185,7 @@ struct airo_info { | |||
1177 | } xmit, xmit11; | 1185 | } xmit, xmit11; |
1178 | struct net_device *wifidev; | 1186 | struct net_device *wifidev; |
1179 | struct iw_statistics wstats; // wireless stats | 1187 | struct iw_statistics wstats; // wireless stats |
1180 | unsigned long scan_timestamp; /* Time started to scan */ | 1188 | unsigned long scan_timeout; /* Time scan should be read */ |
1181 | struct iw_spy_data spy_data; | 1189 | struct iw_spy_data spy_data; |
1182 | struct iw_public_data wireless_data; | 1190 | struct iw_public_data wireless_data; |
1183 | /* MIC stuff */ | 1191 | /* MIC stuff */ |
@@ -1199,6 +1207,10 @@ struct airo_info { | |||
1199 | APListRid *APList; | 1207 | APListRid *APList; |
1200 | #define PCI_SHARED_LEN 2*MPI_MAX_FIDS*PKTSIZE+RIDSIZE | 1208 | #define PCI_SHARED_LEN 2*MPI_MAX_FIDS*PKTSIZE+RIDSIZE |
1201 | char proc_name[IFNAMSIZ]; | 1209 | char proc_name[IFNAMSIZ]; |
1210 | |||
1211 | struct list_head network_list; | ||
1212 | struct list_head network_free_list; | ||
1213 | BSSListElement *networks; | ||
1202 | }; | 1214 | }; |
1203 | 1215 | ||
1204 | static inline int bap_read(struct airo_info *ai, u16 *pu16Dst, int bytelen, | 1216 | static inline int bap_read(struct airo_info *ai, u16 *pu16Dst, int bytelen, |
@@ -2381,6 +2393,8 @@ void stop_airo_card( struct net_device *dev, int freeres ) | |||
2381 | dev_kfree_skb(skb); | 2393 | dev_kfree_skb(skb); |
2382 | } | 2394 | } |
2383 | 2395 | ||
2396 | airo_networks_free (ai); | ||
2397 | |||
2384 | kfree(ai->flash); | 2398 | kfree(ai->flash); |
2385 | kfree(ai->rssi); | 2399 | kfree(ai->rssi); |
2386 | kfree(ai->APList); | 2400 | kfree(ai->APList); |
@@ -2687,6 +2701,42 @@ static int reset_card( struct net_device *dev , int lock) { | |||
2687 | return 0; | 2701 | return 0; |
2688 | } | 2702 | } |
2689 | 2703 | ||
2704 | #define MAX_NETWORK_COUNT 64 | ||
2705 | static int airo_networks_allocate(struct airo_info *ai) | ||
2706 | { | ||
2707 | if (ai->networks) | ||
2708 | return 0; | ||
2709 | |||
2710 | ai->networks = | ||
2711 | kzalloc(MAX_NETWORK_COUNT * sizeof(BSSListElement), | ||
2712 | GFP_KERNEL); | ||
2713 | if (!ai->networks) { | ||
2714 | airo_print_warn(ai->dev->name, "Out of memory allocating beacons"); | ||
2715 | return -ENOMEM; | ||
2716 | } | ||
2717 | |||
2718 | return 0; | ||
2719 | } | ||
2720 | |||
2721 | static void airo_networks_free(struct airo_info *ai) | ||
2722 | { | ||
2723 | if (!ai->networks) | ||
2724 | return; | ||
2725 | kfree(ai->networks); | ||
2726 | ai->networks = NULL; | ||
2727 | } | ||
2728 | |||
2729 | static void airo_networks_initialize(struct airo_info *ai) | ||
2730 | { | ||
2731 | int i; | ||
2732 | |||
2733 | INIT_LIST_HEAD(&ai->network_free_list); | ||
2734 | INIT_LIST_HEAD(&ai->network_list); | ||
2735 | for (i = 0; i < MAX_NETWORK_COUNT; i++) | ||
2736 | list_add_tail(&ai->networks[i].list, | ||
2737 | &ai->network_free_list); | ||
2738 | } | ||
2739 | |||
2690 | static struct net_device *_init_airo_card( unsigned short irq, int port, | 2740 | static struct net_device *_init_airo_card( unsigned short irq, int port, |
2691 | int is_pcmcia, struct pci_dev *pci, | 2741 | int is_pcmcia, struct pci_dev *pci, |
2692 | struct device *dmdev ) | 2742 | struct device *dmdev ) |
@@ -2728,6 +2778,10 @@ static struct net_device *_init_airo_card( unsigned short irq, int port, | |||
2728 | if (rc) | 2778 | if (rc) |
2729 | goto err_out_thr; | 2779 | goto err_out_thr; |
2730 | 2780 | ||
2781 | if (airo_networks_allocate (ai)) | ||
2782 | goto err_out_unlink; | ||
2783 | airo_networks_initialize (ai); | ||
2784 | |||
2731 | /* The Airo-specific entries in the device structure. */ | 2785 | /* The Airo-specific entries in the device structure. */ |
2732 | if (test_bit(FLAG_MPI,&ai->flags)) { | 2786 | if (test_bit(FLAG_MPI,&ai->flags)) { |
2733 | skb_queue_head_init (&ai->txq); | 2787 | skb_queue_head_init (&ai->txq); |
@@ -2749,7 +2803,6 @@ static struct net_device *_init_airo_card( unsigned short irq, int port, | |||
2749 | 2803 | ||
2750 | SET_NETDEV_DEV(dev, dmdev); | 2804 | SET_NETDEV_DEV(dev, dmdev); |
2751 | 2805 | ||
2752 | |||
2753 | reset_card (dev, 1); | 2806 | reset_card (dev, 1); |
2754 | msleep(400); | 2807 | msleep(400); |
2755 | 2808 | ||
@@ -2892,6 +2945,65 @@ static void airo_send_event(struct net_device *dev) { | |||
2892 | wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); | 2945 | wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); |
2893 | } | 2946 | } |
2894 | 2947 | ||
2948 | static void airo_process_scan_results (struct airo_info *ai) { | ||
2949 | union iwreq_data wrqu; | ||
2950 | BSSListRid BSSList; | ||
2951 | int rc; | ||
2952 | BSSListElement * loop_net; | ||
2953 | BSSListElement * tmp_net; | ||
2954 | |||
2955 | /* Blow away current list of scan results */ | ||
2956 | list_for_each_entry_safe (loop_net, tmp_net, &ai->network_list, list) { | ||
2957 | list_move_tail (&loop_net->list, &ai->network_free_list); | ||
2958 | /* Don't blow away ->list, just BSS data */ | ||
2959 | memset (loop_net, 0, sizeof (loop_net->bss)); | ||
2960 | } | ||
2961 | |||
2962 | /* Try to read the first entry of the scan result */ | ||
2963 | rc = PC4500_readrid(ai, RID_BSSLISTFIRST, &BSSList, sizeof(BSSList), 0); | ||
2964 | if((rc) || (BSSList.index == 0xffff)) { | ||
2965 | /* No scan results */ | ||
2966 | goto out; | ||
2967 | } | ||
2968 | |||
2969 | /* Read and parse all entries */ | ||
2970 | tmp_net = NULL; | ||
2971 | while((!rc) && (BSSList.index != 0xffff)) { | ||
2972 | /* Grab a network off the free list */ | ||
2973 | if (!list_empty(&ai->network_free_list)) { | ||
2974 | tmp_net = list_entry(ai->network_free_list.next, | ||
2975 | BSSListElement, list); | ||
2976 | list_del(ai->network_free_list.next); | ||
2977 | } | ||
2978 | |||
2979 | if (tmp_net != NULL) { | ||
2980 | memcpy(tmp_net, &BSSList, sizeof(tmp_net->bss)); | ||
2981 | list_add_tail(&tmp_net->list, &ai->network_list); | ||
2982 | tmp_net = NULL; | ||
2983 | } | ||
2984 | |||
2985 | /* Read next entry */ | ||
2986 | rc = PC4500_readrid(ai, RID_BSSLISTNEXT, | ||
2987 | &BSSList, sizeof(BSSList), 0); | ||
2988 | } | ||
2989 | |||
2990 | out: | ||
2991 | ai->scan_timeout = 0; | ||
2992 | clear_bit(JOB_SCAN_RESULTS, &ai->flags); | ||
2993 | up(&ai->sem); | ||
2994 | |||
2995 | /* Send an empty event to user space. | ||
2996 | * We don't send the received data on | ||
2997 | * the event because it would require | ||
2998 | * us to do complex transcoding, and | ||
2999 | * we want to minimise the work done in | ||
3000 | * the irq handler. Use a request to | ||
3001 | * extract the data - Jean II */ | ||
3002 | wrqu.data.length = 0; | ||
3003 | wrqu.data.flags = 0; | ||
3004 | wireless_send_event(ai->dev, SIOCGIWSCAN, &wrqu, NULL); | ||
3005 | } | ||
3006 | |||
2895 | static int airo_thread(void *data) { | 3007 | static int airo_thread(void *data) { |
2896 | struct net_device *dev = data; | 3008 | struct net_device *dev = data; |
2897 | struct airo_info *ai = dev->priv; | 3009 | struct airo_info *ai = dev->priv; |
@@ -2921,13 +3033,26 @@ static int airo_thread(void *data) { | |||
2921 | set_current_state(TASK_INTERRUPTIBLE); | 3033 | set_current_state(TASK_INTERRUPTIBLE); |
2922 | if (ai->flags & JOB_MASK) | 3034 | if (ai->flags & JOB_MASK) |
2923 | break; | 3035 | break; |
2924 | if (ai->expires) { | 3036 | if (ai->expires || ai->scan_timeout) { |
2925 | if (time_after_eq(jiffies,ai->expires)){ | 3037 | if (ai->scan_timeout && |
3038 | time_after_eq(jiffies,ai->scan_timeout)){ | ||
3039 | set_bit(JOB_SCAN_RESULTS,&ai->flags); | ||
3040 | break; | ||
3041 | } else if (ai->expires && | ||
3042 | time_after_eq(jiffies,ai->expires)){ | ||
2926 | set_bit(JOB_AUTOWEP,&ai->flags); | 3043 | set_bit(JOB_AUTOWEP,&ai->flags); |
2927 | break; | 3044 | break; |
2928 | } | 3045 | } |
2929 | if (!signal_pending(current)) { | 3046 | if (!signal_pending(current)) { |
2930 | schedule_timeout(ai->expires - jiffies); | 3047 | unsigned long wake_at; |
3048 | if (!ai->expires || !ai->scan_timeout) { | ||
3049 | wake_at = max(ai->expires, | ||
3050 | ai->scan_timeout); | ||
3051 | } else { | ||
3052 | wake_at = min(ai->expires, | ||
3053 | ai->scan_timeout); | ||
3054 | } | ||
3055 | schedule_timeout(wake_at - jiffies); | ||
2931 | continue; | 3056 | continue; |
2932 | } | 3057 | } |
2933 | } else if (!signal_pending(current)) { | 3058 | } else if (!signal_pending(current)) { |
@@ -2970,6 +3095,10 @@ static int airo_thread(void *data) { | |||
2970 | airo_send_event(dev); | 3095 | airo_send_event(dev); |
2971 | else if (test_bit(JOB_AUTOWEP, &ai->flags)) | 3096 | else if (test_bit(JOB_AUTOWEP, &ai->flags)) |
2972 | timer_func(dev); | 3097 | timer_func(dev); |
3098 | else if (test_bit(JOB_SCAN_RESULTS, &ai->flags)) | ||
3099 | airo_process_scan_results(ai); | ||
3100 | else /* Shouldn't get here, but we make sure to unlock */ | ||
3101 | up(&ai->sem); | ||
2973 | } | 3102 | } |
2974 | complete_and_exit (&ai->thr_exited, 0); | 3103 | complete_and_exit (&ai->thr_exited, 0); |
2975 | } | 3104 | } |
@@ -3064,19 +3193,15 @@ static irqreturn_t airo_interrupt ( int irq, void* dev_id, struct pt_regs *regs) | |||
3064 | * and reassociations as valid status | 3193 | * and reassociations as valid status |
3065 | * Jean II */ | 3194 | * Jean II */ |
3066 | if(newStatus == ASSOCIATED) { | 3195 | if(newStatus == ASSOCIATED) { |
3067 | if (apriv->scan_timestamp) { | 3196 | #if 0 |
3068 | /* Send an empty event to user space. | 3197 | /* FIXME: Grabbing scan results here |
3069 | * We don't send the received data on | 3198 | * seems to be too early??? Just wait for |
3070 | * the event because it would require | 3199 | * timeout instead. */ |
3071 | * us to do complex transcoding, and | 3200 | if (apriv->scan_timeout > 0) { |
3072 | * we want to minimise the work done in | 3201 | set_bit(JOB_SCAN_RESULTS, &apriv->flags); |
3073 | * the irq handler. Use a request to | 3202 | wake_up_interruptible(&apriv->thr_wait); |
3074 | * extract the data - Jean II */ | ||
3075 | wrqu.data.length = 0; | ||
3076 | wrqu.data.flags = 0; | ||
3077 | wireless_send_event(dev, SIOCGIWSCAN, &wrqu, NULL); | ||
3078 | apriv->scan_timestamp = 0; | ||
3079 | } | 3203 | } |
3204 | #endif | ||
3080 | if (down_trylock(&apriv->sem) != 0) { | 3205 | if (down_trylock(&apriv->sem) != 0) { |
3081 | set_bit(JOB_EVENT, &apriv->flags); | 3206 | set_bit(JOB_EVENT, &apriv->flags); |
3082 | wake_up_interruptible(&apriv->thr_wait); | 3207 | wake_up_interruptible(&apriv->thr_wait); |
@@ -6992,6 +7117,7 @@ static int airo_set_scan(struct net_device *dev, | |||
6992 | struct airo_info *ai = dev->priv; | 7117 | struct airo_info *ai = dev->priv; |
6993 | Cmd cmd; | 7118 | Cmd cmd; |
6994 | Resp rsp; | 7119 | Resp rsp; |
7120 | int wake = 0; | ||
6995 | 7121 | ||
6996 | /* Note : you may have realised that, as this is a SET operation, | 7122 | /* Note : you may have realised that, as this is a SET operation, |
6997 | * this is privileged and therefore a normal user can't | 7123 | * this is privileged and therefore a normal user can't |
@@ -7001,17 +7127,25 @@ static int airo_set_scan(struct net_device *dev, | |||
7001 | * Jean II */ | 7127 | * Jean II */ |
7002 | if (ai->flags & FLAG_RADIO_MASK) return -ENETDOWN; | 7128 | if (ai->flags & FLAG_RADIO_MASK) return -ENETDOWN; |
7003 | 7129 | ||
7130 | if (down_interruptible(&ai->sem)) | ||
7131 | return -ERESTARTSYS; | ||
7132 | |||
7133 | /* If there's already a scan in progress, don't | ||
7134 | * trigger another one. */ | ||
7135 | if (ai->scan_timeout > 0) | ||
7136 | goto out; | ||
7137 | |||
7004 | /* Initiate a scan command */ | 7138 | /* Initiate a scan command */ |
7005 | memset(&cmd, 0, sizeof(cmd)); | 7139 | memset(&cmd, 0, sizeof(cmd)); |
7006 | cmd.cmd=CMD_LISTBSS; | 7140 | cmd.cmd=CMD_LISTBSS; |
7007 | if (down_interruptible(&ai->sem)) | ||
7008 | return -ERESTARTSYS; | ||
7009 | issuecommand(ai, &cmd, &rsp); | 7141 | issuecommand(ai, &cmd, &rsp); |
7010 | ai->scan_timestamp = jiffies; | 7142 | ai->scan_timeout = RUN_AT(3*HZ); |
7011 | up(&ai->sem); | 7143 | wake = 1; |
7012 | |||
7013 | /* At this point, just return to the user. */ | ||
7014 | 7144 | ||
7145 | out: | ||
7146 | up(&ai->sem); | ||
7147 | if (wake) | ||
7148 | wake_up_interruptible(&ai->thr_wait); | ||
7015 | return 0; | 7149 | return 0; |
7016 | } | 7150 | } |
7017 | 7151 | ||
@@ -7131,59 +7265,38 @@ static int airo_get_scan(struct net_device *dev, | |||
7131 | char *extra) | 7265 | char *extra) |
7132 | { | 7266 | { |
7133 | struct airo_info *ai = dev->priv; | 7267 | struct airo_info *ai = dev->priv; |
7134 | BSSListRid BSSList; | 7268 | BSSListElement *net; |
7135 | int rc; | 7269 | int err = 0; |
7136 | char *current_ev = extra; | 7270 | char *current_ev = extra; |
7137 | 7271 | ||
7138 | /* When we are associated again, the scan has surely finished. | 7272 | /* If a scan is in-progress, return -EAGAIN */ |
7139 | * Just in case, let's make sure enough time has elapsed since | 7273 | if (ai->scan_timeout > 0) |
7140 | * we started the scan. - Javier */ | ||
7141 | if(ai->scan_timestamp && time_before(jiffies,ai->scan_timestamp+3*HZ)) { | ||
7142 | /* Important note : we don't want to block the caller | ||
7143 | * until results are ready for various reasons. | ||
7144 | * First, managing wait queues is complex and racy | ||
7145 | * (there may be multiple simultaneous callers). | ||
7146 | * Second, we grab some rtnetlink lock before comming | ||
7147 | * here (in dev_ioctl()). | ||
7148 | * Third, the caller can wait on the Wireless Event | ||
7149 | * - Jean II */ | ||
7150 | return -EAGAIN; | 7274 | return -EAGAIN; |
7151 | } | ||
7152 | ai->scan_timestamp = 0; | ||
7153 | |||
7154 | /* There's only a race with proc_BSSList_open(), but its | ||
7155 | * consequences are begnign. So I don't bother fixing it - Javier */ | ||
7156 | 7275 | ||
7157 | /* Try to read the first entry of the scan result */ | 7276 | if (down_interruptible(&ai->sem)) |
7158 | rc = PC4500_readrid(ai, RID_BSSLISTFIRST, &BSSList, sizeof(BSSList), 1); | 7277 | return -EAGAIN; |
7159 | if((rc) || (BSSList.index == 0xffff)) { | ||
7160 | /* Client error, no scan results... | ||
7161 | * The caller need to restart the scan. */ | ||
7162 | return -ENODATA; | ||
7163 | } | ||
7164 | 7278 | ||
7165 | /* Read and parse all entries */ | 7279 | list_for_each_entry (net, &ai->network_list, list) { |
7166 | while((!rc) && (BSSList.index != 0xffff)) { | ||
7167 | /* Translate to WE format this entry */ | 7280 | /* Translate to WE format this entry */ |
7168 | current_ev = airo_translate_scan(dev, current_ev, | 7281 | current_ev = airo_translate_scan(dev, current_ev, |
7169 | extra + dwrq->length, | 7282 | extra + dwrq->length, |
7170 | &BSSList); | 7283 | &net->bss); |
7171 | 7284 | ||
7172 | /* Check if there is space for one more entry */ | 7285 | /* Check if there is space for one more entry */ |
7173 | if((extra + dwrq->length - current_ev) <= IW_EV_ADDR_LEN) { | 7286 | if((extra + dwrq->length - current_ev) <= IW_EV_ADDR_LEN) { |
7174 | /* Ask user space to try again with a bigger buffer */ | 7287 | /* Ask user space to try again with a bigger buffer */ |
7175 | return -E2BIG; | 7288 | err = -E2BIG; |
7289 | goto out; | ||
7176 | } | 7290 | } |
7177 | |||
7178 | /* Read next entry */ | ||
7179 | rc = PC4500_readrid(ai, RID_BSSLISTNEXT, | ||
7180 | &BSSList, sizeof(BSSList), 1); | ||
7181 | } | 7291 | } |
7292 | |||
7182 | /* Length of data */ | 7293 | /* Length of data */ |
7183 | dwrq->length = (current_ev - extra); | 7294 | dwrq->length = (current_ev - extra); |
7184 | dwrq->flags = 0; /* todo */ | 7295 | dwrq->flags = 0; /* todo */ |
7185 | 7296 | ||
7186 | return 0; | 7297 | out: |
7298 | up(&ai->sem); | ||
7299 | return err; | ||
7187 | } | 7300 | } |
7188 | 7301 | ||
7189 | /*------------------------------------------------------------------*/ | 7302 | /*------------------------------------------------------------------*/ |