Skip to content

Commit

Permalink
ACPI: property: Parse _CRS CSI-2 descriptor
Browse files Browse the repository at this point in the history
Parse newly added ACPI _CRS CSI-2 descriptor for CSI-2 and camera
configuration. For now, only figure out where the descriptor is present in
order to allow adding information from it to related devices.

Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
  • Loading branch information
Sakari Ailus authored and intel-lab-lkp committed Feb 8, 2023
1 parent 4a1990a commit d78f47f
Show file tree
Hide file tree
Showing 5 changed files with 389 additions and 5 deletions.
2 changes: 1 addition & 1 deletion drivers/acpi/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ acpi-$(CONFIG_ACPI_SLEEP) += proc.o
# ACPI Bus and Device Drivers
#
acpi-y += bus.o glue.o
acpi-y += scan.o
acpi-y += scan.o mipi.o
acpi-y += resource.o
acpi-y += acpi_processor.o
acpi-y += processor_core.o
Expand Down
7 changes: 7 additions & 0 deletions drivers/acpi/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,11 @@ void acpi_init_lpit(void);
static inline void acpi_init_lpit(void) { }
#endif

/*--------------------------------------------------------------------------
ACPI and MIPI DisCo for Imaging conversion
-------------------------------------------------------------------------- */

void acpi_crs_csi2_swnodes_del_free(void);
void acpi_bus_scan_crs_csi2(acpi_handle handle);

#endif /* _ACPI_INTERNAL_H_ */
358 changes: 358 additions & 0 deletions drivers/acpi/mipi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,358 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* MIPI DisCo for Imaging support.
*
* Copyright (C) 2023 Intel Corporation
*/

#include <linux/acpi.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/overflow.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/sort.h>
#include <linux/string.h>

#include "internal.h"

/* Temporary ACPI device handle to software node data structure mapping */
struct crs_csi2_swnodes {
struct list_head list;
acpi_handle handle;
struct acpi_device_software_nodes *ads;
};

static LIST_HEAD(crs_csi2_swnodes);

static void crs_csi2_swnode_del_free(struct crs_csi2_swnodes *swnodes)
{
list_del(&swnodes->list);
kfree(swnodes);
}

void acpi_crs_csi2_swnodes_del_free(void)
{
struct crs_csi2_swnodes *swnodes, *swnodes_tmp;

list_for_each_entry_safe(swnodes, swnodes_tmp, &crs_csi2_swnodes, list)
crs_csi2_swnode_del_free(swnodes);
}

/*
* Description of a _CRS CSI2 resource descriptor before software node creation
*/
struct crs_csi2_instance {
struct list_head list;
struct acpi_resource_csi2_serialbus csi2;
acpi_handle remote_handle;
char remote_name[];
};

/* Temporary list of ACPI device nodes with _CRS CSI2 resource descriptors */
struct crs_csi2 {
struct list_head list;
acpi_handle handle;
struct list_head buses;
};

/*
* Context for collecting _CRS CSI2 resource descriptors during ACPI namespace
* walk
*/
struct scan_check_crs_csi2_context {
struct list_head res_head;
acpi_handle handle;
size_t handle_count;
};

/* Context for scanning ACPI device nodes for _CRS CSI2 resource descriptors */
struct crs_csi2_all {
struct list_head crs_csi2_head;
size_t handle_count;
};

/* Scan a single CSI2 resource descriptor in _CRS */
static acpi_status scan_check_crs_csi2_instance(struct acpi_resource *res,
void *context)
{
struct scan_check_crs_csi2_context *inst_context = context;
struct acpi_resource_csi2_serialbus *csi2;
struct crs_csi2_instance *inst;
acpi_handle remote_handle;

if (res->type != ACPI_RESOURCE_TYPE_SERIAL_BUS)
return AE_OK;

csi2 = &res->data.csi2_serial_bus;

if (csi2->type != ACPI_RESOURCE_SERIAL_TYPE_CSI2)
return AE_OK;

if (!csi2->resource_source.string_length) {
acpi_handle_debug(inst_context->handle,
"invalid resource source string length\n");
return AE_OK;
}

if (ACPI_FAILURE(acpi_get_handle(NULL, csi2->resource_source.string_ptr,
&remote_handle))) {
acpi_handle_warn(inst_context->handle,
"cannot get handle for %s\n",
csi2->resource_source.string_ptr);
return AE_OK;
}

/*
* Allocate space to store the _CRS CSI2 entry and its data and add it
* to the list.
*/
inst = kmalloc(struct_size(inst, remote_name, csi2->resource_source.string_length),
GFP_KERNEL);
if (!inst)
return AE_OK;

inst->csi2 = *csi2;
strscpy(inst->remote_name, csi2->resource_source.string_ptr,
csi2->resource_source.string_length);
inst->csi2.resource_source.string_ptr = inst->remote_name;
inst->remote_handle = remote_handle;

list_add(&inst->list, &inst_context->res_head);

inst_context->handle_count++;

return AE_OK;
}

