Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,957 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ISHTP bus driver
*
* Copyright (c) 2012-2016, Intel Corporation.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "bus.h"
#include "ishtp-dev.h"
#include "client.h"
#include "hbm.h"
static int ishtp_use_dma;
module_param_named(ishtp_use_dma, ishtp_use_dma, int, 0600);
MODULE_PARM_DESC(ishtp_use_dma, "Use DMA to send messages");
#define to_ishtp_cl_driver(d) container_of(d, struct ishtp_cl_driver, driver)
#define to_ishtp_cl_device(d) container_of(d, struct ishtp_cl_device, dev)
static bool ishtp_device_ready;
/**
* ishtp_recv() - process ishtp message
* @dev: ishtp device
*
* If a message with valid header and size is received, then
* this function calls appropriate handler. The host or firmware
* address is zero, then they are host bus management message,
* otherwise they are message fo clients.
*/
void ishtp_recv(struct ishtp_device *dev)
{
uint32_t msg_hdr;
struct ishtp_msg_hdr *ishtp_hdr;
/* Read ISHTP header dword */
msg_hdr = dev->ops->ishtp_read_hdr(dev);
if (!msg_hdr)
return;
dev->ops->sync_fw_clock(dev);
ishtp_hdr = (struct ishtp_msg_hdr *)&msg_hdr;
dev->ishtp_msg_hdr = msg_hdr;
/* Sanity check: ISHTP frag. length in header */
if (ishtp_hdr->length > dev->mtu) {
dev_err(dev->devc,
"ISHTP hdr - bad length: %u; dropped [%08X]\n",
(unsigned int)ishtp_hdr->length, msg_hdr);
return;
}
/* ISHTP bus message */
if (!ishtp_hdr->host_addr && !ishtp_hdr->fw_addr)
recv_hbm(dev, ishtp_hdr);
/* ISHTP fixed-client message */
else if (!ishtp_hdr->host_addr)
recv_fixed_cl_msg(dev, ishtp_hdr);
else
/* ISHTP client message */
recv_ishtp_cl_msg(dev, ishtp_hdr);
}
EXPORT_SYMBOL(ishtp_recv);
/**
* ishtp_send_msg() - Send ishtp message
* @dev: ishtp device
* @hdr: Message header
* @msg: Message contents
* @ipc_send_compl: completion callback
* @ipc_send_compl_prm: completion callback parameter
*
* Send a multi fragment message via IPC. After sending the first fragment
* the completion callback is called to schedule transmit of next fragment.
*
* Return: This returns IPC send message status.
*/
int ishtp_send_msg(struct ishtp_device *dev, struct ishtp_msg_hdr *hdr,
void *msg, void(*ipc_send_compl)(void *),
void *ipc_send_compl_prm)
{
unsigned char ipc_msg[IPC_FULL_MSG_SIZE];
uint32_t drbl_val;
drbl_val = dev->ops->ipc_get_header(dev, hdr->length +
sizeof(struct ishtp_msg_hdr),
1);
memcpy(ipc_msg, &drbl_val, sizeof(uint32_t));
memcpy(ipc_msg + sizeof(uint32_t), hdr, sizeof(uint32_t));
memcpy(ipc_msg + 2 * sizeof(uint32_t), msg, hdr->length);
return dev->ops->write(dev, ipc_send_compl, ipc_send_compl_prm,
ipc_msg, 2 * sizeof(uint32_t) + hdr->length);
}
/**
* ishtp_write_message() - Send ishtp single fragment message
* @dev: ishtp device
* @hdr: Message header
* @buf: message data
*
* Send a single fragment message via IPC. This returns IPC send message
* status.
*
* Return: This returns IPC send message status.
*/
int ishtp_write_message(struct ishtp_device *dev, struct ishtp_msg_hdr *hdr,
void *buf)
{
return ishtp_send_msg(dev, hdr, buf, NULL, NULL);
}
/**
* ishtp_fw_cl_by_uuid() - locate index of fw client
* @dev: ishtp device
* @uuid: uuid of the client to search
*
* Search firmware client using UUID.
*
* Return: fw client index or -ENOENT if not found
*/
int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const guid_t *uuid)
{
unsigned int i;
for (i = 0; i < dev->fw_clients_num; ++i) {
if (guid_equal(uuid, &dev->fw_clients[i].props.protocol_name))
return i;
}
return -ENOENT;
}
EXPORT_SYMBOL(ishtp_fw_cl_by_uuid);
/**
* ishtp_fw_cl_get_client() - return client information to client
* @dev: the ishtp device structure
* @uuid: uuid of the client to search
*
* Search firmware client using UUID and reture related client information.
*
* Return: pointer of client information on success, NULL on failure.
*/
struct ishtp_fw_client *ishtp_fw_cl_get_client(struct ishtp_device *dev,
const guid_t *uuid)
{
int i;
unsigned long flags;
spin_lock_irqsave(&dev->fw_clients_lock, flags);
i = ishtp_fw_cl_by_uuid(dev, uuid);
spin_unlock_irqrestore(&dev->fw_clients_lock, flags);
if (i < 0 || dev->fw_clients[i].props.fixed_address)
return NULL;
return &dev->fw_clients[i];
}
EXPORT_SYMBOL(ishtp_fw_cl_get_client);
/**
* ishtp_get_fw_client_id() - Get fw client id
* @fw_client: firmware client used to fetch the ID
*
* This interface is used to reset HW get FW client id.
*
* Return: firmware client id.
*/
int ishtp_get_fw_client_id(struct ishtp_fw_client *fw_client)
{
return fw_client->client_id;
}
EXPORT_SYMBOL(ishtp_get_fw_client_id);
/**
* ishtp_fw_cl_by_id() - return index to fw_clients for client_id
* @dev: the ishtp device structure
* @client_id: fw client id to search
*
* Search firmware client using client id.
*
* Return: index on success, -ENOENT on failure.
*/
int ishtp_fw_cl_by_id(struct ishtp_device *dev, uint8_t client_id)
{
int i, res = -ENOENT;
unsigned long flags;
spin_lock_irqsave(&dev->fw_clients_lock, flags);
for (i = 0; i < dev->fw_clients_num; i++) {
if (dev->fw_clients[i].client_id == client_id) {
res = i;
break;
}
}
spin_unlock_irqrestore(&dev->fw_clients_lock, flags);
return res;
}
/**
* ishtp_cl_device_probe() - Bus probe() callback
* @dev: the device structure
*
* This is a bus probe callback and calls the drive probe function.
*
* Return: Return value from driver probe() call.
*/
static int ishtp_cl_device_probe(struct device *dev)
{
struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
struct ishtp_cl_driver *driver;
if (!device)
return 0;
driver = to_ishtp_cl_driver(dev->driver);
if (!driver || !driver->probe)
return -ENODEV;
return driver->probe(device);
}
/**
* ishtp_cl_bus_match() - Bus match() callback
* @dev: the device structure
* @drv: the driver structure
*
* This is a bus match callback, called when a new ishtp_cl_device is
* registered during ishtp bus client enumeration. Use the guid_t in
* drv and dev to decide whether they match or not.
*
* Return: 1 if dev & drv matches, 0 otherwise.
*/
static int ishtp_cl_bus_match(struct device *dev, const struct device_driver *drv)
{
struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
struct ishtp_cl_driver *driver = to_ishtp_cl_driver(drv);
struct ishtp_fw_client *client = device->fw_client;
const struct ishtp_device_id *id;
if (client) {
for (id = driver->id; !guid_is_null(&id->guid); id++) {
if (guid_equal(&id->guid, &client->props.protocol_name))
return 1;
}
}
return 0;
}
/**
* ishtp_cl_device_remove() - Bus remove() callback
* @dev: the device structure
*
* This is a bus remove callback and calls the drive remove function.
* Since the ISH driver model supports only built in, this is
* primarily can be called during pci driver init failure.
*
* Return: Return value from driver remove() call.
*/
static void ishtp_cl_device_remove(struct device *dev)
{
struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
struct ishtp_cl_driver *driver = to_ishtp_cl_driver(dev->driver);
if (device->event_cb) {
device->event_cb = NULL;
cancel_work_sync(&device->event_work);
}
if (driver->remove)
driver->remove(device);
}
/**
* ishtp_cl_device_suspend() - Bus suspend callback
* @dev: device
*
* Called during device suspend process.
*
* Return: Return value from driver suspend() call.
*/
static int ishtp_cl_device_suspend(struct device *dev)
{
struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
struct ishtp_cl_driver *driver;
int ret = 0;
if (!device)
return 0;
driver = to_ishtp_cl_driver(dev->driver);
if (driver && driver->driver.pm) {
if (driver->driver.pm->suspend)
ret = driver->driver.pm->suspend(dev);
}
return ret;
}
/**
* ishtp_cl_device_resume() - Bus resume callback
* @dev: device
*
* Called during device resume process.
*
* Return: Return value from driver resume() call.
*/
static int ishtp_cl_device_resume(struct device *dev)
{
struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
struct ishtp_cl_driver *driver;
int ret = 0;
if (!device)
return 0;
driver = to_ishtp_cl_driver(dev->driver);
if (driver && driver->driver.pm) {
if (driver->driver.pm->resume)
ret = driver->driver.pm->resume(dev);
}
return ret;
}
/**
* ishtp_cl_device_reset() - Reset callback
* @device: ishtp client device instance
*
* This is a callback when HW reset is done and the device need
* reinit.
*
* Return: Return value from driver reset() call.
*/
static int ishtp_cl_device_reset(struct ishtp_cl_device *device)
{
struct ishtp_cl_driver *driver;
int ret = 0;
device->event_cb = NULL;
cancel_work_sync(&device->event_work);
driver = to_ishtp_cl_driver(device->dev.driver);
if (driver && driver->reset)
ret = driver->reset(device);
return ret;
}
static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
char *buf)
{
int len;
len = snprintf(buf, PAGE_SIZE, ISHTP_MODULE_PREFIX "%s\n", dev_name(dev));
return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
}
static DEVICE_ATTR_RO(modalias);
static struct attribute *ishtp_cl_dev_attrs[] = {
&dev_attr_modalias.attr,
NULL,
};
ATTRIBUTE_GROUPS(ishtp_cl_dev);
static int ishtp_cl_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
if (add_uevent_var(env, "MODALIAS=" ISHTP_MODULE_PREFIX "%s", dev_name(dev)))
return -ENOMEM;
return 0;
}
static const struct dev_pm_ops ishtp_cl_bus_dev_pm_ops = {
/* Suspend callbacks */
.suspend = ishtp_cl_device_suspend,
.resume = ishtp_cl_device_resume,
/* Hibernate callbacks */
.freeze = ishtp_cl_device_suspend,
.thaw = ishtp_cl_device_resume,
.restore = ishtp_cl_device_resume,
};
static const struct bus_type ishtp_cl_bus_type = {
.name = "ishtp",
.dev_groups = ishtp_cl_dev_groups,
.probe = ishtp_cl_device_probe,
.match = ishtp_cl_bus_match,
.remove = ishtp_cl_device_remove,
.pm = &ishtp_cl_bus_dev_pm_ops,
.uevent = ishtp_cl_uevent,
};
static void ishtp_cl_dev_release(struct device *dev)
{
kfree(to_ishtp_cl_device(dev));
}
static const struct device_type ishtp_cl_device_type = {
.release = ishtp_cl_dev_release,
};
/**
* ishtp_bus_add_device() - Function to create device on bus
* @dev: ishtp device
* @uuid: uuid of the client
* @name: Name of the client
*
* Allocate ISHTP bus client device, attach it to uuid
* and register with ISHTP bus.
*
* Return: ishtp_cl_device pointer or NULL on failure
*/
static struct ishtp_cl_device *ishtp_bus_add_device(struct ishtp_device *dev,
guid_t uuid, char *name)
{
struct ishtp_cl_device *device;
int status;
unsigned long flags;
spin_lock_irqsave(&dev->device_list_lock, flags);
list_for_each_entry(device, &dev->device_list, device_link) {
if (!strcmp(name, dev_name(&device->dev))) {
device->fw_client = &dev->fw_clients[
dev->fw_client_presentation_num - 1];
spin_unlock_irqrestore(&dev->device_list_lock, flags);
ishtp_cl_device_reset(device);
return device;
}
}
spin_unlock_irqrestore(&dev->device_list_lock, flags);
device = kzalloc_obj(struct ishtp_cl_device);
if (!device)
return NULL;
device->dev.parent = dev->devc;
device->dev.bus = &ishtp_cl_bus_type;
device->dev.type = &ishtp_cl_device_type;
device->ishtp_dev = dev;
device->fw_client =
&dev->fw_clients[dev->fw_client_presentation_num - 1];
dev_set_name(&device->dev, "%s", name);
spin_lock_irqsave(&dev->device_list_lock, flags);
list_add_tail(&device->device_link, &dev->device_list);
spin_unlock_irqrestore(&dev->device_list_lock, flags);
status = device_register(&device->dev);
if (status) {
spin_lock_irqsave(&dev->device_list_lock, flags);
list_del(&device->device_link);
spin_unlock_irqrestore(&dev->device_list_lock, flags);
dev_err(dev->devc, "Failed to register ISHTP client device\n");
put_device(&device->dev);
return NULL;
}
ishtp_device_ready = true;
return device;
}
/**
* ishtp_bus_remove_device() - Function to relase device on bus
* @device: client device instance
*
* This is a counterpart of ishtp_bus_add_device.
* Device is unregistered.
* the device structure is freed in 'ishtp_cl_dev_release' function
* Called only during error in pci driver init path.
*/
static void ishtp_bus_remove_device(struct ishtp_cl_device *device)
{
device_unregister(&device->dev);
}
/**
* ishtp_cl_driver_register() - Client driver register
* @driver: the client driver instance
* @owner: Owner of this driver module
*
* Once a client driver is probed, it created a client
* instance and registers with the bus.
*
* Return: Return value of driver_register or -ENODEV if not ready
*/
int ishtp_cl_driver_register(struct ishtp_cl_driver *driver,
struct module *owner)
{
if (!ishtp_device_ready)
return -ENODEV;
driver->driver.name = driver->name;
driver->driver.owner = owner;
driver->driver.bus = &ishtp_cl_bus_type;
return driver_register(&driver->driver);
}
EXPORT_SYMBOL(ishtp_cl_driver_register);
/**
* ishtp_cl_driver_unregister() - Client driver unregister
* @driver: the client driver instance
*
* Unregister client during device removal process.
*/
void ishtp_cl_driver_unregister(struct ishtp_cl_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(ishtp_cl_driver_unregister);
/**
* ishtp_bus_event_work() - event work function
* @work: work struct pointer
*
* Once an event is received for a client this work
* function is called. If the device has registered a
* callback then the callback is called.
*/
static void ishtp_bus_event_work(struct work_struct *work)
{
struct ishtp_cl_device *device;
device = container_of(work, struct ishtp_cl_device, event_work);
if (device->event_cb)
device->event_cb(device);
}
/**
* ishtp_cl_bus_rx_event() - schedule event work
* @device: client device instance
*
* Once an event is received for a client this schedules
* a work function to process.
*/
void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device)
{
if (!device || !device->event_cb)
return;
if (device->event_cb)
queue_work(device->ishtp_dev->unbound_wq, &device->event_work);
}
/**
* ishtp_register_event_cb() - Register callback
* @device: client device instance
* @event_cb: Event processor for an client
*
* Register a callback for events, called from client driver
*
* Return: Return 0 or -EALREADY if already registered
*/
int ishtp_register_event_cb(struct ishtp_cl_device *device,
void (*event_cb)(struct ishtp_cl_device *))
{
if (device->event_cb)
return -EALREADY;
device->event_cb = event_cb;
INIT_WORK(&device->event_work, ishtp_bus_event_work);
return 0;
}
EXPORT_SYMBOL(ishtp_register_event_cb);
/**
* ishtp_get_device() - update usage count for the device
* @cl_device: client device instance
*
* Increment the usage count. The device can't be deleted
*/
void ishtp_get_device(struct ishtp_cl_device *cl_device)
{
cl_device->reference_count++;
}
EXPORT_SYMBOL(ishtp_get_device);
/**
* ishtp_put_device() - decrement usage count for the device
* @cl_device: client device instance
*
* Decrement the usage count. The device can be deleted is count = 0
*/
void ishtp_put_device(struct ishtp_cl_device *cl_device)
{
cl_device->reference_count--;
}
EXPORT_SYMBOL(ishtp_put_device);
/**
* ishtp_set_drvdata() - set client driver data
* @cl_device: client device instance
* @data: driver data need to be set
*
* Set client driver data to cl_device->driver_data.
*/
void ishtp_set_drvdata(struct ishtp_cl_device *cl_device, void *data)
{
cl_device->driver_data = data;
}
EXPORT_SYMBOL(ishtp_set_drvdata);
/**
* ishtp_get_drvdata() - get client driver data
* @cl_device: client device instance
*
* Get client driver data from cl_device->driver_data.
*
* Return: pointer of driver data
*/
void *ishtp_get_drvdata(struct ishtp_cl_device *cl_device)
{
return cl_device->driver_data;
}
EXPORT_SYMBOL(ishtp_get_drvdata);
/**
* ishtp_dev_to_cl_device() - get ishtp_cl_device instance from device instance
* @device: device instance
*
* Get ish_cl_device instance which embeds device instance in it.
*
* Return: pointer to ishtp_cl_device instance
*/
struct ishtp_cl_device *ishtp_dev_to_cl_device(struct device *device)
{
return to_ishtp_cl_device(device);
}
EXPORT_SYMBOL(ishtp_dev_to_cl_device);
/**
* ishtp_bus_new_client() - Create a new client
* @dev: ISHTP device instance
*
* Once bus protocol enumerates a client, this is called
* to add a device for the client.
*
* Return: 0 on success or error code on failure
*/
int ishtp_bus_new_client(struct ishtp_device *dev)
{
int i;
char *dev_name;
struct ishtp_cl_device *cl_device;
guid_t device_uuid;
/*
* For all reported clients, create an unconnected client and add its
* device to ISHTP bus.
* If appropriate driver has loaded, this will trigger its probe().
* Otherwise, probe() will be called when driver is loaded
*/
i = dev->fw_client_presentation_num - 1;
device_uuid = dev->fw_clients[i].props.protocol_name;
dev_name = kasprintf(GFP_KERNEL, "{%pUL}", &device_uuid);
if (!dev_name)
return -ENOMEM;
cl_device = ishtp_bus_add_device(dev, device_uuid, dev_name);
if (!cl_device) {
kfree(dev_name);
return -ENOENT;
}
kfree(dev_name);
return 0;
}
/**
* ishtp_cl_device_bind() - bind a device
* @cl: ishtp client device
*
* Binds connected ishtp_cl to ISHTP bus device
*
* Return: 0 on success or fault code
*/
int ishtp_cl_device_bind(struct ishtp_cl *cl)
{
struct ishtp_cl_device *cl_device;
unsigned long flags;
int rv;
if (!cl->fw_client_id || cl->state != ISHTP_CL_CONNECTED)
return -EFAULT;
rv = -ENOENT;
spin_lock_irqsave(&cl->dev->device_list_lock, flags);
list_for_each_entry(cl_device, &cl->dev->device_list,
device_link) {
if (cl_device->fw_client &&
cl_device->fw_client->client_id == cl->fw_client_id) {
cl->device = cl_device;
rv = 0;
break;
}
}
spin_unlock_irqrestore(&cl->dev->device_list_lock, flags);
return rv;
}
/**
* ishtp_bus_remove_all_clients() - Remove all clients
* @ishtp_dev: ishtp device
* @warm_reset: Reset due to FW reset dure to errors or S3 suspend
*
* This is part of reset/remove flow. This function the main processing
* only targets error processing, if the FW has forced reset or
* error to remove connected clients. When warm reset the client devices are
* not removed.
*/
void ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev,
bool warm_reset)
{
struct ishtp_cl_device *cl_device, *n;
struct ishtp_cl *cl;
unsigned long flags;
spin_lock_irqsave(&ishtp_dev->cl_list_lock, flags);
list_for_each_entry(cl, &ishtp_dev->cl_list, link) {
cl->state = ISHTP_CL_DISCONNECTED;
if (warm_reset && cl->device && cl->device->reference_count)
continue;
/*
* Wake any pending process. The waiter would check dev->state
* and determine that it's not enabled already,
* and will return error to its caller
*/
wake_up_interruptible(&cl->wait_ctrl_res);
/* Disband any pending read/write requests and free rb */
ishtp_cl_flush_queues(cl);
/* Remove all free and in_process rings, both Rx and Tx */
ishtp_cl_free_rx_ring(cl);
ishtp_cl_free_tx_ring(cl);
/*
* Free client and ISHTP bus client device structures
* don't free host client because it is part of the OS fd
* structure
*/
}
spin_unlock_irqrestore(&ishtp_dev->cl_list_lock, flags);
/* Release DMA buffers for client messages */
ishtp_cl_free_dma_buf(ishtp_dev);
/* remove bus clients */
spin_lock_irqsave(&ishtp_dev->device_list_lock, flags);
list_for_each_entry_safe(cl_device, n, &ishtp_dev->device_list,
device_link) {
cl_device->fw_client = NULL;
if (warm_reset && cl_device->reference_count)
continue;
list_del(&cl_device->device_link);
spin_unlock_irqrestore(&ishtp_dev->device_list_lock, flags);
ishtp_bus_remove_device(cl_device);
spin_lock_irqsave(&ishtp_dev->device_list_lock, flags);
}
spin_unlock_irqrestore(&ishtp_dev->device_list_lock, flags);
/* Free all client structures */
spin_lock_irqsave(&ishtp_dev->fw_clients_lock, flags);
kfree(ishtp_dev->fw_clients);
ishtp_dev->fw_clients = NULL;
ishtp_dev->fw_clients_num = 0;
ishtp_dev->fw_client_presentation_num = 0;
ishtp_dev->fw_client_index = 0;
bitmap_zero(ishtp_dev->fw_clients_map, ISHTP_CLIENTS_MAX);
spin_unlock_irqrestore(&ishtp_dev->fw_clients_lock, flags);
}
EXPORT_SYMBOL(ishtp_bus_remove_all_clients);
/**
* ishtp_reset_handler() - IPC reset handler
* @dev: ishtp device
*
* ISHTP Handler for IPC_RESET notification
*/
void ishtp_reset_handler(struct ishtp_device *dev)
{
unsigned long flags;
/* Handle FW-initiated reset */
dev->dev_state = ISHTP_DEV_RESETTING;
/* Clear BH processing queue - no further HBMs */
spin_lock_irqsave(&dev->rd_msg_spinlock, flags);
dev->rd_msg_fifo_head = dev->rd_msg_fifo_tail = 0;
spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags);
/* Handle ISH FW reset against upper layers */
ishtp_bus_remove_all_clients(dev, true);
}
EXPORT_SYMBOL(ishtp_reset_handler);
/**
* ishtp_reset_compl_handler() - Reset completion handler
* @dev: ishtp device
*
* ISHTP handler for IPC_RESET sequence completion to start
* host message bus start protocol sequence.
*/
void ishtp_reset_compl_handler(struct ishtp_device *dev)
{
dev->dev_state = ISHTP_DEV_INIT_CLIENTS;
dev->hbm_state = ISHTP_HBM_START;
ishtp_hbm_start_req(dev);
}
EXPORT_SYMBOL(ishtp_reset_compl_handler);
/**
* ishtp_use_dma_transfer() - Function to use DMA
*
* This interface is used to enable usage of DMA
*
* Return non zero if DMA can be enabled
*/
int ishtp_use_dma_transfer(void)
{
return ishtp_use_dma;
}
/**
* ishtp_device() - Return device pointer
* @device: ISH-TP client device instance
*
* This interface is used to return device pointer from ishtp_cl_device
* instance.
*
* Return: device *.
*/
struct device *ishtp_device(struct ishtp_cl_device *device)
{
return &device->dev;
}
EXPORT_SYMBOL(ishtp_device);
/**
* ishtp_wait_resume() - Wait for IPC resume
* @dev: ishtp device
*
* Wait for IPC resume
*
* Return: resume complete or not
*/
bool ishtp_wait_resume(struct ishtp_device *dev)
{
/* Waiting to get resume response */
if (dev->resume_flag)
wait_event_interruptible_timeout(dev->resume_wait,
!dev->resume_flag,
msecs_to_jiffies(WAIT_FOR_RESUME_ACK_MS));
return (!dev->resume_flag);
}
EXPORT_SYMBOL_GPL(ishtp_wait_resume);
/**
* ishtp_get_pci_device() - Return PCI device dev pointer
* This interface is used to return PCI device pointer
* from ishtp_cl_device instance.
* @device: ISH-TP client device instance
*
* Return: device *.
*/
struct device *ishtp_get_pci_device(struct ishtp_cl_device *device)
{
return device->ishtp_dev->devc;
}
EXPORT_SYMBOL(ishtp_get_pci_device);
/**
* ishtp_get_workqueue - Retrieve the workqueue associated with an ISHTP device
* @cl_device: Pointer to the ISHTP client device structure
*
* Returns the workqueue_struct pointer (unbound_wq) associated with the given
* ISHTP client device. This workqueue is typically used for scheduling work
* related to the device.
*
* Return: Pointer to struct workqueue_struct.
*/
struct workqueue_struct *ishtp_get_workqueue(struct ishtp_cl_device *cl_device)
{
return cl_device->ishtp_dev->unbound_wq;
}
EXPORT_SYMBOL(ishtp_get_workqueue);
/**
* ishtp_trace_callback() - Return trace callback
* @cl_device: ISH-TP client device instance
*
* This interface is used to return trace callback function pointer.
*
* Return: *ishtp_print_log()
*/
ishtp_print_log ishtp_trace_callback(struct ishtp_cl_device *cl_device)
{
return cl_device->ishtp_dev->print_log;
}
EXPORT_SYMBOL(ishtp_trace_callback);
/**
* ish_hw_reset() - Call HW reset IPC callback
* @dev: ISHTP device instance
*
* This interface is used to reset HW in case of error.
*
* Return: value from IPC hw_reset callback
*/
int ish_hw_reset(struct ishtp_device *dev)
{
return dev->ops->hw_reset(dev);
}
EXPORT_SYMBOL(ish_hw_reset);
/**
* ishtp_bus_register() - Function to register bus
*
* This register ishtp bus
*
* Return: Return output of bus_register
*/
static int __init ishtp_bus_register(void)
{
return bus_register(&ishtp_cl_bus_type);
}
/**
* ishtp_bus_unregister() - Function to unregister bus
*
* This unregister ishtp bus
*/
static void __exit ishtp_bus_unregister(void)
{
bus_unregister(&ishtp_cl_bus_type);
}
module_init(ishtp_bus_register);
module_exit(ishtp_bus_unregister);
MODULE_DESCRIPTION("ISHTP bus driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,75 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ISHTP bus definitions
*
* Copyright (c) 2014-2016, Intel Corporation.
*/
#ifndef _LINUX_ISHTP_CL_BUS_H
#define _LINUX_ISHTP_CL_BUS_H
#include <linux/device.h>
#include <linux/device-id/ishtp.h>
#include <linux/intel-ish-client-if.h>
struct ishtp_cl;
struct ishtp_cl_device;
struct ishtp_device;
struct ishtp_msg_hdr;
/**
* struct ishtp_cl_device - ISHTP device handle
* @dev: device pointer
* @ishtp_dev: pointer to ishtp device structure to primarily to access
* hw device operation callbacks and properties
* @fw_client: fw_client pointer to get fw information like protocol name
* max message length etc.
* @device_link: Link to next client in the list on a bus
* @event_work: Used to schedule rx event for client
* @driver_data: Storage driver private data
* @reference_count: Used for get/put device
* @event_cb: Callback to driver to send events
*
* An ishtp_cl_device pointer is returned from ishtp_add_device()
* and links ISHTP bus clients to their actual host client pointer.
* Drivers for ISHTP devices will get an ishtp_cl_device pointer
* when being probed and shall use it for doing bus I/O.
*/
struct ishtp_cl_device {
struct device dev;
struct ishtp_device *ishtp_dev;
struct ishtp_fw_client *fw_client;
struct list_head device_link;
struct work_struct event_work;
void *driver_data;
int reference_count;
void (*event_cb)(struct ishtp_cl_device *device);
};
int ishtp_bus_new_client(struct ishtp_device *dev);
int ishtp_cl_device_bind(struct ishtp_cl *cl);
void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device);
/* Write a multi-fragment message */
int ishtp_send_msg(struct ishtp_device *dev,
struct ishtp_msg_hdr *hdr, void *msg,
void (*ipc_send_compl)(void *),
void *ipc_send_compl_prm);
/* Write a single-fragment message */
int ishtp_write_message(struct ishtp_device *dev,
struct ishtp_msg_hdr *hdr,
void *buf);
/* Use DMA to send/receive messages */
int ishtp_use_dma_transfer(void);
/* Exported functions */
void ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev,
bool warm_reset);
void ishtp_recv(struct ishtp_device *dev);
void ishtp_reset_handler(struct ishtp_device *dev);
void ishtp_reset_compl_handler(struct ishtp_device *dev);
int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const guid_t *cuuid);
#endif /* _LINUX_ISHTP_CL_BUS_H */
@@ -0,0 +1,276 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ISHTP Ring Buffers
*
* Copyright (c) 2003-2016, Intel Corporation.
*/
#include <linux/slab.h>
#include "client.h"
/**
* ishtp_cl_alloc_rx_ring() - Allocate RX ring buffers
* @cl: client device instance
*
* Allocate and initialize RX ring buffers
*
* Return: 0 on success else -ENOMEM
*/
int ishtp_cl_alloc_rx_ring(struct ishtp_cl *cl)
{
size_t len = cl->device->fw_client->props.max_msg_length;
int j;
struct ishtp_cl_rb *rb;
int ret = 0;
unsigned long flags;
for (j = 0; j < cl->rx_ring_size; ++j) {
rb = ishtp_io_rb_init(cl);
if (!rb) {
ret = -ENOMEM;
goto out;
}
ret = ishtp_io_rb_alloc_buf(rb, len);
if (ret)
goto out;
spin_lock_irqsave(&cl->free_list_spinlock, flags);
list_add_tail(&rb->list, &cl->free_rb_list.list);
spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
}
return 0;
out:
dev_err(&cl->device->dev, "error in allocating Rx buffers\n");
ishtp_cl_free_rx_ring(cl);
return ret;
}
/**
* ishtp_cl_alloc_tx_ring() - Allocate TX ring buffers
* @cl: client device instance
*
* Allocate and initialize TX ring buffers
*
* Return: 0 on success else -ENOMEM
*/
int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl)
{
size_t len = cl->device->fw_client->props.max_msg_length;
int j;
unsigned long flags;
cl->tx_ring_free_size = 0;
/* Allocate pool to free Tx bufs */
for (j = 0; j < cl->tx_ring_size; ++j) {
struct ishtp_cl_tx_ring *tx_buf;
tx_buf = kzalloc_obj(struct ishtp_cl_tx_ring);
if (!tx_buf)
goto out;
tx_buf->send_buf.data = kmalloc(len, GFP_KERNEL);
if (!tx_buf->send_buf.data) {
kfree(tx_buf);
goto out;
}
spin_lock_irqsave(&cl->tx_free_list_spinlock, flags);
list_add_tail(&tx_buf->list, &cl->tx_free_list.list);
++cl->tx_ring_free_size;
spin_unlock_irqrestore(&cl->tx_free_list_spinlock, flags);
}
return 0;
out:
dev_err(&cl->device->dev, "error in allocating Tx pool\n");
ishtp_cl_free_tx_ring(cl);
return -ENOMEM;
}
/**
* ishtp_cl_free_rx_ring() - Free RX ring buffers
* @cl: client device instance
*
* Free RX ring buffers
*/
void ishtp_cl_free_rx_ring(struct ishtp_cl *cl)
{
struct ishtp_cl_rb *rb;
unsigned long flags;
/* release allocated memory - pass over free_rb_list */
spin_lock_irqsave(&cl->free_list_spinlock, flags);
while (!list_empty(&cl->free_rb_list.list)) {
rb = list_entry(cl->free_rb_list.list.next, struct ishtp_cl_rb,
list);
list_del(&rb->list);
kfree(rb->buffer.data);
kfree(rb);
}
spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
/* release allocated memory - pass over in_process_list */
spin_lock_irqsave(&cl->in_process_spinlock, flags);
while (!list_empty(&cl->in_process_list.list)) {
rb = list_entry(cl->in_process_list.list.next,
struct ishtp_cl_rb, list);
list_del(&rb->list);
kfree(rb->buffer.data);
kfree(rb);
}
spin_unlock_irqrestore(&cl->in_process_spinlock, flags);
}
/**
* ishtp_cl_free_tx_ring() - Free TX ring buffers
* @cl: client device instance
*
* Free TX ring buffers
*/
void ishtp_cl_free_tx_ring(struct ishtp_cl *cl)
{
struct ishtp_cl_tx_ring *tx_buf;
unsigned long flags;
spin_lock_irqsave(&cl->tx_free_list_spinlock, flags);
/* release allocated memory - pass over tx_free_list */
while (!list_empty(&cl->tx_free_list.list)) {
tx_buf = list_entry(cl->tx_free_list.list.next,
struct ishtp_cl_tx_ring, list);
list_del(&tx_buf->list);
--cl->tx_ring_free_size;
kfree(tx_buf->send_buf.data);
kfree(tx_buf);
}
spin_unlock_irqrestore(&cl->tx_free_list_spinlock, flags);
spin_lock_irqsave(&cl->tx_list_spinlock, flags);
/* release allocated memory - pass over tx_list */
while (!list_empty(&cl->tx_list.list)) {
tx_buf = list_entry(cl->tx_list.list.next,
struct ishtp_cl_tx_ring, list);
list_del(&tx_buf->list);
kfree(tx_buf->send_buf.data);
kfree(tx_buf);
}
spin_unlock_irqrestore(&cl->tx_list_spinlock, flags);
}
/**
* ishtp_io_rb_free() - Free IO request block
* @rb: IO request block
*
* Free io request block memory
*/
void ishtp_io_rb_free(struct ishtp_cl_rb *rb)
{
if (rb == NULL)
return;
kfree(rb->buffer.data);
kfree(rb);
}
/**
* ishtp_io_rb_init() - Allocate and init IO request block
* @cl: client device instance
*
* Allocate and initialize request block
*
* Return: Allocted IO request block pointer
*/
struct ishtp_cl_rb *ishtp_io_rb_init(struct ishtp_cl *cl)
{
struct ishtp_cl_rb *rb;
rb = kzalloc_obj(struct ishtp_cl_rb);
if (!rb)
return NULL;
INIT_LIST_HEAD(&rb->list);
rb->cl = cl;
rb->buf_idx = 0;
return rb;
}
/**
* ishtp_io_rb_alloc_buf() - Allocate and init response buffer
* @rb: IO request block
* @length: length of response buffer
*
* Allocate respose buffer
*
* Return: 0 on success else -ENOMEM
*/
int ishtp_io_rb_alloc_buf(struct ishtp_cl_rb *rb, size_t length)
{
if (!rb)
return -EINVAL;
if (length == 0)
return 0;
rb->buffer.data = kmalloc(length, GFP_KERNEL);
if (!rb->buffer.data)
return -ENOMEM;
rb->buffer.size = length;
return 0;
}
/**
* ishtp_cl_io_rb_recycle() - Recycle IO request blocks
* @rb: IO request block
*
* Re-append rb to its client's free list and send flow control if needed
*
* Return: 0 on success else -EFAULT
*/
int ishtp_cl_io_rb_recycle(struct ishtp_cl_rb *rb)
{
struct ishtp_cl *cl;
int rets = 0;
unsigned long flags;
if (!rb || !rb->cl)
return -EFAULT;
cl = rb->cl;
spin_lock_irqsave(&cl->free_list_spinlock, flags);
list_add_tail(&rb->list, &cl->free_rb_list.list);
spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
/*
* If we returned the first buffer to empty 'free' list,
* send flow control
*/
if (!cl->out_flow_ctrl_creds)
rets = ishtp_cl_read_start(cl);
return rets;
}
EXPORT_SYMBOL(ishtp_cl_io_rb_recycle);
/**
* ishtp_cl_rx_get_rb() -Get a rb from client device rx buffer list
* @cl: Pointer to client device instance
*
* Check client device in-processing buffer list and get a rb from it.
*
* Return: rb pointer if buffer list isn't empty else NULL
*/
struct ishtp_cl_rb *ishtp_cl_rx_get_rb(struct ishtp_cl *cl)
{
unsigned long rx_flags;
struct ishtp_cl_rb *rb;
spin_lock_irqsave(&cl->in_process_spinlock, rx_flags);
rb = list_first_entry_or_null(&cl->in_process_list.list,
struct ishtp_cl_rb, list);
if (rb)
list_del_init(&rb->list);
spin_unlock_irqrestore(&cl->in_process_spinlock, rx_flags);
return rb;
}
EXPORT_SYMBOL(ishtp_cl_rx_get_rb);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,152 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ISHTP client logic
*
* Copyright (c) 2003-2016, Intel Corporation.
*/
#ifndef _ISHTP_CLIENT_H_
#define _ISHTP_CLIENT_H_
#include <linux/types.h>
#include "ishtp-dev.h"
/* Tx and Rx ring size */
#define CL_DEF_RX_RING_SIZE 2
#define CL_DEF_TX_RING_SIZE 2
#define CL_MAX_RX_RING_SIZE 32
#define CL_MAX_TX_RING_SIZE 32
#define DMA_SLOT_SIZE 4096
/* Number of IPC fragments after which it's worth sending via DMA */
#define DMA_WORTH_THRESHOLD 3
/* DMA/IPC Tx paths. Other the default means enforcement */
#define CL_TX_PATH_DEFAULT 0
#define CL_TX_PATH_IPC 1
#define CL_TX_PATH_DMA 2
/* Client Tx buffer list entry */
struct ishtp_cl_tx_ring {
struct list_head list;
struct ishtp_msg_data send_buf;
};
/* ISHTP client instance */
struct ishtp_cl {
struct list_head link;
struct ishtp_device *dev;
enum cl_state state;
int status;
/* Link to ISHTP bus device */
struct ishtp_cl_device *device;
/* ID of client connected */
uint8_t host_client_id;
uint8_t fw_client_id;
uint8_t ishtp_flow_ctrl_creds;
uint8_t out_flow_ctrl_creds;
/* dma */
int last_tx_path;
/* 0: ack wasn't received,1:ack was received */
int last_dma_acked;
unsigned char *last_dma_addr;
/* 0: ack wasn't received,1:ack was received */
int last_ipc_acked;
/* Rx ring buffer pool */
unsigned int rx_ring_size;
struct ishtp_cl_rb free_rb_list;
spinlock_t free_list_spinlock;
/* Rx in-process list */
struct ishtp_cl_rb in_process_list;
spinlock_t in_process_spinlock;
/* Client Tx buffers list */
unsigned int tx_ring_size;
struct ishtp_cl_tx_ring tx_list, tx_free_list;
int tx_ring_free_size;
spinlock_t tx_list_spinlock;
spinlock_t tx_free_list_spinlock;
size_t tx_offs; /* Offset in buffer at head of 'tx_list' */
/**
* if we get a FC, and the list is not empty, we must know whether we
* are at the middle of sending.
* if so -need to increase FC counter, otherwise, need to start sending
* the first msg in list
* (!)This is for counting-FC implementation only. Within single-FC the
* other party may NOT send FC until it receives complete message
*/
int sending;
/* Send FC spinlock */
spinlock_t fc_spinlock;
/* wait queue for connect and disconnect response from FW */
wait_queue_head_t wait_ctrl_res;
/* Error stats */
unsigned int err_send_msg;
unsigned int err_send_fc;
/* Send/recv stats */
unsigned int send_msg_cnt_ipc;
unsigned int send_msg_cnt_dma;
unsigned int recv_msg_cnt_ipc;
unsigned int recv_msg_cnt_dma;
unsigned int recv_msg_num_frags;
unsigned int ishtp_flow_ctrl_cnt;
unsigned int out_flow_ctrl_cnt;
/* Rx msg ... out FC timing */
ktime_t ts_rx;
ktime_t ts_out_fc;
ktime_t ts_max_fc_delay;
void *client_data;
};
/* Client connection managenment internal functions */
int ishtp_fw_cl_by_id(struct ishtp_device *dev, uint8_t client_id);
void ishtp_cl_send_msg(struct ishtp_device *dev, struct ishtp_cl *cl);
void recv_ishtp_cl_msg(struct ishtp_device *dev,
struct ishtp_msg_hdr *ishtp_hdr);
int ishtp_cl_read_start(struct ishtp_cl *cl);
/* Ring Buffer I/F */
int ishtp_cl_alloc_rx_ring(struct ishtp_cl *cl);
int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl);
void ishtp_cl_free_rx_ring(struct ishtp_cl *cl);
void ishtp_cl_free_tx_ring(struct ishtp_cl *cl);
/* DMA I/F functions */
void recv_ishtp_cl_msg_dma(struct ishtp_device *dev, void *msg,
struct dma_xfer_hbm *hbm);
void ishtp_cl_alloc_dma_buf(struct ishtp_device *dev);
void ishtp_cl_free_dma_buf(struct ishtp_device *dev);
void *ishtp_cl_get_dma_send_buf(struct ishtp_device *dev,
uint32_t size);
void ishtp_cl_release_dma_acked_mem(struct ishtp_device *dev,
void *msg_addr,
uint8_t size);
/* Request blocks alloc/free I/F */
struct ishtp_cl_rb *ishtp_io_rb_init(struct ishtp_cl *cl);
void ishtp_io_rb_free(struct ishtp_cl_rb *priv_rb);
int ishtp_io_rb_alloc_buf(struct ishtp_cl_rb *rb, size_t length);
/**
* ishtp_cl_cmp_id - tells if file private data have same id
* returns true - if ids are the same and not NULL
*/
static inline bool ishtp_cl_cmp_id(const struct ishtp_cl *cl1,
const struct ishtp_cl *cl2)
{
return cl1 && cl2 &&
(cl1->host_client_id == cl2->host_client_id) &&
(cl1->fw_client_id == cl2->fw_client_id);
}
#endif /* _ISHTP_CLIENT_H_ */
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ISHTP DMA I/F functions
*
* Copyright (c) 2003-2016, Intel Corporation.
*/
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include "ishtp-dev.h"
#include "client.h"
/**
* ishtp_cl_alloc_dma_buf() - Allocate DMA RX and TX buffer
* @dev: ishtp device
*
* Allocate RX and TX DMA buffer once during bus setup.
* It allocates 1MB, RX and TX DMA buffer, which are divided
* into slots.
*/
void ishtp_cl_alloc_dma_buf(struct ishtp_device *dev)
{
dma_addr_t h;
if (dev->ishtp_host_dma_tx_buf)
return;
dev->ishtp_host_dma_tx_buf_size = 1024*1024;
dev->ishtp_host_dma_rx_buf_size = 1024*1024;
/* Allocate Tx buffer and init usage bitmap */
dev->ishtp_host_dma_tx_buf = dma_alloc_coherent(dev->devc,
dev->ishtp_host_dma_tx_buf_size,
&h, GFP_KERNEL);
if (dev->ishtp_host_dma_tx_buf)
dev->ishtp_host_dma_tx_buf_phys = h;
dev->ishtp_dma_num_slots = dev->ishtp_host_dma_tx_buf_size /
DMA_SLOT_SIZE;
dev->ishtp_dma_tx_map = kcalloc(dev->ishtp_dma_num_slots,
sizeof(uint8_t),
GFP_KERNEL);
spin_lock_init(&dev->ishtp_dma_tx_lock);
/* Allocate Rx buffer */
dev->ishtp_host_dma_rx_buf = dma_alloc_coherent(dev->devc,
dev->ishtp_host_dma_rx_buf_size,
&h, GFP_KERNEL);
if (dev->ishtp_host_dma_rx_buf)
dev->ishtp_host_dma_rx_buf_phys = h;
}
/**
* ishtp_cl_free_dma_buf() - Free DMA RX and TX buffer
* @dev: ishtp device
*
* Free DMA buffer when all clients are released. This is
* only happens during error path in ISH built in driver
* model
*/
void ishtp_cl_free_dma_buf(struct ishtp_device *dev)
{
dma_addr_t h;
if (dev->ishtp_host_dma_tx_buf) {
h = dev->ishtp_host_dma_tx_buf_phys;
dma_free_coherent(dev->devc, dev->ishtp_host_dma_tx_buf_size,
dev->ishtp_host_dma_tx_buf, h);
}
if (dev->ishtp_host_dma_rx_buf) {
h = dev->ishtp_host_dma_rx_buf_phys;
dma_free_coherent(dev->devc, dev->ishtp_host_dma_rx_buf_size,
dev->ishtp_host_dma_rx_buf, h);
}
kfree(dev->ishtp_dma_tx_map);
dev->ishtp_host_dma_tx_buf = NULL;
dev->ishtp_host_dma_rx_buf = NULL;
dev->ishtp_dma_tx_map = NULL;
}
/*
* ishtp_cl_get_dma_send_buf() - Get a DMA memory slot
* @dev: ishtp device
* @size: Size of memory to get
*
* Find and return free address of "size" bytes in dma tx buffer.
* the function will mark this address as "in-used" memory.
*
* Return: NULL when no free buffer else a buffer to copy
*/
void *ishtp_cl_get_dma_send_buf(struct ishtp_device *dev,
uint32_t size)
{
unsigned long flags;
int i, j, free;
/* additional slot is needed if there is rem */
int required_slots = (size / DMA_SLOT_SIZE)
+ 1 * (size % DMA_SLOT_SIZE != 0);
if (!dev->ishtp_dma_tx_map) {
dev_err(dev->devc, "Fail to allocate Tx map\n");
return NULL;
}
spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags);
for (i = 0; i <= (dev->ishtp_dma_num_slots - required_slots); i++) {
free = 1;
for (j = 0; j < required_slots; j++)
if (dev->ishtp_dma_tx_map[i+j]) {
free = 0;
i += j;
break;
}
if (free) {
/* mark memory as "caught" */
for (j = 0; j < required_slots; j++)
dev->ishtp_dma_tx_map[i+j] = 1;
spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
return (i * DMA_SLOT_SIZE) +
(unsigned char *)dev->ishtp_host_dma_tx_buf;
}
}
spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
dev_err(dev->devc, "No free DMA buffer to send msg\n");
return NULL;
}
/*
* ishtp_cl_release_dma_acked_mem() - Release DMA memory slot
* @dev: ishtp device
* @msg_addr: message address of slot
* @size: Size of memory to get
*
* Release_dma_acked_mem - returnes the acked memory to free list.
* (from msg_addr, size bytes long)
*/
void ishtp_cl_release_dma_acked_mem(struct ishtp_device *dev,
void *msg_addr,
uint8_t size)
{
unsigned long flags;
int acked_slots = (size / DMA_SLOT_SIZE)
+ 1 * (size % DMA_SLOT_SIZE != 0);
int i, j;
if ((msg_addr - dev->ishtp_host_dma_tx_buf) % DMA_SLOT_SIZE) {
dev_err(dev->devc, "Bad DMA Tx ack address\n");
return;
}
if (!dev->ishtp_dma_tx_map) {
dev_err(dev->devc, "Fail to allocate Tx map\n");
return;
}
i = (msg_addr - dev->ishtp_host_dma_tx_buf) / DMA_SLOT_SIZE;
spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags);
for (j = 0; j < acked_slots; j++) {
if ((i + j) >= dev->ishtp_dma_num_slots ||
!dev->ishtp_dma_tx_map[i+j]) {
/* no such slot, or memory is already free */
spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
dev_err(dev->devc, "Bad DMA Tx ack address\n");
return;
}
dev->ishtp_dma_tx_map[i+j] = 0;
}
spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,314 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ISHTP bus layer messages handling
*
* Copyright (c) 2003-2016, Intel Corporation.
*/
#ifndef _ISHTP_HBM_H_
#define _ISHTP_HBM_H_
#include <linux/uuid.h>
struct ishtp_device;
struct ishtp_msg_hdr;
struct ishtp_cl;
/*
* Timeouts in Seconds
*/
#define ISHTP_INTEROP_TIMEOUT 7 /* Timeout on ready message */
#define ISHTP_CL_CONNECT_TIMEOUT 15 /* HPS: Client Connect Timeout */
/*
* ISHTP Version
*/
#define HBM_MINOR_VERSION 0
#define HBM_MAJOR_VERSION 1
/* Host bus message command opcode */
#define ISHTP_HBM_CMD_OP_MSK 0x7f
/* Host bus message command RESPONSE */
#define ISHTP_HBM_CMD_RES_MSK 0x80
/*
* ISHTP Bus Message Command IDs
*/
#define HOST_START_REQ_CMD 0x01
#define HOST_START_RES_CMD 0x81
#define HOST_STOP_REQ_CMD 0x02
#define HOST_STOP_RES_CMD 0x82
#define FW_STOP_REQ_CMD 0x03
#define HOST_ENUM_REQ_CMD 0x04
#define HOST_ENUM_RES_CMD 0x84
#define HOST_CLIENT_PROPERTIES_REQ_CMD 0x05
#define HOST_CLIENT_PROPERTIES_RES_CMD 0x85
#define CLIENT_CONNECT_REQ_CMD 0x06
#define CLIENT_CONNECT_RES_CMD 0x86
#define CLIENT_DISCONNECT_REQ_CMD 0x07
#define CLIENT_DISCONNECT_RES_CMD 0x87
#define ISHTP_FLOW_CONTROL_CMD 0x08
#define DMA_BUFFER_ALLOC_NOTIFY 0x11
#define DMA_BUFFER_ALLOC_RESPONSE 0x91
#define DMA_XFER 0x12
#define DMA_XFER_ACK 0x92
/*
* ISHTP Stop Reason
* used by hbm_host_stop_request.reason
*/
#define DRIVER_STOP_REQUEST 0x00
/*
* ISHTP BUS Interface Section
*/
struct ishtp_msg_hdr {
uint32_t fw_addr:8;
uint32_t host_addr:8;
uint32_t length:9;
uint32_t reserved:6;
uint32_t msg_complete:1;
} __packed;
struct ishtp_bus_message {
uint8_t hbm_cmd;
uint8_t data[];
} __packed;
/**
* struct hbm_cl_cmd - client specific host bus command
* CONNECT, DISCONNECT, and FlOW CONTROL
*
* @hbm_cmd - bus message command header
* @fw_addr - address of the fw client
* @host_addr - address of the client in the driver
* @data
*/
struct ishtp_hbm_cl_cmd {
uint8_t hbm_cmd;
uint8_t fw_addr;
uint8_t host_addr;
uint8_t data;
};
struct hbm_version {
uint8_t minor_version;
uint8_t major_version;
} __packed;
struct hbm_host_version_request {
uint8_t hbm_cmd;
uint8_t reserved;
struct hbm_version host_version;
} __packed;
struct hbm_host_version_response {
uint8_t hbm_cmd;
uint8_t host_version_supported;
struct hbm_version fw_max_version;
} __packed;
struct hbm_host_stop_request {
uint8_t hbm_cmd;
uint8_t reason;
uint8_t reserved[2];
} __packed;
struct hbm_host_stop_response {
uint8_t hbm_cmd;
uint8_t reserved[3];
} __packed;
struct hbm_host_enum_request {
uint8_t hbm_cmd;
uint8_t reserved[3];
} __packed;
struct hbm_host_enum_response {
uint8_t hbm_cmd;
uint8_t reserved[3];
uint8_t valid_addresses[32];
} __packed;
struct ishtp_client_properties {
guid_t protocol_name;
uint8_t protocol_version;
uint8_t max_number_of_connections;
uint8_t fixed_address;
uint8_t single_recv_buf;
uint32_t max_msg_length;
uint8_t dma_hdr_len;
#define ISHTP_CLIENT_DMA_ENABLED 0x80
uint8_t reserved4;
uint8_t reserved5;
uint8_t reserved6;
} __packed;
struct hbm_props_request {
uint8_t hbm_cmd;
uint8_t address;
uint8_t reserved[2];
} __packed;
struct hbm_props_response {
uint8_t hbm_cmd;
uint8_t address;
uint8_t status;
uint8_t reserved[1];
struct ishtp_client_properties client_properties;
} __packed;
/**
* struct hbm_client_connect_request - connect/disconnect request
*
* @hbm_cmd - bus message command header
* @fw_addr - address of the fw client
* @host_addr - address of the client in the driver
* @reserved
*/
struct hbm_client_connect_request {
uint8_t hbm_cmd;
uint8_t fw_addr;
uint8_t host_addr;
uint8_t reserved;
} __packed;
/**
* struct hbm_client_connect_response - connect/disconnect response
*
* @hbm_cmd - bus message command header
* @fw_addr - address of the fw client
* @host_addr - address of the client in the driver
* @status - status of the request
*/
struct hbm_client_connect_response {
uint8_t hbm_cmd;
uint8_t fw_addr;
uint8_t host_addr;
uint8_t status;
} __packed;
#define ISHTP_FC_MESSAGE_RESERVED_LENGTH 5
struct hbm_flow_control {
uint8_t hbm_cmd;
uint8_t fw_addr;
uint8_t host_addr;
uint8_t reserved[ISHTP_FC_MESSAGE_RESERVED_LENGTH];
} __packed;
struct dma_alloc_notify {
uint8_t hbm;
uint8_t status;
uint8_t reserved[2];
uint32_t buf_size;
uint64_t buf_address;
/* [...] May come more size/address pairs */
} __packed;
struct dma_xfer_hbm {
uint8_t hbm;
uint8_t fw_client_id;
uint8_t host_client_id;
uint8_t reserved;
uint64_t msg_addr;
uint32_t msg_length;
uint32_t reserved2;
} __packed;
/* System state */
#define ISHTP_SYSTEM_STATE_CLIENT_ADDR 13
#define SYSTEM_STATE_SUBSCRIBE 0x1
#define SYSTEM_STATE_STATUS 0x2
#define SYSTEM_STATE_QUERY_SUBSCRIBERS 0x3
#define SYSTEM_STATE_STATE_CHANGE_REQ 0x4
/*indicates suspend and resume states*/
#define CONNECTED_STANDBY_STATE_BIT (1<<0)
#define SUSPEND_STATE_BIT (1<<1)
struct ish_system_states_header {
uint32_t cmd;
uint32_t cmd_status; /*responses will have this set*/
} __packed;
struct ish_system_states_subscribe {
struct ish_system_states_header hdr;
uint32_t states;
} __packed;
struct ish_system_states_status {
struct ish_system_states_header hdr;
uint32_t supported_states;
uint32_t states_status;
} __packed;
struct ish_system_states_query_subscribers {
struct ish_system_states_header hdr;
} __packed;
struct ish_system_states_state_change_req {
struct ish_system_states_header hdr;
uint32_t requested_states;
uint32_t states_status;
} __packed;
/**
* enum ishtp_hbm_state - host bus message protocol state
*
* @ISHTP_HBM_IDLE : protocol not started
* @ISHTP_HBM_START : start request message was sent
* @ISHTP_HBM_ENUM_CLIENTS : enumeration request was sent
* @ISHTP_HBM_CLIENT_PROPERTIES : acquiring clients properties
*/
enum ishtp_hbm_state {
ISHTP_HBM_IDLE = 0,
ISHTP_HBM_START,
ISHTP_HBM_STARTED,
ISHTP_HBM_ENUM_CLIENTS,
ISHTP_HBM_CLIENT_PROPERTIES,
ISHTP_HBM_WORKING,
ISHTP_HBM_STOPPED,
};
static inline void ishtp_hbm_hdr(struct ishtp_msg_hdr *hdr, size_t length)
{
hdr->host_addr = 0;
hdr->fw_addr = 0;
hdr->length = length;
hdr->msg_complete = 1;
hdr->reserved = 0;
}
int ishtp_hbm_start_req(struct ishtp_device *dev);
int ishtp_hbm_start_wait(struct ishtp_device *dev);
int ishtp_hbm_cl_flow_control_req(struct ishtp_device *dev,
struct ishtp_cl *cl);
int ishtp_hbm_cl_disconnect_req(struct ishtp_device *dev, struct ishtp_cl *cl);
int ishtp_hbm_cl_connect_req(struct ishtp_device *dev, struct ishtp_cl *cl);
void ishtp_hbm_enum_clients_req(struct ishtp_device *dev);
void bh_hbm_work_fn(struct work_struct *work);
void recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr);
void recv_fixed_cl_msg(struct ishtp_device *dev,
struct ishtp_msg_hdr *ishtp_hdr);
void ishtp_hbm_dispatch(struct ishtp_device *dev,
struct ishtp_bus_message *hdr);
void ishtp_query_subscribers(struct ishtp_device *dev);
/* Exported I/F */
void ishtp_send_suspend(struct ishtp_device *dev);
void ishtp_send_resume(struct ishtp_device *dev);
#endif /* _ISHTP_HBM_H_ */
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Initialization protocol for ISHTP driver
*
* Copyright (c) 2003-2016, Intel Corporation.
*/
#include <linux/devm-helpers.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include "ishtp-dev.h"
#include "hbm.h"
#include "client.h"
#include "loader.h"
/**
* ishtp_device_init() - ishtp device init
* @dev: ISHTP device instance
*
* After ISHTP device is alloacted, this function is used to initialize
* each field which includes spin lock, work struct and lists
*/
void ishtp_device_init(struct ishtp_device *dev)
{
int ret;
dev->dev_state = ISHTP_DEV_INITIALIZING;
INIT_LIST_HEAD(&dev->cl_list);
INIT_LIST_HEAD(&dev->device_list);
dev->rd_msg_fifo_head = 0;
dev->rd_msg_fifo_tail = 0;
spin_lock_init(&dev->rd_msg_spinlock);
init_waitqueue_head(&dev->wait_hbm_recvd_msg);
init_waitqueue_head(&dev->wait_loader_recvd_msg);
spin_lock_init(&dev->read_list_spinlock);
spin_lock_init(&dev->device_lock);
spin_lock_init(&dev->device_list_lock);
spin_lock_init(&dev->cl_list_lock);
spin_lock_init(&dev->fw_clients_lock);
INIT_WORK(&dev->bh_hbm_work, bh_hbm_work_fn);
bitmap_zero(dev->host_clients_map, ISHTP_CLIENTS_MAX);
dev->open_handle_count = 0;
/*
* Reserving client ID 0 for ISHTP Bus Message communications
*/
bitmap_set(dev->host_clients_map, 0, 1);
INIT_LIST_HEAD(&dev->read_list.list);
ret = devm_work_autocancel(dev->devc, &dev->work_fw_loader, ishtp_loader_work);
if (ret)
dev_err_probe(dev->devc, ret, "Failed to initialise FW loader work\n");
}
EXPORT_SYMBOL(ishtp_device_init);
/**
* ishtp_start() - Start ISH processing
* @dev: ISHTP device instance
*
* Start ISHTP processing by sending query subscriber message
*
* Return: 0 on success else -ENODEV
*/
int ishtp_start(struct ishtp_device *dev)
{
if (ishtp_hbm_start_wait(dev)) {
dev_err(dev->devc, "HBM haven't started");
goto err;
}
/* suspend & resume notification - send QUERY_SUBSCRIBERS msg */
ishtp_query_subscribers(dev);
return 0;
err:
dev_err(dev->devc, "link layer initialization failed.\n");
dev->dev_state = ISHTP_DEV_DISABLED;
return -ENODEV;
}
EXPORT_SYMBOL(ishtp_start);
@@ -0,0 +1,287 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Most ISHTP provider device and ISHTP logic declarations
*
* Copyright (c) 2003-2016, Intel Corporation.
*/
#ifndef _ISHTP_DEV_H_
#define _ISHTP_DEV_H_
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/intel-ish-client-if.h>
#include "bus.h"
#include "hbm.h"
#define IPC_PAYLOAD_SIZE 128
#define ISHTP_RD_MSG_BUF_SIZE IPC_PAYLOAD_SIZE
#define IPC_FULL_MSG_SIZE 132
/* Number of messages to be held in ISR->BH FIFO */
#define RD_INT_FIFO_SIZE 64
/*
* Number of IPC messages to be held in Tx FIFO, to be sent by ISR -
* Tx complete interrupt or RX_COMPLETE handler
*/
#define IPC_TX_FIFO_SIZE 512
/*
* Number of Maximum ISHTP Clients
*/
#define ISHTP_CLIENTS_MAX 256
/*
* Number of File descriptors/handles
* that can be opened to the driver.
*
* Limit to 255: 256 Total Clients
* minus internal client for ISHTP Bus Messages
*/
#define ISHTP_MAX_OPEN_HANDLE_COUNT (ISHTP_CLIENTS_MAX - 1)
/* Internal Clients Number */
#define ISHTP_HOST_CLIENT_ID_ANY (-1)
#define ISHTP_HBM_HOST_CLIENT_ID 0
#define MAX_DMA_DELAY 20
/* 300ms to get resume response */
#define WAIT_FOR_RESUME_ACK_MS 300
/* ISHTP device states */
enum ishtp_dev_state {
ISHTP_DEV_INITIALIZING = 0,
ISHTP_DEV_INIT_CLIENTS,
ISHTP_DEV_ENABLED,
ISHTP_DEV_RESETTING,
ISHTP_DEV_DISABLED,
ISHTP_DEV_POWER_DOWN,
ISHTP_DEV_POWER_UP
};
struct ishtp_cl;
/**
* struct ishtp_fw_client - representation of fw client
*
* @props - client properties
* @client_id - fw client id
*/
struct ishtp_fw_client {
struct ishtp_client_properties props;
uint8_t client_id;
};
/*
* Control info for IPC messages ISHTP/IPC sending FIFO -
* list with inline data buffer
* This structure will be filled with parameters submitted
* by the caller glue layer
* 'buf' may be pointing to the external buffer or to 'inline_data'
* 'offset' will be initialized to 0 by submitting
*
* 'ipc_send_compl' is intended for use by clients that send fragmented
* messages. When a fragment is sent down to IPC msg regs,
* it will be called.
* If it has more fragments to send, it will do it. With last fragment
* it will send appropriate ISHTP "message-complete" flag.
* It will remove the outstanding message
* (mark outstanding buffer as available).
* If counting flow control is in work and there are more flow control
* credits, it can put the next client message queued in cl.
* structure for IPC processing.
*
*/
struct wr_msg_ctl_info {
/* Will be called with 'ipc_send_compl_prm' as parameter */
void (*ipc_send_compl)(void *);
void *ipc_send_compl_prm;
size_t length;
struct list_head link;
unsigned char inline_data[IPC_FULL_MSG_SIZE];
};
/*
* The ISHTP layer talks to hardware IPC message using the following
* callbacks
*/
struct ishtp_hw_ops {
int (*hw_reset)(struct ishtp_device *dev);
int (*ipc_reset)(struct ishtp_device *dev);
uint32_t (*ipc_get_header)(struct ishtp_device *dev, int length,
int busy);
int (*write)(struct ishtp_device *dev,
void (*ipc_send_compl)(void *), void *ipc_send_compl_prm,
unsigned char *msg, int length);
uint32_t (*ishtp_read_hdr)(const struct ishtp_device *dev);
int (*ishtp_read)(struct ishtp_device *dev, unsigned char *buffer,
unsigned long buffer_length);
uint32_t (*get_fw_status)(struct ishtp_device *dev);
void (*sync_fw_clock)(struct ishtp_device *dev);
bool (*dma_no_cache_snooping)(struct ishtp_device *dev);
};
/**
* struct ishtp_driver_data - Driver-specific data for ISHTP devices
*
* This structure holds driver-specific data that can be associated with each
* ISHTP device instance. It allows for the storage of data that is unique to
* a particular driver or hardware variant.
*
* @fw_generation: The generation name associated with a specific hardware
* variant of the Intel Integrated Sensor Hub (ISH). This allows
* the driver to load the correct firmware based on the device's
* hardware variant. For example, "lnlm" for the Lunar Lake-M
* platform. The generation name must not exceed 8 characters
* in length.
*/
struct ishtp_driver_data {
char *fw_generation;
};
struct ish_version {
u16 major;
u16 minor;
u16 hotfix;
u16 build;
};
/**
* struct ishtp_device - ISHTP private device struct
*/
struct ishtp_device {
struct device *devc; /* pointer to lowest device */
struct pci_dev *pdev; /* PCI device to get device ids */
struct ishtp_driver_data *driver_data; /* pointer to driver-specific data */
/* waitq for waiting for suspend response */
wait_queue_head_t suspend_wait;
bool suspend_flag; /* Suspend is active */
/* waitq for waiting for resume response */
wait_queue_head_t resume_wait;
bool resume_flag; /*Resume is active */
/*
* lock for the device, for everything that doesn't have
* a dedicated spinlock
*/
spinlock_t device_lock;
bool recvd_hw_ready;
struct hbm_version version;
int transfer_path; /* Choice of transfer path: IPC or DMA */
/* Alloc a dedicated unbound workqueue for ishtp device */
struct workqueue_struct *unbound_wq;
/* work structure for scheduling firmware loading tasks */
struct work_struct work_fw_loader;
/* waitq for waiting for command response from the firmware loader */
wait_queue_head_t wait_loader_recvd_msg;
/* indicating whether a message from the firmware loader has been received */
bool fw_loader_received;
/* pointer to a buffer for receiving messages from the firmware loader */
void *fw_loader_rx_buf;
/* size of the buffer pointed to by fw_loader_rx_buf */
int fw_loader_rx_size;
/* ishtp device states */
enum ishtp_dev_state dev_state;
enum ishtp_hbm_state hbm_state;
/* driver read queue */
struct ishtp_cl_rb read_list;
spinlock_t read_list_spinlock;
/* list of ishtp_cl's */
struct list_head cl_list;
spinlock_t cl_list_lock;
long open_handle_count;
/* List of bus devices */
struct list_head device_list;
spinlock_t device_list_lock;
/* waiting queues for receive message from FW */
wait_queue_head_t wait_hw_ready;
wait_queue_head_t wait_hbm_recvd_msg;
/* FIFO for input messages for BH processing */
unsigned char rd_msg_fifo[RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE];
unsigned int rd_msg_fifo_head, rd_msg_fifo_tail;
spinlock_t rd_msg_spinlock;
struct work_struct bh_hbm_work;
/* IPC write queue */
struct list_head wr_processing_list, wr_free_list;
/* For both processing list and free list */
spinlock_t wr_processing_spinlock;
struct ishtp_fw_client *fw_clients; /*Note:memory has to be allocated*/
DECLARE_BITMAP(fw_clients_map, ISHTP_CLIENTS_MAX);
DECLARE_BITMAP(host_clients_map, ISHTP_CLIENTS_MAX);
uint8_t fw_clients_num;
uint8_t fw_client_presentation_num;
uint8_t fw_client_index;
spinlock_t fw_clients_lock;
/* TX DMA buffers and slots */
int ishtp_host_dma_enabled;
void *ishtp_host_dma_tx_buf;
unsigned int ishtp_host_dma_tx_buf_size;
uint64_t ishtp_host_dma_tx_buf_phys;
int ishtp_dma_num_slots;
/* map of 4k blocks in Tx dma buf: 0-free, 1-used */
uint8_t *ishtp_dma_tx_map;
spinlock_t ishtp_dma_tx_lock;
/* RX DMA buffers and slots */
void *ishtp_host_dma_rx_buf;
unsigned int ishtp_host_dma_rx_buf_size;
uint64_t ishtp_host_dma_rx_buf_phys;
/* Dump to trace buffers if enabled*/
ishtp_print_log print_log;
/* Base version of Intel's released firmware */
struct ish_version base_ver;
/* Vendor-customized project version */
struct ish_version prj_ver;
/* Debug stats */
unsigned int ipc_rx_cnt;
unsigned long long ipc_rx_bytes_cnt;
unsigned int ipc_tx_cnt;
unsigned long long ipc_tx_bytes_cnt;
/* Time of the last clock sync */
unsigned long prev_sync;
const struct ishtp_hw_ops *ops;
size_t mtu;
uint32_t ishtp_msg_hdr;
char hw[] __aligned(sizeof(void *));
};
static inline unsigned long ishtp_secs_to_jiffies(unsigned long sec)
{
return msecs_to_jiffies(sec * MSEC_PER_SEC);
}
/*
* Register Access Function
*/
static inline int ish_ipc_reset(struct ishtp_device *dev)
{
return dev->ops->ipc_reset(dev);
}
/* Exported function */
void ishtp_device_init(struct ishtp_device *dev);
int ishtp_start(struct ishtp_device *dev);
#endif /*_ISHTP_DEV_H_*/
@@ -0,0 +1,486 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ISHTP firmware loader function
*
* Copyright (c) 2024, Intel Corporation.
*
* This module implements the functionality to load the main ISH firmware from the host, starting
* with the Lunar Lake generation. It leverages a new method that enhances space optimization and
* flexibility by dividing the ISH firmware into a bootloader and main firmware.
*
* Please refer to the [Documentation](Documentation/hid/intel-ish-hid.rst) for the details on
* flows.
*
* Additionally, address potential error scenarios to ensure graceful failure handling.
* - Firmware Image Not Found:
* Occurs when `request_firmware()` cannot locate the firmware image. The ISH firmware will
* remain in a state awaiting firmware loading from the host, with no further action from
* the ISHTP driver.
* Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host.
*
* - DMA Buffer Allocation Failure:
* This happens if allocating a DMA buffer during `prepare_dma_bufs()` fails. The ISH firmware
* will stay in a waiting state, and the ISHTP driver will release any allocated DMA buffers and
* firmware without further actions.
* Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host.
*
* - Incorrect Firmware Image:
* Using an incorrect firmware image will initiate the firmware loading process but will
* eventually be refused by the ISH firmware after three unsuccessful attempts, indicated by
* returning an error code. The ISHTP driver will stop attempting after three tries.
* Recovery: A platform reset is required to retry firmware loading from the host.
*/
#define dev_fmt(fmt) "ISH loader: " fmt
#include <linux/cacheflush.h>
#include <linux/container_of.h>
#include <linux/crc32.h>
#include <linux/dev_printk.h>
#include <linux/dma-mapping.h>
#include <linux/dmi.h>
#include <linux/errno.h>
#include <linux/firmware.h>
#include <linux/gfp_types.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/pfn.h>
#include <linux/sprintf.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/wait.h>
#include "hbm.h"
#include "loader.h"
/**
* loader_write_message() - Write a message to the ISHTP device
* @dev: The ISHTP device
* @buf: The buffer containing the message
* @len: The length of the message
*
* Return: 0 on success, negative error code on failure
*/
static int loader_write_message(struct ishtp_device *dev, void *buf, int len)
{
struct ishtp_msg_hdr ishtp_hdr = {
.fw_addr = ISHTP_LOADER_CLIENT_ADDR,
.length = len,
.msg_complete = 1,
};
dev->fw_loader_received = false;
return ishtp_write_message(dev, &ishtp_hdr, buf);
}
/**
* loader_xfer_cmd() - Transfer a command to the ISHTP device
* @dev: The ISHTP device
* @req: The request buffer
* @req_len: The length of the request
* @resp: The response buffer
* @resp_len: The length of the response
*
* Return: 0 on success, negative error code on failure
*/
static int loader_xfer_cmd(struct ishtp_device *dev, void *req, int req_len,
void *resp, int resp_len)
{
union loader_msg_header req_hdr;
union loader_msg_header resp_hdr;
struct device *devc = dev->devc;
int rv;
dev->fw_loader_rx_buf = resp;
dev->fw_loader_rx_size = resp_len;
rv = loader_write_message(dev, req, req_len);
req_hdr.val32 = le32_to_cpup(req);
if (rv < 0) {
dev_err(devc, "write cmd %u failed:%d\n", req_hdr.command, rv);
return rv;
}
/* Wait the ACK */
wait_event_interruptible_timeout(dev->wait_loader_recvd_msg, dev->fw_loader_received,
ISHTP_LOADER_TIMEOUT);
resp_hdr.val32 = le32_to_cpup(resp);
dev->fw_loader_rx_size = 0;
dev->fw_loader_rx_buf = NULL;
if (!dev->fw_loader_received) {
dev_err(devc, "wait response of cmd %u timeout\n", req_hdr.command);
return -ETIMEDOUT;
}
if (!resp_hdr.is_response) {
dev_err(devc, "not a response for %u\n", req_hdr.command);
return -EBADMSG;
}
if (req_hdr.command != resp_hdr.command) {
dev_err(devc, "unexpected cmd response %u:%u\n", req_hdr.command,
resp_hdr.command);
return -EBADMSG;
}
if (resp_hdr.status) {
dev_err(devc, "cmd %u failed %u\n", req_hdr.command, resp_hdr.status);
return -EIO;
}
return 0;
}
/**
* release_dma_bufs() - Release the DMA buffer for transferring firmware fragments
* @dev: The ISHTP device
* @fragment: The ISHTP firmware fragment descriptor
* @dma_bufs: The array of DMA fragment buffers
* @fragment_size: The size of a single DMA fragment
*/
static void release_dma_bufs(struct ishtp_device *dev,
struct loader_xfer_dma_fragment *fragment,
void **dma_bufs, u32 fragment_size)
{
dma_addr_t dma_addr;
int i;
for (i = 0; i < FRAGMENT_MAX_NUM; i++) {
if (dma_bufs[i]) {
dma_addr = le64_to_cpu(fragment->fragment_tbl[i].ddr_adrs);
dma_free_coherent(dev->devc, fragment_size, dma_bufs[i], dma_addr);
dma_bufs[i] = NULL;
}
}
}
/**
* prepare_dma_bufs() - Prepare the DMA buffer for transferring firmware fragments
* @dev: The ISHTP device
* @ish_fw: The ISH firmware
* @fragment: The ISHTP firmware fragment descriptor
* @dma_bufs: The array of DMA fragment buffers
* @fragment_size: The size of a single DMA fragment
* @fragment_count: Number of fragments
*
* Return: 0 on success, negative error code on failure
*/
static int prepare_dma_bufs(struct ishtp_device *dev,
const struct firmware *ish_fw,
struct loader_xfer_dma_fragment *fragment,
void **dma_bufs, u32 fragment_size, u32 fragment_count)
{
dma_addr_t dma_addr;
u32 offset = 0;
u32 length;
int i;
for (i = 0; i < fragment_count && offset < ish_fw->size; i++) {
dma_bufs[i] = dma_alloc_coherent(dev->devc, fragment_size, &dma_addr, GFP_KERNEL);
if (!dma_bufs[i])
return -ENOMEM;
fragment->fragment_tbl[i].ddr_adrs = cpu_to_le64(dma_addr);
length = clamp(ish_fw->size - offset, 0, fragment_size);
fragment->fragment_tbl[i].length = cpu_to_le32(length);
fragment->fragment_tbl[i].fw_off = cpu_to_le32(offset);
memcpy(dma_bufs[i], ish_fw->data + offset, length);
clflush_cache_range(dma_bufs[i], fragment_size);
offset += length;
}
return 0;
}
/* Patterns with PRODUCT_FAMILY */
#define ISH_FW_FILE_VENDOR_FAMILY_NAME_SKU_FMT "intel/ish/ish_%s_%08x_%08x_%08x_%08x.bin"
#define ISH_FW_FILE_VENDOR_FAMILY_SKU_FMT "intel/ish/ish_%s_%08x_%08x_%08x.bin"
#define ISH_FW_FILE_VENDOR_FAMILY_NAME_FMT "intel/ish/ish_%s_%08x_%08x_%08x.bin"
#define ISH_FW_FILE_VENDOR_FAMILY_FMT "intel/ish/ish_%s_%08x_%08x.bin"
#define ISH_FW_FILE_VENDOR_NAME_SKU_FMT "intel/ish/ish_%s_%08x_%08x_%08x.bin"
#define ISH_FW_FILE_VENDOR_SKU_FMT "intel/ish/ish_%s_%08x_%08x.bin"
#define ISH_FW_FILE_VENDOR_NAME_FMT "intel/ish/ish_%s_%08x_%08x.bin"
#define ISH_FW_FILE_VENDOR_FMT "intel/ish/ish_%s_%08x.bin"
#define ISH_FW_FILE_DEFAULT_FMT "intel/ish/ish_%s.bin"
#define ISH_FW_FILENAME_LEN_MAX 72
#define ISH_CRC_INIT (~0u)
#define ISH_CRC_XOROUT (~0u)
static int _request_ish_firmware(const struct firmware **firmware_p,
const char *name, struct device *dev)
{
int ret;
dev_dbg(dev, "Try to load firmware: %s\n", name);
ret = firmware_request_nowarn(firmware_p, name, dev);
if (!ret)
dev_info(dev, "load firmware: %s\n", name);
return ret;
}
/**
* request_ish_firmware() - Request and load the ISH firmware.
* @firmware_p: Pointer to the firmware image.
* @dev: Device for which firmware is being requested.
*
* This function attempts to load the Integrated Sensor Hub (ISH) firmware
* for the given device in the following order, prioritizing custom firmware
* with more precise matching patterns:
*
* ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_FAMILY_CRC32)
* _$(PRODUCT_NAME_CRC32)_${PRODUCT_SKU_CRC32}.bin
*
* ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_FAMILY_CRC32)_${PRODUCT_SKU_CRC32}.bin
* ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_FAMILY_CRC32)_$(PRODUCT_NAME_CRC32).bin
* ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_FAMILY_CRC32).bin
* ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_NAME_CRC32)_${PRODUCT_SKU_CRC32}.bin
* ish_${fw_generation}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin
* ish_${fw_generation}_${SYS_VENDOR_CRC32}_$(PRODUCT_NAME_CRC32).bin
* ish_${fw_generation}_${SYS_VENDOR_CRC32}.bin
* ish_${fw_generation}.bin
*
* The driver will load the first matching firmware and skip the rest. If no
* matching firmware is found, it will proceed to the next pattern in the
* specified order. If all searches fail, the default Intel firmware, listed
* last in the order above, will be loaded.
*
* The firmware file name is constructed using CRC32 checksums of strings.
* This is done to create a valid file name that does not contain spaces
* or special characters which may be present in the original strings.
*
* The CRC-32 algorithm uses the following parameters:
* Poly: 0x04C11DB7
* Init: 0xFFFFFFFF
* RefIn: true
* RefOut: true
* XorOut: 0xFFFFFFFF
*
* Return: 0 on success, negative error code on failure.
*/
static int request_ish_firmware(const struct firmware **firmware_p,
struct device *dev)
{
const char *gen, *sys_vendor, *product_name, *product_sku;
const char *product_family;
struct ishtp_device *ishtp = dev_get_drvdata(dev);
u32 vendor_crc, name_crc, sku_crc, family_crc;
char filename[ISH_FW_FILENAME_LEN_MAX];
int ret;
gen = ishtp->driver_data->fw_generation;
sys_vendor = dmi_get_system_info(DMI_SYS_VENDOR);
product_name = dmi_get_system_info(DMI_PRODUCT_NAME);
product_sku = dmi_get_system_info(DMI_PRODUCT_SKU);
product_family = dmi_get_system_info(DMI_PRODUCT_FAMILY);
if (sys_vendor)
vendor_crc = crc32(ISH_CRC_INIT, sys_vendor, strlen(sys_vendor)) ^ ISH_CRC_XOROUT;
if (product_family)
family_crc = crc32(ISH_CRC_INIT, product_family,
strlen(product_family)) ^ ISH_CRC_XOROUT;
if (product_name)
name_crc = crc32(ISH_CRC_INIT, product_name, strlen(product_name)) ^ ISH_CRC_XOROUT;
if (product_sku)
sku_crc = crc32(ISH_CRC_INIT, product_sku, strlen(product_sku)) ^ ISH_CRC_XOROUT;
/* PRODUCT_FAMILY-extended matching */
if (sys_vendor && product_family && product_name && product_sku) {
snprintf(filename, sizeof(filename),
ISH_FW_FILE_VENDOR_FAMILY_NAME_SKU_FMT,
gen, vendor_crc, family_crc, name_crc, sku_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
if (sys_vendor && product_family && product_sku) {
snprintf(filename, sizeof(filename),
ISH_FW_FILE_VENDOR_FAMILY_SKU_FMT,
gen, vendor_crc, family_crc, sku_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
if (sys_vendor && product_family && product_name) {
snprintf(filename, sizeof(filename),
ISH_FW_FILE_VENDOR_FAMILY_NAME_FMT,
gen, vendor_crc, family_crc, name_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
if (sys_vendor && product_family) {
snprintf(filename, sizeof(filename),
ISH_FW_FILE_VENDOR_FAMILY_FMT,
gen, vendor_crc, family_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
if (sys_vendor && product_name && product_sku) {
snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_NAME_SKU_FMT, gen,
vendor_crc, name_crc, sku_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
if (sys_vendor && product_sku) {
snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_SKU_FMT, gen, vendor_crc,
sku_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
if (sys_vendor && product_name) {
snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_NAME_FMT, gen, vendor_crc,
name_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
if (sys_vendor) {
snprintf(filename, sizeof(filename), ISH_FW_FILE_VENDOR_FMT, gen, vendor_crc);
ret = _request_ish_firmware(firmware_p, filename, dev);
if (!ret)
return 0;
}
snprintf(filename, sizeof(filename), ISH_FW_FILE_DEFAULT_FMT, gen);
return _request_ish_firmware(firmware_p, filename, dev);
}
static int copy_manifest(const struct firmware *fw, struct ish_global_manifest *manifest)
{
u32 offset;
for (offset = 0; offset + sizeof(*manifest) < fw->size; offset += ISH_MANIFEST_ALIGNMENT) {
memcpy(manifest, fw->data + offset, sizeof(*manifest));
if (le32_to_cpu(manifest->sig_fourcc) == ISH_GLOBAL_SIG)
return 0;
}
return -1;
}
static void copy_ish_version(struct version_in_manifest *src, struct ish_version *dst)
{
dst->major = le16_to_cpu(src->major);
dst->minor = le16_to_cpu(src->minor);
dst->hotfix = le16_to_cpu(src->hotfix);
dst->build = le16_to_cpu(src->build);
}
/**
* ishtp_loader_work() - Load the ISHTP firmware
* @work: The work structure
*
* The ISH Loader attempts to load firmware by sending a series of commands
* to the ISH device. If a command fails to be acknowledged by the ISH device,
* the loader will retry sending the command, up to a maximum of
* ISHTP_LOADER_RETRY_TIMES.
*
* After the maximum number of retries has been reached without success, the
* ISH bootloader will return an error status code and will no longer respond
* to the driver's commands. This behavior indicates that the ISH Loader has
* encountered a critical error during the firmware loading process.
*
* In such a case, where the ISH bootloader is unresponsive after all retries
* have been exhausted, a platform reset is required to restore communication
* with the ISH device and to recover from this error state.
*/
void ishtp_loader_work(struct work_struct *work)
{
DEFINE_RAW_FLEX(struct loader_xfer_dma_fragment, fragment, fragment_tbl, FRAGMENT_MAX_NUM);
struct ishtp_device *dev = container_of(work, struct ishtp_device, work_fw_loader);
union loader_msg_header query_hdr = { .command = LOADER_CMD_XFER_QUERY, };
union loader_msg_header start_hdr = { .command = LOADER_CMD_START, };
union loader_msg_header fragment_hdr = { .command = LOADER_CMD_XFER_FRAGMENT, };
struct loader_xfer_query query = { .header = cpu_to_le32(query_hdr.val32), };
struct loader_start start = { .header = cpu_to_le32(start_hdr.val32), };
union loader_recv_message recv_msg;
struct ish_global_manifest manifest;
const struct firmware *ish_fw;
void *dma_bufs[FRAGMENT_MAX_NUM] = {};
u32 fragment_size;
u32 fragment_count;
int retry = ISHTP_LOADER_RETRY_TIMES;
int rv;
rv = request_ish_firmware(&ish_fw, dev->devc);
if (rv < 0) {
dev_err(dev->devc, "request ISH firmware failed:%d\n", rv);
return;
}
fragment->fragment.header = cpu_to_le32(fragment_hdr.val32);
fragment->fragment.xfer_mode = cpu_to_le32(LOADER_XFER_MODE_DMA);
fragment->fragment.is_last = cpu_to_le32(1);
fragment->fragment.size = cpu_to_le32(ish_fw->size);
/* Calculate the size of a single DMA fragment */
fragment_size = PFN_ALIGN(DIV_ROUND_UP(ish_fw->size, FRAGMENT_MAX_NUM));
/* Calculate the count of DMA fragments */
fragment_count = DIV_ROUND_UP(ish_fw->size, fragment_size);
fragment->fragment_cnt = cpu_to_le32(fragment_count);
rv = prepare_dma_bufs(dev, ish_fw, fragment, dma_bufs, fragment_size, fragment_count);
if (rv) {
dev_err(dev->devc, "prepare DMA buffer failed.\n");
goto out;
}
do {
query.image_size = cpu_to_le32(ish_fw->size);
rv = loader_xfer_cmd(dev, &query, sizeof(query), recv_msg.raw_data,
sizeof(struct loader_xfer_query_ack));
if (rv)
continue; /* try again if failed */
dev_dbg(dev->devc, "ISH Bootloader Version %u.%u.%u.%u\n",
recv_msg.query_ack.version_major,
recv_msg.query_ack.version_minor,
recv_msg.query_ack.version_hotfix,
recv_msg.query_ack.version_build);
rv = loader_xfer_cmd(dev, fragment,
struct_size(fragment, fragment_tbl, fragment_count),
recv_msg.raw_data, sizeof(struct loader_xfer_fragment_ack));
if (rv)
continue; /* try again if failed */
rv = loader_xfer_cmd(dev, &start, sizeof(start), recv_msg.raw_data,
sizeof(struct loader_start_ack));
if (rv)
continue; /* try again if failed */
dev_info(dev->devc, "firmware loaded. size:%zu\n", ish_fw->size);
if (!copy_manifest(ish_fw, &manifest)) {
copy_ish_version(&manifest.base_ver, &dev->base_ver);
copy_ish_version(&manifest.prj_ver, &dev->prj_ver);
dev_info(dev->devc, "FW base version: %u.%u.%u.%u\n",
dev->base_ver.major, dev->base_ver.minor,
dev->base_ver.hotfix, dev->base_ver.build);
dev_info(dev->devc, "FW project version: %u.%u.%u.%u\n",
dev->prj_ver.major, dev->prj_ver.minor,
dev->prj_ver.hotfix, dev->prj_ver.build);
}
break;
} while (--retry);
out:
release_dma_bufs(dev, fragment, dma_bufs, fragment_size);
release_firmware(ish_fw);
}
@@ -0,0 +1,265 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ISHTP firmware loader header
*
* Copyright (c) 2024, Intel Corporation.
*/
#ifndef _ISHTP_LOADER_H_
#define _ISHTP_LOADER_H_
#include <linux/bits.h>
#include <linux/jiffies.h>
#include <linux/sizes.h>
#include <linux/types.h>
#include "ishtp-dev.h"
struct work_struct;
#define LOADER_MSG_SIZE \
(IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr))
/*
* ISHTP firmware loader protocol definition
*/
#define LOADER_CMD_XFER_QUERY 0 /* SW -> FW */
#define LOADER_CMD_XFER_FRAGMENT 1 /* SW -> FW */
#define LOADER_CMD_START 2 /* SW -> FW */
/* Only support DMA mode */
#define LOADER_XFER_MODE_DMA BIT(0)
/**
* union loader_msg_header - ISHTP firmware loader message header
* @command: Command type
* @is_response: Indicates if the message is a response
* @has_next: Indicates if there is a next message
* @reserved: Reserved for future use
* @status: Status of the message
* @val32: entire header as a 32-bit value
*/
union loader_msg_header {
struct {
__u32 command:7;
__u32 is_response:1;
__u32 has_next:1;
__u32 reserved:15;
__u32 status:8;
};
__u32 val32;
};
/**
* struct loader_xfer_query - ISHTP firmware loader transfer query packet
* @header: Header of the message
* @image_size: Size of the image
*/
struct loader_xfer_query {
__le32 header;
__le32 image_size;
};
/**
* struct loader_version - ISHTP firmware loader version
* @value: Value of the version
* @major: Major version
* @minor: Minor version
* @hotfix: Hotfix version
* @build: Build version
*/
struct loader_version {
union {
__le32 value;
struct {
__u8 major;
__u8 minor;
__u8 hotfix;
__u8 build;
};
};
};
/**
* struct loader_capability - ISHTP firmware loader capability
* @max_fw_image_size: Maximum firmware image size
* @support_mode: Support mode
* @reserved: Reserved for future use
* @platform: Platform
* @max_dma_buf_size: Maximum DMA buffer size, multiples of 4096
*/
struct loader_capability {
__le32 max_fw_image_size;
__le16 support_mode;
__u8 reserved;
__u8 platform;
__le32 max_dma_buf_size;
};
/**
* struct loader_xfer_query_ack - ISHTP firmware loader transfer query acknowledgment
* @header: Header of the message
* @version_major: ISH Major version
* @version_minor: ISH Minor version
* @version_hotfix: ISH Hotfix version
* @version_build: ISH Build version
* @protocol_version: Protocol version
* @loader_version: Loader version
* @capability: Loader capability
*/
struct loader_xfer_query_ack {
__le32 header;
__le16 version_major;
__le16 version_minor;
__le16 version_hotfix;
__le16 version_build;
__le32 protocol_version;
struct loader_version loader_version;
struct loader_capability capability;
};
/**
* struct loader_xfer_fragment - ISHTP firmware loader transfer fragment
* @header: Header of the message
* @xfer_mode: Transfer mode
* @offset: Offset
* @size: Size
* @is_last: Is last
*/
struct loader_xfer_fragment {
__le32 header;
__le32 xfer_mode;
__le32 offset;
__le32 size;
__le32 is_last;
};
/**
* struct loader_xfer_fragment_ack - ISHTP firmware loader transfer fragment acknowledgment
* @header: Header of the message
*/
struct loader_xfer_fragment_ack {
__le32 header;
};
/**
* struct fragment_dscrpt - ISHTP firmware loader fragment descriptor
* @ddr_adrs: The address in host DDR
* @fw_off: The offset of the fragment in the fw image
* @length: The length of the fragment
*/
struct fragment_dscrpt {
__le64 ddr_adrs;
__le32 fw_off;
__le32 length;
};
#define FRAGMENT_MAX_NUM \
((LOADER_MSG_SIZE - sizeof(struct loader_xfer_dma_fragment)) / \
sizeof(struct fragment_dscrpt))
/**
* struct loader_xfer_dma_fragment - ISHTP firmware loader transfer DMA fragment
* @fragment: Fragment
* @fragment_cnt: How many descriptors in the fragment_tbl
* @fragment_tbl: Fragment table
*/
struct loader_xfer_dma_fragment {
struct loader_xfer_fragment fragment;
__le32 fragment_cnt;
struct fragment_dscrpt fragment_tbl[] __counted_by(fragment_cnt);
};
/**
* struct loader_start - ISHTP firmware loader start
* @header: Header of the message
*/
struct loader_start {
__le32 header;
};
/**
* struct loader_start_ack - ISHTP firmware loader start acknowledgment
* @header: Header of the message
*/
struct loader_start_ack {
__le32 header;
};
union loader_recv_message {
__le32 header;
struct loader_xfer_query_ack query_ack;
struct loader_xfer_fragment_ack fragment_ack;
struct loader_start_ack start_ack;
__u8 raw_data[LOADER_MSG_SIZE];
};
/*
* ISHTP firmware loader internal use
*/
/* ISHTP firmware loader command timeout */
#define ISHTP_LOADER_TIMEOUT msecs_to_jiffies(100)
/* ISHTP firmware loader retry times */
#define ISHTP_LOADER_RETRY_TIMES 3
/**
* struct ish_firmware_variant - ISH firmware variant
* @device: PCI Device ID
* @filename: The firmware file name
*/
struct ish_firmware_variant {
unsigned short device;
const char *filename;
};
/*
* ISHTP firmware loader API for ISHTP hbm
*/
/* ISHTP capability bit for firmware loader */
#define ISHTP_SUPPORT_CAP_LOADER BIT(4)
/* Firmware loader address */
#define ISHTP_LOADER_CLIENT_ADDR 16
/**
* ishtp_loader_work - The work function to start the firmware loading process
* @work: The work structure
*/
void ishtp_loader_work(struct work_struct *work);
/* ISH Manifest alignment in binary is 4KB aligned */
#define ISH_MANIFEST_ALIGNMENT SZ_4K
/* Signature for ISH global manifest */
#define ISH_GLOBAL_SIG 0x47485349 /* FourCC 'I', 'S', 'H', 'G' */
struct version_in_manifest {
__le16 major;
__le16 minor;
__le16 hotfix;
__le16 build;
};
/**
* struct ish_global_manifest - global manifest for ISH
* @sig_fourcc: Signature FourCC, should be 'I', 'S', 'H', 'G'.
* @len: Length of the manifest.
* @header_version: Version of the manifest header.
* @flags: Flags for additional information.
* @base_ver: Base version of Intel's released firmware.
* @reserved: Reserved space for future use.
* @prj_ver: Vendor-customized project version.
*/
struct ish_global_manifest {
__le32 sig_fourcc;
__le32 len;
__le32 header_version;
__le32 flags;
struct version_in_manifest base_ver;
__le32 reserved[13];
struct version_in_manifest prj_ver;
};
#endif /* _ISHTP_LOADER_H_ */