xref: /openbsd/sys/dev/usb/usps.c (revision 81508fe3)
1 /*	$OpenBSD: usps.c,v 1.12 2024/05/23 03:21:09 jsg Exp $   */
2 
3 /*
4  * Copyright (c) 2011 Yojiro UO <yuo@nui.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /* Driver for usb smart power strip FX-5204PS */
20 
21 #include <sys/param.h>
22 #include <sys/systm.h>
23 #include <sys/malloc.h>
24 #include <sys/device.h>
25 #include <sys/sensors.h>
26 
27 #include <dev/usb/usb.h>
28 #include <dev/usb/usbdi.h>
29 #include <dev/usb/usbdevs.h>
30 
31 #ifdef USPS_DEBUG
32 int	uspsdebug = 0;
33 #define DPRINTFN(n, x)	do { if (uspsdebug > (n)) printf x; } while (0)
34 #else
35 #define DPRINTFN(n, x)
36 #endif
37 
38 #define DPRINTF(x) DPRINTFN(0, x)
39 
40 #define USPS_UPDATE_TICK	1 /* sec */
41 #define USPS_TIMEOUT		1000 /* ms */
42 #define USPS_INTR_TICKS		50 /* ms */
43 
44 /* protocol */
45 #define USPS_CMD_START		0x01
46 #define USPS_CMD_VALUE		0x20
47 #define USPS_CMD_GET_FIRMWARE	0xc0
48 #define USPS_CMD_GET_SERIAL	0xc1
49 #define USPS_CMD_GET_VOLTAGE	0xb0
50 #define USPS_CMD_GET_TEMP	0xb4
51 #define USPS_CMD_GET_FREQ	0xa1
52 #define USPS_CMD_GET_UNK0	0xa2
53 
54 #define USPS_MODE_WATTAGE	0x10
55 #define USPS_MODE_CURRENT	0x30
56 
57 #define FX5204_NUM_PORTS	4
58 
59 struct usps_port_sensor {
60 	struct ksensor ave;
61 	struct ksensor min;
62 	struct ksensor max;
63 	int vave, vmin, vmax;
64 };
65 
66 struct usps_softc {
67 	struct device		 sc_dev;
68 	struct usbd_device	*sc_udev;
69 	struct usbd_interface	*sc_iface;
70 	struct usbd_pipe	*sc_ipipe;
71 	int			 sc_isize;
72 	struct usbd_xfer	*sc_xfer;
73 	uint8_t			 sc_buf[16];
74 	uint8_t			 *sc_intrbuf;
75 
76 	uint16_t		 sc_flag;
77 
78 	/* device info */
79 	uint8_t		 	 sc_firmware_version[2];
80 	uint32_t		 sc_device_serial;
81 
82 	/* sensor framework */
83 	struct usps_port_sensor	 sc_port_sensor[FX5204_NUM_PORTS];
84 	struct usps_port_sensor	 sc_total_sensor;
85 	struct ksensor 		 sc_voltage_sensor;
86 	struct ksensor		 sc_frequency_sensor;
87 	struct ksensor		 sc_temp_sensor;
88 	struct ksensor		 sc_serial_sensor;
89 	struct ksensordev	 sc_sensordev;
90 	struct sensor_task	*sc_sensortask;
91 
92 	int			 sc_count;
93 };
94 
95 struct usps_port_pkt {
96 	uint8_t		header; /* should be 0x80 */
97 	uint16_t	seq;
98 	uint8_t		padding[5];
99 	uint16_t	port[4];
100 } __packed; /* 16 byte length struct */
101 
102 static const struct usb_devno usps_devs[] = {
103 	{ USB_VENDOR_FUJITSUCOMP, USB_PRODUCT_FUJITSUCOMP_FX5204PS},
104 };
105 #define usps_lookup(v, p) usb_lookup(usps_devs, v, p)
106 
107 int  usps_match(struct device *, void *, void *);
108 void usps_attach(struct device *, struct device *, void *);
109 int  usps_detach(struct device *, int);
110 void usps_intr(struct usbd_xfer *, void *, usbd_status);
111 
112 usbd_status usps_cmd(struct usps_softc *, uint8_t, uint16_t, uint16_t);
113 usbd_status usps_set_measurement_mode(struct usps_softc *, int);
114 
115 void usps_get_device_info(struct usps_softc *);
116 void usps_refresh(void *);
117 void usps_refresh_temp(struct usps_softc *);
118 void usps_refresh_power(struct usps_softc *);
119 void usps_refresh_ports(struct usps_softc *);
120 
121 struct cfdriver usps_cd = {
122 	NULL, "usps", DV_DULL
123 };
124 
125 const struct cfattach usps_ca = {
126 	sizeof(struct usps_softc), usps_match, usps_attach, usps_detach
127 };
128 
129 int
usps_match(struct device * parent,void * match,void * aux)130 usps_match(struct device *parent, void *match, void *aux)
131 {
132 	struct usb_attach_arg *uaa = aux;
133 
134 	if (uaa->iface == NULL || uaa->configno != 1)
135 		return UMATCH_NONE;
136 
137 	if (usps_lookup(uaa->vendor, uaa->product) == NULL)
138 		return UMATCH_NONE;
139 
140 	return (UMATCH_VENDOR_PRODUCT);
141 }
142 
143 void
usps_attach(struct device * parent,struct device * self,void * aux)144 usps_attach(struct device *parent, struct device *self, void *aux)
145 {
146 	struct usps_softc *sc = (struct usps_softc *)self;
147 	struct usb_attach_arg *uaa = aux;
148 	usb_interface_descriptor_t *id;
149 	usb_endpoint_descriptor_t *ed;
150 	int ep_ibulk, ep_obulk, ep_intr;
151 	usbd_status err;
152 	int i;
153 
154 	sc->sc_udev = uaa->device;
155 
156 #define USPS_USB_IFACE 0
157 
158 	/* get interface handle */
159 	if ((err = usbd_device2interface_handle(sc->sc_udev, USPS_USB_IFACE,
160 		&sc->sc_iface)) != 0) {
161 		printf("%s: failed to get interface %d: %s\n",
162 		    sc->sc_dev.dv_xname, USPS_USB_IFACE, usbd_errstr(err));
163 		return;
164 	}
165 
166 	/* find endpoints */
167 	ep_ibulk = ep_obulk = ep_intr = -1;
168 	id = usbd_get_interface_descriptor(sc->sc_iface);
169 	for (i = 0; i < id->bNumEndpoints; i++) {
170 		ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i);
171 		if (ed == NULL) {
172 			printf("%s: failed to get endpoint %d descriptor\n",
173 			    sc->sc_dev.dv_xname, i);
174 			return;
175 		}
176 		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
177 		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
178 			ep_ibulk = ed->bEndpointAddress;
179 		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
180 		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
181 			ep_obulk = ed->bEndpointAddress;
182 		if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
183 		    UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT){
184 			ep_intr = ed->bEndpointAddress;
185 			sc->sc_isize = UGETW(ed->wMaxPacketSize);
186 		}
187 	}
188 
189 	if (ep_intr == -1) {
190 		printf("%s: no data endpoint found\n", sc->sc_dev.dv_xname);
191 		return;
192 	}
193 
194 	usps_get_device_info(sc);
195 	strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname,
196 	    sizeof(sc->sc_sensordev.xname));
197 
198 	/* attach sensor */
199 	sc->sc_voltage_sensor.type = SENSOR_VOLTS_AC;
200 	sc->sc_frequency_sensor.type = SENSOR_FREQ;
201 	sc->sc_temp_sensor.type = SENSOR_TEMP;
202 	sc->sc_serial_sensor.type = SENSOR_INTEGER;
203 	sensor_attach(&sc->sc_sensordev, &sc->sc_voltage_sensor);
204 	sensor_attach(&sc->sc_sensordev, &sc->sc_frequency_sensor);
205 	sensor_attach(&sc->sc_sensordev, &sc->sc_temp_sensor);
206 	sensor_attach(&sc->sc_sensordev, &sc->sc_serial_sensor);
207 
208 	sc->sc_serial_sensor.value = sc->sc_device_serial;
209 	strlcpy(sc->sc_serial_sensor.desc, "unit serial#",
210 	    sizeof(sc->sc_serial_sensor.desc));
211 
212 	/*
213 	 * XXX: the device has mode of par port sensor, Watt of Ampair.
214 	 * currently only watt mode is selected.
215 	 */
216 	usps_set_measurement_mode(sc, USPS_MODE_WATTAGE);
217 	for (i = 0; i < FX5204_NUM_PORTS; i++) {
218 		sc->sc_port_sensor[i].ave.type = SENSOR_WATTS;
219 		sc->sc_port_sensor[i].min.type = SENSOR_WATTS;
220 		sc->sc_port_sensor[i].max.type = SENSOR_WATTS;
221 		sensor_attach(&sc->sc_sensordev, &sc->sc_port_sensor[i].ave);
222 		sensor_attach(&sc->sc_sensordev, &sc->sc_port_sensor[i].min);
223 		sensor_attach(&sc->sc_sensordev, &sc->sc_port_sensor[i].max);
224 		(void)snprintf(sc->sc_port_sensor[i].ave.desc,
225 		    sizeof(sc->sc_port_sensor[i].ave.desc),
226 		    "port#%d (average)", i);
227 		(void)snprintf(sc->sc_port_sensor[i].min.desc,
228 		    sizeof(sc->sc_port_sensor[i].min.desc),
229 		    "port#%d (min)", i);
230 		(void)snprintf(sc->sc_port_sensor[i].max.desc,
231 		    sizeof(sc->sc_port_sensor[i].max.desc),
232 		    "port#%d (max)", i);
233 	}
234 
235 	sc->sc_total_sensor.ave.type = SENSOR_WATTS;
236 	sc->sc_total_sensor.min.type = SENSOR_WATTS;
237 	sc->sc_total_sensor.max.type = SENSOR_WATTS;
238 	sensor_attach(&sc->sc_sensordev, &sc->sc_total_sensor.ave);
239 	sensor_attach(&sc->sc_sensordev, &sc->sc_total_sensor.min);
240 	sensor_attach(&sc->sc_sensordev, &sc->sc_total_sensor.max);
241 	(void)snprintf(sc->sc_total_sensor.ave.desc,
242 	    sizeof(sc->sc_total_sensor.ave.desc), "total (average)");
243 	(void)snprintf(sc->sc_total_sensor.min.desc,
244 	    sizeof(sc->sc_total_sensor.ave.desc), "total (min)");
245 	(void)snprintf(sc->sc_total_sensor.max.desc,
246 	    sizeof(sc->sc_total_sensor.ave.desc), "total (max)");
247 
248 	sc->sc_sensortask = sensor_task_register(sc, usps_refresh,
249 	    USPS_UPDATE_TICK);
250 	if (sc->sc_sensortask == NULL) {
251 		printf(", unable to register update task\n");
252 		goto fail;
253 	}
254 
255 	printf("%s: device#=%d, firmware version=V%02dL%02d\n",
256 	    sc->sc_dev.dv_xname, sc->sc_device_serial,
257 	    sc->sc_firmware_version[0],
258 	    sc->sc_firmware_version[1]);
259 
260 	sensordev_install(&sc->sc_sensordev);
261 
262 	/* open interrupt endpoint */
263 	sc->sc_intrbuf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK);
264 	if (sc->sc_intrbuf == NULL)
265 		goto fail;
266 	err = usbd_open_pipe_intr(sc->sc_iface, ep_intr,
267 	    USBD_SHORT_XFER_OK, &sc->sc_ipipe, sc, sc->sc_intrbuf,
268 	    sc->sc_isize, usps_intr, USPS_INTR_TICKS);
269 	if (err) {
270 		printf("%s: could not open intr pipe %s\n",
271 		    sc->sc_dev.dv_xname, usbd_errstr(err));
272 		goto fail;
273 	}
274 
275 	DPRINTF(("usps_attach: complete\n"));
276 	return;
277 
278 fail:
279 	if (sc->sc_ipipe != NULL)
280 		usbd_close_pipe(sc->sc_ipipe);
281 	if (sc->sc_xfer != NULL)
282 		usbd_free_xfer(sc->sc_xfer);
283 	if (sc->sc_intrbuf != NULL)
284 		free(sc->sc_intrbuf, M_USBDEV, sc->sc_isize);
285 }
286 
287 int
usps_detach(struct device * self,int flags)288 usps_detach(struct device *self, int flags)
289 {
290 	struct usps_softc *sc = (struct usps_softc *)self;
291 	int i, rv = 0, s;
292 
293 	usbd_deactivate(sc->sc_udev);
294 
295 	s = splusb();
296 	if (sc->sc_ipipe != NULL) {
297 		usbd_close_pipe(sc->sc_ipipe);
298 		if (sc->sc_intrbuf != NULL)
299 			free(sc->sc_intrbuf, M_USBDEV, sc->sc_isize);
300 		sc->sc_ipipe = NULL;
301 	}
302 	if (sc->sc_xfer != NULL)
303 		usbd_free_xfer(sc->sc_xfer);
304 	splx(s);
305 
306 	wakeup(&sc->sc_sensortask);
307 	sensordev_deinstall(&sc->sc_sensordev);
308 	sensor_detach(&sc->sc_sensordev, &sc->sc_voltage_sensor);
309 	sensor_detach(&sc->sc_sensordev, &sc->sc_frequency_sensor);
310 	sensor_detach(&sc->sc_sensordev, &sc->sc_temp_sensor);
311 	sensor_detach(&sc->sc_sensordev, &sc->sc_serial_sensor);
312 	for (i = 0; i < FX5204_NUM_PORTS; i++) {
313 		sensor_detach(&sc->sc_sensordev, &sc->sc_port_sensor[i].ave);
314 		sensor_detach(&sc->sc_sensordev, &sc->sc_port_sensor[i].min);
315 		sensor_detach(&sc->sc_sensordev, &sc->sc_port_sensor[i].max);
316 	}
317 	sensor_detach(&sc->sc_sensordev, &sc->sc_total_sensor.ave);
318 	sensor_detach(&sc->sc_sensordev, &sc->sc_total_sensor.min);
319 	sensor_detach(&sc->sc_sensordev, &sc->sc_total_sensor.max);
320 
321 	if (sc->sc_sensortask != NULL)
322 		sensor_task_unregister(sc->sc_sensortask);
323 
324 	return (rv);
325 }
326 
327 usbd_status
usps_cmd(struct usps_softc * sc,uint8_t cmd,uint16_t val,uint16_t len)328 usps_cmd(struct usps_softc *sc, uint8_t cmd, uint16_t val, uint16_t len)
329 {
330 	usb_device_request_t req;
331 	usbd_status err;
332 
333 	req.bmRequestType = UT_READ_VENDOR_DEVICE;
334 	req.bRequest = cmd;
335 	USETW(req.wValue, val);
336 	USETW(req.wIndex, 0);
337 	USETW(req.wLength, len);
338 
339 	err = usbd_do_request(sc->sc_udev, &req, &sc->sc_buf);
340 	if (err) {
341 		printf("%s: could not issue sensor cmd: %s\n",
342 		    sc->sc_dev.dv_xname, usbd_errstr(err));
343 		return (EIO);
344 	}
345 
346 	return (0);
347 }
348 
349 usbd_status
usps_set_measurement_mode(struct usps_softc * sc,int mode)350 usps_set_measurement_mode(struct usps_softc *sc, int mode)
351 {
352 	usb_device_request_t req;
353 	usbd_status err;
354 
355 	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
356 	req.bRequest = USPS_CMD_START;
357 	USETW(req.wValue, 0);
358 	USETW(req.wIndex, 0);
359 	USETW(req.wLength, 0);
360 
361 	err = usbd_do_request(sc->sc_udev, &req, &sc->sc_buf);
362 	if (err) {
363 		printf("%s: fail to set sensor mode: %s\n",
364 		    sc->sc_dev.dv_xname, usbd_errstr(err));
365 		return (EIO);
366 	}
367 
368 	req.bRequest = USPS_CMD_VALUE;
369 	USETW(req.wValue, mode);
370 
371 	err = usbd_do_request(sc->sc_udev, &req, &sc->sc_buf);
372 	if (err) {
373 		printf("%s: could not set sensor mode: %s\n",
374 		    sc->sc_dev.dv_xname, usbd_errstr(err));
375 		return (EIO);
376 	}
377 
378 	return (0);
379 }
380 
381 void
usps_intr(struct usbd_xfer * xfer,void * priv,usbd_status status)382 usps_intr(struct usbd_xfer *xfer, void *priv, usbd_status status)
383 {
384 	struct usps_softc *sc = priv;
385 	struct usps_port_pkt *pkt;
386 	struct usps_port_sensor *ps;
387 	int i, total;
388 
389 	if (usbd_is_dying(sc->sc_udev))
390 		return;
391 
392 	if (status != USBD_NORMAL_COMPLETION) {
393 		if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
394 			return;
395 		if (status == USBD_STALLED)
396 			usbd_clear_endpoint_stall_async(sc->sc_ipipe);
397 		return;
398 	}
399 
400 	/* process intr packet */
401 	if (sc->sc_intrbuf == NULL)
402 		return;
403 
404 	pkt = (struct usps_port_pkt *)sc->sc_intrbuf;
405 
406 	total = 0;
407 	for (i = 0; i < FX5204_NUM_PORTS; i++) {
408 		ps = &sc->sc_port_sensor[i];
409 		if (sc->sc_count == 0)
410 			ps->vmax = ps->vmin = pkt->port[i];
411 		if (pkt->port[i] > ps->vmax)
412 			ps->vmax = pkt->port[i];
413 		if (pkt->port[i] < ps->vmin)
414 			ps->vmin = pkt->port[i];
415 		ps->vave =
416 		    (ps->vave * sc->sc_count + pkt->port[i])/(sc->sc_count +1);
417 		total += pkt->port[i];
418 	}
419 
420 	/* calculate ports total */
421 	ps = &sc->sc_total_sensor;
422 	if (sc->sc_count == 0)
423 		ps->vmax = ps->vmin = total;
424 	if (total > ps->vmax)
425 		ps->vmax = total;
426 	if (total < ps->vmin)
427 		ps->vmin = total;
428 	ps->vave = (ps->vave * sc->sc_count + total)/(sc->sc_count +1);
429 
430 	sc->sc_count++;
431 }
432 
433 void
usps_get_device_info(struct usps_softc * sc)434 usps_get_device_info(struct usps_softc *sc)
435 {
436 	int serial;
437 
438 	/* get Firmware version */
439 	usps_cmd(sc, USPS_CMD_GET_FIRMWARE, 0, 2);
440 	sc->sc_firmware_version[0] =
441 	    (sc->sc_buf[0]>>4) * 10 + (sc->sc_buf[0] & 0xf);
442 	sc->sc_firmware_version[1] =
443 	    (sc->sc_buf[1]>>4) * 10 + (sc->sc_buf[1] & 0xf);
444 
445 	/* get device serial number */
446 	usps_cmd(sc, USPS_CMD_GET_SERIAL, 0, 3);
447 
448 	serial = 0;
449 	serial += ((sc->sc_buf[0]>>4) * 10 + (sc->sc_buf[0] & 0xf)) * 10000;
450 	serial += ((sc->sc_buf[1]>>4) * 10 + (sc->sc_buf[1] & 0xf)) * 100;
451 	serial += ((sc->sc_buf[2]>>4) * 10 + (sc->sc_buf[2] & 0xf));
452 	sc->sc_device_serial = serial;
453 }
454 
455 void
usps_refresh(void * arg)456 usps_refresh(void *arg)
457 {
458 	struct usps_softc *sc = arg;
459 
460 	usps_refresh_temp(sc);
461 	usps_refresh_power(sc);
462 	usps_refresh_ports(sc);
463 }
464 
465 void
usps_refresh_ports(struct usps_softc * sc)466 usps_refresh_ports(struct usps_softc *sc)
467 {
468 	int i;
469 	struct usps_port_sensor *ps;
470 
471 	/* update port values */
472 	for (i = 0; i < FX5204_NUM_PORTS; i++) {
473 		ps = &sc->sc_port_sensor[i];
474 		ps->ave.value = ps->vave * 1000000;
475 		ps->min.value = ps->vmin * 1000000;
476 		ps->max.value = ps->vmax * 1000000;
477 	}
478 
479 	/* update total value */
480 	ps = &sc->sc_total_sensor;
481 	ps->ave.value = ps->vave * 1000000;
482 	ps->min.value = ps->vmin * 1000000;
483 	ps->max.value = ps->vmax * 1000000;
484 
485 	sc->sc_count = 0;
486 }
487 
488 void
usps_refresh_temp(struct usps_softc * sc)489 usps_refresh_temp(struct usps_softc *sc)
490 {
491 	int temp;
492 
493 	if (usps_cmd(sc, USPS_CMD_GET_TEMP, 0, 2) != 0) {
494 		DPRINTF(("%s: temperature data read error\n",
495 		    sc->sc_dev.dv_xname));
496 		sc->sc_temp_sensor.flags |= SENSOR_FINVALID;
497 		return;
498 	}
499 	temp = (sc->sc_buf[1] << 8) + sc->sc_buf[0];
500 	sc->sc_temp_sensor.value = (temp * 10000) + 273150000;
501 	sc->sc_temp_sensor.flags &= ~SENSOR_FINVALID;
502 }
503 
504 void
usps_refresh_power(struct usps_softc * sc)505 usps_refresh_power(struct usps_softc *sc)
506 {
507 	int v;
508 	uint val;
509 	uint64_t f;
510 
511 	/* update source voltage */
512 	if (usps_cmd(sc, USPS_CMD_GET_VOLTAGE, 0, 1) != 0) {
513 		DPRINTF(("%s: voltage data read error\n", sc->sc_dev.dv_xname));
514 		sc->sc_voltage_sensor.flags |= SENSOR_FINVALID;
515 		return;
516 	}
517 
518 	v = sc->sc_buf[0] * 1000000;
519 	sc->sc_voltage_sensor.value = v;
520 	sc->sc_voltage_sensor.flags &= ~SENSOR_FINVALID;
521 
522 	/* update source frequency */
523 	if (usps_cmd(sc, USPS_CMD_GET_FREQ, 0, 8) != 0) {
524 		DPRINTF(("%s: frequency data read error\n",
525 		    sc->sc_dev.dv_xname));
526 		sc->sc_frequency_sensor.flags |= SENSOR_FINVALID;
527 		return;
528 	}
529 
530 	if (sc->sc_buf[7] == 0 && sc->sc_buf[6] == 0) {
531 		/* special case */
532 		f = 0;
533 	} else {
534 		val = (sc->sc_buf[1] << 8) + sc->sc_buf[0];
535 		if (val == 0) {
536 			/* guard against "division by zero" */
537 			sc->sc_frequency_sensor.flags |= SENSOR_FINVALID;
538 			return;
539 		}
540 		f = 2000000L;
541 		f *=  1000000L;
542 		f /= val;
543 	}
544 
545 	sc->sc_frequency_sensor.value = f;
546 	sc->sc_frequency_sensor.flags &= ~SENSOR_FINVALID;
547 }
548