/*
* Find all devices with _CRS CSI2 resource descriptors and collect them
* into a list for later use
*/
static acpi_status scan_check_crs_csi2(acpi_handle handle, u32 nesting_level,
void *context, void **ret)
{
struct scan_check_crs_csi2_context inst_context = {
.handle = handle,
.res_head = LIST_HEAD_INIT(inst_context.res_head),
};
struct crs_csi2_all *csi2_all = context;
struct crs_csi2 *csi2;

acpi_walk_resources(handle, METHOD_NAME__CRS,
scan_check_crs_csi2_instance, &inst_context);

if (list_empty(&inst_context.res_head))
return AE_OK;

/*
* Found entry, so allocate memory for it, fill it and add it to the
* list.
*/
csi2 = kmalloc(sizeof(*csi2), GFP_KERNEL);
if (!csi2)
return AE_OK;

csi2->handle = handle;
list_replace(&inst_context.res_head, &csi2->buses);
list_add(&csi2->list, &csi2_all->crs_csi2_head);

/* This handle plus remote handles in _CRS CSI2 resource descriptors */
csi2_all->handle_count += 1 + inst_context.handle_count;

return AE_OK;
}

struct acpi_handle_ref {
acpi_handle handle;
unsigned int count;
};

#define NO_CSI2_PORT (~1U)

static int crs_handle_cmp(const void *__a, const void *__b)
{
const struct acpi_handle_ref *a = __a, *b = __b;

return a->handle < b->handle ? -1 : a->handle > b->handle;
}

/*
* Release entries in temporary storage of ACPI device nodes with _CRS CSI2
* resource descriptors.
*/
static void crs_csi2_release(struct list_head *crs_csi2_handles)
{
struct crs_csi2 *csi2, *csi2_tmp;

list_for_each_entry_safe(csi2, csi2_tmp, crs_csi2_handles, list) {
struct crs_csi2_instance *inst, *inst_tmp;

list_for_each_entry_safe(inst, inst_tmp, &csi2->buses, list) {
list_del(&inst->list);
kfree(inst);
}

list_del(&csi2->list);
kfree(csi2);
}
}

/*
* Allocate memory and set up software nodes for an ACPI device with given
* number of CSI-2 ports.
*/
void acpi_crs_csi2_alloc_fill_swnodes(size_t ports_count, acpi_handle handle)
{
struct acpi_device_software_nodes *ads;
struct crs_csi2_swnodes *swnodes;
size_t alloc_size;
unsigned int i;
bool overflow;
void *end;

/*
* Allocate memory for ports, node pointers (number of nodes +
* 1 (guardian), nodes (root + number of ports * 2 (for for
* every port there is an endpoint)).
*/
overflow = check_mul_overflow(sizeof(*ads->ports) +
sizeof(*ads->nodes) * 2 +
sizeof(*ads->nodeptrs) * 2,
ports_count, &alloc_size);
overflow = overflow ||
check_add_overflow(sizeof(*ads) + sizeof(*ads->nodes) +
sizeof(*ads->nodeptrs) * 2,
alloc_size, &alloc_size);
if (overflow) {
acpi_handle_warn(handle,
"too many _CRS CSI2 resource handles (%zu)",
ports_count);
return;
}

swnodes = kzalloc(sizeof(*swnodes), GFP_KERNEL);
ads = kzalloc(alloc_size, GFP_KERNEL);
ads->ports = (void *)(ads + 1);
ads->nodes = (void *)(ads->ports + ports_count);
ads->nodeptrs = (void *)(ads->nodes +
ports_count * 2 + 1);
end = ads->nodeptrs + ports_count * 2 + 2;
if (!swnodes || !ads || WARN_ON((void *)ads + alloc_size != end)) {
kfree(swnodes);
kfree(ads);
acpi_handle_debug(handle,
"cannot allocate for %zu software nodes\n",
ports_count);
return;
}

ads->num_ports = ports_count;
for (i = 0; i < ports_count * 2 + 1; i++)
ads->nodeptrs[i] = &ads->nodes[i];
ads->nodeptrs[i] = NULL;
for (i = 0; i < ports_count; i++)
ads->ports[i].port_nr = NO_CSI2_PORT;
swnodes->handle = handle;
swnodes->ads = ads;
list_add(&swnodes->list, &crs_csi2_swnodes);
}

