[USBHUB_NEW]
[reactos.git] / drivers / usb / usbhub_new / pdo.c
1 /*
2 * PROJECT: ReactOS Universal Serial Bus Hub Driver
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: drivers/usb/usbhub/fdo.c
5 * PURPOSE: Handle PDO
6 * PROGRAMMERS:
7 * Hervé Poussineau (hpoussin@reactos.org)
8 * Michael Martin (michael.martin@reactos.org)
9 * Johannes Anderwald (johannes.anderwald@reactos.org)
10 */
11
12 #include "usbhub.h"
13
14 #define IO_METHOD_FROM_CTL_CODE(ctlCode) (ctlCode&0x00000003)
15
16 NTSTATUS
17 NTAPI
18 UrbCompletion(
19 PDEVICE_OBJECT DeviceObject,
20 PIRP Irp,
21 PVOID Context)
22 {
23 PIRP OriginalIrp;
24 DPRINT("Entered Urb Completion\n");
25
26 //
27 // Get the original Irp
28 //
29 OriginalIrp = (PIRP)Context;
30
31 //
32 // Update it to match what was returned for the IRP that was passed to RootHub
33 //
34 OriginalIrp->IoStatus.Status = Irp->IoStatus.Status;
35 OriginalIrp->IoStatus.Information = Irp->IoStatus.Information;
36 DPRINT("Status %x, Information %x\n", Irp->IoStatus.Status, Irp->IoStatus.Information);
37
38 //
39 // Complete the original Irp
40 //
41 IoCompleteRequest(OriginalIrp, IO_NO_INCREMENT);
42
43 //
44 // Return this status so the IO Manager doesnt mess with the Irp
45 //
46 return STATUS_MORE_PROCESSING_REQUIRED;
47 }
48
49 NTSTATUS
50 FowardUrbToRootHub(
51 PDEVICE_OBJECT RootHubDeviceObject,
52 IN ULONG IoControlCode,
53 PIRP Irp,
54 OUT PVOID OutParameter1,
55 OUT PVOID OutParameter2)
56 {
57 NTSTATUS Status;
58 PIRP ForwardIrp;
59 IO_STATUS_BLOCK IoStatus;
60 PIO_STACK_LOCATION ForwardStack, CurrentStack;
61 PURB Urb;
62
63 //
64 // Get the current stack location for the Irp
65 //
66 CurrentStack = IoGetCurrentIrpStackLocation(Irp);
67 ASSERT(CurrentStack);
68
69 //
70 // Pull the Urb from that stack, it will be reused in the Irp sent to RootHub
71 //
72 Urb = (PURB)CurrentStack->Parameters.Others.Argument1;
73 ASSERT(Urb);
74
75 //
76 // Create the Irp to forward to RootHub
77 //
78 ForwardIrp = IoBuildAsynchronousFsdRequest(IRP_MJ_SHUTDOWN,
79 RootHubDeviceObject,
80 NULL,
81 0,
82 0,
83 &IoStatus);
84 if (!ForwardIrp)
85 {
86 DPRINT1("Failed to allocate IRP\n");
87 return STATUS_INSUFFICIENT_RESOURCES;
88 }
89
90 //
91 // Get the new Irps next stack
92 //
93 ForwardStack = IoGetNextIrpStackLocation(ForwardIrp);
94
95 //
96 // Copy the stack for the current irp into the next stack of new irp
97 //
98 RtlCopyMemory(ForwardStack, CurrentStack, sizeof(IO_STACK_LOCATION));
99
100 IoStatus.Status = STATUS_NOT_SUPPORTED;
101 IoStatus.Information = 0;
102
103 //
104 // Mark the Irp from upper driver as pending
105 //
106 IoMarkIrpPending(Irp);
107
108 //
109 // Now set the completion routine for the new Irp.
110 //
111 IoSetCompletionRoutine(ForwardIrp,
112 UrbCompletion,
113 Irp,
114 TRUE,
115 TRUE,
116 TRUE);
117
118 Status = IoCallDriver(RootHubDeviceObject, ForwardIrp);
119
120 //
121 // Always return pending as the completion routine will take care of it
122 //
123 return STATUS_PENDING;
124 }
125
126 NTSTATUS
127 USBHUB_PdoHandleInternalDeviceControl(
128 IN PDEVICE_OBJECT DeviceObject,
129 IN PIRP Irp)
130 {
131 NTSTATUS Status;
132 PIO_STACK_LOCATION Stack;
133 ULONG_PTR Information = 0;
134 PHUB_DEVICE_EXTENSION HubDeviceExtension;
135 PHUB_CHILDDEVICE_EXTENSION ChildDeviceExtension;
136 PDEVICE_OBJECT RootHubDeviceObject;
137 PURB Urb;
138
139 //DPRINT1("UsbhubInternalDeviceControlPdo(%x) called\n", DeviceObject);
140
141 //
142 // get current stack location
143 //
144 Stack = IoGetCurrentIrpStackLocation(Irp);
145 ASSERT(Stack);
146
147 //
148 // Set default status
149 //
150 Status = Irp->IoStatus.Status;
151
152 ChildDeviceExtension = (PHUB_CHILDDEVICE_EXTENSION)DeviceObject->DeviceExtension;
153 ASSERT(ChildDeviceExtension->Common.IsFDO == FALSE);
154 HubDeviceExtension = (PHUB_DEVICE_EXTENSION)ChildDeviceExtension->ParentDeviceObject->DeviceExtension;
155 RootHubDeviceObject = HubDeviceExtension->RootHubPhysicalDeviceObject;
156
157 switch (Stack->Parameters.DeviceIoControl.IoControlCode)
158 {
159 case IOCTL_INTERNAL_USB_GET_PARENT_HUB_INFO:
160 {
161 PHUB_DEVICE_EXTENSION DeviceExtension;
162
163 DPRINT1("IOCTL_INTERNAL_USB_GET_PARENT_HUB_INFO\n");
164 if (Irp->AssociatedIrp.SystemBuffer == NULL
165 || Stack->Parameters.DeviceIoControl.OutputBufferLength != sizeof(PVOID))
166 {
167 Status = STATUS_INVALID_PARAMETER;
168 }
169 else
170 {
171 PVOID* pHubPointer;
172 DeviceExtension = (PHUB_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
173
174 pHubPointer = (PVOID*)Irp->AssociatedIrp.SystemBuffer;
175 // FIXME
176 *pHubPointer = NULL;
177 Information = sizeof(PVOID);
178 Status = STATUS_SUCCESS;
179 }
180 break;
181 }
182 case IOCTL_INTERNAL_USB_SUBMIT_URB:
183 {
184 //DPRINT1("IOCTL_INTERNAL_USB_SUBMIT_URB\n");
185
186 //
187 // Get the Urb
188 //
189 Urb = (PURB)Stack->Parameters.Others.Argument1;
190 ASSERT(Urb);
191
192 //
193 // Set the real device handle
194 //
195 //DPRINT("UsbdDeviceHandle %x, ChildDeviceHandle %x\n", Urb->UrbHeader.UsbdDeviceHandle, ChildDeviceExtension->UsbDeviceHandle);
196
197 Urb->UrbHeader.UsbdDeviceHandle = ChildDeviceExtension->UsbDeviceHandle;
198
199 //
200 // Submit to RootHub
201 //
202 switch (Urb->UrbHeader.Function)
203 {
204 //
205 // Debugging only
206 //
207 case URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE:
208 DPRINT1("URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE\n");
209 break;
210 case URB_FUNCTION_CLASS_DEVICE:
211 DPRINT1("URB_FUNCTION_CLASS_DEVICE\n");
212 break;
213 case URB_FUNCTION_GET_STATUS_FROM_DEVICE:
214 DPRINT1("URB_FUNCTION_GET_STATUS_FROM_DEVICE\n");
215 break;
216 case URB_FUNCTION_SELECT_CONFIGURATION:
217 DPRINT1("URB_FUNCTION_SELECT_CONFIGURATION\n");
218 break;
219 case URB_FUNCTION_SELECT_INTERFACE:
220 DPRINT1("URB_FUNCTION_SELECT_INTERFACE\n");
221 break;
222 case URB_FUNCTION_CLASS_OTHER:
223 DPRINT1("URB_FUNCTION_CLASS_OTHER\n");
224 break;
225 case URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER:
226 {
227 /*
228 DPRINT1("URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER\n");
229 DPRINT1("PipeHandle %x\n", Urb->UrbBulkOrInterruptTransfer.PipeHandle);
230 DPRINT1("TransferFlags %x\n", Urb->UrbBulkOrInterruptTransfer.TransferFlags);
231 DPRINT1("Buffer %x\n", Urb->UrbBulkOrInterruptTransfer.TransferBuffer);
232 DPRINT1("BufferMDL %x\n", Urb->UrbBulkOrInterruptTransfer.TransferBufferMDL);
233 DPRINT1("Length %x\n", Urb->UrbBulkOrInterruptTransfer.TransferBufferLength);
234 DPRINT1("UrbLink %x\n", Urb->UrbBulkOrInterruptTransfer.UrbLink);
235 DPRINT1("hca %x\n", Urb->UrbBulkOrInterruptTransfer.hca);
236 if (Urb->UrbBulkOrInterruptTransfer.TransferFlags == USBD_SHORT_TRANSFER_OK)
237 {
238 }
239 */
240 break;
241
242 }
243 case URB_FUNCTION_CLASS_INTERFACE:
244 DPRINT1("URB_FUNCTION_CLASS_INTERFACE\n");
245 break;
246 default:
247 DPRINT1("IOCTL_INTERNAL_USB_SUBMIT_URB Function %x NOT IMPLEMENTED\n", Urb->UrbHeader.Function);
248 break;
249 }
250 Urb->UrbHeader.UsbdDeviceHandle = ChildDeviceExtension->UsbDeviceHandle;
251 //DPRINT1("Stack->CompletionRoutine %x\n", Stack->CompletionRoutine);
252 //
253 // Send the request to RootHub
254 //
255 Status = FowardUrbToRootHub(RootHubDeviceObject, IOCTL_INTERNAL_USB_SUBMIT_URB, Irp, Urb, NULL);
256 return Status;
257 break;
258 }
259 //
260 // FIXME: Can these be sent to RootHub?
261 //
262 case IOCTL_INTERNAL_USB_RESET_PORT:
263 DPRINT1("IOCTL_INTERNAL_USB_RESET_PORT\n");
264 break;
265 case IOCTL_INTERNAL_USB_GET_PORT_STATUS:
266 {
267 PORT_STATUS_CHANGE PortStatus;
268 LONG PortId;
269 PUCHAR PortStatusBits;
270
271 PortStatusBits = (PUCHAR)Stack->Parameters.Others.Argument1;
272 //
273 // USBD_PORT_ENABLED (bit 0) or USBD_PORT_CONNECTED (bit 1)
274 //
275 DPRINT1("IOCTL_INTERNAL_USB_GET_PORT_STATUS\n");
276 DPRINT1("Arg1 %x\n", *PortStatusBits);
277 *PortStatusBits = 0;
278 if (Stack->Parameters.Others.Argument1)
279 {
280 for (PortId = 1; PortId <= HubDeviceExtension->UsbExtHubInfo.NumberOfPorts; PortId++)
281 {
282 Status = GetPortStatusAndChange(RootHubDeviceObject, PortId, &PortStatus);
283 if (NT_SUCCESS(Status))
284 {
285 DPRINT1("Connect %x\n", ((PortStatus.Status & USB_PORT_STATUS_CONNECT) << 1) << ((PortId - 1) * 2));
286 DPRINT1("Enable %x\n", ((PortStatus.Status & USB_PORT_STATUS_ENABLE) >> 1) << ((PortId - 1) * 2));
287 *PortStatusBits +=
288 (((PortStatus.Status & USB_PORT_STATUS_CONNECT) << 1) << ((PortId - 1) * 2)) +
289 (((PortStatus.Status & USB_PORT_STATUS_ENABLE) >> 1) << ((PortId - 1) * 2));
290
291 }
292 }
293 }
294
295 DPRINT1("Arg1 %x\n", *PortStatusBits);
296 Status = STATUS_SUCCESS;
297 break;
298 }
299 case IOCTL_INTERNAL_USB_ENABLE_PORT:
300 DPRINT1("IOCTL_INTERNAL_USB_ENABLE_PORT\n");
301 break;
302 case IOCTL_INTERNAL_USB_CYCLE_PORT:
303 DPRINT1("IOCTL_INTERNAL_USB_CYCLE_PORT\n");
304 break;
305 case IOCTL_INTERNAL_USB_GET_DEVICE_HANDLE:
306 DPRINT1("IOCTL_INTERNAL_USB_GET_DEVICE_HANDLE\n");
307 break;
308 default:
309 {
310 DPRINT1("Unknown IOCTL code 0x%lx\n", Stack->Parameters.DeviceIoControl.IoControlCode);
311 Information = Irp->IoStatus.Information;
312 Status = Irp->IoStatus.Status;
313 }
314 }
315
316 if (Status != STATUS_PENDING)
317 {
318 Irp->IoStatus.Information = Information;
319 Irp->IoStatus.Status = Status;
320 IoCompleteRequest(Irp, IO_NO_INCREMENT);
321 }
322 return Status;
323 }
324
325 NTSTATUS
326 USBHUB_PdoStartDevice(
327 IN PDEVICE_OBJECT DeviceObject,
328 IN PIRP Irp)
329 {
330 PHUB_CHILDDEVICE_EXTENSION ChildDeviceExtension;
331 //NTSTATUS Status;
332 DPRINT1("USBHUB_PdoStartDevice %x\n", DeviceObject);
333 ChildDeviceExtension = (PHUB_CHILDDEVICE_EXTENSION)DeviceObject->DeviceExtension;
334
335 //
336 // This should be a PDO
337 //
338 ASSERT(ChildDeviceExtension->Common.IsFDO == FALSE);
339
340 //
341 // FIXME: Fow now assume success
342 //
343
344 UNIMPLEMENTED
345 return STATUS_SUCCESS;
346 }
347
348 NTSTATUS
349 USBHUB_PdoQueryId(
350 IN PDEVICE_OBJECT DeviceObject,
351 IN PIRP Irp,
352 OUT ULONG_PTR* Information)
353 {
354 PHUB_CHILDDEVICE_EXTENSION ChildDeviceExtension;
355 ULONG IdType;
356 PUNICODE_STRING SourceString = NULL;
357 PWCHAR ReturnString = NULL;
358 NTSTATUS Status = STATUS_SUCCESS;
359
360 IdType = IoGetCurrentIrpStackLocation(Irp)->Parameters.QueryId.IdType;
361 ChildDeviceExtension = (PHUB_CHILDDEVICE_EXTENSION)DeviceObject->DeviceExtension;
362
363 switch (IdType)
364 {
365 case BusQueryDeviceID:
366 {
367 DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_ID / BusQueryDeviceID\n");
368 SourceString = &ChildDeviceExtension->usDeviceId;
369 break;
370 }
371 case BusQueryHardwareIDs:
372 {
373 DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_ID / BusQueryHardwareIDs\n");
374 SourceString = &ChildDeviceExtension->usHardwareIds;
375 break;
376 }
377 case BusQueryCompatibleIDs:
378 {
379 DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_ID / BusQueryCompatibleIDs\n");
380 SourceString = &ChildDeviceExtension->usCompatibleIds;
381 break;
382 }
383 case BusQueryInstanceID:
384 {
385 DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_ID / BusQueryInstanceID\n");
386 SourceString = &ChildDeviceExtension->usInstanceId;
387 break;
388 }
389 default:
390 DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_ID / unknown query id type 0x%lx\n", IdType);
391 return STATUS_NOT_SUPPORTED;
392 }
393
394 if (SourceString)
395 {
396 //
397 // allocate buffer
398 //
399 ReturnString = ExAllocatePool(PagedPool, SourceString->MaximumLength);
400 if (!ReturnString)
401 {
402 //
403 // no memory
404 //
405 return STATUS_INSUFFICIENT_RESOURCES;
406 }
407
408 //
409 // copy buffer
410 //
411 RtlCopyMemory(ReturnString, SourceString->Buffer, SourceString->MaximumLength);
412 }
413
414 *Information = (ULONG_PTR)ReturnString;
415
416 return Status;
417 }
418
419 NTSTATUS
420 USBHUB_PdoQueryDeviceText(
421 IN PDEVICE_OBJECT DeviceObject,
422 IN PIRP Irp,
423 OUT ULONG_PTR* Information)
424 {
425 PHUB_CHILDDEVICE_EXTENSION ChildDeviceExtension;
426 DEVICE_TEXT_TYPE DeviceTextType;
427 PUNICODE_STRING SourceString = NULL;
428 PWCHAR ReturnString = NULL;
429 NTSTATUS Status = STATUS_SUCCESS;
430 LCID LocaleId;
431
432 DeviceTextType = IoGetCurrentIrpStackLocation(Irp)->Parameters.QueryDeviceText.DeviceTextType;
433 LocaleId = IoGetCurrentIrpStackLocation(Irp)->Parameters.QueryDeviceText.LocaleId;
434 ChildDeviceExtension = (PHUB_CHILDDEVICE_EXTENSION)DeviceObject->DeviceExtension;
435
436 //
437 // FIXME: LocaleId
438 //
439
440 switch (DeviceTextType)
441 {
442 case DeviceTextDescription:
443 case DeviceTextLocationInformation:
444 {
445 DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_TEXT / DeviceTextDescription\n");
446
447 //
448 // does the device provide a text description
449 //
450 if (ChildDeviceExtension->usTextDescription.Buffer && ChildDeviceExtension->usTextDescription.Length)
451 {
452 //
453 // use device text
454 //
455 SourceString = &ChildDeviceExtension->usTextDescription;
456 }
457 break;
458 }
459 default:
460 {
461 DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_TEXT / unknown device text type 0x%lx\n", DeviceTextType);
462 Status = STATUS_NOT_SUPPORTED;
463 break;
464 }
465 }
466
467 if (SourceString)
468 {
469 ReturnString = ExAllocatePool(PagedPool, SourceString->Length);
470 RtlCopyMemory(ReturnString, SourceString->Buffer, SourceString->Length);
471 DPRINT1("%S\n", ReturnString);
472 *Information = (ULONG_PTR)ReturnString;
473 }
474
475 return Status;
476 }
477
478 NTSTATUS
479 USBHUB_PdoHandlePnp(
480 IN PDEVICE_OBJECT DeviceObject,
481 IN PIRP Irp)
482 {
483 NTSTATUS Status;
484 ULONG MinorFunction;
485 PIO_STACK_LOCATION Stack;
486 ULONG_PTR Information = 0;
487 PHUB_CHILDDEVICE_EXTENSION UsbChildExtension;
488
489 UsbChildExtension = (PHUB_CHILDDEVICE_EXTENSION)DeviceObject->DeviceExtension;
490 Stack = IoGetCurrentIrpStackLocation(Irp);
491 MinorFunction = Stack->MinorFunction;
492
493 switch (MinorFunction)
494 {
495 case IRP_MN_START_DEVICE:
496 {
497 DPRINT1("IRP_MJ_PNP / IRP_MN_START_DEVICE\n");
498 Status = USBHUB_PdoStartDevice(DeviceObject, Irp);
499 break;
500 }
501 case IRP_MN_QUERY_CAPABILITIES:
502 {
503 PDEVICE_CAPABILITIES DeviceCapabilities;
504 ULONG i;
505 DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_CAPABILITIES\n");
506
507 DeviceCapabilities = (PDEVICE_CAPABILITIES)Stack->Parameters.DeviceCapabilities.Capabilities;
508 // FIXME: capabilities can change with connected device
509 DeviceCapabilities->LockSupported = FALSE;
510 DeviceCapabilities->EjectSupported = FALSE;
511 DeviceCapabilities->Removable = TRUE;
512 DeviceCapabilities->DockDevice = FALSE;
513 DeviceCapabilities->UniqueID = FALSE;
514 DeviceCapabilities->SilentInstall = FALSE;
515 DeviceCapabilities->RawDeviceOK = FALSE;
516 DeviceCapabilities->SurpriseRemovalOK = FALSE;
517 DeviceCapabilities->HardwareDisabled = FALSE;
518 //DeviceCapabilities->NoDisplayInUI = FALSE;
519 DeviceCapabilities->Address = UsbChildExtension->PortNumber;
520 DeviceCapabilities->UINumber = 0;
521 DeviceCapabilities->DeviceState[0] = PowerDeviceD0;
522 for (i = 1; i < PowerSystemMaximum; i++)
523 DeviceCapabilities->DeviceState[i] = PowerDeviceD3;
524 //DeviceCapabilities->DeviceWake = PowerDeviceUndefined;
525 DeviceCapabilities->D1Latency = 0;
526 DeviceCapabilities->D2Latency = 0;
527 DeviceCapabilities->D3Latency = 0;
528 Status = STATUS_SUCCESS;
529 break;
530 }
531 case IRP_MN_QUERY_RESOURCES:
532 {
533 DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_RESOURCES\n");
534
535 Information = Irp->IoStatus.Information;
536 Status = Irp->IoStatus.Status;
537 break;
538 }
539 case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:
540 {
541 DPRINT1("IRP_MJ_PNP / IRP_MN_QUERY_RESOURCE_REQUIREMENTS\n");
542
543 Information = Irp->IoStatus.Information;
544 Status = Irp->IoStatus.Status;
545 break;
546 }
547 case IRP_MN_QUERY_DEVICE_TEXT:
548 {
549 Status = USBHUB_PdoQueryDeviceText(DeviceObject, Irp, &Information);
550 break;
551 }
552 case IRP_MN_QUERY_ID:
553 {
554 Status = USBHUB_PdoQueryId(DeviceObject, Irp, &Information);
555 break;
556 }
557 case IRP_MN_QUERY_BUS_INFORMATION:
558 {
559 PPNP_BUS_INFORMATION BusInfo;
560 BusInfo = (PPNP_BUS_INFORMATION)ExAllocatePool(PagedPool, sizeof(PNP_BUS_INFORMATION));
561 RtlCopyMemory(&BusInfo->BusTypeGuid,
562 &GUID_BUS_TYPE_USB,
563 sizeof(BusInfo->BusTypeGuid));
564 BusInfo->LegacyBusType = PNPBus;
565 // FIXME
566 BusInfo->BusNumber = 0;
567 Information = (ULONG_PTR)BusInfo;
568 Status = STATUS_SUCCESS;
569 break;
570 }
571 case IRP_MN_REMOVE_DEVICE:
572 {
573 PHUB_DEVICE_EXTENSION HubDeviceExtension = (PHUB_DEVICE_EXTENSION)UsbChildExtension->ParentDeviceObject->DeviceExtension;
574 PUSB_BUS_INTERFACE_HUB_V5 HubInterface = &HubDeviceExtension->HubInterface;
575
576 DPRINT1("IRP_MJ_PNP / IRP_MN_REMOVE_DEVICE\n");
577
578 /* Remove the device */
579 HubInterface->RemoveUsbDevice(HubDeviceExtension->UsbDInterface.BusContext, UsbChildExtension->UsbDeviceHandle, 0);
580
581 /* Complete the IRP */
582 Irp->IoStatus.Status = STATUS_SUCCESS;
583 IoCompleteRequest(Irp, IO_NO_INCREMENT);
584
585 /* Delete the device object */
586 IoDeleteDevice(DeviceObject);
587 return STATUS_SUCCESS;
588 }
589 default:
590 {
591 DPRINT1("PDO IRP_MJ_PNP / unknown minor function 0x%lx\n", MinorFunction);
592 Information = Irp->IoStatus.Information;
593 Status = Irp->IoStatus.Status;
594 }
595 }
596
597 Irp->IoStatus.Information = Information;
598 Irp->IoStatus.Status = Status;
599 IoCompleteRequest(Irp, IO_NO_INCREMENT);
600 return Status;
601 }
602