video: driver: handle dpb list property
- subscribe for dpb list property in combined mode on output port. - copy dpb list property payload from fw to static array. - using this array, mark read_only list buffers as non-reference if not part of array. if count of such buffers is greater than output min count, send to fw for release. - once fw returns them, destroy these buffers. - unmap stale output mappings due to lazy unmap feature. - unify release internal functions into common function. Change-Id: Id32f04efb19eecaff453cc4383ee8296a0246263 Signed-off-by: Darshana Patil <darshana@codeaurora.org>
This commit is contained in:
@@ -1168,6 +1168,15 @@ static struct msm_platform_inst_capability instance_data_waipio[] = {
|
||||
V4L2_CID_MPEG_VIDC_ENC_INPUT_COMPRESSION_RATIO,
|
||||
0, CAP_FLAG_DYNAMIC_ALLOWED},
|
||||
|
||||
{DPB_LIST, DEC, CODECS_ALL,
|
||||
V4L2_MPEG_MSM_VIDC_DISABLE, V4L2_MPEG_MSM_VIDC_ENABLE,
|
||||
1, V4L2_MPEG_MSM_VIDC_DISABLE,
|
||||
0,
|
||||
HFI_PROP_DPB_LIST,
|
||||
CAP_FLAG_OUTPUT_PORT,
|
||||
{0}, {0},
|
||||
NULL, NULL},
|
||||
|
||||
{META_LTR_MARK_USE, ENC, H264|HEVC,
|
||||
V4L2_MPEG_MSM_VIDC_DISABLE, V4L2_MPEG_MSM_VIDC_ENABLE,
|
||||
1, V4L2_MPEG_MSM_VIDC_DISABLE,
|
||||
|
@@ -30,5 +30,7 @@ int msm_vdec_input_port_settings_change(struct msm_vidc_inst *inst);
|
||||
int msm_vdec_output_port_settings_change(struct msm_vidc_inst *inst);
|
||||
int msm_vdec_process_cmd(struct msm_vidc_inst *inst, u32 cmd);
|
||||
int msm_vidc_queue_buffer_batch(struct msm_vidc_inst *inst);
|
||||
int msm_vdec_handle_release_buffer(struct msm_vidc_inst *inst,
|
||||
struct msm_vidc_buffer *buf);
|
||||
|
||||
#endif // _MSM_VDEC_H_
|
||||
|
@@ -332,6 +332,9 @@ void put_inst(struct msm_vidc_inst *inst);
|
||||
bool msm_vidc_allow_s_fmt(struct msm_vidc_inst *inst, u32 type);
|
||||
bool msm_vidc_allow_s_ctrl(struct msm_vidc_inst *inst, u32 id);
|
||||
bool msm_vidc_allow_metadata(struct msm_vidc_inst *inst, u32 cap_id);
|
||||
bool msm_vidc_allow_property(struct msm_vidc_inst *inst, u32 hfi_id);
|
||||
int msm_vidc_update_property_cap(struct msm_vidc_inst *inst, u32 hfi_id,
|
||||
bool allow);
|
||||
bool msm_vidc_allow_reqbufs(struct msm_vidc_inst *inst, u32 type);
|
||||
enum msm_vidc_allow msm_vidc_allow_stop(struct msm_vidc_inst *inst);
|
||||
bool msm_vidc_allow_start(struct msm_vidc_inst *inst);
|
||||
|
@@ -63,6 +63,7 @@ struct msm_vidc_buffers_info {
|
||||
struct msm_vidc_buffers input;
|
||||
struct msm_vidc_buffers output;
|
||||
struct msm_vidc_buffers read_only;
|
||||
struct msm_vidc_buffers release;
|
||||
struct msm_vidc_buffers input_meta;
|
||||
struct msm_vidc_buffers output_meta;
|
||||
struct msm_vidc_buffers bin;
|
||||
@@ -149,6 +150,6 @@ struct msm_vidc_inst {
|
||||
u64 last_qbuf_time_ns;
|
||||
bool vb2q_init;
|
||||
u32 max_input_data_size;
|
||||
u32 dpb_list_payload[MAX_DPB_LIST_ARRAY_SIZE];
|
||||
};
|
||||
|
||||
#endif // _MSM_VIDC_INST_H_
|
||||
|
@@ -111,6 +111,23 @@
|
||||
#define HW_RESPONSE_TIMEOUT_VALUE (1000)
|
||||
#define SW_PC_DELAY_VALUE (HW_RESPONSE_TIMEOUT_VALUE + 500)
|
||||
#define FW_UNLOAD_DELAY_VALUE (SW_PC_DELAY_VALUE + 1500)
|
||||
/*
|
||||
* MAX_MAPPED_OUTPUT_COUNT: maximum mappings which can
|
||||
* be present in output map list with refcount 1. These
|
||||
* mappings exist due to lazy unmap feature. Current
|
||||
* threshold is kept as 50 to handle vpp usecases
|
||||
* which might have many output buffers.
|
||||
*/
|
||||
#define MAX_MAPPED_OUTPUT_COUNT 50
|
||||
/*
|
||||
* max dpb count = 16
|
||||
* each dpb: 4 words - <base_address, addr_offset, data_offset>
|
||||
* dpb list array size = 16 * 4
|
||||
* dpb payload size = 16 * 4 * 4
|
||||
*/
|
||||
#define MAX_DPB_COUNT 16
|
||||
#define MAX_DPB_LIST_ARRAY_SIZE (MAX_DPB_COUNT * 4)
|
||||
#define MAX_DPB_LIST_PAYLOAD_SIZE (MAX_DPB_COUNT * 4 * 4)
|
||||
|
||||
enum msm_vidc_domain_type {
|
||||
MSM_VIDC_ENCODER = BIT(0),
|
||||
@@ -420,6 +437,7 @@ enum msm_vidc_inst_capability_type {
|
||||
SEQ_CHANGE_AT_SYNC_FRAME,
|
||||
PRIORITY,
|
||||
ENC_IP_CR,
|
||||
DPB_LIST,
|
||||
META_LTR_MARK_USE,
|
||||
META_DPB_MISR,
|
||||
META_OPB_MISR,
|
||||
|
@@ -17,6 +17,7 @@
|
||||
#include "msm_vidc_debug.h"
|
||||
#include "msm_vidc_power.h"
|
||||
#include "msm_vidc_control.h"
|
||||
#include "msm_vidc_memory.h"
|
||||
#include "venus_hfi.h"
|
||||
#include "hfi_packet.h"
|
||||
/* TODO: update based on clips */
|
||||
@@ -63,6 +64,7 @@ static const u32 msm_vdec_output_subscribe_for_properties[] = {
|
||||
HFI_PROP_WORST_COMPRESSION_RATIO,
|
||||
HFI_PROP_WORST_COMPLEXITY_FACTOR,
|
||||
HFI_PROP_PICTURE_TYPE,
|
||||
HFI_PROP_DPB_LIST,
|
||||
};
|
||||
|
||||
static const u32 msm_vdec_internal_buffer_type[] = {
|
||||
@@ -1021,8 +1023,8 @@ static int msm_vdec_subscribe_property(struct msm_vidc_inst *inst,
|
||||
int rc = 0;
|
||||
struct msm_vidc_core *core;
|
||||
u32 payload[32] = {0};
|
||||
u32 i;
|
||||
u32 payload_size = 0;
|
||||
u32 i, count = 0;
|
||||
bool allow = false;
|
||||
|
||||
if (!inst || !inst->core) {
|
||||
d_vpr_e("%s: invalid params\n", __func__);
|
||||
@@ -1034,15 +1036,21 @@ static int msm_vdec_subscribe_property(struct msm_vidc_inst *inst,
|
||||
payload[0] = HFI_MODE_PROPERTY;
|
||||
|
||||
if (port == INPUT_PORT) {
|
||||
for (i = 0; i < ARRAY_SIZE(msm_vdec_input_subscribe_for_properties); i++)
|
||||
payload[i + 1] = msm_vdec_input_subscribe_for_properties[i];
|
||||
payload_size = (ARRAY_SIZE(msm_vdec_input_subscribe_for_properties) + 1) *
|
||||
sizeof(u32);
|
||||
for (i = 0; i < ARRAY_SIZE(msm_vdec_input_subscribe_for_properties); i++) {
|
||||
payload[count + 1] = msm_vdec_input_subscribe_for_properties[i];
|
||||
count++;
|
||||
}
|
||||
} else if (port == OUTPUT_PORT) {
|
||||
for (i = 0; i < ARRAY_SIZE(msm_vdec_output_subscribe_for_properties); i++)
|
||||
payload[i + 1] = msm_vdec_output_subscribe_for_properties[i];
|
||||
payload_size = (ARRAY_SIZE(msm_vdec_output_subscribe_for_properties) + 1) *
|
||||
sizeof(u32);
|
||||
for (i = 0; i < ARRAY_SIZE(msm_vdec_output_subscribe_for_properties); i++) {
|
||||
allow = msm_vidc_allow_property(inst,
|
||||
msm_vdec_output_subscribe_for_properties[i]);
|
||||
if (allow) {
|
||||
payload[count + 1] = msm_vdec_output_subscribe_for_properties[i];
|
||||
count++;
|
||||
}
|
||||
msm_vidc_update_property_cap(inst,
|
||||
msm_vdec_output_subscribe_for_properties[i], allow);
|
||||
}
|
||||
} else {
|
||||
i_vpr_e(inst, "%s: invalid port: %d\n", __func__, port);
|
||||
return -EINVAL;
|
||||
@@ -1053,7 +1061,7 @@ static int msm_vdec_subscribe_property(struct msm_vidc_inst *inst,
|
||||
port,
|
||||
HFI_PAYLOAD_U32_ARRAY,
|
||||
&payload[0],
|
||||
payload_size);
|
||||
(count + 1) * sizeof(u32));
|
||||
|
||||
return rc;
|
||||
}
|
||||
@@ -1780,15 +1788,158 @@ static int msm_vdec_qbuf_batch(struct msm_vidc_inst *inst,
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int msm_vdec_release_nonref_buffers(struct msm_vidc_inst *inst)
|
||||
{
|
||||
int rc = 0;
|
||||
u32 fw_ro_count = 0, nonref_ro_count = 0;
|
||||
struct msm_vidc_buffer *ro_buf, *rel_buf, *dummy;
|
||||
int i = 0;
|
||||
bool found = false;
|
||||
|
||||
if (!inst) {
|
||||
d_vpr_e("%s: invalid params\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* count num buffers in read_only list */
|
||||
list_for_each_entry(ro_buf, &inst->buffers.read_only.list, list)
|
||||
fw_ro_count++;
|
||||
|
||||
if (fw_ro_count <= MAX_DPB_COUNT)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Mark those buffers present in read_only list as non-reference
|
||||
* if that buffer is not part of dpb_list_payload
|
||||
* count such non-ref read only buffers as nonref_ro_count
|
||||
* dpb_list_payload details:
|
||||
* payload[0-1] : 64 bits base_address of DPB-1
|
||||
* payload[2] : 32 bits addr_offset of DPB-1
|
||||
* payload[3] : 32 bits data_offset of DPB-1
|
||||
*/
|
||||
list_for_each_entry(ro_buf, &inst->buffers.read_only.list, list) {
|
||||
found = false;
|
||||
for (i = 0; (i + 3) < MAX_DPB_LIST_ARRAY_SIZE; i = i + 4) {
|
||||
if (ro_buf->device_addr == inst->dpb_list_payload[i] &&
|
||||
ro_buf->data_offset == inst->dpb_list_payload[i + 3]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
ro_buf->attr &= ~MSM_VIDC_ATTR_READ_ONLY;
|
||||
nonref_ro_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (nonref_ro_count <= inst->buffers.output.min_count)
|
||||
return 0;
|
||||
|
||||
i_vpr_l(inst, "%s: fw ro buf count %d, non-ref ro count %d\n",
|
||||
__func__, fw_ro_count, nonref_ro_count);
|
||||
/*
|
||||
* move non-ref read only buffers from read_only list to
|
||||
* release list
|
||||
*/
|
||||
list_for_each_entry_safe(ro_buf, dummy, &inst->buffers.read_only.list, list) {
|
||||
if (!(ro_buf->attr & MSM_VIDC_ATTR_READ_ONLY)) {
|
||||
list_del(&ro_buf->list);
|
||||
INIT_LIST_HEAD(&ro_buf->list);
|
||||
list_add_tail(&ro_buf->list, &inst->buffers.release.list);
|
||||
}
|
||||
}
|
||||
|
||||
/* send release flag along with read only flag for release list bufs*/
|
||||
list_for_each_entry(rel_buf, &inst->buffers.release.list, list) {
|
||||
/* fw needs RO flag for FTB release buffer */
|
||||
rel_buf->attr |= MSM_VIDC_ATTR_READ_ONLY;
|
||||
print_vidc_buffer(VIDC_HIGH, "high", "release buf", inst, rel_buf);
|
||||
rc = venus_hfi_release_buffer(inst, rel_buf);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int msm_vdec_handle_release_buffer(struct msm_vidc_inst *inst,
|
||||
struct msm_vidc_buffer *buf)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (!inst || !buf) {
|
||||
d_vpr_e("%s: invalid params\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
print_vidc_buffer(VIDC_HIGH, "high", "release done", inst, buf);
|
||||
msm_vidc_unmap_driver_buf(inst, buf);
|
||||
|
||||
/* delete the buffer from release list */
|
||||
list_del(&buf->list);
|
||||
msm_vidc_put_vidc_buffer(inst, buf);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int msm_vidc_unmap_excessive_mappings(struct msm_vidc_inst *inst)
|
||||
{
|
||||
int rc = 0;
|
||||
struct msm_vidc_map *map;
|
||||
u32 refcount_one_bufs_count = 0;
|
||||
|
||||
if (!inst) {
|
||||
d_vpr_e("%s: invalid params\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* count entries from map list whose refcount is 1
|
||||
* these are excess mappings present due to lazy
|
||||
* unmap feature.
|
||||
*/
|
||||
list_for_each_entry(map, &inst->mappings.output.list, list) {
|
||||
if (map->refcount == 1)
|
||||
refcount_one_bufs_count++;
|
||||
}
|
||||
|
||||
if (refcount_one_bufs_count <= MAX_MAPPED_OUTPUT_COUNT)
|
||||
return 0;
|
||||
|
||||
/* unmap these buffers as they are stale entries */
|
||||
list_for_each_entry(map, &inst->mappings.output.list, list) {
|
||||
if (map->refcount == 1) {
|
||||
d_vpr_h(
|
||||
"%s: type %11s, device_addr %#x, refcount %d, region %d\n",
|
||||
__func__, buf_name(map->type), map->device_addr, map->refcount,
|
||||
map->region);
|
||||
msm_vidc_memory_unmap(inst->core, map);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int msm_vdec_qbuf(struct msm_vidc_inst *inst, struct vb2_buffer *vb2)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (!inst || !vb2) {
|
||||
if (!inst || !vb2 || !inst->capabilities) {
|
||||
d_vpr_e("%s: invalid params\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (vb2->type == OUTPUT_MPLANE) {
|
||||
if (inst->capabilities->cap[DPB_LIST].value) {
|
||||
rc = msm_vdec_release_nonref_buffers(inst);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = msm_vidc_unmap_excessive_mappings(inst);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* batch decoder output & meta buffer only */
|
||||
if (inst->decode_batch.enable && vb2->type == OUTPUT_MPLANE)
|
||||
rc = msm_vdec_qbuf_batch(inst, vb2);
|
||||
|
@@ -824,6 +824,7 @@ void *msm_vidc_open(void *vidc_core, u32 session_type)
|
||||
INIT_LIST_HEAD(&inst->buffers.output.list);
|
||||
INIT_LIST_HEAD(&inst->buffers.output_meta.list);
|
||||
INIT_LIST_HEAD(&inst->buffers.read_only.list);
|
||||
INIT_LIST_HEAD(&inst->buffers.release.list);
|
||||
INIT_LIST_HEAD(&inst->buffers.bin.list);
|
||||
INIT_LIST_HEAD(&inst->buffers.arp.list);
|
||||
INIT_LIST_HEAD(&inst->buffers.comv.list);
|
||||
|
@@ -159,6 +159,7 @@ static const struct msm_vidc_cap_name cap_name_arr[] = {
|
||||
{SEQ_CHANGE_AT_SYNC_FRAME, "SEQ_CHANGE_AT_SYNC_FRAME" },
|
||||
{PRIORITY, "PRIORITY" },
|
||||
{ENC_IP_CR, "ENC_IP_CR" },
|
||||
{DPB_LIST, "DPB_LIST" },
|
||||
{META_LTR_MARK_USE, "META_LTR_MARK_USE" },
|
||||
{META_DPB_MISR, "META_DPB_MISR" },
|
||||
{META_OPB_MISR, "META_OPB_MISR" },
|
||||
@@ -1174,6 +1175,64 @@ bool msm_vidc_allow_metadata(struct msm_vidc_inst *inst, u32 cap_id)
|
||||
return is_allowed;
|
||||
}
|
||||
|
||||
bool msm_vidc_allow_property(struct msm_vidc_inst *inst, u32 hfi_id)
|
||||
{
|
||||
bool is_allowed = true;
|
||||
|
||||
if (!inst || !inst->capabilities) {
|
||||
d_vpr_e("%s: invalid params\n", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (hfi_id) {
|
||||
case HFI_PROP_WORST_COMPRESSION_RATIO:
|
||||
case HFI_PROP_WORST_COMPLEXITY_FACTOR:
|
||||
case HFI_PROP_PICTURE_TYPE:
|
||||
is_allowed = true;
|
||||
break;
|
||||
case HFI_PROP_DPB_LIST:
|
||||
if (!is_ubwc_colorformat(inst->capabilities->cap[PIX_FMTS].value)) {
|
||||
i_vpr_h(inst,
|
||||
"%s: cap: %24s not allowed for split mode\n",
|
||||
__func__, cap_name(DPB_LIST));
|
||||
is_allowed = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
is_allowed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return is_allowed;
|
||||
}
|
||||
|
||||
int msm_vidc_update_property_cap(struct msm_vidc_inst *inst, u32 hfi_id,
|
||||
bool allow)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
if (!inst || !inst->capabilities) {
|
||||
d_vpr_e("%s: invalid params\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (hfi_id) {
|
||||
case HFI_PROP_WORST_COMPRESSION_RATIO:
|
||||
case HFI_PROP_WORST_COMPLEXITY_FACTOR:
|
||||
case HFI_PROP_PICTURE_TYPE:
|
||||
break;
|
||||
case HFI_PROP_DPB_LIST:
|
||||
if (!allow)
|
||||
memset(inst->dpb_list_payload, 0, MAX_DPB_LIST_ARRAY_SIZE);
|
||||
msm_vidc_update_cap_value(inst, DPB_LIST, allow, __func__);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool msm_vidc_allow_reqbufs(struct msm_vidc_inst *inst, u32 type)
|
||||
{
|
||||
bool allow = false;
|
||||
@@ -4119,6 +4178,12 @@ void msm_vidc_destroy_buffers(struct msm_vidc_inst *inst)
|
||||
msm_vidc_put_vidc_buffer(inst, buf);
|
||||
}
|
||||
|
||||
list_for_each_entry_safe(buf, dummy, &inst->buffers.release.list, list) {
|
||||
print_vidc_buffer(VIDC_ERR, "err", "destroying release buffer", inst, buf);
|
||||
list_del(&buf->list);
|
||||
msm_vidc_put_vidc_buffer(inst, buf);
|
||||
}
|
||||
|
||||
/* destroy buffers from pool */
|
||||
msm_vidc_destroy_vidc_buffer(inst);
|
||||
msm_vidc_destroy_alloc_buffer(inst);
|
||||
|
@@ -3440,18 +3440,11 @@ int venus_hfi_release_buffer(struct msm_vidc_inst *inst,
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (!is_internal_buffer(buffer->type)) {
|
||||
i_vpr_e(inst, "release not allowed for buffer_type %s\n",
|
||||
buf_name(buffer->type));
|
||||
goto unlock;
|
||||
}
|
||||
core = inst->core;
|
||||
|
||||
rc = get_hfi_buffer(inst, buffer, &hfi_buffer);
|
||||
if (rc)
|
||||
goto unlock;
|
||||
|
||||
/* add pending release flag */
|
||||
/* add release flag */
|
||||
hfi_buffer.flags |= HFI_BUF_HOST_FLAG_RELEASE;
|
||||
|
||||
rc = hfi_create_header(inst->packet, inst->packet_size,
|
||||
|
@@ -482,7 +482,55 @@ static int get_driver_buffer_flags(struct msm_vidc_inst *inst, u32 hfi_flags)
|
||||
}
|
||||
|
||||
static int handle_read_only_buffer(struct msm_vidc_inst *inst,
|
||||
struct msm_vidc_buffer *buffer)
|
||||
struct msm_vidc_buffer *buf)
|
||||
{
|
||||
struct msm_vidc_buffer *ro_buf;
|
||||
struct msm_vidc_buffers *ro_buffers;
|
||||
bool found = false;
|
||||
|
||||
if (!inst || !buf) {
|
||||
d_vpr_e("%s: invalid params\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!is_decode_session(inst) || !is_output_buffer(buf->type))
|
||||
return 0;
|
||||
|
||||
if (!(buf->attr & MSM_VIDC_ATTR_READ_ONLY))
|
||||
return 0;
|
||||
|
||||
ro_buffers = msm_vidc_get_buffers(inst, MSM_VIDC_BUF_READ_ONLY, __func__);
|
||||
if (!ro_buffers)
|
||||
return -EINVAL;
|
||||
|
||||
list_for_each_entry(ro_buf, &ro_buffers->list, list) {
|
||||
if (ro_buf->device_addr == buf->device_addr) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* RO flag: add to read_only list if buffer is not present
|
||||
* if present, do nothing
|
||||
*/
|
||||
if (!found) {
|
||||
ro_buf = msm_vidc_get_vidc_buffer(inst);
|
||||
if (!ro_buf) {
|
||||
i_vpr_e(inst, "%s: buffer alloc failed\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(ro_buf, buf, sizeof(struct msm_vidc_buffer));
|
||||
INIT_LIST_HEAD(&ro_buf->list);
|
||||
list_add_tail(&ro_buf->list, &ro_buffers->list);
|
||||
print_vidc_buffer(VIDC_LOW, "low", "ro buf added", inst, ro_buf);
|
||||
}
|
||||
ro_buf->attr |= MSM_VIDC_ATTR_READ_ONLY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_non_read_only_buffer(struct msm_vidc_inst *inst,
|
||||
struct hfi_buffer *buffer)
|
||||
{
|
||||
struct msm_vidc_buffer *ro_buf;
|
||||
struct msm_vidc_buffers *ro_buffers;
|
||||
@@ -493,7 +541,10 @@ static int handle_read_only_buffer(struct msm_vidc_inst *inst,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!is_decode_session(inst) || !is_output_buffer(buffer->type))
|
||||
if (!is_decode_session(inst) || buffer->type != HFI_BUFFER_RAW)
|
||||
return 0;
|
||||
|
||||
if (buffer->flags & HFI_BUF_FW_FLAG_READONLY)
|
||||
return 0;
|
||||
|
||||
ro_buffers = msm_vidc_get_buffers(inst, MSM_VIDC_BUF_READ_ONLY, __func__);
|
||||
@@ -501,36 +552,20 @@ static int handle_read_only_buffer(struct msm_vidc_inst *inst,
|
||||
return -EINVAL;
|
||||
|
||||
list_for_each_entry(ro_buf, &ro_buffers->list, list) {
|
||||
if (ro_buf->device_addr == buffer->device_addr) {
|
||||
if (ro_buf->device_addr == buffer->base_address) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* RO flag: add to read_only list if buffer is not present
|
||||
* if present, do nothing
|
||||
* Without RO flag: remove buffer from read_only list if present
|
||||
* if not present, do not error out
|
||||
*/
|
||||
if (buffer->attr & MSM_VIDC_ATTR_READ_ONLY) {
|
||||
if (!found) {
|
||||
ro_buf = msm_vidc_get_vidc_buffer(inst);
|
||||
if (!ro_buf) {
|
||||
i_vpr_e(inst, "%s: buffer alloc failed\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(ro_buf, buffer, sizeof(struct msm_vidc_buffer));
|
||||
INIT_LIST_HEAD(&ro_buf->list);
|
||||
list_add_tail(&ro_buf->list, &ro_buffers->list);
|
||||
print_vidc_buffer(VIDC_LOW, "low", "ro buf added", inst, ro_buf);
|
||||
}
|
||||
} else {
|
||||
if (found) {
|
||||
print_vidc_buffer(VIDC_LOW, "low", "ro buf deleted", inst, ro_buf);
|
||||
list_del(&ro_buf->list);
|
||||
msm_vidc_put_vidc_buffer(inst, ro_buf);
|
||||
}
|
||||
if (found) {
|
||||
print_vidc_buffer(VIDC_LOW, "low", "ro buf deleted", inst, ro_buf);
|
||||
list_del(&ro_buf->list);
|
||||
msm_vidc_put_vidc_buffer(inst, ro_buf);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -607,6 +642,19 @@ static int handle_output_buffer(struct msm_vidc_inst *inst,
|
||||
struct msm_vidc_buffer *buf;
|
||||
bool found, fatal = false;
|
||||
|
||||
if (!inst || !inst->capabilities) {
|
||||
d_vpr_e("%s: Invalid params\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (is_decode_session(inst)) {
|
||||
if (!(buffer->flags & HFI_BUF_FW_FLAG_READONLY)) {
|
||||
rc = handle_non_read_only_buffer(inst, buffer);
|
||||
if (rc)
|
||||
msm_vidc_change_inst_state(inst, MSM_VIDC_ERROR, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
buffers = msm_vidc_get_buffers(inst, MSM_VIDC_BUF_OUTPUT, __func__);
|
||||
if (!buffers)
|
||||
return -EINVAL;
|
||||
@@ -622,12 +670,9 @@ static int handle_output_buffer(struct msm_vidc_inst *inst,
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
i_vpr_e(inst, "%s: invalid idx %d daddr %#x data_offset %d\n",
|
||||
__func__, buffer->index, buffer->base_address,
|
||||
buffer->data_offset);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!found)
|
||||
return 0;
|
||||
|
||||
buf->data_offset = buffer->data_offset;
|
||||
buf->data_size = buffer->data_size;
|
||||
buf->timestamp = buffer->timestamp;
|
||||
@@ -678,13 +723,22 @@ static int handle_output_buffer(struct msm_vidc_inst *inst,
|
||||
}
|
||||
|
||||
if (is_decode_session(inst)) {
|
||||
if (buffer->flags & HFI_BUF_FW_FLAG_READONLY)
|
||||
/* RO flag is not expected for linear colorformat */
|
||||
if (is_linear_colorformat(inst->capabilities->cap[PIX_FMTS].value) &&
|
||||
(buffer->flags & HFI_BUF_FW_FLAG_READONLY)) {
|
||||
buffer->flags &= ~HFI_BUF_FW_FLAG_READONLY;
|
||||
print_vidc_buffer(
|
||||
VIDC_HIGH, "err", "RO flag in linear colorformat", inst, buf);
|
||||
}
|
||||
|
||||
if (buffer->flags & HFI_BUF_FW_FLAG_READONLY) {
|
||||
buf->attr |= MSM_VIDC_ATTR_READ_ONLY;
|
||||
else
|
||||
rc = handle_read_only_buffer(inst, buf);
|
||||
if (rc)
|
||||
msm_vidc_change_inst_state(inst, MSM_VIDC_ERROR, __func__);
|
||||
} else {
|
||||
buf->attr &= ~MSM_VIDC_ATTR_READ_ONLY;
|
||||
rc = handle_read_only_buffer(inst, buf);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
buf->flags = 0;
|
||||
@@ -832,8 +886,7 @@ static int handle_dequeue_buffers(struct msm_vidc_inst* inst)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* todo: remove below funcs once fw supports rel done flag for internl buf*/
|
||||
static int handle_dpb_buffer(struct msm_vidc_inst *inst,
|
||||
static int handle_release_internal_buffer(struct msm_vidc_inst *inst,
|
||||
struct hfi_buffer *buffer)
|
||||
{
|
||||
int rc = 0;
|
||||
@@ -841,211 +894,8 @@ static int handle_dpb_buffer(struct msm_vidc_inst *inst,
|
||||
struct msm_vidc_buffer *buf;
|
||||
bool found;
|
||||
|
||||
buffers = msm_vidc_get_buffers(inst, MSM_VIDC_BUF_DPB, __func__);
|
||||
if (!buffers)
|
||||
return -EINVAL;
|
||||
|
||||
found = false;
|
||||
list_for_each_entry(buf, &buffers->list, list) {
|
||||
if (buf->device_addr == buffer->base_address) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
rc = msm_vidc_destroy_internal_buffer(inst, buf);
|
||||
} else {
|
||||
i_vpr_e(inst, "%s: invalid idx %d daddr %#x\n",
|
||||
__func__, buffer->index, buffer->base_address);
|
||||
return -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int handle_persist_buffer(struct msm_vidc_inst *inst,
|
||||
struct hfi_buffer *buffer)
|
||||
{
|
||||
int rc = 0;
|
||||
struct msm_vidc_buffers *buffers;
|
||||
struct msm_vidc_buffer *buf;
|
||||
bool found;
|
||||
|
||||
buffers = msm_vidc_get_buffers(inst, MSM_VIDC_BUF_PERSIST, __func__);
|
||||
if (!buffers)
|
||||
return -EINVAL;
|
||||
|
||||
found = false;
|
||||
list_for_each_entry(buf, &buffers->list, list) {
|
||||
if (buf->device_addr == buffer->base_address) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
rc = msm_vidc_destroy_internal_buffer(inst, buf);
|
||||
} else {
|
||||
i_vpr_e(inst, "%s: invalid idx %d daddr %#x\n",
|
||||
__func__, buffer->index, buffer->base_address);
|
||||
return -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int handle_line_buffer(struct msm_vidc_inst *inst,
|
||||
struct hfi_buffer *buffer)
|
||||
{
|
||||
int rc = 0;
|
||||
struct msm_vidc_buffers *buffers;
|
||||
struct msm_vidc_buffer *buf;
|
||||
bool found;
|
||||
|
||||
buffers = msm_vidc_get_buffers(inst, MSM_VIDC_BUF_LINE, __func__);
|
||||
if (!buffers)
|
||||
return -EINVAL;
|
||||
|
||||
found = false;
|
||||
list_for_each_entry(buf, &buffers->list, list) {
|
||||
if (buf->device_addr == buffer->base_address) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
rc = msm_vidc_destroy_internal_buffer(inst, buf);
|
||||
} else {
|
||||
i_vpr_e(inst, "%s: invalid idx %d daddr %#x\n",
|
||||
__func__, buffer->index, buffer->base_address);
|
||||
return -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int handle_non_comv_buffer(struct msm_vidc_inst *inst,
|
||||
struct hfi_buffer *buffer)
|
||||
{
|
||||
int rc = 0;
|
||||
struct msm_vidc_buffers *buffers;
|
||||
struct msm_vidc_buffer *buf;
|
||||
bool found;
|
||||
|
||||
buffers = msm_vidc_get_buffers(inst, MSM_VIDC_BUF_NON_COMV, __func__);
|
||||
if (!buffers)
|
||||
return -EINVAL;
|
||||
|
||||
found = false;
|
||||
list_for_each_entry(buf, &buffers->list, list) {
|
||||
if (buf->device_addr == buffer->base_address) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
rc = msm_vidc_destroy_internal_buffer(inst, buf);
|
||||
} else {
|
||||
i_vpr_e(inst, "%s: invalid idx %d daddr %#x\n",
|
||||
__func__, buffer->index, buffer->base_address);
|
||||
return -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int handle_comv_buffer(struct msm_vidc_inst *inst,
|
||||
struct hfi_buffer *buffer)
|
||||
{
|
||||
int rc = 0;
|
||||
struct msm_vidc_buffers *buffers;
|
||||
struct msm_vidc_buffer *buf;
|
||||
bool found;
|
||||
|
||||
buffers = msm_vidc_get_buffers(inst, MSM_VIDC_BUF_COMV, __func__);
|
||||
if (!buffers)
|
||||
return -EINVAL;
|
||||
|
||||
found = false;
|
||||
list_for_each_entry(buf, &buffers->list, list) {
|
||||
if (buf->device_addr == buffer->base_address) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
rc = msm_vidc_destroy_internal_buffer(inst, buf);
|
||||
} else {
|
||||
i_vpr_e(inst, "%s: invalid idx %d daddr %#x\n",
|
||||
__func__, buffer->index, buffer->base_address);
|
||||
return -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int handle_bin_buffer(struct msm_vidc_inst *inst,
|
||||
struct hfi_buffer *buffer)
|
||||
{
|
||||
int rc = 0;
|
||||
struct msm_vidc_buffers *buffers;
|
||||
struct msm_vidc_buffer *buf;
|
||||
bool found;
|
||||
|
||||
buffers = msm_vidc_get_buffers(inst, MSM_VIDC_BUF_BIN, __func__);
|
||||
if (!buffers)
|
||||
return -EINVAL;
|
||||
|
||||
found = false;
|
||||
list_for_each_entry(buf, &buffers->list, list) {
|
||||
if (buf->device_addr == buffer->base_address) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
rc = msm_vidc_destroy_internal_buffer(inst, buf);
|
||||
} else {
|
||||
i_vpr_e(inst, "%s: invalid idx %d daddr %#x\n",
|
||||
__func__, buffer->index, buffer->base_address);
|
||||
return -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int handle_arp_buffer(struct msm_vidc_inst *inst,
|
||||
struct hfi_buffer *buffer)
|
||||
{
|
||||
int rc = 0;
|
||||
struct msm_vidc_buffers *buffers;
|
||||
struct msm_vidc_buffer *buf;
|
||||
bool found;
|
||||
|
||||
buffers = msm_vidc_get_buffers(inst, MSM_VIDC_BUF_ARP, __func__);
|
||||
if (!buffers)
|
||||
return -EINVAL;
|
||||
|
||||
found = false;
|
||||
list_for_each_entry(buf, &buffers->list, list) {
|
||||
if (buf->device_addr == buffer->base_address) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
rc = msm_vidc_destroy_internal_buffer(inst, buf);
|
||||
} else {
|
||||
i_vpr_e(inst, "%s: invalid idx %d daddr %#x\n",
|
||||
__func__, buffer->index, buffer->base_address);
|
||||
return -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int handle_release_buffer(struct msm_vidc_inst *inst,
|
||||
struct hfi_buffer *buffer, enum hfi_packet_port_type port_type)
|
||||
{
|
||||
int rc = 0;
|
||||
struct msm_vidc_buffers *buffers;
|
||||
struct msm_vidc_buffer *buf;
|
||||
bool found;
|
||||
|
||||
buffers = msm_vidc_get_buffers(inst, hfi_buf_type_to_driver(inst->domain,
|
||||
buffer->type, port_type), __func__);
|
||||
buffer->type, HFI_PORT_NONE), __func__);
|
||||
if (!buffers)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -1056,15 +906,42 @@ static int handle_release_buffer(struct msm_vidc_inst *inst,
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_internal_buffer(buf->type))
|
||||
return 0;
|
||||
|
||||
if (found) {
|
||||
rc = msm_vidc_destroy_internal_buffer(inst, buf);
|
||||
if (rc)
|
||||
return rc;
|
||||
} else {
|
||||
i_vpr_e(inst, "%s: invalid idx %d daddr %#x\n",
|
||||
__func__, buffer->index, buffer->base_address);
|
||||
return -EINVAL;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int handle_release_output_buffer(struct msm_vidc_inst *inst,
|
||||
struct hfi_buffer *buffer, enum hfi_packet_port_type port_type)
|
||||
{
|
||||
int rc = 0;
|
||||
struct msm_vidc_buffer *buf;
|
||||
bool found = false;
|
||||
|
||||
list_for_each_entry(buf, &inst->buffers.release.list, list) {
|
||||
if (buf->device_addr == buffer->base_address) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
i_vpr_e(inst, "%s: invalid idx %d daddr %#x\n",
|
||||
__func__, buffer->index, buffer->base_address);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (is_internal_buffer(buf->type))
|
||||
rc = msm_vidc_destroy_internal_buffer(inst, buf);
|
||||
else
|
||||
rc = msm_vidc_put_driver_buf(inst, buf);
|
||||
|
||||
rc = msm_vdec_handle_release_buffer(inst, buf);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
@@ -1085,26 +962,26 @@ static int handle_session_buffer(struct msm_vidc_inst *inst,
|
||||
static const struct msm_vidc_hfi_buffer_handle enc_output_hfi_handle[] = {
|
||||
{HFI_BUFFER_METADATA, handle_output_metadata_buffer },
|
||||
{HFI_BUFFER_BITSTREAM, handle_output_buffer },
|
||||
{HFI_BUFFER_BIN, handle_bin_buffer },
|
||||
{HFI_BUFFER_COMV, handle_comv_buffer },
|
||||
{HFI_BUFFER_NON_COMV, handle_non_comv_buffer },
|
||||
{HFI_BUFFER_LINE, handle_line_buffer },
|
||||
{HFI_BUFFER_ARP, handle_arp_buffer },
|
||||
{HFI_BUFFER_DPB, handle_dpb_buffer },
|
||||
{HFI_BUFFER_BIN, handle_release_internal_buffer },
|
||||
{HFI_BUFFER_COMV, handle_release_internal_buffer },
|
||||
{HFI_BUFFER_NON_COMV, handle_release_internal_buffer },
|
||||
{HFI_BUFFER_LINE, handle_release_internal_buffer },
|
||||
{HFI_BUFFER_ARP, handle_release_internal_buffer },
|
||||
{HFI_BUFFER_DPB, handle_release_internal_buffer },
|
||||
};
|
||||
static const struct msm_vidc_hfi_buffer_handle dec_input_hfi_handle[] = {
|
||||
{HFI_BUFFER_METADATA, handle_input_metadata_buffer },
|
||||
{HFI_BUFFER_BITSTREAM, handle_input_buffer },
|
||||
{HFI_BUFFER_BIN, handle_bin_buffer },
|
||||
{HFI_BUFFER_COMV, handle_comv_buffer },
|
||||
{HFI_BUFFER_NON_COMV, handle_non_comv_buffer },
|
||||
{HFI_BUFFER_LINE, handle_line_buffer },
|
||||
{HFI_BUFFER_PERSIST, handle_persist_buffer },
|
||||
{HFI_BUFFER_BIN, handle_release_internal_buffer },
|
||||
{HFI_BUFFER_COMV, handle_release_internal_buffer },
|
||||
{HFI_BUFFER_NON_COMV, handle_release_internal_buffer },
|
||||
{HFI_BUFFER_LINE, handle_release_internal_buffer },
|
||||
{HFI_BUFFER_PERSIST, handle_release_internal_buffer },
|
||||
};
|
||||
static const struct msm_vidc_hfi_buffer_handle dec_output_hfi_handle[] = {
|
||||
{HFI_BUFFER_METADATA, handle_output_metadata_buffer },
|
||||
{HFI_BUFFER_RAW, handle_output_buffer },
|
||||
{HFI_BUFFER_DPB, handle_dpb_buffer },
|
||||
{HFI_BUFFER_DPB, handle_release_internal_buffer },
|
||||
};
|
||||
|
||||
if (pkt->flags & HFI_FW_FLAGS_SESSION_ERROR) {
|
||||
@@ -1129,8 +1006,9 @@ static int handle_session_buffer(struct msm_vidc_inst *inst,
|
||||
msm_vidc_change_inst_state(inst, MSM_VIDC_ERROR, __func__);
|
||||
return 0;
|
||||
}
|
||||
if (buffer->flags & HFI_BUF_FW_FLAG_RELEASE_DONE)
|
||||
return handle_release_buffer(inst, buffer, pkt->port);
|
||||
if (is_decode_session(inst) && buffer->type == HFI_BUFFER_RAW &&
|
||||
buffer->flags & HFI_BUF_FW_FLAG_RELEASE_DONE)
|
||||
return handle_release_output_buffer(inst, buffer, pkt->port);
|
||||
|
||||
if (is_encode_session(inst)) {
|
||||
if (pkt->port == HFI_PORT_RAW) {
|
||||
@@ -1272,6 +1150,36 @@ static int handle_session_command(struct msm_vidc_inst *inst,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_dpb_list_property(struct msm_vidc_inst *inst,
|
||||
struct hfi_packet *pkt)
|
||||
{
|
||||
u32 payload_size, num_words_in_payload;
|
||||
u8 *payload_start;
|
||||
int i = 0;
|
||||
|
||||
payload_size = pkt->size - sizeof(struct hfi_packet);
|
||||
num_words_in_payload = payload_size / 4;
|
||||
payload_start = (u8 *)((u8 *)pkt + sizeof(struct hfi_packet));
|
||||
memset(inst->dpb_list_payload, 0, MAX_DPB_LIST_ARRAY_SIZE);
|
||||
|
||||
if (payload_size > MAX_DPB_LIST_PAYLOAD_SIZE) {
|
||||
i_vpr_e(inst,
|
||||
"%s: dpb list payload size %d exceeds expected max size %d\n",
|
||||
__func__, payload_size, MAX_DPB_LIST_PAYLOAD_SIZE);
|
||||
msm_vidc_change_inst_state(inst, MSM_VIDC_ERROR, __func__);
|
||||
}
|
||||
memcpy(inst->dpb_list_payload, payload_start, payload_size);
|
||||
|
||||
for (i = 0; (i + 3) < num_words_in_payload; i = i + 4) {
|
||||
i_vpr_l(inst,
|
||||
"%s: base addr %#x %#x, addr offset %#x, data offset %#x\n",
|
||||
__func__, inst->dpb_list_payload[i], inst->dpb_list_payload[i + 1],
|
||||
inst->dpb_list_payload[i + 2], inst->dpb_list_payload[i + 3]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_session_property(struct msm_vidc_inst *inst,
|
||||
struct hfi_packet *pkt)
|
||||
{
|
||||
@@ -1279,6 +1187,11 @@ static int handle_session_property(struct msm_vidc_inst *inst,
|
||||
u32 port;
|
||||
u32 *payload_ptr;
|
||||
|
||||
if (!inst || !inst->capabilities) {
|
||||
d_vpr_e("%s: Invalid params\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
i_vpr_l(inst, "%s: property type %#x\n", __func__, pkt->type);
|
||||
|
||||
port = vidc_port_from_hfi(inst, pkt->port);
|
||||
@@ -1327,7 +1240,6 @@ static int handle_session_property(struct msm_vidc_inst *inst,
|
||||
break;
|
||||
case HFI_PROP_NO_OUTPUT:
|
||||
if (port != INPUT_PORT) {
|
||||
rc = -EINVAL;
|
||||
i_vpr_e(inst,
|
||||
"%s: invalid port: %d for property %#x\n",
|
||||
__func__, pkt->port, pkt->type);
|
||||
@@ -1341,10 +1253,23 @@ static int handle_session_property(struct msm_vidc_inst *inst,
|
||||
case HFI_PROP_WORST_COMPLEXITY_FACTOR:
|
||||
inst->power.fw_cf = payload_ptr[0];
|
||||
break;
|
||||
case HFI_PROP_DPB_LIST:
|
||||
if (is_decode_session(inst) && port == OUTPUT_PORT &&
|
||||
inst->capabilities->cap[DPB_LIST].value) {
|
||||
rc = handle_dpb_list_property(inst, pkt);
|
||||
if (rc)
|
||||
break;
|
||||
} else {
|
||||
i_vpr_e(inst,
|
||||
"%s: invalid property %#x for %s port %d dpb cap value %d\n",
|
||||
__func__, is_decode_session(inst) ? "decode" : "encode",
|
||||
port, inst->capabilities->cap[DPB_LIST].value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
i_vpr_e(inst, "%s: invalid port settings property %#x\n",
|
||||
i_vpr_e(inst, "%s: invalid property %#x\n",
|
||||
__func__, pkt->type);
|
||||
return -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
|
Reference in New Issue
Block a user