/**
* acpi_bus_scan_crs_csi2 - Scan a device and its child devices for _CRS CSI-2
*
* @handle: Root of the ACPI Namespace branch to scan
*
* This function does a number of things:
*
* 1. Iteratively scan all ACPI device nodes starting from a handle for _CRS
* CSI-2 instances. The instances are stored for later use.
*
* 2. Count how many references to other devices _CRS CSI-2 instances have in
* total.
*
* 3. Count the number of references to other devices for each _CRS CSI-2
* instance.
*
* 4. Allocate memory for swnodes each ACPI device requires later on, and
* generate a list of such allocations.
*
* Note that struct acpi_device isn't available yet at this time.
*
* acpi_scan_lock in scan.c must be held when calling this function.
*/
void acpi_bus_scan_crs_csi2(acpi_handle handle)
{
struct acpi_handle_ref *handle_refs;
struct acpi_handle_ref *this = NULL;
struct crs_csi2_all csi2_all = {
.crs_csi2_head = LIST_HEAD_INIT(csi2_all.crs_csi2_head),
};
size_t this_count;
unsigned int curr = 0;
struct crs_csi2 *csi2;

/*
* Collect the devices that have a _CRS CSI-2 resource. Each CSI-2 TX
* port has one.
*/
acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, ACPI_UINT32_MAX,
scan_check_crs_csi2, NULL, &csi2_all, NULL);

/* No handles? Bail out here. */
if (!csi2_all.handle_count)
return;

handle_refs = kcalloc(csi2_all.handle_count + 1, sizeof(*handle_refs),
GFP_KERNEL);
if (!handle_refs) {
acpi_handle_debug(handle, "no memory for %zu handle refs\n",
csi2_all.handle_count + 1);
return;
}

/* Associate handles to the number of references. */
list_for_each_entry(csi2, &csi2_all.crs_csi2_head, list) {
struct crs_csi2_instance *inst;
struct acpi_handle_ref *handle_ref;

handle_ref = &handle_refs[curr++];
handle_ref->handle = csi2->handle;

list_for_each_entry(inst, &csi2->buses, list) {
handle_refs[curr].handle = inst->remote_handle;
handle_refs[curr].count = 1;
handle_ref->count++;
curr++;
}
}

handle_refs[curr].handle = NULL;

/* Sort the handles by remote so duplicates canbe easily found */
sort(handle_refs, csi2_all.handle_count, sizeof(*handle_refs), crs_handle_cmp, NULL);

/*
* Finally count references in each handle, allocate space for device
* specific ports, properties and fill the _CRS CSI2 descriptor
* originated data.
*/
this = handle_refs;
this_count = this->count;
for (curr = 1; curr < csi2_all.handle_count + 1; curr++) {
/* Weed out duplicate receiver devices */
if (this->handle == handle_refs[curr].handle) {
this_count += handle_refs[curr].count;
continue;
}

acpi_crs_csi2_alloc_fill_swnodes(this_count, this->handle);

this = &handle_refs[curr];
this_count = this->count;
}

kfree(handle_refs);

crs_csi2_release(&csi2_all.crs_csi2_head);
}

0 comments on commit d78f47f

Please sign in to comment.