ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/BasiliskII/src/AmigaOS/ether_amiga.cpp
Revision: 1.12
Committed: 2002-06-23T08:27:05Z (22 years, 5 months ago) by jlachmann
Branch: MAIN
CVS Tags: nigel-build-19, nigel-build-12, nigel-build-13, nigel-build-16, nigel-build-17, nigel-build-15
Changes since 1.11: +6 -2 lines
Log Message:
Adapted to OO video scheme; Audio volume/muting/sample rate now settable

File Contents

# Content
1 /*
2 * ether_amiga.cpp - Ethernet device driver, AmigaOS specific stuff
3 *
4 * Basilisk II (C) 1997-2001 Christian Bauer
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include <exec/types.h>
22 #include <exec/memory.h>
23 #include <exec/errors.h>
24 #include <dos/dos.h>
25 #include <dos/dosextens.h>
26 #include <dos/dostags.h>
27 #include <devices/sana2.h>
28 #define __USE_SYSBASE
29 #include <proto/exec.h>
30 #include <proto/dos.h>
31 #include <inline/exec.h>
32 #include <inline/dos.h>
33 #include <clib/alib_protos.h>
34
35 #include "sysdeps.h"
36 #include "cpu_emulation.h"
37 #include "main.h"
38 #include "prefs.h"
39 #include "user_strings.h"
40 #include "macos_util.h"
41 #include "ether.h"
42 #include "ether_defs.h"
43
44 #define DEBUG 0
45 #include "debug.h"
46
47 #define MONITOR 0
48
49
50 // These messages are sent to the network process
51 const uint32 MSG_CLEANUP = 'clea'; // Remove all protocols
52 const uint32 MSG_ADD_MULTI = 'addm'; // Add multicast address
53 const uint32 MSG_DEL_MULTI = 'delm'; // Add multicast address
54 const uint32 MSG_ATTACH_PH = 'atph'; // Attach protocol handler
55 const uint32 MSG_DETACH_PH = 'deph'; // Attach protocol handler
56 const uint32 MSG_WRITE = 'writ'; // Write packet
57
58 struct NetMessage : public Message {
59 NetMessage(uint32 what_, const struct MsgPort *reply_port)
60 {
61 what = what_;
62 mn_ReplyPort = (struct MsgPort *)reply_port;
63 mn_Length = sizeof(*this);
64 }
65 uint32 what;
66 uint32 pointer;
67 uint16 type;
68 int16 result;
69 };
70
71
72 // List of attached protocols
73 static const int NUM_READ_REQUESTS = 32; // Number of read requests that are sent to device in advance
74
75 struct NetProtocol : public Node {
76 struct IOSana2Req read_io[NUM_READ_REQUESTS];
77 uint8 read_buf[NUM_READ_REQUESTS][1518]; // 14 bytes header, 1500 bytes data, 4 bytes CRC
78 uint16 type;
79 uint32 handler;
80 };
81
82 static struct List prot_list;
83
84
85 // Global variables
86 static struct Process *net_proc = NULL; // Network device handler process
87 static bool proc_error; // Flag: process didn't initialize
88 static struct MsgPort *proc_port = NULL; // Message port of process, for communication with main task
89 static struct MsgPort *reply_port = NULL; // Reply port for communication with process
90 static struct MsgPort *read_port = NULL; // Reply port for read IORequests (set up and owned by network process)
91
92 static bool write_done = false; // Flag: write request done
93
94 extern struct Task *MainTask; // Pointer to main task (from main_amiga.cpp)
95
96
97 // Prototypes
98 static void net_func(void);
99
100
101 /*
102 * Send message to network process
103 */
104
105 static int16 send_to_proc(uint32 what, uint32 pointer = 0, uint16 type = 0)
106 {
107 D(bug("sending %08lx to net_proc\n", what));
108 NetMessage msg(what, reply_port);
109 msg.pointer = pointer;
110 msg.type = type;
111 PutMsg(proc_port, &msg);
112 WaitPort(reply_port);
113 GetMsg(reply_port);
114 D(bug(" sent\n"));
115 return msg.result;
116 }
117
118
119 /*
120 * Initialization
121 */
122
123 bool ether_init(void)
124 {
125 // Do nothing if no Ethernet device specified
126 if (PrefsFindString("ether") == NULL)
127 return false;
128
129 // Initialize protocol list
130 NewList(&prot_list);
131
132 // Create message port
133 reply_port = CreateMsgPort();
134 if (reply_port == NULL)
135 goto open_error;
136 D(bug("signal mask %08lx\n", 1 << reply_port->mp_SigBit));
137
138 // Start process
139 proc_error = false;
140 SetSignal(0, SIGF_SINGLE);
141 net_proc = CreateNewProcTags(
142 NP_Entry, (ULONG)net_func,
143 NP_Name, (ULONG)"Basilisk II Ethernet Task",
144 NP_Priority, 1,
145 TAG_END
146 );
147 if (net_proc == NULL)
148 goto open_error;
149
150 // Wait for signal from process
151 Wait(SIGF_SINGLE);
152
153 // Initialization error? Then bail out
154 if (proc_error)
155 goto open_error;
156
157 // Everything OK
158 return true;
159
160 open_error:
161 net_proc = NULL;
162 if (reply_port) {
163 DeleteMsgPort(reply_port);
164 reply_port = NULL;
165 }
166 return false;
167 }
168
169
170 /*
171 * Deinitialization
172 */
173
174 void ether_exit(void)
175 {
176 // Stop process
177 if (net_proc) {
178 SetSignal(0, SIGF_SINGLE);
179 Signal(&net_proc->pr_Task, SIGBREAKF_CTRL_C);
180 Wait(SIGF_SINGLE);
181 }
182
183 // Delete reply port
184 if (reply_port) {
185 DeleteMsgPort(reply_port);
186 reply_port = NULL;
187 }
188 }
189
190
191 /*
192 * Reset
193 */
194
195 void ether_reset(void)
196 {
197 // Remove all protocols
198 if (net_proc)
199 send_to_proc(MSG_CLEANUP);
200 }
201
202
203 /*
204 * Add multicast address
205 */
206
207 int16 ether_add_multicast(uint32 pb)
208 {
209 return send_to_proc(MSG_ADD_MULTI, pb);
210 }
211
212
213 /*
214 * Delete multicast address
215 */
216
217 int16 ether_del_multicast(uint32 pb)
218 {
219 return send_to_proc(MSG_DEL_MULTI, pb);
220 }
221
222
223 /*
224 * Attach protocol handler
225 */
226
227 int16 ether_attach_ph(uint16 type, uint32 handler)
228 {
229 return send_to_proc(MSG_ATTACH_PH, handler, type);
230 }
231
232
233 /*
234 * Detach protocol handler
235 */
236
237 int16 ether_detach_ph(uint16 type)
238 {
239 return send_to_proc(MSG_DETACH_PH, type);
240 }
241
242
243 /*
244 * Transmit raw ethernet packet
245 */
246
247 int16 ether_write(uint32 wds)
248 {
249 send_to_proc(MSG_WRITE, wds);
250 return 1; // Command in progress
251 }
252
253
254 /*
255 * Remove protocol from protocol list
256 */
257
258 static void remove_protocol(NetProtocol *p)
259 {
260 // Remove from list
261 Forbid();
262 Remove(p);
263 Permit();
264
265 // Cancel read requests
266 for (int i=0; i<NUM_READ_REQUESTS; i++) {
267 AbortIO((struct IORequest *)(p->read_io + i));
268 WaitIO((struct IORequest *)(p->read_io + i));
269 }
270
271 // Free protocol struct
272 FreeMem(p, sizeof(NetProtocol));
273 }
274
275
276 /*
277 * Remove all protocols
278 */
279
280 static void remove_all_protocols(void)
281 {
282 NetProtocol *n = (NetProtocol *)prot_list.lh_Head, *next;
283 while ((next = (NetProtocol *)n->ln_Succ) != NULL) {
284 remove_protocol(n);
285 n = next;
286 }
287 }
288
289
290 /*
291 * Copy received network packet to Mac side
292 */
293
294 static __saveds __regargs LONG copy_to_buff(uint8 *to /*a0*/, uint8 *from /*a1*/, uint32 packet_len /*d0*/)
295 {
296 D(bug("CopyToBuff to %08lx, from %08lx, size %08lx\n", to, from, packet_len));
297
298 // It would be more efficient (and take up less memory) if we
299 // could invoke the packet handler from here. But we don't know
300 // in what context we run, so calling Execute68k() would not be
301 // a good idea, and even worse, we might run inside a hardware
302 // interrupt, so we can't even trigger a Basilisk interrupt from
303 // here and wait for its completion.
304 CopyMem(from, to, packet_len);
305 #if MONITOR
306 bug("Receiving Ethernet packet:\n");
307 for (int i=0; i<packet_len; i++) {
308 bug("%02lx ", from[i]);
309 }
310 bug("\n");
311 #endif
312 return 1;
313 }
314
315
316 /*
317 * Copy data from Mac WDS to outgoing network packet
318 */
319
320 static __saveds __regargs LONG copy_from_buff(uint8 *to /*a0*/, char *wds /*a1*/, uint32 packet_len /*d0*/)
321 {
322 D(bug("CopyFromBuff to %08lx, wds %08lx, size %08lx\n", to, wds, packet_len));
323 #if MONITOR
324 bug("Sending Ethernet packet:\n");
325 #endif
326 for (;;) {
327 int len = ReadMacInt16((uint32)wds);
328 if (len == 0)
329 break;
330 #if MONITOR
331 uint8 *adr = Mac2HostAddr(ReadMacInt32((uint32)wds + 2));
332 for (int i=0; i<len; i++) {
333 bug("%02lx ", adr[i]);
334 }
335 #endif
336 CopyMem(Mac2HostAddr(ReadMacInt32((uint32)wds + 2)), to, len);
337 to += len;
338 wds += 6;
339 }
340 #if MONITOR
341 bug("\n");
342 #endif
343 return 1;
344 }
345
346
347 /*
348 * Process for communication with the Ethernet device
349 */
350
351 static __saveds void net_func(void)
352 {
353 const char *str;
354 BYTE od_error;
355 struct MsgPort *write_port = NULL, *control_port = NULL;
356 struct IOSana2Req *write_io = NULL, *control_io = NULL;
357 bool opened = false;
358 ULONG read_mask = 0, write_mask = 0, proc_port_mask = 0;
359 struct Sana2DeviceQuery query_data = {sizeof(Sana2DeviceQuery)};
360 ULONG buffer_tags[] = {
361 S2_CopyToBuff, (uint32)copy_to_buff,
362 S2_CopyFromBuff, (uint32)copy_from_buff,
363 TAG_END
364 };
365
366 // Default: error occured
367 proc_error = true;
368
369 // Create message port for communication with main task
370 proc_port = CreateMsgPort();
371 if (proc_port == NULL)
372 goto quit;
373 proc_port_mask = 1 << proc_port->mp_SigBit;
374
375 // Create message ports for device I/O
376 read_port = CreateMsgPort();
377 if (read_port == NULL)
378 goto quit;
379 read_mask = 1 << read_port->mp_SigBit;
380 write_port = CreateMsgPort();
381 if (write_port == NULL)
382 goto quit;
383 write_mask = 1 << write_port->mp_SigBit;
384 control_port = CreateMsgPort();
385 if (control_port == NULL)
386 goto quit;
387
388 // Create control IORequest
389 control_io = (struct IOSana2Req *)CreateIORequest(control_port, sizeof(struct IOSana2Req));
390 if (control_io == NULL)
391 goto quit;
392 control_io->ios2_Req.io_Message.mn_Node.ln_Type = 0; // Avoid CheckIO() bug
393
394 // Parse device name
395 char dev_name[256];
396 ULONG dev_unit;
397
398 str = PrefsFindString("ether");
399 if (str) {
400 const char *FirstSlash = strchr(str, '/');
401 const char *LastSlash = strrchr(str, '/');
402
403 if (FirstSlash && FirstSlash && FirstSlash != LastSlash) {
404
405 // Device name contains path, i.e. "Networks/xyzzy.device"
406 const char *lp = str;
407 char *dp = dev_name;
408
409 while (lp != LastSlash)
410 *dp++ = *lp++;
411 *dp = '\0';
412
413 if (strlen(dev_name) < 1)
414 goto quit;
415
416 if (sscanf(LastSlash, "/%ld", &dev_unit) != 1)
417 goto quit;
418 } else {
419 if (sscanf(str, "%[^/]/%ld", dev_name, &dev_unit) != 2)
420 goto quit;
421 }
422 } else
423 goto quit;
424
425 // Open device
426 control_io->ios2_BufferManagement = buffer_tags;
427 od_error = OpenDevice((UBYTE *) dev_name, dev_unit, (struct IORequest *)control_io, 0);
428 if (od_error != 0 || control_io->ios2_Req.io_Device == 0) {
429 printf("WARNING: OpenDevice(<%s>, unit=%d) returned error %d)\n", (UBYTE *)dev_name, dev_unit, od_error);
430 goto quit;
431 }
432 opened = true;
433
434 // Is it Ethernet?
435 control_io->ios2_Req.io_Command = S2_DEVICEQUERY;
436 control_io->ios2_StatData = (void *)&query_data;
437 DoIO((struct IORequest *)control_io);
438 if (control_io->ios2_Req.io_Error)
439 goto quit;
440 if (query_data.HardwareType != S2WireType_Ethernet) {
441 WarningAlert(GetString(STR_NOT_ETHERNET_WARN));
442 goto quit;
443 }
444
445 // Yes, create IORequest for writing
446 write_io = (struct IOSana2Req *)CreateIORequest(write_port, sizeof(struct IOSana2Req));
447 if (write_io == NULL)
448 goto quit;
449 memcpy(write_io, control_io, sizeof(struct IOSana2Req));
450 write_io->ios2_Req.io_Message.mn_Node.ln_Type = 0; // Avoid CheckIO() bug
451 write_io->ios2_Req.io_Message.mn_ReplyPort = write_port;
452
453 // Configure Ethernet
454 control_io->ios2_Req.io_Command = S2_GETSTATIONADDRESS;
455 DoIO((struct IORequest *)control_io);
456 memcpy(ether_addr, control_io->ios2_DstAddr, 6);
457 memcpy(control_io->ios2_SrcAddr, control_io->ios2_DstAddr, 6);
458 control_io->ios2_Req.io_Command = S2_CONFIGINTERFACE;
459 DoIO((struct IORequest *)control_io);
460 D(bug("Ethernet address %08lx %08lx\n", *(uint32 *)ether_addr, *(uint16 *)(ether_addr + 4)));
461
462 // Initialization went well, inform main task
463 proc_error = false;
464 Signal(MainTask, SIGF_SINGLE);
465
466 // Main loop
467 for (;;) {
468
469 // Wait for I/O and messages (CTRL_C is used for quitting the task)
470 ULONG sig = Wait(proc_port_mask | read_mask | write_mask | SIGBREAKF_CTRL_C);
471
472 // Main task wants to quit us
473 if (sig & SIGBREAKF_CTRL_C)
474 break;
475
476 // Main task sent a command to us
477 if (sig & proc_port_mask) {
478 struct NetMessage *msg;
479 while (msg = (NetMessage *)GetMsg(proc_port)) {
480 D(bug("net_proc received %08lx\n", msg->what));
481 switch (msg->what) {
482 case MSG_CLEANUP:
483 remove_all_protocols();
484 break;
485
486 case MSG_ADD_MULTI:
487 control_io->ios2_Req.io_Command = S2_ADDMULTICASTADDRESS;
488 Mac2Host_memcpy(control_io->ios2_SrcAddr, msg->pointer + eMultiAddr, 6);
489 DoIO((struct IORequest *)control_io);
490 if (control_io->ios2_Req.io_Error == S2ERR_NOT_SUPPORTED) {
491 WarningAlert(GetString(STR_NO_MULTICAST_WARN));
492 msg->result = noErr;
493 } else if (control_io->ios2_Req.io_Error)
494 msg->result = eMultiErr;
495 else
496 msg->result = noErr;
497 break;
498
499 case MSG_DEL_MULTI:
500 control_io->ios2_Req.io_Command = S2_DELMULTICASTADDRESS;
501 Mac2Host_memcpy(control_io->ios2_SrcAddr, msg->pointer + eMultiAddr, 6);
502 DoIO((struct IORequest *)control_io);
503 if (control_io->ios2_Req.io_Error)
504 msg->result = eMultiErr;
505 else
506 msg->result = noErr;
507 break;
508
509 case MSG_ATTACH_PH: {
510 uint16 type = msg->type;
511 uint32 handler = msg->pointer;
512
513 // Protocol of that type already installed?
514 NetProtocol *p = (NetProtocol *)prot_list.lh_Head, *next;
515 while ((next = (NetProtocol *)p->ln_Succ) != NULL) {
516 if (p->type == type) {
517 msg->result = lapProtErr;
518 goto reply;
519 }
520 p = next;
521 }
522
523 // Allocate NetProtocol, set type and handler
524 p = (NetProtocol *)AllocMem(sizeof(NetProtocol), MEMF_PUBLIC);
525 if (p == NULL) {
526 msg->result = lapProtErr;
527 goto reply;
528 }
529 p->type = type;
530 p->handler = handler;
531
532 // Set up and submit read requests
533 for (int i=0; i<NUM_READ_REQUESTS; i++) {
534 memcpy(p->read_io + i, control_io, sizeof(struct IOSana2Req));
535 p->read_io[i].ios2_Req.io_Message.mn_Node.ln_Name = (char *)p; // Hide pointer to NetProtocol in node name
536 p->read_io[i].ios2_Req.io_Message.mn_Node.ln_Type = 0; // Avoid CheckIO() bug
537 p->read_io[i].ios2_Req.io_Message.mn_ReplyPort = read_port;
538 p->read_io[i].ios2_Req.io_Command = CMD_READ;
539 p->read_io[i].ios2_PacketType = type;
540 p->read_io[i].ios2_Data = p->read_buf[i];
541 p->read_io[i].ios2_Req.io_Flags = SANA2IOF_RAW;
542 BeginIO((struct IORequest *)(p->read_io + i));
543 }
544
545 // Add protocol to list
546 AddTail(&prot_list, p);
547
548 // Everything OK
549 msg->result = noErr;
550 break;
551 }
552
553 case MSG_DETACH_PH: {
554 uint16 type = msg->type;
555 msg->result = lapProtErr;
556 NetProtocol *p = (NetProtocol *)prot_list.lh_Head, *next;
557 while ((next = (NetProtocol *)p->ln_Succ) != NULL) {
558 if (p->type == type) {
559 remove_protocol(p);
560 msg->result = noErr;
561 break;
562 }
563 p = next;
564 }
565 break;
566 }
567
568 case MSG_WRITE: {
569 // Get pointer to Write Data Structure
570 uint32 wds = msg->pointer;
571 write_io->ios2_Data = (void *)wds;
572
573 // Calculate total packet length
574 long len = 0;
575 uint32 tmp = wds;
576 for (;;) {
577 int16 w = ReadMacInt16(tmp);
578 if (w == 0)
579 break;
580 len += w;
581 tmp += 6;
582 }
583 write_io->ios2_DataLength = len;
584
585 // Get destination address
586 uint32 hdr = ReadMacInt32(wds + 2);
587 Mac2Host_memcpy(write_io->ios2_DstAddr, hdr, 6);
588
589 // Get packet type
590 uint32 type = ReadMacInt16(hdr + 12);
591 if (type <= 1500)
592 type = 0; // 802.3 packet
593 write_io->ios2_PacketType = type;
594
595 // Multicast/broadcard packet?
596 if (write_io->ios2_DstAddr[0] & 1) {
597 if (*(uint32 *)(write_io->ios2_DstAddr) == 0xffffffff && *(uint16 *)(write_io->ios2_DstAddr + 4) == 0xffff)
598 write_io->ios2_Req.io_Command = S2_BROADCAST;
599 else
600 write_io->ios2_Req.io_Command = S2_MULTICAST;
601 } else
602 write_io->ios2_Req.io_Command = CMD_WRITE;
603
604 // Send packet
605 write_done = false;
606 write_io->ios2_Req.io_Flags = SANA2IOF_RAW;
607 BeginIO((IORequest *)write_io);
608 break;
609 }
610 }
611 reply: D(bug(" net_proc replying\n"));
612 ReplyMsg(msg);
613 }
614 }
615
616 // Packet received
617 if (sig & read_mask) {
618 D(bug(" packet received, triggering Ethernet interrupt\n"));
619 SetInterruptFlag(INTFLAG_ETHER);
620 TriggerInterrupt();
621 }
622
623 // Packet write completed
624 if (sig & write_mask) {
625 GetMsg(write_port);
626 WriteMacInt32(ether_data + ed_Result, write_io->ios2_Req.io_Error ? excessCollsns : 0);
627 write_done = true;
628 D(bug(" packet write done, triggering Ethernet interrupt\n"));
629 SetInterruptFlag(INTFLAG_ETHER);
630 TriggerInterrupt();
631 }
632 }
633 quit:
634
635 // Close everything
636 remove_all_protocols();
637 if (opened) {
638 if (CheckIO((struct IORequest *)write_io) == 0) {
639 AbortIO((struct IORequest *)write_io);
640 WaitIO((struct IORequest *)write_io);
641 }
642 CloseDevice((struct IORequest *)control_io);
643 }
644 if (write_io)
645 DeleteIORequest(write_io);
646 if (control_io)
647 DeleteIORequest(control_io);
648 if (control_port)
649 DeleteMsgPort(control_port);
650 if (write_port)
651 DeleteMsgPort(write_port);
652 if (read_port)
653 DeleteMsgPort(read_port);
654
655 // Send signal to main task to confirm termination
656 Forbid();
657 Signal(MainTask, SIGF_SINGLE);
658 }
659
660
661 /*
662 * Ethernet interrupt - activate deferred tasks to call IODone or protocol handlers
663 */
664
665 void EtherInterrupt(void)
666 {
667 D(bug("EtherIRQ\n"));
668
669 // Packet write done, enqueue DT to call IODone
670 if (write_done) {
671 EnqueueMac(ether_data + ed_DeferredTask, 0xd92);
672 write_done = false;
673 }
674
675 // Call protocol handler for received packets
676 IOSana2Req *io;
677 while (io = (struct IOSana2Req *)GetMsg(read_port)) {
678
679 // Get pointer to NetProtocol (hidden in node name)
680 NetProtocol *p = (NetProtocol *)io->ios2_Req.io_Message.mn_Node.ln_Name;
681
682 // No default handler
683 if (p->handler == 0)
684 continue;
685
686 // Copy header to RHA
687 Host2Mac_memcpy(ether_data + ed_RHA, io->ios2_Data, 14);
688 D(bug(" header %08lx%04lx %08lx%04lx %04lx\n", ReadMacInt32(ether_data + ed_RHA), ReadMacInt16(ether_data + ed_RHA + 4), ReadMacInt32(ether_data + ed_RHA + 6), ReadMacInt16(ether_data + ed_RHA + 10), ReadMacInt16(ether_data + ed_RHA + 12)));
689
690 // Call protocol handler
691 M68kRegisters r;
692 r.d[0] = *(uint16 *)((uint32)io->ios2_Data + 12); // Packet type
693 r.d[1] = io->ios2_DataLength - 18; // Remaining packet length (without header, for ReadPacket) (-18 because the CRC is also included)
694 r.a[0] = (uint32)io->ios2_Data + 14; // Pointer to packet (host address, for ReadPacket)
695 r.a[3] = ether_data + ed_RHA + 14; // Pointer behind header in RHA
696 r.a[4] = ether_data + ed_ReadPacket; // Pointer to ReadPacket/ReadRest routines
697 D(bug(" calling protocol handler %08lx, type %08lx, length %08lx, data %08lx, rha %08lx, read_packet %08lx\n", p->handler, r.d[0], r.d[1], r.a[0], r.a[3], r.a[4]));
698 Execute68k(p->handler, &r);
699
700 // Resend IORequest
701 io->ios2_Req.io_Flags = SANA2IOF_RAW;
702 BeginIO((struct IORequest *)io);
703 }
704 D(bug(" EtherIRQ done\n"));
705 }