2 * Copyright 2014, 2015 Red Hat.
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * on the rights to use, copy, modify, merge, publish, distribute, sub
8 * license, and/or sell copies of the Software, and to permit persons to whom
9 * the Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
21 * USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include "util/u_surface.h"
25 #include "util/u_memory.h"
26 #include "util/u_format.h"
27 #include "util/u_inlines.h"
28 #include "util/os_time.h"
29 #include "state_tracker/sw_winsys.h"
30 #include "os/os_mman.h"
32 #include "virgl_vtest_winsys.h"
33 #include "virgl_vtest_public.h"
35 static void *virgl_vtest_resource_map(struct virgl_winsys
*vws
,
36 struct virgl_hw_res
*res
);
37 static void virgl_vtest_resource_unmap(struct virgl_winsys
*vws
,
38 struct virgl_hw_res
*res
);
40 static inline boolean
can_cache_resource(struct virgl_hw_res
*res
)
42 return res
->cacheable
== TRUE
;
45 static uint32_t vtest_get_transfer_size(struct virgl_hw_res
*res
,
46 const struct pipe_box
*box
,
47 uint32_t stride
, uint32_t layer_stride
,
48 uint32_t level
, uint32_t *valid_stride_p
)
50 uint32_t valid_stride
, valid_layer_stride
;
52 valid_stride
= util_format_get_stride(res
->format
, box
->width
);
55 valid_stride
= stride
;
58 valid_layer_stride
= util_format_get_2d_size(res
->format
, valid_stride
,
62 valid_layer_stride
= layer_stride
;
65 *valid_stride_p
= valid_stride
;
66 return valid_layer_stride
* box
->depth
;
70 virgl_vtest_transfer_put(struct virgl_winsys
*vws
,
71 struct virgl_hw_res
*res
,
72 const struct pipe_box
*box
,
73 uint32_t stride
, uint32_t layer_stride
,
74 uint32_t buf_offset
, uint32_t level
)
76 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
79 uint32_t valid_stride
;
81 size
= vtest_get_transfer_size(res
, box
, stride
, layer_stride
, level
,
84 virgl_vtest_send_transfer_put(vtws
, res
->res_handle
,
85 level
, stride
, layer_stride
,
86 box
, size
, buf_offset
);
88 if (vtws
->protocol_version
>= 2)
91 ptr
= virgl_vtest_resource_map(vws
, res
);
92 virgl_vtest_send_transfer_put_data(vtws
, ptr
+ buf_offset
, size
);
93 virgl_vtest_resource_unmap(vws
, res
);
98 virgl_vtest_transfer_get_internal(struct virgl_winsys
*vws
,
99 struct virgl_hw_res
*res
,
100 const struct pipe_box
*box
,
101 uint32_t stride
, uint32_t layer_stride
,
102 uint32_t buf_offset
, uint32_t level
,
103 bool flush_front_buffer
)
105 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
108 uint32_t valid_stride
;
110 size
= vtest_get_transfer_size(res
, box
, stride
, layer_stride
, level
,
112 virgl_vtest_send_transfer_get(vtws
, res
->res_handle
,
113 level
, stride
, layer_stride
,
114 box
, size
, buf_offset
);
116 if (flush_front_buffer
|| vtws
->protocol_version
>= 2)
117 virgl_vtest_busy_wait(vtws
, res
->res_handle
, VCMD_BUSY_WAIT_FLAG_WAIT
);
119 if (vtws
->protocol_version
>= 2) {
120 if (flush_front_buffer
) {
121 if (box
->depth
> 1 || box
->z
> 1) {
122 fprintf(stderr
, "Expected a 2D resource, received a 3D resource\n");
130 * The display target is aligned to 64 bytes, while the shared resource
131 * between the client/server is not.
133 shm_stride
= util_format_get_stride(res
->format
, res
->width
);
134 ptr
= virgl_vtest_resource_map(vws
, res
);
135 dt_map
= vtws
->sws
->displaytarget_map(vtws
->sws
, res
->dt
, 0);
137 util_copy_rect(dt_map
, res
->format
, res
->stride
, box
->x
, box
->y
,
138 box
->width
, box
->height
, ptr
, shm_stride
, box
->x
,
141 virgl_vtest_resource_unmap(vws
, res
);
142 vtws
->sws
->displaytarget_unmap(vtws
->sws
, res
->dt
);
145 ptr
= virgl_vtest_resource_map(vws
, res
);
146 virgl_vtest_recv_transfer_get_data(vtws
, ptr
+ buf_offset
, size
,
147 valid_stride
, box
, res
->format
);
148 virgl_vtest_resource_unmap(vws
, res
);
155 virgl_vtest_transfer_get(struct virgl_winsys
*vws
,
156 struct virgl_hw_res
*res
,
157 const struct pipe_box
*box
,
158 uint32_t stride
, uint32_t layer_stride
,
159 uint32_t buf_offset
, uint32_t level
)
161 return virgl_vtest_transfer_get_internal(vws
, res
, box
, stride
,
162 layer_stride
, buf_offset
,
166 static void virgl_hw_res_destroy(struct virgl_vtest_winsys
*vtws
,
167 struct virgl_hw_res
*res
)
169 virgl_vtest_send_resource_unref(vtws
, res
->res_handle
);
171 vtws
->sws
->displaytarget_destroy(vtws
->sws
, res
->dt
);
172 if (vtws
->protocol_version
>= 2) {
174 os_munmap(res
->ptr
, res
->size
);
182 static boolean
virgl_vtest_resource_is_busy(struct virgl_vtest_winsys
*vtws
,
183 struct virgl_hw_res
*res
)
185 /* implement busy check */
187 ret
= virgl_vtest_busy_wait(vtws
, res
->res_handle
, 0);
192 return ret
== 1 ? TRUE
: FALSE
;
196 virgl_cache_flush(struct virgl_vtest_winsys
*vtws
)
198 struct list_head
*curr
, *next
;
199 struct virgl_hw_res
*res
;
201 mtx_lock(&vtws
->mutex
);
202 curr
= vtws
->delayed
.next
;
205 while (curr
!= &vtws
->delayed
) {
206 res
= LIST_ENTRY(struct virgl_hw_res
, curr
, head
);
207 LIST_DEL(&res
->head
);
208 virgl_hw_res_destroy(vtws
, res
);
212 mtx_unlock(&vtws
->mutex
);
216 virgl_cache_list_check_free(struct virgl_vtest_winsys
*vtws
)
218 struct list_head
*curr
, *next
;
219 struct virgl_hw_res
*res
;
223 curr
= vtws
->delayed
.next
;
225 while (curr
!= &vtws
->delayed
) {
226 res
= LIST_ENTRY(struct virgl_hw_res
, curr
, head
);
227 if (!os_time_timeout(res
->start
, res
->end
, now
))
230 LIST_DEL(&res
->head
);
231 virgl_hw_res_destroy(vtws
, res
);
237 static void virgl_vtest_resource_reference(struct virgl_vtest_winsys
*vtws
,
238 struct virgl_hw_res
**dres
,
239 struct virgl_hw_res
*sres
)
241 struct virgl_hw_res
*old
= *dres
;
242 if (pipe_reference(&(*dres
)->reference
, &sres
->reference
)) {
243 if (!can_cache_resource(old
)) {
244 virgl_hw_res_destroy(vtws
, old
);
246 mtx_lock(&vtws
->mutex
);
247 virgl_cache_list_check_free(vtws
);
249 old
->start
= os_time_get();
250 old
->end
= old
->start
+ vtws
->usecs
;
251 LIST_ADDTAIL(&old
->head
, &vtws
->delayed
);
253 mtx_unlock(&vtws
->mutex
);
259 static struct virgl_hw_res
*
260 virgl_vtest_winsys_resource_create(struct virgl_winsys
*vws
,
261 enum pipe_texture_target target
,
272 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
273 struct virgl_hw_res
*res
;
274 static int handle
= 1;
277 res
= CALLOC_STRUCT(virgl_hw_res
);
281 if (bind
& (VIRGL_BIND_DISPLAY_TARGET
| VIRGL_BIND_SCANOUT
)) {
282 res
->dt
= vtws
->sws
->displaytarget_create(vtws
->sws
, bind
, format
,
283 width
, height
, 64, NULL
,
286 } else if (vtws
->protocol_version
< 2) {
287 res
->ptr
= align_malloc(size
, 64);
295 res
->format
= format
;
296 res
->height
= height
;
299 virgl_vtest_send_resource_create(vtws
, handle
, target
, format
, bind
,
300 width
, height
, depth
, array_size
,
301 last_level
, nr_samples
, size
, &fd
);
303 if (vtws
->protocol_version
>= 2) {
304 if (res
->size
== 0) {
311 fprintf(stderr
, "Unable to get a valid fd\n");
315 res
->ptr
= os_mmap(NULL
, res
->size
, PROT_WRITE
| PROT_READ
, MAP_SHARED
,
318 if (res
->ptr
== MAP_FAILED
) {
319 fprintf(stderr
, "Client failed to map shared memory region\n");
329 res
->res_handle
= handle
++;
330 pipe_reference_init(&res
->reference
, 1);
331 p_atomic_set(&res
->num_cs_references
, 0);
335 static void virgl_vtest_winsys_resource_unref(struct virgl_winsys
*vws
,
336 struct virgl_hw_res
*hres
)
338 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
339 virgl_vtest_resource_reference(vtws
, &hres
, NULL
);
342 static void *virgl_vtest_resource_map(struct virgl_winsys
*vws
,
343 struct virgl_hw_res
*res
)
345 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
348 * With protocol v0 we can either have a display target or a resource backing
349 * store. With protocol v2 we can have both, so only return the memory mapped
350 * backing store in this function. We can copy to the display target when
353 if (vtws
->protocol_version
>= 2 || !res
->dt
) {
354 res
->mapped
= res
->ptr
;
357 return vtws
->sws
->displaytarget_map(vtws
->sws
, res
->dt
, 0);
361 static void virgl_vtest_resource_unmap(struct virgl_winsys
*vws
,
362 struct virgl_hw_res
*res
)
364 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
368 if (res
->dt
&& vtws
->protocol_version
< 2)
369 vtws
->sws
->displaytarget_unmap(vtws
->sws
, res
->dt
);
372 static void virgl_vtest_resource_wait(struct virgl_winsys
*vws
,
373 struct virgl_hw_res
*res
)
375 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
377 virgl_vtest_busy_wait(vtws
, res
->res_handle
, VCMD_BUSY_WAIT_FLAG_WAIT
);
380 static inline int virgl_is_res_compat(struct virgl_vtest_winsys
*vtws
,
381 struct virgl_hw_res
*res
,
382 uint32_t size
, uint32_t bind
,
385 if (res
->bind
!= bind
)
387 if (res
->format
!= format
)
389 if (res
->size
< size
)
391 if (res
->size
> size
* 2)
394 if (virgl_vtest_resource_is_busy(vtws
, res
)) {
401 static struct virgl_hw_res
*
402 virgl_vtest_winsys_resource_cache_create(struct virgl_winsys
*vws
,
403 enum pipe_texture_target target
,
414 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
415 struct virgl_hw_res
*res
, *curr_res
;
416 struct list_head
*curr
, *next
;
420 /* only store binds for vertex/index/const buffers */
421 if (bind
!= VIRGL_BIND_CONSTANT_BUFFER
&& bind
!= VIRGL_BIND_INDEX_BUFFER
&&
422 bind
!= VIRGL_BIND_VERTEX_BUFFER
&& bind
!= VIRGL_BIND_CUSTOM
)
425 mtx_lock(&vtws
->mutex
);
428 curr
= vtws
->delayed
.next
;
432 while (curr
!= &vtws
->delayed
) {
433 curr_res
= LIST_ENTRY(struct virgl_hw_res
, curr
, head
);
435 if (!res
&& ((ret
= virgl_is_res_compat(vtws
, curr_res
, size
, bind
, format
)) > 0))
437 else if (os_time_timeout(curr_res
->start
, curr_res
->end
, now
)) {
438 LIST_DEL(&curr_res
->head
);
439 virgl_hw_res_destroy(vtws
, curr_res
);
450 if (!res
&& ret
!= -1) {
451 while (curr
!= &vtws
->delayed
) {
452 curr_res
= LIST_ENTRY(struct virgl_hw_res
, curr
, head
);
453 ret
= virgl_is_res_compat(vtws
, curr_res
, size
, bind
, format
);
466 LIST_DEL(&res
->head
);
468 mtx_unlock(&vtws
->mutex
);
469 pipe_reference_init(&res
->reference
, 1);
473 mtx_unlock(&vtws
->mutex
);
476 res
= virgl_vtest_winsys_resource_create(vws
, target
, format
, bind
,
477 width
, height
, depth
, array_size
,
478 last_level
, nr_samples
, size
);
479 if (bind
== VIRGL_BIND_CONSTANT_BUFFER
|| bind
== VIRGL_BIND_INDEX_BUFFER
||
480 bind
== VIRGL_BIND_VERTEX_BUFFER
)
481 res
->cacheable
= TRUE
;
485 static boolean
virgl_vtest_lookup_res(struct virgl_vtest_cmd_buf
*cbuf
,
486 struct virgl_hw_res
*res
)
488 unsigned hash
= res
->res_handle
& (sizeof(cbuf
->is_handle_added
)-1);
491 if (cbuf
->is_handle_added
[hash
]) {
492 i
= cbuf
->reloc_indices_hashlist
[hash
];
493 if (cbuf
->res_bo
[i
] == res
)
496 for (i
= 0; i
< cbuf
->cres
; i
++) {
497 if (cbuf
->res_bo
[i
] == res
) {
498 cbuf
->reloc_indices_hashlist
[hash
] = i
;
506 static void virgl_vtest_release_all_res(struct virgl_vtest_winsys
*vtws
,
507 struct virgl_vtest_cmd_buf
*cbuf
)
511 for (i
= 0; i
< cbuf
->cres
; i
++) {
512 p_atomic_dec(&cbuf
->res_bo
[i
]->num_cs_references
);
513 virgl_vtest_resource_reference(vtws
, &cbuf
->res_bo
[i
], NULL
);
518 static void virgl_vtest_add_res(struct virgl_vtest_winsys
*vtws
,
519 struct virgl_vtest_cmd_buf
*cbuf
,
520 struct virgl_hw_res
*res
)
522 unsigned hash
= res
->res_handle
& (sizeof(cbuf
->is_handle_added
)-1);
524 if (cbuf
->cres
>= cbuf
->nres
) {
525 unsigned new_nres
= cbuf
->nres
+ 256;
526 struct virgl_hw_res
**new_re_bo
= REALLOC(cbuf
->res_bo
,
527 cbuf
->nres
* sizeof(struct virgl_hw_buf
*),
528 new_nres
* sizeof(struct virgl_hw_buf
*));
530 fprintf(stderr
,"failure to add relocation %d, %d\n", cbuf
->cres
, cbuf
->nres
);
534 cbuf
->res_bo
= new_re_bo
;
535 cbuf
->nres
= new_nres
;
538 cbuf
->res_bo
[cbuf
->cres
] = NULL
;
539 virgl_vtest_resource_reference(vtws
, &cbuf
->res_bo
[cbuf
->cres
], res
);
540 cbuf
->is_handle_added
[hash
] = TRUE
;
542 cbuf
->reloc_indices_hashlist
[hash
] = cbuf
->cres
;
543 p_atomic_inc(&res
->num_cs_references
);
547 static struct virgl_cmd_buf
*virgl_vtest_cmd_buf_create(struct virgl_winsys
*vws
,
550 struct virgl_vtest_cmd_buf
*cbuf
;
552 cbuf
= CALLOC_STRUCT(virgl_vtest_cmd_buf
);
557 cbuf
->res_bo
= CALLOC(cbuf
->nres
, sizeof(struct virgl_hw_buf
*));
563 cbuf
->base
.buf
= cbuf
->buf
;
567 static void virgl_vtest_cmd_buf_destroy(struct virgl_cmd_buf
*_cbuf
)
569 struct virgl_vtest_cmd_buf
*cbuf
= virgl_vtest_cmd_buf(_cbuf
);
571 virgl_vtest_release_all_res(virgl_vtest_winsys(cbuf
->ws
), cbuf
);
576 static struct pipe_fence_handle
*
577 virgl_vtest_fence_create(struct virgl_winsys
*vws
)
579 struct virgl_hw_res
*res
;
581 res
= virgl_vtest_winsys_resource_cache_create(vws
,
583 PIPE_FORMAT_R8_UNORM
,
585 8, 1, 1, 0, 0, 0, 8);
587 return (struct pipe_fence_handle
*)res
;
590 static int virgl_vtest_winsys_submit_cmd(struct virgl_winsys
*vws
,
591 struct virgl_cmd_buf
*_cbuf
,
592 struct pipe_fence_handle
**fence
)
594 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
595 struct virgl_vtest_cmd_buf
*cbuf
= virgl_vtest_cmd_buf(_cbuf
);
598 if (cbuf
->base
.cdw
== 0)
601 ret
= virgl_vtest_submit_cmd(vtws
, cbuf
);
602 if (fence
&& ret
== 0)
603 *fence
= virgl_vtest_fence_create(vws
);
605 virgl_vtest_release_all_res(vtws
, cbuf
);
606 memset(cbuf
->is_handle_added
, 0, sizeof(cbuf
->is_handle_added
));
611 static void virgl_vtest_emit_res(struct virgl_winsys
*vws
,
612 struct virgl_cmd_buf
*_cbuf
,
613 struct virgl_hw_res
*res
, boolean write_buf
)
615 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
616 struct virgl_vtest_cmd_buf
*cbuf
= virgl_vtest_cmd_buf(_cbuf
);
617 boolean already_in_list
= virgl_vtest_lookup_res(cbuf
, res
);
620 cbuf
->base
.buf
[cbuf
->base
.cdw
++] = res
->res_handle
;
621 if (!already_in_list
)
622 virgl_vtest_add_res(vtws
, cbuf
, res
);
625 static boolean
virgl_vtest_res_is_ref(struct virgl_winsys
*vws
,
626 struct virgl_cmd_buf
*_cbuf
,
627 struct virgl_hw_res
*res
)
629 if (!p_atomic_read(&res
->num_cs_references
))
635 static int virgl_vtest_get_caps(struct virgl_winsys
*vws
,
636 struct virgl_drm_caps
*caps
)
638 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
640 virgl_ws_fill_new_caps_defaults(caps
);
641 return virgl_vtest_send_get_caps(vtws
, caps
);
644 static struct pipe_fence_handle
*
645 virgl_cs_create_fence(struct virgl_winsys
*vws
, int fd
)
647 return virgl_vtest_fence_create(vws
);
650 static bool virgl_fence_wait(struct virgl_winsys
*vws
,
651 struct pipe_fence_handle
*fence
,
654 struct virgl_vtest_winsys
*vdws
= virgl_vtest_winsys(vws
);
655 struct virgl_hw_res
*res
= virgl_hw_res(fence
);
658 return !virgl_vtest_resource_is_busy(vdws
, res
);
660 if (timeout
!= PIPE_TIMEOUT_INFINITE
) {
661 int64_t start_time
= os_time_get();
663 while (virgl_vtest_resource_is_busy(vdws
, res
)) {
664 if (os_time_get() - start_time
>= timeout
)
670 virgl_vtest_resource_wait(vws
, res
);
674 static void virgl_fence_reference(struct virgl_winsys
*vws
,
675 struct pipe_fence_handle
**dst
,
676 struct pipe_fence_handle
*src
)
678 struct virgl_vtest_winsys
*vdws
= virgl_vtest_winsys(vws
);
679 virgl_vtest_resource_reference(vdws
, (struct virgl_hw_res
**)dst
,
683 static void virgl_vtest_flush_frontbuffer(struct virgl_winsys
*vws
,
684 struct virgl_hw_res
*res
,
685 unsigned level
, unsigned layer
,
686 void *winsys_drawable_handle
,
687 struct pipe_box
*sub_box
)
689 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
695 memset(&box
, 0, sizeof(box
));
699 offset
= box
.y
/ util_format_get_blockheight(res
->format
) * res
->stride
+
700 box
.x
/ util_format_get_blockwidth(res
->format
) * util_format_get_blocksize(res
->format
);
703 box
.width
= res
->width
;
704 box
.height
= res
->height
;
708 virgl_vtest_transfer_get_internal(vws
, res
, &box
, res
->stride
, 0, offset
,
711 vtws
->sws
->displaytarget_display(vtws
->sws
, res
->dt
, winsys_drawable_handle
,
716 virgl_vtest_winsys_destroy(struct virgl_winsys
*vws
)
718 struct virgl_vtest_winsys
*vtws
= virgl_vtest_winsys(vws
);
720 virgl_cache_flush(vtws
);
722 mtx_destroy(&vtws
->mutex
);
726 struct virgl_winsys
*
727 virgl_vtest_winsys_wrap(struct sw_winsys
*sws
)
729 struct virgl_vtest_winsys
*vtws
;
731 vtws
= CALLOC_STRUCT(virgl_vtest_winsys
);
735 virgl_vtest_connect(vtws
);
738 vtws
->usecs
= 1000000;
739 LIST_INITHEAD(&vtws
->delayed
);
740 (void) mtx_init(&vtws
->mutex
, mtx_plain
);
742 vtws
->base
.destroy
= virgl_vtest_winsys_destroy
;
744 vtws
->base
.transfer_put
= virgl_vtest_transfer_put
;
745 vtws
->base
.transfer_get
= virgl_vtest_transfer_get
;
747 vtws
->base
.resource_create
= virgl_vtest_winsys_resource_cache_create
;
748 vtws
->base
.resource_unref
= virgl_vtest_winsys_resource_unref
;
749 vtws
->base
.resource_map
= virgl_vtest_resource_map
;
750 vtws
->base
.resource_wait
= virgl_vtest_resource_wait
;
751 vtws
->base
.cmd_buf_create
= virgl_vtest_cmd_buf_create
;
752 vtws
->base
.cmd_buf_destroy
= virgl_vtest_cmd_buf_destroy
;
753 vtws
->base
.submit_cmd
= virgl_vtest_winsys_submit_cmd
;
755 vtws
->base
.emit_res
= virgl_vtest_emit_res
;
756 vtws
->base
.res_is_referenced
= virgl_vtest_res_is_ref
;
757 vtws
->base
.get_caps
= virgl_vtest_get_caps
;
759 vtws
->base
.cs_create_fence
= virgl_cs_create_fence
;
760 vtws
->base
.fence_wait
= virgl_fence_wait
;
761 vtws
->base
.fence_reference
= virgl_fence_reference
;
762 vtws
->base
.supports_fences
= 0;
763 vtws
->base
.supports_encoded_transfers
= 0;
765 vtws
->base
.flush_frontbuffer
= virgl_vtest_flush_frontbuffer
;