diff options
author | Sarah Sharp <sarah.a.sharp@linux.intel.com> | 2012-02-02 17:47:14 -0500 |
---|---|---|
committer | Sarah Sharp <sarah.a.sharp@linux.intel.com> | 2012-02-02 17:47:14 -0500 |
commit | b603669842b3b2c66a1fc2e926f35a2143be8b3b (patch) | |
tree | 8b924819815197c04c2cc2ca6b514391ce170633 /drivers/usb/storage/uas.c | |
parent | fec67b45bf045582c3172101970090d640cd56d9 (diff) | |
parent | ceb3f91fd53c9fbd7b292fc2754ba4efffeeeedb (diff) |
Merge tag 'uas_for_sarah' of git://linutronix.de/users/bigeasy/linux into for-uas-next
Merge UAS bug fixes from Sebastian Andrzej Siewior, including some patches of
mine that he signed.
UAS fixes for Sarah
Diffstat (limited to 'drivers/usb/storage/uas.c')
-rw-r--r-- | drivers/usb/storage/uas.c | 157 |
1 files changed, 117 insertions, 40 deletions
diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c index a33ead5dce20..e0133c9ab0bf 100644 --- a/drivers/usb/storage/uas.c +++ b/drivers/usb/storage/uas.c | |||
@@ -98,6 +98,8 @@ struct uas_dev_info { | |||
98 | unsigned cmd_pipe, status_pipe, data_in_pipe, data_out_pipe; | 98 | unsigned cmd_pipe, status_pipe, data_in_pipe, data_out_pipe; |
99 | unsigned use_streams:1; | 99 | unsigned use_streams:1; |
100 | unsigned uas_sense_old:1; | 100 | unsigned uas_sense_old:1; |
101 | struct scsi_cmnd *cmnd; | ||
102 | struct urb *status_urb; /* used only if stream support is available */ | ||
101 | }; | 103 | }; |
102 | 104 | ||
103 | enum { | 105 | enum { |
@@ -116,6 +118,7 @@ struct uas_cmd_info { | |||
116 | unsigned int state; | 118 | unsigned int state; |
117 | unsigned int stream; | 119 | unsigned int stream; |
118 | struct urb *cmd_urb; | 120 | struct urb *cmd_urb; |
121 | /* status_urb is used only if stream support isn't available */ | ||
119 | struct urb *status_urb; | 122 | struct urb *status_urb; |
120 | struct urb *data_in_urb; | 123 | struct urb *data_in_urb; |
121 | struct urb *data_out_urb; | 124 | struct urb *data_out_urb; |
@@ -125,29 +128,38 @@ struct uas_cmd_info { | |||
125 | /* I hate forward declarations, but I actually have a loop */ | 128 | /* I hate forward declarations, but I actually have a loop */ |
126 | static int uas_submit_urbs(struct scsi_cmnd *cmnd, | 129 | static int uas_submit_urbs(struct scsi_cmnd *cmnd, |
127 | struct uas_dev_info *devinfo, gfp_t gfp); | 130 | struct uas_dev_info *devinfo, gfp_t gfp); |
131 | static void uas_do_work(struct work_struct *work); | ||
128 | 132 | ||
133 | static DECLARE_WORK(uas_work, uas_do_work); | ||
129 | static DEFINE_SPINLOCK(uas_work_lock); | 134 | static DEFINE_SPINLOCK(uas_work_lock); |
130 | static LIST_HEAD(uas_work_list); | 135 | static LIST_HEAD(uas_work_list); |
131 | 136 | ||
132 | static void uas_do_work(struct work_struct *work) | 137 | static void uas_do_work(struct work_struct *work) |
133 | { | 138 | { |
134 | struct uas_cmd_info *cmdinfo; | 139 | struct uas_cmd_info *cmdinfo; |
140 | struct uas_cmd_info *temp; | ||
135 | struct list_head list; | 141 | struct list_head list; |
142 | int err; | ||
136 | 143 | ||
137 | spin_lock_irq(&uas_work_lock); | 144 | spin_lock_irq(&uas_work_lock); |
138 | list_replace_init(&uas_work_list, &list); | 145 | list_replace_init(&uas_work_list, &list); |
139 | spin_unlock_irq(&uas_work_lock); | 146 | spin_unlock_irq(&uas_work_lock); |
140 | 147 | ||
141 | list_for_each_entry(cmdinfo, &list, list) { | 148 | list_for_each_entry_safe(cmdinfo, temp, &list, list) { |
142 | struct scsi_pointer *scp = (void *)cmdinfo; | 149 | struct scsi_pointer *scp = (void *)cmdinfo; |
143 | struct scsi_cmnd *cmnd = container_of(scp, | 150 | struct scsi_cmnd *cmnd = container_of(scp, |
144 | struct scsi_cmnd, SCp); | 151 | struct scsi_cmnd, SCp); |
145 | uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_NOIO); | 152 | err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_NOIO); |
153 | if (err) { | ||
154 | list_del(&cmdinfo->list); | ||
155 | spin_lock_irq(&uas_work_lock); | ||
156 | list_add_tail(&cmdinfo->list, &uas_work_list); | ||
157 | spin_unlock_irq(&uas_work_lock); | ||
158 | schedule_work(&uas_work); | ||
159 | } | ||
146 | } | 160 | } |
147 | } | 161 | } |
148 | 162 | ||
149 | static DECLARE_WORK(uas_work, uas_do_work); | ||
150 | |||
151 | static void uas_sense(struct urb *urb, struct scsi_cmnd *cmnd) | 163 | static void uas_sense(struct urb *urb, struct scsi_cmnd *cmnd) |
152 | { | 164 | { |
153 | struct sense_iu *sense_iu = urb->transfer_buffer; | 165 | struct sense_iu *sense_iu = urb->transfer_buffer; |
@@ -169,10 +181,7 @@ static void uas_sense(struct urb *urb, struct scsi_cmnd *cmnd) | |||
169 | } | 181 | } |
170 | 182 | ||
171 | cmnd->result = sense_iu->status; | 183 | cmnd->result = sense_iu->status; |
172 | if (sdev->current_cmnd) | ||
173 | sdev->current_cmnd = NULL; | ||
174 | cmnd->scsi_done(cmnd); | 184 | cmnd->scsi_done(cmnd); |
175 | usb_free_urb(urb); | ||
176 | } | 185 | } |
177 | 186 | ||
178 | static void uas_sense_old(struct urb *urb, struct scsi_cmnd *cmnd) | 187 | static void uas_sense_old(struct urb *urb, struct scsi_cmnd *cmnd) |
@@ -196,10 +205,7 @@ static void uas_sense_old(struct urb *urb, struct scsi_cmnd *cmnd) | |||
196 | } | 205 | } |
197 | 206 | ||
198 | cmnd->result = sense_iu->status; | 207 | cmnd->result = sense_iu->status; |
199 | if (sdev->current_cmnd) | ||
200 | sdev->current_cmnd = NULL; | ||
201 | cmnd->scsi_done(cmnd); | 208 | cmnd->scsi_done(cmnd); |
202 | usb_free_urb(urb); | ||
203 | } | 209 | } |
204 | 210 | ||
205 | static void uas_xfer_data(struct urb *urb, struct scsi_cmnd *cmnd, | 211 | static void uas_xfer_data(struct urb *urb, struct scsi_cmnd *cmnd, |
@@ -208,7 +214,7 @@ static void uas_xfer_data(struct urb *urb, struct scsi_cmnd *cmnd, | |||
208 | struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; | 214 | struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; |
209 | int err; | 215 | int err; |
210 | 216 | ||
211 | cmdinfo->state = direction | SUBMIT_STATUS_URB; | 217 | cmdinfo->state = direction; |
212 | err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); | 218 | err = uas_submit_urbs(cmnd, cmnd->device->hostdata, GFP_ATOMIC); |
213 | if (err) { | 219 | if (err) { |
214 | spin_lock(&uas_work_lock); | 220 | spin_lock(&uas_work_lock); |
@@ -221,27 +227,40 @@ static void uas_xfer_data(struct urb *urb, struct scsi_cmnd *cmnd, | |||
221 | static void uas_stat_cmplt(struct urb *urb) | 227 | static void uas_stat_cmplt(struct urb *urb) |
222 | { | 228 | { |
223 | struct iu *iu = urb->transfer_buffer; | 229 | struct iu *iu = urb->transfer_buffer; |
224 | struct scsi_device *sdev = urb->context; | 230 | struct Scsi_Host *shost = urb->context; |
225 | struct uas_dev_info *devinfo = sdev->hostdata; | 231 | struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; |
226 | struct scsi_cmnd *cmnd; | 232 | struct scsi_cmnd *cmnd; |
227 | u16 tag; | 233 | u16 tag; |
234 | int ret; | ||
228 | 235 | ||
229 | if (urb->status) { | 236 | if (urb->status) { |
230 | dev_err(&urb->dev->dev, "URB BAD STATUS %d\n", urb->status); | 237 | dev_err(&urb->dev->dev, "URB BAD STATUS %d\n", urb->status); |
231 | usb_free_urb(urb); | 238 | if (devinfo->use_streams) |
239 | usb_free_urb(urb); | ||
232 | return; | 240 | return; |
233 | } | 241 | } |
234 | 242 | ||
235 | tag = be16_to_cpup(&iu->tag) - 1; | 243 | tag = be16_to_cpup(&iu->tag) - 1; |
236 | if (sdev->current_cmnd) | 244 | if (tag == 0) |
237 | cmnd = sdev->current_cmnd; | 245 | cmnd = devinfo->cmnd; |
238 | else | 246 | else |
239 | cmnd = scsi_find_tag(sdev, tag); | 247 | cmnd = scsi_host_find_tag(shost, tag - 1); |
240 | if (!cmnd) | 248 | if (!cmnd) { |
249 | if (devinfo->use_streams) { | ||
250 | usb_free_urb(urb); | ||
251 | return; | ||
252 | } | ||
253 | ret = usb_submit_urb(urb, GFP_ATOMIC); | ||
254 | if (ret) | ||
255 | dev_err(&urb->dev->dev, "failed submit status urb\n"); | ||
241 | return; | 256 | return; |
257 | } | ||
242 | 258 | ||
243 | switch (iu->iu_id) { | 259 | switch (iu->iu_id) { |
244 | case IU_ID_STATUS: | 260 | case IU_ID_STATUS: |
261 | if (devinfo->cmnd == cmnd) | ||
262 | devinfo->cmnd = NULL; | ||
263 | |||
245 | if (urb->actual_length < 16) | 264 | if (urb->actual_length < 16) |
246 | devinfo->uas_sense_old = 1; | 265 | devinfo->uas_sense_old = 1; |
247 | if (devinfo->uas_sense_old) | 266 | if (devinfo->uas_sense_old) |
@@ -259,6 +278,15 @@ static void uas_stat_cmplt(struct urb *urb) | |||
259 | scmd_printk(KERN_ERR, cmnd, | 278 | scmd_printk(KERN_ERR, cmnd, |
260 | "Bogus IU (%d) received on status pipe\n", iu->iu_id); | 279 | "Bogus IU (%d) received on status pipe\n", iu->iu_id); |
261 | } | 280 | } |
281 | |||
282 | if (devinfo->use_streams) { | ||
283 | usb_free_urb(urb); | ||
284 | return; | ||
285 | } | ||
286 | |||
287 | ret = usb_submit_urb(urb, GFP_ATOMIC); | ||
288 | if (ret) | ||
289 | dev_err(&urb->dev->dev, "failed submit status urb\n"); | ||
262 | } | 290 | } |
263 | 291 | ||
264 | static void uas_data_cmplt(struct urb *urb) | 292 | static void uas_data_cmplt(struct urb *urb) |
@@ -289,7 +317,7 @@ static struct urb *uas_alloc_data_urb(struct uas_dev_info *devinfo, gfp_t gfp, | |||
289 | } | 317 | } |
290 | 318 | ||
291 | static struct urb *uas_alloc_sense_urb(struct uas_dev_info *devinfo, gfp_t gfp, | 319 | static struct urb *uas_alloc_sense_urb(struct uas_dev_info *devinfo, gfp_t gfp, |
292 | struct scsi_cmnd *cmnd, u16 stream_id) | 320 | struct Scsi_Host *shost, u16 stream_id) |
293 | { | 321 | { |
294 | struct usb_device *udev = devinfo->udev; | 322 | struct usb_device *udev = devinfo->udev; |
295 | struct urb *urb = usb_alloc_urb(0, gfp); | 323 | struct urb *urb = usb_alloc_urb(0, gfp); |
@@ -303,7 +331,7 @@ static struct urb *uas_alloc_sense_urb(struct uas_dev_info *devinfo, gfp_t gfp, | |||
303 | goto free; | 331 | goto free; |
304 | 332 | ||
305 | usb_fill_bulk_urb(urb, udev, devinfo->status_pipe, iu, sizeof(*iu), | 333 | usb_fill_bulk_urb(urb, udev, devinfo->status_pipe, iu, sizeof(*iu), |
306 | uas_stat_cmplt, cmnd->device); | 334 | uas_stat_cmplt, shost); |
307 | urb->stream_id = stream_id; | 335 | urb->stream_id = stream_id; |
308 | urb->transfer_flags |= URB_FREE_BUFFER; | 336 | urb->transfer_flags |= URB_FREE_BUFFER; |
309 | out: | 337 | out: |
@@ -334,7 +362,10 @@ static struct urb *uas_alloc_cmd_urb(struct uas_dev_info *devinfo, gfp_t gfp, | |||
334 | goto free; | 362 | goto free; |
335 | 363 | ||
336 | iu->iu_id = IU_ID_COMMAND; | 364 | iu->iu_id = IU_ID_COMMAND; |
337 | iu->tag = cpu_to_be16(stream_id); | 365 | if (blk_rq_tagged(cmnd->request)) |
366 | iu->tag = cpu_to_be16(cmnd->request->tag + 2); | ||
367 | else | ||
368 | iu->tag = cpu_to_be16(1); | ||
338 | iu->prio_attr = UAS_SIMPLE_TAG; | 369 | iu->prio_attr = UAS_SIMPLE_TAG; |
339 | iu->len = len; | 370 | iu->len = len; |
340 | int_to_scsilun(sdev->lun, &iu->lun); | 371 | int_to_scsilun(sdev->lun, &iu->lun); |
@@ -362,8 +393,8 @@ static int uas_submit_urbs(struct scsi_cmnd *cmnd, | |||
362 | struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; | 393 | struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp; |
363 | 394 | ||
364 | if (cmdinfo->state & ALLOC_STATUS_URB) { | 395 | if (cmdinfo->state & ALLOC_STATUS_URB) { |
365 | cmdinfo->status_urb = uas_alloc_sense_urb(devinfo, gfp, cmnd, | 396 | cmdinfo->status_urb = uas_alloc_sense_urb(devinfo, gfp, |
366 | cmdinfo->stream); | 397 | cmnd->device->host, cmdinfo->stream); |
367 | if (!cmdinfo->status_urb) | 398 | if (!cmdinfo->status_urb) |
368 | return SCSI_MLQUEUE_DEVICE_BUSY; | 399 | return SCSI_MLQUEUE_DEVICE_BUSY; |
369 | cmdinfo->state &= ~ALLOC_STATUS_URB; | 400 | cmdinfo->state &= ~ALLOC_STATUS_URB; |
@@ -444,13 +475,13 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, | |||
444 | 475 | ||
445 | BUILD_BUG_ON(sizeof(struct uas_cmd_info) > sizeof(struct scsi_pointer)); | 476 | BUILD_BUG_ON(sizeof(struct uas_cmd_info) > sizeof(struct scsi_pointer)); |
446 | 477 | ||
447 | if (!cmdinfo->status_urb && sdev->current_cmnd) | 478 | if (devinfo->cmnd) |
448 | return SCSI_MLQUEUE_DEVICE_BUSY; | 479 | return SCSI_MLQUEUE_DEVICE_BUSY; |
449 | 480 | ||
450 | if (blk_rq_tagged(cmnd->request)) { | 481 | if (blk_rq_tagged(cmnd->request)) { |
451 | cmdinfo->stream = cmnd->request->tag + 1; | 482 | cmdinfo->stream = cmnd->request->tag + 2; |
452 | } else { | 483 | } else { |
453 | sdev->current_cmnd = cmnd; | 484 | devinfo->cmnd = cmnd; |
454 | cmdinfo->stream = 1; | 485 | cmdinfo->stream = 1; |
455 | } | 486 | } |
456 | 487 | ||
@@ -472,7 +503,8 @@ static int uas_queuecommand_lck(struct scsi_cmnd *cmnd, | |||
472 | } | 503 | } |
473 | 504 | ||
474 | if (!devinfo->use_streams) { | 505 | if (!devinfo->use_streams) { |
475 | cmdinfo->state &= ~(SUBMIT_DATA_IN_URB | SUBMIT_DATA_OUT_URB); | 506 | cmdinfo->state &= ~(SUBMIT_DATA_IN_URB | SUBMIT_DATA_OUT_URB | |
507 | ALLOC_STATUS_URB | SUBMIT_STATUS_URB); | ||
476 | cmdinfo->stream = 0; | 508 | cmdinfo->stream = 0; |
477 | } | 509 | } |
478 | 510 | ||
@@ -551,7 +583,7 @@ static int uas_slave_configure(struct scsi_device *sdev) | |||
551 | { | 583 | { |
552 | struct uas_dev_info *devinfo = sdev->hostdata; | 584 | struct uas_dev_info *devinfo = sdev->hostdata; |
553 | scsi_set_tag_type(sdev, MSG_ORDERED_TAG); | 585 | scsi_set_tag_type(sdev, MSG_ORDERED_TAG); |
554 | scsi_activate_tcq(sdev, devinfo->qdepth - 1); | 586 | scsi_activate_tcq(sdev, devinfo->qdepth - 2); |
555 | return 0; | 587 | return 0; |
556 | } | 588 | } |
557 | 589 | ||
@@ -619,6 +651,7 @@ static void uas_configure_endpoints(struct uas_dev_info *devinfo) | |||
619 | unsigned i, n_endpoints = intf->cur_altsetting->desc.bNumEndpoints; | 651 | unsigned i, n_endpoints = intf->cur_altsetting->desc.bNumEndpoints; |
620 | 652 | ||
621 | devinfo->uas_sense_old = 0; | 653 | devinfo->uas_sense_old = 0; |
654 | devinfo->cmnd = NULL; | ||
622 | 655 | ||
623 | for (i = 0; i < n_endpoints; i++) { | 656 | for (i = 0; i < n_endpoints; i++) { |
624 | unsigned char *extra = endpoint[i].extra; | 657 | unsigned char *extra = endpoint[i].extra; |
@@ -670,6 +703,40 @@ static void uas_configure_endpoints(struct uas_dev_info *devinfo) | |||
670 | } | 703 | } |
671 | } | 704 | } |
672 | 705 | ||
706 | static int uas_alloc_status_urb(struct uas_dev_info *devinfo, | ||
707 | struct Scsi_Host *shost) | ||
708 | { | ||
709 | if (devinfo->use_streams) { | ||
710 | devinfo->status_urb = NULL; | ||
711 | return 0; | ||
712 | } | ||
713 | |||
714 | devinfo->status_urb = uas_alloc_sense_urb(devinfo, GFP_KERNEL, | ||
715 | shost, 0); | ||
716 | if (!devinfo->status_urb) | ||
717 | goto err_s_urb; | ||
718 | |||
719 | if (usb_submit_urb(devinfo->status_urb, GFP_KERNEL)) | ||
720 | goto err_submit_urb; | ||
721 | |||
722 | return 0; | ||
723 | err_submit_urb: | ||
724 | usb_free_urb(devinfo->status_urb); | ||
725 | err_s_urb: | ||
726 | return -ENOMEM; | ||
727 | } | ||
728 | |||
729 | static void uas_free_streams(struct uas_dev_info *devinfo) | ||
730 | { | ||
731 | struct usb_device *udev = devinfo->udev; | ||
732 | struct usb_host_endpoint *eps[3]; | ||
733 | |||
734 | eps[0] = usb_pipe_endpoint(udev, devinfo->status_pipe); | ||
735 | eps[1] = usb_pipe_endpoint(udev, devinfo->data_in_pipe); | ||
736 | eps[2] = usb_pipe_endpoint(udev, devinfo->data_out_pipe); | ||
737 | usb_free_streams(devinfo->intf, eps, 3, GFP_KERNEL); | ||
738 | } | ||
739 | |||
673 | /* | 740 | /* |
674 | * XXX: What I'd like to do here is register a SCSI host for each USB host in | 741 | * XXX: What I'd like to do here is register a SCSI host for each USB host in |
675 | * the system. Follow usb-storage's design of registering a SCSI host for | 742 | * the system. Follow usb-storage's design of registering a SCSI host for |
@@ -699,18 +766,33 @@ static int uas_probe(struct usb_interface *intf, const struct usb_device_id *id) | |||
699 | shost->max_id = 1; | 766 | shost->max_id = 1; |
700 | shost->sg_tablesize = udev->bus->sg_tablesize; | 767 | shost->sg_tablesize = udev->bus->sg_tablesize; |
701 | 768 | ||
702 | result = scsi_add_host(shost, &intf->dev); | 769 | devinfo->intf = intf; |
770 | devinfo->udev = udev; | ||
771 | uas_configure_endpoints(devinfo); | ||
772 | |||
773 | result = scsi_init_shared_tag_map(shost, devinfo->qdepth - 2); | ||
703 | if (result) | 774 | if (result) |
704 | goto free; | 775 | goto free; |
776 | |||
777 | result = scsi_add_host(shost, &intf->dev); | ||
778 | if (result) | ||
779 | goto deconfig_eps; | ||
780 | |||
705 | shost->hostdata[0] = (unsigned long)devinfo; | 781 | shost->hostdata[0] = (unsigned long)devinfo; |
706 | 782 | ||
707 | devinfo->intf = intf; | 783 | result = uas_alloc_status_urb(devinfo, shost); |
708 | devinfo->udev = udev; | 784 | if (result) |
709 | uas_configure_endpoints(devinfo); | 785 | goto err_alloc_status; |
710 | 786 | ||
711 | scsi_scan_host(shost); | 787 | scsi_scan_host(shost); |
712 | usb_set_intfdata(intf, shost); | 788 | usb_set_intfdata(intf, shost); |
713 | return result; | 789 | return result; |
790 | |||
791 | err_alloc_status: | ||
792 | scsi_remove_host(shost); | ||
793 | shost = NULL; | ||
794 | deconfig_eps: | ||
795 | uas_free_streams(devinfo); | ||
714 | free: | 796 | free: |
715 | kfree(devinfo); | 797 | kfree(devinfo); |
716 | if (shost) | 798 | if (shost) |
@@ -732,18 +814,13 @@ static int uas_post_reset(struct usb_interface *intf) | |||
732 | 814 | ||
733 | static void uas_disconnect(struct usb_interface *intf) | 815 | static void uas_disconnect(struct usb_interface *intf) |
734 | { | 816 | { |
735 | struct usb_device *udev = interface_to_usbdev(intf); | ||
736 | struct usb_host_endpoint *eps[3]; | ||
737 | struct Scsi_Host *shost = usb_get_intfdata(intf); | 817 | struct Scsi_Host *shost = usb_get_intfdata(intf); |
738 | struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; | 818 | struct uas_dev_info *devinfo = (void *)shost->hostdata[0]; |
739 | 819 | ||
740 | scsi_remove_host(shost); | 820 | scsi_remove_host(shost); |
741 | 821 | usb_kill_urb(devinfo->status_urb); | |
742 | eps[0] = usb_pipe_endpoint(udev, devinfo->status_pipe); | 822 | usb_free_urb(devinfo->status_urb); |
743 | eps[1] = usb_pipe_endpoint(udev, devinfo->data_in_pipe); | 823 | uas_free_streams(devinfo); |
744 | eps[2] = usb_pipe_endpoint(udev, devinfo->data_out_pipe); | ||
745 | usb_free_streams(intf, eps, 3, GFP_KERNEL); | ||
746 | |||
747 | kfree(devinfo); | 824 | kfree(devinfo); |
748 | } | 825 | } |
749 | 826 | ||