diff options
Diffstat (limited to 'drivers/misc/mei/client.c')
| -rw-r--r-- | drivers/misc/mei/client.c | 915 |
1 files changed, 746 insertions, 169 deletions
diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 1fc8ea0f519b..5dc665515263 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -1,17 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0 /* - * + * Copyright (c) 2003-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver - * Copyright (c) 2003-2012, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * */ #include <linux/sched/signal.h> @@ -19,6 +9,7 @@ #include <linux/delay.h> #include <linux/slab.h> #include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> #include <linux/mei.h> @@ -57,9 +48,9 @@ struct mei_me_client *mei_me_cl_get(struct mei_me_client *me_cl) /** * mei_me_cl_release - free me client * - * Locking: called under "dev->device_lock" lock - * * @ref: me_client refcount + * + * Locking: called under "dev->device_lock" lock */ static void mei_me_cl_release(struct kref *ref) { @@ -72,9 +63,9 @@ static void mei_me_cl_release(struct kref *ref) /** * mei_me_cl_put - decrease me client refcount and free client if necessary * - * Locking: called under "dev->device_lock" lock - * * @me_cl: me client + * + * Locking: called under "dev->device_lock" lock */ void mei_me_cl_put(struct mei_me_client *me_cl) { @@ -271,32 +262,12 @@ void mei_me_cl_rm_by_uuid(struct mei_device *dev, const uuid_le *uuid) { struct mei_me_client *me_cl; - dev_dbg(dev->dev, "remove %pUl\n", uuid); + dev_dbg(&dev->dev, "remove %pUl\n", uuid); down_write(&dev->me_clients_rwsem); me_cl = __mei_me_cl_by_uuid(dev, uuid); __mei_me_cl_del(dev, me_cl); - up_write(&dev->me_clients_rwsem); -} - -/** - * mei_me_cl_rm_by_uuid_id - remove all me clients matching client id - * - * @dev: the device structure - * @uuid: me client uuid - * @id: me client id - * - * Locking: called under "dev->device_lock" lock - */ -void mei_me_cl_rm_by_uuid_id(struct mei_device *dev, const uuid_le *uuid, u8 id) -{ - struct mei_me_client *me_cl; - - dev_dbg(dev->dev, "remove %pUl %d\n", uuid, id); - - down_write(&dev->me_clients_rwsem); - me_cl = __mei_me_cl_by_uuid_id(dev, uuid, id); - __mei_me_cl_del(dev, me_cl); + mei_me_cl_put(me_cl); up_write(&dev->me_clients_rwsem); } @@ -328,17 +299,18 @@ void mei_io_cb_free(struct mei_cl_cb *cb) return; list_del(&cb->list); - kfree(cb->buf.data); + kvfree(cb->buf.data); + kfree(cb->ext_hdr); kfree(cb); } /** - * mei_tx_cb_queue - queue tx callback - * - * Locking: called under "dev->device_lock" lock + * mei_tx_cb_enqueue - queue tx callback * * @cb: mei callback struct * @head: an instance of list to queue on + * + * Locking: called under "dev->device_lock" lock */ static inline void mei_tx_cb_enqueue(struct mei_cl_cb *cb, struct list_head *head) @@ -350,9 +322,9 @@ static inline void mei_tx_cb_enqueue(struct mei_cl_cb *cb, /** * mei_tx_cb_dequeue - dequeue tx callback * - * Locking: called under "dev->device_lock" lock - * * @cb: mei callback struct to dequeue and free + * + * Locking: called under "dev->device_lock" lock */ static inline void mei_tx_cb_dequeue(struct mei_cl_cb *cb) { @@ -363,6 +335,27 @@ static inline void mei_tx_cb_dequeue(struct mei_cl_cb *cb) } /** + * mei_cl_set_read_by_fp - set pending_read flag to vtag struct for given fp + * + * @cl: mei client + * @fp: pointer to file structure + * + * Locking: called under "dev->device_lock" lock + */ +static void mei_cl_set_read_by_fp(const struct mei_cl *cl, + const struct file *fp) +{ + struct mei_cl_vtag *cl_vtag; + + list_for_each_entry(cl_vtag, &cl->vtag_map, list) { + if (cl_vtag->fp == fp) { + cl_vtag->pending_read = true; + return; + } + } +} + +/** * mei_io_cb_init - allocate and initialize io callback * * @cl: mei client @@ -377,7 +370,7 @@ static struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, { struct mei_cl_cb *cb; - cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + cb = kzalloc(sizeof(*cb), GFP_KERNEL); if (!cb) return NULL; @@ -386,6 +379,9 @@ static struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, cb->cl = cl; cb->buf_idx = 0; cb->fop_type = type; + cb->vtag = 0; + cb->ext_hdr = NULL; + return cb; } @@ -401,8 +397,11 @@ static void mei_io_list_flush_cl(struct list_head *head, struct mei_cl_cb *cb, *next; list_for_each_entry_safe(cb, next, head, list) { - if (cl == cb->cl) + if (cl == cb->cl) { list_del_init(&cb->list); + if (cb->fop_type == MEI_FOP_READ) + mei_io_cb_free(cb); + } } } @@ -411,14 +410,16 @@ static void mei_io_list_flush_cl(struct list_head *head, * * @head: An instance of our list structure * @cl: host client + * @fp: file pointer (matching cb file object), may be NULL */ static void mei_io_tx_list_free_cl(struct list_head *head, - const struct mei_cl *cl) + const struct mei_cl *cl, + const struct file *fp) { struct mei_cl_cb *cb, *next; list_for_each_entry_safe(cb, next, head, list) { - if (cl == cb->cl) + if (cl == cb->cl && (!fp || fp == cb->fp)) mei_tx_cb_dequeue(cb); } } @@ -439,6 +440,19 @@ static void mei_io_list_free_fp(struct list_head *head, const struct file *fp) } /** + * mei_cl_free_pending - free pending cb + * + * @cl: host client + */ +static void mei_cl_free_pending(struct mei_cl *cl) +{ + struct mei_cl_cb *cb; + + cb = list_first_entry_or_null(&cl->rd_pending, struct mei_cl_cb, list); + mei_io_cb_free(cb); +} + +/** * mei_cl_alloc_cb - a convenient wrapper for allocating read cb * * @cl: host client @@ -461,7 +475,7 @@ struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length, if (length == 0) return cb; - cb->buf.data = kmalloc(roundup(length, MEI_SLOT_SIZE), GFP_KERNEL); + cb->buf.data = kvmalloc(roundup(length, MEI_SLOT_SIZE), GFP_KERNEL); if (!cb->buf.data) { mei_io_cb_free(cb); return NULL; @@ -510,15 +524,19 @@ struct mei_cl_cb *mei_cl_enqueue_ctrl_wr_cb(struct mei_cl *cl, size_t length, * * Return: cb on success, NULL if cb is not found */ -struct mei_cl_cb *mei_cl_read_cb(const struct mei_cl *cl, const struct file *fp) +struct mei_cl_cb *mei_cl_read_cb(struct mei_cl *cl, const struct file *fp) { struct mei_cl_cb *cb; + struct mei_cl_cb *ret_cb = NULL; + spin_lock(&cl->rd_completed_lock); list_for_each_entry(cb, &cl->rd_completed, list) - if (!fp || fp == cb->fp) - return cb; - - return NULL; + if (!fp || fp == cb->fp) { + ret_cb = cb; + break; + } + spin_unlock(&cl->rd_completed_lock); + return ret_cb; } /** @@ -539,12 +557,17 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp) dev = cl->dev; cl_dbg(dev, cl, "remove list entry belonging to cl\n"); - mei_io_tx_list_free_cl(&cl->dev->write_list, cl); - mei_io_tx_list_free_cl(&cl->dev->write_waiting_list, cl); - mei_io_list_flush_cl(&cl->dev->ctrl_wr_list, cl); - mei_io_list_flush_cl(&cl->dev->ctrl_rd_list, cl); - mei_io_list_free_fp(&cl->rd_pending, fp); + mei_io_tx_list_free_cl(&cl->dev->write_list, cl, fp); + mei_io_tx_list_free_cl(&cl->dev->write_waiting_list, cl, fp); + /* free pending and control cb only in final flush */ + if (!fp) { + mei_io_list_flush_cl(&cl->dev->ctrl_wr_list, cl); + mei_io_list_flush_cl(&cl->dev->ctrl_rd_list, cl); + mei_cl_free_pending(cl); + } + spin_lock(&cl->rd_completed_lock); mei_io_list_free_fp(&cl->rd_completed, fp); + spin_unlock(&cl->rd_completed_lock); return 0; } @@ -557,11 +580,13 @@ int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp) */ static void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) { - memset(cl, 0, sizeof(struct mei_cl)); + memset(cl, 0, sizeof(*cl)); init_waitqueue_head(&cl->wait); init_waitqueue_head(&cl->rx_wait); init_waitqueue_head(&cl->tx_wait); init_waitqueue_head(&cl->ev_wait); + INIT_LIST_HEAD(&cl->vtag_map); + spin_lock_init(&cl->rd_completed_lock); INIT_LIST_HEAD(&cl->rd_completed); INIT_LIST_HEAD(&cl->rd_pending); INIT_LIST_HEAD(&cl->link); @@ -580,7 +605,7 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev) { struct mei_cl *cl; - cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL); + cl = kmalloc(sizeof(*cl), GFP_KERNEL); if (!cl) return NULL; @@ -610,12 +635,12 @@ int mei_cl_link(struct mei_cl *cl) id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX); if (id >= MEI_CLIENTS_MAX) { - dev_err(dev->dev, "id exceeded %d", MEI_CLIENTS_MAX); + dev_err(&dev->dev, "id exceeded %d", MEI_CLIENTS_MAX); return -EMFILE; } if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { - dev_err(dev->dev, "open_handle_count exceeded %d", + dev_err(&dev->dev, "open_handle_count exceeded %d", MEI_MAX_OPEN_HANDLE_COUNT); return -EMFILE; } @@ -655,6 +680,9 @@ int mei_cl_unlink(struct mei_cl *cl) cl_dbg(dev, cl, "unlink client"); + if (cl->state == MEI_FILE_UNINITIALIZED) + return 0; + if (dev->open_handle_count > 0) dev->open_handle_count--; @@ -676,14 +704,13 @@ int mei_cl_unlink(struct mei_cl *cl) void mei_host_client_init(struct mei_device *dev) { - dev->dev_state = MEI_DEV_ENABLED; + mei_set_devstate(dev, MEI_DEV_ENABLED); dev->reset_count = 0; schedule_work(&dev->bus_rescan_work); - pm_runtime_mark_last_busy(dev->dev); - dev_dbg(dev->dev, "rpm: autosuspend\n"); - pm_request_autosuspend(dev->dev); + dev_dbg(&dev->dev, "rpm: autosuspend\n"); + pm_request_autosuspend(dev->parent); } /** @@ -696,12 +723,12 @@ bool mei_hbuf_acquire(struct mei_device *dev) { if (mei_pg_state(dev) == MEI_PG_ON || mei_pg_in_transition(dev)) { - dev_dbg(dev->dev, "device is in pg\n"); + dev_dbg(&dev->dev, "device is in pg\n"); return false; } if (!dev->hbuf_is_ready) { - dev_dbg(dev->dev, "hbuf is not ready\n"); + dev_dbg(&dev->dev, "hbuf is not ready\n"); return false; } @@ -757,8 +784,8 @@ static void mei_cl_set_disconnected(struct mei_cl *cl) return; cl->state = MEI_FILE_DISCONNECTED; - mei_io_tx_list_free_cl(&dev->write_list, cl); - mei_io_tx_list_free_cl(&dev->write_waiting_list, cl); + mei_io_tx_list_free_cl(&dev->write_list, cl, NULL); + mei_io_tx_list_free_cl(&dev->write_waiting_list, cl, NULL); mei_io_list_flush_cl(&dev->ctrl_rd_list, cl); mei_io_list_flush_cl(&dev->ctrl_wr_list, cl); mei_cl_wake_all(cl); @@ -822,7 +849,7 @@ static int mei_cl_send_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb) } list_move_tail(&cb->list, &dev->ctrl_rd_list); - cl->timer_count = MEI_CONNECT_TIMEOUT; + cl->timer_count = dev->timeouts.connect; mei_schedule_stall_timer(dev); return 0; @@ -897,7 +924,7 @@ static int __mei_cl_disconnect(struct mei_cl *cl) wait_event_timeout(cl->wait, cl->state == MEI_FILE_DISCONNECT_REPLY || cl->state == MEI_FILE_DISCONNECTED, - mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + dev->timeouts.cl_connect); mutex_lock(&dev->device_lock); rets = cl->status; @@ -946,15 +973,16 @@ int mei_cl_disconnect(struct mei_cl *cl) return 0; } - if (dev->dev_state == MEI_DEV_POWER_DOWN) { + if (dev->dev_state == MEI_DEV_POWERING_DOWN || + dev->dev_state == MEI_DEV_POWER_DOWN) { cl_dbg(dev, cl, "Device is powering down, don't bother with disconnection\n"); mei_cl_set_disconnected(cl); return 0; } - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } @@ -962,8 +990,7 @@ int mei_cl_disconnect(struct mei_cl *cl) rets = __mei_cl_disconnect(cl); cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); return rets; } @@ -1016,7 +1043,7 @@ static int mei_cl_send_connect(struct mei_cl *cl, struct mei_cl_cb *cb) } list_move_tail(&cb->list, &dev->ctrl_rd_list); - cl->timer_count = MEI_CONNECT_TIMEOUT; + cl->timer_count = dev->timeouts.connect; mei_schedule_stall_timer(dev); return 0; } @@ -1089,9 +1116,9 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, goto nortpm; } - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); goto nortpm; } @@ -1115,7 +1142,7 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, cl->state == MEI_FILE_DISCONNECTED || cl->state == MEI_FILE_DISCONNECT_REQUIRED || cl->state == MEI_FILE_DISCONNECT_REPLY), - mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + dev->timeouts.cl_connect); mutex_lock(&dev->device_lock); if (!mei_cl_is_connected(cl)) { @@ -1138,8 +1165,7 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, rets = cl->status; out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); @@ -1234,6 +1260,161 @@ static int mei_cl_tx_flow_ctrl_creds_reduce(struct mei_cl *cl) } /** + * mei_cl_vtag_alloc - allocate and fill the vtag structure + * + * @fp: pointer to file structure + * @vtag: vm tag + * + * Return: + * * Pointer to allocated struct - on success + * * ERR_PTR(-ENOMEM) on memory allocation failure + */ +struct mei_cl_vtag *mei_cl_vtag_alloc(struct file *fp, u8 vtag) +{ + struct mei_cl_vtag *cl_vtag; + + cl_vtag = kzalloc(sizeof(*cl_vtag), GFP_KERNEL); + if (!cl_vtag) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&cl_vtag->list); + cl_vtag->vtag = vtag; + cl_vtag->fp = fp; + + return cl_vtag; +} + +/** + * mei_cl_fp_by_vtag - obtain the file pointer by vtag + * + * @cl: host client + * @vtag: virtual tag + * + * Return: + * * A file pointer - on success + * * ERR_PTR(-ENOENT) if vtag is not found in the client vtag list + */ +const struct file *mei_cl_fp_by_vtag(const struct mei_cl *cl, u8 vtag) +{ + struct mei_cl_vtag *vtag_l; + + list_for_each_entry(vtag_l, &cl->vtag_map, list) + /* The client on bus has one fixed fp */ + if ((cl->cldev && mei_cldev_enabled(cl->cldev)) || + vtag_l->vtag == vtag) + return vtag_l->fp; + + return ERR_PTR(-ENOENT); +} + +/** + * mei_cl_reset_read_by_vtag - reset pending_read flag by given vtag + * + * @cl: host client + * @vtag: vm tag + */ +static void mei_cl_reset_read_by_vtag(const struct mei_cl *cl, u8 vtag) +{ + struct mei_cl_vtag *vtag_l; + + list_for_each_entry(vtag_l, &cl->vtag_map, list) { + /* The client on bus has one fixed vtag map */ + if ((cl->cldev && mei_cldev_enabled(cl->cldev)) || + vtag_l->vtag == vtag) { + vtag_l->pending_read = false; + break; + } + } +} + +/** + * mei_cl_read_vtag_add_fc - add flow control for next pending reader + * in the vtag list + * + * @cl: host client + */ +static void mei_cl_read_vtag_add_fc(struct mei_cl *cl) +{ + struct mei_cl_vtag *cl_vtag; + + list_for_each_entry(cl_vtag, &cl->vtag_map, list) { + if (cl_vtag->pending_read) { + if (mei_cl_enqueue_ctrl_wr_cb(cl, + mei_cl_mtu(cl), + MEI_FOP_READ, + cl_vtag->fp)) + cl->rx_flow_ctrl_creds++; + break; + } + } +} + +/** + * mei_cl_vt_support_check - check if client support vtags + * + * @cl: host client + * + * Return: + * * 0 - supported, or not connected at all + * * -EOPNOTSUPP - vtags are not supported by client + */ +int mei_cl_vt_support_check(const struct mei_cl *cl) +{ + struct mei_device *dev = cl->dev; + + if (!dev->hbm_f_vt_supported) + return -EOPNOTSUPP; + + if (!cl->me_cl) + return 0; + + return cl->me_cl->props.vt_supported ? 0 : -EOPNOTSUPP; +} + +/** + * mei_cl_add_rd_completed - add read completed callback to list with lock + * and vtag check + * + * @cl: host client + * @cb: callback block + * + */ +void mei_cl_add_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb) +{ + const struct file *fp; + + if (!mei_cl_vt_support_check(cl)) { + fp = mei_cl_fp_by_vtag(cl, cb->vtag); + if (IS_ERR(fp)) { + /* client already disconnected, discarding */ + mei_io_cb_free(cb); + return; + } + cb->fp = fp; + mei_cl_reset_read_by_vtag(cl, cb->vtag); + mei_cl_read_vtag_add_fc(cl); + } + + spin_lock(&cl->rd_completed_lock); + list_add_tail(&cb->list, &cl->rd_completed); + spin_unlock(&cl->rd_completed_lock); +} + +/** + * mei_cl_del_rd_completed - free read completed callback with lock + * + * @cl: host client + * @cb: callback block + * + */ +void mei_cl_del_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb) +{ + spin_lock(&cl->rd_completed_lock); + mei_io_cb_free(cb); + spin_unlock(&cl->rd_completed_lock); +} + +/** * mei_cl_notify_fop2req - convert fop to proper request * * @fop: client notification start response command @@ -1333,9 +1514,9 @@ int mei_cl_notify_request(struct mei_cl *cl, if (!mei_cl_is_connected(cl)) return -ENODEV; - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } @@ -1360,7 +1541,7 @@ int mei_cl_notify_request(struct mei_cl *cl, cl->notify_en == request || cl->status || !mei_cl_is_connected(cl), - mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + dev->timeouts.cl_connect); mutex_lock(&dev->device_lock); if (cl->notify_en != request && !cl->status) @@ -1370,8 +1551,7 @@ int mei_cl_notify_request(struct mei_cl *cl, out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); return rets; @@ -1488,16 +1668,20 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp) return 0; /* HW currently supports only one pending read */ - if (cl->rx_flow_ctrl_creds) + if (cl->rx_flow_ctrl_creds) { + mei_cl_set_read_by_fp(cl, fp); return -EBUSY; + } cb = mei_cl_enqueue_ctrl_wr_cb(cl, length, MEI_FOP_READ, fp); if (!cb) return -ENOMEM; - rets = pm_runtime_get(dev->dev); + mei_cl_set_read_by_fp(cl, fp); + + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); goto nortpm; } @@ -1514,8 +1698,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp) out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); nortpm: if (rets) mei_io_cb_free(cb); @@ -1523,21 +1706,96 @@ nortpm: return rets; } +static inline u8 mei_ext_hdr_set_vtag(void *ext, u8 vtag) +{ + struct mei_ext_hdr_vtag *vtag_hdr = ext; + + vtag_hdr->hdr.type = MEI_EXT_HDR_VTAG; + vtag_hdr->hdr.length = mei_data2slots(sizeof(*vtag_hdr)); + vtag_hdr->vtag = vtag; + vtag_hdr->reserved = 0; + return vtag_hdr->hdr.length; +} + +static inline bool mei_ext_hdr_is_gsc(struct mei_ext_hdr *ext) +{ + return ext && ext->type == MEI_EXT_HDR_GSC; +} + +static inline u8 mei_ext_hdr_set_gsc(struct mei_ext_hdr *ext, struct mei_ext_hdr *gsc_hdr) +{ + memcpy(ext, gsc_hdr, mei_ext_hdr_len(gsc_hdr)); + return ext->length; +} + /** - * mei_msg_hdr_init - initialize mei message header + * mei_msg_hdr_init - allocate and initialize mei message header * - * @mei_hdr: mei message header * @cb: message callback structure + * + * Return: a pointer to initialized header or ERR_PTR on failure */ -static void mei_msg_hdr_init(struct mei_msg_hdr *mei_hdr, struct mei_cl_cb *cb) +static struct mei_msg_hdr *mei_msg_hdr_init(const struct mei_cl_cb *cb) { + size_t hdr_len; + struct mei_ext_meta_hdr *meta; + struct mei_msg_hdr *mei_hdr; + bool is_ext, is_hbm, is_gsc, is_vtag; + struct mei_ext_hdr *next_ext; + + if (!cb) + return ERR_PTR(-EINVAL); + + /* Extended header for vtag is attached only on the first fragment */ + is_vtag = (cb->vtag && cb->buf_idx == 0); + is_hbm = cb->cl->me_cl->client_id == 0; + is_gsc = ((!is_hbm) && cb->cl->dev->hbm_f_gsc_supported && mei_ext_hdr_is_gsc(cb->ext_hdr)); + is_ext = is_vtag || is_gsc; + + /* Compute extended header size */ + hdr_len = sizeof(*mei_hdr); + + if (!is_ext) + goto setup_hdr; + + hdr_len += sizeof(*meta); + if (is_vtag) + hdr_len += sizeof(struct mei_ext_hdr_vtag); + + if (is_gsc) + hdr_len += mei_ext_hdr_len(cb->ext_hdr); + +setup_hdr: + mei_hdr = kzalloc(hdr_len, GFP_KERNEL); + if (!mei_hdr) + return ERR_PTR(-ENOMEM); + mei_hdr->host_addr = mei_cl_host_addr(cb->cl); mei_hdr->me_addr = mei_cl_me_id(cb->cl); - mei_hdr->length = 0; - mei_hdr->reserved = 0; - mei_hdr->msg_complete = 0; - mei_hdr->dma_ring = 0; mei_hdr->internal = cb->internal; + mei_hdr->extended = is_ext; + + if (!is_ext) + goto out; + + meta = (struct mei_ext_meta_hdr *)mei_hdr->extension; + meta->size = 0; + next_ext = (struct mei_ext_hdr *)meta->hdrs; + if (is_vtag) { + meta->count++; + meta->size += mei_ext_hdr_set_vtag(next_ext, cb->vtag); + next_ext = mei_ext_next(next_ext); + } + + if (is_gsc) { + meta->count++; + meta->size += mei_ext_hdr_set_gsc(next_ext, cb->ext_hdr); + next_ext = mei_ext_next(next_ext); + } + +out: + mei_hdr->length = hdr_len - sizeof(*mei_hdr); + return mei_hdr; } /** @@ -1555,16 +1813,17 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, { struct mei_device *dev; struct mei_msg_data *buf; - struct mei_msg_hdr mei_hdr; - size_t hdr_len = sizeof(mei_hdr); - size_t len; + struct mei_msg_hdr *mei_hdr = NULL; + size_t hdr_len; size_t hbuf_len, dr_len; + size_t buf_len = 0; + size_t data_len; int hbuf_slots; u32 dr_slots; u32 dma_len; int rets; bool first_chunk; - const void *data; + const void *data = NULL; if (WARN_ON(!cl || !cl->dev)) return -ENODEV; @@ -1584,54 +1843,65 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, return 0; } - len = buf->size - cb->buf_idx; - data = buf->data + cb->buf_idx; + if (buf->data) { + buf_len = buf->size - cb->buf_idx; + data = buf->data + cb->buf_idx; + } hbuf_slots = mei_hbuf_empty_slots(dev); if (hbuf_slots < 0) { rets = -EOVERFLOW; goto err; } - hbuf_len = mei_slots2data(hbuf_slots); + hbuf_len = mei_slots2data(hbuf_slots) & MEI_MSG_MAX_LEN_MASK; dr_slots = mei_dma_ring_empty_slots(dev); dr_len = mei_slots2data(dr_slots); - mei_msg_hdr_init(&mei_hdr, cb); + mei_hdr = mei_msg_hdr_init(cb); + if (IS_ERR(mei_hdr)) { + rets = PTR_ERR(mei_hdr); + mei_hdr = NULL; + goto err; + } + + hdr_len = sizeof(*mei_hdr) + mei_hdr->length; /** * Split the message only if we can write the whole host buffer * otherwise wait for next time the host buffer is empty. */ - if (len + hdr_len <= hbuf_len) { - mei_hdr.length = len; - mei_hdr.msg_complete = 1; + if (hdr_len + buf_len <= hbuf_len) { + data_len = buf_len; + mei_hdr->msg_complete = 1; } else if (dr_slots && hbuf_len >= hdr_len + sizeof(dma_len)) { - mei_hdr.dma_ring = 1; - if (len > dr_len) - len = dr_len; + mei_hdr->dma_ring = 1; + if (buf_len > dr_len) + buf_len = dr_len; else - mei_hdr.msg_complete = 1; + mei_hdr->msg_complete = 1; - mei_hdr.length = sizeof(dma_len); - dma_len = len; + data_len = sizeof(dma_len); + dma_len = buf_len; data = &dma_len; } else if ((u32)hbuf_slots == mei_hbuf_depth(dev)) { - len = hbuf_len - hdr_len; - mei_hdr.length = len; + buf_len = hbuf_len - hdr_len; + data_len = buf_len; } else { + kfree(mei_hdr); return 0; } + mei_hdr->length += data_len; - if (mei_hdr.dma_ring) - mei_dma_ring_write(dev, buf->data + cb->buf_idx, len); + if (mei_hdr->dma_ring && buf->data) + mei_dma_ring_write(dev, buf->data + cb->buf_idx, buf_len); + rets = mei_write_message(dev, mei_hdr, hdr_len, data, data_len); - rets = mei_write_message(dev, &mei_hdr, hdr_len, data, mei_hdr.length); if (rets) goto err; cl->status = 0; cl->writing_state = MEI_WRITING; - cb->buf_idx += len; + cb->buf_idx += buf_len; if (first_chunk) { if (mei_cl_tx_flow_ctrl_creds_reduce(cl)) { @@ -1640,12 +1910,14 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, } } - if (mei_hdr.msg_complete) + if (mei_hdr->msg_complete) list_move_tail(&cb->list, &dev->write_waiting_list); + kfree(mei_hdr); return 0; err: + kfree(mei_hdr); cl->status = rets; list_move_tail(&cb->list, cmpl_list); return rets; @@ -1657,16 +1929,21 @@ err: * * @cl: host client * @cb: write callback with filled data + * @timeout: send timeout in milliseconds. + * effective only for blocking writes: the cb->blocking is set. + * set timeout to the MAX_SCHEDULE_TIMEOUT to maixum allowed wait. * * Return: number of bytes sent on success, <0 on failure. */ -ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) +ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, unsigned long timeout) { struct mei_device *dev; struct mei_msg_data *buf; - struct mei_msg_hdr mei_hdr; - size_t hdr_len = sizeof(mei_hdr); - size_t len, hbuf_len, dr_len; + struct mei_msg_hdr *mei_hdr = NULL; + size_t hdr_len; + size_t hbuf_len, dr_len; + size_t buf_len; + size_t data_len; int hbuf_slots; u32 dr_slots; u32 dma_len; @@ -1683,16 +1960,16 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) dev = cl->dev; buf = &cb->buf; - len = buf->size; + buf_len = buf->size; - cl_dbg(dev, cl, "len=%zd\n", len); + cl_dbg(dev, cl, "buf_len=%zd\n", buf_len); blocking = cb->blocking; data = buf->data; - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %zd\n", rets); goto free; } @@ -1705,53 +1982,61 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) if (rets < 0) goto err; - mei_msg_hdr_init(&mei_hdr, cb); + mei_hdr = mei_msg_hdr_init(cb); + if (IS_ERR(mei_hdr)) { + rets = PTR_ERR(mei_hdr); + mei_hdr = NULL; + goto err; + } + + hdr_len = sizeof(*mei_hdr) + mei_hdr->length; if (rets == 0) { cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); - rets = len; + rets = buf_len; goto out; } if (!mei_hbuf_acquire(dev)) { cl_dbg(dev, cl, "Cannot acquire the host buffer: not sending.\n"); - rets = len; + rets = buf_len; goto out; } hbuf_slots = mei_hbuf_empty_slots(dev); if (hbuf_slots < 0) { - rets = -EOVERFLOW; + buf_len = -EOVERFLOW; goto out; } - hbuf_len = mei_slots2data(hbuf_slots); + hbuf_len = mei_slots2data(hbuf_slots) & MEI_MSG_MAX_LEN_MASK; dr_slots = mei_dma_ring_empty_slots(dev); dr_len = mei_slots2data(dr_slots); - if (len + hdr_len <= hbuf_len) { - mei_hdr.length = len; - mei_hdr.msg_complete = 1; + if (hdr_len + buf_len <= hbuf_len) { + data_len = buf_len; + mei_hdr->msg_complete = 1; } else if (dr_slots && hbuf_len >= hdr_len + sizeof(dma_len)) { - mei_hdr.dma_ring = 1; - if (len > dr_len) - len = dr_len; + mei_hdr->dma_ring = 1; + if (buf_len > dr_len) + buf_len = dr_len; else - mei_hdr.msg_complete = 1; + mei_hdr->msg_complete = 1; - mei_hdr.length = sizeof(dma_len); - dma_len = len; + data_len = sizeof(dma_len); + dma_len = buf_len; data = &dma_len; } else { - len = hbuf_len - hdr_len; - mei_hdr.length = len; + buf_len = hbuf_len - hdr_len; + data_len = buf_len; } - if (mei_hdr.dma_ring) - mei_dma_ring_write(dev, buf->data, len); + mei_hdr->length += data_len; + + if (mei_hdr->dma_ring && buf->data) + mei_dma_ring_write(dev, buf->data, buf_len); + rets = mei_write_message(dev, mei_hdr, hdr_len, data, data_len); - rets = mei_write_message(dev, &mei_hdr, hdr_len, - data, mei_hdr.length); if (rets) goto err; @@ -1760,12 +2045,12 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) goto err; cl->writing_state = MEI_WRITING; - cb->buf_idx = len; + cb->buf_idx = buf_len; /* restore return value */ - len = buf->size; + buf_len = buf->size; out: - if (mei_hdr.msg_complete) + if (mei_hdr->msg_complete) mei_tx_cb_enqueue(cb, &dev->write_waiting_list); else mei_tx_cb_enqueue(cb, &dev->write_list); @@ -1774,11 +2059,20 @@ out: if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) { mutex_unlock(&dev->device_lock); - rets = wait_event_interruptible(cl->tx_wait, - cl->writing_state == MEI_WRITE_COMPLETE || - (!mei_cl_is_connected(cl))); + rets = wait_event_interruptible_timeout(cl->tx_wait, + cl->writing_state == MEI_WRITE_COMPLETE || + (!mei_cl_is_connected(cl)), + msecs_to_jiffies(timeout)); mutex_lock(&dev->device_lock); + /* clean all queue on timeout as something fatal happened */ + if (rets == 0) { + rets = -ETIME; + mei_io_tx_list_free_cl(&dev->write_list, cl, NULL); + mei_io_tx_list_free_cl(&dev->write_waiting_list, cl, NULL); + } /* wait_event_interruptible returns -ERESTARTSYS */ + if (rets > 0) + rets = 0; if (rets) { if (signal_pending(current)) rets = -EINTR; @@ -1790,18 +2084,18 @@ out: } } - rets = len; + rets = buf_len; err: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); free: mei_io_cb_free(cb); + kfree(mei_hdr); + return rets; } - /** * mei_cl_complete - processes completed operation for a client * @@ -1816,16 +2110,14 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) case MEI_FOP_WRITE: mei_tx_cb_dequeue(cb); cl->writing_state = MEI_WRITE_COMPLETE; - if (waitqueue_active(&cl->tx_wait)) { + if (waitqueue_active(&cl->tx_wait)) wake_up_interruptible(&cl->tx_wait); - } else { - pm_runtime_mark_last_busy(dev->dev); - pm_request_autosuspend(dev->dev); - } + else + pm_request_autosuspend(dev->parent); break; case MEI_FOP_READ: - list_add_tail(&cb->list, &cl->rd_completed); + mei_cl_add_rd_completed(cl, cb); if (!mei_cl_is_fixed_address(cl) && !WARN_ON(!cl->rx_flow_ctrl_creds)) cl->rx_flow_ctrl_creds--; @@ -1837,6 +2129,8 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) case MEI_FOP_DISCONNECT: case MEI_FOP_NOTIFY_STOP: case MEI_FOP_NOTIFY_START: + case MEI_FOP_DMA_MAP: + case MEI_FOP_DMA_UNMAP: if (waitqueue_active(&cl->wait)) wake_up(&cl->wait); @@ -1863,3 +2157,286 @@ void mei_cl_all_disconnect(struct mei_device *dev) list_for_each_entry(cl, &dev->file_list, link) mei_cl_set_disconnected(cl); } +EXPORT_SYMBOL_GPL(mei_cl_all_disconnect); + +static struct mei_cl *mei_cl_dma_map_find(struct mei_device *dev, u8 buffer_id) +{ + struct mei_cl *cl; + + list_for_each_entry(cl, &dev->file_list, link) + if (cl->dma.buffer_id == buffer_id) + return cl; + return NULL; +} + +/** + * mei_cl_irq_dma_map - send client dma map request in irq_thread context + * + * @cl: client + * @cb: callback block. + * @cmpl_list: complete list. + * + * Return: 0 on such and error otherwise. + */ +int mei_cl_irq_dma_map(struct mei_cl *cl, struct mei_cl_cb *cb, + struct list_head *cmpl_list) +{ + struct mei_device *dev = cl->dev; + u32 msg_slots; + int slots; + int ret; + + msg_slots = mei_hbm2slots(sizeof(struct hbm_client_dma_map_request)); + slots = mei_hbuf_empty_slots(dev); + if (slots < 0) + return -EOVERFLOW; + + if ((u32)slots < msg_slots) + return -EMSGSIZE; + + ret = mei_hbm_cl_dma_map_req(dev, cl); + if (ret) { + cl->status = ret; + list_move_tail(&cb->list, cmpl_list); + return ret; + } + + list_move_tail(&cb->list, &dev->ctrl_rd_list); + return 0; +} + +/** + * mei_cl_irq_dma_unmap - send client dma unmap request in irq_thread context + * + * @cl: client + * @cb: callback block. + * @cmpl_list: complete list. + * + * Return: 0 on such and error otherwise. + */ +int mei_cl_irq_dma_unmap(struct mei_cl *cl, struct mei_cl_cb *cb, + struct list_head *cmpl_list) +{ + struct mei_device *dev = cl->dev; + u32 msg_slots; + int slots; + int ret; + + msg_slots = mei_hbm2slots(sizeof(struct hbm_client_dma_unmap_request)); + slots = mei_hbuf_empty_slots(dev); + if (slots < 0) + return -EOVERFLOW; + + if ((u32)slots < msg_slots) + return -EMSGSIZE; + + ret = mei_hbm_cl_dma_unmap_req(dev, cl); + if (ret) { + cl->status = ret; + list_move_tail(&cb->list, cmpl_list); + return ret; + } + + list_move_tail(&cb->list, &dev->ctrl_rd_list); + return 0; +} + +static int mei_cl_dma_alloc(struct mei_cl *cl, u8 buf_id, size_t size) +{ + cl->dma.vaddr = dmam_alloc_coherent(&cl->dev->dev, size, + &cl->dma.daddr, GFP_KERNEL); + if (!cl->dma.vaddr) + return -ENOMEM; + + cl->dma.buffer_id = buf_id; + cl->dma.size = size; + + return 0; +} + +static void mei_cl_dma_free(struct mei_cl *cl) +{ + cl->dma.buffer_id = 0; + dmam_free_coherent(&cl->dev->dev, + cl->dma.size, cl->dma.vaddr, cl->dma.daddr); + cl->dma.size = 0; + cl->dma.vaddr = NULL; + cl->dma.daddr = 0; +} + +/** + * mei_cl_dma_alloc_and_map - send client dma map request + * + * @cl: host client + * @fp: pointer to file structure + * @buffer_id: id of the mapped buffer + * @size: size of the buffer + * + * Locking: called under "dev->device_lock" lock + * + * Return: + * * -ENODEV + * * -EINVAL + * * -EOPNOTSUPP + * * -EPROTO + * * -ENOMEM; + */ +int mei_cl_dma_alloc_and_map(struct mei_cl *cl, const struct file *fp, + u8 buffer_id, size_t size) +{ + struct mei_device *dev; + struct mei_cl_cb *cb; + int rets; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + if (!dev->hbm_f_cd_supported) { + cl_dbg(dev, cl, "client dma is not supported\n"); + return -EOPNOTSUPP; + } + + if (buffer_id == 0) + return -EINVAL; + + if (mei_cl_is_connected(cl)) + return -EPROTO; + + if (cl->dma_mapped) + return -EPROTO; + + if (mei_cl_dma_map_find(dev, buffer_id)) { + cl_dbg(dev, cl, "client dma with id %d is already allocated\n", + cl->dma.buffer_id); + return -EPROTO; + } + + rets = pm_runtime_get(dev->parent); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(dev->parent); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + + rets = mei_cl_dma_alloc(cl, buffer_id, size); + if (rets) { + pm_runtime_put_noidle(dev->parent); + return rets; + } + + cb = mei_cl_enqueue_ctrl_wr_cb(cl, 0, MEI_FOP_DMA_MAP, fp); + if (!cb) { + rets = -ENOMEM; + goto out; + } + + if (mei_hbuf_acquire(dev)) { + if (mei_hbm_cl_dma_map_req(dev, cl)) { + rets = -ENODEV; + goto out; + } + list_move_tail(&cb->list, &dev->ctrl_rd_list); + } + + cl->status = 0; + + mutex_unlock(&dev->device_lock); + wait_event_timeout(cl->wait, + cl->dma_mapped || cl->status, + dev->timeouts.cl_connect); + mutex_lock(&dev->device_lock); + + if (!cl->dma_mapped && !cl->status) + cl->status = -EFAULT; + + rets = cl->status; + +out: + if (rets) + mei_cl_dma_free(cl); + + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_put_autosuspend(dev->parent); + + mei_io_cb_free(cb); + return rets; +} + +/** + * mei_cl_dma_unmap - send client dma unmap request + * + * @cl: host client + * @fp: pointer to file structure + * + * Locking: called under "dev->device_lock" lock + * + * Return: 0 on such and error otherwise. + */ +int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp) +{ + struct mei_device *dev; + struct mei_cl_cb *cb; + int rets; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + if (!dev->hbm_f_cd_supported) { + cl_dbg(dev, cl, "client dma is not supported\n"); + return -EOPNOTSUPP; + } + + /* do not allow unmap for connected client */ + if (mei_cl_is_connected(cl)) + return -EPROTO; + + if (!cl->dma_mapped) + return -EPROTO; + + rets = pm_runtime_get(dev->parent); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(dev->parent); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + + cb = mei_cl_enqueue_ctrl_wr_cb(cl, 0, MEI_FOP_DMA_UNMAP, fp); + if (!cb) { + rets = -ENOMEM; + goto out; + } + + if (mei_hbuf_acquire(dev)) { + if (mei_hbm_cl_dma_unmap_req(dev, cl)) { + rets = -ENODEV; + goto out; + } + list_move_tail(&cb->list, &dev->ctrl_rd_list); + } + + cl->status = 0; + + mutex_unlock(&dev->device_lock); + wait_event_timeout(cl->wait, + !cl->dma_mapped || cl->status, + dev->timeouts.cl_connect); + mutex_lock(&dev->device_lock); + + if (cl->dma_mapped && !cl->status) + cl->status = -EFAULT; + + rets = cl->status; + + if (!rets) + mei_cl_dma_free(cl); +out: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_put_autosuspend(dev->parent); + + mei_io_cb_free(cb); + return rets; +} |
