From 438763f37eb9664b6372bdfee990f8c33acdc63c Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:05:59 +0200 Subject: mei: drop redundant length parameter from mei_write_message function The length is already part of the message header and it is validated before the function call Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 6 ++---- drivers/misc/mei/init.c | 7 +++---- drivers/misc/mei/interface.c | 24 +++++++++++------------- drivers/misc/mei/interface.h | 5 ++--- drivers/misc/mei/interrupt.c | 17 ++++++++--------- drivers/misc/mei/main.c | 3 +-- drivers/misc/mei/wd.c | 18 +++++++++--------- 7 files changed, 36 insertions(+), 44 deletions(-) diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 18794aea6062..8a9313a1ee7b 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -300,8 +300,7 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) mei_hdr.reserved = 0; dev->iamthif_msg_buf_index += mei_hdr.length; if (mei_write_message(dev, &mei_hdr, - (unsigned char *)(dev->iamthif_msg_buf), - mei_hdr.length)) + (unsigned char *)dev->iamthif_msg_buf)) return -ENODEV; if (mei_hdr.msg_complete) { @@ -463,8 +462,7 @@ int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, *slots -= msg_slots; if (mei_write_message(dev, mei_hdr, - dev->iamthif_msg_buf + dev->iamthif_msg_buf_index, - mei_hdr->length)) { + dev->iamthif_msg_buf + dev->iamthif_msg_buf_index)) { dev->iamthif_state = MEI_IAMTHIF_IDLE; cl->status = -ENODEV; list_del(&cb->list); diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index a54cd5567ca2..c0c0b3e22579 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -345,7 +345,7 @@ void mei_host_start_message(struct mei_device *dev) start_req->host_version.minor_version = HBM_MINOR_VERSION; dev->recvd_msg = false; - if (mei_write_message(dev, mei_hdr, (unsigned char *)start_req, len)) { + if (mei_write_message(dev, mei_hdr, (unsigned char *)start_req)) { dev_dbg(&dev->pdev->dev, "write send version message to FW fail.\n"); dev->dev_state = MEI_DEV_RESETING; mei_reset(dev, 1); @@ -374,7 +374,7 @@ void mei_host_enum_clients_message(struct mei_device *dev) memset(enum_req, 0, sizeof(struct hbm_host_enum_request)); enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; - if (mei_write_message(dev, mei_hdr, (unsigned char *)enum_req, len)) { + if (mei_write_message(dev, mei_hdr, (unsigned char *)enum_req)) { dev->dev_state = MEI_DEV_RESETING; dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n"); mei_reset(dev, 1); @@ -492,8 +492,7 @@ int mei_host_client_enumerate(struct mei_device *dev) prop_req->hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD; prop_req->address = next_client_index; - if (mei_write_message(dev, mei_hdr, (unsigned char *) prop_req, - mei_hdr->length)) { + if (mei_write_message(dev, mei_hdr, (unsigned char *) prop_req)) { dev->dev_state = MEI_DEV_RESETING; dev_err(&dev->pdev->dev, "Properties request command failed\n"); mei_reset(dev, 1); diff --git a/drivers/misc/mei/interface.c b/drivers/misc/mei/interface.c index 8de854785960..21ccbe6f7162 100644 --- a/drivers/misc/mei/interface.c +++ b/drivers/misc/mei/interface.c @@ -113,21 +113,20 @@ int mei_hbuf_empty_slots(struct mei_device *dev) * mei_write_message - writes a message to mei device. * * @dev: the device structure - * @header: header of message - * @write_buffer: message buffer will be written - * @write_length: message size will be written + * @hader: mei HECI header of message + * @buf: message payload will be written * * This function returns -EIO if write has failed */ int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, - unsigned char *buf, unsigned long length) + unsigned char *buf) { unsigned long rem, dw_cnt; + unsigned long length = header->length; u32 *reg_buf = (u32 *)buf; int i; int empty_slots; - dev_dbg(&dev->pdev->dev, "mei_write_message header=%08x.\n", *((u32 *) header)); @@ -307,8 +306,7 @@ int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl) dev_dbg(&dev->pdev->dev, "sending flow control host client = %d, ME client = %d\n", cl->host_client_id, cl->me_client_id); - return mei_write_message(dev, mei_hdr, - (unsigned char *) flow_ctrl, len); + return mei_write_message(dev, mei_hdr, (unsigned char *) flow_ctrl); } /** @@ -346,11 +344,11 @@ int mei_other_client_is_connecting(struct mei_device *dev, */ int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) { - struct mei_msg_hdr *mei_hdr; + struct mei_msg_hdr *hdr; struct hbm_client_connect_request *req; const size_t len = sizeof(struct hbm_client_connect_request); - mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); req = (struct hbm_client_connect_request *)&dev->wr_msg_buf[1]; memset(req, 0, len); @@ -359,7 +357,7 @@ int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) req->me_addr = cl->me_client_id; req->reserved = 0; - return mei_write_message(dev, mei_hdr, (unsigned char *)req, len); + return mei_write_message(dev, hdr, (unsigned char *)req); } /** @@ -372,11 +370,11 @@ int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) */ int mei_connect(struct mei_device *dev, struct mei_cl *cl) { - struct mei_msg_hdr *mei_hdr; + struct mei_msg_hdr *hdr; struct hbm_client_connect_request *req; const size_t len = sizeof(struct hbm_client_connect_request); - mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); req = (struct hbm_client_connect_request *) &dev->wr_msg_buf[1]; req->hbm_cmd = CLIENT_CONNECT_REQ_CMD; @@ -384,5 +382,5 @@ int mei_connect(struct mei_device *dev, struct mei_cl *cl) req->me_addr = cl->me_client_id; req->reserved = 0; - return mei_write_message(dev, mei_hdr, (unsigned char *) req, len); + return mei_write_message(dev, hdr, (unsigned char *) req); } diff --git a/drivers/misc/mei/interface.h b/drivers/misc/mei/interface.h index ec6c785a3961..ca732990a7eb 100644 --- a/drivers/misc/mei/interface.h +++ b/drivers/misc/mei/interface.h @@ -29,9 +29,8 @@ void mei_read_slots(struct mei_device *dev, unsigned long buffer_length); int mei_write_message(struct mei_device *dev, - struct mei_msg_hdr *header, - unsigned char *write_buffer, - unsigned long write_length); + struct mei_msg_hdr *header, + unsigned char *buf); bool mei_hbuf_is_empty(struct mei_device *dev); diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 04fa2134615e..b72fa8196ddb 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -465,7 +465,7 @@ static void mei_client_disconnect_request(struct mei_device *dev, * @mei_hdr: header of bus message */ static void mei_irq_thread_read_bus_message(struct mei_device *dev, - struct mei_msg_hdr *mei_hdr) + struct mei_msg_hdr *hdr) { struct mei_bus_message *mei_msg; struct mei_me_client *me_client; @@ -479,8 +479,8 @@ static void mei_irq_thread_read_bus_message(struct mei_device *dev, struct hbm_host_stop_request *stop_req; /* read the message to our buffer */ - BUG_ON(mei_hdr->length >= sizeof(dev->rd_msg_buf)); - mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); + BUG_ON(hdr->length >= sizeof(dev->rd_msg_buf)); + mei_read_slots(dev, dev->rd_msg_buf, hdr->length); mei_msg = (struct mei_bus_message *)dev->rd_msg_buf; switch (mei_msg->hbm_cmd) { @@ -506,14 +506,13 @@ static void mei_irq_thread_read_bus_message(struct mei_device *dev, dev->version = version_res->me_max_version; /* send stop message */ - mei_hdr = mei_hbm_hdr(&buf[0], len); + hdr = mei_hbm_hdr(&buf[0], len); stop_req = (struct hbm_host_stop_request *)&buf[1]; memset(stop_req, 0, len); stop_req->hbm_cmd = HOST_STOP_REQ_CMD; stop_req->reason = DRIVER_STOP_REQUEST; - mei_write_message(dev, mei_hdr, - (unsigned char *)stop_req, len); + mei_write_message(dev, hdr, (unsigned char *)stop_req); dev_dbg(&dev->pdev->dev, "version mismatch.\n"); return; } @@ -615,7 +614,7 @@ static void mei_irq_thread_read_bus_message(struct mei_device *dev, const size_t len = sizeof(struct hbm_host_stop_request); - mei_hdr = mei_hbm_hdr((u32 *)&dev->wr_ext_msg.hdr, len); + hdr = mei_hbm_hdr((u32 *)&dev->wr_ext_msg.hdr, len); stop_req = (struct hbm_host_stop_request *)&dev->wr_ext_msg.data; memset(stop_req, 0, len); stop_req->hbm_cmd = HOST_STOP_REQ_CMD; @@ -748,7 +747,7 @@ static int mei_irq_thread_write_complete(struct mei_device *dev, s32 *slots, *slots -= msg_slots; if (mei_write_message(dev, mei_hdr, - cb->request_buffer.data + cb->buf_idx, len)) { + cb->request_buffer.data + cb->buf_idx)) { cl->status = -ENODEV; list_move_tail(&cb->list, &cmpl_list->list); return -ENODEV; @@ -930,7 +929,7 @@ static int mei_irq_thread_write_handler(struct mei_device *dev, if (dev->wr_ext_msg.hdr.length) { mei_write_message(dev, &dev->wr_ext_msg.hdr, - dev->wr_ext_msg.data, dev->wr_ext_msg.hdr.length); + dev->wr_ext_msg.data); slots -= mei_data2slots(dev->wr_ext_msg.hdr.length); dev->wr_ext_msg.hdr.length = 0; } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 43fb52ff98ad..b281c235d898 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -554,8 +554,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, mei_hdr.reserved = 0; dev_dbg(&dev->pdev->dev, "call mei_write_message header=%08x.\n", *((u32 *) &mei_hdr)); - if (mei_write_message(dev, &mei_hdr, - write_cb->request_buffer.data, mei_hdr.length)) { + if (mei_write_message(dev, &mei_hdr, write_cb->request_buffer.data)) { rets = -ENODEV; goto err; } diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index 9299a8c29a6f..9d4d4aa0f0e8 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -101,22 +101,22 @@ int mei_wd_host_init(struct mei_device *dev) */ int mei_wd_send(struct mei_device *dev) { - struct mei_msg_hdr *mei_hdr; + struct mei_msg_hdr *hdr; - mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; - mei_hdr->host_addr = dev->wd_cl.host_client_id; - mei_hdr->me_addr = dev->wd_cl.me_client_id; - mei_hdr->msg_complete = 1; - mei_hdr->reserved = 0; + hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + hdr->host_addr = dev->wd_cl.host_client_id; + hdr->me_addr = dev->wd_cl.me_client_id; + hdr->msg_complete = 1; + hdr->reserved = 0; if (!memcmp(dev->wd_data, mei_start_wd_params, MEI_WD_HDR_SIZE)) - mei_hdr->length = MEI_WD_START_MSG_SIZE; + hdr->length = MEI_WD_START_MSG_SIZE; else if (!memcmp(dev->wd_data, mei_stop_wd_params, MEI_WD_HDR_SIZE)) - mei_hdr->length = MEI_WD_STOP_MSG_SIZE; + hdr->length = MEI_WD_STOP_MSG_SIZE; else return -EINVAL; - return mei_write_message(dev, mei_hdr, dev->wd_data, mei_hdr->length); + return mei_write_message(dev, hdr, dev->wd_data); } /** -- cgit v1.2.1 From 15d4acc57f23b6e02e587b773458a7c0e23e501d Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:00 +0200 Subject: mei: use unified format for printing mei message header Introduce MEI_HDR_FMT and MEI_HDR_PRM macros. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 3 +-- drivers/misc/mei/interface.c | 4 +--- drivers/misc/mei/interrupt.c | 14 ++++++-------- drivers/misc/mei/main.c | 5 +++-- drivers/misc/mei/mei_dev.h | 5 +++++ 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 8a9313a1ee7b..77acd2349b1b 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -457,8 +457,7 @@ int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, return 0; } - dev_dbg(&dev->pdev->dev, "msg: len = %d complete = %d\n", - mei_hdr->length, mei_hdr->msg_complete); + dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); *slots -= msg_slots; if (mei_write_message(dev, mei_hdr, diff --git a/drivers/misc/mei/interface.c b/drivers/misc/mei/interface.c index 21ccbe6f7162..17d93f71d6bf 100644 --- a/drivers/misc/mei/interface.c +++ b/drivers/misc/mei/interface.c @@ -127,9 +127,7 @@ int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, int i; int empty_slots; - dev_dbg(&dev->pdev->dev, - "mei_write_message header=%08x.\n", - *((u32 *) header)); + dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(header)); empty_slots = mei_hbuf_empty_slots(dev); dev_dbg(&dev->pdev->dev, "empty slots = %hu.\n", empty_slots); diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index b72fa8196ddb..fe93d1c9c242 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -150,8 +150,8 @@ quit: dev_dbg(&dev->pdev->dev, "message read\n"); if (!buffer) { mei_read_slots(dev, dev->rd_msg_buf, mei_hdr->length); - dev_dbg(&dev->pdev->dev, "discarding message, header =%08x.\n", - *(u32 *) dev->rd_msg_buf); + dev_dbg(&dev->pdev->dev, "discarding message " MEI_HDR_FMT "\n", + MEI_HDR_PRM(mei_hdr)); } return 0; @@ -742,8 +742,7 @@ static int mei_irq_thread_write_complete(struct mei_device *dev, s32 *slots, dev_dbg(&dev->pdev->dev, "buf: size = %d idx = %lu\n", cb->request_buffer.size, cb->buf_idx); - dev_dbg(&dev->pdev->dev, "msg: len = %d complete = %d\n", - mei_hdr->length, mei_hdr->msg_complete); + dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); *slots -= msg_slots; if (mei_write_message(dev, mei_hdr, @@ -790,7 +789,7 @@ static int mei_irq_thread_read_handler(struct mei_cl_cb *cmpl_list, dev_dbg(&dev->pdev->dev, "slots =%08x.\n", *slots); } mei_hdr = (struct mei_msg_hdr *) &dev->rd_msg_hdr; - dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", mei_hdr->length); + dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); if (mei_hdr->reserved || !dev->rd_msg_hdr) { dev_dbg(&dev->pdev->dev, "corrupted message header.\n"); @@ -835,13 +834,12 @@ static int mei_irq_thread_read_handler(struct mei_cl_cb *cmpl_list, (MEI_FILE_CONNECTED == dev->iamthif_cl.state) && (dev->iamthif_state == MEI_IAMTHIF_READING)) { dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_iamthif_message.\n"); - dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", - mei_hdr->length); + + dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); ret = mei_amthif_irq_read_message(cmpl_list, dev, mei_hdr); if (ret) goto end; - } else { dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_client_message.\n"); ret = mei_irq_thread_read_client_message(cmpl_list, diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index b281c235d898..7751b58540ac 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -552,8 +552,9 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, mei_hdr.host_addr = cl->host_client_id; mei_hdr.me_addr = cl->me_client_id; mei_hdr.reserved = 0; - dev_dbg(&dev->pdev->dev, "call mei_write_message header=%08x.\n", - *((u32 *) &mei_hdr)); + + dev_dbg(&dev->pdev->dev, "write " MEI_HDR_FMT "\n", + MEI_HDR_PRM(&mei_hdr)); if (mei_write_message(dev, &mei_hdr, write_cb->request_buffer.data)) { rets = -ENODEV; goto err; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 25da04549d04..da0980531c62 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -506,4 +506,9 @@ static inline struct mei_msg_hdr *mei_hbm_hdr(u32 *buf, size_t length) return hdr; } +#define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d comp=%1d" +#define MEI_HDR_PRM(hdr) \ + (hdr)->host_addr, (hdr)->me_addr, \ + (hdr)->length, (hdr)->msg_complete + #endif -- cgit v1.2.1 From 1d3f3da3e20b154eecf840c2e589130868a98634 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:01 +0200 Subject: mei: move internal host clients ids to mei_dev.h from hw.h Internal clients numbers are implementation choice and not defined by the hardware. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw.h | 5 ----- drivers/misc/mei/mei_dev.h | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index be8ca6b333ca..7d47366a90f4 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -31,11 +31,6 @@ #define MEI_IAMTHIF_STALL_TIMER 12 /* HPS */ #define MEI_IAMTHIF_READ_TIMER 10 /* HPS */ -/* - * Internal Clients Number - */ -#define MEI_WD_HOST_CLIENT_ID 1 -#define MEI_IAMTHIF_HOST_CLIENT_ID 2 /* * MEI device IDs diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index da0980531c62..39cd289415e0 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -72,6 +72,12 @@ extern const u8 mei_wd_state_independence_msg[3][4]; */ #define MEI_MAX_OPEN_HANDLE_COUNT (MEI_CLIENTS_MAX - 3) +/* + * Internal Clients Number + */ +#define MEI_WD_HOST_CLIENT_ID 1 +#define MEI_IAMTHIF_HOST_CLIENT_ID 2 + /* File state */ enum file_state { -- cgit v1.2.1 From 66ef5ea9b0984110eb62f46c40932d49707f89bb Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:02 +0200 Subject: mei: extract device dependent constants into hw-me.h Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.h | 167 +++++++++++++++++++++++++++++++++++++++++++++ drivers/misc/mei/hw.h | 98 -------------------------- drivers/misc/mei/mei_dev.h | 2 + 3 files changed, 169 insertions(+), 98 deletions(-) create mode 100644 drivers/misc/mei/hw-me.h diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h new file mode 100644 index 000000000000..a42b2a2bafa5 --- /dev/null +++ b/drivers/misc/mei/hw-me.h @@ -0,0 +1,167 @@ +/****************************************************************************** + * Intel Management Engine Interface (Intel MEI) Linux driver + * Intel MEI Interface Header + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2003 - 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Corporation. + * linux-mei@linux.intel.com + * http://www.intel.com + * + * BSD LICENSE + * + * Copyright(c) 2003 - 2012 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef _MEI_HW_MEI_H_ +#define _MEI_HW_MEI_H_ + +/* + * MEI device IDs + */ +#define MEI_DEV_ID_82946GZ 0x2974 /* 82946GZ/GL */ +#define MEI_DEV_ID_82G35 0x2984 /* 82G35 Express */ +#define MEI_DEV_ID_82Q965 0x2994 /* 82Q963/Q965 */ +#define MEI_DEV_ID_82G965 0x29A4 /* 82P965/G965 */ + +#define MEI_DEV_ID_82GM965 0x2A04 /* Mobile PM965/GM965 */ +#define MEI_DEV_ID_82GME965 0x2A14 /* Mobile GME965/GLE960 */ + +#define MEI_DEV_ID_ICH9_82Q35 0x29B4 /* 82Q35 Express */ +#define MEI_DEV_ID_ICH9_82G33 0x29C4 /* 82G33/G31/P35/P31 Express */ +#define MEI_DEV_ID_ICH9_82Q33 0x29D4 /* 82Q33 Express */ +#define MEI_DEV_ID_ICH9_82X38 0x29E4 /* 82X38/X48 Express */ +#define MEI_DEV_ID_ICH9_3200 0x29F4 /* 3200/3210 Server */ + +#define MEI_DEV_ID_ICH9_6 0x28B4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_7 0x28C4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_8 0x28D4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_9 0x28E4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_10 0x28F4 /* Bearlake */ + +#define MEI_DEV_ID_ICH9M_1 0x2A44 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_2 0x2A54 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_3 0x2A64 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_4 0x2A74 /* Cantiga */ + +#define MEI_DEV_ID_ICH10_1 0x2E04 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_2 0x2E14 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_3 0x2E24 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_4 0x2E34 /* Eaglelake */ + +#define MEI_DEV_ID_IBXPK_1 0x3B64 /* Calpella */ +#define MEI_DEV_ID_IBXPK_2 0x3B65 /* Calpella */ + +#define MEI_DEV_ID_CPT_1 0x1C3A /* Couger Point */ +#define MEI_DEV_ID_PBG_1 0x1D3A /* C600/X79 Patsburg */ + +#define MEI_DEV_ID_PPT_1 0x1E3A /* Panther Point */ +#define MEI_DEV_ID_PPT_2 0x1CBA /* Panther Point */ +#define MEI_DEV_ID_PPT_3 0x1DBA /* Panther Point */ + +#define MEI_DEV_ID_LPT 0x8C3A /* Lynx Point */ +#define MEI_DEV_ID_LPT_LP 0x9C3A /* Lynx Point LP */ +/* + * MEI HW Section + */ + +/* MEI registers */ +/* H_CB_WW - Host Circular Buffer (CB) Write Window register */ +#define H_CB_WW 0 +/* H_CSR - Host Control Status register */ +#define H_CSR 4 +/* ME_CB_RW - ME Circular Buffer Read Window register (read only) */ +#define ME_CB_RW 8 +/* ME_CSR_HA - ME Control Status Host Access register (read only) */ +#define ME_CSR_HA 0xC + + +/* register bits of H_CSR (Host Control Status register) */ +/* Host Circular Buffer Depth - maximum number of 32-bit entries in CB */ +#define H_CBD 0xFF000000 +/* Host Circular Buffer Write Pointer */ +#define H_CBWP 0x00FF0000 +/* Host Circular Buffer Read Pointer */ +#define H_CBRP 0x0000FF00 +/* Host Reset */ +#define H_RST 0x00000010 +/* Host Ready */ +#define H_RDY 0x00000008 +/* Host Interrupt Generate */ +#define H_IG 0x00000004 +/* Host Interrupt Status */ +#define H_IS 0x00000002 +/* Host Interrupt Enable */ +#define H_IE 0x00000001 + + +/* register bits of ME_CSR_HA (ME Control Status Host Access register) */ +/* ME CB (Circular Buffer) Depth HRA (Host Read Access) - host read only +access to ME_CBD */ +#define ME_CBD_HRA 0xFF000000 +/* ME CB Write Pointer HRA - host read only access to ME_CBWP */ +#define ME_CBWP_HRA 0x00FF0000 +/* ME CB Read Pointer HRA - host read only access to ME_CBRP */ +#define ME_CBRP_HRA 0x0000FF00 +/* ME Reset HRA - host read only access to ME_RST */ +#define ME_RST_HRA 0x00000010 +/* ME Ready HRA - host read only access to ME_RDY */ +#define ME_RDY_HRA 0x00000008 +/* ME Interrupt Generate HRA - host read only access to ME_IG */ +#define ME_IG_HRA 0x00000004 +/* ME Interrupt Status HRA - host read only access to ME_IS */ +#define ME_IS_HRA 0x00000002 +/* ME Interrupt Enable HRA - host read only access to ME_IE */ +#define ME_IE_HRA 0x00000001 + +#endif /* _MEI_HW_MEI_H_ */ diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 7d47366a90f4..ba7d06ce8858 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -32,104 +32,6 @@ #define MEI_IAMTHIF_READ_TIMER 10 /* HPS */ -/* - * MEI device IDs - */ -#define MEI_DEV_ID_82946GZ 0x2974 /* 82946GZ/GL */ -#define MEI_DEV_ID_82G35 0x2984 /* 82G35 Express */ -#define MEI_DEV_ID_82Q965 0x2994 /* 82Q963/Q965 */ -#define MEI_DEV_ID_82G965 0x29A4 /* 82P965/G965 */ - -#define MEI_DEV_ID_82GM965 0x2A04 /* Mobile PM965/GM965 */ -#define MEI_DEV_ID_82GME965 0x2A14 /* Mobile GME965/GLE960 */ - -#define MEI_DEV_ID_ICH9_82Q35 0x29B4 /* 82Q35 Express */ -#define MEI_DEV_ID_ICH9_82G33 0x29C4 /* 82G33/G31/P35/P31 Express */ -#define MEI_DEV_ID_ICH9_82Q33 0x29D4 /* 82Q33 Express */ -#define MEI_DEV_ID_ICH9_82X38 0x29E4 /* 82X38/X48 Express */ -#define MEI_DEV_ID_ICH9_3200 0x29F4 /* 3200/3210 Server */ - -#define MEI_DEV_ID_ICH9_6 0x28B4 /* Bearlake */ -#define MEI_DEV_ID_ICH9_7 0x28C4 /* Bearlake */ -#define MEI_DEV_ID_ICH9_8 0x28D4 /* Bearlake */ -#define MEI_DEV_ID_ICH9_9 0x28E4 /* Bearlake */ -#define MEI_DEV_ID_ICH9_10 0x28F4 /* Bearlake */ - -#define MEI_DEV_ID_ICH9M_1 0x2A44 /* Cantiga */ -#define MEI_DEV_ID_ICH9M_2 0x2A54 /* Cantiga */ -#define MEI_DEV_ID_ICH9M_3 0x2A64 /* Cantiga */ -#define MEI_DEV_ID_ICH9M_4 0x2A74 /* Cantiga */ - -#define MEI_DEV_ID_ICH10_1 0x2E04 /* Eaglelake */ -#define MEI_DEV_ID_ICH10_2 0x2E14 /* Eaglelake */ -#define MEI_DEV_ID_ICH10_3 0x2E24 /* Eaglelake */ -#define MEI_DEV_ID_ICH10_4 0x2E34 /* Eaglelake */ - -#define MEI_DEV_ID_IBXPK_1 0x3B64 /* Calpella */ -#define MEI_DEV_ID_IBXPK_2 0x3B65 /* Calpella */ - -#define MEI_DEV_ID_CPT_1 0x1C3A /* Couger Point */ -#define MEI_DEV_ID_PBG_1 0x1D3A /* C600/X79 Patsburg */ - -#define MEI_DEV_ID_PPT_1 0x1E3A /* Panther Point */ -#define MEI_DEV_ID_PPT_2 0x1CBA /* Panther Point */ -#define MEI_DEV_ID_PPT_3 0x1DBA /* Panther Point */ - -#define MEI_DEV_ID_LPT 0x8C3A /* Lynx Point */ -#define MEI_DEV_ID_LPT_LP 0x9C3A /* Lynx Point LP */ -/* - * MEI HW Section - */ - -/* MEI registers */ -/* H_CB_WW - Host Circular Buffer (CB) Write Window register */ -#define H_CB_WW 0 -/* H_CSR - Host Control Status register */ -#define H_CSR 4 -/* ME_CB_RW - ME Circular Buffer Read Window register (read only) */ -#define ME_CB_RW 8 -/* ME_CSR_HA - ME Control Status Host Access register (read only) */ -#define ME_CSR_HA 0xC - - -/* register bits of H_CSR (Host Control Status register) */ -/* Host Circular Buffer Depth - maximum number of 32-bit entries in CB */ -#define H_CBD 0xFF000000 -/* Host Circular Buffer Write Pointer */ -#define H_CBWP 0x00FF0000 -/* Host Circular Buffer Read Pointer */ -#define H_CBRP 0x0000FF00 -/* Host Reset */ -#define H_RST 0x00000010 -/* Host Ready */ -#define H_RDY 0x00000008 -/* Host Interrupt Generate */ -#define H_IG 0x00000004 -/* Host Interrupt Status */ -#define H_IS 0x00000002 -/* Host Interrupt Enable */ -#define H_IE 0x00000001 - - -/* register bits of ME_CSR_HA (ME Control Status Host Access register) */ -/* ME CB (Circular Buffer) Depth HRA (Host Read Access) - host read only -access to ME_CBD */ -#define ME_CBD_HRA 0xFF000000 -/* ME CB Write Pointer HRA - host read only access to ME_CBWP */ -#define ME_CBWP_HRA 0x00FF0000 -/* ME CB Read Pointer HRA - host read only access to ME_CBRP */ -#define ME_CBRP_HRA 0x0000FF00 -/* ME Reset HRA - host read only access to ME_RST */ -#define ME_RST_HRA 0x00000010 -/* ME Ready HRA - host read only access to ME_RDY */ -#define ME_RDY_HRA 0x00000008 -/* ME Interrupt Generate HRA - host read only access to ME_IG */ -#define ME_IG_HRA 0x00000004 -/* ME Interrupt Status HRA - host read only access to ME_IS */ -#define ME_IS_HRA 0x00000002 -/* ME Interrupt Enable HRA - host read only access to ME_IE */ -#define ME_IE_HRA 0x00000001 - /* * MEI Version */ diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 39cd289415e0..f0b02ba89ade 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -21,7 +21,9 @@ #include #include #include + #include "hw.h" +#include "hw-me.h" /* * watch dog definition -- cgit v1.2.1 From 47a73801f498883ea3acccb8f6ff1b5c7a3553de Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:03 +0200 Subject: mei: include local headers after the system ones first include linux/mei.h then only local headers to avoid possible false dependencies Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 3 +-- drivers/misc/mei/init.c | 4 ++-- drivers/misc/mei/interface.c | 3 ++- drivers/misc/mei/interrupt.c | 4 ++-- drivers/misc/mei/iorw.c | 4 ++-- drivers/misc/mei/main.c | 3 ++- drivers/misc/mei/wd.c | 4 ++-- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 77acd2349b1b..bb613f733309 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -31,10 +31,9 @@ #include #include +#include #include "mei_dev.h" -#include "hw.h" -#include #include "interface.h" const uuid_le mei_amthi_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac, diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index c0c0b3e22579..08884ef13f31 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -19,10 +19,10 @@ #include #include +#include + #include "mei_dev.h" -#include "hw.h" #include "interface.h" -#include const char *mei_dev_state_str(int state) { diff --git a/drivers/misc/mei/interface.c b/drivers/misc/mei/interface.c index 17d93f71d6bf..810431e30cd2 100644 --- a/drivers/misc/mei/interface.c +++ b/drivers/misc/mei/interface.c @@ -15,8 +15,9 @@ */ #include -#include "mei_dev.h" #include + +#include "mei_dev.h" #include "interface.h" diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index fe93d1c9c242..0a020ad92f00 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -21,9 +21,9 @@ #include #include -#include "mei_dev.h" #include -#include "hw.h" + +#include "mei_dev.h" #include "interface.h" diff --git a/drivers/misc/mei/iorw.c b/drivers/misc/mei/iorw.c index eb93a1b53b9b..7ccc3d8a079e 100644 --- a/drivers/misc/mei/iorw.c +++ b/drivers/misc/mei/iorw.c @@ -33,9 +33,9 @@ #include -#include "mei_dev.h" -#include "hw.h" #include + +#include "mei_dev.h" #include "interface.h" /** diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 7751b58540ac..da9426054815 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -37,8 +37,9 @@ #include #include -#include "mei_dev.h" #include + +#include "mei_dev.h" #include "interface.h" /* AMT device is a singleton on the platform */ diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index 9d4d4aa0f0e8..9c3738a4eb08 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -21,10 +21,10 @@ #include #include +#include + #include "mei_dev.h" -#include "hw.h" #include "interface.h" -#include static const u8 mei_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 }; static const u8 mei_stop_wd_params[] = { 0x02, 0x02, 0x14, 0x10 }; -- cgit v1.2.1 From fecb0d584ee96fb2ab3c65825029a66a23ee7e31 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:04 +0200 Subject: mei: kill not used BAR0 length and base variables 1. mem_base and mem_length are not used so we can delete them 2. add kdoc for mem_addr member of mei_device Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/mei_dev.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index f0b02ba89ade..6ea430e49f13 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -201,6 +201,7 @@ struct mei_cl { /** * struct mei_device - MEI private device struct + * @mem_addr - mem mapped base register address * @hbuf_depth - depth of host(write) buffer * @wr_ext_msg - buffer for hbm control responses (set in read cycle) */ @@ -221,11 +222,7 @@ struct mei_device { */ struct list_head file_list; long open_handle_count; - /* - * memory of device - */ - unsigned int mem_base; - unsigned int mem_length; + void __iomem *mem_addr; /* * lock for the device -- cgit v1.2.1 From db7da79df1a9eafb4f07653bf9011537325a9b62 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:05 +0200 Subject: mei: mei_me_client is not hw API move to mei_dev.h Move struct mei_me_client from hw.h to mei_dev.h as it is not part of the hardware API. The structutre doesn't have to be packed. Add kdoc for this structure. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw.h | 6 ------ drivers/misc/mei/mei_dev.h | 13 +++++++++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index ba7d06ce8858..6ebb369af668 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -230,11 +230,5 @@ struct hbm_flow_control { u8 reserved[MEI_FC_MESSAGE_RESERVED_LENGTH]; } __packed; -struct mei_me_client { - struct mei_client_properties props; - u8 client_id; - u8 mei_flow_ctrl_creds; -} __packed; - #endif diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 6ea430e49f13..228a98c97b37 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -158,6 +158,19 @@ struct mei_message_data { unsigned char *data; }; +/** + * struct mei_me_client - representation of me (fw) client + * + * @props - client properties + * @client_id - me client id + * @mei_flow_ctrl_creds - flow control credits + */ +struct mei_me_client { + struct mei_client_properties props; + u8 client_id; + u8 mei_flow_ctrl_creds; +}; + struct mei_cl; -- cgit v1.2.1 From 3a65dd4ea32c3e3a3befec58ad20d1c96580834e Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:06 +0200 Subject: mei: move hw dependent functions to interface.c 1. move direct register handling to interface.c and make them static 2. add new function mei_clear_interrupts that wraps direct register access 3. export other functions in mei_dev.h Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/init.c | 3 +- drivers/misc/mei/interface.c | 92 +++++++++++++++++++++++++++++++++++++++++++- drivers/misc/mei/interrupt.c | 24 +----------- drivers/misc/mei/mei_dev.h | 71 +++------------------------------- 4 files changed, 97 insertions(+), 93 deletions(-) diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 08884ef13f31..eb180555d282 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -142,8 +142,7 @@ int mei_hw_init(struct mei_device *dev) dev->host_hw_state, dev->me_hw_state); /* acknowledge interrupt and stop interupts */ - if ((dev->host_hw_state & H_IS) == H_IS) - mei_reg_write(dev, H_CSR, dev->host_hw_state); + mei_clear_interrupts(dev); /* Doesn't change in runtime */ dev->hbuf_depth = (dev->host_hw_state & H_CBD) >> 24; diff --git a/drivers/misc/mei/interface.c b/drivers/misc/mei/interface.c index 810431e30cd2..939d85b40738 100644 --- a/drivers/misc/mei/interface.c +++ b/drivers/misc/mei/interface.c @@ -20,7 +20,61 @@ #include "mei_dev.h" #include "interface.h" +/** + * mei_reg_read - Reads 32bit data from the mei device + * + * @dev: the device structure + * @offset: offset from which to read the data + * + * returns register value (u32) + */ +static inline u32 mei_reg_read(const struct mei_device *dev, + unsigned long offset) +{ + return ioread32(dev->mem_addr + offset); +} + + +/** + * mei_reg_write - Writes 32bit data to the mei device + * + * @dev: the device structure + * @offset: offset from which to write the data + * @value: register value to write (u32) + */ +static inline void mei_reg_write(const struct mei_device *dev, + unsigned long offset, u32 value) +{ + iowrite32(value, dev->mem_addr + offset); +} +/** + * mei_hcsr_read - Reads 32bit data from the host CSR + * + * @dev: the device structure + * + * returns the byte read. + */ +u32 mei_hcsr_read(const struct mei_device *dev) +{ + return mei_reg_read(dev, H_CSR); +} + +u32 mei_mecbrw_read(const struct mei_device *dev) +{ + return mei_reg_read(dev, ME_CB_RW); +} +/** + * mei_mecsr_read - Reads 32bit data from the ME CSR + * + * @dev: the device structure + * + * returns ME_CSR_HA register value (u32) + */ +u32 mei_mecsr_read(const struct mei_device *dev) +{ + return mei_reg_read(dev, ME_CSR_HA); +} /** * mei_set_csr_register - writes H_CSR register to the mei device, @@ -37,7 +91,18 @@ void mei_hcsr_set(struct mei_device *dev) } /** - * mei_csr_enable_interrupts - enables mei device interrupts + * mei_enable_interrupts - clear and stop interrupts + * + * @dev: the device structure + */ +void mei_clear_interrupts(struct mei_device *dev) +{ + if ((dev->host_hw_state & H_IS) == H_IS) + mei_reg_write(dev, H_CSR, dev->host_hw_state); +} + +/** + * mei_enable_interrupts - enables mei device interrupts * * @dev: the device structure */ @@ -48,7 +113,7 @@ void mei_enable_interrupts(struct mei_device *dev) } /** - * mei_csr_disable_interrupts - disables mei device interrupts + * mei_disable_interrupts - disables mei device interrupts * * @dev: the device structure */ @@ -58,6 +123,29 @@ void mei_disable_interrupts(struct mei_device *dev) mei_hcsr_set(dev); } + +/** + * mei_interrupt_quick_handler - The ISR of the MEI device + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + */ +irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) +{ + struct mei_device *dev = (struct mei_device *) dev_id; + u32 csr_reg = mei_hcsr_read(dev); + + if ((csr_reg & H_IS) != H_IS) + return IRQ_NONE; + + /* clear H_IS bit in H_CSR */ + mei_reg_write(dev, H_CSR, csr_reg); + + return IRQ_WAKE_THREAD; +} + /** * mei_hbuf_filled_slots - gets number of device filled buffer slots * diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 0a020ad92f00..cd89b68fcf43 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -27,28 +27,6 @@ #include "interface.h" -/** - * mei_interrupt_quick_handler - The ISR of the MEI device - * - * @irq: The irq number - * @dev_id: pointer to the device structure - * - * returns irqreturn_t - */ -irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) -{ - struct mei_device *dev = (struct mei_device *) dev_id; - u32 csr_reg = mei_hcsr_read(dev); - - if ((csr_reg & H_IS) != H_IS) - return IRQ_NONE; - - /* clear H_IS bit in H_CSR */ - mei_reg_write(dev, H_CSR, csr_reg); - - return IRQ_WAKE_THREAD; -} - /** * _mei_cmpl - processes completed operation. * @@ -1150,7 +1128,7 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) /* Ack the interrupt here * In case of MSI we don't go through the quick handler */ if (pci_dev_msi_enabled(dev->pdev)) - mei_reg_write(dev, H_CSR, dev->host_hw_state); + mei_clear_interrupts(dev); dev->me_hw_state = mei_mecsr_read(dev); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 228a98c97b37..472c9cacc543 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -440,79 +440,18 @@ int mei_amthif_irq_read(struct mei_device *dev, s32 *slots); * Register Access Function */ -/** - * mei_reg_read - Reads 32bit data from the mei device - * - * @dev: the device structure - * @offset: offset from which to read the data - * - * returns register value (u32) - */ -static inline u32 mei_reg_read(const struct mei_device *dev, - unsigned long offset) -{ - return ioread32(dev->mem_addr + offset); -} - -/** - * mei_reg_write - Writes 32bit data to the mei device - * - * @dev: the device structure - * @offset: offset from which to write the data - * @value: register value to write (u32) - */ -static inline void mei_reg_write(const struct mei_device *dev, - unsigned long offset, u32 value) -{ - iowrite32(value, dev->mem_addr + offset); -} - -/** - * mei_hcsr_read - Reads 32bit data from the host CSR - * - * @dev: the device structure - * - * returns the byte read. - */ -static inline u32 mei_hcsr_read(const struct mei_device *dev) -{ - return mei_reg_read(dev, H_CSR); -} +u32 mei_hcsr_read(const struct mei_device *dev); +u32 mei_mecsr_read(const struct mei_device *dev); +u32 mei_mecbrw_read(const struct mei_device *dev); -/** - * mei_mecsr_read - Reads 32bit data from the ME CSR - * - * @dev: the device structure - * - * returns ME_CSR_HA register value (u32) - */ -static inline u32 mei_mecsr_read(const struct mei_device *dev) -{ - return mei_reg_read(dev, ME_CSR_HA); -} - -/** - * get_me_cb_rw - Reads 32bit data from the mei ME_CB_RW register - * - * @dev: the device structure - * - * returns ME_CB_RW register value (u32) - */ -static inline u32 mei_mecbrw_read(const struct mei_device *dev) -{ - return mei_reg_read(dev, ME_CB_RW); -} - - -/* - * mei interface function prototypes - */ void mei_hcsr_set(struct mei_device *dev); void mei_csr_clear_his(struct mei_device *dev); +void mei_clear_interrupts(struct mei_device *dev); void mei_enable_interrupts(struct mei_device *dev); void mei_disable_interrupts(struct mei_device *dev); + static inline struct mei_msg_hdr *mei_hbm_hdr(u32 *buf, size_t length) { struct mei_msg_hdr *hdr = (struct mei_msg_hdr *)buf; -- cgit v1.2.1 From bb1b0133b3780987c2c74f267e294d016f9fa04c Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:07 +0200 Subject: mei: move host bus message handling to hbm.c for sake of more layered design we move host bus message handling to the new hbm.c file Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/Makefile | 1 + drivers/misc/mei/hbm.c | 440 +++++++++++++++++++++++++++++++++++++++++++ drivers/misc/mei/init.c | 111 ----------- drivers/misc/mei/interface.c | 78 -------- drivers/misc/mei/interrupt.c | 235 +---------------------- drivers/misc/mei/mei_dev.h | 10 + 6 files changed, 456 insertions(+), 419 deletions(-) create mode 100644 drivers/misc/mei/hbm.c diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 0017842e166c..1f382a5ca3a6 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -4,6 +4,7 @@ # obj-$(CONFIG_INTEL_MEI) += mei.o mei-objs := init.o +mei-objs += hbm.o mei-objs += interrupt.o mei-objs += interface.o mei-objs += iorw.o diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c new file mode 100644 index 000000000000..2c4c1bb6d36a --- /dev/null +++ b/drivers/misc/mei/hbm.c @@ -0,0 +1,440 @@ +/* + * + * 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 +#include +#include +#include + +#include "mei_dev.h" +#include "interface.h" + +/** + * host_start_message - mei host sends start message. + * + * @dev: the device structure + * + * returns none. + */ +void mei_host_start_message(struct mei_device *dev) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_host_version_request *start_req; + const size_t len = sizeof(struct hbm_host_version_request); + + mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + + /* host start message */ + start_req = (struct hbm_host_version_request *)&dev->wr_msg_buf[1]; + memset(start_req, 0, len); + start_req->hbm_cmd = HOST_START_REQ_CMD; + start_req->host_version.major_version = HBM_MAJOR_VERSION; + start_req->host_version.minor_version = HBM_MINOR_VERSION; + + dev->recvd_msg = false; + if (mei_write_message(dev, mei_hdr, (unsigned char *)start_req)) { + dev_dbg(&dev->pdev->dev, "write send version message to FW fail.\n"); + dev->dev_state = MEI_DEV_RESETING; + mei_reset(dev, 1); + } + dev->init_clients_state = MEI_START_MESSAGE; + dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; + return ; +} + +/** + * host_enum_clients_message - host sends enumeration client request message. + * + * @dev: the device structure + * + * returns none. + */ +void mei_host_enum_clients_message(struct mei_device *dev) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_host_enum_request *enum_req; + const size_t len = sizeof(struct hbm_host_enum_request); + /* enumerate clients */ + mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + + enum_req = (struct hbm_host_enum_request *) &dev->wr_msg_buf[1]; + memset(enum_req, 0, sizeof(struct hbm_host_enum_request)); + enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; + + if (mei_write_message(dev, mei_hdr, (unsigned char *)enum_req)) { + dev->dev_state = MEI_DEV_RESETING; + dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n"); + mei_reset(dev, 1); + } + dev->init_clients_state = MEI_ENUM_CLIENTS_MESSAGE; + dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; + return; +} + + +int mei_host_client_enumerate(struct mei_device *dev) +{ + + struct mei_msg_hdr *mei_hdr; + struct hbm_props_request *prop_req; + const size_t len = sizeof(struct hbm_props_request); + unsigned long next_client_index; + u8 client_num; + + + client_num = dev->me_client_presentation_num; + + next_client_index = find_next_bit(dev->me_clients_map, MEI_CLIENTS_MAX, + dev->me_client_index); + + /* We got all client properties */ + if (next_client_index == MEI_CLIENTS_MAX) { + schedule_work(&dev->init_work); + + return 0; + } + + dev->me_clients[client_num].client_id = next_client_index; + dev->me_clients[client_num].mei_flow_ctrl_creds = 0; + + mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + prop_req = (struct hbm_props_request *)&dev->wr_msg_buf[1]; + + memset(prop_req, 0, sizeof(struct hbm_props_request)); + + + prop_req->hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD; + prop_req->address = next_client_index; + + if (mei_write_message(dev, mei_hdr, (unsigned char *) prop_req)) { + dev->dev_state = MEI_DEV_RESETING; + dev_err(&dev->pdev->dev, "Properties request command failed\n"); + mei_reset(dev, 1); + + return -EIO; + } + + dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; + dev->me_client_index = next_client_index; + + return 0; +} + +/** + * mei_send_flow_control - sends flow control to fw. + * + * @dev: the device structure + * @cl: private data of the file object + * + * This function returns -EIO on write failure + */ +int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_msg_hdr *mei_hdr; + struct hbm_flow_control *flow_ctrl; + const size_t len = sizeof(struct hbm_flow_control); + + mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + + flow_ctrl = (struct hbm_flow_control *)&dev->wr_msg_buf[1]; + memset(flow_ctrl, 0, len); + flow_ctrl->hbm_cmd = MEI_FLOW_CONTROL_CMD; + flow_ctrl->host_addr = cl->host_client_id; + flow_ctrl->me_addr = cl->me_client_id; + /* FIXME: reserved !? */ + memset(flow_ctrl->reserved, 0, sizeof(flow_ctrl->reserved)); + dev_dbg(&dev->pdev->dev, "sending flow control host client = %d, ME client = %d\n", + cl->host_client_id, cl->me_client_id); + + return mei_write_message(dev, mei_hdr, (unsigned char *) flow_ctrl); +} + +/** + * mei_disconnect - sends disconnect message to fw. + * + * @dev: the device structure + * @cl: private data of the file object + * + * This function returns -EIO on write failure + */ +int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_msg_hdr *hdr; + struct hbm_client_connect_request *req; + const size_t len = sizeof(struct hbm_client_connect_request); + + hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + + req = (struct hbm_client_connect_request *)&dev->wr_msg_buf[1]; + memset(req, 0, len); + req->hbm_cmd = CLIENT_DISCONNECT_REQ_CMD; + req->host_addr = cl->host_client_id; + req->me_addr = cl->me_client_id; + req->reserved = 0; + + return mei_write_message(dev, hdr, (unsigned char *)req); +} + +/** + * mei_connect - sends connect message to fw. + * + * @dev: the device structure + * @cl: private data of the file object + * + * This function returns -EIO on write failure + */ +int mei_connect(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_msg_hdr *hdr; + struct hbm_client_connect_request *req; + const size_t len = sizeof(struct hbm_client_connect_request); + + hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + + req = (struct hbm_client_connect_request *) &dev->wr_msg_buf[1]; + req->hbm_cmd = CLIENT_CONNECT_REQ_CMD; + req->host_addr = cl->host_client_id; + req->me_addr = cl->me_client_id; + req->reserved = 0; + + return mei_write_message(dev, hdr, (unsigned char *) req); +} + +/** + * same_disconn_addr - tells if they have the same address + * + * @file: private data of the file object. + * @disconn: disconnection request. + * + * returns !=0, same; 0,not. + */ +static int same_disconn_addr(struct mei_cl *cl, + struct hbm_client_connect_request *req) +{ + return (cl->host_client_id == req->host_addr && + cl->me_client_id == req->me_addr); +} + +/** + * mei_client_disconnect_request - disconnects from request irq routine + * + * @dev: the device structure. + * @disconnect_req: disconnect request bus message. + */ +static void mei_client_disconnect_request(struct mei_device *dev, + struct hbm_client_connect_request *disconnect_req) +{ + struct hbm_client_connect_response *disconnect_res; + struct mei_cl *pos, *next; + const size_t len = sizeof(struct hbm_client_connect_response); + + list_for_each_entry_safe(pos, next, &dev->file_list, link) { + if (same_disconn_addr(pos, disconnect_req)) { + dev_dbg(&dev->pdev->dev, "disconnect request host client %d ME client %d.\n", + disconnect_req->host_addr, + disconnect_req->me_addr); + pos->state = MEI_FILE_DISCONNECTED; + pos->timer_count = 0; + if (pos == &dev->wd_cl) + dev->wd_pending = false; + else if (pos == &dev->iamthif_cl) + dev->iamthif_timer = 0; + + /* prepare disconnect response */ + (void)mei_hbm_hdr((u32 *)&dev->wr_ext_msg.hdr, len); + disconnect_res = + (struct hbm_client_connect_response *) + &dev->wr_ext_msg.data; + disconnect_res->hbm_cmd = CLIENT_DISCONNECT_RES_CMD; + disconnect_res->host_addr = pos->host_client_id; + disconnect_res->me_addr = pos->me_client_id; + disconnect_res->status = 0; + break; + } + } +} + + +/** + * mei_hbm_dispatch - bottom half read routine after ISR to + * handle the read bus message cmd processing. + * + * @dev: the device structure + * @mei_hdr: header of bus message + */ +void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) +{ + struct mei_bus_message *mei_msg; + struct mei_me_client *me_client; + struct hbm_host_version_response *version_res; + struct hbm_client_connect_response *connect_res; + struct hbm_client_connect_response *disconnect_res; + struct hbm_client_connect_request *disconnect_req; + struct hbm_flow_control *flow_control; + struct hbm_props_response *props_res; + struct hbm_host_enum_response *enum_res; + struct hbm_host_stop_request *stop_req; + + /* read the message to our buffer */ + BUG_ON(hdr->length >= sizeof(dev->rd_msg_buf)); + mei_read_slots(dev, dev->rd_msg_buf, hdr->length); + mei_msg = (struct mei_bus_message *)dev->rd_msg_buf; + + switch (mei_msg->hbm_cmd) { + case HOST_START_RES_CMD: + version_res = (struct hbm_host_version_response *)mei_msg; + if (version_res->host_version_supported) { + dev->version.major_version = HBM_MAJOR_VERSION; + dev->version.minor_version = HBM_MINOR_VERSION; + if (dev->dev_state == MEI_DEV_INIT_CLIENTS && + dev->init_clients_state == MEI_START_MESSAGE) { + dev->init_clients_timer = 0; + mei_host_enum_clients_message(dev); + } else { + dev->recvd_msg = false; + dev_dbg(&dev->pdev->dev, "IMEI reset due to received host start response bus message.\n"); + mei_reset(dev, 1); + return; + } + } else { + u32 *buf = dev->wr_msg_buf; + const size_t len = sizeof(struct hbm_host_stop_request); + + dev->version = version_res->me_max_version; + + /* send stop message */ + hdr = mei_hbm_hdr(&buf[0], len); + stop_req = (struct hbm_host_stop_request *)&buf[1]; + memset(stop_req, 0, len); + stop_req->hbm_cmd = HOST_STOP_REQ_CMD; + stop_req->reason = DRIVER_STOP_REQUEST; + + mei_write_message(dev, hdr, (unsigned char *)stop_req); + dev_dbg(&dev->pdev->dev, "version mismatch.\n"); + return; + } + + dev->recvd_msg = true; + dev_dbg(&dev->pdev->dev, "host start response message received.\n"); + break; + + case CLIENT_CONNECT_RES_CMD: + connect_res = (struct hbm_client_connect_response *) mei_msg; + mei_client_connect_response(dev, connect_res); + dev_dbg(&dev->pdev->dev, "client connect response message received.\n"); + wake_up(&dev->wait_recvd_msg); + break; + + case CLIENT_DISCONNECT_RES_CMD: + disconnect_res = (struct hbm_client_connect_response *) mei_msg; + mei_client_disconnect_response(dev, disconnect_res); + dev_dbg(&dev->pdev->dev, "client disconnect response message received.\n"); + wake_up(&dev->wait_recvd_msg); + break; + + case MEI_FLOW_CONTROL_CMD: + flow_control = (struct hbm_flow_control *) mei_msg; + mei_client_flow_control_response(dev, flow_control); + dev_dbg(&dev->pdev->dev, "client flow control response message received.\n"); + break; + + case HOST_CLIENT_PROPERTIES_RES_CMD: + props_res = (struct hbm_props_response *)mei_msg; + me_client = &dev->me_clients[dev->me_client_presentation_num]; + + if (props_res->status || !dev->me_clients) { + dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message wrong status.\n"); + mei_reset(dev, 1); + return; + } + + if (me_client->client_id != props_res->address) { + dev_err(&dev->pdev->dev, + "Host client properties reply mismatch\n"); + mei_reset(dev, 1); + + return; + } + + if (dev->dev_state != MEI_DEV_INIT_CLIENTS || + dev->init_clients_state != MEI_CLIENT_PROPERTIES_MESSAGE) { + dev_err(&dev->pdev->dev, + "Unexpected client properties reply\n"); + mei_reset(dev, 1); + + return; + } + + me_client->props = props_res->client_properties; + dev->me_client_index++; + dev->me_client_presentation_num++; + + mei_host_client_enumerate(dev); + + break; + + case HOST_ENUM_RES_CMD: + enum_res = (struct hbm_host_enum_response *) mei_msg; + memcpy(dev->me_clients_map, enum_res->valid_addresses, 32); + if (dev->dev_state == MEI_DEV_INIT_CLIENTS && + dev->init_clients_state == MEI_ENUM_CLIENTS_MESSAGE) { + dev->init_clients_timer = 0; + dev->me_client_presentation_num = 0; + dev->me_client_index = 0; + mei_allocate_me_clients_storage(dev); + dev->init_clients_state = + MEI_CLIENT_PROPERTIES_MESSAGE; + + mei_host_client_enumerate(dev); + } else { + dev_dbg(&dev->pdev->dev, "reset due to received host enumeration clients response bus message.\n"); + mei_reset(dev, 1); + return; + } + break; + + case HOST_STOP_RES_CMD: + dev->dev_state = MEI_DEV_DISABLED; + dev_dbg(&dev->pdev->dev, "resetting because of FW stop response.\n"); + mei_reset(dev, 1); + break; + + case CLIENT_DISCONNECT_REQ_CMD: + /* search for client */ + disconnect_req = (struct hbm_client_connect_request *)mei_msg; + mei_client_disconnect_request(dev, disconnect_req); + break; + + case ME_STOP_REQ_CMD: + { + /* prepare stop request: sent in next interrupt event */ + + const size_t len = sizeof(struct hbm_host_stop_request); + + hdr = mei_hbm_hdr((u32 *)&dev->wr_ext_msg.hdr, len); + stop_req = (struct hbm_host_stop_request *)&dev->wr_ext_msg.data; + memset(stop_req, 0, len); + stop_req->hbm_cmd = HOST_STOP_REQ_CMD; + stop_req->reason = DRIVER_STOP_REQUEST; + break; + } + default: + BUG(); + break; + + } +} + diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index eb180555d282..0536170ff856 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -320,70 +320,6 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) } - -/** - * host_start_message - mei host sends start message. - * - * @dev: the device structure - * - * returns none. - */ -void mei_host_start_message(struct mei_device *dev) -{ - struct mei_msg_hdr *mei_hdr; - struct hbm_host_version_request *start_req; - const size_t len = sizeof(struct hbm_host_version_request); - - mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); - - /* host start message */ - start_req = (struct hbm_host_version_request *)&dev->wr_msg_buf[1]; - memset(start_req, 0, len); - start_req->hbm_cmd = HOST_START_REQ_CMD; - start_req->host_version.major_version = HBM_MAJOR_VERSION; - start_req->host_version.minor_version = HBM_MINOR_VERSION; - - dev->recvd_msg = false; - if (mei_write_message(dev, mei_hdr, (unsigned char *)start_req)) { - dev_dbg(&dev->pdev->dev, "write send version message to FW fail.\n"); - dev->dev_state = MEI_DEV_RESETING; - mei_reset(dev, 1); - } - dev->init_clients_state = MEI_START_MESSAGE; - dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; - return ; -} - -/** - * host_enum_clients_message - host sends enumeration client request message. - * - * @dev: the device structure - * - * returns none. - */ -void mei_host_enum_clients_message(struct mei_device *dev) -{ - struct mei_msg_hdr *mei_hdr; - struct hbm_host_enum_request *enum_req; - const size_t len = sizeof(struct hbm_host_enum_request); - /* enumerate clients */ - mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); - - enum_req = (struct hbm_host_enum_request *) &dev->wr_msg_buf[1]; - memset(enum_req, 0, sizeof(struct hbm_host_enum_request)); - enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; - - if (mei_write_message(dev, mei_hdr, (unsigned char *)enum_req)) { - dev->dev_state = MEI_DEV_RESETING; - dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n"); - mei_reset(dev, 1); - } - dev->init_clients_state = MEI_ENUM_CLIENTS_MESSAGE; - dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; - return; -} - - /** * allocate_me_clients_storage - allocates storage for me clients * @@ -457,53 +393,6 @@ void mei_host_client_init(struct work_struct *work) mutex_unlock(&dev->device_lock); } -int mei_host_client_enumerate(struct mei_device *dev) -{ - - struct mei_msg_hdr *mei_hdr; - struct hbm_props_request *prop_req; - const size_t len = sizeof(struct hbm_props_request); - unsigned long next_client_index; - u8 client_num; - - - client_num = dev->me_client_presentation_num; - - next_client_index = find_next_bit(dev->me_clients_map, MEI_CLIENTS_MAX, - dev->me_client_index); - - /* We got all client properties */ - if (next_client_index == MEI_CLIENTS_MAX) { - schedule_work(&dev->init_work); - - return 0; - } - - dev->me_clients[client_num].client_id = next_client_index; - dev->me_clients[client_num].mei_flow_ctrl_creds = 0; - - mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); - prop_req = (struct hbm_props_request *)&dev->wr_msg_buf[1]; - - memset(prop_req, 0, sizeof(struct hbm_props_request)); - - - prop_req->hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD; - prop_req->address = next_client_index; - - if (mei_write_message(dev, mei_hdr, (unsigned char *) prop_req)) { - dev->dev_state = MEI_DEV_RESETING; - dev_err(&dev->pdev->dev, "Properties request command failed\n"); - mei_reset(dev, 1); - - return -EIO; - } - - dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; - dev->me_client_index = next_client_index; - - return 0; -} /** * mei_init_file_private - initializes private file structure. diff --git a/drivers/misc/mei/interface.c b/drivers/misc/mei/interface.c index 939d85b40738..155bd7efe4e5 100644 --- a/drivers/misc/mei/interface.c +++ b/drivers/misc/mei/interface.c @@ -367,34 +367,6 @@ int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl) return -ENOENT; } -/** - * mei_send_flow_control - sends flow control to fw. - * - * @dev: the device structure - * @cl: private data of the file object - * - * This function returns -EIO on write failure - */ -int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl) -{ - struct mei_msg_hdr *mei_hdr; - struct hbm_flow_control *flow_ctrl; - const size_t len = sizeof(struct hbm_flow_control); - - mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); - - flow_ctrl = (struct hbm_flow_control *)&dev->wr_msg_buf[1]; - memset(flow_ctrl, 0, len); - flow_ctrl->hbm_cmd = MEI_FLOW_CONTROL_CMD; - flow_ctrl->host_addr = cl->host_client_id; - flow_ctrl->me_addr = cl->me_client_id; - /* FIXME: reserved !? */ - memset(flow_ctrl->reserved, 0, sizeof(flow_ctrl->reserved)); - dev_dbg(&dev->pdev->dev, "sending flow control host client = %d, ME client = %d\n", - cl->host_client_id, cl->me_client_id); - - return mei_write_message(dev, mei_hdr, (unsigned char *) flow_ctrl); -} /** * mei_other_client_is_connecting - checks if other @@ -421,53 +393,3 @@ int mei_other_client_is_connecting(struct mei_device *dev, return 0; } -/** - * mei_disconnect - sends disconnect message to fw. - * - * @dev: the device structure - * @cl: private data of the file object - * - * This function returns -EIO on write failure - */ -int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) -{ - struct mei_msg_hdr *hdr; - struct hbm_client_connect_request *req; - const size_t len = sizeof(struct hbm_client_connect_request); - - hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); - - req = (struct hbm_client_connect_request *)&dev->wr_msg_buf[1]; - memset(req, 0, len); - req->hbm_cmd = CLIENT_DISCONNECT_REQ_CMD; - req->host_addr = cl->host_client_id; - req->me_addr = cl->me_client_id; - req->reserved = 0; - - return mei_write_message(dev, hdr, (unsigned char *)req); -} - -/** - * mei_connect - sends connect message to fw. - * - * @dev: the device structure - * @cl: private data of the file object - * - * This function returns -EIO on write failure - */ -int mei_connect(struct mei_device *dev, struct mei_cl *cl) -{ - struct mei_msg_hdr *hdr; - struct hbm_client_connect_request *req; - const size_t len = sizeof(struct hbm_client_connect_request); - - hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); - - req = (struct hbm_client_connect_request *) &dev->wr_msg_buf[1]; - req->hbm_cmd = CLIENT_CONNECT_REQ_CMD; - req->host_addr = cl->host_client_id; - req->me_addr = cl->me_client_id; - req->reserved = 0; - - return mei_write_message(dev, hdr, (unsigned char *) req); -} diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index cd89b68fcf43..d4312a8e139a 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -208,7 +208,7 @@ static bool is_treat_specially_client(struct mei_cl *cl, * @dev: the device structure * @rs: connect response bus message */ -static void mei_client_connect_response(struct mei_device *dev, +void mei_client_connect_response(struct mei_device *dev, struct hbm_client_connect_response *rs) { @@ -261,8 +261,8 @@ static void mei_client_connect_response(struct mei_device *dev, * @dev: the device structure * @rs: disconnect response bus message */ -static void mei_client_disconnect_response(struct mei_device *dev, - struct hbm_client_connect_response *rs) +void mei_client_disconnect_response(struct mei_device *dev, + struct hbm_client_connect_response *rs) { struct mei_cl *cl; struct mei_cl_cb *pos = NULL, *next = NULL; @@ -347,7 +347,7 @@ static void add_single_flow_creds(struct mei_device *dev, * @dev: the device structure * @flow_control: flow control response bus message */ -static void mei_client_flow_control_response(struct mei_device *dev, +void mei_client_flow_control_response(struct mei_device *dev, struct hbm_flow_control *flow_control) { struct mei_cl *cl_pos = NULL; @@ -381,231 +381,6 @@ static void mei_client_flow_control_response(struct mei_device *dev, } } -/** - * same_disconn_addr - tells if they have the same address - * - * @file: private data of the file object. - * @disconn: disconnection request. - * - * returns !=0, same; 0,not. - */ -static int same_disconn_addr(struct mei_cl *cl, - struct hbm_client_connect_request *req) -{ - return (cl->host_client_id == req->host_addr && - cl->me_client_id == req->me_addr); -} - -/** - * mei_client_disconnect_request - disconnects from request irq routine - * - * @dev: the device structure. - * @disconnect_req: disconnect request bus message. - */ -static void mei_client_disconnect_request(struct mei_device *dev, - struct hbm_client_connect_request *disconnect_req) -{ - struct hbm_client_connect_response *disconnect_res; - struct mei_cl *pos, *next; - const size_t len = sizeof(struct hbm_client_connect_response); - - list_for_each_entry_safe(pos, next, &dev->file_list, link) { - if (same_disconn_addr(pos, disconnect_req)) { - dev_dbg(&dev->pdev->dev, "disconnect request host client %d ME client %d.\n", - disconnect_req->host_addr, - disconnect_req->me_addr); - pos->state = MEI_FILE_DISCONNECTED; - pos->timer_count = 0; - if (pos == &dev->wd_cl) - dev->wd_pending = false; - else if (pos == &dev->iamthif_cl) - dev->iamthif_timer = 0; - - /* prepare disconnect response */ - (void)mei_hbm_hdr((u32 *)&dev->wr_ext_msg.hdr, len); - disconnect_res = - (struct hbm_client_connect_response *) - &dev->wr_ext_msg.data; - disconnect_res->hbm_cmd = CLIENT_DISCONNECT_RES_CMD; - disconnect_res->host_addr = pos->host_client_id; - disconnect_res->me_addr = pos->me_client_id; - disconnect_res->status = 0; - break; - } - } -} - -/** - * mei_irq_thread_read_bus_message - bottom half read routine after ISR to - * handle the read bus message cmd processing. - * - * @dev: the device structure - * @mei_hdr: header of bus message - */ -static void mei_irq_thread_read_bus_message(struct mei_device *dev, - struct mei_msg_hdr *hdr) -{ - struct mei_bus_message *mei_msg; - struct mei_me_client *me_client; - struct hbm_host_version_response *version_res; - struct hbm_client_connect_response *connect_res; - struct hbm_client_connect_response *disconnect_res; - struct hbm_client_connect_request *disconnect_req; - struct hbm_flow_control *flow_control; - struct hbm_props_response *props_res; - struct hbm_host_enum_response *enum_res; - struct hbm_host_stop_request *stop_req; - - /* read the message to our buffer */ - BUG_ON(hdr->length >= sizeof(dev->rd_msg_buf)); - mei_read_slots(dev, dev->rd_msg_buf, hdr->length); - mei_msg = (struct mei_bus_message *)dev->rd_msg_buf; - - switch (mei_msg->hbm_cmd) { - case HOST_START_RES_CMD: - version_res = (struct hbm_host_version_response *) mei_msg; - if (version_res->host_version_supported) { - dev->version.major_version = HBM_MAJOR_VERSION; - dev->version.minor_version = HBM_MINOR_VERSION; - if (dev->dev_state == MEI_DEV_INIT_CLIENTS && - dev->init_clients_state == MEI_START_MESSAGE) { - dev->init_clients_timer = 0; - mei_host_enum_clients_message(dev); - } else { - dev->recvd_msg = false; - dev_dbg(&dev->pdev->dev, "IMEI reset due to received host start response bus message.\n"); - mei_reset(dev, 1); - return; - } - } else { - u32 *buf = dev->wr_msg_buf; - const size_t len = sizeof(struct hbm_host_stop_request); - - dev->version = version_res->me_max_version; - - /* send stop message */ - hdr = mei_hbm_hdr(&buf[0], len); - stop_req = (struct hbm_host_stop_request *)&buf[1]; - memset(stop_req, 0, len); - stop_req->hbm_cmd = HOST_STOP_REQ_CMD; - stop_req->reason = DRIVER_STOP_REQUEST; - - mei_write_message(dev, hdr, (unsigned char *)stop_req); - dev_dbg(&dev->pdev->dev, "version mismatch.\n"); - return; - } - - dev->recvd_msg = true; - dev_dbg(&dev->pdev->dev, "host start response message received.\n"); - break; - - case CLIENT_CONNECT_RES_CMD: - connect_res = (struct hbm_client_connect_response *) mei_msg; - mei_client_connect_response(dev, connect_res); - dev_dbg(&dev->pdev->dev, "client connect response message received.\n"); - wake_up(&dev->wait_recvd_msg); - break; - - case CLIENT_DISCONNECT_RES_CMD: - disconnect_res = (struct hbm_client_connect_response *) mei_msg; - mei_client_disconnect_response(dev, disconnect_res); - dev_dbg(&dev->pdev->dev, "client disconnect response message received.\n"); - wake_up(&dev->wait_recvd_msg); - break; - - case MEI_FLOW_CONTROL_CMD: - flow_control = (struct hbm_flow_control *) mei_msg; - mei_client_flow_control_response(dev, flow_control); - dev_dbg(&dev->pdev->dev, "client flow control response message received.\n"); - break; - - case HOST_CLIENT_PROPERTIES_RES_CMD: - props_res = (struct hbm_props_response *)mei_msg; - me_client = &dev->me_clients[dev->me_client_presentation_num]; - - if (props_res->status || !dev->me_clients) { - dev_dbg(&dev->pdev->dev, "reset due to received host client properties response bus message wrong status.\n"); - mei_reset(dev, 1); - return; - } - - if (me_client->client_id != props_res->address) { - dev_err(&dev->pdev->dev, - "Host client properties reply mismatch\n"); - mei_reset(dev, 1); - - return; - } - - if (dev->dev_state != MEI_DEV_INIT_CLIENTS || - dev->init_clients_state != MEI_CLIENT_PROPERTIES_MESSAGE) { - dev_err(&dev->pdev->dev, - "Unexpected client properties reply\n"); - mei_reset(dev, 1); - - return; - } - - me_client->props = props_res->client_properties; - dev->me_client_index++; - dev->me_client_presentation_num++; - - mei_host_client_enumerate(dev); - - break; - - case HOST_ENUM_RES_CMD: - enum_res = (struct hbm_host_enum_response *) mei_msg; - memcpy(dev->me_clients_map, enum_res->valid_addresses, 32); - if (dev->dev_state == MEI_DEV_INIT_CLIENTS && - dev->init_clients_state == MEI_ENUM_CLIENTS_MESSAGE) { - dev->init_clients_timer = 0; - dev->me_client_presentation_num = 0; - dev->me_client_index = 0; - mei_allocate_me_clients_storage(dev); - dev->init_clients_state = - MEI_CLIENT_PROPERTIES_MESSAGE; - - mei_host_client_enumerate(dev); - } else { - dev_dbg(&dev->pdev->dev, "reset due to received host enumeration clients response bus message.\n"); - mei_reset(dev, 1); - return; - } - break; - - case HOST_STOP_RES_CMD: - dev->dev_state = MEI_DEV_DISABLED; - dev_dbg(&dev->pdev->dev, "resetting because of FW stop response.\n"); - mei_reset(dev, 1); - break; - - case CLIENT_DISCONNECT_REQ_CMD: - /* search for client */ - disconnect_req = (struct hbm_client_connect_request *)mei_msg; - mei_client_disconnect_request(dev, disconnect_req); - break; - - case ME_STOP_REQ_CMD: - { - /* prepare stop request: sent in next interrupt event */ - - const size_t len = sizeof(struct hbm_host_stop_request); - - hdr = mei_hbm_hdr((u32 *)&dev->wr_ext_msg.hdr, len); - stop_req = (struct hbm_host_stop_request *)&dev->wr_ext_msg.data; - memset(stop_req, 0, len); - stop_req->hbm_cmd = HOST_STOP_REQ_CMD; - stop_req->reason = DRIVER_STOP_REQUEST; - break; - } - default: - BUG(); - break; - - } -} - /** * _mei_hb_read - processes read related operation. @@ -806,7 +581,7 @@ static int mei_irq_thread_read_handler(struct mei_cl_cb *cmpl_list, /* decide where to read the message too */ if (!mei_hdr->host_addr) { dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_bus_message.\n"); - mei_irq_thread_read_bus_message(dev, mei_hdr); + mei_hbm_dispatch(dev, mei_hdr); dev_dbg(&dev->pdev->dev, "end mei_irq_thread_read_bus_message.\n"); } else if (mei_hdr->host_addr == dev->iamthif_cl.host_client_id && (MEI_FILE_CONNECTED == dev->iamthif_cl.state) && diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 472c9cacc543..b437f8b15406 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -402,6 +402,14 @@ int mei_ioctl_connect_client(struct file *file, int mei_start_read(struct mei_device *dev, struct mei_cl *cl); +void mei_client_disconnect_response(struct mei_device *dev, + struct hbm_client_connect_response *rs); + +void mei_client_connect_response(struct mei_device *dev, + struct hbm_client_connect_response *rs); + +void mei_client_flow_control_response(struct mei_device *dev, + struct hbm_flow_control *flow_control); /* * AMTHIF - AMT Host Interface Functions */ @@ -452,6 +460,8 @@ void mei_enable_interrupts(struct mei_device *dev); void mei_disable_interrupts(struct mei_device *dev); +void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr); + static inline struct mei_msg_hdr *mei_hbm_hdr(u32 *buf, size_t length) { struct mei_msg_hdr *hdr = (struct mei_msg_hdr *)buf; -- cgit v1.2.1 From 2efdf54603806cba681f7a09ec6c6205bddfd9a9 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:08 +0200 Subject: mei: drop nonexistent function prototype mei_amthif_read_message prototype was not dropped in one of the moving/renaming patches Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/mei_dev.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index b437f8b15406..107a820a6ba4 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -433,9 +433,6 @@ struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev, void mei_amthif_run_next_cmd(struct mei_device *dev); -int mei_amthif_read_message(struct mei_cl_cb *complete_list, - struct mei_device *dev, struct mei_msg_hdr *mei_hdr); - int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list); -- cgit v1.2.1 From cd51ed649fa4bd55c6a78db52b57260797ed56b4 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:09 +0200 Subject: mei: simplify preparing client host bus messages Define a new parent type mei_hbm_cl_cmd for hbm commands that are sent on behalf of specific ME client. This allows us compacting boilerplate code via mei_hbm_cl_hdr function Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hbm.c | 110 ++++++++++++++++++++++++------------------------- drivers/misc/mei/hw.h | 16 +++++++ 2 files changed, 70 insertions(+), 56 deletions(-) diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 2c4c1bb6d36a..bc36c23cd2db 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -22,6 +22,42 @@ #include "mei_dev.h" #include "interface.h" +/** + * mei_hbm_cl_hdr - construct client hbm header + * @cl: - client + * @hbm_cmd: host bus message command + * @buf: buffer for cl header + * @len: buffer length + */ +static inline +void mei_hbm_cl_hdr(struct mei_cl *cl, u8 hbm_cmd, void *buf, size_t len) +{ + struct mei_hbm_cl_cmd *cmd = buf; + + memset(cmd, 0, len); + + cmd->hbm_cmd = hbm_cmd; + cmd->host_addr = cl->host_client_id; + cmd->me_addr = cl->me_client_id; +} + +/** + * same_disconn_addr - tells if they have the same address + * + * @file: private data of the file object. + * @disconn: disconnection request. + * + * returns true if addres are same + */ +static inline +bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf) +{ + struct mei_hbm_cl_cmd *cmd = buf; + return cl->host_client_id == cmd->host_addr && + cl->me_client_id == cmd->me_addr; +} + + /** * host_start_message - mei host sends start message. * @@ -144,22 +180,16 @@ int mei_host_client_enumerate(struct mei_device *dev) int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl) { struct mei_msg_hdr *mei_hdr; - struct hbm_flow_control *flow_ctrl; + unsigned char *buf = (unsigned char *)&dev->wr_msg_buf[1]; const size_t len = sizeof(struct hbm_flow_control); mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + mei_hbm_cl_hdr(cl, MEI_FLOW_CONTROL_CMD, buf, len); - flow_ctrl = (struct hbm_flow_control *)&dev->wr_msg_buf[1]; - memset(flow_ctrl, 0, len); - flow_ctrl->hbm_cmd = MEI_FLOW_CONTROL_CMD; - flow_ctrl->host_addr = cl->host_client_id; - flow_ctrl->me_addr = cl->me_client_id; - /* FIXME: reserved !? */ - memset(flow_ctrl->reserved, 0, sizeof(flow_ctrl->reserved)); dev_dbg(&dev->pdev->dev, "sending flow control host client = %d, ME client = %d\n", cl->host_client_id, cl->me_client_id); - return mei_write_message(dev, mei_hdr, (unsigned char *) flow_ctrl); + return mei_write_message(dev, mei_hdr, buf); } /** @@ -173,19 +203,13 @@ int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl) int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) { struct mei_msg_hdr *hdr; - struct hbm_client_connect_request *req; + unsigned char *buf = (unsigned char *)&dev->wr_msg_buf[1]; const size_t len = sizeof(struct hbm_client_connect_request); hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_REQ_CMD, buf, len); - req = (struct hbm_client_connect_request *)&dev->wr_msg_buf[1]; - memset(req, 0, len); - req->hbm_cmd = CLIENT_DISCONNECT_REQ_CMD; - req->host_addr = cl->host_client_id; - req->me_addr = cl->me_client_id; - req->reserved = 0; - - return mei_write_message(dev, hdr, (unsigned char *)req); + return mei_write_message(dev, hdr, buf); } /** @@ -199,33 +223,13 @@ int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) int mei_connect(struct mei_device *dev, struct mei_cl *cl) { struct mei_msg_hdr *hdr; - struct hbm_client_connect_request *req; + unsigned char *buf = (unsigned char *)&dev->wr_msg_buf[1]; const size_t len = sizeof(struct hbm_client_connect_request); hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + mei_hbm_cl_hdr(cl, CLIENT_CONNECT_REQ_CMD, buf, len); - req = (struct hbm_client_connect_request *) &dev->wr_msg_buf[1]; - req->hbm_cmd = CLIENT_CONNECT_REQ_CMD; - req->host_addr = cl->host_client_id; - req->me_addr = cl->me_client_id; - req->reserved = 0; - - return mei_write_message(dev, hdr, (unsigned char *) req); -} - -/** - * same_disconn_addr - tells if they have the same address - * - * @file: private data of the file object. - * @disconn: disconnection request. - * - * returns !=0, same; 0,not. - */ -static int same_disconn_addr(struct mei_cl *cl, - struct hbm_client_connect_request *req) -{ - return (cl->host_client_id == req->host_addr && - cl->me_client_id == req->me_addr); + return mei_write_message(dev, hdr, buf); } /** @@ -237,31 +241,25 @@ static int same_disconn_addr(struct mei_cl *cl, static void mei_client_disconnect_request(struct mei_device *dev, struct hbm_client_connect_request *disconnect_req) { - struct hbm_client_connect_response *disconnect_res; - struct mei_cl *pos, *next; + struct mei_cl *cl, *next; const size_t len = sizeof(struct hbm_client_connect_response); - list_for_each_entry_safe(pos, next, &dev->file_list, link) { - if (same_disconn_addr(pos, disconnect_req)) { + list_for_each_entry_safe(cl, next, &dev->file_list, link) { + if (mei_hbm_cl_addr_equal(cl, disconnect_req)) { dev_dbg(&dev->pdev->dev, "disconnect request host client %d ME client %d.\n", disconnect_req->host_addr, disconnect_req->me_addr); - pos->state = MEI_FILE_DISCONNECTED; - pos->timer_count = 0; - if (pos == &dev->wd_cl) + cl->state = MEI_FILE_DISCONNECTED; + cl->timer_count = 0; + if (cl == &dev->wd_cl) dev->wd_pending = false; - else if (pos == &dev->iamthif_cl) + else if (cl == &dev->iamthif_cl) dev->iamthif_timer = 0; /* prepare disconnect response */ (void)mei_hbm_hdr((u32 *)&dev->wr_ext_msg.hdr, len); - disconnect_res = - (struct hbm_client_connect_response *) - &dev->wr_ext_msg.data; - disconnect_res->hbm_cmd = CLIENT_DISCONNECT_RES_CMD; - disconnect_res->host_addr = pos->host_client_id; - disconnect_res->me_addr = pos->me_client_id; - disconnect_res->status = 0; + mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_RES_CMD, + &dev->wr_ext_msg.data, len); break; } } diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 6ebb369af668..cb2f556b4252 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -121,6 +121,22 @@ struct mei_bus_message { u8 data[0]; } __packed; +/** + * struct hbm_cl_cmd - client specific host bus command + * CONNECT, DISCONNECT, and FlOW CONTROL + * + * @hbm_cmd - bus message command header + * @me_addr - address of the client in ME + * @host_addr - address of the client in the driver + * @data + */ +struct mei_hbm_cl_cmd { + u8 hbm_cmd; + u8 me_addr; + u8 host_addr; + u8 data; +}; + struct hbm_version { u8 minor_version; u8 major_version; -- cgit v1.2.1 From e46f187487a8c28e64417e51ba628746a5397838 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:10 +0200 Subject: mei: use structured buffer for the write buffer We can drop useless castings and use proper types. We remove the casting in mei_hbm_hdr function and add new function mei_hbm_stop_request_prepare that utilize the new structure Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 25 ++++---- drivers/misc/mei/hbm.c | 134 ++++++++++++++++++++++--------------------- drivers/misc/mei/interrupt.c | 25 ++++---- drivers/misc/mei/mei_dev.h | 12 ++-- drivers/misc/mei/wd.c | 17 +++--- 5 files changed, 108 insertions(+), 105 deletions(-) diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index bb613f733309..f9d458cced21 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -432,34 +432,33 @@ unsigned int mei_amthif_poll(struct mei_device *dev, int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list) { - struct mei_msg_hdr *mei_hdr; + struct mei_msg_hdr mei_hdr; struct mei_cl *cl = cb->cl; size_t len = dev->iamthif_msg_buf_size - dev->iamthif_msg_buf_index; size_t msg_slots = mei_data2slots(len); - mei_hdr = (struct mei_msg_hdr *)&dev->wr_msg_buf[0]; - mei_hdr->host_addr = cl->host_client_id; - mei_hdr->me_addr = cl->me_client_id; - mei_hdr->reserved = 0; + mei_hdr.host_addr = cl->host_client_id; + mei_hdr.me_addr = cl->me_client_id; + mei_hdr.reserved = 0; if (*slots >= msg_slots) { - mei_hdr->length = len; - mei_hdr->msg_complete = 1; + mei_hdr.length = len; + mei_hdr.msg_complete = 1; /* Split the message only if we can write the whole host buffer */ } else if (*slots == dev->hbuf_depth) { msg_slots = *slots; len = (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); - mei_hdr->length = len; - mei_hdr->msg_complete = 0; + mei_hdr.length = len; + mei_hdr.msg_complete = 0; } else { /* wait for next time the host buffer is empty */ return 0; } - dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); + dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(&mei_hdr)); *slots -= msg_slots; - if (mei_write_message(dev, mei_hdr, + if (mei_write_message(dev, &mei_hdr, dev->iamthif_msg_buf + dev->iamthif_msg_buf_index)) { dev->iamthif_state = MEI_IAMTHIF_IDLE; cl->status = -ENODEV; @@ -470,10 +469,10 @@ int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, if (mei_flow_ctrl_reduce(dev, cl)) return -ENODEV; - dev->iamthif_msg_buf_index += mei_hdr->length; + dev->iamthif_msg_buf_index += mei_hdr.length; cl->status = 0; - if (mei_hdr->msg_complete) { + if (mei_hdr.msg_complete) { dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; dev->iamthif_flow_control_pending = true; diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index bc36c23cd2db..e9ba51d5a46c 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -67,21 +67,21 @@ bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf) */ void mei_host_start_message(struct mei_device *dev) { - struct mei_msg_hdr *mei_hdr; + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; struct hbm_host_version_request *start_req; const size_t len = sizeof(struct hbm_host_version_request); - mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + mei_hbm_hdr(mei_hdr, len); /* host start message */ - start_req = (struct hbm_host_version_request *)&dev->wr_msg_buf[1]; + start_req = (struct hbm_host_version_request *)dev->wr_msg.data; memset(start_req, 0, len); start_req->hbm_cmd = HOST_START_REQ_CMD; start_req->host_version.major_version = HBM_MAJOR_VERSION; start_req->host_version.minor_version = HBM_MINOR_VERSION; dev->recvd_msg = false; - if (mei_write_message(dev, mei_hdr, (unsigned char *)start_req)) { + if (mei_write_message(dev, mei_hdr, dev->wr_msg.data)) { dev_dbg(&dev->pdev->dev, "write send version message to FW fail.\n"); dev->dev_state = MEI_DEV_RESETING; mei_reset(dev, 1); @@ -100,17 +100,17 @@ void mei_host_start_message(struct mei_device *dev) */ void mei_host_enum_clients_message(struct mei_device *dev) { - struct mei_msg_hdr *mei_hdr; + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; struct hbm_host_enum_request *enum_req; const size_t len = sizeof(struct hbm_host_enum_request); /* enumerate clients */ - mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); + mei_hbm_hdr(mei_hdr, len); - enum_req = (struct hbm_host_enum_request *) &dev->wr_msg_buf[1]; - memset(enum_req, 0, sizeof(struct hbm_host_enum_request)); + enum_req = (struct hbm_host_enum_request *)dev->wr_msg.data; + memset(enum_req, 0, len); enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; - if (mei_write_message(dev, mei_hdr, (unsigned char *)enum_req)) { + if (mei_write_message(dev, mei_hdr, dev->wr_msg.data)) { dev->dev_state = MEI_DEV_RESETING; dev_dbg(&dev->pdev->dev, "write send enumeration request message to FW fail.\n"); mei_reset(dev, 1); @@ -124,7 +124,7 @@ void mei_host_enum_clients_message(struct mei_device *dev) int mei_host_client_enumerate(struct mei_device *dev) { - struct mei_msg_hdr *mei_hdr; + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; struct hbm_props_request *prop_req; const size_t len = sizeof(struct hbm_props_request); unsigned long next_client_index; @@ -146,8 +146,8 @@ int mei_host_client_enumerate(struct mei_device *dev) dev->me_clients[client_num].client_id = next_client_index; dev->me_clients[client_num].mei_flow_ctrl_creds = 0; - mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); - prop_req = (struct hbm_props_request *)&dev->wr_msg_buf[1]; + mei_hbm_hdr(mei_hdr, len); + prop_req = (struct hbm_props_request *)dev->wr_msg.data; memset(prop_req, 0, sizeof(struct hbm_props_request)); @@ -155,7 +155,7 @@ int mei_host_client_enumerate(struct mei_device *dev) prop_req->hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD; prop_req->address = next_client_index; - if (mei_write_message(dev, mei_hdr, (unsigned char *) prop_req)) { + if (mei_write_message(dev, mei_hdr, dev->wr_msg.data)) { dev->dev_state = MEI_DEV_RESETING; dev_err(&dev->pdev->dev, "Properties request command failed\n"); mei_reset(dev, 1); @@ -169,6 +169,27 @@ int mei_host_client_enumerate(struct mei_device *dev) return 0; } +/** + * mei_hbm_stop_req_prepare - perpare stop request message + * + * @dev - mei device + * @mei_hdr - mei message header + * @data - hbm message body buffer + */ +static void mei_hbm_stop_req_prepare(struct mei_device *dev, + struct mei_msg_hdr *mei_hdr, unsigned char *data) +{ + struct hbm_host_stop_request *req = + (struct hbm_host_stop_request *)data; + const size_t len = sizeof(struct hbm_host_stop_request); + + mei_hbm_hdr(mei_hdr, len); + + memset(req, 0, len); + req->hbm_cmd = HOST_STOP_REQ_CMD; + req->reason = DRIVER_STOP_REQUEST; +} + /** * mei_send_flow_control - sends flow control to fw. * @@ -179,17 +200,16 @@ int mei_host_client_enumerate(struct mei_device *dev) */ int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl) { - struct mei_msg_hdr *mei_hdr; - unsigned char *buf = (unsigned char *)&dev->wr_msg_buf[1]; + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; const size_t len = sizeof(struct hbm_flow_control); - mei_hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); - mei_hbm_cl_hdr(cl, MEI_FLOW_CONTROL_CMD, buf, len); + mei_hbm_hdr(mei_hdr, len); + mei_hbm_cl_hdr(cl, MEI_FLOW_CONTROL_CMD, dev->wr_msg.data, len); dev_dbg(&dev->pdev->dev, "sending flow control host client = %d, ME client = %d\n", cl->host_client_id, cl->me_client_id); - return mei_write_message(dev, mei_hdr, buf); + return mei_write_message(dev, mei_hdr, dev->wr_msg.data); } /** @@ -202,14 +222,13 @@ int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl) */ int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) { - struct mei_msg_hdr *hdr; - unsigned char *buf = (unsigned char *)&dev->wr_msg_buf[1]; + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; const size_t len = sizeof(struct hbm_client_connect_request); - hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); - mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_REQ_CMD, buf, len); + mei_hbm_hdr(mei_hdr, len); + mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_REQ_CMD, dev->wr_msg.data, len); - return mei_write_message(dev, hdr, buf); + return mei_write_message(dev, mei_hdr, dev->wr_msg.data); } /** @@ -222,14 +241,13 @@ int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) */ int mei_connect(struct mei_device *dev, struct mei_cl *cl) { - struct mei_msg_hdr *hdr; - unsigned char *buf = (unsigned char *)&dev->wr_msg_buf[1]; + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; const size_t len = sizeof(struct hbm_client_connect_request); - hdr = mei_hbm_hdr(&dev->wr_msg_buf[0], len); - mei_hbm_cl_hdr(cl, CLIENT_CONNECT_REQ_CMD, buf, len); + mei_hbm_hdr(mei_hdr, len); + mei_hbm_cl_hdr(cl, CLIENT_CONNECT_REQ_CMD, dev->wr_msg.data, len); - return mei_write_message(dev, hdr, buf); + return mei_write_message(dev, mei_hdr, dev->wr_msg.data); } /** @@ -257,9 +275,9 @@ static void mei_client_disconnect_request(struct mei_device *dev, dev->iamthif_timer = 0; /* prepare disconnect response */ - (void)mei_hbm_hdr((u32 *)&dev->wr_ext_msg.hdr, len); + mei_hbm_hdr(&dev->wr_ext_msg.hdr, len); mei_hbm_cl_hdr(cl, CLIENT_DISCONNECT_RES_CMD, - &dev->wr_ext_msg.data, len); + dev->wr_ext_msg.data, len); break; } } @@ -284,7 +302,6 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) struct hbm_flow_control *flow_control; struct hbm_props_response *props_res; struct hbm_host_enum_response *enum_res; - struct hbm_host_stop_request *stop_req; /* read the message to our buffer */ BUG_ON(hdr->length >= sizeof(dev->rd_msg_buf)); @@ -294,34 +311,27 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) switch (mei_msg->hbm_cmd) { case HOST_START_RES_CMD: version_res = (struct hbm_host_version_response *)mei_msg; - if (version_res->host_version_supported) { - dev->version.major_version = HBM_MAJOR_VERSION; - dev->version.minor_version = HBM_MINOR_VERSION; - if (dev->dev_state == MEI_DEV_INIT_CLIENTS && - dev->init_clients_state == MEI_START_MESSAGE) { - dev->init_clients_timer = 0; - mei_host_enum_clients_message(dev); - } else { - dev->recvd_msg = false; - dev_dbg(&dev->pdev->dev, "IMEI reset due to received host start response bus message.\n"); - mei_reset(dev, 1); - return; - } - } else { - u32 *buf = dev->wr_msg_buf; - const size_t len = sizeof(struct hbm_host_stop_request); - + if (!version_res->host_version_supported) { dev->version = version_res->me_max_version; + dev_dbg(&dev->pdev->dev, "version mismatch.\n"); - /* send stop message */ - hdr = mei_hbm_hdr(&buf[0], len); - stop_req = (struct hbm_host_stop_request *)&buf[1]; - memset(stop_req, 0, len); - stop_req->hbm_cmd = HOST_STOP_REQ_CMD; - stop_req->reason = DRIVER_STOP_REQUEST; + mei_hbm_stop_req_prepare(dev, &dev->wr_msg.hdr, + dev->wr_msg.data); + mei_write_message(dev, &dev->wr_msg.hdr, + dev->wr_msg.data); + return; + } - mei_write_message(dev, hdr, (unsigned char *)stop_req); - dev_dbg(&dev->pdev->dev, "version mismatch.\n"); + dev->version.major_version = HBM_MAJOR_VERSION; + dev->version.minor_version = HBM_MINOR_VERSION; + if (dev->dev_state == MEI_DEV_INIT_CLIENTS && + dev->init_clients_state == MEI_START_MESSAGE) { + dev->init_clients_timer = 0; + mei_host_enum_clients_message(dev); + } else { + dev->recvd_msg = false; + dev_dbg(&dev->pdev->dev, "reset due to received hbm: host start\n"); + mei_reset(dev, 1); return; } @@ -417,18 +427,10 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case ME_STOP_REQ_CMD: - { - /* prepare stop request: sent in next interrupt event */ - const size_t len = sizeof(struct hbm_host_stop_request); - - hdr = mei_hbm_hdr((u32 *)&dev->wr_ext_msg.hdr, len); - stop_req = (struct hbm_host_stop_request *)&dev->wr_ext_msg.data; - memset(stop_req, 0, len); - stop_req->hbm_cmd = HOST_STOP_REQ_CMD; - stop_req->reason = DRIVER_STOP_REQUEST; + mei_hbm_stop_req_prepare(dev, &dev->wr_ext_msg.hdr, + dev->wr_ext_msg.data); break; - } default: BUG(); break; diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index d4312a8e139a..9cbf148e02e0 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -469,25 +469,24 @@ static int _mei_irq_thread_ioctl(struct mei_device *dev, s32 *slots, static int mei_irq_thread_write_complete(struct mei_device *dev, s32 *slots, struct mei_cl_cb *cb, struct mei_cl_cb *cmpl_list) { - struct mei_msg_hdr *mei_hdr; + struct mei_msg_hdr mei_hdr; struct mei_cl *cl = cb->cl; size_t len = cb->request_buffer.size - cb->buf_idx; size_t msg_slots = mei_data2slots(len); - mei_hdr = (struct mei_msg_hdr *)&dev->wr_msg_buf[0]; - mei_hdr->host_addr = cl->host_client_id; - mei_hdr->me_addr = cl->me_client_id; - mei_hdr->reserved = 0; + mei_hdr.host_addr = cl->host_client_id; + mei_hdr.me_addr = cl->me_client_id; + mei_hdr.reserved = 0; if (*slots >= msg_slots) { - mei_hdr->length = len; - mei_hdr->msg_complete = 1; + mei_hdr.length = len; + mei_hdr.msg_complete = 1; /* Split the message only if we can write the whole host buffer */ } else if (*slots == dev->hbuf_depth) { msg_slots = *slots; len = (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); - mei_hdr->length = len; - mei_hdr->msg_complete = 0; + mei_hdr.length = len; + mei_hdr.msg_complete = 0; } else { /* wait for next time the host buffer is empty */ return 0; @@ -495,10 +494,10 @@ static int mei_irq_thread_write_complete(struct mei_device *dev, s32 *slots, dev_dbg(&dev->pdev->dev, "buf: size = %d idx = %lu\n", cb->request_buffer.size, cb->buf_idx); - dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); + dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(&mei_hdr)); *slots -= msg_slots; - if (mei_write_message(dev, mei_hdr, + if (mei_write_message(dev, &mei_hdr, cb->request_buffer.data + cb->buf_idx)) { cl->status = -ENODEV; list_move_tail(&cb->list, &cmpl_list->list); @@ -509,8 +508,8 @@ static int mei_irq_thread_write_complete(struct mei_device *dev, s32 *slots, return -ENODEV; cl->status = 0; - cb->buf_idx += mei_hdr->length; - if (mei_hdr->msg_complete) + cb->buf_idx += mei_hdr.length; + if (mei_hdr.msg_complete) list_move_tail(&cb->list, &dev->write_waiting_list.list); return 0; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 107a820a6ba4..1ea331ac2463 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -265,7 +265,13 @@ struct mei_device { unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */ u32 rd_msg_hdr; - u32 wr_msg_buf[128]; /* used for control messages */ + + /* used for control messages */ + struct { + struct mei_msg_hdr hdr; + unsigned char data[128]; + } wr_msg; + struct { struct mei_msg_hdr hdr; unsigned char data[4]; /* All HBM messages are 4 bytes */ @@ -459,15 +465,13 @@ void mei_disable_interrupts(struct mei_device *dev); void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr); -static inline struct mei_msg_hdr *mei_hbm_hdr(u32 *buf, size_t length) +static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length) { - struct mei_msg_hdr *hdr = (struct mei_msg_hdr *)buf; hdr->host_addr = 0; hdr->me_addr = 0; hdr->length = length; hdr->msg_complete = 1; hdr->reserved = 0; - return hdr; } #define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d comp=%1d" diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index 9c3738a4eb08..3997a630847f 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -101,22 +101,21 @@ int mei_wd_host_init(struct mei_device *dev) */ int mei_wd_send(struct mei_device *dev) { - struct mei_msg_hdr *hdr; + struct mei_msg_hdr hdr; - hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; - hdr->host_addr = dev->wd_cl.host_client_id; - hdr->me_addr = dev->wd_cl.me_client_id; - hdr->msg_complete = 1; - hdr->reserved = 0; + hdr.host_addr = dev->wd_cl.host_client_id; + hdr.me_addr = dev->wd_cl.me_client_id; + hdr.msg_complete = 1; + hdr.reserved = 0; if (!memcmp(dev->wd_data, mei_start_wd_params, MEI_WD_HDR_SIZE)) - hdr->length = MEI_WD_START_MSG_SIZE; + hdr.length = MEI_WD_START_MSG_SIZE; else if (!memcmp(dev->wd_data, mei_stop_wd_params, MEI_WD_HDR_SIZE)) - hdr->length = MEI_WD_STOP_MSG_SIZE; + hdr.length = MEI_WD_STOP_MSG_SIZE; else return -EINVAL; - return mei_write_message(dev, hdr, dev->wd_data); + return mei_write_message(dev, &hdr, dev->wd_data); } /** -- cgit v1.2.1 From 8120e7201cf9795bc98ffb2e3064b657c0f34c05 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:11 +0200 Subject: mei: add common prefix to hbm function 1. use mei_hbm_ for basic host bus message function 2. use mei_hbm_cl prefix for host bus messages that operation on behalf of a client Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 7 +++--- drivers/misc/mei/hbm.c | 56 +++++++++++++++++++++++++------------------- drivers/misc/mei/init.c | 4 ++-- drivers/misc/mei/interface.h | 11 +++++---- drivers/misc/mei/interrupt.c | 10 ++++---- drivers/misc/mei/iorw.c | 4 ++-- drivers/misc/mei/mei_dev.h | 9 ------- drivers/misc/mei/wd.c | 2 +- 8 files changed, 53 insertions(+), 50 deletions(-) diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index f9d458cced21..6e3cd31eae3b 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -98,7 +98,7 @@ void mei_amthif_host_init(struct mei_device *dev) dev->iamthif_msg_buf = msg_buf; - if (mei_connect(dev, &dev->iamthif_cl)) { + if (mei_hbm_cl_connect_req(dev, &dev->iamthif_cl)) { dev_dbg(&dev->pdev->dev, "Failed to connect to AMTHI client\n"); dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; dev->iamthif_cl.host_client_id = 0; @@ -558,7 +558,7 @@ int mei_amthif_irq_read(struct mei_device *dev, s32 *slots) return -EMSGSIZE; } *slots -= mei_data2slots(sizeof(struct hbm_flow_control)); - if (mei_send_flow_control(dev, &dev->iamthif_cl)) { + if (mei_hbm_cl_flow_control_req(dev, &dev->iamthif_cl)) { dev_dbg(&dev->pdev->dev, "iamthif flow control failed\n"); return -EIO; } @@ -630,7 +630,8 @@ static bool mei_clear_list(struct mei_device *dev, if (dev->iamthif_current_cb == cb_pos) { dev->iamthif_current_cb = NULL; /* send flow control to iamthif client */ - mei_send_flow_control(dev, &dev->iamthif_cl); + mei_hbm_cl_flow_control_req(dev, + &dev->iamthif_cl); } /* free all allocated buffers */ mei_io_cb_free(cb_pos); diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index e9ba51d5a46c..3c9914038490 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -59,13 +59,11 @@ bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf) /** - * host_start_message - mei host sends start message. + * mei_hbm_start_req - sends start request message. * * @dev: the device structure - * - * returns none. */ -void mei_host_start_message(struct mei_device *dev) +void mei_hbm_start_req(struct mei_device *dev) { struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; struct hbm_host_version_request *start_req; @@ -92,13 +90,13 @@ void mei_host_start_message(struct mei_device *dev) } /** - * host_enum_clients_message - host sends enumeration client request message. + * mei_hbm_enum_clients_req - sends enumeration client request message. * * @dev: the device structure * * returns none. */ -void mei_host_enum_clients_message(struct mei_device *dev) +static void mei_hbm_enum_clients_req(struct mei_device *dev) { struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; struct hbm_host_enum_request *enum_req; @@ -120,8 +118,15 @@ void mei_host_enum_clients_message(struct mei_device *dev) return; } +/** + * mei_hbm_prop_requsest - request property for a single client + * + * @dev: the device structure + * + * returns none. + */ -int mei_host_client_enumerate(struct mei_device *dev) +static int mei_hbm_prop_req(struct mei_device *dev) { struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; @@ -191,14 +196,14 @@ static void mei_hbm_stop_req_prepare(struct mei_device *dev, } /** - * mei_send_flow_control - sends flow control to fw. + * mei_hbm_cl_flow_control_req - sends flow control requst. * * @dev: the device structure - * @cl: private data of the file object + * @cl: client info * * This function returns -EIO on write failure */ -int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl) +int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl) { struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; const size_t len = sizeof(struct hbm_flow_control); @@ -213,14 +218,14 @@ int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl) } /** - * mei_disconnect - sends disconnect message to fw. + * mei_hbm_cl_disconnect_req - sends disconnect message to fw. * * @dev: the device structure - * @cl: private data of the file object + * @cl: a client to disconnect from * * This function returns -EIO on write failure */ -int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) +int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl) { struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; const size_t len = sizeof(struct hbm_client_connect_request); @@ -232,14 +237,14 @@ int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) } /** - * mei_connect - sends connect message to fw. + * mei_hbm_cl_connect_req - send connection request to specific me client * * @dev: the device structure - * @cl: private data of the file object + * @cl: a client to connect to * - * This function returns -EIO on write failure + * returns -EIO on write failure */ -int mei_connect(struct mei_device *dev, struct mei_cl *cl) +int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl) { struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; const size_t len = sizeof(struct hbm_client_connect_request); @@ -251,12 +256,13 @@ int mei_connect(struct mei_device *dev, struct mei_cl *cl) } /** - * mei_client_disconnect_request - disconnects from request irq routine + * mei_client_disconnect_request - disconnect request initiated by me + * host sends disoconnect response * * @dev: the device structure. - * @disconnect_req: disconnect request bus message. + * @disconnect_req: disconnect request bus message from the me */ -static void mei_client_disconnect_request(struct mei_device *dev, +static void mei_hbm_fw_disconnect_req(struct mei_device *dev, struct hbm_client_connect_request *disconnect_req) { struct mei_cl *cl, *next; @@ -327,7 +333,7 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) if (dev->dev_state == MEI_DEV_INIT_CLIENTS && dev->init_clients_state == MEI_START_MESSAGE) { dev->init_clients_timer = 0; - mei_host_enum_clients_message(dev); + mei_hbm_enum_clients_req(dev); } else { dev->recvd_msg = false; dev_dbg(&dev->pdev->dev, "reset due to received hbm: host start\n"); @@ -390,7 +396,8 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->me_client_index++; dev->me_client_presentation_num++; - mei_host_client_enumerate(dev); + /* request property for the next client */ + mei_hbm_prop_req(dev); break; @@ -406,7 +413,8 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->init_clients_state = MEI_CLIENT_PROPERTIES_MESSAGE; - mei_host_client_enumerate(dev); + /* first property reqeust */ + mei_hbm_prop_req(dev); } else { dev_dbg(&dev->pdev->dev, "reset due to received host enumeration clients response bus message.\n"); mei_reset(dev, 1); @@ -423,7 +431,7 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) case CLIENT_DISCONNECT_REQ_CMD: /* search for client */ disconnect_req = (struct hbm_client_connect_request *)mei_msg; - mei_client_disconnect_request(dev, disconnect_req); + mei_hbm_fw_disconnect_req(dev, disconnect_req); break; case ME_STOP_REQ_CMD: diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 0536170ff856..418a85f315f1 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -529,9 +529,9 @@ int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl) cb->fop_type = MEI_FOP_CLOSE; if (dev->mei_host_buffer_is_empty) { dev->mei_host_buffer_is_empty = false; - if (mei_disconnect(dev, cl)) { + if (mei_hbm_cl_disconnect_req(dev, cl)) { rets = -ENODEV; - dev_dbg(&dev->pdev->dev, "failed to call mei_disconnect.\n"); + dev_err(&dev->pdev->dev, "failed to disconnect.\n"); goto free; } mdelay(10); /* Wait for hardware disconnection ready */ diff --git a/drivers/misc/mei/interface.h b/drivers/misc/mei/interface.h index ca732990a7eb..90a3dfda9db5 100644 --- a/drivers/misc/mei/interface.h +++ b/drivers/misc/mei/interface.h @@ -69,12 +69,15 @@ void mei_watchdog_register(struct mei_device *dev); */ void mei_watchdog_unregister(struct mei_device *dev); +int mei_other_client_is_connecting(struct mei_device *dev, struct mei_cl *cl); int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl); -int mei_send_flow_control(struct mei_device *dev, struct mei_cl *cl); +void mei_hbm_start_req(struct mei_device *dev); -int mei_disconnect(struct mei_device *dev, struct mei_cl *cl); -int mei_other_client_is_connecting(struct mei_device *dev, struct mei_cl *cl); -int mei_connect(struct mei_device *dev, struct mei_cl *cl); +int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl); +int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl); +int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl); + +void mei_host_client_init(struct work_struct *work); #endif /* _MEI_INTERFACE_H_ */ diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 9cbf148e02e0..eb744cc4f72a 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -157,7 +157,7 @@ static int _mei_irq_thread_close(struct mei_device *dev, s32 *slots, *slots -= mei_data2slots(sizeof(struct hbm_client_connect_request)); - if (mei_disconnect(dev, cl)) { + if (mei_hbm_cl_disconnect_req(dev, cl)) { cl->status = 0; cb_pos->buf_idx = 0; list_move_tail(&cb_pos->list, &cmpl_list->list); @@ -407,7 +407,7 @@ static int _mei_irq_thread_read(struct mei_device *dev, s32 *slots, *slots -= mei_data2slots(sizeof(struct hbm_flow_control)); - if (mei_send_flow_control(dev, cl)) { + if (mei_hbm_cl_flow_control_req(dev, cl)) { cl->status = -ENODEV; cb_pos->buf_idx = 0; list_move_tail(&cb_pos->list, &cmpl_list->list); @@ -443,8 +443,8 @@ static int _mei_irq_thread_ioctl(struct mei_device *dev, s32 *slots, } cl->state = MEI_FILE_CONNECTING; - *slots -= mei_data2slots(sizeof(struct hbm_client_connect_request)); - if (mei_connect(dev, cl)) { + *slots -= mei_data2slots(sizeof(struct hbm_client_connect_request)); + if (mei_hbm_cl_connect_req(dev, cl)) { cl->status = -ENODEV; cb_pos->buf_idx = 0; list_del(&cb_pos->list); @@ -927,7 +927,7 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) /* link is established * start sending messages. */ - mei_host_start_message(dev); + mei_hbm_start_req(dev); mutex_unlock(&dev->device_lock); return IRQ_HANDLED; } else { diff --git a/drivers/misc/mei/iorw.c b/drivers/misc/mei/iorw.c index 7ccc3d8a079e..d8e08bcf3263 100644 --- a/drivers/misc/mei/iorw.c +++ b/drivers/misc/mei/iorw.c @@ -258,7 +258,7 @@ int mei_ioctl_connect_client(struct file *file, && !mei_other_client_is_connecting(dev, cl)) { dev_dbg(&dev->pdev->dev, "Sending Connect Message\n"); dev->mei_host_buffer_is_empty = false; - if (mei_connect(dev, cl)) { + if (mei_hbm_cl_connect_req(dev, cl)) { dev_dbg(&dev->pdev->dev, "Sending connect message - failed\n"); rets = -ENODEV; goto end; @@ -350,7 +350,7 @@ int mei_start_read(struct mei_device *dev, struct mei_cl *cl) cl->read_cb = cb; if (dev->mei_host_buffer_is_empty) { dev->mei_host_buffer_is_empty = false; - if (mei_send_flow_control(dev, cl)) { + if (mei_hbm_cl_flow_control_req(dev, cl)) { rets = -ENODEV; goto err; } diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 1ea331ac2463..0ad32cc49c06 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -383,15 +383,6 @@ static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, } - -/* - * MEI Host Client Functions - */ -void mei_host_start_message(struct mei_device *dev); -void mei_host_enum_clients_message(struct mei_device *dev); -int mei_host_client_enumerate(struct mei_device *dev); -void mei_host_client_init(struct work_struct *work); - /* * MEI interrupt functions prototype */ diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index 3997a630847f..4f2e9db86478 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -79,7 +79,7 @@ int mei_wd_host_init(struct mei_device *dev) return -ENOENT; } - if (mei_connect(dev, &dev->wd_cl)) { + if (mei_hbm_cl_connect_req(dev, &dev->wd_cl)) { dev_err(&dev->pdev->dev, "wd: failed to connect to the client\n"); dev->wd_cl.state = MEI_FILE_DISCONNECTED; dev->wd_cl.host_client_id = 0; -- cgit v1.2.1 From 6bbda15f279ec99c4ac0d56c4ad680299d0b768b Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 25 Dec 2012 19:06:12 +0200 Subject: mei: move hbm responses from interrupt.c to hbm.c 1. Add common prefix mei_hbm_ to all handlers and made them static 2. Drop now useless function same_flow_addr Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hbm.c | 189 ++++++++++++++++++++++++++++++++++++++- drivers/misc/mei/interrupt.c | 208 ------------------------------------------- drivers/misc/mei/mei_dev.h | 9 -- 3 files changed, 186 insertions(+), 220 deletions(-) diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 3c9914038490..6b58b0a10378 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -58,6 +58,33 @@ bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf) } +/** + * is_treat_specially_client - checks if the message belongs + * to the file private data. + * + * @cl: private data of the file object + * @rs: connect response bus message + * + */ +static bool is_treat_specially_client(struct mei_cl *cl, + struct hbm_client_connect_response *rs) +{ + if (mei_hbm_cl_addr_equal(cl, rs)) { + if (!rs->status) { + cl->state = MEI_FILE_CONNECTED; + cl->status = 0; + + } else { + cl->state = MEI_FILE_DISCONNECTED; + cl->status = -ENODEV; + } + cl->timer_count = 0; + + return true; + } + return false; +} + /** * mei_hbm_start_req - sends start request message. * @@ -217,6 +244,66 @@ int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl) return mei_write_message(dev, mei_hdr, dev->wr_msg.data); } +/** + * add_single_flow_creds - adds single buffer credentials. + * + * @file: private data ot the file object. + * @flow: flow control. + */ +static void mei_hbm_add_single_flow_creds(struct mei_device *dev, + struct hbm_flow_control *flow) +{ + struct mei_me_client *client; + int i; + + for (i = 0; i < dev->me_clients_num; i++) { + client = &dev->me_clients[i]; + if (client && flow->me_addr == client->client_id) { + if (client->props.single_recv_buf) { + client->mei_flow_ctrl_creds++; + dev_dbg(&dev->pdev->dev, "recv flow ctrl msg ME %d (single).\n", + flow->me_addr); + dev_dbg(&dev->pdev->dev, "flow control credentials =%d.\n", + client->mei_flow_ctrl_creds); + } else { + BUG(); /* error in flow control */ + } + } + } +} + +/** + * mei_hbm_cl_flow_control_res - flow control response from me + * + * @dev: the device structure + * @flow_control: flow control response bus message + */ +static void mei_hbm_cl_flow_control_res(struct mei_device *dev, + struct hbm_flow_control *flow_control) +{ + struct mei_cl *cl = NULL; + struct mei_cl *next = NULL; + + if (!flow_control->host_addr) { + /* single receive buffer */ + mei_hbm_add_single_flow_creds(dev, flow_control); + return; + } + + /* normal connection */ + list_for_each_entry_safe(cl, next, &dev->file_list, link) { + if (mei_hbm_cl_addr_equal(cl, flow_control)) { + cl->mei_flow_ctrl_creds++; + dev_dbg(&dev->pdev->dev, "flow ctrl msg for host %d ME %d.\n", + flow_control->host_addr, flow_control->me_addr); + dev_dbg(&dev->pdev->dev, "flow control credentials = %d.\n", + cl->mei_flow_ctrl_creds); + break; + } + } +} + + /** * mei_hbm_cl_disconnect_req - sends disconnect message to fw. * @@ -236,6 +323,48 @@ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl) return mei_write_message(dev, mei_hdr, dev->wr_msg.data); } +/** + * mei_hbm_cl_disconnect_res - disconnect response from ME + * + * @dev: the device structure + * @rs: disconnect response bus message + */ +static void mei_hbm_cl_disconnect_res(struct mei_device *dev, + struct hbm_client_connect_response *rs) +{ + struct mei_cl *cl; + struct mei_cl_cb *pos = NULL, *next = NULL; + + dev_dbg(&dev->pdev->dev, + "disconnect_response:\n" + "ME Client = %d\n" + "Host Client = %d\n" + "Status = %d\n", + rs->me_addr, + rs->host_addr, + rs->status); + + list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) { + cl = pos->cl; + + if (!cl) { + list_del(&pos->list); + return; + } + + dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in ctrl_rd_list.\n"); + if (mei_hbm_cl_addr_equal(cl, rs)) { + list_del(&pos->list); + if (!rs->status) + cl->state = MEI_FILE_DISCONNECTED; + + cl->status = 0; + cl->timer_count = 0; + break; + } + } +} + /** * mei_hbm_cl_connect_req - send connection request to specific me client * @@ -255,6 +384,60 @@ int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl) return mei_write_message(dev, mei_hdr, dev->wr_msg.data); } +/** + * mei_hbm_cl_connect_res - connect resposne from the ME + * + * @dev: the device structure + * @rs: connect response bus message + */ +static void mei_hbm_cl_connect_res(struct mei_device *dev, + struct hbm_client_connect_response *rs) +{ + + struct mei_cl *cl; + struct mei_cl_cb *pos = NULL, *next = NULL; + + dev_dbg(&dev->pdev->dev, + "connect_response:\n" + "ME Client = %d\n" + "Host Client = %d\n" + "Status = %d\n", + rs->me_addr, + rs->host_addr, + rs->status); + + /* if WD or iamthif client treat specially */ + + if (is_treat_specially_client(&dev->wd_cl, rs)) { + dev_dbg(&dev->pdev->dev, "successfully connected to WD client.\n"); + mei_watchdog_register(dev); + + return; + } + + if (is_treat_specially_client(&dev->iamthif_cl, rs)) { + dev->iamthif_state = MEI_IAMTHIF_IDLE; + return; + } + list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) { + + cl = pos->cl; + if (!cl) { + list_del(&pos->list); + return; + } + if (pos->fop_type == MEI_FOP_IOCTL) { + if (is_treat_specially_client(cl, rs)) { + list_del(&pos->list); + cl->status = 0; + cl->timer_count = 0; + break; + } + } + } +} + + /** * mei_client_disconnect_request - disconnect request initiated by me * host sends disoconnect response @@ -347,21 +530,21 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) case CLIENT_CONNECT_RES_CMD: connect_res = (struct hbm_client_connect_response *) mei_msg; - mei_client_connect_response(dev, connect_res); + mei_hbm_cl_connect_res(dev, connect_res); dev_dbg(&dev->pdev->dev, "client connect response message received.\n"); wake_up(&dev->wait_recvd_msg); break; case CLIENT_DISCONNECT_RES_CMD: disconnect_res = (struct hbm_client_connect_response *) mei_msg; - mei_client_disconnect_response(dev, disconnect_res); + mei_hbm_cl_disconnect_res(dev, disconnect_res); dev_dbg(&dev->pdev->dev, "client disconnect response message received.\n"); wake_up(&dev->wait_recvd_msg); break; case MEI_FLOW_CONTROL_CMD: flow_control = (struct hbm_flow_control *) mei_msg; - mei_client_flow_control_response(dev, flow_control); + mei_hbm_cl_flow_control_res(dev, flow_control); dev_dbg(&dev->pdev->dev, "client flow control response message received.\n"); break; diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index eb744cc4f72a..a735c8b7ca82 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -173,214 +173,6 @@ static int _mei_irq_thread_close(struct mei_device *dev, s32 *slots, return 0; } -/** - * is_treat_specially_client - checks if the message belongs - * to the file private data. - * - * @cl: private data of the file object - * @rs: connect response bus message - * - */ -static bool is_treat_specially_client(struct mei_cl *cl, - struct hbm_client_connect_response *rs) -{ - - if (cl->host_client_id == rs->host_addr && - cl->me_client_id == rs->me_addr) { - if (!rs->status) { - cl->state = MEI_FILE_CONNECTED; - cl->status = 0; - - } else { - cl->state = MEI_FILE_DISCONNECTED; - cl->status = -ENODEV; - } - cl->timer_count = 0; - - return true; - } - return false; -} - -/** - * mei_client_connect_response - connects to response irq routine - * - * @dev: the device structure - * @rs: connect response bus message - */ -void mei_client_connect_response(struct mei_device *dev, - struct hbm_client_connect_response *rs) -{ - - struct mei_cl *cl; - struct mei_cl_cb *pos = NULL, *next = NULL; - - dev_dbg(&dev->pdev->dev, - "connect_response:\n" - "ME Client = %d\n" - "Host Client = %d\n" - "Status = %d\n", - rs->me_addr, - rs->host_addr, - rs->status); - - /* if WD or iamthif client treat specially */ - - if (is_treat_specially_client(&(dev->wd_cl), rs)) { - dev_dbg(&dev->pdev->dev, "successfully connected to WD client.\n"); - mei_watchdog_register(dev); - - return; - } - - if (is_treat_specially_client(&(dev->iamthif_cl), rs)) { - dev->iamthif_state = MEI_IAMTHIF_IDLE; - return; - } - list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) { - - cl = pos->cl; - if (!cl) { - list_del(&pos->list); - return; - } - if (pos->fop_type == MEI_FOP_IOCTL) { - if (is_treat_specially_client(cl, rs)) { - list_del(&pos->list); - cl->status = 0; - cl->timer_count = 0; - break; - } - } - } -} - -/** - * mei_client_disconnect_response - disconnects from response irq routine - * - * @dev: the device structure - * @rs: disconnect response bus message - */ -void mei_client_disconnect_response(struct mei_device *dev, - struct hbm_client_connect_response *rs) -{ - struct mei_cl *cl; - struct mei_cl_cb *pos = NULL, *next = NULL; - - dev_dbg(&dev->pdev->dev, - "disconnect_response:\n" - "ME Client = %d\n" - "Host Client = %d\n" - "Status = %d\n", - rs->me_addr, - rs->host_addr, - rs->status); - - list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) { - cl = pos->cl; - - if (!cl) { - list_del(&pos->list); - return; - } - - dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in ctrl_rd_list.\n"); - if (cl->host_client_id == rs->host_addr && - cl->me_client_id == rs->me_addr) { - - list_del(&pos->list); - if (!rs->status) - cl->state = MEI_FILE_DISCONNECTED; - - cl->status = 0; - cl->timer_count = 0; - break; - } - } -} - -/** - * same_flow_addr - tells if they have the same address. - * - * @file: private data of the file object. - * @flow: flow control. - * - * returns !=0, same; 0,not. - */ -static int same_flow_addr(struct mei_cl *cl, struct hbm_flow_control *flow) -{ - return (cl->host_client_id == flow->host_addr && - cl->me_client_id == flow->me_addr); -} - -/** - * add_single_flow_creds - adds single buffer credentials. - * - * @file: private data ot the file object. - * @flow: flow control. - */ -static void add_single_flow_creds(struct mei_device *dev, - struct hbm_flow_control *flow) -{ - struct mei_me_client *client; - int i; - - for (i = 0; i < dev->me_clients_num; i++) { - client = &dev->me_clients[i]; - if (client && flow->me_addr == client->client_id) { - if (client->props.single_recv_buf) { - client->mei_flow_ctrl_creds++; - dev_dbg(&dev->pdev->dev, "recv flow ctrl msg ME %d (single).\n", - flow->me_addr); - dev_dbg(&dev->pdev->dev, "flow control credentials =%d.\n", - client->mei_flow_ctrl_creds); - } else { - BUG(); /* error in flow control */ - } - } - } -} - -/** - * mei_client_flow_control_response - flow control response irq routine - * - * @dev: the device structure - * @flow_control: flow control response bus message - */ -void mei_client_flow_control_response(struct mei_device *dev, - struct hbm_flow_control *flow_control) -{ - struct mei_cl *cl_pos = NULL; - struct mei_cl *cl_next = NULL; - - if (!flow_control->host_addr) { - /* single receive buffer */ - add_single_flow_creds(dev, flow_control); - } else { - /* normal connection */ - list_for_each_entry_safe(cl_pos, cl_next, - &dev->file_list, link) { - dev_dbg(&dev->pdev->dev, "list_for_each_entry_safe in file_list\n"); - - dev_dbg(&dev->pdev->dev, "cl of host client %d ME client %d.\n", - cl_pos->host_client_id, - cl_pos->me_client_id); - dev_dbg(&dev->pdev->dev, "flow ctrl msg for host %d ME %d.\n", - flow_control->host_addr, - flow_control->me_addr); - if (same_flow_addr(cl_pos, flow_control)) { - dev_dbg(&dev->pdev->dev, "recv ctrl msg for host %d ME %d.\n", - flow_control->host_addr, - flow_control->me_addr); - cl_pos->mei_flow_ctrl_creds++; - dev_dbg(&dev->pdev->dev, "flow control credentials = %d.\n", - cl_pos->mei_flow_ctrl_creds); - break; - } - } - } -} - /** * _mei_hb_read - processes read related operation. diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 0ad32cc49c06..54ddac324578 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -398,15 +398,6 @@ int mei_ioctl_connect_client(struct file *file, int mei_start_read(struct mei_device *dev, struct mei_cl *cl); - -void mei_client_disconnect_response(struct mei_device *dev, - struct hbm_client_connect_response *rs); - -void mei_client_connect_response(struct mei_device *dev, - struct hbm_client_connect_response *rs); - -void mei_client_flow_control_response(struct mei_device *dev, - struct hbm_flow_control *flow_control); /* * AMTHIF - AMT Host Interface Functions */ -- cgit v1.2.1 From 28d6692cd8fb2a900edba5e5983be4478756ef6f Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:52:59 -0800 Subject: VMCI: context implementation. VMCI Context code maintains state for vmci and allows the driver to communicate with multiple VMs. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_context.c | 1214 ++++++++++++++++++++++++++++++++++ drivers/misc/vmw_vmci/vmci_context.h | 182 +++++ 2 files changed, 1396 insertions(+) create mode 100644 drivers/misc/vmw_vmci/vmci_context.c create mode 100644 drivers/misc/vmw_vmci/vmci_context.h diff --git a/drivers/misc/vmw_vmci/vmci_context.c b/drivers/misc/vmw_vmci/vmci_context.c new file mode 100644 index 000000000000..f866a4baecb5 --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_context.c @@ -0,0 +1,1214 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include + +#include "vmci_queue_pair.h" +#include "vmci_datagram.h" +#include "vmci_doorbell.h" +#include "vmci_context.h" +#include "vmci_driver.h" +#include "vmci_event.h" + +/* + * List of current VMCI contexts. Contexts can be added by + * vmci_ctx_create() and removed via vmci_ctx_destroy(). + * These, along with context lookup, are protected by the + * list structure's lock. + */ +static struct { + struct list_head head; + spinlock_t lock; /* Spinlock for context list operations */ +} ctx_list = { + .head = LIST_HEAD_INIT(ctx_list.head), + .lock = __SPIN_LOCK_UNLOCKED(ctx_list.lock), +}; + +/* Used by contexts that did not set up notify flag pointers */ +static bool ctx_dummy_notify; + +static void ctx_signal_notify(struct vmci_ctx *context) +{ + *context->notify = true; +} + +static void ctx_clear_notify(struct vmci_ctx *context) +{ + *context->notify = false; +} + +/* + * If nothing requires the attention of the guest, clears both + * notify flag and call. + */ +static void ctx_clear_notify_call(struct vmci_ctx *context) +{ + if (context->pending_datagrams == 0 && + vmci_handle_arr_get_size(context->pending_doorbell_array) == 0) + ctx_clear_notify(context); +} + +/* + * Sets the context's notify flag iff datagrams are pending for this + * context. Called from vmci_setup_notify(). + */ +void vmci_ctx_check_signal_notify(struct vmci_ctx *context) +{ + spin_lock(&context->lock); + if (context->pending_datagrams) + ctx_signal_notify(context); + spin_unlock(&context->lock); +} + +/* + * Allocates and initializes a VMCI context. + */ +struct vmci_ctx *vmci_ctx_create(u32 cid, u32 priv_flags, + uintptr_t event_hnd, + int user_version, + const struct cred *cred) +{ + struct vmci_ctx *context; + int error; + + if (cid == VMCI_INVALID_ID) { + pr_devel("Invalid context ID for VMCI context\n"); + error = -EINVAL; + goto err_out; + } + + if (priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS) { + pr_devel("Invalid flag (flags=0x%x) for VMCI context\n", + priv_flags); + error = -EINVAL; + goto err_out; + } + + if (user_version == 0) { + pr_devel("Invalid suer_version %d\n", user_version); + error = -EINVAL; + goto err_out; + } + + context = kzalloc(sizeof(*context), GFP_KERNEL); + if (!context) { + pr_warn("Failed to allocate memory for VMCI context\n"); + error = -EINVAL; + goto err_out; + } + + kref_init(&context->kref); + spin_lock_init(&context->lock); + INIT_LIST_HEAD(&context->list_item); + INIT_LIST_HEAD(&context->datagram_queue); + INIT_LIST_HEAD(&context->notifier_list); + + /* Initialize host-specific VMCI context. */ + init_waitqueue_head(&context->host_context.wait_queue); + + context->queue_pair_array = vmci_handle_arr_create(0); + if (!context->queue_pair_array) { + error = -ENOMEM; + goto err_free_ctx; + } + + context->doorbell_array = vmci_handle_arr_create(0); + if (!context->doorbell_array) { + error = -ENOMEM; + goto err_free_qp_array; + } + + context->pending_doorbell_array = vmci_handle_arr_create(0); + if (!context->pending_doorbell_array) { + error = -ENOMEM; + goto err_free_db_array; + } + + context->user_version = user_version; + + context->priv_flags = priv_flags; + + if (cred) + context->cred = get_cred(cred); + + context->notify = &ctx_dummy_notify; + context->notify_page = NULL; + + /* + * If we collide with an existing context we generate a new + * and use it instead. The VMX will determine if regeneration + * is okay. Since there isn't 4B - 16 VMs running on a given + * host, the below loop will terminate. + */ + spin_lock(&ctx_list.lock); + + while (vmci_ctx_exists(cid)) { + /* We reserve the lowest 16 ids for fixed contexts. */ + cid = max(cid, VMCI_RESERVED_CID_LIMIT - 1) + 1; + if (cid == VMCI_INVALID_ID) + cid = VMCI_RESERVED_CID_LIMIT; + } + context->cid = cid; + + list_add_tail_rcu(&context->list_item, &ctx_list.head); + spin_unlock(&ctx_list.lock); + + return context; + + err_free_db_array: + vmci_handle_arr_destroy(context->doorbell_array); + err_free_qp_array: + vmci_handle_arr_destroy(context->queue_pair_array); + err_free_ctx: + kfree(context); + err_out: + return ERR_PTR(error); +} + +/* + * Destroy VMCI context. + */ +void vmci_ctx_destroy(struct vmci_ctx *context) +{ + spin_lock(&ctx_list.lock); + list_del_rcu(&context->list_item); + spin_unlock(&ctx_list.lock); + synchronize_rcu(); + + vmci_ctx_put(context); +} + +/* + * Fire notification for all contexts interested in given cid. + */ +static int ctx_fire_notification(u32 context_id, u32 priv_flags) +{ + u32 i, array_size; + struct vmci_ctx *sub_ctx; + struct vmci_handle_arr *subscriber_array; + struct vmci_handle context_handle = + vmci_make_handle(context_id, VMCI_EVENT_HANDLER); + + /* + * We create an array to hold the subscribers we find when + * scanning through all contexts. + */ + subscriber_array = vmci_handle_arr_create(0); + if (subscriber_array == NULL) + return VMCI_ERROR_NO_MEM; + + /* + * Scan all contexts to find who is interested in being + * notified about given contextID. + */ + rcu_read_lock(); + list_for_each_entry_rcu(sub_ctx, &ctx_list.head, list_item) { + struct vmci_handle_list *node; + + /* + * We only deliver notifications of the removal of + * contexts, if the two contexts are allowed to + * interact. + */ + if (vmci_deny_interaction(priv_flags, sub_ctx->priv_flags)) + continue; + + list_for_each_entry_rcu(node, &sub_ctx->notifier_list, node) { + if (!vmci_handle_is_equal(node->handle, context_handle)) + continue; + + vmci_handle_arr_append_entry(&subscriber_array, + vmci_make_handle(sub_ctx->cid, + VMCI_EVENT_HANDLER)); + } + } + rcu_read_unlock(); + + /* Fire event to all subscribers. */ + array_size = vmci_handle_arr_get_size(subscriber_array); + for (i = 0; i < array_size; i++) { + int result; + struct vmci_event_ctx ev; + + ev.msg.hdr.dst = vmci_handle_arr_get_entry(subscriber_array, i); + ev.msg.hdr.src = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_CONTEXT_RESOURCE_ID); + ev.msg.hdr.payload_size = sizeof(ev) - sizeof(ev.msg.hdr); + ev.msg.event_data.event = VMCI_EVENT_CTX_REMOVED; + ev.payload.context_id = context_id; + + result = vmci_datagram_dispatch(VMCI_HYPERVISOR_CONTEXT_ID, + &ev.msg.hdr, false); + if (result < VMCI_SUCCESS) { + pr_devel("Failed to enqueue event datagram (type=%d) for context (ID=0x%x)\n", + ev.msg.event_data.event, + ev.msg.hdr.dst.context); + /* We continue to enqueue on next subscriber. */ + } + } + vmci_handle_arr_destroy(subscriber_array); + + return VMCI_SUCCESS; +} + +/* + * Returns the current number of pending datagrams. The call may + * also serve as a synchronization point for the datagram queue, + * as no enqueue operations can occur concurrently. + */ +int vmci_ctx_pending_datagrams(u32 cid, u32 *pending) +{ + struct vmci_ctx *context; + + context = vmci_ctx_get(cid); + if (context == NULL) + return VMCI_ERROR_INVALID_ARGS; + + spin_lock(&context->lock); + if (pending) + *pending = context->pending_datagrams; + spin_unlock(&context->lock); + vmci_ctx_put(context); + + return VMCI_SUCCESS; +} + +/* + * Queues a VMCI datagram for the appropriate target VM context. + */ +int vmci_ctx_enqueue_datagram(u32 cid, struct vmci_datagram *dg) +{ + struct vmci_datagram_queue_entry *dq_entry; + struct vmci_ctx *context; + struct vmci_handle dg_src; + size_t vmci_dg_size; + + vmci_dg_size = VMCI_DG_SIZE(dg); + if (vmci_dg_size > VMCI_MAX_DG_SIZE) { + pr_devel("Datagram too large (bytes=%Zu)\n", vmci_dg_size); + return VMCI_ERROR_INVALID_ARGS; + } + + /* Get the target VM's VMCI context. */ + context = vmci_ctx_get(cid); + if (!context) { + pr_devel("Invalid context (ID=0x%x)\n", cid); + return VMCI_ERROR_INVALID_ARGS; + } + + /* Allocate guest call entry and add it to the target VM's queue. */ + dq_entry = kmalloc(sizeof(*dq_entry), GFP_KERNEL); + if (dq_entry == NULL) { + pr_warn("Failed to allocate memory for datagram\n"); + vmci_ctx_put(context); + return VMCI_ERROR_NO_MEM; + } + dq_entry->dg = dg; + dq_entry->dg_size = vmci_dg_size; + dg_src = dg->src; + INIT_LIST_HEAD(&dq_entry->list_item); + + spin_lock(&context->lock); + + /* + * We put a higher limit on datagrams from the hypervisor. If + * the pending datagram is not from hypervisor, then we check + * if enqueueing it would exceed the + * VMCI_MAX_DATAGRAM_QUEUE_SIZE limit on the destination. If + * the pending datagram is from hypervisor, we allow it to be + * queued at the destination side provided we don't reach the + * VMCI_MAX_DATAGRAM_AND_EVENT_QUEUE_SIZE limit. + */ + if (context->datagram_queue_size + vmci_dg_size >= + VMCI_MAX_DATAGRAM_QUEUE_SIZE && + (!vmci_handle_is_equal(dg_src, + vmci_make_handle + (VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_CONTEXT_RESOURCE_ID)) || + context->datagram_queue_size + vmci_dg_size >= + VMCI_MAX_DATAGRAM_AND_EVENT_QUEUE_SIZE)) { + spin_unlock(&context->lock); + vmci_ctx_put(context); + kfree(dq_entry); + pr_devel("Context (ID=0x%x) receive queue is full\n", cid); + return VMCI_ERROR_NO_RESOURCES; + } + + list_add(&dq_entry->list_item, &context->datagram_queue); + context->pending_datagrams++; + context->datagram_queue_size += vmci_dg_size; + ctx_signal_notify(context); + wake_up(&context->host_context.wait_queue); + spin_unlock(&context->lock); + vmci_ctx_put(context); + + return vmci_dg_size; +} + +/* + * Verifies whether a context with the specified context ID exists. + * FIXME: utility is dubious as no decisions can be reliably made + * using this data as context can appear and disappear at any time. + */ +bool vmci_ctx_exists(u32 cid) +{ + struct vmci_ctx *context; + bool exists = false; + + rcu_read_lock(); + + list_for_each_entry_rcu(context, &ctx_list.head, list_item) { + if (context->cid == cid) { + exists = true; + break; + } + } + + rcu_read_unlock(); + return exists; +} + +/* + * Retrieves VMCI context corresponding to the given cid. + */ +struct vmci_ctx *vmci_ctx_get(u32 cid) +{ + struct vmci_ctx *c, *context = NULL; + + if (cid == VMCI_INVALID_ID) + return NULL; + + rcu_read_lock(); + list_for_each_entry_rcu(c, &ctx_list.head, list_item) { + if (c->cid == cid) { + /* + * The context owner drops its own reference to the + * context only after removing it from the list and + * waiting for RCU grace period to expire. This + * means that we are not about to increase the + * reference count of something that is in the + * process of being destroyed. + */ + context = c; + kref_get(&context->kref); + break; + } + } + rcu_read_unlock(); + + return context; +} + +/* + * Deallocates all parts of a context data structure. This + * function doesn't lock the context, because it assumes that + * the caller was holding the last reference to context. + */ +static void ctx_free_ctx(struct kref *kref) +{ + struct vmci_ctx *context = container_of(kref, struct vmci_ctx, kref); + struct vmci_datagram_queue_entry *dq_entry, *dq_entry_tmp; + struct vmci_handle temp_handle; + struct vmci_handle_list *notifier, *tmp; + + /* + * Fire event to all contexts interested in knowing this + * context is dying. + */ + ctx_fire_notification(context->cid, context->priv_flags); + + /* + * Cleanup all queue pair resources attached to context. If + * the VM dies without cleaning up, this code will make sure + * that no resources are leaked. + */ + temp_handle = vmci_handle_arr_get_entry(context->queue_pair_array, 0); + while (!vmci_handle_is_equal(temp_handle, VMCI_INVALID_HANDLE)) { + if (vmci_qp_broker_detach(temp_handle, + context) < VMCI_SUCCESS) { + /* + * When vmci_qp_broker_detach() succeeds it + * removes the handle from the array. If + * detach fails, we must remove the handle + * ourselves. + */ + vmci_handle_arr_remove_entry(context->queue_pair_array, + temp_handle); + } + temp_handle = + vmci_handle_arr_get_entry(context->queue_pair_array, 0); + } + + /* + * It is fine to destroy this without locking the callQueue, as + * this is the only thread having a reference to the context. + */ + list_for_each_entry_safe(dq_entry, dq_entry_tmp, + &context->datagram_queue, list_item) { + WARN_ON(dq_entry->dg_size != VMCI_DG_SIZE(dq_entry->dg)); + list_del(&dq_entry->list_item); + kfree(dq_entry->dg); + kfree(dq_entry); + } + + list_for_each_entry_safe(notifier, tmp, + &context->notifier_list, node) { + list_del(¬ifier->node); + kfree(notifier); + } + + vmci_handle_arr_destroy(context->queue_pair_array); + vmci_handle_arr_destroy(context->doorbell_array); + vmci_handle_arr_destroy(context->pending_doorbell_array); + vmci_ctx_unset_notify(context); + if (context->cred) + put_cred(context->cred); + kfree(context); +} + +/* + * Drops reference to VMCI context. If this is the last reference to + * the context it will be deallocated. A context is created with + * a reference count of one, and on destroy, it is removed from + * the context list before its reference count is decremented. Thus, + * if we reach zero, we are sure that nobody else are about to increment + * it (they need the entry in the context list for that), and so there + * is no need for locking. + */ +void vmci_ctx_put(struct vmci_ctx *context) +{ + kref_put(&context->kref, ctx_free_ctx); +} + +/* + * Dequeues the next datagram and returns it to caller. + * The caller passes in a pointer to the max size datagram + * it can handle and the datagram is only unqueued if the + * size is less than max_size. If larger max_size is set to + * the size of the datagram to give the caller a chance to + * set up a larger buffer for the guestcall. + */ +int vmci_ctx_dequeue_datagram(struct vmci_ctx *context, + size_t *max_size, + struct vmci_datagram **dg) +{ + struct vmci_datagram_queue_entry *dq_entry; + struct list_head *list_item; + int rv; + + /* Dequeue the next datagram entry. */ + spin_lock(&context->lock); + if (context->pending_datagrams == 0) { + ctx_clear_notify_call(context); + spin_unlock(&context->lock); + pr_devel("No datagrams pending\n"); + return VMCI_ERROR_NO_MORE_DATAGRAMS; + } + + list_item = context->datagram_queue.next; + + dq_entry = + list_entry(list_item, struct vmci_datagram_queue_entry, list_item); + + /* Check size of caller's buffer. */ + if (*max_size < dq_entry->dg_size) { + *max_size = dq_entry->dg_size; + spin_unlock(&context->lock); + pr_devel("Caller's buffer should be at least (size=%u bytes)\n", + (u32) *max_size); + return VMCI_ERROR_NO_MEM; + } + + list_del(list_item); + context->pending_datagrams--; + context->datagram_queue_size -= dq_entry->dg_size; + if (context->pending_datagrams == 0) { + ctx_clear_notify_call(context); + rv = VMCI_SUCCESS; + } else { + /* + * Return the size of the next datagram. + */ + struct vmci_datagram_queue_entry *next_entry; + + list_item = context->datagram_queue.next; + next_entry = + list_entry(list_item, struct vmci_datagram_queue_entry, + list_item); + + /* + * The following size_t -> int truncation is fine as + * the maximum size of a (routable) datagram is 68KB. + */ + rv = (int)next_entry->dg_size; + } + spin_unlock(&context->lock); + + /* Caller must free datagram. */ + *dg = dq_entry->dg; + dq_entry->dg = NULL; + kfree(dq_entry); + + return rv; +} + +/* + * Reverts actions set up by vmci_setup_notify(). Unmaps and unlocks the + * page mapped/locked by vmci_setup_notify(). + */ +void vmci_ctx_unset_notify(struct vmci_ctx *context) +{ + struct page *notify_page; + + spin_lock(&context->lock); + + notify_page = context->notify_page; + context->notify = &ctx_dummy_notify; + context->notify_page = NULL; + + spin_unlock(&context->lock); + + if (notify_page) { + kunmap(notify_page); + put_page(notify_page); + } +} + +/* + * Add remote_cid to list of contexts current contexts wants + * notifications from/about. + */ +int vmci_ctx_add_notification(u32 context_id, u32 remote_cid) +{ + struct vmci_ctx *context; + struct vmci_handle_list *notifier, *n; + int result; + bool exists = false; + + context = vmci_ctx_get(context_id); + if (!context) + return VMCI_ERROR_NOT_FOUND; + + if (VMCI_CONTEXT_IS_VM(context_id) && VMCI_CONTEXT_IS_VM(remote_cid)) { + pr_devel("Context removed notifications for other VMs not supported (src=0x%x, remote=0x%x)\n", + context_id, remote_cid); + result = VMCI_ERROR_DST_UNREACHABLE; + goto out; + } + + if (context->priv_flags & VMCI_PRIVILEGE_FLAG_RESTRICTED) { + result = VMCI_ERROR_NO_ACCESS; + goto out; + } + + notifier = kmalloc(sizeof(struct vmci_handle_list), GFP_KERNEL); + if (!notifier) { + result = VMCI_ERROR_NO_MEM; + goto out; + } + + INIT_LIST_HEAD(¬ifier->node); + notifier->handle = vmci_make_handle(remote_cid, VMCI_EVENT_HANDLER); + + spin_lock(&context->lock); + + list_for_each_entry(n, &context->notifier_list, node) { + if (vmci_handle_is_equal(n->handle, notifier->handle)) { + exists = true; + break; + } + } + + if (exists) { + kfree(notifier); + result = VMCI_ERROR_ALREADY_EXISTS; + } else { + list_add_tail_rcu(¬ifier->node, &context->notifier_list); + context->n_notifiers++; + result = VMCI_SUCCESS; + } + + spin_unlock(&context->lock); + + out: + vmci_ctx_put(context); + return result; +} + +/* + * Remove remote_cid from current context's list of contexts it is + * interested in getting notifications from/about. + */ +int vmci_ctx_remove_notification(u32 context_id, u32 remote_cid) +{ + struct vmci_ctx *context; + struct vmci_handle_list *notifier, *tmp; + struct vmci_handle handle; + bool found = false; + + context = vmci_ctx_get(context_id); + if (!context) + return VMCI_ERROR_NOT_FOUND; + + handle = vmci_make_handle(remote_cid, VMCI_EVENT_HANDLER); + + spin_lock(&context->lock); + list_for_each_entry_safe(notifier, tmp, + &context->notifier_list, node) { + if (vmci_handle_is_equal(notifier->handle, handle)) { + list_del_rcu(¬ifier->node); + context->n_notifiers--; + found = true; + break; + } + } + spin_unlock(&context->lock); + + if (found) { + synchronize_rcu(); + kfree(notifier); + } + + vmci_ctx_put(context); + + return found ? VMCI_SUCCESS : VMCI_ERROR_NOT_FOUND; +} + +static int vmci_ctx_get_chkpt_notifiers(struct vmci_ctx *context, + u32 *buf_size, void **pbuf) +{ + u32 *notifiers; + size_t data_size; + struct vmci_handle_list *entry; + int i = 0; + + if (context->n_notifiers == 0) { + *buf_size = 0; + *pbuf = NULL; + return VMCI_SUCCESS; + } + + data_size = context->n_notifiers * sizeof(*notifiers); + if (*buf_size < data_size) { + *buf_size = data_size; + return VMCI_ERROR_MORE_DATA; + } + + notifiers = kmalloc(data_size, GFP_ATOMIC); /* FIXME: want GFP_KERNEL */ + if (!notifiers) + return VMCI_ERROR_NO_MEM; + + list_for_each_entry(entry, &context->notifier_list, node) + notifiers[i++] = entry->handle.context; + + *buf_size = data_size; + *pbuf = notifiers; + return VMCI_SUCCESS; +} + +static int vmci_ctx_get_chkpt_doorbells(struct vmci_ctx *context, + u32 *buf_size, void **pbuf) +{ + struct dbell_cpt_state *dbells; + size_t n_doorbells; + int i; + + n_doorbells = vmci_handle_arr_get_size(context->doorbell_array); + if (n_doorbells > 0) { + size_t data_size = n_doorbells * sizeof(*dbells); + if (*buf_size < data_size) { + *buf_size = data_size; + return VMCI_ERROR_MORE_DATA; + } + + dbells = kmalloc(data_size, GFP_ATOMIC); + if (!dbells) + return VMCI_ERROR_NO_MEM; + + for (i = 0; i < n_doorbells; i++) + dbells[i].handle = vmci_handle_arr_get_entry( + context->doorbell_array, i); + + *buf_size = data_size; + *pbuf = dbells; + } else { + *buf_size = 0; + *pbuf = NULL; + } + + return VMCI_SUCCESS; +} + +/* + * Get current context's checkpoint state of given type. + */ +int vmci_ctx_get_chkpt_state(u32 context_id, + u32 cpt_type, + u32 *buf_size, + void **pbuf) +{ + struct vmci_ctx *context; + int result; + + context = vmci_ctx_get(context_id); + if (!context) + return VMCI_ERROR_NOT_FOUND; + + spin_lock(&context->lock); + + switch (cpt_type) { + case VMCI_NOTIFICATION_CPT_STATE: + result = vmci_ctx_get_chkpt_notifiers(context, buf_size, pbuf); + break; + + case VMCI_WELLKNOWN_CPT_STATE: + /* + * For compatibility with VMX'en with VM to VM communication, we + * always return zero wellknown handles. + */ + + *buf_size = 0; + *pbuf = NULL; + result = VMCI_SUCCESS; + break; + + case VMCI_DOORBELL_CPT_STATE: + result = vmci_ctx_get_chkpt_doorbells(context, buf_size, pbuf); + break; + + default: + pr_devel("Invalid cpt state (type=%d)\n", cpt_type); + result = VMCI_ERROR_INVALID_ARGS; + break; + } + + spin_unlock(&context->lock); + vmci_ctx_put(context); + + return result; +} + +/* + * Set current context's checkpoint state of given type. + */ +int vmci_ctx_set_chkpt_state(u32 context_id, + u32 cpt_type, + u32 buf_size, + void *cpt_buf) +{ + u32 i; + u32 current_id; + int result = VMCI_SUCCESS; + u32 num_ids = buf_size / sizeof(u32); + + if (cpt_type == VMCI_WELLKNOWN_CPT_STATE && num_ids > 0) { + /* + * We would end up here if VMX with VM to VM communication + * attempts to restore a checkpoint with wellknown handles. + */ + pr_warn("Attempt to restore checkpoint with obsolete wellknown handles\n"); + return VMCI_ERROR_OBSOLETE; + } + + if (cpt_type != VMCI_NOTIFICATION_CPT_STATE) { + pr_devel("Invalid cpt state (type=%d)\n", cpt_type); + return VMCI_ERROR_INVALID_ARGS; + } + + for (i = 0; i < num_ids && result == VMCI_SUCCESS; i++) { + current_id = ((u32 *)cpt_buf)[i]; + result = vmci_ctx_add_notification(context_id, current_id); + if (result != VMCI_SUCCESS) + break; + } + if (result != VMCI_SUCCESS) + pr_devel("Failed to set cpt state (type=%d) (error=%d)\n", + cpt_type, result); + + return result; +} + +/* + * Retrieves the specified context's pending notifications in the + * form of a handle array. The handle arrays returned are the + * actual data - not a copy and should not be modified by the + * caller. They must be released using + * vmci_ctx_rcv_notifications_release. + */ +int vmci_ctx_rcv_notifications_get(u32 context_id, + struct vmci_handle_arr **db_handle_array, + struct vmci_handle_arr **qp_handle_array) +{ + struct vmci_ctx *context; + int result = VMCI_SUCCESS; + + context = vmci_ctx_get(context_id); + if (context == NULL) + return VMCI_ERROR_NOT_FOUND; + + spin_lock(&context->lock); + + *db_handle_array = context->pending_doorbell_array; + context->pending_doorbell_array = vmci_handle_arr_create(0); + if (!context->pending_doorbell_array) { + context->pending_doorbell_array = *db_handle_array; + *db_handle_array = NULL; + result = VMCI_ERROR_NO_MEM; + } + *qp_handle_array = NULL; + + spin_unlock(&context->lock); + vmci_ctx_put(context); + + return result; +} + +/* + * Releases handle arrays with pending notifications previously + * retrieved using vmci_ctx_rcv_notifications_get. If the + * notifications were not successfully handed over to the guest, + * success must be false. + */ +void vmci_ctx_rcv_notifications_release(u32 context_id, + struct vmci_handle_arr *db_handle_array, + struct vmci_handle_arr *qp_handle_array, + bool success) +{ + struct vmci_ctx *context = vmci_ctx_get(context_id); + + spin_lock(&context->lock); + if (!success) { + struct vmci_handle handle; + + /* + * New notifications may have been added while we were not + * holding the context lock, so we transfer any new pending + * doorbell notifications to the old array, and reinstate the + * old array. + */ + + handle = vmci_handle_arr_remove_tail( + context->pending_doorbell_array); + while (!vmci_handle_is_invalid(handle)) { + if (!vmci_handle_arr_has_entry(db_handle_array, + handle)) { + vmci_handle_arr_append_entry( + &db_handle_array, handle); + } + handle = vmci_handle_arr_remove_tail( + context->pending_doorbell_array); + } + vmci_handle_arr_destroy(context->pending_doorbell_array); + context->pending_doorbell_array = db_handle_array; + db_handle_array = NULL; + } else { + ctx_clear_notify_call(context); + } + spin_unlock(&context->lock); + vmci_ctx_put(context); + + if (db_handle_array) + vmci_handle_arr_destroy(db_handle_array); + + if (qp_handle_array) + vmci_handle_arr_destroy(qp_handle_array); +} + +/* + * Registers that a new doorbell handle has been allocated by the + * context. Only doorbell handles registered can be notified. + */ +int vmci_ctx_dbell_create(u32 context_id, struct vmci_handle handle) +{ + struct vmci_ctx *context; + int result; + + if (context_id == VMCI_INVALID_ID || vmci_handle_is_invalid(handle)) + return VMCI_ERROR_INVALID_ARGS; + + context = vmci_ctx_get(context_id); + if (context == NULL) + return VMCI_ERROR_NOT_FOUND; + + spin_lock(&context->lock); + if (!vmci_handle_arr_has_entry(context->doorbell_array, handle)) { + vmci_handle_arr_append_entry(&context->doorbell_array, handle); + result = VMCI_SUCCESS; + } else { + result = VMCI_ERROR_DUPLICATE_ENTRY; + } + + spin_unlock(&context->lock); + vmci_ctx_put(context); + + return result; +} + +/* + * Unregisters a doorbell handle that was previously registered + * with vmci_ctx_dbell_create. + */ +int vmci_ctx_dbell_destroy(u32 context_id, struct vmci_handle handle) +{ + struct vmci_ctx *context; + struct vmci_handle removed_handle; + + if (context_id == VMCI_INVALID_ID || vmci_handle_is_invalid(handle)) + return VMCI_ERROR_INVALID_ARGS; + + context = vmci_ctx_get(context_id); + if (context == NULL) + return VMCI_ERROR_NOT_FOUND; + + spin_lock(&context->lock); + removed_handle = + vmci_handle_arr_remove_entry(context->doorbell_array, handle); + vmci_handle_arr_remove_entry(context->pending_doorbell_array, handle); + spin_unlock(&context->lock); + + vmci_ctx_put(context); + + return vmci_handle_is_invalid(removed_handle) ? + VMCI_ERROR_NOT_FOUND : VMCI_SUCCESS; +} + +/* + * Unregisters all doorbell handles that were previously + * registered with vmci_ctx_dbell_create. + */ +int vmci_ctx_dbell_destroy_all(u32 context_id) +{ + struct vmci_ctx *context; + struct vmci_handle handle; + + if (context_id == VMCI_INVALID_ID) + return VMCI_ERROR_INVALID_ARGS; + + context = vmci_ctx_get(context_id); + if (context == NULL) + return VMCI_ERROR_NOT_FOUND; + + spin_lock(&context->lock); + do { + struct vmci_handle_arr *arr = context->doorbell_array; + handle = vmci_handle_arr_remove_tail(arr); + } while (!vmci_handle_is_invalid(handle)); + do { + struct vmci_handle_arr *arr = context->pending_doorbell_array; + handle = vmci_handle_arr_remove_tail(arr); + } while (!vmci_handle_is_invalid(handle)); + spin_unlock(&context->lock); + + vmci_ctx_put(context); + + return VMCI_SUCCESS; +} + +/* + * Registers a notification of a doorbell handle initiated by the + * specified source context. The notification of doorbells are + * subject to the same isolation rules as datagram delivery. To + * allow host side senders of notifications a finer granularity + * of sender rights than those assigned to the sending context + * itself, the host context is required to specify a different + * set of privilege flags that will override the privileges of + * the source context. + */ +int vmci_ctx_notify_dbell(u32 src_cid, + struct vmci_handle handle, + u32 src_priv_flags) +{ + struct vmci_ctx *dst_context; + int result; + + if (vmci_handle_is_invalid(handle)) + return VMCI_ERROR_INVALID_ARGS; + + /* Get the target VM's VMCI context. */ + dst_context = vmci_ctx_get(handle.context); + if (!dst_context) { + pr_devel("Invalid context (ID=0x%x)\n", handle.context); + return VMCI_ERROR_NOT_FOUND; + } + + if (src_cid != handle.context) { + u32 dst_priv_flags; + + if (VMCI_CONTEXT_IS_VM(src_cid) && + VMCI_CONTEXT_IS_VM(handle.context)) { + pr_devel("Doorbell notification from VM to VM not supported (src=0x%x, dst=0x%x)\n", + src_cid, handle.context); + result = VMCI_ERROR_DST_UNREACHABLE; + goto out; + } + + result = vmci_dbell_get_priv_flags(handle, &dst_priv_flags); + if (result < VMCI_SUCCESS) { + pr_warn("Failed to get privilege flags for destination (handle=0x%x:0x%x)\n", + handle.context, handle.resource); + goto out; + } + + if (src_cid != VMCI_HOST_CONTEXT_ID || + src_priv_flags == VMCI_NO_PRIVILEGE_FLAGS) { + src_priv_flags = vmci_context_get_priv_flags(src_cid); + } + + if (vmci_deny_interaction(src_priv_flags, dst_priv_flags)) { + result = VMCI_ERROR_NO_ACCESS; + goto out; + } + } + + if (handle.context == VMCI_HOST_CONTEXT_ID) { + result = vmci_dbell_host_context_notify(src_cid, handle); + } else { + spin_lock(&dst_context->lock); + + if (!vmci_handle_arr_has_entry(dst_context->doorbell_array, + handle)) { + result = VMCI_ERROR_NOT_FOUND; + } else { + if (!vmci_handle_arr_has_entry( + dst_context->pending_doorbell_array, + handle)) { + vmci_handle_arr_append_entry( + &dst_context->pending_doorbell_array, + handle); + + ctx_signal_notify(dst_context); + wake_up(&dst_context->host_context.wait_queue); + + } + result = VMCI_SUCCESS; + } + spin_unlock(&dst_context->lock); + } + + out: + vmci_ctx_put(dst_context); + + return result; +} + +bool vmci_ctx_supports_host_qp(struct vmci_ctx *context) +{ + return context && context->user_version >= VMCI_VERSION_HOSTQP; +} + +/* + * Registers that a new queue pair handle has been allocated by + * the context. + */ +int vmci_ctx_qp_create(struct vmci_ctx *context, struct vmci_handle handle) +{ + int result; + + if (context == NULL || vmci_handle_is_invalid(handle)) + return VMCI_ERROR_INVALID_ARGS; + + if (!vmci_handle_arr_has_entry(context->queue_pair_array, handle)) { + vmci_handle_arr_append_entry(&context->queue_pair_array, + handle); + result = VMCI_SUCCESS; + } else { + result = VMCI_ERROR_DUPLICATE_ENTRY; + } + + return result; +} + +/* + * Unregisters a queue pair handle that was previously registered + * with vmci_ctx_qp_create. + */ +int vmci_ctx_qp_destroy(struct vmci_ctx *context, struct vmci_handle handle) +{ + struct vmci_handle hndl; + + if (context == NULL || vmci_handle_is_invalid(handle)) + return VMCI_ERROR_INVALID_ARGS; + + hndl = vmci_handle_arr_remove_entry(context->queue_pair_array, handle); + + return vmci_handle_is_invalid(hndl) ? + VMCI_ERROR_NOT_FOUND : VMCI_SUCCESS; +} + +/* + * Determines whether a given queue pair handle is registered + * with the given context. + */ +bool vmci_ctx_qp_exists(struct vmci_ctx *context, struct vmci_handle handle) +{ + if (context == NULL || vmci_handle_is_invalid(handle)) + return false; + + return vmci_handle_arr_has_entry(context->queue_pair_array, handle); +} + +/* + * vmci_context_get_priv_flags() - Retrieve privilege flags. + * @context_id: The context ID of the VMCI context. + * + * Retrieves privilege flags of the given VMCI context ID. + */ +u32 vmci_context_get_priv_flags(u32 context_id) +{ + if (vmci_host_code_active()) { + u32 flags; + struct vmci_ctx *context; + + context = vmci_ctx_get(context_id); + if (!context) + return VMCI_LEAST_PRIVILEGE_FLAGS; + + flags = context->priv_flags; + vmci_ctx_put(context); + return flags; + } + return VMCI_NO_PRIVILEGE_FLAGS; +} +EXPORT_SYMBOL_GPL(vmci_context_get_priv_flags); + +/* + * vmci_is_context_owner() - Determimnes if user is the context owner + * @context_id: The context ID of the VMCI context. + * @uid: The host user id (real kernel value). + * + * Determines whether a given UID is the owner of given VMCI context. + */ +bool vmci_is_context_owner(u32 context_id, kuid_t uid) +{ + bool is_owner = false; + + if (vmci_host_code_active()) { + struct vmci_ctx *context = vmci_ctx_get(context_id); + if (context) { + if (context->cred) + is_owner = uid_eq(context->cred->uid, uid); + vmci_ctx_put(context); + } + } + + return is_owner; +} +EXPORT_SYMBOL_GPL(vmci_is_context_owner); diff --git a/drivers/misc/vmw_vmci/vmci_context.h b/drivers/misc/vmw_vmci/vmci_context.h new file mode 100644 index 000000000000..24a88e68a1e6 --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_context.h @@ -0,0 +1,182 @@ +/* + * VMware VMCI driver (vmciContext.h) + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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. + */ + +#ifndef _VMCI_CONTEXT_H_ +#define _VMCI_CONTEXT_H_ + +#include +#include +#include +#include +#include + +#include "vmci_handle_array.h" +#include "vmci_datagram.h" + +/* Used to determine what checkpoint state to get and set. */ +enum { + VMCI_NOTIFICATION_CPT_STATE = 1, + VMCI_WELLKNOWN_CPT_STATE = 2, + VMCI_DG_OUT_STATE = 3, + VMCI_DG_IN_STATE = 4, + VMCI_DG_IN_SIZE_STATE = 5, + VMCI_DOORBELL_CPT_STATE = 6, +}; + +/* Host specific struct used for signalling */ +struct vmci_host { + wait_queue_head_t wait_queue; +}; + +struct vmci_handle_list { + struct list_head node; + struct vmci_handle handle; +}; + +struct vmci_ctx { + struct list_head list_item; /* For global VMCI list. */ + u32 cid; + struct kref kref; + struct list_head datagram_queue; /* Head of per VM queue. */ + u32 pending_datagrams; + size_t datagram_queue_size; /* Size of datagram queue in bytes. */ + + /* + * Version of the code that created + * this context; e.g., VMX. + */ + int user_version; + spinlock_t lock; /* Locks callQueue and handle_arrays. */ + + /* + * queue_pairs attached to. The array of + * handles for queue pairs is accessed + * from the code for QP API, and there + * it is protected by the QP lock. It + * is also accessed from the context + * clean up path, which does not + * require a lock. VMCILock is not + * used to protect the QP array field. + */ + struct vmci_handle_arr *queue_pair_array; + + /* Doorbells created by context. */ + struct vmci_handle_arr *doorbell_array; + + /* Doorbells pending for context. */ + struct vmci_handle_arr *pending_doorbell_array; + + /* Contexts current context is subscribing to. */ + struct list_head notifier_list; + unsigned int n_notifiers; + + struct vmci_host host_context; + u32 priv_flags; + + const struct cred *cred; + bool *notify; /* Notify flag pointer - hosted only. */ + struct page *notify_page; /* Page backing the notify UVA. */ +}; + +/* VMCINotifyAddRemoveInfo: Used to add/remove remote context notifications. */ +struct vmci_ctx_info { + u32 remote_cid; + int result; +}; + +/* VMCICptBufInfo: Used to set/get current context's checkpoint state. */ +struct vmci_ctx_chkpt_buf_info { + u64 cpt_buf; + u32 cpt_type; + u32 buf_size; + s32 result; + u32 _pad; +}; + +/* + * VMCINotificationReceiveInfo: Used to recieve pending notifications + * for doorbells and queue pairs. + */ +struct vmci_ctx_notify_recv_info { + u64 db_handle_buf_uva; + u64 db_handle_buf_size; + u64 qp_handle_buf_uva; + u64 qp_handle_buf_size; + s32 result; + u32 _pad; +}; + +/* + * Utilility function that checks whether two entities are allowed + * to interact. If one of them is restricted, the other one must + * be trusted. + */ +static inline bool vmci_deny_interaction(u32 part_one, u32 part_two) +{ + return ((part_one & VMCI_PRIVILEGE_FLAG_RESTRICTED) && + !(part_two & VMCI_PRIVILEGE_FLAG_TRUSTED)) || + ((part_two & VMCI_PRIVILEGE_FLAG_RESTRICTED) && + !(part_one & VMCI_PRIVILEGE_FLAG_TRUSTED)); +} + +struct vmci_ctx *vmci_ctx_create(u32 cid, u32 flags, + uintptr_t event_hnd, int version, + const struct cred *cred); +void vmci_ctx_destroy(struct vmci_ctx *context); + +bool vmci_ctx_supports_host_qp(struct vmci_ctx *context); +int vmci_ctx_enqueue_datagram(u32 cid, struct vmci_datagram *dg); +int vmci_ctx_dequeue_datagram(struct vmci_ctx *context, + size_t *max_size, struct vmci_datagram **dg); +int vmci_ctx_pending_datagrams(u32 cid, u32 *pending); +struct vmci_ctx *vmci_ctx_get(u32 cid); +void vmci_ctx_put(struct vmci_ctx *context); +bool vmci_ctx_exists(u32 cid); + +int vmci_ctx_add_notification(u32 context_id, u32 remote_cid); +int vmci_ctx_remove_notification(u32 context_id, u32 remote_cid); +int vmci_ctx_get_chkpt_state(u32 context_id, u32 cpt_type, + u32 *num_cids, void **cpt_buf_ptr); +int vmci_ctx_set_chkpt_state(u32 context_id, u32 cpt_type, + u32 num_cids, void *cpt_buf); + +int vmci_ctx_qp_create(struct vmci_ctx *context, struct vmci_handle handle); +int vmci_ctx_qp_destroy(struct vmci_ctx *context, struct vmci_handle handle); +bool vmci_ctx_qp_exists(struct vmci_ctx *context, struct vmci_handle handle); + +void vmci_ctx_check_signal_notify(struct vmci_ctx *context); +void vmci_ctx_unset_notify(struct vmci_ctx *context); + +int vmci_ctx_dbell_create(u32 context_id, struct vmci_handle handle); +int vmci_ctx_dbell_destroy(u32 context_id, struct vmci_handle handle); +int vmci_ctx_dbell_destroy_all(u32 context_id); +int vmci_ctx_notify_dbell(u32 cid, struct vmci_handle handle, + u32 src_priv_flags); + +int vmci_ctx_rcv_notifications_get(u32 context_id, struct vmci_handle_arr + **db_handle_array, struct vmci_handle_arr + **qp_handle_array); +void vmci_ctx_rcv_notifications_release(u32 context_id, struct vmci_handle_arr + *db_handle_array, struct vmci_handle_arr + *qp_handle_array, bool success); + +static inline u32 vmci_ctx_get_id(struct vmci_ctx *context) +{ + if (!context) + return VMCI_INVALID_ID; + return context->cid; +} + +#endif /* _VMCI_CONTEXT_H_ */ -- cgit v1.2.1 From a110b7ebb9c674a2b591af2780dd512ad0198d50 Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:53:15 -0800 Subject: VMCI: datagram implementation. VMCI datagram Implements datagrams to allow data to be sent between host and guest. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_datagram.c | 500 ++++++++++++++++++++++++++++++++++ drivers/misc/vmw_vmci/vmci_datagram.h | 52 ++++ 2 files changed, 552 insertions(+) create mode 100644 drivers/misc/vmw_vmci/vmci_datagram.c create mode 100644 drivers/misc/vmw_vmci/vmci_datagram.h diff --git a/drivers/misc/vmw_vmci/vmci_datagram.c b/drivers/misc/vmw_vmci/vmci_datagram.c new file mode 100644 index 000000000000..ed5c433cd493 --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_datagram.c @@ -0,0 +1,500 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include + +#include "vmci_datagram.h" +#include "vmci_resource.h" +#include "vmci_context.h" +#include "vmci_driver.h" +#include "vmci_event.h" +#include "vmci_route.h" + +/* + * struct datagram_entry describes the datagram entity. It is used for datagram + * entities created only on the host. + */ +struct datagram_entry { + struct vmci_resource resource; + u32 flags; + bool run_delayed; + vmci_datagram_recv_cb recv_cb; + void *client_data; + u32 priv_flags; +}; + +struct delayed_datagram_info { + struct datagram_entry *entry; + struct vmci_datagram msg; + struct work_struct work; + bool in_dg_host_queue; +}; + +/* Number of in-flight host->host datagrams */ +static atomic_t delayed_dg_host_queue_size = ATOMIC_INIT(0); + +/* + * Create a datagram entry given a handle pointer. + */ +static int dg_create_handle(u32 resource_id, + u32 flags, + u32 priv_flags, + vmci_datagram_recv_cb recv_cb, + void *client_data, struct vmci_handle *out_handle) +{ + int result; + u32 context_id; + struct vmci_handle handle; + struct datagram_entry *entry; + + if ((flags & VMCI_FLAG_WELLKNOWN_DG_HND) != 0) + return VMCI_ERROR_INVALID_ARGS; + + if ((flags & VMCI_FLAG_ANYCID_DG_HND) != 0) { + context_id = VMCI_INVALID_ID; + } else { + context_id = vmci_get_context_id(); + if (context_id == VMCI_INVALID_ID) + return VMCI_ERROR_NO_RESOURCES; + } + + handle = vmci_make_handle(context_id, resource_id); + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + pr_warn("Failed allocating memory for datagram entry\n"); + return VMCI_ERROR_NO_MEM; + } + + entry->run_delayed = (flags & VMCI_FLAG_DG_DELAYED_CB) ? true : false; + entry->flags = flags; + entry->recv_cb = recv_cb; + entry->client_data = client_data; + entry->priv_flags = priv_flags; + + /* Make datagram resource live. */ + result = vmci_resource_add(&entry->resource, + VMCI_RESOURCE_TYPE_DATAGRAM, + handle); + if (result != VMCI_SUCCESS) { + pr_warn("Failed to add new resource (handle=0x%x:0x%x), error: %d\n", + handle.context, handle.resource, result); + kfree(entry); + return result; + } + + *out_handle = vmci_resource_handle(&entry->resource); + return VMCI_SUCCESS; +} + +/* + * Internal utility function with the same purpose as + * vmci_datagram_get_priv_flags that also takes a context_id. + */ +static int vmci_datagram_get_priv_flags(u32 context_id, + struct vmci_handle handle, + u32 *priv_flags) +{ + if (context_id == VMCI_INVALID_ID) + return VMCI_ERROR_INVALID_ARGS; + + if (context_id == VMCI_HOST_CONTEXT_ID) { + struct datagram_entry *src_entry; + struct vmci_resource *resource; + + resource = vmci_resource_by_handle(handle, + VMCI_RESOURCE_TYPE_DATAGRAM); + if (!resource) + return VMCI_ERROR_INVALID_ARGS; + + src_entry = container_of(resource, struct datagram_entry, + resource); + *priv_flags = src_entry->priv_flags; + vmci_resource_put(resource); + } else if (context_id == VMCI_HYPERVISOR_CONTEXT_ID) + *priv_flags = VMCI_MAX_PRIVILEGE_FLAGS; + else + *priv_flags = vmci_context_get_priv_flags(context_id); + + return VMCI_SUCCESS; +} + +/* + * Calls the specified callback in a delayed context. + */ +static void dg_delayed_dispatch(struct work_struct *work) +{ + struct delayed_datagram_info *dg_info = + container_of(work, struct delayed_datagram_info, work); + + dg_info->entry->recv_cb(dg_info->entry->client_data, &dg_info->msg); + + vmci_resource_put(&dg_info->entry->resource); + + if (dg_info->in_dg_host_queue) + atomic_dec(&delayed_dg_host_queue_size); + + kfree(dg_info); +} + +/* + * Dispatch datagram as a host, to the host, or other vm context. This + * function cannot dispatch to hypervisor context handlers. This should + * have been handled before we get here by vmci_datagram_dispatch. + * Returns number of bytes sent on success, error code otherwise. + */ +static int dg_dispatch_as_host(u32 context_id, struct vmci_datagram *dg) +{ + int retval; + size_t dg_size; + u32 src_priv_flags; + + dg_size = VMCI_DG_SIZE(dg); + + /* Host cannot send to the hypervisor. */ + if (dg->dst.context == VMCI_HYPERVISOR_CONTEXT_ID) + return VMCI_ERROR_DST_UNREACHABLE; + + /* Check that source handle matches sending context. */ + if (dg->src.context != context_id) { + pr_devel("Sender context (ID=0x%x) is not owner of src datagram entry (handle=0x%x:0x%x)\n", + context_id, dg->src.context, dg->src.resource); + return VMCI_ERROR_NO_ACCESS; + } + + /* Get hold of privileges of sending endpoint. */ + retval = vmci_datagram_get_priv_flags(context_id, dg->src, + &src_priv_flags); + if (retval != VMCI_SUCCESS) { + pr_warn("Couldn't get privileges (handle=0x%x:0x%x)\n", + dg->src.context, dg->src.resource); + return retval; + } + + /* Determine if we should route to host or guest destination. */ + if (dg->dst.context == VMCI_HOST_CONTEXT_ID) { + /* Route to host datagram entry. */ + struct datagram_entry *dst_entry; + struct vmci_resource *resource; + + if (dg->src.context == VMCI_HYPERVISOR_CONTEXT_ID && + dg->dst.resource == VMCI_EVENT_HANDLER) { + return vmci_event_dispatch(dg); + } + + resource = vmci_resource_by_handle(dg->dst, + VMCI_RESOURCE_TYPE_DATAGRAM); + if (!resource) { + pr_devel("Sending to invalid destination (handle=0x%x:0x%x)\n", + dg->dst.context, dg->dst.resource); + return VMCI_ERROR_INVALID_RESOURCE; + } + dst_entry = container_of(resource, struct datagram_entry, + resource); + if (vmci_deny_interaction(src_priv_flags, + dst_entry->priv_flags)) { + vmci_resource_put(resource); + return VMCI_ERROR_NO_ACCESS; + } + + /* + * If a VMCI datagram destined for the host is also sent by the + * host, we always run it delayed. This ensures that no locks + * are held when the datagram callback runs. + */ + if (dst_entry->run_delayed || + dg->src.context == VMCI_HOST_CONTEXT_ID) { + struct delayed_datagram_info *dg_info; + + if (atomic_add_return(1, &delayed_dg_host_queue_size) + == VMCI_MAX_DELAYED_DG_HOST_QUEUE_SIZE) { + atomic_dec(&delayed_dg_host_queue_size); + vmci_resource_put(resource); + return VMCI_ERROR_NO_MEM; + } + + dg_info = kmalloc(sizeof(*dg_info) + + (size_t) dg->payload_size, GFP_ATOMIC); + if (!dg_info) { + atomic_dec(&delayed_dg_host_queue_size); + vmci_resource_put(resource); + return VMCI_ERROR_NO_MEM; + } + + dg_info->in_dg_host_queue = true; + dg_info->entry = dst_entry; + memcpy(&dg_info->msg, dg, dg_size); + + INIT_WORK(&dg_info->work, dg_delayed_dispatch); + schedule_work(&dg_info->work); + retval = VMCI_SUCCESS; + + } else { + retval = dst_entry->recv_cb(dst_entry->client_data, dg); + vmci_resource_put(resource); + if (retval < VMCI_SUCCESS) + return retval; + } + } else { + /* Route to destination VM context. */ + struct vmci_datagram *new_dg; + + if (context_id != dg->dst.context) { + if (vmci_deny_interaction(src_priv_flags, + vmci_context_get_priv_flags + (dg->dst.context))) { + return VMCI_ERROR_NO_ACCESS; + } else if (VMCI_CONTEXT_IS_VM(context_id)) { + /* + * If the sending context is a VM, it + * cannot reach another VM. + */ + + pr_devel("Datagram communication between VMs not supported (src=0x%x, dst=0x%x)\n", + context_id, dg->dst.context); + return VMCI_ERROR_DST_UNREACHABLE; + } + } + + /* We make a copy to enqueue. */ + new_dg = kmalloc(dg_size, GFP_KERNEL); + if (new_dg == NULL) + return VMCI_ERROR_NO_MEM; + + memcpy(new_dg, dg, dg_size); + retval = vmci_ctx_enqueue_datagram(dg->dst.context, new_dg); + if (retval < VMCI_SUCCESS) { + kfree(new_dg); + return retval; + } + } + + /* + * We currently truncate the size to signed 32 bits. This doesn't + * matter for this handler as it only support 4Kb messages. + */ + return (int)dg_size; +} + +/* + * Dispatch datagram as a guest, down through the VMX and potentially to + * the host. + * Returns number of bytes sent on success, error code otherwise. + */ +static int dg_dispatch_as_guest(struct vmci_datagram *dg) +{ + int retval; + struct vmci_resource *resource; + + resource = vmci_resource_by_handle(dg->src, + VMCI_RESOURCE_TYPE_DATAGRAM); + if (!resource) + return VMCI_ERROR_NO_HANDLE; + + retval = vmci_send_datagram(dg); + vmci_resource_put(resource); + return retval; +} + +/* + * Dispatch datagram. This will determine the routing for the datagram + * and dispatch it accordingly. + * Returns number of bytes sent on success, error code otherwise. + */ +int vmci_datagram_dispatch(u32 context_id, + struct vmci_datagram *dg, bool from_guest) +{ + int retval; + enum vmci_route route; + + BUILD_BUG_ON(sizeof(struct vmci_datagram) != 24); + + if (VMCI_DG_SIZE(dg) > VMCI_MAX_DG_SIZE) { + pr_devel("Payload (size=%llu bytes) too big to send\n", + (unsigned long long)dg->payload_size); + return VMCI_ERROR_INVALID_ARGS; + } + + retval = vmci_route(&dg->src, &dg->dst, from_guest, &route); + if (retval < VMCI_SUCCESS) { + pr_devel("Failed to route datagram (src=0x%x, dst=0x%x, err=%d)\n", + dg->src.context, dg->dst.context, retval); + return retval; + } + + if (VMCI_ROUTE_AS_HOST == route) { + if (VMCI_INVALID_ID == context_id) + context_id = VMCI_HOST_CONTEXT_ID; + return dg_dispatch_as_host(context_id, dg); + } + + if (VMCI_ROUTE_AS_GUEST == route) + return dg_dispatch_as_guest(dg); + + pr_warn("Unknown route (%d) for datagram\n", route); + return VMCI_ERROR_DST_UNREACHABLE; +} + +/* + * Invoke the handler for the given datagram. This is intended to be + * called only when acting as a guest and receiving a datagram from the + * virtual device. + */ +int vmci_datagram_invoke_guest_handler(struct vmci_datagram *dg) +{ + struct vmci_resource *resource; + struct datagram_entry *dst_entry; + + resource = vmci_resource_by_handle(dg->dst, + VMCI_RESOURCE_TYPE_DATAGRAM); + if (!resource) { + pr_devel("destination (handle=0x%x:0x%x) doesn't exist\n", + dg->dst.context, dg->dst.resource); + return VMCI_ERROR_NO_HANDLE; + } + + dst_entry = container_of(resource, struct datagram_entry, resource); + if (dst_entry->run_delayed) { + struct delayed_datagram_info *dg_info; + + dg_info = kmalloc(sizeof(*dg_info) + (size_t)dg->payload_size, + GFP_ATOMIC); + if (!dg_info) { + vmci_resource_put(resource); + return VMCI_ERROR_NO_MEM; + } + + dg_info->in_dg_host_queue = false; + dg_info->entry = dst_entry; + memcpy(&dg_info->msg, dg, VMCI_DG_SIZE(dg)); + + INIT_WORK(&dg_info->work, dg_delayed_dispatch); + schedule_work(&dg_info->work); + } else { + dst_entry->recv_cb(dst_entry->client_data, dg); + vmci_resource_put(resource); + } + + return VMCI_SUCCESS; +} + +/* + * vmci_datagram_create_handle_priv() - Create host context datagram endpoint + * @resource_id: The resource ID. + * @flags: Datagram Flags. + * @priv_flags: Privilege Flags. + * @recv_cb: Callback when receiving datagrams. + * @client_data: Pointer for a datagram_entry struct + * @out_handle: vmci_handle that is populated as a result of this function. + * + * Creates a host context datagram endpoint and returns a handle to it. + */ +int vmci_datagram_create_handle_priv(u32 resource_id, + u32 flags, + u32 priv_flags, + vmci_datagram_recv_cb recv_cb, + void *client_data, + struct vmci_handle *out_handle) +{ + if (out_handle == NULL) + return VMCI_ERROR_INVALID_ARGS; + + if (recv_cb == NULL) { + pr_devel("Client callback needed when creating datagram\n"); + return VMCI_ERROR_INVALID_ARGS; + } + + if (priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS) + return VMCI_ERROR_INVALID_ARGS; + + return dg_create_handle(resource_id, flags, priv_flags, recv_cb, + client_data, out_handle); +} +EXPORT_SYMBOL_GPL(vmci_datagram_create_handle_priv); + +/* + * vmci_datagram_create_handle() - Create host context datagram endpoint + * @resource_id: Resource ID. + * @flags: Datagram Flags. + * @recv_cb: Callback when receiving datagrams. + * @client_ata: Pointer for a datagram_entry struct + * @out_handle: vmci_handle that is populated as a result of this function. + * + * Creates a host context datagram endpoint and returns a handle to + * it. Same as vmci_datagram_create_handle_priv without the priviledge + * flags argument. + */ +int vmci_datagram_create_handle(u32 resource_id, + u32 flags, + vmci_datagram_recv_cb recv_cb, + void *client_data, + struct vmci_handle *out_handle) +{ + return vmci_datagram_create_handle_priv( + resource_id, flags, + VMCI_DEFAULT_PROC_PRIVILEGE_FLAGS, + recv_cb, client_data, + out_handle); +} +EXPORT_SYMBOL_GPL(vmci_datagram_create_handle); + +/* + * vmci_datagram_destroy_handle() - Destroys datagram handle + * @handle: vmci_handle to be destroyed and reaped. + * + * Use this function to destroy any datagram handles created by + * vmci_datagram_create_handle{,Priv} functions. + */ +int vmci_datagram_destroy_handle(struct vmci_handle handle) +{ + struct datagram_entry *entry; + struct vmci_resource *resource; + + resource = vmci_resource_by_handle(handle, VMCI_RESOURCE_TYPE_DATAGRAM); + if (!resource) { + pr_devel("Failed to destroy datagram (handle=0x%x:0x%x)\n", + handle.context, handle.resource); + return VMCI_ERROR_NOT_FOUND; + } + + entry = container_of(resource, struct datagram_entry, resource); + + vmci_resource_put(&entry->resource); + vmci_resource_remove(&entry->resource); + kfree(entry); + + return VMCI_SUCCESS; +} +EXPORT_SYMBOL_GPL(vmci_datagram_destroy_handle); + +/* + * vmci_datagram_send() - Send a datagram + * @msg: The datagram to send. + * + * Sends the provided datagram on its merry way. + */ +int vmci_datagram_send(struct vmci_datagram *msg) +{ + if (msg == NULL) + return VMCI_ERROR_INVALID_ARGS; + + return vmci_datagram_dispatch(VMCI_INVALID_ID, msg, false); +} +EXPORT_SYMBOL_GPL(vmci_datagram_send); diff --git a/drivers/misc/vmw_vmci/vmci_datagram.h b/drivers/misc/vmw_vmci/vmci_datagram.h new file mode 100644 index 000000000000..eb4aab7f64ec --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_datagram.h @@ -0,0 +1,52 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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. + */ + +#ifndef _VMCI_DATAGRAM_H_ +#define _VMCI_DATAGRAM_H_ + +#include +#include + +#include "vmci_context.h" + +#define VMCI_MAX_DELAYED_DG_HOST_QUEUE_SIZE 256 + +/* + * The struct vmci_datagram_queue_entry is a queue header for the in-kernel VMCI + * datagram queues. It is allocated in non-paged memory, as the + * content is accessed while holding a spinlock. The pending datagram + * itself may be allocated from paged memory. We shadow the size of + * the datagram in the non-paged queue entry as this size is used + * while holding the same spinlock as above. + */ +struct vmci_datagram_queue_entry { + struct list_head list_item; /* For queuing. */ + size_t dg_size; /* Size of datagram. */ + struct vmci_datagram *dg; /* Pending datagram. */ +}; + +/* VMCIDatagramSendRecvInfo */ +struct vmci_datagram_snd_rcv_info { + u64 addr; + u32 len; + s32 result; +}; + +/* Datagram API for non-public use. */ +int vmci_datagram_dispatch(u32 context_id, struct vmci_datagram *dg, + bool from_guest); +int vmci_datagram_invoke_guest_handler(struct vmci_datagram *dg); + +#endif /* _VMCI_DATAGRAM_H_ */ -- cgit v1.2.1 From 83e2ec765be03e8a8a07619e65df70b48a1db023 Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:53:51 -0800 Subject: VMCI: doorbell implementation. VMCI doorbell code allows for notifcations between host and guest. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_doorbell.c | 604 ++++++++++++++++++++++++++++++++++ drivers/misc/vmw_vmci/vmci_doorbell.h | 51 +++ 2 files changed, 655 insertions(+) create mode 100644 drivers/misc/vmw_vmci/vmci_doorbell.c create mode 100644 drivers/misc/vmw_vmci/vmci_doorbell.h diff --git a/drivers/misc/vmw_vmci/vmci_doorbell.c b/drivers/misc/vmw_vmci/vmci_doorbell.c new file mode 100644 index 000000000000..c3e8397f62ed --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_doorbell.c @@ -0,0 +1,604 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vmci_datagram.h" +#include "vmci_doorbell.h" +#include "vmci_resource.h" +#include "vmci_driver.h" +#include "vmci_route.h" + + +#define VMCI_DOORBELL_INDEX_BITS 6 +#define VMCI_DOORBELL_INDEX_TABLE_SIZE (1 << VMCI_DOORBELL_INDEX_BITS) +#define VMCI_DOORBELL_HASH(_idx) hash_32(_idx, VMCI_DOORBELL_INDEX_BITS) + +/* + * DoorbellEntry describes the a doorbell notification handle allocated by the + * host. + */ +struct dbell_entry { + struct vmci_resource resource; + struct hlist_node node; + struct work_struct work; + vmci_callback notify_cb; + void *client_data; + u32 idx; + u32 priv_flags; + bool run_delayed; + atomic_t active; /* Only used by guest personality */ +}; + +/* The VMCI index table keeps track of currently registered doorbells. */ +struct dbell_index_table { + spinlock_t lock; /* Index table lock */ + struct hlist_head entries[VMCI_DOORBELL_INDEX_TABLE_SIZE]; +}; + +static struct dbell_index_table vmci_doorbell_it = { + .lock = __SPIN_LOCK_UNLOCKED(vmci_doorbell_it.lock), +}; + +/* + * The max_notify_idx is one larger than the currently known bitmap index in + * use, and is used to determine how much of the bitmap needs to be scanned. + */ +static u32 max_notify_idx; + +/* + * The notify_idx_count is used for determining whether there are free entries + * within the bitmap (if notify_idx_count + 1 < max_notify_idx). + */ +static u32 notify_idx_count; + +/* + * The last_notify_idx_reserved is used to track the last index handed out - in + * the case where multiple handles share a notification index, we hand out + * indexes round robin based on last_notify_idx_reserved. + */ +static u32 last_notify_idx_reserved; + +/* This is a one entry cache used to by the index allocation. */ +static u32 last_notify_idx_released = PAGE_SIZE; + + +/* + * Utility function that retrieves the privilege flags associated + * with a given doorbell handle. For guest endpoints, the + * privileges are determined by the context ID, but for host + * endpoints privileges are associated with the complete + * handle. Hypervisor endpoints are not yet supported. + */ +int vmci_dbell_get_priv_flags(struct vmci_handle handle, u32 *priv_flags) +{ + if (priv_flags == NULL || handle.context == VMCI_INVALID_ID) + return VMCI_ERROR_INVALID_ARGS; + + if (handle.context == VMCI_HOST_CONTEXT_ID) { + struct dbell_entry *entry; + struct vmci_resource *resource; + + resource = vmci_resource_by_handle(handle, + VMCI_RESOURCE_TYPE_DOORBELL); + if (!resource) + return VMCI_ERROR_NOT_FOUND; + + entry = container_of(resource, struct dbell_entry, resource); + *priv_flags = entry->priv_flags; + vmci_resource_put(resource); + } else if (handle.context == VMCI_HYPERVISOR_CONTEXT_ID) { + /* + * Hypervisor endpoints for notifications are not + * supported (yet). + */ + return VMCI_ERROR_INVALID_ARGS; + } else { + *priv_flags = vmci_context_get_priv_flags(handle.context); + } + + return VMCI_SUCCESS; +} + +/* + * Find doorbell entry by bitmap index. + */ +static struct dbell_entry *dbell_index_table_find(u32 idx) +{ + u32 bucket = VMCI_DOORBELL_HASH(idx); + struct dbell_entry *dbell; + struct hlist_node *node; + + hlist_for_each_entry(dbell, node, &vmci_doorbell_it.entries[bucket], + node) { + if (idx == dbell->idx) + return dbell; + } + + return NULL; +} + +/* + * Add the given entry to the index table. This willi take a reference to the + * entry's resource so that the entry is not deleted before it is removed from + * the * table. + */ +static void dbell_index_table_add(struct dbell_entry *entry) +{ + u32 bucket; + u32 new_notify_idx; + + vmci_resource_get(&entry->resource); + + spin_lock_bh(&vmci_doorbell_it.lock); + + /* + * Below we try to allocate an index in the notification + * bitmap with "not too much" sharing between resources. If we + * use less that the full bitmap, we either add to the end if + * there are no unused flags within the currently used area, + * or we search for unused ones. If we use the full bitmap, we + * allocate the index round robin. + */ + if (max_notify_idx < PAGE_SIZE || notify_idx_count < PAGE_SIZE) { + if (last_notify_idx_released < max_notify_idx && + !dbell_index_table_find(last_notify_idx_released)) { + new_notify_idx = last_notify_idx_released; + last_notify_idx_released = PAGE_SIZE; + } else { + bool reused = false; + new_notify_idx = last_notify_idx_reserved; + if (notify_idx_count + 1 < max_notify_idx) { + do { + if (!dbell_index_table_find + (new_notify_idx)) { + reused = true; + break; + } + new_notify_idx = (new_notify_idx + 1) % + max_notify_idx; + } while (new_notify_idx != + last_notify_idx_released); + } + if (!reused) { + new_notify_idx = max_notify_idx; + max_notify_idx++; + } + } + } else { + new_notify_idx = (last_notify_idx_reserved + 1) % PAGE_SIZE; + } + + last_notify_idx_reserved = new_notify_idx; + notify_idx_count++; + + entry->idx = new_notify_idx; + bucket = VMCI_DOORBELL_HASH(entry->idx); + hlist_add_head(&entry->node, &vmci_doorbell_it.entries[bucket]); + + spin_unlock_bh(&vmci_doorbell_it.lock); +} + +/* + * Remove the given entry from the index table. This will release() the + * entry's resource. + */ +static void dbell_index_table_remove(struct dbell_entry *entry) +{ + spin_lock_bh(&vmci_doorbell_it.lock); + + hlist_del_init(&entry->node); + + notify_idx_count--; + if (entry->idx == max_notify_idx - 1) { + /* + * If we delete an entry with the maximum known + * notification index, we take the opportunity to + * prune the current max. As there might be other + * unused indices immediately below, we lower the + * maximum until we hit an index in use. + */ + while (max_notify_idx > 0 && + !dbell_index_table_find(max_notify_idx - 1)) + max_notify_idx--; + } + + last_notify_idx_released = entry->idx; + + spin_unlock_bh(&vmci_doorbell_it.lock); + + vmci_resource_put(&entry->resource); +} + +/* + * Creates a link between the given doorbell handle and the given + * index in the bitmap in the device backend. A notification state + * is created in hypervisor. + */ +static int dbell_link(struct vmci_handle handle, u32 notify_idx) +{ + struct vmci_doorbell_link_msg link_msg; + + link_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_DOORBELL_LINK); + link_msg.hdr.src = VMCI_ANON_SRC_HANDLE; + link_msg.hdr.payload_size = sizeof(link_msg) - VMCI_DG_HEADERSIZE; + link_msg.handle = handle; + link_msg.notify_idx = notify_idx; + + return vmci_send_datagram(&link_msg.hdr); +} + +/* + * Unlinks the given doorbell handle from an index in the bitmap in + * the device backend. The notification state is destroyed in hypervisor. + */ +static int dbell_unlink(struct vmci_handle handle) +{ + struct vmci_doorbell_unlink_msg unlink_msg; + + unlink_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_DOORBELL_UNLINK); + unlink_msg.hdr.src = VMCI_ANON_SRC_HANDLE; + unlink_msg.hdr.payload_size = sizeof(unlink_msg) - VMCI_DG_HEADERSIZE; + unlink_msg.handle = handle; + + return vmci_send_datagram(&unlink_msg.hdr); +} + +/* + * Notify another guest or the host. We send a datagram down to the + * host via the hypervisor with the notification info. + */ +static int dbell_notify_as_guest(struct vmci_handle handle, u32 priv_flags) +{ + struct vmci_doorbell_notify_msg notify_msg; + + notify_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_DOORBELL_NOTIFY); + notify_msg.hdr.src = VMCI_ANON_SRC_HANDLE; + notify_msg.hdr.payload_size = sizeof(notify_msg) - VMCI_DG_HEADERSIZE; + notify_msg.handle = handle; + + return vmci_send_datagram(¬ify_msg.hdr); +} + +/* + * Calls the specified callback in a delayed context. + */ +static void dbell_delayed_dispatch(struct work_struct *work) +{ + struct dbell_entry *entry = container_of(work, + struct dbell_entry, work); + + entry->notify_cb(entry->client_data); + vmci_resource_put(&entry->resource); +} + +/* + * Dispatches a doorbell notification to the host context. + */ +int vmci_dbell_host_context_notify(u32 src_cid, struct vmci_handle handle) +{ + struct dbell_entry *entry; + struct vmci_resource *resource; + + if (vmci_handle_is_invalid(handle)) { + pr_devel("Notifying an invalid doorbell (handle=0x%x:0x%x)\n", + handle.context, handle.resource); + return VMCI_ERROR_INVALID_ARGS; + } + + resource = vmci_resource_by_handle(handle, + VMCI_RESOURCE_TYPE_DOORBELL); + if (!resource) { + pr_devel("Notifying an unknown doorbell (handle=0x%x:0x%x)\n", + handle.context, handle.resource); + return VMCI_ERROR_NOT_FOUND; + } + + entry = container_of(resource, struct dbell_entry, resource); + if (entry->run_delayed) { + schedule_work(&entry->work); + } else { + entry->notify_cb(entry->client_data); + vmci_resource_put(resource); + } + + return VMCI_SUCCESS; +} + +/* + * Register the notification bitmap with the host. + */ +bool vmci_dbell_register_notification_bitmap(u32 bitmap_ppn) +{ + int result; + struct vmci_notify_bm_set_msg bitmap_set_msg; + + bitmap_set_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_SET_NOTIFY_BITMAP); + bitmap_set_msg.hdr.src = VMCI_ANON_SRC_HANDLE; + bitmap_set_msg.hdr.payload_size = sizeof(bitmap_set_msg) - + VMCI_DG_HEADERSIZE; + bitmap_set_msg.bitmap_ppn = bitmap_ppn; + + result = vmci_send_datagram(&bitmap_set_msg.hdr); + if (result != VMCI_SUCCESS) { + pr_devel("Failed to register (PPN=%u) as notification bitmap (error=%d)\n", + bitmap_ppn, result); + return false; + } + return true; +} + +/* + * Executes or schedules the handlers for a given notify index. + */ +static void dbell_fire_entries(u32 notify_idx) +{ + u32 bucket = VMCI_DOORBELL_HASH(notify_idx); + struct dbell_entry *dbell; + struct hlist_node *node; + + spin_lock_bh(&vmci_doorbell_it.lock); + + hlist_for_each_entry(dbell, node, + &vmci_doorbell_it.entries[bucket], node) { + if (dbell->idx == notify_idx && + atomic_read(&dbell->active) == 1) { + if (dbell->run_delayed) { + vmci_resource_get(&dbell->resource); + schedule_work(&dbell->work); + } else { + dbell->notify_cb(dbell->client_data); + } + } + } + + spin_unlock_bh(&vmci_doorbell_it.lock); +} + +/* + * Scans the notification bitmap, collects pending notifications, + * resets the bitmap and invokes appropriate callbacks. + */ +void vmci_dbell_scan_notification_entries(u8 *bitmap) +{ + u32 idx; + + for (idx = 0; idx < max_notify_idx; idx++) { + if (bitmap[idx] & 0x1) { + bitmap[idx] &= ~1; + dbell_fire_entries(idx); + } + } +} + +/* + * vmci_doorbell_create() - Creates a doorbell + * @handle: A handle used to track the resource. Can be invalid. + * @flags: Flag that determines context of callback. + * @priv_flags: Privileges flags. + * @notify_cb: The callback to be ivoked when the doorbell fires. + * @client_data: A parameter to be passed to the callback. + * + * Creates a doorbell with the given callback. If the handle is + * VMCI_INVALID_HANDLE, a free handle will be assigned, if + * possible. The callback can be run immediately (potentially with + * locks held - the default) or delayed (in a kernel thread) by + * specifying the flag VMCI_FLAG_DELAYED_CB. If delayed execution + * is selected, a given callback may not be run if the kernel is + * unable to allocate memory for the delayed execution (highly + * unlikely). + */ +int vmci_doorbell_create(struct vmci_handle *handle, + u32 flags, + u32 priv_flags, + vmci_callback notify_cb, void *client_data) +{ + struct dbell_entry *entry; + struct vmci_handle new_handle; + int result; + + if (!handle || !notify_cb || flags & ~VMCI_FLAG_DELAYED_CB || + priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS) + return VMCI_ERROR_INVALID_ARGS; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (entry == NULL) { + pr_warn("Failed allocating memory for datagram entry\n"); + return VMCI_ERROR_NO_MEM; + } + + if (vmci_handle_is_invalid(*handle)) { + u32 context_id = vmci_get_context_id(); + + /* Let resource code allocate a free ID for us */ + new_handle = vmci_make_handle(context_id, VMCI_INVALID_ID); + } else { + bool valid_context = false; + + /* + * Validate the handle. We must do both of the checks below + * because we can be acting as both a host and a guest at the + * same time. We always allow the host context ID, since the + * host functionality is in practice always there with the + * unified driver. + */ + if (handle->context == VMCI_HOST_CONTEXT_ID || + (vmci_guest_code_active() && + vmci_get_context_id() == handle->context)) { + valid_context = true; + } + + if (!valid_context || handle->resource == VMCI_INVALID_ID) { + pr_devel("Invalid argument (handle=0x%x:0x%x)\n", + handle->context, handle->resource); + result = VMCI_ERROR_INVALID_ARGS; + goto free_mem; + } + + new_handle = *handle; + } + + entry->idx = 0; + INIT_HLIST_NODE(&entry->node); + entry->priv_flags = priv_flags; + INIT_WORK(&entry->work, dbell_delayed_dispatch); + entry->run_delayed = flags & VMCI_FLAG_DELAYED_CB; + entry->notify_cb = notify_cb; + entry->client_data = client_data; + atomic_set(&entry->active, 0); + + result = vmci_resource_add(&entry->resource, + VMCI_RESOURCE_TYPE_DOORBELL, + new_handle); + if (result != VMCI_SUCCESS) { + pr_warn("Failed to add new resource (handle=0x%x:0x%x), error: %d\n", + new_handle.context, new_handle.resource, result); + goto free_mem; + } + + new_handle = vmci_resource_handle(&entry->resource); + if (vmci_guest_code_active()) { + dbell_index_table_add(entry); + result = dbell_link(new_handle, entry->idx); + if (VMCI_SUCCESS != result) + goto destroy_resource; + + atomic_set(&entry->active, 1); + } + + *handle = new_handle; + + return result; + + destroy_resource: + dbell_index_table_remove(entry); + vmci_resource_remove(&entry->resource); + free_mem: + kfree(entry); + return result; +} +EXPORT_SYMBOL_GPL(vmci_doorbell_create); + +/* + * vmci_doorbell_destroy() - Destroy a doorbell. + * @handle: The handle tracking the resource. + * + * Destroys a doorbell previously created with vmcii_doorbell_create. This + * operation may block waiting for a callback to finish. + */ +int vmci_doorbell_destroy(struct vmci_handle handle) +{ + struct dbell_entry *entry; + struct vmci_resource *resource; + + if (vmci_handle_is_invalid(handle)) + return VMCI_ERROR_INVALID_ARGS; + + resource = vmci_resource_by_handle(handle, + VMCI_RESOURCE_TYPE_DOORBELL); + if (!resource) { + pr_devel("Failed to destroy doorbell (handle=0x%x:0x%x)\n", + handle.context, handle.resource); + return VMCI_ERROR_NOT_FOUND; + } + + entry = container_of(resource, struct dbell_entry, resource); + + if (vmci_guest_code_active()) { + int result; + + dbell_index_table_remove(entry); + + result = dbell_unlink(handle); + if (VMCI_SUCCESS != result) { + + /* + * The only reason this should fail would be + * an inconsistency between guest and + * hypervisor state, where the guest believes + * it has an active registration whereas the + * hypervisor doesn't. One case where this may + * happen is if a doorbell is unregistered + * following a hibernation at a time where the + * doorbell state hasn't been restored on the + * hypervisor side yet. Since the handle has + * now been removed in the guest, we just + * print a warning and return success. + */ + pr_devel("Unlink of doorbell (handle=0x%x:0x%x) unknown by hypervisor (error=%d)\n", + handle.context, handle.resource, result); + } + } + + /* + * Now remove the resource from the table. It might still be in use + * after this, in a callback or still on the delayed work queue. + */ + vmci_resource_put(&entry->resource); + vmci_resource_remove(&entry->resource); + + kfree(entry); + + return VMCI_SUCCESS; +} +EXPORT_SYMBOL_GPL(vmci_doorbell_destroy); + +/* + * vmci_doorbell_notify() - Ring the doorbell (and hide in the bushes). + * @dst: The handlle identifying the doorbell resource + * @priv_flags: Priviledge flags. + * + * Generates a notification on the doorbell identified by the + * handle. For host side generation of notifications, the caller + * can specify what the privilege of the calling side is. + */ +int vmci_doorbell_notify(struct vmci_handle dst, u32 priv_flags) +{ + int retval; + enum vmci_route route; + struct vmci_handle src; + + if (vmci_handle_is_invalid(dst) || + (priv_flags & ~VMCI_PRIVILEGE_ALL_FLAGS)) + return VMCI_ERROR_INVALID_ARGS; + + src = VMCI_INVALID_HANDLE; + retval = vmci_route(&src, &dst, false, &route); + if (retval < VMCI_SUCCESS) + return retval; + + if (VMCI_ROUTE_AS_HOST == route) + return vmci_ctx_notify_dbell(VMCI_HOST_CONTEXT_ID, + dst, priv_flags); + + if (VMCI_ROUTE_AS_GUEST == route) + return dbell_notify_as_guest(dst, priv_flags); + + pr_warn("Unknown route (%d) for doorbell\n", route); + return VMCI_ERROR_DST_UNREACHABLE; +} +EXPORT_SYMBOL_GPL(vmci_doorbell_notify); diff --git a/drivers/misc/vmw_vmci/vmci_doorbell.h b/drivers/misc/vmw_vmci/vmci_doorbell.h new file mode 100644 index 000000000000..e4c0b17486a5 --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_doorbell.h @@ -0,0 +1,51 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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. + */ + +#ifndef VMCI_DOORBELL_H +#define VMCI_DOORBELL_H + +#include +#include + +#include "vmci_driver.h" + +/* + * VMCINotifyResourceInfo: Used to create and destroy doorbells, and + * generate a notification for a doorbell or queue pair. + */ +struct vmci_dbell_notify_resource_info { + struct vmci_handle handle; + u16 resource; + u16 action; + s32 result; +}; + +/* + * Structure used for checkpointing the doorbell mappings. It is + * written to the checkpoint as is, so changing this structure will + * break checkpoint compatibility. + */ +struct dbell_cpt_state { + struct vmci_handle handle; + u64 bitmap_idx; +}; + +int vmci_dbell_host_context_notify(u32 src_cid, struct vmci_handle handle); +int vmci_dbell_get_priv_flags(struct vmci_handle handle, u32 *priv_flags); + +bool vmci_dbell_register_notification_bitmap(u32 bitmap_ppn); +void vmci_dbell_scan_notification_entries(u8 *bitmap); + +#endif /* VMCI_DOORBELL_H */ -- cgit v1.2.1 From 197dbaaabd51c170f9b77bd1c401d4ea0361bb7b Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:54:10 -0800 Subject: VMCI: device driver implementaton. VMCI driver code implementes both the host and guest personalities of the VMCI driver. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_driver.c | 117 ++++++++++++++++++++++++++++++++++++ drivers/misc/vmw_vmci/vmci_driver.h | 50 +++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 drivers/misc/vmw_vmci/vmci_driver.c create mode 100644 drivers/misc/vmw_vmci/vmci_driver.h diff --git a/drivers/misc/vmw_vmci/vmci_driver.c b/drivers/misc/vmw_vmci/vmci_driver.c new file mode 100644 index 000000000000..7b3fce2da6c3 --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_driver.c @@ -0,0 +1,117 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include + +#include "vmci_driver.h" +#include "vmci_event.h" + +static bool vmci_disable_host; +module_param_named(disable_host, vmci_disable_host, bool, 0); +MODULE_PARM_DESC(disable_host, + "Disable driver host personality (default=enabled)"); + +static bool vmci_disable_guest; +module_param_named(disable_guest, vmci_disable_guest, bool, 0); +MODULE_PARM_DESC(disable_guest, + "Disable driver guest personality (default=enabled)"); + +static bool vmci_guest_personality_initialized; +static bool vmci_host_personality_initialized; + +/* + * vmci_get_context_id() - Gets the current context ID. + * + * Returns the current context ID. Note that since this is accessed only + * from code running in the host, this always returns the host context ID. + */ +u32 vmci_get_context_id(void) +{ + if (vmci_guest_code_active()) + return vmci_get_vm_context_id(); + else if (vmci_host_code_active()) + return VMCI_HOST_CONTEXT_ID; + + return VMCI_INVALID_ID; +} +EXPORT_SYMBOL_GPL(vmci_get_context_id); + +static int __init vmci_drv_init(void) +{ + int vmci_err; + int error; + + vmci_err = vmci_event_init(); + if (vmci_err < VMCI_SUCCESS) { + pr_err("Failed to initialize VMCIEvent (result=%d)\n", + vmci_err); + return -EINVAL; + } + + if (!vmci_disable_guest) { + error = vmci_guest_init(); + if (error) { + pr_warn("Failed to initialize guest personality (err=%d)\n", + error); + } else { + vmci_guest_personality_initialized = true; + pr_info("Guest personality initialized and is %s\n", + vmci_guest_code_active() ? + "active" : "inactive"); + } + } + + if (!vmci_disable_host) { + error = vmci_host_init(); + if (error) { + pr_warn("Unable to initialize host personality (err=%d)\n", + error); + } else { + vmci_host_personality_initialized = true; + pr_info("Initialized host personality\n"); + } + } + + if (!vmci_guest_personality_initialized && + !vmci_host_personality_initialized) { + vmci_event_exit(); + return -ENODEV; + } + + return 0; +} +module_init(vmci_drv_init); + +static void __exit vmci_drv_exit(void) +{ + if (vmci_guest_personality_initialized) + vmci_guest_exit(); + + if (vmci_host_personality_initialized) + vmci_host_exit(); + + vmci_event_exit(); +} +module_exit(vmci_drv_exit); + +MODULE_AUTHOR("VMware, Inc."); +MODULE_DESCRIPTION("VMware Virtual Machine Communication Interface."); +MODULE_VERSION("1.0.0.0-k"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/vmw_vmci/vmci_driver.h b/drivers/misc/vmw_vmci/vmci_driver.h new file mode 100644 index 000000000000..f69156a1f30c --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_driver.h @@ -0,0 +1,50 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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. + */ + +#ifndef _VMCI_DRIVER_H_ +#define _VMCI_DRIVER_H_ + +#include +#include + +#include "vmci_queue_pair.h" +#include "vmci_context.h" + +enum vmci_obj_type { + VMCIOBJ_VMX_VM = 10, + VMCIOBJ_CONTEXT, + VMCIOBJ_SOCKET, + VMCIOBJ_NOT_SET, +}; + +/* For storing VMCI structures in file handles. */ +struct vmci_obj { + void *ptr; + enum vmci_obj_type type; +}; + +u32 vmci_get_context_id(void); +int vmci_send_datagram(struct vmci_datagram *dg); + +int vmci_host_init(void); +void vmci_host_exit(void); +bool vmci_host_code_active(void); + +int vmci_guest_init(void); +void vmci_guest_exit(void); +bool vmci_guest_code_active(void); +u32 vmci_get_vm_context_id(void); + +#endif /* _VMCI_DRIVER_H_ */ -- cgit v1.2.1 From 1d990201f9bb499b7c76ab00abeb7e803c0bcb2a Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:54:23 -0800 Subject: VMCI: event handling implementation. VMCI event code that manages event handlers and handles callbacks when specific events fire. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_event.c | 224 +++++++++++++++++++++++++++++++++++++ drivers/misc/vmw_vmci/vmci_event.h | 25 +++++ 2 files changed, 249 insertions(+) create mode 100644 drivers/misc/vmw_vmci/vmci_event.c create mode 100644 drivers/misc/vmw_vmci/vmci_event.h diff --git a/drivers/misc/vmw_vmci/vmci_event.c b/drivers/misc/vmw_vmci/vmci_event.c new file mode 100644 index 000000000000..8449516d6ac6 --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_event.c @@ -0,0 +1,224 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include + +#include "vmci_driver.h" +#include "vmci_event.h" + +#define EVENT_MAGIC 0xEABE0000 +#define VMCI_EVENT_MAX_ATTEMPTS 10 + +struct vmci_subscription { + u32 id; + u32 event; + vmci_event_cb callback; + void *callback_data; + struct list_head node; /* on one of subscriber lists */ +}; + +static struct list_head subscriber_array[VMCI_EVENT_MAX]; +static DEFINE_MUTEX(subscriber_mutex); + +int __init vmci_event_init(void) +{ + int i; + + for (i = 0; i < VMCI_EVENT_MAX; i++) + INIT_LIST_HEAD(&subscriber_array[i]); + + return VMCI_SUCCESS; +} + +void vmci_event_exit(void) +{ + int e; + + /* We free all memory at exit. */ + for (e = 0; e < VMCI_EVENT_MAX; e++) { + struct vmci_subscription *cur, *p2; + list_for_each_entry_safe(cur, p2, &subscriber_array[e], node) { + + /* + * We should never get here because all events + * should have been unregistered before we try + * to unload the driver module. + */ + pr_warn("Unexpected free events occurring\n"); + list_del(&cur->node); + kfree(cur); + } + } +} + +/* + * Find entry. Assumes subscriber_mutex is held. + */ +static struct vmci_subscription *event_find(u32 sub_id) +{ + int e; + + for (e = 0; e < VMCI_EVENT_MAX; e++) { + struct vmci_subscription *cur; + list_for_each_entry(cur, &subscriber_array[e], node) { + if (cur->id == sub_id) + return cur; + } + } + return NULL; +} + +/* + * Actually delivers the events to the subscribers. + * The callback function for each subscriber is invoked. + */ +static void event_deliver(struct vmci_event_msg *event_msg) +{ + struct vmci_subscription *cur; + struct list_head *subscriber_list; + + rcu_read_lock(); + subscriber_list = &subscriber_array[event_msg->event_data.event]; + list_for_each_entry_rcu(cur, subscriber_list, node) { + cur->callback(cur->id, &event_msg->event_data, + cur->callback_data); + } + rcu_read_unlock(); +} + +/* + * Dispatcher for the VMCI_EVENT_RECEIVE datagrams. Calls all + * subscribers for given event. + */ +int vmci_event_dispatch(struct vmci_datagram *msg) +{ + struct vmci_event_msg *event_msg = (struct vmci_event_msg *)msg; + + if (msg->payload_size < sizeof(u32) || + msg->payload_size > sizeof(struct vmci_event_data_max)) + return VMCI_ERROR_INVALID_ARGS; + + if (!VMCI_EVENT_VALID(event_msg->event_data.event)) + return VMCI_ERROR_EVENT_UNKNOWN; + + event_deliver(event_msg); + return VMCI_SUCCESS; +} + +/* + * vmci_event_subscribe() - Subscribe to a given event. + * @event: The event to subscribe to. + * @callback: The callback to invoke upon the event. + * @callback_data: Data to pass to the callback. + * @subscription_id: ID used to track subscription. Used with + * vmci_event_unsubscribe() + * + * Subscribes to the provided event. The callback specified will be + * fired from RCU critical section and therefore must not sleep. + */ +int vmci_event_subscribe(u32 event, + vmci_event_cb callback, + void *callback_data, + u32 *new_subscription_id) +{ + struct vmci_subscription *sub; + int attempts; + int retval; + bool have_new_id = false; + + if (!new_subscription_id) { + pr_devel("%s: Invalid subscription (NULL)\n", __func__); + return VMCI_ERROR_INVALID_ARGS; + } + + if (!VMCI_EVENT_VALID(event) || !callback) { + pr_devel("%s: Failed to subscribe to event (type=%d) (callback=%p) (data=%p)\n", + __func__, event, callback, callback_data); + return VMCI_ERROR_INVALID_ARGS; + } + + sub = kzalloc(sizeof(*sub), GFP_KERNEL); + if (!sub) + return VMCI_ERROR_NO_MEM; + + sub->id = VMCI_EVENT_MAX; + sub->event = event; + sub->callback = callback; + sub->callback_data = callback_data; + INIT_LIST_HEAD(&sub->node); + + mutex_lock(&subscriber_mutex); + + /* Creation of a new event is always allowed. */ + for (attempts = 0; attempts < VMCI_EVENT_MAX_ATTEMPTS; attempts++) { + static u32 subscription_id; + /* + * We try to get an id a couple of time before + * claiming we are out of resources. + */ + + /* Test for duplicate id. */ + if (!event_find(++subscription_id)) { + sub->id = subscription_id; + have_new_id = true; + break; + } + } + + if (have_new_id) { + list_add_rcu(&sub->node, &subscriber_array[event]); + retval = VMCI_SUCCESS; + } else { + retval = VMCI_ERROR_NO_RESOURCES; + } + + mutex_unlock(&subscriber_mutex); + + *new_subscription_id = sub->id; + return retval; +} +EXPORT_SYMBOL_GPL(vmci_event_subscribe); + +/* + * vmci_event_unsubscribe() - unsubscribe from an event. + * @sub_id: A subscription ID as provided by vmci_event_subscribe() + * + * Unsubscribe from given event. Removes it from list and frees it. + * Will return callback_data if requested by caller. + */ +int vmci_event_unsubscribe(u32 sub_id) +{ + struct vmci_subscription *s; + + mutex_lock(&subscriber_mutex); + s = event_find(sub_id); + if (s) + list_del_rcu(&s->node); + mutex_unlock(&subscriber_mutex); + + if (!s) + return VMCI_ERROR_NOT_FOUND; + + synchronize_rcu(); + kfree(s); + + return VMCI_SUCCESS; +} +EXPORT_SYMBOL_GPL(vmci_event_unsubscribe); diff --git a/drivers/misc/vmw_vmci/vmci_event.h b/drivers/misc/vmw_vmci/vmci_event.h new file mode 100644 index 000000000000..7df9b1c0a96c --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_event.h @@ -0,0 +1,25 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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. + */ + +#ifndef __VMCI_EVENT_H__ +#define __VMCI_EVENT_H__ + +#include + +int vmci_event_init(void); +void vmci_event_exit(void); +int vmci_event_dispatch(struct vmci_datagram *msg); + +#endif /*__VMCI_EVENT_H__ */ -- cgit v1.2.1 From b484b26cc7be6ccf3676deb5e03aed2609ee9a40 Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:54:39 -0800 Subject: VMCI: handle array implementation. VMCI handle code adds support for dynamic arrays that will grow if they need to. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_handle_array.c | 142 ++++++++++++++++++++++++++++++ drivers/misc/vmw_vmci/vmci_handle_array.h | 52 +++++++++++ 2 files changed, 194 insertions(+) create mode 100644 drivers/misc/vmw_vmci/vmci_handle_array.c create mode 100644 drivers/misc/vmw_vmci/vmci_handle_array.h diff --git a/drivers/misc/vmw_vmci/vmci_handle_array.c b/drivers/misc/vmw_vmci/vmci_handle_array.c new file mode 100644 index 000000000000..344973a0fb0a --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_handle_array.c @@ -0,0 +1,142 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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 +#include "vmci_handle_array.h" + +static size_t handle_arr_calc_size(size_t capacity) +{ + return sizeof(struct vmci_handle_arr) + + capacity * sizeof(struct vmci_handle); +} + +struct vmci_handle_arr *vmci_handle_arr_create(size_t capacity) +{ + struct vmci_handle_arr *array; + + if (capacity == 0) + capacity = VMCI_HANDLE_ARRAY_DEFAULT_SIZE; + + array = kmalloc(handle_arr_calc_size(capacity), GFP_ATOMIC); + if (!array) + return NULL; + + array->capacity = capacity; + array->size = 0; + + return array; +} + +void vmci_handle_arr_destroy(struct vmci_handle_arr *array) +{ + kfree(array); +} + +void vmci_handle_arr_append_entry(struct vmci_handle_arr **array_ptr, + struct vmci_handle handle) +{ + struct vmci_handle_arr *array = *array_ptr; + + if (unlikely(array->size >= array->capacity)) { + /* reallocate. */ + struct vmci_handle_arr *new_array; + size_t new_capacity = array->capacity * VMCI_ARR_CAP_MULT; + size_t new_size = handle_arr_calc_size(new_capacity); + + new_array = krealloc(array, new_size, GFP_ATOMIC); + if (!new_array) + return; + + new_array->capacity = new_capacity; + *array_ptr = array = new_array; + } + + array->entries[array->size] = handle; + array->size++; +} + +/* + * Handle that was removed, VMCI_INVALID_HANDLE if entry not found. + */ +struct vmci_handle vmci_handle_arr_remove_entry(struct vmci_handle_arr *array, + struct vmci_handle entry_handle) +{ + struct vmci_handle handle = VMCI_INVALID_HANDLE; + size_t i; + + for (i = 0; i < array->size; i++) { + if (vmci_handle_is_equal(array->entries[i], entry_handle)) { + handle = array->entries[i]; + array->size--; + array->entries[i] = array->entries[array->size]; + array->entries[array->size] = VMCI_INVALID_HANDLE; + break; + } + } + + return handle; +} + +/* + * Handle that was removed, VMCI_INVALID_HANDLE if array was empty. + */ +struct vmci_handle vmci_handle_arr_remove_tail(struct vmci_handle_arr *array) +{ + struct vmci_handle handle = VMCI_INVALID_HANDLE; + + if (array->size) { + array->size--; + handle = array->entries[array->size]; + array->entries[array->size] = VMCI_INVALID_HANDLE; + } + + return handle; +} + +/* + * Handle at given index, VMCI_INVALID_HANDLE if invalid index. + */ +struct vmci_handle +vmci_handle_arr_get_entry(const struct vmci_handle_arr *array, size_t index) +{ + if (unlikely(index >= array->size)) + return VMCI_INVALID_HANDLE; + + return array->entries[index]; +} + +bool vmci_handle_arr_has_entry(const struct vmci_handle_arr *array, + struct vmci_handle entry_handle) +{ + size_t i; + + for (i = 0; i < array->size; i++) + if (vmci_handle_is_equal(array->entries[i], entry_handle)) + return true; + + return false; +} + +/* + * NULL if the array is empty. Otherwise, a pointer to the array + * of VMCI handles in the handle array. + */ +struct vmci_handle *vmci_handle_arr_get_handles(struct vmci_handle_arr *array) +{ + if (array->size) + return array->entries; + + return NULL; +} diff --git a/drivers/misc/vmw_vmci/vmci_handle_array.h b/drivers/misc/vmw_vmci/vmci_handle_array.h new file mode 100644 index 000000000000..b5f3a7f98cf1 --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_handle_array.h @@ -0,0 +1,52 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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. + */ + +#ifndef _VMCI_HANDLE_ARRAY_H_ +#define _VMCI_HANDLE_ARRAY_H_ + +#include +#include + +#define VMCI_HANDLE_ARRAY_DEFAULT_SIZE 4 +#define VMCI_ARR_CAP_MULT 2 /* Array capacity multiplier */ + +struct vmci_handle_arr { + size_t capacity; + size_t size; + struct vmci_handle entries[]; +}; + +struct vmci_handle_arr *vmci_handle_arr_create(size_t capacity); +void vmci_handle_arr_destroy(struct vmci_handle_arr *array); +void vmci_handle_arr_append_entry(struct vmci_handle_arr **array_ptr, + struct vmci_handle handle); +struct vmci_handle vmci_handle_arr_remove_entry(struct vmci_handle_arr *array, + struct vmci_handle + entry_handle); +struct vmci_handle vmci_handle_arr_remove_tail(struct vmci_handle_arr *array); +struct vmci_handle +vmci_handle_arr_get_entry(const struct vmci_handle_arr *array, size_t index); +bool vmci_handle_arr_has_entry(const struct vmci_handle_arr *array, + struct vmci_handle entry_handle); +struct vmci_handle *vmci_handle_arr_get_handles(struct vmci_handle_arr *array); + +static inline size_t vmci_handle_arr_get_size( + const struct vmci_handle_arr *array) +{ + return array->size; +} + + +#endif /* _VMCI_HANDLE_ARRAY_H_ */ -- cgit v1.2.1 From 06164d2b72aa752ce4633184b3e0d97601017135 Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:54:54 -0800 Subject: VMCI: queue pairs implementation. VMCI queue pairs allow for bi-directional ordered communication between host and guests. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_queue_pair.c | 3420 +++++++++++++++++++++++++++++++ drivers/misc/vmw_vmci/vmci_queue_pair.h | 191 ++ 2 files changed, 3611 insertions(+) create mode 100644 drivers/misc/vmw_vmci/vmci_queue_pair.c create mode 100644 drivers/misc/vmw_vmci/vmci_queue_pair.h diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c new file mode 100644 index 000000000000..1123111ba1bf --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c @@ -0,0 +1,3420 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include + +#include "vmci_handle_array.h" +#include "vmci_queue_pair.h" +#include "vmci_datagram.h" +#include "vmci_resource.h" +#include "vmci_context.h" +#include "vmci_driver.h" +#include "vmci_event.h" +#include "vmci_route.h" + +/* + * In the following, we will distinguish between two kinds of VMX processes - + * the ones with versions lower than VMCI_VERSION_NOVMVM that use specialized + * VMCI page files in the VMX and supporting VM to VM communication and the + * newer ones that use the guest memory directly. We will in the following + * refer to the older VMX versions as old-style VMX'en, and the newer ones as + * new-style VMX'en. + * + * The state transition datagram is as follows (the VMCIQPB_ prefix has been + * removed for readability) - see below for more details on the transtions: + * + * -------------- NEW ------------- + * | | + * \_/ \_/ + * CREATED_NO_MEM <-----------------> CREATED_MEM + * | | | + * | o-----------------------o | + * | | | + * \_/ \_/ \_/ + * ATTACHED_NO_MEM <----------------> ATTACHED_MEM + * | | | + * | o----------------------o | + * | | | + * \_/ \_/ \_/ + * SHUTDOWN_NO_MEM <----------------> SHUTDOWN_MEM + * | | + * | | + * -------------> gone <------------- + * + * In more detail. When a VMCI queue pair is first created, it will be in the + * VMCIQPB_NEW state. It will then move into one of the following states: + * + * - VMCIQPB_CREATED_NO_MEM: this state indicates that either: + * + * - the created was performed by a host endpoint, in which case there is + * no backing memory yet. + * + * - the create was initiated by an old-style VMX, that uses + * vmci_qp_broker_set_page_store to specify the UVAs of the queue pair at + * a later point in time. This state can be distinguished from the one + * above by the context ID of the creator. A host side is not allowed to + * attach until the page store has been set. + * + * - VMCIQPB_CREATED_MEM: this state is the result when the queue pair + * is created by a VMX using the queue pair device backend that + * sets the UVAs of the queue pair immediately and stores the + * information for later attachers. At this point, it is ready for + * the host side to attach to it. + * + * Once the queue pair is in one of the created states (with the exception of + * the case mentioned for older VMX'en above), it is possible to attach to the + * queue pair. Again we have two new states possible: + * + * - VMCIQPB_ATTACHED_MEM: this state can be reached through the following + * paths: + * + * - from VMCIQPB_CREATED_NO_MEM when a new-style VMX allocates a queue + * pair, and attaches to a queue pair previously created by the host side. + * + * - from VMCIQPB_CREATED_MEM when the host side attaches to a queue pair + * already created by a guest. + * + * - from VMCIQPB_ATTACHED_NO_MEM, when an old-style VMX calls + * vmci_qp_broker_set_page_store (see below). + * + * - VMCIQPB_ATTACHED_NO_MEM: If the queue pair already was in the + * VMCIQPB_CREATED_NO_MEM due to a host side create, an old-style VMX will + * bring the queue pair into this state. Once vmci_qp_broker_set_page_store + * is called to register the user memory, the VMCIQPB_ATTACH_MEM state + * will be entered. + * + * From the attached queue pair, the queue pair can enter the shutdown states + * when either side of the queue pair detaches. If the guest side detaches + * first, the queue pair will enter the VMCIQPB_SHUTDOWN_NO_MEM state, where + * the content of the queue pair will no longer be available. If the host + * side detaches first, the queue pair will either enter the + * VMCIQPB_SHUTDOWN_MEM, if the guest memory is currently mapped, or + * VMCIQPB_SHUTDOWN_NO_MEM, if the guest memory is not mapped + * (e.g., the host detaches while a guest is stunned). + * + * New-style VMX'en will also unmap guest memory, if the guest is + * quiesced, e.g., during a snapshot operation. In that case, the guest + * memory will no longer be available, and the queue pair will transition from + * *_MEM state to a *_NO_MEM state. The VMX may later map the memory once more, + * in which case the queue pair will transition from the *_NO_MEM state at that + * point back to the *_MEM state. Note that the *_NO_MEM state may have changed, + * since the peer may have either attached or detached in the meantime. The + * values are laid out such that ++ on a state will move from a *_NO_MEM to a + * *_MEM state, and vice versa. + */ + +/* + * VMCIMemcpy{To,From}QueueFunc() prototypes. Functions of these + * types are passed around to enqueue and dequeue routines. Note that + * often the functions passed are simply wrappers around memcpy + * itself. + * + * Note: In order for the memcpy typedefs to be compatible with the VMKernel, + * there's an unused last parameter for the hosted side. In + * ESX, that parameter holds a buffer type. + */ +typedef int vmci_memcpy_to_queue_func(struct vmci_queue *queue, + u64 queue_offset, const void *src, + size_t src_offset, size_t size); +typedef int vmci_memcpy_from_queue_func(void *dest, size_t dest_offset, + const struct vmci_queue *queue, + u64 queue_offset, size_t size); + +/* The Kernel specific component of the struct vmci_queue structure. */ +struct vmci_queue_kern_if { + struct page **page; + struct page **header_page; + void *va; + struct mutex __mutex; /* Protects the queue. */ + struct mutex *mutex; /* Shared by producer and consumer queues. */ + bool host; + size_t num_pages; + bool mapped; +}; + +/* + * This structure is opaque to the clients. + */ +struct vmci_qp { + struct vmci_handle handle; + struct vmci_queue *produce_q; + struct vmci_queue *consume_q; + u64 produce_q_size; + u64 consume_q_size; + u32 peer; + u32 flags; + u32 priv_flags; + bool guest_endpoint; + unsigned int blocked; + unsigned int generation; + wait_queue_head_t event; +}; + +enum qp_broker_state { + VMCIQPB_NEW, + VMCIQPB_CREATED_NO_MEM, + VMCIQPB_CREATED_MEM, + VMCIQPB_ATTACHED_NO_MEM, + VMCIQPB_ATTACHED_MEM, + VMCIQPB_SHUTDOWN_NO_MEM, + VMCIQPB_SHUTDOWN_MEM, + VMCIQPB_GONE +}; + +#define QPBROKERSTATE_HAS_MEM(_qpb) (_qpb->state == VMCIQPB_CREATED_MEM || \ + _qpb->state == VMCIQPB_ATTACHED_MEM || \ + _qpb->state == VMCIQPB_SHUTDOWN_MEM) + +/* + * In the queue pair broker, we always use the guest point of view for + * the produce and consume queue values and references, e.g., the + * produce queue size stored is the guests produce queue size. The + * host endpoint will need to swap these around. The only exception is + * the local queue pairs on the host, in which case the host endpoint + * that creates the queue pair will have the right orientation, and + * the attaching host endpoint will need to swap. + */ +struct qp_entry { + struct list_head list_item; + struct vmci_handle handle; + u32 peer; + u32 flags; + u64 produce_size; + u64 consume_size; + u32 ref_count; +}; + +struct qp_broker_entry { + struct vmci_resource resource; + struct qp_entry qp; + u32 create_id; + u32 attach_id; + enum qp_broker_state state; + bool require_trusted_attach; + bool created_by_trusted; + bool vmci_page_files; /* Created by VMX using VMCI page files */ + struct vmci_queue *produce_q; + struct vmci_queue *consume_q; + struct vmci_queue_header saved_produce_q; + struct vmci_queue_header saved_consume_q; + vmci_event_release_cb wakeup_cb; + void *client_data; + void *local_mem; /* Kernel memory for local queue pair */ +}; + +struct qp_guest_endpoint { + struct vmci_resource resource; + struct qp_entry qp; + u64 num_ppns; + void *produce_q; + void *consume_q; + struct PPNSet ppn_set; +}; + +struct qp_list { + struct list_head head; + struct mutex mutex; /* Protect queue list. */ +}; + +static struct qp_list qp_broker_list = { + .head = LIST_HEAD_INIT(qp_broker_list.head), + .mutex = __MUTEX_INITIALIZER(qp_broker_list.mutex), +}; + +static struct qp_list qp_guest_endpoints = { + .head = LIST_HEAD_INIT(qp_guest_endpoints.head), + .mutex = __MUTEX_INITIALIZER(qp_guest_endpoints.mutex), +}; + +#define INVALID_VMCI_GUEST_MEM_ID 0 +#define QPE_NUM_PAGES(_QPE) ((u32) \ + (dm_div_up(_QPE.produce_size, PAGE_SIZE) + \ + dm_div_up(_QPE.consume_size, PAGE_SIZE) + 2)) + + +/* + * Frees kernel VA space for a given queue and its queue header, and + * frees physical data pages. + */ +static void qp_free_queue(void *q, u64 size) +{ + struct vmci_queue *queue = q; + + if (queue) { + u64 i = dm_div_up(size, PAGE_SIZE); + + if (queue->kernel_if->mapped) { + vunmap(queue->kernel_if->va); + queue->kernel_if->va = NULL; + } + + while (i) + __free_page(queue->kernel_if->page[--i]); + + vfree(queue->q_header); + } +} + +/* + * Allocates kernel VA space of specified size, plus space for the + * queue structure/kernel interface and the queue header. Allocates + * physical pages for the queue data pages. + * + * PAGE m: struct vmci_queue_header (struct vmci_queue->q_header) + * PAGE m+1: struct vmci_queue + * PAGE m+1+q: struct vmci_queue_kern_if (struct vmci_queue->kernel_if) + * PAGE n-size: Data pages (struct vmci_queue->kernel_if->page[]) + */ +static void *qp_alloc_queue(u64 size, u32 flags) +{ + u64 i; + struct vmci_queue *queue; + struct vmci_queue_header *q_header; + const u64 num_data_pages = dm_div_up(size, PAGE_SIZE); + const uint queue_size = + PAGE_SIZE + + sizeof(*queue) + sizeof(*(queue->kernel_if)) + + num_data_pages * sizeof(*(queue->kernel_if->page)); + + q_header = vmalloc(queue_size); + if (!q_header) + return NULL; + + queue = (void *)q_header + PAGE_SIZE; + queue->q_header = q_header; + queue->saved_header = NULL; + queue->kernel_if = (struct vmci_queue_kern_if *)(queue + 1); + queue->kernel_if->header_page = NULL; /* Unused in guest. */ + queue->kernel_if->page = (struct page **)(queue->kernel_if + 1); + queue->kernel_if->host = false; + queue->kernel_if->va = NULL; + queue->kernel_if->mapped = false; + + for (i = 0; i < num_data_pages; i++) { + queue->kernel_if->page[i] = alloc_pages(GFP_KERNEL, 0); + if (!queue->kernel_if->page[i]) + goto fail; + } + + if (vmci_qp_pinned(flags)) { + queue->kernel_if->va = + vmap(queue->kernel_if->page, num_data_pages, VM_MAP, + PAGE_KERNEL); + if (!queue->kernel_if->va) + goto fail; + + queue->kernel_if->mapped = true; + } + + return (void *)queue; + + fail: + qp_free_queue(queue, i * PAGE_SIZE); + return NULL; +} + +/* + * Copies from a given buffer or iovector to a VMCI Queue. Uses + * kmap()/kunmap() to dynamically map/unmap required portions of the queue + * by traversing the offset -> page translation structure for the queue. + * Assumes that offset + size does not wrap around in the queue. + */ +static int __qp_memcpy_to_queue(struct vmci_queue *queue, + u64 queue_offset, + const void *src, + size_t size, + bool is_iovec) +{ + struct vmci_queue_kern_if *kernel_if = queue->kernel_if; + size_t bytes_copied = 0; + + while (bytes_copied < size) { + u64 page_index = (queue_offset + bytes_copied) / PAGE_SIZE; + size_t page_offset = + (queue_offset + bytes_copied) & (PAGE_SIZE - 1); + void *va; + size_t to_copy; + + if (!kernel_if->mapped) + va = kmap(kernel_if->page[page_index]); + else + va = (void *)((u8 *)kernel_if->va + + (page_index * PAGE_SIZE)); + + if (size - bytes_copied > PAGE_SIZE - page_offset) + /* Enough payload to fill up from this page. */ + to_copy = PAGE_SIZE - page_offset; + else + to_copy = size - bytes_copied; + + if (is_iovec) { + struct iovec *iov = (struct iovec *)src; + int err; + + /* The iovec will track bytes_copied internally. */ + err = memcpy_fromiovec((u8 *)va + page_offset, + iov, to_copy); + if (err != 0) { + kunmap(kernel_if->page[page_index]); + return VMCI_ERROR_INVALID_ARGS; + } + } else { + memcpy((u8 *)va + page_offset, + (u8 *)src + bytes_copied, to_copy); + } + + bytes_copied += to_copy; + if (!kernel_if->mapped) + kunmap(kernel_if->page[page_index]); + } + + return VMCI_SUCCESS; +} + +/* + * Copies to a given buffer or iovector from a VMCI Queue. Uses + * kmap()/kunmap() to dynamically map/unmap required portions of the queue + * by traversing the offset -> page translation structure for the queue. + * Assumes that offset + size does not wrap around in the queue. + */ +static int __qp_memcpy_from_queue(void *dest, + const struct vmci_queue *queue, + u64 queue_offset, + size_t size, + bool is_iovec) +{ + struct vmci_queue_kern_if *kernel_if = queue->kernel_if; + size_t bytes_copied = 0; + + while (bytes_copied < size) { + u64 page_index = (queue_offset + bytes_copied) / PAGE_SIZE; + size_t page_offset = + (queue_offset + bytes_copied) & (PAGE_SIZE - 1); + void *va; + size_t to_copy; + + if (!kernel_if->mapped) + va = kmap(kernel_if->page[page_index]); + else + va = (void *)((u8 *)kernel_if->va + + (page_index * PAGE_SIZE)); + + if (size - bytes_copied > PAGE_SIZE - page_offset) + /* Enough payload to fill up this page. */ + to_copy = PAGE_SIZE - page_offset; + else + to_copy = size - bytes_copied; + + if (is_iovec) { + struct iovec *iov = (struct iovec *)dest; + int err; + + /* The iovec will track bytes_copied internally. */ + err = memcpy_toiovec(iov, (u8 *)va + page_offset, + to_copy); + if (err != 0) { + kunmap(kernel_if->page[page_index]); + return VMCI_ERROR_INVALID_ARGS; + } + } else { + memcpy((u8 *)dest + bytes_copied, + (u8 *)va + page_offset, to_copy); + } + + bytes_copied += to_copy; + if (!kernel_if->mapped) + kunmap(kernel_if->page[page_index]); + } + + return VMCI_SUCCESS; +} + +/* + * Allocates two list of PPNs --- one for the pages in the produce queue, + * and the other for the pages in the consume queue. Intializes the list + * of PPNs with the page frame numbers of the KVA for the two queues (and + * the queue headers). + */ +static int qp_alloc_ppn_set(void *prod_q, + u64 num_produce_pages, + void *cons_q, + u64 num_consume_pages, struct PPNSet *ppn_set) +{ + u32 *produce_ppns; + u32 *consume_ppns; + struct vmci_queue *produce_q = prod_q; + struct vmci_queue *consume_q = cons_q; + u64 i; + + if (!produce_q || !num_produce_pages || !consume_q || + !num_consume_pages || !ppn_set) + return VMCI_ERROR_INVALID_ARGS; + + if (ppn_set->initialized) + return VMCI_ERROR_ALREADY_EXISTS; + + produce_ppns = + kmalloc(num_produce_pages * sizeof(*produce_ppns), GFP_KERNEL); + if (!produce_ppns) + return VMCI_ERROR_NO_MEM; + + consume_ppns = + kmalloc(num_consume_pages * sizeof(*consume_ppns), GFP_KERNEL); + if (!consume_ppns) { + kfree(produce_ppns); + return VMCI_ERROR_NO_MEM; + } + + produce_ppns[0] = page_to_pfn(vmalloc_to_page(produce_q->q_header)); + for (i = 1; i < num_produce_pages; i++) { + unsigned long pfn; + + produce_ppns[i] = + page_to_pfn(produce_q->kernel_if->page[i - 1]); + pfn = produce_ppns[i]; + + /* Fail allocation if PFN isn't supported by hypervisor. */ + if (sizeof(pfn) > sizeof(*produce_ppns) + && pfn != produce_ppns[i]) + goto ppn_error; + } + + consume_ppns[0] = page_to_pfn(vmalloc_to_page(consume_q->q_header)); + for (i = 1; i < num_consume_pages; i++) { + unsigned long pfn; + + consume_ppns[i] = + page_to_pfn(consume_q->kernel_if->page[i - 1]); + pfn = consume_ppns[i]; + + /* Fail allocation if PFN isn't supported by hypervisor. */ + if (sizeof(pfn) > sizeof(*consume_ppns) + && pfn != consume_ppns[i]) + goto ppn_error; + } + + ppn_set->num_produce_pages = num_produce_pages; + ppn_set->num_consume_pages = num_consume_pages; + ppn_set->produce_ppns = produce_ppns; + ppn_set->consume_ppns = consume_ppns; + ppn_set->initialized = true; + return VMCI_SUCCESS; + + ppn_error: + kfree(produce_ppns); + kfree(consume_ppns); + return VMCI_ERROR_INVALID_ARGS; +} + +/* + * Frees the two list of PPNs for a queue pair. + */ +static void qp_free_ppn_set(struct PPNSet *ppn_set) +{ + if (ppn_set->initialized) { + /* Do not call these functions on NULL inputs. */ + kfree(ppn_set->produce_ppns); + kfree(ppn_set->consume_ppns); + } + memset(ppn_set, 0, sizeof(*ppn_set)); +} + +/* + * Populates the list of PPNs in the hypercall structure with the PPNS + * of the produce queue and the consume queue. + */ +static int qp_populate_ppn_set(u8 *call_buf, const struct PPNSet *ppn_set) +{ + memcpy(call_buf, ppn_set->produce_ppns, + ppn_set->num_produce_pages * sizeof(*ppn_set->produce_ppns)); + memcpy(call_buf + + ppn_set->num_produce_pages * sizeof(*ppn_set->produce_ppns), + ppn_set->consume_ppns, + ppn_set->num_consume_pages * sizeof(*ppn_set->consume_ppns)); + + return VMCI_SUCCESS; +} + +static int qp_memcpy_to_queue(struct vmci_queue *queue, + u64 queue_offset, + const void *src, size_t src_offset, size_t size) +{ + return __qp_memcpy_to_queue(queue, queue_offset, + (u8 *)src + src_offset, size, false); +} + +static int qp_memcpy_from_queue(void *dest, + size_t dest_offset, + const struct vmci_queue *queue, + u64 queue_offset, size_t size) +{ + return __qp_memcpy_from_queue((u8 *)dest + dest_offset, + queue, queue_offset, size, false); +} + +/* + * Copies from a given iovec from a VMCI Queue. + */ +static int qp_memcpy_to_queue_iov(struct vmci_queue *queue, + u64 queue_offset, + const void *src, + size_t src_offset, size_t size) +{ + + /* + * We ignore src_offset because src is really a struct iovec * and will + * maintain offset internally. + */ + return __qp_memcpy_to_queue(queue, queue_offset, src, size, true); +} + +/* + * Copies to a given iovec from a VMCI Queue. + */ +static int qp_memcpy_from_queue_iov(void *dest, + size_t dest_offset, + const struct vmci_queue *queue, + u64 queue_offset, size_t size) +{ + /* + * We ignore dest_offset because dest is really a struct iovec * and + * will maintain offset internally. + */ + return __qp_memcpy_from_queue(dest, queue, queue_offset, size, true); +} + +/* + * Allocates kernel VA space of specified size plus space for the queue + * and kernel interface. This is different from the guest queue allocator, + * because we do not allocate our own queue header/data pages here but + * share those of the guest. + */ +static struct vmci_queue *qp_host_alloc_queue(u64 size) +{ + struct vmci_queue *queue; + const size_t num_pages = dm_div_up(size, PAGE_SIZE) + 1; + const size_t queue_size = sizeof(*queue) + sizeof(*(queue->kernel_if)); + const size_t queue_page_size = + num_pages * sizeof(*queue->kernel_if->page); + + queue = kzalloc(queue_size + queue_page_size, GFP_KERNEL); + if (queue) { + queue->q_header = NULL; + queue->saved_header = NULL; + queue->kernel_if = + (struct vmci_queue_kern_if *)((u8 *)queue + + sizeof(*queue)); + queue->kernel_if->host = true; + queue->kernel_if->mutex = NULL; + queue->kernel_if->num_pages = num_pages; + queue->kernel_if->header_page = + (struct page **)((u8 *)queue + queue_size); + queue->kernel_if->page = &queue->kernel_if->header_page[1]; + queue->kernel_if->va = NULL; + queue->kernel_if->mapped = false; + } + + return queue; +} + +/* + * Frees kernel memory for a given queue (header plus translation + * structure). + */ +static void qp_host_free_queue(struct vmci_queue *queue, u64 queue_size) +{ + kfree(queue); +} + +/* + * Initialize the mutex for the pair of queues. This mutex is used to + * protect the q_header and the buffer from changing out from under any + * users of either queue. Of course, it's only any good if the mutexes + * are actually acquired. Queue structure must lie on non-paged memory + * or we cannot guarantee access to the mutex. + */ +static void qp_init_queue_mutex(struct vmci_queue *produce_q, + struct vmci_queue *consume_q) +{ + /* + * Only the host queue has shared state - the guest queues do not + * need to synchronize access using a queue mutex. + */ + + if (produce_q->kernel_if->host) { + produce_q->kernel_if->mutex = &produce_q->kernel_if->__mutex; + consume_q->kernel_if->mutex = &produce_q->kernel_if->__mutex; + mutex_init(produce_q->kernel_if->mutex); + } +} + +/* + * Cleans up the mutex for the pair of queues. + */ +static void qp_cleanup_queue_mutex(struct vmci_queue *produce_q, + struct vmci_queue *consume_q) +{ + if (produce_q->kernel_if->host) { + produce_q->kernel_if->mutex = NULL; + consume_q->kernel_if->mutex = NULL; + } +} + +/* + * Acquire the mutex for the queue. Note that the produce_q and + * the consume_q share a mutex. So, only one of the two need to + * be passed in to this routine. Either will work just fine. + */ +static void qp_acquire_queue_mutex(struct vmci_queue *queue) +{ + if (queue->kernel_if->host) + mutex_lock(queue->kernel_if->mutex); +} + +/* + * Release the mutex for the queue. Note that the produce_q and + * the consume_q share a mutex. So, only one of the two need to + * be passed in to this routine. Either will work just fine. + */ +static void qp_release_queue_mutex(struct vmci_queue *queue) +{ + if (queue->kernel_if->host) + mutex_unlock(queue->kernel_if->mutex); +} + +/* + * Helper function to release pages in the PageStoreAttachInfo + * previously obtained using get_user_pages. + */ +static void qp_release_pages(struct page **pages, + u64 num_pages, bool dirty) +{ + int i; + + for (i = 0; i < num_pages; i++) { + if (dirty) + set_page_dirty(pages[i]); + + page_cache_release(pages[i]); + pages[i] = NULL; + } +} + +/* + * Lock the user pages referenced by the {produce,consume}Buffer + * struct into memory and populate the {produce,consume}Pages + * arrays in the attach structure with them. + */ +static int qp_host_get_user_memory(u64 produce_uva, + u64 consume_uva, + struct vmci_queue *produce_q, + struct vmci_queue *consume_q) +{ + int retval; + int err = VMCI_SUCCESS; + + down_write(¤t->mm->mmap_sem); + retval = get_user_pages(current, + current->mm, + (uintptr_t) produce_uva, + produce_q->kernel_if->num_pages, + 1, 0, produce_q->kernel_if->header_page, NULL); + if (retval < produce_q->kernel_if->num_pages) { + pr_warn("get_user_pages(produce) failed (retval=%d)", retval); + qp_release_pages(produce_q->kernel_if->header_page, retval, + false); + err = VMCI_ERROR_NO_MEM; + goto out; + } + + retval = get_user_pages(current, + current->mm, + (uintptr_t) consume_uva, + consume_q->kernel_if->num_pages, + 1, 0, consume_q->kernel_if->header_page, NULL); + if (retval < consume_q->kernel_if->num_pages) { + pr_warn("get_user_pages(consume) failed (retval=%d)", retval); + qp_release_pages(consume_q->kernel_if->header_page, retval, + false); + qp_release_pages(produce_q->kernel_if->header_page, + produce_q->kernel_if->num_pages, false); + err = VMCI_ERROR_NO_MEM; + } + + out: + up_write(¤t->mm->mmap_sem); + + return err; +} + +/* + * Registers the specification of the user pages used for backing a queue + * pair. Enough information to map in pages is stored in the OS specific + * part of the struct vmci_queue structure. + */ +static int qp_host_register_user_memory(struct vmci_qp_page_store *page_store, + struct vmci_queue *produce_q, + struct vmci_queue *consume_q) +{ + u64 produce_uva; + u64 consume_uva; + + /* + * The new style and the old style mapping only differs in + * that we either get a single or two UVAs, so we split the + * single UVA range at the appropriate spot. + */ + produce_uva = page_store->pages; + consume_uva = page_store->pages + + produce_q->kernel_if->num_pages * PAGE_SIZE; + return qp_host_get_user_memory(produce_uva, consume_uva, produce_q, + consume_q); +} + +/* + * Releases and removes the references to user pages stored in the attach + * struct. Pages are released from the page cache and may become + * swappable again. + */ +static void qp_host_unregister_user_memory(struct vmci_queue *produce_q, + struct vmci_queue *consume_q) +{ + qp_release_pages(produce_q->kernel_if->header_page, + produce_q->kernel_if->num_pages, true); + memset(produce_q->kernel_if->header_page, 0, + sizeof(*produce_q->kernel_if->header_page) * + produce_q->kernel_if->num_pages); + qp_release_pages(consume_q->kernel_if->header_page, + consume_q->kernel_if->num_pages, true); + memset(consume_q->kernel_if->header_page, 0, + sizeof(*consume_q->kernel_if->header_page) * + consume_q->kernel_if->num_pages); +} + +/* + * Once qp_host_register_user_memory has been performed on a + * queue, the queue pair headers can be mapped into the + * kernel. Once mapped, they must be unmapped with + * qp_host_unmap_queues prior to calling + * qp_host_unregister_user_memory. + * Pages are pinned. + */ +static int qp_host_map_queues(struct vmci_queue *produce_q, + struct vmci_queue *consume_q) +{ + int result; + + if (!produce_q->q_header || !consume_q->q_header) { + struct page *headers[2]; + + if (produce_q->q_header != consume_q->q_header) + return VMCI_ERROR_QUEUEPAIR_MISMATCH; + + if (produce_q->kernel_if->header_page == NULL || + *produce_q->kernel_if->header_page == NULL) + return VMCI_ERROR_UNAVAILABLE; + + headers[0] = *produce_q->kernel_if->header_page; + headers[1] = *consume_q->kernel_if->header_page; + + produce_q->q_header = vmap(headers, 2, VM_MAP, PAGE_KERNEL); + if (produce_q->q_header != NULL) { + consume_q->q_header = + (struct vmci_queue_header *)((u8 *) + produce_q->q_header + + PAGE_SIZE); + result = VMCI_SUCCESS; + } else { + pr_warn("vmap failed\n"); + result = VMCI_ERROR_NO_MEM; + } + } else { + result = VMCI_SUCCESS; + } + + return result; +} + +/* + * Unmaps previously mapped queue pair headers from the kernel. + * Pages are unpinned. + */ +static int qp_host_unmap_queues(u32 gid, + struct vmci_queue *produce_q, + struct vmci_queue *consume_q) +{ + if (produce_q->q_header) { + if (produce_q->q_header < consume_q->q_header) + vunmap(produce_q->q_header); + else + vunmap(consume_q->q_header); + + produce_q->q_header = NULL; + consume_q->q_header = NULL; + } + + return VMCI_SUCCESS; +} + +/* + * Finds the entry in the list corresponding to a given handle. Assumes + * that the list is locked. + */ +static struct qp_entry *qp_list_find(struct qp_list *qp_list, + struct vmci_handle handle) +{ + struct qp_entry *entry; + + if (vmci_handle_is_invalid(handle)) + return NULL; + + list_for_each_entry(entry, &qp_list->head, list_item) { + if (vmci_handle_is_equal(entry->handle, handle)) + return entry; + } + + return NULL; +} + +/* + * Finds the entry in the list corresponding to a given handle. + */ +static struct qp_guest_endpoint * +qp_guest_handle_to_entry(struct vmci_handle handle) +{ + struct qp_guest_endpoint *entry; + struct qp_entry *qp = qp_list_find(&qp_guest_endpoints, handle); + + entry = qp ? container_of( + qp, struct qp_guest_endpoint, qp) : NULL; + return entry; +} + +/* + * Finds the entry in the list corresponding to a given handle. + */ +static struct qp_broker_entry * +qp_broker_handle_to_entry(struct vmci_handle handle) +{ + struct qp_broker_entry *entry; + struct qp_entry *qp = qp_list_find(&qp_broker_list, handle); + + entry = qp ? container_of( + qp, struct qp_broker_entry, qp) : NULL; + return entry; +} + +/* + * Dispatches a queue pair event message directly into the local event + * queue. + */ +static int qp_notify_peer_local(bool attach, struct vmci_handle handle) +{ + u32 context_id = vmci_get_context_id(); + struct vmci_event_qp ev; + + ev.msg.hdr.dst = vmci_make_handle(context_id, VMCI_EVENT_HANDLER); + ev.msg.hdr.src = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_CONTEXT_RESOURCE_ID); + ev.msg.hdr.payload_size = sizeof(ev) - sizeof(ev.msg.hdr); + ev.msg.event_data.event = + attach ? VMCI_EVENT_QP_PEER_ATTACH : VMCI_EVENT_QP_PEER_DETACH; + ev.payload.peer_id = context_id; + ev.payload.handle = handle; + + return vmci_event_dispatch(&ev.msg.hdr); +} + +/* + * Allocates and initializes a qp_guest_endpoint structure. + * Allocates a queue_pair rid (and handle) iff the given entry has + * an invalid handle. 0 through VMCI_RESERVED_RESOURCE_ID_MAX + * are reserved handles. Assumes that the QP list mutex is held + * by the caller. + */ +static struct qp_guest_endpoint * +qp_guest_endpoint_create(struct vmci_handle handle, + u32 peer, + u32 flags, + u64 produce_size, + u64 consume_size, + void *produce_q, + void *consume_q) +{ + int result; + struct qp_guest_endpoint *entry; + /* One page each for the queue headers. */ + const u64 num_ppns = dm_div_up(produce_size, PAGE_SIZE) + + dm_div_up(consume_size, PAGE_SIZE) + 2; + + if (vmci_handle_is_invalid(handle)) { + u32 context_id = vmci_get_context_id(); + + handle = vmci_make_handle(context_id, VMCI_INVALID_ID); + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (entry) { + entry->qp.peer = peer; + entry->qp.flags = flags; + entry->qp.produce_size = produce_size; + entry->qp.consume_size = consume_size; + entry->qp.ref_count = 0; + entry->num_ppns = num_ppns; + entry->produce_q = produce_q; + entry->consume_q = consume_q; + INIT_LIST_HEAD(&entry->qp.list_item); + + /* Add resource obj */ + result = vmci_resource_add(&entry->resource, + VMCI_RESOURCE_TYPE_QPAIR_GUEST, + handle); + entry->qp.handle = vmci_resource_handle(&entry->resource); + if ((result != VMCI_SUCCESS) || + qp_list_find(&qp_guest_endpoints, entry->qp.handle)) { + pr_warn("Failed to add new resource (handle=0x%x:0x%x), error: %d", + handle.context, handle.resource, result); + kfree(entry); + entry = NULL; + } + } + return entry; +} + +/* + * Frees a qp_guest_endpoint structure. + */ +static void qp_guest_endpoint_destroy(struct qp_guest_endpoint *entry) +{ + qp_free_ppn_set(&entry->ppn_set); + qp_cleanup_queue_mutex(entry->produce_q, entry->consume_q); + qp_free_queue(entry->produce_q, entry->qp.produce_size); + qp_free_queue(entry->consume_q, entry->qp.consume_size); + /* Unlink from resource hash table and free callback */ + vmci_resource_remove(&entry->resource); + + kfree(entry); +} + +/* + * Helper to make a queue_pairAlloc hypercall when the driver is + * supporting a guest device. + */ +static int qp_alloc_hypercall(const struct qp_guest_endpoint *entry) +{ + struct vmci_qp_alloc_msg *alloc_msg; + size_t msg_size; + int result; + + if (!entry || entry->num_ppns <= 2) + return VMCI_ERROR_INVALID_ARGS; + + msg_size = sizeof(*alloc_msg) + + (size_t) entry->num_ppns * sizeof(u32); + alloc_msg = kmalloc(msg_size, GFP_KERNEL); + if (!alloc_msg) + return VMCI_ERROR_NO_MEM; + + alloc_msg->hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_QUEUEPAIR_ALLOC); + alloc_msg->hdr.src = VMCI_ANON_SRC_HANDLE; + alloc_msg->hdr.payload_size = msg_size - VMCI_DG_HEADERSIZE; + alloc_msg->handle = entry->qp.handle; + alloc_msg->peer = entry->qp.peer; + alloc_msg->flags = entry->qp.flags; + alloc_msg->produce_size = entry->qp.produce_size; + alloc_msg->consume_size = entry->qp.consume_size; + alloc_msg->num_ppns = entry->num_ppns; + + result = qp_populate_ppn_set((u8 *)alloc_msg + sizeof(*alloc_msg), + &entry->ppn_set); + if (result == VMCI_SUCCESS) + result = vmci_send_datagram(&alloc_msg->hdr); + + kfree(alloc_msg); + + return result; +} + +/* + * Helper to make a queue_pairDetach hypercall when the driver is + * supporting a guest device. + */ +static int qp_detatch_hypercall(struct vmci_handle handle) +{ + struct vmci_qp_detach_msg detach_msg; + + detach_msg.hdr.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_QUEUEPAIR_DETACH); + detach_msg.hdr.src = VMCI_ANON_SRC_HANDLE; + detach_msg.hdr.payload_size = sizeof(handle); + detach_msg.handle = handle; + + return vmci_send_datagram(&detach_msg.hdr); +} + +/* + * Adds the given entry to the list. Assumes that the list is locked. + */ +static void qp_list_add_entry(struct qp_list *qp_list, struct qp_entry *entry) +{ + if (entry) + list_add(&entry->list_item, &qp_list->head); +} + +/* + * Removes the given entry from the list. Assumes that the list is locked. + */ +static void qp_list_remove_entry(struct qp_list *qp_list, + struct qp_entry *entry) +{ + if (entry) + list_del(&entry->list_item); +} + +/* + * Helper for VMCI queue_pair detach interface. Frees the physical + * pages for the queue pair. + */ +static int qp_detatch_guest_work(struct vmci_handle handle) +{ + int result; + struct qp_guest_endpoint *entry; + u32 ref_count = ~0; /* To avoid compiler warning below */ + + mutex_lock(&qp_guest_endpoints.mutex); + + entry = qp_guest_handle_to_entry(handle); + if (!entry) { + mutex_unlock(&qp_guest_endpoints.mutex); + return VMCI_ERROR_NOT_FOUND; + } + + if (entry->qp.flags & VMCI_QPFLAG_LOCAL) { + result = VMCI_SUCCESS; + + if (entry->qp.ref_count > 1) { + result = qp_notify_peer_local(false, handle); + /* + * We can fail to notify a local queuepair + * because we can't allocate. We still want + * to release the entry if that happens, so + * don't bail out yet. + */ + } + } else { + result = qp_detatch_hypercall(handle); + if (result < VMCI_SUCCESS) { + /* + * We failed to notify a non-local queuepair. + * That other queuepair might still be + * accessing the shared memory, so don't + * release the entry yet. It will get cleaned + * up by VMCIqueue_pair_Exit() if necessary + * (assuming we are going away, otherwise why + * did this fail?). + */ + + mutex_unlock(&qp_guest_endpoints.mutex); + return result; + } + } + + /* + * If we get here then we either failed to notify a local queuepair, or + * we succeeded in all cases. Release the entry if required. + */ + + entry->qp.ref_count--; + if (entry->qp.ref_count == 0) + qp_list_remove_entry(&qp_guest_endpoints, &entry->qp); + + /* If we didn't remove the entry, this could change once we unlock. */ + if (entry) + ref_count = entry->qp.ref_count; + + mutex_unlock(&qp_guest_endpoints.mutex); + + if (ref_count == 0) + qp_guest_endpoint_destroy(entry); + + return result; +} + +/* + * This functions handles the actual allocation of a VMCI queue + * pair guest endpoint. Allocates physical pages for the queue + * pair. It makes OS dependent calls through generic wrappers. + */ +static int qp_alloc_guest_work(struct vmci_handle *handle, + struct vmci_queue **produce_q, + u64 produce_size, + struct vmci_queue **consume_q, + u64 consume_size, + u32 peer, + u32 flags, + u32 priv_flags) +{ + const u64 num_produce_pages = + dm_div_up(produce_size, PAGE_SIZE) + 1; + const u64 num_consume_pages = + dm_div_up(consume_size, PAGE_SIZE) + 1; + void *my_produce_q = NULL; + void *my_consume_q = NULL; + int result; + struct qp_guest_endpoint *queue_pair_entry = NULL; + + if (priv_flags != VMCI_NO_PRIVILEGE_FLAGS) + return VMCI_ERROR_NO_ACCESS; + + mutex_lock(&qp_guest_endpoints.mutex); + + queue_pair_entry = qp_guest_handle_to_entry(*handle); + if (queue_pair_entry) { + if (queue_pair_entry->qp.flags & VMCI_QPFLAG_LOCAL) { + /* Local attach case. */ + if (queue_pair_entry->qp.ref_count > 1) { + pr_devel("Error attempting to attach more than once\n"); + result = VMCI_ERROR_UNAVAILABLE; + goto error_keep_entry; + } + + if (queue_pair_entry->qp.produce_size != consume_size || + queue_pair_entry->qp.consume_size != + produce_size || + queue_pair_entry->qp.flags != + (flags & ~VMCI_QPFLAG_ATTACH_ONLY)) { + pr_devel("Error mismatched queue pair in local attach\n"); + result = VMCI_ERROR_QUEUEPAIR_MISMATCH; + goto error_keep_entry; + } + + /* + * Do a local attach. We swap the consume and + * produce queues for the attacher and deliver + * an attach event. + */ + result = qp_notify_peer_local(true, *handle); + if (result < VMCI_SUCCESS) + goto error_keep_entry; + + my_produce_q = queue_pair_entry->consume_q; + my_consume_q = queue_pair_entry->produce_q; + goto out; + } + + result = VMCI_ERROR_ALREADY_EXISTS; + goto error_keep_entry; + } + + my_produce_q = qp_alloc_queue(produce_size, flags); + if (!my_produce_q) { + pr_warn("Error allocating pages for produce queue\n"); + result = VMCI_ERROR_NO_MEM; + goto error; + } + + my_consume_q = qp_alloc_queue(consume_size, flags); + if (!my_consume_q) { + pr_warn("Error allocating pages for consume queue\n"); + result = VMCI_ERROR_NO_MEM; + goto error; + } + + queue_pair_entry = qp_guest_endpoint_create(*handle, peer, flags, + produce_size, consume_size, + my_produce_q, my_consume_q); + if (!queue_pair_entry) { + pr_warn("Error allocating memory in %s\n", __func__); + result = VMCI_ERROR_NO_MEM; + goto error; + } + + result = qp_alloc_ppn_set(my_produce_q, num_produce_pages, my_consume_q, + num_consume_pages, + &queue_pair_entry->ppn_set); + if (result < VMCI_SUCCESS) { + pr_warn("qp_alloc_ppn_set failed\n"); + goto error; + } + + /* + * It's only necessary to notify the host if this queue pair will be + * attached to from another context. + */ + if (queue_pair_entry->qp.flags & VMCI_QPFLAG_LOCAL) { + /* Local create case. */ + u32 context_id = vmci_get_context_id(); + + /* + * Enforce similar checks on local queue pairs as we + * do for regular ones. The handle's context must + * match the creator or attacher context id (here they + * are both the current context id) and the + * attach-only flag cannot exist during create. We + * also ensure specified peer is this context or an + * invalid one. + */ + if (queue_pair_entry->qp.handle.context != context_id || + (queue_pair_entry->qp.peer != VMCI_INVALID_ID && + queue_pair_entry->qp.peer != context_id)) { + result = VMCI_ERROR_NO_ACCESS; + goto error; + } + + if (queue_pair_entry->qp.flags & VMCI_QPFLAG_ATTACH_ONLY) { + result = VMCI_ERROR_NOT_FOUND; + goto error; + } + } else { + result = qp_alloc_hypercall(queue_pair_entry); + if (result < VMCI_SUCCESS) { + pr_warn("qp_alloc_hypercall result = %d\n", result); + goto error; + } + } + + qp_init_queue_mutex((struct vmci_queue *)my_produce_q, + (struct vmci_queue *)my_consume_q); + + qp_list_add_entry(&qp_guest_endpoints, &queue_pair_entry->qp); + + out: + queue_pair_entry->qp.ref_count++; + *handle = queue_pair_entry->qp.handle; + *produce_q = (struct vmci_queue *)my_produce_q; + *consume_q = (struct vmci_queue *)my_consume_q; + + /* + * We should initialize the queue pair header pages on a local + * queue pair create. For non-local queue pairs, the + * hypervisor initializes the header pages in the create step. + */ + if ((queue_pair_entry->qp.flags & VMCI_QPFLAG_LOCAL) && + queue_pair_entry->qp.ref_count == 1) { + vmci_q_header_init((*produce_q)->q_header, *handle); + vmci_q_header_init((*consume_q)->q_header, *handle); + } + + mutex_unlock(&qp_guest_endpoints.mutex); + + return VMCI_SUCCESS; + + error: + mutex_unlock(&qp_guest_endpoints.mutex); + if (queue_pair_entry) { + /* The queues will be freed inside the destroy routine. */ + qp_guest_endpoint_destroy(queue_pair_entry); + } else { + qp_free_queue(my_produce_q, produce_size); + qp_free_queue(my_consume_q, consume_size); + } + return result; + + error_keep_entry: + /* This path should only be used when an existing entry was found. */ + mutex_unlock(&qp_guest_endpoints.mutex); + return result; +} + +/* + * The first endpoint issuing a queue pair allocation will create the state + * of the queue pair in the queue pair broker. + * + * If the creator is a guest, it will associate a VMX virtual address range + * with the queue pair as specified by the page_store. For compatibility with + * older VMX'en, that would use a separate step to set the VMX virtual + * address range, the virtual address range can be registered later using + * vmci_qp_broker_set_page_store. In that case, a page_store of NULL should be + * used. + * + * If the creator is the host, a page_store of NULL should be used as well, + * since the host is not able to supply a page store for the queue pair. + * + * For older VMX and host callers, the queue pair will be created in the + * VMCIQPB_CREATED_NO_MEM state, and for current VMX callers, it will be + * created in VMCOQPB_CREATED_MEM state. + */ +static int qp_broker_create(struct vmci_handle handle, + u32 peer, + u32 flags, + u32 priv_flags, + u64 produce_size, + u64 consume_size, + struct vmci_qp_page_store *page_store, + struct vmci_ctx *context, + vmci_event_release_cb wakeup_cb, + void *client_data, struct qp_broker_entry **ent) +{ + struct qp_broker_entry *entry = NULL; + const u32 context_id = vmci_ctx_get_id(context); + bool is_local = flags & VMCI_QPFLAG_LOCAL; + int result; + u64 guest_produce_size; + u64 guest_consume_size; + + /* Do not create if the caller asked not to. */ + if (flags & VMCI_QPFLAG_ATTACH_ONLY) + return VMCI_ERROR_NOT_FOUND; + + /* + * Creator's context ID should match handle's context ID or the creator + * must allow the context in handle's context ID as the "peer". + */ + if (handle.context != context_id && handle.context != peer) + return VMCI_ERROR_NO_ACCESS; + + if (VMCI_CONTEXT_IS_VM(context_id) && VMCI_CONTEXT_IS_VM(peer)) + return VMCI_ERROR_DST_UNREACHABLE; + + /* + * Creator's context ID for local queue pairs should match the + * peer, if a peer is specified. + */ + if (is_local && peer != VMCI_INVALID_ID && context_id != peer) + return VMCI_ERROR_NO_ACCESS; + + entry = kzalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) + return VMCI_ERROR_NO_MEM; + + if (vmci_ctx_get_id(context) == VMCI_HOST_CONTEXT_ID && !is_local) { + /* + * The queue pair broker entry stores values from the guest + * point of view, so a creating host side endpoint should swap + * produce and consume values -- unless it is a local queue + * pair, in which case no swapping is necessary, since the local + * attacher will swap queues. + */ + + guest_produce_size = consume_size; + guest_consume_size = produce_size; + } else { + guest_produce_size = produce_size; + guest_consume_size = consume_size; + } + + entry->qp.handle = handle; + entry->qp.peer = peer; + entry->qp.flags = flags; + entry->qp.produce_size = guest_produce_size; + entry->qp.consume_size = guest_consume_size; + entry->qp.ref_count = 1; + entry->create_id = context_id; + entry->attach_id = VMCI_INVALID_ID; + entry->state = VMCIQPB_NEW; + entry->require_trusted_attach = + !!(context->priv_flags & VMCI_PRIVILEGE_FLAG_RESTRICTED); + entry->created_by_trusted = + !!(priv_flags & VMCI_PRIVILEGE_FLAG_TRUSTED); + entry->vmci_page_files = false; + entry->wakeup_cb = wakeup_cb; + entry->client_data = client_data; + entry->produce_q = qp_host_alloc_queue(guest_produce_size); + if (entry->produce_q == NULL) { + result = VMCI_ERROR_NO_MEM; + goto error; + } + entry->consume_q = qp_host_alloc_queue(guest_consume_size); + if (entry->consume_q == NULL) { + result = VMCI_ERROR_NO_MEM; + goto error; + } + + qp_init_queue_mutex(entry->produce_q, entry->consume_q); + + INIT_LIST_HEAD(&entry->qp.list_item); + + if (is_local) { + u8 *tmp; + + entry->local_mem = kcalloc(QPE_NUM_PAGES(entry->qp), + PAGE_SIZE, GFP_KERNEL); + if (entry->local_mem == NULL) { + result = VMCI_ERROR_NO_MEM; + goto error; + } + entry->state = VMCIQPB_CREATED_MEM; + entry->produce_q->q_header = entry->local_mem; + tmp = (u8 *)entry->local_mem + PAGE_SIZE * + (dm_div_up(entry->qp.produce_size, PAGE_SIZE) + 1); + entry->consume_q->q_header = (struct vmci_queue_header *)tmp; + } else if (page_store) { + /* + * The VMX already initialized the queue pair headers, so no + * need for the kernel side to do that. + */ + result = qp_host_register_user_memory(page_store, + entry->produce_q, + entry->consume_q); + if (result < VMCI_SUCCESS) + goto error; + + entry->state = VMCIQPB_CREATED_MEM; + } else { + /* + * A create without a page_store may be either a host + * side create (in which case we are waiting for the + * guest side to supply the memory) or an old style + * queue pair create (in which case we will expect a + * set page store call as the next step). + */ + entry->state = VMCIQPB_CREATED_NO_MEM; + } + + qp_list_add_entry(&qp_broker_list, &entry->qp); + if (ent != NULL) + *ent = entry; + + /* Add to resource obj */ + result = vmci_resource_add(&entry->resource, + VMCI_RESOURCE_TYPE_QPAIR_HOST, + handle); + if (result != VMCI_SUCCESS) { + pr_warn("Failed to add new resource (handle=0x%x:0x%x), error: %d", + handle.context, handle.resource, result); + goto error; + } + + entry->qp.handle = vmci_resource_handle(&entry->resource); + if (is_local) { + vmci_q_header_init(entry->produce_q->q_header, + entry->qp.handle); + vmci_q_header_init(entry->consume_q->q_header, + entry->qp.handle); + } + + vmci_ctx_qp_create(context, entry->qp.handle); + + return VMCI_SUCCESS; + + error: + if (entry != NULL) { + qp_host_free_queue(entry->produce_q, guest_produce_size); + qp_host_free_queue(entry->consume_q, guest_consume_size); + kfree(entry); + } + + return result; +} + +/* + * Enqueues an event datagram to notify the peer VM attached to + * the given queue pair handle about attach/detach event by the + * given VM. Returns Payload size of datagram enqueued on + * success, error code otherwise. + */ +static int qp_notify_peer(bool attach, + struct vmci_handle handle, + u32 my_id, + u32 peer_id) +{ + int rv; + struct vmci_event_qp ev; + + if (vmci_handle_is_invalid(handle) || my_id == VMCI_INVALID_ID || + peer_id == VMCI_INVALID_ID) + return VMCI_ERROR_INVALID_ARGS; + + /* + * In vmci_ctx_enqueue_datagram() we enforce the upper limit on + * number of pending events from the hypervisor to a given VM + * otherwise a rogue VM could do an arbitrary number of attach + * and detach operations causing memory pressure in the host + * kernel. + */ + + ev.msg.hdr.dst = vmci_make_handle(peer_id, VMCI_EVENT_HANDLER); + ev.msg.hdr.src = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_CONTEXT_RESOURCE_ID); + ev.msg.hdr.payload_size = sizeof(ev) - sizeof(ev.msg.hdr); + ev.msg.event_data.event = attach ? + VMCI_EVENT_QP_PEER_ATTACH : VMCI_EVENT_QP_PEER_DETACH; + ev.payload.handle = handle; + ev.payload.peer_id = my_id; + + rv = vmci_datagram_dispatch(VMCI_HYPERVISOR_CONTEXT_ID, + &ev.msg.hdr, false); + if (rv < VMCI_SUCCESS) + pr_warn("Failed to enqueue queue_pair %s event datagram for context (ID=0x%x)\n", + attach ? "ATTACH" : "DETACH", peer_id); + + return rv; +} + +/* + * The second endpoint issuing a queue pair allocation will attach to + * the queue pair registered with the queue pair broker. + * + * If the attacher is a guest, it will associate a VMX virtual address + * range with the queue pair as specified by the page_store. At this + * point, the already attach host endpoint may start using the queue + * pair, and an attach event is sent to it. For compatibility with + * older VMX'en, that used a separate step to set the VMX virtual + * address range, the virtual address range can be registered later + * using vmci_qp_broker_set_page_store. In that case, a page_store of + * NULL should be used, and the attach event will be generated once + * the actual page store has been set. + * + * If the attacher is the host, a page_store of NULL should be used as + * well, since the page store information is already set by the guest. + * + * For new VMX and host callers, the queue pair will be moved to the + * VMCIQPB_ATTACHED_MEM state, and for older VMX callers, it will be + * moved to the VMCOQPB_ATTACHED_NO_MEM state. + */ +static int qp_broker_attach(struct qp_broker_entry *entry, + u32 peer, + u32 flags, + u32 priv_flags, + u64 produce_size, + u64 consume_size, + struct vmci_qp_page_store *page_store, + struct vmci_ctx *context, + vmci_event_release_cb wakeup_cb, + void *client_data, + struct qp_broker_entry **ent) +{ + const u32 context_id = vmci_ctx_get_id(context); + bool is_local = flags & VMCI_QPFLAG_LOCAL; + int result; + + if (entry->state != VMCIQPB_CREATED_NO_MEM && + entry->state != VMCIQPB_CREATED_MEM) + return VMCI_ERROR_UNAVAILABLE; + + if (is_local) { + if (!(entry->qp.flags & VMCI_QPFLAG_LOCAL) || + context_id != entry->create_id) { + return VMCI_ERROR_INVALID_ARGS; + } + } else if (context_id == entry->create_id || + context_id == entry->attach_id) { + return VMCI_ERROR_ALREADY_EXISTS; + } + + if (VMCI_CONTEXT_IS_VM(context_id) && + VMCI_CONTEXT_IS_VM(entry->create_id)) + return VMCI_ERROR_DST_UNREACHABLE; + + /* + * If we are attaching from a restricted context then the queuepair + * must have been created by a trusted endpoint. + */ + if ((context->priv_flags & VMCI_PRIVILEGE_FLAG_RESTRICTED) && + !entry->created_by_trusted) + return VMCI_ERROR_NO_ACCESS; + + /* + * If we are attaching to a queuepair that was created by a restricted + * context then we must be trusted. + */ + if (entry->require_trusted_attach && + (!(priv_flags & VMCI_PRIVILEGE_FLAG_TRUSTED))) + return VMCI_ERROR_NO_ACCESS; + + /* + * If the creator specifies VMCI_INVALID_ID in "peer" field, access + * control check is not performed. + */ + if (entry->qp.peer != VMCI_INVALID_ID && entry->qp.peer != context_id) + return VMCI_ERROR_NO_ACCESS; + + if (entry->create_id == VMCI_HOST_CONTEXT_ID) { + /* + * Do not attach if the caller doesn't support Host Queue Pairs + * and a host created this queue pair. + */ + + if (!vmci_ctx_supports_host_qp(context)) + return VMCI_ERROR_INVALID_RESOURCE; + + } else if (context_id == VMCI_HOST_CONTEXT_ID) { + struct vmci_ctx *create_context; + bool supports_host_qp; + + /* + * Do not attach a host to a user created queue pair if that + * user doesn't support host queue pair end points. + */ + + create_context = vmci_ctx_get(entry->create_id); + supports_host_qp = vmci_ctx_supports_host_qp(create_context); + vmci_ctx_put(create_context); + + if (!supports_host_qp) + return VMCI_ERROR_INVALID_RESOURCE; + } + + if ((entry->qp.flags & ~VMCI_QP_ASYMM) != (flags & ~VMCI_QP_ASYMM_PEER)) + return VMCI_ERROR_QUEUEPAIR_MISMATCH; + + if (context_id != VMCI_HOST_CONTEXT_ID) { + /* + * The queue pair broker entry stores values from the guest + * point of view, so an attaching guest should match the values + * stored in the entry. + */ + + if (entry->qp.produce_size != produce_size || + entry->qp.consume_size != consume_size) { + return VMCI_ERROR_QUEUEPAIR_MISMATCH; + } + } else if (entry->qp.produce_size != consume_size || + entry->qp.consume_size != produce_size) { + return VMCI_ERROR_QUEUEPAIR_MISMATCH; + } + + if (context_id != VMCI_HOST_CONTEXT_ID) { + /* + * If a guest attached to a queue pair, it will supply + * the backing memory. If this is a pre NOVMVM vmx, + * the backing memory will be supplied by calling + * vmci_qp_broker_set_page_store() following the + * return of the vmci_qp_broker_alloc() call. If it is + * a vmx of version NOVMVM or later, the page store + * must be supplied as part of the + * vmci_qp_broker_alloc call. Under all circumstances + * must the initially created queue pair not have any + * memory associated with it already. + */ + + if (entry->state != VMCIQPB_CREATED_NO_MEM) + return VMCI_ERROR_INVALID_ARGS; + + if (page_store != NULL) { + /* + * Patch up host state to point to guest + * supplied memory. The VMX already + * initialized the queue pair headers, so no + * need for the kernel side to do that. + */ + + result = qp_host_register_user_memory(page_store, + entry->produce_q, + entry->consume_q); + if (result < VMCI_SUCCESS) + return result; + + /* + * Preemptively load in the headers if non-blocking to + * prevent blocking later. + */ + if (entry->qp.flags & VMCI_QPFLAG_NONBLOCK) { + result = qp_host_map_queues(entry->produce_q, + entry->consume_q); + if (result < VMCI_SUCCESS) { + qp_host_unregister_user_memory( + entry->produce_q, + entry->consume_q); + return result; + } + } + + entry->state = VMCIQPB_ATTACHED_MEM; + } else { + entry->state = VMCIQPB_ATTACHED_NO_MEM; + } + } else if (entry->state == VMCIQPB_CREATED_NO_MEM) { + /* + * The host side is attempting to attach to a queue + * pair that doesn't have any memory associated with + * it. This must be a pre NOVMVM vmx that hasn't set + * the page store information yet, or a quiesced VM. + */ + + return VMCI_ERROR_UNAVAILABLE; + } else { + /* + * For non-blocking queue pairs, we cannot rely on + * enqueue/dequeue to map in the pages on the + * host-side, since it may block, so we make an + * attempt here. + */ + + if (flags & VMCI_QPFLAG_NONBLOCK) { + result = + qp_host_map_queues(entry->produce_q, + entry->consume_q); + if (result < VMCI_SUCCESS) + return result; + + entry->qp.flags |= flags & + (VMCI_QPFLAG_NONBLOCK | VMCI_QPFLAG_PINNED); + } + + /* The host side has successfully attached to a queue pair. */ + entry->state = VMCIQPB_ATTACHED_MEM; + } + + if (entry->state == VMCIQPB_ATTACHED_MEM) { + result = + qp_notify_peer(true, entry->qp.handle, context_id, + entry->create_id); + if (result < VMCI_SUCCESS) + pr_warn("Failed to notify peer (ID=0x%x) of attach to queue pair (handle=0x%x:0x%x)\n", + entry->create_id, entry->qp.handle.context, + entry->qp.handle.resource); + } + + entry->attach_id = context_id; + entry->qp.ref_count++; + if (wakeup_cb) { + entry->wakeup_cb = wakeup_cb; + entry->client_data = client_data; + } + + /* + * When attaching to local queue pairs, the context already has + * an entry tracking the queue pair, so don't add another one. + */ + if (!is_local) + vmci_ctx_qp_create(context, entry->qp.handle); + + if (ent != NULL) + *ent = entry; + + return VMCI_SUCCESS; +} + +/* + * queue_pair_Alloc for use when setting up queue pair endpoints + * on the host. + */ +static int qp_broker_alloc(struct vmci_handle handle, + u32 peer, + u32 flags, + u32 priv_flags, + u64 produce_size, + u64 consume_size, + struct vmci_qp_page_store *page_store, + struct vmci_ctx *context, + vmci_event_release_cb wakeup_cb, + void *client_data, + struct qp_broker_entry **ent, + bool *swap) +{ + const u32 context_id = vmci_ctx_get_id(context); + bool create; + struct qp_broker_entry *entry = NULL; + bool is_local = flags & VMCI_QPFLAG_LOCAL; + int result; + + if (vmci_handle_is_invalid(handle) || + (flags & ~VMCI_QP_ALL_FLAGS) || is_local || + !(produce_size || consume_size) || + !context || context_id == VMCI_INVALID_ID || + handle.context == VMCI_INVALID_ID) { + return VMCI_ERROR_INVALID_ARGS; + } + + if (page_store && !VMCI_QP_PAGESTORE_IS_WELLFORMED(page_store)) + return VMCI_ERROR_INVALID_ARGS; + + /* + * In the initial argument check, we ensure that non-vmkernel hosts + * are not allowed to create local queue pairs. + */ + + mutex_lock(&qp_broker_list.mutex); + + if (!is_local && vmci_ctx_qp_exists(context, handle)) { + pr_devel("Context (ID=0x%x) already attached to queue pair (handle=0x%x:0x%x)\n", + context_id, handle.context, handle.resource); + mutex_unlock(&qp_broker_list.mutex); + return VMCI_ERROR_ALREADY_EXISTS; + } + + if (handle.resource != VMCI_INVALID_ID) + entry = qp_broker_handle_to_entry(handle); + + if (!entry) { + create = true; + result = + qp_broker_create(handle, peer, flags, priv_flags, + produce_size, consume_size, page_store, + context, wakeup_cb, client_data, ent); + } else { + create = false; + result = + qp_broker_attach(entry, peer, flags, priv_flags, + produce_size, consume_size, page_store, + context, wakeup_cb, client_data, ent); + } + + mutex_unlock(&qp_broker_list.mutex); + + if (swap) + *swap = (context_id == VMCI_HOST_CONTEXT_ID) && + !(create && is_local); + + return result; +} + +/* + * This function implements the kernel API for allocating a queue + * pair. + */ +static int qp_alloc_host_work(struct vmci_handle *handle, + struct vmci_queue **produce_q, + u64 produce_size, + struct vmci_queue **consume_q, + u64 consume_size, + u32 peer, + u32 flags, + u32 priv_flags, + vmci_event_release_cb wakeup_cb, + void *client_data) +{ + struct vmci_handle new_handle; + struct vmci_ctx *context; + struct qp_broker_entry *entry; + int result; + bool swap; + + if (vmci_handle_is_invalid(*handle)) { + new_handle = vmci_make_handle( + VMCI_HOST_CONTEXT_ID, VMCI_INVALID_ID); + } else + new_handle = *handle; + + context = vmci_ctx_get(VMCI_HOST_CONTEXT_ID); + entry = NULL; + result = + qp_broker_alloc(new_handle, peer, flags, priv_flags, + produce_size, consume_size, NULL, context, + wakeup_cb, client_data, &entry, &swap); + if (result == VMCI_SUCCESS) { + if (swap) { + /* + * If this is a local queue pair, the attacher + * will swap around produce and consume + * queues. + */ + + *produce_q = entry->consume_q; + *consume_q = entry->produce_q; + } else { + *produce_q = entry->produce_q; + *consume_q = entry->consume_q; + } + + *handle = vmci_resource_handle(&entry->resource); + } else { + *handle = VMCI_INVALID_HANDLE; + pr_devel("queue pair broker failed to alloc (result=%d)\n", + result); + } + vmci_ctx_put(context); + return result; +} + +/* + * Allocates a VMCI queue_pair. Only checks validity of input + * arguments. The real work is done in the host or guest + * specific function. + */ +int vmci_qp_alloc(struct vmci_handle *handle, + struct vmci_queue **produce_q, + u64 produce_size, + struct vmci_queue **consume_q, + u64 consume_size, + u32 peer, + u32 flags, + u32 priv_flags, + bool guest_endpoint, + vmci_event_release_cb wakeup_cb, + void *client_data) +{ + if (!handle || !produce_q || !consume_q || + (!produce_size && !consume_size) || (flags & ~VMCI_QP_ALL_FLAGS)) + return VMCI_ERROR_INVALID_ARGS; + + if (guest_endpoint) { + return qp_alloc_guest_work(handle, produce_q, + produce_size, consume_q, + consume_size, peer, + flags, priv_flags); + } else { + return qp_alloc_host_work(handle, produce_q, + produce_size, consume_q, + consume_size, peer, flags, + priv_flags, wakeup_cb, client_data); + } +} + +/* + * This function implements the host kernel API for detaching from + * a queue pair. + */ +static int qp_detatch_host_work(struct vmci_handle handle) +{ + int result; + struct vmci_ctx *context; + + context = vmci_ctx_get(VMCI_HOST_CONTEXT_ID); + + result = vmci_qp_broker_detach(handle, context); + + vmci_ctx_put(context); + return result; +} + +/* + * Detaches from a VMCI queue_pair. Only checks validity of input argument. + * Real work is done in the host or guest specific function. + */ +static int qp_detatch(struct vmci_handle handle, bool guest_endpoint) +{ + if (vmci_handle_is_invalid(handle)) + return VMCI_ERROR_INVALID_ARGS; + + if (guest_endpoint) + return qp_detatch_guest_work(handle); + else + return qp_detatch_host_work(handle); +} + +/* + * Returns the entry from the head of the list. Assumes that the list is + * locked. + */ +static struct qp_entry *qp_list_get_head(struct qp_list *qp_list) +{ + if (!list_empty(&qp_list->head)) { + struct qp_entry *entry = + list_first_entry(&qp_list->head, struct qp_entry, + list_item); + return entry; + } + + return NULL; +} + +void vmci_qp_broker_exit(void) +{ + struct qp_entry *entry; + struct qp_broker_entry *be; + + mutex_lock(&qp_broker_list.mutex); + + while ((entry = qp_list_get_head(&qp_broker_list))) { + be = (struct qp_broker_entry *)entry; + + qp_list_remove_entry(&qp_broker_list, entry); + kfree(be); + } + + mutex_unlock(&qp_broker_list.mutex); +} + +/* + * Requests that a queue pair be allocated with the VMCI queue + * pair broker. Allocates a queue pair entry if one does not + * exist. Attaches to one if it exists, and retrieves the page + * files backing that queue_pair. Assumes that the queue pair + * broker lock is held. + */ +int vmci_qp_broker_alloc(struct vmci_handle handle, + u32 peer, + u32 flags, + u32 priv_flags, + u64 produce_size, + u64 consume_size, + struct vmci_qp_page_store *page_store, + struct vmci_ctx *context) +{ + return qp_broker_alloc(handle, peer, flags, priv_flags, + produce_size, consume_size, + page_store, context, NULL, NULL, NULL, NULL); +} + +/* + * VMX'en with versions lower than VMCI_VERSION_NOVMVM use a separate + * step to add the UVAs of the VMX mapping of the queue pair. This function + * provides backwards compatibility with such VMX'en, and takes care of + * registering the page store for a queue pair previously allocated by the + * VMX during create or attach. This function will move the queue pair state + * to either from VMCIQBP_CREATED_NO_MEM to VMCIQBP_CREATED_MEM or + * VMCIQBP_ATTACHED_NO_MEM to VMCIQBP_ATTACHED_MEM. If moving to the + * attached state with memory, the queue pair is ready to be used by the + * host peer, and an attached event will be generated. + * + * Assumes that the queue pair broker lock is held. + * + * This function is only used by the hosted platform, since there is no + * issue with backwards compatibility for vmkernel. + */ +int vmci_qp_broker_set_page_store(struct vmci_handle handle, + u64 produce_uva, + u64 consume_uva, + struct vmci_ctx *context) +{ + struct qp_broker_entry *entry; + int result; + const u32 context_id = vmci_ctx_get_id(context); + + if (vmci_handle_is_invalid(handle) || !context || + context_id == VMCI_INVALID_ID) + return VMCI_ERROR_INVALID_ARGS; + + /* + * We only support guest to host queue pairs, so the VMX must + * supply UVAs for the mapped page files. + */ + + if (produce_uva == 0 || consume_uva == 0) + return VMCI_ERROR_INVALID_ARGS; + + mutex_lock(&qp_broker_list.mutex); + + if (!vmci_ctx_qp_exists(context, handle)) { + pr_warn("Context (ID=0x%x) not attached to queue pair (handle=0x%x:0x%x)\n", + context_id, handle.context, handle.resource); + result = VMCI_ERROR_NOT_FOUND; + goto out; + } + + entry = qp_broker_handle_to_entry(handle); + if (!entry) { + result = VMCI_ERROR_NOT_FOUND; + goto out; + } + + /* + * If I'm the owner then I can set the page store. + * + * Or, if a host created the queue_pair and I'm the attached peer + * then I can set the page store. + */ + if (entry->create_id != context_id && + (entry->create_id != VMCI_HOST_CONTEXT_ID || + entry->attach_id != context_id)) { + result = VMCI_ERROR_QUEUEPAIR_NOTOWNER; + goto out; + } + + if (entry->state != VMCIQPB_CREATED_NO_MEM && + entry->state != VMCIQPB_ATTACHED_NO_MEM) { + result = VMCI_ERROR_UNAVAILABLE; + goto out; + } + + result = qp_host_get_user_memory(produce_uva, consume_uva, + entry->produce_q, entry->consume_q); + if (result < VMCI_SUCCESS) + goto out; + + result = qp_host_map_queues(entry->produce_q, entry->consume_q); + if (result < VMCI_SUCCESS) { + qp_host_unregister_user_memory(entry->produce_q, + entry->consume_q); + goto out; + } + + if (entry->state == VMCIQPB_CREATED_NO_MEM) + entry->state = VMCIQPB_CREATED_MEM; + else + entry->state = VMCIQPB_ATTACHED_MEM; + + entry->vmci_page_files = true; + + if (entry->state == VMCIQPB_ATTACHED_MEM) { + result = + qp_notify_peer(true, handle, context_id, entry->create_id); + if (result < VMCI_SUCCESS) { + pr_warn("Failed to notify peer (ID=0x%x) of attach to queue pair (handle=0x%x:0x%x)\n", + entry->create_id, entry->qp.handle.context, + entry->qp.handle.resource); + } + } + + result = VMCI_SUCCESS; + out: + mutex_unlock(&qp_broker_list.mutex); + return result; +} + +/* + * Resets saved queue headers for the given QP broker + * entry. Should be used when guest memory becomes available + * again, or the guest detaches. + */ +static void qp_reset_saved_headers(struct qp_broker_entry *entry) +{ + entry->produce_q->saved_header = NULL; + entry->consume_q->saved_header = NULL; +} + +/* + * The main entry point for detaching from a queue pair registered with the + * queue pair broker. If more than one endpoint is attached to the queue + * pair, the first endpoint will mainly decrement a reference count and + * generate a notification to its peer. The last endpoint will clean up + * the queue pair state registered with the broker. + * + * When a guest endpoint detaches, it will unmap and unregister the guest + * memory backing the queue pair. If the host is still attached, it will + * no longer be able to access the queue pair content. + * + * If the queue pair is already in a state where there is no memory + * registered for the queue pair (any *_NO_MEM state), it will transition to + * the VMCIQPB_SHUTDOWN_NO_MEM state. This will also happen, if a guest + * endpoint is the first of two endpoints to detach. If the host endpoint is + * the first out of two to detach, the queue pair will move to the + * VMCIQPB_SHUTDOWN_MEM state. + */ +int vmci_qp_broker_detach(struct vmci_handle handle, struct vmci_ctx *context) +{ + struct qp_broker_entry *entry; + const u32 context_id = vmci_ctx_get_id(context); + u32 peer_id; + bool is_local = false; + int result; + + if (vmci_handle_is_invalid(handle) || !context || + context_id == VMCI_INVALID_ID) { + return VMCI_ERROR_INVALID_ARGS; + } + + mutex_lock(&qp_broker_list.mutex); + + if (!vmci_ctx_qp_exists(context, handle)) { + pr_devel("Context (ID=0x%x) not attached to queue pair (handle=0x%x:0x%x)\n", + context_id, handle.context, handle.resource); + result = VMCI_ERROR_NOT_FOUND; + goto out; + } + + entry = qp_broker_handle_to_entry(handle); + if (!entry) { + pr_devel("Context (ID=0x%x) reports being attached to queue pair(handle=0x%x:0x%x) that isn't present in broker\n", + context_id, handle.context, handle.resource); + result = VMCI_ERROR_NOT_FOUND; + goto out; + } + + if (context_id != entry->create_id && context_id != entry->attach_id) { + result = VMCI_ERROR_QUEUEPAIR_NOTATTACHED; + goto out; + } + + if (context_id == entry->create_id) { + peer_id = entry->attach_id; + entry->create_id = VMCI_INVALID_ID; + } else { + peer_id = entry->create_id; + entry->attach_id = VMCI_INVALID_ID; + } + entry->qp.ref_count--; + + is_local = entry->qp.flags & VMCI_QPFLAG_LOCAL; + + if (context_id != VMCI_HOST_CONTEXT_ID) { + bool headers_mapped; + + /* + * Pre NOVMVM vmx'en may detach from a queue pair + * before setting the page store, and in that case + * there is no user memory to detach from. Also, more + * recent VMX'en may detach from a queue pair in the + * quiesced state. + */ + + qp_acquire_queue_mutex(entry->produce_q); + headers_mapped = entry->produce_q->q_header || + entry->consume_q->q_header; + if (QPBROKERSTATE_HAS_MEM(entry)) { + result = + qp_host_unmap_queues(INVALID_VMCI_GUEST_MEM_ID, + entry->produce_q, + entry->consume_q); + if (result < VMCI_SUCCESS) + pr_warn("Failed to unmap queue headers for queue pair (handle=0x%x:0x%x,result=%d)\n", + handle.context, handle.resource, + result); + + if (entry->vmci_page_files) + qp_host_unregister_user_memory(entry->produce_q, + entry-> + consume_q); + else + qp_host_unregister_user_memory(entry->produce_q, + entry-> + consume_q); + + } + + if (!headers_mapped) + qp_reset_saved_headers(entry); + + qp_release_queue_mutex(entry->produce_q); + + if (!headers_mapped && entry->wakeup_cb) + entry->wakeup_cb(entry->client_data); + + } else { + if (entry->wakeup_cb) { + entry->wakeup_cb = NULL; + entry->client_data = NULL; + } + } + + if (entry->qp.ref_count == 0) { + qp_list_remove_entry(&qp_broker_list, &entry->qp); + + if (is_local) + kfree(entry->local_mem); + + qp_cleanup_queue_mutex(entry->produce_q, entry->consume_q); + qp_host_free_queue(entry->produce_q, entry->qp.produce_size); + qp_host_free_queue(entry->consume_q, entry->qp.consume_size); + /* Unlink from resource hash table and free callback */ + vmci_resource_remove(&entry->resource); + + kfree(entry); + + vmci_ctx_qp_destroy(context, handle); + } else { + qp_notify_peer(false, handle, context_id, peer_id); + if (context_id == VMCI_HOST_CONTEXT_ID && + QPBROKERSTATE_HAS_MEM(entry)) { + entry->state = VMCIQPB_SHUTDOWN_MEM; + } else { + entry->state = VMCIQPB_SHUTDOWN_NO_MEM; + } + + if (!is_local) + vmci_ctx_qp_destroy(context, handle); + + } + result = VMCI_SUCCESS; + out: + mutex_unlock(&qp_broker_list.mutex); + return result; +} + +/* + * Establishes the necessary mappings for a queue pair given a + * reference to the queue pair guest memory. This is usually + * called when a guest is unquiesced and the VMX is allowed to + * map guest memory once again. + */ +int vmci_qp_broker_map(struct vmci_handle handle, + struct vmci_ctx *context, + u64 guest_mem) +{ + struct qp_broker_entry *entry; + const u32 context_id = vmci_ctx_get_id(context); + bool is_local = false; + int result; + + if (vmci_handle_is_invalid(handle) || !context || + context_id == VMCI_INVALID_ID) + return VMCI_ERROR_INVALID_ARGS; + + mutex_lock(&qp_broker_list.mutex); + + if (!vmci_ctx_qp_exists(context, handle)) { + pr_devel("Context (ID=0x%x) not attached to queue pair (handle=0x%x:0x%x)\n", + context_id, handle.context, handle.resource); + result = VMCI_ERROR_NOT_FOUND; + goto out; + } + + entry = qp_broker_handle_to_entry(handle); + if (!entry) { + pr_devel("Context (ID=0x%x) reports being attached to queue pair (handle=0x%x:0x%x) that isn't present in broker\n", + context_id, handle.context, handle.resource); + result = VMCI_ERROR_NOT_FOUND; + goto out; + } + + if (context_id != entry->create_id && context_id != entry->attach_id) { + result = VMCI_ERROR_QUEUEPAIR_NOTATTACHED; + goto out; + } + + is_local = entry->qp.flags & VMCI_QPFLAG_LOCAL; + result = VMCI_SUCCESS; + + if (context_id != VMCI_HOST_CONTEXT_ID) { + struct vmci_qp_page_store page_store; + + page_store.pages = guest_mem; + page_store.len = QPE_NUM_PAGES(entry->qp); + + qp_acquire_queue_mutex(entry->produce_q); + qp_reset_saved_headers(entry); + result = + qp_host_register_user_memory(&page_store, + entry->produce_q, + entry->consume_q); + qp_release_queue_mutex(entry->produce_q); + if (result == VMCI_SUCCESS) { + /* Move state from *_NO_MEM to *_MEM */ + + entry->state++; + + if (entry->wakeup_cb) + entry->wakeup_cb(entry->client_data); + } + } + + out: + mutex_unlock(&qp_broker_list.mutex); + return result; +} + +/* + * Saves a snapshot of the queue headers for the given QP broker + * entry. Should be used when guest memory is unmapped. + * Results: + * VMCI_SUCCESS on success, appropriate error code if guest memory + * can't be accessed.. + */ +static int qp_save_headers(struct qp_broker_entry *entry) +{ + int result; + + if (entry->produce_q->saved_header != NULL && + entry->consume_q->saved_header != NULL) { + /* + * If the headers have already been saved, we don't need to do + * it again, and we don't want to map in the headers + * unnecessarily. + */ + + return VMCI_SUCCESS; + } + + if (NULL == entry->produce_q->q_header || + NULL == entry->consume_q->q_header) { + result = qp_host_map_queues(entry->produce_q, entry->consume_q); + if (result < VMCI_SUCCESS) + return result; + } + + memcpy(&entry->saved_produce_q, entry->produce_q->q_header, + sizeof(entry->saved_produce_q)); + entry->produce_q->saved_header = &entry->saved_produce_q; + memcpy(&entry->saved_consume_q, entry->consume_q->q_header, + sizeof(entry->saved_consume_q)); + entry->consume_q->saved_header = &entry->saved_consume_q; + + return VMCI_SUCCESS; +} + +/* + * Removes all references to the guest memory of a given queue pair, and + * will move the queue pair from state *_MEM to *_NO_MEM. It is usually + * called when a VM is being quiesced where access to guest memory should + * avoided. + */ +int vmci_qp_broker_unmap(struct vmci_handle handle, + struct vmci_ctx *context, + u32 gid) +{ + struct qp_broker_entry *entry; + const u32 context_id = vmci_ctx_get_id(context); + bool is_local = false; + int result; + + if (vmci_handle_is_invalid(handle) || !context || + context_id == VMCI_INVALID_ID) + return VMCI_ERROR_INVALID_ARGS; + + mutex_lock(&qp_broker_list.mutex); + + if (!vmci_ctx_qp_exists(context, handle)) { + pr_devel("Context (ID=0x%x) not attached to queue pair (handle=0x%x:0x%x)\n", + context_id, handle.context, handle.resource); + result = VMCI_ERROR_NOT_FOUND; + goto out; + } + + entry = qp_broker_handle_to_entry(handle); + if (!entry) { + pr_devel("Context (ID=0x%x) reports being attached to queue pair (handle=0x%x:0x%x) that isn't present in broker\n", + context_id, handle.context, handle.resource); + result = VMCI_ERROR_NOT_FOUND; + goto out; + } + + if (context_id != entry->create_id && context_id != entry->attach_id) { + result = VMCI_ERROR_QUEUEPAIR_NOTATTACHED; + goto out; + } + + is_local = entry->qp.flags & VMCI_QPFLAG_LOCAL; + + if (context_id != VMCI_HOST_CONTEXT_ID) { + qp_acquire_queue_mutex(entry->produce_q); + result = qp_save_headers(entry); + if (result < VMCI_SUCCESS) + pr_warn("Failed to save queue headers for queue pair (handle=0x%x:0x%x,result=%d)\n", + handle.context, handle.resource, result); + + qp_host_unmap_queues(gid, entry->produce_q, entry->consume_q); + + /* + * On hosted, when we unmap queue pairs, the VMX will also + * unmap the guest memory, so we invalidate the previously + * registered memory. If the queue pair is mapped again at a + * later point in time, we will need to reregister the user + * memory with a possibly new user VA. + */ + qp_host_unregister_user_memory(entry->produce_q, + entry->consume_q); + + /* + * Move state from *_MEM to *_NO_MEM. + */ + entry->state--; + + qp_release_queue_mutex(entry->produce_q); + } + + result = VMCI_SUCCESS; + + out: + mutex_unlock(&qp_broker_list.mutex); + return result; +} + +/* + * Destroys all guest queue pair endpoints. If active guest queue + * pairs still exist, hypercalls to attempt detach from these + * queue pairs will be made. Any failure to detach is silently + * ignored. + */ +void vmci_qp_guest_endpoints_exit(void) +{ + struct qp_entry *entry; + struct qp_guest_endpoint *ep; + + mutex_lock(&qp_guest_endpoints.mutex); + + while ((entry = qp_list_get_head(&qp_guest_endpoints))) { + ep = (struct qp_guest_endpoint *)entry; + + /* Don't make a hypercall for local queue_pairs. */ + if (!(entry->flags & VMCI_QPFLAG_LOCAL)) + qp_detatch_hypercall(entry->handle); + + /* We cannot fail the exit, so let's reset ref_count. */ + entry->ref_count = 0; + qp_list_remove_entry(&qp_guest_endpoints, entry); + + qp_guest_endpoint_destroy(ep); + } + + mutex_unlock(&qp_guest_endpoints.mutex); +} + +/* + * Helper routine that will lock the queue pair before subsequent + * operations. + * Note: Non-blocking on the host side is currently only implemented in ESX. + * Since non-blocking isn't yet implemented on the host personality we + * have no reason to acquire a spin lock. So to avoid the use of an + * unnecessary lock only acquire the mutex if we can block. + * Note: It is assumed that QPFLAG_PINNED implies QPFLAG_NONBLOCK. Therefore + * we can use the same locking function for access to both the queue + * and the queue headers as it is the same logic. Assert this behvior. + */ +static void qp_lock(const struct vmci_qp *qpair) +{ + if (vmci_can_block(qpair->flags)) + qp_acquire_queue_mutex(qpair->produce_q); +} + +/* + * Helper routine that unlocks the queue pair after calling + * qp_lock. Respects non-blocking and pinning flags. + */ +static void qp_unlock(const struct vmci_qp *qpair) +{ + if (vmci_can_block(qpair->flags)) + qp_release_queue_mutex(qpair->produce_q); +} + +/* + * The queue headers may not be mapped at all times. If a queue is + * currently not mapped, it will be attempted to do so. + */ +static int qp_map_queue_headers(struct vmci_queue *produce_q, + struct vmci_queue *consume_q, + bool can_block) +{ + int result; + + if (NULL == produce_q->q_header || NULL == consume_q->q_header) { + if (can_block) + result = qp_host_map_queues(produce_q, consume_q); + else + result = VMCI_ERROR_QUEUEPAIR_NOT_READY; + + if (result < VMCI_SUCCESS) + return (produce_q->saved_header && + consume_q->saved_header) ? + VMCI_ERROR_QUEUEPAIR_NOT_READY : + VMCI_ERROR_QUEUEPAIR_NOTATTACHED; + } + + return VMCI_SUCCESS; +} + +/* + * Helper routine that will retrieve the produce and consume + * headers of a given queue pair. If the guest memory of the + * queue pair is currently not available, the saved queue headers + * will be returned, if these are available. + */ +static int qp_get_queue_headers(const struct vmci_qp *qpair, + struct vmci_queue_header **produce_q_header, + struct vmci_queue_header **consume_q_header) +{ + int result; + + result = qp_map_queue_headers(qpair->produce_q, qpair->consume_q, + vmci_can_block(qpair->flags)); + if (result == VMCI_SUCCESS) { + *produce_q_header = qpair->produce_q->q_header; + *consume_q_header = qpair->consume_q->q_header; + } else if (qpair->produce_q->saved_header && + qpair->consume_q->saved_header) { + *produce_q_header = qpair->produce_q->saved_header; + *consume_q_header = qpair->consume_q->saved_header; + result = VMCI_SUCCESS; + } + + return result; +} + +/* + * Callback from VMCI queue pair broker indicating that a queue + * pair that was previously not ready, now either is ready or + * gone forever. + */ +static int qp_wakeup_cb(void *client_data) +{ + struct vmci_qp *qpair = (struct vmci_qp *)client_data; + + qp_lock(qpair); + while (qpair->blocked > 0) { + qpair->blocked--; + qpair->generation++; + wake_up(&qpair->event); + } + qp_unlock(qpair); + + return VMCI_SUCCESS; +} + +/* + * Makes the calling thread wait for the queue pair to become + * ready for host side access. Returns true when thread is + * woken up after queue pair state change, false otherwise. + */ +static bool qp_wait_for_ready_queue(struct vmci_qp *qpair) +{ + unsigned int generation; + + if (qpair->flags & VMCI_QPFLAG_NONBLOCK) + return false; + + qpair->blocked++; + generation = qpair->generation; + qp_unlock(qpair); + wait_event(qpair->event, generation != qpair->generation); + qp_lock(qpair); + + return true; +} + +/* + * Enqueues a given buffer to the produce queue using the provided + * function. As many bytes as possible (space available in the queue) + * are enqueued. Assumes the queue->mutex has been acquired. Returns + * VMCI_ERROR_QUEUEPAIR_NOSPACE if no space was available to enqueue + * data, VMCI_ERROR_INVALID_SIZE, if any queue pointer is outside the + * queue (as defined by the queue size), VMCI_ERROR_INVALID_ARGS, if + * an error occured when accessing the buffer, + * VMCI_ERROR_QUEUEPAIR_NOTATTACHED, if the queue pair pages aren't + * available. Otherwise, the number of bytes written to the queue is + * returned. Updates the tail pointer of the produce queue. + */ +static ssize_t qp_enqueue_locked(struct vmci_queue *produce_q, + struct vmci_queue *consume_q, + const u64 produce_q_size, + const void *buf, + size_t buf_size, + vmci_memcpy_to_queue_func memcpy_to_queue, + bool can_block) +{ + s64 free_space; + u64 tail; + size_t written; + ssize_t result; + + result = qp_map_queue_headers(produce_q, consume_q, can_block); + if (unlikely(result != VMCI_SUCCESS)) + return result; + + free_space = vmci_q_header_free_space(produce_q->q_header, + consume_q->q_header, + produce_q_size); + if (free_space == 0) + return VMCI_ERROR_QUEUEPAIR_NOSPACE; + + if (free_space < VMCI_SUCCESS) + return (ssize_t) free_space; + + written = (size_t) (free_space > buf_size ? buf_size : free_space); + tail = vmci_q_header_producer_tail(produce_q->q_header); + if (likely(tail + written < produce_q_size)) { + result = memcpy_to_queue(produce_q, tail, buf, 0, written); + } else { + /* Tail pointer wraps around. */ + + const size_t tmp = (size_t) (produce_q_size - tail); + + result = memcpy_to_queue(produce_q, tail, buf, 0, tmp); + if (result >= VMCI_SUCCESS) + result = memcpy_to_queue(produce_q, 0, buf, tmp, + written - tmp); + } + + if (result < VMCI_SUCCESS) + return result; + + vmci_q_header_add_producer_tail(produce_q->q_header, written, + produce_q_size); + return written; +} + +/* + * Dequeues data (if available) from the given consume queue. Writes data + * to the user provided buffer using the provided function. + * Assumes the queue->mutex has been acquired. + * Results: + * VMCI_ERROR_QUEUEPAIR_NODATA if no data was available to dequeue. + * VMCI_ERROR_INVALID_SIZE, if any queue pointer is outside the queue + * (as defined by the queue size). + * VMCI_ERROR_INVALID_ARGS, if an error occured when accessing the buffer. + * Otherwise the number of bytes dequeued is returned. + * Side effects: + * Updates the head pointer of the consume queue. + */ +static ssize_t qp_dequeue_locked(struct vmci_queue *produce_q, + struct vmci_queue *consume_q, + const u64 consume_q_size, + void *buf, + size_t buf_size, + vmci_memcpy_from_queue_func memcpy_from_queue, + bool update_consumer, + bool can_block) +{ + s64 buf_ready; + u64 head; + size_t read; + ssize_t result; + + result = qp_map_queue_headers(produce_q, consume_q, can_block); + if (unlikely(result != VMCI_SUCCESS)) + return result; + + buf_ready = vmci_q_header_buf_ready(consume_q->q_header, + produce_q->q_header, + consume_q_size); + if (buf_ready == 0) + return VMCI_ERROR_QUEUEPAIR_NODATA; + + if (buf_ready < VMCI_SUCCESS) + return (ssize_t) buf_ready; + + read = (size_t) (buf_ready > buf_size ? buf_size : buf_ready); + head = vmci_q_header_consumer_head(produce_q->q_header); + if (likely(head + read < consume_q_size)) { + result = memcpy_from_queue(buf, 0, consume_q, head, read); + } else { + /* Head pointer wraps around. */ + + const size_t tmp = (size_t) (consume_q_size - head); + + result = memcpy_from_queue(buf, 0, consume_q, head, tmp); + if (result >= VMCI_SUCCESS) + result = memcpy_from_queue(buf, tmp, consume_q, 0, + read - tmp); + + } + + if (result < VMCI_SUCCESS) + return result; + + if (update_consumer) + vmci_q_header_add_consumer_head(produce_q->q_header, + read, consume_q_size); + + return read; +} + +/* + * vmci_qpair_alloc() - Allocates a queue pair. + * @qpair: Pointer for the new vmci_qp struct. + * @handle: Handle to track the resource. + * @produce_qsize: Desired size of the producer queue. + * @consume_qsize: Desired size of the consumer queue. + * @peer: ContextID of the peer. + * @flags: VMCI flags. + * @priv_flags: VMCI priviledge flags. + * + * This is the client interface for allocating the memory for a + * vmci_qp structure and then attaching to the underlying + * queue. If an error occurs allocating the memory for the + * vmci_qp structure no attempt is made to attach. If an + * error occurs attaching, then the structure is freed. + */ +int vmci_qpair_alloc(struct vmci_qp **qpair, + struct vmci_handle *handle, + u64 produce_qsize, + u64 consume_qsize, + u32 peer, + u32 flags, + u32 priv_flags) +{ + struct vmci_qp *my_qpair; + int retval; + struct vmci_handle src = VMCI_INVALID_HANDLE; + struct vmci_handle dst = vmci_make_handle(peer, VMCI_INVALID_ID); + enum vmci_route route; + vmci_event_release_cb wakeup_cb; + void *client_data; + + /* + * Restrict the size of a queuepair. The device already + * enforces a limit on the total amount of memory that can be + * allocated to queuepairs for a guest. However, we try to + * allocate this memory before we make the queuepair + * allocation hypercall. On Linux, we allocate each page + * separately, which means rather than fail, the guest will + * thrash while it tries to allocate, and will become + * increasingly unresponsive to the point where it appears to + * be hung. So we place a limit on the size of an individual + * queuepair here, and leave the device to enforce the + * restriction on total queuepair memory. (Note that this + * doesn't prevent all cases; a user with only this much + * physical memory could still get into trouble.) The error + * used by the device is NO_RESOURCES, so use that here too. + */ + + if (produce_qsize + consume_qsize < max(produce_qsize, consume_qsize) || + produce_qsize + consume_qsize > VMCI_MAX_GUEST_QP_MEMORY) + return VMCI_ERROR_NO_RESOURCES; + + retval = vmci_route(&src, &dst, false, &route); + if (retval < VMCI_SUCCESS) + route = vmci_guest_code_active() ? + VMCI_ROUTE_AS_GUEST : VMCI_ROUTE_AS_HOST; + + /* If NONBLOCK or PINNED is set, we better be the guest personality. */ + if ((!vmci_can_block(flags) || vmci_qp_pinned(flags)) && + VMCI_ROUTE_AS_GUEST != route) { + pr_devel("Not guest personality w/ NONBLOCK OR PINNED set"); + return VMCI_ERROR_INVALID_ARGS; + } + + /* + * Limit the size of pinned QPs and check sanity. + * + * Pinned pages implies non-blocking mode. Mutexes aren't acquired + * when the NONBLOCK flag is set in qpair code; and also should not be + * acquired when the PINNED flagged is set. Since pinning pages + * implies we want speed, it makes no sense not to have NONBLOCK + * set if PINNED is set. Hence enforce this implication. + */ + if (vmci_qp_pinned(flags)) { + if (vmci_can_block(flags)) { + pr_err("Attempted to enable pinning w/o non-blocking"); + return VMCI_ERROR_INVALID_ARGS; + } + + if (produce_qsize + consume_qsize > VMCI_MAX_PINNED_QP_MEMORY) + return VMCI_ERROR_NO_RESOURCES; + } + + my_qpair = kzalloc(sizeof(*my_qpair), GFP_KERNEL); + if (!my_qpair) + return VMCI_ERROR_NO_MEM; + + my_qpair->produce_q_size = produce_qsize; + my_qpair->consume_q_size = consume_qsize; + my_qpair->peer = peer; + my_qpair->flags = flags; + my_qpair->priv_flags = priv_flags; + + wakeup_cb = NULL; + client_data = NULL; + + if (VMCI_ROUTE_AS_HOST == route) { + my_qpair->guest_endpoint = false; + if (!(flags & VMCI_QPFLAG_LOCAL)) { + my_qpair->blocked = 0; + my_qpair->generation = 0; + init_waitqueue_head(&my_qpair->event); + wakeup_cb = qp_wakeup_cb; + client_data = (void *)my_qpair; + } + } else { + my_qpair->guest_endpoint = true; + } + + retval = vmci_qp_alloc(handle, + &my_qpair->produce_q, + my_qpair->produce_q_size, + &my_qpair->consume_q, + my_qpair->consume_q_size, + my_qpair->peer, + my_qpair->flags, + my_qpair->priv_flags, + my_qpair->guest_endpoint, + wakeup_cb, client_data); + + if (retval < VMCI_SUCCESS) { + kfree(my_qpair); + return retval; + } + + *qpair = my_qpair; + my_qpair->handle = *handle; + + return retval; +} +EXPORT_SYMBOL_GPL(vmci_qpair_alloc); + +/* + * vmci_qpair_detach() - Detatches the client from a queue pair. + * @qpair: Reference of a pointer to the qpair struct. + * + * This is the client interface for detaching from a VMCIQPair. + * Note that this routine will free the memory allocated for the + * vmci_qp structure too. + */ +int vmci_qpair_detach(struct vmci_qp **qpair) +{ + int result; + struct vmci_qp *old_qpair; + + if (!qpair || !(*qpair)) + return VMCI_ERROR_INVALID_ARGS; + + old_qpair = *qpair; + result = qp_detatch(old_qpair->handle, old_qpair->guest_endpoint); + + /* + * The guest can fail to detach for a number of reasons, and + * if it does so, it will cleanup the entry (if there is one). + * The host can fail too, but it won't cleanup the entry + * immediately, it will do that later when the context is + * freed. Either way, we need to release the qpair struct + * here; there isn't much the caller can do, and we don't want + * to leak. + */ + + memset(old_qpair, 0, sizeof(*old_qpair)); + old_qpair->handle = VMCI_INVALID_HANDLE; + old_qpair->peer = VMCI_INVALID_ID; + kfree(old_qpair); + *qpair = NULL; + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_detach); + +/* + * vmci_qpair_get_produce_indexes() - Retrieves the indexes of the producer. + * @qpair: Pointer to the queue pair struct. + * @producer_tail: Reference used for storing producer tail index. + * @consumer_head: Reference used for storing the consumer head index. + * + * This is the client interface for getting the current indexes of the + * QPair from the point of the view of the caller as the producer. + */ +int vmci_qpair_get_produce_indexes(const struct vmci_qp *qpair, + u64 *producer_tail, + u64 *consumer_head) +{ + struct vmci_queue_header *produce_q_header; + struct vmci_queue_header *consume_q_header; + int result; + + if (!qpair) + return VMCI_ERROR_INVALID_ARGS; + + qp_lock(qpair); + result = + qp_get_queue_headers(qpair, &produce_q_header, &consume_q_header); + if (result == VMCI_SUCCESS) + vmci_q_header_get_pointers(produce_q_header, consume_q_header, + producer_tail, consumer_head); + qp_unlock(qpair); + + if (result == VMCI_SUCCESS && + ((producer_tail && *producer_tail >= qpair->produce_q_size) || + (consumer_head && *consumer_head >= qpair->produce_q_size))) + return VMCI_ERROR_INVALID_SIZE; + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_get_produce_indexes); + +/* + * vmci_qpair_get_consume_indexes() - Retrieves the indexes of the comsumer. + * @qpair: Pointer to the queue pair struct. + * @consumer_tail: Reference used for storing consumer tail index. + * @producer_head: Reference used for storing the producer head index. + * + * This is the client interface for getting the current indexes of the + * QPair from the point of the view of the caller as the consumer. + */ +int vmci_qpair_get_consume_indexes(const struct vmci_qp *qpair, + u64 *consumer_tail, + u64 *producer_head) +{ + struct vmci_queue_header *produce_q_header; + struct vmci_queue_header *consume_q_header; + int result; + + if (!qpair) + return VMCI_ERROR_INVALID_ARGS; + + qp_lock(qpair); + result = + qp_get_queue_headers(qpair, &produce_q_header, &consume_q_header); + if (result == VMCI_SUCCESS) + vmci_q_header_get_pointers(consume_q_header, produce_q_header, + consumer_tail, producer_head); + qp_unlock(qpair); + + if (result == VMCI_SUCCESS && + ((consumer_tail && *consumer_tail >= qpair->consume_q_size) || + (producer_head && *producer_head >= qpair->consume_q_size))) + return VMCI_ERROR_INVALID_SIZE; + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_get_consume_indexes); + +/* + * vmci_qpair_produce_free_space() - Retrieves free space in producer queue. + * @qpair: Pointer to the queue pair struct. + * + * This is the client interface for getting the amount of free + * space in the QPair from the point of the view of the caller as + * the producer which is the common case. Returns < 0 if err, else + * available bytes into which data can be enqueued if > 0. + */ +s64 vmci_qpair_produce_free_space(const struct vmci_qp *qpair) +{ + struct vmci_queue_header *produce_q_header; + struct vmci_queue_header *consume_q_header; + s64 result; + + if (!qpair) + return VMCI_ERROR_INVALID_ARGS; + + qp_lock(qpair); + result = + qp_get_queue_headers(qpair, &produce_q_header, &consume_q_header); + if (result == VMCI_SUCCESS) + result = vmci_q_header_free_space(produce_q_header, + consume_q_header, + qpair->produce_q_size); + else + result = 0; + + qp_unlock(qpair); + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_produce_free_space); + +/* + * vmci_qpair_consume_free_space() - Retrieves free space in consumer queue. + * @qpair: Pointer to the queue pair struct. + * + * This is the client interface for getting the amount of free + * space in the QPair from the point of the view of the caller as + * the consumer which is not the common case. Returns < 0 if err, else + * available bytes into which data can be enqueued if > 0. + */ +s64 vmci_qpair_consume_free_space(const struct vmci_qp *qpair) +{ + struct vmci_queue_header *produce_q_header; + struct vmci_queue_header *consume_q_header; + s64 result; + + if (!qpair) + return VMCI_ERROR_INVALID_ARGS; + + qp_lock(qpair); + result = + qp_get_queue_headers(qpair, &produce_q_header, &consume_q_header); + if (result == VMCI_SUCCESS) + result = vmci_q_header_free_space(consume_q_header, + produce_q_header, + qpair->consume_q_size); + else + result = 0; + + qp_unlock(qpair); + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_consume_free_space); + +/* + * vmci_qpair_produce_buf_ready() - Gets bytes ready to read from + * producer queue. + * @qpair: Pointer to the queue pair struct. + * + * This is the client interface for getting the amount of + * enqueued data in the QPair from the point of the view of the + * caller as the producer which is not the common case. Returns < 0 if err, + * else available bytes that may be read. + */ +s64 vmci_qpair_produce_buf_ready(const struct vmci_qp *qpair) +{ + struct vmci_queue_header *produce_q_header; + struct vmci_queue_header *consume_q_header; + s64 result; + + if (!qpair) + return VMCI_ERROR_INVALID_ARGS; + + qp_lock(qpair); + result = + qp_get_queue_headers(qpair, &produce_q_header, &consume_q_header); + if (result == VMCI_SUCCESS) + result = vmci_q_header_buf_ready(produce_q_header, + consume_q_header, + qpair->produce_q_size); + else + result = 0; + + qp_unlock(qpair); + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_produce_buf_ready); + +/* + * vmci_qpair_consume_buf_ready() - Gets bytes ready to read from + * consumer queue. + * @qpair: Pointer to the queue pair struct. + * + * This is the client interface for getting the amount of + * enqueued data in the QPair from the point of the view of the + * caller as the consumer which is the normal case. Returns < 0 if err, + * else available bytes that may be read. + */ +s64 vmci_qpair_consume_buf_ready(const struct vmci_qp *qpair) +{ + struct vmci_queue_header *produce_q_header; + struct vmci_queue_header *consume_q_header; + s64 result; + + if (!qpair) + return VMCI_ERROR_INVALID_ARGS; + + qp_lock(qpair); + result = + qp_get_queue_headers(qpair, &produce_q_header, &consume_q_header); + if (result == VMCI_SUCCESS) + result = vmci_q_header_buf_ready(consume_q_header, + produce_q_header, + qpair->consume_q_size); + else + result = 0; + + qp_unlock(qpair); + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_consume_buf_ready); + +/* + * vmci_qpair_enqueue() - Throw data on the queue. + * @qpair: Pointer to the queue pair struct. + * @buf: Pointer to buffer containing data + * @buf_size: Length of buffer. + * @buf_type: Buffer type (Unused). + * + * This is the client interface for enqueueing data into the queue. + * Returns number of bytes enqueued or < 0 on error. + */ +ssize_t vmci_qpair_enqueue(struct vmci_qp *qpair, + const void *buf, + size_t buf_size, + int buf_type) +{ + ssize_t result; + + if (!qpair || !buf) + return VMCI_ERROR_INVALID_ARGS; + + qp_lock(qpair); + + do { + result = qp_enqueue_locked(qpair->produce_q, + qpair->consume_q, + qpair->produce_q_size, + buf, buf_size, + qp_memcpy_to_queue, + vmci_can_block(qpair->flags)); + + if (result == VMCI_ERROR_QUEUEPAIR_NOT_READY && + !qp_wait_for_ready_queue(qpair)) + result = VMCI_ERROR_WOULD_BLOCK; + + } while (result == VMCI_ERROR_QUEUEPAIR_NOT_READY); + + qp_unlock(qpair); + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_enqueue); + +/* + * vmci_qpair_dequeue() - Get data from the queue. + * @qpair: Pointer to the queue pair struct. + * @buf: Pointer to buffer for the data + * @buf_size: Length of buffer. + * @buf_type: Buffer type (Unused). + * + * This is the client interface for dequeueing data from the queue. + * Returns number of bytes dequeued or < 0 on error. + */ +ssize_t vmci_qpair_dequeue(struct vmci_qp *qpair, + void *buf, + size_t buf_size, + int buf_type) +{ + ssize_t result; + + if (!qpair || !buf) + return VMCI_ERROR_INVALID_ARGS; + + qp_lock(qpair); + + do { + result = qp_dequeue_locked(qpair->produce_q, + qpair->consume_q, + qpair->consume_q_size, + buf, buf_size, + qp_memcpy_from_queue, true, + vmci_can_block(qpair->flags)); + + if (result == VMCI_ERROR_QUEUEPAIR_NOT_READY && + !qp_wait_for_ready_queue(qpair)) + result = VMCI_ERROR_WOULD_BLOCK; + + } while (result == VMCI_ERROR_QUEUEPAIR_NOT_READY); + + qp_unlock(qpair); + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_dequeue); + +/* + * vmci_qpair_peek() - Peek at the data in the queue. + * @qpair: Pointer to the queue pair struct. + * @buf: Pointer to buffer for the data + * @buf_size: Length of buffer. + * @buf_type: Buffer type (Unused on Linux). + * + * This is the client interface for peeking into a queue. (I.e., + * copy data from the queue without updating the head pointer.) + * Returns number of bytes dequeued or < 0 on error. + */ +ssize_t vmci_qpair_peek(struct vmci_qp *qpair, + void *buf, + size_t buf_size, + int buf_type) +{ + ssize_t result; + + if (!qpair || !buf) + return VMCI_ERROR_INVALID_ARGS; + + qp_lock(qpair); + + do { + result = qp_dequeue_locked(qpair->produce_q, + qpair->consume_q, + qpair->consume_q_size, + buf, buf_size, + qp_memcpy_from_queue, false, + vmci_can_block(qpair->flags)); + + if (result == VMCI_ERROR_QUEUEPAIR_NOT_READY && + !qp_wait_for_ready_queue(qpair)) + result = VMCI_ERROR_WOULD_BLOCK; + + } while (result == VMCI_ERROR_QUEUEPAIR_NOT_READY); + + qp_unlock(qpair); + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_peek); + +/* + * vmci_qpair_enquev() - Throw data on the queue using iov. + * @qpair: Pointer to the queue pair struct. + * @iov: Pointer to buffer containing data + * @iov_size: Length of buffer. + * @buf_type: Buffer type (Unused). + * + * This is the client interface for enqueueing data into the queue. + * This function uses IO vectors to handle the work. Returns number + * of bytes enqueued or < 0 on error. + */ +ssize_t vmci_qpair_enquev(struct vmci_qp *qpair, + void *iov, + size_t iov_size, + int buf_type) +{ + ssize_t result; + + if (!qpair || !iov) + return VMCI_ERROR_INVALID_ARGS; + + qp_lock(qpair); + + do { + result = qp_enqueue_locked(qpair->produce_q, + qpair->consume_q, + qpair->produce_q_size, + iov, iov_size, + qp_memcpy_to_queue_iov, + vmci_can_block(qpair->flags)); + + if (result == VMCI_ERROR_QUEUEPAIR_NOT_READY && + !qp_wait_for_ready_queue(qpair)) + result = VMCI_ERROR_WOULD_BLOCK; + + } while (result == VMCI_ERROR_QUEUEPAIR_NOT_READY); + + qp_unlock(qpair); + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_enquev); + +/* + * vmci_qpair_dequev() - Get data from the queue using iov. + * @qpair: Pointer to the queue pair struct. + * @iov: Pointer to buffer for the data + * @iov_size: Length of buffer. + * @buf_type: Buffer type (Unused). + * + * This is the client interface for dequeueing data from the queue. + * This function uses IO vectors to handle the work. Returns number + * of bytes dequeued or < 0 on error. + */ +ssize_t vmci_qpair_dequev(struct vmci_qp *qpair, + void *iov, + size_t iov_size, + int buf_type) +{ + ssize_t result; + + qp_lock(qpair); + + if (!qpair || !iov) + return VMCI_ERROR_INVALID_ARGS; + + do { + result = qp_dequeue_locked(qpair->produce_q, + qpair->consume_q, + qpair->consume_q_size, + iov, iov_size, + qp_memcpy_from_queue_iov, + true, vmci_can_block(qpair->flags)); + + if (result == VMCI_ERROR_QUEUEPAIR_NOT_READY && + !qp_wait_for_ready_queue(qpair)) + result = VMCI_ERROR_WOULD_BLOCK; + + } while (result == VMCI_ERROR_QUEUEPAIR_NOT_READY); + + qp_unlock(qpair); + + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_dequev); + +/* + * vmci_qpair_peekv() - Peek at the data in the queue using iov. + * @qpair: Pointer to the queue pair struct. + * @iov: Pointer to buffer for the data + * @iov_size: Length of buffer. + * @buf_type: Buffer type (Unused on Linux). + * + * This is the client interface for peeking into a queue. (I.e., + * copy data from the queue without updating the head pointer.) + * This function uses IO vectors to handle the work. Returns number + * of bytes peeked or < 0 on error. + */ +ssize_t vmci_qpair_peekv(struct vmci_qp *qpair, + void *iov, + size_t iov_size, + int buf_type) +{ + ssize_t result; + + if (!qpair || !iov) + return VMCI_ERROR_INVALID_ARGS; + + qp_lock(qpair); + + do { + result = qp_dequeue_locked(qpair->produce_q, + qpair->consume_q, + qpair->consume_q_size, + iov, iov_size, + qp_memcpy_from_queue_iov, + false, vmci_can_block(qpair->flags)); + + if (result == VMCI_ERROR_QUEUEPAIR_NOT_READY && + !qp_wait_for_ready_queue(qpair)) + result = VMCI_ERROR_WOULD_BLOCK; + + } while (result == VMCI_ERROR_QUEUEPAIR_NOT_READY); + + qp_unlock(qpair); + return result; +} +EXPORT_SYMBOL_GPL(vmci_qpair_peekv); diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.h b/drivers/misc/vmw_vmci/vmci_queue_pair.h new file mode 100644 index 000000000000..8d8d6a1170c3 --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.h @@ -0,0 +1,191 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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. + */ + +#ifndef _VMCI_QUEUE_PAIR_H_ +#define _VMCI_QUEUE_PAIR_H_ + +#include +#include + +#include "vmci_context.h" + +/* Callback needed for correctly waiting on events. */ +typedef int (*vmci_event_release_cb) (void *client_data); + +/* Guest device port I/O. */ +struct PPNSet { + u64 num_produce_pages; + u64 num_consume_pages; + u32 *produce_ppns; + u32 *consume_ppns; + bool initialized; +}; + +/* VMCIqueue_pairAllocInfo */ +struct vmci_qp_alloc_info { + struct vmci_handle handle; + u32 peer; + u32 flags; + u64 produce_size; + u64 consume_size; + u64 ppn_va; /* Start VA of queue pair PPNs. */ + u64 num_ppns; + s32 result; + u32 version; +}; + +/* VMCIqueue_pairSetVAInfo */ +struct vmci_qp_set_va_info { + struct vmci_handle handle; + u64 va; /* Start VA of queue pair PPNs. */ + u64 num_ppns; + u32 version; + s32 result; +}; + +/* + * For backwards compatibility, here is a version of the + * VMCIqueue_pairPageFileInfo before host support end-points was added. + * Note that the current version of that structure requires VMX to + * pass down the VA of the mapped file. Before host support was added + * there was nothing of the sort. So, when the driver sees the ioctl + * with a parameter that is the sizeof + * VMCIqueue_pairPageFileInfo_NoHostQP then it can infer that the version + * of VMX running can't attach to host end points because it doesn't + * provide the VA of the mapped files. + * + * The Linux driver doesn't get an indication of the size of the + * structure passed down from user space. So, to fix a long standing + * but unfiled bug, the _pad field has been renamed to version. + * Existing versions of VMX always initialize the PageFileInfo + * structure so that _pad, er, version is set to 0. + * + * A version value of 1 indicates that the size of the structure has + * been increased to include two UVA's: produce_uva and consume_uva. + * These UVA's are of the mmap()'d queue contents backing files. + * + * In addition, if when VMX is sending down the + * VMCIqueue_pairPageFileInfo structure it gets an error then it will + * try again with the _NoHostQP version of the file to see if an older + * VMCI kernel module is running. + */ + +/* VMCIqueue_pairPageFileInfo */ +struct vmci_qp_page_file_info { + struct vmci_handle handle; + u64 produce_page_file; /* User VA. */ + u64 consume_page_file; /* User VA. */ + u64 produce_page_file_size; /* Size of the file name array. */ + u64 consume_page_file_size; /* Size of the file name array. */ + s32 result; + u32 version; /* Was _pad. */ + u64 produce_va; /* User VA of the mapped file. */ + u64 consume_va; /* User VA of the mapped file. */ +}; + +/* vmci queuepair detach info */ +struct vmci_qp_dtch_info { + struct vmci_handle handle; + s32 result; + u32 _pad; +}; + +/* + * struct vmci_qp_page_store describes how the memory of a given queue pair + * is backed. When the queue pair is between the host and a guest, the + * page store consists of references to the guest pages. On vmkernel, + * this is a list of PPNs, and on hosted, it is a user VA where the + * queue pair is mapped into the VMX address space. + */ +struct vmci_qp_page_store { + /* Reference to pages backing the queue pair. */ + u64 pages; + /* Length of pageList/virtual addres range (in pages). */ + u32 len; +}; + +/* + * This data type contains the information about a queue. + * There are two queues (hence, queue pairs) per transaction model between a + * pair of end points, A & B. One queue is used by end point A to transmit + * commands and responses to B. The other queue is used by B to transmit + * commands and responses. + * + * struct vmci_queue_kern_if is a per-OS defined Queue structure. It contains + * either a direct pointer to the linear address of the buffer contents or a + * pointer to structures which help the OS locate those data pages. See + * vmciKernelIf.c for each platform for its definition. + */ +struct vmci_queue { + struct vmci_queue_header *q_header; + struct vmci_queue_header *saved_header; + struct vmci_queue_kern_if *kernel_if; +}; + +/* + * Utility function that checks whether the fields of the page + * store contain valid values. + * Result: + * true if the page store is wellformed. false otherwise. + */ +static inline bool +VMCI_QP_PAGESTORE_IS_WELLFORMED(struct vmci_qp_page_store *page_store) +{ + return page_store->len >= 2; +} + +/* + * Helper function to check if the non-blocking flag + * is set for a given queue pair. + */ +static inline bool vmci_can_block(u32 flags) +{ + return !(flags & VMCI_QPFLAG_NONBLOCK); +} + +/* + * Helper function to check if the queue pair is pinned + * into memory. + */ +static inline bool vmci_qp_pinned(u32 flags) +{ + return flags & VMCI_QPFLAG_PINNED; +} + +void vmci_qp_broker_exit(void); +int vmci_qp_broker_alloc(struct vmci_handle handle, u32 peer, + u32 flags, u32 priv_flags, + u64 produce_size, u64 consume_size, + struct vmci_qp_page_store *page_store, + struct vmci_ctx *context); +int vmci_qp_broker_set_page_store(struct vmci_handle handle, + u64 produce_uva, u64 consume_uva, + struct vmci_ctx *context); +int vmci_qp_broker_detach(struct vmci_handle handle, struct vmci_ctx *context); + +void vmci_qp_guest_endpoints_exit(void); + +int vmci_qp_alloc(struct vmci_handle *handle, + struct vmci_queue **produce_q, u64 produce_size, + struct vmci_queue **consume_q, u64 consume_size, + u32 peer, u32 flags, u32 priv_flags, + bool guest_endpoint, vmci_event_release_cb wakeup_cb, + void *client_data); +int vmci_qp_broker_map(struct vmci_handle handle, + struct vmci_ctx *context, u64 guest_mem); +int vmci_qp_broker_unmap(struct vmci_handle handle, + struct vmci_ctx *context, u32 gid); + +#endif /* _VMCI_QUEUE_PAIR_H_ */ -- cgit v1.2.1 From bc63dedb7d46a7d690c6b6edf69136b88af06cc6 Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:55:07 -0800 Subject: VMCI: resource object implementation. VMCI resource tracks all used resources within the vmci code. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_resource.c | 229 ++++++++++++++++++++++++++++++++++ drivers/misc/vmw_vmci/vmci_resource.h | 59 +++++++++ 2 files changed, 288 insertions(+) create mode 100644 drivers/misc/vmw_vmci/vmci_resource.c create mode 100644 drivers/misc/vmw_vmci/vmci_resource.h diff --git a/drivers/misc/vmw_vmci/vmci_resource.c b/drivers/misc/vmw_vmci/vmci_resource.c new file mode 100644 index 000000000000..a196f84a4fd2 --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_resource.c @@ -0,0 +1,229 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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 +#include +#include +#include + +#include "vmci_resource.h" +#include "vmci_driver.h" + + +#define VMCI_RESOURCE_HASH_BITS 7 +#define VMCI_RESOURCE_HASH_BUCKETS (1 << VMCI_RESOURCE_HASH_BITS) + +struct vmci_hash_table { + spinlock_t lock; + struct hlist_head entries[VMCI_RESOURCE_HASH_BUCKETS]; +}; + +static struct vmci_hash_table vmci_resource_table = { + .lock = __SPIN_LOCK_UNLOCKED(vmci_resource_table.lock), +}; + +static unsigned int vmci_resource_hash(struct vmci_handle handle) +{ + return hash_32(handle.resource, VMCI_RESOURCE_HASH_BITS); +} + +/* + * Gets a resource (if one exists) matching given handle from the hash table. + */ +static struct vmci_resource *vmci_resource_lookup(struct vmci_handle handle, + enum vmci_resource_type type) +{ + struct vmci_resource *r, *resource = NULL; + struct hlist_node *node; + unsigned int idx = vmci_resource_hash(handle); + + rcu_read_lock(); + hlist_for_each_entry_rcu(r, node, + &vmci_resource_table.entries[idx], node) { + u32 cid = r->handle.context; + u32 rid = r->handle.resource; + + if (r->type == type && + rid == handle.resource && + (cid == handle.context || cid == VMCI_INVALID_ID)) { + resource = r; + break; + } + } + rcu_read_unlock(); + + return resource; +} + +/* + * Find an unused resource ID and return it. The first + * VMCI_RESERVED_RESOURCE_ID_MAX are reserved so we start from + * its value + 1. + * Returns VMCI resource id on success, VMCI_INVALID_ID on failure. + */ +static u32 vmci_resource_find_id(u32 context_id, + enum vmci_resource_type resource_type) +{ + static u32 resource_id = VMCI_RESERVED_RESOURCE_ID_MAX + 1; + u32 old_rid = resource_id; + u32 current_rid; + + /* + * Generate a unique resource ID. Keep on trying until we wrap around + * in the RID space. + */ + do { + struct vmci_handle handle; + + current_rid = resource_id; + resource_id++; + if (unlikely(resource_id == VMCI_INVALID_ID)) { + /* Skip the reserved rids. */ + resource_id = VMCI_RESERVED_RESOURCE_ID_MAX + 1; + } + + handle = vmci_make_handle(context_id, current_rid); + if (!vmci_resource_lookup(handle, resource_type)) + return current_rid; + } while (resource_id != old_rid); + + return VMCI_INVALID_ID; +} + + +int vmci_resource_add(struct vmci_resource *resource, + enum vmci_resource_type resource_type, + struct vmci_handle handle) + +{ + unsigned int idx; + int result; + + spin_lock(&vmci_resource_table.lock); + + if (handle.resource == VMCI_INVALID_ID) { + handle.resource = vmci_resource_find_id(handle.context, + resource_type); + if (handle.resource == VMCI_INVALID_ID) { + result = VMCI_ERROR_NO_HANDLE; + goto out; + } + } else if (vmci_resource_lookup(handle, resource_type)) { + result = VMCI_ERROR_ALREADY_EXISTS; + goto out; + } + + resource->handle = handle; + resource->type = resource_type; + INIT_HLIST_NODE(&resource->node); + kref_init(&resource->kref); + init_completion(&resource->done); + + idx = vmci_resource_hash(resource->handle); + hlist_add_head_rcu(&resource->node, &vmci_resource_table.entries[idx]); + + result = VMCI_SUCCESS; + +out: + spin_unlock(&vmci_resource_table.lock); + return result; +} + +void vmci_resource_remove(struct vmci_resource *resource) +{ + struct vmci_handle handle = resource->handle; + unsigned int idx = vmci_resource_hash(handle); + struct vmci_resource *r; + struct hlist_node *node; + + /* Remove resource from hash table. */ + spin_lock(&vmci_resource_table.lock); + + hlist_for_each_entry(r, node, &vmci_resource_table.entries[idx], node) { + if (vmci_handle_is_equal(r->handle, resource->handle)) { + hlist_del_init_rcu(&r->node); + break; + } + } + + spin_unlock(&vmci_resource_table.lock); + synchronize_rcu(); + + vmci_resource_put(resource); + wait_for_completion(&resource->done); +} + +struct vmci_resource * +vmci_resource_by_handle(struct vmci_handle resource_handle, + enum vmci_resource_type resource_type) +{ + struct vmci_resource *r, *resource = NULL; + + rcu_read_lock(); + + r = vmci_resource_lookup(resource_handle, resource_type); + if (r && + (resource_type == r->type || + resource_type == VMCI_RESOURCE_TYPE_ANY)) { + resource = vmci_resource_get(r); + } + + rcu_read_unlock(); + + return resource; +} + +/* + * Get a reference to given resource. + */ +struct vmci_resource *vmci_resource_get(struct vmci_resource *resource) +{ + kref_get(&resource->kref); + + return resource; +} + +static void vmci_release_resource(struct kref *kref) +{ + struct vmci_resource *resource = + container_of(kref, struct vmci_resource, kref); + + /* Verify the resource has been unlinked from hash table */ + WARN_ON(!hlist_unhashed(&resource->node)); + + /* Signal that container of this resource can now be destroyed */ + complete(&resource->done); +} + +/* + * Resource's release function will get called if last reference. + * If it is the last reference, then we are sure that nobody else + * can increment the count again (it's gone from the resource hash + * table), so there's no need for locking here. + */ +int vmci_resource_put(struct vmci_resource *resource) +{ + /* + * We propagate the information back to caller in case it wants to know + * whether entry was freed. + */ + return kref_put(&resource->kref, vmci_release_resource) ? + VMCI_SUCCESS_ENTRY_DEAD : VMCI_SUCCESS; +} + +struct vmci_handle vmci_resource_handle(struct vmci_resource *resource) +{ + return resource->handle; +} diff --git a/drivers/misc/vmw_vmci/vmci_resource.h b/drivers/misc/vmw_vmci/vmci_resource.h new file mode 100644 index 000000000000..9190cd298bee --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_resource.h @@ -0,0 +1,59 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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. + */ + +#ifndef _VMCI_RESOURCE_H_ +#define _VMCI_RESOURCE_H_ + +#include +#include + +#include "vmci_context.h" + + +enum vmci_resource_type { + VMCI_RESOURCE_TYPE_ANY, + VMCI_RESOURCE_TYPE_API, + VMCI_RESOURCE_TYPE_GROUP, + VMCI_RESOURCE_TYPE_DATAGRAM, + VMCI_RESOURCE_TYPE_DOORBELL, + VMCI_RESOURCE_TYPE_QPAIR_GUEST, + VMCI_RESOURCE_TYPE_QPAIR_HOST +}; + +struct vmci_resource { + struct vmci_handle handle; + enum vmci_resource_type type; + struct hlist_node node; + struct kref kref; + struct completion done; +}; + + +int vmci_resource_add(struct vmci_resource *resource, + enum vmci_resource_type resource_type, + struct vmci_handle handle); + +void vmci_resource_remove(struct vmci_resource *resource); + +struct vmci_resource * +vmci_resource_by_handle(struct vmci_handle resource_handle, + enum vmci_resource_type resource_type); + +struct vmci_resource *vmci_resource_get(struct vmci_resource *resource); +int vmci_resource_put(struct vmci_resource *resource); + +struct vmci_handle vmci_resource_handle(struct vmci_resource *resource); + +#endif /* _VMCI_RESOURCE_H_ */ -- cgit v1.2.1 From e76ffea3216bfea2b46bbc40f322b43430ec3367 Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:55:20 -0800 Subject: VMCI: routing implementation. VMCI routing code is responsible for routing between various hosts/guests as well as routing in nested scenarios. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_route.c | 226 +++++++++++++++++++++++++++++++++++++ drivers/misc/vmw_vmci/vmci_route.h | 30 +++++ 2 files changed, 256 insertions(+) create mode 100644 drivers/misc/vmw_vmci/vmci_route.c create mode 100644 drivers/misc/vmw_vmci/vmci_route.h diff --git a/drivers/misc/vmw_vmci/vmci_route.c b/drivers/misc/vmw_vmci/vmci_route.c new file mode 100644 index 000000000000..91090658b929 --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_route.c @@ -0,0 +1,226 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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 +#include + +#include "vmci_context.h" +#include "vmci_driver.h" +#include "vmci_route.h" + +/* + * Make a routing decision for the given source and destination handles. + * This will try to determine the route using the handles and the available + * devices. Will set the source context if it is invalid. + */ +int vmci_route(struct vmci_handle *src, + const struct vmci_handle *dst, + bool from_guest, + enum vmci_route *route) +{ + bool has_host_device = vmci_host_code_active(); + bool has_guest_device = vmci_guest_code_active(); + + *route = VMCI_ROUTE_NONE; + + /* + * "from_guest" is only ever set to true by + * IOCTL_VMCI_DATAGRAM_SEND (or by the vmkernel equivalent), + * which comes from the VMX, so we know it is coming from a + * guest. + * + * To avoid inconsistencies, test these once. We will test + * them again when we do the actual send to ensure that we do + * not touch a non-existent device. + */ + + /* Must have a valid destination context. */ + if (VMCI_INVALID_ID == dst->context) + return VMCI_ERROR_INVALID_ARGS; + + /* Anywhere to hypervisor. */ + if (VMCI_HYPERVISOR_CONTEXT_ID == dst->context) { + + /* + * If this message already came from a guest then we + * cannot send it to the hypervisor. It must come + * from a local client. + */ + if (from_guest) + return VMCI_ERROR_DST_UNREACHABLE; + + /* + * We must be acting as a guest in order to send to + * the hypervisor. + */ + if (!has_guest_device) + return VMCI_ERROR_DEVICE_NOT_FOUND; + + /* And we cannot send if the source is the host context. */ + if (VMCI_HOST_CONTEXT_ID == src->context) + return VMCI_ERROR_INVALID_ARGS; + + /* + * If the client passed the ANON source handle then + * respect it (both context and resource are invalid). + * However, if they passed only an invalid context, + * then they probably mean ANY, in which case we + * should set the real context here before passing it + * down. + */ + if (VMCI_INVALID_ID == src->context && + VMCI_INVALID_ID != src->resource) + src->context = vmci_get_context_id(); + + /* Send from local client down to the hypervisor. */ + *route = VMCI_ROUTE_AS_GUEST; + return VMCI_SUCCESS; + } + + /* Anywhere to local client on host. */ + if (VMCI_HOST_CONTEXT_ID == dst->context) { + /* + * If it is not from a guest but we are acting as a + * guest, then we need to send it down to the host. + * Note that if we are also acting as a host then this + * will prevent us from sending from local client to + * local client, but we accept that restriction as a + * way to remove any ambiguity from the host context. + */ + if (src->context == VMCI_HYPERVISOR_CONTEXT_ID) { + /* + * If the hypervisor is the source, this is + * host local communication. The hypervisor + * may send vmci event datagrams to the host + * itself, but it will never send datagrams to + * an "outer host" through the guest device. + */ + + if (has_host_device) { + *route = VMCI_ROUTE_AS_HOST; + return VMCI_SUCCESS; + } else { + return VMCI_ERROR_DEVICE_NOT_FOUND; + } + } + + if (!from_guest && has_guest_device) { + /* If no source context then use the current. */ + if (VMCI_INVALID_ID == src->context) + src->context = vmci_get_context_id(); + + /* Send it from local client down to the host. */ + *route = VMCI_ROUTE_AS_GUEST; + return VMCI_SUCCESS; + } + + /* + * Otherwise we already received it from a guest and + * it is destined for a local client on this host, or + * it is from another local client on this host. We + * must be acting as a host to service it. + */ + if (!has_host_device) + return VMCI_ERROR_DEVICE_NOT_FOUND; + + if (VMCI_INVALID_ID == src->context) { + /* + * If it came from a guest then it must have a + * valid context. Otherwise we can use the + * host context. + */ + if (from_guest) + return VMCI_ERROR_INVALID_ARGS; + + src->context = VMCI_HOST_CONTEXT_ID; + } + + /* Route to local client. */ + *route = VMCI_ROUTE_AS_HOST; + return VMCI_SUCCESS; + } + + /* + * If we are acting as a host then this might be destined for + * a guest. + */ + if (has_host_device) { + /* It will have a context if it is meant for a guest. */ + if (vmci_ctx_exists(dst->context)) { + if (VMCI_INVALID_ID == src->context) { + /* + * If it came from a guest then it + * must have a valid context. + * Otherwise we can use the host + * context. + */ + + if (from_guest) + return VMCI_ERROR_INVALID_ARGS; + + src->context = VMCI_HOST_CONTEXT_ID; + } else if (VMCI_CONTEXT_IS_VM(src->context) && + src->context != dst->context) { + /* + * VM to VM communication is not + * allowed. Since we catch all + * communication destined for the host + * above, this must be destined for a + * VM since there is a valid context. + */ + + return VMCI_ERROR_DST_UNREACHABLE; + } + + /* Pass it up to the guest. */ + *route = VMCI_ROUTE_AS_HOST; + return VMCI_SUCCESS; + } else if (!has_guest_device) { + /* + * The host is attempting to reach a CID + * without an active context, and we can't + * send it down, since we have no guest + * device. + */ + + return VMCI_ERROR_DST_UNREACHABLE; + } + } + + /* + * We must be a guest trying to send to another guest, which means + * we need to send it down to the host. We do not filter out VM to + * VM communication here, since we want to be able to use the guest + * driver on older versions that do support VM to VM communication. + */ + if (!has_guest_device) { + /* + * Ending up here means we have neither guest nor host + * device. + */ + return VMCI_ERROR_DEVICE_NOT_FOUND; + } + + /* If no source context then use the current context. */ + if (VMCI_INVALID_ID == src->context) + src->context = vmci_get_context_id(); + + /* + * Send it from local client down to the host, which will + * route it to the other guest for us. + */ + *route = VMCI_ROUTE_AS_GUEST; + return VMCI_SUCCESS; +} diff --git a/drivers/misc/vmw_vmci/vmci_route.h b/drivers/misc/vmw_vmci/vmci_route.h new file mode 100644 index 000000000000..3b30e82419c3 --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_route.h @@ -0,0 +1,30 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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. + */ + +#ifndef _VMCI_ROUTE_H_ +#define _VMCI_ROUTE_H_ + +#include + +enum vmci_route { + VMCI_ROUTE_NONE, + VMCI_ROUTE_AS_HOST, + VMCI_ROUTE_AS_GUEST, +}; + +int vmci_route(struct vmci_handle *src, const struct vmci_handle *dst, + bool from_guest, enum vmci_route *route); + +#endif /* _VMCI_ROUTE_H_ */ -- cgit v1.2.1 From 1f166439917b69d3046e2e49fe923579d9181212 Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:55:32 -0800 Subject: VMCI: guest side driver implementation. VMCI guest side driver code implementation. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_guest.c | 759 +++++++++++++++++++++++++++++++++++++ 1 file changed, 759 insertions(+) create mode 100644 drivers/misc/vmw_vmci/vmci_guest.c diff --git a/drivers/misc/vmw_vmci/vmci_guest.c b/drivers/misc/vmw_vmci/vmci_guest.c new file mode 100644 index 000000000000..d302c89d2bfa --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_guest.c @@ -0,0 +1,759 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vmci_datagram.h" +#include "vmci_doorbell.h" +#include "vmci_context.h" +#include "vmci_driver.h" +#include "vmci_event.h" + +#define PCI_VENDOR_ID_VMWARE 0x15AD +#define PCI_DEVICE_ID_VMWARE_VMCI 0x0740 + +#define VMCI_UTIL_NUM_RESOURCES 1 + +static bool vmci_disable_msi; +module_param_named(disable_msi, vmci_disable_msi, bool, 0); +MODULE_PARM_DESC(disable_msi, "Disable MSI use in driver - (default=0)"); + +static bool vmci_disable_msix; +module_param_named(disable_msix, vmci_disable_msix, bool, 0); +MODULE_PARM_DESC(disable_msix, "Disable MSI-X use in driver - (default=0)"); + +static u32 ctx_update_sub_id = VMCI_INVALID_ID; +static u32 vm_context_id = VMCI_INVALID_ID; + +struct vmci_guest_device { + struct device *dev; /* PCI device we are attached to */ + void __iomem *iobase; + + unsigned int irq; + unsigned int intr_type; + bool exclusive_vectors; + struct msix_entry msix_entries[VMCI_MAX_INTRS]; + + struct tasklet_struct datagram_tasklet; + struct tasklet_struct bm_tasklet; + + void *data_buffer; + void *notification_bitmap; +}; + +/* vmci_dev singleton device and supporting data*/ +static struct vmci_guest_device *vmci_dev_g; +static DEFINE_SPINLOCK(vmci_dev_spinlock); + +static atomic_t vmci_num_guest_devices = ATOMIC_INIT(0); + +bool vmci_guest_code_active(void) +{ + return atomic_read(&vmci_num_guest_devices) != 0; +} + +u32 vmci_get_vm_context_id(void) +{ + if (vm_context_id == VMCI_INVALID_ID) { + u32 result; + struct vmci_datagram get_cid_msg; + get_cid_msg.dst = + vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_GET_CONTEXT_ID); + get_cid_msg.src = VMCI_ANON_SRC_HANDLE; + get_cid_msg.payload_size = 0; + result = vmci_send_datagram(&get_cid_msg); + if (result >= 0) + vm_context_id = result; + } + return vm_context_id; +} + +/* + * VM to hypervisor call mechanism. We use the standard VMware naming + * convention since shared code is calling this function as well. + */ +int vmci_send_datagram(struct vmci_datagram *dg) +{ + unsigned long flags; + int result; + + /* Check args. */ + if (dg == NULL) + return VMCI_ERROR_INVALID_ARGS; + + /* + * Need to acquire spinlock on the device because the datagram + * data may be spread over multiple pages and the monitor may + * interleave device user rpc calls from multiple + * VCPUs. Acquiring the spinlock precludes that + * possibility. Disabling interrupts to avoid incoming + * datagrams during a "rep out" and possibly landing up in + * this function. + */ + spin_lock_irqsave(&vmci_dev_spinlock, flags); + + if (vmci_dev_g) { + iowrite8_rep(vmci_dev_g->iobase + VMCI_DATA_OUT_ADDR, + dg, VMCI_DG_SIZE(dg)); + result = ioread32(vmci_dev_g->iobase + VMCI_RESULT_LOW_ADDR); + } else { + result = VMCI_ERROR_UNAVAILABLE; + } + + spin_unlock_irqrestore(&vmci_dev_spinlock, flags); + + return result; +} +EXPORT_SYMBOL_GPL(vmci_send_datagram); + +/* + * Gets called with the new context id if updated or resumed. + * Context id. + */ +static void vmci_guest_cid_update(u32 sub_id, + const struct vmci_event_data *event_data, + void *client_data) +{ + const struct vmci_event_payld_ctx *ev_payload = + vmci_event_data_const_payload(event_data); + + if (sub_id != ctx_update_sub_id) { + pr_devel("Invalid subscriber (ID=0x%x)\n", sub_id); + return; + } + + if (!event_data || ev_payload->context_id == VMCI_INVALID_ID) { + pr_devel("Invalid event data\n"); + return; + } + + pr_devel("Updating context from (ID=0x%x) to (ID=0x%x) on event (type=%d)\n", + vm_context_id, ev_payload->context_id, event_data->event); + + vm_context_id = ev_payload->context_id; +} + +/* + * Verify that the host supports the hypercalls we need. If it does not, + * try to find fallback hypercalls and use those instead. Returns + * true if required hypercalls (or fallback hypercalls) are + * supported by the host, false otherwise. + */ +static bool vmci_check_host_caps(struct pci_dev *pdev) +{ + bool result; + struct vmci_resource_query_msg *msg; + u32 msg_size = sizeof(struct vmci_resource_query_hdr) + + VMCI_UTIL_NUM_RESOURCES * sizeof(u32); + struct vmci_datagram *check_msg; + + check_msg = kmalloc(msg_size, GFP_KERNEL); + if (!check_msg) { + dev_err(&pdev->dev, "%s: Insufficient memory\n", __func__); + return false; + } + + check_msg->dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, + VMCI_RESOURCES_QUERY); + check_msg->src = VMCI_ANON_SRC_HANDLE; + check_msg->payload_size = msg_size - VMCI_DG_HEADERSIZE; + msg = (struct vmci_resource_query_msg *)VMCI_DG_PAYLOAD(check_msg); + + msg->num_resources = VMCI_UTIL_NUM_RESOURCES; + msg->resources[0] = VMCI_GET_CONTEXT_ID; + + /* Checks that hyper calls are supported */ + result = vmci_send_datagram(check_msg) == 0x01; + kfree(check_msg); + + dev_dbg(&pdev->dev, "%s: Host capability check: %s\n", + __func__, result ? "PASSED" : "FAILED"); + + /* We need the vector. There are no fallbacks. */ + return result; +} + +/* + * Reads datagrams from the data in port and dispatches them. We + * always start reading datagrams into only the first page of the + * datagram buffer. If the datagrams don't fit into one page, we + * use the maximum datagram buffer size for the remainder of the + * invocation. This is a simple heuristic for not penalizing + * small datagrams. + * + * This function assumes that it has exclusive access to the data + * in port for the duration of the call. + */ +static void vmci_dispatch_dgs(unsigned long data) +{ + struct vmci_guest_device *vmci_dev = (struct vmci_guest_device *)data; + u8 *dg_in_buffer = vmci_dev->data_buffer; + struct vmci_datagram *dg; + size_t dg_in_buffer_size = VMCI_MAX_DG_SIZE; + size_t current_dg_in_buffer_size = PAGE_SIZE; + size_t remaining_bytes; + + BUILD_BUG_ON(VMCI_MAX_DG_SIZE < PAGE_SIZE); + + ioread8_rep(vmci_dev->iobase + VMCI_DATA_IN_ADDR, + vmci_dev->data_buffer, current_dg_in_buffer_size); + dg = (struct vmci_datagram *)dg_in_buffer; + remaining_bytes = current_dg_in_buffer_size; + + while (dg->dst.resource != VMCI_INVALID_ID || + remaining_bytes > PAGE_SIZE) { + unsigned dg_in_size; + + /* + * When the input buffer spans multiple pages, a datagram can + * start on any page boundary in the buffer. + */ + if (dg->dst.resource == VMCI_INVALID_ID) { + dg = (struct vmci_datagram *)roundup( + (uintptr_t)dg + 1, PAGE_SIZE); + remaining_bytes = + (size_t)(dg_in_buffer + + current_dg_in_buffer_size - + (u8 *)dg); + continue; + } + + dg_in_size = VMCI_DG_SIZE_ALIGNED(dg); + + if (dg_in_size <= dg_in_buffer_size) { + int result; + + /* + * If the remaining bytes in the datagram + * buffer doesn't contain the complete + * datagram, we first make sure we have enough + * room for it and then we read the reminder + * of the datagram and possibly any following + * datagrams. + */ + if (dg_in_size > remaining_bytes) { + if (remaining_bytes != + current_dg_in_buffer_size) { + + /* + * We move the partial + * datagram to the front and + * read the reminder of the + * datagram and possibly + * following calls into the + * following bytes. + */ + memmove(dg_in_buffer, dg_in_buffer + + current_dg_in_buffer_size - + remaining_bytes, + remaining_bytes); + dg = (struct vmci_datagram *) + dg_in_buffer; + } + + if (current_dg_in_buffer_size != + dg_in_buffer_size) + current_dg_in_buffer_size = + dg_in_buffer_size; + + ioread8_rep(vmci_dev->iobase + + VMCI_DATA_IN_ADDR, + vmci_dev->data_buffer + + remaining_bytes, + current_dg_in_buffer_size - + remaining_bytes); + } + + /* + * We special case event datagrams from the + * hypervisor. + */ + if (dg->src.context == VMCI_HYPERVISOR_CONTEXT_ID && + dg->dst.resource == VMCI_EVENT_HANDLER) { + result = vmci_event_dispatch(dg); + } else { + result = vmci_datagram_invoke_guest_handler(dg); + } + if (result < VMCI_SUCCESS) + dev_dbg(vmci_dev->dev, + "Datagram with resource (ID=0x%x) failed (err=%d)\n", + dg->dst.resource, result); + + /* On to the next datagram. */ + dg = (struct vmci_datagram *)((u8 *)dg + + dg_in_size); + } else { + size_t bytes_to_skip; + + /* + * Datagram doesn't fit in datagram buffer of maximal + * size. We drop it. + */ + dev_dbg(vmci_dev->dev, + "Failed to receive datagram (size=%u bytes)\n", + dg_in_size); + + bytes_to_skip = dg_in_size - remaining_bytes; + if (current_dg_in_buffer_size != dg_in_buffer_size) + current_dg_in_buffer_size = dg_in_buffer_size; + + for (;;) { + ioread8_rep(vmci_dev->iobase + + VMCI_DATA_IN_ADDR, + vmci_dev->data_buffer, + current_dg_in_buffer_size); + if (bytes_to_skip <= current_dg_in_buffer_size) + break; + + bytes_to_skip -= current_dg_in_buffer_size; + } + dg = (struct vmci_datagram *)(dg_in_buffer + + bytes_to_skip); + } + + remaining_bytes = + (size_t) (dg_in_buffer + current_dg_in_buffer_size - + (u8 *)dg); + + if (remaining_bytes < VMCI_DG_HEADERSIZE) { + /* Get the next batch of datagrams. */ + + ioread8_rep(vmci_dev->iobase + VMCI_DATA_IN_ADDR, + vmci_dev->data_buffer, + current_dg_in_buffer_size); + dg = (struct vmci_datagram *)dg_in_buffer; + remaining_bytes = current_dg_in_buffer_size; + } + } +} + +/* + * Scans the notification bitmap for raised flags, clears them + * and handles the notifications. + */ +static void vmci_process_bitmap(unsigned long data) +{ + struct vmci_guest_device *dev = (struct vmci_guest_device *)data; + + if (!dev->notification_bitmap) { + dev_dbg(dev->dev, "No bitmap present in %s\n", __func__); + return; + } + + vmci_dbell_scan_notification_entries(dev->notification_bitmap); +} + +/* + * Enable MSI-X. Try exclusive vectors first, then shared vectors. + */ +static int vmci_enable_msix(struct pci_dev *pdev, + struct vmci_guest_device *vmci_dev) +{ + int i; + int result; + + for (i = 0; i < VMCI_MAX_INTRS; ++i) { + vmci_dev->msix_entries[i].entry = i; + vmci_dev->msix_entries[i].vector = i; + } + + result = pci_enable_msix(pdev, vmci_dev->msix_entries, VMCI_MAX_INTRS); + if (result == 0) + vmci_dev->exclusive_vectors = true; + else if (result > 0) + result = pci_enable_msix(pdev, vmci_dev->msix_entries, 1); + + return result; +} + +/* + * Interrupt handler for legacy or MSI interrupt, or for first MSI-X + * interrupt (vector VMCI_INTR_DATAGRAM). + */ +static irqreturn_t vmci_interrupt(int irq, void *_dev) +{ + struct vmci_guest_device *dev = _dev; + + /* + * If we are using MSI-X with exclusive vectors then we simply schedule + * the datagram tasklet, since we know the interrupt was meant for us. + * Otherwise we must read the ICR to determine what to do. + */ + + if (dev->intr_type == VMCI_INTR_TYPE_MSIX && dev->exclusive_vectors) { + tasklet_schedule(&dev->datagram_tasklet); + } else { + unsigned int icr; + + /* Acknowledge interrupt and determine what needs doing. */ + icr = ioread32(dev->iobase + VMCI_ICR_ADDR); + if (icr == 0 || icr == ~0) + return IRQ_NONE; + + if (icr & VMCI_ICR_DATAGRAM) { + tasklet_schedule(&dev->datagram_tasklet); + icr &= ~VMCI_ICR_DATAGRAM; + } + + if (icr & VMCI_ICR_NOTIFICATION) { + tasklet_schedule(&dev->bm_tasklet); + icr &= ~VMCI_ICR_NOTIFICATION; + } + + if (icr != 0) + dev_warn(dev->dev, + "Ignoring unknown interrupt cause (%d)\n", + icr); + } + + return IRQ_HANDLED; +} + +/* + * Interrupt handler for MSI-X interrupt vector VMCI_INTR_NOTIFICATION, + * which is for the notification bitmap. Will only get called if we are + * using MSI-X with exclusive vectors. + */ +static irqreturn_t vmci_interrupt_bm(int irq, void *_dev) +{ + struct vmci_guest_device *dev = _dev; + + /* For MSI-X we can just assume it was meant for us. */ + tasklet_schedule(&dev->bm_tasklet); + + return IRQ_HANDLED; +} + +/* + * Most of the initialization at module load time is done here. + */ +static int vmci_guest_probe_device(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct vmci_guest_device *vmci_dev; + void __iomem *iobase; + unsigned int capabilities; + unsigned long cmd; + int vmci_err; + int error; + + dev_dbg(&pdev->dev, "Probing for vmci/PCI guest device\n"); + + error = pcim_enable_device(pdev); + if (error) { + dev_err(&pdev->dev, + "Failed to enable VMCI device: %d\n", error); + return error; + } + + error = pcim_iomap_regions(pdev, 1 << 0, KBUILD_MODNAME); + if (error) { + dev_err(&pdev->dev, "Failed to reserve/map IO regions\n"); + return error; + } + + iobase = pcim_iomap_table(pdev)[0]; + + dev_info(&pdev->dev, "Found VMCI PCI device at %#lx, irq %u\n", + (unsigned long)iobase, pdev->irq); + + vmci_dev = devm_kzalloc(&pdev->dev, sizeof(*vmci_dev), GFP_KERNEL); + if (!vmci_dev) { + dev_err(&pdev->dev, + "Can't allocate memory for VMCI device\n"); + return -ENOMEM; + } + + vmci_dev->dev = &pdev->dev; + vmci_dev->intr_type = VMCI_INTR_TYPE_INTX; + vmci_dev->exclusive_vectors = false; + vmci_dev->iobase = iobase; + + tasklet_init(&vmci_dev->datagram_tasklet, + vmci_dispatch_dgs, (unsigned long)vmci_dev); + tasklet_init(&vmci_dev->bm_tasklet, + vmci_process_bitmap, (unsigned long)vmci_dev); + + vmci_dev->data_buffer = vmalloc(VMCI_MAX_DG_SIZE); + if (!vmci_dev->data_buffer) { + dev_err(&pdev->dev, + "Can't allocate memory for datagram buffer\n"); + return -ENOMEM; + } + + pci_set_master(pdev); /* To enable queue_pair functionality. */ + + /* + * Verify that the VMCI Device supports the capabilities that + * we need. If the device is missing capabilities that we would + * like to use, check for fallback capabilities and use those + * instead (so we can run a new VM on old hosts). Fail the load if + * a required capability is missing and there is no fallback. + * + * Right now, we need datagrams. There are no fallbacks. + */ + capabilities = ioread32(vmci_dev->iobase + VMCI_CAPS_ADDR); + if (!(capabilities & VMCI_CAPS_DATAGRAM)) { + dev_err(&pdev->dev, "Device does not support datagrams\n"); + error = -ENXIO; + goto err_free_data_buffer; + } + + /* + * If the hardware supports notifications, we will use that as + * well. + */ + if (capabilities & VMCI_CAPS_NOTIFICATIONS) { + vmci_dev->notification_bitmap = vmalloc(PAGE_SIZE); + if (!vmci_dev->notification_bitmap) { + dev_warn(&pdev->dev, + "Unable to allocate notification bitmap\n"); + } else { + memset(vmci_dev->notification_bitmap, 0, PAGE_SIZE); + capabilities |= VMCI_CAPS_NOTIFICATIONS; + } + } + + dev_info(&pdev->dev, "Using capabilities 0x%x\n", capabilities); + + /* Let the host know which capabilities we intend to use. */ + iowrite32(capabilities, vmci_dev->iobase + VMCI_CAPS_ADDR); + + /* Set up global device so that we can start sending datagrams */ + spin_lock_irq(&vmci_dev_spinlock); + vmci_dev_g = vmci_dev; + spin_unlock_irq(&vmci_dev_spinlock); + + /* + * Register notification bitmap with device if that capability is + * used. + */ + if (capabilities & VMCI_CAPS_NOTIFICATIONS) { + struct page *page = + vmalloc_to_page(vmci_dev->notification_bitmap); + unsigned long bitmap_ppn = page_to_pfn(page); + if (!vmci_dbell_register_notification_bitmap(bitmap_ppn)) { + dev_warn(&pdev->dev, + "VMCI device unable to register notification bitmap with PPN 0x%x\n", + (u32) bitmap_ppn); + goto err_remove_vmci_dev_g; + } + } + + /* Check host capabilities. */ + if (!vmci_check_host_caps(pdev)) + goto err_remove_bitmap; + + /* Enable device. */ + + /* + * We subscribe to the VMCI_EVENT_CTX_ID_UPDATE here so we can + * update the internal context id when needed. + */ + vmci_err = vmci_event_subscribe(VMCI_EVENT_CTX_ID_UPDATE, + vmci_guest_cid_update, NULL, + &ctx_update_sub_id); + if (vmci_err < VMCI_SUCCESS) + dev_warn(&pdev->dev, + "Failed to subscribe to event (type=%d): %d\n", + VMCI_EVENT_CTX_ID_UPDATE, vmci_err); + + /* + * Enable interrupts. Try MSI-X first, then MSI, and then fallback on + * legacy interrupts. + */ + if (!vmci_disable_msix && !vmci_enable_msix(pdev, vmci_dev)) { + vmci_dev->intr_type = VMCI_INTR_TYPE_MSIX; + vmci_dev->irq = vmci_dev->msix_entries[0].vector; + } else if (!vmci_disable_msi && !pci_enable_msi(pdev)) { + vmci_dev->intr_type = VMCI_INTR_TYPE_MSI; + vmci_dev->irq = pdev->irq; + } else { + vmci_dev->intr_type = VMCI_INTR_TYPE_INTX; + vmci_dev->irq = pdev->irq; + } + + /* + * Request IRQ for legacy or MSI interrupts, or for first + * MSI-X vector. + */ + error = request_irq(vmci_dev->irq, vmci_interrupt, IRQF_SHARED, + KBUILD_MODNAME, vmci_dev); + if (error) { + dev_err(&pdev->dev, "Irq %u in use: %d\n", + vmci_dev->irq, error); + goto err_disable_msi; + } + + /* + * For MSI-X with exclusive vectors we need to request an + * interrupt for each vector so that we get a separate + * interrupt handler routine. This allows us to distinguish + * between the vectors. + */ + if (vmci_dev->exclusive_vectors) { + error = request_irq(vmci_dev->msix_entries[1].vector, + vmci_interrupt_bm, 0, KBUILD_MODNAME, + vmci_dev); + if (error) { + dev_err(&pdev->dev, + "Failed to allocate irq %u: %d\n", + vmci_dev->msix_entries[1].vector, error); + goto err_free_irq; + } + } + + dev_dbg(&pdev->dev, "Registered device\n"); + + atomic_inc(&vmci_num_guest_devices); + + /* Enable specific interrupt bits. */ + cmd = VMCI_IMR_DATAGRAM; + if (capabilities & VMCI_CAPS_NOTIFICATIONS) + cmd |= VMCI_IMR_NOTIFICATION; + iowrite32(cmd, vmci_dev->iobase + VMCI_IMR_ADDR); + + /* Enable interrupts. */ + iowrite32(VMCI_CONTROL_INT_ENABLE, + vmci_dev->iobase + VMCI_CONTROL_ADDR); + + pci_set_drvdata(pdev, vmci_dev); + return 0; + +err_free_irq: + free_irq(vmci_dev->irq, &vmci_dev); + tasklet_kill(&vmci_dev->datagram_tasklet); + tasklet_kill(&vmci_dev->bm_tasklet); + +err_disable_msi: + if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSIX) + pci_disable_msix(pdev); + else if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSI) + pci_disable_msi(pdev); + + vmci_err = vmci_event_unsubscribe(ctx_update_sub_id); + if (vmci_err < VMCI_SUCCESS) + dev_warn(&pdev->dev, + "Failed to unsubscribe from event (type=%d) with subscriber (ID=0x%x): %d\n", + VMCI_EVENT_CTX_ID_UPDATE, ctx_update_sub_id, vmci_err); + +err_remove_bitmap: + if (vmci_dev->notification_bitmap) { + iowrite32(VMCI_CONTROL_RESET, + vmci_dev->iobase + VMCI_CONTROL_ADDR); + vfree(vmci_dev->notification_bitmap); + } + +err_remove_vmci_dev_g: + spin_lock_irq(&vmci_dev_spinlock); + vmci_dev_g = NULL; + spin_unlock_irq(&vmci_dev_spinlock); + +err_free_data_buffer: + vfree(vmci_dev->data_buffer); + + /* The rest are managed resources and will be freed by PCI core */ + return error; +} + +static void vmci_guest_remove_device(struct pci_dev *pdev) +{ + struct vmci_guest_device *vmci_dev = pci_get_drvdata(pdev); + int vmci_err; + + dev_dbg(&pdev->dev, "Removing device\n"); + + atomic_dec(&vmci_num_guest_devices); + + vmci_qp_guest_endpoints_exit(); + + vmci_err = vmci_event_unsubscribe(ctx_update_sub_id); + if (vmci_err < VMCI_SUCCESS) + dev_warn(&pdev->dev, + "Failed to unsubscribe from event (type=%d) with subscriber (ID=0x%x): %d\n", + VMCI_EVENT_CTX_ID_UPDATE, ctx_update_sub_id, vmci_err); + + spin_lock_irq(&vmci_dev_spinlock); + vmci_dev_g = NULL; + spin_unlock_irq(&vmci_dev_spinlock); + + dev_dbg(&pdev->dev, "Resetting vmci device\n"); + iowrite32(VMCI_CONTROL_RESET, vmci_dev->iobase + VMCI_CONTROL_ADDR); + + /* + * Free IRQ and then disable MSI/MSI-X as appropriate. For + * MSI-X, we might have multiple vectors, each with their own + * IRQ, which we must free too. + */ + free_irq(vmci_dev->irq, vmci_dev); + if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSIX) { + if (vmci_dev->exclusive_vectors) + free_irq(vmci_dev->msix_entries[1].vector, vmci_dev); + pci_disable_msix(pdev); + } else if (vmci_dev->intr_type == VMCI_INTR_TYPE_MSI) { + pci_disable_msi(pdev); + } + + tasklet_kill(&vmci_dev->datagram_tasklet); + tasklet_kill(&vmci_dev->bm_tasklet); + + if (vmci_dev->notification_bitmap) { + /* + * The device reset above cleared the bitmap state of the + * device, so we can safely free it here. + */ + + vfree(vmci_dev->notification_bitmap); + } + + vfree(vmci_dev->data_buffer); + + /* The rest are managed resources and will be freed by PCI core */ +} + +static DEFINE_PCI_DEVICE_TABLE(vmci_ids) = { + { PCI_DEVICE(PCI_VENDOR_ID_VMWARE, PCI_DEVICE_ID_VMWARE_VMCI), }, + { 0 }, +}; +MODULE_DEVICE_TABLE(pci, vmci_ids); + +static struct pci_driver vmci_guest_driver = { + .name = KBUILD_MODNAME, + .id_table = vmci_ids, + .probe = vmci_guest_probe_device, + .remove = vmci_guest_remove_device, +}; + +int __init vmci_guest_init(void) +{ + return pci_register_driver(&vmci_guest_driver); +} + +void __exit vmci_guest_exit(void) +{ + pci_unregister_driver(&vmci_guest_driver); +} -- cgit v1.2.1 From 8bf503991f87e32ea42a7bd69b79ba084fddc5d7 Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:55:45 -0800 Subject: VMCI: host side driver implementation. VMCI host side driver code implementation. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_host.c | 1042 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1042 insertions(+) create mode 100644 drivers/misc/vmw_vmci/vmci_host.c diff --git a/drivers/misc/vmw_vmci/vmci_host.c b/drivers/misc/vmw_vmci/vmci_host.c new file mode 100644 index 000000000000..16e7f547555f --- /dev/null +++ b/drivers/misc/vmw_vmci/vmci_host.c @@ -0,0 +1,1042 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vmci_handle_array.h" +#include "vmci_queue_pair.h" +#include "vmci_datagram.h" +#include "vmci_doorbell.h" +#include "vmci_resource.h" +#include "vmci_context.h" +#include "vmci_driver.h" +#include "vmci_event.h" + +#define VMCI_UTIL_NUM_RESOURCES 1 + +enum { + VMCI_NOTIFY_RESOURCE_QUEUE_PAIR = 0, + VMCI_NOTIFY_RESOURCE_DOOR_BELL = 1, +}; + +enum { + VMCI_NOTIFY_RESOURCE_ACTION_NOTIFY = 0, + VMCI_NOTIFY_RESOURCE_ACTION_CREATE = 1, + VMCI_NOTIFY_RESOURCE_ACTION_DESTROY = 2, +}; + +/* + * VMCI driver initialization. This block can also be used to + * pass initial group membership etc. + */ +struct vmci_init_blk { + u32 cid; + u32 flags; +}; + +/* VMCIqueue_pairAllocInfo_VMToVM */ +struct vmci_qp_alloc_info_vmvm { + struct vmci_handle handle; + u32 peer; + u32 flags; + u64 produce_size; + u64 consume_size; + u64 produce_page_file; /* User VA. */ + u64 consume_page_file; /* User VA. */ + u64 produce_page_file_size; /* Size of the file name array. */ + u64 consume_page_file_size; /* Size of the file name array. */ + s32 result; + u32 _pad; +}; + +/* VMCISetNotifyInfo: Used to pass notify flag's address to the host driver. */ +struct vmci_set_notify_info { + u64 notify_uva; + s32 result; + u32 _pad; +}; + +/* + * Per-instance host state + */ +struct vmci_host_dev { + struct vmci_ctx *context; + int user_version; + enum vmci_obj_type ct_type; + struct mutex lock; /* Mutex lock for vmci context access */ +}; + +static struct vmci_ctx *host_context; +static bool vmci_host_device_initialized; +static atomic_t vmci_host_active_users = ATOMIC_INIT(0); + +/* + * Determines whether the VMCI host personality is + * available. Since the core functionality of the host driver is + * always present, all guests could possibly use the host + * personality. However, to minimize the deviation from the + * pre-unified driver state of affairs, we only consider the host + * device active if there is no active guest device or if there + * are VMX'en with active VMCI contexts using the host device. + */ +bool vmci_host_code_active(void) +{ + return vmci_host_device_initialized && + (!vmci_guest_code_active() || + atomic_read(&vmci_host_active_users) > 0); +} + +/* + * Called on open of /dev/vmci. + */ +static int vmci_host_open(struct inode *inode, struct file *filp) +{ + struct vmci_host_dev *vmci_host_dev; + + vmci_host_dev = kzalloc(sizeof(struct vmci_host_dev), GFP_KERNEL); + if (vmci_host_dev == NULL) + return -ENOMEM; + + vmci_host_dev->ct_type = VMCIOBJ_NOT_SET; + mutex_init(&vmci_host_dev->lock); + filp->private_data = vmci_host_dev; + + return 0; +} + +/* + * Called on close of /dev/vmci, most often when the process + * exits. + */ +static int vmci_host_close(struct inode *inode, struct file *filp) +{ + struct vmci_host_dev *vmci_host_dev = filp->private_data; + + if (vmci_host_dev->ct_type == VMCIOBJ_CONTEXT) { + vmci_ctx_destroy(vmci_host_dev->context); + vmci_host_dev->context = NULL; + + /* + * The number of active contexts is used to track whether any + * VMX'en are using the host personality. It is incremented when + * a context is created through the IOCTL_VMCI_INIT_CONTEXT + * ioctl. + */ + atomic_dec(&vmci_host_active_users); + } + vmci_host_dev->ct_type = VMCIOBJ_NOT_SET; + + kfree(vmci_host_dev); + filp->private_data = NULL; + return 0; +} + +/* + * This is used to wake up the VMX when a VMCI call arrives, or + * to wake up select() or poll() at the next clock tick. + */ +static unsigned int vmci_host_poll(struct file *filp, poll_table *wait) +{ + struct vmci_host_dev *vmci_host_dev = filp->private_data; + struct vmci_ctx *context = vmci_host_dev->context; + unsigned int mask = 0; + + if (vmci_host_dev->ct_type == VMCIOBJ_CONTEXT) { + /* Check for VMCI calls to this VM context. */ + if (wait) + poll_wait(filp, &context->host_context.wait_queue, + wait); + + spin_lock(&context->lock); + if (context->pending_datagrams > 0 || + vmci_handle_arr_get_size( + context->pending_doorbell_array) > 0) { + mask = POLLIN; + } + spin_unlock(&context->lock); + } + return mask; +} + +/* + * Copies the handles of a handle array into a user buffer, and + * returns the new length in userBufferSize. If the copy to the + * user buffer fails, the functions still returns VMCI_SUCCESS, + * but retval != 0. + */ +static int drv_cp_harray_to_user(void __user *user_buf_uva, + u64 *user_buf_size, + struct vmci_handle_arr *handle_array, + int *retval) +{ + u32 array_size = 0; + struct vmci_handle *handles; + + if (handle_array) + array_size = vmci_handle_arr_get_size(handle_array); + + if (array_size * sizeof(*handles) > *user_buf_size) + return VMCI_ERROR_MORE_DATA; + + *user_buf_size = array_size * sizeof(*handles); + if (*user_buf_size) + *retval = copy_to_user(user_buf_uva, + vmci_handle_arr_get_handles + (handle_array), *user_buf_size); + + return VMCI_SUCCESS; +} + +/* + * Sets up a given context for notify to work. Calls drv_map_bool_ptr() + * which maps the notify boolean in user VA in kernel space. + */ +static int vmci_host_setup_notify(struct vmci_ctx *context, + unsigned long uva) +{ + struct page *page; + int retval; + + if (context->notify_page) { + pr_devel("%s: Notify mechanism is already set up\n", __func__); + return VMCI_ERROR_DUPLICATE_ENTRY; + } + + /* + * We are using 'bool' internally, but let's make sure we explicit + * about the size. + */ + BUILD_BUG_ON(sizeof(bool) != sizeof(u8)); + if (!access_ok(VERIFY_WRITE, (void __user *)uva, sizeof(u8))) + return VMCI_ERROR_GENERIC; + + /* + * Lock physical page backing a given user VA. + */ + down_read(¤t->mm->mmap_sem); + retval = get_user_pages(current, current->mm, + PAGE_ALIGN(uva), + 1, 1, 0, &page, NULL); + up_read(¤t->mm->mmap_sem); + if (retval != 1) + return VMCI_ERROR_GENERIC; + + /* + * Map the locked page and set up notify pointer. + */ + context->notify = kmap(page) + (uva & (PAGE_SIZE - 1)); + vmci_ctx_check_signal_notify(context); + + return VMCI_SUCCESS; +} + +static int vmci_host_get_version(struct vmci_host_dev *vmci_host_dev, + unsigned int cmd, void __user *uptr) +{ + if (cmd == IOCTL_VMCI_VERSION2) { + int __user *vptr = uptr; + if (get_user(vmci_host_dev->user_version, vptr)) + return -EFAULT; + } + + /* + * The basic logic here is: + * + * If the user sends in a version of 0 tell it our version. + * If the user didn't send in a version, tell it our version. + * If the user sent in an old version, tell it -its- version. + * If the user sent in an newer version, tell it our version. + * + * The rationale behind telling the caller its version is that + * Workstation 6.5 required that VMX and VMCI kernel module were + * version sync'd. All new VMX users will be programmed to + * handle the VMCI kernel module version. + */ + + if (vmci_host_dev->user_version > 0 && + vmci_host_dev->user_version < VMCI_VERSION_HOSTQP) { + return vmci_host_dev->user_version; + } + + return VMCI_VERSION; +} + +#define vmci_ioctl_err(fmt, ...) \ + pr_devel("%s: " fmt, ioctl_name, ##__VA_ARGS__) + +static int vmci_host_do_init_context(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_init_blk init_block; + const struct cred *cred; + int retval; + + if (copy_from_user(&init_block, uptr, sizeof(init_block))) { + vmci_ioctl_err("error reading init block\n"); + return -EFAULT; + } + + mutex_lock(&vmci_host_dev->lock); + + if (vmci_host_dev->ct_type != VMCIOBJ_NOT_SET) { + vmci_ioctl_err("received VMCI init on initialized handle\n"); + retval = -EINVAL; + goto out; + } + + if (init_block.flags & ~VMCI_PRIVILEGE_FLAG_RESTRICTED) { + vmci_ioctl_err("unsupported VMCI restriction flag\n"); + retval = -EINVAL; + goto out; + } + + cred = get_current_cred(); + vmci_host_dev->context = vmci_ctx_create(init_block.cid, + init_block.flags, 0, + vmci_host_dev->user_version, + cred); + put_cred(cred); + if (IS_ERR(vmci_host_dev->context)) { + retval = PTR_ERR(vmci_host_dev->context); + vmci_ioctl_err("error initializing context\n"); + goto out; + } + + /* + * Copy cid to userlevel, we do this to allow the VMX + * to enforce its policy on cid generation. + */ + init_block.cid = vmci_ctx_get_id(vmci_host_dev->context); + if (copy_to_user(uptr, &init_block, sizeof(init_block))) { + vmci_ctx_destroy(vmci_host_dev->context); + vmci_host_dev->context = NULL; + vmci_ioctl_err("error writing init block\n"); + retval = -EFAULT; + goto out; + } + + vmci_host_dev->ct_type = VMCIOBJ_CONTEXT; + atomic_inc(&vmci_host_active_users); + + retval = 0; + +out: + mutex_unlock(&vmci_host_dev->lock); + return retval; +} + +static int vmci_host_do_send_datagram(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_datagram_snd_rcv_info send_info; + struct vmci_datagram *dg = NULL; + u32 cid; + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (copy_from_user(&send_info, uptr, sizeof(send_info))) + return -EFAULT; + + if (send_info.len > VMCI_MAX_DG_SIZE) { + vmci_ioctl_err("datagram is too big (size=%d)\n", + send_info.len); + return -EINVAL; + } + + if (send_info.len < sizeof(*dg)) { + vmci_ioctl_err("datagram is too small (size=%d)\n", + send_info.len); + return -EINVAL; + } + + dg = kmalloc(send_info.len, GFP_KERNEL); + if (!dg) { + vmci_ioctl_err( + "cannot allocate memory to dispatch datagram\n"); + return -ENOMEM; + } + + if (copy_from_user(dg, (void __user *)(uintptr_t)send_info.addr, + send_info.len)) { + vmci_ioctl_err("error getting datagram\n"); + kfree(dg); + return -EFAULT; + } + + pr_devel("Datagram dst (handle=0x%x:0x%x) src (handle=0x%x:0x%x), payload (size=%llu bytes)\n", + dg->dst.context, dg->dst.resource, + dg->src.context, dg->src.resource, + (unsigned long long)dg->payload_size); + + /* Get source context id. */ + cid = vmci_ctx_get_id(vmci_host_dev->context); + send_info.result = vmci_datagram_dispatch(cid, dg, true); + kfree(dg); + + return copy_to_user(uptr, &send_info, sizeof(send_info)) ? -EFAULT : 0; +} + +static int vmci_host_do_receive_datagram(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_datagram_snd_rcv_info recv_info; + struct vmci_datagram *dg = NULL; + int retval; + size_t size; + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (copy_from_user(&recv_info, uptr, sizeof(recv_info))) + return -EFAULT; + + size = recv_info.len; + recv_info.result = vmci_ctx_dequeue_datagram(vmci_host_dev->context, + &size, &dg); + + if (recv_info.result >= VMCI_SUCCESS) { + void __user *ubuf = (void __user *)(uintptr_t)recv_info.addr; + retval = copy_to_user(ubuf, dg, VMCI_DG_SIZE(dg)); + kfree(dg); + if (retval != 0) + return -EFAULT; + } + + return copy_to_user(uptr, &recv_info, sizeof(recv_info)) ? -EFAULT : 0; +} + +static int vmci_host_do_alloc_queuepair(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_handle handle; + int vmci_status; + int __user *retptr; + u32 cid; + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + cid = vmci_ctx_get_id(vmci_host_dev->context); + + if (vmci_host_dev->user_version < VMCI_VERSION_NOVMVM) { + struct vmci_qp_alloc_info_vmvm alloc_info; + struct vmci_qp_alloc_info_vmvm __user *info = uptr; + + if (copy_from_user(&alloc_info, uptr, sizeof(alloc_info))) + return -EFAULT; + + handle = alloc_info.handle; + retptr = &info->result; + + vmci_status = vmci_qp_broker_alloc(alloc_info.handle, + alloc_info.peer, + alloc_info.flags, + VMCI_NO_PRIVILEGE_FLAGS, + alloc_info.produce_size, + alloc_info.consume_size, + NULL, + vmci_host_dev->context); + + if (vmci_status == VMCI_SUCCESS) + vmci_status = VMCI_SUCCESS_QUEUEPAIR_CREATE; + } else { + struct vmci_qp_alloc_info alloc_info; + struct vmci_qp_alloc_info __user *info = uptr; + struct vmci_qp_page_store page_store; + + if (copy_from_user(&alloc_info, uptr, sizeof(alloc_info))) + return -EFAULT; + + handle = alloc_info.handle; + retptr = &info->result; + + page_store.pages = alloc_info.ppn_va; + page_store.len = alloc_info.num_ppns; + + vmci_status = vmci_qp_broker_alloc(alloc_info.handle, + alloc_info.peer, + alloc_info.flags, + VMCI_NO_PRIVILEGE_FLAGS, + alloc_info.produce_size, + alloc_info.consume_size, + &page_store, + vmci_host_dev->context); + } + + if (put_user(vmci_status, retptr)) { + if (vmci_status >= VMCI_SUCCESS) { + vmci_status = vmci_qp_broker_detach(handle, + vmci_host_dev->context); + } + return -EFAULT; + } + + return 0; +} + +static int vmci_host_do_queuepair_setva(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_qp_set_va_info set_va_info; + struct vmci_qp_set_va_info __user *info = uptr; + s32 result; + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (vmci_host_dev->user_version < VMCI_VERSION_NOVMVM) { + vmci_ioctl_err("is not allowed\n"); + return -EINVAL; + } + + if (copy_from_user(&set_va_info, uptr, sizeof(set_va_info))) + return -EFAULT; + + if (set_va_info.va) { + /* + * VMX is passing down a new VA for the queue + * pair mapping. + */ + result = vmci_qp_broker_map(set_va_info.handle, + vmci_host_dev->context, + set_va_info.va); + } else { + /* + * The queue pair is about to be unmapped by + * the VMX. + */ + result = vmci_qp_broker_unmap(set_va_info.handle, + vmci_host_dev->context, 0); + } + + return put_user(result, &info->result) ? -EFAULT : 0; +} + +static int vmci_host_do_queuepair_setpf(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_qp_page_file_info page_file_info; + struct vmci_qp_page_file_info __user *info = uptr; + s32 result; + + if (vmci_host_dev->user_version < VMCI_VERSION_HOSTQP || + vmci_host_dev->user_version >= VMCI_VERSION_NOVMVM) { + vmci_ioctl_err("not supported on this VMX (version=%d)\n", + vmci_host_dev->user_version); + return -EINVAL; + } + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (copy_from_user(&page_file_info, uptr, sizeof(*info))) + return -EFAULT; + + /* + * Communicate success pre-emptively to the caller. Note that the + * basic premise is that it is incumbent upon the caller not to look at + * the info.result field until after the ioctl() returns. And then, + * only if the ioctl() result indicates no error. We send up the + * SUCCESS status before calling SetPageStore() store because failing + * to copy up the result code means unwinding the SetPageStore(). + * + * It turns out the logic to unwind a SetPageStore() opens a can of + * worms. For example, if a host had created the queue_pair and a + * guest attaches and SetPageStore() is successful but writing success + * fails, then ... the host has to be stopped from writing (anymore) + * data into the queue_pair. That means an additional test in the + * VMCI_Enqueue() code path. Ugh. + */ + + if (put_user(VMCI_SUCCESS, &info->result)) { + /* + * In this case, we can't write a result field of the + * caller's info block. So, we don't even try to + * SetPageStore(). + */ + return -EFAULT; + } + + result = vmci_qp_broker_set_page_store(page_file_info.handle, + page_file_info.produce_va, + page_file_info.consume_va, + vmci_host_dev->context); + if (result < VMCI_SUCCESS) { + if (put_user(result, &info->result)) { + /* + * Note that in this case the SetPageStore() + * call failed but we were unable to + * communicate that to the caller (because the + * copy_to_user() call failed). So, if we + * simply return an error (in this case + * -EFAULT) then the caller will know that the + * SetPageStore failed even though we couldn't + * put the result code in the result field and + * indicate exactly why it failed. + * + * That says nothing about the issue where we + * were once able to write to the caller's info + * memory and now can't. Something more + * serious is probably going on than the fact + * that SetPageStore() didn't work. + */ + return -EFAULT; + } + } + + return 0; +} + +static int vmci_host_do_qp_detach(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_qp_dtch_info detach_info; + struct vmci_qp_dtch_info __user *info = uptr; + s32 result; + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (copy_from_user(&detach_info, uptr, sizeof(detach_info))) + return -EFAULT; + + result = vmci_qp_broker_detach(detach_info.handle, + vmci_host_dev->context); + if (result == VMCI_SUCCESS && + vmci_host_dev->user_version < VMCI_VERSION_NOVMVM) { + result = VMCI_SUCCESS_LAST_DETACH; + } + + return put_user(result, &info->result) ? -EFAULT : 0; +} + +static int vmci_host_do_ctx_add_notify(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_ctx_info ar_info; + struct vmci_ctx_info __user *info = uptr; + s32 result; + u32 cid; + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (copy_from_user(&ar_info, uptr, sizeof(ar_info))) + return -EFAULT; + + cid = vmci_ctx_get_id(vmci_host_dev->context); + result = vmci_ctx_add_notification(cid, ar_info.remote_cid); + + return put_user(result, &info->result) ? -EFAULT : 0; +} + +static int vmci_host_do_ctx_remove_notify(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_ctx_info ar_info; + struct vmci_ctx_info __user *info = uptr; + u32 cid; + int result; + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (copy_from_user(&ar_info, uptr, sizeof(ar_info))) + return -EFAULT; + + cid = vmci_ctx_get_id(vmci_host_dev->context); + result = vmci_ctx_remove_notification(cid, + ar_info.remote_cid); + + return put_user(result, &info->result) ? -EFAULT : 0; +} + +static int vmci_host_do_ctx_get_cpt_state(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_ctx_chkpt_buf_info get_info; + u32 cid; + void *cpt_buf; + int retval; + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (copy_from_user(&get_info, uptr, sizeof(get_info))) + return -EFAULT; + + cid = vmci_ctx_get_id(vmci_host_dev->context); + get_info.result = vmci_ctx_get_chkpt_state(cid, get_info.cpt_type, + &get_info.buf_size, &cpt_buf); + if (get_info.result == VMCI_SUCCESS && get_info.buf_size) { + void __user *ubuf = (void __user *)(uintptr_t)get_info.cpt_buf; + retval = copy_to_user(ubuf, cpt_buf, get_info.buf_size); + kfree(cpt_buf); + + if (retval) + return -EFAULT; + } + + return copy_to_user(uptr, &get_info, sizeof(get_info)) ? -EFAULT : 0; +} + +static int vmci_host_do_ctx_set_cpt_state(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_ctx_chkpt_buf_info set_info; + u32 cid; + void *cpt_buf; + int retval; + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (copy_from_user(&set_info, uptr, sizeof(set_info))) + return -EFAULT; + + cpt_buf = kmalloc(set_info.buf_size, GFP_KERNEL); + if (!cpt_buf) { + vmci_ioctl_err( + "cannot allocate memory to set cpt state (type=%d)\n", + set_info.cpt_type); + return -ENOMEM; + } + + if (copy_from_user(cpt_buf, (void __user *)(uintptr_t)set_info.cpt_buf, + set_info.buf_size)) { + retval = -EFAULT; + goto out; + } + + cid = vmci_ctx_get_id(vmci_host_dev->context); + set_info.result = vmci_ctx_set_chkpt_state(cid, set_info.cpt_type, + set_info.buf_size, cpt_buf); + + retval = copy_to_user(uptr, &set_info, sizeof(set_info)) ? -EFAULT : 0; + +out: + kfree(cpt_buf); + return retval; +} + +static int vmci_host_do_get_context_id(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + u32 __user *u32ptr = uptr; + + return put_user(VMCI_HOST_CONTEXT_ID, u32ptr) ? -EFAULT : 0; +} + +static int vmci_host_do_set_notify(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_set_notify_info notify_info; + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (copy_from_user(¬ify_info, uptr, sizeof(notify_info))) + return -EFAULT; + + if (notify_info.notify_uva) { + notify_info.result = + vmci_host_setup_notify(vmci_host_dev->context, + notify_info.notify_uva); + } else { + vmci_ctx_unset_notify(vmci_host_dev->context); + notify_info.result = VMCI_SUCCESS; + } + + return copy_to_user(uptr, ¬ify_info, sizeof(notify_info)) ? + -EFAULT : 0; +} + +static int vmci_host_do_notify_resource(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_dbell_notify_resource_info info; + u32 cid; + + if (vmci_host_dev->user_version < VMCI_VERSION_NOTIFY) { + vmci_ioctl_err("invalid for current VMX versions\n"); + return -EINVAL; + } + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (copy_from_user(&info, uptr, sizeof(info))) + return -EFAULT; + + cid = vmci_ctx_get_id(vmci_host_dev->context); + + switch (info.action) { + case VMCI_NOTIFY_RESOURCE_ACTION_NOTIFY: + if (info.resource == VMCI_NOTIFY_RESOURCE_DOOR_BELL) { + u32 flags = VMCI_NO_PRIVILEGE_FLAGS; + info.result = vmci_ctx_notify_dbell(cid, info.handle, + flags); + } else { + info.result = VMCI_ERROR_UNAVAILABLE; + } + break; + + case VMCI_NOTIFY_RESOURCE_ACTION_CREATE: + info.result = vmci_ctx_dbell_create(cid, info.handle); + break; + + case VMCI_NOTIFY_RESOURCE_ACTION_DESTROY: + info.result = vmci_ctx_dbell_destroy(cid, info.handle); + break; + + default: + vmci_ioctl_err("got unknown action (action=%d)\n", + info.action); + info.result = VMCI_ERROR_INVALID_ARGS; + } + + return copy_to_user(uptr, &info, sizeof(info)) ? -EFAULT : 0; +} + +static int vmci_host_do_recv_notifications(struct vmci_host_dev *vmci_host_dev, + const char *ioctl_name, + void __user *uptr) +{ + struct vmci_ctx_notify_recv_info info; + struct vmci_handle_arr *db_handle_array; + struct vmci_handle_arr *qp_handle_array; + void __user *ubuf; + u32 cid; + int retval = 0; + + if (vmci_host_dev->ct_type != VMCIOBJ_CONTEXT) { + vmci_ioctl_err("only valid for contexts\n"); + return -EINVAL; + } + + if (vmci_host_dev->user_version < VMCI_VERSION_NOTIFY) { + vmci_ioctl_err("not supported for the current vmx version\n"); + return -EINVAL; + } + + if (copy_from_user(&info, uptr, sizeof(info))) + return -EFAULT; + + if ((info.db_handle_buf_size && !info.db_handle_buf_uva) || + (info.qp_handle_buf_size && !info.qp_handle_buf_uva)) { + return -EINVAL; + } + + cid = vmci_ctx_get_id(vmci_host_dev->context); + + info.result = vmci_ctx_rcv_notifications_get(cid, + &db_handle_array, &qp_handle_array); + if (info.result != VMCI_SUCCESS) + return copy_to_user(uptr, &info, sizeof(info)) ? -EFAULT : 0; + + ubuf = (void __user *)(uintptr_t)info.db_handle_buf_uva; + info.result = drv_cp_harray_to_user(ubuf, &info.db_handle_buf_size, + db_handle_array, &retval); + if (info.result == VMCI_SUCCESS && !retval) { + ubuf = (void __user *)(uintptr_t)info.qp_handle_buf_uva; + info.result = drv_cp_harray_to_user(ubuf, + &info.qp_handle_buf_size, + qp_handle_array, &retval); + } + + if (!retval && copy_to_user(uptr, &info, sizeof(info))) + retval = -EFAULT; + + vmci_ctx_rcv_notifications_release(cid, + db_handle_array, qp_handle_array, + info.result == VMCI_SUCCESS && !retval); + + return retval; +} + +static long vmci_host_unlocked_ioctl(struct file *filp, + unsigned int iocmd, unsigned long ioarg) +{ +#define VMCI_DO_IOCTL(ioctl_name, ioctl_fn) do { \ + char *name = __stringify(IOCTL_VMCI_ ## ioctl_name); \ + return vmci_host_do_ ## ioctl_fn( \ + vmci_host_dev, name, uptr); \ + } while (0) + + struct vmci_host_dev *vmci_host_dev = filp->private_data; + void __user *uptr = (void __user *)ioarg; + + switch (iocmd) { + case IOCTL_VMCI_INIT_CONTEXT: + VMCI_DO_IOCTL(INIT_CONTEXT, init_context); + case IOCTL_VMCI_DATAGRAM_SEND: + VMCI_DO_IOCTL(DATAGRAM_SEND, send_datagram); + case IOCTL_VMCI_DATAGRAM_RECEIVE: + VMCI_DO_IOCTL(DATAGRAM_RECEIVE, receive_datagram); + case IOCTL_VMCI_QUEUEPAIR_ALLOC: + VMCI_DO_IOCTL(QUEUEPAIR_ALLOC, alloc_queuepair); + case IOCTL_VMCI_QUEUEPAIR_SETVA: + VMCI_DO_IOCTL(QUEUEPAIR_SETVA, queuepair_setva); + case IOCTL_VMCI_QUEUEPAIR_SETPAGEFILE: + VMCI_DO_IOCTL(QUEUEPAIR_SETPAGEFILE, queuepair_setpf); + case IOCTL_VMCI_QUEUEPAIR_DETACH: + VMCI_DO_IOCTL(QUEUEPAIR_DETACH, qp_detach); + case IOCTL_VMCI_CTX_ADD_NOTIFICATION: + VMCI_DO_IOCTL(CTX_ADD_NOTIFICATION, ctx_add_notify); + case IOCTL_VMCI_CTX_REMOVE_NOTIFICATION: + VMCI_DO_IOCTL(CTX_REMOVE_NOTIFICATION, ctx_remove_notify); + case IOCTL_VMCI_CTX_GET_CPT_STATE: + VMCI_DO_IOCTL(CTX_GET_CPT_STATE, ctx_get_cpt_state); + case IOCTL_VMCI_CTX_SET_CPT_STATE: + VMCI_DO_IOCTL(CTX_SET_CPT_STATE, ctx_set_cpt_state); + case IOCTL_VMCI_GET_CONTEXT_ID: + VMCI_DO_IOCTL(GET_CONTEXT_ID, get_context_id); + case IOCTL_VMCI_SET_NOTIFY: + VMCI_DO_IOCTL(SET_NOTIFY, set_notify); + case IOCTL_VMCI_NOTIFY_RESOURCE: + VMCI_DO_IOCTL(NOTIFY_RESOURCE, notify_resource); + case IOCTL_VMCI_NOTIFICATIONS_RECEIVE: + VMCI_DO_IOCTL(NOTIFICATIONS_RECEIVE, recv_notifications); + + case IOCTL_VMCI_VERSION: + case IOCTL_VMCI_VERSION2: + return vmci_host_get_version(vmci_host_dev, iocmd, uptr); + + default: + pr_devel("%s: Unknown ioctl (iocmd=%d)\n", __func__, iocmd); + return -EINVAL; + } + +#undef VMCI_DO_IOCTL +} + +static const struct file_operations vmuser_fops = { + .owner = THIS_MODULE, + .open = vmci_host_open, + .release = vmci_host_close, + .poll = vmci_host_poll, + .unlocked_ioctl = vmci_host_unlocked_ioctl, + .compat_ioctl = vmci_host_unlocked_ioctl, +}; + +static struct miscdevice vmci_host_miscdev = { + .name = "vmci", + .minor = MISC_DYNAMIC_MINOR, + .fops = &vmuser_fops, +}; + +int __init vmci_host_init(void) +{ + int error; + + host_context = vmci_ctx_create(VMCI_HOST_CONTEXT_ID, + VMCI_DEFAULT_PROC_PRIVILEGE_FLAGS, + -1, VMCI_VERSION, NULL); + if (IS_ERR(host_context)) { + error = PTR_ERR(host_context); + pr_warn("Failed to initialize VMCIContext (error%d)\n", + error); + return error; + } + + error = misc_register(&vmci_host_miscdev); + if (error) { + pr_warn("Module registration error (name=%s, major=%d, minor=%d, err=%d)\n", + vmci_host_miscdev.name, + MISC_MAJOR, vmci_host_miscdev.minor, + error); + pr_warn("Unable to initialize host personality\n"); + vmci_ctx_destroy(host_context); + return error; + } + + pr_info("VMCI host device registered (name=%s, major=%d, minor=%d)\n", + vmci_host_miscdev.name, MISC_MAJOR, vmci_host_miscdev.minor); + + vmci_host_device_initialized = true; + return 0; +} + +void __exit vmci_host_exit(void) +{ + int error; + + vmci_host_device_initialized = false; + + error = misc_deregister(&vmci_host_miscdev); + if (error) + pr_warn("Error unregistering character device: %d\n", error); + + vmci_ctx_destroy(host_context); + vmci_qp_broker_exit(); + + pr_debug("VMCI host driver module unloaded\n"); +} -- cgit v1.2.1 From 20259849bb1ac1ffb0156eb359810e8b99cb644d Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:55:59 -0800 Subject: VMCI: Some header and config files. VMCI head config patch Adds all the necessary files to enable building of the VMCI module with the Linux Makefiles and Kconfig systems. Also adds the header files used for building modules against the driver. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 2 + drivers/misc/vmw_vmci/Kconfig | 16 + drivers/misc/vmw_vmci/Makefile | 4 + include/linux/vmw_vmci_api.h | 82 ++++ include/linux/vmw_vmci_defs.h | 880 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 985 insertions(+) create mode 100644 drivers/misc/vmw_vmci/Kconfig create mode 100644 drivers/misc/vmw_vmci/Makefile create mode 100644 include/linux/vmw_vmci_api.h create mode 100644 include/linux/vmw_vmci_defs.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index b151b7c1bd59..264e647413b5 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -507,4 +507,5 @@ source "drivers/misc/lis3lv02d/Kconfig" source "drivers/misc/carma/Kconfig" source "drivers/misc/altera-stapl/Kconfig" source "drivers/misc/mei/Kconfig" +source "drivers/misc/vmw_vmci/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 2129377c0de6..5c99726dd617 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -49,3 +49,5 @@ obj-y += carma/ obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ obj-$(CONFIG_INTEL_MEI) += mei/ +obj-$(CONFIG_MAX8997_MUIC) += max8997-muic.o +obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ diff --git a/drivers/misc/vmw_vmci/Kconfig b/drivers/misc/vmw_vmci/Kconfig new file mode 100644 index 000000000000..55015e75683c --- /dev/null +++ b/drivers/misc/vmw_vmci/Kconfig @@ -0,0 +1,16 @@ +# +# VMware VMCI device +# + +config VMWARE_VMCI + tristate "VMware VMCI Driver" + depends on X86 + help + This is VMware's Virtual Machine Communication Interface. It enables + high-speed communication between host and guest in a virtual + environment via the VMCI virtual device. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called vmw_vmci. diff --git a/drivers/misc/vmw_vmci/Makefile b/drivers/misc/vmw_vmci/Makefile new file mode 100644 index 000000000000..4da9893c3942 --- /dev/null +++ b/drivers/misc/vmw_vmci/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci.o +vmw_vmci-y += vmci_context.o vmci_datagram.o vmci_doorbell.o \ + vmci_driver.o vmci_event.o vmci_guest.o vmci_handle_array.o \ + vmci_host.o vmci_queue_pair.o vmci_resource.o vmci_route.o diff --git a/include/linux/vmw_vmci_api.h b/include/linux/vmw_vmci_api.h new file mode 100644 index 000000000000..023430e265fe --- /dev/null +++ b/include/linux/vmw_vmci_api.h @@ -0,0 +1,82 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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. + */ + +#ifndef __VMW_VMCI_API_H__ +#define __VMW_VMCI_API_H__ + +#include +#include + +#undef VMCI_KERNEL_API_VERSION +#define VMCI_KERNEL_API_VERSION_1 1 +#define VMCI_KERNEL_API_VERSION_2 2 +#define VMCI_KERNEL_API_VERSION VMCI_KERNEL_API_VERSION_2 + +typedef void (vmci_device_shutdown_fn) (void *device_registration, + void *user_data); + +int vmci_datagram_create_handle(u32 resource_id, u32 flags, + vmci_datagram_recv_cb recv_cb, + void *client_data, + struct vmci_handle *out_handle); +int vmci_datagram_create_handle_priv(u32 resource_id, u32 flags, u32 priv_flags, + vmci_datagram_recv_cb recv_cb, + void *client_data, + struct vmci_handle *out_handle); +int vmci_datagram_destroy_handle(struct vmci_handle handle); +int vmci_datagram_send(struct vmci_datagram *msg); +int vmci_doorbell_create(struct vmci_handle *handle, u32 flags, + u32 priv_flags, + vmci_callback notify_cb, void *client_data); +int vmci_doorbell_destroy(struct vmci_handle handle); +int vmci_doorbell_notify(struct vmci_handle handle, u32 priv_flags); +u32 vmci_get_context_id(void); +bool vmci_is_context_owner(u32 context_id, kuid_t uid); + +int vmci_event_subscribe(u32 event, + vmci_event_cb callback, void *callback_data, + u32 *subid); +int vmci_event_unsubscribe(u32 subid); +u32 vmci_context_get_priv_flags(u32 context_id); +int vmci_qpair_alloc(struct vmci_qp **qpair, + struct vmci_handle *handle, + u64 produce_qsize, + u64 consume_qsize, + u32 peer, u32 flags, u32 priv_flags); +int vmci_qpair_detach(struct vmci_qp **qpair); +int vmci_qpair_get_produce_indexes(const struct vmci_qp *qpair, + u64 *producer_tail, + u64 *consumer_head); +int vmci_qpair_get_consume_indexes(const struct vmci_qp *qpair, + u64 *consumer_tail, + u64 *producer_head); +s64 vmci_qpair_produce_free_space(const struct vmci_qp *qpair); +s64 vmci_qpair_produce_buf_ready(const struct vmci_qp *qpair); +s64 vmci_qpair_consume_free_space(const struct vmci_qp *qpair); +s64 vmci_qpair_consume_buf_ready(const struct vmci_qp *qpair); +ssize_t vmci_qpair_enqueue(struct vmci_qp *qpair, + const void *buf, size_t buf_size, int mode); +ssize_t vmci_qpair_dequeue(struct vmci_qp *qpair, + void *buf, size_t buf_size, int mode); +ssize_t vmci_qpair_peek(struct vmci_qp *qpair, void *buf, size_t buf_size, + int mode); +ssize_t vmci_qpair_enquev(struct vmci_qp *qpair, + void *iov, size_t iov_size, int mode); +ssize_t vmci_qpair_dequev(struct vmci_qp *qpair, + void *iov, size_t iov_size, int mode); +ssize_t vmci_qpair_peekv(struct vmci_qp *qpair, void *iov, size_t iov_size, + int mode); + +#endif /* !__VMW_VMCI_API_H__ */ diff --git a/include/linux/vmw_vmci_defs.h b/include/linux/vmw_vmci_defs.h new file mode 100644 index 000000000000..65ac54c61c18 --- /dev/null +++ b/include/linux/vmw_vmci_defs.h @@ -0,0 +1,880 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2 and no later version. + * + * This program is distributed in the hope that 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. + */ + +#ifndef _VMW_VMCI_DEF_H_ +#define _VMW_VMCI_DEF_H_ + +#include + +/* Register offsets. */ +#define VMCI_STATUS_ADDR 0x00 +#define VMCI_CONTROL_ADDR 0x04 +#define VMCI_ICR_ADDR 0x08 +#define VMCI_IMR_ADDR 0x0c +#define VMCI_DATA_OUT_ADDR 0x10 +#define VMCI_DATA_IN_ADDR 0x14 +#define VMCI_CAPS_ADDR 0x18 +#define VMCI_RESULT_LOW_ADDR 0x1c +#define VMCI_RESULT_HIGH_ADDR 0x20 + +/* Max number of devices. */ +#define VMCI_MAX_DEVICES 1 + +/* Status register bits. */ +#define VMCI_STATUS_INT_ON 0x1 + +/* Control register bits. */ +#define VMCI_CONTROL_RESET 0x1 +#define VMCI_CONTROL_INT_ENABLE 0x2 +#define VMCI_CONTROL_INT_DISABLE 0x4 + +/* Capabilities register bits. */ +#define VMCI_CAPS_HYPERCALL 0x1 +#define VMCI_CAPS_GUESTCALL 0x2 +#define VMCI_CAPS_DATAGRAM 0x4 +#define VMCI_CAPS_NOTIFICATIONS 0x8 + +/* Interrupt Cause register bits. */ +#define VMCI_ICR_DATAGRAM 0x1 +#define VMCI_ICR_NOTIFICATION 0x2 + +/* Interrupt Mask register bits. */ +#define VMCI_IMR_DATAGRAM 0x1 +#define VMCI_IMR_NOTIFICATION 0x2 + +/* Interrupt type. */ +enum { + VMCI_INTR_TYPE_INTX = 0, + VMCI_INTR_TYPE_MSI = 1, + VMCI_INTR_TYPE_MSIX = 2, +}; + +/* Maximum MSI/MSI-X interrupt vectors in the device. */ +#define VMCI_MAX_INTRS 2 + +/* + * Supported interrupt vectors. There is one for each ICR value above, + * but here they indicate the position in the vector array/message ID. + */ +enum { + VMCI_INTR_DATAGRAM = 0, + VMCI_INTR_NOTIFICATION = 1, +}; + +/* + * A single VMCI device has an upper limit of 128MB on the amount of + * memory that can be used for queue pairs. + */ +#define VMCI_MAX_GUEST_QP_MEMORY (128 * 1024 * 1024) + +/* + * Queues with pre-mapped data pages must be small, so that we don't pin + * too much kernel memory (especially on vmkernel). We limit a queuepair to + * 32 KB, or 16 KB per queue for symmetrical pairs. + */ +#define VMCI_MAX_PINNED_QP_MEMORY (32 * 1024) + +/* + * We have a fixed set of resource IDs available in the VMX. + * This allows us to have a very simple implementation since we statically + * know how many will create datagram handles. If a new caller arrives and + * we have run out of slots we can manually increment the maximum size of + * available resource IDs. + * + * VMCI reserved hypervisor datagram resource IDs. + */ +enum { + VMCI_RESOURCES_QUERY = 0, + VMCI_GET_CONTEXT_ID = 1, + VMCI_SET_NOTIFY_BITMAP = 2, + VMCI_DOORBELL_LINK = 3, + VMCI_DOORBELL_UNLINK = 4, + VMCI_DOORBELL_NOTIFY = 5, + /* + * VMCI_DATAGRAM_REQUEST_MAP and VMCI_DATAGRAM_REMOVE_MAP are + * obsoleted by the removal of VM to VM communication. + */ + VMCI_DATAGRAM_REQUEST_MAP = 6, + VMCI_DATAGRAM_REMOVE_MAP = 7, + VMCI_EVENT_SUBSCRIBE = 8, + VMCI_EVENT_UNSUBSCRIBE = 9, + VMCI_QUEUEPAIR_ALLOC = 10, + VMCI_QUEUEPAIR_DETACH = 11, + + /* + * VMCI_VSOCK_VMX_LOOKUP was assigned to 12 for Fusion 3.0/3.1, + * WS 7.0/7.1 and ESX 4.1 + */ + VMCI_HGFS_TRANSPORT = 13, + VMCI_UNITY_PBRPC_REGISTER = 14, + VMCI_RPC_PRIVILEGED = 15, + VMCI_RPC_UNPRIVILEGED = 16, + VMCI_RESOURCE_MAX = 17, +}; + +/* + * struct vmci_handle - Ownership information structure + * @context: The VMX context ID. + * @resource: The resource ID (used for locating in resource hash). + * + * The vmci_handle structure is used to track resources used within + * vmw_vmci. + */ +struct vmci_handle { + u32 context; + u32 resource; +}; + +#define vmci_make_handle(_cid, _rid) \ + (struct vmci_handle){ .context = _cid, .resource = _rid } + +static inline bool vmci_handle_is_equal(struct vmci_handle h1, + struct vmci_handle h2) +{ + return h1.context == h2.context && h1.resource == h2.resource; +} + +#define VMCI_INVALID_ID ~0 +static const struct vmci_handle VMCI_INVALID_HANDLE = { + .context = VMCI_INVALID_ID, + .resource = VMCI_INVALID_ID +}; + +static inline bool vmci_handle_is_invalid(struct vmci_handle h) +{ + return vmci_handle_is_equal(h, VMCI_INVALID_HANDLE); +} + +/* + * The below defines can be used to send anonymous requests. + * This also indicates that no response is expected. + */ +#define VMCI_ANON_SRC_CONTEXT_ID VMCI_INVALID_ID +#define VMCI_ANON_SRC_RESOURCE_ID VMCI_INVALID_ID +static const struct vmci_handle VMCI_ANON_SRC_HANDLE = { + .context = VMCI_ANON_SRC_CONTEXT_ID, + .resource = VMCI_ANON_SRC_RESOURCE_ID +}; + +/* The lowest 16 context ids are reserved for internal use. */ +#define VMCI_RESERVED_CID_LIMIT ((u32) 16) + +/* + * Hypervisor context id, used for calling into hypervisor + * supplied services from the VM. + */ +#define VMCI_HYPERVISOR_CONTEXT_ID 0 + +/* + * Well-known context id, a logical context that contains a set of + * well-known services. This context ID is now obsolete. + */ +#define VMCI_WELL_KNOWN_CONTEXT_ID 1 + +/* + * Context ID used by host endpoints. + */ +#define VMCI_HOST_CONTEXT_ID 2 + +#define VMCI_CONTEXT_IS_VM(_cid) (VMCI_INVALID_ID != (_cid) && \ + (_cid) > VMCI_HOST_CONTEXT_ID) + +/* + * The VMCI_CONTEXT_RESOURCE_ID is used together with vmci_make_handle to make + * handles that refer to a specific context. + */ +#define VMCI_CONTEXT_RESOURCE_ID 0 + +/* + * VMCI error codes. + */ +enum { + VMCI_SUCCESS_QUEUEPAIR_ATTACH = 5, + VMCI_SUCCESS_QUEUEPAIR_CREATE = 4, + VMCI_SUCCESS_LAST_DETACH = 3, + VMCI_SUCCESS_ACCESS_GRANTED = 2, + VMCI_SUCCESS_ENTRY_DEAD = 1, + VMCI_SUCCESS = 0, + VMCI_ERROR_INVALID_RESOURCE = (-1), + VMCI_ERROR_INVALID_ARGS = (-2), + VMCI_ERROR_NO_MEM = (-3), + VMCI_ERROR_DATAGRAM_FAILED = (-4), + VMCI_ERROR_MORE_DATA = (-5), + VMCI_ERROR_NO_MORE_DATAGRAMS = (-6), + VMCI_ERROR_NO_ACCESS = (-7), + VMCI_ERROR_NO_HANDLE = (-8), + VMCI_ERROR_DUPLICATE_ENTRY = (-9), + VMCI_ERROR_DST_UNREACHABLE = (-10), + VMCI_ERROR_PAYLOAD_TOO_LARGE = (-11), + VMCI_ERROR_INVALID_PRIV = (-12), + VMCI_ERROR_GENERIC = (-13), + VMCI_ERROR_PAGE_ALREADY_SHARED = (-14), + VMCI_ERROR_CANNOT_SHARE_PAGE = (-15), + VMCI_ERROR_CANNOT_UNSHARE_PAGE = (-16), + VMCI_ERROR_NO_PROCESS = (-17), + VMCI_ERROR_NO_DATAGRAM = (-18), + VMCI_ERROR_NO_RESOURCES = (-19), + VMCI_ERROR_UNAVAILABLE = (-20), + VMCI_ERROR_NOT_FOUND = (-21), + VMCI_ERROR_ALREADY_EXISTS = (-22), + VMCI_ERROR_NOT_PAGE_ALIGNED = (-23), + VMCI_ERROR_INVALID_SIZE = (-24), + VMCI_ERROR_REGION_ALREADY_SHARED = (-25), + VMCI_ERROR_TIMEOUT = (-26), + VMCI_ERROR_DATAGRAM_INCOMPLETE = (-27), + VMCI_ERROR_INCORRECT_IRQL = (-28), + VMCI_ERROR_EVENT_UNKNOWN = (-29), + VMCI_ERROR_OBSOLETE = (-30), + VMCI_ERROR_QUEUEPAIR_MISMATCH = (-31), + VMCI_ERROR_QUEUEPAIR_NOTSET = (-32), + VMCI_ERROR_QUEUEPAIR_NOTOWNER = (-33), + VMCI_ERROR_QUEUEPAIR_NOTATTACHED = (-34), + VMCI_ERROR_QUEUEPAIR_NOSPACE = (-35), + VMCI_ERROR_QUEUEPAIR_NODATA = (-36), + VMCI_ERROR_BUSMEM_INVALIDATION = (-37), + VMCI_ERROR_MODULE_NOT_LOADED = (-38), + VMCI_ERROR_DEVICE_NOT_FOUND = (-39), + VMCI_ERROR_QUEUEPAIR_NOT_READY = (-40), + VMCI_ERROR_WOULD_BLOCK = (-41), + + /* VMCI clients should return error code within this range */ + VMCI_ERROR_CLIENT_MIN = (-500), + VMCI_ERROR_CLIENT_MAX = (-550), + + /* Internal error codes. */ + VMCI_SHAREDMEM_ERROR_BAD_CONTEXT = (-1000), +}; + +/* VMCI reserved events. */ +enum { + /* Only applicable to guest endpoints */ + VMCI_EVENT_CTX_ID_UPDATE = 0, + + /* Applicable to guest and host */ + VMCI_EVENT_CTX_REMOVED = 1, + + /* Only applicable to guest endpoints */ + VMCI_EVENT_QP_RESUMED = 2, + + /* Applicable to guest and host */ + VMCI_EVENT_QP_PEER_ATTACH = 3, + + /* Applicable to guest and host */ + VMCI_EVENT_QP_PEER_DETACH = 4, + + /* + * Applicable to VMX and vmk. On vmk, + * this event has the Context payload type. + */ + VMCI_EVENT_MEM_ACCESS_ON = 5, + + /* + * Applicable to VMX and vmk. Same as + * above for the payload type. + */ + VMCI_EVENT_MEM_ACCESS_OFF = 6, + VMCI_EVENT_MAX = 7, +}; + +/* + * Of the above events, a few are reserved for use in the VMX, and + * other endpoints (guest and host kernel) should not use them. For + * the rest of the events, we allow both host and guest endpoints to + * subscribe to them, to maintain the same API for host and guest + * endpoints. + */ +#define VMCI_EVENT_VALID_VMX(_event) ((_event) == VMCI_EVENT_MEM_ACCESS_ON || \ + (_event) == VMCI_EVENT_MEM_ACCESS_OFF) + +#define VMCI_EVENT_VALID(_event) ((_event) < VMCI_EVENT_MAX && \ + !VMCI_EVENT_VALID_VMX(_event)) + +/* Reserved guest datagram resource ids. */ +#define VMCI_EVENT_HANDLER 0 + +/* + * VMCI coarse-grained privileges (per context or host + * process/endpoint. An entity with the restricted flag is only + * allowed to interact with the hypervisor and trusted entities. + */ +enum { + VMCI_NO_PRIVILEGE_FLAGS = 0, + VMCI_PRIVILEGE_FLAG_RESTRICTED = 1, + VMCI_PRIVILEGE_FLAG_TRUSTED = 2, + VMCI_PRIVILEGE_ALL_FLAGS = (VMCI_PRIVILEGE_FLAG_RESTRICTED | + VMCI_PRIVILEGE_FLAG_TRUSTED), + VMCI_DEFAULT_PROC_PRIVILEGE_FLAGS = VMCI_NO_PRIVILEGE_FLAGS, + VMCI_LEAST_PRIVILEGE_FLAGS = VMCI_PRIVILEGE_FLAG_RESTRICTED, + VMCI_MAX_PRIVILEGE_FLAGS = VMCI_PRIVILEGE_FLAG_TRUSTED, +}; + +/* 0 through VMCI_RESERVED_RESOURCE_ID_MAX are reserved. */ +#define VMCI_RESERVED_RESOURCE_ID_MAX 1023 + +/* + * Driver version. + * + * Increment major version when you make an incompatible change. + * Compatibility goes both ways (old driver with new executable + * as well as new driver with old executable). + */ + +/* Never change VMCI_VERSION_SHIFT_WIDTH */ +#define VMCI_VERSION_SHIFT_WIDTH 16 +#define VMCI_MAKE_VERSION(_major, _minor) \ + ((_major) << VMCI_VERSION_SHIFT_WIDTH | (u16) (_minor)) + +#define VMCI_VERSION_MAJOR(v) ((u32) (v) >> VMCI_VERSION_SHIFT_WIDTH) +#define VMCI_VERSION_MINOR(v) ((u16) (v)) + +/* + * VMCI_VERSION is always the current version. Subsequently listed + * versions are ways of detecting previous versions of the connecting + * application (i.e., VMX). + * + * VMCI_VERSION_NOVMVM: This version removed support for VM to VM + * communication. + * + * VMCI_VERSION_NOTIFY: This version introduced doorbell notification + * support. + * + * VMCI_VERSION_HOSTQP: This version introduced host end point support + * for hosted products. + * + * VMCI_VERSION_PREHOSTQP: This is the version prior to the adoption of + * support for host end-points. + * + * VMCI_VERSION_PREVERS2: This fictional version number is intended to + * represent the version of a VMX which doesn't call into the driver + * with ioctl VERSION2 and thus doesn't establish its version with the + * driver. + */ + +#define VMCI_VERSION VMCI_VERSION_NOVMVM +#define VMCI_VERSION_NOVMVM VMCI_MAKE_VERSION(11, 0) +#define VMCI_VERSION_NOTIFY VMCI_MAKE_VERSION(10, 0) +#define VMCI_VERSION_HOSTQP VMCI_MAKE_VERSION(9, 0) +#define VMCI_VERSION_PREHOSTQP VMCI_MAKE_VERSION(8, 0) +#define VMCI_VERSION_PREVERS2 VMCI_MAKE_VERSION(1, 0) + +#define VMCI_SOCKETS_MAKE_VERSION(_p) \ + ((((_p)[0] & 0xFF) << 24) | (((_p)[1] & 0xFF) << 16) | ((_p)[2])) + +/* + * The VMCI IOCTLs. We use identity code 7, as noted in ioctl-number.h, and + * we start at sequence 9f. This gives us the same values that our shipping + * products use, starting at 1951, provided we leave out the direction and + * structure size. Note that VMMon occupies the block following us, starting + * at 2001. + */ +#define IOCTL_VMCI_VERSION _IO(7, 0x9f) /* 1951 */ +#define IOCTL_VMCI_INIT_CONTEXT _IO(7, 0xa0) +#define IOCTL_VMCI_QUEUEPAIR_SETVA _IO(7, 0xa4) +#define IOCTL_VMCI_NOTIFY_RESOURCE _IO(7, 0xa5) +#define IOCTL_VMCI_NOTIFICATIONS_RECEIVE _IO(7, 0xa6) +#define IOCTL_VMCI_VERSION2 _IO(7, 0xa7) +#define IOCTL_VMCI_QUEUEPAIR_ALLOC _IO(7, 0xa8) +#define IOCTL_VMCI_QUEUEPAIR_SETPAGEFILE _IO(7, 0xa9) +#define IOCTL_VMCI_QUEUEPAIR_DETACH _IO(7, 0xaa) +#define IOCTL_VMCI_DATAGRAM_SEND _IO(7, 0xab) +#define IOCTL_VMCI_DATAGRAM_RECEIVE _IO(7, 0xac) +#define IOCTL_VMCI_CTX_ADD_NOTIFICATION _IO(7, 0xaf) +#define IOCTL_VMCI_CTX_REMOVE_NOTIFICATION _IO(7, 0xb0) +#define IOCTL_VMCI_CTX_GET_CPT_STATE _IO(7, 0xb1) +#define IOCTL_VMCI_CTX_SET_CPT_STATE _IO(7, 0xb2) +#define IOCTL_VMCI_GET_CONTEXT_ID _IO(7, 0xb3) +#define IOCTL_VMCI_SOCKETS_VERSION _IO(7, 0xb4) +#define IOCTL_VMCI_SOCKETS_GET_AF_VALUE _IO(7, 0xb8) +#define IOCTL_VMCI_SOCKETS_GET_LOCAL_CID _IO(7, 0xb9) +#define IOCTL_VMCI_SET_NOTIFY _IO(7, 0xcb) /* 1995 */ +/*IOCTL_VMMON_START _IO(7, 0xd1)*/ /* 2001 */ + +/* + * struct vmci_queue_header - VMCI Queue Header information. + * + * A Queue cannot stand by itself as designed. Each Queue's header + * contains a pointer into itself (the producer_tail) and into its peer + * (consumer_head). The reason for the separation is one of + * accessibility: Each end-point can modify two things: where the next + * location to enqueue is within its produce_q (producer_tail); and + * where the next dequeue location is in its consume_q (consumer_head). + * + * An end-point cannot modify the pointers of its peer (guest to + * guest; NOTE that in the host both queue headers are mapped r/w). + * But, each end-point needs read access to both Queue header + * structures in order to determine how much space is used (or left) + * in the Queue. This is because for an end-point to know how full + * its produce_q is, it needs to use the consumer_head that points into + * the produce_q but -that- consumer_head is in the Queue header for + * that end-points consume_q. + * + * Thoroughly confused? Sorry. + * + * producer_tail: the point to enqueue new entrants. When you approach + * a line in a store, for example, you walk up to the tail. + * + * consumer_head: the point in the queue from which the next element is + * dequeued. In other words, who is next in line is he who is at the + * head of the line. + * + * Also, producer_tail points to an empty byte in the Queue, whereas + * consumer_head points to a valid byte of data (unless producer_tail == + * consumer_head in which case consumer_head does not point to a valid + * byte of data). + * + * For a queue of buffer 'size' bytes, the tail and head pointers will be in + * the range [0, size-1]. + * + * If produce_q_header->producer_tail == consume_q_header->consumer_head + * then the produce_q is empty. + */ +struct vmci_queue_header { + /* All fields are 64bit and aligned. */ + struct vmci_handle handle; /* Identifier. */ + atomic64_t producer_tail; /* Offset in this queue. */ + atomic64_t consumer_head; /* Offset in peer queue. */ +}; + +/* + * struct vmci_datagram - Base struct for vmci datagrams. + * @dst: A vmci_handle that tracks the destination of the datagram. + * @src: A vmci_handle that tracks the source of the datagram. + * @payload_size: The size of the payload. + * + * vmci_datagram structs are used when sending vmci datagrams. They include + * the necessary source and destination information to properly route + * the information along with the size of the package. + */ +struct vmci_datagram { + struct vmci_handle dst; + struct vmci_handle src; + u64 payload_size; +}; + +/* + * Second flag is for creating a well-known handle instead of a per context + * handle. Next flag is for deferring datagram delivery, so that the + * datagram callback is invoked in a delayed context (not interrupt context). + */ +#define VMCI_FLAG_DG_NONE 0 +#define VMCI_FLAG_WELLKNOWN_DG_HND 0x1 +#define VMCI_FLAG_ANYCID_DG_HND 0x2 +#define VMCI_FLAG_DG_DELAYED_CB 0x4 + +/* + * Maximum supported size of a VMCI datagram for routable datagrams. + * Datagrams going to the hypervisor are allowed to be larger. + */ +#define VMCI_MAX_DG_SIZE (17 * 4096) +#define VMCI_MAX_DG_PAYLOAD_SIZE (VMCI_MAX_DG_SIZE - \ + sizeof(struct vmci_datagram)) +#define VMCI_DG_PAYLOAD(_dg) (void *)((char *)(_dg) + \ + sizeof(struct vmci_datagram)) +#define VMCI_DG_HEADERSIZE sizeof(struct vmci_datagram) +#define VMCI_DG_SIZE(_dg) (VMCI_DG_HEADERSIZE + (size_t)(_dg)->payload_size) +#define VMCI_DG_SIZE_ALIGNED(_dg) ((VMCI_DG_SIZE(_dg) + 7) & (~((size_t) 0x7))) +#define VMCI_MAX_DATAGRAM_QUEUE_SIZE (VMCI_MAX_DG_SIZE * 2) + +struct vmci_event_payload_qp { + struct vmci_handle handle; /* queue_pair handle. */ + u32 peer_id; /* Context id of attaching/detaching VM. */ + u32 _pad; +}; + +/* Flags for VMCI queue_pair API. */ +enum { + /* Fail alloc if QP not created by peer. */ + VMCI_QPFLAG_ATTACH_ONLY = 1 << 0, + + /* Only allow attaches from local context. */ + VMCI_QPFLAG_LOCAL = 1 << 1, + + /* Host won't block when guest is quiesced. */ + VMCI_QPFLAG_NONBLOCK = 1 << 2, + + /* Pin data pages in ESX. Used with NONBLOCK */ + VMCI_QPFLAG_PINNED = 1 << 3, + + /* Update the following flag when adding new flags. */ + VMCI_QP_ALL_FLAGS = (VMCI_QPFLAG_ATTACH_ONLY | VMCI_QPFLAG_LOCAL | + VMCI_QPFLAG_NONBLOCK | VMCI_QPFLAG_PINNED), + + /* Convenience flags */ + VMCI_QP_ASYMM = (VMCI_QPFLAG_NONBLOCK | VMCI_QPFLAG_PINNED), + VMCI_QP_ASYMM_PEER = (VMCI_QPFLAG_ATTACH_ONLY | VMCI_QP_ASYMM), +}; + +/* + * We allow at least 1024 more event datagrams from the hypervisor past the + * normally allowed datagrams pending for a given context. We define this + * limit on event datagrams from the hypervisor to guard against DoS attack + * from a malicious VM which could repeatedly attach to and detach from a queue + * pair, causing events to be queued at the destination VM. However, the rate + * at which such events can be generated is small since it requires a VM exit + * and handling of queue pair attach/detach call at the hypervisor. Event + * datagrams may be queued up at the destination VM if it has interrupts + * disabled or if it is not draining events for some other reason. 1024 + * datagrams is a grossly conservative estimate of the time for which + * interrupts may be disabled in the destination VM, but at the same time does + * not exacerbate the memory pressure problem on the host by much (size of each + * event datagram is small). + */ +#define VMCI_MAX_DATAGRAM_AND_EVENT_QUEUE_SIZE \ + (VMCI_MAX_DATAGRAM_QUEUE_SIZE + \ + 1024 * (sizeof(struct vmci_datagram) + \ + sizeof(struct vmci_event_data_max))) + +/* + * Struct used for querying, via VMCI_RESOURCES_QUERY, the availability of + * hypervisor resources. Struct size is 16 bytes. All fields in struct are + * aligned to their natural alignment. + */ +struct vmci_resource_query_hdr { + struct vmci_datagram hdr; + u32 num_resources; + u32 _padding; +}; + +/* + * Convenience struct for negotiating vectors. Must match layout of + * VMCIResourceQueryHdr minus the struct vmci_datagram header. + */ +struct vmci_resource_query_msg { + u32 num_resources; + u32 _padding; + u32 resources[1]; +}; + +/* + * The maximum number of resources that can be queried using + * VMCI_RESOURCE_QUERY is 31, as the result is encoded in the lower 31 + * bits of a positive return value. Negative values are reserved for + * errors. + */ +#define VMCI_RESOURCE_QUERY_MAX_NUM 31 + +/* Maximum size for the VMCI_RESOURCE_QUERY request. */ +#define VMCI_RESOURCE_QUERY_MAX_SIZE \ + (sizeof(struct vmci_resource_query_hdr) + \ + sizeof(u32) * VMCI_RESOURCE_QUERY_MAX_NUM) + +/* + * Struct used for setting the notification bitmap. All fields in + * struct are aligned to their natural alignment. + */ +struct vmci_notify_bm_set_msg { + struct vmci_datagram hdr; + u32 bitmap_ppn; + u32 _pad; +}; + +/* + * Struct used for linking a doorbell handle with an index in the + * notify bitmap. All fields in struct are aligned to their natural + * alignment. + */ +struct vmci_doorbell_link_msg { + struct vmci_datagram hdr; + struct vmci_handle handle; + u64 notify_idx; +}; + +/* + * Struct used for unlinking a doorbell handle from an index in the + * notify bitmap. All fields in struct are aligned to their natural + * alignment. + */ +struct vmci_doorbell_unlink_msg { + struct vmci_datagram hdr; + struct vmci_handle handle; +}; + +/* + * Struct used for generating a notification on a doorbell handle. All + * fields in struct are aligned to their natural alignment. + */ +struct vmci_doorbell_notify_msg { + struct vmci_datagram hdr; + struct vmci_handle handle; +}; + +/* + * This struct is used to contain data for events. Size of this struct is a + * multiple of 8 bytes, and all fields are aligned to their natural alignment. + */ +struct vmci_event_data { + u32 event; /* 4 bytes. */ + u32 _pad; + /* Event payload is put here. */ +}; + +/* + * Define the different VMCI_EVENT payload data types here. All structs must + * be a multiple of 8 bytes, and fields must be aligned to their natural + * alignment. + */ +struct vmci_event_payld_ctx { + u32 context_id; /* 4 bytes. */ + u32 _pad; +}; + +struct vmci_event_payld_qp { + struct vmci_handle handle; /* queue_pair handle. */ + u32 peer_id; /* Context id of attaching/detaching VM. */ + u32 _pad; +}; + +/* + * We define the following struct to get the size of the maximum event + * data the hypervisor may send to the guest. If adding a new event + * payload type above, add it to the following struct too (inside the + * union). + */ +struct vmci_event_data_max { + struct vmci_event_data event_data; + union { + struct vmci_event_payld_ctx context_payload; + struct vmci_event_payld_qp qp_payload; + } ev_data_payload; +}; + +/* + * Struct used for VMCI_EVENT_SUBSCRIBE/UNSUBSCRIBE and + * VMCI_EVENT_HANDLER messages. Struct size is 32 bytes. All fields + * in struct are aligned to their natural alignment. + */ +struct vmci_event_msg { + struct vmci_datagram hdr; + + /* Has event type and payload. */ + struct vmci_event_data event_data; + + /* Payload gets put here. */ +}; + +/* Event with context payload. */ +struct vmci_event_ctx { + struct vmci_event_msg msg; + struct vmci_event_payld_ctx payload; +}; + +/* Event with QP payload. */ +struct vmci_event_qp { + struct vmci_event_msg msg; + struct vmci_event_payld_qp payload; +}; + +/* + * Structs used for queue_pair alloc and detach messages. We align fields of + * these structs to 64bit boundaries. + */ +struct vmci_qp_alloc_msg { + struct vmci_datagram hdr; + struct vmci_handle handle; + u32 peer; + u32 flags; + u64 produce_size; + u64 consume_size; + u64 num_ppns; + + /* List of PPNs placed here. */ +}; + +struct vmci_qp_detach_msg { + struct vmci_datagram hdr; + struct vmci_handle handle; +}; + +/* VMCI Doorbell API. */ +#define VMCI_FLAG_DELAYED_CB 0x01 + +typedef void (*vmci_callback) (void *client_data); + +/* + * struct vmci_qp - A vmw_vmci queue pair handle. + * + * This structure is used as a handle to a queue pair created by + * VMCI. It is intentionally left opaque to clients. + */ +struct vmci_qp; + +/* Callback needed for correctly waiting on events. */ +typedef int (*vmci_datagram_recv_cb) (void *client_data, + struct vmci_datagram *msg); + +/* VMCI Event API. */ +typedef void (*vmci_event_cb) (u32 sub_id, const struct vmci_event_data *ed, + void *client_data); + +/* + * We use the following inline function to access the payload data + * associated with an event data. + */ +static inline const void * +vmci_event_data_const_payload(const struct vmci_event_data *ev_data) +{ + return (const char *)ev_data + sizeof(*ev_data); +} + +static inline void *vmci_event_data_payload(struct vmci_event_data *ev_data) +{ + return (void *)vmci_event_data_const_payload(ev_data); +} + +/* + * Helper to add a given offset to a head or tail pointer. Wraps the + * value of the pointer around the max size of the queue. + */ +static inline void vmci_qp_add_pointer(atomic64_t *var, + size_t add, + u64 size) +{ + u64 new_val = atomic64_read(var); + + if (new_val >= size - add) + new_val -= size; + + new_val += add; + + atomic64_set(var, new_val); +} + +/* + * Helper routine to get the Producer Tail from the supplied queue. + */ +static inline u64 +vmci_q_header_producer_tail(const struct vmci_queue_header *q_header) +{ + struct vmci_queue_header *qh = (struct vmci_queue_header *)q_header; + return atomic64_read(&qh->producer_tail); +} + +/* + * Helper routine to get the Consumer Head from the supplied queue. + */ +static inline u64 +vmci_q_header_consumer_head(const struct vmci_queue_header *q_header) +{ + struct vmci_queue_header *qh = (struct vmci_queue_header *)q_header; + return atomic64_read(&qh->consumer_head); +} + +/* + * Helper routine to increment the Producer Tail. Fundamentally, + * vmci_qp_add_pointer() is used to manipulate the tail itself. + */ +static inline void +vmci_q_header_add_producer_tail(struct vmci_queue_header *q_header, + size_t add, + u64 queue_size) +{ + vmci_qp_add_pointer(&q_header->producer_tail, add, queue_size); +} + +/* + * Helper routine to increment the Consumer Head. Fundamentally, + * vmci_qp_add_pointer() is used to manipulate the head itself. + */ +static inline void +vmci_q_header_add_consumer_head(struct vmci_queue_header *q_header, + size_t add, + u64 queue_size) +{ + vmci_qp_add_pointer(&q_header->consumer_head, add, queue_size); +} + +/* + * Helper routine for getting the head and the tail pointer for a queue. + * Both the VMCIQueues are needed to get both the pointers for one queue. + */ +static inline void +vmci_q_header_get_pointers(const struct vmci_queue_header *produce_q_header, + const struct vmci_queue_header *consume_q_header, + u64 *producer_tail, + u64 *consumer_head) +{ + if (producer_tail) + *producer_tail = vmci_q_header_producer_tail(produce_q_header); + + if (consumer_head) + *consumer_head = vmci_q_header_consumer_head(consume_q_header); +} + +static inline void vmci_q_header_init(struct vmci_queue_header *q_header, + const struct vmci_handle handle) +{ + q_header->handle = handle; + atomic64_set(&q_header->producer_tail, 0); + atomic64_set(&q_header->consumer_head, 0); +} + +/* + * Finds available free space in a produce queue to enqueue more + * data or reports an error if queue pair corruption is detected. + */ +static s64 +vmci_q_header_free_space(const struct vmci_queue_header *produce_q_header, + const struct vmci_queue_header *consume_q_header, + const u64 produce_q_size) +{ + u64 tail; + u64 head; + u64 free_space; + + tail = vmci_q_header_producer_tail(produce_q_header); + head = vmci_q_header_consumer_head(consume_q_header); + + if (tail >= produce_q_size || head >= produce_q_size) + return VMCI_ERROR_INVALID_SIZE; + + /* + * Deduct 1 to avoid tail becoming equal to head which causes + * ambiguity. If head and tail are equal it means that the + * queue is empty. + */ + if (tail >= head) + free_space = produce_q_size - (tail - head) - 1; + else + free_space = head - tail - 1; + + return free_space; +} + +/* + * vmci_q_header_free_space() does all the heavy lifting of + * determing the number of free bytes in a Queue. This routine, + * then subtracts that size from the full size of the Queue so + * the caller knows how many bytes are ready to be dequeued. + * Results: + * On success, available data size in bytes (up to MAX_INT64). + * On failure, appropriate error code. + */ +static inline s64 +vmci_q_header_buf_ready(const struct vmci_queue_header *consume_q_header, + const struct vmci_queue_header *produce_q_header, + const u64 consume_q_size) +{ + s64 free_space; + + free_space = vmci_q_header_free_space(consume_q_header, + produce_q_header, consume_q_size); + if (free_space < VMCI_SUCCESS) + return free_space; + + return consume_q_size - free_space - 1; +} + + +#endif /* _VMW_VMCI_DEF_H_ */ -- cgit v1.2.1 From 0edb23fc3451c84350edcc999c023d225a49530d Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:12 +0200 Subject: mei: add new hbm.h header to export hbm protocol hbm.h provides access host bus messaging functionality for other MEI layers Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 1 + drivers/misc/mei/hbm.c | 1 + drivers/misc/mei/hbm.h | 39 +++++++++++++++++++++++++++++++++++++++ drivers/misc/mei/init.c | 1 + drivers/misc/mei/interface.h | 7 ++----- drivers/misc/mei/interrupt.c | 1 + drivers/misc/mei/iorw.c | 1 + drivers/misc/mei/mei_dev.h | 10 ---------- drivers/misc/mei/wd.c | 1 + 9 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 drivers/misc/mei/hbm.h diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 6e3cd31eae3b..add4254eb850 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -34,6 +34,7 @@ #include #include "mei_dev.h" +#include "hbm.h" #include "interface.h" const uuid_le mei_amthi_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac, diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 6b58b0a10378..9956aaf58aa4 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -20,6 +20,7 @@ #include #include "mei_dev.h" +#include "hbm.h" #include "interface.h" /** diff --git a/drivers/misc/mei/hbm.h b/drivers/misc/mei/hbm.h new file mode 100644 index 000000000000..b552afbaf85c --- /dev/null +++ b/drivers/misc/mei/hbm.h @@ -0,0 +1,39 @@ +/* + * + * 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. + * + */ + +#ifndef _MEI_HBM_H_ +#define _MEI_HBM_H_ + +void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr); + +static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length) +{ + hdr->host_addr = 0; + hdr->me_addr = 0; + hdr->length = length; + hdr->msg_complete = 1; + hdr->reserved = 0; +} + +void mei_hbm_start_req(struct mei_device *dev); + +int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl); +int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl); +int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl); + + +#endif /* _MEI_HBM_H_ */ + diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 418a85f315f1..55895fc21ff1 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -22,6 +22,7 @@ #include #include "mei_dev.h" +#include "hbm.h" #include "interface.h" const char *mei_dev_state_str(int state) diff --git a/drivers/misc/mei/interface.h b/drivers/misc/mei/interface.h index 90a3dfda9db5..3d06c087ddd2 100644 --- a/drivers/misc/mei/interface.h +++ b/drivers/misc/mei/interface.h @@ -72,12 +72,9 @@ void mei_watchdog_unregister(struct mei_device *dev); int mei_other_client_is_connecting(struct mei_device *dev, struct mei_cl *cl); int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl); -void mei_hbm_start_req(struct mei_device *dev); +void mei_host_client_init(struct work_struct *work); + -int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl); -int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl); -int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl); -void mei_host_client_init(struct work_struct *work); #endif /* _MEI_INTERFACE_H_ */ diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index a735c8b7ca82..2495e35ccb27 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -24,6 +24,7 @@ #include #include "mei_dev.h" +#include "hbm.h" #include "interface.h" diff --git a/drivers/misc/mei/iorw.c b/drivers/misc/mei/iorw.c index d8e08bcf3263..4328c2d2ca54 100644 --- a/drivers/misc/mei/iorw.c +++ b/drivers/misc/mei/iorw.c @@ -36,6 +36,7 @@ #include #include "mei_dev.h" +#include "hbm.h" #include "interface.h" /** diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 54ddac324578..1b54e675d3f1 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -445,16 +445,6 @@ void mei_enable_interrupts(struct mei_device *dev); void mei_disable_interrupts(struct mei_device *dev); -void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr); - -static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length) -{ - hdr->host_addr = 0; - hdr->me_addr = 0; - hdr->length = length; - hdr->msg_complete = 1; - hdr->reserved = 0; -} #define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d comp=%1d" #define MEI_HDR_PRM(hdr) \ diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index 4f2e9db86478..9814bc1dba01 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -24,6 +24,7 @@ #include #include "mei_dev.h" +#include "hbm.h" #include "interface.h" static const u8 mei_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 }; -- cgit v1.2.1 From 9ca9050b3df690d9d44e39424ab2a531120af936 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:13 +0200 Subject: mei: move client functions to client.c This file now contains me and host client functions and also io callback helpers We also kill iorw.c which is no longer needed Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/Makefile | 2 +- drivers/misc/mei/client.c | 710 +++++++++++++++++++++++++++++++++++++++++++ drivers/misc/mei/init.c | 252 --------------- drivers/misc/mei/interface.c | 96 ------ drivers/misc/mei/iorw.c | 367 ---------------------- 5 files changed, 711 insertions(+), 716 deletions(-) create mode 100644 drivers/misc/mei/client.c delete mode 100644 drivers/misc/mei/iorw.c diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 1f382a5ca3a6..67f543ab234f 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -7,7 +7,7 @@ mei-objs := init.o mei-objs += hbm.o mei-objs += interrupt.o mei-objs += interface.o -mei-objs += iorw.o mei-objs += main.o mei-objs += amthif.o mei-objs += wd.o +mei-objs += client.o diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c new file mode 100644 index 000000000000..19f62073fa67 --- /dev/null +++ b/drivers/misc/mei/client.c @@ -0,0 +1,710 @@ +/* + * + * 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 +#include +#include +#include + +#include + +#include "mei_dev.h" +#include "hbm.h" +#include "interface.h" + + +/** + * mei_io_list_flush - removes list entry belonging to cl. + * + * @list: An instance of our list structure + * @cl: host client + */ +void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl) +{ + struct mei_cl_cb *cb; + struct mei_cl_cb *next; + + list_for_each_entry_safe(cb, next, &list->list, list) { + if (cb->cl && mei_cl_cmp_id(cl, cb->cl)) + list_del(&cb->list); + } +} + +/** + * mei_io_cb_free - free mei_cb_private related memory + * + * @cb: mei callback struct + */ +void mei_io_cb_free(struct mei_cl_cb *cb) +{ + if (cb == NULL) + return; + + kfree(cb->request_buffer.data); + kfree(cb->response_buffer.data); + kfree(cb); +} + +/** + * mei_io_cb_init - allocate and initialize io callback + * + * @cl - mei client + * @file: pointer to file structure + * + * returns mei_cl_cb pointer or NULL; + */ +struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp) +{ + struct mei_cl_cb *cb; + + cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + if (!cb) + return NULL; + + mei_io_list_init(cb); + + cb->file_object = fp; + cb->cl = cl; + cb->buf_idx = 0; + return cb; +} + +/** + * mei_io_cb_alloc_req_buf - allocate request buffer + * + * @cb - io callback structure + * @size: size of the buffer + * + * returns 0 on success + * -EINVAL if cb is NULL + * -ENOMEM if allocation failed + */ +int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length) +{ + if (!cb) + return -EINVAL; + + if (length == 0) + return 0; + + cb->request_buffer.data = kmalloc(length, GFP_KERNEL); + if (!cb->request_buffer.data) + return -ENOMEM; + cb->request_buffer.size = length; + return 0; +} +/** + * mei_io_cb_alloc_req_buf - allocate respose buffer + * + * @cb - io callback structure + * @size: size of the buffer + * + * returns 0 on success + * -EINVAL if cb is NULL + * -ENOMEM if allocation failed + */ +int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length) +{ + if (!cb) + return -EINVAL; + + if (length == 0) + return 0; + + cb->response_buffer.data = kmalloc(length, GFP_KERNEL); + if (!cb->response_buffer.data) + return -ENOMEM; + cb->response_buffer.size = length; + return 0; +} + + + +/** + * mei_cl_flush_queues - flushes queue lists belonging to cl. + * + * @dev: the device structure + * @cl: host client + */ +int mei_cl_flush_queues(struct mei_cl *cl) +{ + if (!cl || !cl->dev) + return -EINVAL; + + dev_dbg(&cl->dev->pdev->dev, "remove list entry belonging to cl\n"); + mei_io_list_flush(&cl->dev->read_list, cl); + mei_io_list_flush(&cl->dev->write_list, cl); + mei_io_list_flush(&cl->dev->write_waiting_list, cl); + mei_io_list_flush(&cl->dev->ctrl_wr_list, cl); + mei_io_list_flush(&cl->dev->ctrl_rd_list, cl); + mei_io_list_flush(&cl->dev->amthif_cmd_list, cl); + mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl); + return 0; +} + +/** + * mei_me_cl_by_uuid - locate index of me client + * + * @dev: mei device + * returns me client index or -ENOENT if not found + */ +int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *uuid) +{ + int i, res = -ENOENT; + + for (i = 0; i < dev->me_clients_num; ++i) + if (uuid_le_cmp(*uuid, + dev->me_clients[i].props.protocol_name) == 0) { + res = i; + break; + } + + return res; +} + + +/** + * mei_me_cl_by_id return index to me_clients for client_id + * + * @dev: the device structure + * @client_id: me client id + * + * Locking: called under "dev->device_lock" lock + * + * returns index on success, -ENOENT on failure. + */ + +int mei_me_cl_by_id(struct mei_device *dev, u8 client_id) +{ + int i; + for (i = 0; i < dev->me_clients_num; i++) + if (dev->me_clients[i].client_id == client_id) + break; + if (WARN_ON(dev->me_clients[i].client_id != client_id)) + return -ENOENT; + + if (i == dev->me_clients_num) + return -ENOENT; + + return i; +} + +/** + * mei_cl_init - initializes intialize cl. + * + * @cl: host client to be initialized + * @dev: mei device + */ +void mei_cl_init(struct mei_cl *cl, struct mei_device *dev) +{ + memset(cl, 0, sizeof(struct mei_cl)); + init_waitqueue_head(&cl->wait); + init_waitqueue_head(&cl->rx_wait); + init_waitqueue_head(&cl->tx_wait); + INIT_LIST_HEAD(&cl->link); + cl->reading_state = MEI_IDLE; + cl->writing_state = MEI_IDLE; + cl->dev = dev; +} + +/** + * mei_cl_allocate - allocates cl structure and sets it up. + * + * @dev: mei device + * returns The allocated file or NULL on failure + */ +struct mei_cl *mei_cl_allocate(struct mei_device *dev) +{ + struct mei_cl *cl; + + cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL); + if (!cl) + return NULL; + + mei_cl_init(cl, dev); + + return cl; +} + + +/** + * mei_me_cl_link - create link between host and me clinet and add + * me_cl to the list + * + * @dev: the device structure + * @cl: link between me and host client assocated with opened file descriptor + * @uuid: uuid of ME client + * @client_id: id of the host client + * + * returns ME client index if ME client + * -EINVAL on incorrect values + * -ENONET if client not found + */ +int mei_me_cl_link(struct mei_device *dev, struct mei_cl *cl, + const uuid_le *uuid, u8 host_cl_id) +{ + int i; + + if (!dev || !cl || !uuid) + return -EINVAL; + + /* check for valid client id */ + i = mei_me_cl_by_uuid(dev, uuid); + if (i >= 0) { + cl->me_client_id = dev->me_clients[i].client_id; + cl->state = MEI_FILE_CONNECTING; + cl->host_client_id = host_cl_id; + + list_add_tail(&cl->link, &dev->file_list); + return (u8)i; + } + + return -ENOENT; +} +/** + * mei_me_cl_unlink - remove me_cl from the list + * + * @dev: the device structure + * @host_client_id: host client id to be removed + */ +void mei_me_cl_unlink(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_cl *pos, *next; + list_for_each_entry_safe(pos, next, &dev->file_list, link) { + if (cl->host_client_id == pos->host_client_id) { + dev_dbg(&dev->pdev->dev, "remove host client = %d, ME client = %d\n", + pos->host_client_id, pos->me_client_id); + list_del_init(&pos->link); + break; + } + } +} + + +void mei_host_client_init(struct work_struct *work) +{ + struct mei_device *dev = container_of(work, + struct mei_device, init_work); + struct mei_client_properties *client_props; + int i; + + mutex_lock(&dev->device_lock); + + bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); + dev->open_handle_count = 0; + + /* + * Reserving the first three client IDs + * 0: Reserved for MEI Bus Message communications + * 1: Reserved for Watchdog + * 2: Reserved for AMTHI + */ + bitmap_set(dev->host_clients_map, 0, 3); + + for (i = 0; i < dev->me_clients_num; i++) { + client_props = &dev->me_clients[i].props; + + if (!uuid_le_cmp(client_props->protocol_name, mei_amthi_guid)) + mei_amthif_host_init(dev); + else if (!uuid_le_cmp(client_props->protocol_name, mei_wd_guid)) + mei_wd_host_init(dev); + } + + dev->dev_state = MEI_DEV_ENABLED; + + mutex_unlock(&dev->device_lock); +} + + +/** + * mei_disconnect_host_client - sends disconnect message to fw from host client. + * + * @dev: the device structure + * @cl: private data of the file object + * + * Locking: called under "dev->device_lock" lock + * + * returns 0 on success, <0 on failure. + */ +int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_cl_cb *cb; + int rets, err; + + if (!dev || !cl) + return -ENODEV; + + if (cl->state != MEI_FILE_DISCONNECTING) + return 0; + + cb = mei_io_cb_init(cl, NULL); + if (!cb) + return -ENOMEM; + + cb->fop_type = MEI_FOP_CLOSE; + if (dev->mei_host_buffer_is_empty) { + dev->mei_host_buffer_is_empty = false; + if (mei_hbm_cl_disconnect_req(dev, cl)) { + rets = -ENODEV; + dev_err(&dev->pdev->dev, "failed to disconnect.\n"); + goto free; + } + mdelay(10); /* Wait for hardware disconnection ready */ + list_add_tail(&cb->list, &dev->ctrl_rd_list.list); + } else { + dev_dbg(&dev->pdev->dev, "add disconnect cb to control write list\n"); + list_add_tail(&cb->list, &dev->ctrl_wr_list.list); + + } + mutex_unlock(&dev->device_lock); + + err = wait_event_timeout(dev->wait_recvd_msg, + MEI_FILE_DISCONNECTED == cl->state, + mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + + mutex_lock(&dev->device_lock); + if (MEI_FILE_DISCONNECTED == cl->state) { + rets = 0; + dev_dbg(&dev->pdev->dev, "successfully disconnected from FW client.\n"); + } else { + rets = -ENODEV; + if (MEI_FILE_DISCONNECTED != cl->state) + dev_dbg(&dev->pdev->dev, "wrong status client disconnect.\n"); + + if (err) + dev_dbg(&dev->pdev->dev, + "wait failed disconnect err=%08x\n", + err); + + dev_dbg(&dev->pdev->dev, "failed to disconnect from FW client.\n"); + } + + mei_io_list_flush(&dev->ctrl_rd_list, cl); + mei_io_list_flush(&dev->ctrl_wr_list, cl); +free: + mei_io_cb_free(cb); + return rets; +} + + +/** + * mei_other_client_is_connecting - checks if other + * client with the same client id is connected. + * + * @dev: the device structure + * @cl: private data of the file object + * + * returns 1 if other client is connected, 0 - otherwise. + */ +int mei_other_client_is_connecting(struct mei_device *dev, + struct mei_cl *cl) +{ + struct mei_cl *cl_pos = NULL; + struct mei_cl *cl_next = NULL; + + list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { + if ((cl_pos->state == MEI_FILE_CONNECTING) && + (cl_pos != cl) && + cl->me_client_id == cl_pos->me_client_id) + return 1; + + } + return 0; +} + +/** + * mei_flow_ctrl_creds - checks flow_control credentials. + * + * @dev: the device structure + * @cl: private data of the file object + * + * returns 1 if mei_flow_ctrl_creds >0, 0 - otherwise. + * -ENOENT if mei_cl is not present + * -EINVAL if single_recv_buf == 0 + */ +int mei_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl) +{ + int i; + + if (!dev->me_clients_num) + return 0; + + if (cl->mei_flow_ctrl_creds > 0) + return 1; + + for (i = 0; i < dev->me_clients_num; i++) { + struct mei_me_client *me_cl = &dev->me_clients[i]; + if (me_cl->client_id == cl->me_client_id) { + if (me_cl->mei_flow_ctrl_creds) { + if (WARN_ON(me_cl->props.single_recv_buf == 0)) + return -EINVAL; + return 1; + } else { + return 0; + } + } + } + return -ENOENT; +} + +/** + * mei_flow_ctrl_reduce - reduces flow_control. + * + * @dev: the device structure + * @cl: private data of the file object + * @returns + * 0 on success + * -ENOENT when me client is not found + * -EINVAL when ctrl credits are <= 0 + */ +int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl) +{ + int i; + + if (!dev->me_clients_num) + return -ENOENT; + + for (i = 0; i < dev->me_clients_num; i++) { + struct mei_me_client *me_cl = &dev->me_clients[i]; + if (me_cl->client_id == cl->me_client_id) { + if (me_cl->props.single_recv_buf != 0) { + if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) + return -EINVAL; + dev->me_clients[i].mei_flow_ctrl_creds--; + } else { + if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) + return -EINVAL; + cl->mei_flow_ctrl_creds--; + } + return 0; + } + } + return -ENOENT; +} + + + +/** + * mei_ioctl_connect_client - the connect to fw client IOCTL function + * + * @dev: the device structure + * @data: IOCTL connect data, input and output parameters + * @file: private data of the file object + * + * Locking: called under "dev->device_lock" lock + * + * returns 0 on success, <0 on failure. + */ +int mei_ioctl_connect_client(struct file *file, + struct mei_connect_client_data *data) +{ + struct mei_device *dev; + struct mei_cl_cb *cb; + struct mei_client *client; + struct mei_cl *cl; + long timeout = mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT); + int i; + int err; + int rets; + + cl = file->private_data; + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + dev_dbg(&dev->pdev->dev, "mei_ioctl_connect_client() Entry\n"); + + /* buffered ioctl cb */ + cb = mei_io_cb_init(cl, file); + if (!cb) { + rets = -ENOMEM; + goto end; + } + + cb->fop_type = MEI_FOP_IOCTL; + + if (dev->dev_state != MEI_DEV_ENABLED) { + rets = -ENODEV; + goto end; + } + if (cl->state != MEI_FILE_INITIALIZING && + cl->state != MEI_FILE_DISCONNECTED) { + rets = -EBUSY; + goto end; + } + + /* find ME client we're trying to connect to */ + i = mei_me_cl_by_uuid(dev, &data->in_client_uuid); + if (i >= 0 && !dev->me_clients[i].props.fixed_address) { + cl->me_client_id = dev->me_clients[i].client_id; + cl->state = MEI_FILE_CONNECTING; + } + + dev_dbg(&dev->pdev->dev, "Connect to FW Client ID = %d\n", + cl->me_client_id); + dev_dbg(&dev->pdev->dev, "FW Client - Protocol Version = %d\n", + dev->me_clients[i].props.protocol_version); + dev_dbg(&dev->pdev->dev, "FW Client - Max Msg Len = %d\n", + dev->me_clients[i].props.max_msg_length); + + /* if we're connecting to amthi client then we will use the + * existing connection + */ + if (uuid_le_cmp(data->in_client_uuid, mei_amthi_guid) == 0) { + dev_dbg(&dev->pdev->dev, "FW Client is amthi\n"); + if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { + rets = -ENODEV; + goto end; + } + clear_bit(cl->host_client_id, dev->host_clients_map); + mei_me_cl_unlink(dev, cl); + + kfree(cl); + cl = NULL; + file->private_data = &dev->iamthif_cl; + + client = &data->out_client_properties; + client->max_msg_length = + dev->me_clients[i].props.max_msg_length; + client->protocol_version = + dev->me_clients[i].props.protocol_version; + rets = dev->iamthif_cl.status; + + goto end; + } + + if (cl->state != MEI_FILE_CONNECTING) { + rets = -ENODEV; + goto end; + } + + + /* prepare the output buffer */ + client = &data->out_client_properties; + client->max_msg_length = dev->me_clients[i].props.max_msg_length; + client->protocol_version = dev->me_clients[i].props.protocol_version; + dev_dbg(&dev->pdev->dev, "Can connect?\n"); + if (dev->mei_host_buffer_is_empty + && !mei_other_client_is_connecting(dev, cl)) { + dev_dbg(&dev->pdev->dev, "Sending Connect Message\n"); + dev->mei_host_buffer_is_empty = false; + if (mei_hbm_cl_connect_req(dev, cl)) { + dev_dbg(&dev->pdev->dev, "Sending connect message - failed\n"); + rets = -ENODEV; + goto end; + } else { + dev_dbg(&dev->pdev->dev, "Sending connect message - succeeded\n"); + cl->timer_count = MEI_CONNECT_TIMEOUT; + list_add_tail(&cb->list, &dev->ctrl_rd_list.list); + } + + + } else { + dev_dbg(&dev->pdev->dev, "Queuing the connect request due to device busy\n"); + dev_dbg(&dev->pdev->dev, "add connect cb to control write list.\n"); + list_add_tail(&cb->list, &dev->ctrl_wr_list.list); + } + mutex_unlock(&dev->device_lock); + err = wait_event_timeout(dev->wait_recvd_msg, + (MEI_FILE_CONNECTED == cl->state || + MEI_FILE_DISCONNECTED == cl->state), timeout); + + mutex_lock(&dev->device_lock); + if (MEI_FILE_CONNECTED == cl->state) { + dev_dbg(&dev->pdev->dev, "successfully connected to FW client.\n"); + rets = cl->status; + goto end; + } else { + dev_dbg(&dev->pdev->dev, "failed to connect to FW client.cl->state = %d.\n", + cl->state); + if (!err) { + dev_dbg(&dev->pdev->dev, + "wait_event_interruptible_timeout failed on client" + " connect message fw response message.\n"); + } + rets = -EFAULT; + + mei_io_list_flush(&dev->ctrl_rd_list, cl); + mei_io_list_flush(&dev->ctrl_wr_list, cl); + goto end; + } + rets = 0; +end: + dev_dbg(&dev->pdev->dev, "free connect cb memory."); + mei_io_cb_free(cb); + return rets; +} + +/** + * mei_start_read - the start read client message function. + * + * @dev: the device structure + * @if_num: minor number + * @cl: private data of the file object + * + * returns 0 on success, <0 on failure. + */ +int mei_start_read(struct mei_device *dev, struct mei_cl *cl) +{ + struct mei_cl_cb *cb; + int rets; + int i; + + if (cl->state != MEI_FILE_CONNECTED) + return -ENODEV; + + if (dev->dev_state != MEI_DEV_ENABLED) + return -ENODEV; + + if (cl->read_pending || cl->read_cb) { + dev_dbg(&dev->pdev->dev, "read is pending.\n"); + return -EBUSY; + } + i = mei_me_cl_by_id(dev, cl->me_client_id); + if (i < 0) { + dev_err(&dev->pdev->dev, "no such me client %d\n", + cl->me_client_id); + return -ENODEV; + } + + cb = mei_io_cb_init(cl, NULL); + if (!cb) + return -ENOMEM; + + rets = mei_io_cb_alloc_resp_buf(cb, + dev->me_clients[i].props.max_msg_length); + if (rets) + goto err; + + cb->fop_type = MEI_FOP_READ; + cl->read_cb = cb; + if (dev->mei_host_buffer_is_empty) { + dev->mei_host_buffer_is_empty = false; + if (mei_hbm_cl_flow_control_req(dev, cl)) { + rets = -ENODEV; + goto err; + } + list_add_tail(&cb->list, &dev->read_list.list); + } else { + list_add_tail(&cb->list, &dev->ctrl_wr_list.list); + } + return rets; +err: + mei_io_cb_free(cb); + return rets; +} + diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 55895fc21ff1..6c1f1f838d2b 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -22,7 +22,6 @@ #include #include "mei_dev.h" -#include "hbm.h" #include "interface.h" const char *mei_dev_state_str(int state) @@ -45,47 +44,6 @@ const char *mei_dev_state_str(int state) -/** - * mei_io_list_flush - removes list entry belonging to cl. - * - * @list: An instance of our list structure - * @cl: private data of the file object - */ -void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl) -{ - struct mei_cl_cb *pos; - struct mei_cl_cb *next; - - list_for_each_entry_safe(pos, next, &list->list, list) { - if (pos->cl) { - if (mei_cl_cmp_id(cl, pos->cl)) - list_del(&pos->list); - } - } -} -/** - * mei_cl_flush_queues - flushes queue lists belonging to cl. - * - * @dev: the device structure - * @cl: private data of the file object - */ -int mei_cl_flush_queues(struct mei_cl *cl) -{ - if (!cl || !cl->dev) - return -EINVAL; - - dev_dbg(&cl->dev->pdev->dev, "remove list entry belonging to cl\n"); - mei_io_list_flush(&cl->dev->read_list, cl); - mei_io_list_flush(&cl->dev->write_list, cl); - mei_io_list_flush(&cl->dev->write_waiting_list, cl); - mei_io_list_flush(&cl->dev->ctrl_wr_list, cl); - mei_io_list_flush(&cl->dev->ctrl_rd_list, cl); - mei_io_list_flush(&cl->dev->amthif_cmd_list, cl); - mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl); - return 0; -} - - /** * init_mei_device - allocates and initializes the mei device structure @@ -360,215 +318,5 @@ void mei_allocate_me_clients_storage(struct mei_device *dev) return ; } -void mei_host_client_init(struct work_struct *work) -{ - struct mei_device *dev = container_of(work, - struct mei_device, init_work); - struct mei_client_properties *client_props; - int i; - - mutex_lock(&dev->device_lock); - - bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); - dev->open_handle_count = 0; - - /* - * Reserving the first three client IDs - * 0: Reserved for MEI Bus Message communications - * 1: Reserved for Watchdog - * 2: Reserved for AMTHI - */ - bitmap_set(dev->host_clients_map, 0, 3); - - for (i = 0; i < dev->me_clients_num; i++) { - client_props = &dev->me_clients[i].props; - - if (!uuid_le_cmp(client_props->protocol_name, mei_amthi_guid)) - mei_amthif_host_init(dev); - else if (!uuid_le_cmp(client_props->protocol_name, mei_wd_guid)) - mei_wd_host_init(dev); - } - dev->dev_state = MEI_DEV_ENABLED; - - mutex_unlock(&dev->device_lock); -} - - -/** - * mei_init_file_private - initializes private file structure. - * - * @priv: private file structure to be initialized - * @file: the file structure - */ -void mei_cl_init(struct mei_cl *priv, struct mei_device *dev) -{ - memset(priv, 0, sizeof(struct mei_cl)); - init_waitqueue_head(&priv->wait); - init_waitqueue_head(&priv->rx_wait); - init_waitqueue_head(&priv->tx_wait); - INIT_LIST_HEAD(&priv->link); - priv->reading_state = MEI_IDLE; - priv->writing_state = MEI_IDLE; - priv->dev = dev; -} - -int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *cuuid) -{ - int i, res = -ENOENT; - - for (i = 0; i < dev->me_clients_num; ++i) - if (uuid_le_cmp(*cuuid, - dev->me_clients[i].props.protocol_name) == 0) { - res = i; - break; - } - - return res; -} - - -/** - * mei_me_cl_link - create link between host and me clinet and add - * me_cl to the list - * - * @dev: the device structure - * @cl: link between me and host client assocated with opened file descriptor - * @cuuid: uuid of ME client - * @client_id: id of the host client - * - * returns ME client index if ME client - * -EINVAL on incorrect values - * -ENONET if client not found - */ -int mei_me_cl_link(struct mei_device *dev, struct mei_cl *cl, - const uuid_le *cuuid, u8 host_cl_id) -{ - int i; - - if (!dev || !cl || !cuuid) - return -EINVAL; - - /* check for valid client id */ - i = mei_me_cl_by_uuid(dev, cuuid); - if (i >= 0) { - cl->me_client_id = dev->me_clients[i].client_id; - cl->state = MEI_FILE_CONNECTING; - cl->host_client_id = host_cl_id; - - list_add_tail(&cl->link, &dev->file_list); - return (u8)i; - } - - return -ENOENT; -} -/** - * mei_me_cl_unlink - remove me_cl from the list - * - * @dev: the device structure - * @host_client_id: host client id to be removed - */ -void mei_me_cl_unlink(struct mei_device *dev, struct mei_cl *cl) -{ - struct mei_cl *pos, *next; - list_for_each_entry_safe(pos, next, &dev->file_list, link) { - if (cl->host_client_id == pos->host_client_id) { - dev_dbg(&dev->pdev->dev, "remove host client = %d, ME client = %d\n", - pos->host_client_id, pos->me_client_id); - list_del_init(&pos->link); - break; - } - } -} - -/** - * mei_alloc_file_private - allocates a private file structure and sets it up. - * @file: the file structure - * - * returns The allocated file or NULL on failure - */ -struct mei_cl *mei_cl_allocate(struct mei_device *dev) -{ - struct mei_cl *cl; - - cl = kmalloc(sizeof(struct mei_cl), GFP_KERNEL); - if (!cl) - return NULL; - - mei_cl_init(cl, dev); - - return cl; -} - - - -/** - * mei_disconnect_host_client - sends disconnect message to fw from host client. - * - * @dev: the device structure - * @cl: private data of the file object - * - * Locking: called under "dev->device_lock" lock - * - * returns 0 on success, <0 on failure. - */ -int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl) -{ - struct mei_cl_cb *cb; - int rets, err; - - if (!dev || !cl) - return -ENODEV; - - if (cl->state != MEI_FILE_DISCONNECTING) - return 0; - - cb = mei_io_cb_init(cl, NULL); - if (!cb) - return -ENOMEM; - - cb->fop_type = MEI_FOP_CLOSE; - if (dev->mei_host_buffer_is_empty) { - dev->mei_host_buffer_is_empty = false; - if (mei_hbm_cl_disconnect_req(dev, cl)) { - rets = -ENODEV; - dev_err(&dev->pdev->dev, "failed to disconnect.\n"); - goto free; - } - mdelay(10); /* Wait for hardware disconnection ready */ - list_add_tail(&cb->list, &dev->ctrl_rd_list.list); - } else { - dev_dbg(&dev->pdev->dev, "add disconnect cb to control write list\n"); - list_add_tail(&cb->list, &dev->ctrl_wr_list.list); - - } - mutex_unlock(&dev->device_lock); - - err = wait_event_timeout(dev->wait_recvd_msg, - MEI_FILE_DISCONNECTED == cl->state, - mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); - - mutex_lock(&dev->device_lock); - if (MEI_FILE_DISCONNECTED == cl->state) { - rets = 0; - dev_dbg(&dev->pdev->dev, "successfully disconnected from FW client.\n"); - } else { - rets = -ENODEV; - if (MEI_FILE_DISCONNECTED != cl->state) - dev_dbg(&dev->pdev->dev, "wrong status client disconnect.\n"); - - if (err) - dev_dbg(&dev->pdev->dev, - "wait failed disconnect err=%08x\n", - err); - - dev_dbg(&dev->pdev->dev, "failed to disconnect from FW client.\n"); - } - - mei_io_list_flush(&dev->ctrl_rd_list, cl); - mei_io_list_flush(&dev->ctrl_wr_list, cl); -free: - mei_io_cb_free(cb); - return rets; -} diff --git a/drivers/misc/mei/interface.c b/drivers/misc/mei/interface.c index 155bd7efe4e5..3cb0cff01285 100644 --- a/drivers/misc/mei/interface.c +++ b/drivers/misc/mei/interface.c @@ -297,99 +297,3 @@ void mei_read_slots(struct mei_device *dev, unsigned char *buffer, mei_hcsr_set(dev); } -/** - * mei_flow_ctrl_creds - checks flow_control credentials. - * - * @dev: the device structure - * @cl: private data of the file object - * - * returns 1 if mei_flow_ctrl_creds >0, 0 - otherwise. - * -ENOENT if mei_cl is not present - * -EINVAL if single_recv_buf == 0 - */ -int mei_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl) -{ - int i; - - if (!dev->me_clients_num) - return 0; - - if (cl->mei_flow_ctrl_creds > 0) - return 1; - - for (i = 0; i < dev->me_clients_num; i++) { - struct mei_me_client *me_cl = &dev->me_clients[i]; - if (me_cl->client_id == cl->me_client_id) { - if (me_cl->mei_flow_ctrl_creds) { - if (WARN_ON(me_cl->props.single_recv_buf == 0)) - return -EINVAL; - return 1; - } else { - return 0; - } - } - } - return -ENOENT; -} - -/** - * mei_flow_ctrl_reduce - reduces flow_control. - * - * @dev: the device structure - * @cl: private data of the file object - * @returns - * 0 on success - * -ENOENT when me client is not found - * -EINVAL when ctrl credits are <= 0 - */ -int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl) -{ - int i; - - if (!dev->me_clients_num) - return -ENOENT; - - for (i = 0; i < dev->me_clients_num; i++) { - struct mei_me_client *me_cl = &dev->me_clients[i]; - if (me_cl->client_id == cl->me_client_id) { - if (me_cl->props.single_recv_buf != 0) { - if (WARN_ON(me_cl->mei_flow_ctrl_creds <= 0)) - return -EINVAL; - dev->me_clients[i].mei_flow_ctrl_creds--; - } else { - if (WARN_ON(cl->mei_flow_ctrl_creds <= 0)) - return -EINVAL; - cl->mei_flow_ctrl_creds--; - } - return 0; - } - } - return -ENOENT; -} - - -/** - * mei_other_client_is_connecting - checks if other - * client with the same client id is connected. - * - * @dev: the device structure - * @cl: private data of the file object - * - * returns 1 if other client is connected, 0 - otherwise. - */ -int mei_other_client_is_connecting(struct mei_device *dev, - struct mei_cl *cl) -{ - struct mei_cl *cl_pos = NULL; - struct mei_cl *cl_next = NULL; - - list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { - if ((cl_pos->state == MEI_FILE_CONNECTING) && - (cl_pos != cl) && - cl->me_client_id == cl_pos->me_client_id) - return 1; - - } - return 0; -} - diff --git a/drivers/misc/mei/iorw.c b/drivers/misc/mei/iorw.c deleted file mode 100644 index 4328c2d2ca54..000000000000 --- a/drivers/misc/mei/iorw.c +++ /dev/null @@ -1,367 +0,0 @@ -/* - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#include - -#include "mei_dev.h" -#include "hbm.h" -#include "interface.h" - -/** - * mei_io_cb_free - free mei_cb_private related memory - * - * @cb: mei callback struct - */ -void mei_io_cb_free(struct mei_cl_cb *cb) -{ - if (cb == NULL) - return; - - kfree(cb->request_buffer.data); - kfree(cb->response_buffer.data); - kfree(cb); -} -/** - * mei_io_cb_init - allocate and initialize io callback - * - * @cl - mei client - * @file: pointer to file structure - * - * returns mei_cl_cb pointer or NULL; - */ -struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp) -{ - struct mei_cl_cb *cb; - - cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); - if (!cb) - return NULL; - - mei_io_list_init(cb); - - cb->file_object = fp; - cb->cl = cl; - cb->buf_idx = 0; - return cb; -} - - -/** - * mei_io_cb_alloc_req_buf - allocate request buffer - * - * @cb - io callback structure - * @size: size of the buffer - * - * returns 0 on success - * -EINVAL if cb is NULL - * -ENOMEM if allocation failed - */ -int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length) -{ - if (!cb) - return -EINVAL; - - if (length == 0) - return 0; - - cb->request_buffer.data = kmalloc(length, GFP_KERNEL); - if (!cb->request_buffer.data) - return -ENOMEM; - cb->request_buffer.size = length; - return 0; -} -/** - * mei_io_cb_alloc_req_buf - allocate respose buffer - * - * @cb - io callback structure - * @size: size of the buffer - * - * returns 0 on success - * -EINVAL if cb is NULL - * -ENOMEM if allocation failed - */ -int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length) -{ - if (!cb) - return -EINVAL; - - if (length == 0) - return 0; - - cb->response_buffer.data = kmalloc(length, GFP_KERNEL); - if (!cb->response_buffer.data) - return -ENOMEM; - cb->response_buffer.size = length; - return 0; -} - - -/** - * mei_me_cl_by_id return index to me_clients for client_id - * - * @dev: the device structure - * @client_id: me client id - * - * Locking: called under "dev->device_lock" lock - * - * returns index on success, -ENOENT on failure. - */ - -int mei_me_cl_by_id(struct mei_device *dev, u8 client_id) -{ - int i; - for (i = 0; i < dev->me_clients_num; i++) - if (dev->me_clients[i].client_id == client_id) - break; - if (WARN_ON(dev->me_clients[i].client_id != client_id)) - return -ENOENT; - - if (i == dev->me_clients_num) - return -ENOENT; - - return i; -} - -/** - * mei_ioctl_connect_client - the connect to fw client IOCTL function - * - * @dev: the device structure - * @data: IOCTL connect data, input and output parameters - * @file: private data of the file object - * - * Locking: called under "dev->device_lock" lock - * - * returns 0 on success, <0 on failure. - */ -int mei_ioctl_connect_client(struct file *file, - struct mei_connect_client_data *data) -{ - struct mei_device *dev; - struct mei_cl_cb *cb; - struct mei_client *client; - struct mei_cl *cl; - long timeout = mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT); - int i; - int err; - int rets; - - cl = file->private_data; - if (WARN_ON(!cl || !cl->dev)) - return -ENODEV; - - dev = cl->dev; - - dev_dbg(&dev->pdev->dev, "mei_ioctl_connect_client() Entry\n"); - - /* buffered ioctl cb */ - cb = mei_io_cb_init(cl, file); - if (!cb) { - rets = -ENOMEM; - goto end; - } - - cb->fop_type = MEI_FOP_IOCTL; - - if (dev->dev_state != MEI_DEV_ENABLED) { - rets = -ENODEV; - goto end; - } - if (cl->state != MEI_FILE_INITIALIZING && - cl->state != MEI_FILE_DISCONNECTED) { - rets = -EBUSY; - goto end; - } - - /* find ME client we're trying to connect to */ - i = mei_me_cl_by_uuid(dev, &data->in_client_uuid); - if (i >= 0 && !dev->me_clients[i].props.fixed_address) { - cl->me_client_id = dev->me_clients[i].client_id; - cl->state = MEI_FILE_CONNECTING; - } - - dev_dbg(&dev->pdev->dev, "Connect to FW Client ID = %d\n", - cl->me_client_id); - dev_dbg(&dev->pdev->dev, "FW Client - Protocol Version = %d\n", - dev->me_clients[i].props.protocol_version); - dev_dbg(&dev->pdev->dev, "FW Client - Max Msg Len = %d\n", - dev->me_clients[i].props.max_msg_length); - - /* if we're connecting to amthi client then we will use the - * existing connection - */ - if (uuid_le_cmp(data->in_client_uuid, mei_amthi_guid) == 0) { - dev_dbg(&dev->pdev->dev, "FW Client is amthi\n"); - if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { - rets = -ENODEV; - goto end; - } - clear_bit(cl->host_client_id, dev->host_clients_map); - mei_me_cl_unlink(dev, cl); - - kfree(cl); - cl = NULL; - file->private_data = &dev->iamthif_cl; - - client = &data->out_client_properties; - client->max_msg_length = - dev->me_clients[i].props.max_msg_length; - client->protocol_version = - dev->me_clients[i].props.protocol_version; - rets = dev->iamthif_cl.status; - - goto end; - } - - if (cl->state != MEI_FILE_CONNECTING) { - rets = -ENODEV; - goto end; - } - - - /* prepare the output buffer */ - client = &data->out_client_properties; - client->max_msg_length = dev->me_clients[i].props.max_msg_length; - client->protocol_version = dev->me_clients[i].props.protocol_version; - dev_dbg(&dev->pdev->dev, "Can connect?\n"); - if (dev->mei_host_buffer_is_empty - && !mei_other_client_is_connecting(dev, cl)) { - dev_dbg(&dev->pdev->dev, "Sending Connect Message\n"); - dev->mei_host_buffer_is_empty = false; - if (mei_hbm_cl_connect_req(dev, cl)) { - dev_dbg(&dev->pdev->dev, "Sending connect message - failed\n"); - rets = -ENODEV; - goto end; - } else { - dev_dbg(&dev->pdev->dev, "Sending connect message - succeeded\n"); - cl->timer_count = MEI_CONNECT_TIMEOUT; - list_add_tail(&cb->list, &dev->ctrl_rd_list.list); - } - - - } else { - dev_dbg(&dev->pdev->dev, "Queuing the connect request due to device busy\n"); - dev_dbg(&dev->pdev->dev, "add connect cb to control write list.\n"); - list_add_tail(&cb->list, &dev->ctrl_wr_list.list); - } - mutex_unlock(&dev->device_lock); - err = wait_event_timeout(dev->wait_recvd_msg, - (MEI_FILE_CONNECTED == cl->state || - MEI_FILE_DISCONNECTED == cl->state), timeout); - - mutex_lock(&dev->device_lock); - if (MEI_FILE_CONNECTED == cl->state) { - dev_dbg(&dev->pdev->dev, "successfully connected to FW client.\n"); - rets = cl->status; - goto end; - } else { - dev_dbg(&dev->pdev->dev, "failed to connect to FW client.cl->state = %d.\n", - cl->state); - if (!err) { - dev_dbg(&dev->pdev->dev, - "wait_event_interruptible_timeout failed on client" - " connect message fw response message.\n"); - } - rets = -EFAULT; - - mei_io_list_flush(&dev->ctrl_rd_list, cl); - mei_io_list_flush(&dev->ctrl_wr_list, cl); - goto end; - } - rets = 0; -end: - dev_dbg(&dev->pdev->dev, "free connect cb memory."); - mei_io_cb_free(cb); - return rets; -} - -/** - * mei_start_read - the start read client message function. - * - * @dev: the device structure - * @if_num: minor number - * @cl: private data of the file object - * - * returns 0 on success, <0 on failure. - */ -int mei_start_read(struct mei_device *dev, struct mei_cl *cl) -{ - struct mei_cl_cb *cb; - int rets; - int i; - - if (cl->state != MEI_FILE_CONNECTED) - return -ENODEV; - - if (dev->dev_state != MEI_DEV_ENABLED) - return -ENODEV; - - if (cl->read_pending || cl->read_cb) { - dev_dbg(&dev->pdev->dev, "read is pending.\n"); - return -EBUSY; - } - i = mei_me_cl_by_id(dev, cl->me_client_id); - if (i < 0) { - dev_err(&dev->pdev->dev, "no such me client %d\n", - cl->me_client_id); - return -ENODEV; - } - - cb = mei_io_cb_init(cl, NULL); - if (!cb) - return -ENOMEM; - - rets = mei_io_cb_alloc_resp_buf(cb, - dev->me_clients[i].props.max_msg_length); - if (rets) - goto err; - - cb->fop_type = MEI_FOP_READ; - cl->read_cb = cb; - if (dev->mei_host_buffer_is_empty) { - dev->mei_host_buffer_is_empty = false; - if (mei_hbm_cl_flow_control_req(dev, cl)) { - rets = -ENODEV; - goto err; - } - list_add_tail(&cb->list, &dev->read_list.list); - } else { - list_add_tail(&cb->list, &dev->ctrl_wr_list.list); - } - return rets; -err: - mei_io_cb_free(cb); - return rets; -} - -- cgit v1.2.1 From 90e0b5f18569bdd03c5ddd1d8c99946f42af77b8 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:14 +0200 Subject: mei: fix client functions names Use common prefix for function names: mei_cl_ - for host clients mei_me_ - for me clients mei_io_ - for io callback functions Because mei_cl holds mei_device back pointer we can also drop the dev argument from the client functions add client.h header to export the clients API Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 9 +- drivers/misc/mei/client.c | 221 ++++++++++++++++++++++++++----------------- drivers/misc/mei/client.h | 97 +++++++++++++++++++ drivers/misc/mei/init.c | 6 +- drivers/misc/mei/interface.h | 8 -- drivers/misc/mei/interrupt.c | 11 ++- drivers/misc/mei/main.c | 47 +++------ drivers/misc/mei/mei_dev.h | 53 ----------- drivers/misc/mei/wd.c | 13 +-- 9 files changed, 267 insertions(+), 198 deletions(-) create mode 100644 drivers/misc/mei/client.h diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index add4254eb850..cbc9c4e4e321 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -36,6 +36,7 @@ #include "mei_dev.h" #include "hbm.h" #include "interface.h" +#include "client.h" const uuid_le mei_amthi_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac, 0xa8, 0x46, 0xe0, 0xff, 0x65, @@ -73,7 +74,7 @@ void mei_amthif_host_init(struct mei_device *dev) dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; /* find ME amthi client */ - i = mei_me_cl_link(dev, &dev->iamthif_cl, + i = mei_cl_link_me(&dev->iamthif_cl, &mei_amthi_guid, MEI_IAMTHIF_HOST_CLIENT_ID); if (i < 0) { dev_info(&dev->pdev->dev, "failed to find iamthif client.\n"); @@ -280,7 +281,7 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) memcpy(dev->iamthif_msg_buf, cb->request_buffer.data, cb->request_buffer.size); - ret = mei_flow_ctrl_creds(dev, &dev->iamthif_cl); + ret = mei_cl_flow_ctrl_creds(&dev->iamthif_cl); if (ret < 0) return ret; @@ -304,7 +305,7 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) return -ENODEV; if (mei_hdr.msg_complete) { - if (mei_flow_ctrl_reduce(dev, &dev->iamthif_cl)) + if (mei_cl_flow_ctrl_reduce(&dev->iamthif_cl)) return -ENODEV; dev->iamthif_flow_control_pending = true; dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; @@ -467,7 +468,7 @@ int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, return -ENODEV; } - if (mei_flow_ctrl_reduce(dev, cl)) + if (mei_cl_flow_ctrl_reduce(cl)) return -ENODEV; dev->iamthif_msg_buf_index += mei_hdr.length; diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 19f62073fa67..e300637c89ed 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -24,6 +24,54 @@ #include "mei_dev.h" #include "hbm.h" #include "interface.h" +#include "client.h" + +/** + * mei_me_cl_by_uuid - locate index of me client + * + * @dev: mei device + * returns me client index or -ENOENT if not found + */ +int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *uuid) +{ + int i, res = -ENOENT; + + for (i = 0; i < dev->me_clients_num; ++i) + if (uuid_le_cmp(*uuid, + dev->me_clients[i].props.protocol_name) == 0) { + res = i; + break; + } + + return res; +} + + +/** + * mei_me_cl_by_id return index to me_clients for client_id + * + * @dev: the device structure + * @client_id: me client id + * + * Locking: called under "dev->device_lock" lock + * + * returns index on success, -ENOENT on failure. + */ + +int mei_me_cl_by_id(struct mei_device *dev, u8 client_id) +{ + int i; + for (i = 0; i < dev->me_clients_num; i++) + if (dev->me_clients[i].client_id == client_id) + break; + if (WARN_ON(dev->me_clients[i].client_id != client_id)) + return -ENOENT; + + if (i == dev->me_clients_num) + return -ENOENT; + + return i; +} /** @@ -141,7 +189,7 @@ int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length) */ int mei_cl_flush_queues(struct mei_cl *cl) { - if (!cl || !cl->dev) + if (WARN_ON(!cl || !cl->dev)) return -EINVAL; dev_dbg(&cl->dev->pdev->dev, "remove list entry belonging to cl\n"); @@ -155,52 +203,6 @@ int mei_cl_flush_queues(struct mei_cl *cl) return 0; } -/** - * mei_me_cl_by_uuid - locate index of me client - * - * @dev: mei device - * returns me client index or -ENOENT if not found - */ -int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *uuid) -{ - int i, res = -ENOENT; - - for (i = 0; i < dev->me_clients_num; ++i) - if (uuid_le_cmp(*uuid, - dev->me_clients[i].props.protocol_name) == 0) { - res = i; - break; - } - - return res; -} - - -/** - * mei_me_cl_by_id return index to me_clients for client_id - * - * @dev: the device structure - * @client_id: me client id - * - * Locking: called under "dev->device_lock" lock - * - * returns index on success, -ENOENT on failure. - */ - -int mei_me_cl_by_id(struct mei_device *dev, u8 client_id) -{ - int i; - for (i = 0; i < dev->me_clients_num; i++) - if (dev->me_clients[i].client_id == client_id) - break; - if (WARN_ON(dev->me_clients[i].client_id != client_id)) - return -ENOENT; - - if (i == dev->me_clients_num) - return -ENOENT; - - return i; -} /** * mei_cl_init - initializes intialize cl. @@ -239,12 +241,29 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev) return cl; } +/** + * mei_cl_find_read_cb - find this cl's callback in the read list + * + * @dev: device structure + * returns cb on success, NULL on error + */ +struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) +{ + struct mei_device *dev = cl->dev; + struct mei_cl_cb *cb = NULL; + struct mei_cl_cb *next = NULL; + + list_for_each_entry_safe(cb, next, &dev->read_list.list, list) + if (mei_cl_cmp_id(cl, cb->cl)) + return cb; + return NULL; +} + /** * mei_me_cl_link - create link between host and me clinet and add * me_cl to the list * - * @dev: the device structure * @cl: link between me and host client assocated with opened file descriptor * @uuid: uuid of ME client * @client_id: id of the host client @@ -253,14 +272,16 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev) * -EINVAL on incorrect values * -ENONET if client not found */ -int mei_me_cl_link(struct mei_device *dev, struct mei_cl *cl, - const uuid_le *uuid, u8 host_cl_id) +int mei_cl_link_me(struct mei_cl *cl, const uuid_le *uuid, u8 host_cl_id) { + struct mei_device *dev; int i; - if (!dev || !cl || !uuid) + if (WARN_ON(!cl || !cl->dev || !uuid)) return -EINVAL; + dev = cl->dev; + /* check for valid client id */ i = mei_me_cl_by_uuid(dev, uuid); if (i >= 0) { @@ -275,22 +296,30 @@ int mei_me_cl_link(struct mei_device *dev, struct mei_cl *cl, return -ENOENT; } /** - * mei_me_cl_unlink - remove me_cl from the list + * mei_cl_unlink - remove me_cl from the list * * @dev: the device structure * @host_client_id: host client id to be removed */ -void mei_me_cl_unlink(struct mei_device *dev, struct mei_cl *cl) +int mei_cl_unlink(struct mei_cl *cl) { + struct mei_device *dev; struct mei_cl *pos, *next; + + if (WARN_ON(!cl || !cl->dev)) + return -EINVAL; + + dev = cl->dev; + list_for_each_entry_safe(pos, next, &dev->file_list, link) { if (cl->host_client_id == pos->host_client_id) { dev_dbg(&dev->pdev->dev, "remove host client = %d, ME client = %d\n", - pos->host_client_id, pos->me_client_id); + pos->host_client_id, pos->me_client_id); list_del_init(&pos->link); break; } } + return 0; } @@ -330,23 +359,25 @@ void mei_host_client_init(struct work_struct *work) /** - * mei_disconnect_host_client - sends disconnect message to fw from host client. + * mei_cl_disconnect - disconnect host clinet form the me one * - * @dev: the device structure - * @cl: private data of the file object + * @cl: host client * * Locking: called under "dev->device_lock" lock * * returns 0 on success, <0 on failure. */ -int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl) +int mei_cl_disconnect(struct mei_cl *cl) { + struct mei_device *dev; struct mei_cl_cb *cb; int rets, err; - if (!dev || !cl) + if (WARN_ON(!cl || !cl->dev)) return -ENODEV; + dev = cl->dev; + if (cl->state != MEI_FILE_DISCONNECTING) return 0; @@ -401,32 +432,36 @@ free: /** - * mei_other_client_is_connecting - checks if other - * client with the same client id is connected. + * mei_cl_is_other_connecting - checks if other + * client with the same me client id is connecting * - * @dev: the device structure * @cl: private data of the file object * - * returns 1 if other client is connected, 0 - otherwise. + * returns ture if other client is connected, 0 - otherwise. */ -int mei_other_client_is_connecting(struct mei_device *dev, - struct mei_cl *cl) +bool mei_cl_is_other_connecting(struct mei_cl *cl) { - struct mei_cl *cl_pos = NULL; - struct mei_cl *cl_next = NULL; + struct mei_device *dev; + struct mei_cl *pos; + struct mei_cl *next; - list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { - if ((cl_pos->state == MEI_FILE_CONNECTING) && - (cl_pos != cl) && - cl->me_client_id == cl_pos->me_client_id) - return 1; + if (WARN_ON(!cl || !cl->dev)) + return false; + + dev = cl->dev; + + list_for_each_entry_safe(pos, next, &dev->file_list, link) { + if ((pos->state == MEI_FILE_CONNECTING) && + (pos != cl) && cl->me_client_id == pos->me_client_id) + return true; } - return 0; + + return false; } /** - * mei_flow_ctrl_creds - checks flow_control credentials. + * mei_cl_flow_ctrl_creds - checks flow_control credits for cl. * * @dev: the device structure * @cl: private data of the file object @@ -435,10 +470,16 @@ int mei_other_client_is_connecting(struct mei_device *dev, * -ENOENT if mei_cl is not present * -EINVAL if single_recv_buf == 0 */ -int mei_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl) +int mei_cl_flow_ctrl_creds(struct mei_cl *cl) { + struct mei_device *dev; int i; + if (WARN_ON(!cl || !cl->dev)) + return -EINVAL; + + dev = cl->dev; + if (!dev->me_clients_num) return 0; @@ -461,7 +502,7 @@ int mei_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl) } /** - * mei_flow_ctrl_reduce - reduces flow_control. + * mei_cl_flow_ctrl_reduce - reduces flow_control. * * @dev: the device structure * @cl: private data of the file object @@ -470,10 +511,16 @@ int mei_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl) * -ENOENT when me client is not found * -EINVAL when ctrl credits are <= 0 */ -int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl) +int mei_cl_flow_ctrl_reduce(struct mei_cl *cl) { + struct mei_device *dev; int i; + if (WARN_ON(!cl || !cl->dev)) + return -EINVAL; + + dev = cl->dev; + if (!dev->me_clients_num) return -ENOENT; @@ -571,7 +618,7 @@ int mei_ioctl_connect_client(struct file *file, goto end; } clear_bit(cl->host_client_id, dev->host_clients_map); - mei_me_cl_unlink(dev, cl); + mei_cl_unlink(cl); kfree(cl); cl = NULL; @@ -598,8 +645,8 @@ int mei_ioctl_connect_client(struct file *file, client->max_msg_length = dev->me_clients[i].props.max_msg_length; client->protocol_version = dev->me_clients[i].props.protocol_version; dev_dbg(&dev->pdev->dev, "Can connect?\n"); - if (dev->mei_host_buffer_is_empty - && !mei_other_client_is_connecting(dev, cl)) { + if (dev->mei_host_buffer_is_empty && + !mei_cl_is_other_connecting(cl)) { dev_dbg(&dev->pdev->dev, "Sending Connect Message\n"); dev->mei_host_buffer_is_empty = false; if (mei_hbm_cl_connect_req(dev, cl)) { @@ -650,20 +697,24 @@ end: } /** - * mei_start_read - the start read client message function. + * mei_cl_start_read - the start read client message function. * - * @dev: the device structure - * @if_num: minor number - * @cl: private data of the file object + * @cl: host client * * returns 0 on success, <0 on failure. */ -int mei_start_read(struct mei_device *dev, struct mei_cl *cl) +int mei_cl_read_start(struct mei_cl *cl) { + struct mei_device *dev; struct mei_cl_cb *cb; int rets; int i; + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + if (cl->state != MEI_FILE_CONNECTED) return -ENODEV; diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h new file mode 100644 index 000000000000..8dfd052dd0e6 --- /dev/null +++ b/drivers/misc/mei/client.h @@ -0,0 +1,97 @@ +/* + * + * 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. + * + */ + +#ifndef _MEI_CLIENT_H_ +#define _MEI_CLIENT_H_ + +#include +#include +#include +#include + +#include "mei_dev.h" + +int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *cuuid); +int mei_me_cl_by_id(struct mei_device *dev, u8 client_id); + +/* + * MEI IO Functions + */ +struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp); +void mei_io_cb_free(struct mei_cl_cb *priv_cb); +int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length); +int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length); + + +/** + * mei_io_list_init - Sets up a queue list. + * + * @list: An instance cl callback structure + */ +static inline void mei_io_list_init(struct mei_cl_cb *list) +{ + INIT_LIST_HEAD(&list->list); +} +void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl); + +/* + * MEI Host Client Functions + */ + +struct mei_cl *mei_cl_allocate(struct mei_device *dev); +void mei_cl_init(struct mei_cl *cl, struct mei_device *dev); + + +int mei_cl_link_me(struct mei_cl *cl, const uuid_le *uuid, u8 host_cl_id); +int mei_cl_unlink(struct mei_cl *cl); + +int mei_cl_flush_queues(struct mei_cl *cl); +struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl); + +/** + * mei_cl_cmp_id - tells if file private data have same id + * + * @fe1: private data of 1. file object + * @fe2: private data of 2. file object + * + * returns true - if ids are the same and not NULL + */ +static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, + const struct mei_cl *cl2) +{ + return cl1 && cl2 && + (cl1->host_client_id == cl2->host_client_id) && + (cl1->me_client_id == cl2->me_client_id); +} + + +int mei_cl_flow_ctrl_creds(struct mei_cl *cl); + +int mei_cl_flow_ctrl_reduce(struct mei_cl *cl); +/* + * MEI input output function prototype + */ +bool mei_cl_is_other_connecting(struct mei_cl *cl); +int mei_cl_disconnect(struct mei_cl *cl); + +int mei_cl_read_start(struct mei_cl *cl); + +int mei_cl_connect(struct mei_cl *cl, struct file *file); + +void mei_host_client_init(struct work_struct *work); + + +#endif /* _MEI_CLIENT_H_ */ diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 6c1f1f838d2b..7028dbd99cf7 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -23,6 +23,7 @@ #include "mei_dev.h" #include "interface.h" +#include "client.h" const char *mei_dev_state_str(int state) { @@ -241,9 +242,8 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) } /* remove entry if already in list */ dev_dbg(&dev->pdev->dev, "remove iamthif and wd from the file list.\n"); - mei_me_cl_unlink(dev, &dev->wd_cl); - - mei_me_cl_unlink(dev, &dev->iamthif_cl); + mei_cl_unlink(&dev->wd_cl); + mei_cl_unlink(&dev->iamthif_cl); mei_amthif_reset_params(dev); memset(&dev->wr_ext_msg, 0, sizeof(dev->wr_ext_msg)); diff --git a/drivers/misc/mei/interface.h b/drivers/misc/mei/interface.h index 3d06c087ddd2..01d1ef518595 100644 --- a/drivers/misc/mei/interface.h +++ b/drivers/misc/mei/interface.h @@ -50,7 +50,6 @@ static inline unsigned char mei_data2slots(size_t length) int mei_count_full_read_slots(struct mei_device *dev); -int mei_flow_ctrl_creds(struct mei_device *dev, struct mei_cl *cl); @@ -69,12 +68,5 @@ void mei_watchdog_register(struct mei_device *dev); */ void mei_watchdog_unregister(struct mei_device *dev); -int mei_other_client_is_connecting(struct mei_device *dev, struct mei_cl *cl); -int mei_flow_ctrl_reduce(struct mei_device *dev, struct mei_cl *cl); - -void mei_host_client_init(struct work_struct *work); - - - #endif /* _MEI_INTERFACE_H_ */ diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 2495e35ccb27..0a141afcea89 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -26,6 +26,7 @@ #include "mei_dev.h" #include "hbm.h" #include "interface.h" +#include "client.h" /** @@ -297,7 +298,7 @@ static int mei_irq_thread_write_complete(struct mei_device *dev, s32 *slots, return -ENODEV; } - if (mei_flow_ctrl_reduce(dev, cl)) + if (mei_cl_flow_ctrl_reduce(cl)) return -ENODEV; cl->status = 0; @@ -478,10 +479,10 @@ static int mei_irq_thread_write_handler(struct mei_device *dev, } if (dev->dev_state == MEI_DEV_ENABLED) { if (dev->wd_pending && - mei_flow_ctrl_creds(dev, &dev->wd_cl) > 0) { + mei_cl_flow_ctrl_creds(&dev->wd_cl) > 0) { if (mei_wd_send(dev)) dev_dbg(&dev->pdev->dev, "wd send failed.\n"); - else if (mei_flow_ctrl_reduce(dev, &dev->wd_cl)) + else if (mei_cl_flow_ctrl_reduce(&dev->wd_cl)) return -ENODEV; dev->wd_pending = false; @@ -520,7 +521,7 @@ static int mei_irq_thread_write_handler(struct mei_device *dev, break; case MEI_FOP_IOCTL: /* connect message */ - if (mei_other_client_is_connecting(dev, cl)) + if (mei_cl_is_other_connecting(cl)) continue; ret = _mei_irq_thread_ioctl(dev, &slots, pos, cl, cmpl_list); @@ -540,7 +541,7 @@ static int mei_irq_thread_write_handler(struct mei_device *dev, cl = pos->cl; if (cl == NULL) continue; - if (mei_flow_ctrl_creds(dev, cl) <= 0) { + if (mei_cl_flow_ctrl_creds(cl) <= 0) { dev_dbg(&dev->pdev->dev, "No flow control credentials for client %d, not sending.\n", cl->host_client_id); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index da9426054815..95f05d97a115 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -41,6 +41,7 @@ #include "mei_dev.h" #include "interface.h" +#include "client.h" /* AMT device is a singleton on the platform */ static struct pci_dev *mei_pdev; @@ -90,28 +91,6 @@ MODULE_DEVICE_TABLE(pci, mei_pci_tbl); static DEFINE_MUTEX(mei_mutex); -/** - * find_read_list_entry - find read list entry - * - * @dev: device structure - * @file: pointer to file structure - * - * returns cb on success, NULL on error - */ -static struct mei_cl_cb *find_read_list_entry( - struct mei_device *dev, - struct mei_cl *cl) -{ - struct mei_cl_cb *pos = NULL; - struct mei_cl_cb *next = NULL; - - dev_dbg(&dev->pdev->dev, "remove read_list CB\n"); - list_for_each_entry_safe(pos, next, &dev->read_list.list, list) - if (mei_cl_cmp_id(cl, pos->cl)) - return pos; - return NULL; -} - /** * mei_open - the open function * @@ -217,7 +196,7 @@ static int mei_release(struct inode *inode, struct file *file) "ME client = %d\n", cl->host_client_id, cl->me_client_id); - rets = mei_disconnect_host_client(dev, cl); + rets = mei_cl_disconnect(cl); } mei_cl_flush_queues(cl); dev_dbg(&dev->pdev->dev, "remove client host client = %d, ME client = %d\n", @@ -228,12 +207,12 @@ static int mei_release(struct inode *inode, struct file *file) clear_bit(cl->host_client_id, dev->host_clients_map); dev->open_handle_count--; } - mei_me_cl_unlink(dev, cl); + mei_cl_unlink(cl); /* free read cb */ cb = NULL; if (cl->read_cb) { - cb = find_read_list_entry(dev, cl); + cb = mei_cl_find_read_cb(cl); /* Remove entry from read list */ if (cb) list_del(&cb->list); @@ -323,7 +302,7 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, goto out; } - err = mei_start_read(dev, cl); + err = mei_cl_read_start(cl); if (err && err != -EBUSY) { dev_dbg(&dev->pdev->dev, "mei start read failure with status = %d\n", err); @@ -394,7 +373,7 @@ copy_buffer: goto out; free: - cb_pos = find_read_list_entry(dev, cl); + cb_pos = mei_cl_find_read_cb(cl); /* Remove entry from read list */ if (cb_pos) list_del(&cb_pos->list); @@ -476,7 +455,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, /* free entry used in read */ if (cl->reading_state == MEI_READ_COMPLETE) { *offset = 0; - write_cb = find_read_list_entry(dev, cl); + write_cb = mei_cl_find_read_cb(cl); if (write_cb) { list_del(&write_cb->list); mei_io_cb_free(write_cb); @@ -531,7 +510,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, dev_dbg(&dev->pdev->dev, "host client = %d, ME client = %d\n", cl->host_client_id, cl->me_client_id); - rets = mei_flow_ctrl_creds(dev, cl); + rets = mei_cl_flow_ctrl_creds(cl); if (rets < 0) goto err; @@ -565,7 +544,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, out: if (mei_hdr.msg_complete) { - if (mei_flow_ctrl_reduce(dev, cl)) { + if (mei_cl_flow_ctrl_reduce(cl)) { rets = -ENODEV; goto err; } @@ -904,11 +883,11 @@ static void mei_remove(struct pci_dev *pdev) if (dev->iamthif_cl.state == MEI_FILE_CONNECTED) { dev->iamthif_cl.state = MEI_FILE_DISCONNECTING; - mei_disconnect_host_client(dev, &dev->iamthif_cl); + mei_cl_disconnect(&dev->iamthif_cl); } if (dev->wd_cl.state == MEI_FILE_CONNECTED) { dev->wd_cl.state = MEI_FILE_DISCONNECTING; - mei_disconnect_host_client(dev, &dev->wd_cl); + mei_cl_disconnect(&dev->wd_cl); } /* Unregistering watchdog device */ @@ -916,8 +895,8 @@ static void mei_remove(struct pci_dev *pdev) /* remove entry if already in list */ dev_dbg(&pdev->dev, "list del iamthif and wd file list.\n"); - mei_me_cl_unlink(dev, &dev->wd_cl); - mei_me_cl_unlink(dev, &dev->iamthif_cl); + mei_cl_unlink(&dev->wd_cl); + mei_cl_unlink(&dev->iamthif_cl); dev->iamthif_current_cb = NULL; dev->me_clients_num = 0; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 1b54e675d3f1..5a1ac9a37e10 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -329,59 +329,9 @@ void mei_reset(struct mei_device *dev, int interrupts); int mei_hw_init(struct mei_device *dev); int mei_task_initialize_clients(void *data); int mei_initialize_clients(struct mei_device *dev); -int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl); void mei_allocate_me_clients_storage(struct mei_device *dev); -int mei_me_cl_link(struct mei_device *dev, struct mei_cl *cl, - const uuid_le *cguid, u8 host_client_id); -void mei_me_cl_unlink(struct mei_device *dev, struct mei_cl *cl); -int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *cuuid); -int mei_me_cl_by_id(struct mei_device *dev, u8 client_id); - -/* - * MEI IO Functions - */ -struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp); -void mei_io_cb_free(struct mei_cl_cb *priv_cb); -int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length); -int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length); - - -/** - * mei_io_list_init - Sets up a queue list. - * - * @list: An instance cl callback structure - */ -static inline void mei_io_list_init(struct mei_cl_cb *list) -{ - INIT_LIST_HEAD(&list->list); -} -void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl); - -/* - * MEI ME Client Functions - */ - -struct mei_cl *mei_cl_allocate(struct mei_device *dev); -void mei_cl_init(struct mei_cl *cl, struct mei_device *dev); -int mei_cl_flush_queues(struct mei_cl *cl); -/** - * mei_cl_cmp_id - tells if file private data have same id - * - * @fe1: private data of 1. file object - * @fe2: private data of 2. file object - * - * returns true - if ids are the same and not NULL - */ -static inline bool mei_cl_cmp_id(const struct mei_cl *cl1, - const struct mei_cl *cl2) -{ - return cl1 && cl2 && - (cl1->host_client_id == cl2->host_client_id) && - (cl1->me_client_id == cl2->me_client_id); -} - /* * MEI interrupt functions prototype @@ -395,9 +345,6 @@ void mei_timer(struct work_struct *work); */ int mei_ioctl_connect_client(struct file *file, struct mei_connect_client_data *data); - -int mei_start_read(struct mei_device *dev, struct mei_cl *cl); - /* * AMTHIF - AMT Host Interface Functions */ diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index 9814bc1dba01..5ad5225ea2b9 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -26,6 +26,7 @@ #include "mei_dev.h" #include "hbm.h" #include "interface.h" +#include "client.h" static const u8 mei_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 }; static const u8 mei_stop_wd_params[] = { 0x02, 0x02, 0x14, 0x10 }; @@ -72,7 +73,7 @@ int mei_wd_host_init(struct mei_device *dev) dev->wd_state = MEI_WD_IDLE; /* Connect WD ME client to the host client */ - id = mei_me_cl_link(dev, &dev->wd_cl, + id = mei_cl_link_me(&dev->wd_cl, &mei_wd_guid, MEI_WD_HOST_CLIENT_ID); if (id < 0) { @@ -141,7 +142,7 @@ int mei_wd_stop(struct mei_device *dev) dev->wd_state = MEI_WD_STOPPING; - ret = mei_flow_ctrl_creds(dev, &dev->wd_cl); + ret = mei_cl_flow_ctrl_creds(&dev->wd_cl); if (ret < 0) goto out; @@ -150,7 +151,7 @@ int mei_wd_stop(struct mei_device *dev) dev->mei_host_buffer_is_empty = false; if (!mei_wd_send(dev)) { - ret = mei_flow_ctrl_reduce(dev, &dev->wd_cl); + ret = mei_cl_flow_ctrl_reduce(&dev->wd_cl); if (ret) goto out; } else { @@ -271,7 +272,7 @@ static int mei_wd_ops_ping(struct watchdog_device *wd_dev) /* Check if we can send the ping to HW*/ if (dev->mei_host_buffer_is_empty && - mei_flow_ctrl_creds(dev, &dev->wd_cl) > 0) { + mei_cl_flow_ctrl_creds(&dev->wd_cl) > 0) { dev->mei_host_buffer_is_empty = false; dev_dbg(&dev->pdev->dev, "wd: sending ping\n"); @@ -282,9 +283,9 @@ static int mei_wd_ops_ping(struct watchdog_device *wd_dev) goto end; } - if (mei_flow_ctrl_reduce(dev, &dev->wd_cl)) { + if (mei_cl_flow_ctrl_reduce(&dev->wd_cl)) { dev_err(&dev->pdev->dev, - "wd: mei_flow_ctrl_reduce() failed.\n"); + "wd: mei_cl_flow_ctrl_reduce() failed.\n"); ret = -EIO; goto end; } -- cgit v1.2.1 From 9f81abdac3629629a246fdc9e2a7c01ffd52ce8a Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:15 +0200 Subject: mei: implement mei_cl_connect function Implement mei_cl_connect that warps host client parts of the connection and leave the ioctl specifics in the mei_ioctl_connect_client function. Move mei_ioctl_connect_client to main.c where it belongs Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 219 ++++++++++++++------------------------------- drivers/misc/mei/main.c | 98 ++++++++++++++++++++ drivers/misc/mei/mei_dev.h | 5 -- 3 files changed, 163 insertions(+), 159 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index e300637c89ed..cc3e76c60417 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -460,6 +460,71 @@ bool mei_cl_is_other_connecting(struct mei_cl *cl) return false; } +/** + * mei_cl_connect - connect host clinet to the me one + * + * @cl: host client + * + * Locking: called under "dev->device_lock" lock + * + * returns 0 on success, <0 on failure. + */ +int mei_cl_connect(struct mei_cl *cl, struct file *file) +{ + struct mei_device *dev; + struct mei_cl_cb *cb; + long timeout = mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT); + int rets; + + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + cb = mei_io_cb_init(cl, file); + if (!cb) { + rets = -ENOMEM; + goto out; + } + + cb->fop_type = MEI_FOP_IOCTL; + + if (dev->mei_host_buffer_is_empty && + !mei_cl_is_other_connecting(cl)) { + dev->mei_host_buffer_is_empty = false; + + if (mei_hbm_cl_connect_req(dev, cl)) { + rets = -ENODEV; + goto out; + } + cl->timer_count = MEI_CONNECT_TIMEOUT; + list_add_tail(&cb->list, &dev->ctrl_rd_list.list); + } else { + list_add_tail(&cb->list, &dev->ctrl_wr_list.list); + } + + mutex_unlock(&dev->device_lock); + rets = wait_event_timeout(dev->wait_recvd_msg, + (cl->state == MEI_FILE_CONNECTED || + cl->state == MEI_FILE_DISCONNECTED), + timeout * HZ); + mutex_lock(&dev->device_lock); + + if (cl->state != MEI_FILE_CONNECTED) { + rets = -EFAULT; + + mei_io_list_flush(&dev->ctrl_rd_list, cl); + mei_io_list_flush(&dev->ctrl_wr_list, cl); + goto out; + } + + rets = cl->status; + +out: + mei_io_cb_free(cb); + return rets; +} + /** * mei_cl_flow_ctrl_creds - checks flow_control credits for cl. * @@ -542,160 +607,6 @@ int mei_cl_flow_ctrl_reduce(struct mei_cl *cl) return -ENOENT; } - - -/** - * mei_ioctl_connect_client - the connect to fw client IOCTL function - * - * @dev: the device structure - * @data: IOCTL connect data, input and output parameters - * @file: private data of the file object - * - * Locking: called under "dev->device_lock" lock - * - * returns 0 on success, <0 on failure. - */ -int mei_ioctl_connect_client(struct file *file, - struct mei_connect_client_data *data) -{ - struct mei_device *dev; - struct mei_cl_cb *cb; - struct mei_client *client; - struct mei_cl *cl; - long timeout = mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT); - int i; - int err; - int rets; - - cl = file->private_data; - if (WARN_ON(!cl || !cl->dev)) - return -ENODEV; - - dev = cl->dev; - - dev_dbg(&dev->pdev->dev, "mei_ioctl_connect_client() Entry\n"); - - /* buffered ioctl cb */ - cb = mei_io_cb_init(cl, file); - if (!cb) { - rets = -ENOMEM; - goto end; - } - - cb->fop_type = MEI_FOP_IOCTL; - - if (dev->dev_state != MEI_DEV_ENABLED) { - rets = -ENODEV; - goto end; - } - if (cl->state != MEI_FILE_INITIALIZING && - cl->state != MEI_FILE_DISCONNECTED) { - rets = -EBUSY; - goto end; - } - - /* find ME client we're trying to connect to */ - i = mei_me_cl_by_uuid(dev, &data->in_client_uuid); - if (i >= 0 && !dev->me_clients[i].props.fixed_address) { - cl->me_client_id = dev->me_clients[i].client_id; - cl->state = MEI_FILE_CONNECTING; - } - - dev_dbg(&dev->pdev->dev, "Connect to FW Client ID = %d\n", - cl->me_client_id); - dev_dbg(&dev->pdev->dev, "FW Client - Protocol Version = %d\n", - dev->me_clients[i].props.protocol_version); - dev_dbg(&dev->pdev->dev, "FW Client - Max Msg Len = %d\n", - dev->me_clients[i].props.max_msg_length); - - /* if we're connecting to amthi client then we will use the - * existing connection - */ - if (uuid_le_cmp(data->in_client_uuid, mei_amthi_guid) == 0) { - dev_dbg(&dev->pdev->dev, "FW Client is amthi\n"); - if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { - rets = -ENODEV; - goto end; - } - clear_bit(cl->host_client_id, dev->host_clients_map); - mei_cl_unlink(cl); - - kfree(cl); - cl = NULL; - file->private_data = &dev->iamthif_cl; - - client = &data->out_client_properties; - client->max_msg_length = - dev->me_clients[i].props.max_msg_length; - client->protocol_version = - dev->me_clients[i].props.protocol_version; - rets = dev->iamthif_cl.status; - - goto end; - } - - if (cl->state != MEI_FILE_CONNECTING) { - rets = -ENODEV; - goto end; - } - - - /* prepare the output buffer */ - client = &data->out_client_properties; - client->max_msg_length = dev->me_clients[i].props.max_msg_length; - client->protocol_version = dev->me_clients[i].props.protocol_version; - dev_dbg(&dev->pdev->dev, "Can connect?\n"); - if (dev->mei_host_buffer_is_empty && - !mei_cl_is_other_connecting(cl)) { - dev_dbg(&dev->pdev->dev, "Sending Connect Message\n"); - dev->mei_host_buffer_is_empty = false; - if (mei_hbm_cl_connect_req(dev, cl)) { - dev_dbg(&dev->pdev->dev, "Sending connect message - failed\n"); - rets = -ENODEV; - goto end; - } else { - dev_dbg(&dev->pdev->dev, "Sending connect message - succeeded\n"); - cl->timer_count = MEI_CONNECT_TIMEOUT; - list_add_tail(&cb->list, &dev->ctrl_rd_list.list); - } - - - } else { - dev_dbg(&dev->pdev->dev, "Queuing the connect request due to device busy\n"); - dev_dbg(&dev->pdev->dev, "add connect cb to control write list.\n"); - list_add_tail(&cb->list, &dev->ctrl_wr_list.list); - } - mutex_unlock(&dev->device_lock); - err = wait_event_timeout(dev->wait_recvd_msg, - (MEI_FILE_CONNECTED == cl->state || - MEI_FILE_DISCONNECTED == cl->state), timeout); - - mutex_lock(&dev->device_lock); - if (MEI_FILE_CONNECTED == cl->state) { - dev_dbg(&dev->pdev->dev, "successfully connected to FW client.\n"); - rets = cl->status; - goto end; - } else { - dev_dbg(&dev->pdev->dev, "failed to connect to FW client.cl->state = %d.\n", - cl->state); - if (!err) { - dev_dbg(&dev->pdev->dev, - "wait_event_interruptible_timeout failed on client" - " connect message fw response message.\n"); - } - rets = -EFAULT; - - mei_io_list_flush(&dev->ctrl_rd_list, cl); - mei_io_list_flush(&dev->ctrl_wr_list, cl); - goto end; - } - rets = 0; -end: - dev_dbg(&dev->pdev->dev, "free connect cb memory."); - mei_io_cb_free(cb); - return rets; -} - /** * mei_cl_start_read - the start read client message function. * diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 95f05d97a115..8d3c134314c6 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -562,6 +562,103 @@ err: return rets; } +/** + * mei_ioctl_connect_client - the connect to fw client IOCTL function + * + * @dev: the device structure + * @data: IOCTL connect data, input and output parameters + * @file: private data of the file object + * + * Locking: called under "dev->device_lock" lock + * + * returns 0 on success, <0 on failure. + */ +static int mei_ioctl_connect_client(struct file *file, + struct mei_connect_client_data *data) +{ + struct mei_device *dev; + struct mei_client *client; + struct mei_cl *cl; + int i; + int rets; + + cl = file->private_data; + if (WARN_ON(!cl || !cl->dev)) + return -ENODEV; + + dev = cl->dev; + + if (dev->dev_state != MEI_DEV_ENABLED) { + rets = -ENODEV; + goto end; + } + + if (cl->state != MEI_FILE_INITIALIZING && + cl->state != MEI_FILE_DISCONNECTED) { + rets = -EBUSY; + goto end; + } + + /* find ME client we're trying to connect to */ + i = mei_me_cl_by_uuid(dev, &data->in_client_uuid); + if (i >= 0 && !dev->me_clients[i].props.fixed_address) { + cl->me_client_id = dev->me_clients[i].client_id; + cl->state = MEI_FILE_CONNECTING; + } + + dev_dbg(&dev->pdev->dev, "Connect to FW Client ID = %d\n", + cl->me_client_id); + dev_dbg(&dev->pdev->dev, "FW Client - Protocol Version = %d\n", + dev->me_clients[i].props.protocol_version); + dev_dbg(&dev->pdev->dev, "FW Client - Max Msg Len = %d\n", + dev->me_clients[i].props.max_msg_length); + + /* if we're connecting to amthi client then we will use the + * existing connection + */ + if (uuid_le_cmp(data->in_client_uuid, mei_amthi_guid) == 0) { + dev_dbg(&dev->pdev->dev, "FW Client is amthi\n"); + if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { + rets = -ENODEV; + goto end; + } + clear_bit(cl->host_client_id, dev->host_clients_map); + mei_cl_unlink(cl); + + kfree(cl); + cl = NULL; + file->private_data = &dev->iamthif_cl; + + client = &data->out_client_properties; + client->max_msg_length = + dev->me_clients[i].props.max_msg_length; + client->protocol_version = + dev->me_clients[i].props.protocol_version; + rets = dev->iamthif_cl.status; + + goto end; + } + + if (cl->state != MEI_FILE_CONNECTING) { + rets = -ENODEV; + goto end; + } + + + /* prepare the output buffer */ + client = &data->out_client_properties; + client->max_msg_length = dev->me_clients[i].props.max_msg_length; + client->protocol_version = dev->me_clients[i].props.protocol_version; + dev_dbg(&dev->pdev->dev, "Can connect?\n"); + + + rets = mei_cl_connect(cl, file); + +end: + dev_dbg(&dev->pdev->dev, "free connect cb memory."); + return rets; +} + /** * mei_ioctl - the IOCTL function @@ -610,6 +707,7 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) rets = -EFAULT; goto out; } + rets = mei_ioctl_connect_client(file, connect_data); /* if all is ok, copying the data back to user. */ diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 5a1ac9a37e10..f3da1533c619 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -340,11 +340,6 @@ irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id); irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id); void mei_timer(struct work_struct *work); -/* - * MEI input output function prototype - */ -int mei_ioctl_connect_client(struct file *file, - struct mei_connect_client_data *data); /* * AMTHIF - AMT Host Interface Functions */ -- cgit v1.2.1 From 37e7d6e74f2e80196410f6e5c7533d99fe9aa9d1 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:16 +0200 Subject: mei: move watchdog prototypes to mei_dev.h from interface.h interface.h contains lower layer functionality Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/interface.h | 20 -------------------- drivers/misc/mei/mei_dev.h | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/drivers/misc/mei/interface.h b/drivers/misc/mei/interface.h index 01d1ef518595..73bef545e4d5 100644 --- a/drivers/misc/mei/interface.h +++ b/drivers/misc/mei/interface.h @@ -49,24 +49,4 @@ static inline unsigned char mei_data2slots(size_t length) int mei_count_full_read_slots(struct mei_device *dev); - - - - -int mei_wd_send(struct mei_device *dev); -int mei_wd_stop(struct mei_device *dev); -int mei_wd_host_init(struct mei_device *dev); -/* - * mei_watchdog_register - Registering watchdog interface - * once we got connection to the WD Client - * @dev - mei device - */ -void mei_watchdog_register(struct mei_device *dev); -/* - * mei_watchdog_unregister - Unregistering watchdog interface - * @dev - mei device - */ -void mei_watchdog_unregister(struct mei_device *dev); - - #endif /* _MEI_INTERFACE_H_ */ diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index f3da1533c619..d41177b3fc69 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -371,6 +371,23 @@ int mei_amthif_irq_read_message(struct mei_cl_cb *complete_list, struct mei_device *dev, struct mei_msg_hdr *mei_hdr); int mei_amthif_irq_read(struct mei_device *dev, s32 *slots); + +int mei_wd_send(struct mei_device *dev); +int mei_wd_stop(struct mei_device *dev); +int mei_wd_host_init(struct mei_device *dev); +/* + * mei_watchdog_register - Registering watchdog interface + * once we got connection to the WD Client + * @dev - mei device + */ +void mei_watchdog_register(struct mei_device *dev); +/* + * mei_watchdog_unregister - Unregistering watchdog interface + * @dev - mei device + */ +void mei_watchdog_unregister(struct mei_device *dev); + + /* * Register Access Function */ -- cgit v1.2.1 From 9dc64d6a26b016df52d222abe9279a92d9f7cc4c Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:17 +0200 Subject: mei: rename interface to hw-me Rename hw-me.h to hw-me-regs.h as this file contains only register definitions. Files hw-me.[ch] now contains ME hw dependant functionality Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/Makefile | 2 +- drivers/misc/mei/amthif.c | 2 +- drivers/misc/mei/client.c | 1 - drivers/misc/mei/hbm.c | 2 +- drivers/misc/mei/hw-me-regs.h | 167 +++++++++++++++++++++++ drivers/misc/mei/hw-me.c | 299 ++++++++++++++++++++++++++++++++++++++++++ drivers/misc/mei/hw-me.h | 181 +++++-------------------- drivers/misc/mei/init.c | 1 - drivers/misc/mei/interface.c | 299 ------------------------------------------ drivers/misc/mei/interface.h | 52 -------- drivers/misc/mei/interrupt.c | 2 +- drivers/misc/mei/main.c | 2 +- drivers/misc/mei/mei_dev.h | 2 +- drivers/misc/mei/wd.c | 2 +- 14 files changed, 506 insertions(+), 508 deletions(-) create mode 100644 drivers/misc/mei/hw-me-regs.h create mode 100644 drivers/misc/mei/hw-me.c delete mode 100644 drivers/misc/mei/interface.c delete mode 100644 drivers/misc/mei/interface.h diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 67f543ab234f..9f719339e594 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_INTEL_MEI) += mei.o mei-objs := init.o mei-objs += hbm.o mei-objs += interrupt.o -mei-objs += interface.o +mei-objs += hw-me.o mei-objs += main.o mei-objs += amthif.o mei-objs += wd.o diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index cbc9c4e4e321..adc4120c91d3 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -35,7 +35,7 @@ #include "mei_dev.h" #include "hbm.h" -#include "interface.h" +#include "hw-me.h" #include "client.h" const uuid_le mei_amthi_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac, diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index cc3e76c60417..e2e9cb7df067 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -23,7 +23,6 @@ #include "mei_dev.h" #include "hbm.h" -#include "interface.h" #include "client.h" /** diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 9956aaf58aa4..f0c3fc4590d5 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -21,7 +21,7 @@ #include "mei_dev.h" #include "hbm.h" -#include "interface.h" +#include "hw-me.h" /** * mei_hbm_cl_hdr - construct client hbm header diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h new file mode 100644 index 000000000000..6a203b6e8346 --- /dev/null +++ b/drivers/misc/mei/hw-me-regs.h @@ -0,0 +1,167 @@ +/****************************************************************************** + * Intel Management Engine Interface (Intel MEI) Linux driver + * Intel MEI Interface Header + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2003 - 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, + * USA + * + * The full GNU General Public License is included in this distribution + * in the file called LICENSE.GPL. + * + * Contact Information: + * Intel Corporation. + * linux-mei@linux.intel.com + * http://www.intel.com + * + * BSD LICENSE + * + * Copyright(c) 2003 - 2012 Intel Corporation. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ +#ifndef _MEI_HW_MEI_REGS_H_ +#define _MEI_HW_MEI_REGS_H_ + +/* + * MEI device IDs + */ +#define MEI_DEV_ID_82946GZ 0x2974 /* 82946GZ/GL */ +#define MEI_DEV_ID_82G35 0x2984 /* 82G35 Express */ +#define MEI_DEV_ID_82Q965 0x2994 /* 82Q963/Q965 */ +#define MEI_DEV_ID_82G965 0x29A4 /* 82P965/G965 */ + +#define MEI_DEV_ID_82GM965 0x2A04 /* Mobile PM965/GM965 */ +#define MEI_DEV_ID_82GME965 0x2A14 /* Mobile GME965/GLE960 */ + +#define MEI_DEV_ID_ICH9_82Q35 0x29B4 /* 82Q35 Express */ +#define MEI_DEV_ID_ICH9_82G33 0x29C4 /* 82G33/G31/P35/P31 Express */ +#define MEI_DEV_ID_ICH9_82Q33 0x29D4 /* 82Q33 Express */ +#define MEI_DEV_ID_ICH9_82X38 0x29E4 /* 82X38/X48 Express */ +#define MEI_DEV_ID_ICH9_3200 0x29F4 /* 3200/3210 Server */ + +#define MEI_DEV_ID_ICH9_6 0x28B4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_7 0x28C4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_8 0x28D4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_9 0x28E4 /* Bearlake */ +#define MEI_DEV_ID_ICH9_10 0x28F4 /* Bearlake */ + +#define MEI_DEV_ID_ICH9M_1 0x2A44 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_2 0x2A54 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_3 0x2A64 /* Cantiga */ +#define MEI_DEV_ID_ICH9M_4 0x2A74 /* Cantiga */ + +#define MEI_DEV_ID_ICH10_1 0x2E04 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_2 0x2E14 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_3 0x2E24 /* Eaglelake */ +#define MEI_DEV_ID_ICH10_4 0x2E34 /* Eaglelake */ + +#define MEI_DEV_ID_IBXPK_1 0x3B64 /* Calpella */ +#define MEI_DEV_ID_IBXPK_2 0x3B65 /* Calpella */ + +#define MEI_DEV_ID_CPT_1 0x1C3A /* Couger Point */ +#define MEI_DEV_ID_PBG_1 0x1D3A /* C600/X79 Patsburg */ + +#define MEI_DEV_ID_PPT_1 0x1E3A /* Panther Point */ +#define MEI_DEV_ID_PPT_2 0x1CBA /* Panther Point */ +#define MEI_DEV_ID_PPT_3 0x1DBA /* Panther Point */ + +#define MEI_DEV_ID_LPT 0x8C3A /* Lynx Point */ +#define MEI_DEV_ID_LPT_LP 0x9C3A /* Lynx Point LP */ +/* + * MEI HW Section + */ + +/* MEI registers */ +/* H_CB_WW - Host Circular Buffer (CB) Write Window register */ +#define H_CB_WW 0 +/* H_CSR - Host Control Status register */ +#define H_CSR 4 +/* ME_CB_RW - ME Circular Buffer Read Window register (read only) */ +#define ME_CB_RW 8 +/* ME_CSR_HA - ME Control Status Host Access register (read only) */ +#define ME_CSR_HA 0xC + + +/* register bits of H_CSR (Host Control Status register) */ +/* Host Circular Buffer Depth - maximum number of 32-bit entries in CB */ +#define H_CBD 0xFF000000 +/* Host Circular Buffer Write Pointer */ +#define H_CBWP 0x00FF0000 +/* Host Circular Buffer Read Pointer */ +#define H_CBRP 0x0000FF00 +/* Host Reset */ +#define H_RST 0x00000010 +/* Host Ready */ +#define H_RDY 0x00000008 +/* Host Interrupt Generate */ +#define H_IG 0x00000004 +/* Host Interrupt Status */ +#define H_IS 0x00000002 +/* Host Interrupt Enable */ +#define H_IE 0x00000001 + + +/* register bits of ME_CSR_HA (ME Control Status Host Access register) */ +/* ME CB (Circular Buffer) Depth HRA (Host Read Access) - host read only +access to ME_CBD */ +#define ME_CBD_HRA 0xFF000000 +/* ME CB Write Pointer HRA - host read only access to ME_CBWP */ +#define ME_CBWP_HRA 0x00FF0000 +/* ME CB Read Pointer HRA - host read only access to ME_CBRP */ +#define ME_CBRP_HRA 0x0000FF00 +/* ME Reset HRA - host read only access to ME_RST */ +#define ME_RST_HRA 0x00000010 +/* ME Ready HRA - host read only access to ME_RDY */ +#define ME_RDY_HRA 0x00000008 +/* ME Interrupt Generate HRA - host read only access to ME_IG */ +#define ME_IG_HRA 0x00000004 +/* ME Interrupt Status HRA - host read only access to ME_IS */ +#define ME_IS_HRA 0x00000002 +/* ME Interrupt Enable HRA - host read only access to ME_IE */ +#define ME_IE_HRA 0x00000001 + +#endif /* _MEI_HW_MEI_REGS_H_ */ diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c new file mode 100644 index 000000000000..4e6b657cd806 --- /dev/null +++ b/drivers/misc/mei/hw-me.c @@ -0,0 +1,299 @@ +/* + * + * 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 +#include + +#include "mei_dev.h" +#include "hw-me.h" + +/** + * mei_reg_read - Reads 32bit data from the mei device + * + * @dev: the device structure + * @offset: offset from which to read the data + * + * returns register value (u32) + */ +static inline u32 mei_reg_read(const struct mei_device *dev, + unsigned long offset) +{ + return ioread32(dev->mem_addr + offset); +} + + +/** + * mei_reg_write - Writes 32bit data to the mei device + * + * @dev: the device structure + * @offset: offset from which to write the data + * @value: register value to write (u32) + */ +static inline void mei_reg_write(const struct mei_device *dev, + unsigned long offset, u32 value) +{ + iowrite32(value, dev->mem_addr + offset); +} + +/** + * mei_hcsr_read - Reads 32bit data from the host CSR + * + * @dev: the device structure + * + * returns the byte read. + */ +u32 mei_hcsr_read(const struct mei_device *dev) +{ + return mei_reg_read(dev, H_CSR); +} + +u32 mei_mecbrw_read(const struct mei_device *dev) +{ + return mei_reg_read(dev, ME_CB_RW); +} +/** + * mei_mecsr_read - Reads 32bit data from the ME CSR + * + * @dev: the device structure + * + * returns ME_CSR_HA register value (u32) + */ +u32 mei_mecsr_read(const struct mei_device *dev) +{ + return mei_reg_read(dev, ME_CSR_HA); +} + +/** + * mei_set_csr_register - writes H_CSR register to the mei device, + * and ignores the H_IS bit for it is write-one-to-zero. + * + * @dev: the device structure + */ +void mei_hcsr_set(struct mei_device *dev) +{ + if ((dev->host_hw_state & H_IS) == H_IS) + dev->host_hw_state &= ~H_IS; + mei_reg_write(dev, H_CSR, dev->host_hw_state); + dev->host_hw_state = mei_hcsr_read(dev); +} + +/** + * mei_enable_interrupts - clear and stop interrupts + * + * @dev: the device structure + */ +void mei_clear_interrupts(struct mei_device *dev) +{ + if ((dev->host_hw_state & H_IS) == H_IS) + mei_reg_write(dev, H_CSR, dev->host_hw_state); +} + +/** + * mei_enable_interrupts - enables mei device interrupts + * + * @dev: the device structure + */ +void mei_enable_interrupts(struct mei_device *dev) +{ + dev->host_hw_state |= H_IE; + mei_hcsr_set(dev); +} + +/** + * mei_disable_interrupts - disables mei device interrupts + * + * @dev: the device structure + */ +void mei_disable_interrupts(struct mei_device *dev) +{ + dev->host_hw_state &= ~H_IE; + mei_hcsr_set(dev); +} + + +/** + * mei_interrupt_quick_handler - The ISR of the MEI device + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + */ +irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) +{ + struct mei_device *dev = (struct mei_device *) dev_id; + u32 csr_reg = mei_hcsr_read(dev); + + if ((csr_reg & H_IS) != H_IS) + return IRQ_NONE; + + /* clear H_IS bit in H_CSR */ + mei_reg_write(dev, H_CSR, csr_reg); + + return IRQ_WAKE_THREAD; +} + +/** + * mei_hbuf_filled_slots - gets number of device filled buffer slots + * + * @device: the device structure + * + * returns number of filled slots + */ +static unsigned char mei_hbuf_filled_slots(struct mei_device *dev) +{ + char read_ptr, write_ptr; + + dev->host_hw_state = mei_hcsr_read(dev); + + read_ptr = (char) ((dev->host_hw_state & H_CBRP) >> 8); + write_ptr = (char) ((dev->host_hw_state & H_CBWP) >> 16); + + return (unsigned char) (write_ptr - read_ptr); +} + +/** + * mei_hbuf_is_empty - checks if host buffer is empty. + * + * @dev: the device structure + * + * returns true if empty, false - otherwise. + */ +bool mei_hbuf_is_empty(struct mei_device *dev) +{ + return mei_hbuf_filled_slots(dev) == 0; +} + +/** + * mei_hbuf_empty_slots - counts write empty slots. + * + * @dev: the device structure + * + * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise empty slots count + */ +int mei_hbuf_empty_slots(struct mei_device *dev) +{ + unsigned char filled_slots, empty_slots; + + filled_slots = mei_hbuf_filled_slots(dev); + empty_slots = dev->hbuf_depth - filled_slots; + + /* check for overflow */ + if (filled_slots > dev->hbuf_depth) + return -EOVERFLOW; + + return empty_slots; +} + +/** + * mei_write_message - writes a message to mei device. + * + * @dev: the device structure + * @hader: mei HECI header of message + * @buf: message payload will be written + * + * This function returns -EIO if write has failed + */ +int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, + unsigned char *buf) +{ + unsigned long rem, dw_cnt; + unsigned long length = header->length; + u32 *reg_buf = (u32 *)buf; + int i; + int empty_slots; + + dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(header)); + + empty_slots = mei_hbuf_empty_slots(dev); + dev_dbg(&dev->pdev->dev, "empty slots = %hu.\n", empty_slots); + + dw_cnt = mei_data2slots(length); + if (empty_slots < 0 || dw_cnt > empty_slots) + return -EIO; + + mei_reg_write(dev, H_CB_WW, *((u32 *) header)); + + for (i = 0; i < length / 4; i++) + mei_reg_write(dev, H_CB_WW, reg_buf[i]); + + rem = length & 0x3; + if (rem > 0) { + u32 reg = 0; + memcpy(®, &buf[length - rem], rem); + mei_reg_write(dev, H_CB_WW, reg); + } + + dev->host_hw_state = mei_hcsr_read(dev); + dev->host_hw_state |= H_IG; + mei_hcsr_set(dev); + dev->me_hw_state = mei_mecsr_read(dev); + if ((dev->me_hw_state & ME_RDY_HRA) != ME_RDY_HRA) + return -EIO; + + return 0; +} + +/** + * mei_count_full_read_slots - counts read full slots. + * + * @dev: the device structure + * + * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise filled slots count + */ +int mei_count_full_read_slots(struct mei_device *dev) +{ + char read_ptr, write_ptr; + unsigned char buffer_depth, filled_slots; + + dev->me_hw_state = mei_mecsr_read(dev); + buffer_depth = (unsigned char)((dev->me_hw_state & ME_CBD_HRA) >> 24); + read_ptr = (char) ((dev->me_hw_state & ME_CBRP_HRA) >> 8); + write_ptr = (char) ((dev->me_hw_state & ME_CBWP_HRA) >> 16); + filled_slots = (unsigned char) (write_ptr - read_ptr); + + /* check for overflow */ + if (filled_slots > buffer_depth) + return -EOVERFLOW; + + dev_dbg(&dev->pdev->dev, "filled_slots =%08x\n", filled_slots); + return (int)filled_slots; +} + +/** + * mei_read_slots - reads a message from mei device. + * + * @dev: the device structure + * @buffer: message buffer will be written + * @buffer_length: message size will be read + */ +void mei_read_slots(struct mei_device *dev, unsigned char *buffer, + unsigned long buffer_length) +{ + u32 *reg_buf = (u32 *)buffer; + + for (; buffer_length >= sizeof(u32); buffer_length -= sizeof(u32)) + *reg_buf++ = mei_mecbrw_read(dev); + + if (buffer_length > 0) { + u32 reg = mei_mecbrw_read(dev); + memcpy(reg_buf, ®, buffer_length); + } + + dev->host_hw_state |= H_IG; + mei_hcsr_set(dev); +} + diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index a42b2a2bafa5..73bef545e4d5 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -1,167 +1,52 @@ -/****************************************************************************** - * Intel Management Engine Interface (Intel MEI) Linux driver - * Intel MEI Interface Header - * - * This file is provided under a dual BSD/GPLv2 license. When using or - * redistributing this file, you may do so under either license. - * - * GPL LICENSE SUMMARY - * - * Copyright(c) 2003 - 2012 Intel Corporation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that 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. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, - * USA - * - * The full GNU General Public License is included in this distribution - * in the file called LICENSE.GPL. - * - * Contact Information: - * Intel Corporation. - * linux-mei@linux.intel.com - * http://www.intel.com - * - * BSD LICENSE - * - * Copyright(c) 2003 - 2012 Intel Corporation. All rights reserved. - * All rights reserved. +/* * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, Intel Corporation. * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * * Neither the name Intel Corporation nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. + * 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 SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * 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. * - *****************************************************************************/ -#ifndef _MEI_HW_MEI_H_ -#define _MEI_HW_MEI_H_ - -/* - * MEI device IDs */ -#define MEI_DEV_ID_82946GZ 0x2974 /* 82946GZ/GL */ -#define MEI_DEV_ID_82G35 0x2984 /* 82G35 Express */ -#define MEI_DEV_ID_82Q965 0x2994 /* 82Q963/Q965 */ -#define MEI_DEV_ID_82G965 0x29A4 /* 82P965/G965 */ - -#define MEI_DEV_ID_82GM965 0x2A04 /* Mobile PM965/GM965 */ -#define MEI_DEV_ID_82GME965 0x2A14 /* Mobile GME965/GLE960 */ -#define MEI_DEV_ID_ICH9_82Q35 0x29B4 /* 82Q35 Express */ -#define MEI_DEV_ID_ICH9_82G33 0x29C4 /* 82G33/G31/P35/P31 Express */ -#define MEI_DEV_ID_ICH9_82Q33 0x29D4 /* 82Q33 Express */ -#define MEI_DEV_ID_ICH9_82X38 0x29E4 /* 82X38/X48 Express */ -#define MEI_DEV_ID_ICH9_3200 0x29F4 /* 3200/3210 Server */ -#define MEI_DEV_ID_ICH9_6 0x28B4 /* Bearlake */ -#define MEI_DEV_ID_ICH9_7 0x28C4 /* Bearlake */ -#define MEI_DEV_ID_ICH9_8 0x28D4 /* Bearlake */ -#define MEI_DEV_ID_ICH9_9 0x28E4 /* Bearlake */ -#define MEI_DEV_ID_ICH9_10 0x28F4 /* Bearlake */ -#define MEI_DEV_ID_ICH9M_1 0x2A44 /* Cantiga */ -#define MEI_DEV_ID_ICH9M_2 0x2A54 /* Cantiga */ -#define MEI_DEV_ID_ICH9M_3 0x2A64 /* Cantiga */ -#define MEI_DEV_ID_ICH9M_4 0x2A74 /* Cantiga */ +#ifndef _MEI_INTERFACE_H_ +#define _MEI_INTERFACE_H_ -#define MEI_DEV_ID_ICH10_1 0x2E04 /* Eaglelake */ -#define MEI_DEV_ID_ICH10_2 0x2E14 /* Eaglelake */ -#define MEI_DEV_ID_ICH10_3 0x2E24 /* Eaglelake */ -#define MEI_DEV_ID_ICH10_4 0x2E34 /* Eaglelake */ +#include +#include "mei_dev.h" -#define MEI_DEV_ID_IBXPK_1 0x3B64 /* Calpella */ -#define MEI_DEV_ID_IBXPK_2 0x3B65 /* Calpella */ -#define MEI_DEV_ID_CPT_1 0x1C3A /* Couger Point */ -#define MEI_DEV_ID_PBG_1 0x1D3A /* C600/X79 Patsburg */ -#define MEI_DEV_ID_PPT_1 0x1E3A /* Panther Point */ -#define MEI_DEV_ID_PPT_2 0x1CBA /* Panther Point */ -#define MEI_DEV_ID_PPT_3 0x1DBA /* Panther Point */ +void mei_read_slots(struct mei_device *dev, + unsigned char *buffer, + unsigned long buffer_length); -#define MEI_DEV_ID_LPT 0x8C3A /* Lynx Point */ -#define MEI_DEV_ID_LPT_LP 0x9C3A /* Lynx Point LP */ -/* - * MEI HW Section - */ +int mei_write_message(struct mei_device *dev, + struct mei_msg_hdr *header, + unsigned char *buf); -/* MEI registers */ -/* H_CB_WW - Host Circular Buffer (CB) Write Window register */ -#define H_CB_WW 0 -/* H_CSR - Host Control Status register */ -#define H_CSR 4 -/* ME_CB_RW - ME Circular Buffer Read Window register (read only) */ -#define ME_CB_RW 8 -/* ME_CSR_HA - ME Control Status Host Access register (read only) */ -#define ME_CSR_HA 0xC +bool mei_hbuf_is_empty(struct mei_device *dev); +int mei_hbuf_empty_slots(struct mei_device *dev); -/* register bits of H_CSR (Host Control Status register) */ -/* Host Circular Buffer Depth - maximum number of 32-bit entries in CB */ -#define H_CBD 0xFF000000 -/* Host Circular Buffer Write Pointer */ -#define H_CBWP 0x00FF0000 -/* Host Circular Buffer Read Pointer */ -#define H_CBRP 0x0000FF00 -/* Host Reset */ -#define H_RST 0x00000010 -/* Host Ready */ -#define H_RDY 0x00000008 -/* Host Interrupt Generate */ -#define H_IG 0x00000004 -/* Host Interrupt Status */ -#define H_IS 0x00000002 -/* Host Interrupt Enable */ -#define H_IE 0x00000001 +static inline size_t mei_hbuf_max_data(const struct mei_device *dev) +{ + return dev->hbuf_depth * sizeof(u32) - sizeof(struct mei_msg_hdr); +} +/* get slots (dwords) from a message length + header (bytes) */ +static inline unsigned char mei_data2slots(size_t length) +{ + return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, 4); +} -/* register bits of ME_CSR_HA (ME Control Status Host Access register) */ -/* ME CB (Circular Buffer) Depth HRA (Host Read Access) - host read only -access to ME_CBD */ -#define ME_CBD_HRA 0xFF000000 -/* ME CB Write Pointer HRA - host read only access to ME_CBWP */ -#define ME_CBWP_HRA 0x00FF0000 -/* ME CB Read Pointer HRA - host read only access to ME_CBRP */ -#define ME_CBRP_HRA 0x0000FF00 -/* ME Reset HRA - host read only access to ME_RST */ -#define ME_RST_HRA 0x00000010 -/* ME Ready HRA - host read only access to ME_RDY */ -#define ME_RDY_HRA 0x00000008 -/* ME Interrupt Generate HRA - host read only access to ME_IG */ -#define ME_IG_HRA 0x00000004 -/* ME Interrupt Status HRA - host read only access to ME_IS */ -#define ME_IS_HRA 0x00000002 -/* ME Interrupt Enable HRA - host read only access to ME_IE */ -#define ME_IE_HRA 0x00000001 +int mei_count_full_read_slots(struct mei_device *dev); -#endif /* _MEI_HW_MEI_H_ */ +#endif /* _MEI_INTERFACE_H_ */ diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 7028dbd99cf7..88407dfd8557 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -22,7 +22,6 @@ #include #include "mei_dev.h" -#include "interface.h" #include "client.h" const char *mei_dev_state_str(int state) diff --git a/drivers/misc/mei/interface.c b/drivers/misc/mei/interface.c deleted file mode 100644 index 3cb0cff01285..000000000000 --- a/drivers/misc/mei/interface.c +++ /dev/null @@ -1,299 +0,0 @@ -/* - * - * 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 -#include - -#include "mei_dev.h" -#include "interface.h" - -/** - * mei_reg_read - Reads 32bit data from the mei device - * - * @dev: the device structure - * @offset: offset from which to read the data - * - * returns register value (u32) - */ -static inline u32 mei_reg_read(const struct mei_device *dev, - unsigned long offset) -{ - return ioread32(dev->mem_addr + offset); -} - - -/** - * mei_reg_write - Writes 32bit data to the mei device - * - * @dev: the device structure - * @offset: offset from which to write the data - * @value: register value to write (u32) - */ -static inline void mei_reg_write(const struct mei_device *dev, - unsigned long offset, u32 value) -{ - iowrite32(value, dev->mem_addr + offset); -} - -/** - * mei_hcsr_read - Reads 32bit data from the host CSR - * - * @dev: the device structure - * - * returns the byte read. - */ -u32 mei_hcsr_read(const struct mei_device *dev) -{ - return mei_reg_read(dev, H_CSR); -} - -u32 mei_mecbrw_read(const struct mei_device *dev) -{ - return mei_reg_read(dev, ME_CB_RW); -} -/** - * mei_mecsr_read - Reads 32bit data from the ME CSR - * - * @dev: the device structure - * - * returns ME_CSR_HA register value (u32) - */ -u32 mei_mecsr_read(const struct mei_device *dev) -{ - return mei_reg_read(dev, ME_CSR_HA); -} - -/** - * mei_set_csr_register - writes H_CSR register to the mei device, - * and ignores the H_IS bit for it is write-one-to-zero. - * - * @dev: the device structure - */ -void mei_hcsr_set(struct mei_device *dev) -{ - if ((dev->host_hw_state & H_IS) == H_IS) - dev->host_hw_state &= ~H_IS; - mei_reg_write(dev, H_CSR, dev->host_hw_state); - dev->host_hw_state = mei_hcsr_read(dev); -} - -/** - * mei_enable_interrupts - clear and stop interrupts - * - * @dev: the device structure - */ -void mei_clear_interrupts(struct mei_device *dev) -{ - if ((dev->host_hw_state & H_IS) == H_IS) - mei_reg_write(dev, H_CSR, dev->host_hw_state); -} - -/** - * mei_enable_interrupts - enables mei device interrupts - * - * @dev: the device structure - */ -void mei_enable_interrupts(struct mei_device *dev) -{ - dev->host_hw_state |= H_IE; - mei_hcsr_set(dev); -} - -/** - * mei_disable_interrupts - disables mei device interrupts - * - * @dev: the device structure - */ -void mei_disable_interrupts(struct mei_device *dev) -{ - dev->host_hw_state &= ~H_IE; - mei_hcsr_set(dev); -} - - -/** - * mei_interrupt_quick_handler - The ISR of the MEI device - * - * @irq: The irq number - * @dev_id: pointer to the device structure - * - * returns irqreturn_t - */ -irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) -{ - struct mei_device *dev = (struct mei_device *) dev_id; - u32 csr_reg = mei_hcsr_read(dev); - - if ((csr_reg & H_IS) != H_IS) - return IRQ_NONE; - - /* clear H_IS bit in H_CSR */ - mei_reg_write(dev, H_CSR, csr_reg); - - return IRQ_WAKE_THREAD; -} - -/** - * mei_hbuf_filled_slots - gets number of device filled buffer slots - * - * @device: the device structure - * - * returns number of filled slots - */ -static unsigned char mei_hbuf_filled_slots(struct mei_device *dev) -{ - char read_ptr, write_ptr; - - dev->host_hw_state = mei_hcsr_read(dev); - - read_ptr = (char) ((dev->host_hw_state & H_CBRP) >> 8); - write_ptr = (char) ((dev->host_hw_state & H_CBWP) >> 16); - - return (unsigned char) (write_ptr - read_ptr); -} - -/** - * mei_hbuf_is_empty - checks if host buffer is empty. - * - * @dev: the device structure - * - * returns true if empty, false - otherwise. - */ -bool mei_hbuf_is_empty(struct mei_device *dev) -{ - return mei_hbuf_filled_slots(dev) == 0; -} - -/** - * mei_hbuf_empty_slots - counts write empty slots. - * - * @dev: the device structure - * - * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise empty slots count - */ -int mei_hbuf_empty_slots(struct mei_device *dev) -{ - unsigned char filled_slots, empty_slots; - - filled_slots = mei_hbuf_filled_slots(dev); - empty_slots = dev->hbuf_depth - filled_slots; - - /* check for overflow */ - if (filled_slots > dev->hbuf_depth) - return -EOVERFLOW; - - return empty_slots; -} - -/** - * mei_write_message - writes a message to mei device. - * - * @dev: the device structure - * @hader: mei HECI header of message - * @buf: message payload will be written - * - * This function returns -EIO if write has failed - */ -int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, - unsigned char *buf) -{ - unsigned long rem, dw_cnt; - unsigned long length = header->length; - u32 *reg_buf = (u32 *)buf; - int i; - int empty_slots; - - dev_dbg(&dev->pdev->dev, MEI_HDR_FMT, MEI_HDR_PRM(header)); - - empty_slots = mei_hbuf_empty_slots(dev); - dev_dbg(&dev->pdev->dev, "empty slots = %hu.\n", empty_slots); - - dw_cnt = mei_data2slots(length); - if (empty_slots < 0 || dw_cnt > empty_slots) - return -EIO; - - mei_reg_write(dev, H_CB_WW, *((u32 *) header)); - - for (i = 0; i < length / 4; i++) - mei_reg_write(dev, H_CB_WW, reg_buf[i]); - - rem = length & 0x3; - if (rem > 0) { - u32 reg = 0; - memcpy(®, &buf[length - rem], rem); - mei_reg_write(dev, H_CB_WW, reg); - } - - dev->host_hw_state = mei_hcsr_read(dev); - dev->host_hw_state |= H_IG; - mei_hcsr_set(dev); - dev->me_hw_state = mei_mecsr_read(dev); - if ((dev->me_hw_state & ME_RDY_HRA) != ME_RDY_HRA) - return -EIO; - - return 0; -} - -/** - * mei_count_full_read_slots - counts read full slots. - * - * @dev: the device structure - * - * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise filled slots count - */ -int mei_count_full_read_slots(struct mei_device *dev) -{ - char read_ptr, write_ptr; - unsigned char buffer_depth, filled_slots; - - dev->me_hw_state = mei_mecsr_read(dev); - buffer_depth = (unsigned char)((dev->me_hw_state & ME_CBD_HRA) >> 24); - read_ptr = (char) ((dev->me_hw_state & ME_CBRP_HRA) >> 8); - write_ptr = (char) ((dev->me_hw_state & ME_CBWP_HRA) >> 16); - filled_slots = (unsigned char) (write_ptr - read_ptr); - - /* check for overflow */ - if (filled_slots > buffer_depth) - return -EOVERFLOW; - - dev_dbg(&dev->pdev->dev, "filled_slots =%08x\n", filled_slots); - return (int)filled_slots; -} - -/** - * mei_read_slots - reads a message from mei device. - * - * @dev: the device structure - * @buffer: message buffer will be written - * @buffer_length: message size will be read - */ -void mei_read_slots(struct mei_device *dev, unsigned char *buffer, - unsigned long buffer_length) -{ - u32 *reg_buf = (u32 *)buffer; - - for (; buffer_length >= sizeof(u32); buffer_length -= sizeof(u32)) - *reg_buf++ = mei_mecbrw_read(dev); - - if (buffer_length > 0) { - u32 reg = mei_mecbrw_read(dev); - memcpy(reg_buf, ®, buffer_length); - } - - dev->host_hw_state |= H_IG; - mei_hcsr_set(dev); -} - diff --git a/drivers/misc/mei/interface.h b/drivers/misc/mei/interface.h deleted file mode 100644 index 73bef545e4d5..000000000000 --- a/drivers/misc/mei/interface.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * - * 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. - * - */ - - - -#ifndef _MEI_INTERFACE_H_ -#define _MEI_INTERFACE_H_ - -#include -#include "mei_dev.h" - - - -void mei_read_slots(struct mei_device *dev, - unsigned char *buffer, - unsigned long buffer_length); - -int mei_write_message(struct mei_device *dev, - struct mei_msg_hdr *header, - unsigned char *buf); - -bool mei_hbuf_is_empty(struct mei_device *dev); - -int mei_hbuf_empty_slots(struct mei_device *dev); - -static inline size_t mei_hbuf_max_data(const struct mei_device *dev) -{ - return dev->hbuf_depth * sizeof(u32) - sizeof(struct mei_msg_hdr); -} - -/* get slots (dwords) from a message length + header (bytes) */ -static inline unsigned char mei_data2slots(size_t length) -{ - return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, 4); -} - -int mei_count_full_read_slots(struct mei_device *dev); - -#endif /* _MEI_INTERFACE_H_ */ diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 0a141afcea89..d1ef92617c19 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -25,7 +25,7 @@ #include "mei_dev.h" #include "hbm.h" -#include "interface.h" +#include "hw-me.h" #include "client.h" diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 8d3c134314c6..e31ade30086d 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -40,7 +40,7 @@ #include #include "mei_dev.h" -#include "interface.h" +#include "hw-me.h" #include "client.h" /* AMT device is a singleton on the platform */ diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index d41177b3fc69..87b1f6a1a1dd 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -23,7 +23,7 @@ #include #include "hw.h" -#include "hw-me.h" +#include "hw-me-regs.h" /* * watch dog definition diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index 5ad5225ea2b9..bfcbcc8c028b 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -25,7 +25,7 @@ #include "mei_dev.h" #include "hbm.h" -#include "interface.h" +#include "hw-me.h" #include "client.h" static const u8 mei_start_wd_params[] = { 0x02, 0x12, 0x13, 0x10 }; -- cgit v1.2.1 From d91aaed30a938c5daae2641e6758dfab8727862e Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:18 +0200 Subject: mei: drop read_pending member form struct mei_cl read_pending never changes, it is a leftover from the old code. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 2 +- drivers/misc/mei/main.c | 4 +--- drivers/misc/mei/mei_dev.h | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index e2e9cb7df067..231eda2b09e1 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -631,7 +631,7 @@ int mei_cl_read_start(struct mei_cl *cl) if (dev->dev_state != MEI_DEV_ENABLED) return -ENODEV; - if (cl->read_pending || cl->read_cb) { + if (cl->read_cb) { dev_dbg(&dev->pdev->dev, "read is pending.\n"); return -EBUSY; } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index e31ade30086d..5c7c85505c91 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -380,7 +380,6 @@ free: mei_io_cb_free(cb); cl->reading_state = MEI_IDLE; cl->read_cb = NULL; - cl->read_pending = 0; out: dev_dbg(&dev->pdev->dev, "end mei read rets= %d\n", rets); mutex_unlock(&dev->device_lock); @@ -462,9 +461,8 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, write_cb = NULL; cl->reading_state = MEI_IDLE; cl->read_cb = NULL; - cl->read_pending = 0; } - } else if (cl->reading_state == MEI_IDLE && !cl->read_pending) + } else if (cl->reading_state == MEI_IDLE) *offset = 0; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 87b1f6a1a1dd..11ff875e8658 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -199,7 +199,6 @@ struct mei_cl { wait_queue_head_t tx_wait; wait_queue_head_t rx_wait; wait_queue_head_t wait; - int read_pending; int status; /* ID of client connected */ u8 host_client_id; -- cgit v1.2.1 From a40b260da6d96ab5231ebdabd06e70568ca81885 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:19 +0200 Subject: mei: move me client storage allocation to hbm.c rename function to mei_me_cl_allocate to match the current names convention: mei_hbm_me_cl_allocate Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hbm.c | 39 ++++++++++++++++++++++++++++++++++++++- drivers/misc/mei/init.c | 39 --------------------------------------- drivers/misc/mei/mei_dev.h | 1 - 3 files changed, 38 insertions(+), 41 deletions(-) diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index f0c3fc4590d5..fb9e63ba3bb1 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -23,6 +23,43 @@ #include "hbm.h" #include "hw-me.h" +/** + * mei_hbm_me_cl_allocate - allocates storage for me clients + * + * @dev: the device structure + * + * returns none. + */ +static void mei_hbm_me_cl_allocate(struct mei_device *dev) +{ + struct mei_me_client *clients; + int b; + + /* count how many ME clients we have */ + for_each_set_bit(b, dev->me_clients_map, MEI_CLIENTS_MAX) + dev->me_clients_num++; + + if (dev->me_clients_num <= 0) + return; + + kfree(dev->me_clients); + dev->me_clients = NULL; + + dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%zd.\n", + dev->me_clients_num * sizeof(struct mei_me_client)); + /* allocate storage for ME clients representation */ + clients = kcalloc(dev->me_clients_num, + sizeof(struct mei_me_client), GFP_KERNEL); + if (!clients) { + dev_err(&dev->pdev->dev, "memory allocation for ME clients failed.\n"); + dev->dev_state = MEI_DEV_RESETING; + mei_reset(dev, 1); + return; + } + dev->me_clients = clients; + return; +} + /** * mei_hbm_cl_hdr - construct client hbm header * @cl: - client @@ -593,7 +630,7 @@ void mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->init_clients_timer = 0; dev->me_client_presentation_num = 0; dev->me_client_index = 0; - mei_allocate_me_clients_storage(dev); + mei_hbm_me_cl_allocate(dev); dev->init_clients_state = MEI_CLIENT_PROPERTIES_MESSAGE; diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 88407dfd8557..636639fbfc0a 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -278,44 +278,5 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) } -/** - * allocate_me_clients_storage - allocates storage for me clients - * - * @dev: the device structure - * - * returns none. - */ -void mei_allocate_me_clients_storage(struct mei_device *dev) -{ - struct mei_me_client *clients; - int b; - - /* count how many ME clients we have */ - for_each_set_bit(b, dev->me_clients_map, MEI_CLIENTS_MAX) - dev->me_clients_num++; - - if (dev->me_clients_num <= 0) - return ; - - - if (dev->me_clients != NULL) { - kfree(dev->me_clients); - dev->me_clients = NULL; - } - dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%zd.\n", - dev->me_clients_num * sizeof(struct mei_me_client)); - /* allocate storage for ME clients representation */ - clients = kcalloc(dev->me_clients_num, - sizeof(struct mei_me_client), GFP_KERNEL); - if (!clients) { - dev_dbg(&dev->pdev->dev, "memory allocation for ME clients failed.\n"); - dev->dev_state = MEI_DEV_RESETING; - mei_reset(dev, 1); - return ; - } - dev->me_clients = clients; - return ; -} - diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 11ff875e8658..d07d81ffbb57 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -328,7 +328,6 @@ void mei_reset(struct mei_device *dev, int interrupts); int mei_hw_init(struct mei_device *dev); int mei_task_initialize_clients(void *data); int mei_initialize_clients(struct mei_device *dev); -void mei_allocate_me_clients_storage(struct mei_device *dev); -- cgit v1.2.1 From 038c8a6ebc50ec981557f02197ed2dc5795c40fb Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:20 +0200 Subject: mei: mei_dev.h - remove prototypes of dropped functions mei_task_initialize_clients and mei_initialize_clients are no longer among us Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/mei_dev.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index d07d81ffbb57..681c0ee7a5de 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -326,10 +326,6 @@ static inline unsigned long mei_secs_to_jiffies(unsigned long sec) struct mei_device *mei_device_init(struct pci_dev *pdev); void mei_reset(struct mei_device *dev, int interrupts); int mei_hw_init(struct mei_device *dev); -int mei_task_initialize_clients(void *data); -int mei_initialize_clients(struct mei_device *dev); - - /* * MEI interrupt functions prototype -- cgit v1.2.1 From 1a1aca42c989051dce34d49b4e04a25dafe01d74 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:21 +0200 Subject: mei: rename remaining amthi strings to amthif the only real thing that left was mei_amthi_guid the rest was in the strings and comments Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 62 +++++++++++++++++++++++----------------------- drivers/misc/mei/client.c | 2 +- drivers/misc/mei/main.c | 6 ++--- drivers/misc/mei/mei_dev.h | 2 +- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index adc4120c91d3..64a9bfa5ad20 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -38,9 +38,9 @@ #include "hw-me.h" #include "client.h" -const uuid_le mei_amthi_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac, - 0xa8, 0x46, 0xe0, 0xff, 0x65, - 0x81, 0x4c); +const uuid_le mei_amthif_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, + 0xac, 0xa8, 0x46, 0xe0, + 0xff, 0x65, 0x81, 0x4c); /** * mei_amthif_reset_params - initializes mei device iamthif @@ -73,9 +73,9 @@ void mei_amthif_host_init(struct mei_device *dev) mei_cl_init(&dev->iamthif_cl, dev); dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; - /* find ME amthi client */ + /* find ME amthif client */ i = mei_cl_link_me(&dev->iamthif_cl, - &mei_amthi_guid, MEI_IAMTHIF_HOST_CLIENT_ID); + &mei_amthif_guid, MEI_IAMTHIF_HOST_CLIENT_ID); if (i < 0) { dev_info(&dev->pdev->dev, "failed to find iamthif client.\n"); return; @@ -169,10 +169,10 @@ int mei_amthif_read(struct mei_device *dev, struct file *file, i = mei_me_cl_by_id(dev, dev->iamthif_cl.me_client_id); if (i < 0) { - dev_dbg(&dev->pdev->dev, "amthi client not found.\n"); + dev_dbg(&dev->pdev->dev, "amthif client not found.\n"); return -ENODEV; } - dev_dbg(&dev->pdev->dev, "checking amthi data\n"); + dev_dbg(&dev->pdev->dev, "checking amthif data\n"); cb = mei_amthif_find_read_list_entry(dev, file); /* Check for if we can block or not*/ @@ -180,7 +180,7 @@ int mei_amthif_read(struct mei_device *dev, struct file *file, return -EAGAIN; - dev_dbg(&dev->pdev->dev, "waiting for amthi data\n"); + dev_dbg(&dev->pdev->dev, "waiting for amthif data\n"); while (cb == NULL) { /* unlock the Mutex */ mutex_unlock(&dev->device_lock); @@ -198,17 +198,17 @@ int mei_amthif_read(struct mei_device *dev, struct file *file, } - dev_dbg(&dev->pdev->dev, "Got amthi data\n"); + dev_dbg(&dev->pdev->dev, "Got amthif data\n"); dev->iamthif_timer = 0; if (cb) { timeout = cb->read_time + mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER); - dev_dbg(&dev->pdev->dev, "amthi timeout = %lud\n", + dev_dbg(&dev->pdev->dev, "amthif timeout = %lud\n", timeout); if (time_after(jiffies, timeout)) { - dev_dbg(&dev->pdev->dev, "amthi Time out\n"); + dev_dbg(&dev->pdev->dev, "amthif Time out\n"); /* 15 sec for the message has expired */ list_del(&cb->list); rets = -ETIMEDOUT; @@ -228,9 +228,9 @@ int mei_amthif_read(struct mei_device *dev, struct file *file, * remove message from deletion list */ - dev_dbg(&dev->pdev->dev, "amthi cb->response_buffer size - %d\n", + dev_dbg(&dev->pdev->dev, "amthif cb->response_buffer size - %d\n", cb->response_buffer.size); - dev_dbg(&dev->pdev->dev, "amthi cb->buf_idx - %lu\n", cb->buf_idx); + dev_dbg(&dev->pdev->dev, "amthif cb->buf_idx - %lu\n", cb->buf_idx); /* length is being turncated to PAGE_SIZE, however, * the buf_idx may point beyond */ @@ -246,7 +246,7 @@ int mei_amthif_read(struct mei_device *dev, struct file *file, } } free: - dev_dbg(&dev->pdev->dev, "free amthi cb memory.\n"); + dev_dbg(&dev->pdev->dev, "free amthif cb memory.\n"); *offset = 0; mei_io_cb_free(cb); out: @@ -270,7 +270,7 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) if (!dev || !cb) return -ENODEV; - dev_dbg(&dev->pdev->dev, "write data to amthi client.\n"); + dev_dbg(&dev->pdev->dev, "write data to amthif client.\n"); dev->iamthif_state = MEI_IAMTHIF_WRITING; dev->iamthif_current_cb = cb; @@ -309,12 +309,12 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) return -ENODEV; dev->iamthif_flow_control_pending = true; dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; - dev_dbg(&dev->pdev->dev, "add amthi cb to write waiting list\n"); + dev_dbg(&dev->pdev->dev, "add amthif cb to write waiting list\n"); dev->iamthif_current_cb = cb; dev->iamthif_file_object = cb->file_object; list_add_tail(&cb->list, &dev->write_waiting_list.list); } else { - dev_dbg(&dev->pdev->dev, "message does not complete, so add amthi cb to write list.\n"); + dev_dbg(&dev->pdev->dev, "message does not complete, so add amthif cb to write list.\n"); list_add_tail(&cb->list, &dev->write_list.list); } } else { @@ -383,7 +383,7 @@ void mei_amthif_run_next_cmd(struct mei_device *dev) dev->iamthif_timer = 0; dev->iamthif_file_object = NULL; - dev_dbg(&dev->pdev->dev, "complete amthi cmd_list cb.\n"); + dev_dbg(&dev->pdev->dev, "complete amthif cmd_list cb.\n"); list_for_each_entry_safe(pos, next, &dev->amthif_cmd_list.list, list) { list_del(&pos->list); @@ -392,7 +392,7 @@ void mei_amthif_run_next_cmd(struct mei_device *dev) status = mei_amthif_send_cmd(dev, pos); if (status) { dev_dbg(&dev->pdev->dev, - "amthi write failed status = %d\n", + "amthif write failed status = %d\n", status); return; } @@ -412,7 +412,7 @@ unsigned int mei_amthif_poll(struct mei_device *dev, if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE && dev->iamthif_file_object == file) { mask |= (POLLIN | POLLRDNORM); - dev_dbg(&dev->pdev->dev, "run next amthi cb\n"); + dev_dbg(&dev->pdev->dev, "run next amthif cb\n"); mei_amthif_run_next_cmd(dev); } return mask; @@ -478,7 +478,7 @@ int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; dev->iamthif_flow_control_pending = true; - /* save iamthif cb sent to amthi client */ + /* save iamthif cb sent to amthif client */ cb->buf_idx = dev->iamthif_msg_buf_index; dev->iamthif_current_cb = cb; @@ -491,11 +491,11 @@ int mei_amthif_irq_write_complete(struct mei_device *dev, s32 *slots, /** * mei_amthif_irq_read_message - read routine after ISR to - * handle the read amthi message + * handle the read amthif message * * @complete_list: An instance of our list structure * @dev: the device structure - * @mei_hdr: header of amthi message + * @mei_hdr: header of amthif message * * returns 0 on success, <0 on failure. */ @@ -519,10 +519,10 @@ int mei_amthif_irq_read_message(struct mei_cl_cb *complete_list, return 0; dev_dbg(&dev->pdev->dev, - "amthi_message_buffer_index =%d\n", + "amthif_message_buffer_index =%d\n", mei_hdr->length); - dev_dbg(&dev->pdev->dev, "completed amthi read.\n "); + dev_dbg(&dev->pdev->dev, "completed amthif read.\n "); if (!dev->iamthif_current_cb) return -ENODEV; @@ -537,8 +537,8 @@ int mei_amthif_irq_read_message(struct mei_cl_cb *complete_list, cb->read_time = jiffies; if (dev->iamthif_ioctl && cb->cl == &dev->iamthif_cl) { /* found the iamthif cb */ - dev_dbg(&dev->pdev->dev, "complete the amthi read cb.\n "); - dev_dbg(&dev->pdev->dev, "add the amthi read cb to complete.\n "); + dev_dbg(&dev->pdev->dev, "complete the amthif read cb.\n "); + dev_dbg(&dev->pdev->dev, "add the amthif read cb to complete.\n "); list_add_tail(&cb->list, &complete_list->list); } return 0; @@ -590,7 +590,7 @@ void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb) dev->iamthif_msg_buf, dev->iamthif_msg_buf_index); list_add_tail(&cb->list, &dev->amthif_rd_complete_list.list); - dev_dbg(&dev->pdev->dev, "amthi read completed\n"); + dev_dbg(&dev->pdev->dev, "amthif read completed\n"); dev->iamthif_timer = jiffies; dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n", dev->iamthif_timer); @@ -598,7 +598,7 @@ void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb) mei_amthif_run_next_cmd(dev); } - dev_dbg(&dev->pdev->dev, "completing amthi call back.\n"); + dev_dbg(&dev->pdev->dev, "completing amthif call back.\n"); wake_up_interruptible(&dev->iamthif_cl.wait); } @@ -704,11 +704,11 @@ int mei_amthif_release(struct mei_device *dev, struct file *file) if (dev->iamthif_file_object == file && dev->iamthif_state != MEI_IAMTHIF_IDLE) { - dev_dbg(&dev->pdev->dev, "amthi canceled iamthif state %d\n", + dev_dbg(&dev->pdev->dev, "amthif canceled iamthif state %d\n", dev->iamthif_state); dev->iamthif_canceled = true; if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE) { - dev_dbg(&dev->pdev->dev, "run next amthi iamthif cb\n"); + dev_dbg(&dev->pdev->dev, "run next amthif iamthif cb\n"); mei_amthif_run_next_cmd(dev); } } diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 231eda2b09e1..8103d94facb8 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -345,7 +345,7 @@ void mei_host_client_init(struct work_struct *work) for (i = 0; i < dev->me_clients_num; i++) { client_props = &dev->me_clients[i].props; - if (!uuid_le_cmp(client_props->protocol_name, mei_amthi_guid)) + if (!uuid_le_cmp(client_props->protocol_name, mei_amthif_guid)) mei_amthif_host_init(dev); else if (!uuid_le_cmp(client_props->protocol_name, mei_wd_guid)) mei_wd_host_init(dev); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 5c7c85505c91..d5d1a5957d5f 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -497,7 +497,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, if (rets) { dev_err(&dev->pdev->dev, - "amthi write failed with status = %d\n", rets); + "amthif write failed with status = %d\n", rets); goto err; } mutex_unlock(&dev->device_lock); @@ -611,10 +611,10 @@ static int mei_ioctl_connect_client(struct file *file, dev_dbg(&dev->pdev->dev, "FW Client - Max Msg Len = %d\n", dev->me_clients[i].props.max_msg_length); - /* if we're connecting to amthi client then we will use the + /* if we're connecting to amthif client then we will use the * existing connection */ - if (uuid_le_cmp(data->in_client_uuid, mei_amthi_guid) == 0) { + if (uuid_le_cmp(data->in_client_uuid, mei_amthif_guid) == 0) { dev_dbg(&dev->pdev->dev, "FW Client is amthi\n"); if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { rets = -ENODEV; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 681c0ee7a5de..285e8e01d429 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -46,7 +46,7 @@ /* * AMTHI Client UUID */ -extern const uuid_le mei_amthi_guid; +extern const uuid_le mei_amthif_guid; /* * Watchdog Client UUID -- cgit v1.2.1 From 781d0d89224bbbc438c2c0360cfd4822bb35d280 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:22 +0200 Subject: mei: normalize me host client linking routines In order we can use the same code pattern for in-kernel and user space host clients we replace mei_cl_link_to_me with mei_cl_link function. We then have to keep me client lookupout of the new link function. The unlinking cannot be yet symetric due to amthif connection handling Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 42 ++++++++++++++++++++++-------------- drivers/misc/mei/client.c | 53 ++++++++++++++++++++++++++-------------------- drivers/misc/mei/client.h | 2 +- drivers/misc/mei/init.c | 4 ++++ drivers/misc/mei/main.c | 27 ++++++++--------------- drivers/misc/mei/mei_dev.h | 10 ++++----- drivers/misc/mei/wd.c | 35 +++++++++++++++++++----------- 7 files changed, 98 insertions(+), 75 deletions(-) diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 64a9bfa5ad20..88e6aa0f6b4e 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -65,22 +65,22 @@ void mei_amthif_reset_params(struct mei_device *dev) * @dev: the device structure * */ -void mei_amthif_host_init(struct mei_device *dev) +int mei_amthif_host_init(struct mei_device *dev) { - int i; + struct mei_cl *cl = &dev->iamthif_cl; unsigned char *msg_buf; + int ret, i; - mei_cl_init(&dev->iamthif_cl, dev); - dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; + mei_cl_init(cl, dev); - /* find ME amthif client */ - i = mei_cl_link_me(&dev->iamthif_cl, - &mei_amthif_guid, MEI_IAMTHIF_HOST_CLIENT_ID); + i = mei_me_cl_by_uuid(dev, &mei_amthif_guid); if (i < 0) { - dev_info(&dev->pdev->dev, "failed to find iamthif client.\n"); - return; + dev_info(&dev->pdev->dev, "amthif: failed to find the client\n"); + return -ENOENT; } + cl->me_client_id = dev->me_clients[i].client_id; + /* Assign iamthif_mtu to the value received from ME */ dev->iamthif_mtu = dev->me_clients[i].props.max_msg_length; @@ -94,19 +94,29 @@ void mei_amthif_host_init(struct mei_device *dev) msg_buf = kcalloc(dev->iamthif_mtu, sizeof(unsigned char), GFP_KERNEL); if (!msg_buf) { - dev_dbg(&dev->pdev->dev, "memory allocation for ME message buffer failed.\n"); - return; + dev_err(&dev->pdev->dev, "amthif: memory allocation for ME message buffer failed.\n"); + return -ENOMEM; } dev->iamthif_msg_buf = msg_buf; - if (mei_hbm_cl_connect_req(dev, &dev->iamthif_cl)) { - dev_dbg(&dev->pdev->dev, "Failed to connect to AMTHI client\n"); - dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; - dev->iamthif_cl.host_client_id = 0; + ret = mei_cl_link(cl, MEI_IAMTHIF_HOST_CLIENT_ID); + + if (ret < 0) { + dev_err(&dev->pdev->dev, "amthif: failed link client\n"); + return -ENOENT; + } + + cl->state = MEI_FILE_CONNECTING; + + if (mei_hbm_cl_connect_req(dev, cl)) { + dev_dbg(&dev->pdev->dev, "amthif: Failed to connect to ME client\n"); + cl->state = MEI_FILE_DISCONNECTED; + cl->host_client_id = 0; } else { - dev->iamthif_cl.timer_count = MEI_CONNECT_TIMEOUT; + cl->timer_count = MEI_CONNECT_TIMEOUT; } + return 0; } /** diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 8103d94facb8..d566dd880eb0 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -258,54 +258,61 @@ struct mei_cl_cb *mei_cl_find_read_cb(struct mei_cl *cl) return NULL; } - -/** - * mei_me_cl_link - create link between host and me clinet and add - * me_cl to the list - * - * @cl: link between me and host client assocated with opened file descriptor - * @uuid: uuid of ME client - * @client_id: id of the host client +/** mei_cl_link: allocte host id in the host map * - * returns ME client index if ME client + * @cl - host client + * @id - fixed host id or -1 for genereting one + * returns 0 on success * -EINVAL on incorrect values * -ENONET if client not found */ -int mei_cl_link_me(struct mei_cl *cl, const uuid_le *uuid, u8 host_cl_id) +int mei_cl_link(struct mei_cl *cl, int id) { struct mei_device *dev; - int i; - if (WARN_ON(!cl || !cl->dev || !uuid)) + if (WARN_ON(!cl || !cl->dev)) return -EINVAL; dev = cl->dev; - /* check for valid client id */ - i = mei_me_cl_by_uuid(dev, uuid); - if (i >= 0) { - cl->me_client_id = dev->me_clients[i].client_id; - cl->state = MEI_FILE_CONNECTING; - cl->host_client_id = host_cl_id; + /* If Id is not asigned get one*/ + if (id == MEI_HOST_CLIENT_ID_ANY) + id = find_first_zero_bit(dev->host_clients_map, + MEI_CLIENTS_MAX); - list_add_tail(&cl->link, &dev->file_list); - return (u8)i; + if (id >= MEI_CLIENTS_MAX) { + dev_err(&dev->pdev->dev, "id exceded %d", MEI_CLIENTS_MAX) ; + return -ENOENT; } - return -ENOENT; + dev->open_handle_count++; + + cl->host_client_id = id; + list_add_tail(&cl->link, &dev->file_list); + + set_bit(id, dev->host_clients_map); + + cl->state = MEI_FILE_INITIALIZING; + + dev_dbg(&dev->pdev->dev, "link cl host id = %d\n", cl->host_client_id); + return 0; } + /** * mei_cl_unlink - remove me_cl from the list * * @dev: the device structure - * @host_client_id: host client id to be removed */ int mei_cl_unlink(struct mei_cl *cl) { struct mei_device *dev; struct mei_cl *pos, *next; - if (WARN_ON(!cl || !cl->dev)) + /* don't shout on error exit path */ + if (!cl) + return 0; + + if (WARN_ON(!cl->dev)) return -EINVAL; dev = cl->dev; diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 8dfd052dd0e6..240a1f321342 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -55,7 +55,7 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev); void mei_cl_init(struct mei_cl *cl, struct mei_device *dev); -int mei_cl_link_me(struct mei_cl *cl, const uuid_le *uuid, u8 host_cl_id); +int mei_cl_link(struct mei_cl *cl, int id); int mei_cl_unlink(struct mei_cl *cl); int mei_cl_flush_queues(struct mei_cl *cl); diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 636639fbfc0a..9e4011ef8508 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -242,7 +242,11 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) /* remove entry if already in list */ dev_dbg(&dev->pdev->dev, "remove iamthif and wd from the file list.\n"); mei_cl_unlink(&dev->wd_cl); + if (dev->open_handle_count > 0) + dev->open_handle_count--; mei_cl_unlink(&dev->iamthif_cl); + if (dev->open_handle_count > 0) + dev->open_handle_count--; mei_amthif_reset_params(dev); memset(&dev->wr_ext_msg, 0, sizeof(dev->wr_ext_msg)); diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index d5d1a5957d5f..ec5fd7a0e289 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -103,7 +103,6 @@ static int mei_open(struct inode *inode, struct file *file) { struct mei_cl *cl; struct mei_device *dev; - unsigned long cl_id; int err; err = -ENODEV; @@ -133,24 +132,9 @@ static int mei_open(struct inode *inode, struct file *file) goto out_unlock; } - cl_id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX); - if (cl_id >= MEI_CLIENTS_MAX) { - dev_err(&dev->pdev->dev, "client_id exceded %d", - MEI_CLIENTS_MAX) ; + err = mei_cl_link(cl, MEI_HOST_CLIENT_ID_ANY); + if (err) goto out_unlock; - } - - cl->host_client_id = cl_id; - - dev_dbg(&dev->pdev->dev, "client_id = %d\n", cl->host_client_id); - - dev->open_handle_count++; - - list_add_tail(&cl->link, &dev->file_list); - - set_bit(cl->host_client_id, dev->host_clients_map); - cl->state = MEI_FILE_INITIALIZING; - cl->sm_state = 0; file->private_data = cl; mutex_unlock(&dev->device_lock); @@ -209,6 +193,7 @@ static int mei_release(struct inode *inode, struct file *file) } mei_cl_unlink(cl); + /* free read cb */ cb = NULL; if (cl->read_cb) { @@ -991,7 +976,13 @@ static void mei_remove(struct pci_dev *pdev) /* remove entry if already in list */ dev_dbg(&pdev->dev, "list del iamthif and wd file list.\n"); + + if (dev->open_handle_count > 0) + dev->open_handle_count--; mei_cl_unlink(&dev->wd_cl); + + if (dev->open_handle_count > 0) + dev->open_handle_count--; mei_cl_unlink(&dev->iamthif_cl); dev->iamthif_current_cb = NULL; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 285e8e01d429..dcd7a44a806e 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -67,16 +67,16 @@ extern const u8 mei_wd_state_independence_msg[3][4]; * Number of File descriptors/handles * that can be opened to the driver. * - * Limit to 253: 256 Total Clients + * Limit to 255: 256 Total Clients * minus internal client for MEI Bus Messags - * minus internal client for AMTHI - * minus internal client for Watchdog */ -#define MEI_MAX_OPEN_HANDLE_COUNT (MEI_CLIENTS_MAX - 3) +#define MEI_MAX_OPEN_HANDLE_COUNT (MEI_CLIENTS_MAX - 1) /* * Internal Clients Number */ +#define MEI_HOST_CLIENT_ID_ANY (-1) +#define MEI_HBM_HOST_CLIENT_ID 0 /* not used, just for documentation */ #define MEI_WD_HOST_CLIENT_ID 1 #define MEI_IAMTHIF_HOST_CLIENT_ID 2 @@ -339,7 +339,7 @@ void mei_timer(struct work_struct *work); */ void mei_amthif_reset_params(struct mei_device *dev); -void mei_amthif_host_init(struct mei_device *dev); +int mei_amthif_host_init(struct mei_device *dev); int mei_amthif_write(struct mei_device *dev, struct mei_cl_cb *priv_cb); diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index bfcbcc8c028b..77b3820380b0 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -64,30 +64,41 @@ static void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout) */ int mei_wd_host_init(struct mei_device *dev) { - int id; - mei_cl_init(&dev->wd_cl, dev); + struct mei_cl *cl = &dev->wd_cl; + int i; + int ret; + + mei_cl_init(cl, dev); - /* look for WD client and connect to it */ - dev->wd_cl.state = MEI_FILE_DISCONNECTED; dev->wd_timeout = MEI_WD_DEFAULT_TIMEOUT; dev->wd_state = MEI_WD_IDLE; - /* Connect WD ME client to the host client */ - id = mei_cl_link_me(&dev->wd_cl, - &mei_wd_guid, MEI_WD_HOST_CLIENT_ID); - if (id < 0) { + /* check for valid client id */ + i = mei_me_cl_by_uuid(dev, &mei_wd_guid); + if (i < 0) { dev_info(&dev->pdev->dev, "wd: failed to find the client\n"); return -ENOENT; } - if (mei_hbm_cl_connect_req(dev, &dev->wd_cl)) { + cl->me_client_id = dev->me_clients[i].client_id; + + ret = mei_cl_link(cl, MEI_WD_HOST_CLIENT_ID); + + if (ret < 0) { + dev_info(&dev->pdev->dev, "wd: failed link client\n"); + return -ENOENT; + } + + cl->state = MEI_FILE_CONNECTING; + + if (mei_hbm_cl_connect_req(dev, cl)) { dev_err(&dev->pdev->dev, "wd: failed to connect to the client\n"); - dev->wd_cl.state = MEI_FILE_DISCONNECTED; - dev->wd_cl.host_client_id = 0; + cl->state = MEI_FILE_DISCONNECTED; + cl->host_client_id = 0; return -EIO; } - dev->wd_cl.timer_count = MEI_CONNECT_TIMEOUT; + cl->timer_count = MEI_CONNECT_TIMEOUT; return 0; } -- cgit v1.2.1 From 6222f7bf8d7e2b16ffcc14bcb2c32ea069aac9fa Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:23 +0200 Subject: mei: move MEI_IAMTHIF_IDLE to amthif host init function Since the amthif state is not examined until amthif is connected we can safely move it to the amthif host init function Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 2 ++ drivers/misc/mei/init.c | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 88e6aa0f6b4e..7199e8369f1e 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -71,6 +71,8 @@ int mei_amthif_host_init(struct mei_device *dev) unsigned char *msg_buf; int ret, i; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + mei_cl_init(cl, dev); i = mei_me_cl_by_uuid(dev, &mei_amthif_guid); diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 9e4011ef8508..70fad4ea474c 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -68,7 +68,6 @@ struct mei_device *mei_device_init(struct pci_dev *pdev) init_waitqueue_head(&dev->wait_recvd_msg); init_waitqueue_head(&dev->wait_stop_wd); dev->dev_state = MEI_DEV_INITIALIZING; - dev->iamthif_state = MEI_IAMTHIF_IDLE; mei_io_list_init(&dev->read_list); mei_io_list_init(&dev->write_list); -- cgit v1.2.1 From d025284d06458367cd833a5aceca901fb6da3785 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:24 +0200 Subject: mei: hw-me.c fix kernel doc Fix the kernel doc for the functions in hw-me.c Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 4e6b657cd806..1e82e3311ea2 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -49,17 +49,13 @@ static inline void mei_reg_write(const struct mei_device *dev, } /** - * mei_hcsr_read - Reads 32bit data from the host CSR + * mei_mecbrw_read - Reads 32bit data from ME circular buffer + * read window register * * @dev: the device structure * - * returns the byte read. + * returns ME_CB_RW register value (u32) */ -u32 mei_hcsr_read(const struct mei_device *dev) -{ - return mei_reg_read(dev, H_CSR); -} - u32 mei_mecbrw_read(const struct mei_device *dev) { return mei_reg_read(dev, ME_CB_RW); @@ -77,13 +73,26 @@ u32 mei_mecsr_read(const struct mei_device *dev) } /** - * mei_set_csr_register - writes H_CSR register to the mei device, + * mei_hcsr_read - Reads 32bit data from the host CSR + * + * @dev: the device structure + * + * returns H_CSR register value (u32) + */ +u32 mei_hcsr_read(const struct mei_device *dev) +{ + return mei_reg_read(dev, H_CSR); +} + +/** + * mei_hcsr_set - writes H_CSR register to the mei device, * and ignores the H_IS bit for it is write-one-to-zero. * * @dev: the device structure */ void mei_hcsr_set(struct mei_device *dev) { + if ((dev->host_hw_state & H_IS) == H_IS) dev->host_hw_state &= ~H_IS; mei_reg_write(dev, H_CSR, dev->host_hw_state); @@ -91,7 +100,7 @@ void mei_hcsr_set(struct mei_device *dev) } /** - * mei_enable_interrupts - clear and stop interrupts + * mei_clear_interrupts - clear and stop interrupts * * @dev: the device structure */ -- cgit v1.2.1 From a9f6b133ab97bd481d82731a30b7d4a90427f56a Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:25 +0200 Subject: mei: remove write only need_reset member of struct mei_device need_reset is not used anymore Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/init.c | 6 +----- drivers/misc/mei/mei_dev.h | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 70fad4ea474c..2391832bfa6f 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -199,10 +199,8 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) struct mei_cl_cb *cb_next = NULL; bool unexpected; - if (dev->dev_state == MEI_DEV_RECOVERING_FROM_RESET) { - dev->need_reset = true; + if (dev->dev_state == MEI_DEV_RECOVERING_FROM_RESET) return; - } unexpected = (dev->dev_state != MEI_DEV_INITIALIZING && dev->dev_state != MEI_DEV_DISABLED && @@ -224,8 +222,6 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) dev_dbg(&dev->pdev->dev, "currently saved host_hw_state = 0x%08x.\n", dev->host_hw_state); - dev->need_reset = false; - if (dev->dev_state != MEI_DEV_INITIALIZING) { if (dev->dev_state != MEI_DEV_DISABLED && dev->dev_state != MEI_DEV_POWER_DOWN) diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index dcd7a44a806e..e42db314f7c2 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -260,7 +260,6 @@ struct mei_device { enum mei_dev_state dev_state; enum mei_init_clients_states init_clients_state; u16 init_clients_timer; - bool need_reset; unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */ u32 rd_msg_hdr; -- cgit v1.2.1 From 528c8eb4e7c08c5531533de62a558aaf3d076ebb Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:26 +0200 Subject: mei: remove mei_csr_clear_his prototype The function mei_csr_clear_his is not implemented Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/mei_dev.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index e42db314f7c2..88fa194ca30e 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -386,11 +386,10 @@ void mei_watchdog_unregister(struct mei_device *dev); */ u32 mei_hcsr_read(const struct mei_device *dev); +void mei_hcsr_set(struct mei_device *dev); u32 mei_mecsr_read(const struct mei_device *dev); u32 mei_mecbrw_read(const struct mei_device *dev); -void mei_hcsr_set(struct mei_device *dev); -void mei_csr_clear_his(struct mei_device *dev); void mei_clear_interrupts(struct mei_device *dev); void mei_enable_interrupts(struct mei_device *dev); -- cgit v1.2.1 From adfba3220b625ce4bee08e7e6f48c8a27aac23bb Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:27 +0200 Subject: mei: don't use cached value for hcsr in mei_hw_reset Open code mei_hw_reset to avoid using cached hcsr. Using cached hcsr can cause unwanted side effects. Move mei_hw_restet function to hw-me.c as it is hw dependent Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 35 +++++++++++++++++++++++++++++++++++ drivers/misc/mei/init.c | 28 ---------------------------- drivers/misc/mei/mei_dev.h | 1 + 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 1e82e3311ea2..ec4ab895f03d 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -132,6 +132,41 @@ void mei_disable_interrupts(struct mei_device *dev) mei_hcsr_set(dev); } +/** + * mei_hw_reset - resets fw via mei csr register. + * + * @dev: the device structure + * @interrupts_enabled: if interrupt should be enabled after reset. + */ +void mei_hw_reset(struct mei_device *dev, bool intr_enable) +{ + u32 hcsr = mei_hcsr_read(dev); + + dev_dbg(&dev->pdev->dev, "before reset HCSR = 0x%08x.\n", hcsr); + + hcsr |= (H_RST | H_IG); + + if (intr_enable) + hcsr |= H_IE; + else + hcsr &= ~H_IE; + + hcsr &= ~H_IS; + + mei_reg_write(dev, H_CSR, hcsr); + hcsr = mei_hcsr_read(dev); + + hcsr &= ~H_RST; + hcsr |= H_IG; + hcsr &= ~H_IS; + + mei_reg_write(dev, H_CSR, hcsr); + + hcsr = mei_hcsr_read(dev); + + dev_dbg(&dev->pdev->dev, "current HCSR = 0x%08x.\n", hcsr); +} + /** * mei_interrupt_quick_handler - The ISR of the MEI device diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 2391832bfa6f..5c2054d06f6b 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -169,22 +169,6 @@ out: return ret; } -/** - * mei_hw_reset - resets fw via mei csr register. - * - * @dev: the device structure - * @interrupts_enabled: if interrupt should be enabled after reset. - */ -static void mei_hw_reset(struct mei_device *dev, int interrupts_enabled) -{ - dev->host_hw_state |= (H_RST | H_IG); - - if (interrupts_enabled) - mei_enable_interrupts(dev); - else - mei_disable_interrupts(dev); -} - /** * mei_reset - resets host and fw. * @@ -207,20 +191,8 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) dev->dev_state != MEI_DEV_POWER_DOWN && dev->dev_state != MEI_DEV_POWER_UP); - dev->host_hw_state = mei_hcsr_read(dev); - - dev_dbg(&dev->pdev->dev, "before reset host_hw_state = 0x%08x.\n", - dev->host_hw_state); - mei_hw_reset(dev, interrupts_enabled); - dev->host_hw_state &= ~H_RST; - dev->host_hw_state |= H_IG; - - mei_hcsr_set(dev); - - dev_dbg(&dev->pdev->dev, "currently saved host_hw_state = 0x%08x.\n", - dev->host_hw_state); if (dev->dev_state != MEI_DEV_INITIALIZING) { if (dev->dev_state != MEI_DEV_DISABLED && diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 88fa194ca30e..8692ac8c98d4 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -385,6 +385,7 @@ void mei_watchdog_unregister(struct mei_device *dev); * Register Access Function */ +void mei_hw_reset(struct mei_device *dev, bool intr_enable); u32 mei_hcsr_read(const struct mei_device *dev); void mei_hcsr_set(struct mei_device *dev); u32 mei_mecsr_read(const struct mei_device *dev); -- cgit v1.2.1 From 9ea73ddd4f144952b8f69fac93dc592ea48e4113 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:28 +0200 Subject: mei: use non cached hcsr for interrupt enablement Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 17 +++++++++++------ drivers/misc/mei/interrupt.c | 2 +- drivers/misc/mei/main.c | 2 -- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index ec4ab895f03d..ed61659fd388 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -106,8 +106,9 @@ void mei_hcsr_set(struct mei_device *dev) */ void mei_clear_interrupts(struct mei_device *dev) { - if ((dev->host_hw_state & H_IS) == H_IS) - mei_reg_write(dev, H_CSR, dev->host_hw_state); + u32 hcsr = mei_hcsr_read(dev); + if ((hcsr & H_IS) == H_IS) + mei_reg_write(dev, H_CSR, hcsr); } /** @@ -117,8 +118,10 @@ void mei_clear_interrupts(struct mei_device *dev) */ void mei_enable_interrupts(struct mei_device *dev) { - dev->host_hw_state |= H_IE; - mei_hcsr_set(dev); + u32 hcsr = mei_hcsr_read(dev); + hcsr |= H_IE; + hcsr &= ~H_IS; + mei_reg_write(dev, H_CSR, hcsr); } /** @@ -128,8 +131,10 @@ void mei_enable_interrupts(struct mei_device *dev) */ void mei_disable_interrupts(struct mei_device *dev) { - dev->host_hw_state &= ~H_IE; - mei_hcsr_set(dev); + u32 hcsr = mei_hcsr_read(dev); + hcsr &= ~H_IE; + hcsr &= ~H_IS; + mei_reg_write(dev, H_CSR, hcsr); } /** diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index d1ef92617c19..d7e1b797e87b 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -691,7 +691,6 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) /* initialize our complete list */ mutex_lock(&dev->device_lock); mei_io_list_init(&complete_list); - dev->host_hw_state = mei_hcsr_read(dev); /* Ack the interrupt here * In case of MSI we don't go through the quick handler */ @@ -710,6 +709,7 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) return IRQ_HANDLED; } + dev->host_hw_state = mei_hcsr_read(dev); /* check if we need to start the dev */ if ((dev->host_hw_state & H_RDY) == 0) { if ((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA) { diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index ec5fd7a0e289..f72bb77fc279 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -915,8 +915,6 @@ static int mei_probe(struct pci_dev *pdev, return 0; release_irq: - /* disable interrupts */ - dev->host_hw_state = mei_hcsr_read(dev); mei_disable_interrupts(dev); flush_scheduled_work(); free_irq(pdev->irq, dev); -- cgit v1.2.1 From 115ba28c5e075c6bffd8106a2b5e23db88d0c3b5 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:29 +0200 Subject: mei: abstract host and device readieness Add mei_host_set_ready function to enable the device and is_ready function to query the host and me readiness Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 35 ++++++++++++++++++++++++++++++++++- drivers/misc/mei/init.c | 14 ++++++-------- drivers/misc/mei/interrupt.c | 19 ++++++++++--------- drivers/misc/mei/mei_dev.h | 4 ++++ 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index ed61659fd388..49b558ddf5b5 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -172,6 +172,39 @@ void mei_hw_reset(struct mei_device *dev, bool intr_enable) dev_dbg(&dev->pdev->dev, "current HCSR = 0x%08x.\n", hcsr); } +/** + * mei_host_set_ready - enable device + * + * @dev - mei device + * returns bool + */ + +void mei_host_set_ready(struct mei_device *dev) +{ + dev->host_hw_state |= H_IE | H_IG | H_RDY; + mei_hcsr_set(dev); +} +/** + * mei_host_is_ready - check whether the host has turned ready + * + * @dev - mei device + * returns bool + */ +bool mei_host_is_ready(struct mei_device *dev) +{ + return (dev->host_hw_state & H_RDY) == H_RDY; +} + +/** + * mei_me_is_ready - check whether the me has turned ready + * + * @dev - mei device + * returns bool + */ +bool mei_me_is_ready(struct mei_device *dev) +{ + return (dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA; +} /** * mei_interrupt_quick_handler - The ISR of the MEI device @@ -290,7 +323,7 @@ int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, dev->host_hw_state |= H_IG; mei_hcsr_set(dev); dev->me_hw_state = mei_mecsr_read(dev); - if ((dev->me_hw_state & ME_RDY_HRA) != ME_RDY_HRA) + if (!mei_me_is_ready(dev)) return -EIO; return 0; diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 5c2054d06f6b..d0ee02ac8201 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -131,18 +131,18 @@ int mei_hw_init(struct mei_device *dev) goto out; } - if (!(((dev->host_hw_state & H_RDY) == H_RDY) && - ((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA))) { + if (!(mei_host_is_ready(dev) && mei_me_is_ready(dev))) { dev->dev_state = MEI_DEV_DISABLED; + dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", dev->host_hw_state, dev->me_hw_state); - if (!(dev->host_hw_state & H_RDY)) - dev_dbg(&dev->pdev->dev, "host turn off H_RDY.\n"); + if (!mei_host_is_ready(dev)) + dev_dbg(&dev->pdev->dev, "host is not ready.\n"); - if (!(dev->me_hw_state & ME_RDY_HRA)) - dev_dbg(&dev->pdev->dev, "ME turn off ME_RDY.\n"); + if (!mei_me_is_ready(dev)) + dev_dbg(&dev->pdev->dev, "ME is not ready.\n"); dev_err(&dev->pdev->dev, "link layer initialization failed.\n"); ret = -ENODEV; @@ -159,9 +159,7 @@ int mei_hw_init(struct mei_device *dev) dev->recvd_msg = false; dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", dev->host_hw_state, dev->me_hw_state); - dev_dbg(&dev->pdev->dev, "ME turn on ME_RDY and host turn on H_RDY.\n"); dev_dbg(&dev->pdev->dev, "link layer has been established.\n"); - dev_dbg(&dev->pdev->dev, "MEI start success.\n"); ret = 0; out: diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index d7e1b797e87b..27374b6b6424 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -700,7 +700,7 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) dev->me_hw_state = mei_mecsr_read(dev); /* check if ME wants a reset */ - if ((dev->me_hw_state & ME_RDY_HRA) == 0 && + if (!mei_me_is_ready(dev) && dev->dev_state != MEI_DEV_RESETING && dev->dev_state != MEI_DEV_INITIALIZING) { dev_dbg(&dev->pdev->dev, "FW not ready.\n"); @@ -711,16 +711,17 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) dev->host_hw_state = mei_hcsr_read(dev); /* check if we need to start the dev */ - if ((dev->host_hw_state & H_RDY) == 0) { - if ((dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA) { + if (!mei_host_is_ready(dev)) { + if (mei_me_is_ready(dev)) { dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); - dev->host_hw_state |= (H_IE | H_IG | H_RDY); - mei_hcsr_set(dev); - dev->dev_state = MEI_DEV_INIT_CLIENTS; + + mei_host_set_ready(dev); + dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); - /* link is established - * start sending messages. - */ + /* link is established * start sending messages. */ + + dev->dev_state = MEI_DEV_INIT_CLIENTS; + mei_hbm_start_req(dev); mutex_unlock(&dev->device_lock); return IRQ_HANDLED; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 8692ac8c98d4..7b4365952be5 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -396,6 +396,10 @@ void mei_clear_interrupts(struct mei_device *dev); void mei_enable_interrupts(struct mei_device *dev); void mei_disable_interrupts(struct mei_device *dev); +void mei_host_set_ready(struct mei_device *dev); +bool mei_host_is_ready(struct mei_device *dev); +bool mei_me_is_ready(struct mei_device *dev); + #define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d comp=%1d" -- cgit v1.2.1 From 88eb99f29c0026f4b7d7702652eb529f04c69073 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:30 +0200 Subject: mei: reenable mei_hcsr_set abstraction Now when mei_hcsr_set is local to hw-me.c we can benefit form the fact that it wraps H_IS removal from the host csr. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 38 +++++++++++++++----------------------- drivers/misc/mei/mei_dev.h | 1 - 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 49b558ddf5b5..319002797578 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -90,13 +90,10 @@ u32 mei_hcsr_read(const struct mei_device *dev) * * @dev: the device structure */ -void mei_hcsr_set(struct mei_device *dev) +static inline void mei_hcsr_set(struct mei_device *dev, u32 hcsr) { - - if ((dev->host_hw_state & H_IS) == H_IS) - dev->host_hw_state &= ~H_IS; - mei_reg_write(dev, H_CSR, dev->host_hw_state); - dev->host_hw_state = mei_hcsr_read(dev); + hcsr &= ~H_IS; + mei_reg_write(dev, H_CSR, hcsr); } /** @@ -120,8 +117,7 @@ void mei_enable_interrupts(struct mei_device *dev) { u32 hcsr = mei_hcsr_read(dev); hcsr |= H_IE; - hcsr &= ~H_IS; - mei_reg_write(dev, H_CSR, hcsr); + mei_hcsr_set(dev, hcsr); } /** @@ -133,8 +129,7 @@ void mei_disable_interrupts(struct mei_device *dev) { u32 hcsr = mei_hcsr_read(dev); hcsr &= ~H_IE; - hcsr &= ~H_IS; - mei_reg_write(dev, H_CSR, hcsr); + mei_hcsr_set(dev, hcsr); } /** @@ -156,16 +151,12 @@ void mei_hw_reset(struct mei_device *dev, bool intr_enable) else hcsr &= ~H_IE; - hcsr &= ~H_IS; - - mei_reg_write(dev, H_CSR, hcsr); - hcsr = mei_hcsr_read(dev); + mei_hcsr_set(dev, hcsr); + hcsr = mei_hcsr_read(dev) | H_IG; hcsr &= ~H_RST; - hcsr |= H_IG; - hcsr &= ~H_IS; - mei_reg_write(dev, H_CSR, hcsr); + mei_hcsr_set(dev, hcsr); hcsr = mei_hcsr_read(dev); @@ -182,7 +173,7 @@ void mei_hw_reset(struct mei_device *dev, bool intr_enable) void mei_host_set_ready(struct mei_device *dev) { dev->host_hw_state |= H_IE | H_IG | H_RDY; - mei_hcsr_set(dev); + mei_hcsr_set(dev, dev->host_hw_state); } /** * mei_host_is_ready - check whether the host has turned ready @@ -295,6 +286,7 @@ int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, unsigned long rem, dw_cnt; unsigned long length = header->length; u32 *reg_buf = (u32 *)buf; + u32 hcsr; int i; int empty_slots; @@ -319,9 +311,8 @@ int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, mei_reg_write(dev, H_CB_WW, reg); } - dev->host_hw_state = mei_hcsr_read(dev); - dev->host_hw_state |= H_IG; - mei_hcsr_set(dev); + hcsr = mei_hcsr_read(dev) | H_IG; + mei_hcsr_set(dev, hcsr); dev->me_hw_state = mei_mecsr_read(dev); if (!mei_me_is_ready(dev)) return -EIO; @@ -366,6 +357,7 @@ void mei_read_slots(struct mei_device *dev, unsigned char *buffer, unsigned long buffer_length) { u32 *reg_buf = (u32 *)buffer; + u32 hcsr; for (; buffer_length >= sizeof(u32); buffer_length -= sizeof(u32)) *reg_buf++ = mei_mecbrw_read(dev); @@ -375,7 +367,7 @@ void mei_read_slots(struct mei_device *dev, unsigned char *buffer, memcpy(reg_buf, ®, buffer_length); } - dev->host_hw_state |= H_IG; - mei_hcsr_set(dev); + hcsr = mei_hcsr_read(dev) | H_IG; + mei_hcsr_set(dev, hcsr); } diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 7b4365952be5..ae4c5ffc712b 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -387,7 +387,6 @@ void mei_watchdog_unregister(struct mei_device *dev); void mei_hw_reset(struct mei_device *dev, bool intr_enable); u32 mei_hcsr_read(const struct mei_device *dev); -void mei_hcsr_set(struct mei_device *dev); u32 mei_mecsr_read(const struct mei_device *dev); u32 mei_mecbrw_read(const struct mei_device *dev); -- cgit v1.2.1 From e7e0c231aaa7a01df28634390381974cb76d3cb2 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:31 +0200 Subject: mei: make host csr and me csr internal to hw-me Move csr reading into me hardware functional calls. Since we gave up on registers caching we remove some of the unnecessary queries in mei_hw_init ane mei_reset functions. We add mei_hw_config function to wrap up host buffer depth configuration. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 19 ++++++++++--- drivers/misc/mei/init.c | 63 +++++++++++++------------------------------- drivers/misc/mei/interrupt.c | 4 --- drivers/misc/mei/mei_dev.h | 4 +-- 4 files changed, 37 insertions(+), 53 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 319002797578..93a2a56a5f2c 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -67,7 +67,7 @@ u32 mei_mecbrw_read(const struct mei_device *dev) * * returns ME_CSR_HA register value (u32) */ -u32 mei_mecsr_read(const struct mei_device *dev) +static inline u32 mei_mecsr_read(const struct mei_device *dev) { return mei_reg_read(dev, ME_CSR_HA); } @@ -79,7 +79,7 @@ u32 mei_mecsr_read(const struct mei_device *dev) * * returns H_CSR register value (u32) */ -u32 mei_hcsr_read(const struct mei_device *dev) +static inline u32 mei_hcsr_read(const struct mei_device *dev) { return mei_reg_read(dev, H_CSR); } @@ -96,6 +96,18 @@ static inline void mei_hcsr_set(struct mei_device *dev, u32 hcsr) mei_reg_write(dev, H_CSR, hcsr); } + +/** + * me_hw_config - configure hw dependent settings + * + * @dev: mei device + */ +void mei_hw_config(struct mei_device *dev) +{ + u32 hcsr = mei_hcsr_read(dev); + /* Doesn't change in runtime */ + dev->hbuf_depth = (hcsr & H_CBD) >> 24; +} /** * mei_clear_interrupts - clear and stop interrupts * @@ -183,6 +195,7 @@ void mei_host_set_ready(struct mei_device *dev) */ bool mei_host_is_ready(struct mei_device *dev) { + dev->host_hw_state = mei_hcsr_read(dev); return (dev->host_hw_state & H_RDY) == H_RDY; } @@ -194,6 +207,7 @@ bool mei_host_is_ready(struct mei_device *dev) */ bool mei_me_is_ready(struct mei_device *dev) { + dev->me_hw_state = mei_mecsr_read(dev); return (dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA; } @@ -313,7 +327,6 @@ int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, hcsr = mei_hcsr_read(dev) | H_IG; mei_hcsr_set(dev, hcsr); - dev->me_hw_state = mei_mecsr_read(dev); if (!mei_me_is_ready(dev)) return -EIO; diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index d0ee02ac8201..98a7fc18a90a 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -89,82 +89,64 @@ struct mei_device *mei_device_init(struct pci_dev *pdev) */ int mei_hw_init(struct mei_device *dev) { - int err = 0; - int ret; + int ret = 0; mutex_lock(&dev->device_lock); - dev->host_hw_state = mei_hcsr_read(dev); - dev->me_hw_state = mei_mecsr_read(dev); - dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, mestate = 0x%08x.\n", - dev->host_hw_state, dev->me_hw_state); - /* acknowledge interrupt and stop interupts */ mei_clear_interrupts(dev); - /* Doesn't change in runtime */ - dev->hbuf_depth = (dev->host_hw_state & H_CBD) >> 24; + mei_hw_config(dev); dev->recvd_msg = false; dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n"); mei_reset(dev, 1); - dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", - dev->host_hw_state, dev->me_hw_state); - /* wait for ME to turn on ME_RDY */ if (!dev->recvd_msg) { mutex_unlock(&dev->device_lock); - err = wait_event_interruptible_timeout(dev->wait_recvd_msg, + ret = wait_event_interruptible_timeout(dev->wait_recvd_msg, dev->recvd_msg, mei_secs_to_jiffies(MEI_INTEROP_TIMEOUT)); mutex_lock(&dev->device_lock); } - if (err <= 0 && !dev->recvd_msg) { + if (ret <= 0 && !dev->recvd_msg) { dev->dev_state = MEI_DEV_DISABLED; dev_dbg(&dev->pdev->dev, "wait_event_interruptible_timeout failed" "on wait for ME to turn on ME_RDY.\n"); - ret = -ENODEV; - goto out; + goto err; } - if (!(mei_host_is_ready(dev) && mei_me_is_ready(dev))) { - dev->dev_state = MEI_DEV_DISABLED; - - dev_dbg(&dev->pdev->dev, - "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", - dev->host_hw_state, dev->me_hw_state); - - if (!mei_host_is_ready(dev)) - dev_dbg(&dev->pdev->dev, "host is not ready.\n"); - if (!mei_me_is_ready(dev)) - dev_dbg(&dev->pdev->dev, "ME is not ready.\n"); + if (!mei_host_is_ready(dev)) { + dev_err(&dev->pdev->dev, "host is not ready.\n"); + goto err; + } - dev_err(&dev->pdev->dev, "link layer initialization failed.\n"); - ret = -ENODEV; - goto out; + if (!mei_me_is_ready(dev)) { + dev_err(&dev->pdev->dev, "ME is not ready.\n"); + goto err; } if (dev->version.major_version != HBM_MAJOR_VERSION || dev->version.minor_version != HBM_MINOR_VERSION) { dev_dbg(&dev->pdev->dev, "MEI start failed.\n"); - ret = -ENODEV; - goto out; + goto err; } dev->recvd_msg = false; - dev_dbg(&dev->pdev->dev, "host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", - dev->host_hw_state, dev->me_hw_state); dev_dbg(&dev->pdev->dev, "link layer has been established.\n"); - ret = 0; -out: mutex_unlock(&dev->device_lock); - return ret; + return 0; +err: + dev_err(&dev->pdev->dev, "link layer initialization failed.\n"); + dev->dev_state = MEI_DEV_DISABLED; + mutex_unlock(&dev->device_lock); + return -ENODEV; } /** @@ -221,13 +203,6 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) dev->rd_msg_hdr = 0; dev->wd_pending = false; - /* update the state of the registers after reset */ - dev->host_hw_state = mei_hcsr_read(dev); - dev->me_hw_state = mei_mecsr_read(dev); - - dev_dbg(&dev->pdev->dev, "after reset host_hw_state = 0x%08x, me_hw_state = 0x%08x.\n", - dev->host_hw_state, dev->me_hw_state); - if (unexpected) dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n", mei_dev_state_str(dev->dev_state)); diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 27374b6b6424..b04ed9bdf758 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -697,8 +697,6 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) if (pci_dev_msi_enabled(dev->pdev)) mei_clear_interrupts(dev); - dev->me_hw_state = mei_mecsr_read(dev); - /* check if ME wants a reset */ if (!mei_me_is_ready(dev) && dev->dev_state != MEI_DEV_RESETING && @@ -709,7 +707,6 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) return IRQ_HANDLED; } - dev->host_hw_state = mei_hcsr_read(dev); /* check if we need to start the dev */ if (!mei_host_is_ready(dev)) { if (mei_me_is_ready(dev)) { @@ -746,7 +743,6 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) rets = mei_irq_thread_write_handler(dev, &complete_list); end: dev_dbg(&dev->pdev->dev, "end of bottom half function.\n"); - dev->host_hw_state = mei_hcsr_read(dev); dev->mei_host_buffer_is_empty = mei_hbuf_is_empty(dev); bus_message_received = false; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index ae4c5ffc712b..d6589d0d305a 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -385,12 +385,12 @@ void mei_watchdog_unregister(struct mei_device *dev); * Register Access Function */ +void mei_hw_config(struct mei_device *dev); void mei_hw_reset(struct mei_device *dev, bool intr_enable); -u32 mei_hcsr_read(const struct mei_device *dev); -u32 mei_mecsr_read(const struct mei_device *dev); u32 mei_mecbrw_read(const struct mei_device *dev); + void mei_clear_interrupts(struct mei_device *dev); void mei_enable_interrupts(struct mei_device *dev); void mei_disable_interrupts(struct mei_device *dev); -- cgit v1.2.1 From 627ca75733c84427992d798cfebb8e4fd2428917 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 8 Jan 2013 23:07:32 +0200 Subject: mei: move work initialization to mei_device_init Let mei_device_init initialize all the software constructs. Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/init.c | 4 ++++ drivers/misc/mei/main.c | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 98a7fc18a90a..5d08db5b314e 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -76,6 +76,10 @@ struct mei_device *mei_device_init(struct pci_dev *pdev) mei_io_list_init(&dev->ctrl_rd_list); mei_io_list_init(&dev->amthif_cmd_list); mei_io_list_init(&dev->amthif_rd_complete_list); + + INIT_DELAYED_WORK(&dev->timer_work, mei_timer); + INIT_WORK(&dev->init_work, mei_host_client_init); + dev->pdev = pdev; return dev; } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index f72bb77fc279..123c663509ef 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -889,8 +889,6 @@ static int mei_probe(struct pci_dev *pdev, pdev->irq); goto disable_msi; } - INIT_DELAYED_WORK(&dev->timer_work, mei_timer); - INIT_WORK(&dev->init_work, mei_host_client_init); if (mei_hw_init(dev)) { dev_err(&pdev->dev, "init hw failure.\n"); -- cgit v1.2.1 From 8e9a4a9a5c8e8765417d54ed6917c7e1e4d09f4d Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Thu, 10 Jan 2013 17:32:14 +0200 Subject: mei: drop the warning when cl is not initialized during unlinking On systems where wd and amthif is not initialized we will hit cl->dev == NULL. This condition is okay so we don't need to be laud about it. Fixes the follwing warning during suspend [ 137.061985] WARNING: at drivers/misc/mei/client.c:315 mei_cl_unlink+0x86/0x90 [mei]() [ 137.061986] Hardware name: 530U3BI/530U4BI/530U4BH [ 137.062140] Modules linked in: snd_hda_codec_hdmi snd_hda_codec_realtek joydev coretemp kvm_intel snd_hda_intel snd_hda_codec kvm arc4 iwldvm snd_hwdep i915 snd_pcm mac80211 ghash_clmulni_intel snd_page_alloc aesni_intel snd_seq_midi xts snd_seq_midi_event aes_x86_64 rfcomm snd_rawmidi parport_pc bnep lrw snd_seq uvcvideo i2c_algo_bit ppdev gf128mul iwlwifi snd_timer drm_kms_helper ablk_helper cryptd drm snd_seq_device videobuf2_vmalloc psmouse videobuf2_memops snd cfg80211 btusb videobuf2_core soundcore videodev lp bluetooth samsung_laptop wmi microcode mei serio_raw mac_hid video hid_generic lpc_ich parport usbhid hid r8169 [ 137.062143] Pid: 2706, comm: kworker/u:15 Tainted: G D W 3.8.0-rc2-next20130109-1-iniza-generic #1 [ 137.062144] Call Trace: [ 137.062156] [] warn_slowpath_common+0x7f/0xc0 [ 137.062159] [] ? ioread32+0x3a/0x40 [ 137.062162] [] warn_slowpath_null+0x1a/0x20 [ 137.062168] [] mei_cl_unlink+0x86/0x90 [mei] [ 137.062173] [] mei_reset+0xc5/0x240 [mei] [ 137.062178] [] mei_pci_resume+0xa3/0x110 [mei] [ 137.062183] [] pci_pm_resume+0x7e/0xe0 [ 137.062185] [] ? pci_pm_thaw+0x80/0x80 [ 137.062189] [] dpm_run_callback.isra.6+0x25/0x50 [ 137.062192] [] device_resume+0x9f/0x140 [ 137.062194] [] async_resume+0x21/0x50 [ 137.062200] [] async_run_entry_fn+0x90/0x1c0 [ 137.062203] [] process_one_work+0x155/0x460 [ 137.062207] [] worker_thread+0x168/0x400 [ 137.062210] [] ? manage_workers+0x2b0/0x2b0 [ 137.062214] [] kthread+0xc0/0xd0 [ 137.062218] [] ? flush_kthread_worker+0xb0/0xb0 [ 137.062222] [] ret_from_fork+0x7c/0xb0 [ 137.062228] [] ? flush_kthread_worker+0xb0/0xb0 Reported-by: Sedat Dilek Tested-by: Sedat Dilek Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index d566dd880eb0..a921001053ba 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -312,8 +312,9 @@ int mei_cl_unlink(struct mei_cl *cl) if (!cl) return 0; - if (WARN_ON(!cl->dev)) - return -EINVAL; + /* wd and amthif might not be initialized */ + if (!cl->dev) + return 0; dev = cl->dev; -- cgit v1.2.1 From 154f757fd315270e42bd17f4a68d84bd070e5758 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Tue, 27 Nov 2012 09:40:32 +0900 Subject: extcon: max77693: Remove duplicate code by making function This patch make max77693-muic_get_cable_type() function to remove duplicate code because almost internal function need to read adc/adc1k/adclow/chg_type value of MUIC register. Also, this patch add description of internal function move field constant of muic device from extcon-max77693 driver to max77693 header file because of it is needed for masking some interrupt through platform data. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 440 +++++++++++++++++++---------------- include/linux/mfd/max77693-private.h | 86 +++++++ 2 files changed, 327 insertions(+), 199 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 8c17b65eb74d..e84d5dc06798 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -30,92 +30,6 @@ #define DEV_NAME "max77693-muic" -/* MAX77693 MUIC - STATUS1~3 Register */ -#define STATUS1_ADC_SHIFT (0) -#define STATUS1_ADCLOW_SHIFT (5) -#define STATUS1_ADCERR_SHIFT (6) -#define STATUS1_ADC1K_SHIFT (7) -#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) -#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) -#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) -#define STATUS1_ADC1K_MASK (0x1 << STATUS1_ADC1K_SHIFT) - -#define STATUS2_CHGTYP_SHIFT (0) -#define STATUS2_CHGDETRUN_SHIFT (3) -#define STATUS2_DCDTMR_SHIFT (4) -#define STATUS2_DXOVP_SHIFT (5) -#define STATUS2_VBVOLT_SHIFT (6) -#define STATUS2_VIDRM_SHIFT (7) -#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) -#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) -#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) -#define STATUS2_DXOVP_MASK (0x1 << STATUS2_DXOVP_SHIFT) -#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) -#define STATUS2_VIDRM_MASK (0x1 << STATUS2_VIDRM_SHIFT) - -#define STATUS3_OVP_SHIFT (2) -#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) - -/* MAX77693 CDETCTRL1~2 register */ -#define CDETCTRL1_CHGDETEN_SHIFT (0) -#define CDETCTRL1_CHGTYPMAN_SHIFT (1) -#define CDETCTRL1_DCDEN_SHIFT (2) -#define CDETCTRL1_DCD2SCT_SHIFT (3) -#define CDETCTRL1_CDDELAY_SHIFT (4) -#define CDETCTRL1_DCDCPL_SHIFT (5) -#define CDETCTRL1_CDPDET_SHIFT (7) -#define CDETCTRL1_CHGDETEN_MASK (0x1 << CDETCTRL1_CHGDETEN_SHIFT) -#define CDETCTRL1_CHGTYPMAN_MASK (0x1 << CDETCTRL1_CHGTYPMAN_SHIFT) -#define CDETCTRL1_DCDEN_MASK (0x1 << CDETCTRL1_DCDEN_SHIFT) -#define CDETCTRL1_DCD2SCT_MASK (0x1 << CDETCTRL1_DCD2SCT_SHIFT) -#define CDETCTRL1_CDDELAY_MASK (0x1 << CDETCTRL1_CDDELAY_SHIFT) -#define CDETCTRL1_DCDCPL_MASK (0x1 << CDETCTRL1_DCDCPL_SHIFT) -#define CDETCTRL1_CDPDET_MASK (0x1 << CDETCTRL1_CDPDET_SHIFT) - -#define CDETCTRL2_VIDRMEN_SHIFT (1) -#define CDETCTRL2_DXOVPEN_SHIFT (3) -#define CDETCTRL2_VIDRMEN_MASK (0x1 << CDETCTRL2_VIDRMEN_SHIFT) -#define CDETCTRL2_DXOVPEN_MASK (0x1 << CDETCTRL2_DXOVPEN_SHIFT) - -/* MAX77693 MUIC - CONTROL1~3 register */ -#define COMN1SW_SHIFT (0) -#define COMP2SW_SHIFT (3) -#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) -#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) -#define COMP_SW_MASK (COMP2SW_MASK | COMN1SW_MASK) -#define CONTROL1_SW_USB ((1 << COMP2SW_SHIFT) \ - | (1 << COMN1SW_SHIFT)) -#define CONTROL1_SW_AUDIO ((2 << COMP2SW_SHIFT) \ - | (2 << COMN1SW_SHIFT)) -#define CONTROL1_SW_UART ((3 << COMP2SW_SHIFT) \ - | (3 << COMN1SW_SHIFT)) -#define CONTROL1_SW_OPEN ((0 << COMP2SW_SHIFT) \ - | (0 << COMN1SW_SHIFT)) - -#define CONTROL2_LOWPWR_SHIFT (0) -#define CONTROL2_ADCEN_SHIFT (1) -#define CONTROL2_CPEN_SHIFT (2) -#define CONTROL2_SFOUTASRT_SHIFT (3) -#define CONTROL2_SFOUTORD_SHIFT (4) -#define CONTROL2_ACCDET_SHIFT (5) -#define CONTROL2_USBCPINT_SHIFT (6) -#define CONTROL2_RCPS_SHIFT (7) -#define CONTROL2_LOWPWR_MASK (0x1 << CONTROL2_LOWPWR_SHIFT) -#define CONTROL2_ADCEN_MASK (0x1 << CONTROL2_ADCEN_SHIFT) -#define CONTROL2_CPEN_MASK (0x1 << CONTROL2_CPEN_SHIFT) -#define CONTROL2_SFOUTASRT_MASK (0x1 << CONTROL2_SFOUTASRT_SHIFT) -#define CONTROL2_SFOUTORD_MASK (0x1 << CONTROL2_SFOUTORD_SHIFT) -#define CONTROL2_ACCDET_MASK (0x1 << CONTROL2_ACCDET_SHIFT) -#define CONTROL2_USBCPINT_MASK (0x1 << CONTROL2_USBCPINT_SHIFT) -#define CONTROL2_RCPS_MASK (0x1 << CONTROL2_RCPS_SHIFT) - -#define CONTROL3_JIGSET_SHIFT (0) -#define CONTROL3_BTLDSET_SHIFT (2) -#define CONTROL3_ADCDBSET_SHIFT (4) -#define CONTROL3_JIGSET_MASK (0x3 << CONTROL3_JIGSET_SHIFT) -#define CONTROL3_BTLDSET_MASK (0x3 << CONTROL3_BTLDSET_SHIFT) -#define CONTROL3_ADCDBSET_MASK (0x3 << CONTROL3_ADCDBSET_SHIFT) - enum max77693_muic_adc_debounce_time { ADC_DEBOUNCE_TIME_5MS = 0, ADC_DEBOUNCE_TIME_10MS, @@ -127,8 +41,8 @@ struct max77693_muic_info { struct device *dev; struct max77693_dev *max77693; struct extcon_dev *edev; - int prev_adc; - int prev_adc_gnd; + int prev_cable_type; + int prev_cable_type_gnd; int prev_chg_type; u8 status[2]; @@ -137,6 +51,13 @@ struct max77693_muic_info { struct mutex mutex; }; +enum max77693_muic_cable_group { + MAX77693_CABLE_GROUP_ADC = 0, + MAX77693_CABLE_GROUP_ADC_GND, + MAX77693_CABLE_GROUP_CHG, + MAX77693_CABLE_GROUP_VBVOLT, +}; + enum max77693_muic_charger_type { MAX77693_CHARGER_TYPE_NONE = 0, MAX77693_CHARGER_TYPE_USB, @@ -221,21 +142,40 @@ enum max77693_muic_acc_type { }; /* MAX77693 MUIC device support below list of accessories(external connector) */ -const char *max77693_extcon_cable[] = { - [0] = "USB", - [1] = "USB-Host", - [2] = "TA", - [3] = "Fast-charger", - [4] = "Slow-charger", - [5] = "Charge-downstream", - [6] = "MHL", - [7] = "Audio-video-load", - [8] = "Audio-video-noload", - [9] = "JIG", +enum { + EXTCON_CABLE_USB = 0, + EXTCON_CABLE_USB_HOST, + EXTCON_CABLE_TA, + EXTCON_CABLE_FAST_CHARGER, + EXTCON_CABLE_SLOW_CHARGER, + EXTCON_CABLE_CHARGE_DOWNSTREAM, + EXTCON_CABLE_MHL, + EXTCON_CABLE_AUDIO_VIDEO_LOAD, + EXTCON_CABLE_AUDIO_VIDEO_NOLOAD, + EXTCON_CABLE_JIG, + + _EXTCON_CABLE_NUM, +}; +const char *max77693_extcon_cable[] = { + [EXTCON_CABLE_USB] = "USB", + [EXTCON_CABLE_USB_HOST] = "USB-Host", + [EXTCON_CABLE_TA] = "TA", + [EXTCON_CABLE_FAST_CHARGER] = "Fast-charger", + [EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger", + [EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream", + [EXTCON_CABLE_MHL] = "MHL", + [EXTCON_CABLE_AUDIO_VIDEO_LOAD] = "Audio-video-load", + [EXTCON_CABLE_AUDIO_VIDEO_NOLOAD] = "Audio-video-noload", + [EXTCON_CABLE_JIG] = "JIG", NULL, }; +/* + * max77693_muic_set_debounce_time - Set the debounce time of ADC + * @info: the instance including private data of max77693 MUIC + * @time: the debounce time of ADC + */ static int max77693_muic_set_debounce_time(struct max77693_muic_info *info, enum max77693_muic_adc_debounce_time time) { @@ -262,6 +202,16 @@ static int max77693_muic_set_debounce_time(struct max77693_muic_info *info, return ret; }; +/* + * max77693_muic_set_path - Set hardware line according to attached cable + * @info: the instance including private data of max77693 MUIC + * @value: the path according to attached cable + * @attached: the state of cable (true:attached, false:detached) + * + * The max77693 MUIC device share outside H/W line among a varity of cables + * so, this function set internal path of H/W line according to the type of + * attached cable. + */ static int max77693_muic_set_path(struct max77693_muic_info *info, u8 val, bool attached) { @@ -300,36 +250,169 @@ out: return ret; } -static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info, - bool attached) +/* + * max77693_muic_get_cable_type - Return cable type and check cable state + * @info: the instance including private data of max77693 MUIC + * @group: the path according to attached cable + * @attached: store cable state and return + * + * This function check the cable state either attached or detached, + * and then divide precise type of cable according to cable group. + * - MAX77693_CABLE_GROUP_ADC + * - MAX77693_CABLE_GROUP_ADC_GND + * - MAX77693_CABLE_GROUP_CHG + * - MAX77693_CABLE_GROUP_VBVOLT + */ +static int max77693_muic_get_cable_type(struct max77693_muic_info *info, + enum max77693_muic_cable_group group, bool *attached) { - int ret = 0; - int type; - int adc, adc1k, adclow; + int cable_type = 0; + int adc; + int adc1k; + int adclow; + int vbvolt; + int chg_type; + + switch (group) { + case MAX77693_CABLE_GROUP_ADC: + /* + * Read ADC value to check cable type and decide cable state + * according to cable type + */ + adc = info->status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + /* + * Check current cable state/cable type and store cable type + * (info->prev_cable_type) for handling cable when cable is + * detached. + */ + if (adc == MAX77693_MUIC_ADC_OPEN) { + *attached = false; + + cable_type = info->prev_cable_type; + info->prev_cable_type = MAX77693_MUIC_ADC_OPEN; + } else { + *attached = true; + + cable_type = info->prev_cable_type = adc; + } + break; + case MAX77693_CABLE_GROUP_ADC_GND: + /* + * Read ADC value to check cable type and decide cable state + * according to cable type + */ + adc = info->status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; - if (attached) { + /* + * Check current cable state/cable type and store cable type + * (info->prev_cable_type/_gnd) for handling cable when cable + * is detached. + */ + if (adc == MAX77693_MUIC_ADC_OPEN) { + *attached = false; + + cable_type = info->prev_cable_type_gnd; + info->prev_cable_type_gnd = MAX77693_MUIC_ADC_OPEN; + } else { + *attached = true; + + adclow = info->status[0] & STATUS1_ADCLOW_MASK; + adclow >>= STATUS1_ADCLOW_SHIFT; + adc1k = info->status[0] & STATUS1_ADC1K_MASK; + adc1k >>= STATUS1_ADC1K_SHIFT; + + vbvolt = info->status[1] & STATUS2_VBVOLT_MASK; + vbvolt >>= STATUS2_VBVOLT_SHIFT; + + /** + * [0x1][VBVolt][ADCLow][ADC1K] + * [0x1 0 0 0 ] : USB_OTG + * [0x1 0 1 0 ] : Audio Video Cable with load + * [0x1 0 1 1 ] : MHL without charging connector + * [0x1 1 1 1 ] : MHL with charging connector + */ + cable_type = ((0x1 << 8) + | (vbvolt << 2) + | (adclow << 1) + | adc1k); + + info->prev_cable_type = adc; + info->prev_cable_type_gnd = cable_type; + } + + break; + case MAX77693_CABLE_GROUP_CHG: + /* + * Read charger type to check cable type and decide cable state + * according to type of charger cable. + */ + chg_type = info->status[1] & STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + if (chg_type == MAX77693_CHARGER_TYPE_NONE) { + *attached = false; + + cable_type = info->prev_chg_type; + info->prev_chg_type = MAX77693_CHARGER_TYPE_NONE; + } else { + *attached = true; + + /* + * Check current cable state/cable type and store cable + * type(info->prev_chg_type) for handling cable when + * charger cable is detached. + */ + cable_type = info->prev_chg_type = chg_type; + } + + break; + case MAX77693_CABLE_GROUP_VBVOLT: + /* + * Read ADC value to check cable type and decide cable state + * according to cable type + */ adc = info->status[0] & STATUS1_ADC_MASK; - adclow = info->status[0] & STATUS1_ADCLOW_MASK; - adclow >>= STATUS1_ADCLOW_SHIFT; - adc1k = info->status[0] & STATUS1_ADC1K_MASK; - adc1k >>= STATUS1_ADC1K_SHIFT; - - /** - * [0x1][ADCLow][ADC1K] - * [0x1 0 0 ] : USB_OTG - * [0x1 1 0 ] : Audio Video Cable with load - * [0x1 1 1 ] : MHL + adc >>= STATUS1_ADC_SHIFT; + chg_type = info->status[1] & STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + if (adc == MAX77693_MUIC_ADC_OPEN + && chg_type == MAX77693_CHARGER_TYPE_NONE) + *attached = false; + else + *attached = true; + + /* + * Read vbvolt field, if vbvolt is 1, + * this cable is used for charging. */ - type = ((0x1 << 8) | (adclow << 1) | adc1k); + vbvolt = info->status[1] & STATUS2_VBVOLT_MASK; + vbvolt >>= STATUS2_VBVOLT_SHIFT; + + cable_type = vbvolt; + break; + default: + dev_err(info->dev, "Unknown cable group (%d)\n", group); + cable_type = -EINVAL; + break; + } + + return cable_type; +} + +static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info) +{ + int cable_type_gnd; + int ret = 0; + bool attached; - /* Store previous ADC value to handle accessory - when accessory will be detached */ - info->prev_adc = adc; - info->prev_adc_gnd = type; - } else - type = info->prev_adc_gnd; + cable_type_gnd = max77693_muic_get_cable_type(info, + MAX77693_CABLE_GROUP_ADC_GND, &attached); - switch (type) { + switch (cable_type_gnd) { case MAX77693_MUIC_GND_USB_OTG: /* USB_OTG */ ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); @@ -352,8 +435,6 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info, default: dev_err(info->dev, "failed to detect %s accessory\n", attached ? "attached" : "detached"); - dev_err(info->dev, "- adc:0x%x, adclow:0x%x, adc1k:0x%x\n", - adc, adclow, adc1k); ret = -EINVAL; break; } @@ -362,45 +443,36 @@ out: return ret; } -static int max77693_muic_adc_handler(struct max77693_muic_info *info, - int curr_adc, bool attached) +static int max77693_muic_adc_handler(struct max77693_muic_info *info) { + int cable_type; + bool attached; int ret = 0; - int adc; - if (attached) { - /* Store ADC value to handle accessory - when accessory will be detached */ - info->prev_adc = curr_adc; - adc = curr_adc; - } else - adc = info->prev_adc; + /* Check accessory state which is either detached or attached */ + cable_type = max77693_muic_get_cable_type(info, + MAX77693_CABLE_GROUP_ADC, &attached); dev_info(info->dev, "external connector is %s (adc:0x%02x, prev_adc:0x%x)\n", - attached ? "attached" : "detached", curr_adc, info->prev_adc); + attached ? "attached" : "detached", cable_type, + info->prev_cable_type); - switch (adc) { + switch (cable_type) { case MAX77693_MUIC_ADC_GROUND: /* USB_OTG/MHL/Audio */ - max77693_muic_adc_ground_handler(info, attached); + max77693_muic_adc_ground_handler(info); break; case MAX77693_MUIC_ADC_FACTORY_MODE_USB_OFF: case MAX77693_MUIC_ADC_FACTORY_MODE_USB_ON: - /* USB */ - ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); - if (ret < 0) - goto out; - extcon_set_cable_state(info->edev, "USB", attached); - break; case MAX77693_MUIC_ADC_FACTORY_MODE_UART_OFF: - case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: /* JIG */ ret = max77693_muic_set_path(info, CONTROL1_SW_UART, attached); if (ret < 0) goto out; extcon_set_cable_state(info->edev, "JIG", attached); break; + case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: case MAX77693_MUIC_ADC_AUDIO_MODE_REMOTE: /* Audio Video cable with no-load */ ret = max77693_muic_set_path(info, CONTROL1_SW_AUDIO, attached); @@ -439,12 +511,12 @@ static int max77693_muic_adc_handler(struct max77693_muic_info *info, proper operation when this accessory is attached/detached. */ dev_info(info->dev, "accessory is %s but it isn't used (adc:0x%x)\n", - attached ? "attached" : "detached", adc); + attached ? "attached" : "detached", cable_type); goto out; default: dev_err(info->dev, "failed to detect %s accessory (adc:0x%x)\n", - attached ? "attached" : "detached", adc); + attached ? "attached" : "detached", cable_type); ret = -EINVAL; goto out; } @@ -453,24 +525,19 @@ out: return ret; } -static int max77693_muic_chg_handler(struct max77693_muic_info *info, - int curr_chg_type, bool attached) +static int max77693_muic_chg_handler(struct max77693_muic_info *info) { - int ret = 0; int chg_type; + bool attached; + int ret = 0; - if (attached) { - /* Store previous charger type to control - when charger accessory will be detached */ - info->prev_chg_type = curr_chg_type; - chg_type = curr_chg_type; - } else - chg_type = info->prev_chg_type; + chg_type = max77693_muic_get_cable_type(info, + MAX77693_CABLE_GROUP_CHG, &attached); dev_info(info->dev, "external connector is %s(chg_type:0x%x, prev_chg_type:0x%x)\n", attached ? "attached" : "detached", - curr_chg_type, info->prev_chg_type); + chg_type, info->prev_chg_type); switch (chg_type) { case MAX77693_CHARGER_TYPE_USB: @@ -510,10 +577,8 @@ static void max77693_muic_irq_work(struct work_struct *work) { struct max77693_muic_info *info = container_of(work, struct max77693_muic_info, irq_work); - int curr_adc, curr_chg_type; int irq_type = -1; int i, ret = 0; - bool attached = true; if (!info->edev) return; @@ -539,14 +604,7 @@ static void max77693_muic_irq_work(struct work_struct *work) case MAX77693_MUIC_IRQ_INT1_ADC1K: /* Handle all of accessory except for type of charger accessory */ - curr_adc = info->status[0] & STATUS1_ADC_MASK; - curr_adc >>= STATUS1_ADC_SHIFT; - - /* Check accessory state which is either detached or attached */ - if (curr_adc == MAX77693_MUIC_ADC_OPEN) - attached = false; - - ret = max77693_muic_adc_handler(info, curr_adc, attached); + ret = max77693_muic_adc_handler(info); break; case MAX77693_MUIC_IRQ_INT2_CHGTYP: case MAX77693_MUIC_IRQ_INT2_CHGDETREUN: @@ -555,15 +613,7 @@ static void max77693_muic_irq_work(struct work_struct *work) case MAX77693_MUIC_IRQ_INT2_VBVOLT: case MAX77693_MUIC_IRQ_INT2_VIDRM: /* Handle charger accessory */ - curr_chg_type = info->status[1] & STATUS2_CHGTYP_MASK; - curr_chg_type >>= STATUS2_CHGTYP_SHIFT; - - /* Check charger accessory state which - is either detached or attached */ - if (curr_chg_type == MAX77693_CHARGER_TYPE_NONE) - attached = false; - - ret = max77693_muic_chg_handler(info, curr_chg_type, attached); + ret = max77693_muic_chg_handler(info); break; case MAX77693_MUIC_IRQ_INT3_EOC: case MAX77693_MUIC_IRQ_INT3_CGMBC: @@ -604,7 +654,9 @@ static struct regmap_config max77693_muic_regmap_config = { static int max77693_muic_detect_accessory(struct max77693_muic_info *info) { int ret = 0; - int adc, chg_type; + int adc; + int chg_type; + bool attached; mutex_lock(&info->mutex); @@ -617,34 +669,24 @@ static int max77693_muic_detect_accessory(struct max77693_muic_info *info) return -EINVAL; } - adc = info->status[0] & STATUS1_ADC_MASK; - adc >>= STATUS1_ADC_SHIFT; - - if (adc != MAX77693_MUIC_ADC_OPEN) { - dev_info(info->dev, - "external connector is attached (adc:0x%02x)\n", adc); - - ret = max77693_muic_adc_handler(info, adc, true); + adc = max77693_muic_get_cable_type(info, MAX77693_CABLE_GROUP_ADC, + &attached); + if (attached && adc != MAX77693_MUIC_ADC_OPEN) { + ret = max77693_muic_adc_handler(info); if (ret < 0) - dev_err(info->dev, "failed to detect accessory\n"); - goto out; + dev_err(info->dev, "Cannot detect accessory\n"); } - chg_type = info->status[1] & STATUS2_CHGTYP_MASK; - chg_type >>= STATUS2_CHGTYP_SHIFT; - - if (chg_type != MAX77693_CHARGER_TYPE_NONE) { - dev_info(info->dev, - "external connector is attached (chg_type:0x%x)\n", - chg_type); - - max77693_muic_chg_handler(info, chg_type, true); + chg_type = max77693_muic_get_cable_type(info, MAX77693_CABLE_GROUP_CHG, + &attached); + if (attached && chg_type != MAX77693_CHARGER_TYPE_NONE) { + ret = max77693_muic_chg_handler(info); if (ret < 0) - dev_err(info->dev, "failed to detect charger accessory\n"); + dev_err(info->dev, "Cannot detect charger accessory\n"); } -out: mutex_unlock(&info->mutex); + return ret; } diff --git a/include/linux/mfd/max77693-private.h b/include/linux/mfd/max77693-private.h index 1eeae5c07915..5b18ecde69b5 100644 --- a/include/linux/mfd/max77693-private.h +++ b/include/linux/mfd/max77693-private.h @@ -106,6 +106,92 @@ enum max77693_muic_reg { MAX77693_MUIC_REG_END, }; +/* MAX77693 MUIC - STATUS1~3 Register */ +#define STATUS1_ADC_SHIFT (0) +#define STATUS1_ADCLOW_SHIFT (5) +#define STATUS1_ADCERR_SHIFT (6) +#define STATUS1_ADC1K_SHIFT (7) +#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) +#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) +#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) +#define STATUS1_ADC1K_MASK (0x1 << STATUS1_ADC1K_SHIFT) + +#define STATUS2_CHGTYP_SHIFT (0) +#define STATUS2_CHGDETRUN_SHIFT (3) +#define STATUS2_DCDTMR_SHIFT (4) +#define STATUS2_DXOVP_SHIFT (5) +#define STATUS2_VBVOLT_SHIFT (6) +#define STATUS2_VIDRM_SHIFT (7) +#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) +#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) +#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) +#define STATUS2_DXOVP_MASK (0x1 << STATUS2_DXOVP_SHIFT) +#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) +#define STATUS2_VIDRM_MASK (0x1 << STATUS2_VIDRM_SHIFT) + +#define STATUS3_OVP_SHIFT (2) +#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) + +/* MAX77693 CDETCTRL1~2 register */ +#define CDETCTRL1_CHGDETEN_SHIFT (0) +#define CDETCTRL1_CHGTYPMAN_SHIFT (1) +#define CDETCTRL1_DCDEN_SHIFT (2) +#define CDETCTRL1_DCD2SCT_SHIFT (3) +#define CDETCTRL1_CDDELAY_SHIFT (4) +#define CDETCTRL1_DCDCPL_SHIFT (5) +#define CDETCTRL1_CDPDET_SHIFT (7) +#define CDETCTRL1_CHGDETEN_MASK (0x1 << CDETCTRL1_CHGDETEN_SHIFT) +#define CDETCTRL1_CHGTYPMAN_MASK (0x1 << CDETCTRL1_CHGTYPMAN_SHIFT) +#define CDETCTRL1_DCDEN_MASK (0x1 << CDETCTRL1_DCDEN_SHIFT) +#define CDETCTRL1_DCD2SCT_MASK (0x1 << CDETCTRL1_DCD2SCT_SHIFT) +#define CDETCTRL1_CDDELAY_MASK (0x1 << CDETCTRL1_CDDELAY_SHIFT) +#define CDETCTRL1_DCDCPL_MASK (0x1 << CDETCTRL1_DCDCPL_SHIFT) +#define CDETCTRL1_CDPDET_MASK (0x1 << CDETCTRL1_CDPDET_SHIFT) + +#define CDETCTRL2_VIDRMEN_SHIFT (1) +#define CDETCTRL2_DXOVPEN_SHIFT (3) +#define CDETCTRL2_VIDRMEN_MASK (0x1 << CDETCTRL2_VIDRMEN_SHIFT) +#define CDETCTRL2_DXOVPEN_MASK (0x1 << CDETCTRL2_DXOVPEN_SHIFT) + +/* MAX77693 MUIC - CONTROL1~3 register */ +#define COMN1SW_SHIFT (0) +#define COMP2SW_SHIFT (3) +#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) +#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) +#define COMP_SW_MASK (COMP2SW_MASK | COMN1SW_MASK) +#define CONTROL1_SW_USB ((1 << COMP2SW_SHIFT) \ + | (1 << COMN1SW_SHIFT)) +#define CONTROL1_SW_AUDIO ((2 << COMP2SW_SHIFT) \ + | (2 << COMN1SW_SHIFT)) +#define CONTROL1_SW_UART ((3 << COMP2SW_SHIFT) \ + | (3 << COMN1SW_SHIFT)) +#define CONTROL1_SW_OPEN ((0 << COMP2SW_SHIFT) \ + | (0 << COMN1SW_SHIFT)) + +#define CONTROL2_LOWPWR_SHIFT (0) +#define CONTROL2_ADCEN_SHIFT (1) +#define CONTROL2_CPEN_SHIFT (2) +#define CONTROL2_SFOUTASRT_SHIFT (3) +#define CONTROL2_SFOUTORD_SHIFT (4) +#define CONTROL2_ACCDET_SHIFT (5) +#define CONTROL2_USBCPINT_SHIFT (6) +#define CONTROL2_RCPS_SHIFT (7) +#define CONTROL2_LOWPWR_MASK (0x1 << CONTROL2_LOWPWR_SHIFT) +#define CONTROL2_ADCEN_MASK (0x1 << CONTROL2_ADCEN_SHIFT) +#define CONTROL2_CPEN_MASK (0x1 << CONTROL2_CPEN_SHIFT) +#define CONTROL2_SFOUTASRT_MASK (0x1 << CONTROL2_SFOUTASRT_SHIFT) +#define CONTROL2_SFOUTORD_MASK (0x1 << CONTROL2_SFOUTORD_SHIFT) +#define CONTROL2_ACCDET_MASK (0x1 << CONTROL2_ACCDET_SHIFT) +#define CONTROL2_USBCPINT_MASK (0x1 << CONTROL2_USBCPINT_SHIFT) +#define CONTROL2_RCPS_MASK (0x1 << CONTROL2_RCPS_SHIFT) + +#define CONTROL3_JIGSET_SHIFT (0) +#define CONTROL3_BTLDSET_SHIFT (2) +#define CONTROL3_ADCDBSET_SHIFT (4) +#define CONTROL3_JIGSET_MASK (0x3 << CONTROL3_JIGSET_SHIFT) +#define CONTROL3_BTLDSET_MASK (0x3 << CONTROL3_BTLDSET_SHIFT) +#define CONTROL3_ADCDBSET_MASK (0x3 << CONTROL3_ADCDBSET_SHIFT) + /* Slave addr = 0x90: Haptic */ enum max77693_haptic_reg { MAX77693_HAPTIC_REG_STATUS = 0x00, -- cgit v1.2.1 From 06bed0afa24e98b9afa26456c6bed37a6510f7d1 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Tue, 27 Nov 2012 11:30:35 +0900 Subject: extcon: max77693: Add support MHL_TA cable for charging battery This patch support MHL_TA cable for charging battery. The MHL_TA cable include MHL with TA cable or MHL with micro USB cable. When MHL with TA/USB cable is attached, extcon-max77693 driver detect two interrupt for handling precise operation according to each cable (MHL and TA/USB cable). Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 56 ++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index e84d5dc06798..78dc7505b965 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -136,9 +136,11 @@ enum max77693_muic_acc_type { /* The below accessories have same ADC value so ADCLow and ADC1K bit is used to separate specific accessory */ - MAX77693_MUIC_GND_USB_OTG = 0x100, /* ADC:0x0, ADCLow:0, ADC1K:0 */ - MAX77693_MUIC_GND_AV_CABLE_LOAD = 0x102,/* ADC:0x0, ADCLow:1, ADC1K:0 */ - MAX77693_MUIC_GND_MHL_CABLE = 0x103, /* ADC:0x0, ADCLow:1, ADC1K:1 */ + MAX77693_MUIC_GND_USB_OTG = 0x100, /* ADC:0x0, VBVolot:0, ADCLow:0, ADC1K:0 */ + MAX77693_MUIC_GND_USB_OTG_VB = 0x104, /* ADC:0x0, VBVolot:1, ADCLow:0, ADC1K:0 */ + MAX77693_MUIC_GND_AV_CABLE_LOAD = 0x102,/* ADC:0x0, VBVolot:0, ADCLow:1, ADC1K:0 */ + MAX77693_MUIC_GND_MHL = 0x103, /* ADC:0x0, VBVolot:0, ADCLow:1, ADC1K:1 */ + MAX77693_MUIC_GND_MHL_VB = 0x107, /* ADC:0x0, VBVolot:1, ADCLow:1, ADC1K:1 */ }; /* MAX77693 MUIC device support below list of accessories(external connector) */ @@ -150,6 +152,7 @@ enum { EXTCON_CABLE_SLOW_CHARGER, EXTCON_CABLE_CHARGE_DOWNSTREAM, EXTCON_CABLE_MHL, + EXTCON_CABLE_MHL_TA, EXTCON_CABLE_AUDIO_VIDEO_LOAD, EXTCON_CABLE_AUDIO_VIDEO_NOLOAD, EXTCON_CABLE_JIG, @@ -165,6 +168,7 @@ const char *max77693_extcon_cable[] = { [EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger", [EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream", [EXTCON_CABLE_MHL] = "MHL", + [EXTCON_CABLE_MHL_TA] = "MHL_TA", [EXTCON_CABLE_AUDIO_VIDEO_LOAD] = "Audio-video-load", [EXTCON_CABLE_AUDIO_VIDEO_NOLOAD] = "Audio-video-noload", [EXTCON_CABLE_JIG] = "JIG", @@ -330,6 +334,7 @@ static int max77693_muic_get_cable_type(struct max77693_muic_info *info, /** * [0x1][VBVolt][ADCLow][ADC1K] * [0x1 0 0 0 ] : USB_OTG + * [0x1 1 0 0 ] : USB_OTG_VB * [0x1 0 1 0 ] : Audio Video Cable with load * [0x1 0 1 1 ] : MHL without charging connector * [0x1 1 1 1 ] : MHL with charging connector @@ -414,22 +419,24 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info) switch (cable_type_gnd) { case MAX77693_MUIC_GND_USB_OTG: - /* USB_OTG */ + case MAX77693_MUIC_GND_USB_OTG_VB: + /* USB_OTG, PATH: AP_USB */ ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); if (ret < 0) goto out; extcon_set_cable_state(info->edev, "USB-Host", attached); break; case MAX77693_MUIC_GND_AV_CABLE_LOAD: - /* Audio Video Cable with load */ + /* Audio Video Cable with load, PATH:AUDIO */ ret = max77693_muic_set_path(info, CONTROL1_SW_AUDIO, attached); if (ret < 0) goto out; extcon_set_cable_state(info->edev, "Audio-video-load", attached); break; - case MAX77693_MUIC_GND_MHL_CABLE: - /* MHL */ + case MAX77693_MUIC_GND_MHL: + case MAX77693_MUIC_GND_MHL_VB: + /* MHL or MHL with USB/TA cable */ extcon_set_cable_state(info->edev, "MHL", attached); break; default: @@ -528,7 +535,9 @@ out: static int max77693_muic_chg_handler(struct max77693_muic_info *info) { int chg_type; + int cable_type_gnd; bool attached; + bool cable_attached; int ret = 0; chg_type = max77693_muic_get_cable_type(info, @@ -541,10 +550,35 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info) switch (chg_type) { case MAX77693_CHARGER_TYPE_USB: - ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); - if (ret < 0) - goto out; - extcon_set_cable_state(info->edev, "USB", attached); + cable_type_gnd = max77693_muic_get_cable_type(info, + MAX77693_CABLE_GROUP_ADC_GND, + &cable_attached); + + switch (cable_type_gnd) { + case MAX77693_MUIC_GND_MHL: + case MAX77693_MUIC_GND_MHL_VB: + /* + * USB/TA with MHL cable + * - MHL cable, which connect micro USB or TA cable, + * is used to charging battery. So, extcon driver check + * charging type whether micro USB or TA cable is + * connected to MHL cable when extcon driver detect MHL + * cable. + */ + extcon_set_cable_state(info->edev, "MHL_TA", attached); + + if (!cable_attached) + extcon_set_cable_state(info->edev, + "MHL", false); + break; + default: + /* Only USB cable, PATH:AP_USB */ + ret = max77693_muic_set_path(info, CONTROL1_SW_USB, + attached); + if (ret < 0) + goto out; + extcon_set_cable_state(info->edev, "USB", attached); + } break; case MAX77693_CHARGER_TYPE_DOWNSTREAM_PORT: extcon_set_cable_state(info->edev, -- cgit v1.2.1 From d0587eb794da221a5c210348abc8f6cceae93896 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Tue, 27 Nov 2012 12:06:49 +0900 Subject: extcon: max77693: Add support jig cable This patch detect several kinds of JIG cable according to ADC value and set the hardware line path according to type of JIG cable(JIG-USB-ON /JIG-USB-OFF/JIG-UART-OFF). Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 52 +++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 78dc7505b965..26ce4dfeda10 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -153,9 +153,10 @@ enum { EXTCON_CABLE_CHARGE_DOWNSTREAM, EXTCON_CABLE_MHL, EXTCON_CABLE_MHL_TA, + EXTCON_CABLE_JIG_USB_ON, + EXTCON_CABLE_JIG_USB_OFF, + EXTCON_CABLE_JIG_UART_OFF, EXTCON_CABLE_AUDIO_VIDEO_LOAD, - EXTCON_CABLE_AUDIO_VIDEO_NOLOAD, - EXTCON_CABLE_JIG, _EXTCON_CABLE_NUM, }; @@ -169,9 +170,11 @@ const char *max77693_extcon_cable[] = { [EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream", [EXTCON_CABLE_MHL] = "MHL", [EXTCON_CABLE_MHL_TA] = "MHL_TA", + [EXTCON_CABLE_JIG_USB_ON] = "JIG-USB-ON", + [EXTCON_CABLE_JIG_USB_OFF] = "JIG-USB-OFF", + [EXTCON_CABLE_JIG_UART_OFF] = "JIG-UART-OFF", [EXTCON_CABLE_AUDIO_VIDEO_LOAD] = "Audio-video-load", - [EXTCON_CABLE_AUDIO_VIDEO_NOLOAD] = "Audio-video-noload", - [EXTCON_CABLE_JIG] = "JIG", + NULL, }; @@ -450,6 +453,44 @@ out: return ret; } +static int max77693_muic_jig_handler(struct max77693_muic_info *info, + int cable_type, bool attached) +{ + char cable_name[32]; + int ret = 0; + u8 path = CONTROL1_SW_OPEN; + + dev_info(info->dev, + "external connector is %s (adc:0x%02x)\n", + attached ? "attached" : "detached", cable_type); + + switch (cable_type) { + case MAX77693_MUIC_ADC_FACTORY_MODE_USB_OFF: /* ADC_JIG_USB_OFF */ + /* PATH:AP_USB */ + strcpy(cable_name, "JIG-USB-OFF"); + path = CONTROL1_SW_USB; + break; + case MAX77693_MUIC_ADC_FACTORY_MODE_USB_ON: /* ADC_JIG_USB_ON */ + /* PATH:AP_USB */ + strcpy(cable_name, "JIG-USB-ON"); + path = CONTROL1_SW_USB; + break; + case MAX77693_MUIC_ADC_FACTORY_MODE_UART_OFF: /* ADC_JIG_UART_OFF */ + /* PATH:AP_UART */ + strcpy(cable_name, "JIG-UART-OFF"); + path = CONTROL1_SW_UART; + break; + } + + ret = max77693_muic_set_path(info, path, attached); + if (ret < 0) + goto out; + + extcon_set_cable_state(info->edev, cable_name, attached); +out: + return ret; +} + static int max77693_muic_adc_handler(struct max77693_muic_info *info) { int cable_type; @@ -474,10 +515,9 @@ static int max77693_muic_adc_handler(struct max77693_muic_info *info) case MAX77693_MUIC_ADC_FACTORY_MODE_USB_ON: case MAX77693_MUIC_ADC_FACTORY_MODE_UART_OFF: /* JIG */ - ret = max77693_muic_set_path(info, CONTROL1_SW_UART, attached); + ret = max77693_muic_jig_handler(info, cable_type, attached); if (ret < 0) goto out; - extcon_set_cable_state(info->edev, "JIG", attached); break; case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: case MAX77693_MUIC_ADC_AUDIO_MODE_REMOTE: -- cgit v1.2.1 From 39bf369e4ed321158eb8dc5031b4a9f2108ea614 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 3 Dec 2012 13:09:41 +0900 Subject: extcon: max77693: Add support dock device and buttons This patch support detection of dock device with extcon and buttons of dock device for playing music with input subsystem. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 255 +++++++++++++++++++++++++++++++++------ 1 file changed, 216 insertions(+), 39 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 26ce4dfeda10..07ea96bfd0cb 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -44,11 +45,15 @@ struct max77693_muic_info { int prev_cable_type; int prev_cable_type_gnd; int prev_chg_type; + int prev_button_type; u8 status[2]; int irq; struct work_struct irq_work; struct mutex mutex; + + /* Button of dock device */ + struct input_dev *dock; }; enum max77693_muic_cable_group { @@ -156,7 +161,10 @@ enum { EXTCON_CABLE_JIG_USB_ON, EXTCON_CABLE_JIG_USB_OFF, EXTCON_CABLE_JIG_UART_OFF, - EXTCON_CABLE_AUDIO_VIDEO_LOAD, + EXTCON_CABLE_JIG_UART_ON, + EXTCON_CABLE_DOCK_SMART, + EXTCON_CABLE_DOCK_DESK, + EXTCON_CABLE_DOCK_AUDIO, _EXTCON_CABLE_NUM, }; @@ -173,7 +181,10 @@ const char *max77693_extcon_cable[] = { [EXTCON_CABLE_JIG_USB_ON] = "JIG-USB-ON", [EXTCON_CABLE_JIG_USB_OFF] = "JIG-USB-OFF", [EXTCON_CABLE_JIG_UART_OFF] = "JIG-UART-OFF", - [EXTCON_CABLE_AUDIO_VIDEO_LOAD] = "Audio-video-load", + [EXTCON_CABLE_JIG_UART_ON] = "Dock-Car", + [EXTCON_CABLE_DOCK_SMART] = "Dock-Smart", + [EXTCON_CABLE_DOCK_DESK] = "Dock-Desk", + [EXTCON_CABLE_DOCK_AUDIO] = "Dock-Audio", NULL, }; @@ -411,6 +422,96 @@ static int max77693_muic_get_cable_type(struct max77693_muic_info *info, return cable_type; } +static int max77693_muic_dock_handler(struct max77693_muic_info *info, + int cable_type, bool attached) +{ + int ret = 0; + char dock_name[CABLE_NAME_MAX]; + + dev_info(info->dev, + "external connector is %s (adc:0x%02x)\n", + attached ? "attached" : "detached", cable_type); + + switch (cable_type) { + case MAX77693_MUIC_ADC_RESERVED_ACC_3: /* Dock-Smart */ + /* PATH:AP_USB */ + ret = max77693_muic_set_path(info, + CONTROL1_SW_USB, attached); + if (ret < 0) + goto out; + + /* Dock-Smart */ + extcon_set_cable_state(info->edev, "Dock-Smart", attached); + goto out; + case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: /* Dock-Car */ + strcpy(dock_name, "Dock-Car"); + break; + case MAX77693_MUIC_ADC_AUDIO_MODE_REMOTE: /* Dock-Desk */ + strcpy(dock_name, "Dock-Desk"); + break; + case MAX77693_MUIC_ADC_AV_CABLE_NOLOAD: /* Dock-Audio */ + strcpy(dock_name, "Dock-Audio"); + if (!attached) + extcon_set_cable_state(info->edev, "USB", false); + break; + } + + /* Dock-Car/Desk/Audio, PATH:AUDIO */ + ret = max77693_muic_set_path(info, CONTROL1_SW_AUDIO, attached); + if (ret < 0) + goto out; + extcon_set_cable_state(info->edev, dock_name, attached); + +out: + return ret; +} + +static int max77693_muic_dock_button_handler(struct max77693_muic_info *info, + int button_type, bool attached) +{ + struct input_dev *dock = info->dock; + unsigned int code; + int ret = 0; + + switch (button_type) { + case MAX77693_MUIC_ADC_REMOTE_S3_BUTTON-1 + ... MAX77693_MUIC_ADC_REMOTE_S3_BUTTON+1: + /* DOCK_KEY_PREV */ + code = KEY_PREVIOUSSONG; + break; + case MAX77693_MUIC_ADC_REMOTE_S7_BUTTON-1 + ... MAX77693_MUIC_ADC_REMOTE_S7_BUTTON+1: + /* DOCK_KEY_NEXT */ + code = KEY_NEXTSONG; + break; + case MAX77693_MUIC_ADC_REMOTE_S9_BUTTON: + /* DOCK_VOL_DOWN */ + code = KEY_VOLUMEDOWN; + break; + case MAX77693_MUIC_ADC_REMOTE_S10_BUTTON: + /* DOCK_VOL_UP */ + code = KEY_VOLUMEUP; + break; + case MAX77693_MUIC_ADC_REMOTE_S12_BUTTON-1 + ... MAX77693_MUIC_ADC_REMOTE_S12_BUTTON+1: + /* DOCK_KEY_PLAY_PAUSE */ + code = KEY_PLAYPAUSE; + break; + default: + dev_err(info->dev, + "failed to detect %s key (adc:0x%x)\n", + attached ? "pressed" : "released", button_type); + ret = -EINVAL; + goto out; + } + + input_event(dock, EV_KEY, code, attached); + input_sync(dock); + +out: + return 0; +} + static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info) { int cable_type_gnd; @@ -494,6 +595,7 @@ out: static int max77693_muic_adc_handler(struct max77693_muic_info *info) { int cable_type; + int button_type; bool attached; int ret = 0; @@ -519,31 +621,58 @@ static int max77693_muic_adc_handler(struct max77693_muic_info *info) if (ret < 0) goto out; break; - case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: - case MAX77693_MUIC_ADC_AUDIO_MODE_REMOTE: - /* Audio Video cable with no-load */ - ret = max77693_muic_set_path(info, CONTROL1_SW_AUDIO, attached); + case MAX77693_MUIC_ADC_RESERVED_ACC_3: /* Dock-Smart */ + case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: /* Dock-Car */ + case MAX77693_MUIC_ADC_AUDIO_MODE_REMOTE: /* Dock-Desk */ + case MAX77693_MUIC_ADC_AV_CABLE_NOLOAD: /* Dock-Audio */ + /* + * DOCK device + * + * The MAX77693 MUIC device can detect total 34 cable type + * except of charger cable and MUIC device didn't define + * specfic role of cable in the range of from 0x01 to 0x12 + * of ADC value. So, can use/define cable with no role according + * to schema of hardware board. + */ + ret = max77693_muic_dock_handler(info, cable_type, attached); + if (ret < 0) + goto out; + break; + case MAX77693_MUIC_ADC_REMOTE_S3_BUTTON: /* DOCK_KEY_PREV */ + case MAX77693_MUIC_ADC_REMOTE_S7_BUTTON: /* DOCK_KEY_NEXT */ + case MAX77693_MUIC_ADC_REMOTE_S9_BUTTON: /* DOCK_VOL_DOWN */ + case MAX77693_MUIC_ADC_REMOTE_S10_BUTTON: /* DOCK_VOL_UP */ + case MAX77693_MUIC_ADC_REMOTE_S12_BUTTON: /* DOCK_KEY_PLAY_PAUSE */ + /* + * Button of DOCK device + * - the Prev/Next/Volume Up/Volume Down/Play-Pause button + * + * The MAX77693 MUIC device can detect total 34 cable type + * except of charger cable and MUIC device didn't define + * specfic role of cable in the range of from 0x01 to 0x12 + * of ADC value. So, can use/define cable with no role according + * to schema of hardware board. + */ + if (attached) + button_type = info->prev_button_type = cable_type; + else + button_type = info->prev_button_type; + + ret = max77693_muic_dock_button_handler(info, button_type, + attached); if (ret < 0) goto out; - extcon_set_cable_state(info->edev, - "Audio-video-noload", attached); break; case MAX77693_MUIC_ADC_SEND_END_BUTTON: case MAX77693_MUIC_ADC_REMOTE_S1_BUTTON: case MAX77693_MUIC_ADC_REMOTE_S2_BUTTON: - case MAX77693_MUIC_ADC_REMOTE_S3_BUTTON: case MAX77693_MUIC_ADC_REMOTE_S4_BUTTON: case MAX77693_MUIC_ADC_REMOTE_S5_BUTTON: case MAX77693_MUIC_ADC_REMOTE_S6_BUTTON: - case MAX77693_MUIC_ADC_REMOTE_S7_BUTTON: case MAX77693_MUIC_ADC_REMOTE_S8_BUTTON: - case MAX77693_MUIC_ADC_REMOTE_S9_BUTTON: - case MAX77693_MUIC_ADC_REMOTE_S10_BUTTON: case MAX77693_MUIC_ADC_REMOTE_S11_BUTTON: - case MAX77693_MUIC_ADC_REMOTE_S12_BUTTON: case MAX77693_MUIC_ADC_RESERVED_ACC_1: case MAX77693_MUIC_ADC_RESERVED_ACC_2: - case MAX77693_MUIC_ADC_RESERVED_ACC_3: case MAX77693_MUIC_ADC_RESERVED_ACC_4: case MAX77693_MUIC_ADC_RESERVED_ACC_5: case MAX77693_MUIC_ADC_CEA936_AUDIO: @@ -551,11 +680,12 @@ static int max77693_muic_adc_handler(struct max77693_muic_info *info) case MAX77693_MUIC_ADC_TTY_CONVERTER: case MAX77693_MUIC_ADC_UART_CABLE: case MAX77693_MUIC_ADC_CEA936A_TYPE1_CHG: - case MAX77693_MUIC_ADC_AV_CABLE_NOLOAD: case MAX77693_MUIC_ADC_CEA936A_TYPE2_CHG: - /* This accessory isn't used in general case if it is specially - needed to detect additional accessory, should implement - proper operation when this accessory is attached/detached. */ + /* + * This accessory isn't used in general case if it is specially + * needed to detect additional accessory, should implement + * proper operation when this accessory is attached/detached. + */ dev_info(info->dev, "accessory is %s but it isn't used (adc:0x%x)\n", attached ? "attached" : "detached", cable_type); @@ -576,6 +706,7 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info) { int chg_type; int cable_type_gnd; + int cable_type; bool attached; bool cable_attached; int ret = 0; @@ -590,35 +721,52 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info) switch (chg_type) { case MAX77693_CHARGER_TYPE_USB: + /* + * MHL_TA(USB/TA) with MHL cable + * - MHL cable include two port(HDMI line and separate micro + * -usb port. When the target connect MHL cable, extcon driver + * check whether MHL_TA(USB/TA) cable is connected. If MHL_TA + * cable is connected, extcon driver notify state to notifiee + * for charging battery. + */ cable_type_gnd = max77693_muic_get_cable_type(info, MAX77693_CABLE_GROUP_ADC_GND, &cable_attached); - - switch (cable_type_gnd) { - case MAX77693_MUIC_GND_MHL: - case MAX77693_MUIC_GND_MHL_VB: - /* - * USB/TA with MHL cable - * - MHL cable, which connect micro USB or TA cable, - * is used to charging battery. So, extcon driver check - * charging type whether micro USB or TA cable is - * connected to MHL cable when extcon driver detect MHL - * cable. - */ + if (cable_type_gnd == MAX77693_MUIC_GND_MHL + || cable_type_gnd == MAX77693_MUIC_GND_MHL_VB) { extcon_set_cable_state(info->edev, "MHL_TA", attached); if (!cable_attached) extcon_set_cable_state(info->edev, - "MHL", false); - break; - default: - /* Only USB cable, PATH:AP_USB */ - ret = max77693_muic_set_path(info, CONTROL1_SW_USB, - attached); - if (ret < 0) - goto out; + "MHL", false); + goto out; + } + + /* + * USB/TA cable with Dock-Audio device + * - Dock device include two port(Dock-Audio and micro-usb + * port). When the target connect Dock-Audio device, extcon + * driver check whether USB/TA cable is connected. + * If USB/TA cable is connected, extcon driver notify state + * to notifiee for charging battery. + */ + cable_type = max77693_muic_get_cable_type(info, + MAX77693_CABLE_GROUP_ADC, + &cable_attached); + if (cable_type == MAX77693_MUIC_ADC_AV_CABLE_NOLOAD) { extcon_set_cable_state(info->edev, "USB", attached); + + if (!cable_attached) + extcon_set_cable_state(info->edev, + "Dock-Audio", false); + goto out; } + + /* Only USB cable, PATH:AP_USB */ + ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); + if (ret < 0) + goto out; + extcon_set_cable_state(info->edev, "USB", attached); break; case MAX77693_CHARGER_TYPE_DOWNSTREAM_PORT: extcon_set_cable_state(info->edev, @@ -794,6 +942,32 @@ static int max77693_muic_probe(struct platform_device *pdev) return ret; } } + + /* Register input device for button of dock device */ + info->dock = input_allocate_device(); + if (!info->dock) { + dev_err(&pdev->dev, "%s: failed to allocate input\n", __func__); + return -ENOMEM; + } + info->dock->name = "max77693-muic/dock"; + info->dock->phys = "max77693-muic/extcon"; + info->dock->dev.parent = &pdev->dev; + + __set_bit(EV_REP, info->dock->evbit); + + input_set_capability(info->dock, EV_KEY, KEY_VOLUMEUP); + input_set_capability(info->dock, EV_KEY, KEY_VOLUMEDOWN); + input_set_capability(info->dock, EV_KEY, KEY_PLAYPAUSE); + input_set_capability(info->dock, EV_KEY, KEY_PREVIOUSSONG); + input_set_capability(info->dock, EV_KEY, KEY_NEXTSONG); + + ret = input_register_device(info->dock); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot register input device error(%d)\n", + ret); + return ret; + } + platform_set_drvdata(pdev, info); mutex_init(&info->mutex); @@ -870,7 +1044,7 @@ static int max77693_muic_probe(struct platform_device *pdev) MAX77693_MUIC_REG_ID, &id); if (ret < 0) { dev_err(&pdev->dev, "failed to read revision number\n"); - goto err_irq; + goto err_extcon; } dev_info(info->dev, "device ID : 0x%x\n", id); @@ -882,6 +1056,8 @@ static int max77693_muic_probe(struct platform_device *pdev) return ret; +err_extcon: + extcon_dev_unregister(info->edev); err_irq: while (--i >= 0) free_irq(muic_irqs[i].virq, info); @@ -896,6 +1072,7 @@ static int max77693_muic_remove(struct platform_device *pdev) for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) free_irq(muic_irqs[i].virq, info); cancel_work_sync(&info->irq_work); + input_unregister_device(info->dock); extcon_dev_unregister(info->edev); return 0; -- cgit v1.2.1 From 297620fd1e14edf5fefa1736f873b9228336eee1 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 26 Dec 2012 13:10:11 +0900 Subject: extcon: max77693: Check the state/type of cable after boot completed This patch check the state/type of cable after completing the initialization of platform and notify platform of cable state/type through extcon. If extcon provider driver notify the state/type of cable before completing platform boot, this uevent is unused and ignored. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 37 ++++++++++++++++++++++++++++++++++--- include/linux/mfd/max77693.h | 2 ++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 07ea96bfd0cb..10f41f3d5be4 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -30,6 +30,7 @@ #include #define DEV_NAME "max77693-muic" +#define DELAY_MS_DEFAULT 20000 /* unit: millisecond */ enum max77693_muic_adc_debounce_time { ADC_DEBOUNCE_TIME_5MS = 0, @@ -52,6 +53,14 @@ struct max77693_muic_info { struct work_struct irq_work; struct mutex mutex; + /* + * Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + struct delayed_work wq_detcable; + /* Button of dock device */ struct input_dev *dock; }; @@ -912,13 +921,23 @@ static int max77693_muic_detect_accessory(struct max77693_muic_info *info) return ret; } +static void max77693_muic_detect_cable_wq(struct work_struct *work) +{ + struct max77693_muic_info *info = container_of(to_delayed_work(work), + struct max77693_muic_info, wq_detcable); + + max77693_muic_detect_accessory(info); +} + static int max77693_muic_probe(struct platform_device *pdev) { struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent); struct max77693_platform_data *pdata = dev_get_platdata(max77693->dev); struct max77693_muic_platform_data *muic_pdata = pdata->muic_data; struct max77693_muic_info *info; - int ret, i; + int delay_jiffies; + int ret; + int i; u8 id; info = devm_kzalloc(&pdev->dev, sizeof(struct max77693_muic_info), @@ -1051,8 +1070,20 @@ static int max77693_muic_probe(struct platform_device *pdev) /* Set ADC debounce time */ max77693_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); - /* Detect accessory on boot */ - max77693_muic_detect_accessory(info); + /* + * Detect accessory after completing the initialization of platform + * + * - Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + INIT_DELAYED_WORK(&info->wq_detcable, max77693_muic_detect_cable_wq); + if (muic_pdata->detcable_delay_ms) + delay_jiffies = msecs_to_jiffies(muic_pdata->detcable_delay_ms); + else + delay_jiffies = msecs_to_jiffies(DELAY_MS_DEFAULT); + schedule_delayed_work(&info->wq_detcable, delay_jiffies); return ret; diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h index fe03b2d35d4f..9aa9c203f28b 100644 --- a/include/linux/mfd/max77693.h +++ b/include/linux/mfd/max77693.h @@ -38,6 +38,8 @@ struct max77693_reg_data { struct max77693_muic_platform_data { struct max77693_reg_data *init_data; int num_init_data; + + int detcable_delay_ms; }; struct max77693_platform_data { -- cgit v1.2.1 From ae3b3215f8e16ee8234024c77787bac9befb4f4c Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 28 Nov 2012 12:39:01 +0900 Subject: extcon: max8997/max77693: Support IRQF_NO_SUSPEND flag for interrupt Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 4 ++-- drivers/extcon/extcon-max8997.c | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 10f41f3d5be4..7b7f1a2a0846 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1006,13 +1006,13 @@ static int max77693_muic_probe(struct platform_device *pdev) ret = request_threaded_irq(virq, NULL, max77693_muic_irq_handler, - IRQF_ONESHOT, muic_irq->name, info); + IRQF_NO_SUSPEND, + muic_irq->name, info); if (ret) { dev_err(&pdev->dev, "failed: irq request (IRQ: %d," " error :%d)\n", muic_irq->irq, ret); - goto err_irq; } } diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 93009fe6ef05..df9358e30814 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -459,8 +459,10 @@ static int max8997_muic_probe(struct platform_device *pdev) } muic_irq->virq = virq; - ret = request_threaded_irq(virq, NULL, max8997_muic_irq_handler, - 0, muic_irq->name, info); + ret = request_threaded_irq(virq, NULL, + max8997_muic_irq_handler, + IRQF_NO_SUSPEND, + muic_irq->name, info); if (ret) { dev_err(&pdev->dev, "failed: irq request (IRQ: %d," -- cgit v1.2.1 From 2b75799f5ae9f31ea9778003591fd295d3bc3159 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Thu, 6 Dec 2012 21:27:56 +0900 Subject: extcon: max77693: Set default uart/usb path by using platform data This patch determine default uart/usb path by using platform data. The MAX77693 MUIC device can possibliy set USB/UART/AUDIO/USB_AUX /UART_AUX to internal h/w path of MUIC device. So, drvier should determine default uart/usb path. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 24 ++++++++++++++++++++++++ include/linux/mfd/max77693.h | 7 +++++++ 2 files changed, 31 insertions(+) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 7b7f1a2a0846..abab068adc35 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -63,6 +63,13 @@ struct max77693_muic_info { /* Button of dock device */ struct input_dev *dock; + + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + int path_usb; + int path_uart; }; enum max77693_muic_cable_group { @@ -1058,6 +1065,23 @@ static int max77693_muic_probe(struct platform_device *pdev) = muic_pdata->init_data[i].data; } + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + if (muic_pdata->path_uart) + info->path_uart = muic_pdata->path_uart; + else + info->path_uart = CONTROL1_SW_UART; + + if (muic_pdata->path_usb) + info->path_usb = muic_pdata->path_usb; + else + info->path_usb = CONTROL1_SW_USB; + + /* Set initial path for UART */ + max77693_muic_set_path(info, info->path_uart, true); + /* Check revision number of MUIC device*/ ret = max77693_read_reg(info->max77693->regmap_muic, MAX77693_MUIC_REG_ID, &id); diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h index 9aa9c203f28b..3109a6c5c948 100644 --- a/include/linux/mfd/max77693.h +++ b/include/linux/mfd/max77693.h @@ -40,6 +40,13 @@ struct max77693_muic_platform_data { int num_init_data; int detcable_delay_ms; + + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + int path_usb; + int path_uart; }; struct max77693_platform_data { -- cgit v1.2.1 From 0e2738f59c6db185a70a683059980bd2296571ca Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Thu, 6 Dec 2012 21:36:18 +0900 Subject: extcon: max77693: Fix bug when detecting MHL/Dock-Audio with USB/TA cable This patch fix bug that muic couldn't detect MHL/Dock-Audio with USB/TA cable on exception situation. I explain detail case on following: When MHL(with USB/TA cable) or Dock-Audio with USB/TA cable is attached, the MUIC device happen following two interrupt. - 'MAX77693_MUIC_IRQ_INT1_ADC' for detecting MHL/Dock-Audio. - 'MAX77693_MUIC_IRQ_INT2_CHGTYP' for detecting USB/TA cable connected to MHL/Dock-Audio. Always, happen eariler MAX77693_MUIC_IRQ_INT1_ADC interrupt than MAX77693_MUIC_IRQ_INT2_CHGTYP interrupt. If user attach MHL with USB/TA cable and immediately detach MHL with USB/TA cable before MAX77693_MUIC_IRQ_INT2_CHGTYP interrupt is happened, USB/TA connected to MHL cable remain connected state to target. But USB/TA connected to MHL cable isn't connected to target. user be faced with unusual action. So, driver should check this situation in spite of that, previous charger type is N/A. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index abab068adc35..28eff88fca18 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -737,6 +737,7 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info) switch (chg_type) { case MAX77693_CHARGER_TYPE_USB: + case MAX77693_CHARGER_TYPE_NONE: /* * MHL_TA(USB/TA) with MHL cable * - MHL cable include two port(HDMI line and separate micro @@ -778,6 +779,25 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info) goto out; } + /* + * When MHL(with USB/TA cable) or Dock-Audio with USB/TA cable + * is attached, muic device happen below two interrupt. + * - 'MAX77693_MUIC_IRQ_INT1_ADC' for detecting MHL/Dock-Audio. + * - 'MAX77693_MUIC_IRQ_INT2_CHGTYP' for detecting USB/TA cable + * connected to MHL or Dock-Audio. + * Always, happen eariler MAX77693_MUIC_IRQ_INT1_ADC interrupt + * than MAX77693_MUIC_IRQ_INT2_CHGTYP interrupt. + * + * If user attach MHL (with USB/TA cable and immediately detach + * MHL with USB/TA cable before MAX77693_MUIC_IRQ_INT2_CHGTYP + * interrupt is happened, USB/TA cable remain connected state to + * target. But USB/TA cable isn't connected to target. The user + * be face with unusual action. So, driver should check this + * situation in spite of, that previous charger type is N/A. + */ + if (chg_type == MAX77693_CHARGER_TYPE_NONE) + break; + /* Only USB cable, PATH:AP_USB */ ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); if (ret < 0) -- cgit v1.2.1 From a162629859a03c07b9603ea59a0b7ae24f695689 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 10 Dec 2012 19:07:53 +0900 Subject: extcon: max77693: Add support Dock-Smart device for desktop mode This patch support the detection of Dock-Smart device which include three type of port(HDMI, USB for mouse/keyboard and Micro-USB for USB/TA cable).The Dock-Smart device need always exteranl power supply (USB/TA cable through micro-usb cable). Dock-Smart device support screen output of target to separate monitor and mouse/keyboard for desktop mode. Features of 'Dock-Smart device' - Support HDMI - Support external output feature of audio - Support charging through micro-usb port without data connection if TA cable is connected to target. - Support charging and data connection through micro-usb port if USB cable is connected between target and host device. - Support OTG device (Mouse/Keyboard) Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 200 +++++++++++++++++++++++++++------------ 1 file changed, 138 insertions(+), 62 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 28eff88fca18..fb6527607a11 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -442,6 +442,8 @@ static int max77693_muic_dock_handler(struct max77693_muic_info *info, int cable_type, bool attached) { int ret = 0; + int vbvolt; + bool cable_attached; char dock_name[CABLE_NAME_MAX]; dev_info(info->dev, @@ -450,14 +452,45 @@ static int max77693_muic_dock_handler(struct max77693_muic_info *info, switch (cable_type) { case MAX77693_MUIC_ADC_RESERVED_ACC_3: /* Dock-Smart */ - /* PATH:AP_USB */ - ret = max77693_muic_set_path(info, - CONTROL1_SW_USB, attached); + /* + * Check power cable whether attached or detached state. + * The Dock-Smart device need surely external power supply. + * If power cable(USB/TA) isn't connected to Dock device, + * user can't use Dock-Smart for desktop mode. + */ + vbvolt = max77693_muic_get_cable_type(info, + MAX77693_CABLE_GROUP_VBVOLT, &cable_attached); + if (attached && !vbvolt) { + dev_warn(info->dev, + "Cannot detect external power supply\n"); + return 0; + } + + /* + * Notify Dock-Smart/MHL state. + * - Dock-Smart device include three type of cable which + * are HDMI, USB for mouse/keyboard and micro-usb port + * for USB/TA cable. Dock-Smart device need always exteranl + * power supply(USB/TA cable through micro-usb cable). Dock- + * Smart device support screen output of target to separate + * monitor and mouse/keyboard for desktop mode. + * + * Features of 'USB/TA cable with Dock-Smart device' + * - Support MHL + * - Support external output feature of audio + * - Support charging through micro-usb port without data + * connection if TA cable is connected to target. + * - Support charging and data connection through micro-usb port + * if USB cable is connected between target and host + * device. + * - Support OTG device (Mouse/Keyboard) + */ + ret = max77693_muic_set_path(info, info->path_usb, attached); if (ret < 0) - goto out; + return ret; - /* Dock-Smart */ extcon_set_cable_state(info->edev, "Dock-Smart", attached); + extcon_set_cable_state(info->edev, "MHL", attached); goto out; case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: /* Dock-Car */ strcpy(dock_name, "Dock-Car"); @@ -475,11 +508,11 @@ static int max77693_muic_dock_handler(struct max77693_muic_info *info, /* Dock-Car/Desk/Audio, PATH:AUDIO */ ret = max77693_muic_set_path(info, CONTROL1_SW_AUDIO, attached); if (ret < 0) - goto out; + return ret; extcon_set_cable_state(info->edev, dock_name, attached); out: - return ret; + return 0; } static int max77693_muic_dock_button_handler(struct max77693_muic_info *info, @@ -737,80 +770,125 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info) switch (chg_type) { case MAX77693_CHARGER_TYPE_USB: + case MAX77693_CHARGER_TYPE_DEDICATED_CHG: case MAX77693_CHARGER_TYPE_NONE: - /* - * MHL_TA(USB/TA) with MHL cable - * - MHL cable include two port(HDMI line and separate micro - * -usb port. When the target connect MHL cable, extcon driver - * check whether MHL_TA(USB/TA) cable is connected. If MHL_TA - * cable is connected, extcon driver notify state to notifiee - * for charging battery. - */ + /* Check MAX77693_CABLE_GROUP_ADC_GND type */ cable_type_gnd = max77693_muic_get_cable_type(info, MAX77693_CABLE_GROUP_ADC_GND, &cable_attached); - if (cable_type_gnd == MAX77693_MUIC_GND_MHL - || cable_type_gnd == MAX77693_MUIC_GND_MHL_VB) { + switch (cable_type_gnd) { + case MAX77693_MUIC_GND_MHL: + case MAX77693_MUIC_GND_MHL_VB: + /* + * MHL cable with MHL_TA(USB/TA) cable + * - MHL cable include two port(HDMI line and separate micro- + * usb port. When the target connect MHL cable, extcon driver + * check whether MHL_TA(USB/TA) cable is connected. If MHL_TA + * cable is connected, extcon driver notify state to notifiee + * for charging battery. + * + * Features of 'MHL_TA(USB/TA) with MHL cable' + * - Support MHL + * - Support charging through micro-usb port without data connection + */ extcon_set_cable_state(info->edev, "MHL_TA", attached); - if (!cable_attached) - extcon_set_cable_state(info->edev, - "MHL", false); - goto out; + extcon_set_cable_state(info->edev, "MHL", cable_attached); + break; } - /* - * USB/TA cable with Dock-Audio device - * - Dock device include two port(Dock-Audio and micro-usb - * port). When the target connect Dock-Audio device, extcon - * driver check whether USB/TA cable is connected. - * If USB/TA cable is connected, extcon driver notify state - * to notifiee for charging battery. - */ + /* Check MAX77693_CABLE_GROUP_ADC type */ cable_type = max77693_muic_get_cable_type(info, MAX77693_CABLE_GROUP_ADC, &cable_attached); - if (cable_type == MAX77693_MUIC_ADC_AV_CABLE_NOLOAD) { + switch (cable_type) { + case MAX77693_MUIC_ADC_AV_CABLE_NOLOAD: /* Dock-Audio */ + /* + * Dock-Audio device with USB/TA cable + * - Dock device include two port(Dock-Audio and micro-usb + * port). When the target connect Dock-Audio device, extcon + * driver check whether USB/TA cable is connected. If USB/TA + * cable is connected, extcon driver notify state to notifiee + * for charging battery. + * + * Features of 'USB/TA cable with Dock-Audio device' + * - Support external output feature of audio. + * - Support charging through micro-usb port without data + * connection. + */ extcon_set_cable_state(info->edev, "USB", attached); if (!cable_attached) - extcon_set_cable_state(info->edev, - "Dock-Audio", false); - goto out; + extcon_set_cable_state(info->edev, "Dock-Audio", cable_attached); + break; + case MAX77693_MUIC_ADC_RESERVED_ACC_3: /* Dock-Smart */ + /* + * Dock-Smart device with USB/TA cable + * - Dock-Desk device include three type of cable which + * are HDMI, USB for mouse/keyboard and micro-usb port + * for USB/TA cable. Dock-Smart device need always exteranl + * power supply(USB/TA cable through micro-usb cable). Dock- + * Smart device support screen output of target to separate + * monitor and mouse/keyboard for desktop mode. + * + * Features of 'USB/TA cable with Dock-Smart device' + * - Support MHL + * - Support external output feature of audio + * - Support charging through micro-usb port without data + * connection if TA cable is connected to target. + * - Support charging and data connection through micro-usb port + * if USB cable is connected between target and host + * device. + * - Support OTG device (Mouse/Keyboard) + */ + ret = max77693_muic_set_path(info, info->path_usb, attached); + if (ret < 0) + return ret; + + extcon_set_cable_state(info->edev, "Dock-Smart", attached); + extcon_set_cable_state(info->edev, "MHL", attached); + + break; } - /* - * When MHL(with USB/TA cable) or Dock-Audio with USB/TA cable - * is attached, muic device happen below two interrupt. - * - 'MAX77693_MUIC_IRQ_INT1_ADC' for detecting MHL/Dock-Audio. - * - 'MAX77693_MUIC_IRQ_INT2_CHGTYP' for detecting USB/TA cable - * connected to MHL or Dock-Audio. - * Always, happen eariler MAX77693_MUIC_IRQ_INT1_ADC interrupt - * than MAX77693_MUIC_IRQ_INT2_CHGTYP interrupt. - * - * If user attach MHL (with USB/TA cable and immediately detach - * MHL with USB/TA cable before MAX77693_MUIC_IRQ_INT2_CHGTYP - * interrupt is happened, USB/TA cable remain connected state to - * target. But USB/TA cable isn't connected to target. The user - * be face with unusual action. So, driver should check this - * situation in spite of, that previous charger type is N/A. - */ - if (chg_type == MAX77693_CHARGER_TYPE_NONE) + /* Check MAX77693_CABLE_GROUP_CHG type */ + switch (chg_type) { + case MAX77693_CHARGER_TYPE_NONE: + /* + * When MHL(with USB/TA cable) or Dock-Audio with USB/TA cable + * is attached, muic device happen below two interrupt. + * - 'MAX77693_MUIC_IRQ_INT1_ADC' for detecting MHL/Dock-Audio. + * - 'MAX77693_MUIC_IRQ_INT2_CHGTYP' for detecting USB/TA cable + * connected to MHL or Dock-Audio. + * Always, happen eariler MAX77693_MUIC_IRQ_INT1_ADC interrupt + * than MAX77693_MUIC_IRQ_INT2_CHGTYP interrupt. + * + * If user attach MHL (with USB/TA cable and immediately detach + * MHL with USB/TA cable before MAX77693_MUIC_IRQ_INT2_CHGTYP + * interrupt is happened, USB/TA cable remain connected state to + * target. But USB/TA cable isn't connected to target. The user + * be face with unusual action. So, driver should check this + * situation in spite of, that previous charger type is N/A. + */ break; + case MAX77693_CHARGER_TYPE_USB: + /* Only USB cable, PATH:AP_USB */ + ret = max77693_muic_set_path(info, info->path_usb, attached); + if (ret < 0) + return ret; - /* Only USB cable, PATH:AP_USB */ - ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); - if (ret < 0) - goto out; - extcon_set_cable_state(info->edev, "USB", attached); + extcon_set_cable_state(info->edev, "USB", attached); + break; + case MAX77693_CHARGER_TYPE_DEDICATED_CHG: + /* Only TA cable */ + extcon_set_cable_state(info->edev, "TA", attached); + break; + } break; case MAX77693_CHARGER_TYPE_DOWNSTREAM_PORT: extcon_set_cable_state(info->edev, "Charge-downstream", attached); break; - case MAX77693_CHARGER_TYPE_DEDICATED_CHG: - extcon_set_cable_state(info->edev, "TA", attached); - break; case MAX77693_CHARGER_TYPE_APPLE_500MA: extcon_set_cable_state(info->edev, "Slow-charger", attached); break; @@ -823,12 +901,10 @@ static int max77693_muic_chg_handler(struct max77693_muic_info *info) dev_err(info->dev, "failed to detect %s accessory (chg_type:0x%x)\n", attached ? "attached" : "detached", chg_type); - ret = -EINVAL; - goto out; + return -EINVAL; } -out: - return ret; + return 0; } static void max77693_muic_irq_work(struct work_struct *work) -- cgit v1.2.1 From 3d44ea1cfbf671152d3ac0ede94439550afa7406 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:51:13 +0900 Subject: extcon: arizona: Convert to devm_input_allocate_device() Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 414aed50b1bc..4454d2df3a58 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -418,7 +418,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) arizona_extcon_set_mode(info, 0); - info->input = input_allocate_device(); + info->input = devm_input_allocate_device(&pdev->dev); if (!info->input) { dev_err(arizona->dev, "Can't allocate input dev\n"); ret = -ENOMEM; @@ -510,7 +510,6 @@ err_rise_wake: err_rise: arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info); err_input: - input_free_device(info->input); err_register: pm_runtime_disable(&pdev->dev); extcon_dev_unregister(&info->edev); @@ -533,7 +532,6 @@ static int arizona_extcon_remove(struct platform_device *pdev) regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, ARIZONA_JD1_ENA, 0); arizona_clk32k_disable(arizona); - input_unregister_device(info->input); extcon_dev_unregister(&info->edev); return 0; -- cgit v1.2.1 From db72e6a1629ab79f5599f21ab19f8eb31137befa Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:51:15 +0900 Subject: extcon: arizona: Remove duplicate mic ramp configuration Now this is configured by platform data remove the defualt configuration the driver had. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 4454d2df3a58..7dbefe8fe5c2 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -473,9 +473,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) } regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, - ARIZONA_MICD_BIAS_STARTTIME_MASK | ARIZONA_MICD_RATE_MASK, - 7 << ARIZONA_MICD_BIAS_STARTTIME_SHIFT | 8 << ARIZONA_MICD_RATE_SHIFT); arizona_clk32k_enable(arizona); -- cgit v1.2.1 From cd74f7b3b387cbc6153b15c100982bac119af753 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 27 Nov 2012 16:14:26 +0900 Subject: extcon: arizona: Only set GPIO if it has been requested The micd_pol GPIO is only requested if we've specified one greater than 0 so apply the same test before we set it. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 7dbefe8fe5c2..9e1d104803e7 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -85,8 +85,9 @@ static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode) { struct arizona *arizona = info->arizona; - gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio, - info->micd_modes[mode].gpio); + if (arizona->pdata.micd_pol_gpio > 0) + gpio_set_value_cansleep(arizona->pdata.micd_pol_gpio, + info->micd_modes[mode].gpio); regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, ARIZONA_MICD_BIAS_SRC_MASK, info->micd_modes[mode].bias); -- cgit v1.2.1 From b17e54625cff1b06aec9df505b46c9bcdcd4823d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:24 +0900 Subject: extcon: arizona: Allow configuration of MICBIAS rise time Allow configuration of the rise time for MICBIAS via platform data, the delay required depends on things like the external component selection. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Acked-by: MyungJoo Ham --- drivers/extcon/extcon-arizona.c | 6 ++++++ include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 9 insertions(+) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 9e1d104803e7..635b7078ce5e 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -417,6 +417,12 @@ static int arizona_extcon_probe(struct platform_device *pdev) } } + if (arizona->pdata.micd_bias_start_time) + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_BIAS_STARTTIME_MASK, + arizona->pdata.micd_bias_start_time + << ARIZONA_MICD_BIAS_STARTTIME_SHIFT); + arizona_extcon_set_mode(info, 0); info->input = devm_input_allocate_device(&pdev->dev); diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 8b1d1daaae16..5b0508853290 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -99,6 +99,9 @@ struct arizona_pdata { /** GPIO for mic detection polarity */ int micd_pol_gpio; + /** Mic detect ramp rate */ + int micd_bias_start_time; + /** Headset polarity configurations */ struct arizona_micd_config *micd_configs; int num_micd_configs; -- cgit v1.2.1 From dab63eb25ced7539a51b8f4218f7b6b56d34df22 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:36 +0900 Subject: extcon: arizona: Use microphone clamp function if available Newer Arizona devices include a microphone clamp function which is tied to jack detect. Activate this feature when present in order to ensure best performance of the subsystem. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 19 +++++++++++++++++++ include/linux/mfd/arizona/core.h | 4 +++- include/linux/mfd/arizona/registers.h | 28 ++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 635b7078ce5e..3ef3bee7d1e6 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -45,6 +45,7 @@ struct arizona_extcon_info { int micd_num_modes; bool micd_reva; + bool micd_clamp; bool mic; bool detecting; @@ -375,6 +376,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) info->micd_reva = true; break; default: + info->micd_clamp = true; break; } break; @@ -423,6 +425,19 @@ static int arizona_extcon_probe(struct platform_device *pdev) arizona->pdata.micd_bias_start_time << ARIZONA_MICD_BIAS_STARTTIME_SHIFT); + /* + * If we have a clamp use it. + */ + if (info->micd_clamp) { + regmap_update_bits(arizona->regmap, + ARIZONA_MICD_CLAMP_CONTROL, + ARIZONA_MICD_CLAMP_MODE_MASK, 4); + regmap_update_bits(arizona->regmap, + ARIZONA_JACK_DETECT_DEBOUNCE, + ARIZONA_MICD_CLAMP_DB, + ARIZONA_MICD_CLAMP_DB); + } + arizona_extcon_set_mode(info, 0); info->input = devm_input_allocate_device(&pdev->dev); @@ -529,6 +544,10 @@ static int arizona_extcon_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); + regmap_update_bits(arizona->regmap, + ARIZONA_MICD_CLAMP_CONTROL, + ARIZONA_MICD_CLAMP_MODE_MASK, 0); + arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0); arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0); arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); diff --git a/include/linux/mfd/arizona/core.h b/include/linux/mfd/arizona/core.h index a580363a7d29..a710255528d7 100644 --- a/include/linux/mfd/arizona/core.h +++ b/include/linux/mfd/arizona/core.h @@ -75,8 +75,10 @@ enum arizona_type { #define ARIZONA_IRQ_DCS_HP_DONE 47 #define ARIZONA_IRQ_FLL2_CLOCK_OK 48 #define ARIZONA_IRQ_FLL1_CLOCK_OK 49 +#define ARIZONA_IRQ_MICD_CLAMP_RISE 50 +#define ARIZONA_IRQ_MICD_CLAMP_FALL 51 -#define ARIZONA_NUM_IRQ 50 +#define ARIZONA_NUM_IRQ 52 struct snd_soc_dapm_context; diff --git a/include/linux/mfd/arizona/registers.h b/include/linux/mfd/arizona/registers.h index 1f6fe31a4d5c..f3211f06cec6 100644 --- a/include/linux/mfd/arizona/registers.h +++ b/include/linux/mfd/arizona/registers.h @@ -119,6 +119,7 @@ #define ARIZONA_ACCESSORY_DETECT_MODE_1 0x293 #define ARIZONA_HEADPHONE_DETECT_1 0x29B #define ARIZONA_HEADPHONE_DETECT_2 0x29C +#define ARIZONA_MICD_CLAMP_CONTROL 0x2A2 #define ARIZONA_MIC_DETECT_1 0x2A3 #define ARIZONA_MIC_DETECT_2 0x2A4 #define ARIZONA_MIC_DETECT_3 0x2A5 @@ -2069,6 +2070,13 @@ #define ARIZONA_HP_LVL_SHIFT 0 /* HP_LVL - [6:0] */ #define ARIZONA_HP_LVL_WIDTH 7 /* HP_LVL - [6:0] */ +/* + * R674 (0x2A2) - MICD clamp control + */ +#define ARIZONA_MICD_CLAMP_MODE_MASK 0x000F /* MICD_CLAMP_MODE - [3:0] */ +#define ARIZONA_MICD_CLAMP_MODE_SHIFT 0 /* MICD_CLAMP_MODE - [3:0] */ +#define ARIZONA_MICD_CLAMP_MODE_WIDTH 4 /* MICD_CLAMP_MODE - [3:0] */ + /* * R675 (0x2A3) - Mic Detect 1 */ @@ -5267,6 +5275,12 @@ /* * R3409 (0xD51) - AOD IRQ1 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT1 0x0080 /* MICD_CLAMP_FALL_EINT1 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT1_MASK 0x0080 /* MICD_CLAMP_FALL_EINT1 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT1_SHIFT 7 /* MICD_CLAMP_FALL_EINT1 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT1 0x0040 /* MICD_CLAMP_RISE_EINT1 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT1_MASK 0x0040 /* MICD_CLAMP_RISE_EINT1 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT1_SHIFT 6 /* MICD_CLAMP_RISE_EINT1 */ #define ARIZONA_GP5_FALL_EINT1 0x0020 /* GP5_FALL_EINT1 */ #define ARIZONA_GP5_FALL_EINT1_MASK 0x0020 /* GP5_FALL_EINT1 */ #define ARIZONA_GP5_FALL_EINT1_SHIFT 5 /* GP5_FALL_EINT1 */ @@ -5295,6 +5309,12 @@ /* * R3410 (0xD52) - AOD IRQ2 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT2 0x0080 /* MICD_CLAMP_FALL_EINT2 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT2_MASK 0x0080 /* MICD_CLAMP_FALL_EINT2 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT2_SHIFT 7 /* MICD_CLAMP_FALL_EINT2 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT2 0x0040 /* MICD_CLAMP_RISE_EINT2 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT2_MASK 0x0040 /* MICD_CLAMP_RISE_EINT2 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT2_SHIFT 6 /* MICD_CLAMP_RISE_EINT2 */ #define ARIZONA_GP5_FALL_EINT2 0x0020 /* GP5_FALL_EINT2 */ #define ARIZONA_GP5_FALL_EINT2_MASK 0x0020 /* GP5_FALL_EINT2 */ #define ARIZONA_GP5_FALL_EINT2_SHIFT 5 /* GP5_FALL_EINT2 */ @@ -5379,6 +5399,10 @@ /* * R3413 (0xD55) - AOD IRQ Raw Status */ +#define ARIZONA_MICD_CLAMP_STS 0x0008 /* MICD_CLAMP_STS */ +#define ARIZONA_MICD_CLAMP_STS_MASK 0x0008 /* MICD_CLAMP_STS */ +#define ARIZONA_MICD_CLAMP_STS_SHIFT 3 /* MICD_CLAMP_STS */ +#define ARIZONA_MICD_CLAMP_STS_WIDTH 1 /* MICD_CLAMP_STS */ #define ARIZONA_GP5_STS 0x0004 /* GP5_STS */ #define ARIZONA_GP5_STS_MASK 0x0004 /* GP5_STS */ #define ARIZONA_GP5_STS_SHIFT 2 /* GP5_STS */ @@ -5395,6 +5419,10 @@ /* * R3414 (0xD56) - Jack detect debounce */ +#define ARIZONA_MICD_CLAMP_DB 0x0008 /* MICD_CLAMP_DB */ +#define ARIZONA_MICD_CLAMP_DB_MASK 0x0008 /* MICD_CLAMP_DB */ +#define ARIZONA_MICD_CLAMP_DB_SHIFT 3 /* MICD_CLAMP_DB */ +#define ARIZONA_MICD_CLAMP_DB_WIDTH 1 /* MICD_CLAMP_DB */ #define ARIZONA_JD2_DB 0x0002 /* JD2_DB */ #define ARIZONA_JD2_DB_MASK 0x0002 /* JD2_DB */ #define ARIZONA_JD2_DB_SHIFT 1 /* JD2_DB */ -- cgit v1.2.1 From 92a49871b378b1e523a95da569cd38efdd06eee5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:39 +0900 Subject: extcon: arizona: Support use of GPIO5 as an input to jack detection Some system designs provide an input on GPIO5 which in conjunction with the jack detection feature indicates the presence of an accessory. Support such systems, using the microphone clamp feature to minimise wakeups of the processor. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 76 +++++++++++++++++++++++++++++---------- include/linux/mfd/arizona/pdata.h | 3 ++ 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 3ef3bee7d1e6..b5190a811601 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -290,13 +290,21 @@ static irqreturn_t arizona_jackdet(int irq, void *data) { struct arizona_extcon_info *info = data; struct arizona *arizona = info->arizona; - unsigned int val; + unsigned int val, present, mask; int ret, i; pm_runtime_get_sync(info->dev); mutex_lock(&info->lock); + if (arizona->pdata.jd_gpio5) { + mask = ARIZONA_MICD_CLAMP_STS; + present = 0; + } else { + mask = ARIZONA_JD1_STS; + present = ARIZONA_JD1_STS; + } + ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val); if (ret != 0) { dev_err(arizona->dev, "Failed to read jackdet status: %d\n", @@ -306,7 +314,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) return IRQ_NONE; } - if (val & ARIZONA_JD1_STS) { + if ((val & mask) == present) { dev_dbg(arizona->dev, "Detected jack\n"); ret = extcon_set_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL, true); @@ -321,6 +329,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) arizona_stop_mic(info); + for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) input_report_key(info->input, arizona_lvl_to_key[i].report, 0); @@ -345,6 +354,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); struct arizona_pdata *pdata; struct arizona_extcon_info *info; + int jack_irq_fall, jack_irq_rise; int ret, mode, i; pdata = dev_get_platdata(arizona->dev); @@ -426,12 +436,24 @@ static int arizona_extcon_probe(struct platform_device *pdev) << ARIZONA_MICD_BIAS_STARTTIME_SHIFT); /* - * If we have a clamp use it. + * If we have a clamp use it, activating in conjunction with + * GPIO5 if that is connected for jack detect operation. */ if (info->micd_clamp) { - regmap_update_bits(arizona->regmap, - ARIZONA_MICD_CLAMP_CONTROL, - ARIZONA_MICD_CLAMP_MODE_MASK, 4); + if (arizona->pdata.jd_gpio5) { + /* Put the GPIO into input mode */ + regmap_write(arizona->regmap, ARIZONA_GPIO5_CTRL, + 0xc101); + + regmap_update_bits(arizona->regmap, + ARIZONA_MICD_CLAMP_CONTROL, + ARIZONA_MICD_CLAMP_MODE_MASK, 0x9); + } else { + regmap_update_bits(arizona->regmap, + ARIZONA_MICD_CLAMP_CONTROL, + ARIZONA_MICD_CLAMP_MODE_MASK, 0x4); + } + regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE, ARIZONA_MICD_CLAMP_DB, @@ -458,7 +480,15 @@ static int arizona_extcon_probe(struct platform_device *pdev) pm_runtime_idle(&pdev->dev); pm_runtime_get_sync(&pdev->dev); - ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_RISE, + if (arizona->pdata.jd_gpio5) { + jack_irq_rise = ARIZONA_IRQ_MICD_CLAMP_RISE; + jack_irq_fall = ARIZONA_IRQ_MICD_CLAMP_FALL; + } else { + jack_irq_rise = ARIZONA_IRQ_JD_RISE; + jack_irq_fall = ARIZONA_IRQ_JD_FALL; + } + + ret = arizona_request_irq(arizona, jack_irq_rise, "JACKDET rise", arizona_jackdet, info); if (ret != 0) { dev_err(&pdev->dev, "Failed to get JACKDET rise IRQ: %d\n", @@ -466,21 +496,21 @@ static int arizona_extcon_probe(struct platform_device *pdev) goto err_input; } - ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 1); + ret = arizona_set_irq_wake(arizona, jack_irq_rise, 1); if (ret != 0) { dev_err(&pdev->dev, "Failed to set JD rise IRQ wake: %d\n", ret); goto err_rise; } - ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_FALL, + ret = arizona_request_irq(arizona, jack_irq_fall, "JACKDET fall", arizona_jackdet, info); if (ret != 0) { dev_err(&pdev->dev, "Failed to get JD fall IRQ: %d\n", ret); goto err_rise_wake; } - ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 1); + ret = arizona_set_irq_wake(arizona, jack_irq_fall, 1); if (ret != 0) { dev_err(&pdev->dev, "Failed to set JD fall IRQ wake: %d\n", ret); @@ -522,13 +552,13 @@ static int arizona_extcon_probe(struct platform_device *pdev) err_micdet: arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); err_fall_wake: - arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0); + arizona_set_irq_wake(arizona, jack_irq_fall, 0); err_fall: - arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info); + arizona_free_irq(arizona, jack_irq_fall, info); err_rise_wake: - arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0); + arizona_set_irq_wake(arizona, jack_irq_rise, 0); err_rise: - arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info); + arizona_free_irq(arizona, jack_irq_rise, info); err_input: err_register: pm_runtime_disable(&pdev->dev); @@ -541,6 +571,7 @@ static int arizona_extcon_remove(struct platform_device *pdev) { struct arizona_extcon_info *info = platform_get_drvdata(pdev); struct arizona *arizona = info->arizona; + int jack_irq_rise, jack_irq_fall; pm_runtime_disable(&pdev->dev); @@ -548,11 +579,20 @@ static int arizona_extcon_remove(struct platform_device *pdev) ARIZONA_MICD_CLAMP_CONTROL, ARIZONA_MICD_CLAMP_MODE_MASK, 0); - arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0); - arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0); + if (arizona->pdata.jd_gpio5) { + jack_irq_rise = ARIZONA_IRQ_MICD_CLAMP_RISE; + jack_irq_fall = ARIZONA_IRQ_MICD_CLAMP_FALL; + } else { + jack_irq_rise = ARIZONA_IRQ_JD_RISE; + jack_irq_fall = ARIZONA_IRQ_JD_FALL; + } + + arizona_set_irq_wake(arizona, jack_irq_rise, 0); + arizona_set_irq_wake(arizona, jack_irq_fall, 0); + arizona_free_irq(arizona, ARIZONA_IRQ_HPDET, info); arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); - arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info); - arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info); + arizona_free_irq(arizona, jack_irq_rise, info); + arizona_free_irq(arizona, jack_irq_fall, info); regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, ARIZONA_JD1_ENA, 0); arizona_clk32k_disable(arizona); diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 5b0508853290..0fc26a480be3 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -96,6 +96,9 @@ struct arizona_pdata { /** Pin state for GPIO pins */ int gpio_defaults[ARIZONA_MAX_GPIO]; + /** GPIO5 is used for jack detection */ + bool jd_gpio5; + /** GPIO for mic detection polarity */ int micd_pol_gpio; -- cgit v1.2.1 From 4f340333822de79b3439bddccdbf3846f9794e42 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:43 +0900 Subject: extcon: arizona: Enable basic headphone identification Use the headphone detection to identify if the accessory is a headphone or line load. There are two different revisions of the IP with different register layouts, support both. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 341 +++++++++++++++++++++++++++++++--- drivers/mfd/wm5102-tables.c | 3 + include/linux/mfd/arizona/registers.h | 12 ++ 3 files changed, 335 insertions(+), 21 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index b5190a811601..cc258a8842ef 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -31,8 +31,14 @@ #include #include +#define ARIZONA_DEFAULT_HP 32 + #define ARIZONA_NUM_BUTTONS 6 +#define ARIZONA_ACCDET_MODE_MIC 0 +#define ARIZONA_ACCDET_MODE_HPL 1 +#define ARIZONA_ACCDET_MODE_HPR 2 + struct arizona_extcon_info { struct device *dev; struct arizona *arizona; @@ -47,10 +53,14 @@ struct arizona_extcon_info { bool micd_reva; bool micd_clamp; + bool hpdet_active; + bool mic; bool detecting; int jack_flips; + int hpdet_ip; + struct extcon_dev edev; }; @@ -74,11 +84,13 @@ static struct { #define ARIZONA_CABLE_MECHANICAL 0 #define ARIZONA_CABLE_MICROPHONE 1 #define ARIZONA_CABLE_HEADPHONE 2 +#define ARIZONA_CABLE_LINEOUT 3 static const char *arizona_cable[] = { "Mechanical", "Microphone", "Headphone", + "Line-out", NULL, }; @@ -100,6 +112,290 @@ static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode) dev_dbg(arizona->dev, "Set jack polarity to %d\n", mode); } +static struct { + unsigned int factor_a; + unsigned int factor_b; +} arizona_hpdet_b_ranges[] = { + { 5528, 362464 }, + { 11084, 6186851 }, + { 11065, 65460395 }, +}; + +static struct { + int min; + int max; +} arizona_hpdet_c_ranges[] = { + { 0, 30 }, + { 8, 100 }, + { 100, 1000 }, + { 1000, 10000 }, +}; + +static int arizona_hpdet_read(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + unsigned int val, range; + int ret; + + ret = regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_2, &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read HPDET status: %d\n", + ret); + return ret; + } + + switch (info->hpdet_ip) { + case 0: + if (!(val & ARIZONA_HP_DONE)) { + dev_err(arizona->dev, "HPDET did not complete: %x\n", + val); + val = ARIZONA_DEFAULT_HP; + } + + val &= ARIZONA_HP_LVL_MASK; + break; + + case 1: + if (!(val & ARIZONA_HP_DONE_B)) { + dev_err(arizona->dev, "HPDET did not complete: %x\n", + val); + return ARIZONA_DEFAULT_HP; + } + + ret = regmap_read(arizona->regmap, ARIZONA_HP_DACVAL, &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read HP value: %d\n", + ret); + return ARIZONA_DEFAULT_HP; + } + + regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, + &range); + range = (range & ARIZONA_HP_IMPEDANCE_RANGE_MASK) + >> ARIZONA_HP_IMPEDANCE_RANGE_SHIFT; + + if (range < ARRAY_SIZE(arizona_hpdet_b_ranges) - 1 && + (val < 100 || val > 0x3fb)) { + range++; + dev_dbg(arizona->dev, "Moving to HPDET range %d\n", + range); + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_IMPEDANCE_RANGE_MASK, + range << + ARIZONA_HP_IMPEDANCE_RANGE_SHIFT); + return -EAGAIN; + } + + /* If we go out of range report top of range */ + if (val < 100 || val > 0x3fb) { + dev_dbg(arizona->dev, "Measurement out of range\n"); + return 10000; + } + + dev_dbg(arizona->dev, "HPDET read %d in range %d\n", + val, range); + + val = arizona_hpdet_b_ranges[range].factor_b + / ((val * 100) - + arizona_hpdet_b_ranges[range].factor_a); + break; + + default: + dev_warn(arizona->dev, "Unknown HPDET IP revision %d\n", + info->hpdet_ip); + case 2: + if (!(val & ARIZONA_HP_DONE_B)) { + dev_err(arizona->dev, "HPDET did not complete: %x\n", + val); + return ARIZONA_DEFAULT_HP; + } + + val &= ARIZONA_HP_LVL_B_MASK; + + regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, + &range); + range = (range & ARIZONA_HP_IMPEDANCE_RANGE_MASK) + >> ARIZONA_HP_IMPEDANCE_RANGE_SHIFT; + + /* Skip up or down a range? */ + if (range && (val < arizona_hpdet_c_ranges[range].min)) { + range--; + dev_dbg(arizona->dev, "Moving to HPDET range %d-%d\n", + arizona_hpdet_c_ranges[range].min, + arizona_hpdet_c_ranges[range].max); + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_IMPEDANCE_RANGE_MASK, + range << + ARIZONA_HP_IMPEDANCE_RANGE_SHIFT); + return -EAGAIN; + } + + if (range < ARRAY_SIZE(arizona_hpdet_c_ranges) - 1 && + (val >= arizona_hpdet_c_ranges[range].max)) { + range++; + dev_dbg(arizona->dev, "Moving to HPDET range %d-%d\n", + arizona_hpdet_c_ranges[range].min, + arizona_hpdet_c_ranges[range].max); + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_IMPEDANCE_RANGE_MASK, + range << + ARIZONA_HP_IMPEDANCE_RANGE_SHIFT); + return -EAGAIN; + } + } + + dev_dbg(arizona->dev, "HP impedance %d ohms\n", val); + return val; +} + +static irqreturn_t arizona_hpdet_irq(int irq, void *data) +{ + struct arizona_extcon_info *info = data; + struct arizona *arizona = info->arizona; + int report = ARIZONA_CABLE_HEADPHONE; + int ret; + + mutex_lock(&info->lock); + + /* If we got a spurious IRQ for some reason then ignore it */ + if (!info->hpdet_active) { + dev_warn(arizona->dev, "Spurious HPDET IRQ\n"); + mutex_unlock(&info->lock); + return IRQ_NONE; + } + + /* If the cable was removed while measuring ignore the result */ + ret = extcon_get_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL); + if (ret < 0) { + dev_err(arizona->dev, "Failed to check cable state: %d\n", + ret); + goto out; + } else if (!ret) { + dev_dbg(arizona->dev, "Ignoring HPDET for removed cable\n"); + goto done; + } + + ret = arizona_hpdet_read(info); + if (ret == -EAGAIN) { + goto out; + } else if (ret < 0) { + goto done; + } + + /* Reset back to starting range */ + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_IMPEDANCE_RANGE_MASK, 0); + + /* Report high impedence cables as line outputs */ + if (ret >= 5000) + report = ARIZONA_CABLE_LINEOUT; + else + report = ARIZONA_CABLE_HEADPHONE; + + ret = extcon_set_cable_state_(&info->edev, report, true); + if (ret != 0) + dev_err(arizona->dev, "Failed to report HP/line: %d\n", + ret); + + ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0); + if (ret != 0) + dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); + + ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0); + if (ret != 0) + dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); + +done: + regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, 0); + + /* Revert back to MICDET mode */ + regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); + + /* If we have a mic then reenable MICDET */ + if (info->mic) + arizona_start_mic(info); + + if (info->hpdet_active) { + pm_runtime_put_autosuspend(info->dev); + info->hpdet_active = false; + } + +out: + mutex_unlock(&info->lock); + + return IRQ_HANDLED; +} + +static void arizona_identify_headphone(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + int ret; + + dev_dbg(arizona->dev, "Starting HPDET\n"); + + /* Make sure we keep the device enabled during the measurement */ + pm_runtime_get(info->dev); + + info->hpdet_active = true; + + if (info->mic) + arizona_stop_mic(info); + + ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0x4000); + if (ret != 0) + dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); + + ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0x4000); + if (ret != 0) + dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); + + ret = regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK, + ARIZONA_ACCDET_MODE_HPL); + if (ret != 0) { + dev_err(arizona->dev, "Failed to set HPDETL mode: %d\n", ret); + goto err; + } + + ret = regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, ARIZONA_HP_POLL); + if (ret != 0) { + dev_err(arizona->dev, "Can't start HPDETL measurement: %d\n", + ret); + goto err; + } + + return; + +err: + regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); + + /* Just report headphone */ + ret = extcon_update_state(&info->edev, + 1 << ARIZONA_CABLE_HEADPHONE, + 1 << ARIZONA_CABLE_HEADPHONE); + if (ret != 0) + dev_err(arizona->dev, "Failed to report headphone: %d\n", ret); + + if (info->mic) + arizona_start_mic(info); + + info->hpdet_active = false; +} + } + + info->hpdet_active = false; +} + static void arizona_start_mic(struct arizona_extcon_info *info) { struct arizona *arizona = info->arizona; @@ -125,6 +421,10 @@ static void arizona_start_mic(struct arizona_extcon_info *info) regmap_write(arizona->regmap, 0x80, 0x0); } + regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); + regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, ARIZONA_MICD_ENA, ARIZONA_MICD_ENA, &change); @@ -189,11 +489,11 @@ static irqreturn_t arizona_micdet(int irq, void *data) /* If we got a high impedence we should have a headset, report it. */ if (info->detecting && (val & 0x400)) { + arizona_identify_headphone(info); + ret = extcon_update_state(&info->edev, - 1 << ARIZONA_CABLE_MICROPHONE | - 1 << ARIZONA_CABLE_HEADPHONE, - 1 << ARIZONA_CABLE_MICROPHONE | - 1 << ARIZONA_CABLE_HEADPHONE); + 1 << ARIZONA_CABLE_MICROPHONE, + 1 << ARIZONA_CABLE_MICROPHONE); if (ret != 0) dev_err(arizona->dev, "Headset report failed: %d\n", @@ -214,17 +514,12 @@ static irqreturn_t arizona_micdet(int irq, void *data) info->jack_flips++; if (info->jack_flips >= info->micd_num_modes) { - dev_dbg(arizona->dev, "Detected headphone\n"); + dev_dbg(arizona->dev, "Detected HP/line\n"); + arizona_identify_headphone(info); + info->detecting = false; - arizona_stop_mic(info); - ret = extcon_set_cable_state_(&info->edev, - ARIZONA_CABLE_HEADPHONE, - true); - if (ret != 0) - dev_err(arizona->dev, - "Headphone report failed: %d\n", - ret); + arizona_stop_mic(info); } else { info->micd_mode++; if (info->micd_mode == info->micd_num_modes) @@ -260,13 +555,7 @@ static irqreturn_t arizona_micdet(int irq, void *data) info->detecting = false; arizona_stop_mic(info); - ret = extcon_set_cable_state_(&info->edev, - ARIZONA_CABLE_HEADPHONE, - true); - if (ret != 0) - dev_err(arizona->dev, - "Headphone report failed: %d\n", - ret); + arizona_identify_headphone(info); } else { dev_warn(arizona->dev, "Button with no mic: %x\n", val); @@ -387,6 +676,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) break; default: info->micd_clamp = true; + info->hpdet_ip = 1; break; } break; @@ -524,6 +814,13 @@ static int arizona_extcon_probe(struct platform_device *pdev) goto err_fall_wake; } + ret = arizona_request_irq(arizona, ARIZONA_IRQ_HPDET, + "HPDET", arizona_hpdet_irq, info); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to get HPDET IRQ: %d\n", ret); + goto err_micdet; + } + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, ARIZONA_MICD_RATE_MASK, 8 << ARIZONA_MICD_RATE_SHIFT); @@ -544,11 +841,13 @@ static int arizona_extcon_probe(struct platform_device *pdev) ret = input_register_device(info->input); if (ret) { dev_err(&pdev->dev, "Can't register input device: %d\n", ret); - goto err_micdet; + goto err_hpdet; } return 0; +err_hpdet: + arizona_free_irq(arizona, ARIZONA_IRQ_HPDET, info); err_micdet: arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); err_fall_wake: diff --git a/drivers/mfd/wm5102-tables.c b/drivers/mfd/wm5102-tables.c index 088872ab6338..311cc3657b0b 100644 --- a/drivers/mfd/wm5102-tables.c +++ b/drivers/mfd/wm5102-tables.c @@ -1106,6 +1106,8 @@ static bool wm5102_readable_register(struct device *dev, unsigned int reg) case ARIZONA_ACCESSORY_DETECT_MODE_1: case ARIZONA_HEADPHONE_DETECT_1: case ARIZONA_HEADPHONE_DETECT_2: + case ARIZONA_HP_DACVAL: + case ARIZONA_MICD_CLAMP_CONTROL: case ARIZONA_MIC_DETECT_1: case ARIZONA_MIC_DETECT_2: case ARIZONA_MIC_DETECT_3: @@ -1875,6 +1877,7 @@ static bool wm5102_volatile_register(struct device *dev, unsigned int reg) case ARIZONA_DSP1_STATUS_2: case ARIZONA_DSP1_STATUS_3: case ARIZONA_HEADPHONE_DETECT_2: + case ARIZONA_HP_DACVAL: case ARIZONA_MIC_DETECT_3: return true; default: diff --git a/include/linux/mfd/arizona/registers.h b/include/linux/mfd/arizona/registers.h index f3211f06cec6..e9150533e701 100644 --- a/include/linux/mfd/arizona/registers.h +++ b/include/linux/mfd/arizona/registers.h @@ -119,6 +119,7 @@ #define ARIZONA_ACCESSORY_DETECT_MODE_1 0x293 #define ARIZONA_HEADPHONE_DETECT_1 0x29B #define ARIZONA_HEADPHONE_DETECT_2 0x29C +#define ARIZONA_HP_DACVAL 0x29F #define ARIZONA_MICD_CLAMP_CONTROL 0x2A2 #define ARIZONA_MIC_DETECT_1 0x2A3 #define ARIZONA_MIC_DETECT_2 0x2A4 @@ -2036,6 +2037,9 @@ /* * R667 (0x29B) - Headphone Detect 1 */ +#define ARIZONA_HP_IMPEDANCE_RANGE_MASK 0x0600 /* HP_IMPEDANCE_RANGE - [10:9] */ +#define ARIZONA_HP_IMPEDANCE_RANGE_SHIFT 9 /* HP_IMPEDANCE_RANGE - [10:9] */ +#define ARIZONA_HP_IMPEDANCE_RANGE_WIDTH 2 /* HP_IMPEDANCE_RANGE - [10:9] */ #define ARIZONA_HP_STEP_SIZE 0x0100 /* HP_STEP_SIZE */ #define ARIZONA_HP_STEP_SIZE_MASK 0x0100 /* HP_STEP_SIZE */ #define ARIZONA_HP_STEP_SIZE_SHIFT 8 /* HP_STEP_SIZE */ @@ -2070,6 +2074,14 @@ #define ARIZONA_HP_LVL_SHIFT 0 /* HP_LVL - [6:0] */ #define ARIZONA_HP_LVL_WIDTH 7 /* HP_LVL - [6:0] */ +#define ARIZONA_HP_DONE_B 0x8000 /* HP_DONE */ +#define ARIZONA_HP_DONE_B_MASK 0x8000 /* HP_DONE */ +#define ARIZONA_HP_DONE_B_SHIFT 15 /* HP_DONE */ +#define ARIZONA_HP_DONE_B_WIDTH 1 /* HP_DONE */ +#define ARIZONA_HP_LVL_B_MASK 0x7FFF /* HP_LVL - [14:0] */ +#define ARIZONA_HP_LVL_B_SHIFT 0 /* HP_LVL - [14:0] */ +#define ARIZONA_HP_LVL_B_WIDTH 15 /* HP_LVL - [14:0] */ + /* * R674 (0x2A2) - MICD clamp control */ -- cgit v1.2.1 From 9b1270c71fb5f4783c72cae7e458b2cf8c657f84 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:46 +0900 Subject: extcon: Simple code motion supporting future work. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 120 ++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index cc258a8842ef..dab4584a4ad5 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -112,6 +112,66 @@ static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode) dev_dbg(arizona->dev, "Set jack polarity to %d\n", mode); } +static void arizona_start_mic(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + bool change; + int ret; + + info->detecting = true; + info->mic = false; + info->jack_flips = 0; + + /* Microphone detection can't use idle mode */ + pm_runtime_get(info->dev); + + ret = regulator_enable(info->micvdd); + if (ret != 0) { + dev_err(arizona->dev, "Failed to enable MICVDD: %d\n", + ret); + } + + if (info->micd_reva) { + regmap_write(arizona->regmap, 0x80, 0x3); + regmap_write(arizona->regmap, 0x294, 0); + regmap_write(arizona->regmap, 0x80, 0x0); + } + + regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); + + regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_ENA, ARIZONA_MICD_ENA, + &change); + if (!change) { + regulator_disable(info->micvdd); + pm_runtime_put_autosuspend(info->dev); + } +} + +static void arizona_stop_mic(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + bool change; + + regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_ENA, 0, + &change); + + if (info->micd_reva) { + regmap_write(arizona->regmap, 0x80, 0x3); + regmap_write(arizona->regmap, 0x294, 2); + regmap_write(arizona->regmap, 0x80, 0x0); + } + + if (change) { + regulator_disable(info->micvdd); + pm_runtime_mark_last_busy(info->dev); + pm_runtime_put_autosuspend(info->dev); + } +} + static struct { unsigned int factor_a; unsigned int factor_b; @@ -396,66 +456,6 @@ err: info->hpdet_active = false; } -static void arizona_start_mic(struct arizona_extcon_info *info) -{ - struct arizona *arizona = info->arizona; - bool change; - int ret; - - info->detecting = true; - info->mic = false; - info->jack_flips = 0; - - /* Microphone detection can't use idle mode */ - pm_runtime_get(info->dev); - - ret = regulator_enable(info->micvdd); - if (ret != 0) { - dev_err(arizona->dev, "Failed to enable MICVDD: %d\n", - ret); - } - - if (info->micd_reva) { - regmap_write(arizona->regmap, 0x80, 0x3); - regmap_write(arizona->regmap, 0x294, 0); - regmap_write(arizona->regmap, 0x80, 0x0); - } - - regmap_update_bits(arizona->regmap, - ARIZONA_ACCESSORY_DETECT_MODE_1, - ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); - - regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, - ARIZONA_MICD_ENA, ARIZONA_MICD_ENA, - &change); - if (!change) { - regulator_disable(info->micvdd); - pm_runtime_put_autosuspend(info->dev); - } -} - -static void arizona_stop_mic(struct arizona_extcon_info *info) -{ - struct arizona *arizona = info->arizona; - bool change; - - regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, - ARIZONA_MICD_ENA, 0, - &change); - - if (info->micd_reva) { - regmap_write(arizona->regmap, 0x80, 0x3); - regmap_write(arizona->regmap, 0x294, 2); - regmap_write(arizona->regmap, 0x80, 0x0); - } - - if (change) { - regulator_disable(info->micvdd); - pm_runtime_mark_last_busy(info->dev); - pm_runtime_put_autosuspend(info->dev); - } -} - static irqreturn_t arizona_micdet(int irq, void *data) { struct arizona_extcon_info *info = data; -- cgit v1.2.1 From dd235eea4ed75b1599dd9a53bb618fe5befeb731 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:51 +0900 Subject: extcon: arizona: Support HPDET based accessory identification The accessory detection functionality in Arizona devices is flexible and supports several system designs in addition to the default one implemented by the existing driver. One such design uses the HPDET feature to determine what kind of accessory is present by comparing measurements taken with the two headphone grounds available on the device, implement that if selected by platform data. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 159 +++++++++++++++++++++++++++++++++++--- include/linux/mfd/arizona/pdata.h | 3 + 2 files changed, 150 insertions(+), 12 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index dab4584a4ad5..ba9525ee4706 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -55,6 +55,9 @@ struct arizona_extcon_info { bool hpdet_active; + int num_hpdet_res; + unsigned int hpdet_res[2]; + bool mic; bool detecting; int jack_flips; @@ -65,8 +68,8 @@ struct arizona_extcon_info { }; static const struct arizona_micd_config micd_default_modes[] = { - { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 }, { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 }, + { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 }, }; static struct { @@ -118,10 +121,6 @@ static void arizona_start_mic(struct arizona_extcon_info *info) bool change; int ret; - info->detecting = true; - info->mic = false; - info->jack_flips = 0; - /* Microphone detection can't use idle mode */ pm_runtime_get(info->dev); @@ -311,12 +310,80 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) return val; } +static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) +{ + struct arizona *arizona = info->arizona; + int ret; + + /* + * If we're using HPDET for accessory identification we need + * to take multiple measurements, step through them in sequence. + */ + if (arizona->pdata.hpdet_acc_id) { + info->hpdet_res[info->num_hpdet_res++] = *reading; + + /* + * If the impedence is too high don't measure the + * second ground. + */ + if (info->num_hpdet_res == 1 && *reading >= 45) { + dev_dbg(arizona->dev, "Skipping ground flip\n"); + info->hpdet_res[info->num_hpdet_res++] = *reading; + } + + if (info->num_hpdet_res == 1) { + dev_dbg(arizona->dev, "Flipping ground\n"); + + regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_SRC, + ~info->micd_modes[0].src); + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, 0); + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, ARIZONA_HP_POLL); + return -EAGAIN; + } + + /* OK, got both. Now, compare... */ + dev_dbg(arizona->dev, "HPDET measured %d %d\n", + info->hpdet_res[0], info->hpdet_res[1]); + + if (info->hpdet_res[0] > info->hpdet_res[1] * 2) { + dev_dbg(arizona->dev, "Detected mic\n"); + info->mic = true; + ret = extcon_set_cable_state_(&info->edev, + ARIZONA_CABLE_MICROPHONE, + true); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to report mic: %d\n", ret); + } + + /* Take the headphone impedance for the main report */ + *reading = info->hpdet_res[1]; + } else { + dev_dbg(arizona->dev, "Detected headphone\n"); + } + + /* Make sure everything is reset back to the real polarity */ + regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_SRC, + info->micd_modes[0].src); + } + + return 0; +} + static irqreturn_t arizona_hpdet_irq(int irq, void *data) { struct arizona_extcon_info *info = data; struct arizona *arizona = info->arizona; int report = ARIZONA_CABLE_HEADPHONE; - int ret; + int ret, reading; mutex_lock(&info->lock); @@ -344,14 +411,23 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) } else if (ret < 0) { goto done; } + reading = ret; /* Reset back to starting range */ regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, - ARIZONA_HP_IMPEDANCE_RANGE_MASK, 0); + ARIZONA_HP_IMPEDANCE_RANGE_MASK | ARIZONA_HP_POLL, + 0); + + ret = arizona_hpdet_do_id(info, &reading); + if (ret == -EAGAIN) { + goto out; + } else if (ret < 0) { + goto done; + } /* Report high impedence cables as line outputs */ - if (ret >= 5000) + if (reading >= 5000) report = ARIZONA_CABLE_LINEOUT; else report = ARIZONA_CABLE_HEADPHONE; @@ -370,8 +446,6 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); done: - regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, - ARIZONA_HP_POLL, 0); /* Revert back to MICDET mode */ regmap_update_bits(arizona->regmap, @@ -451,8 +525,58 @@ err: info->hpdet_active = false; } + +static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + int ret; + + dev_dbg(arizona->dev, "Starting identification via HPDET\n"); + + /* Make sure we keep the device enabled during the measurement */ + pm_runtime_get(info->dev); + + info->hpdet_active = true; + + ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0x4000); + if (ret != 0) + dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); + + ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0x4000); + if (ret != 0) + dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); + + ret = regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_SRC | ARIZONA_ACCDET_MODE_MASK, + info->micd_modes[0].src | + ARIZONA_ACCDET_MODE_HPL); + if (ret != 0) { + dev_err(arizona->dev, "Failed to set HPDETL mode: %d\n", ret); + goto err; } + ret = regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, ARIZONA_HP_POLL); + if (ret != 0) { + dev_err(arizona->dev, "Can't start HPDETL measurement: %d\n", + ret); + goto err; + } + + return; + +err: + regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); + + /* Just report headphone */ + ret = extcon_update_state(&info->edev, + 1 << ARIZONA_CABLE_HEADPHONE, + 1 << ARIZONA_CABLE_HEADPHONE); + if (ret != 0) + dev_err(arizona->dev, "Failed to report headphone: %d\n", ret); + info->hpdet_active = false; } @@ -612,12 +736,24 @@ static irqreturn_t arizona_jackdet(int irq, void *data) dev_err(arizona->dev, "Mechanical report failed: %d\n", ret); - arizona_start_mic(info); + if (!arizona->pdata.hpdet_acc_id) { + info->detecting = true; + info->mic = false; + info->jack_flips = 0; + + arizona_start_mic(info); + } else { + arizona_start_hpdet_acc_id(info); + } } else { dev_dbg(arizona->dev, "Detected jack removal\n"); arizona_stop_mic(info); + info->num_hpdet_res = 0; + for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) + info->hpdet_res[i] = 0; + info->mic = false; for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) input_report_key(info->input, @@ -665,7 +801,6 @@ static int arizona_extcon_probe(struct platform_device *pdev) mutex_init(&info->lock); info->arizona = arizona; info->dev = &pdev->dev; - info->detecting = true; platform_set_drvdata(pdev, info); switch (arizona->type) { diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 0fc26a480be3..7c0878778357 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -99,6 +99,9 @@ struct arizona_pdata { /** GPIO5 is used for jack detection */ bool jd_gpio5; + /** Use the headphone detect circuit to identify the accessory */ + bool hpdet_acc_id; + /** GPIO for mic detection polarity */ int micd_pol_gpio; -- cgit v1.2.1 From 1eda6aa7ce101b59dfd91abef2c8b0e51e96e199 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:54 +0900 Subject: extcon: arizona: Support direct microphone measurement via HPDET With some GPIO control it is possible to detect microphones in a wider range of configurations by directly measuring the microphone impedance when the HPDET method cannot distinguish between the behaviour of the two grounds. Allow a GPIO to be provided in platform data and use it to implement this behaviour. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 50 +++++++++++++++++++++++++++++++++++---- include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index ba9525ee4706..de141f72c8e5 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -56,7 +56,7 @@ struct arizona_extcon_info { bool hpdet_active; int num_hpdet_res; - unsigned int hpdet_res[2]; + unsigned int hpdet_res[3]; bool mic; bool detecting; @@ -313,6 +313,7 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) { struct arizona *arizona = info->arizona; + int id_gpio = arizona->pdata.hpdet_id_gpio; int ret; /* @@ -338,9 +339,27 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) ARIZONA_ACCESSORY_DETECT_MODE_1, ARIZONA_ACCDET_SRC, ~info->micd_modes[0].src); + regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, - ARIZONA_HP_POLL, 0); + ARIZONA_HP_POLL, ARIZONA_HP_POLL); + return -EAGAIN; + } + + /* Only check the mic directly if we didn't already ID it */ + if (id_gpio && info->num_hpdet_res == 2 && + !((info->hpdet_res[0] > info->hpdet_res[1] * 2))) { + dev_dbg(arizona->dev, "Measuring mic\n"); + + regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK | + ARIZONA_ACCDET_SRC, + ARIZONA_ACCDET_MODE_HPR | + info->micd_modes[0].src); + + gpio_set_value_cansleep(id_gpio, 1); + regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, ARIZONA_HP_POLL, ARIZONA_HP_POLL); @@ -348,10 +367,16 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) } /* OK, got both. Now, compare... */ - dev_dbg(arizona->dev, "HPDET measured %d %d\n", - info->hpdet_res[0], info->hpdet_res[1]); + dev_dbg(arizona->dev, "HPDET measured %d %d %d\n", + info->hpdet_res[0], info->hpdet_res[1], + info->hpdet_res[2]); - if (info->hpdet_res[0] > info->hpdet_res[1] * 2) { + /* + * Either the two grounds measure differently or we + * measure the mic as high impedance. + */ + if ((info->hpdet_res[0] > info->hpdet_res[1] * 2) || + (id_gpio && info->hpdet_res[2] > 10)) { dev_dbg(arizona->dev, "Detected mic\n"); info->mic = true; ret = extcon_set_cable_state_(&info->edev, @@ -382,6 +407,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) { struct arizona_extcon_info *info = data; struct arizona *arizona = info->arizona; + int id_gpio = arizona->pdata.hpdet_id_gpio; int report = ARIZONA_CABLE_HEADPHONE; int ret, reading; @@ -446,6 +472,8 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); done: + if (id_gpio) + gpio_set_value_cansleep(id_gpio, 0); /* Revert back to MICDET mode */ regmap_update_bits(arizona->regmap, @@ -854,6 +882,18 @@ static int arizona_extcon_probe(struct platform_device *pdev) } } + if (arizona->pdata.hpdet_id_gpio > 0) { + ret = devm_gpio_request_one(&pdev->dev, + arizona->pdata.hpdet_id_gpio, + GPIOF_OUT_INIT_LOW, + "HPDET"); + if (ret != 0) { + dev_err(arizona->dev, "Failed to request GPIO%d: %d\n", + arizona->pdata.hpdet_id_gpio, ret); + goto err_register; + } + } + if (arizona->pdata.micd_bias_start_time) regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, ARIZONA_MICD_BIAS_STARTTIME_MASK, diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 7c0878778357..bcbe4fda87cb 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -102,6 +102,9 @@ struct arizona_pdata { /** Use the headphone detect circuit to identify the accessory */ bool hpdet_acc_id; + /** GPIO used for mic isolation with HPDET */ + int hpdet_id_gpio; + /** GPIO for mic detection polarity */ int micd_pol_gpio; -- cgit v1.2.1 From 689557d3c7045320958d5bc4141088f7b4dff7ba Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:57 +0900 Subject: mfd: wm5102: Add microphone clamp control registers Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/mfd/wm5102-tables.c | 7 +++++++ include/linux/mfd/arizona/registers.h | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/drivers/mfd/wm5102-tables.c b/drivers/mfd/wm5102-tables.c index 311cc3657b0b..a396a3ae2045 100644 --- a/drivers/mfd/wm5102-tables.c +++ b/drivers/mfd/wm5102-tables.c @@ -84,6 +84,12 @@ int wm5102_patch(struct arizona *arizona) } static const struct regmap_irq wm5102_aod_irqs[ARIZONA_NUM_IRQ] = { + [ARIZONA_IRQ_MICD_CLAMP_FALL] = { + .mask = ARIZONA_MICD_CLAMP_FALL_EINT1 + }, + [ARIZONA_IRQ_MICD_CLAMP_RISE] = { + .mask = ARIZONA_MICD_CLAMP_RISE_EINT1 + }, [ARIZONA_IRQ_GP5_FALL] = { .mask = ARIZONA_GP5_FALL_EINT1 }, [ARIZONA_IRQ_GP5_RISE] = { .mask = ARIZONA_GP5_RISE_EINT1 }, [ARIZONA_IRQ_JD_FALL] = { .mask = ARIZONA_JD1_FALL_EINT1 }, @@ -312,6 +318,7 @@ static const struct reg_default wm5102_reg_default[] = { { 0x0000021A, 0x01A6 }, /* R538 - Mic Bias Ctrl 3 */ { 0x00000293, 0x0000 }, /* R659 - Accessory Detect Mode 1 */ { 0x0000029B, 0x0020 }, /* R667 - Headphone Detect 1 */ + { 0x000002A2, 0x0000 }, /* R674 - Micd clamp control */ { 0x000002A3, 0x1102 }, /* R675 - Mic Detect 1 */ { 0x000002A4, 0x009F }, /* R676 - Mic Detect 2 */ { 0x000002A5, 0x0000 }, /* R677 - Mic Detect 3 */ diff --git a/include/linux/mfd/arizona/registers.h b/include/linux/mfd/arizona/registers.h index e9150533e701..79e9dd4073d8 100644 --- a/include/linux/mfd/arizona/registers.h +++ b/include/linux/mfd/arizona/registers.h @@ -1196,6 +1196,14 @@ /* * R64 (0x40) - Wake control */ +#define ARIZONA_WKUP_MICD_CLAMP_FALL 0x0080 /* WKUP_MICD_CLAMP_FALL */ +#define ARIZONA_WKUP_MICD_CLAMP_FALL_MASK 0x0080 /* WKUP_MICD_CLAMP_FALL */ +#define ARIZONA_WKUP_MICD_CLAMP_FALL_SHIFT 7 /* WKUP_MICD_CLAMP_FALL */ +#define ARIZONA_WKUP_MICD_CLAMP_FALL_WIDTH 1 /* WKUP_MICD_CLAMP_FALL */ +#define ARIZONA_WKUP_MICD_CLAMP_RISE 0x0040 /* WKUP_MICD_CLAMP_RISE */ +#define ARIZONA_WKUP_MICD_CLAMP_RISE_MASK 0x0040 /* WKUP_MICD_CLAMP_RISE */ +#define ARIZONA_WKUP_MICD_CLAMP_RISE_SHIFT 6 /* WKUP_MICD_CLAMP_RISE */ +#define ARIZONA_WKUP_MICD_CLAMP_RISE_WIDTH 1 /* WKUP_MICD_CLAMP_RISE */ #define ARIZONA_WKUP_GP5_FALL 0x0020 /* WKUP_GP5_FALL */ #define ARIZONA_WKUP_GP5_FALL_MASK 0x0020 /* WKUP_GP5_FALL */ #define ARIZONA_WKUP_GP5_FALL_SHIFT 5 /* WKUP_GP5_FALL */ -- cgit v1.2.1 From cc83f833c77c1d233e3843af18c1abf8d561d1fa Mon Sep 17 00:00:00 2001 From: Alberto Garcia Date: Mon, 10 Dec 2012 11:49:57 +0100 Subject: ipack/devices/ipoctal: Fix race condition during Tx In order to transmit data, the driver enables Tx and sleeps until *board_write is set to 1 by the interrupt handler. It can happen, though, that the data is sent even before the process is asleep. In this case *board_write must be set to 1 anyway, otherwise we will be waiting for a condition that will never be true. Signed-off-by: Alberto Garcia Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 576d53d92677..e66135da63ce 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -195,13 +195,10 @@ static void ipoctal_irq_tx(struct ipoctal_channel *channel) *pointer_write = *pointer_write % PAGE_SIZE; channel->nb_bytes--; - if ((channel->nb_bytes == 0) && - (waitqueue_active(&channel->queue))) { - - if (channel->board_id != IPACK1_DEVICE_ID_SBS_OCTAL_485) { - *channel->board_write = 1; - wake_up_interruptible(&channel->queue); - } + if (channel->nb_bytes == 0 && + channel->board_id != IPACK1_DEVICE_ID_SBS_OCTAL_485) { + *channel->board_write = 1; + wake_up_interruptible(&channel->queue); } } -- cgit v1.2.1 From 69a6b9b1b6aec645d2efa23db2c15ed287e672dd Mon Sep 17 00:00:00 2001 From: Alberto Garcia Date: Mon, 10 Dec 2012 11:49:58 +0100 Subject: ipack/devices/ipoctal: don't check if nb_bytes is < 0 It is an unsigned int so that check is pointless. Signed-off-by: Alberto Garcia Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index e66135da63ce..a33a849765c6 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -183,10 +183,8 @@ static void ipoctal_irq_tx(struct ipoctal_channel *channel) unsigned char value; unsigned int *pointer_write = &channel->pointer_write; - if (channel->nb_bytes <= 0) { - channel->nb_bytes = 0; + if (channel->nb_bytes == 0) return; - } value = channel->tty_port.xmit_buf[*pointer_write]; iowrite8(value, &channel->regs->w.thr); -- cgit v1.2.1 From 7e5730d7c22267e406454b5cff0c40e4ebf9a0da Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Mon, 10 Dec 2012 11:49:59 +0100 Subject: ipack/devices/ipoctal: fix kernel bug when using pppd Trying to setup the pppd server to use ipoctal's serial ports, it says the ports are busy the first time. If the operation is repeated, a kernel bug due to a dereference of a NULL pointer appears. Removing the one-access-only setup from the driver, removes this kernel bug. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index a33a849765c6..8d0a86631908 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -38,7 +38,6 @@ struct ipoctal_channel { spinlock_t lock; unsigned int pointer_read; unsigned int pointer_write; - atomic_t open; struct tty_port tty_port; union scc2698_channel __iomem *regs; union scc2698_block __iomem *block_regs; @@ -70,22 +69,12 @@ static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty) static int ipoctal_open(struct tty_struct *tty, struct file *file) { - int res; struct ipoctal_channel *channel; channel = dev_get_drvdata(tty->dev); - - if (atomic_read(&channel->open)) - return -EBUSY; - tty->driver_data = channel; - res = tty_port_open(&channel->tty_port, tty, file); - if (res) - return res; - - atomic_inc(&channel->open); - return 0; + return tty_port_open(&channel->tty_port, tty, file); } static void ipoctal_reset_stats(struct ipoctal_stats *stats) @@ -111,9 +100,7 @@ static void ipoctal_close(struct tty_struct *tty, struct file *filp) struct ipoctal_channel *channel = tty->driver_data; tty_port_close(&channel->tty_port, tty, filp); - - if (atomic_dec_and_test(&channel->open)) - ipoctal_free_channel(channel); + ipoctal_free_channel(channel); } static int ipoctal_get_icount(struct tty_struct *tty, @@ -205,10 +192,6 @@ static void ipoctal_irq_channel(struct ipoctal_channel *channel) u8 isr, sr; struct tty_struct *tty; - /* If there is no client, skip the check */ - if (!atomic_read(&channel->open)) - return; - tty = tty_port_tty_get(&channel->tty_port); if (!tty) return; -- cgit v1.2.1 From a1da13a67afa45cf996ae9325030dd86c26573fc Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Mon, 10 Dec 2012 11:50:00 +0100 Subject: ipack/devices/ipoctal: remove wait_queue and atomic_t board_write Don't block the TTY client when sending characters. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 8d0a86631908..18f9cf1ffe2b 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include "ipoctal.h" @@ -42,7 +41,6 @@ struct ipoctal_channel { union scc2698_channel __iomem *regs; union scc2698_block __iomem *block_regs; unsigned int board_id; - unsigned char *board_write; u8 isr_rx_rdy_mask; u8 isr_tx_rdy_mask; }; @@ -51,7 +49,6 @@ struct ipoctal { struct ipack_device *dev; unsigned int board_id; struct ipoctal_channel channel[NR_CHANNELS]; - unsigned char write; struct tty_driver *tty_drv; u8 __iomem *mem8_space; u8 __iomem *int_space; @@ -181,10 +178,8 @@ static void ipoctal_irq_tx(struct ipoctal_channel *channel) channel->nb_bytes--; if (channel->nb_bytes == 0 && - channel->board_id != IPACK1_DEVICE_ID_SBS_OCTAL_485) { - *channel->board_write = 1; - wake_up_interruptible(&channel->queue); - } + channel->board_id != IPACK1_DEVICE_ID_SBS_OCTAL_485) + iowrite8(CR_DISABLE_TX, &channel->regs->w.cr); } static void ipoctal_irq_channel(struct ipoctal_channel *channel) @@ -207,8 +202,7 @@ static void ipoctal_irq_channel(struct ipoctal_channel *channel) iowrite8(CR_DISABLE_TX, &channel->regs->w.cr); iowrite8(CR_CMD_NEGATE_RTSN, &channel->regs->w.cr); iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); - *channel->board_write = 1; - wake_up_interruptible(&channel->queue); + iowrite8(CR_DISABLE_TX, &channel->regs->w.cr); } /* RX data */ @@ -302,7 +296,6 @@ static int ipoctal_inst_slot(struct ipoctal *ipoctal, unsigned int bus_nr, struct ipoctal_channel *channel = &ipoctal->channel[i]; channel->regs = chan_regs + i; channel->block_regs = block_regs + (i >> 1); - channel->board_write = &ipoctal->write; channel->board_id = ipoctal->board_id; if (i & 1) { channel->isr_tx_rdy_mask = ISR_TxRDY_B; @@ -385,8 +378,6 @@ static int ipoctal_inst_slot(struct ipoctal *ipoctal, unsigned int bus_nr, ipoctal_reset_stats(&channel->stats); channel->nb_bytes = 0; - init_waitqueue_head(&channel->queue); - spin_lock_init(&channel->lock); channel->pointer_read = 0; channel->pointer_write = 0; @@ -450,10 +441,6 @@ static int ipoctal_write_tty(struct tty_struct *tty, * operations */ iowrite8(CR_ENABLE_TX, &channel->regs->w.cr); - wait_event_interruptible(channel->queue, *channel->board_write); - iowrite8(CR_DISABLE_TX, &channel->regs->w.cr); - - *channel->board_write = 0; return char_copied; } -- cgit v1.2.1 From b5071f2cd89bfd88cc3c3a820cbb9e7d7d9b5c92 Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Mon, 10 Dec 2012 11:50:01 +0100 Subject: ipack/devices/ipoctal: setup TTY_NORMAL flag for each character. In case of several characters present in RxFIFO, they will have the flag of the previous one, no matter if the actual character was received properly or not. This patch fixes this bug. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 18f9cf1ffe2b..850f10506a79 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -121,11 +121,12 @@ static void ipoctal_irq_rx(struct ipoctal_channel *channel, struct tty_struct *tty, u8 sr) { unsigned char value; - unsigned char flag = TTY_NORMAL; + unsigned char flag; u8 isr; do { value = ioread8(&channel->regs->r.rhr); + flag = TTY_NORMAL; /* Error: count statistics */ if (sr & SR_ERROR) { iowrite8(CR_CMD_RESET_ERR_STATUS, &channel->regs->w.cr); -- cgit v1.2.1 From 9d01b6f064c130028be8beb729ada7c39021b582 Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Mon, 10 Dec 2012 11:50:02 +0100 Subject: ipack/devices/ipoctal: rework disable TX when the TX buffer is empty Depending of the device, it disables the TX mode in different places when there is no more data to transmit. This patch reorder them and disable the TX mode in the same place. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 850f10506a79..f2875f0f14d4 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -177,10 +177,6 @@ static void ipoctal_irq_tx(struct ipoctal_channel *channel) (*pointer_write)++; *pointer_write = *pointer_write % PAGE_SIZE; channel->nb_bytes--; - - if (channel->nb_bytes == 0 && - channel->board_id != IPACK1_DEVICE_ID_SBS_OCTAL_485) - iowrite8(CR_DISABLE_TX, &channel->regs->w.cr); } static void ipoctal_irq_channel(struct ipoctal_channel *channel) @@ -196,14 +192,14 @@ static void ipoctal_irq_channel(struct ipoctal_channel *channel) isr = ioread8(&channel->block_regs->r.isr); sr = ioread8(&channel->regs->r.sr); - /* In case of RS-485, change from TX to RX when finishing TX. - * Half-duplex. */ - if ((channel->board_id == IPACK1_DEVICE_ID_SBS_OCTAL_485) && - (sr & SR_TX_EMPTY) && (channel->nb_bytes == 0)) { - iowrite8(CR_DISABLE_TX, &channel->regs->w.cr); - iowrite8(CR_CMD_NEGATE_RTSN, &channel->regs->w.cr); - iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); + if ((sr & SR_TX_EMPTY) && (channel->nb_bytes == 0)) { iowrite8(CR_DISABLE_TX, &channel->regs->w.cr); + /* In case of RS-485, change from TX to RX when finishing TX. + * Half-duplex. */ + if (channel->board_id == IPACK1_DEVICE_ID_SBS_OCTAL_485) { + iowrite8(CR_CMD_NEGATE_RTSN, &channel->regs->w.cr); + iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); + } } /* RX data */ -- cgit v1.2.1 From a3882b7814fb3a5b7ea211e421451b1c4685f8f9 Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Mon, 10 Dec 2012 11:50:03 +0100 Subject: ipack/devices/ipoctal: avoid re-enable RX two times. RX is enabled when the tty port is open, so no need to do it in initialization time: it can allow the device to receive characters but no TTY client is listening to them. It produced an infinite number of IRQ as RxFIFO is not read to clear that IRQ in the device, so it is still pending. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index f2875f0f14d4..09e3a8e63e22 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -60,6 +60,10 @@ static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty) channel = dev_get_drvdata(tty->dev); + /* + * Enable RX. TX will be enabled when + * there is something to send + */ iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); return 0; } @@ -385,12 +389,6 @@ static int ipoctal_inst_slot(struct ipoctal *ipoctal, unsigned int bus_nr, continue; } dev_set_drvdata(tty_dev, channel); - - /* - * Enable again the RX. TX will be enabled when - * there is something to send - */ - iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); } return 0; -- cgit v1.2.1 From 21d27ed4616c9a7f2886c4159b4c409f73f96e76 Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Mon, 10 Dec 2012 11:50:04 +0100 Subject: ipack/devices/ipoctal: ack IRQ before processing it Due to the IRQ processing, we can generate another IRQ that can come before we end the previous one, so we lost it. E.g. when transmitting a character. To allow the processing in SMP machines, we ack the IRQ at the beginning of the IRQ handler. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 09e3a8e63e22..9cd5572457ff 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -223,14 +223,14 @@ static irqreturn_t ipoctal_irq_handler(void *arg) unsigned int i; struct ipoctal *ipoctal = (struct ipoctal *) arg; - /* Check all channels */ - for (i = 0; i < NR_CHANNELS; i++) - ipoctal_irq_channel(&ipoctal->channel[i]); - /* Clear the IPack device interrupt */ readw(ipoctal->int_space + ACK_INT_REQ0); readw(ipoctal->int_space + ACK_INT_REQ1); + /* Check all channels */ + for (i = 0; i < NR_CHANNELS; i++) + ipoctal_irq_channel(&ipoctal->channel[i]); + return IRQ_HANDLED; } -- cgit v1.2.1 From e7e664fd688a4a882ce571575ad721203f0cd584 Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Mon, 10 Dec 2012 11:50:05 +0100 Subject: ipack/devices/ipoctal: protect the channel data processing with a spinlock We protect important data such as TX buffer pointer, nb_bytes counter and status registers of the device, from accessing several times at the same time. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 9cd5572457ff..5ce2c4c5bb32 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -191,6 +191,8 @@ static void ipoctal_irq_channel(struct ipoctal_channel *channel) tty = tty_port_tty_get(&channel->tty_port); if (!tty) return; + + spin_lock(&channel->lock); /* The HW is organized in pair of channels. See which register we need * to read from */ isr = ioread8(&channel->block_regs->r.isr); @@ -216,6 +218,7 @@ static void ipoctal_irq_channel(struct ipoctal_channel *channel) tty_flip_buffer_push(tty); tty_kref_put(tty); + spin_unlock(&channel->lock); } static irqreturn_t ipoctal_irq_handler(void *arg) -- cgit v1.2.1 From b06073f963b7b00a628e58e87d02028e2c9e430d Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Mon, 10 Dec 2012 11:50:06 +0100 Subject: ipack/devices/ipoctal: remove redundant tty_flip_buffer_push() The function is already called in ipoctal_irq_rx() Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 5ce2c4c5bb32..34fcfce0cdd9 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -216,7 +216,6 @@ static void ipoctal_irq_channel(struct ipoctal_channel *channel) if ((isr & channel->isr_tx_rdy_mask) && (sr & SR_TX_READY)) ipoctal_irq_tx(channel); - tty_flip_buffer_push(tty); tty_kref_put(tty); spin_unlock(&channel->lock); } -- cgit v1.2.1 From b0d17fbdacb32f9f4b9ee1ad2b8f42f6a480d842 Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Mon, 10 Dec 2012 11:50:07 +0100 Subject: ipack/devices/ipoctal: add rx_enable flag Thus, we don't enable RX when a termios setup has been called, as it could be disabled previously. As the control registers (Rx, Tx flags specifically) cannot be read from the device, we keep this info in rx_enable. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 34fcfce0cdd9..8666d2d05eef 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -43,6 +43,7 @@ struct ipoctal_channel { unsigned int board_id; u8 isr_rx_rdy_mask; u8 isr_tx_rdy_mask; + unsigned int rx_enable; }; struct ipoctal { @@ -65,6 +66,7 @@ static int ipoctal_port_activate(struct tty_port *port, struct tty_struct *tty) * there is something to send */ iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); + channel->rx_enable = 1; return 0; } @@ -309,6 +311,7 @@ static int ipoctal_inst_slot(struct ipoctal *ipoctal, unsigned int bus_nr, } iowrite8(CR_DISABLE_RX | CR_DISABLE_TX, &channel->regs->w.cr); + channel->rx_enable = 0; iowrite8(CR_CMD_RESET_RX, &channel->regs->w.cr); iowrite8(CR_CMD_RESET_TX, &channel->regs->w.cr); iowrite8(MR1_CHRL_8_BITS | MR1_ERROR_CHAR | MR1_RxINT_RxRDY, @@ -430,6 +433,7 @@ static int ipoctal_write_tty(struct tty_struct *tty, /* As the IP-OCTAL 485 only supports half duplex, do it manually */ if (channel->board_id == IPACK1_DEVICE_ID_SBS_OCTAL_485) { iowrite8(CR_DISABLE_RX, &channel->regs->w.cr); + channel->rx_enable = 0; iowrite8(CR_CMD_ASSERT_RTSN, &channel->regs->w.cr); } @@ -589,8 +593,9 @@ static void ipoctal_set_termios(struct tty_struct *tty, iowrite8(mr2, &channel->regs->w.mr); iowrite8(csr, &channel->regs->w.csr); - /* Enable again the RX */ - iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); + /* Enable again the RX, if it was before */ + if (channel->rx_enable) + iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); } static void ipoctal_hangup(struct tty_struct *tty) @@ -610,6 +615,7 @@ static void ipoctal_hangup(struct tty_struct *tty) tty_port_hangup(&channel->tty_port); iowrite8(CR_DISABLE_RX | CR_DISABLE_TX, &channel->regs->w.cr); + channel->rx_enable = 0; iowrite8(CR_CMD_RESET_RX, &channel->regs->w.cr); iowrite8(CR_CMD_RESET_TX, &channel->regs->w.cr); iowrite8(CR_CMD_RESET_ERR_STATUS, &channel->regs->w.cr); -- cgit v1.2.1 From e0f8d323f34dee3f47388dc3d87e7c428b077a0d Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Mon, 10 Dec 2012 11:50:08 +0100 Subject: ipack/devices/ipoctal: added shutdown callback Added shutdown callback to disable RX and TX when there is no other client accesing the device. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 8666d2d05eef..0b3c4b8fe830 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -625,6 +625,22 @@ static void ipoctal_hangup(struct tty_struct *tty) wake_up_interruptible(&channel->tty_port.open_wait); } +static void ipoctal_shutdown(struct tty_struct *tty) +{ + struct ipoctal_channel *channel = tty->driver_data; + + if (channel == NULL) + return; + + iowrite8(CR_DISABLE_RX | CR_DISABLE_TX, &channel->regs->w.cr); + channel->rx_enable = 0; + iowrite8(CR_CMD_RESET_RX, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_TX, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_ERR_STATUS, &channel->regs->w.cr); + iowrite8(CR_CMD_RESET_MR, &channel->regs->w.cr); + clear_bit(ASYNCB_INITIALIZED, &channel->tty_port.flags); +} + static const struct tty_operations ipoctal_fops = { .ioctl = NULL, .open = ipoctal_open, @@ -635,6 +651,7 @@ static const struct tty_operations ipoctal_fops = { .chars_in_buffer = ipoctal_chars_in_buffer, .get_icount = ipoctal_get_icount, .hangup = ipoctal_hangup, + .shutdown = ipoctal_shutdown, }; static int ipoctal_probe(struct ipack_device *dev) -- cgit v1.2.1 From eea2172e6915a92cab1d3a79a4961e14a3c388ff Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Fri, 7 Dec 2012 00:15:23 +0100 Subject: drivers/w1/masters/ds1wm.c: use devm_ functions The various devm_ functions allocate memory that is released when a driver detaches. This patch uses these functions for data that is allocated in the probe function of a platform device and is only freed in the remove function. Signed-off-by: Julia Lawall Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/ds1wm.c | 52 ++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/drivers/w1/masters/ds1wm.c b/drivers/w1/masters/ds1wm.c index 7c294f4dc0ed..96cab6ac2b4e 100644 --- a/drivers/w1/masters/ds1wm.c +++ b/drivers/w1/masters/ds1wm.c @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -459,43 +460,34 @@ static int ds1wm_probe(struct platform_device *pdev) if (!pdev) return -ENODEV; - ds1wm_data = kzalloc(sizeof(*ds1wm_data), GFP_KERNEL); + ds1wm_data = devm_kzalloc(&pdev->dev, sizeof(*ds1wm_data), GFP_KERNEL); if (!ds1wm_data) return -ENOMEM; platform_set_drvdata(pdev, ds1wm_data); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - ret = -ENXIO; - goto err0; - } - ds1wm_data->map = ioremap(res->start, resource_size(res)); - if (!ds1wm_data->map) { - ret = -ENOMEM; - goto err0; - } + if (!res) + return -ENXIO; + ds1wm_data->map = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!ds1wm_data->map) + return -ENOMEM; /* calculate bus shift from mem resource */ ds1wm_data->bus_shift = resource_size(res) >> 3; ds1wm_data->pdev = pdev; ds1wm_data->cell = mfd_get_cell(pdev); - if (!ds1wm_data->cell) { - ret = -ENODEV; - goto err1; - } + if (!ds1wm_data->cell) + return -ENODEV; plat = pdev->dev.platform_data; - if (!plat) { - ret = -ENODEV; - goto err1; - } + if (!plat) + return -ENODEV; res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!res) { - ret = -ENXIO; - goto err1; - } + if (!res) + return -ENXIO; ds1wm_data->irq = res->start; ds1wm_data->int_en_reg_none = (plat->active_high ? DS1WM_INTEN_IAS : 0); ds1wm_data->reset_recover_delay = plat->reset_recover_delay; @@ -505,10 +497,10 @@ static int ds1wm_probe(struct platform_device *pdev) if (res->flags & IORESOURCE_IRQ_LOWEDGE) irq_set_irq_type(ds1wm_data->irq, IRQ_TYPE_EDGE_FALLING); - ret = request_irq(ds1wm_data->irq, ds1wm_isr, + ret = devm_request_irq(&pdev->dev, ds1wm_data->irq, ds1wm_isr, IRQF_DISABLED | IRQF_SHARED, "ds1wm", ds1wm_data); if (ret) - goto err1; + return ret; ds1wm_up(ds1wm_data); @@ -516,17 +508,12 @@ static int ds1wm_probe(struct platform_device *pdev) ret = w1_add_master_device(&ds1wm_master); if (ret) - goto err2; + goto err; return 0; -err2: +err: ds1wm_down(ds1wm_data); - free_irq(ds1wm_data->irq, ds1wm_data); -err1: - iounmap(ds1wm_data->map); -err0: - kfree(ds1wm_data); return ret; } @@ -560,9 +547,6 @@ static int ds1wm_remove(struct platform_device *pdev) w1_remove_master_device(&ds1wm_master); ds1wm_down(ds1wm_data); - free_irq(ds1wm_data->irq, ds1wm_data); - iounmap(ds1wm_data->map); - kfree(ds1wm_data); return 0; } -- cgit v1.2.1 From e5279ff6c9f5e950feac6e6f48621db912324c07 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Fri, 7 Dec 2012 00:15:24 +0100 Subject: drivers/w1/masters/mxc_w1.c: use devm_ functions The various devm_ functions allocate memory that is released when a driver detaches. This patch uses these functions for data that is allocated in the probe function of a platform device and is only freed in the remove function. At the same time, this fixes two faults. First, mdev, the result of kzalloc, was never freed. Second, on failure of ioremap, 0 was returned. This has been replaced by -EBUSY, which was the failure value for the call to request_mem_region, with which the call to ioremap has been combined. The warning message on failure of ioremap is dropped, because devm_request_and_ioremap already gives such messages on failure. Finally, the initial call to platform_get_resource is moved closer to the call to devm_request_and_ioremap, which takes care of checking whether its result is NULL, implying that a test on the result of this call to platform_get_resource is not needed. Signed-off-by: Julia Lawall Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/mxc_w1.c | 49 +++++++++------------------------------------ 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c index 708a25fc9961..372c8c0d54a0 100644 --- a/drivers/w1/masters/mxc_w1.c +++ b/drivers/w1/masters/mxc_w1.c @@ -109,34 +109,21 @@ static int mxc_w1_probe(struct platform_device *pdev) struct resource *res; int err = 0; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -ENODEV; - - mdev = kzalloc(sizeof(struct mxc_w1_device), GFP_KERNEL); + mdev = devm_kzalloc(&pdev->dev, sizeof(struct mxc_w1_device), + GFP_KERNEL); if (!mdev) return -ENOMEM; - mdev->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(mdev->clk)) { - err = PTR_ERR(mdev->clk); - goto failed_clk; - } + mdev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mdev->clk)) + return PTR_ERR(mdev->clk); mdev->clkdiv = (clk_get_rate(mdev->clk) / 1000000) - 1; - res = request_mem_region(res->start, resource_size(res), - "mxc_w1"); - if (!res) { - err = -EBUSY; - goto failed_req; - } - - mdev->regs = ioremap(res->start, resource_size(res)); - if (!mdev->regs) { - dev_err(&pdev->dev, "Cannot map mxc_w1 registers\n"); - goto failed_ioremap; - } + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mdev->regs = devm_request_and_ioremap(&pdev->dev, res); + if (!mdev->regs) + return -EBUSY; clk_prepare_enable(mdev->clk); __raw_writeb(mdev->clkdiv, mdev->regs + MXC_W1_TIME_DIVIDER); @@ -148,20 +135,10 @@ static int mxc_w1_probe(struct platform_device *pdev) err = w1_add_master_device(&mdev->bus_master); if (err) - goto failed_add; + return err; platform_set_drvdata(pdev, mdev); return 0; - -failed_add: - iounmap(mdev->regs); -failed_ioremap: - release_mem_region(res->start, resource_size(res)); -failed_req: - clk_put(mdev->clk); -failed_clk: - kfree(mdev); - return err; } /* @@ -170,16 +147,10 @@ failed_clk: static int mxc_w1_remove(struct platform_device *pdev) { struct mxc_w1_device *mdev = platform_get_drvdata(pdev); - struct resource *res; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); w1_remove_master_device(&mdev->bus_master); - iounmap(mdev->regs); - release_mem_region(res->start, resource_size(res)); clk_disable_unprepare(mdev->clk); - clk_put(mdev->clk); platform_set_drvdata(pdev, NULL); -- cgit v1.2.1 From 867ff9880d5d71a38433c0471bc09bcc10851f36 Mon Sep 17 00:00:00 2001 From: David Stevenson Date: Tue, 18 Dec 2012 01:37:56 +0000 Subject: w1_therm: Retries: remove old code add CRC w1_therm includes some obsolete code to detect bad_roms, this is no longer relevant. The retry code is only used for this bad_rom test, however there is a CRC check that detects a bad read, but does not trigger a retry. This patch removes all the bad_rom code and uses the CRC check to trigger retries. Signed-off-by: David Stevenson Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/slaves/w1_therm.c | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c index 92d08e7fcba2..5ef583d520fa 100644 --- a/drivers/w1/slaves/w1_therm.c +++ b/drivers/w1/slaves/w1_therm.c @@ -45,10 +45,6 @@ MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol, temperature famil static int w1_strong_pullup = 1; module_param_named(strong_pullup, w1_strong_pullup, int, 0); -static u8 bad_roms[][9] = { - {0xaa, 0x00, 0x4b, 0x46, 0xff, 0xff, 0x0c, 0x10, 0x87}, - {} - }; static ssize_t w1_therm_read(struct device *device, struct device_attribute *attr, char *buf); @@ -168,16 +164,6 @@ static inline int w1_convert_temp(u8 rom[9], u8 fid) return 0; } -static int w1_therm_check_rom(u8 rom[9]) -{ - int i; - - for (i=0; irom, rom, sizeof(sl->rom)); else - dev_warn(device, "18S20 doesn't respond to CONVERT_TEMP.\n"); + dev_warn(device, "Read failed CRC check\n"); for (i = 0; i < 9; ++i) c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", sl->rom[i]); -- cgit v1.2.1 From 25c22d5bead5b0211f3ecc84fd6152dfdf95c75d Mon Sep 17 00:00:00 2001 From: channing Date: Thu, 10 Jan 2013 16:27:29 +0800 Subject: misc: st_core: Error triggered by convert "char" to "int" When st driver decodes protocol index received from raw data, it does a value convert from "char" to "int". Because it's sign extension from bit8 to bit32, the "int" value maybe minus, in another word, the protocol index might be minus, but driver doesn't filter such case and may continue access memory pointed by this minus index. This patch is to change the variable type of index from "int" to "unsigned char", so that it avoids do such kind of type conversion. cc: liu chuansheng Signed-off-by: channing Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ti-st/st_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/misc/ti-st/st_core.c b/drivers/misc/ti-st/st_core.c index b90a2241d79c..0a1428016350 100644 --- a/drivers/misc/ti-st/st_core.c +++ b/drivers/misc/ti-st/st_core.c @@ -240,7 +240,8 @@ void st_int_recv(void *disc_data, char *ptr; struct st_proto_s *proto; unsigned short payload_len = 0; - int len = 0, type = 0; + int len = 0; + unsigned char type = 0; unsigned char *plen; struct st_data_s *st_gdata = (struct st_data_s *)disc_data; unsigned long flags; -- cgit v1.2.1 From 93ce83b6e0ffe8d48dae46bc0983dd3f01ec7e32 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Wed, 16 Jan 2013 10:05:32 -0800 Subject: uio-howto: example bug Bug in demo program, checking wrong return value Signed-off-by: Stephen Hemminger Cc: "Hans J. Koch" Signed-off-by: Greg Kroah-Hartman --- Documentation/DocBook/uio-howto.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/DocBook/uio-howto.tmpl b/Documentation/DocBook/uio-howto.tmpl index ddb05e98af0d..95618159e29b 100644 --- a/Documentation/DocBook/uio-howto.tmpl +++ b/Documentation/DocBook/uio-howto.tmpl @@ -984,7 +984,7 @@ int main() return errno; } configfd = open("/sys/class/uio/uio0/device/config", O_RDWR); - if (uiofd < 0) { + if (configfd < 0) { perror("config open:"); return errno; } -- cgit v1.2.1 From 781551df57c792aee291cd80a6aca24cd56cdd71 Mon Sep 17 00:00:00 2001 From: Stefan Roese Date: Fri, 7 Dec 2012 09:06:59 +0100 Subject: misc: Add Lattice ECP3 FPGA configuration via SPI This patch adds support for bitstream configuration (programming / loading) of the Lattice ECP3 FPGA's via the SPI bus. Here an example on my custom MPC5200 based board: $ echo 1 > /sys/class/firmware/spi0.0/loading $ cat fpga_a4m2k.bit > /sys/class/firmware/spi0.0/data $ echo 0 > /sys/class/firmware/spi0.0/loading leads to these messages: lattice-ecp3 spi0.0: FPGA Lattice ECP3-35 detected lattice-ecp3 spi0.0: Configuring the FPGA... lattice-ecp3 spi0.0: FPGA succesfully configured! Signed-off-by: Stefan Roese Acked-by: Ming Lei Reviewed-by: Grant Likely Cc: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 11 ++ drivers/misc/Makefile | 1 + drivers/misc/lattice-ecp3-config.c | 243 +++++++++++++++++++++++++++++++++++++ 3 files changed, 255 insertions(+) create mode 100644 drivers/misc/lattice-ecp3-config.c diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 264e647413b5..3ec3f9ece9d9 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -499,6 +499,17 @@ config USB_SWITCH_FSA9480 stereo and mono audio, video, microphone and UART data to use a common connector port. +config LATTICE_ECP3_CONFIG + tristate "Lattice ECP3 FPGA bitstream configuration via SPI" + depends on SPI && SYSFS + select FW_LOADER + default n + help + This option enables support for bitstream configuration (programming + or loading) of the Lattice ECP3 FPGA family via SPI. + + If unsure, say N. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 5c99726dd617..35a1463c72d9 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -51,3 +51,4 @@ obj-$(CONFIG_ALTERA_STAPL) +=altera-stapl/ obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_MAX8997_MUIC) += max8997-muic.o obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/ +obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o diff --git a/drivers/misc/lattice-ecp3-config.c b/drivers/misc/lattice-ecp3-config.c new file mode 100644 index 000000000000..ff3106ce8237 --- /dev/null +++ b/drivers/misc/lattice-ecp3-config.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2012 Stefan Roese + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FIRMWARE_NAME "lattice-ecp3.bit" + +/* + * The JTAG ID's of the supported FPGA's. The ID is 32bit wide + * reversed as noted in the manual. + */ +#define ID_ECP3_17 0xc2088080 +#define ID_ECP3_35 0xc2048080 + +/* FPGA commands */ +#define FPGA_CMD_READ_ID 0x07 /* plus 24 bits */ +#define FPGA_CMD_READ_STATUS 0x09 /* plus 24 bits */ +#define FPGA_CMD_CLEAR 0x70 +#define FPGA_CMD_REFRESH 0x71 +#define FPGA_CMD_WRITE_EN 0x4a /* plus 2 bits */ +#define FPGA_CMD_WRITE_DIS 0x4f /* plus 8 bits */ +#define FPGA_CMD_WRITE_INC 0x41 /* plus 0 bits */ + +/* + * The status register is 32bit revered, DONE is bit 17 from the TN1222.pdf + * (LatticeECP3 Slave SPI Port User's Guide) + */ +#define FPGA_STATUS_DONE 0x00004000 +#define FPGA_STATUS_CLEARED 0x00010000 + +#define FPGA_CLEAR_TIMEOUT 5000 /* max. 5000ms for FPGA clear */ +#define FPGA_CLEAR_MSLEEP 10 +#define FPGA_CLEAR_LOOP_COUNT (FPGA_CLEAR_TIMEOUT / FPGA_CLEAR_MSLEEP) + +struct fpga_data { + struct completion fw_loaded; +}; + +struct ecp3_dev { + u32 jedec_id; + char *name; +}; + +static const struct ecp3_dev ecp3_dev[] = { + { + .jedec_id = ID_ECP3_17, + .name = "Lattice ECP3-17", + }, + { + .jedec_id = ID_ECP3_35, + .name = "Lattice ECP3-35", + }, +}; + +static void firmware_load(const struct firmware *fw, void *context) +{ + struct spi_device *spi = (struct spi_device *)context; + struct fpga_data *data = dev_get_drvdata(&spi->dev); + u8 *buffer; + int ret; + u8 txbuf[8]; + u8 rxbuf[8]; + int rx_len = 8; + int i; + u32 jedec_id; + u32 status; + + if (fw->size == 0) { + dev_err(&spi->dev, "Error: Firmware size is 0!\n"); + return; + } + + /* Fill dummy data (24 stuffing bits for commands) */ + txbuf[1] = 0x00; + txbuf[2] = 0x00; + txbuf[3] = 0x00; + + /* Trying to speak with the FPGA via SPI... */ + txbuf[0] = FPGA_CMD_READ_ID; + ret = spi_write_then_read(spi, txbuf, 8, rxbuf, rx_len); + dev_dbg(&spi->dev, "FPGA JTAG ID=%08x\n", *(u32 *)&rxbuf[4]); + jedec_id = *(u32 *)&rxbuf[4]; + + for (i = 0; i < ARRAY_SIZE(ecp3_dev); i++) { + if (jedec_id == ecp3_dev[i].jedec_id) + break; + } + if (i == ARRAY_SIZE(ecp3_dev)) { + dev_err(&spi->dev, + "Error: No supported FPGA detected (JEDEC_ID=%08x)!\n", + jedec_id); + return; + } + + dev_info(&spi->dev, "FPGA %s detected\n", ecp3_dev[i].name); + + txbuf[0] = FPGA_CMD_READ_STATUS; + ret = spi_write_then_read(spi, txbuf, 8, rxbuf, rx_len); + dev_dbg(&spi->dev, "FPGA Status=%08x\n", *(u32 *)&rxbuf[4]); + + buffer = kzalloc(fw->size + 8, GFP_KERNEL); + if (!buffer) { + dev_err(&spi->dev, "Error: Can't allocate memory!\n"); + return; + } + + /* + * Insert WRITE_INC command into stream (one SPI frame) + */ + buffer[0] = FPGA_CMD_WRITE_INC; + buffer[1] = 0xff; + buffer[2] = 0xff; + buffer[3] = 0xff; + memcpy(buffer + 4, fw->data, fw->size); + + txbuf[0] = FPGA_CMD_REFRESH; + ret = spi_write(spi, txbuf, 4); + + txbuf[0] = FPGA_CMD_WRITE_EN; + ret = spi_write(spi, txbuf, 4); + + txbuf[0] = FPGA_CMD_CLEAR; + ret = spi_write(spi, txbuf, 4); + + /* + * Wait for FPGA memory to become cleared + */ + for (i = 0; i < FPGA_CLEAR_LOOP_COUNT; i++) { + txbuf[0] = FPGA_CMD_READ_STATUS; + ret = spi_write_then_read(spi, txbuf, 8, rxbuf, rx_len); + status = *(u32 *)&rxbuf[4]; + if (status == FPGA_STATUS_CLEARED) + break; + + msleep(FPGA_CLEAR_MSLEEP); + } + + if (i == FPGA_CLEAR_LOOP_COUNT) { + dev_err(&spi->dev, + "Error: Timeout waiting for FPGA to clear (status=%08x)!\n", + status); + kfree(buffer); + return; + } + + dev_info(&spi->dev, "Configuring the FPGA...\n"); + ret = spi_write(spi, buffer, fw->size + 8); + + txbuf[0] = FPGA_CMD_WRITE_DIS; + ret = spi_write(spi, txbuf, 4); + + txbuf[0] = FPGA_CMD_READ_STATUS; + ret = spi_write_then_read(spi, txbuf, 8, rxbuf, rx_len); + dev_dbg(&spi->dev, "FPGA Status=%08x\n", *(u32 *)&rxbuf[4]); + status = *(u32 *)&rxbuf[4]; + + /* Check result */ + if (status & FPGA_STATUS_DONE) + dev_info(&spi->dev, "FPGA succesfully configured!\n"); + else + dev_info(&spi->dev, "FPGA not configured (DONE not set)\n"); + + /* + * Don't forget to release the firmware again + */ + release_firmware(fw); + + kfree(buffer); + + complete(&data->fw_loaded); +} + +static int __devinit lattice_ecp3_probe(struct spi_device *spi) +{ + struct fpga_data *data; + int err; + + data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&spi->dev, "Memory allocation for fpga_data failed\n"); + return -ENOMEM; + } + spi_set_drvdata(spi, data); + + init_completion(&data->fw_loaded); + err = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG, + FIRMWARE_NAME, &spi->dev, + GFP_KERNEL, spi, firmware_load); + if (err) { + dev_err(&spi->dev, "Firmware loading failed with %d!\n", err); + return err; + } + + dev_info(&spi->dev, "FPGA bitstream configuration driver registered\n"); + + return 0; +} + +static int __devexit lattice_ecp3_remove(struct spi_device *spi) +{ + struct fpga_data *data = spi_get_drvdata(spi); + + wait_for_completion(&data->fw_loaded); + + return 0; +} + +static const struct spi_device_id lattice_ecp3_id[] __devinitdata = { + { "ecp3-17", 0 }, + { "ecp3-35", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, lattice_ecp3_id); + +static struct spi_driver lattice_ecp3_driver = { + .driver = { + .name = "lattice-ecp3", + .owner = THIS_MODULE, + }, + .probe = lattice_ecp3_probe, + .remove = __devexit_p(lattice_ecp3_remove), + .id_table = lattice_ecp3_id, +}; + +module_spi_driver(lattice_ecp3_driver); + +MODULE_AUTHOR("Stefan Roese "); +MODULE_DESCRIPTION("Lattice ECP3 FPGA configuration via SPI"); +MODULE_LICENSE("GPL"); -- cgit v1.2.1 From 8292ac21f35b94e9ce717bcd038dad92a42d8396 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 17 Jan 2013 10:25:26 -0800 Subject: misc: lattice-ecp3-config.c: remove __dev* markings These are now removed from the kernel, so remove them to allow the driver to build properly. Cc: Stefan Roese Signed-off-by: Greg Kroah-Hartman --- drivers/misc/lattice-ecp3-config.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/misc/lattice-ecp3-config.c b/drivers/misc/lattice-ecp3-config.c index ff3106ce8237..155700bfd2b6 100644 --- a/drivers/misc/lattice-ecp3-config.c +++ b/drivers/misc/lattice-ecp3-config.c @@ -184,7 +184,7 @@ static void firmware_load(const struct firmware *fw, void *context) complete(&data->fw_loaded); } -static int __devinit lattice_ecp3_probe(struct spi_device *spi) +static int lattice_ecp3_probe(struct spi_device *spi) { struct fpga_data *data; int err; @@ -210,7 +210,7 @@ static int __devinit lattice_ecp3_probe(struct spi_device *spi) return 0; } -static int __devexit lattice_ecp3_remove(struct spi_device *spi) +static int lattice_ecp3_remove(struct spi_device *spi) { struct fpga_data *data = spi_get_drvdata(spi); @@ -219,7 +219,7 @@ static int __devexit lattice_ecp3_remove(struct spi_device *spi) return 0; } -static const struct spi_device_id lattice_ecp3_id[] __devinitdata = { +static const struct spi_device_id lattice_ecp3_id[] = { { "ecp3-17", 0 }, { "ecp3-35", 0 }, { } @@ -232,7 +232,7 @@ static struct spi_driver lattice_ecp3_driver = { .owner = THIS_MODULE, }, .probe = lattice_ecp3_probe, - .remove = __devexit_p(lattice_ecp3_remove), + .remove = lattice_ecp3_remove, .id_table = lattice_ecp3_id, }; -- cgit v1.2.1 From 10d498b13f73f6c171f974d32d231740e6fbb98c Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 28 Nov 2012 21:22:43 -0500 Subject: hv: hv_balloon: remove duplicated include from hv_balloon.c Remove duplicated include. Signed-off-by: Wei Yongjun Acked-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv_balloon.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index f6c0011a0337..0ce08c401dc4 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -29,7 +29,6 @@ #include #include #include -#include #include #include -- cgit v1.2.1 From 40424f5fff65c7ae8763ca69ff3a9b8ddd8e2760 Mon Sep 17 00:00:00 2001 From: Tomas Hozza Date: Tue, 27 Nov 2012 08:56:33 +0100 Subject: tools/hv: Fix /var subdirectory Initial patch by Ben Hutchings We will install this in /usr, so it must use /var/lib for its state. Only programs installed under /opt should use /var/opt. Signed-off-by: Tomas Hozza Acked-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- tools/hv/hv_kvp_daemon.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index d25a46925e61..5c052dbcaf75 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c @@ -97,7 +97,7 @@ static struct utsname uts_buf; * The location of the interface configuration file. */ -#define KVP_CONFIG_LOC "/var/opt/" +#define KVP_CONFIG_LOC "/var/lib/hyperv" #define MAX_FILE_NAME 100 #define ENTRIES_PER_BLOCK 50 @@ -234,9 +234,9 @@ static int kvp_file_init(void) int i; int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK; - if (access("/var/opt/hyperv", F_OK)) { - if (mkdir("/var/opt/hyperv", S_IRUSR | S_IWUSR | S_IROTH)) { - syslog(LOG_ERR, " Failed to create /var/opt/hyperv"); + if (access(KVP_CONFIG_LOC, F_OK)) { + if (mkdir(KVP_CONFIG_LOC, S_IRUSR | S_IWUSR | S_IROTH)) { + syslog(LOG_ERR, " Failed to create %s", KVP_CONFIG_LOC); exit(EXIT_FAILURE); } } @@ -245,7 +245,7 @@ static int kvp_file_init(void) fname = kvp_file_info[i].fname; records_read = 0; num_blocks = 1; - sprintf(fname, "/var/opt/hyperv/.kvp_pool_%d", i); + sprintf(fname, "%s/.kvp_pool_%d", KVP_CONFIG_LOC, i); fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IROTH); if (fd == -1) @@ -1271,7 +1271,7 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) */ snprintf(if_file, sizeof(if_file), "%s%s%s", KVP_CONFIG_LOC, - "hyperv/ifcfg-", if_name); + "/ifcfg-", if_name); file = fopen(if_file, "w"); -- cgit v1.2.1 From 0bffd25ce919f88ea6473150b66d26e7fa5712af Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Tue, 27 Nov 2012 08:56:34 +0100 Subject: tools/hv: Fix permissions of created directory and files It's silly to create directories without execute permission, or to give permissions to 'other' but not the group-owner. Write the permissions in octal and 'ls -l' format since these are much easier to read than the named macros. Signed-off-by: Ben Hutchings Signed-off-by: Tomas Hozza Acked-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- tools/hv/hv_kvp_daemon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index 5c052dbcaf75..0358ad26d2fd 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c @@ -235,7 +235,7 @@ static int kvp_file_init(void) int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK; if (access(KVP_CONFIG_LOC, F_OK)) { - if (mkdir(KVP_CONFIG_LOC, S_IRUSR | S_IWUSR | S_IROTH)) { + if (mkdir(KVP_CONFIG_LOC, 0755 /* rwxr-xr-x */)) { syslog(LOG_ERR, " Failed to create %s", KVP_CONFIG_LOC); exit(EXIT_FAILURE); } @@ -246,7 +246,7 @@ static int kvp_file_init(void) records_read = 0; num_blocks = 1; sprintf(fname, "%s/.kvp_pool_%d", KVP_CONFIG_LOC, i); - fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IROTH); + fd = open(fname, O_RDWR | O_CREAT, 0644 /* rw-r--r-- */); if (fd == -1) return 1; -- cgit v1.2.1 From 6fdf3b21433e901dcba0ac186f00d604ce944f56 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:32 -0800 Subject: Drivers: hv: Implement routines for read side signaling optimization Implement functions that will support read-side signaling optimization. By having the reader indicate the start of the "read" operation and the "end" of the read operation we can more efficiently handle the signaling protocol: while the read is in progress, there is no need for the "writer" to signal the "reader" as new items are put on the read queue. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hyperv_vmbus.h | 4 ++++ drivers/hv/ring_buffer.c | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index d8d1fadb398a..3184f6ff4e74 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -570,6 +570,10 @@ u32 hv_get_ringbuffer_interrupt_mask(struct hv_ring_buffer_info *ring_info); void hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info, struct hv_ring_buffer_debug_info *debug_info); +void hv_begin_read(struct hv_ring_buffer_info *rbi); + +u32 hv_end_read(struct hv_ring_buffer_info *rbi); + /* * Maximum channels is determined by the size of the interrupt page * which is PAGE_SIZE. 1/2 of PAGE_SIZE is for send endpoint interrupt diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 7233c88f01b8..001079287709 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -29,6 +29,30 @@ #include "hyperv_vmbus.h" +void hv_begin_read(struct hv_ring_buffer_info *rbi) +{ + rbi->ring_buffer->interrupt_mask = 1; + smp_mb(); +} + +u32 hv_end_read(struct hv_ring_buffer_info *rbi) +{ + u32 read; + u32 write; + + rbi->ring_buffer->interrupt_mask = 0; + smp_mb(); + + /* + * Now check to see if the ring buffer is still empty. + * If it is not, we raced and we need to process new + * incoming messages. + */ + hv_get_ringbuffer_availbytes(rbi, &read, &write); + + return read; +} + /* * hv_get_next_write_location() -- cgit v1.2.1 From 132368bd0b286457f83f56d0bbdecd85999562dc Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:33 -0800 Subject: Drivers: hv: Add state to manage batched reading For the "read" side signaling optimization, the reader has to completely drain the queue before exiting. Add state to manage this "batched" reading. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 7 +++++++ include/linux/hyperv.h | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 2f84c5cff8d4..7bf59177aaf8 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -275,6 +275,13 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) return; } + /* + * By default we setup state to enable batched + * reading. A specific service can choose to + * disable this prior to opening the channel. + */ + newchannel->batched_reading = true; + memcpy(&newchannel->offermsg, offer, sizeof(struct vmbus_channel_offer_channel)); newchannel->monitor_grp = (u8)offer->monitorid / 32; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index e73b852156b1..1ffe84de6c55 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -882,8 +882,28 @@ struct vmbus_channel { void (*onchannel_callback)(void *context); void *channel_callback_context; + + /* + * A channel can be marked for efficient (batched) + * reading: + * If batched_reading is set to "true", we read until the + * channel is empty and hold off interrupts from the host + * during the entire read process. + * If batched_reading is set to "false", the client is not + * going to perform batched reading. + * + * By default we will enable batched reading; specific + * drivers that don't want this behavior can turn it off. + */ + + bool batched_reading; }; +static inline void set_channel_read_state(struct vmbus_channel *c, bool state) +{ + c->batched_reading = state; +} + void vmbus_onmessage(void *context); int vmbus_request_offers(void); -- cgit v1.2.1 From 7ae3e035195735f9b6ce3308b6240af877a41ea0 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:34 -0800 Subject: Drivers: hv: Turn off batched reading for util drivers Util driver is not a performance critical driver and furthermore some util services such as KVP can only handle one outstanding message from the host. Turn off batched reading for util drivers. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv_util.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index a0667de7a04c..a62a76062508 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -274,6 +274,16 @@ static int util_probe(struct hv_device *dev, } } + /* + * The set of services managed by the util driver are not performance + * critical and do not need batched reading. Furthermore, some services + * such as KVP can only handle one message from the host at a time. + * Turn off batched reading for all util drivers before we open the + * channel. + */ + + set_channel_read_state(dev->channel, false); + ret = vmbus_open(dev->channel, 4 * PAGE_SIZE, 4 * PAGE_SIZE, NULL, 0, srv->util_cb, dev->channel); if (ret) -- cgit v1.2.1 From f878f3d59ed26f489add852ed6d5c8e5f3bbb1aa Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:35 -0800 Subject: Drivers: hv: Optimize signaling in the read path Now that we have the infratructure for correctly determining when we should signal the host; optimize the signaling on the read side - signaling the guest from the host. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 650c9f0b6642..d1019a770ad7 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -212,6 +212,9 @@ static void process_chn_event(u32 relid) { struct vmbus_channel *channel; unsigned long flags; + void *arg; + bool read_state; + u32 bytes_to_read; /* * Find the channel based on this relid and invokes the @@ -234,10 +237,29 @@ static void process_chn_event(u32 relid) */ spin_lock_irqsave(&channel->inbound_lock, flags); - if (channel->onchannel_callback != NULL) - channel->onchannel_callback(channel->channel_callback_context); - else + if (channel->onchannel_callback != NULL) { + arg = channel->channel_callback_context; + read_state = channel->batched_reading; + /* + * This callback reads the messages sent by the host. + * We can optimize host to guest signaling by ensuring: + * 1. While reading the channel, we disable interrupts from + * host. + * 2. Ensure that we process all posted messages from the host + * before returning from this callback. + * 3. Once we return, enable signaling from the host. Once this + * state is set we check to see if additional packets are + * available to read. In this case we repeat the process. + */ + + do { + hv_begin_read(&channel->inbound); + channel->onchannel_callback(arg); + bytes_to_read = hv_end_read(&channel->inbound); + } while (read_state && (bytes_to_read != 0)); + } else { pr_err("no channel callback for relid - %u\n", relid); + } spin_unlock_irqrestore(&channel->inbound_lock, flags); } -- cgit v1.2.1 From 98fa8cf4bcc79cb14de8fd815bbcd00dcbd7b20e Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:36 -0800 Subject: Drivers: hv: Optimize the signaling on the write path The host has already implemented the "read" side optimizations. Leverage that to optimize "write" side signaling. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 15 +++++++++------ drivers/hv/hyperv_vmbus.h | 2 +- drivers/hv/ring_buffer.c | 42 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 773a2f25a8f0..727c5f1d6acf 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -564,6 +564,7 @@ int vmbus_sendpacket(struct vmbus_channel *channel, const void *buffer, struct scatterlist bufferlist[3]; u64 aligned_data = 0; int ret; + bool signal = false; /* Setup the descriptor */ @@ -580,9 +581,9 @@ int vmbus_sendpacket(struct vmbus_channel *channel, const void *buffer, sg_set_buf(&bufferlist[2], &aligned_data, packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3); + ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal); - if (ret == 0 && !hv_get_ringbuffer_interrupt_mask(&channel->outbound)) + if (ret == 0 && signal) vmbus_setevent(channel); return ret; @@ -606,6 +607,7 @@ int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel, u32 packetlen_aligned; struct scatterlist bufferlist[3]; u64 aligned_data = 0; + bool signal = false; if (pagecount > MAX_PAGE_BUFFER_COUNT) return -EINVAL; @@ -641,9 +643,9 @@ int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel, sg_set_buf(&bufferlist[2], &aligned_data, packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3); + ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal); - if (ret == 0 && !hv_get_ringbuffer_interrupt_mask(&channel->outbound)) + if (ret == 0 && signal) vmbus_setevent(channel); return ret; @@ -665,6 +667,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, u32 packetlen_aligned; struct scatterlist bufferlist[3]; u64 aligned_data = 0; + bool signal = false; u32 pfncount = NUM_PAGES_SPANNED(multi_pagebuffer->offset, multi_pagebuffer->len); @@ -703,9 +706,9 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, sg_set_buf(&bufferlist[2], &aligned_data, packetlen_aligned - packetlen); - ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3); + ret = hv_ringbuffer_write(&channel->outbound, bufferlist, 3, &signal); - if (ret == 0 && !hv_get_ringbuffer_interrupt_mask(&channel->outbound)) + if (ret == 0 && signal) vmbus_setevent(channel); return ret; diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 3184f6ff4e74..895c898812bd 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -555,7 +555,7 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info); int hv_ringbuffer_write(struct hv_ring_buffer_info *ring_info, struct scatterlist *sglist, - u32 sgcount); + u32 sgcount, bool *signal); int hv_ringbuffer_peek(struct hv_ring_buffer_info *ring_info, void *buffer, u32 buflen); diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 001079287709..9bc55f0dcf47 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -53,6 +53,37 @@ u32 hv_end_read(struct hv_ring_buffer_info *rbi) return read; } +/* + * When we write to the ring buffer, check if the host needs to + * be signaled. Here is the details of this protocol: + * + * 1. The host guarantees that while it is draining the + * ring buffer, it will set the interrupt_mask to + * indicate it does not need to be interrupted when + * new data is placed. + * + * 2. The host guarantees that it will completely drain + * the ring buffer before exiting the read loop. Further, + * once the ring buffer is empty, it will clear the + * interrupt_mask and re-check to see if new data has + * arrived. + */ + +static bool hv_need_to_signal(u32 old_write, struct hv_ring_buffer_info *rbi) +{ + if (rbi->ring_buffer->interrupt_mask) + return false; + + /* + * This is the only case we need to signal when the + * ring transitions from being empty to non-empty. + */ + if (old_write == rbi->ring_buffer->read_index) + return true; + + return false; +} + /* * hv_get_next_write_location() @@ -322,7 +353,7 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info) * */ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info, - struct scatterlist *sglist, u32 sgcount) + struct scatterlist *sglist, u32 sgcount, bool *signal) { int i = 0; u32 bytes_avail_towrite; @@ -331,6 +362,7 @@ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info, struct scatterlist *sg; u32 next_write_location; + u32 old_write; u64 prev_indices = 0; unsigned long flags; @@ -359,6 +391,8 @@ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info, /* Write to the ring buffer */ next_write_location = hv_get_next_write_location(outring_info); + old_write = next_write_location; + for_each_sg(sglist, sg, sgcount, i) { next_write_location = hv_copyto_ringbuffer(outring_info, @@ -375,14 +409,16 @@ int hv_ringbuffer_write(struct hv_ring_buffer_info *outring_info, &prev_indices, sizeof(u64)); - /* Make sure we flush all writes before updating the writeIndex */ - smp_wmb(); + /* Issue a full memory barrier before updating the write index */ + smp_mb(); /* Now, update the write location */ hv_set_next_write_location(outring_info, next_write_location); spin_unlock_irqrestore(&outring_info->ring_lock, flags); + + *signal = hv_need_to_signal(old_write, outring_info); return 0; } -- cgit v1.2.1 From 4fa152ce24724a4a6b2edd26ca2eb7757ff5b2b8 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:37 -0800 Subject: Drivers: hv: Get rid of hv_get_ringbuffer_interrupt_mask() This function is no longer used; get rid of it. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hyperv_vmbus.h | 1 - drivers/hv/ring_buffer.c | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 895c898812bd..ac1e419d4b16 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -565,7 +565,6 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *ring_info, u32 buflen, u32 offset); -u32 hv_get_ringbuffer_interrupt_mask(struct hv_ring_buffer_info *ring_info); void hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info, struct hv_ring_buffer_debug_info *debug_info); diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 9bc55f0dcf47..2a0babc95709 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -294,19 +294,6 @@ void hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info, } } - -/* - * - * hv_get_ringbuffer_interrupt_mask() - * - * Get the interrupt mask for the specified ring buffer - * - */ -u32 hv_get_ringbuffer_interrupt_mask(struct hv_ring_buffer_info *rbi) -{ - return rbi->ring_buffer->interrupt_mask; -} - /* * * hv_ringbuffer_init() -- cgit v1.2.1 From 610071c38463998d5a66388ff9956aaeb24b49a8 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:38 -0800 Subject: Drivers: hv: Support handling multiple VMBUS versions The current code hard coded the vmbus version independent of the host it was running on. Add code to dynamically negotiate the most appropriate version. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 165 ++++++++++++++++++++++++++++++++---------------- include/linux/hyperv.h | 6 -- 2 files changed, 111 insertions(+), 60 deletions(-) diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index d1019a770ad7..2b56a3f47b30 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -39,16 +39,112 @@ struct vmbus_connection vmbus_connection = { .next_gpadl_handle = ATOMIC_INIT(0xE1E10), }; +/* + * VMBUS version is 32 bit entity broken up into + * two 16 bit quantities: major_number. minor_number. + * + * 0 . 13 (Windows Server 2008) + * 1 . 1 (Windows 7) + * 2 . 4 (Windows 8) + */ + +#define VERSION_WS2008 ((0 << 16) | (13)) +#define VERSION_WIN7 ((1 << 16) | (1)) +#define VERSION_WIN8 ((2 << 16) | (4)) + +#define VERSION_INVAL -1 + +static __u32 vmbus_get_next_version(__u32 current_version) +{ + switch (current_version) { + case (VERSION_WIN7): + return VERSION_WS2008; + + case (VERSION_WIN8): + return VERSION_WIN7; + + case (VERSION_WS2008): + default: + return VERSION_INVAL; + } +} + +static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, + __u32 version) +{ + int ret = 0; + struct vmbus_channel_initiate_contact *msg; + unsigned long flags; + int t; + + init_completion(&msginfo->waitevent); + + msg = (struct vmbus_channel_initiate_contact *)msginfo->msg; + + msg->header.msgtype = CHANNELMSG_INITIATE_CONTACT; + msg->vmbus_version_requested = version; + msg->interrupt_page = virt_to_phys(vmbus_connection.int_page); + msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages); + msg->monitor_page2 = virt_to_phys( + (void *)((unsigned long)vmbus_connection.monitor_pages + + PAGE_SIZE)); + + /* + * Add to list before we send the request since we may + * receive the response before returning from this routine + */ + spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); + list_add_tail(&msginfo->msglistentry, + &vmbus_connection.chn_msg_list); + + spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); + + ret = vmbus_post_msg(msg, + sizeof(struct vmbus_channel_initiate_contact)); + if (ret != 0) { + spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); + list_del(&msginfo->msglistentry); + spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, + flags); + return ret; + } + + /* Wait for the connection response */ + t = wait_for_completion_timeout(&msginfo->waitevent, 5*HZ); + if (t == 0) { + spin_lock_irqsave(&vmbus_connection.channelmsg_lock, + flags); + list_del(&msginfo->msglistentry); + spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, + flags); + return -ETIMEDOUT; + } + + spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); + list_del(&msginfo->msglistentry); + spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); + + /* Check if successful */ + if (msginfo->response.version_response.version_supported) { + vmbus_connection.conn_state = CONNECTED; + } else { + pr_err("Unable to connect, " + "Version %d not supported by Hyper-V\n", + version); + return -ECONNREFUSED; + } + + return ret; +} + /* * vmbus_connect - Sends a connect request on the partition service connection */ int vmbus_connect(void) { int ret = 0; - int t; struct vmbus_channel_msginfo *msginfo = NULL; - struct vmbus_channel_initiate_contact *msg; - unsigned long flags; + __u32 version; /* Initialize the vmbus connection */ vmbus_connection.conn_state = CONNECTING; @@ -99,64 +195,25 @@ int vmbus_connect(void) goto cleanup; } - init_completion(&msginfo->waitevent); - - msg = (struct vmbus_channel_initiate_contact *)msginfo->msg; - - msg->header.msgtype = CHANNELMSG_INITIATE_CONTACT; - msg->vmbus_version_requested = VMBUS_REVISION_NUMBER; - msg->interrupt_page = virt_to_phys(vmbus_connection.int_page); - msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages); - msg->monitor_page2 = virt_to_phys( - (void *)((unsigned long)vmbus_connection.monitor_pages + - PAGE_SIZE)); - /* - * Add to list before we send the request since we may - * receive the response before returning from this routine + * Negotiate a compatible VMBUS version number with the + * host. We start with the highest number we can support + * and work our way down until we negotiate a compatible + * version. */ - spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); - list_add_tail(&msginfo->msglistentry, - &vmbus_connection.chn_msg_list); - - spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); - ret = vmbus_post_msg(msg, - sizeof(struct vmbus_channel_initiate_contact)); - if (ret != 0) { - spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); - list_del(&msginfo->msglistentry); - spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, - flags); - goto cleanup; - } + version = VERSION_WS2008; - /* Wait for the connection response */ - t = wait_for_completion_timeout(&msginfo->waitevent, 5*HZ); - if (t == 0) { - spin_lock_irqsave(&vmbus_connection.channelmsg_lock, - flags); - list_del(&msginfo->msglistentry); - spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, - flags); - ret = -ETIMEDOUT; - goto cleanup; - } + do { + ret = vmbus_negotiate_version(msginfo, version); + if (ret == 0) + break; - spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); - list_del(&msginfo->msglistentry); - spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); + version = vmbus_get_next_version(version); + } while (version != VERSION_INVAL); - /* Check if successful */ - if (msginfo->response.version_response.version_supported) { - vmbus_connection.conn_state = CONNECTED; - } else { - pr_err("Unable to connect, " - "Version %d not supported by Hyper-V\n", - VMBUS_REVISION_NUMBER); - ret = -ECONNREFUSED; + if (version == VERSION_INVAL) goto cleanup; - } kfree(msginfo); return 0; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 1ffe84de6c55..b097bf9d9328 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -406,12 +406,6 @@ hv_get_ringbuffer_availbytes(struct hv_ring_buffer_info *rbi, #define HV_DRV_VERSION "3.1" -/* - * A revision number of vmbus that is used for ensuring both ends on a - * partition are using compatible versions. - */ -#define VMBUS_REVISION_NUMBER 13 - /* Make maximum size of pipe payload of 16K */ #define MAX_PIPE_DATA_PAYLOAD (sizeof(u8) * 16384) -- cgit v1.2.1 From 2416603ef1e12955850ade0d0cdecbef1fc59fa3 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:39 -0800 Subject: Drivers: hv: Update the ring buffer structure to match win8 functionality Update the ringbuffer structure to support win8 functionality. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- include/linux/hyperv.h | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index b097bf9d9328..2b5480126394 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -325,14 +325,28 @@ struct hv_ring_buffer { u32 interrupt_mask; - /* Pad it to PAGE_SIZE so that data starts on page boundary */ - u8 reserved[4084]; - - /* NOTE: - * The interrupt_mask field is used only for channels but since our - * vmbus connection also uses this data structure and its data starts - * here, we commented out this field. + /* + * Win8 uses some of the reserved bits to implement + * interrupt driven flow management. On the send side + * we can request that the receiver interrupt the sender + * when the ring transitions from being full to being able + * to handle a message of size "pending_send_sz". + * + * Add necessary state for this enhancement. */ + u32 pending_send_sz; + + u32 reserved1[12]; + + union { + struct { + u32 feat_pending_send_sz:1; + }; + u32 value; + } feature_bits; + + /* Pad it to PAGE_SIZE so that data starts on page boundary */ + u8 reserved2[4028]; /* * Ring data starts here + RingDataStartOffset -- cgit v1.2.1 From 29423b7e51a8ea4d687cf98c3a50f33c554be194 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:40 -0800 Subject: Drivers: hv: Extend/modify vmbus_channel_offer_channel for win7 and beyond The "offfer" message sent by the host has been extended in win7 (ws2008 R2). Add/modify state to reflect this extension. All these changes are backward compatible. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- include/linux/hyperv.h | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 2b5480126394..bee559ada3bb 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -440,9 +440,13 @@ hv_get_ringbuffer_availbytes(struct hv_ring_buffer_info *rbi, struct vmbus_channel_offer { uuid_le if_type; uuid_le if_instance; - u64 int_latency; /* in 100ns units */ - u32 if_revision; - u32 server_ctx_size; /* in bytes */ + + /* + * These two fields are not currently used. + */ + u64 reserved1; + u64 reserved2; + u16 chn_flags; u16 mmio_megabytes; /* in bytes * 1024 * 1024 */ @@ -464,7 +468,11 @@ struct vmbus_channel_offer { unsigned char user_def[MAX_PIPE_USER_DEFINED_BYTES]; } pipe; } u; - u32 padding; + /* + * The sub_channel_index is defined in win8. + */ + u16 sub_channel_index; + u16 reserved3; } __packed; /* Server Flags */ @@ -660,7 +668,25 @@ struct vmbus_channel_offer_channel { struct vmbus_channel_offer offer; u32 child_relid; u8 monitorid; - u8 monitor_allocated; + /* + * win7 and beyond splits this field into a bit field. + */ + u8 monitor_allocated:1; + u8 reserved:7; + /* + * These are new fields added in win7 and later. + * Do not access these fields without checking the + * negotiated protocol. + * + * If "is_dedicated_interrupt" is set, we must not set the + * associated bit in the channel bitmap while sending the + * interrupt to the host. + * + * connection_id is to be used in signaling the host. + */ + u16 is_dedicated_interrupt:1; + u16 reserved1:15; + u32 connection_id; } __packed; /* Rescind Offer parameters */ -- cgit v1.2.1 From 37f7278b81a9ab9b76cf4d716ba420444ca4a616 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:41 -0800 Subject: Drivers: hv: Save and export negotiated vmbus version Export the negotiated vmbus version as this may be useful for individual drivers. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 9 +++++++++ include/linux/hyperv.h | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 2b56a3f47b30..70ea5d1fc16c 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "hyperv_vmbus.h" @@ -54,6 +55,12 @@ struct vmbus_connection vmbus_connection = { #define VERSION_INVAL -1 +/* + * Negotiated protocol version with the host. + */ +__u32 vmbus_proto_version; +EXPORT_SYMBOL_GPL(vmbus_proto_version); + static __u32 vmbus_get_next_version(__u32 current_version) { switch (current_version) { @@ -215,6 +222,8 @@ int vmbus_connect(void) if (version == VERSION_INVAL) goto cleanup; + vmbus_proto_version = version; + pr_info("Negotiated host information %d\n", version); kfree(msginfo); return 0; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index bee559ada3bb..134a2022a7a3 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1204,5 +1204,11 @@ int hv_kvp_init(struct hv_util_service *); void hv_kvp_deinit(void); void hv_kvp_onchannelcallback(void *); +/* + * Negotiated version with the Host. + */ + +extern __u32 vmbus_proto_version; + #endif /* __KERNEL__ */ #endif /* _HYPERV_H */ -- cgit v1.2.1 From 1f42248d724a413baaafd5f83a8f4746bc6f51a5 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:42 -0800 Subject: Drivers: hv: Change the signature for hv_signal_event() In preparation for implementing a per-connection signaling framework, change the signature of the function hv_signal_event(). The current code uses a global handle for signaling the host. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 2 +- drivers/hv/hv.c | 7 +++---- drivers/hv/hyperv_vmbus.h | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 70ea5d1fc16c..3f8ce7be4c18 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -402,5 +402,5 @@ int vmbus_set_event(u32 child_relid) (unsigned long *)vmbus_connection.send_int_page + (child_relid >> 5)); - return hv_signal_event(); + return hv_signal_event(hv_context.signal_event_param); } diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 3648f8f0f368..dd0af8940657 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -273,13 +273,12 @@ int hv_post_message(union hv_connection_id connection_id, * * This involves a hypercall. */ -u16 hv_signal_event(void) +u16 hv_signal_event(void *con_id) { u16 status; - status = do_hypercall(HVCALL_SIGNAL_EVENT, - hv_context.signal_event_param, - NULL) & 0xFFFF; + status = (do_hypercall(HVCALL_SIGNAL_EVENT, con_id, NULL) & 0xFFFF); + return status; } diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index ac1e419d4b16..61d2c4f6fc17 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -538,7 +538,7 @@ extern int hv_post_message(union hv_connection_id connection_id, enum hv_message_type message_type, void *payload, size_t payload_size); -extern u16 hv_signal_event(void); +extern u16 hv_signal_event(void *con_id); extern void hv_synic_init(void *irqarg); -- cgit v1.2.1 From 21c3bef5db359596806f19fee6c3ec0c033881d0 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:43 -0800 Subject: Drivers: hv: Change the signature of vmbus_set_event() In preparation for supporting a per-connection signaling mechanism, change the signature of vmbus_set_event(). This change is also needed to implement other aspects of the signaling optimization. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 2 +- drivers/hv/connection.c | 3 ++- drivers/hv/hyperv_vmbus.h | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 727c5f1d6acf..70a34daa04c1 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -55,7 +55,7 @@ static void vmbus_setevent(struct vmbus_channel *channel) [channel->monitor_grp].pending); } else { - vmbus_set_event(channel->offermsg.child_relid); + vmbus_set_event(channel); } } diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 3f8ce7be4c18..0d8b13290e93 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -395,8 +395,9 @@ int vmbus_post_msg(void *buffer, size_t buflen) /* * vmbus_set_event - Send an event notification to the parent */ -int vmbus_set_event(u32 child_relid) +int vmbus_set_event(struct vmbus_channel *channel) { + u32 child_relid = channel->offermsg.child_relid; /* Each u32 represents 32 channels */ sync_set_bit(child_relid & 31, (unsigned long *)vmbus_connection.send_int_page + diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 61d2c4f6fc17..cd48ac331708 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -660,7 +660,7 @@ int vmbus_connect(void); int vmbus_post_msg(void *buffer, size_t buflen); -int vmbus_set_event(u32 child_relid); +int vmbus_set_event(struct vmbus_channel *channel); void vmbus_on_event(unsigned long data); -- cgit v1.2.1 From eafa7072e7cd806dff42b705284ca26189e527a4 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:44 -0800 Subject: Drivers: hv: Move vmbus version definitions to hyperv.h To support version specific optimization in various vmbus drivers, move the vmbus definitions to the public header file. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 15 --------------- include/linux/hyperv.h | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 0d8b13290e93..56b14e57bdd8 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -40,21 +40,6 @@ struct vmbus_connection vmbus_connection = { .next_gpadl_handle = ATOMIC_INIT(0xE1E10), }; -/* - * VMBUS version is 32 bit entity broken up into - * two 16 bit quantities: major_number. minor_number. - * - * 0 . 13 (Windows Server 2008) - * 1 . 1 (Windows 7) - * 2 . 4 (Windows 8) - */ - -#define VERSION_WS2008 ((0 << 16) | (13)) -#define VERSION_WIN7 ((1 << 16) | (1)) -#define VERSION_WIN8 ((2 << 16) | (4)) - -#define VERSION_INVAL -1 - /* * Negotiated protocol version with the host. */ diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 134a2022a7a3..e72502689cdc 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -419,6 +419,21 @@ hv_get_ringbuffer_availbytes(struct hv_ring_buffer_info *rbi, */ #define HV_DRV_VERSION "3.1" +/* + * VMBUS version is 32 bit entity broken up into + * two 16 bit quantities: major_number. minor_number. + * + * 0 . 13 (Windows Server 2008) + * 1 . 1 (Windows 7) + * 2 . 4 (Windows 8) + */ + +#define VERSION_WS2008 ((0 << 16) | (13)) +#define VERSION_WIN7 ((1 << 16) | (1)) +#define VERSION_WIN8 ((2 << 16) | (4)) + +#define VERSION_INVAL -1 + /* Make maximum size of pipe payload of 16K */ #define MAX_PIPE_DATA_PAYLOAD (sizeof(u8) * 16384) -- cgit v1.2.1 From b3bf60c7b4665d40b8eae2217b54c4745f49f470 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:45 -0800 Subject: Drivers: hv: Manage signaling state on a per-connection basis The current code has a global handle for supporting signaling of the host from guest. Make this a per-channel attribute as on some versions of the host we can signal on per-channel handle. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 20 ++++++++++++++++++++ drivers/hv/hyperv_vmbus.h | 21 --------------------- include/linux/hyperv.h | 25 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 7bf59177aaf8..f4d990285d99 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -282,6 +282,26 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) */ newchannel->batched_reading = true; + /* + * Setup state for signalling the host. + */ + newchannel->sig_event = (struct hv_input_signal_event *) + (ALIGN((unsigned long) + &newchannel->sig_buf, + HV_HYPERCALL_PARAM_ALIGN)); + + newchannel->sig_event->connectionid.asu32 = 0; + newchannel->sig_event->connectionid.u.id = VMBUS_EVENT_CONNECTION_ID; + newchannel->sig_event->flag_number = 0; + newchannel->sig_event->rsvdz = 0; + + if (vmbus_proto_version != VERSION_WS2008) { + newchannel->is_dedicated_interrupt = + (offer->is_dedicated_interrupt != 0); + newchannel->sig_event->connectionid.u.id = + offer->connection_id; + } + memcpy(&newchannel->offermsg, offer, sizeof(struct vmbus_channel_offer_channel)); newchannel->monitor_grp = (u8)offer->monitorid / 32; diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index cd48ac331708..1bc7500fb84c 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -101,15 +101,6 @@ enum hv_message_type { /* Define invalid partition identifier. */ #define HV_PARTITION_ID_INVALID ((u64)0x0) -/* Define connection identifier type. */ -union hv_connection_id { - u32 asu32; - struct { - u32 id:24; - u32 reserved:8; - } u; -}; - /* Define port identifier type. */ union hv_port_id { u32 asu32; @@ -338,13 +329,6 @@ struct hv_input_post_message { u64 payload[HV_MESSAGE_PAYLOAD_QWORD_COUNT]; }; -/* Definition of the hv_signal_event hypercall input structure. */ -struct hv_input_signal_event { - union hv_connection_id connectionid; - u16 flag_number; - u16 rsvdz; -}; - /* * Versioning definitions used for guests reporting themselves to the * hypervisor, and visa versa. @@ -498,11 +482,6 @@ static const uuid_le VMBUS_SERVICE_ID = { -struct hv_input_signal_event_buffer { - u64 align8; - struct hv_input_signal_event event; -}; - struct hv_context { /* We only support running on top of Hyper-V * So at this point this really can only contain the Hyper-V ID diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index e72502689cdc..c6e2c44a1be9 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -897,6 +897,27 @@ struct vmbus_close_msg { struct vmbus_channel_close_channel msg; }; +/* Define connection identifier type. */ +union hv_connection_id { + u32 asu32; + struct { + u32 id:24; + u32 reserved:8; + } u; +}; + +/* Definition of the hv_signal_event hypercall input structure. */ +struct hv_input_signal_event { + union hv_connection_id connectionid; + u16 flag_number; + u16 rsvdz; +}; + +struct hv_input_signal_event_buffer { + u64 align8; + struct hv_input_signal_event event; +}; + struct vmbus_channel { struct list_head listentry; @@ -946,6 +967,10 @@ struct vmbus_channel { */ bool batched_reading; + + bool is_dedicated_interrupt; + struct hv_input_signal_event_buffer sig_buf; + struct hv_input_signal_event *sig_event; }; static inline void set_channel_read_state(struct vmbus_channel *c, bool state) -- cgit v1.2.1 From 3be77774017fc3c7dd0b434265dfa417aac23545 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:46 -0800 Subject: Drivers: hv: Cleanup vmbus_set_event() to support win7 and beyond On win7 (ws2008 R2) and beyond, we have the notion of having dedicated interrupts on a per-channel basis. When a channel has a dedicated interrupt assigned, there is no need to set the interrupt bit in the shared page. Implement this optimization. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 56b14e57bdd8..114050dc8e28 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -383,10 +383,13 @@ int vmbus_post_msg(void *buffer, size_t buflen) int vmbus_set_event(struct vmbus_channel *channel) { u32 child_relid = channel->offermsg.child_relid; - /* Each u32 represents 32 channels */ - sync_set_bit(child_relid & 31, - (unsigned long *)vmbus_connection.send_int_page + - (child_relid >> 5)); - return hv_signal_event(hv_context.signal_event_param); + if (!channel->is_dedicated_interrupt) { + /* Each u32 represents 32 channels */ + sync_set_bit(child_relid & 31, + (unsigned long *)vmbus_connection.send_int_page + + (child_relid >> 5)); + } + + return hv_signal_event(channel->sig_event); } -- cgit v1.2.1 From 917ea427c78670958488f7f304e4629c325969a4 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:47 -0800 Subject: Drivers: hv: Setup a mapping for Hyper-V's notion cpu ID On win8 (ws2012), incoming vmbus interrupt load can be spread across all available VCPUs in the guest. On a per-channel basis, the interrupts can be bound to specific CPUs. The Linux notion of cpu ID may be different from that of the hypervisor's. Setup a mapping structure. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv.c | 11 +++++++++++ drivers/hv/hyperv_vmbus.h | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index dd0af8940657..76304a622140 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -137,6 +137,8 @@ int hv_init(void) memset(hv_context.synic_event_page, 0, sizeof(void *) * NR_CPUS); memset(hv_context.synic_message_page, 0, sizeof(void *) * NR_CPUS); + memset(hv_context.vp_index, 0, + sizeof(int) * NR_CPUS); max_leaf = query_hypervisor_info(); @@ -296,6 +298,7 @@ void hv_synic_init(void *irqarg) union hv_synic_siefp siefp; union hv_synic_sint shared_sint; union hv_synic_scontrol sctrl; + u64 vp_index; u32 irq_vector = *((u32 *)(irqarg)); int cpu = smp_processor_id(); @@ -355,6 +358,14 @@ void hv_synic_init(void *irqarg) wrmsrl(HV_X64_MSR_SCONTROL, sctrl.as_uint64); hv_context.synic_initialized = true; + + /* + * Setup the mapping between Hyper-V's notion + * of cpuid and Linux' notion of cpuid. + * This array will be indexed using Linux cpuid. + */ + rdmsrl(HV_X64_MSR_VP_INDEX, vp_index); + hv_context.vp_index[cpu] = (u32)vp_index; return; cleanup: diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 1bc7500fb84c..6bbc197cbdfa 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -502,6 +502,16 @@ struct hv_context { void *synic_message_page[NR_CPUS]; void *synic_event_page[NR_CPUS]; + /* + * Hypervisor's notion of virtual processor ID is different from + * Linux' notion of CPU ID. This information can only be retrieved + * in the context of the calling CPU. Setup a map for easy access + * to this information: + * + * vp_index[a] is the Hyper-V's processor ID corresponding to + * Linux cpuid 'a'. + */ + u32 vp_index[NR_CPUS]; }; extern struct hv_context hv_context; -- cgit v1.2.1 From abbf3b2aa090b4a6bf22c935924b6467990266da Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:48 -0800 Subject: Drivers: hv: Add state to manage incoming channel interrupt load Add state to bind a channel to a specific VCPU. This will help us better distribute incoming interrupt load. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 2 +- drivers/hv/channel_mgmt.c | 2 ++ include/linux/hyperv.h | 21 +++++++++++++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 70a34daa04c1..9303252b2e19 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -181,7 +181,7 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size, open_msg->ringbuffer_gpadlhandle = newchannel->ringbuffer_gpadlhandle; open_msg->downstream_ringbuffer_pageoffset = send_ringbuffer_size >> PAGE_SHIFT; - open_msg->server_contextarea_gpadlhandle = 0; + open_msg->target_vp = newchannel->target_vp; if (userdatalen > MAX_USER_DEFINED_BYTES) { err = -EINVAL; diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index f4d990285d99..56ed45c74d01 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -302,6 +302,8 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) offer->connection_id; } + newchannel->target_vp = 0; + memcpy(&newchannel->offermsg, offer, sizeof(struct vmbus_channel_offer_channel)); newchannel->monitor_grp = (u8)offer->monitorid / 32; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index c6e2c44a1be9..8c3cb1fc34d4 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -732,8 +732,15 @@ struct vmbus_channel_open_channel { /* GPADL for the channel's ring buffer. */ u32 ringbuffer_gpadlhandle; - /* GPADL for the channel's server context save area. */ - u32 server_contextarea_gpadlhandle; + /* + * Starting with win8, this field will be used to specify + * the target virtual processor on which to deliver the interrupt for + * the host to guest communication. + * Prior to win8, incoming channel interrupts would only + * be delivered on cpu 0. Setting this value to 0 would + * preserve the earlier behavior. + */ + u32 target_vp; /* * The upstream ring buffer begins at offset zero in the memory @@ -971,6 +978,16 @@ struct vmbus_channel { bool is_dedicated_interrupt; struct hv_input_signal_event_buffer sig_buf; struct hv_input_signal_event *sig_event; + + /* + * Starting with win8, this field will be used to specify + * the target virtual processor on which to deliver the interrupt for + * the host to guest communication. + * Prior to win8, incoming channel interrupts would only + * be delivered on cpu 0. Setting this value to 0 would + * preserve the earlier behavior. + */ + u32 target_vp; }; static inline void set_channel_read_state(struct vmbus_channel *c, bool state) -- cgit v1.2.1 From 6552ecd70cc6f31f4bb9d63f36a7dd023db3e519 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:49 -0800 Subject: Drivers: hv: Modify the interrupt handling code to support win8 and beyond Starting with Win8 (WS2012), the event page can be used to directly get the channel ID that needs servicing. Modify the channel event handling code to take advantage of this feature. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 26 ++++++++++++++++++++++++-- drivers/hv/vmbus_drv.c | 27 ++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 114050dc8e28..ac71653abb77 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -321,10 +321,32 @@ static void process_chn_event(u32 relid) void vmbus_on_event(unsigned long data) { u32 dword; - u32 maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5; + u32 maxdword; int bit; u32 relid; - u32 *recv_int_page = vmbus_connection.recv_int_page; + u32 *recv_int_page = NULL; + void *page_addr; + int cpu = smp_processor_id(); + union hv_synic_event_flags *event; + + if ((vmbus_proto_version == VERSION_WS2008) || + (vmbus_proto_version == VERSION_WIN7)) { + maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5; + recv_int_page = vmbus_connection.recv_int_page; + } else { + /* + * When the host is win8 and beyond, the event page + * can be directly checked to get the id of the channel + * that has the interrupt pending. + */ + maxdword = HV_EVENT_FLAGS_DWORD_COUNT; + page_addr = hv_context.synic_event_page[cpu]; + event = (union hv_synic_event_flags *)page_addr + + VMBUS_MESSAGE_SINT; + recv_int_page = event->flags32; + } + + /* Check events */ if (!recv_int_page) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 8e1a9ec53003..c583a0403c53 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -460,15 +460,32 @@ static irqreturn_t vmbus_isr(int irq, void *dev_id) * Hyper-V, and the Windows team suggested we do the same. */ - page_addr = hv_context.synic_event_page[cpu]; - event = (union hv_synic_event_flags *)page_addr + VMBUS_MESSAGE_SINT; + if ((vmbus_proto_version == VERSION_WS2008) || + (vmbus_proto_version == VERSION_WIN7)) { - /* Since we are a child, we only need to check bit 0 */ - if (sync_test_and_clear_bit(0, (unsigned long *) &event->flags32[0])) { + page_addr = hv_context.synic_event_page[cpu]; + event = (union hv_synic_event_flags *)page_addr + + VMBUS_MESSAGE_SINT; + + /* Since we are a child, we only need to check bit 0 */ + if (sync_test_and_clear_bit(0, + (unsigned long *) &event->flags32[0])) { + handled = true; + } + } else { + /* + * Our host is win8 or above. The signaling mechanism + * has changed and we can directly look at the event page. + * If bit n is set then we have an interrup on the channel + * whose id is n. + */ handled = true; - tasklet_schedule(&event_dpc); } + if (handled) + tasklet_schedule(&event_dpc); + + page_addr = hv_context.synic_message_page[cpu]; msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT; -- cgit v1.2.1 From a119845f6e98c890831bb5639cdad5ca9b79965f Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:50 -0800 Subject: Drivers: hv: Add code to distribute channel interrupt load Implement a simple policy for distributing incoming interrupt load. We classify channels as (a) performance critical and (b) not performance critical. All non-performance critical channels will be bound to the boot cpu. Performance critical channels will be bound to the remaining available CPUs on a round-robin basis. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 56ed45c74d01..c80fe6245f5b 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -257,6 +257,89 @@ static void vmbus_process_offer(struct work_struct *work) } } +enum { + IDE = 0, + SCSI, + NIC, + MAX_PERF_CHN, +}; + +/* + * This is an array of channels (devices) that are performance critical. + * We attempt to distribute the interrupt load for these devices across + * all available CPUs. + */ +static const uuid_le hp_devs[] = { + /* {32412632-86cb-44a2-9b5c-50d1417354f5} */ + /* IDE */ + { + .b = { + 0x32, 0x26, 0x41, 0x32, 0xcb, 0x86, 0xa2, 0x44, + 0x9b, 0x5c, 0x50, 0xd1, 0x41, 0x73, 0x54, 0xf5 + } + }, + /* {ba6163d9-04a1-4d29-b605-72e2ffb1dc7f} */ + /* Storage - SCSI */ + { + .b = { + 0xd9, 0x63, 0x61, 0xba, 0xa1, 0x04, 0x29, 0x4d, + 0xb6, 0x05, 0x72, 0xe2, 0xff, 0xb1, 0xdc, 0x7f + } + }, + /* {F8615163-DF3E-46c5-913F-F2D2F965ED0E} */ + /* Network */ + { + .b = { + 0x63, 0x51, 0x61, 0xF8, 0x3E, 0xDF, 0xc5, 0x46, + 0x91, 0x3F, 0xF2, 0xD2, 0xF9, 0x65, 0xED, 0x0E + } + }, + +}; + + +/* + * We use this state to statically distribute the channel interrupt load. + */ +static u32 next_vp; + +/* + * Starting with Win8, we can statically distribute the incoming + * channel interrupt load by binding a channel to VCPU. We + * implement here a simple round robin scheme for distributing + * the interrupt load. + * We will bind channels that are not performance critical to cpu 0 and + * performance critical channels (IDE, SCSI and Network) will be uniformly + * distributed across all available CPUs. + */ +static u32 get_vp_index(uuid_le *type_guid) +{ + u32 cur_cpu; + int i; + bool perf_chn = false; + u32 max_cpus = num_online_cpus(); + + for (i = IDE; i < MAX_PERF_CHN; i++) { + if (!memcmp(type_guid->b, hp_devs[i].b, + sizeof(uuid_le))) { + perf_chn = true; + break; + } + } + if ((vmbus_proto_version == VERSION_WS2008) || + (vmbus_proto_version == VERSION_WIN7) || (!perf_chn)) { + /* + * Prior to win8, all channel interrupts are + * delivered on cpu 0. + * Also if the channel is not a performance critical + * channel, bind it to cpu 0. + */ + return 0; + } + cur_cpu = (++next_vp % max_cpus); + return hv_context.vp_index[cur_cpu]; +} + /* * vmbus_onoffer - Handler for channel offers from vmbus in parent partition. * @@ -302,7 +385,7 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) offer->connection_id; } - newchannel->target_vp = 0; + newchannel->target_vp = get_vp_index(&offer->offer.if_type); memcpy(&newchannel->offermsg, offer, sizeof(struct vmbus_channel_offer_channel)); -- cgit v1.2.1 From 9acd6442c6839d404c1f47dc0f3ff3e0fb13e44c Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:51 -0800 Subject: Drivers: hv: Get rid of the unused global signaling state Now that we have implemented a per-connection signaling mechanism, get rid of the global signaling state. For hosts that don't support per-connection signaling handle, we have moved the global state to be a per-channel state. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv.c | 24 ------------------------ drivers/hv/hyperv_vmbus.h | 8 -------- 2 files changed, 32 deletions(-) diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 76304a622140..e989c6fd1f8f 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -34,8 +34,6 @@ struct hv_context hv_context = { .synic_initialized = false, .hypercall_page = NULL, - .signal_event_param = NULL, - .signal_event_buffer = NULL, }; /* @@ -170,24 +168,6 @@ int hv_init(void) hv_context.hypercall_page = virtaddr; - /* Setup the global signal event param for the signal event hypercall */ - hv_context.signal_event_buffer = - kmalloc(sizeof(struct hv_input_signal_event_buffer), - GFP_KERNEL); - if (!hv_context.signal_event_buffer) - goto cleanup; - - hv_context.signal_event_param = - (struct hv_input_signal_event *) - (ALIGN((unsigned long) - hv_context.signal_event_buffer, - HV_HYPERCALL_PARAM_ALIGN)); - hv_context.signal_event_param->connectionid.asu32 = 0; - hv_context.signal_event_param->connectionid.u.id = - VMBUS_EVENT_CONNECTION_ID; - hv_context.signal_event_param->flag_number = 0; - hv_context.signal_event_param->rsvdz = 0; - return 0; cleanup: @@ -215,10 +195,6 @@ void hv_cleanup(void) /* Reset our OS id */ wrmsrl(HV_X64_MSR_GUEST_OS_ID, 0); - kfree(hv_context.signal_event_buffer); - hv_context.signal_event_buffer = NULL; - hv_context.signal_event_param = NULL; - if (hv_context.hypercall_page) { hypercall_msr.as_uint64 = 0; wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64); diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 6bbc197cbdfa..9135a6fcf3bd 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -492,14 +492,6 @@ struct hv_context { bool synic_initialized; - /* - * This is used as an input param to HvCallSignalEvent hypercall. The - * input param is immutable in our usage and must be dynamic mem (vs - * stack or global). */ - struct hv_input_signal_event_buffer *signal_event_buffer; - /* 8-bytes aligned of the buffer above */ - struct hv_input_signal_event *signal_event_param; - void *synic_message_page[NR_CPUS]; void *synic_event_page[NR_CPUS]; /* -- cgit v1.2.1 From 01986313289bd2e143d693f803c7675ea121a832 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:52 -0800 Subject: Drivers: hv: Get rid of unnecessary request for offers This call to seek offers is not necessary and just adds unnecessary delay. Get rid of it. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/vmbus_drv.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index c583a0403c53..4c92337ddae6 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -592,8 +592,6 @@ int __vmbus_driver_register(struct hv_driver *hv_driver, struct module *owner, c ret = driver_register(&hv_driver->driver); - vmbus_request_offers(); - return ret; } EXPORT_SYMBOL_GPL(__vmbus_driver_register); -- cgit v1.2.1 From db11f12a11c9f04d504510e1cc20775209b0e509 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:53 -0800 Subject: Drivers: hv: Manage event tasklets on per-cpu basis Now that we can potentially take vmbus interrupts on any CPU, make the tasklets per-CPU. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv.c | 12 ++++++++++++ drivers/hv/hyperv_vmbus.h | 6 ++++++ drivers/hv/vmbus_drv.c | 4 +--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index e989c6fd1f8f..363532add4c7 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "hyperv_vmbus.h" @@ -137,6 +138,8 @@ int hv_init(void) sizeof(void *) * NR_CPUS); memset(hv_context.vp_index, 0, sizeof(int) * NR_CPUS); + memset(hv_context.event_dpc, 0, + sizeof(void *) * NR_CPUS); max_leaf = query_hypervisor_info(); @@ -285,6 +288,15 @@ void hv_synic_init(void *irqarg) /* Check the version */ rdmsrl(HV_X64_MSR_SVERSION, version); + hv_context.event_dpc[cpu] = (struct tasklet_struct *) + kmalloc(sizeof(struct tasklet_struct), + GFP_ATOMIC); + if (hv_context.event_dpc[cpu] == NULL) { + pr_err("Unable to allocate event dpc\n"); + goto cleanup; + } + tasklet_init(hv_context.event_dpc[cpu], vmbus_on_event, cpu); + hv_context.synic_message_page[cpu] = (void *)get_zeroed_page(GFP_ATOMIC); diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 9135a6fcf3bd..becb106918d6 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -504,6 +504,12 @@ struct hv_context { * Linux cpuid 'a'. */ u32 vp_index[NR_CPUS]; + /* + * Starting with win8, we can take channel interrupts on any CPU; + * we will manage the tasklet that handles events on a per CPU + * basis. + */ + struct tasklet_struct *event_dpc[NR_CPUS]; }; extern struct hv_context hv_context; diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 4c92337ddae6..6e4f85720f26 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -41,7 +41,6 @@ static struct acpi_device *hv_acpi_dev; static struct tasklet_struct msg_dpc; -static struct tasklet_struct event_dpc; static struct completion probe_event; static int irq; @@ -483,7 +482,7 @@ static irqreturn_t vmbus_isr(int irq, void *dev_id) } if (handled) - tasklet_schedule(&event_dpc); + tasklet_schedule(hv_context.event_dpc[cpu]); page_addr = hv_context.synic_message_page[cpu]; @@ -523,7 +522,6 @@ static int vmbus_bus_init(int irq) } tasklet_init(&msg_dpc, vmbus_on_msg_dpc, 0); - tasklet_init(&event_dpc, vmbus_on_event, 0); ret = bus_register(&hv_bus); if (ret) -- cgit v1.2.1 From b0209501dc7586cbfbf6d023f2dd3ce4621aff2c Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:54 -0800 Subject: Drivers: hv: Handle vmbus interrupts concurrently on all cpus Vmbus interrupts are unique in that while the interrupt is delivered on a given vector, these can be handled concurrently on different CPUs. Handle the vmbus interrupts concurrently on all the CPUs. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv.c | 2 +- drivers/hv/vmbus_drv.c | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 363532add4c7..03e6a1eb1145 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -335,7 +335,7 @@ void hv_synic_init(void *irqarg) shared_sint.as_uint64 = 0; shared_sint.vector = irq_vector; /* HV_SHARED_SINT_IDT_VECTOR + 0x20; */ shared_sint.masked = false; - shared_sint.auto_eoi = false; + shared_sint.auto_eoi = true; wrmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, shared_sint.as_uint64); diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 6e4f85720f26..e066d418be97 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include "hyperv_vmbus.h" @@ -500,6 +501,19 @@ static irqreturn_t vmbus_isr(int irq, void *dev_id) return IRQ_NONE; } +/* + * vmbus interrupt flow handler: + * vmbus interrupts can concurrently occur on multiple CPUs and + * can be handled concurrently. + */ + +void vmbus_flow_handler(unsigned int irq, struct irq_desc *desc) +{ + kstat_incr_irqs_this_cpu(irq, desc); + + desc->action->handler(irq, desc->action->dev_id); +} + /* * vmbus_bus_init -Main vmbus driver initialization routine. * @@ -535,6 +549,13 @@ static int vmbus_bus_init(int irq) goto err_unregister; } + /* + * Vmbus interrupts can be handled concurrently on + * different CPUs. Establish an appropriate interrupt flow + * handler that can support this model. + */ + irq_set_handler(irq, vmbus_flow_handler); + vector = IRQ0_VECTOR + irq; /* -- cgit v1.2.1 From 5ab05951c586a2ed9e19a3ed8c437346bfabbfb7 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:55 -0800 Subject: Drivers: hv: Add a check to deal with spurious interrupts We establish the handler before we have fully initialized the VMBUS state. Deal with spurious interrupts. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/vmbus_drv.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index e066d418be97..797142c07fa0 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -454,6 +454,12 @@ static irqreturn_t vmbus_isr(int irq, void *dev_id) union hv_synic_event_flags *event; bool handled = false; + page_addr = hv_context.synic_event_page[cpu]; + if (page_addr == NULL) + return IRQ_NONE; + + event = (union hv_synic_event_flags *)page_addr + + VMBUS_MESSAGE_SINT; /* * Check for events before checking for messages. This is the order * in which events and messages are checked in Windows guests on @@ -463,10 +469,6 @@ static irqreturn_t vmbus_isr(int irq, void *dev_id) if ((vmbus_proto_version == VERSION_WS2008) || (vmbus_proto_version == VERSION_WIN7)) { - page_addr = hv_context.synic_event_page[cpu]; - event = (union hv_synic_event_flags *)page_addr + - VMBUS_MESSAGE_SINT; - /* Since we are a child, we only need to check bit 0 */ if (sync_test_and_clear_bit(0, (unsigned long *) &event->flags32[0])) { -- cgit v1.2.1 From 2a5c43a821b3b26e6af1cdb987b4daeba6f13a6f Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:56 -0800 Subject: Drivers: hv: Enable protocol negotiation with win8 hosts Now that we have implemented all of the Win8 (WS2012) functionality, negotiate Win8 protocol with the host. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 2 +- include/linux/hyperv.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index ac71653abb77..3965537a659d 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -194,7 +194,7 @@ int vmbus_connect(void) * version. */ - version = VERSION_WS2008; + version = VERSION_CURRENT; do { ret = vmbus_negotiate_version(msginfo, version); diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 8c3cb1fc34d4..5095b066df94 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -434,6 +434,7 @@ hv_get_ringbuffer_availbytes(struct hv_ring_buffer_info *rbi, #define VERSION_INVAL -1 +#define VERSION_CURRENT VERSION_WIN8 /* Make maximum size of pipe payload of 16K */ #define MAX_PIPE_DATA_PAYLOAD (sizeof(u8) * 16384) -- cgit v1.2.1 From c2b8e5202cf7670f918d0f7439ed2123cd58e1b7 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:57 -0800 Subject: Drivers: hv: Implement flow management on the send side Implement flow management on the send side. When the sender is blocked, the reader can potentially signal the sender to indicate there is now room to send. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 12 +++++++++-- drivers/hv/hyperv_vmbus.h | 2 +- drivers/hv/ring_buffer.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 9303252b2e19..064257e79f23 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -735,6 +735,7 @@ int vmbus_recvpacket(struct vmbus_channel *channel, void *buffer, u32 packetlen; u32 userlen; int ret; + bool signal = false; *buffer_actual_len = 0; *requestid = 0; @@ -761,8 +762,10 @@ int vmbus_recvpacket(struct vmbus_channel *channel, void *buffer, /* Copy over the packet to the user buffer */ ret = hv_ringbuffer_read(&channel->inbound, buffer, userlen, - (desc.offset8 << 3)); + (desc.offset8 << 3), &signal); + if (signal) + vmbus_setevent(channel); return 0; } @@ -779,6 +782,7 @@ int vmbus_recvpacket_raw(struct vmbus_channel *channel, void *buffer, u32 packetlen; u32 userlen; int ret; + bool signal = false; *buffer_actual_len = 0; *requestid = 0; @@ -805,7 +809,11 @@ int vmbus_recvpacket_raw(struct vmbus_channel *channel, void *buffer, *requestid = desc.trans_id; /* Copy over the entire packet to the user buffer */ - ret = hv_ringbuffer_read(&channel->inbound, buffer, packetlen, 0); + ret = hv_ringbuffer_read(&channel->inbound, buffer, packetlen, 0, + &signal); + + if (signal) + vmbus_setevent(channel); return 0; } diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index becb106918d6..ac111f223821 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -550,7 +550,7 @@ int hv_ringbuffer_peek(struct hv_ring_buffer_info *ring_info, void *buffer, int hv_ringbuffer_read(struct hv_ring_buffer_info *ring_info, void *buffer, u32 buflen, - u32 offset); + u32 offset, bool *signal); void hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info, diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 2a0babc95709..cafa72ffdc30 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -84,6 +84,50 @@ static bool hv_need_to_signal(u32 old_write, struct hv_ring_buffer_info *rbi) return false; } +/* + * To optimize the flow management on the send-side, + * when the sender is blocked because of lack of + * sufficient space in the ring buffer, potential the + * consumer of the ring buffer can signal the producer. + * This is controlled by the following parameters: + * + * 1. pending_send_sz: This is the size in bytes that the + * producer is trying to send. + * 2. The feature bit feat_pending_send_sz set to indicate if + * the consumer of the ring will signal when the ring + * state transitions from being full to a state where + * there is room for the producer to send the pending packet. + */ + +static bool hv_need_to_signal_on_read(u32 old_rd, + struct hv_ring_buffer_info *rbi) +{ + u32 prev_write_sz; + u32 cur_write_sz; + u32 r_size; + u32 write_loc = rbi->ring_buffer->write_index; + u32 read_loc = rbi->ring_buffer->read_index; + u32 pending_sz = rbi->ring_buffer->pending_send_sz; + + /* + * If the other end is not blocked on write don't bother. + */ + if (pending_sz == 0) + return false; + + r_size = rbi->ring_datasize; + cur_write_sz = write_loc >= read_loc ? r_size - (write_loc - read_loc) : + read_loc - write_loc; + + prev_write_sz = write_loc >= old_rd ? r_size - (write_loc - old_rd) : + old_rd - write_loc; + + + if ((prev_write_sz < pending_sz) && (cur_write_sz >= pending_sz)) + return true; + + return false; +} /* * hv_get_next_write_location() @@ -461,13 +505,14 @@ int hv_ringbuffer_peek(struct hv_ring_buffer_info *Inring_info, * */ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, void *buffer, - u32 buflen, u32 offset) + u32 buflen, u32 offset, bool *signal) { u32 bytes_avail_towrite; u32 bytes_avail_toread; u32 next_read_location = 0; u64 prev_indices = 0; unsigned long flags; + u32 old_read; if (buflen <= 0) return -EINVAL; @@ -478,6 +523,8 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, void *buffer, &bytes_avail_toread, &bytes_avail_towrite); + old_read = bytes_avail_toread; + /* Make sure there is something to read */ if (bytes_avail_toread < buflen) { spin_unlock_irqrestore(&inring_info->ring_lock, flags); @@ -508,5 +555,7 @@ int hv_ringbuffer_read(struct hv_ring_buffer_info *inring_info, void *buffer, spin_unlock_irqrestore(&inring_info->ring_lock, flags); + *signal = hv_need_to_signal_on_read(old_read, inring_info); + return 0; } -- cgit v1.2.1 From 5fbebb2d2095e5c7d289d5f4ffecc2f2661c584a Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:58 -0800 Subject: Drivers: hv: Capture the host build information Capture the host build information so it can be presented along with the negotiated vmbus version information. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv.c | 9 +++++++++ drivers/hv/hyperv_vmbus.h | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 03e6a1eb1145..0cd2da3b22f5 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -40,6 +40,11 @@ struct hv_context hv_context = { /* * query_hypervisor_info - Get version info of the windows hypervisor */ +unsigned int host_info_eax; +unsigned int host_info_ebx; +unsigned int host_info_ecx; +unsigned int host_info_edx; + static int query_hypervisor_info(void) { unsigned int eax; @@ -76,6 +81,10 @@ static int query_hypervisor_info(void) ecx, edx >> 24, edx & 0xFFFFFF); + host_info_eax = eax; + host_info_ebx = ebx; + host_info_ecx = ecx; + host_info_edx = edx; } return max_leaf; } diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index ac111f223821..12f2f9e989f7 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -531,6 +531,13 @@ extern void hv_synic_init(void *irqarg); extern void hv_synic_cleanup(void *arg); +/* + * Host version information. + */ +extern unsigned int host_info_eax; +extern unsigned int host_info_ebx; +extern unsigned int host_info_ecx; +extern unsigned int host_info_edx; /* Interface */ -- cgit v1.2.1 From 3bacaf0ce106d9caca75f64a58f3b938b070df29 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:59 -0800 Subject: Drivers: hv: Cleanup and consolidate reporting of build/version info Now, cleanup and consolidate reporting of host and vmbus version numbers. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 11 +++++++---- drivers/hv/hv.c | 7 ------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 3965537a659d..253a74ba245c 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -120,9 +120,6 @@ static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, if (msginfo->response.version_response.version_supported) { vmbus_connection.conn_state = CONNECTED; } else { - pr_err("Unable to connect, " - "Version %d not supported by Hyper-V\n", - version); return -ECONNREFUSED; } @@ -208,11 +205,17 @@ int vmbus_connect(void) goto cleanup; vmbus_proto_version = version; - pr_info("Negotiated host information %d\n", version); + pr_info("Hyper-V Host Build:%d-%d.%d-%d-%d.%d; Vmbus version:%d.%d\n", + host_info_eax, host_info_ebx >> 16, + host_info_ebx & 0xFFFF, host_info_ecx, + host_info_edx >> 24, host_info_edx & 0xFFFFFF, + version >> 16, version & 0xFFFF); + kfree(msginfo); return 0; cleanup: + pr_err("Unable to connect to host\n"); vmbus_connection.conn_state = DISCONNECTED; if (vmbus_connection.work_queue) diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index 0cd2da3b22f5..1c5481da6e4a 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -74,13 +74,6 @@ static int query_hypervisor_info(void) edx = 0; op = HVCPUID_VERSION; cpuid(op, &eax, &ebx, &ecx, &edx); - pr_info("Hyper-V Host OS Build:%d-%d.%d-%d-%d.%d\n", - eax, - ebx >> 16, - ebx & 0xFFFF, - ecx, - edx >> 24, - edx & 0xFFFFFF); host_info_eax = eax; host_info_ebx = ebx; host_info_ecx = ecx; -- cgit v1.2.1 From f994a154c5318d132200862686fde13dca0979b1 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Tue, 4 Dec 2012 00:15:42 -0500 Subject: Drivers: hv: remove unused variable in vmbus_recvpacket_raw() The variable userlen is initialized but never used otherwise, so remove the unused variable. Signed-off-by: Wei Yongjun Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 064257e79f23..0b122f8c7005 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -780,7 +780,6 @@ int vmbus_recvpacket_raw(struct vmbus_channel *channel, void *buffer, { struct vmpacket_descriptor desc; u32 packetlen; - u32 userlen; int ret; bool signal = false; @@ -795,7 +794,6 @@ int vmbus_recvpacket_raw(struct vmbus_channel *channel, void *buffer, packetlen = desc.len8 << 3; - userlen = packetlen - (desc.offset8 << 3); *buffer_actual_len = packetlen; -- cgit v1.2.1 From 00246d08be901b9ac27c3082bd066119b71b4679 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Sat, 5 Jan 2013 13:03:06 +0800 Subject: tools: hv: fix a typo in hv_set_ifconfig.sh Signed-off-by: Jason Wang Acked-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- tools/hv/hv_set_ifconfig.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/hv/hv_set_ifconfig.sh b/tools/hv/hv_set_ifconfig.sh index 3e9427e08d80..daf7ec0cd04e 100755 --- a/tools/hv/hv_set_ifconfig.sh +++ b/tools/hv/hv_set_ifconfig.sh @@ -65,4 +65,4 @@ cp $1 /etc/sysconfig/network-scripts/ interface=$(echo $1 | awk -F - '{ print $2 }') /sbin/ifdown $interface 2>/dev/null -/sbin/ifup $interfac 2>/dev/null +/sbin/ifup $interface 2>/dev/null -- cgit v1.2.1 From 0783d72fa4cd8aae7c3f30746c70f6f3a2507594 Mon Sep 17 00:00:00 2001 From: Tomas Hozza Date: Sun, 13 Jan 2013 22:27:40 +0100 Subject: tools: hv: Fix how ifcfg-* file is created Fix for the daemon code and for hv_set_ifconfig.sh script, so that the created ifcfg-* file is consistent with initscripts documentation. Signed-off-by: Tomas Hozza Signed-off-by: Greg Kroah-Hartman --- tools/hv/hv_kvp_daemon.c | 59 ++++++++++++++++++++++----------------------- tools/hv/hv_set_ifconfig.sh | 22 +++++++---------- 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index 0358ad26d2fd..384051745c5e 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c @@ -1162,16 +1162,13 @@ static int process_ip_string(FILE *f, char *ip_string, int type) snprintf(str, sizeof(str), "%s", "DNS"); break; } - if (i != 0) { - if (type != DNS) { - snprintf(sub_str, sizeof(sub_str), - "_%d", i++); - } else { - snprintf(sub_str, sizeof(sub_str), - "%d", ++i); - } - } else if (type == DNS) { + + if (type == DNS) { snprintf(sub_str, sizeof(sub_str), "%d", ++i); + } else if (type == GATEWAY && i == 0) { + ++i; + } else { + snprintf(sub_str, sizeof(sub_str), "%d", i++); } @@ -1191,17 +1188,13 @@ static int process_ip_string(FILE *f, char *ip_string, int type) snprintf(str, sizeof(str), "%s", "DNS"); break; } - if ((j != 0) || (type == DNS)) { - if (type != DNS) { - snprintf(sub_str, sizeof(sub_str), - "_%d", j++); - } else { - snprintf(sub_str, sizeof(sub_str), - "%d", ++i); - } - } else if (type == DNS) { - snprintf(sub_str, sizeof(sub_str), - "%d", ++i); + + if (type == DNS) { + snprintf(sub_str, sizeof(sub_str), "%d", ++i); + } else if (j == 0) { + ++j; + } else { + snprintf(sub_str, sizeof(sub_str), "_%d", j++); } } else { return HV_INVALIDARG; @@ -1244,18 +1237,19 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) * Here is the format of the ip configuration file: * * HWADDR=macaddr - * IF_NAME=interface name - * DHCP=yes (This is optional; if yes, DHCP is configured) + * DEVICE=interface name + * BOOTPROTO= (where is "dhcp" if DHCP is configured + * or "none" if no boot-time protocol should be used) * - * IPADDR=ipaddr1 - * IPADDR_1=ipaddr2 - * IPADDR_x=ipaddry (where y = x + 1) + * IPADDR0=ipaddr1 + * IPADDR1=ipaddr2 + * IPADDRx=ipaddry (where y = x + 1) * - * NETMASK=netmask1 - * NETMASK_x=netmasky (where y = x + 1) + * NETMASK0=netmask1 + * NETMASKx=netmasky (where y = x + 1) * * GATEWAY=ipaddr1 - * GATEWAY_x=ipaddry (where y = x + 1) + * GATEWAYx=ipaddry (where y = x + 1) * * DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc) * @@ -1294,12 +1288,12 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) if (error) goto setval_error; - error = kvp_write_file(file, "IF_NAME", "", if_name); + error = kvp_write_file(file, "DEVICE", "", if_name); if (error) goto setval_error; if (new_val->dhcp_enabled) { - error = kvp_write_file(file, "DHCP", "", "yes"); + error = kvp_write_file(file, "BOOTPROTO", "", "dhcp"); if (error) goto setval_error; @@ -1307,6 +1301,11 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) * We are done!. */ goto setval_done; + + } else { + error = kvp_write_file(file, "BOOTPROTO", "", "none"); + if (error) + goto setval_error; } /* diff --git a/tools/hv/hv_set_ifconfig.sh b/tools/hv/hv_set_ifconfig.sh index daf7ec0cd04e..735aafd64a3f 100755 --- a/tools/hv/hv_set_ifconfig.sh +++ b/tools/hv/hv_set_ifconfig.sh @@ -20,18 +20,19 @@ # Here is the format of the ip configuration file: # # HWADDR=macaddr -# IF_NAME=interface name -# DHCP=yes (This is optional; if yes, DHCP is configured) +# DEVICE=interface name +# BOOTPROTO= (where is "dhcp" if DHCP is configured +# or "none" if no boot-time protocol should be used) # -# IPADDR=ipaddr1 -# IPADDR_1=ipaddr2 -# IPADDR_x=ipaddry (where y = x + 1) +# IPADDR0=ipaddr1 +# IPADDR1=ipaddr2 +# IPADDRx=ipaddry (where y = x + 1) # -# NETMASK=netmask1 -# NETMASK_x=netmasky (where y = x + 1) +# NETMASK0=netmask1 +# NETMASKx=netmasky (where y = x + 1) # # GATEWAY=ipaddr1 -# GATEWAY_x=ipaddry (where y = x + 1) +# GATEWAYx=ipaddry (where y = x + 1) # # DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc) # @@ -53,11 +54,6 @@ echo "NM_CONTROLLED=no" >> $1 echo "PEERDNS=yes" >> $1 echo "ONBOOT=yes" >> $1 -dhcp=$(grep "DHCP" $1 2>/dev/null) -if [ "$dhcp" != "" ]; -then -echo "BOOTPROTO=dhcp" >> $1; -fi cp $1 /etc/sysconfig/network-scripts/ -- cgit v1.2.1 From 7353f85ce82baa363b0338ef4cb3745eb0686760 Mon Sep 17 00:00:00 2001 From: Sedat Dilek Date: Thu, 17 Jan 2013 19:54:15 +0100 Subject: mei: Fix some more kernel-doc typos in hw-me.c Signed-off-by: Sedat Dilek Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 93a2a56a5f2c..94b203ec9b1f 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -236,7 +236,7 @@ irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) /** * mei_hbuf_filled_slots - gets number of device filled buffer slots * - * @device: the device structure + * @dev: the device structure * * returns number of filled slots */ @@ -289,7 +289,7 @@ int mei_hbuf_empty_slots(struct mei_device *dev) * mei_write_message - writes a message to mei device. * * @dev: the device structure - * @hader: mei HECI header of message + * @header: mei HECI header of message * @buf: message payload will be written * * This function returns -EIO if write has failed -- cgit v1.2.1 From 36c286d5a45731d957d02c317ee2ce775e856765 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 14 Dec 2012 18:01:08 +0300 Subject: pcmcia: i82092: fix i82092aa_pci_remove() Smatch complains because the call to pci_set_drvdata(dev, &sockets[i].socket); is reading one step beyond the end of the sockets[] array. It will crash when we use it later. The only place which uses pci_get_drvdata() is i82092aa_pci_remove(). That function should loop through all the sockets and unregister them. Signed-off-by: Dan Carpenter Signed-off-by: Greg Kroah-Hartman --- drivers/pcmcia/i82092.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/pcmcia/i82092.c b/drivers/pcmcia/i82092.c index 3578e1ca97a0..519c4d6003a6 100644 --- a/drivers/pcmcia/i82092.c +++ b/drivers/pcmcia/i82092.c @@ -133,8 +133,6 @@ static int i82092aa_pci_probe(struct pci_dev *dev, const struct pci_device_id *i goto err_out_free_res; } - pci_set_drvdata(dev, &sockets[i].socket); - for (i = 0; idev; sockets[i].socket.ops = &i82092aa_operations; @@ -164,14 +162,14 @@ err_out_disable: static void i82092aa_pci_remove(struct pci_dev *dev) { - struct pcmcia_socket *socket = pci_get_drvdata(dev); + int i; enter("i82092aa_pci_remove"); free_irq(dev->irq, i82092aa_interrupt); - if (socket) - pcmcia_unregister_socket(socket); + for (i = 0; i < socket_count; i++) + pcmcia_unregister_socket(&sockets[i].socket); leave("i82092aa_pci_remove"); } -- cgit v1.2.1 From 811af9723859884f2f771f3174f3ddedab7c53b5 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Sun, 16 Dec 2012 22:00:50 +0100 Subject: pcmcia/vrc4171: Add missing spinlock init It doesn't seem this spinlock was properly initialized. This bug was introduced by commit 7a410e8d4d97457c8c381e2de9cdc7bd3306badc. Signed-off-by: Jean Delvare Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/pcmcia/vrc4171_card.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pcmcia/vrc4171_card.c b/drivers/pcmcia/vrc4171_card.c index 75806be344e5..d98a08612492 100644 --- a/drivers/pcmcia/vrc4171_card.c +++ b/drivers/pcmcia/vrc4171_card.c @@ -246,6 +246,7 @@ static int pccard_init(struct pcmcia_socket *sock) socket = &vrc4171_sockets[slot]; socket->csc_irq = search_nonuse_irq(); socket->io_irq = search_nonuse_irq(); + spin_lock_init(&socket->lock); return 0; } -- cgit v1.2.1 From bad7d9df274b03a0761913b6628fc7663ad3bfa6 Mon Sep 17 00:00:00 2001 From: Andy King Date: Thu, 10 Jan 2013 15:41:38 -0800 Subject: VMCI: Add PCI as a dependency Add PCI as a dependency to our build, since we always compile in the guest-side PCI device support. Reported-by: Randy Dunlap Signed-off-by: Andy King Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/vmw_vmci/Kconfig b/drivers/misc/vmw_vmci/Kconfig index 55015e75683c..39c2ecadb273 100644 --- a/drivers/misc/vmw_vmci/Kconfig +++ b/drivers/misc/vmw_vmci/Kconfig @@ -4,7 +4,7 @@ config VMWARE_VMCI tristate "VMware VMCI Driver" - depends on X86 + depends on X86 && PCI help This is VMware's Virtual Machine Communication Interface. It enables high-speed communication between host and guest in a virtual -- cgit v1.2.1 From 42281d20cdf94a9d2aae67ee019f8bcc390ebed6 Mon Sep 17 00:00:00 2001 From: Andy King Date: Thu, 10 Jan 2013 15:41:39 -0800 Subject: VMCI: Remove dependency on BLOCK I/O No need to bring in dm-mapper.h and along with it a dependency on BLOCK I/O just to use dm_div_up(). Just use the existing DIV_ROUND_UP(). Reported-by: Randy Dunlap Signed-off-by: Andy King Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_queue_pair.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c index 1123111ba1bf..da47e457e158 100644 --- a/drivers/misc/vmw_vmci/vmci_queue_pair.c +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c @@ -13,12 +13,16 @@ * for more details. */ -#include #include #include +#include #include +#include #include #include +#include +#include +#include #include #include @@ -246,9 +250,9 @@ static struct qp_list qp_guest_endpoints = { }; #define INVALID_VMCI_GUEST_MEM_ID 0 -#define QPE_NUM_PAGES(_QPE) ((u32) \ - (dm_div_up(_QPE.produce_size, PAGE_SIZE) + \ - dm_div_up(_QPE.consume_size, PAGE_SIZE) + 2)) +#define QPE_NUM_PAGES(_QPE) ((u32) \ + (DIV_ROUND_UP(_QPE.produce_size, PAGE_SIZE) + \ + DIV_ROUND_UP(_QPE.consume_size, PAGE_SIZE) + 2)) /* @@ -260,7 +264,7 @@ static void qp_free_queue(void *q, u64 size) struct vmci_queue *queue = q; if (queue) { - u64 i = dm_div_up(size, PAGE_SIZE); + u64 i = DIV_ROUND_UP(size, PAGE_SIZE); if (queue->kernel_if->mapped) { vunmap(queue->kernel_if->va); @@ -289,7 +293,7 @@ static void *qp_alloc_queue(u64 size, u32 flags) u64 i; struct vmci_queue *queue; struct vmci_queue_header *q_header; - const u64 num_data_pages = dm_div_up(size, PAGE_SIZE); + const u64 num_data_pages = DIV_ROUND_UP(size, PAGE_SIZE); const uint queue_size = PAGE_SIZE + sizeof(*queue) + sizeof(*(queue->kernel_if)) + @@ -611,7 +615,7 @@ static int qp_memcpy_from_queue_iov(void *dest, static struct vmci_queue *qp_host_alloc_queue(u64 size) { struct vmci_queue *queue; - const size_t num_pages = dm_div_up(size, PAGE_SIZE) + 1; + const size_t num_pages = DIV_ROUND_UP(size, PAGE_SIZE) + 1; const size_t queue_size = sizeof(*queue) + sizeof(*(queue->kernel_if)); const size_t queue_page_size = num_pages * sizeof(*queue->kernel_if->page); @@ -963,8 +967,8 @@ qp_guest_endpoint_create(struct vmci_handle handle, int result; struct qp_guest_endpoint *entry; /* One page each for the queue headers. */ - const u64 num_ppns = dm_div_up(produce_size, PAGE_SIZE) + - dm_div_up(consume_size, PAGE_SIZE) + 2; + const u64 num_ppns = DIV_ROUND_UP(produce_size, PAGE_SIZE) + + DIV_ROUND_UP(consume_size, PAGE_SIZE) + 2; if (vmci_handle_is_invalid(handle)) { u32 context_id = vmci_get_context_id(); @@ -1175,9 +1179,9 @@ static int qp_alloc_guest_work(struct vmci_handle *handle, u32 priv_flags) { const u64 num_produce_pages = - dm_div_up(produce_size, PAGE_SIZE) + 1; + DIV_ROUND_UP(produce_size, PAGE_SIZE) + 1; const u64 num_consume_pages = - dm_div_up(consume_size, PAGE_SIZE) + 1; + DIV_ROUND_UP(consume_size, PAGE_SIZE) + 1; void *my_produce_q = NULL; void *my_consume_q = NULL; int result; @@ -1456,7 +1460,7 @@ static int qp_broker_create(struct vmci_handle handle, entry->state = VMCIQPB_CREATED_MEM; entry->produce_q->q_header = entry->local_mem; tmp = (u8 *)entry->local_mem + PAGE_SIZE * - (dm_div_up(entry->qp.produce_size, PAGE_SIZE) + 1); + (DIV_ROUND_UP(entry->qp.produce_size, PAGE_SIZE) + 1); entry->consume_q->q_header = (struct vmci_queue_header *)tmp; } else if (page_store) { /* -- cgit v1.2.1 From 32b083a3fd1452b9d5aba8e781ca95b566f3e054 Mon Sep 17 00:00:00 2001 From: Andy King Date: Thu, 10 Jan 2013 15:41:40 -0800 Subject: VMCI: Fix deref before NULL-check of queuepair ptr Check for a valid queuepair ptr before trying to lock the queuepair (which will deref it). Reported-by: Dan Carpenter Signed-off-by: Andy King Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_queue_pair.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c index da47e457e158..6417a26df8d8 100644 --- a/drivers/misc/vmw_vmci/vmci_queue_pair.c +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c @@ -3355,11 +3355,11 @@ ssize_t vmci_qpair_dequev(struct vmci_qp *qpair, { ssize_t result; - qp_lock(qpair); - if (!qpair || !iov) return VMCI_ERROR_INVALID_ARGS; + qp_lock(qpair); + do { result = qp_dequeue_locked(qpair->produce_q, qpair->consume_q, -- cgit v1.2.1 From 0e7894dc82bd9d4d82c16d16189f58da6d41d018 Mon Sep 17 00:00:00 2001 From: Andy King Date: Thu, 10 Jan 2013 15:41:41 -0800 Subject: VMCI: Fix "always true condition" vmci_send_datagram() returns an int, with negative values indicating failure. But we store it locally in a u32, which makes comparison of >= 0 useless. Fixed to use an int. Reported-by: Dan Carpenter Signed-off-by: Andy King Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_guest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/vmw_vmci/vmci_guest.c b/drivers/misc/vmw_vmci/vmci_guest.c index d302c89d2bfa..d7df6cfed28a 100644 --- a/drivers/misc/vmw_vmci/vmci_guest.c +++ b/drivers/misc/vmw_vmci/vmci_guest.c @@ -78,7 +78,7 @@ bool vmci_guest_code_active(void) u32 vmci_get_vm_context_id(void) { if (vm_context_id == VMCI_INVALID_ID) { - u32 result; + int result; struct vmci_datagram get_cid_msg; get_cid_msg.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, -- cgit v1.2.1 From e6389a13e42d5388ec044ce8da1b3bcfa39598ef Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 10 Jan 2013 15:41:42 -0800 Subject: VMCI: rename PPNset to ppn_set to avoid camel case Acked-by: Andy King Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_queue_pair.c | 8 ++++---- drivers/misc/vmw_vmci/vmci_queue_pair.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c index 6417a26df8d8..ef81fec0fcb4 100644 --- a/drivers/misc/vmw_vmci/vmci_queue_pair.c +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c @@ -231,7 +231,7 @@ struct qp_guest_endpoint { u64 num_ppns; void *produce_q; void *consume_q; - struct PPNSet ppn_set; + struct ppn_set ppn_set; }; struct qp_list { @@ -461,7 +461,7 @@ static int __qp_memcpy_from_queue(void *dest, static int qp_alloc_ppn_set(void *prod_q, u64 num_produce_pages, void *cons_q, - u64 num_consume_pages, struct PPNSet *ppn_set) + u64 num_consume_pages, struct ppn_set *ppn_set) { u32 *produce_ppns; u32 *consume_ppns; @@ -532,7 +532,7 @@ static int qp_alloc_ppn_set(void *prod_q, /* * Frees the two list of PPNs for a queue pair. */ -static void qp_free_ppn_set(struct PPNSet *ppn_set) +static void qp_free_ppn_set(struct ppn_set *ppn_set) { if (ppn_set->initialized) { /* Do not call these functions on NULL inputs. */ @@ -546,7 +546,7 @@ static void qp_free_ppn_set(struct PPNSet *ppn_set) * Populates the list of PPNs in the hypercall structure with the PPNS * of the produce queue and the consume queue. */ -static int qp_populate_ppn_set(u8 *call_buf, const struct PPNSet *ppn_set) +static int qp_populate_ppn_set(u8 *call_buf, const struct ppn_set *ppn_set) { memcpy(call_buf, ppn_set->produce_ppns, ppn_set->num_produce_pages * sizeof(*ppn_set->produce_ppns)); diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.h b/drivers/misc/vmw_vmci/vmci_queue_pair.h index 8d8d6a1170c3..58c6959f6b6d 100644 --- a/drivers/misc/vmw_vmci/vmci_queue_pair.h +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.h @@ -25,7 +25,7 @@ typedef int (*vmci_event_release_cb) (void *client_data); /* Guest device port I/O. */ -struct PPNSet { +struct ppn_set { u64 num_produce_pages; u64 num_consume_pages; u32 *produce_ppns; -- cgit v1.2.1 From ea8a83a4b718f78a8ea2ce3f0237e78a23f8f12b Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 10 Jan 2013 15:41:43 -0800 Subject: VMCI: include slab.h into files using kmalloc/kfree Do not rely on implicit header dependencies as they are known to break. Reported-by: Randy Dunlap Acked-by: Andy King Signed-off-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_guest.c | 3 +++ drivers/misc/vmw_vmci/vmci_host.c | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/misc/vmw_vmci/vmci_guest.c b/drivers/misc/vmw_vmci/vmci_guest.c index d7df6cfed28a..de1a90be160a 100644 --- a/drivers/misc/vmw_vmci/vmci_guest.c +++ b/drivers/misc/vmw_vmci/vmci_guest.c @@ -19,12 +19,15 @@ #include #include #include +#include #include #include +#include #include #include #include #include +#include #include "vmci_datagram.h" #include "vmci_doorbell.h" diff --git a/drivers/misc/vmw_vmci/vmci_host.c b/drivers/misc/vmw_vmci/vmci_host.c index 16e7f547555f..d4722b3dc8ec 100644 --- a/drivers/misc/vmw_vmci/vmci_host.c +++ b/drivers/misc/vmw_vmci/vmci_host.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include -- cgit v1.2.1 From fce8a7bb5b4bfb8a27324703fd5b002ee9247e90 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Fri, 16 Nov 2012 19:27:12 -0700 Subject: PCI-Express Non-Transparent Bridge Support A PCI-Express non-transparent bridge (NTB) is a point-to-point PCIe bus connecting 2 systems, providing electrical isolation between the two subsystems. A non-transparent bridge is functionally similar to a transparent bridge except that both sides of the bridge have their own independent address domains. The host on one side of the bridge will not have the visibility of the complete memory or I/O space on the other side of the bridge. To communicate across the non-transparent bridge, each NTB endpoint has one (or more) apertures exposed to the local system. Writes to these apertures are mirrored to memory on the remote system. Communications can also occur through the use of doorbell registers that initiate interrupts to the alternate domain, and scratch-pad registers accessible from both sides. The NTB device driver is needed to configure these memory windows, doorbell, and scratch-pad registers as well as use them in such a way as they can be turned into a viable communication channel to the remote system. ntb_hw.[ch] determines the usage model (NTB to NTB or NTB to Root Port) and abstracts away the underlying hardware to provide access and a common interface to the doorbell registers, scratch pads, and memory windows. These hardware interfaces are exported so that other, non-mainlined kernel drivers can access these. ntb_transport.[ch] also uses the exported interfaces in ntb_hw.[ch] to setup a communication channel(s) and provide a reliable way of transferring data from one side to the other, which it then exports so that "client" drivers can access them. These client drivers are used to provide a standard kernel interface (i.e., Ethernet device) to NTB, such that Linux can transfer data from one system to the other in a standard way. Signed-off-by: Jon Mason Reviewed-by: Nicholas Bellinger Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 6 + drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/ntb/Kconfig | 13 + drivers/ntb/Makefile | 3 + drivers/ntb/ntb_hw.c | 1157 +++++++++++++++++++++++++++++++++++ drivers/ntb/ntb_hw.h | 181 ++++++ drivers/ntb/ntb_regs.h | 139 +++++ drivers/ntb/ntb_transport.c | 1427 +++++++++++++++++++++++++++++++++++++++++++ include/linux/ntb.h | 83 +++ 10 files changed, 3012 insertions(+) create mode 100644 drivers/ntb/Kconfig create mode 100644 drivers/ntb/Makefile create mode 100644 drivers/ntb/ntb_hw.c create mode 100644 drivers/ntb/ntb_hw.h create mode 100644 drivers/ntb/ntb_regs.h create mode 100644 drivers/ntb/ntb_transport.c create mode 100644 include/linux/ntb.h diff --git a/MAINTAINERS b/MAINTAINERS index 915564eda145..c5d675a8c53d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5394,6 +5394,12 @@ S: Maintained F: Documentation/scsi/NinjaSCSI.txt F: drivers/scsi/nsp32* +NTB DRIVER +M: Jon Mason +S: Supported +F: drivers/ntb/ +F: include/linux/ntb.h + NTFS FILESYSTEM M: Anton Altaparmakov L: linux-ntfs-dev@lists.sourceforge.net diff --git a/drivers/Kconfig b/drivers/Kconfig index f5fb0722a63a..c13044cb8aa9 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -150,6 +150,8 @@ source "drivers/memory/Kconfig" source "drivers/iio/Kconfig" +source "drivers/ntb/Kconfig" + source "drivers/vme/Kconfig" source "drivers/pwm/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 7863b9fee50b..3d92e1269672 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -146,3 +146,4 @@ obj-$(CONFIG_MEMORY) += memory/ obj-$(CONFIG_IIO) += iio/ obj-$(CONFIG_VME_BUS) += vme/ obj-$(CONFIG_IPACK_BUS) += ipack/ +obj-$(CONFIG_NTB) += ntb/ diff --git a/drivers/ntb/Kconfig b/drivers/ntb/Kconfig new file mode 100644 index 000000000000..f69df793dbe2 --- /dev/null +++ b/drivers/ntb/Kconfig @@ -0,0 +1,13 @@ +config NTB + tristate "Intel Non-Transparent Bridge support" + depends on PCI + depends on X86 + help + The PCI-E Non-transparent bridge hardware is a point-to-point PCI-E bus + connecting 2 systems. When configured, writes to the device's PCI + mapped memory will be mirrored to a buffer on the remote system. The + ntb Linux driver uses this point-to-point communication as a method to + transfer data from one system to the other. + + If unsure, say N. + diff --git a/drivers/ntb/Makefile b/drivers/ntb/Makefile new file mode 100644 index 000000000000..15cb59fd354e --- /dev/null +++ b/drivers/ntb/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_NTB) += ntb.o + +ntb-objs := ntb_hw.o ntb_transport.o diff --git a/drivers/ntb/ntb_hw.c b/drivers/ntb/ntb_hw.c new file mode 100644 index 000000000000..facad51fbc7a --- /dev/null +++ b/drivers/ntb/ntb_hw.c @@ -0,0 +1,1157 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * BSD LICENSE + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe NTB Linux driver + * + * Contact Information: + * Jon Mason + */ +#include +#include +#include +#include +#include +#include +#include "ntb_hw.h" +#include "ntb_regs.h" + +#define NTB_NAME "Intel(R) PCI-E Non-Transparent Bridge Driver" +#define NTB_VER "0.24" + +MODULE_DESCRIPTION(NTB_NAME); +MODULE_VERSION(NTB_VER); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Intel Corporation"); + +enum { + NTB_CONN_CLASSIC = 0, + NTB_CONN_B2B, + NTB_CONN_RP, +}; + +enum { + NTB_DEV_USD = 0, + NTB_DEV_DSD, +}; + +enum { + SNB_HW = 0, + BWD_HW, +}; + +/* Translate memory window 0,1 to BAR 2,4 */ +#define MW_TO_BAR(mw) (mw * 2 + 2) + +static DEFINE_PCI_DEVICE_TABLE(ntb_pci_tbl) = { + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_B2B_BWD)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_B2B_JSF)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_CLASSIC_JSF)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_RP_JSF)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_RP_SNB)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_B2B_SNB)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_CLASSIC_SNB)}, + {0} +}; +MODULE_DEVICE_TABLE(pci, ntb_pci_tbl); + +/** + * ntb_register_event_callback() - register event callback + * @ndev: pointer to ntb_device instance + * @func: callback function to register + * + * This function registers a callback for any HW driver events such as link + * up/down, power management notices and etc. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_register_event_callback(struct ntb_device *ndev, + void (*func)(void *handle, unsigned int event)) +{ + if (ndev->event_cb) + return -EINVAL; + + ndev->event_cb = func; + + return 0; +} + +/** + * ntb_unregister_event_callback() - unregisters the event callback + * @ndev: pointer to ntb_device instance + * + * This function unregisters the existing callback from transport + */ +void ntb_unregister_event_callback(struct ntb_device *ndev) +{ + ndev->event_cb = NULL; +} + +/** + * ntb_register_db_callback() - register a callback for doorbell interrupt + * @ndev: pointer to ntb_device instance + * @idx: doorbell index to register callback, zero based + * @func: callback function to register + * + * This function registers a callback function for the doorbell interrupt + * on the primary side. The function will unmask the doorbell as well to + * allow interrupt. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_register_db_callback(struct ntb_device *ndev, unsigned int idx, + void *data, void (*func)(void *data, int db_num)) +{ + unsigned long mask; + + if (idx >= ndev->max_cbs || ndev->db_cb[idx].callback) { + dev_warn(&ndev->pdev->dev, "Invalid Index.\n"); + return -EINVAL; + } + + ndev->db_cb[idx].callback = func; + ndev->db_cb[idx].data = data; + + /* unmask interrupt */ + mask = readw(ndev->reg_ofs.pdb_mask); + clear_bit(idx * ndev->bits_per_vector, &mask); + writew(mask, ndev->reg_ofs.pdb_mask); + + return 0; +} + +/** + * ntb_unregister_db_callback() - unregister a callback for doorbell interrupt + * @ndev: pointer to ntb_device instance + * @idx: doorbell index to register callback, zero based + * + * This function unregisters a callback function for the doorbell interrupt + * on the primary side. The function will also mask the said doorbell. + */ +void ntb_unregister_db_callback(struct ntb_device *ndev, unsigned int idx) +{ + unsigned long mask; + + if (idx >= ndev->max_cbs || !ndev->db_cb[idx].callback) + return; + + mask = readw(ndev->reg_ofs.pdb_mask); + set_bit(idx * ndev->bits_per_vector, &mask); + writew(mask, ndev->reg_ofs.pdb_mask); + + ndev->db_cb[idx].callback = NULL; +} + +/** + * ntb_find_transport() - find the transport pointer + * @transport: pointer to pci device + * + * Given the pci device pointer, return the transport pointer passed in when + * the transport attached when it was inited. + * + * RETURNS: pointer to transport. + */ +void *ntb_find_transport(struct pci_dev *pdev) +{ + struct ntb_device *ndev = pci_get_drvdata(pdev); + return ndev->ntb_transport; +} + +/** + * ntb_register_transport() - Register NTB transport with NTB HW driver + * @transport: transport identifier + * + * This function allows a transport to reserve the hardware driver for + * NTB usage. + * + * RETURNS: pointer to ntb_device, NULL on error. + */ +struct ntb_device *ntb_register_transport(struct pci_dev *pdev, void *transport) +{ + struct ntb_device *ndev = pci_get_drvdata(pdev); + + if (ndev->ntb_transport) + return NULL; + + ndev->ntb_transport = transport; + return ndev; +} + +/** + * ntb_unregister_transport() - Unregister the transport with the NTB HW driver + * @ndev - ntb_device of the transport to be freed + * + * This function unregisters the transport from the HW driver and performs any + * necessary cleanups. + */ +void ntb_unregister_transport(struct ntb_device *ndev) +{ + int i; + + if (!ndev->ntb_transport) + return; + + for (i = 0; i < ndev->max_cbs; i++) + ntb_unregister_db_callback(ndev, i); + + ntb_unregister_event_callback(ndev); + ndev->ntb_transport = NULL; +} + +/** + * ntb_get_max_spads() - get the total scratch regs usable + * @ndev: pointer to ntb_device instance + * + * This function returns the max 32bit scratchpad registers usable by the + * upper layer. + * + * RETURNS: total number of scratch pad registers available + */ +int ntb_get_max_spads(struct ntb_device *ndev) +{ + return ndev->limits.max_spads; +} + +/** + * ntb_write_local_spad() - write to the secondary scratchpad register + * @ndev: pointer to ntb_device instance + * @idx: index to the scratchpad register, 0 based + * @val: the data value to put into the register + * + * This function allows writing of a 32bit value to the indexed scratchpad + * register. This writes over the data mirrored to the local scratchpad register + * by the remote system. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_write_local_spad(struct ntb_device *ndev, unsigned int idx, u32 val) +{ + if (idx >= ndev->limits.max_spads) + return -EINVAL; + + dev_dbg(&ndev->pdev->dev, "Writing %x to local scratch pad index %d\n", + val, idx); + writel(val, ndev->reg_ofs.spad_read + idx * 4); + + return 0; +} + +/** + * ntb_read_local_spad() - read from the primary scratchpad register + * @ndev: pointer to ntb_device instance + * @idx: index to scratchpad register, 0 based + * @val: pointer to 32bit integer for storing the register value + * + * This function allows reading of the 32bit scratchpad register on + * the primary (internal) side. This allows the local system to read data + * written and mirrored to the scratchpad register by the remote system. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_read_local_spad(struct ntb_device *ndev, unsigned int idx, u32 *val) +{ + if (idx >= ndev->limits.max_spads) + return -EINVAL; + + *val = readl(ndev->reg_ofs.spad_write + idx * 4); + dev_dbg(&ndev->pdev->dev, + "Reading %x from local scratch pad index %d\n", *val, idx); + + return 0; +} + +/** + * ntb_write_remote_spad() - write to the secondary scratchpad register + * @ndev: pointer to ntb_device instance + * @idx: index to the scratchpad register, 0 based + * @val: the data value to put into the register + * + * This function allows writing of a 32bit value to the indexed scratchpad + * register. The register resides on the secondary (external) side. This allows + * the local system to write data to be mirrored to the remote systems + * scratchpad register. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_write_remote_spad(struct ntb_device *ndev, unsigned int idx, u32 val) +{ + if (idx >= ndev->limits.max_spads) + return -EINVAL; + + dev_dbg(&ndev->pdev->dev, "Writing %x to remote scratch pad index %d\n", + val, idx); + writel(val, ndev->reg_ofs.spad_write + idx * 4); + + return 0; +} + +/** + * ntb_read_remote_spad() - read from the primary scratchpad register + * @ndev: pointer to ntb_device instance + * @idx: index to scratchpad register, 0 based + * @val: pointer to 32bit integer for storing the register value + * + * This function allows reading of the 32bit scratchpad register on + * the primary (internal) side. This alloows the local system to read the data + * it wrote to be mirrored on the remote system. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_read_remote_spad(struct ntb_device *ndev, unsigned int idx, u32 *val) +{ + if (idx >= ndev->limits.max_spads) + return -EINVAL; + + *val = readl(ndev->reg_ofs.spad_read + idx * 4); + dev_dbg(&ndev->pdev->dev, + "Reading %x from remote scratch pad index %d\n", *val, idx); + + return 0; +} + +/** + * ntb_get_mw_vbase() - get virtual addr for the NTB memory window + * @ndev: pointer to ntb_device instance + * @mw: memory window number + * + * This function provides the base virtual address of the memory window + * specified. + * + * RETURNS: pointer to virtual address, or NULL on error. + */ +void *ntb_get_mw_vbase(struct ntb_device *ndev, unsigned int mw) +{ + if (mw > NTB_NUM_MW) + return NULL; + + return ndev->mw[mw].vbase; +} + +/** + * ntb_get_mw_size() - return size of NTB memory window + * @ndev: pointer to ntb_device instance + * @mw: memory window number + * + * This function provides the physical size of the memory window specified + * + * RETURNS: the size of the memory window or zero on error + */ +resource_size_t ntb_get_mw_size(struct ntb_device *ndev, unsigned int mw) +{ + if (mw > NTB_NUM_MW) + return 0; + + return ndev->mw[mw].bar_sz; +} + +/** + * ntb_set_mw_addr - set the memory window address + * @ndev: pointer to ntb_device instance + * @mw: memory window number + * @addr: base address for data + * + * This function sets the base physical address of the memory window. This + * memory address is where data from the remote system will be transfered into + * or out of depending on how the transport is configured. + */ +void ntb_set_mw_addr(struct ntb_device *ndev, unsigned int mw, u64 addr) +{ + if (mw > NTB_NUM_MW) + return; + + dev_dbg(&ndev->pdev->dev, "Writing addr %Lx to BAR %d\n", addr, + MW_TO_BAR(mw)); + + ndev->mw[mw].phys_addr = addr; + + switch (MW_TO_BAR(mw)) { + case NTB_BAR_23: + writeq(addr, ndev->reg_ofs.sbar2_xlat); + break; + case NTB_BAR_45: + writeq(addr, ndev->reg_ofs.sbar4_xlat); + break; + } +} + +/** + * ntb_ring_sdb() - Set the doorbell on the secondary/external side + * @ndev: pointer to ntb_device instance + * @db: doorbell to ring + * + * This function allows triggering of a doorbell on the secondary/external + * side that will initiate an interrupt on the remote host + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +void ntb_ring_sdb(struct ntb_device *ndev, unsigned int db) +{ + dev_dbg(&ndev->pdev->dev, "%s: ringing doorbell %d\n", __func__, db); + + if (ndev->hw_type == BWD_HW) + writeq((u64) 1 << db, ndev->reg_ofs.sdb); + else + writew(((1 << ndev->bits_per_vector) - 1) << + (db * ndev->bits_per_vector), ndev->reg_ofs.sdb); +} + +static void ntb_link_event(struct ntb_device *ndev, int link_state) +{ + unsigned int event; + + if (ndev->link_status == link_state) + return; + + if (link_state == NTB_LINK_UP) { + u16 status; + + dev_info(&ndev->pdev->dev, "Link Up\n"); + ndev->link_status = NTB_LINK_UP; + event = NTB_EVENT_HW_LINK_UP; + + if (ndev->hw_type == BWD_HW) + status = readw(ndev->reg_ofs.lnk_stat); + else { + int rc = pci_read_config_word(ndev->pdev, + SNB_LINK_STATUS_OFFSET, + &status); + if (rc) + return; + } + dev_info(&ndev->pdev->dev, "Link Width %d, Link Speed %d\n", + (status & NTB_LINK_WIDTH_MASK) >> 4, + (status & NTB_LINK_SPEED_MASK)); + } else { + dev_info(&ndev->pdev->dev, "Link Down\n"); + ndev->link_status = NTB_LINK_DOWN; + event = NTB_EVENT_HW_LINK_DOWN; + } + + /* notify the upper layer if we have an event change */ + if (ndev->event_cb) + ndev->event_cb(ndev->ntb_transport, event); +} + +static int ntb_link_status(struct ntb_device *ndev) +{ + int link_state; + + if (ndev->hw_type == BWD_HW) { + u32 ntb_cntl; + + ntb_cntl = readl(ndev->reg_ofs.lnk_cntl); + if (ntb_cntl & BWD_CNTL_LINK_DOWN) + link_state = NTB_LINK_DOWN; + else + link_state = NTB_LINK_UP; + } else { + u16 status; + int rc; + + rc = pci_read_config_word(ndev->pdev, SNB_LINK_STATUS_OFFSET, + &status); + if (rc) + return rc; + + if (status & NTB_LINK_STATUS_ACTIVE) + link_state = NTB_LINK_UP; + else + link_state = NTB_LINK_DOWN; + } + + ntb_link_event(ndev, link_state); + + return 0; +} + +/* BWD doesn't have link status interrupt, poll on that platform */ +static void bwd_link_poll(struct work_struct *work) +{ + struct ntb_device *ndev = container_of(work, struct ntb_device, + hb_timer.work); + unsigned long ts = jiffies; + + /* If we haven't gotten an interrupt in a while, check the BWD link + * status bit + */ + if (ts > ndev->last_ts + NTB_HB_TIMEOUT) { + int rc = ntb_link_status(ndev); + if (rc) + dev_err(&ndev->pdev->dev, + "Error determining link status\n"); + } + + schedule_delayed_work(&ndev->hb_timer, NTB_HB_TIMEOUT); +} + +static int ntb_xeon_setup(struct ntb_device *ndev) +{ + int rc; + u8 val; + + ndev->hw_type = SNB_HW; + + rc = pci_read_config_byte(ndev->pdev, NTB_PPD_OFFSET, &val); + if (rc) + return rc; + + switch (val & SNB_PPD_CONN_TYPE) { + case NTB_CONN_B2B: + ndev->conn_type = NTB_CONN_B2B; + break; + case NTB_CONN_CLASSIC: + case NTB_CONN_RP: + default: + dev_err(&ndev->pdev->dev, "Only B2B supported at this time\n"); + return -EINVAL; + } + + if (val & SNB_PPD_DEV_TYPE) + ndev->dev_type = NTB_DEV_DSD; + else + ndev->dev_type = NTB_DEV_USD; + + ndev->reg_ofs.pdb = ndev->reg_base + SNB_PDOORBELL_OFFSET; + ndev->reg_ofs.pdb_mask = ndev->reg_base + SNB_PDBMSK_OFFSET; + ndev->reg_ofs.sbar2_xlat = ndev->reg_base + SNB_SBAR2XLAT_OFFSET; + ndev->reg_ofs.sbar4_xlat = ndev->reg_base + SNB_SBAR4XLAT_OFFSET; + ndev->reg_ofs.lnk_cntl = ndev->reg_base + SNB_NTBCNTL_OFFSET; + ndev->reg_ofs.lnk_stat = ndev->reg_base + SNB_LINK_STATUS_OFFSET; + ndev->reg_ofs.spad_read = ndev->reg_base + SNB_SPAD_OFFSET; + ndev->reg_ofs.spci_cmd = ndev->reg_base + SNB_PCICMD_OFFSET; + + if (ndev->conn_type == NTB_CONN_B2B) { + ndev->reg_ofs.sdb = ndev->reg_base + SNB_B2B_DOORBELL_OFFSET; + ndev->reg_ofs.spad_write = ndev->reg_base + SNB_B2B_SPAD_OFFSET; + ndev->limits.max_spads = SNB_MAX_SPADS; + } else { + ndev->reg_ofs.sdb = ndev->reg_base + SNB_SDOORBELL_OFFSET; + ndev->reg_ofs.spad_write = ndev->reg_base + SNB_SPAD_OFFSET; + ndev->limits.max_spads = SNB_MAX_COMPAT_SPADS; + } + + ndev->limits.max_db_bits = SNB_MAX_DB_BITS; + ndev->limits.msix_cnt = SNB_MSIX_CNT; + ndev->bits_per_vector = SNB_DB_BITS_PER_VEC; + + return 0; +} + +static int ntb_bwd_setup(struct ntb_device *ndev) +{ + int rc; + u32 val; + + ndev->hw_type = BWD_HW; + + rc = pci_read_config_dword(ndev->pdev, NTB_PPD_OFFSET, &val); + if (rc) + return rc; + + switch ((val & BWD_PPD_CONN_TYPE) >> 8) { + case NTB_CONN_B2B: + ndev->conn_type = NTB_CONN_B2B; + break; + case NTB_CONN_RP: + default: + dev_err(&ndev->pdev->dev, "Only B2B supported at this time\n"); + return -EINVAL; + } + + if (val & BWD_PPD_DEV_TYPE) + ndev->dev_type = NTB_DEV_DSD; + else + ndev->dev_type = NTB_DEV_USD; + + /* Initiate PCI-E link training */ + rc = pci_write_config_dword(ndev->pdev, NTB_PPD_OFFSET, + val | BWD_PPD_INIT_LINK); + if (rc) + return rc; + + ndev->reg_ofs.pdb = ndev->reg_base + BWD_PDOORBELL_OFFSET; + ndev->reg_ofs.pdb_mask = ndev->reg_base + BWD_PDBMSK_OFFSET; + ndev->reg_ofs.sbar2_xlat = ndev->reg_base + BWD_SBAR2XLAT_OFFSET; + ndev->reg_ofs.sbar4_xlat = ndev->reg_base + BWD_SBAR4XLAT_OFFSET; + ndev->reg_ofs.lnk_cntl = ndev->reg_base + BWD_NTBCNTL_OFFSET; + ndev->reg_ofs.lnk_stat = ndev->reg_base + BWD_LINK_STATUS_OFFSET; + ndev->reg_ofs.spad_read = ndev->reg_base + BWD_SPAD_OFFSET; + ndev->reg_ofs.spci_cmd = ndev->reg_base + BWD_PCICMD_OFFSET; + + if (ndev->conn_type == NTB_CONN_B2B) { + ndev->reg_ofs.sdb = ndev->reg_base + BWD_B2B_DOORBELL_OFFSET; + ndev->reg_ofs.spad_write = ndev->reg_base + BWD_B2B_SPAD_OFFSET; + ndev->limits.max_spads = BWD_MAX_SPADS; + } else { + ndev->reg_ofs.sdb = ndev->reg_base + BWD_PDOORBELL_OFFSET; + ndev->reg_ofs.spad_write = ndev->reg_base + BWD_SPAD_OFFSET; + ndev->limits.max_spads = BWD_MAX_COMPAT_SPADS; + } + + ndev->limits.max_db_bits = BWD_MAX_DB_BITS; + ndev->limits.msix_cnt = BWD_MSIX_CNT; + ndev->bits_per_vector = BWD_DB_BITS_PER_VEC; + + /* Since bwd doesn't have a link interrupt, setup a poll timer */ + INIT_DELAYED_WORK(&ndev->hb_timer, bwd_link_poll); + schedule_delayed_work(&ndev->hb_timer, NTB_HB_TIMEOUT); + + return 0; +} + +static int __devinit ntb_device_setup(struct ntb_device *ndev) +{ + int rc; + + switch (ndev->pdev->device) { + case PCI_DEVICE_ID_INTEL_NTB_2ND_SNB: + case PCI_DEVICE_ID_INTEL_NTB_RP_JSF: + case PCI_DEVICE_ID_INTEL_NTB_RP_SNB: + case PCI_DEVICE_ID_INTEL_NTB_CLASSIC_JSF: + case PCI_DEVICE_ID_INTEL_NTB_CLASSIC_SNB: + case PCI_DEVICE_ID_INTEL_NTB_B2B_JSF: + case PCI_DEVICE_ID_INTEL_NTB_B2B_SNB: + rc = ntb_xeon_setup(ndev); + break; + case PCI_DEVICE_ID_INTEL_NTB_B2B_BWD: + rc = ntb_bwd_setup(ndev); + break; + default: + rc = -ENODEV; + } + + /* Enable Bus Master and Memory Space on the secondary side */ + writew(PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER, ndev->reg_ofs.spci_cmd); + + return rc; +} + +static void ntb_device_free(struct ntb_device *ndev) +{ + if (ndev->hw_type == BWD_HW) + cancel_delayed_work_sync(&ndev->hb_timer); +} + +static irqreturn_t bwd_callback_msix_irq(int irq, void *data) +{ + struct ntb_db_cb *db_cb = data; + struct ntb_device *ndev = db_cb->ndev; + + dev_dbg(&ndev->pdev->dev, "MSI-X irq %d received for DB %d\n", irq, + db_cb->db_num); + + if (db_cb->callback) + db_cb->callback(db_cb->data, db_cb->db_num); + + /* No need to check for the specific HB irq, any interrupt means + * we're connected. + */ + ndev->last_ts = jiffies; + + writeq((u64) 1 << db_cb->db_num, ndev->reg_ofs.pdb); + + return IRQ_HANDLED; +} + +static irqreturn_t xeon_callback_msix_irq(int irq, void *data) +{ + struct ntb_db_cb *db_cb = data; + struct ntb_device *ndev = db_cb->ndev; + + dev_dbg(&ndev->pdev->dev, "MSI-X irq %d received for DB %d\n", irq, + db_cb->db_num); + + if (db_cb->callback) + db_cb->callback(db_cb->data, db_cb->db_num); + + /* On Sandybridge, there are 16 bits in the interrupt register + * but only 4 vectors. So, 5 bits are assigned to the first 3 + * vectors, with the 4th having a single bit for link + * interrupts. + */ + writew(((1 << ndev->bits_per_vector) - 1) << + (db_cb->db_num * ndev->bits_per_vector), ndev->reg_ofs.pdb); + + return IRQ_HANDLED; +} + +/* Since we do not have a HW doorbell in BWD, this is only used in JF/JT */ +static irqreturn_t xeon_event_msix_irq(int irq, void *dev) +{ + struct ntb_device *ndev = dev; + int rc; + + dev_dbg(&ndev->pdev->dev, "MSI-X irq %d received for Events\n", irq); + + rc = ntb_link_status(ndev); + if (rc) + dev_err(&ndev->pdev->dev, "Error determining link status\n"); + + /* bit 15 is always the link bit */ + writew(1 << ndev->limits.max_db_bits, ndev->reg_ofs.pdb); + + return IRQ_HANDLED; +} + +static irqreturn_t ntb_interrupt(int irq, void *dev) +{ + struct ntb_device *ndev = dev; + unsigned int i = 0; + + if (ndev->hw_type == BWD_HW) { + u64 pdb = readq(ndev->reg_ofs.pdb); + + dev_dbg(&ndev->pdev->dev, "irq %d - pdb = %Lx\n", irq, pdb); + + while (pdb) { + i = __ffs(pdb); + pdb &= pdb - 1; + bwd_callback_msix_irq(irq, &ndev->db_cb[i]); + } + } else { + u16 pdb = readw(ndev->reg_ofs.pdb); + + dev_dbg(&ndev->pdev->dev, "irq %d - pdb = %x sdb %x\n", irq, + pdb, readw(ndev->reg_ofs.sdb)); + + if (pdb & SNB_DB_HW_LINK) { + xeon_event_msix_irq(irq, dev); + pdb &= ~SNB_DB_HW_LINK; + } + + while (pdb) { + i = __ffs(pdb); + pdb &= pdb - 1; + xeon_callback_msix_irq(irq, &ndev->db_cb[i]); + } + } + + return IRQ_HANDLED; +} + +static int ntb_setup_msix(struct ntb_device *ndev) +{ + struct pci_dev *pdev = ndev->pdev; + struct msix_entry *msix; + int msix_entries; + int rc, i, pos; + u16 val; + + pos = pci_find_capability(pdev, PCI_CAP_ID_MSIX); + if (!pos) { + rc = -EIO; + goto err; + } + + rc = pci_read_config_word(pdev, pos + PCI_MSIX_FLAGS, &val); + if (rc) + goto err; + + msix_entries = msix_table_size(val); + if (msix_entries > ndev->limits.msix_cnt) { + rc = -EINVAL; + goto err; + } + + ndev->msix_entries = kmalloc(sizeof(struct msix_entry) * msix_entries, + GFP_KERNEL); + if (!ndev->msix_entries) { + rc = -ENOMEM; + goto err; + } + + for (i = 0; i < msix_entries; i++) + ndev->msix_entries[i].entry = i; + + rc = pci_enable_msix(pdev, ndev->msix_entries, msix_entries); + if (rc < 0) + goto err1; + if (rc > 0) { + /* On SNB, the link interrupt is always tied to 4th vector. If + * we can't get all 4, then we can't use MSI-X. + */ + if (ndev->hw_type != BWD_HW) { + rc = -EIO; + goto err1; + } + + dev_warn(&pdev->dev, + "Only %d MSI-X vectors. Limiting the number of queues to that number.\n", + rc); + msix_entries = rc; + } + + for (i = 0; i < msix_entries; i++) { + msix = &ndev->msix_entries[i]; + WARN_ON(!msix->vector); + + /* Use the last MSI-X vector for Link status */ + if (ndev->hw_type == BWD_HW) { + rc = request_irq(msix->vector, bwd_callback_msix_irq, 0, + "ntb-callback-msix", &ndev->db_cb[i]); + if (rc) + goto err2; + } else { + if (i == msix_entries - 1) { + rc = request_irq(msix->vector, + xeon_event_msix_irq, 0, + "ntb-event-msix", ndev); + if (rc) + goto err2; + } else { + rc = request_irq(msix->vector, + xeon_callback_msix_irq, 0, + "ntb-callback-msix", + &ndev->db_cb[i]); + if (rc) + goto err2; + } + } + } + + ndev->num_msix = msix_entries; + if (ndev->hw_type == BWD_HW) + ndev->max_cbs = msix_entries; + else + ndev->max_cbs = msix_entries - 1; + + return 0; + +err2: + while (--i >= 0) { + msix = &ndev->msix_entries[i]; + if (ndev->hw_type != BWD_HW && i == ndev->num_msix - 1) + free_irq(msix->vector, ndev); + else + free_irq(msix->vector, &ndev->db_cb[i]); + } + pci_disable_msix(pdev); +err1: + kfree(ndev->msix_entries); + dev_err(&pdev->dev, "Error allocating MSI-X interrupt\n"); +err: + ndev->num_msix = 0; + return rc; +} + +static int ntb_setup_msi(struct ntb_device *ndev) +{ + struct pci_dev *pdev = ndev->pdev; + int rc; + + rc = pci_enable_msi(pdev); + if (rc) + return rc; + + rc = request_irq(pdev->irq, ntb_interrupt, 0, "ntb-msi", ndev); + if (rc) { + pci_disable_msi(pdev); + dev_err(&pdev->dev, "Error allocating MSI interrupt\n"); + return rc; + } + + return 0; +} + +static int ntb_setup_intx(struct ntb_device *ndev) +{ + struct pci_dev *pdev = ndev->pdev; + int rc; + + pci_msi_off(pdev); + + /* Verify intx is enabled */ + pci_intx(pdev, 1); + + rc = request_irq(pdev->irq, ntb_interrupt, IRQF_SHARED, "ntb-intx", + ndev); + if (rc) + return rc; + + return 0; +} + +static int __devinit ntb_setup_interrupts(struct ntb_device *ndev) +{ + int rc; + + /* On BWD, disable all interrupts. On SNB, disable all but Link + * Interrupt. The rest will be unmasked as callbacks are registered. + */ + if (ndev->hw_type == BWD_HW) + writeq(~0, ndev->reg_ofs.pdb_mask); + else + writew(~(1 << ndev->limits.max_db_bits), + ndev->reg_ofs.pdb_mask); + + rc = ntb_setup_msix(ndev); + if (!rc) + goto done; + + ndev->bits_per_vector = 1; + ndev->max_cbs = ndev->limits.max_db_bits; + + rc = ntb_setup_msi(ndev); + if (!rc) + goto done; + + rc = ntb_setup_intx(ndev); + if (rc) { + dev_err(&ndev->pdev->dev, "no usable interrupts\n"); + return rc; + } + +done: + return 0; +} + +static void __devexit ntb_free_interrupts(struct ntb_device *ndev) +{ + struct pci_dev *pdev = ndev->pdev; + + /* mask interrupts */ + if (ndev->hw_type == BWD_HW) + writeq(~0, ndev->reg_ofs.pdb_mask); + else + writew(~0, ndev->reg_ofs.pdb_mask); + + if (ndev->num_msix) { + struct msix_entry *msix; + u32 i; + + for (i = 0; i < ndev->num_msix; i++) { + msix = &ndev->msix_entries[i]; + if (ndev->hw_type != BWD_HW && i == ndev->num_msix - 1) + free_irq(msix->vector, ndev); + else + free_irq(msix->vector, &ndev->db_cb[i]); + } + pci_disable_msix(pdev); + } else { + free_irq(pdev->irq, ndev); + + if (pci_dev_msi_enabled(pdev)) + pci_disable_msi(pdev); + } +} + +static int __devinit ntb_create_callbacks(struct ntb_device *ndev) +{ + int i; + + /* Checken-egg issue. We won't know how many callbacks are necessary + * until we see how many MSI-X vectors we get, but these pointers need + * to be passed into the MSI-X register fucntion. So, we allocate the + * max, knowing that they might not all be used, to work around this. + */ + ndev->db_cb = kcalloc(ndev->limits.max_db_bits, + sizeof(struct ntb_db_cb), + GFP_KERNEL); + if (!ndev->db_cb) + return -ENOMEM; + + for (i = 0; i < ndev->limits.max_db_bits; i++) { + ndev->db_cb[i].db_num = i; + ndev->db_cb[i].ndev = ndev; + } + + return 0; +} + +static void ntb_free_callbacks(struct ntb_device *ndev) +{ + int i; + + for (i = 0; i < ndev->limits.max_db_bits; i++) + ntb_unregister_db_callback(ndev, i); + + kfree(ndev->db_cb); +} + +static int __devinit +ntb_pci_probe(struct pci_dev *pdev, + __attribute__((unused)) const struct pci_device_id *id) +{ + struct ntb_device *ndev; + int rc, i; + + ndev = kzalloc(sizeof(struct ntb_device), GFP_KERNEL); + if (!ndev) + return -ENOMEM; + + ndev->pdev = pdev; + ndev->link_status = NTB_LINK_DOWN; + pci_set_drvdata(pdev, ndev); + + rc = pci_enable_device(pdev); + if (rc) + goto err; + + pci_set_master(ndev->pdev); + + rc = pci_request_selected_regions(pdev, NTB_BAR_MASK, KBUILD_MODNAME); + if (rc) + goto err1; + + ndev->reg_base = pci_ioremap_bar(pdev, NTB_BAR_MMIO); + if (!ndev->reg_base) { + dev_warn(&pdev->dev, "Cannot remap BAR 0\n"); + rc = -EIO; + goto err2; + } + + for (i = 0; i < NTB_NUM_MW; i++) { + ndev->mw[i].bar_sz = pci_resource_len(pdev, MW_TO_BAR(i)); + ndev->mw[i].vbase = + ioremap_wc(pci_resource_start(pdev, MW_TO_BAR(i)), + ndev->mw[i].bar_sz); + dev_info(&pdev->dev, "MW %d size %d\n", i, + (u32) pci_resource_len(pdev, MW_TO_BAR(i))); + if (!ndev->mw[i].vbase) { + dev_warn(&pdev->dev, "Cannot remap BAR %d\n", + MW_TO_BAR(i)); + rc = -EIO; + goto err3; + } + } + + rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(64)); + if (rc) { + rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (rc) + goto err3; + + dev_warn(&pdev->dev, "Cannot DMA highmem\n"); + } + + rc = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)); + if (rc) { + rc = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); + if (rc) + goto err3; + + dev_warn(&pdev->dev, "Cannot DMA consistent highmem\n"); + } + + rc = ntb_device_setup(ndev); + if (rc) + goto err3; + + rc = ntb_create_callbacks(ndev); + if (rc) + goto err4; + + rc = ntb_setup_interrupts(ndev); + if (rc) + goto err5; + + /* The scratchpad registers keep the values between rmmod/insmod, + * blast them now + */ + for (i = 0; i < ndev->limits.max_spads; i++) { + ntb_write_local_spad(ndev, i, 0); + ntb_write_remote_spad(ndev, i, 0); + } + + rc = ntb_transport_init(pdev); + if (rc) + goto err6; + + /* Let's bring the NTB link up */ + writel(NTB_CNTL_BAR23_SNOOP | NTB_CNTL_BAR45_SNOOP, + ndev->reg_ofs.lnk_cntl); + + return 0; + +err6: + ntb_free_interrupts(ndev); +err5: + ntb_free_callbacks(ndev); +err4: + ntb_device_free(ndev); +err3: + for (i--; i >= 0; i--) + iounmap(ndev->mw[i].vbase); + iounmap(ndev->reg_base); +err2: + pci_release_selected_regions(pdev, NTB_BAR_MASK); +err1: + pci_disable_device(pdev); +err: + kfree(ndev); + + dev_err(&pdev->dev, "Error loading %s module\n", KBUILD_MODNAME); + return rc; +} + +static void __devexit ntb_pci_remove(struct pci_dev *pdev) +{ + struct ntb_device *ndev = pci_get_drvdata(pdev); + int i; + u32 ntb_cntl; + + /* Bring NTB link down */ + ntb_cntl = readl(ndev->reg_ofs.lnk_cntl); + ntb_cntl |= NTB_LINK_DISABLE; + writel(ntb_cntl, ndev->reg_ofs.lnk_cntl); + + ntb_transport_free(ndev->ntb_transport); + + ntb_free_interrupts(ndev); + ntb_free_callbacks(ndev); + ntb_device_free(ndev); + + for (i = 0; i < NTB_NUM_MW; i++) + iounmap(ndev->mw[i].vbase); + + iounmap(ndev->reg_base); + pci_release_selected_regions(pdev, NTB_BAR_MASK); + pci_disable_device(pdev); + kfree(ndev); +} + +static struct pci_driver ntb_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = ntb_pci_tbl, + .probe = ntb_pci_probe, + .remove = __devexit_p(ntb_pci_remove), +}; +module_pci_driver(ntb_pci_driver); diff --git a/drivers/ntb/ntb_hw.h b/drivers/ntb/ntb_hw.h new file mode 100644 index 000000000000..5e009519c64f --- /dev/null +++ b/drivers/ntb/ntb_hw.h @@ -0,0 +1,181 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * BSD LICENSE + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe NTB Linux driver + * + * Contact Information: + * Jon Mason + */ + +#define PCI_DEVICE_ID_INTEL_NTB_B2B_JSF 0x3725 +#define PCI_DEVICE_ID_INTEL_NTB_CLASSIC_JSF 0x3726 +#define PCI_DEVICE_ID_INTEL_NTB_RP_JSF 0x3727 +#define PCI_DEVICE_ID_INTEL_NTB_RP_SNB 0x3C08 +#define PCI_DEVICE_ID_INTEL_NTB_B2B_SNB 0x3C0D +#define PCI_DEVICE_ID_INTEL_NTB_CLASSIC_SNB 0x3C0E +#define PCI_DEVICE_ID_INTEL_NTB_2ND_SNB 0x3C0F +#define PCI_DEVICE_ID_INTEL_NTB_B2B_BWD 0x0C4E + +#define msix_table_size(control) ((control & PCI_MSIX_FLAGS_QSIZE)+1) + +#define NTB_BAR_MMIO 0 +#define NTB_BAR_23 2 +#define NTB_BAR_45 4 +#define NTB_BAR_MASK ((1 << NTB_BAR_MMIO) | (1 << NTB_BAR_23) |\ + (1 << NTB_BAR_45)) + +#define NTB_LINK_DOWN 0 +#define NTB_LINK_UP 1 + +#define NTB_HB_TIMEOUT msecs_to_jiffies(1000) + +#define NTB_NUM_MW 2 + +enum ntb_hw_event { + NTB_EVENT_SW_EVENT0 = 0, + NTB_EVENT_SW_EVENT1, + NTB_EVENT_SW_EVENT2, + NTB_EVENT_HW_ERROR, + NTB_EVENT_HW_LINK_UP, + NTB_EVENT_HW_LINK_DOWN, +}; + +struct ntb_mw { + dma_addr_t phys_addr; + void __iomem *vbase; + resource_size_t bar_sz; +}; + +struct ntb_db_cb { + void (*callback) (void *data, int db_num); + unsigned int db_num; + void *data; + struct ntb_device *ndev; +}; + +struct ntb_device { + struct pci_dev *pdev; + struct msix_entry *msix_entries; + void __iomem *reg_base; + struct ntb_mw mw[NTB_NUM_MW]; + struct { + unsigned int max_spads; + unsigned int max_db_bits; + unsigned int msix_cnt; + } limits; + struct { + void __iomem *pdb; + void __iomem *pdb_mask; + void __iomem *sdb; + void __iomem *sbar2_xlat; + void __iomem *sbar4_xlat; + void __iomem *spad_write; + void __iomem *spad_read; + void __iomem *lnk_cntl; + void __iomem *lnk_stat; + void __iomem *spci_cmd; + } reg_ofs; + struct ntb_transport *ntb_transport; + void (*event_cb)(void *handle, enum ntb_hw_event event); + + struct ntb_db_cb *db_cb; + unsigned char hw_type; + unsigned char conn_type; + unsigned char dev_type; + unsigned char num_msix; + unsigned char bits_per_vector; + unsigned char max_cbs; + unsigned char link_status; + struct delayed_work hb_timer; + unsigned long last_ts; +}; + +/** + * ntb_hw_link_status() - return the hardware link status + * @ndev: pointer to ntb_device instance + * + * Returns true if the hardware is connected to the remote system + * + * RETURNS: true or false based on the hardware link state + */ +static inline bool ntb_hw_link_status(struct ntb_device *ndev) +{ + return ndev->link_status == NTB_LINK_UP; +} + +/** + * ntb_query_pdev() - return the pci_dev pointer + * @ndev: pointer to ntb_device instance + * + * Given the ntb pointer return the pci_dev pointerfor the NTB hardware device + * + * RETURNS: a pointer to the ntb pci_dev + */ +static inline struct pci_dev *ntb_query_pdev(struct ntb_device *ndev) +{ + return ndev->pdev; +} + +struct ntb_device *ntb_register_transport(struct pci_dev *pdev, + void *transport); +void ntb_unregister_transport(struct ntb_device *ndev); +void ntb_set_mw_addr(struct ntb_device *ndev, unsigned int mw, u64 addr); +int ntb_register_db_callback(struct ntb_device *ndev, unsigned int idx, + void *data, void (*db_cb_func) (void *data, + int db_num)); +void ntb_unregister_db_callback(struct ntb_device *ndev, unsigned int idx); +int ntb_register_event_callback(struct ntb_device *ndev, + void (*event_cb_func) (void *handle, + unsigned int event)); +void ntb_unregister_event_callback(struct ntb_device *ndev); +int ntb_get_max_spads(struct ntb_device *ndev); +int ntb_write_local_spad(struct ntb_device *ndev, unsigned int idx, u32 val); +int ntb_read_local_spad(struct ntb_device *ndev, unsigned int idx, u32 *val); +int ntb_write_remote_spad(struct ntb_device *ndev, unsigned int idx, u32 val); +int ntb_read_remote_spad(struct ntb_device *ndev, unsigned int idx, u32 *val); +void *ntb_get_mw_vbase(struct ntb_device *ndev, unsigned int mw); +resource_size_t ntb_get_mw_size(struct ntb_device *ndev, unsigned int mw); +void ntb_ring_sdb(struct ntb_device *ndev, unsigned int idx); +void *ntb_find_transport(struct pci_dev *pdev); + +int ntb_transport_init(struct pci_dev *pdev); +void ntb_transport_free(void *transport); diff --git a/drivers/ntb/ntb_regs.h b/drivers/ntb/ntb_regs.h new file mode 100644 index 000000000000..5bfa8c06c059 --- /dev/null +++ b/drivers/ntb/ntb_regs.h @@ -0,0 +1,139 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * BSD LICENSE + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe NTB Linux driver + * + * Contact Information: + * Jon Mason + */ + +#define NTB_LINK_ENABLE 0x0000 +#define NTB_LINK_DISABLE 0x0002 +#define NTB_LINK_STATUS_ACTIVE 0x2000 +#define NTB_LINK_SPEED_MASK 0x000f +#define NTB_LINK_WIDTH_MASK 0x03f0 + +#define SNB_MSIX_CNT 4 +#define SNB_MAX_SPADS 16 +#define SNB_MAX_COMPAT_SPADS 8 +/* Reserve the uppermost bit for link interrupt */ +#define SNB_MAX_DB_BITS 15 +#define SNB_DB_BITS_PER_VEC 5 + +#define SNB_DB_HW_LINK 0x8000 + +#define SNB_PCICMD_OFFSET 0x0504 +#define SNB_DEVCTRL_OFFSET 0x0598 +#define SNB_LINK_STATUS_OFFSET 0x01A2 + +#define SNB_PBAR2LMT_OFFSET 0x0000 +#define SNB_PBAR4LMT_OFFSET 0x0008 +#define SNB_PBAR2XLAT_OFFSET 0x0010 +#define SNB_PBAR4XLAT_OFFSET 0x0018 +#define SNB_SBAR2LMT_OFFSET 0x0020 +#define SNB_SBAR4LMT_OFFSET 0x0028 +#define SNB_SBAR2XLAT_OFFSET 0x0030 +#define SNB_SBAR4XLAT_OFFSET 0x0038 +#define SNB_SBAR0BASE_OFFSET 0x0040 +#define SNB_SBAR2BASE_OFFSET 0x0048 +#define SNB_SBAR4BASE_OFFSET 0x0050 +#define SNB_NTBCNTL_OFFSET 0x0058 +#define SNB_SBDF_OFFSET 0x005C +#define SNB_PDOORBELL_OFFSET 0x0060 +#define SNB_PDBMSK_OFFSET 0x0062 +#define SNB_SDOORBELL_OFFSET 0x0064 +#define SNB_SDBMSK_OFFSET 0x0066 +#define SNB_USMEMMISS 0x0070 +#define SNB_SPAD_OFFSET 0x0080 +#define SNB_SPADSEMA4_OFFSET 0x00c0 +#define SNB_WCCNTRL_OFFSET 0x00e0 +#define SNB_B2B_SPAD_OFFSET 0x0100 +#define SNB_B2B_DOORBELL_OFFSET 0x0140 +#define SNB_B2B_XLAT_OFFSET 0x0144 + +#define BWD_MSIX_CNT 34 +#define BWD_MAX_SPADS 16 +#define BWD_MAX_COMPAT_SPADS 16 +#define BWD_MAX_DB_BITS 34 +#define BWD_DB_BITS_PER_VEC 1 + +#define BWD_PCICMD_OFFSET 0xb004 +#define BWD_MBAR23_OFFSET 0xb018 +#define BWD_MBAR45_OFFSET 0xb020 +#define BWD_DEVCTRL_OFFSET 0xb048 +#define BWD_LINK_STATUS_OFFSET 0xb052 + +#define BWD_SBAR2XLAT_OFFSET 0x0008 +#define BWD_SBAR4XLAT_OFFSET 0x0010 +#define BWD_PDOORBELL_OFFSET 0x0020 +#define BWD_PDBMSK_OFFSET 0x0028 +#define BWD_NTBCNTL_OFFSET 0x0060 +#define BWD_EBDF_OFFSET 0x0064 +#define BWD_SPAD_OFFSET 0x0080 +#define BWD_SPADSEMA_OFFSET 0x00c0 +#define BWD_STKYSPAD_OFFSET 0x00c4 +#define BWD_PBAR2XLAT_OFFSET 0x8008 +#define BWD_PBAR4XLAT_OFFSET 0x8010 +#define BWD_B2B_DOORBELL_OFFSET 0x8020 +#define BWD_B2B_SPAD_OFFSET 0x8080 +#define BWD_B2B_SPADSEMA_OFFSET 0x80c0 +#define BWD_B2B_STKYSPAD_OFFSET 0x80c4 + +#define NTB_CNTL_BAR23_SNOOP (1 << 2) +#define NTB_CNTL_BAR45_SNOOP (1 << 6) +#define BWD_CNTL_LINK_DOWN (1 << 16) + +#define NTB_PPD_OFFSET 0x00D4 +#define SNB_PPD_CONN_TYPE 0x0003 +#define SNB_PPD_DEV_TYPE 0x0010 +#define BWD_PPD_INIT_LINK 0x0008 +#define BWD_PPD_CONN_TYPE 0x0300 +#define BWD_PPD_DEV_TYPE 0x1000 + +#define BWD_PBAR2XLAT_USD_ADDR 0x0000004000000000 +#define BWD_PBAR4XLAT_USD_ADDR 0x0000008000000000 +#define BWD_MBAR23_USD_ADDR 0x000000410000000C +#define BWD_MBAR45_USD_ADDR 0x000000810000000C +#define BWD_PBAR2XLAT_DSD_ADDR 0x0000004100000000 +#define BWD_PBAR4XLAT_DSD_ADDR 0x0000008100000000 +#define BWD_MBAR23_DSD_ADDR 0x000000400000000C +#define BWD_MBAR45_DSD_ADDR 0x000000800000000C diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c new file mode 100644 index 000000000000..c907e0773532 --- /dev/null +++ b/drivers/ntb/ntb_transport.c @@ -0,0 +1,1427 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * BSD LICENSE + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe NTB Linux driver + * + * Contact Information: + * Jon Mason + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ntb_hw.h" + +#define NTB_TRANSPORT_VERSION 1 + +static int transport_mtu = 0x401E; +module_param(transport_mtu, uint, 0644); +MODULE_PARM_DESC(transport_mtu, "Maximum size of NTB transport packets"); + +static unsigned char max_num_clients = 2; +module_param(max_num_clients, byte, 0644); +MODULE_PARM_DESC(max_num_clients, "Maximum number of NTB transport clients"); + +struct ntb_queue_entry { + /* ntb_queue list reference */ + struct list_head entry; + /* pointers to data to be transfered */ + void *cb_data; + void *buf; + unsigned int len; + unsigned int flags; +}; + +struct ntb_transport_qp { + struct ntb_transport *transport; + struct ntb_device *ndev; + void *cb_data; + + bool client_ready; + bool qp_link; + u8 qp_num; /* Only 64 QP's are allowed. 0-63 */ + + void (*tx_handler) (struct ntb_transport_qp *qp, void *qp_data, + void *data, int len); + struct list_head tx_free_q; + spinlock_t ntb_tx_free_q_lock; + void *tx_mw_begin; + void *tx_mw_end; + void *tx_offset; + + void (*rx_handler) (struct ntb_transport_qp *qp, void *qp_data, + void *data, int len); + struct tasklet_struct rx_work; + struct list_head rx_pend_q; + struct list_head rx_free_q; + spinlock_t ntb_rx_pend_q_lock; + spinlock_t ntb_rx_free_q_lock; + void *rx_buff_begin; + void *rx_buff_end; + void *rx_offset; + + void (*event_handler) (void *data, int status); + struct delayed_work link_work; + + struct dentry *debugfs_dir; + struct dentry *debugfs_stats; + + /* Stats */ + u64 rx_bytes; + u64 rx_pkts; + u64 rx_ring_empty; + u64 rx_err_no_buf; + u64 rx_err_oflow; + u64 rx_err_ver; + u64 tx_bytes; + u64 tx_pkts; + u64 tx_ring_full; +}; + +struct ntb_transport_mw { + size_t size; + void *virt_addr; + dma_addr_t dma_addr; +}; + +struct ntb_transport_client_dev { + struct list_head entry; + struct device dev; +}; + +struct ntb_transport { + struct list_head entry; + struct list_head client_devs; + + struct ntb_device *ndev; + struct ntb_transport_mw mw[NTB_NUM_MW]; + struct ntb_transport_qp *qps; + unsigned int max_qps; + unsigned long qp_bitmap; + bool transport_link; + struct delayed_work link_work; + struct dentry *debugfs_dir; +}; + +enum { + DESC_DONE_FLAG = 1 << 0, + LINK_DOWN_FLAG = 1 << 1, +}; + +struct ntb_payload_header { + u64 ver; + unsigned int len; + unsigned int flags; +}; + +enum { + VERSION = 0, + MW0_SZ, + MW1_SZ, + NUM_QPS, + QP_LINKS, + MAX_SPAD, +}; + +#define QP_TO_MW(qp) ((qp) % NTB_NUM_MW) +#define NTB_QP_DEF_NUM_ENTRIES 100 +#define NTB_LINK_DOWN_TIMEOUT 10 + +static int ntb_match_bus(struct device *dev, struct device_driver *drv) +{ + return !strncmp(dev_name(dev), drv->name, strlen(drv->name)); +} + +static int ntb_client_probe(struct device *dev) +{ + const struct ntb_client *drv = container_of(dev->driver, + struct ntb_client, driver); + struct pci_dev *pdev = container_of(dev->parent, struct pci_dev, dev); + int rc = -EINVAL; + + get_device(dev); + if (drv && drv->probe) + rc = drv->probe(pdev); + if (rc) + put_device(dev); + + return rc; +} + +static int ntb_client_remove(struct device *dev) +{ + const struct ntb_client *drv = container_of(dev->driver, + struct ntb_client, driver); + struct pci_dev *pdev = container_of(dev->parent, struct pci_dev, dev); + + if (drv && drv->remove) + drv->remove(pdev); + + put_device(dev); + + return 0; +} + +struct bus_type ntb_bus_type = { + .name = "ntb_bus", + .match = ntb_match_bus, + .probe = ntb_client_probe, + .remove = ntb_client_remove, +}; + +static LIST_HEAD(ntb_transport_list); + +static int __devinit ntb_bus_init(struct ntb_transport *nt) +{ + if (list_empty(&ntb_transport_list)) { + int rc = bus_register(&ntb_bus_type); + if (rc) + return rc; + } + + list_add(&nt->entry, &ntb_transport_list); + + return 0; +} + +static void __devexit ntb_bus_remove(struct ntb_transport *nt) +{ + struct ntb_transport_client_dev *client_dev, *cd; + + list_for_each_entry_safe(client_dev, cd, &nt->client_devs, entry) { + dev_err(client_dev->dev.parent, "%s still attached to bus, removing\n", + dev_name(&client_dev->dev)); + list_del(&client_dev->entry); + device_unregister(&client_dev->dev); + } + + list_del(&nt->entry); + + if (list_empty(&ntb_transport_list)) + bus_unregister(&ntb_bus_type); +} + +static void ntb_client_release(struct device *dev) +{ + struct ntb_transport_client_dev *client_dev; + client_dev = container_of(dev, struct ntb_transport_client_dev, dev); + + kfree(client_dev); +} + +/** + * ntb_unregister_client_dev - Unregister NTB client device + * @device_name: Name of NTB client device + * + * Unregister an NTB client device with the NTB transport layer + */ +void ntb_unregister_client_dev(char *device_name) +{ + struct ntb_transport_client_dev *client, *cd; + struct ntb_transport *nt; + + list_for_each_entry(nt, &ntb_transport_list, entry) + list_for_each_entry_safe(client, cd, &nt->client_devs, entry) + if (!strncmp(dev_name(&client->dev), device_name, + strlen(device_name))) { + list_del(&client->entry); + device_unregister(&client->dev); + } +} +EXPORT_SYMBOL_GPL(ntb_unregister_client_dev); + +/** + * ntb_register_client_dev - Register NTB client device + * @device_name: Name of NTB client device + * + * Register an NTB client device with the NTB transport layer + */ +int ntb_register_client_dev(char *device_name) +{ + struct ntb_transport_client_dev *client_dev; + struct ntb_transport *nt; + int rc; + + list_for_each_entry(nt, &ntb_transport_list, entry) { + struct device *dev; + + client_dev = kzalloc(sizeof(struct ntb_transport_client_dev), + GFP_KERNEL); + if (!client_dev) { + rc = -ENOMEM; + goto err; + } + + dev = &client_dev->dev; + + /* setup and register client devices */ + dev_set_name(dev, "%s", device_name); + dev->bus = &ntb_bus_type; + dev->release = ntb_client_release; + dev->parent = &ntb_query_pdev(nt->ndev)->dev; + + rc = device_register(dev); + if (rc) { + kfree(client_dev); + goto err; + } + + list_add_tail(&client_dev->entry, &nt->client_devs); + } + + return 0; + +err: + ntb_unregister_client_dev(device_name); + + return rc; +} +EXPORT_SYMBOL_GPL(ntb_register_client_dev); + +/** + * ntb_register_client - Register NTB client driver + * @drv: NTB client driver to be registered + * + * Register an NTB client driver with the NTB transport layer + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_register_client(struct ntb_client *drv) +{ + drv->driver.bus = &ntb_bus_type; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(ntb_register_client); + +/** + * ntb_unregister_client - Unregister NTB client driver + * @drv: NTB client driver to be unregistered + * + * Unregister an NTB client driver with the NTB transport layer + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +void ntb_unregister_client(struct ntb_client *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(ntb_unregister_client); + +static int debugfs_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +static ssize_t debugfs_read(struct file *filp, char __user *ubuf, size_t count, + loff_t *offp) +{ + struct ntb_transport_qp *qp; + char buf[1024]; + ssize_t ret, out_offset, out_count; + + out_count = 1024; + + qp = filp->private_data; + out_offset = 0; + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "NTB QP stats\n"); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_bytes - \t%llu\n", qp->rx_bytes); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_pkts - \t%llu\n", qp->rx_pkts); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_ring_empty - %llu\n", qp->rx_ring_empty); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_err_no_buf - %llu\n", qp->rx_err_no_buf); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_err_oflow - \t%llu\n", qp->rx_err_oflow); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_err_ver - \t%llu\n", qp->rx_err_ver); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_buff_begin - %p\n", qp->rx_buff_begin); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_offset - \t%p\n", qp->rx_offset); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_buff_end - \t%p\n", qp->rx_buff_end); + + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_bytes - \t%llu\n", qp->tx_bytes); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_pkts - \t%llu\n", qp->tx_pkts); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_ring_full - \t%llu\n", qp->tx_ring_full); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_mw_begin - \t%p\n", qp->tx_mw_begin); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_offset - \t%p\n", qp->tx_offset); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_mw_end - \t%p\n", qp->tx_mw_end); + + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "QP Link %s\n", (qp->qp_link == NTB_LINK_UP) ? + "Up" : "Down"); + + ret = simple_read_from_buffer(ubuf, count, offp, buf, out_offset); + return ret; +} + +static const struct file_operations ntb_qp_debugfs_stats = { + .owner = THIS_MODULE, + .open = debugfs_open, + .read = debugfs_read, +}; + +static void ntb_list_add(spinlock_t *lock, struct list_head *entry, + struct list_head *list) +{ + unsigned long flags; + + spin_lock_irqsave(lock, flags); + list_add_tail(entry, list); + spin_unlock_irqrestore(lock, flags); +} + +static struct ntb_queue_entry *ntb_list_rm(spinlock_t *lock, + struct list_head *list) +{ + struct ntb_queue_entry *entry; + unsigned long flags; + + spin_lock_irqsave(lock, flags); + if (list_empty(list)) { + entry = NULL; + goto out; + } + entry = list_first_entry(list, struct ntb_queue_entry, entry); + list_del(&entry->entry); +out: + spin_unlock_irqrestore(lock, flags); + + return entry; +} + +static void ntb_transport_setup_qp_mw(struct ntb_transport *nt, + unsigned int qp_num) +{ + struct ntb_transport_qp *qp = &nt->qps[qp_num]; + unsigned int size, num_qps_mw; + u8 mw_num = QP_TO_MW(qp_num); + + WARN_ON(nt->mw[mw_num].virt_addr == 0); + + if (nt->max_qps % NTB_NUM_MW && !mw_num) + num_qps_mw = nt->max_qps / NTB_NUM_MW + + (nt->max_qps % NTB_NUM_MW - mw_num); + else + num_qps_mw = nt->max_qps / NTB_NUM_MW; + + size = nt->mw[mw_num].size / num_qps_mw; + + qp->rx_buff_begin = nt->mw[mw_num].virt_addr + + (qp_num / NTB_NUM_MW * size); + qp->rx_buff_end = qp->rx_buff_begin + size; + qp->rx_offset = qp->rx_buff_begin; + + qp->tx_mw_begin = ntb_get_mw_vbase(nt->ndev, mw_num) + + (qp_num / NTB_NUM_MW * size); + qp->tx_mw_end = qp->tx_mw_begin + size; + qp->tx_offset = qp->tx_mw_begin; + + qp->rx_pkts = 0; + qp->tx_pkts = 0; +} + +static int ntb_set_mw(struct ntb_transport *nt, int num_mw, unsigned int size) +{ + struct ntb_transport_mw *mw = &nt->mw[num_mw]; + struct pci_dev *pdev = ntb_query_pdev(nt->ndev); + void *offset; + + /* Alloc memory for receiving data. Must be 4k aligned */ + mw->size = ALIGN(size, 4096); + + mw->virt_addr = dma_alloc_coherent(&pdev->dev, mw->size, &mw->dma_addr, + GFP_KERNEL); + if (!mw->virt_addr) { + dev_err(&pdev->dev, "Unable to allocate MW buffer of size %d\n", + (int) mw->size); + return -ENOMEM; + } + + /* setup the hdr offsets with 0's */ + for (offset = mw->virt_addr + transport_mtu - + sizeof(struct ntb_payload_header); + offset < mw->virt_addr + size; offset += transport_mtu) + memset(offset, 0, sizeof(struct ntb_payload_header)); + + /* Notify HW the memory location of the receive buffer */ + ntb_set_mw_addr(nt->ndev, num_mw, mw->dma_addr); + + return 0; +} + +static void ntb_qp_link_down(struct ntb_transport_qp *qp) +{ + struct ntb_transport *nt = qp->transport; + struct pci_dev *pdev = ntb_query_pdev(nt->ndev); + + if (qp->qp_link == NTB_LINK_DOWN) { + cancel_delayed_work_sync(&qp->link_work); + return; + } + + if (qp->event_handler) + qp->event_handler(qp->cb_data, NTB_LINK_DOWN); + + dev_info(&pdev->dev, "qp %d: Link Down\n", qp->qp_num); + qp->qp_link = NTB_LINK_DOWN; + + if (nt->transport_link == NTB_LINK_UP) + schedule_delayed_work(&qp->link_work, + msecs_to_jiffies(NTB_LINK_DOWN_TIMEOUT)); +} + +static void ntb_transport_conn_down(struct ntb_transport *nt) +{ + int i; + + if (nt->transport_link == NTB_LINK_DOWN) + cancel_delayed_work_sync(&nt->link_work); + else + nt->transport_link = NTB_LINK_DOWN; + + /* Pass along the info to any clients */ + for (i = 0; i < nt->max_qps; i++) + if (!test_bit(i, &nt->qp_bitmap)) + ntb_qp_link_down(&nt->qps[i]); + + /* The scratchpad registers keep the values if the remote side + * goes down, blast them now to give them a sane value the next + * time they are accessed + */ + for (i = 0; i < MAX_SPAD; i++) + ntb_write_local_spad(nt->ndev, i, 0); +} + +static void ntb_transport_event_callback(void *data, enum ntb_hw_event event) +{ + struct ntb_transport *nt = data; + + switch (event) { + case NTB_EVENT_HW_LINK_UP: + schedule_delayed_work(&nt->link_work, 0); + break; + case NTB_EVENT_HW_LINK_DOWN: + ntb_transport_conn_down(nt); + break; + default: + BUG(); + } +} + +static void ntb_transport_link_work(struct work_struct *work) +{ + struct ntb_transport *nt = container_of(work, struct ntb_transport, + link_work.work); + struct ntb_device *ndev = nt->ndev; + struct pci_dev *pdev = ntb_query_pdev(ndev); + u32 val; + int rc, i; + + /* send the local info */ + rc = ntb_write_remote_spad(ndev, VERSION, NTB_TRANSPORT_VERSION); + if (rc) { + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + 0, VERSION); + goto out; + } + + rc = ntb_write_remote_spad(ndev, MW0_SZ, ntb_get_mw_size(ndev, 0)); + if (rc) { + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + (u32) ntb_get_mw_size(ndev, 0), MW0_SZ); + goto out; + } + + rc = ntb_write_remote_spad(ndev, MW1_SZ, ntb_get_mw_size(ndev, 1)); + if (rc) { + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + (u32) ntb_get_mw_size(ndev, 1), MW1_SZ); + goto out; + } + + rc = ntb_write_remote_spad(ndev, NUM_QPS, nt->max_qps); + if (rc) { + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + nt->max_qps, NUM_QPS); + goto out; + } + + rc = ntb_read_local_spad(nt->ndev, QP_LINKS, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading spad %d\n", QP_LINKS); + goto out; + } + + rc = ntb_write_remote_spad(ndev, QP_LINKS, val); + if (rc) { + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + val, QP_LINKS); + goto out; + } + + /* Query the remote side for its info */ + rc = ntb_read_remote_spad(ndev, VERSION, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading remote spad %d\n", VERSION); + goto out; + } + + if (val != NTB_TRANSPORT_VERSION) + goto out; + dev_dbg(&pdev->dev, "Remote version = %d\n", val); + + rc = ntb_read_remote_spad(ndev, NUM_QPS, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading remote spad %d\n", NUM_QPS); + goto out; + } + + if (val != nt->max_qps) + goto out; + dev_dbg(&pdev->dev, "Remote max number of qps = %d\n", val); + + rc = ntb_read_remote_spad(ndev, MW0_SZ, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading remote spad %d\n", MW0_SZ); + goto out; + } + + if (!val) + goto out; + dev_dbg(&pdev->dev, "Remote MW0 size = %d\n", val); + + rc = ntb_set_mw(nt, 0, val); + if (rc) + goto out; + + rc = ntb_read_remote_spad(ndev, MW1_SZ, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading remote spad %d\n", MW1_SZ); + goto out; + } + + if (!val) + goto out; + dev_dbg(&pdev->dev, "Remote MW1 size = %d\n", val); + + rc = ntb_set_mw(nt, 1, val); + if (rc) + goto out; + + nt->transport_link = NTB_LINK_UP; + + for (i = 0; i < nt->max_qps; i++) { + struct ntb_transport_qp *qp = &nt->qps[i]; + + ntb_transport_setup_qp_mw(nt, i); + + if (qp->client_ready == NTB_LINK_UP) + schedule_delayed_work(&qp->link_work, 0); + } + + return; + +out: + if (ntb_hw_link_status(ndev)) + schedule_delayed_work(&nt->link_work, + msecs_to_jiffies(NTB_LINK_DOWN_TIMEOUT)); +} + +static void ntb_qp_link_work(struct work_struct *work) +{ + struct ntb_transport_qp *qp = container_of(work, + struct ntb_transport_qp, + link_work.work); + struct pci_dev *pdev = ntb_query_pdev(qp->ndev); + struct ntb_transport *nt = qp->transport; + int rc, val; + + WARN_ON(nt->transport_link != NTB_LINK_UP); + + rc = ntb_read_local_spad(nt->ndev, QP_LINKS, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading spad %d\n", QP_LINKS); + return; + } + + rc = ntb_write_remote_spad(nt->ndev, QP_LINKS, val | 1 << qp->qp_num); + if (rc) + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + val | 1 << qp->qp_num, QP_LINKS); + + /* query remote spad for qp ready bits */ + rc = ntb_read_remote_spad(nt->ndev, QP_LINKS, &val); + if (rc) + dev_err(&pdev->dev, "Error reading remote spad %d\n", QP_LINKS); + + dev_dbg(&pdev->dev, "Remote QP link status = %x\n", val); + + /* See if the remote side is up */ + if (1 << qp->qp_num & val) { + qp->qp_link = NTB_LINK_UP; + + dev_info(&pdev->dev, "qp %d: Link Up\n", qp->qp_num); + if (qp->event_handler) + qp->event_handler(qp->cb_data, NTB_LINK_UP); + } else if (nt->transport_link == NTB_LINK_UP) + schedule_delayed_work(&qp->link_work, + msecs_to_jiffies(NTB_LINK_DOWN_TIMEOUT)); +} + +static void ntb_transport_init_queue(struct ntb_transport *nt, + unsigned int qp_num) +{ + struct ntb_transport_qp *qp; + + qp = &nt->qps[qp_num]; + qp->qp_num = qp_num; + qp->transport = nt; + qp->ndev = nt->ndev; + qp->qp_link = NTB_LINK_DOWN; + qp->client_ready = NTB_LINK_DOWN; + qp->event_handler = NULL; + + if (nt->debugfs_dir) { + char debugfs_name[4]; + + snprintf(debugfs_name, 4, "qp%d", qp_num); + qp->debugfs_dir = debugfs_create_dir(debugfs_name, + nt->debugfs_dir); + + qp->debugfs_stats = debugfs_create_file("stats", S_IRUSR, + qp->debugfs_dir, qp, + &ntb_qp_debugfs_stats); + } + + INIT_DELAYED_WORK(&qp->link_work, ntb_qp_link_work); + + spin_lock_init(&qp->ntb_rx_pend_q_lock); + spin_lock_init(&qp->ntb_rx_free_q_lock); + spin_lock_init(&qp->ntb_tx_free_q_lock); + + INIT_LIST_HEAD(&qp->rx_pend_q); + INIT_LIST_HEAD(&qp->rx_free_q); + INIT_LIST_HEAD(&qp->tx_free_q); +} + +int ntb_transport_init(struct pci_dev *pdev) +{ + struct ntb_transport *nt; + int rc, i; + + nt = kzalloc(sizeof(struct ntb_transport), GFP_KERNEL); + if (!nt) + return -ENOMEM; + + if (debugfs_initialized()) + nt->debugfs_dir = debugfs_create_dir(KBUILD_MODNAME, NULL); + else + nt->debugfs_dir = NULL; + + nt->ndev = ntb_register_transport(pdev, nt); + if (!nt->ndev) { + rc = -EIO; + goto err; + } + + nt->max_qps = min(nt->ndev->max_cbs, max_num_clients); + + nt->qps = kcalloc(nt->max_qps, sizeof(struct ntb_transport_qp), + GFP_KERNEL); + if (!nt->qps) { + rc = -ENOMEM; + goto err1; + } + + nt->qp_bitmap = ((u64) 1 << nt->max_qps) - 1; + + for (i = 0; i < nt->max_qps; i++) + ntb_transport_init_queue(nt, i); + + INIT_DELAYED_WORK(&nt->link_work, ntb_transport_link_work); + + rc = ntb_register_event_callback(nt->ndev, + ntb_transport_event_callback); + if (rc) + goto err2; + + INIT_LIST_HEAD(&nt->client_devs); + rc = ntb_bus_init(nt); + if (rc) + goto err3; + + if (ntb_hw_link_status(nt->ndev)) + schedule_delayed_work(&nt->link_work, 0); + + return 0; + +err3: + ntb_unregister_event_callback(nt->ndev); +err2: + kfree(nt->qps); +err1: + ntb_unregister_transport(nt->ndev); +err: + debugfs_remove_recursive(nt->debugfs_dir); + kfree(nt); + return rc; +} + +void ntb_transport_free(void *transport) +{ + struct ntb_transport *nt = transport; + struct pci_dev *pdev; + int i; + + nt->transport_link = NTB_LINK_DOWN; + + /* verify that all the qp's are freed */ + for (i = 0; i < nt->max_qps; i++) + if (!test_bit(i, &nt->qp_bitmap)) + ntb_transport_free_queue(&nt->qps[i]); + + ntb_bus_remove(nt); + + cancel_delayed_work_sync(&nt->link_work); + + debugfs_remove_recursive(nt->debugfs_dir); + + ntb_unregister_event_callback(nt->ndev); + + pdev = ntb_query_pdev(nt->ndev); + + for (i = 0; i < NTB_NUM_MW; i++) + if (nt->mw[i].virt_addr) + dma_free_coherent(&pdev->dev, nt->mw[i].size, + nt->mw[i].virt_addr, + nt->mw[i].dma_addr); + + kfree(nt->qps); + ntb_unregister_transport(nt->ndev); + kfree(nt); +} + +static void ntb_rx_copy_task(struct ntb_transport_qp *qp, + struct ntb_queue_entry *entry, void *offset) +{ + + struct ntb_payload_header *hdr; + + BUG_ON(offset < qp->rx_buff_begin || + offset + transport_mtu >= qp->rx_buff_end); + + hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + entry->len = hdr->len; + + memcpy(entry->buf, offset, entry->len); + + /* Ensure that the data is fully copied out before clearing the flag */ + wmb(); + hdr->flags = 0; + + if (qp->rx_handler && qp->client_ready == NTB_LINK_UP) + qp->rx_handler(qp, qp->cb_data, entry->cb_data, entry->len); + + ntb_list_add(&qp->ntb_rx_free_q_lock, &entry->entry, &qp->rx_free_q); +} + +static int ntb_process_rxc(struct ntb_transport_qp *qp) +{ + struct ntb_payload_header *hdr; + struct ntb_queue_entry *entry; + void *offset; + + entry = ntb_list_rm(&qp->ntb_rx_pend_q_lock, &qp->rx_pend_q); + if (!entry) { + hdr = offset + transport_mtu - + sizeof(struct ntb_payload_header); + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, + "no buffer - HDR ver %llu, len %d, flags %x\n", + hdr->ver, hdr->len, hdr->flags); + qp->rx_err_no_buf++; + return -ENOMEM; + } + + offset = qp->rx_offset; + hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + + if (!(hdr->flags & DESC_DONE_FLAG)) { + ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, + &qp->rx_pend_q); + qp->rx_ring_empty++; + return -EAGAIN; + } + + if (hdr->ver != qp->rx_pkts) { + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, + "qp %d: version mismatch, expected %llu - got %llu\n", + qp->qp_num, qp->rx_pkts, hdr->ver); + ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, + &qp->rx_pend_q); + qp->rx_err_ver++; + return -EIO; + } + + if (hdr->flags & LINK_DOWN_FLAG) { + ntb_qp_link_down(qp); + + ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, + &qp->rx_pend_q); + + /* Ensure that the data is fully copied out before clearing the + * done flag + */ + wmb(); + hdr->flags = 0; + goto out; + } + + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, + "rx offset %p, ver %llu - %d payload received, buf size %d\n", + qp->rx_offset, hdr->ver, hdr->len, entry->len); + + if (hdr->len <= entry->len) + ntb_rx_copy_task(qp, entry, offset); + else { + ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, + &qp->rx_pend_q); + + /* Ensure that the data is fully copied out before clearing the + * done flag + */ + wmb(); + hdr->flags = 0; + qp->rx_err_oflow++; + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, + "RX overflow! Wanted %d got %d\n", + hdr->len, entry->len); + } + + qp->rx_bytes += hdr->len; + qp->rx_pkts++; + +out: + qp->rx_offset += transport_mtu; + if (qp->rx_offset + transport_mtu >= qp->rx_buff_end) + qp->rx_offset = qp->rx_buff_begin; + + return 0; +} + +static void ntb_transport_rx(unsigned long data) +{ + struct ntb_transport_qp *qp = (struct ntb_transport_qp *)data; + int rc; + + do { + rc = ntb_process_rxc(qp); + } while (!rc); +} + +static void ntb_transport_rxc_db(void *data, int db_num) +{ + struct ntb_transport_qp *qp = data; + + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, "%s: doorbell %d received\n", + __func__, db_num); + + tasklet_schedule(&qp->rx_work); +} + +static void ntb_tx_copy_task(struct ntb_transport_qp *qp, + struct ntb_queue_entry *entry, + void *offset) +{ + struct ntb_payload_header *hdr; + + BUG_ON(offset < qp->tx_mw_begin || + offset + transport_mtu >= qp->tx_mw_end); + + memcpy_toio(offset, entry->buf, entry->len); + + hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + hdr->len = entry->len; + hdr->ver = qp->tx_pkts; + + /* Ensure that the data is fully copied out before setting the flag */ + mmiowb(); + hdr->flags = entry->flags | DESC_DONE_FLAG; + + ntb_ring_sdb(qp->ndev, qp->qp_num); + + /* The entry length can only be zero if the packet is intended to be a + * "link down" or similar. Since no payload is being sent in these + * cases, there is nothing to add to the completion queue. + */ + if (entry->len > 0) { + qp->tx_bytes += entry->len; + + if (qp->tx_handler) + qp->tx_handler(qp, qp->cb_data, entry->cb_data, + entry->len); + } + + ntb_list_add(&qp->ntb_tx_free_q_lock, &entry->entry, &qp->tx_free_q); +} + +static int ntb_process_tx(struct ntb_transport_qp *qp, + struct ntb_queue_entry *entry) +{ + struct ntb_payload_header *hdr; + void *offset; + + offset = qp->tx_offset; + hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, "%lld - offset %p, tx %p, entry len %d flags %x buff %p\n", + qp->tx_pkts, offset, qp->tx_offset, entry->len, entry->flags, + entry->buf); + if (hdr->flags) { + qp->tx_ring_full++; + return -EAGAIN; + } + + if (entry->len > transport_mtu - sizeof(struct ntb_payload_header)) { + if (qp->tx_handler) + qp->tx_handler(qp->cb_data, qp, NULL, -EIO); + + ntb_list_add(&qp->ntb_tx_free_q_lock, &entry->entry, + &qp->tx_free_q); + return 0; + } + + ntb_tx_copy_task(qp, entry, offset); + + qp->tx_offset += transport_mtu; + if (qp->tx_offset + transport_mtu >= qp->tx_mw_end) + qp->tx_offset = qp->tx_mw_begin; + + qp->tx_pkts++; + + return 0; +} + +static void ntb_send_link_down(struct ntb_transport_qp *qp) +{ + struct pci_dev *pdev = ntb_query_pdev(qp->ndev); + struct ntb_queue_entry *entry; + int i, rc; + + if (qp->qp_link == NTB_LINK_DOWN) + return; + + qp->qp_link = NTB_LINK_DOWN; + dev_info(&pdev->dev, "qp %d: Link Down\n", qp->qp_num); + + for (i = 0; i < NTB_LINK_DOWN_TIMEOUT; i++) { + entry = ntb_list_rm(&qp->ntb_tx_free_q_lock, + &qp->tx_free_q); + if (entry) + break; + msleep(100); + } + + if (!entry) + return; + + entry->cb_data = NULL; + entry->buf = NULL; + entry->len = 0; + entry->flags = LINK_DOWN_FLAG; + + rc = ntb_process_tx(qp, entry); + if (rc) + dev_err(&pdev->dev, "ntb: QP%d unable to send linkdown msg\n", + qp->qp_num); +} + +/** + * ntb_transport_create_queue - Create a new NTB transport layer queue + * @rx_handler: receive callback function + * @tx_handler: transmit callback function + * @event_handler: event callback function + * + * Create a new NTB transport layer queue and provide the queue with a callback + * routine for both transmit and receive. The receive callback routine will be + * used to pass up data when the transport has received it on the queue. The + * transmit callback routine will be called when the transport has completed the + * transmission of the data on the queue and the data is ready to be freed. + * + * RETURNS: pointer to newly created ntb_queue, NULL on error. + */ +struct ntb_transport_qp * +ntb_transport_create_queue(void *data, struct pci_dev *pdev, + const struct ntb_queue_handlers *handlers) +{ + struct ntb_queue_entry *entry; + struct ntb_transport_qp *qp; + struct ntb_transport *nt; + unsigned int free_queue; + int rc, i; + + nt = ntb_find_transport(pdev); + if (!nt) + goto err; + + free_queue = ffs(nt->qp_bitmap); + if (!free_queue) + goto err; + + /* decrement free_queue to make it zero based */ + free_queue--; + + clear_bit(free_queue, &nt->qp_bitmap); + + qp = &nt->qps[free_queue]; + qp->cb_data = data; + qp->rx_handler = handlers->rx_handler; + qp->tx_handler = handlers->tx_handler; + qp->event_handler = handlers->event_handler; + + for (i = 0; i < NTB_QP_DEF_NUM_ENTRIES; i++) { + entry = kzalloc(sizeof(struct ntb_queue_entry), GFP_ATOMIC); + if (!entry) + goto err1; + + ntb_list_add(&qp->ntb_rx_free_q_lock, &entry->entry, + &qp->rx_free_q); + } + + for (i = 0; i < NTB_QP_DEF_NUM_ENTRIES; i++) { + entry = kzalloc(sizeof(struct ntb_queue_entry), GFP_ATOMIC); + if (!entry) + goto err2; + + ntb_list_add(&qp->ntb_tx_free_q_lock, &entry->entry, + &qp->tx_free_q); + } + + tasklet_init(&qp->rx_work, ntb_transport_rx, (unsigned long) qp); + + rc = ntb_register_db_callback(qp->ndev, free_queue, qp, + ntb_transport_rxc_db); + if (rc) + goto err3; + + dev_info(&pdev->dev, "NTB Transport QP %d created\n", qp->qp_num); + + return qp; + +err3: + tasklet_disable(&qp->rx_work); +err2: + while ((entry = + ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q))) + kfree(entry); +err1: + while ((entry = + ntb_list_rm(&qp->ntb_rx_free_q_lock, &qp->rx_free_q))) + kfree(entry); + set_bit(free_queue, &nt->qp_bitmap); +err: + return NULL; +} +EXPORT_SYMBOL_GPL(ntb_transport_create_queue); + +/** + * ntb_transport_free_queue - Frees NTB transport queue + * @qp: NTB queue to be freed + * + * Frees NTB transport queue + */ +void ntb_transport_free_queue(struct ntb_transport_qp *qp) +{ + struct pci_dev *pdev = ntb_query_pdev(qp->ndev); + struct ntb_queue_entry *entry; + + if (!qp) + return; + + cancel_delayed_work_sync(&qp->link_work); + + ntb_unregister_db_callback(qp->ndev, qp->qp_num); + tasklet_disable(&qp->rx_work); + + while ((entry = + ntb_list_rm(&qp->ntb_rx_free_q_lock, &qp->rx_free_q))) + kfree(entry); + + while ((entry = + ntb_list_rm(&qp->ntb_rx_pend_q_lock, &qp->rx_pend_q))) { + dev_warn(&pdev->dev, "Freeing item from a non-empty queue\n"); + kfree(entry); + } + + while ((entry = + ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q))) + kfree(entry); + + set_bit(qp->qp_num, &qp->transport->qp_bitmap); + + dev_info(&pdev->dev, "NTB Transport QP %d freed\n", qp->qp_num); +} +EXPORT_SYMBOL_GPL(ntb_transport_free_queue); + +/** + * ntb_transport_rx_remove - Dequeues enqueued rx packet + * @qp: NTB queue to be freed + * @len: pointer to variable to write enqueued buffers length + * + * Dequeues unused buffers from receive queue. Should only be used during + * shutdown of qp. + * + * RETURNS: NULL error value on error, or void* for success. + */ +void *ntb_transport_rx_remove(struct ntb_transport_qp *qp, unsigned int *len) +{ + struct ntb_queue_entry *entry; + void *buf; + + if (!qp || qp->client_ready == NTB_LINK_UP) + return NULL; + + entry = ntb_list_rm(&qp->ntb_rx_pend_q_lock, &qp->rx_pend_q); + if (!entry) + return NULL; + + buf = entry->cb_data; + *len = entry->len; + + ntb_list_add(&qp->ntb_rx_free_q_lock, &entry->entry, + &qp->rx_free_q); + + return buf; +} +EXPORT_SYMBOL_GPL(ntb_transport_rx_remove); + +/** + * ntb_transport_rx_enqueue - Enqueue a new NTB queue entry + * @qp: NTB transport layer queue the entry is to be enqueued on + * @cb: per buffer pointer for callback function to use + * @data: pointer to data buffer that incoming packets will be copied into + * @len: length of the data buffer + * + * Enqueue a new receive buffer onto the transport queue into which a NTB + * payload can be received into. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_transport_rx_enqueue(struct ntb_transport_qp *qp, void *cb, void *data, + unsigned int len) +{ + struct ntb_queue_entry *entry; + + if (!qp) + return -EINVAL; + + entry = ntb_list_rm(&qp->ntb_rx_free_q_lock, &qp->rx_free_q); + if (!entry) + return -ENOMEM; + + entry->cb_data = cb; + entry->buf = data; + entry->len = len; + + ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, + &qp->rx_pend_q); + + return 0; +} +EXPORT_SYMBOL_GPL(ntb_transport_rx_enqueue); + +/** + * ntb_transport_tx_enqueue - Enqueue a new NTB queue entry + * @qp: NTB transport layer queue the entry is to be enqueued on + * @cb: per buffer pointer for callback function to use + * @data: pointer to data buffer that will be sent + * @len: length of the data buffer + * + * Enqueue a new transmit buffer onto the transport queue from which a NTB + * payload will be transmitted. This assumes that a lock is behing held to + * serialize access to the qp. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_transport_tx_enqueue(struct ntb_transport_qp *qp, void *cb, void *data, + unsigned int len) +{ + struct ntb_queue_entry *entry; + int rc; + + if (!qp || qp->qp_link != NTB_LINK_UP || !len) + return -EINVAL; + + entry = ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q); + if (!entry) + return -ENOMEM; + + entry->cb_data = cb; + entry->buf = data; + entry->len = len; + entry->flags = 0; + + rc = ntb_process_tx(qp, entry); + if (rc) + ntb_list_add(&qp->ntb_tx_free_q_lock, &entry->entry, + &qp->tx_free_q); + + return rc; +} +EXPORT_SYMBOL_GPL(ntb_transport_tx_enqueue); + +/** + * ntb_transport_link_up - Notify NTB transport of client readiness to use queue + * @qp: NTB transport layer queue to be enabled + * + * Notify NTB transport layer of client readiness to use queue + */ +void ntb_transport_link_up(struct ntb_transport_qp *qp) +{ + if (!qp) + return; + + qp->client_ready = NTB_LINK_UP; + + if (qp->transport->transport_link == NTB_LINK_UP) + schedule_delayed_work(&qp->link_work, 0); +} +EXPORT_SYMBOL_GPL(ntb_transport_link_up); + +/** + * ntb_transport_link_down - Notify NTB transport to no longer enqueue data + * @qp: NTB transport layer queue to be disabled + * + * Notify NTB transport layer of client's desire to no longer receive data on + * transport queue specified. It is the client's responsibility to ensure all + * entries on queue are purged or otherwise handled appropraitely. + */ +void ntb_transport_link_down(struct ntb_transport_qp *qp) +{ + struct pci_dev *pdev = ntb_query_pdev(qp->ndev); + int rc, val; + + if (!qp) + return; + + qp->client_ready = NTB_LINK_DOWN; + + rc = ntb_read_local_spad(qp->ndev, QP_LINKS, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading spad %d\n", QP_LINKS); + return; + } + + rc = ntb_write_remote_spad(qp->ndev, QP_LINKS, + val & ~(1 << qp->qp_num)); + if (rc) + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + val & ~(1 << qp->qp_num), QP_LINKS); + + if (qp->qp_link == NTB_LINK_UP) + ntb_send_link_down(qp); + else + cancel_delayed_work_sync(&qp->link_work); +} +EXPORT_SYMBOL_GPL(ntb_transport_link_down); + +/** + * ntb_transport_link_query - Query transport link state + * @qp: NTB transport layer queue to be queried + * + * Query connectivity to the remote system of the NTB transport queue + * + * RETURNS: true for link up or false for link down + */ +bool ntb_transport_link_query(struct ntb_transport_qp *qp) +{ + return qp->qp_link == NTB_LINK_UP; +} +EXPORT_SYMBOL_GPL(ntb_transport_link_query); + +/** + * ntb_transport_qp_num - Query the qp number + * @qp: NTB transport layer queue to be queried + * + * Query qp number of the NTB transport queue + * + * RETURNS: a zero based number specifying the qp number + */ +unsigned char ntb_transport_qp_num(struct ntb_transport_qp *qp) +{ + return qp->qp_num; +} +EXPORT_SYMBOL_GPL(ntb_transport_qp_num); + +/** + * ntb_transport_max_size - Query the max payload size of a qp + * @qp: NTB transport layer queue to be queried + * + * Query the maximum payload size permissible on the given qp + * + * RETURNS: the max payload size of a qp + */ +unsigned int +ntb_transport_max_size(__attribute__((unused)) struct ntb_transport_qp *qp) +{ + return transport_mtu - sizeof(struct ntb_payload_header); +} +EXPORT_SYMBOL_GPL(ntb_transport_max_size); diff --git a/include/linux/ntb.h b/include/linux/ntb.h new file mode 100644 index 000000000000..f6a15205853b --- /dev/null +++ b/include/linux/ntb.h @@ -0,0 +1,83 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * BSD LICENSE + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe NTB Linux driver + * + * Contact Information: + * Jon Mason + */ + +struct ntb_transport_qp; + +struct ntb_client { + struct device_driver driver; + int (*probe) (struct pci_dev *pdev); + void (*remove) (struct pci_dev *pdev); +}; + +int ntb_register_client(struct ntb_client *drvr); +void ntb_unregister_client(struct ntb_client *drvr); +int ntb_register_client_dev(char *device_name); +void ntb_unregister_client_dev(char *device_name); + +struct ntb_queue_handlers { + void (*rx_handler) (struct ntb_transport_qp *qp, void *qp_data, + void *data, int len); + void (*tx_handler) (struct ntb_transport_qp *qp, void *qp_data, + void *data, int len); + void (*event_handler) (void *data, int status); +}; + +unsigned char ntb_transport_qp_num(struct ntb_transport_qp *qp); +unsigned int ntb_transport_max_size(struct ntb_transport_qp *qp); +struct ntb_transport_qp * +ntb_transport_create_queue(void *data, struct pci_dev *pdev, + const struct ntb_queue_handlers *handlers); +void ntb_transport_free_queue(struct ntb_transport_qp *qp); +int ntb_transport_rx_enqueue(struct ntb_transport_qp *qp, void *cb, void *data, + unsigned int len); +int ntb_transport_tx_enqueue(struct ntb_transport_qp *qp, void *cb, void *data, + unsigned int len); +void *ntb_transport_rx_remove(struct ntb_transport_qp *qp, unsigned int *len); +void ntb_transport_link_up(struct ntb_transport_qp *qp); +void ntb_transport_link_down(struct ntb_transport_qp *qp); +bool ntb_transport_link_query(struct ntb_transport_qp *qp); -- cgit v1.2.1 From 548c237c0a9972df5d1afaca38aa733ee577128d Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Fri, 16 Nov 2012 19:27:13 -0700 Subject: net: Add support for NTB virtual ethernet device A virtual ethernet device that uses the NTB transport API to send/receive data. Signed-off-by: Jon Mason Reviewed-by: Nicholas Bellinger Acked-by: David S. Miller Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + drivers/net/Kconfig | 4 + drivers/net/Makefile | 1 + drivers/net/ntb_netdev.c | 419 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 425 insertions(+) create mode 100644 drivers/net/ntb_netdev.c diff --git a/MAINTAINERS b/MAINTAINERS index c5d675a8c53d..01bee6aadb48 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5398,6 +5398,7 @@ NTB DRIVER M: Jon Mason S: Supported F: drivers/ntb/ +F: drivers/net/ntb_netdev.c F: include/linux/ntb.h NTFS FILESYSTEM diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 6a70184c3f23..5db9acbc0cae 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -189,6 +189,10 @@ config NETPOLL_TRAP config NET_POLL_CONTROLLER def_bool NETPOLL +config NTB_NETDEV + tristate "Virtual Ethernet over NTB" + depends on NTB + config RIONET tristate "RapidIO Ethernet over messaging driver support" depends on RAPIDIO diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 335db78fd987..ef3d090efedf 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -71,3 +71,4 @@ obj-$(CONFIG_USB_IPHETH) += usb/ obj-$(CONFIG_USB_CDC_PHONET) += usb/ obj-$(CONFIG_HYPERV_NET) += hyperv/ +obj-$(CONFIG_NTB_NETDEV) += ntb_netdev.o diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c new file mode 100644 index 000000000000..af48a69cab02 --- /dev/null +++ b/drivers/net/ntb_netdev.c @@ -0,0 +1,419 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * BSD LICENSE + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe NTB Network Linux driver + * + * Contact Information: + * Jon Mason + */ +#include +#include +#include +#include +#include + +#define NTB_NETDEV_VER "0.6" + +MODULE_DESCRIPTION(KBUILD_MODNAME); +MODULE_VERSION(NTB_NETDEV_VER); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Intel Corporation"); + +struct ntb_netdev { + struct list_head list; + struct pci_dev *pdev; + struct net_device *ndev; + struct ntb_transport_qp *qp; +}; + +#define NTB_TX_TIMEOUT_MS 1000 +#define NTB_RXQ_SIZE 100 + +static LIST_HEAD(dev_list); + +static void ntb_netdev_event_handler(void *data, int status) +{ + struct net_device *ndev = data; + struct ntb_netdev *dev = netdev_priv(ndev); + + netdev_dbg(ndev, "Event %x, Link %x\n", status, + ntb_transport_link_query(dev->qp)); + + /* Currently, only link status event is supported */ + if (status) + netif_carrier_on(ndev); + else + netif_carrier_off(ndev); +} + +static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data, + void *data, int len) +{ + struct net_device *ndev = qp_data; + struct sk_buff *skb; + int rc; + + skb = data; + if (!skb) + return; + + netdev_dbg(ndev, "%s: %d byte payload received\n", __func__, len); + + skb_put(skb, len); + skb->protocol = eth_type_trans(skb, ndev); + skb->ip_summed = CHECKSUM_NONE; + + if (netif_rx(skb) == NET_RX_DROP) { + ndev->stats.rx_errors++; + ndev->stats.rx_dropped++; + } else { + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += len; + } + + skb = netdev_alloc_skb(ndev, ndev->mtu + ETH_HLEN); + if (!skb) { + ndev->stats.rx_errors++; + ndev->stats.rx_frame_errors++; + return; + } + + rc = ntb_transport_rx_enqueue(qp, skb, skb->data, ndev->mtu + ETH_HLEN); + if (rc) { + ndev->stats.rx_errors++; + ndev->stats.rx_fifo_errors++; + } +} + +static void ntb_netdev_tx_handler(struct ntb_transport_qp *qp, void *qp_data, + void *data, int len) +{ + struct net_device *ndev = qp_data; + struct sk_buff *skb; + + skb = data; + if (!skb || !ndev) + return; + + if (len > 0) { + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += skb->len; + } else { + ndev->stats.tx_errors++; + ndev->stats.tx_aborted_errors++; + } + + dev_kfree_skb(skb); + + if (netif_queue_stopped(ndev)) + netif_wake_queue(ndev); +} + +static netdev_tx_t ntb_netdev_start_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + struct ntb_netdev *dev = netdev_priv(ndev); + int rc; + + netdev_dbg(ndev, "ntb_transport_tx_enqueue\n"); + + rc = ntb_transport_tx_enqueue(dev->qp, skb, skb->data, skb->len); + if (rc) + goto err; + + return NETDEV_TX_OK; + +err: + ndev->stats.tx_dropped++; + ndev->stats.tx_errors++; + netif_stop_queue(ndev); + return NETDEV_TX_BUSY; +} + +static int ntb_netdev_open(struct net_device *ndev) +{ + struct ntb_netdev *dev = netdev_priv(ndev); + struct sk_buff *skb; + int rc, i, len; + + /* Add some empty rx bufs */ + for (i = 0; i < NTB_RXQ_SIZE; i++) { + skb = netdev_alloc_skb(ndev, ndev->mtu + ETH_HLEN); + if (!skb) { + rc = -ENOMEM; + goto err; + } + + rc = ntb_transport_rx_enqueue(dev->qp, skb, skb->data, + ndev->mtu + ETH_HLEN); + if (rc == -EINVAL) + goto err; + } + + netif_carrier_off(ndev); + ntb_transport_link_up(dev->qp); + + return 0; + +err: + while ((skb = ntb_transport_rx_remove(dev->qp, &len))) + dev_kfree_skb(skb); + return rc; +} + +static int ntb_netdev_close(struct net_device *ndev) +{ + struct ntb_netdev *dev = netdev_priv(ndev); + struct sk_buff *skb; + int len; + + ntb_transport_link_down(dev->qp); + + while ((skb = ntb_transport_rx_remove(dev->qp, &len))) + dev_kfree_skb(skb); + + return 0; +} + +static int ntb_netdev_change_mtu(struct net_device *ndev, int new_mtu) +{ + struct ntb_netdev *dev = netdev_priv(ndev); + struct sk_buff *skb; + int len, rc; + + if (new_mtu > ntb_transport_max_size(dev->qp) - ETH_HLEN) + return -EINVAL; + + if (!netif_running(ndev)) { + ndev->mtu = new_mtu; + return 0; + } + + /* Bring down the link and dispose of posted rx entries */ + ntb_transport_link_down(dev->qp); + + if (ndev->mtu < new_mtu) { + int i; + + for (i = 0; (skb = ntb_transport_rx_remove(dev->qp, &len)); i++) + dev_kfree_skb(skb); + + for (; i; i--) { + skb = netdev_alloc_skb(ndev, new_mtu + ETH_HLEN); + if (!skb) { + rc = -ENOMEM; + goto err; + } + + rc = ntb_transport_rx_enqueue(dev->qp, skb, skb->data, + new_mtu + ETH_HLEN); + if (rc) { + dev_kfree_skb(skb); + goto err; + } + } + } + + ndev->mtu = new_mtu; + + ntb_transport_link_up(dev->qp); + + return 0; + +err: + ntb_transport_link_down(dev->qp); + + while ((skb = ntb_transport_rx_remove(dev->qp, &len))) + dev_kfree_skb(skb); + + netdev_err(ndev, "Error changing MTU, device inoperable\n"); + return rc; +} + +static void ntb_netdev_tx_timeout(struct net_device *ndev) +{ + if (netif_running(ndev)) + netif_wake_queue(ndev); +} + +static const struct net_device_ops ntb_netdev_ops = { + .ndo_open = ntb_netdev_open, + .ndo_stop = ntb_netdev_close, + .ndo_start_xmit = ntb_netdev_start_xmit, + .ndo_change_mtu = ntb_netdev_change_mtu, + .ndo_tx_timeout = ntb_netdev_tx_timeout, + .ndo_set_mac_address = eth_mac_addr, +}; + +static void ntb_get_drvinfo(struct net_device *ndev, + struct ethtool_drvinfo *info) +{ + struct ntb_netdev *dev = netdev_priv(ndev); + + strlcpy(info->driver, KBUILD_MODNAME, sizeof(info->driver)); + strlcpy(info->version, NTB_NETDEV_VER, sizeof(info->version)); + strlcpy(info->bus_info, pci_name(dev->pdev), sizeof(info->bus_info)); +} + +static int ntb_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + cmd->supported = SUPPORTED_Backplane; + cmd->advertising = ADVERTISED_Backplane; + cmd->speed = SPEED_UNKNOWN; + ethtool_cmd_speed_set(cmd, SPEED_UNKNOWN); + cmd->duplex = DUPLEX_FULL; + cmd->port = PORT_OTHER; + cmd->phy_address = 0; + cmd->transceiver = XCVR_DUMMY1; + cmd->autoneg = AUTONEG_ENABLE; + cmd->maxtxpkt = 0; + cmd->maxrxpkt = 0; + + return 0; +} + +static const struct ethtool_ops ntb_ethtool_ops = { + .get_drvinfo = ntb_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_settings = ntb_get_settings, +}; + +static const struct ntb_queue_handlers ntb_netdev_handlers = { + .tx_handler = ntb_netdev_tx_handler, + .rx_handler = ntb_netdev_rx_handler, + .event_handler = ntb_netdev_event_handler, +}; + +static int __devinit ntb_netdev_probe(struct pci_dev *pdev) +{ + struct net_device *ndev; + struct ntb_netdev *dev; + int rc; + + ndev = alloc_etherdev(sizeof(struct ntb_netdev)); + if (!ndev) + return -ENOMEM; + + dev = netdev_priv(ndev); + dev->ndev = ndev; + dev->pdev = pdev; + BUG_ON(!dev->pdev); + ndev->features = NETIF_F_HIGHDMA; + + ndev->priv_flags |= IFF_LIVE_ADDR_CHANGE; + + ndev->hw_features = ndev->features; + ndev->watchdog_timeo = msecs_to_jiffies(NTB_TX_TIMEOUT_MS); + + random_ether_addr(ndev->perm_addr); + memcpy(ndev->dev_addr, ndev->perm_addr, ndev->addr_len); + + ndev->netdev_ops = &ntb_netdev_ops; + SET_ETHTOOL_OPS(ndev, &ntb_ethtool_ops); + + dev->qp = ntb_transport_create_queue(ndev, pdev, &ntb_netdev_handlers); + if (!dev->qp) { + rc = -EIO; + goto err; + } + + ndev->mtu = ntb_transport_max_size(dev->qp) - ETH_HLEN; + + rc = register_netdev(ndev); + if (rc) + goto err1; + + list_add(&dev->list, &dev_list); + pr_info("%s: %s created\n", KBUILD_MODNAME, ndev->name); + return 0; + +err1: + ntb_transport_free_queue(dev->qp); +err: + free_netdev(ndev); + return rc; +} + +static void __exit ntb_netdev_remove(struct pci_dev *pdev) +{ + struct net_device *ndev; + struct ntb_netdev *dev; + + list_for_each_entry(dev, &dev_list, list) { + if (dev->pdev == pdev) + break; + } + if (dev == NULL) + return; + + ndev = dev->ndev; + + unregister_netdev(ndev); + ntb_transport_free_queue(dev->qp); + free_netdev(ndev); +} + +static struct ntb_client ntb_netdev_client = { + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .probe = ntb_netdev_probe, + .remove = ntb_netdev_remove, +}; + +static int __init ntb_netdev_init_module(void) +{ + int rc; + + rc = ntb_register_client_dev(KBUILD_MODNAME); + if (rc) + return rc; + return ntb_register_client(&ntb_netdev_client); +} +module_init(ntb_netdev_init_module); + +static void __exit ntb_netdev_exit_module(void) +{ + ntb_unregister_client(&ntb_netdev_client); + ntb_unregister_client_dev(KBUILD_MODNAME); + pr_info("%s: Driver removed\n", KBUILD_MODNAME); +} +module_exit(ntb_netdev_exit_module); -- cgit v1.2.1 From 78a61ab79ae6bd75593b63fbaf5299c96bac0ea4 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 17 Jan 2013 19:17:42 -0800 Subject: ntb: remove __dev* markings These are now gone from the kernel, so remove them from the newly-added drivers before they start to cause build errors for people. Cc: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/net/ntb_netdev.c | 2 +- drivers/ntb/ntb_hw.c | 16 +++++++--------- drivers/ntb/ntb_transport.c | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index af48a69cab02..4e52fd2b8226 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -323,7 +323,7 @@ static const struct ntb_queue_handlers ntb_netdev_handlers = { .event_handler = ntb_netdev_event_handler, }; -static int __devinit ntb_netdev_probe(struct pci_dev *pdev) +static int ntb_netdev_probe(struct pci_dev *pdev) { struct net_device *ndev; struct ntb_netdev *dev; diff --git a/drivers/ntb/ntb_hw.c b/drivers/ntb/ntb_hw.c index facad51fbc7a..4c71b17f0166 100644 --- a/drivers/ntb/ntb_hw.c +++ b/drivers/ntb/ntb_hw.c @@ -637,7 +637,7 @@ static int ntb_bwd_setup(struct ntb_device *ndev) return 0; } -static int __devinit ntb_device_setup(struct ntb_device *ndev) +static int ntb_device_setup(struct ntb_device *ndev) { int rc; @@ -909,7 +909,7 @@ static int ntb_setup_intx(struct ntb_device *ndev) return 0; } -static int __devinit ntb_setup_interrupts(struct ntb_device *ndev) +static int ntb_setup_interrupts(struct ntb_device *ndev) { int rc; @@ -943,7 +943,7 @@ done: return 0; } -static void __devexit ntb_free_interrupts(struct ntb_device *ndev) +static void ntb_free_interrupts(struct ntb_device *ndev) { struct pci_dev *pdev = ndev->pdev; @@ -973,7 +973,7 @@ static void __devexit ntb_free_interrupts(struct ntb_device *ndev) } } -static int __devinit ntb_create_callbacks(struct ntb_device *ndev) +static int ntb_create_callbacks(struct ntb_device *ndev) { int i; @@ -1006,9 +1006,7 @@ static void ntb_free_callbacks(struct ntb_device *ndev) kfree(ndev->db_cb); } -static int __devinit -ntb_pci_probe(struct pci_dev *pdev, - __attribute__((unused)) const struct pci_device_id *id) +static int ntb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct ntb_device *ndev; int rc, i; @@ -1122,7 +1120,7 @@ err: return rc; } -static void __devexit ntb_pci_remove(struct pci_dev *pdev) +static void ntb_pci_remove(struct pci_dev *pdev) { struct ntb_device *ndev = pci_get_drvdata(pdev); int i; @@ -1152,6 +1150,6 @@ static struct pci_driver ntb_pci_driver = { .name = KBUILD_MODNAME, .id_table = ntb_pci_tbl, .probe = ntb_pci_probe, - .remove = __devexit_p(ntb_pci_remove), + .remove = ntb_pci_remove, }; module_pci_driver(ntb_pci_driver); diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index c907e0773532..250190fba757 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -217,7 +217,7 @@ struct bus_type ntb_bus_type = { static LIST_HEAD(ntb_transport_list); -static int __devinit ntb_bus_init(struct ntb_transport *nt) +static int ntb_bus_init(struct ntb_transport *nt) { if (list_empty(&ntb_transport_list)) { int rc = bus_register(&ntb_bus_type); @@ -230,7 +230,7 @@ static int __devinit ntb_bus_init(struct ntb_transport *nt) return 0; } -static void __devexit ntb_bus_remove(struct ntb_transport *nt) +static void ntb_bus_remove(struct ntb_transport *nt) { struct ntb_transport_client_dev *client_dev, *cd; -- cgit v1.2.1 From 83ebf6e562afc31cf4c6346ee87a7dd9fe39c322 Mon Sep 17 00:00:00 2001 From: Fengguang Wu Date: Fri, 18 Jan 2013 09:09:32 +0800 Subject: Drivers: hv: vmbus_flow_handler() can be static Signed-off-by: Fengguang Wu Signed-off-by: Greg Kroah-Hartman --- drivers/hv/vmbus_drv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 797142c07fa0..cf19dfa5ead1 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -509,7 +509,7 @@ static irqreturn_t vmbus_isr(int irq, void *dev_id) * can be handled concurrently. */ -void vmbus_flow_handler(unsigned int irq, struct irq_desc *desc) +static void vmbus_flow_handler(unsigned int irq, struct irq_desc *desc) { kstat_incr_irqs_this_cpu(irq, desc); -- cgit v1.2.1 From 0f3f2f86b22c41bb102a15ca4ca7f41c7414ab0d Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Thu, 17 Jan 2013 21:00:51 -0800 Subject: Drivers: hv: Bind all vmbbus interrupts to the boot CPU The default interrupt delivery model in Linux does not support the Hyper-V vmbus delivery model when the guest is configured with multiple VCPUs. I have sent a patch to address this - delivering the vmbus interrupt on a separate IDT vector. Until this patch is applied, bind all vmbus interrupts to the boot CPU. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index c80fe6245f5b..7478ef914b9a 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -337,7 +337,7 @@ static u32 get_vp_index(uuid_le *type_guid) return 0; } cur_cpu = (++next_vp % max_cpus); - return hv_context.vp_index[cur_cpu]; + return 0; } /* -- cgit v1.2.1 From 8467fdbb09ca0766b638171723624f3da8703055 Mon Sep 17 00:00:00 2001 From: Tomas Hozza Date: Fri, 18 Jan 2013 15:23:41 +0100 Subject: tools: hv: Use CLOEXEC when opening kvp_pool files Use CLOEXEC flag when opening kvp_pool_x files to prevent file descriptor leakage. Not using it was causing a problem when SELinux was enabled. Signed-off-by: Tomas Hozza Acked-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- tools/hv/hv_kvp_daemon.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index 384051745c5e..c800ea4c8bf9 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c @@ -151,7 +151,7 @@ static void kvp_update_file(int pool) */ kvp_acquire_lock(pool); - filep = fopen(kvp_file_info[pool].fname, "w"); + filep = fopen(kvp_file_info[pool].fname, "we"); if (!filep) { kvp_release_lock(pool); syslog(LOG_ERR, "Failed to open file, pool: %d", pool); @@ -182,7 +182,7 @@ static void kvp_update_mem_state(int pool) kvp_acquire_lock(pool); - filep = fopen(kvp_file_info[pool].fname, "r"); + filep = fopen(kvp_file_info[pool].fname, "re"); if (!filep) { kvp_release_lock(pool); syslog(LOG_ERR, "Failed to open file, pool: %d", pool); @@ -246,13 +246,13 @@ static int kvp_file_init(void) records_read = 0; num_blocks = 1; sprintf(fname, "%s/.kvp_pool_%d", KVP_CONFIG_LOC, i); - fd = open(fname, O_RDWR | O_CREAT, 0644 /* rw-r--r-- */); + fd = open(fname, O_RDWR | O_CREAT | O_CLOEXEC, 0644 /* rw-r--r-- */); if (fd == -1) return 1; - filep = fopen(fname, "r"); + filep = fopen(fname, "re"); if (!filep) return 1; -- cgit v1.2.1 From 2910fe2a7d0dc0d01944110e462045441ba0856f Mon Sep 17 00:00:00 2001 From: Samuel Iglesias Gonsalvez Date: Fri, 18 Jan 2013 08:57:21 +0100 Subject: ipack/devices/ipoctal: add missing rx_enable = 1 There was a bug in the code when managing a GE IP-OCTAL-485 board. The RX would be enabled but we have a wrong state in the rx_enable flag. Then, if the user changes the terminal settings, RX would not be enabled again. Signed-off-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 0b3c4b8fe830..93cbf9dda1fe 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -207,6 +207,7 @@ static void ipoctal_irq_channel(struct ipoctal_channel *channel) if (channel->board_id == IPACK1_DEVICE_ID_SBS_OCTAL_485) { iowrite8(CR_CMD_NEGATE_RTSN, &channel->regs->w.cr); iowrite8(CR_ENABLE_RX, &channel->regs->w.cr); + channel->rx_enable = 1; } } -- cgit v1.2.1 From 8222b402e2df3b92948141046bae82fb774f3f64 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:16 -0700 Subject: NTB: Handle ntb client device probes without present hardware Attempts to probe client ntb drivers without ntb hardware present will result in null pointer dereference due to the lack of the ntb bus device being registers. Check to see if this is the case, and fail all calls by the clients registering their drivers. Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_transport.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index 250190fba757..1d17857a2d97 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -288,6 +288,9 @@ int ntb_register_client_dev(char *device_name) struct ntb_transport *nt; int rc; + if (list_empty(&ntb_transport_list)) + return -ENODEV; + list_for_each_entry(nt, &ntb_transport_list, entry) { struct device *dev; @@ -336,6 +339,9 @@ int ntb_register_client(struct ntb_client *drv) { drv->driver.bus = &ntb_bus_type; + if (list_empty(&ntb_transport_list)) + return -ENODEV; + return driver_register(&drv->driver); } EXPORT_SYMBOL_GPL(ntb_register_client); -- cgit v1.2.1 From 842c1ddea5f9949cb21e568408d2af9d986eee69 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:17 -0700 Subject: NTB: correct memory barrier mmiowb is not sufficient to flush the data and is causing data corruption. Change to wmb and the data corruption is no more. Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index 1d17857a2d97..e9666bd7ef41 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -1009,7 +1009,7 @@ static void ntb_tx_copy_task(struct ntb_transport_qp *qp, hdr->ver = qp->tx_pkts; /* Ensure that the data is fully copied out before setting the flag */ - mmiowb(); + wmb(); hdr->flags = entry->flags | DESC_DONE_FLAG; ntb_ring_sdb(qp->ndev, qp->qp_num); -- cgit v1.2.1 From ef114ed5064d35982c16f5cbb338fb586ef48bf7 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:18 -0700 Subject: NTB: separate transmit and receive windows Since it is possible for the memory windows on the two NTB connected systems to be different sizes, the divergent sizes must be accounted for in the segmentation of the MW's on each side. Create separate size variables and initialization as necessary. Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_transport.c | 79 +++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index e9666bd7ef41..2823087a1338 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -60,7 +60,7 @@ #define NTB_TRANSPORT_VERSION 1 -static int transport_mtu = 0x401E; +static unsigned int transport_mtu = 0x401E; module_param(transport_mtu, uint, 0644); MODULE_PARM_DESC(transport_mtu, "Maximum size of NTB transport packets"); @@ -94,6 +94,7 @@ struct ntb_transport_qp { void *tx_mw_begin; void *tx_mw_end; void *tx_offset; + unsigned int tx_max_frame; void (*rx_handler) (struct ntb_transport_qp *qp, void *qp_data, void *data, int len); @@ -105,6 +106,7 @@ struct ntb_transport_qp { void *rx_buff_begin; void *rx_buff_end; void *rx_offset; + unsigned int rx_max_frame; void (*event_handler) (void *data, int status); struct delayed_work link_work; @@ -458,28 +460,29 @@ static void ntb_transport_setup_qp_mw(struct ntb_transport *nt, unsigned int qp_num) { struct ntb_transport_qp *qp = &nt->qps[qp_num]; - unsigned int size, num_qps_mw; + unsigned int rx_size, num_qps_mw; u8 mw_num = QP_TO_MW(qp_num); + void *offset; WARN_ON(nt->mw[mw_num].virt_addr == 0); - if (nt->max_qps % NTB_NUM_MW && !mw_num) - num_qps_mw = nt->max_qps / NTB_NUM_MW + - (nt->max_qps % NTB_NUM_MW - mw_num); + if (nt->max_qps % NTB_NUM_MW && mw_num < nt->max_qps % NTB_NUM_MW) + num_qps_mw = nt->max_qps / NTB_NUM_MW + 1; else num_qps_mw = nt->max_qps / NTB_NUM_MW; - size = nt->mw[mw_num].size / num_qps_mw; - + rx_size = nt->mw[mw_num].size / num_qps_mw; qp->rx_buff_begin = nt->mw[mw_num].virt_addr + - (qp_num / NTB_NUM_MW * size); - qp->rx_buff_end = qp->rx_buff_begin + size; + (qp_num / NTB_NUM_MW * rx_size); + qp->rx_buff_end = qp->rx_buff_begin + rx_size; qp->rx_offset = qp->rx_buff_begin; + qp->rx_max_frame = min(transport_mtu, rx_size); - qp->tx_mw_begin = ntb_get_mw_vbase(nt->ndev, mw_num) + - (qp_num / NTB_NUM_MW * size); - qp->tx_mw_end = qp->tx_mw_begin + size; - qp->tx_offset = qp->tx_mw_begin; + /* setup the hdr offsets with 0's */ + for (offset = qp->rx_buff_begin + qp->rx_max_frame - + sizeof(struct ntb_payload_header); + offset < qp->rx_buff_end; offset += qp->rx_max_frame) + memset(offset, 0, sizeof(struct ntb_payload_header)); qp->rx_pkts = 0; qp->tx_pkts = 0; @@ -489,7 +492,6 @@ static int ntb_set_mw(struct ntb_transport *nt, int num_mw, unsigned int size) { struct ntb_transport_mw *mw = &nt->mw[num_mw]; struct pci_dev *pdev = ntb_query_pdev(nt->ndev); - void *offset; /* Alloc memory for receiving data. Must be 4k aligned */ mw->size = ALIGN(size, 4096); @@ -502,12 +504,6 @@ static int ntb_set_mw(struct ntb_transport *nt, int num_mw, unsigned int size) return -ENOMEM; } - /* setup the hdr offsets with 0's */ - for (offset = mw->virt_addr + transport_mtu - - sizeof(struct ntb_payload_header); - offset < mw->virt_addr + size; offset += transport_mtu) - memset(offset, 0, sizeof(struct ntb_payload_header)); - /* Notify HW the memory location of the receive buffer */ ntb_set_mw_addr(nt->ndev, num_mw, mw->dma_addr); @@ -737,6 +733,8 @@ static void ntb_transport_init_queue(struct ntb_transport *nt, unsigned int qp_num) { struct ntb_transport_qp *qp; + unsigned int num_qps_mw, tx_size; + u8 mw_num = QP_TO_MW(qp_num); qp = &nt->qps[qp_num]; qp->qp_num = qp_num; @@ -746,6 +744,18 @@ static void ntb_transport_init_queue(struct ntb_transport *nt, qp->client_ready = NTB_LINK_DOWN; qp->event_handler = NULL; + if (nt->max_qps % NTB_NUM_MW && mw_num < nt->max_qps % NTB_NUM_MW) + num_qps_mw = nt->max_qps / NTB_NUM_MW + 1; + else + num_qps_mw = nt->max_qps / NTB_NUM_MW; + + tx_size = ntb_get_mw_size(qp->ndev, mw_num) / num_qps_mw; + qp->tx_mw_begin = ntb_get_mw_vbase(nt->ndev, mw_num) + + (qp_num / NTB_NUM_MW * tx_size); + qp->tx_mw_end = qp->tx_mw_begin + tx_size; + qp->tx_offset = qp->tx_mw_begin; + qp->tx_max_frame = min(transport_mtu, tx_size); + if (nt->debugfs_dir) { char debugfs_name[4]; @@ -873,9 +883,9 @@ static void ntb_rx_copy_task(struct ntb_transport_qp *qp, struct ntb_payload_header *hdr; BUG_ON(offset < qp->rx_buff_begin || - offset + transport_mtu >= qp->rx_buff_end); + offset + qp->rx_max_frame >= qp->rx_buff_end); - hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + hdr = offset + qp->rx_max_frame - sizeof(struct ntb_payload_header); entry->len = hdr->len; memcpy(entry->buf, offset, entry->len); @@ -898,7 +908,7 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) entry = ntb_list_rm(&qp->ntb_rx_pend_q_lock, &qp->rx_pend_q); if (!entry) { - hdr = offset + transport_mtu - + hdr = offset + qp->rx_max_frame - sizeof(struct ntb_payload_header); dev_dbg(&ntb_query_pdev(qp->ndev)->dev, "no buffer - HDR ver %llu, len %d, flags %x\n", @@ -908,7 +918,7 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) } offset = qp->rx_offset; - hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + hdr = offset + qp->rx_max_frame - sizeof(struct ntb_payload_header); if (!(hdr->flags & DESC_DONE_FLAG)) { ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, @@ -966,8 +976,8 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) qp->rx_pkts++; out: - qp->rx_offset += transport_mtu; - if (qp->rx_offset + transport_mtu >= qp->rx_buff_end) + qp->rx_offset += qp->rx_max_frame; + if (qp->rx_offset + qp->rx_max_frame >= qp->rx_buff_end) qp->rx_offset = qp->rx_buff_begin; return 0; @@ -1000,11 +1010,11 @@ static void ntb_tx_copy_task(struct ntb_transport_qp *qp, struct ntb_payload_header *hdr; BUG_ON(offset < qp->tx_mw_begin || - offset + transport_mtu >= qp->tx_mw_end); + offset + qp->tx_max_frame >= qp->tx_mw_end); memcpy_toio(offset, entry->buf, entry->len); - hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + hdr = offset + qp->tx_max_frame - sizeof(struct ntb_payload_header); hdr->len = entry->len; hdr->ver = qp->tx_pkts; @@ -1036,7 +1046,7 @@ static int ntb_process_tx(struct ntb_transport_qp *qp, void *offset; offset = qp->tx_offset; - hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + hdr = offset + qp->tx_max_frame - sizeof(struct ntb_payload_header); dev_dbg(&ntb_query_pdev(qp->ndev)->dev, "%lld - offset %p, tx %p, entry len %d flags %x buff %p\n", qp->tx_pkts, offset, qp->tx_offset, entry->len, entry->flags, @@ -1046,7 +1056,7 @@ static int ntb_process_tx(struct ntb_transport_qp *qp, return -EAGAIN; } - if (entry->len > transport_mtu - sizeof(struct ntb_payload_header)) { + if (entry->len > qp->tx_max_frame - sizeof(struct ntb_payload_header)) { if (qp->tx_handler) qp->tx_handler(qp->cb_data, qp, NULL, -EIO); @@ -1057,8 +1067,8 @@ static int ntb_process_tx(struct ntb_transport_qp *qp, ntb_tx_copy_task(qp, entry, offset); - qp->tx_offset += transport_mtu; - if (qp->tx_offset + transport_mtu >= qp->tx_mw_end) + qp->tx_offset += qp->tx_max_frame; + if (qp->tx_offset + qp->tx_max_frame >= qp->tx_mw_end) qp->tx_offset = qp->tx_mw_begin; qp->tx_pkts++; @@ -1425,9 +1435,8 @@ EXPORT_SYMBOL_GPL(ntb_transport_qp_num); * * RETURNS: the max payload size of a qp */ -unsigned int -ntb_transport_max_size(__attribute__((unused)) struct ntb_transport_qp *qp) +unsigned int ntb_transport_max_size(struct ntb_transport_qp *qp) { - return transport_mtu - sizeof(struct ntb_payload_header); + return qp->tx_max_frame - sizeof(struct ntb_payload_header); } EXPORT_SYMBOL_GPL(ntb_transport_max_size); -- cgit v1.2.1 From 7b4f2d3c3b8285fe63cae6b92c7b7030d1d1aa7c Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:19 -0700 Subject: NTB: No sleeping in interrupt context Move all cancel_delayed_work_sync to a work thread to prevent sleeping in interrupt context (when the NTB link goes down). Caught via 'Sleep inside atomic section checking' Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_transport.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index 2823087a1338..bf7ade14c742 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -110,6 +110,7 @@ struct ntb_transport_qp { void (*event_handler) (void *data, int status); struct delayed_work link_work; + struct work_struct link_cleanup; struct dentry *debugfs_dir; struct dentry *debugfs_stats; @@ -148,6 +149,7 @@ struct ntb_transport { unsigned long qp_bitmap; bool transport_link; struct delayed_work link_work; + struct work_struct link_cleanup; struct dentry *debugfs_dir; }; @@ -510,8 +512,11 @@ static int ntb_set_mw(struct ntb_transport *nt, int num_mw, unsigned int size) return 0; } -static void ntb_qp_link_down(struct ntb_transport_qp *qp) +static void ntb_qp_link_cleanup(struct work_struct *work) { + struct ntb_transport_qp *qp = container_of(work, + struct ntb_transport_qp, + link_cleanup); struct ntb_transport *nt = qp->transport; struct pci_dev *pdev = ntb_query_pdev(nt->ndev); @@ -531,8 +536,15 @@ static void ntb_qp_link_down(struct ntb_transport_qp *qp) msecs_to_jiffies(NTB_LINK_DOWN_TIMEOUT)); } -static void ntb_transport_conn_down(struct ntb_transport *nt) +static void ntb_qp_link_down(struct ntb_transport_qp *qp) +{ + schedule_work(&qp->link_cleanup); +} + +static void ntb_transport_link_cleanup(struct work_struct *work) { + struct ntb_transport *nt = container_of(work, struct ntb_transport, + link_cleanup); int i; if (nt->transport_link == NTB_LINK_DOWN) @@ -562,7 +574,7 @@ static void ntb_transport_event_callback(void *data, enum ntb_hw_event event) schedule_delayed_work(&nt->link_work, 0); break; case NTB_EVENT_HW_LINK_DOWN: - ntb_transport_conn_down(nt); + schedule_work(&nt->link_cleanup); break; default: BUG(); @@ -769,6 +781,7 @@ static void ntb_transport_init_queue(struct ntb_transport *nt, } INIT_DELAYED_WORK(&qp->link_work, ntb_qp_link_work); + INIT_WORK(&qp->link_cleanup, ntb_qp_link_cleanup); spin_lock_init(&qp->ntb_rx_pend_q_lock); spin_lock_init(&qp->ntb_rx_free_q_lock); @@ -814,6 +827,7 @@ int ntb_transport_init(struct pci_dev *pdev) ntb_transport_init_queue(nt, i); INIT_DELAYED_WORK(&nt->link_work, ntb_transport_link_work); + INIT_WORK(&nt->link_cleanup, ntb_transport_link_cleanup); rc = ntb_register_event_callback(nt->ndev, ntb_transport_event_callback); -- cgit v1.2.1 From d66d7ac2e09f26dbcd3bfbd8bb05e658de89719a Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:20 -0700 Subject: NTB: use simple_open for debugfs Use simple_open for debugfs instead of recreating it in the NTB driver. Caught by coccicheck. Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_transport.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index bf7ade14c742..c0eca02eb6d0 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -364,12 +364,6 @@ void ntb_unregister_client(struct ntb_client *drv) } EXPORT_SYMBOL_GPL(ntb_unregister_client); -static int debugfs_open(struct inode *inode, struct file *filp) -{ - filp->private_data = inode->i_private; - return 0; -} - static ssize_t debugfs_read(struct file *filp, char __user *ubuf, size_t count, loff_t *offp) { @@ -425,7 +419,7 @@ static ssize_t debugfs_read(struct file *filp, char __user *ubuf, size_t count, static const struct file_operations ntb_qp_debugfs_stats = { .owner = THIS_MODULE, - .open = debugfs_open, + .open = simple_open, .read = debugfs_read, }; -- cgit v1.2.1 From 170d35a57cf2c87bbfdfe6ec0485f678bdb53737 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:23 -0700 Subject: NTB: namespacecheck cleanups Declare ntb_bus_type static to remove it from name space, and remove unused ntb_get_max_spads function. Found via `make namespacecheck`. Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_hw.c | 14 -------------- drivers/ntb/ntb_transport.c | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/drivers/ntb/ntb_hw.c b/drivers/ntb/ntb_hw.c index 4c71b17f0166..7d087bbab190 100644 --- a/drivers/ntb/ntb_hw.c +++ b/drivers/ntb/ntb_hw.c @@ -236,20 +236,6 @@ void ntb_unregister_transport(struct ntb_device *ndev) ndev->ntb_transport = NULL; } -/** - * ntb_get_max_spads() - get the total scratch regs usable - * @ndev: pointer to ntb_device instance - * - * This function returns the max 32bit scratchpad registers usable by the - * upper layer. - * - * RETURNS: total number of scratch pad registers available - */ -int ntb_get_max_spads(struct ntb_device *ndev) -{ - return ndev->limits.max_spads; -} - /** * ntb_write_local_spad() - write to the secondary scratchpad register * @ndev: pointer to ntb_device instance diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index c0eca02eb6d0..903a72e7d112 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -212,7 +212,7 @@ static int ntb_client_remove(struct device *dev) return 0; } -struct bus_type ntb_bus_type = { +static struct bus_type ntb_bus_type = { .name = "ntb_bus", .match = ntb_match_bus, .probe = ntb_client_probe, -- cgit v1.2.1 From f766755c3057c36dc0796d2b0c633611dde6eccf Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:24 -0700 Subject: NTB: whitespace cleanups Whitespace cleanups found via `indent` Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_transport.c | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index 903a72e7d112..e11b57e1939c 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -930,7 +930,7 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) if (!(hdr->flags & DESC_DONE_FLAG)) { ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, - &qp->rx_pend_q); + &qp->rx_pend_q); qp->rx_ring_empty++; return -EAGAIN; } @@ -940,7 +940,7 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) "qp %d: version mismatch, expected %llu - got %llu\n", qp->qp_num, qp->rx_pkts, hdr->ver); ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, - &qp->rx_pend_q); + &qp->rx_pend_q); qp->rx_err_ver++; return -EIO; } @@ -949,7 +949,7 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) ntb_qp_link_down(qp); ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, - &qp->rx_pend_q); + &qp->rx_pend_q); /* Ensure that the data is fully copied out before clearing the * done flag @@ -967,7 +967,7 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) ntb_rx_copy_task(qp, entry, offset); else { ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, - &qp->rx_pend_q); + &qp->rx_pend_q); /* Ensure that the data is fully copied out before clearing the * done flag @@ -1057,8 +1057,8 @@ static int ntb_process_tx(struct ntb_transport_qp *qp, hdr = offset + qp->tx_max_frame - sizeof(struct ntb_payload_header); dev_dbg(&ntb_query_pdev(qp->ndev)->dev, "%lld - offset %p, tx %p, entry len %d flags %x buff %p\n", - qp->tx_pkts, offset, qp->tx_offset, entry->len, entry->flags, - entry->buf); + qp->tx_pkts, offset, qp->tx_offset, entry->len, entry->flags, + entry->buf); if (hdr->flags) { qp->tx_ring_full++; return -EAGAIN; @@ -1097,8 +1097,7 @@ static void ntb_send_link_down(struct ntb_transport_qp *qp) dev_info(&pdev->dev, "qp %d: Link Down\n", qp->qp_num); for (i = 0; i < NTB_LINK_DOWN_TIMEOUT; i++) { - entry = ntb_list_rm(&qp->ntb_tx_free_q_lock, - &qp->tx_free_q); + entry = ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q); if (entry) break; msleep(100); @@ -1167,7 +1166,7 @@ ntb_transport_create_queue(void *data, struct pci_dev *pdev, goto err1; ntb_list_add(&qp->ntb_rx_free_q_lock, &entry->entry, - &qp->rx_free_q); + &qp->rx_free_q); } for (i = 0; i < NTB_QP_DEF_NUM_ENTRIES; i++) { @@ -1176,7 +1175,7 @@ ntb_transport_create_queue(void *data, struct pci_dev *pdev, goto err2; ntb_list_add(&qp->ntb_tx_free_q_lock, &entry->entry, - &qp->tx_free_q); + &qp->tx_free_q); } tasklet_init(&qp->rx_work, ntb_transport_rx, (unsigned long) qp); @@ -1193,12 +1192,10 @@ ntb_transport_create_queue(void *data, struct pci_dev *pdev, err3: tasklet_disable(&qp->rx_work); err2: - while ((entry = - ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q))) + while ((entry = ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q))) kfree(entry); err1: - while ((entry = - ntb_list_rm(&qp->ntb_rx_free_q_lock, &qp->rx_free_q))) + while ((entry = ntb_list_rm(&qp->ntb_rx_free_q_lock, &qp->rx_free_q))) kfree(entry); set_bit(free_queue, &nt->qp_bitmap); err: @@ -1225,18 +1222,15 @@ void ntb_transport_free_queue(struct ntb_transport_qp *qp) ntb_unregister_db_callback(qp->ndev, qp->qp_num); tasklet_disable(&qp->rx_work); - while ((entry = - ntb_list_rm(&qp->ntb_rx_free_q_lock, &qp->rx_free_q))) + while ((entry = ntb_list_rm(&qp->ntb_rx_free_q_lock, &qp->rx_free_q))) kfree(entry); - while ((entry = - ntb_list_rm(&qp->ntb_rx_pend_q_lock, &qp->rx_pend_q))) { + while ((entry = ntb_list_rm(&qp->ntb_rx_pend_q_lock, &qp->rx_pend_q))) { dev_warn(&pdev->dev, "Freeing item from a non-empty queue\n"); kfree(entry); } - while ((entry = - ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q))) + while ((entry = ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q))) kfree(entry); set_bit(qp->qp_num, &qp->transport->qp_bitmap); @@ -1270,8 +1264,7 @@ void *ntb_transport_rx_remove(struct ntb_transport_qp *qp, unsigned int *len) buf = entry->cb_data; *len = entry->len; - ntb_list_add(&qp->ntb_rx_free_q_lock, &entry->entry, - &qp->rx_free_q); + ntb_list_add(&qp->ntb_rx_free_q_lock, &entry->entry, &qp->rx_free_q); return buf; } @@ -1305,8 +1298,7 @@ int ntb_transport_rx_enqueue(struct ntb_transport_qp *qp, void *cb, void *data, entry->buf = data; entry->len = len; - ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, - &qp->rx_pend_q); + ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, &qp->rx_pend_q); return 0; } -- cgit v1.2.1 From d7237e22bbcffc3237a234fdf165fde4c2b0a22d Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:25 -0700 Subject: NTB: correct stack usage warning in debugfs_read Correct gcc warning of using too much stack debugfs_read. This is done by kmallocing the buffer instead of using the char array on stack. Also, shrinking the buffer to something closer to what is currently being used. Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_transport.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index e11b57e1939c..1bed1ba2fe5e 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -368,10 +368,14 @@ static ssize_t debugfs_read(struct file *filp, char __user *ubuf, size_t count, loff_t *offp) { struct ntb_transport_qp *qp; - char buf[1024]; + char *buf; ssize_t ret, out_offset, out_count; - out_count = 1024; + out_count = 600; + + buf = kmalloc(out_count, GFP_KERNEL); + if (!buf) + return -ENOMEM; qp = filp->private_data; out_offset = 0; @@ -410,10 +414,13 @@ static ssize_t debugfs_read(struct file *filp, char __user *ubuf, size_t count, "tx_mw_end - \t%p\n", qp->tx_mw_end); out_offset += snprintf(buf + out_offset, out_count - out_offset, - "QP Link %s\n", (qp->qp_link == NTB_LINK_UP) ? + "\nQP Link %s\n", (qp->qp_link == NTB_LINK_UP) ? "Up" : "Down"); + if (out_offset > out_count) + out_offset = out_count; ret = simple_read_from_buffer(ubuf, count, offp, buf, out_offset); + kfree(buf); return ret; } -- cgit v1.2.1 From 793c20e9c924e6bc91bc9b1c98e2f6b8e1bf2fae Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:26 -0700 Subject: NTB: Remove reads across NTB CPU reads across NTB are slow(er) and can hang the local system if an ECC error is encountered on the remote. To work around the need for a read, have the remote side write its current position in the rx buffer to the local system's buffer and use that to see if there is room when transmitting. Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_transport.c | 137 ++++++++++++++++++++------------------------ 1 file changed, 63 insertions(+), 74 deletions(-) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index 1bed1ba2fe5e..69c58da0fa34 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -78,6 +78,10 @@ struct ntb_queue_entry { unsigned int flags; }; +struct ntb_rx_info { + unsigned int entry; +}; + struct ntb_transport_qp { struct ntb_transport *transport; struct ntb_device *ndev; @@ -87,13 +91,16 @@ struct ntb_transport_qp { bool qp_link; u8 qp_num; /* Only 64 QP's are allowed. 0-63 */ + struct ntb_rx_info *rx_info; + struct ntb_rx_info *remote_rx_info; + void (*tx_handler) (struct ntb_transport_qp *qp, void *qp_data, void *data, int len); struct list_head tx_free_q; spinlock_t ntb_tx_free_q_lock; - void *tx_mw_begin; - void *tx_mw_end; - void *tx_offset; + void *tx_mw; + unsigned int tx_index; + unsigned int tx_max_entry; unsigned int tx_max_frame; void (*rx_handler) (struct ntb_transport_qp *qp, void *qp_data, @@ -103,9 +110,9 @@ struct ntb_transport_qp { struct list_head rx_free_q; spinlock_t ntb_rx_pend_q_lock; spinlock_t ntb_rx_free_q_lock; - void *rx_buff_begin; - void *rx_buff_end; - void *rx_offset; + void *rx_buff; + unsigned int rx_index; + unsigned int rx_max_entry; unsigned int rx_max_frame; void (*event_handler) (void *data, int status); @@ -394,11 +401,11 @@ static ssize_t debugfs_read(struct file *filp, char __user *ubuf, size_t count, out_offset += snprintf(buf + out_offset, out_count - out_offset, "rx_err_ver - \t%llu\n", qp->rx_err_ver); out_offset += snprintf(buf + out_offset, out_count - out_offset, - "rx_buff_begin - %p\n", qp->rx_buff_begin); + "rx_buff - \t%p\n", qp->rx_buff); out_offset += snprintf(buf + out_offset, out_count - out_offset, - "rx_offset - \t%p\n", qp->rx_offset); + "rx_index - \t%u\n", qp->rx_index); out_offset += snprintf(buf + out_offset, out_count - out_offset, - "rx_buff_end - \t%p\n", qp->rx_buff_end); + "rx_max_entry - \t%u\n", qp->rx_max_entry); out_offset += snprintf(buf + out_offset, out_count - out_offset, "tx_bytes - \t%llu\n", qp->tx_bytes); @@ -407,11 +414,11 @@ static ssize_t debugfs_read(struct file *filp, char __user *ubuf, size_t count, out_offset += snprintf(buf + out_offset, out_count - out_offset, "tx_ring_full - \t%llu\n", qp->tx_ring_full); out_offset += snprintf(buf + out_offset, out_count - out_offset, - "tx_mw_begin - \t%p\n", qp->tx_mw_begin); + "tx_mw - \t%p\n", qp->tx_mw); out_offset += snprintf(buf + out_offset, out_count - out_offset, - "tx_offset - \t%p\n", qp->tx_offset); + "tx_index - \t%u\n", qp->tx_index); out_offset += snprintf(buf + out_offset, out_count - out_offset, - "tx_mw_end - \t%p\n", qp->tx_mw_end); + "tx_max_entry - \t%u\n", qp->tx_max_entry); out_offset += snprintf(buf + out_offset, out_count - out_offset, "\nQP Link %s\n", (qp->qp_link == NTB_LINK_UP) ? @@ -465,7 +472,7 @@ static void ntb_transport_setup_qp_mw(struct ntb_transport *nt, struct ntb_transport_qp *qp = &nt->qps[qp_num]; unsigned int rx_size, num_qps_mw; u8 mw_num = QP_TO_MW(qp_num); - void *offset; + unsigned int i; WARN_ON(nt->mw[mw_num].virt_addr == 0); @@ -474,18 +481,24 @@ static void ntb_transport_setup_qp_mw(struct ntb_transport *nt, else num_qps_mw = nt->max_qps / NTB_NUM_MW; - rx_size = nt->mw[mw_num].size / num_qps_mw; - qp->rx_buff_begin = nt->mw[mw_num].virt_addr + - (qp_num / NTB_NUM_MW * rx_size); - qp->rx_buff_end = qp->rx_buff_begin + rx_size; - qp->rx_offset = qp->rx_buff_begin; + rx_size = (unsigned int) nt->mw[mw_num].size / num_qps_mw; + qp->remote_rx_info = nt->mw[mw_num].virt_addr + + (qp_num / NTB_NUM_MW * rx_size); + rx_size -= sizeof(struct ntb_rx_info); + + qp->rx_buff = qp->remote_rx_info + sizeof(struct ntb_rx_info); qp->rx_max_frame = min(transport_mtu, rx_size); + qp->rx_max_entry = rx_size / qp->rx_max_frame; + qp->rx_index = 0; + + qp->remote_rx_info->entry = qp->rx_max_entry; /* setup the hdr offsets with 0's */ - for (offset = qp->rx_buff_begin + qp->rx_max_frame - - sizeof(struct ntb_payload_header); - offset < qp->rx_buff_end; offset += qp->rx_max_frame) + for (i = 0; i < qp->rx_max_entry; i++) { + void *offset = qp->rx_buff + qp->rx_max_frame * (i + 1) - + sizeof(struct ntb_payload_header); memset(offset, 0, sizeof(struct ntb_payload_header)); + } qp->rx_pkts = 0; qp->tx_pkts = 0; @@ -762,12 +775,15 @@ static void ntb_transport_init_queue(struct ntb_transport *nt, else num_qps_mw = nt->max_qps / NTB_NUM_MW; - tx_size = ntb_get_mw_size(qp->ndev, mw_num) / num_qps_mw; - qp->tx_mw_begin = ntb_get_mw_vbase(nt->ndev, mw_num) + - (qp_num / NTB_NUM_MW * tx_size); - qp->tx_mw_end = qp->tx_mw_begin + tx_size; - qp->tx_offset = qp->tx_mw_begin; + tx_size = (unsigned int) ntb_get_mw_size(qp->ndev, mw_num) / num_qps_mw; + qp->rx_info = ntb_get_mw_vbase(nt->ndev, mw_num) + + (qp_num / NTB_NUM_MW * tx_size); + tx_size -= sizeof(struct ntb_rx_info); + + qp->tx_mw = qp->rx_info + sizeof(struct ntb_rx_info); qp->tx_max_frame = min(transport_mtu, tx_size); + qp->tx_max_entry = tx_size / qp->tx_max_frame; + qp->tx_index = 0; if (nt->debugfs_dir) { char debugfs_name[4]; @@ -894,21 +910,8 @@ void ntb_transport_free(void *transport) static void ntb_rx_copy_task(struct ntb_transport_qp *qp, struct ntb_queue_entry *entry, void *offset) { - - struct ntb_payload_header *hdr; - - BUG_ON(offset < qp->rx_buff_begin || - offset + qp->rx_max_frame >= qp->rx_buff_end); - - hdr = offset + qp->rx_max_frame - sizeof(struct ntb_payload_header); - entry->len = hdr->len; - memcpy(entry->buf, offset, entry->len); - /* Ensure that the data is fully copied out before clearing the flag */ - wmb(); - hdr->flags = 0; - if (qp->rx_handler && qp->client_ready == NTB_LINK_UP) qp->rx_handler(qp, qp->cb_data, entry->cb_data, entry->len); @@ -921,10 +924,11 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) struct ntb_queue_entry *entry; void *offset; + offset = qp->rx_buff + qp->rx_max_frame * qp->rx_index; + hdr = offset + qp->rx_max_frame - sizeof(struct ntb_payload_header); + entry = ntb_list_rm(&qp->ntb_rx_pend_q_lock, &qp->rx_pend_q); if (!entry) { - hdr = offset + qp->rx_max_frame - - sizeof(struct ntb_payload_header); dev_dbg(&ntb_query_pdev(qp->ndev)->dev, "no buffer - HDR ver %llu, len %d, flags %x\n", hdr->ver, hdr->len, hdr->flags); @@ -932,9 +936,6 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) return -ENOMEM; } - offset = qp->rx_offset; - hdr = offset + qp->rx_max_frame - sizeof(struct ntb_payload_header); - if (!(hdr->flags & DESC_DONE_FLAG)) { ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, &qp->rx_pend_q); @@ -957,30 +958,20 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, &qp->rx_pend_q); - - /* Ensure that the data is fully copied out before clearing the - * done flag - */ - wmb(); - hdr->flags = 0; goto out; } dev_dbg(&ntb_query_pdev(qp->ndev)->dev, - "rx offset %p, ver %llu - %d payload received, buf size %d\n", - qp->rx_offset, hdr->ver, hdr->len, entry->len); + "rx offset %u, ver %llu - %d payload received, buf size %d\n", + qp->rx_index, hdr->ver, hdr->len, entry->len); - if (hdr->len <= entry->len) + if (hdr->len <= entry->len) { + entry->len = hdr->len; ntb_rx_copy_task(qp, entry, offset); - else { + } else { ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, &qp->rx_pend_q); - /* Ensure that the data is fully copied out before clearing the - * done flag - */ - wmb(); - hdr->flags = 0; qp->rx_err_oflow++; dev_dbg(&ntb_query_pdev(qp->ndev)->dev, "RX overflow! Wanted %d got %d\n", @@ -991,9 +982,13 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) qp->rx_pkts++; out: - qp->rx_offset += qp->rx_max_frame; - if (qp->rx_offset + qp->rx_max_frame >= qp->rx_buff_end) - qp->rx_offset = qp->rx_buff_begin; + /* Ensure that the data is fully copied out before clearing the flag */ + wmb(); + hdr->flags = 0; + qp->rx_info->entry = qp->rx_index; + + qp->rx_index++; + qp->rx_index %= qp->rx_max_entry; return 0; } @@ -1024,9 +1019,6 @@ static void ntb_tx_copy_task(struct ntb_transport_qp *qp, { struct ntb_payload_header *hdr; - BUG_ON(offset < qp->tx_mw_begin || - offset + qp->tx_max_frame >= qp->tx_mw_end); - memcpy_toio(offset, entry->buf, entry->len); hdr = offset + qp->tx_max_frame - sizeof(struct ntb_payload_header); @@ -1057,16 +1049,14 @@ static void ntb_tx_copy_task(struct ntb_transport_qp *qp, static int ntb_process_tx(struct ntb_transport_qp *qp, struct ntb_queue_entry *entry) { - struct ntb_payload_header *hdr; void *offset; - offset = qp->tx_offset; - hdr = offset + qp->tx_max_frame - sizeof(struct ntb_payload_header); + offset = qp->tx_mw + qp->tx_max_frame * qp->tx_index; - dev_dbg(&ntb_query_pdev(qp->ndev)->dev, "%lld - offset %p, tx %p, entry len %d flags %x buff %p\n", - qp->tx_pkts, offset, qp->tx_offset, entry->len, entry->flags, + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, "%lld - offset %p, tx %u, entry len %d flags %x buff %p\n", + qp->tx_pkts, offset, qp->tx_index, entry->len, entry->flags, entry->buf); - if (hdr->flags) { + if (qp->tx_index == qp->remote_rx_info->entry) { qp->tx_ring_full++; return -EAGAIN; } @@ -1082,9 +1072,8 @@ static int ntb_process_tx(struct ntb_transport_qp *qp, ntb_tx_copy_task(qp, entry, offset); - qp->tx_offset += qp->tx_max_frame; - if (qp->tx_offset + qp->tx_max_frame >= qp->tx_mw_end) - qp->tx_offset = qp->tx_mw_begin; + qp->tx_index++; + qp->tx_index %= qp->tx_max_entry; qp->tx_pkts++; -- cgit v1.2.1 From 448c6fb3a39bf4d0b644f5b942b7aa9473b0f597 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:27 -0700 Subject: NTB: Out of free receive entries issue If the NTB client driver enqueues the maximum number of rx buffers, it will not be able to re-enqueue another in its callback handler due to a lack of free entries. This can be avoided by adding the current entry to the free queue prior to calling the client callback handler. With this change, ntb_netdev will no longer encounter a rx error on its first packet. Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_transport.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index 69c58da0fa34..b3afb2442dc0 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -910,12 +910,15 @@ void ntb_transport_free(void *transport) static void ntb_rx_copy_task(struct ntb_transport_qp *qp, struct ntb_queue_entry *entry, void *offset) { - memcpy(entry->buf, offset, entry->len); + void *cb_data = entry->cb_data; + unsigned int len = entry->len; - if (qp->rx_handler && qp->client_ready == NTB_LINK_UP) - qp->rx_handler(qp, qp->cb_data, entry->cb_data, entry->len); + memcpy(entry->buf, offset, entry->len); ntb_list_add(&qp->ntb_rx_free_q_lock, &entry->entry, &qp->rx_free_q); + + if (qp->rx_handler && qp->client_ready == NTB_LINK_UP) + qp->rx_handler(qp, qp->cb_data, cb_data, len); } static int ntb_process_rxc(struct ntb_transport_qp *qp) -- cgit v1.2.1 From 50228c5505fee2c836e1f3895d82f9cf5f89174f Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:29 -0700 Subject: NTB: Update Version Update NTB version to 0.25 Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_hw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ntb/ntb_hw.c b/drivers/ntb/ntb_hw.c index 7d087bbab190..6d8e937670c4 100644 --- a/drivers/ntb/ntb_hw.c +++ b/drivers/ntb/ntb_hw.c @@ -55,7 +55,7 @@ #include "ntb_regs.h" #define NTB_NAME "Intel(R) PCI-E Non-Transparent Bridge Driver" -#define NTB_VER "0.24" +#define NTB_VER "0.25" MODULE_DESCRIPTION(NTB_NAME); MODULE_VERSION(NTB_VER); -- cgit v1.2.1 From 19e17f7248e8ef72a8b625db7ddd99c0ad3da27c Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:30 -0700 Subject: ntb_netdev: remove init/exit from probe/remove Remove init/exit from probe/remove routines to correct warnings of "Section mismatch". Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/net/ntb_netdev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index 4e52fd2b8226..0d4a6ee5a7f0 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -373,7 +373,7 @@ err: return rc; } -static void __exit ntb_netdev_remove(struct pci_dev *pdev) +static void ntb_netdev_remove(struct pci_dev *pdev) { struct net_device *ndev; struct ntb_netdev *dev; -- cgit v1.2.1 From 765ccc7bc3d913e18b887a86de1e86db701a2d78 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:31 -0700 Subject: ntb_netdev: correct skb leak If ntb_netdev is unable to pass a new skb to the ntb transport for future rx packets, it should free the newly alloc'ed skb in the error case. Found by Kernel memory leak detector. Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/net/ntb_netdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index 0d4a6ee5a7f0..28d6fea54384 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -119,6 +119,7 @@ static void ntb_netdev_rx_handler(struct ntb_transport_qp *qp, void *qp_data, rc = ntb_transport_rx_enqueue(qp, skb, skb->data, ndev->mtu + ETH_HLEN); if (rc) { + dev_kfree_skb(skb); ndev->stats.rx_errors++; ndev->stats.rx_fifo_errors++; } -- cgit v1.2.1 From d723485cb4ca9eefaf43fd9f165554094b97f2c2 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:32 -0700 Subject: ntb_netdev: remove tx timeout There is a race between disabling and enabling the tx queue, resulting in tx timeouts. Since all the tx timeout does is re-enable the tx queue, simple remove the start/stop of the queue and the tx timeout routine. Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/net/ntb_netdev.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index 28d6fea54384..07c06cdb97b4 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -144,9 +144,6 @@ static void ntb_netdev_tx_handler(struct ntb_transport_qp *qp, void *qp_data, } dev_kfree_skb(skb); - - if (netif_queue_stopped(ndev)) - netif_wake_queue(ndev); } static netdev_tx_t ntb_netdev_start_xmit(struct sk_buff *skb, @@ -166,7 +163,6 @@ static netdev_tx_t ntb_netdev_start_xmit(struct sk_buff *skb, err: ndev->stats.tx_dropped++; ndev->stats.tx_errors++; - netif_stop_queue(ndev); return NETDEV_TX_BUSY; } @@ -270,18 +266,11 @@ err: return rc; } -static void ntb_netdev_tx_timeout(struct net_device *ndev) -{ - if (netif_running(ndev)) - netif_wake_queue(ndev); -} - static const struct net_device_ops ntb_netdev_ops = { .ndo_open = ntb_netdev_open, .ndo_stop = ntb_netdev_close, .ndo_start_xmit = ntb_netdev_start_xmit, .ndo_change_mtu = ntb_netdev_change_mtu, - .ndo_tx_timeout = ntb_netdev_tx_timeout, .ndo_set_mac_address = eth_mac_addr, }; -- cgit v1.2.1 From 7bcd2b111f3ac77370ec7efe93e16e19878827c1 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:34 -0700 Subject: ntb_netdev: improve logging Improve driver logging to be more helpful Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/net/ntb_netdev.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index 07c06cdb97b4..1626dc198f52 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -152,7 +152,7 @@ static netdev_tx_t ntb_netdev_start_xmit(struct sk_buff *skb, struct ntb_netdev *dev = netdev_priv(ndev); int rc; - netdev_dbg(ndev, "ntb_transport_tx_enqueue\n"); + netdev_dbg(ndev, "%s: skb len %d\n", __func__, skb->len); rc = ntb_transport_tx_enqueue(dev->qp, skb, skb->data, skb->len); if (rc) @@ -353,7 +353,7 @@ static int ntb_netdev_probe(struct pci_dev *pdev) goto err1; list_add(&dev->list, &dev_list); - pr_info("%s: %s created\n", KBUILD_MODNAME, ndev->name); + dev_info(&pdev->dev, "%s created\n", ndev->name); return 0; err1: @@ -404,6 +404,5 @@ static void __exit ntb_netdev_exit_module(void) { ntb_unregister_client(&ntb_netdev_client); ntb_unregister_client_dev(KBUILD_MODNAME); - pr_info("%s: Driver removed\n", KBUILD_MODNAME); } module_exit(ntb_netdev_exit_module); -- cgit v1.2.1 From 24208bbed9e1116455be2e4b82c9b559f2462a59 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Sat, 19 Jan 2013 02:02:35 -0700 Subject: ntb_netdev: Update Version Update NTB netdev version to 0.7 Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/net/ntb_netdev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ntb_netdev.c b/drivers/net/ntb_netdev.c index 1626dc198f52..ed947dd76fbd 100644 --- a/drivers/net/ntb_netdev.c +++ b/drivers/net/ntb_netdev.c @@ -51,7 +51,7 @@ #include #include -#define NTB_NETDEV_VER "0.6" +#define NTB_NETDEV_VER "0.7" MODULE_DESCRIPTION(KBUILD_MODNAME); MODULE_VERSION(NTB_NETDEV_VER); -- cgit v1.2.1 From ef4458a16561ea901ac410cb38407d0928a54d1d Mon Sep 17 00:00:00 2001 From: Fengguang Wu Date: Sat, 19 Jan 2013 23:37:19 +0800 Subject: Drivers: char: exynos_rng_pm_ops can be static Signed-off-by: Fengguang Wu Signed-off-by: Greg Kroah-Hartman --- drivers/char/hw_random/exynos-rng.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/hw_random/exynos-rng.c b/drivers/char/hw_random/exynos-rng.c index 48bbfeca4b5d..2a4a86d97333 100644 --- a/drivers/char/hw_random/exynos-rng.c +++ b/drivers/char/hw_random/exynos-rng.c @@ -162,7 +162,7 @@ static int exynos_rng_runtime_resume(struct device *dev) } -UNIVERSAL_DEV_PM_OPS(exynos_rng_pm_ops, exynos_rng_runtime_suspend, +static UNIVERSAL_DEV_PM_OPS(exynos_rng_pm_ops, exynos_rng_runtime_suspend, exynos_rng_runtime_resume, NULL); static struct platform_driver exynos_rng_driver = { -- cgit v1.2.1 From 55cd0e36f6e4796f39f0dada112436bdcc9d8a3b Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Mon, 21 Jan 2013 13:12:42 +0200 Subject: Revert "drivers/misc/ti-st: remove gpio handling" This reverts commit eccf2979b2c034b516e01b8a104c3739f7ef07d1. The reason is that it broke TI WiLink shared transport on Panda. Also, callback functions should not be added to board files anymore, so revert to implementing the power functions in the driver itself. Additionally, changed a variable name ('status' to 'err') so that this revert compiles properly. Cc: stable [3.7] Signed-off-by: Luciano Coelho Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ti-st/st_kim.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/misc/ti-st/st_kim.c b/drivers/misc/ti-st/st_kim.c index 9ff942a346ed..83269f1d16e3 100644 --- a/drivers/misc/ti-st/st_kim.c +++ b/drivers/misc/ti-st/st_kim.c @@ -468,6 +468,11 @@ long st_kim_start(void *kim_data) if (pdata->chip_enable) pdata->chip_enable(kim_gdata); + /* Configure BT nShutdown to HIGH state */ + gpio_set_value(kim_gdata->nshutdown, GPIO_LOW); + mdelay(5); /* FIXME: a proper toggle */ + gpio_set_value(kim_gdata->nshutdown, GPIO_HIGH); + mdelay(100); /* re-initialize the completion */ INIT_COMPLETION(kim_gdata->ldisc_installed); /* send notification to UIM */ @@ -509,7 +514,8 @@ long st_kim_start(void *kim_data) * (b) upon failure to either install ldisc or download firmware. * The function is responsible to (a) notify UIM about un-installation, * (b) flush UART if the ldisc was installed. - * (c) invoke platform's chip disabling routine. + * (c) reset BT_EN - pull down nshutdown at the end. + * (d) invoke platform's chip disabling routine. */ long st_kim_stop(void *kim_data) { @@ -541,6 +547,13 @@ long st_kim_stop(void *kim_data) err = -ETIMEDOUT; } + /* By default configure BT nShutdown to LOW state */ + gpio_set_value(kim_gdata->nshutdown, GPIO_LOW); + mdelay(1); + gpio_set_value(kim_gdata->nshutdown, GPIO_HIGH); + mdelay(1); + gpio_set_value(kim_gdata->nshutdown, GPIO_LOW); + /* platform specific disable */ if (pdata->chip_disable) pdata->chip_disable(kim_gdata); @@ -733,6 +746,20 @@ static int kim_probe(struct platform_device *pdev) /* refer to itself */ kim_gdata->core_data->kim_data = kim_gdata; + /* Claim the chip enable nShutdown gpio from the system */ + kim_gdata->nshutdown = pdata->nshutdown_gpio; + err = gpio_request(kim_gdata->nshutdown, "kim"); + if (unlikely(err)) { + pr_err(" gpio %ld request failed ", kim_gdata->nshutdown); + return err; + } + + /* Configure nShutdown GPIO as output=0 */ + err = gpio_direction_output(kim_gdata->nshutdown, 0); + if (unlikely(err)) { + pr_err(" unable to configure gpio %ld", kim_gdata->nshutdown); + return err; + } /* get reference of pdev for request_firmware */ kim_gdata->kim_pdev = pdev; @@ -779,10 +806,18 @@ err_core_init: static int kim_remove(struct platform_device *pdev) { + /* free the GPIOs requested */ + struct ti_st_plat_data *pdata = pdev->dev.platform_data; struct kim_data_s *kim_gdata; kim_gdata = dev_get_drvdata(&pdev->dev); + /* Free the Bluetooth/FM/GPIO + * nShutdown gpio from the system + */ + gpio_free(pdata->nshutdown_gpio); + pr_info("nshutdown GPIO Freed"); + debugfs_remove_recursive(kim_debugfs_dir); sysfs_remove_group(&pdev->dev.kobj, &uim_attr_grp); pr_info("sysfs entries removed"); -- cgit v1.2.1 From fc8d713ecd01acb312afed8c560130ea7284c6db Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Mon, 21 Jan 2013 14:02:53 +0100 Subject: drivers/ipack/devices/ipoctal.c: adjust duplicate test Delete successive tests to the same location. The code tested the result of a previous allocation, that itself was already tested. It is changed to test the result of the most recent allocation. A simplified version of the semantic match that finds this problem is as follows: (http://coccinelle.lip6.fr/) // @s exists@ local idexpression y; expression x,e; @@ *if ( \(x == NULL\|IS_ERR(x)\|y != 0\) ) { ... when forall return ...; } ... when != \(y = e\|y += e\|y -= e\|y |= e\|y &= e\|y++\|y--\|&y\) when != \(XT_GETPAGE(...,y)\|WMI_CMD_BUF(...)\) *if ( \(x == NULL\|IS_ERR(x)\|y != 0\) ) { ... when forall return ...; } // Signed-off-by: Julia Lawall Cc: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 93cbf9dda1fe..a2cf0f240929 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -289,7 +289,7 @@ static int ipoctal_inst_slot(struct ipoctal *ipoctal, unsigned int bus_nr, ipoctal->mem8_space = devm_ioremap_nocache(&ipoctal->dev->dev, region->start, 0x8000); - if (!addr) { + if (!ipoctal->mem8_space) { dev_err(&ipoctal->dev->dev, "Unable to map slot [%d:%d] MEM8 space!\n", bus_nr, slot); -- cgit v1.2.1 From 719234f9444552a8b3ac30a7c373db62e13c14f4 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Mon, 21 Jan 2013 15:28:51 -0700 Subject: NTB: disable x86_32 support Atomic readq and writeq do not exist by default on some 32bit architectures, thus causing compile errors due to non-existent symbols. Since NTB has not been tested 32bit, disable x86_32 support until such time as this and any other issues can be properly discovered. Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ntb/Kconfig b/drivers/ntb/Kconfig index f69df793dbe2..37ee6495acc1 100644 --- a/drivers/ntb/Kconfig +++ b/drivers/ntb/Kconfig @@ -1,7 +1,7 @@ config NTB tristate "Intel Non-Transparent Bridge support" depends on PCI - depends on X86 + depends on X86_64 help The PCI-E Non-transparent bridge hardware is a point-to-point PCI-E bus connecting 2 systems. When configured, writes to the device's PCI -- cgit v1.2.1 From 74465645cdb4391b9fc95d12fd750a88012ad479 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Mon, 21 Jan 2013 15:28:52 -0700 Subject: NTB: Fix Sparse Warnings Address the sparse warnings and resulting fallout Signed-off-by: Jon Mason Signed-off-by: Greg Kroah-Hartman --- drivers/ntb/ntb_hw.c | 4 ++-- drivers/ntb/ntb_hw.h | 4 ++-- drivers/ntb/ntb_transport.c | 32 ++++++++++++++++---------------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/drivers/ntb/ntb_hw.c b/drivers/ntb/ntb_hw.c index 6d8e937670c4..f802e7c92356 100644 --- a/drivers/ntb/ntb_hw.c +++ b/drivers/ntb/ntb_hw.c @@ -104,7 +104,7 @@ MODULE_DEVICE_TABLE(pci, ntb_pci_tbl); * RETURNS: An appropriate -ERRNO error value on error, or zero for success. */ int ntb_register_event_callback(struct ntb_device *ndev, - void (*func)(void *handle, unsigned int event)) + void (*func)(void *handle, enum ntb_hw_event event)) { if (ndev->event_cb) return -EINVAL; @@ -343,7 +343,7 @@ int ntb_read_remote_spad(struct ntb_device *ndev, unsigned int idx, u32 *val) * * RETURNS: pointer to virtual address, or NULL on error. */ -void *ntb_get_mw_vbase(struct ntb_device *ndev, unsigned int mw) +void __iomem *ntb_get_mw_vbase(struct ntb_device *ndev, unsigned int mw) { if (mw > NTB_NUM_MW) return NULL; diff --git a/drivers/ntb/ntb_hw.h b/drivers/ntb/ntb_hw.h index 5e009519c64f..3a3038ca83e6 100644 --- a/drivers/ntb/ntb_hw.h +++ b/drivers/ntb/ntb_hw.h @@ -165,14 +165,14 @@ int ntb_register_db_callback(struct ntb_device *ndev, unsigned int idx, void ntb_unregister_db_callback(struct ntb_device *ndev, unsigned int idx); int ntb_register_event_callback(struct ntb_device *ndev, void (*event_cb_func) (void *handle, - unsigned int event)); + enum ntb_hw_event event)); void ntb_unregister_event_callback(struct ntb_device *ndev); int ntb_get_max_spads(struct ntb_device *ndev); int ntb_write_local_spad(struct ntb_device *ndev, unsigned int idx, u32 val); int ntb_read_local_spad(struct ntb_device *ndev, unsigned int idx, u32 *val); int ntb_write_remote_spad(struct ntb_device *ndev, unsigned int idx, u32 val); int ntb_read_remote_spad(struct ntb_device *ndev, unsigned int idx, u32 *val); -void *ntb_get_mw_vbase(struct ntb_device *ndev, unsigned int mw); +void __iomem *ntb_get_mw_vbase(struct ntb_device *ndev, unsigned int mw); resource_size_t ntb_get_mw_size(struct ntb_device *ndev, unsigned int mw); void ntb_ring_sdb(struct ntb_device *ndev, unsigned int idx); void *ntb_find_transport(struct pci_dev *pdev); diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c index b3afb2442dc0..e0bdfd7f9930 100644 --- a/drivers/ntb/ntb_transport.c +++ b/drivers/ntb/ntb_transport.c @@ -58,7 +58,7 @@ #include #include "ntb_hw.h" -#define NTB_TRANSPORT_VERSION 1 +#define NTB_TRANSPORT_VERSION 2 static unsigned int transport_mtu = 0x401E; module_param(transport_mtu, uint, 0644); @@ -91,14 +91,14 @@ struct ntb_transport_qp { bool qp_link; u8 qp_num; /* Only 64 QP's are allowed. 0-63 */ - struct ntb_rx_info *rx_info; + struct ntb_rx_info __iomem *rx_info; struct ntb_rx_info *remote_rx_info; void (*tx_handler) (struct ntb_transport_qp *qp, void *qp_data, void *data, int len); struct list_head tx_free_q; spinlock_t ntb_tx_free_q_lock; - void *tx_mw; + void __iomem *tx_mw; unsigned int tx_index; unsigned int tx_max_entry; unsigned int tx_max_frame; @@ -166,7 +166,7 @@ enum { }; struct ntb_payload_header { - u64 ver; + unsigned int ver; unsigned int len; unsigned int flags; }; @@ -474,7 +474,7 @@ static void ntb_transport_setup_qp_mw(struct ntb_transport *nt, u8 mw_num = QP_TO_MW(qp_num); unsigned int i; - WARN_ON(nt->mw[mw_num].virt_addr == 0); + WARN_ON(nt->mw[mw_num].virt_addr == NULL); if (nt->max_qps % NTB_NUM_MW && mw_num < nt->max_qps % NTB_NUM_MW) num_qps_mw = nt->max_qps / NTB_NUM_MW + 1; @@ -933,7 +933,7 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) entry = ntb_list_rm(&qp->ntb_rx_pend_q_lock, &qp->rx_pend_q); if (!entry) { dev_dbg(&ntb_query_pdev(qp->ndev)->dev, - "no buffer - HDR ver %llu, len %d, flags %x\n", + "no buffer - HDR ver %u, len %d, flags %x\n", hdr->ver, hdr->len, hdr->flags); qp->rx_err_no_buf++; return -ENOMEM; @@ -946,9 +946,9 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) return -EAGAIN; } - if (hdr->ver != qp->rx_pkts) { + if (hdr->ver != (u32) qp->rx_pkts) { dev_dbg(&ntb_query_pdev(qp->ndev)->dev, - "qp %d: version mismatch, expected %llu - got %llu\n", + "qp %d: version mismatch, expected %llu - got %u\n", qp->qp_num, qp->rx_pkts, hdr->ver); ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, &qp->rx_pend_q); @@ -965,7 +965,7 @@ static int ntb_process_rxc(struct ntb_transport_qp *qp) } dev_dbg(&ntb_query_pdev(qp->ndev)->dev, - "rx offset %u, ver %llu - %d payload received, buf size %d\n", + "rx offset %u, ver %u - %d payload received, buf size %d\n", qp->rx_index, hdr->ver, hdr->len, entry->len); if (hdr->len <= entry->len) { @@ -988,7 +988,7 @@ out: /* Ensure that the data is fully copied out before clearing the flag */ wmb(); hdr->flags = 0; - qp->rx_info->entry = qp->rx_index; + iowrite32(qp->rx_index, &qp->rx_info->entry); qp->rx_index++; qp->rx_index %= qp->rx_max_entry; @@ -1018,19 +1018,19 @@ static void ntb_transport_rxc_db(void *data, int db_num) static void ntb_tx_copy_task(struct ntb_transport_qp *qp, struct ntb_queue_entry *entry, - void *offset) + void __iomem *offset) { - struct ntb_payload_header *hdr; + struct ntb_payload_header __iomem *hdr; memcpy_toio(offset, entry->buf, entry->len); hdr = offset + qp->tx_max_frame - sizeof(struct ntb_payload_header); - hdr->len = entry->len; - hdr->ver = qp->tx_pkts; + iowrite32(entry->len, &hdr->len); + iowrite32((u32) qp->tx_pkts, &hdr->ver); /* Ensure that the data is fully copied out before setting the flag */ wmb(); - hdr->flags = entry->flags | DESC_DONE_FLAG; + iowrite32(entry->flags | DESC_DONE_FLAG, &hdr->flags); ntb_ring_sdb(qp->ndev, qp->qp_num); @@ -1052,7 +1052,7 @@ static void ntb_tx_copy_task(struct ntb_transport_qp *qp, static int ntb_process_tx(struct ntb_transport_qp *qp, struct ntb_queue_entry *entry) { - void *offset; + void __iomem *offset; offset = qp->tx_mw + qp->tx_max_frame * qp->tx_index; -- cgit v1.2.1 From 5a19b78972c39be0c5378c14c23e6c998683e80f Mon Sep 17 00:00:00 2001 From: Andy King Date: Tue, 22 Jan 2013 09:15:04 -0800 Subject: VMCI: Fix broken context ID retrieval I'm an idiot. The context ID can be a really large unsigned number, which means it'll appear negative as an int. So actually the right fix here is just to set it regardless of the returned value (but only for this particular hypercall; normally we would check it). Acked-by: Dmitry Torokhov Signed-off-by: Andy King Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_guest.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/misc/vmw_vmci/vmci_guest.c b/drivers/misc/vmw_vmci/vmci_guest.c index de1a90be160a..60c01999f489 100644 --- a/drivers/misc/vmw_vmci/vmci_guest.c +++ b/drivers/misc/vmw_vmci/vmci_guest.c @@ -81,16 +81,13 @@ bool vmci_guest_code_active(void) u32 vmci_get_vm_context_id(void) { if (vm_context_id == VMCI_INVALID_ID) { - int result; struct vmci_datagram get_cid_msg; get_cid_msg.dst = vmci_make_handle(VMCI_HYPERVISOR_CONTEXT_ID, VMCI_GET_CONTEXT_ID); get_cid_msg.src = VMCI_ANON_SRC_HANDLE; get_cid_msg.payload_size = 0; - result = vmci_send_datagram(&get_cid_msg); - if (result >= 0) - vm_context_id = result; + vm_context_id = vmci_send_datagram(&get_cid_msg); } return vm_context_id; } -- cgit v1.2.1 From 4e6168779508f62c27e43a4f7ded786bfdb0a394 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 15 Jan 2013 22:09:20 +0900 Subject: extcon: arizona: Disable debouce for accessory removal detection Ensure we clamp as quickly as possible after removal by disabling the debounce while there is an accessory present. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index de141f72c8e5..ce95f8625e8d 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -773,6 +773,10 @@ static irqreturn_t arizona_jackdet(int irq, void *data) } else { arizona_start_hpdet_acc_id(info); } + + regmap_update_bits(arizona->regmap, + ARIZONA_JACK_DETECT_DEBOUNCE, + ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, 0); } else { dev_dbg(arizona->dev, "Detected jack removal\n"); @@ -792,6 +796,11 @@ static irqreturn_t arizona_jackdet(int irq, void *data) if (ret != 0) dev_err(arizona->dev, "Removal report failed: %d\n", ret); + + regmap_update_bits(arizona->regmap, + ARIZONA_JACK_DETECT_DEBOUNCE, + ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB, + ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); } mutex_unlock(&info->lock); -- cgit v1.2.1 From e6dd8cf223d1a41b3c3168e97e2c33df0ef05e9d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 21 Jan 2013 17:30:02 +0900 Subject: extcon: arizona: Retry failed HP measurements We now have mechanisms in place to allow retries so let's use them rather than guessing. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index ce95f8625e8d..528303440c2e 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -31,8 +31,6 @@ #include #include -#define ARIZONA_DEFAULT_HP 32 - #define ARIZONA_NUM_BUTTONS 6 #define ARIZONA_ACCDET_MODE_MIC 0 @@ -208,7 +206,7 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) if (!(val & ARIZONA_HP_DONE)) { dev_err(arizona->dev, "HPDET did not complete: %x\n", val); - val = ARIZONA_DEFAULT_HP; + return -EAGAIN; } val &= ARIZONA_HP_LVL_MASK; @@ -218,14 +216,14 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) if (!(val & ARIZONA_HP_DONE_B)) { dev_err(arizona->dev, "HPDET did not complete: %x\n", val); - return ARIZONA_DEFAULT_HP; + return -EAGAIN; } ret = regmap_read(arizona->regmap, ARIZONA_HP_DACVAL, &val); if (ret != 0) { dev_err(arizona->dev, "Failed to read HP value: %d\n", ret); - return ARIZONA_DEFAULT_HP; + return -EAGAIN; } regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, @@ -267,7 +265,7 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) if (!(val & ARIZONA_HP_DONE_B)) { dev_err(arizona->dev, "HPDET did not complete: %x\n", val); - return ARIZONA_DEFAULT_HP; + return -EAGAIN; } val &= ARIZONA_HP_LVL_B_MASK; -- cgit v1.2.1 From f9365d07dd5cb3c76b454b4c7827b2f6339cace2 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 21 Jan 2013 17:35:44 +0900 Subject: extcon: arizona: Remove duplicate rate configuration Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 528303440c2e..ab8b9c7359fb 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -1003,10 +1003,6 @@ static int arizona_extcon_probe(struct platform_device *pdev) goto err_micdet; } - regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, - ARIZONA_MICD_RATE_MASK, - 8 << ARIZONA_MICD_RATE_SHIFT); - arizona_clk32k_enable(arizona); regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE, ARIZONA_JD1_DB, ARIZONA_JD1_DB); -- cgit v1.2.1 From 7fb96565e3e18ad41857ca6ffdaa9a26ae92df5a Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 23 Jan 2013 17:42:40 -0800 Subject: Drivers: hv: vmbus: Consolidate all offer GUID definitions in hyperv.h Consolidate all GUID definitions in hyperv.h and use these definitions in implementing channel bindings (as far as interrupt delivery goes). Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 31 +++------------- include/linux/hyperv.h | 94 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 7478ef914b9a..53a8600162a5 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -265,36 +265,17 @@ enum { }; /* - * This is an array of channels (devices) that are performance critical. + * This is an array of device_ids (device types) that are performance critical. * We attempt to distribute the interrupt load for these devices across * all available CPUs. */ -static const uuid_le hp_devs[] = { - /* {32412632-86cb-44a2-9b5c-50d1417354f5} */ +static const struct hv_vmbus_device_id hp_devs[] = { /* IDE */ - { - .b = { - 0x32, 0x26, 0x41, 0x32, 0xcb, 0x86, 0xa2, 0x44, - 0x9b, 0x5c, 0x50, 0xd1, 0x41, 0x73, 0x54, 0xf5 - } - }, - /* {ba6163d9-04a1-4d29-b605-72e2ffb1dc7f} */ + { HV_IDE_GUID, }, /* Storage - SCSI */ - { - .b = { - 0xd9, 0x63, 0x61, 0xba, 0xa1, 0x04, 0x29, 0x4d, - 0xb6, 0x05, 0x72, 0xe2, 0xff, 0xb1, 0xdc, 0x7f - } - }, - /* {F8615163-DF3E-46c5-913F-F2D2F965ED0E} */ + { HV_SCSI_GUID, }, /* Network */ - { - .b = { - 0x63, 0x51, 0x61, 0xF8, 0x3E, 0xDF, 0xc5, 0x46, - 0x91, 0x3F, 0xF2, 0xD2, 0xF9, 0x65, 0xED, 0x0E - } - }, - + { HV_NIC_GUID, }, }; @@ -320,7 +301,7 @@ static u32 get_vp_index(uuid_le *type_guid) u32 max_cpus = num_online_cpus(); for (i = IDE; i < MAX_PERF_CHN; i++) { - if (!memcmp(type_guid->b, hp_devs[i].b, + if (!memcmp(type_guid->b, hp_devs[i].guid, sizeof(uuid_le))) { perf_chn = true; break; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 5095b066df94..df77ba9a8166 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1158,6 +1158,100 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver); .guid = { g0, g1, g2, g3, g4, g5, g6, g7, \ g8, g9, ga, gb, gc, gd, ge, gf }, +/* + * GUID definitions of various offer types - services offered to the guest. + */ + +/* + * Network GUID + * {f8615163-df3e-46c5-913f-f2d2f965ed0e} + */ +#define HV_NIC_GUID \ + .guid = { \ + 0x63, 0x51, 0x61, 0xf8, 0x3e, 0xdf, 0xc5, 0x46, \ + 0x91, 0x3f, 0xf2, 0xd2, 0xf9, 0x65, 0xed, 0x0e \ + } + +/* + * IDE GUID + * {32412632-86cb-44a2-9b5c-50d1417354f5} + */ +#define HV_IDE_GUID \ + .guid = { \ + 0x32, 0x26, 0x41, 0x32, 0xcb, 0x86, 0xa2, 0x44, \ + 0x9b, 0x5c, 0x50, 0xd1, 0x41, 0x73, 0x54, 0xf5 \ + } + +/* + * SCSI GUID + * {ba6163d9-04a1-4d29-b605-72e2ffb1dc7f} + */ +#define HV_SCSI_GUID \ + .guid = { \ + 0xd9, 0x63, 0x61, 0xba, 0xa1, 0x04, 0x29, 0x4d, \ + 0xb6, 0x05, 0x72, 0xe2, 0xff, 0xb1, 0xdc, 0x7f \ + } + +/* + * Shutdown GUID + * {0e0b6031-5213-4934-818b-38d90ced39db} + */ +#define HV_SHUTDOWN_GUID \ + .guid = { \ + 0x31, 0x60, 0x0b, 0x0e, 0x13, 0x52, 0x34, 0x49, \ + 0x81, 0x8b, 0x38, 0xd9, 0x0c, 0xed, 0x39, 0xdb \ + } + +/* + * Time Synch GUID + * {9527E630-D0AE-497b-ADCE-E80AB0175CAF} + */ +#define HV_TS_GUID \ + .guid = { \ + 0x30, 0xe6, 0x27, 0x95, 0xae, 0xd0, 0x7b, 0x49, \ + 0xad, 0xce, 0xe8, 0x0a, 0xb0, 0x17, 0x5c, 0xaf \ + } + +/* + * Heartbeat GUID + * {57164f39-9115-4e78-ab55-382f3bd5422d} + */ +#define HV_HEART_BEAT_GUID \ + .guid = { \ + 0x39, 0x4f, 0x16, 0x57, 0x15, 0x91, 0x78, 0x4e, \ + 0xab, 0x55, 0x38, 0x2f, 0x3b, 0xd5, 0x42, 0x2d \ + } + +/* + * KVP GUID + * {a9a0f4e7-5a45-4d96-b827-8a841e8c03e6} + */ +#define HV_KVP_GUID \ + .guid = { \ + 0xe7, 0xf4, 0xa0, 0xa9, 0x45, 0x5a, 0x96, 0x4d, \ + 0xb8, 0x27, 0x8a, 0x84, 0x1e, 0x8c, 0x3, 0xe6 \ + } + +/* + * Dynamic memory GUID + * {525074dc-8985-46e2-8057-a307dc18a502} + */ +#define HV_DM_GUID \ + .guid = { \ + 0xdc, 0x74, 0x50, 0X52, 0x85, 0x89, 0xe2, 0x46, \ + 0x80, 0x57, 0xa3, 0x07, 0xdc, 0x18, 0xa5, 0x02 \ + } + +/* + * Mouse GUID + * {cfa8b69e-5b4a-4cc0-b98b-8ba1a1f3f95a} + */ +#define HV_MOUSE_GUID \ + .guid = { \ + 0x9e, 0xb6, 0xa8, 0xcf, 0x4a, 0x5b, 0xc0, 0x4c, \ + 0xb9, 0x8b, 0x8b, 0xa1, 0xa1, 0xf3, 0xf9, 0x5a \ + } + /* * Common header for Hyper-V ICs */ -- cgit v1.2.1 From d13984e5c75d1d1db0fb60b468a0e504504c1b4f Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 23 Jan 2013 17:42:41 -0800 Subject: Drivers: hv: Use consolidated GUID definitions Use the consolidated GUID definitions in the util and balloon drivers. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv_balloon.c | 4 +--- drivers/hv/hv_util.c | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 0ce08c401dc4..c1ad16f763ce 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -1005,9 +1005,7 @@ static int balloon_remove(struct hv_device *dev) static const struct hv_vmbus_device_id id_table[] = { /* Dynamic Memory Class ID */ /* 525074DC-8985-46e2-8057-A307DC18A502 */ - { VMBUS_DEVICE(0xdc, 0x74, 0x50, 0X52, 0x85, 0x89, 0xe2, 0x46, - 0x80, 0x57, 0xa3, 0x07, 0xdc, 0x18, 0xa5, 0x02) - }, + { HV_DM_GUID, }, { }, }; diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index a62a76062508..8b7868a5bad0 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -314,21 +314,21 @@ static int util_remove(struct hv_device *dev) static const struct hv_vmbus_device_id id_table[] = { /* Shutdown guid */ - { VMBUS_DEVICE(0x31, 0x60, 0x0B, 0X0E, 0x13, 0x52, 0x34, 0x49, - 0x81, 0x8B, 0x38, 0XD9, 0x0C, 0xED, 0x39, 0xDB) - .driver_data = (unsigned long)&util_shutdown }, + { HV_SHUTDOWN_GUID, + .driver_data = (unsigned long)&util_shutdown + }, /* Time synch guid */ - { VMBUS_DEVICE(0x30, 0xe6, 0x27, 0x95, 0xae, 0xd0, 0x7b, 0x49, - 0xad, 0xce, 0xe8, 0x0a, 0xb0, 0x17, 0x5c, 0xaf) - .driver_data = (unsigned long)&util_timesynch }, + { HV_TS_GUID, + .driver_data = (unsigned long)&util_timesynch + }, /* Heartbeat guid */ - { VMBUS_DEVICE(0x39, 0x4f, 0x16, 0x57, 0x15, 0x91, 0x78, 0x4e, - 0xab, 0x55, 0x38, 0x2f, 0x3b, 0xd5, 0x42, 0x2d) - .driver_data = (unsigned long)&util_heartbeat }, + { HV_HEART_BEAT_GUID, + .driver_data = (unsigned long)&util_heartbeat + }, /* KVP guid */ - { VMBUS_DEVICE(0xe7, 0xf4, 0xa0, 0xa9, 0x45, 0x5a, 0x96, 0x4d, - 0xb8, 0x27, 0x8a, 0x84, 0x1e, 0x8c, 0x3, 0xe6) - .driver_data = (unsigned long)&util_kvp }, + { HV_KVP_GUID, + .driver_data = (unsigned long)&util_kvp + }, { }, }; -- cgit v1.2.1 From 8f5059446bbb47fb14fdd743c9e1548713c4b9e8 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 23 Jan 2013 17:42:42 -0800 Subject: Drivers: net: hyperv: Use the consolidated GUID definition Use the consolidated GUID definitions in the Hyper-V network driver. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/net/hyperv/netvsc_drv.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c index f825a629a699..5ac9cafc4543 100644 --- a/drivers/net/hyperv/netvsc_drv.c +++ b/drivers/net/hyperv/netvsc_drv.c @@ -498,8 +498,7 @@ static int netvsc_remove(struct hv_device *dev) static const struct hv_vmbus_device_id id_table[] = { /* Network guid */ - { VMBUS_DEVICE(0x63, 0x51, 0x61, 0xF8, 0x3E, 0xDF, 0xc5, 0x46, - 0x91, 0x3F, 0xF2, 0xD2, 0xF9, 0x65, 0xED, 0x0E) }, + { HV_NIC_GUID, }, { }, }; -- cgit v1.2.1 From 35c3bc2040673ae8b80a60aefe753b4724eb5414 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 23 Jan 2013 17:42:43 -0800 Subject: Drivers: scsi: storvsc: Use the consolidated GUID definition Use the consolidated GUID definitions in the Hyper-V storage driver. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/scsi/storvsc_drv.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/scsi/storvsc_drv.c b/drivers/scsi/storvsc_drv.c index 01440782feb2..270b3cf6f372 100644 --- a/drivers/scsi/storvsc_drv.c +++ b/drivers/scsi/storvsc_drv.c @@ -1410,13 +1410,13 @@ enum { static const struct hv_vmbus_device_id id_table[] = { /* SCSI guid */ - { VMBUS_DEVICE(0xd9, 0x63, 0x61, 0xba, 0xa1, 0x04, 0x29, 0x4d, - 0xb6, 0x05, 0x72, 0xe2, 0xff, 0xb1, 0xdc, 0x7f) - .driver_data = SCSI_GUID }, + { HV_SCSI_GUID, + .driver_data = SCSI_GUID + }, /* IDE guid */ - { VMBUS_DEVICE(0x32, 0x26, 0x41, 0x32, 0xcb, 0x86, 0xa2, 0x44, - 0x9b, 0x5c, 0x50, 0xd1, 0x41, 0x73, 0x54, 0xf5) - .driver_data = IDE_GUID }, + { HV_IDE_GUID, + .driver_data = IDE_GUID + }, { }, }; -- cgit v1.2.1 From 048c5add0865eca299189263b0bf1c7a606de24a Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 23 Jan 2013 17:42:44 -0800 Subject: Drivers: hid: hid-hyperv: Use consolidated GUID definitions Use the consolidated GUID definitions in the Hyper-V mouse driver. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Acked-by: Jiri Kosina Signed-off-by: Greg Kroah-Hartman --- drivers/hid/hid-hyperv.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c index 3d62781b8993..aa3fec0d9dc6 100644 --- a/drivers/hid/hid-hyperv.c +++ b/drivers/hid/hid-hyperv.c @@ -568,8 +568,7 @@ static int mousevsc_remove(struct hv_device *dev) static const struct hv_vmbus_device_id id_table[] = { /* Mouse guid */ - { VMBUS_DEVICE(0x9E, 0xB6, 0xA8, 0xCF, 0x4A, 0x5B, 0xc0, 0x4c, - 0xB9, 0x8B, 0x8B, 0xA1, 0xA1, 0xF3, 0xF9, 0x5A) }, + { HV_MOUSE_GUID, }, { }, }; -- cgit v1.2.1 From 3dd6cb497198a0533a2530b6a345c60c9a29b9bc Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 23 Jan 2013 17:42:45 -0800 Subject: Drivers: hv: Execute shutdown in a thread context Execute the shutdown code in a thread context. With recent changes made to the shutdown code, shutdown code cannot be invoked from an interrupt context. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv_util.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c index 8b7868a5bad0..1d4cbd8e8261 100644 --- a/drivers/hv/hv_util.c +++ b/drivers/hv/hv_util.c @@ -49,6 +49,16 @@ static struct hv_util_service util_kvp = { .util_deinit = hv_kvp_deinit, }; +static void perform_shutdown(struct work_struct *dummy) +{ + orderly_poweroff(true); +} + +/* + * Perform the shutdown operation in a thread context. + */ +static DECLARE_WORK(shutdown_work, perform_shutdown); + static void shutdown_onchannelcallback(void *context) { struct vmbus_channel *channel = context; @@ -106,7 +116,7 @@ static void shutdown_onchannelcallback(void *context) } if (execute_shutdown == true) - orderly_poweroff(true); + schedule_work(&shutdown_work); } /* -- cgit v1.2.1 From 1aa0590525c673316fc63b4ab956691f249a8ad3 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Fri, 25 Jan 2013 10:56:10 +0900 Subject: extcon: max77693: Fix bug of build error related to INPUT subsystem This patch fix build error of following log: drivers/built-in.o: In function `max77693_muic_remove': extcon-max77693.c:(.text+0x664853): undefined reference to `input_unregister_device' drivers/built-in.o: In function `max77693_muic_probe': extcon-max77693.c:(.text+0x664971): undefined reference to `input_allocate_device' extcon-max77693.c:(.text+0x6649c1): undefined reference to `input_set_capability' extcon-max77693.c:(.text+0x6649d6): undefined reference to `input_set_capability' extcon-max77693.c:(.text+0x6649eb): undefined reference to `input_set_capability' extcon-max77693.c:(.text+0x664a00): undefined reference to `input_set_capability' extcon-max77693.c:(.text+0x664a15): undefined reference to `input_set_capability' extcon-max77693.c:(.text+0x664a20): undefined reference to `input_register_device' drivers/built-in.o: In function `max77693_muic_adc_handler': extcon-max77693.c:(.text+0x665318): undefined reference to `input_event' extcon-max77693.c:(.text+0x66532a): undefined reference to `input_event' make[1]: *** [vmlinux] Error 1 Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham Reported-by: Randy Dunlap Acked-by: Randy Dunlap Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 07122a9ef36e..10df3fa3db67 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -29,7 +29,7 @@ config EXTCON_ADC_JACK config EXTCON_MAX77693 tristate "MAX77693 EXTCON Support" - depends on MFD_MAX77693 + depends on MFD_MAX77693 && INPUT select IRQ_DOMAIN select REGMAP_I2C help -- cgit v1.2.1 From f6dcf8e747a0723ace5275334bacfcd88ab39333 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Thu, 24 Jan 2013 14:49:31 -0800 Subject: drivers, vmci: Fix build error We can't rely on vmalloc.h being included by other included files because under some configs it is possible for the build to fail: drivers/misc/vmw_vmci/vmci_queue_pair.c: In function 'qp_free_queue': drivers/misc/vmw_vmci/vmci_queue_pair.c:270: error: implicit declaration of function 'vunmap' drivers/misc/vmw_vmci/vmci_queue_pair.c:277: error: implicit declaration of function 'vfree' drivers/misc/vmw_vmci/vmci_queue_pair.c: In function 'qp_alloc_queue': drivers/misc/vmw_vmci/vmci_queue_pair.c:302: error: implicit declaration of function 'vmalloc' drivers/misc/vmw_vmci/vmci_queue_pair.c:302: warning: assignment makes pointer from integer without a cast drivers/misc/vmw_vmci/vmci_queue_pair.c:324: error: implicit declaration of function 'vmap' drivers/misc/vmw_vmci/vmci_queue_pair.c:324: error: 'VM_MAP' undeclared (first use in this function) drivers/misc/vmw_vmci/vmci_queue_pair.c:324: error: (Each undeclared identifier is reported only once drivers/misc/vmw_vmci/vmci_queue_pair.c:324: error: for each function it appears in.) drivers/misc/vmw_vmci/vmci_queue_pair.c: In function 'qp_host_map_queues': drivers/misc/vmw_vmci/vmci_queue_pair.c:843: error: 'VM_MAP' undeclared (first use in this function) Fix the build by directly including vmalloc.h. Signed-off-by: David Rientjes Cc: George Zhang Cc: Andy King Cc: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vmw_vmci/vmci_queue_pair.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/misc/vmw_vmci/vmci_queue_pair.c b/drivers/misc/vmw_vmci/vmci_queue_pair.c index ef81fec0fcb4..d94245dbd765 100644 --- a/drivers/misc/vmw_vmci/vmci_queue_pair.c +++ b/drivers/misc/vmw_vmci/vmci_queue_pair.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "vmci_handle_array.h" #include "vmci_queue_pair.h" -- cgit v1.2.1 From 06a8f1feb9e82e5b66f781ba3e39055e3f89a641 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 27 Jan 2013 21:07:57 +0100 Subject: w1-gpio: fix section mismatch This fixes the following section mismatch: WARNING: drivers/w1/masters/w1-gpio.o(.data+0x188): Section mismatch in reference from the variable w1_gpio_driver to the function .init.text:w1_gpio_probe() The variable w1_gpio_driver references the function __init w1_gpio_probe() If the reference is valid then annotate the variable with __init* or __refdata (see linux/init.h) or name the variable: *_template, *_timer, *_sht, *_ops, *_probe, *_probe_one, *_console Signed-off-by: Hauke Mehrtens Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/w1-gpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c index 85b363a5bd0f..d39dfa4cc235 100644 --- a/drivers/w1/masters/w1-gpio.c +++ b/drivers/w1/masters/w1-gpio.c @@ -72,7 +72,7 @@ static int w1_gpio_probe_dt(struct platform_device *pdev) return 0; } -static int __init w1_gpio_probe(struct platform_device *pdev) +static int w1_gpio_probe(struct platform_device *pdev) { struct w1_bus_master *master; struct w1_gpio_platform_data *pdata; -- cgit v1.2.1 From 0731572b6c529f8e8a320dc4df6d67d9a595ecf3 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Fri, 25 Jan 2013 16:18:47 -0800 Subject: Drivers: hv: balloon: Make adjustments to the pressure report The host expects that the pressure report includes the pressure due to the pages that have been ballooned. Make necessary adjustments to reflect that. Also, include the free memory information in the pressure report. Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv_balloon.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index ce6f984535ec..32a96f164dc8 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -529,15 +529,21 @@ static void process_info(struct hv_dynmem_device *dm, struct dm_info_msg *msg) static void post_status(struct hv_dynmem_device *dm) { struct dm_status status; + struct sysinfo val; - + si_meminfo(&val); memset(&status, 0, sizeof(struct dm_status)); status.hdr.type = DM_STATUS_REPORT; status.hdr.size = sizeof(struct dm_status); status.hdr.trans_id = atomic_inc_return(&trans_id); - - status.num_committed = vm_memory_committed(); + /* + * The host expects the guest to report free memory. + * Further, the host expects the pressure information to + * include the ballooned out pages. + */ + status.num_avail = val.freeram; + status.num_committed = vm_memory_committed() + dm->num_pages_ballooned; vmbus_sendpacket(dm->dev->channel, &status, sizeof(struct dm_status), -- cgit v1.2.1 From 890537b3ac953ad2cc4f5ecb83744e967ae2aa31 Mon Sep 17 00:00:00 2001 From: Hans Grob Date: Wed, 6 Feb 2013 11:37:20 +0100 Subject: drivers/char/mem.c: fix small coding style issues This patch fixes four foo * bar errors, and one trailing whitespace complaint from checkpatch.pl Signed-off-by: Hans Grob Signed-off-by: Greg Kroah-Hartman --- drivers/char/mem.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/char/mem.c b/drivers/char/mem.c index c6fa3bc2baa8..6f6e92a3102d 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -399,7 +399,7 @@ static ssize_t read_kmem(struct file *file, char __user *buf, { unsigned long p = *ppos; ssize_t low_count, read, sz; - char * kbuf; /* k-addr because vread() takes vmlist_lock rwlock */ + char *kbuf; /* k-addr because vread() takes vmlist_lock rwlock */ int err = 0; read = 0; @@ -527,7 +527,7 @@ static ssize_t write_kmem(struct file *file, const char __user *buf, unsigned long p = *ppos; ssize_t wrote = 0; ssize_t virtr = 0; - char * kbuf; /* k-addr because vwrite() takes vmlist_lock rwlock */ + char *kbuf; /* k-addr because vwrite() takes vmlist_lock rwlock */ int err = 0; if (p < (unsigned long) high_memory) { @@ -595,7 +595,7 @@ static ssize_t write_port(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { unsigned long i = *ppos; - const char __user * tmp = buf; + const char __user *tmp = buf; if (!access_ok(VERIFY_READ, buf, count)) return -EFAULT; @@ -729,7 +729,7 @@ static loff_t memory_lseek(struct file *file, loff_t offset, int orig) return ret; } -static int open_port(struct inode * inode, struct file * filp) +static int open_port(struct inode *inode, struct file *filp) { return capable(CAP_SYS_RAWIO) ? 0 : -EPERM; } @@ -898,7 +898,7 @@ static int __init chr_dev_init(void) continue; /* - * Create /dev/port? + * Create /dev/port? */ if ((minor == DEVPORT_MINOR) && !arch_has_dev_port()) continue; -- cgit v1.2.1 From 2703d4b2e673cc240ad06d79d131fd1d0f77d65d Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 6 Feb 2013 14:06:39 +0200 Subject: mei: sperate interface and pci code into two files leave misc file operations in the main and move PCI related code into pci-me Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/Makefile | 1 + drivers/misc/mei/main.c | 354 ++-------------------------------------- drivers/misc/mei/mei_dev.h | 3 + drivers/misc/mei/pci-me.c | 393 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 409 insertions(+), 342 deletions(-) create mode 100644 drivers/misc/mei/pci-me.c diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 9f719339e594..068f55354811 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -11,3 +11,4 @@ mei-objs += main.o mei-objs += amthif.o mei-objs += wd.o mei-objs += client.o +mei-objs += pci-me.o diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 123c663509ef..018623c9a8e1 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -43,54 +43,6 @@ #include "hw-me.h" #include "client.h" -/* AMT device is a singleton on the platform */ -static struct pci_dev *mei_pdev; - -/* mei_pci_tbl - PCI Device ID Table */ -static DEFINE_PCI_DEVICE_TABLE(mei_pci_tbl) = { - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82946GZ)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G35)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82Q965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GM965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GME965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q35)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82G33)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q33)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82X38)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_3200)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_6)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_7)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_8)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_9)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_10)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_4)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_4)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_CPT_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PBG_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_LP)}, - - /* required last entry */ - {0, } -}; - -MODULE_DEVICE_TABLE(pci, mei_pci_tbl); - -static DEFINE_MUTEX(mei_mutex); - - /** * mei_open - the open function * @@ -101,15 +53,20 @@ static DEFINE_MUTEX(mei_mutex); */ static int mei_open(struct inode *inode, struct file *file) { + struct miscdevice *misc = file->private_data; + struct pci_dev *pdev; struct mei_cl *cl; struct mei_device *dev; + int err; err = -ENODEV; - if (!mei_pdev) + if (!misc->parent) goto out; - dev = pci_get_drvdata(mei_pdev); + pdev = container_of(misc->parent, struct pci_dev, dev); + + dev = pci_get_drvdata(pdev); if (!dev) goto out; @@ -787,7 +744,6 @@ static const struct file_operations mei_fops = { .llseek = no_llseek }; - /* * Misc Device Struct */ @@ -797,302 +753,16 @@ static struct miscdevice mei_misc_device = { .minor = MISC_DYNAMIC_MINOR, }; -/** - * mei_quirk_probe - probe for devices that doesn't valid ME interface - * @pdev: PCI device structure - * @ent: entry into pci_device_table - * - * returns true if ME Interface is valid, false otherwise - */ -static bool mei_quirk_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) +int mei_register(struct device *dev) { - u32 reg; - if (ent->device == MEI_DEV_ID_PBG_1) { - pci_read_config_dword(pdev, 0x48, ®); - /* make sure that bit 9 is up and bit 10 is down */ - if ((reg & 0x600) == 0x200) { - dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n"); - return false; - } - } - return true; -} -/** - * mei_probe - Device Initialization Routine - * - * @pdev: PCI device structure - * @ent: entry in kcs_pci_tbl - * - * returns 0 on success, <0 on failure. - */ -static int mei_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) -{ - struct mei_device *dev; - int err; - - mutex_lock(&mei_mutex); - - if (!mei_quirk_probe(pdev, ent)) { - err = -ENODEV; - goto end; - } - - if (mei_pdev) { - err = -EEXIST; - goto end; - } - /* enable pci dev */ - err = pci_enable_device(pdev); - if (err) { - dev_err(&pdev->dev, "failed to enable pci device.\n"); - goto end; - } - /* set PCI host mastering */ - pci_set_master(pdev); - /* pci request regions for mei driver */ - err = pci_request_regions(pdev, KBUILD_MODNAME); - if (err) { - dev_err(&pdev->dev, "failed to get pci regions.\n"); - goto disable_device; - } - /* allocates and initializes the mei dev structure */ - dev = mei_device_init(pdev); - if (!dev) { - err = -ENOMEM; - goto release_regions; - } - /* mapping IO device memory */ - dev->mem_addr = pci_iomap(pdev, 0, 0); - if (!dev->mem_addr) { - dev_err(&pdev->dev, "mapping I/O device memory failure.\n"); - err = -ENOMEM; - goto free_device; - } - pci_enable_msi(pdev); - - /* request and enable interrupt */ - if (pci_dev_msi_enabled(pdev)) - err = request_threaded_irq(pdev->irq, - NULL, - mei_interrupt_thread_handler, - IRQF_ONESHOT, KBUILD_MODNAME, dev); - else - err = request_threaded_irq(pdev->irq, - mei_interrupt_quick_handler, - mei_interrupt_thread_handler, - IRQF_SHARED, KBUILD_MODNAME, dev); - - if (err) { - dev_err(&pdev->dev, "request_threaded_irq failure. irq = %d\n", - pdev->irq); - goto disable_msi; - } - - if (mei_hw_init(dev)) { - dev_err(&pdev->dev, "init hw failure.\n"); - err = -ENODEV; - goto release_irq; - } - - err = misc_register(&mei_misc_device); - if (err) - goto release_irq; - - mei_pdev = pdev; - pci_set_drvdata(pdev, dev); - - - schedule_delayed_work(&dev->timer_work, HZ); - - mutex_unlock(&mei_mutex); - - pr_debug("initialization successful.\n"); - - return 0; - -release_irq: - mei_disable_interrupts(dev); - flush_scheduled_work(); - free_irq(pdev->irq, dev); -disable_msi: - pci_disable_msi(pdev); - pci_iounmap(pdev, dev->mem_addr); -free_device: - kfree(dev); -release_regions: - pci_release_regions(pdev); -disable_device: - pci_disable_device(pdev); -end: - mutex_unlock(&mei_mutex); - dev_err(&pdev->dev, "initialization failed.\n"); - return err; + mei_misc_device.parent = dev; + return misc_register(&mei_misc_device); } -/** - * mei_remove - Device Removal Routine - * - * @pdev: PCI device structure - * - * mei_remove is called by the PCI subsystem to alert the driver - * that it should release a PCI device. - */ -static void mei_remove(struct pci_dev *pdev) +void mei_deregister(void) { - struct mei_device *dev; - - if (mei_pdev != pdev) - return; - - dev = pci_get_drvdata(pdev); - if (!dev) - return; - - mutex_lock(&dev->device_lock); - - cancel_delayed_work(&dev->timer_work); - - mei_wd_stop(dev); - - mei_pdev = NULL; - - if (dev->iamthif_cl.state == MEI_FILE_CONNECTED) { - dev->iamthif_cl.state = MEI_FILE_DISCONNECTING; - mei_cl_disconnect(&dev->iamthif_cl); - } - if (dev->wd_cl.state == MEI_FILE_CONNECTED) { - dev->wd_cl.state = MEI_FILE_DISCONNECTING; - mei_cl_disconnect(&dev->wd_cl); - } - - /* Unregistering watchdog device */ - mei_watchdog_unregister(dev); - - /* remove entry if already in list */ - dev_dbg(&pdev->dev, "list del iamthif and wd file list.\n"); - - if (dev->open_handle_count > 0) - dev->open_handle_count--; - mei_cl_unlink(&dev->wd_cl); - - if (dev->open_handle_count > 0) - dev->open_handle_count--; - mei_cl_unlink(&dev->iamthif_cl); - - dev->iamthif_current_cb = NULL; - dev->me_clients_num = 0; - - mutex_unlock(&dev->device_lock); - - flush_scheduled_work(); - - /* disable interrupts */ - mei_disable_interrupts(dev); - - free_irq(pdev->irq, dev); - pci_disable_msi(pdev); - pci_set_drvdata(pdev, NULL); - - if (dev->mem_addr) - pci_iounmap(pdev, dev->mem_addr); - - kfree(dev); - - pci_release_regions(pdev); - pci_disable_device(pdev); - misc_deregister(&mei_misc_device); + mei_misc_device.parent = NULL; } -#ifdef CONFIG_PM -static int mei_pci_suspend(struct device *device) -{ - struct pci_dev *pdev = to_pci_dev(device); - struct mei_device *dev = pci_get_drvdata(pdev); - int err; - - if (!dev) - return -ENODEV; - mutex_lock(&dev->device_lock); - - cancel_delayed_work(&dev->timer_work); - - /* Stop watchdog if exists */ - err = mei_wd_stop(dev); - /* Set new mei state */ - if (dev->dev_state == MEI_DEV_ENABLED || - dev->dev_state == MEI_DEV_RECOVERING_FROM_RESET) { - dev->dev_state = MEI_DEV_POWER_DOWN; - mei_reset(dev, 0); - } - mutex_unlock(&dev->device_lock); - - free_irq(pdev->irq, dev); - pci_disable_msi(pdev); - - return err; -} - -static int mei_pci_resume(struct device *device) -{ - struct pci_dev *pdev = to_pci_dev(device); - struct mei_device *dev; - int err; - - dev = pci_get_drvdata(pdev); - if (!dev) - return -ENODEV; - - pci_enable_msi(pdev); - - /* request and enable interrupt */ - if (pci_dev_msi_enabled(pdev)) - err = request_threaded_irq(pdev->irq, - NULL, - mei_interrupt_thread_handler, - IRQF_ONESHOT, KBUILD_MODNAME, dev); - else - err = request_threaded_irq(pdev->irq, - mei_interrupt_quick_handler, - mei_interrupt_thread_handler, - IRQF_SHARED, KBUILD_MODNAME, dev); - - if (err) { - dev_err(&pdev->dev, "request_threaded_irq failed: irq = %d.\n", - pdev->irq); - return err; - } - - mutex_lock(&dev->device_lock); - dev->dev_state = MEI_DEV_POWER_UP; - mei_reset(dev, 1); - mutex_unlock(&dev->device_lock); - - /* Start timer if stopped in suspend */ - schedule_delayed_work(&dev->timer_work, HZ); - - return err; -} -static SIMPLE_DEV_PM_OPS(mei_pm_ops, mei_pci_suspend, mei_pci_resume); -#define MEI_PM_OPS (&mei_pm_ops) -#else -#define MEI_PM_OPS NULL -#endif /* CONFIG_PM */ -/* - * PCI driver structure - */ -static struct pci_driver mei_driver = { - .name = KBUILD_MODNAME, - .id_table = mei_pci_tbl, - .probe = mei_probe, - .remove = mei_remove, - .shutdown = mei_remove, - .driver.pm = MEI_PM_OPS, -}; -module_pci_driver(mei_driver); -MODULE_AUTHOR("Intel Corporation"); -MODULE_DESCRIPTION("Intel(R) Management Engine Interface"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index d6589d0d305a..3b2bca386e5a 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -401,6 +401,9 @@ bool mei_me_is_ready(struct mei_device *dev); +int mei_register(struct device *dev); +void mei_deregister(void); + #define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d comp=%1d" #define MEI_HDR_PRM(hdr) \ (hdr)->host_addr, (hdr)->me_addr, \ diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c new file mode 100644 index 000000000000..eaed398bed6b --- /dev/null +++ b/drivers/misc/mei/pci-me.c @@ -0,0 +1,393 @@ +/* + * + * 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. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "mei_dev.h" +#include "hw-me.h" +#include "client.h" + +/* AMT device is a singleton on the platform */ +static struct pci_dev *mei_pdev; + +/* mei_pci_tbl - PCI Device ID Table */ +static DEFINE_PCI_DEVICE_TABLE(mei_pci_tbl) = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82946GZ)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G35)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82Q965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GM965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GME965)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q35)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82G33)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q33)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82X38)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_3200)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_6)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_7)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_8)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_9)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_10)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_3)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_3)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_4)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_CPT_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PBG_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_1)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_2)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_3)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_LP)}, + + /* required last entry */ + {0, } +}; + +MODULE_DEVICE_TABLE(pci, mei_pci_tbl); + +static DEFINE_MUTEX(mei_mutex); + + +/** + * mei_quirk_probe - probe for devices that doesn't valid ME interface + * @pdev: PCI device structure + * @ent: entry into pci_device_table + * + * returns true if ME Interface is valid, false otherwise + */ +static bool mei_quirk_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + u32 reg; + if (ent->device == MEI_DEV_ID_PBG_1) { + pci_read_config_dword(pdev, 0x48, ®); + /* make sure that bit 9 is up and bit 10 is down */ + if ((reg & 0x600) == 0x200) { + dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n"); + return false; + } + } + return true; +} +/** + * mei_probe - Device Initialization Routine + * + * @pdev: PCI device structure + * @ent: entry in kcs_pci_tbl + * + * returns 0 on success, <0 on failure. + */ +static int mei_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct mei_device *dev; + int err; + + mutex_lock(&mei_mutex); + + if (!mei_quirk_probe(pdev, ent)) { + err = -ENODEV; + goto end; + } + + if (mei_pdev) { + err = -EEXIST; + goto end; + } + /* enable pci dev */ + err = pci_enable_device(pdev); + if (err) { + dev_err(&pdev->dev, "failed to enable pci device.\n"); + goto end; + } + /* set PCI host mastering */ + pci_set_master(pdev); + /* pci request regions for mei driver */ + err = pci_request_regions(pdev, KBUILD_MODNAME); + if (err) { + dev_err(&pdev->dev, "failed to get pci regions.\n"); + goto disable_device; + } + /* allocates and initializes the mei dev structure */ + dev = mei_device_init(pdev); + if (!dev) { + err = -ENOMEM; + goto release_regions; + } + /* mapping IO device memory */ + dev->mem_addr = pci_iomap(pdev, 0, 0); + if (!dev->mem_addr) { + dev_err(&pdev->dev, "mapping I/O device memory failure.\n"); + err = -ENOMEM; + goto free_device; + } + pci_enable_msi(pdev); + + /* request and enable interrupt */ + if (pci_dev_msi_enabled(pdev)) + err = request_threaded_irq(pdev->irq, + NULL, + mei_interrupt_thread_handler, + IRQF_ONESHOT, KBUILD_MODNAME, dev); + else + err = request_threaded_irq(pdev->irq, + mei_interrupt_quick_handler, + mei_interrupt_thread_handler, + IRQF_SHARED, KBUILD_MODNAME, dev); + + if (err) { + dev_err(&pdev->dev, "request_threaded_irq failure. irq = %d\n", + pdev->irq); + goto disable_msi; + } + + if (mei_hw_init(dev)) { + dev_err(&pdev->dev, "init hw failure.\n"); + err = -ENODEV; + goto release_irq; + } + + err = mei_register(&pdev->dev); + if (err) + goto release_irq; + + mei_pdev = pdev; + pci_set_drvdata(pdev, dev); + + + schedule_delayed_work(&dev->timer_work, HZ); + + mutex_unlock(&mei_mutex); + + pr_debug("initialization successful.\n"); + + return 0; + +release_irq: + mei_disable_interrupts(dev); + flush_scheduled_work(); + free_irq(pdev->irq, dev); +disable_msi: + pci_disable_msi(pdev); + pci_iounmap(pdev, dev->mem_addr); +free_device: + kfree(dev); +release_regions: + pci_release_regions(pdev); +disable_device: + pci_disable_device(pdev); +end: + mutex_unlock(&mei_mutex); + dev_err(&pdev->dev, "initialization failed.\n"); + return err; +} + +/** + * mei_remove - Device Removal Routine + * + * @pdev: PCI device structure + * + * mei_remove is called by the PCI subsystem to alert the driver + * that it should release a PCI device. + */ +static void mei_remove(struct pci_dev *pdev) +{ + struct mei_device *dev; + + if (mei_pdev != pdev) + return; + + dev = pci_get_drvdata(pdev); + if (!dev) + return; + + mutex_lock(&dev->device_lock); + + cancel_delayed_work(&dev->timer_work); + + mei_wd_stop(dev); + + mei_pdev = NULL; + + if (dev->iamthif_cl.state == MEI_FILE_CONNECTED) { + dev->iamthif_cl.state = MEI_FILE_DISCONNECTING; + mei_cl_disconnect(&dev->iamthif_cl); + } + if (dev->wd_cl.state == MEI_FILE_CONNECTED) { + dev->wd_cl.state = MEI_FILE_DISCONNECTING; + mei_cl_disconnect(&dev->wd_cl); + } + + /* Unregistering watchdog device */ + mei_watchdog_unregister(dev); + + /* remove entry if already in list */ + dev_dbg(&pdev->dev, "list del iamthif and wd file list.\n"); + + if (dev->open_handle_count > 0) + dev->open_handle_count--; + mei_cl_unlink(&dev->wd_cl); + + if (dev->open_handle_count > 0) + dev->open_handle_count--; + mei_cl_unlink(&dev->iamthif_cl); + + dev->iamthif_current_cb = NULL; + dev->me_clients_num = 0; + + mutex_unlock(&dev->device_lock); + + flush_scheduled_work(); + + /* disable interrupts */ + mei_disable_interrupts(dev); + + free_irq(pdev->irq, dev); + pci_disable_msi(pdev); + pci_set_drvdata(pdev, NULL); + + if (dev->mem_addr) + pci_iounmap(pdev, dev->mem_addr); + + kfree(dev); + + pci_release_regions(pdev); + pci_disable_device(pdev); + + mei_deregister(); + +} +#ifdef CONFIG_PM +static int mei_pci_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev = pci_get_drvdata(pdev); + int err; + + if (!dev) + return -ENODEV; + mutex_lock(&dev->device_lock); + + cancel_delayed_work(&dev->timer_work); + + /* Stop watchdog if exists */ + err = mei_wd_stop(dev); + /* Set new mei state */ + if (dev->dev_state == MEI_DEV_ENABLED || + dev->dev_state == MEI_DEV_RECOVERING_FROM_RESET) { + dev->dev_state = MEI_DEV_POWER_DOWN; + mei_reset(dev, 0); + } + mutex_unlock(&dev->device_lock); + + free_irq(pdev->irq, dev); + pci_disable_msi(pdev); + + return err; +} + +static int mei_pci_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int err; + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + pci_enable_msi(pdev); + + /* request and enable interrupt */ + if (pci_dev_msi_enabled(pdev)) + err = request_threaded_irq(pdev->irq, + NULL, + mei_interrupt_thread_handler, + IRQF_ONESHOT, KBUILD_MODNAME, dev); + else + err = request_threaded_irq(pdev->irq, + mei_interrupt_quick_handler, + mei_interrupt_thread_handler, + IRQF_SHARED, KBUILD_MODNAME, dev); + + if (err) { + dev_err(&pdev->dev, "request_threaded_irq failed: irq = %d.\n", + pdev->irq); + return err; + } + + mutex_lock(&dev->device_lock); + dev->dev_state = MEI_DEV_POWER_UP; + mei_reset(dev, 1); + mutex_unlock(&dev->device_lock); + + /* Start timer if stopped in suspend */ + schedule_delayed_work(&dev->timer_work, HZ); + + return err; +} +static SIMPLE_DEV_PM_OPS(mei_pm_ops, mei_pci_suspend, mei_pci_resume); +#define MEI_PM_OPS (&mei_pm_ops) +#else +#define MEI_PM_OPS NULL +#endif /* CONFIG_PM */ +/* + * PCI driver structure + */ +static struct pci_driver mei_driver = { + .name = KBUILD_MODNAME, + .id_table = mei_pci_tbl, + .probe = mei_probe, + .remove = mei_remove, + .shutdown = mei_remove, + .driver.pm = MEI_PM_OPS, +}; + +module_pci_driver(mei_driver); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel(R) Management Engine Interface"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.1 From 52c34561415b420301f1580413a9d1891d079494 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 6 Feb 2013 14:06:40 +0200 Subject: mei: initial extract of ME hw specifics from mei_device This is initial step of move the ME hw specifics out of mei_device structure into mei_me_hw Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 131 +++++++++++++++++++++++++++++---------------- drivers/misc/mei/hw-me.h | 12 +++++ drivers/misc/mei/init.c | 28 +--------- drivers/misc/mei/mei_dev.h | 10 ++-- drivers/misc/mei/pci-me.c | 21 ++++---- 5 files changed, 114 insertions(+), 88 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 94b203ec9b1f..3bdf22848a91 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -28,10 +28,10 @@ * * returns register value (u32) */ -static inline u32 mei_reg_read(const struct mei_device *dev, +static inline u32 mei_reg_read(const struct mei_me_hw *hw, unsigned long offset) { - return ioread32(dev->mem_addr + offset); + return ioread32(hw->mem_addr + offset); } @@ -42,10 +42,10 @@ static inline u32 mei_reg_read(const struct mei_device *dev, * @offset: offset from which to write the data * @value: register value to write (u32) */ -static inline void mei_reg_write(const struct mei_device *dev, +static inline void mei_reg_write(const struct mei_me_hw *hw, unsigned long offset, u32 value) { - iowrite32(value, dev->mem_addr + offset); + iowrite32(value, hw->mem_addr + offset); } /** @@ -58,7 +58,7 @@ static inline void mei_reg_write(const struct mei_device *dev, */ u32 mei_mecbrw_read(const struct mei_device *dev) { - return mei_reg_read(dev, ME_CB_RW); + return mei_reg_read(to_me_hw(dev), ME_CB_RW); } /** * mei_mecsr_read - Reads 32bit data from the ME CSR @@ -67,9 +67,9 @@ u32 mei_mecbrw_read(const struct mei_device *dev) * * returns ME_CSR_HA register value (u32) */ -static inline u32 mei_mecsr_read(const struct mei_device *dev) +static inline u32 mei_mecsr_read(const struct mei_me_hw *hw) { - return mei_reg_read(dev, ME_CSR_HA); + return mei_reg_read(hw, ME_CSR_HA); } /** @@ -79,9 +79,9 @@ static inline u32 mei_mecsr_read(const struct mei_device *dev) * * returns H_CSR register value (u32) */ -static inline u32 mei_hcsr_read(const struct mei_device *dev) +static inline u32 mei_hcsr_read(const struct mei_me_hw *hw) { - return mei_reg_read(dev, H_CSR); + return mei_reg_read(hw, H_CSR); } /** @@ -90,10 +90,10 @@ static inline u32 mei_hcsr_read(const struct mei_device *dev) * * @dev: the device structure */ -static inline void mei_hcsr_set(struct mei_device *dev, u32 hcsr) +static inline void mei_hcsr_set(struct mei_me_hw *hw, u32 hcsr) { hcsr &= ~H_IS; - mei_reg_write(dev, H_CSR, hcsr); + mei_reg_write(hw, H_CSR, hcsr); } @@ -104,7 +104,7 @@ static inline void mei_hcsr_set(struct mei_device *dev, u32 hcsr) */ void mei_hw_config(struct mei_device *dev) { - u32 hcsr = mei_hcsr_read(dev); + u32 hcsr = mei_hcsr_read(to_me_hw(dev)); /* Doesn't change in runtime */ dev->hbuf_depth = (hcsr & H_CBD) >> 24; } @@ -115,9 +115,10 @@ void mei_hw_config(struct mei_device *dev) */ void mei_clear_interrupts(struct mei_device *dev) { - u32 hcsr = mei_hcsr_read(dev); + struct mei_me_hw *hw = to_me_hw(dev); + u32 hcsr = mei_hcsr_read(hw); if ((hcsr & H_IS) == H_IS) - mei_reg_write(dev, H_CSR, hcsr); + mei_reg_write(hw, H_CSR, hcsr); } /** @@ -127,9 +128,10 @@ void mei_clear_interrupts(struct mei_device *dev) */ void mei_enable_interrupts(struct mei_device *dev) { - u32 hcsr = mei_hcsr_read(dev); + struct mei_me_hw *hw = to_me_hw(dev); + u32 hcsr = mei_hcsr_read(hw); hcsr |= H_IE; - mei_hcsr_set(dev, hcsr); + mei_hcsr_set(hw, hcsr); } /** @@ -139,9 +141,10 @@ void mei_enable_interrupts(struct mei_device *dev) */ void mei_disable_interrupts(struct mei_device *dev) { - u32 hcsr = mei_hcsr_read(dev); + struct mei_me_hw *hw = to_me_hw(dev); + u32 hcsr = mei_hcsr_read(hw); hcsr &= ~H_IE; - mei_hcsr_set(dev, hcsr); + mei_hcsr_set(hw, hcsr); } /** @@ -152,7 +155,8 @@ void mei_disable_interrupts(struct mei_device *dev) */ void mei_hw_reset(struct mei_device *dev, bool intr_enable) { - u32 hcsr = mei_hcsr_read(dev); + struct mei_me_hw *hw = to_me_hw(dev); + u32 hcsr = mei_hcsr_read(hw); dev_dbg(&dev->pdev->dev, "before reset HCSR = 0x%08x.\n", hcsr); @@ -163,14 +167,14 @@ void mei_hw_reset(struct mei_device *dev, bool intr_enable) else hcsr &= ~H_IE; - mei_hcsr_set(dev, hcsr); + mei_hcsr_set(hw, hcsr); - hcsr = mei_hcsr_read(dev) | H_IG; + hcsr = mei_hcsr_read(hw) | H_IG; hcsr &= ~H_RST; - mei_hcsr_set(dev, hcsr); + mei_hcsr_set(hw, hcsr); - hcsr = mei_hcsr_read(dev); + hcsr = mei_hcsr_read(hw); dev_dbg(&dev->pdev->dev, "current HCSR = 0x%08x.\n", hcsr); } @@ -184,8 +188,9 @@ void mei_hw_reset(struct mei_device *dev, bool intr_enable) void mei_host_set_ready(struct mei_device *dev) { - dev->host_hw_state |= H_IE | H_IG | H_RDY; - mei_hcsr_set(dev, dev->host_hw_state); + struct mei_me_hw *hw = to_me_hw(dev); + hw->host_hw_state |= H_IE | H_IG | H_RDY; + mei_hcsr_set(hw, hw->host_hw_state); } /** * mei_host_is_ready - check whether the host has turned ready @@ -195,8 +200,9 @@ void mei_host_set_ready(struct mei_device *dev) */ bool mei_host_is_ready(struct mei_device *dev) { - dev->host_hw_state = mei_hcsr_read(dev); - return (dev->host_hw_state & H_RDY) == H_RDY; + struct mei_me_hw *hw = to_me_hw(dev); + hw->host_hw_state = mei_hcsr_read(hw); + return (hw->host_hw_state & H_RDY) == H_RDY; } /** @@ -207,8 +213,9 @@ bool mei_host_is_ready(struct mei_device *dev) */ bool mei_me_is_ready(struct mei_device *dev) { - dev->me_hw_state = mei_mecsr_read(dev); - return (dev->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA; + struct mei_me_hw *hw = to_me_hw(dev); + hw->me_hw_state = mei_mecsr_read(hw); + return (hw->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA; } /** @@ -222,13 +229,14 @@ bool mei_me_is_ready(struct mei_device *dev) irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) { struct mei_device *dev = (struct mei_device *) dev_id; - u32 csr_reg = mei_hcsr_read(dev); + struct mei_me_hw *hw = to_me_hw(dev); + u32 csr_reg = mei_hcsr_read(hw); if ((csr_reg & H_IS) != H_IS) return IRQ_NONE; /* clear H_IS bit in H_CSR */ - mei_reg_write(dev, H_CSR, csr_reg); + mei_reg_write(hw, H_CSR, csr_reg); return IRQ_WAKE_THREAD; } @@ -242,12 +250,13 @@ irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) */ static unsigned char mei_hbuf_filled_slots(struct mei_device *dev) { + struct mei_me_hw *hw = to_me_hw(dev); char read_ptr, write_ptr; - dev->host_hw_state = mei_hcsr_read(dev); + hw->host_hw_state = mei_hcsr_read(hw); - read_ptr = (char) ((dev->host_hw_state & H_CBRP) >> 8); - write_ptr = (char) ((dev->host_hw_state & H_CBWP) >> 16); + read_ptr = (char) ((hw->host_hw_state & H_CBRP) >> 8); + write_ptr = (char) ((hw->host_hw_state & H_CBWP) >> 16); return (unsigned char) (write_ptr - read_ptr); } @@ -297,6 +306,7 @@ int mei_hbuf_empty_slots(struct mei_device *dev) int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, unsigned char *buf) { + struct mei_me_hw *hw = to_me_hw(dev); unsigned long rem, dw_cnt; unsigned long length = header->length; u32 *reg_buf = (u32 *)buf; @@ -313,20 +323,20 @@ int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, if (empty_slots < 0 || dw_cnt > empty_slots) return -EIO; - mei_reg_write(dev, H_CB_WW, *((u32 *) header)); + mei_reg_write(hw, H_CB_WW, *((u32 *) header)); for (i = 0; i < length / 4; i++) - mei_reg_write(dev, H_CB_WW, reg_buf[i]); + mei_reg_write(hw, H_CB_WW, reg_buf[i]); rem = length & 0x3; if (rem > 0) { u32 reg = 0; memcpy(®, &buf[length - rem], rem); - mei_reg_write(dev, H_CB_WW, reg); + mei_reg_write(hw, H_CB_WW, reg); } - hcsr = mei_hcsr_read(dev) | H_IG; - mei_hcsr_set(dev, hcsr); + hcsr = mei_hcsr_read(hw) | H_IG; + mei_hcsr_set(hw, hcsr); if (!mei_me_is_ready(dev)) return -EIO; @@ -342,13 +352,14 @@ int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, */ int mei_count_full_read_slots(struct mei_device *dev) { + struct mei_me_hw *hw = to_me_hw(dev); char read_ptr, write_ptr; unsigned char buffer_depth, filled_slots; - dev->me_hw_state = mei_mecsr_read(dev); - buffer_depth = (unsigned char)((dev->me_hw_state & ME_CBD_HRA) >> 24); - read_ptr = (char) ((dev->me_hw_state & ME_CBRP_HRA) >> 8); - write_ptr = (char) ((dev->me_hw_state & ME_CBWP_HRA) >> 16); + hw->me_hw_state = mei_mecsr_read(hw); + buffer_depth = (unsigned char)((hw->me_hw_state & ME_CBD_HRA) >> 24); + read_ptr = (char) ((hw->me_hw_state & ME_CBRP_HRA) >> 8); + write_ptr = (char) ((hw->me_hw_state & ME_CBWP_HRA) >> 16); filled_slots = (unsigned char) (write_ptr - read_ptr); /* check for overflow */ @@ -369,6 +380,7 @@ int mei_count_full_read_slots(struct mei_device *dev) void mei_read_slots(struct mei_device *dev, unsigned char *buffer, unsigned long buffer_length) { + struct mei_me_hw *hw = to_me_hw(dev); u32 *reg_buf = (u32 *)buffer; u32 hcsr; @@ -380,7 +392,36 @@ void mei_read_slots(struct mei_device *dev, unsigned char *buffer, memcpy(reg_buf, ®, buffer_length); } - hcsr = mei_hcsr_read(dev) | H_IG; - mei_hcsr_set(dev, hcsr); + hcsr = mei_hcsr_read(hw) | H_IG; + mei_hcsr_set(hw, hcsr); } +/** + * init_mei_device - allocates and initializes the mei device structure + * + * @pdev: The pci device structure + * + * returns The mei_device_device pointer on success, NULL on failure. + */ +struct mei_device *mei_me_dev_init(struct pci_dev *pdev) +{ + struct mei_device *dev; + + dev = kzalloc(sizeof(struct mei_device) + + sizeof(struct mei_me_hw), GFP_KERNEL); + if (!dev) + return NULL; + + mei_device_init(dev); + + INIT_LIST_HEAD(&dev->wd_cl.link); + INIT_LIST_HEAD(&dev->iamthif_cl.link); + mei_io_list_init(&dev->amthif_cmd_list); + mei_io_list_init(&dev->amthif_rd_complete_list); + + INIT_DELAYED_WORK(&dev->timer_work, mei_timer); + INIT_WORK(&dev->init_work, mei_host_client_init); + + dev->pdev = pdev; + return dev; +} diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 73bef545e4d5..53bcc0087e26 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -21,8 +21,20 @@ #include #include "mei_dev.h" +#include "client.h" +struct mei_me_hw { + void __iomem *mem_addr; + /* + * hw states of host and fw(ME) + */ + u32 host_hw_state; + u32 me_hw_state; +}; +#define to_me_hw(dev) (struct mei_me_hw *)((dev)->hw) + +struct mei_device *mei_me_dev_init(struct pci_dev *pdev); void mei_read_slots(struct mei_device *dev, unsigned char *buffer, diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 5d08db5b314e..1e1876ff25b1 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -42,28 +42,10 @@ const char *mei_dev_state_str(int state) #undef MEI_DEV_STATE } - - - -/** - * init_mei_device - allocates and initializes the mei device structure - * - * @pdev: The pci device structure - * - * returns The mei_device_device pointer on success, NULL on failure. - */ -struct mei_device *mei_device_init(struct pci_dev *pdev) +void mei_device_init(struct mei_device *dev) { - struct mei_device *dev; - - dev = kzalloc(sizeof(struct mei_device), GFP_KERNEL); - if (!dev) - return NULL; - /* setup our list array */ INIT_LIST_HEAD(&dev->file_list); - INIT_LIST_HEAD(&dev->wd_cl.link); - INIT_LIST_HEAD(&dev->iamthif_cl.link); mutex_init(&dev->device_lock); init_waitqueue_head(&dev->wait_recvd_msg); init_waitqueue_head(&dev->wait_stop_wd); @@ -74,14 +56,6 @@ struct mei_device *mei_device_init(struct pci_dev *pdev) mei_io_list_init(&dev->write_waiting_list); mei_io_list_init(&dev->ctrl_wr_list); mei_io_list_init(&dev->ctrl_rd_list); - mei_io_list_init(&dev->amthif_cmd_list); - mei_io_list_init(&dev->amthif_rd_complete_list); - - INIT_DELAYED_WORK(&dev->timer_work, mei_timer); - INIT_WORK(&dev->init_work, mei_host_client_init); - - dev->pdev = pdev; - return dev; } /** diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 3b2bca386e5a..bb759fd9ee4a 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -235,18 +235,13 @@ struct mei_device { struct list_head file_list; long open_handle_count; - void __iomem *mem_addr; /* * lock for the device */ struct mutex device_lock; /* device lock */ struct delayed_work timer_work; /* MEI timer delayed work (timeouts) */ bool recvd_msg; - /* - * hw states of host and fw(ME) - */ - u32 host_hw_state; - u32 me_hw_state; + u8 hbuf_depth; /* * waiting queue for receive message from FW @@ -311,6 +306,7 @@ struct mei_device { bool iamthif_canceled; struct work_struct init_work; + char hw[0] __aligned(sizeof(void *)); }; static inline unsigned long mei_secs_to_jiffies(unsigned long sec) @@ -322,7 +318,7 @@ static inline unsigned long mei_secs_to_jiffies(unsigned long sec) /* * mei init function prototypes */ -struct mei_device *mei_device_init(struct pci_dev *pdev); +void mei_device_init(struct mei_device *dev); void mei_reset(struct mei_device *dev, int interrupts); int mei_hw_init(struct mei_device *dev); diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index eaed398bed6b..27ac71767981 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -90,7 +90,6 @@ MODULE_DEVICE_TABLE(pci, mei_pci_tbl); static DEFINE_MUTEX(mei_mutex); - /** * mei_quirk_probe - probe for devices that doesn't valid ME interface * @pdev: PCI device structure @@ -120,10 +119,10 @@ static bool mei_quirk_probe(struct pci_dev *pdev, * * returns 0 on success, <0 on failure. */ -static int mei_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) +static int mei_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct mei_device *dev; + struct mei_me_hw *hw; int err; mutex_lock(&mei_mutex); @@ -152,14 +151,15 @@ static int mei_probe(struct pci_dev *pdev, goto disable_device; } /* allocates and initializes the mei dev structure */ - dev = mei_device_init(pdev); + dev = mei_me_dev_init(pdev); if (!dev) { err = -ENOMEM; goto release_regions; } + hw = to_me_hw(dev); /* mapping IO device memory */ - dev->mem_addr = pci_iomap(pdev, 0, 0); - if (!dev->mem_addr) { + hw->mem_addr = pci_iomap(pdev, 0, 0); + if (!hw->mem_addr) { dev_err(&pdev->dev, "mapping I/O device memory failure.\n"); err = -ENOMEM; goto free_device; @@ -212,7 +212,7 @@ release_irq: free_irq(pdev->irq, dev); disable_msi: pci_disable_msi(pdev); - pci_iounmap(pdev, dev->mem_addr); + pci_iounmap(pdev, hw->mem_addr); free_device: kfree(dev); release_regions: @@ -236,6 +236,7 @@ end: static void mei_remove(struct pci_dev *pdev) { struct mei_device *dev; + struct mei_me_hw *hw; if (mei_pdev != pdev) return; @@ -244,6 +245,8 @@ static void mei_remove(struct pci_dev *pdev) if (!dev) return; + hw = to_me_hw(dev); + mutex_lock(&dev->device_lock); cancel_delayed_work(&dev->timer_work); @@ -289,8 +292,8 @@ static void mei_remove(struct pci_dev *pdev) pci_disable_msi(pdev); pci_set_drvdata(pdev, NULL); - if (dev->mem_addr) - pci_iounmap(pdev, dev->mem_addr); + if (hw->mem_addr) + pci_iounmap(pdev, hw->mem_addr); kfree(dev); -- cgit v1.2.1 From 827eef51f8dd9a4ab62b4ad270c15472f46938f2 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 6 Feb 2013 14:06:41 +0200 Subject: mei: separate compilation of the ME hardware specifics We add struct mei_hw_ops to virtualize access to hw specific configurations. This allows us to separate the compilation of the ME interface from the ME hardware specifics Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/Kconfig | 17 ++++-- drivers/misc/mei/Makefile | 6 +- drivers/misc/mei/amthif.c | 6 +- drivers/misc/mei/hw-me.c | 87 +++++++++++++++++++--------- drivers/misc/mei/hw-me.h | 19 ------- drivers/misc/mei/init.c | 2 +- drivers/misc/mei/interrupt.c | 10 ++-- drivers/misc/mei/main.c | 5 +- drivers/misc/mei/mei_dev.h | 132 +++++++++++++++++++++++++++++++++++++++---- 9 files changed, 210 insertions(+), 74 deletions(-) diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index 5a79ccde2fdf..fa5c24982c88 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -1,11 +1,22 @@ config INTEL_MEI - tristate "Intel Management Engine Interface (Intel MEI)" - depends on X86 && PCI && WATCHDOG_CORE + tristate "Intel Management Engine Interface" + depends on X86 && PCI help The Intel Management Engine (Intel ME) provides Manageability, Security and Media services for system containing Intel chipsets. if selected /dev/mei misc device will be created. + For more information see + + +config INTEL_MEI_ME + bool "ME Enabled Intel Chipsets" + depends on INTEL_MEI + depends on X86 && PCI && WATCHDOG_CORE + default y + help + MEI support for ME Enabled Intel chipsets. + Supported Chipsets are: 7 Series Chipset Family 6 Series Chipset Family @@ -24,5 +35,3 @@ config INTEL_MEI 82Q33 Express 82X38/X48 Express - For more information see - diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 068f55354811..040af6c7b147 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -6,9 +6,9 @@ obj-$(CONFIG_INTEL_MEI) += mei.o mei-objs := init.o mei-objs += hbm.o mei-objs += interrupt.o -mei-objs += hw-me.o +mei-objs += client.o mei-objs += main.o mei-objs += amthif.o mei-objs += wd.o -mei-objs += client.o -mei-objs += pci-me.o +mei-$(CONFIG_INTEL_MEI_ME) += pci-me.o +mei-$(CONFIG_INTEL_MEI_ME) += hw-me.o diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index 263ed4ccd6f2..a7c483850083 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -300,8 +300,8 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) if (ret && dev->mei_host_buffer_is_empty) { ret = 0; dev->mei_host_buffer_is_empty = false; - if (cb->request_buffer.size > mei_hbuf_max_data(dev)) { - mei_hdr.length = mei_hbuf_max_data(dev); + if (cb->request_buffer.size > mei_hbuf_max_len(dev)) { + mei_hdr.length = mei_hbuf_max_len(dev); mei_hdr.msg_complete = 0; } else { mei_hdr.length = cb->request_buffer.size; @@ -583,7 +583,7 @@ int mei_amthif_irq_read(struct mei_device *dev, s32 *slots) dev->iamthif_msg_buf_index = 0; dev->iamthif_msg_buf_size = 0; dev->iamthif_stall_timer = MEI_IAMTHIF_STALL_TIMER; - dev->mei_host_buffer_is_empty = mei_hbuf_is_empty(dev); + dev->mei_host_buffer_is_empty = mei_hbuf_is_ready(dev); return 0; } diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 3bdf22848a91..6300943497ae 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -56,7 +56,7 @@ static inline void mei_reg_write(const struct mei_me_hw *hw, * * returns ME_CB_RW register value (u32) */ -u32 mei_mecbrw_read(const struct mei_device *dev) +static u32 mei_me_mecbrw_read(const struct mei_device *dev) { return mei_reg_read(to_me_hw(dev), ME_CB_RW); } @@ -102,7 +102,7 @@ static inline void mei_hcsr_set(struct mei_me_hw *hw, u32 hcsr) * * @dev: mei device */ -void mei_hw_config(struct mei_device *dev) +static void mei_me_hw_config(struct mei_device *dev) { u32 hcsr = mei_hcsr_read(to_me_hw(dev)); /* Doesn't change in runtime */ @@ -113,20 +113,19 @@ void mei_hw_config(struct mei_device *dev) * * @dev: the device structure */ -void mei_clear_interrupts(struct mei_device *dev) +static void mei_me_intr_clear(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr = mei_hcsr_read(hw); if ((hcsr & H_IS) == H_IS) mei_reg_write(hw, H_CSR, hcsr); } - /** - * mei_enable_interrupts - enables mei device interrupts + * mei_me_intr_enable - enables mei device interrupts * * @dev: the device structure */ -void mei_enable_interrupts(struct mei_device *dev) +static void mei_me_intr_enable(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr = mei_hcsr_read(hw); @@ -139,7 +138,7 @@ void mei_enable_interrupts(struct mei_device *dev) * * @dev: the device structure */ -void mei_disable_interrupts(struct mei_device *dev) +static void mei_me_intr_disable(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr = mei_hcsr_read(hw); @@ -148,12 +147,12 @@ void mei_disable_interrupts(struct mei_device *dev) } /** - * mei_hw_reset - resets fw via mei csr register. + * mei_me_hw_reset - resets fw via mei csr register. * * @dev: the device structure * @interrupts_enabled: if interrupt should be enabled after reset. */ -void mei_hw_reset(struct mei_device *dev, bool intr_enable) +static void mei_me_hw_reset(struct mei_device *dev, bool intr_enable) { struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr = mei_hcsr_read(hw); @@ -180,25 +179,25 @@ void mei_hw_reset(struct mei_device *dev, bool intr_enable) } /** - * mei_host_set_ready - enable device + * mei_me_host_set_ready - enable device * * @dev - mei device * returns bool */ -void mei_host_set_ready(struct mei_device *dev) +static void mei_me_host_set_ready(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); hw->host_hw_state |= H_IE | H_IG | H_RDY; mei_hcsr_set(hw, hw->host_hw_state); } /** - * mei_host_is_ready - check whether the host has turned ready + * mei_me_host_is_ready - check whether the host has turned ready * * @dev - mei device * returns bool */ -bool mei_host_is_ready(struct mei_device *dev) +static bool mei_me_host_is_ready(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); hw->host_hw_state = mei_hcsr_read(hw); @@ -206,12 +205,12 @@ bool mei_host_is_ready(struct mei_device *dev) } /** - * mei_me_is_ready - check whether the me has turned ready + * mei_me_hw_is_ready - check whether the me(hw) has turned ready * * @dev - mei device * returns bool */ -bool mei_me_is_ready(struct mei_device *dev) +static bool mei_me_hw_is_ready(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); hw->me_hw_state = mei_mecsr_read(hw); @@ -268,19 +267,19 @@ static unsigned char mei_hbuf_filled_slots(struct mei_device *dev) * * returns true if empty, false - otherwise. */ -bool mei_hbuf_is_empty(struct mei_device *dev) +static bool mei_me_hbuf_is_empty(struct mei_device *dev) { return mei_hbuf_filled_slots(dev) == 0; } /** - * mei_hbuf_empty_slots - counts write empty slots. + * mei_me_hbuf_empty_slots - counts write empty slots. * * @dev: the device structure * * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise empty slots count */ -int mei_hbuf_empty_slots(struct mei_device *dev) +static int mei_me_hbuf_empty_slots(struct mei_device *dev) { unsigned char filled_slots, empty_slots; @@ -294,6 +293,12 @@ int mei_hbuf_empty_slots(struct mei_device *dev) return empty_slots; } +static size_t mei_me_hbuf_max_len(const struct mei_device *dev) +{ + return dev->hbuf_depth * sizeof(u32) - sizeof(struct mei_msg_hdr); +} + + /** * mei_write_message - writes a message to mei device. * @@ -303,8 +308,9 @@ int mei_hbuf_empty_slots(struct mei_device *dev) * * This function returns -EIO if write has failed */ -int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, - unsigned char *buf) +static int mei_me_write_message(struct mei_device *dev, + struct mei_msg_hdr *header, + unsigned char *buf) { struct mei_me_hw *hw = to_me_hw(dev); unsigned long rem, dw_cnt; @@ -337,20 +343,20 @@ int mei_write_message(struct mei_device *dev, struct mei_msg_hdr *header, hcsr = mei_hcsr_read(hw) | H_IG; mei_hcsr_set(hw, hcsr); - if (!mei_me_is_ready(dev)) + if (!mei_me_hw_is_ready(dev)) return -EIO; return 0; } /** - * mei_count_full_read_slots - counts read full slots. + * mei_me_count_full_read_slots - counts read full slots. * * @dev: the device structure * * returns -1(ESLOTS_OVERFLOW) if overflow, otherwise filled slots count */ -int mei_count_full_read_slots(struct mei_device *dev) +static int mei_me_count_full_read_slots(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); char read_ptr, write_ptr; @@ -371,13 +377,13 @@ int mei_count_full_read_slots(struct mei_device *dev) } /** - * mei_read_slots - reads a message from mei device. + * mei_me_read_slots - reads a message from mei device. * * @dev: the device structure * @buffer: message buffer will be written * @buffer_length: message size will be read */ -void mei_read_slots(struct mei_device *dev, unsigned char *buffer, +static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, unsigned long buffer_length) { struct mei_me_hw *hw = to_me_hw(dev); @@ -385,17 +391,42 @@ void mei_read_slots(struct mei_device *dev, unsigned char *buffer, u32 hcsr; for (; buffer_length >= sizeof(u32); buffer_length -= sizeof(u32)) - *reg_buf++ = mei_mecbrw_read(dev); + *reg_buf++ = mei_me_mecbrw_read(dev); if (buffer_length > 0) { - u32 reg = mei_mecbrw_read(dev); + u32 reg = mei_me_mecbrw_read(dev); memcpy(reg_buf, ®, buffer_length); } hcsr = mei_hcsr_read(hw) | H_IG; mei_hcsr_set(hw, hcsr); + return 0; } +static const struct mei_hw_ops mei_me_hw_ops = { + + .host_set_ready = mei_me_host_set_ready, + .host_is_ready = mei_me_host_is_ready, + + .hw_is_ready = mei_me_hw_is_ready, + .hw_reset = mei_me_hw_reset, + .hw_config = mei_me_hw_config, + + .intr_clear = mei_me_intr_clear, + .intr_enable = mei_me_intr_enable, + .intr_disable = mei_me_intr_disable, + + .hbuf_free_slots = mei_me_hbuf_empty_slots, + .hbuf_is_ready = mei_me_hbuf_is_empty, + .hbuf_max_len = mei_me_hbuf_max_len, + + .write = mei_me_write_message, + + .rdbuf_full_slots = mei_me_count_full_read_slots, + .read_hdr = mei_me_mecbrw_read, + .read = mei_me_read_slots +}; + /** * init_mei_device - allocates and initializes the mei device structure * @@ -422,6 +453,8 @@ struct mei_device *mei_me_dev_init(struct pci_dev *pdev) INIT_DELAYED_WORK(&dev->timer_work, mei_timer); INIT_WORK(&dev->init_work, mei_host_client_init); + dev->ops = &mei_me_hw_ops; + dev->pdev = pdev; return dev; } diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 53bcc0087e26..9a3aaab9bcf0 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -36,29 +36,10 @@ struct mei_me_hw { struct mei_device *mei_me_dev_init(struct pci_dev *pdev); -void mei_read_slots(struct mei_device *dev, - unsigned char *buffer, - unsigned long buffer_length); - -int mei_write_message(struct mei_device *dev, - struct mei_msg_hdr *header, - unsigned char *buf); - -bool mei_hbuf_is_empty(struct mei_device *dev); - -int mei_hbuf_empty_slots(struct mei_device *dev); - -static inline size_t mei_hbuf_max_data(const struct mei_device *dev) -{ - return dev->hbuf_depth * sizeof(u32) - sizeof(struct mei_msg_hdr); -} - /* get slots (dwords) from a message length + header (bytes) */ static inline unsigned char mei_data2slots(size_t length) { return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, 4); } -int mei_count_full_read_slots(struct mei_device *dev); - #endif /* _MEI_INTERFACE_H_ */ diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 1e1876ff25b1..51a005e80952 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -104,7 +104,7 @@ int mei_hw_init(struct mei_device *dev) goto err; } - if (!mei_me_is_ready(dev)) { + if (!mei_hw_is_ready(dev)) { dev_err(&dev->pdev->dev, "ME is not ready.\n"); goto err; } diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index b04ed9bdf758..431aa91fd002 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -329,7 +329,7 @@ static int mei_irq_thread_read_handler(struct mei_cl_cb *cmpl_list, int ret = 0; if (!dev->rd_msg_hdr) { - dev->rd_msg_hdr = mei_mecbrw_read(dev); + dev->rd_msg_hdr = mei_read_hdr(dev); dev_dbg(&dev->pdev->dev, "slots =%08x.\n", *slots); (*slots)--; dev_dbg(&dev->pdev->dev, "slots =%08x.\n", *slots); @@ -430,7 +430,7 @@ static int mei_irq_thread_write_handler(struct mei_device *dev, s32 slots; int ret; - if (!mei_hbuf_is_empty(dev)) { + if (!mei_hbuf_is_ready(dev)) { dev_dbg(&dev->pdev->dev, "host buffer is not empty.\n"); return 0; } @@ -698,7 +698,7 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) mei_clear_interrupts(dev); /* check if ME wants a reset */ - if (!mei_me_is_ready(dev) && + if (!mei_hw_is_ready(dev) && dev->dev_state != MEI_DEV_RESETING && dev->dev_state != MEI_DEV_INITIALIZING) { dev_dbg(&dev->pdev->dev, "FW not ready.\n"); @@ -709,7 +709,7 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) /* check if we need to start the dev */ if (!mei_host_is_ready(dev)) { - if (mei_me_is_ready(dev)) { + if (mei_hw_is_ready(dev)) { dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); mei_host_set_ready(dev); @@ -743,7 +743,7 @@ irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) rets = mei_irq_thread_write_handler(dev, &complete_list); end: dev_dbg(&dev->pdev->dev, "end of bottom half function.\n"); - dev->mei_host_buffer_is_empty = mei_hbuf_is_empty(dev); + dev->mei_host_buffer_is_empty = mei_hbuf_is_ready(dev); bus_message_received = false; if (dev->recvd_msg && waitqueue_active(&dev->wait_recvd_msg)) { diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 018623c9a8e1..843ae2febc70 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -462,8 +462,8 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, } dev->mei_host_buffer_is_empty = false; - if (length > mei_hbuf_max_data(dev)) { - mei_hdr.length = mei_hbuf_max_data(dev); + if (length > mei_hbuf_max_len(dev)) { + mei_hdr.length = mei_hbuf_max_len(dev); mei_hdr.msg_complete = 0; } else { mei_hdr.length = length; @@ -765,4 +765,5 @@ void mei_deregister(void) mei_misc_device.parent = NULL; } +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index bb759fd9ee4a..c974292f16d6 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -211,6 +211,58 @@ struct mei_cl { struct mei_cl_cb *read_cb; }; +/** struct mei_hw_ops + * + * @host_set_ready - notify FW that host side is ready + * @host_is_ready - query for host readiness + + * @hw_is_ready - query if hw is ready + * @hw_reset - reset hw + * @hw_config - configure hw + + * @intr_clear - clear pending interrupts + * @intr_enable - enable interrupts + * @intr_disable - disable interrupts + + * @hbuf_free_slots - query for write buffer empty slots + * @hbuf_is_ready - query if write buffer is empty + * @hbuf_max_len - query for write buffer max len + + * @write - write a message to FW + + * @rdbuf_full_slots - query how many slots are filled + + * @read_hdr - get first 4 bytes (header) + * @read - read a buffer from the FW + */ +struct mei_hw_ops { + + void (*host_set_ready) (struct mei_device *dev); + bool (*host_is_ready) (struct mei_device *dev); + + bool (*hw_is_ready) (struct mei_device *dev); + void (*hw_reset) (struct mei_device *dev, bool enable); + void (*hw_config) (struct mei_device *dev); + + void (*intr_clear) (struct mei_device *dev); + void (*intr_enable) (struct mei_device *dev); + void (*intr_disable) (struct mei_device *dev); + + int (*hbuf_free_slots) (struct mei_device *dev); + bool (*hbuf_is_ready) (struct mei_device *dev); + size_t (*hbuf_max_len) (const struct mei_device *dev); + + int (*write)(struct mei_device *dev, + struct mei_msg_hdr *hdr, + unsigned char *buf); + + int (*rdbuf_full_slots)(struct mei_device *dev); + + u32 (*read_hdr)(const struct mei_device *dev); + int (*read) (struct mei_device *dev, + unsigned char *buf, unsigned long len); +}; + /** * struct mei_device - MEI private device struct * @mem_addr - mem mapped base register address @@ -306,6 +358,8 @@ struct mei_device { bool iamthif_canceled; struct work_struct init_work; + + const struct mei_hw_ops *ops; char hw[0] __aligned(sizeof(void *)); }; @@ -376,26 +430,84 @@ void mei_watchdog_register(struct mei_device *dev); */ void mei_watchdog_unregister(struct mei_device *dev); - /* * Register Access Function */ -void mei_hw_config(struct mei_device *dev); -void mei_hw_reset(struct mei_device *dev, bool intr_enable); -u32 mei_mecbrw_read(const struct mei_device *dev); +static inline void mei_hw_config(struct mei_device *dev) +{ + dev->ops->hw_config(dev); +} +static inline void mei_hw_reset(struct mei_device *dev, bool enable) +{ + dev->ops->hw_reset(dev, enable); +} + +static inline void mei_clear_interrupts(struct mei_device *dev) +{ + dev->ops->intr_clear(dev); +} + +static inline void mei_enable_interrupts(struct mei_device *dev) +{ + dev->ops->intr_enable(dev); +} +static inline void mei_disable_interrupts(struct mei_device *dev) +{ + dev->ops->intr_disable(dev); +} +static inline void mei_host_set_ready(struct mei_device *dev) +{ + dev->ops->host_set_ready(dev); +} +static inline bool mei_host_is_ready(struct mei_device *dev) +{ + return dev->ops->host_is_ready(dev); +} +static inline bool mei_hw_is_ready(struct mei_device *dev) +{ + return dev->ops->hw_is_ready(dev); +} -void mei_clear_interrupts(struct mei_device *dev); -void mei_enable_interrupts(struct mei_device *dev); -void mei_disable_interrupts(struct mei_device *dev); +static inline bool mei_hbuf_is_ready(struct mei_device *dev) +{ + return dev->ops->hbuf_is_ready(dev); +} -void mei_host_set_ready(struct mei_device *dev); -bool mei_host_is_ready(struct mei_device *dev); -bool mei_me_is_ready(struct mei_device *dev); +static inline int mei_hbuf_empty_slots(struct mei_device *dev) +{ + return dev->ops->hbuf_free_slots(dev); +} +static inline size_t mei_hbuf_max_len(const struct mei_device *dev) +{ + return dev->ops->hbuf_max_len(dev); +} +static inline int mei_write_message(struct mei_device *dev, + struct mei_msg_hdr *hdr, + unsigned char *buf) +{ + return dev->ops->write(dev, hdr, buf); +} + +static inline u32 mei_read_hdr(const struct mei_device *dev) +{ + return dev->ops->read_hdr(dev); +} + +static inline void mei_read_slots(struct mei_device *dev, + unsigned char *buf, unsigned long len) +{ + dev->ops->read(dev, buf, len); +} + +static inline int mei_count_full_read_slots(struct mei_device *dev) +{ + return dev->ops->rdbuf_full_slots(dev); +} int mei_register(struct device *dev); void mei_deregister(void); -- cgit v1.2.1 From 06ecd6459800962155c485e27d9dd30268b579bf Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 6 Feb 2013 14:06:42 +0200 Subject: mei: move interrupt handlers to be me hw specific interrupt handler are platform specifics so we move them to hw-mei.c. For sake of that we need to export write, read, and complete handlers from the interrupt.c Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 164 ++++++++++++++++++++++++++++++++++++------- drivers/misc/mei/hw-me.h | 3 + drivers/misc/mei/interrupt.c | 126 +++------------------------------ drivers/misc/mei/mei_dev.h | 9 ++- drivers/misc/mei/pci-me.c | 12 ++-- 5 files changed, 164 insertions(+), 150 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 6300943497ae..3bebf8d85ff9 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -15,11 +15,16 @@ */ #include -#include + +#include +#include #include "mei_dev.h" #include "hw-me.h" +#include "hbm.h" + + /** * mei_reg_read - Reads 32bit data from the mei device * @@ -217,29 +222,6 @@ static bool mei_me_hw_is_ready(struct mei_device *dev) return (hw->me_hw_state & ME_RDY_HRA) == ME_RDY_HRA; } -/** - * mei_interrupt_quick_handler - The ISR of the MEI device - * - * @irq: The irq number - * @dev_id: pointer to the device structure - * - * returns irqreturn_t - */ -irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) -{ - struct mei_device *dev = (struct mei_device *) dev_id; - struct mei_me_hw *hw = to_me_hw(dev); - u32 csr_reg = mei_hcsr_read(hw); - - if ((csr_reg & H_IS) != H_IS) - return IRQ_NONE; - - /* clear H_IS bit in H_CSR */ - mei_reg_write(hw, H_CSR, csr_reg); - - return IRQ_WAKE_THREAD; -} - /** * mei_hbuf_filled_slots - gets number of device filled buffer slots * @@ -403,6 +385,139 @@ static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, return 0; } +/** + * mei_me_irq_quick_handler - The ISR of the MEI device + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + */ + +irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id) +{ + struct mei_device *dev = (struct mei_device *) dev_id; + struct mei_me_hw *hw = to_me_hw(dev); + u32 csr_reg = mei_hcsr_read(hw); + + if ((csr_reg & H_IS) != H_IS) + return IRQ_NONE; + + /* clear H_IS bit in H_CSR */ + mei_reg_write(hw, H_CSR, csr_reg); + + return IRQ_WAKE_THREAD; +} + +/** + * mei_me_irq_thread_handler - function called after ISR to handle the interrupt + * processing. + * + * @irq: The irq number + * @dev_id: pointer to the device structure + * + * returns irqreturn_t + * + */ +irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) +{ + struct mei_device *dev = (struct mei_device *) dev_id; + struct mei_cl_cb complete_list; + struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + struct mei_cl *cl; + s32 slots; + int rets; + bool bus_message_received; + + + dev_dbg(&dev->pdev->dev, "function called after ISR to handle the interrupt processing.\n"); + /* initialize our complete list */ + mutex_lock(&dev->device_lock); + mei_io_list_init(&complete_list); + + /* Ack the interrupt here + * In case of MSI we don't go through the quick handler */ + if (pci_dev_msi_enabled(dev->pdev)) + mei_clear_interrupts(dev); + + /* check if ME wants a reset */ + if (!mei_hw_is_ready(dev) && + dev->dev_state != MEI_DEV_RESETING && + dev->dev_state != MEI_DEV_INITIALIZING) { + dev_dbg(&dev->pdev->dev, "FW not ready.\n"); + mei_reset(dev, 1); + mutex_unlock(&dev->device_lock); + return IRQ_HANDLED; + } + + /* check if we need to start the dev */ + if (!mei_host_is_ready(dev)) { + if (mei_hw_is_ready(dev)) { + dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); + + mei_host_set_ready(dev); + + dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); + /* link is established * start sending messages. */ + + dev->dev_state = MEI_DEV_INIT_CLIENTS; + + mei_hbm_start_req(dev); + mutex_unlock(&dev->device_lock); + return IRQ_HANDLED; + } else { + dev_dbg(&dev->pdev->dev, "FW not ready.\n"); + mutex_unlock(&dev->device_lock); + return IRQ_HANDLED; + } + } + /* check slots available for reading */ + slots = mei_count_full_read_slots(dev); + while (slots > 0) { + /* we have urgent data to send so break the read */ + if (dev->wr_ext_msg.hdr.length) + break; + dev_dbg(&dev->pdev->dev, "slots =%08x\n", slots); + dev_dbg(&dev->pdev->dev, "call mei_irq_read_handler.\n"); + rets = mei_irq_read_handler(dev, &complete_list, &slots); + if (rets) + goto end; + } + rets = mei_irq_write_handler(dev, &complete_list); +end: + dev_dbg(&dev->pdev->dev, "end of bottom half function.\n"); + dev->mei_host_buffer_is_empty = mei_hbuf_is_ready(dev); + + bus_message_received = false; + if (dev->recvd_msg && waitqueue_active(&dev->wait_recvd_msg)) { + dev_dbg(&dev->pdev->dev, "received waiting bus message\n"); + bus_message_received = true; + } + mutex_unlock(&dev->device_lock); + if (bus_message_received) { + dev_dbg(&dev->pdev->dev, "wake up dev->wait_recvd_msg\n"); + wake_up_interruptible(&dev->wait_recvd_msg); + bus_message_received = false; + } + if (list_empty(&complete_list.list)) + return IRQ_HANDLED; + + + list_for_each_entry_safe(cb_pos, cb_next, &complete_list.list, list) { + cl = cb_pos->cl; + list_del(&cb_pos->list); + if (cl) { + if (cl != &dev->iamthif_cl) { + dev_dbg(&dev->pdev->dev, "completing call back.\n"); + mei_irq_complete_handler(cl, cb_pos); + cb_pos = NULL; + } else if (cl == &dev->iamthif_cl) { + mei_amthif_complete(dev, cb_pos); + } + } + } + return IRQ_HANDLED; +} static const struct mei_hw_ops mei_me_hw_ops = { .host_set_ready = mei_me_host_set_ready, @@ -458,3 +573,4 @@ struct mei_device *mei_me_dev_init(struct pci_dev *pdev) dev->pdev = pdev; return dev; } + diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 9a3aaab9bcf0..8518d3eeb838 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -42,4 +42,7 @@ static inline unsigned char mei_data2slots(size_t length) return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, 4); } +irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id); +irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id); + #endif /* _MEI_INTERFACE_H_ */ diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 431aa91fd002..3535b2676c97 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -30,12 +30,12 @@ /** - * _mei_cmpl - processes completed operation. + * mei_complete_handler - processes completed operation. * * @cl: private data of the file object. * @cb_pos: callback block. */ -static void _mei_cmpl(struct mei_cl *cl, struct mei_cl_cb *cb_pos) +void mei_irq_complete_handler(struct mei_cl *cl, struct mei_cl_cb *cb_pos) { if (cb_pos->fop_type == MEI_FOP_WRITE) { mei_io_cb_free(cb_pos); @@ -313,15 +313,14 @@ static int mei_irq_thread_write_complete(struct mei_device *dev, s32 *slots, * mei_irq_thread_read_handler - bottom half read routine after ISR to * handle the read processing. * - * @cmpl_list: An instance of our list structure * @dev: the device structure + * @cmpl_list: An instance of our list structure * @slots: slots to read. * * returns 0 on success, <0 on failure. */ -static int mei_irq_thread_read_handler(struct mei_cl_cb *cmpl_list, - struct mei_device *dev, - s32 *slots) +int mei_irq_read_handler(struct mei_device *dev, + struct mei_cl_cb *cmpl_list, s32 *slots) { struct mei_msg_hdr *mei_hdr; struct mei_cl *cl_pos = NULL; @@ -412,15 +411,15 @@ end: /** - * mei_irq_thread_write_handler - bottom half write routine after - * ISR to handle the write processing. + * mei_irq_write_handler - dispatch write requests + * after irq received * * @dev: the device structure * @cmpl_list: An instance of our list structure * * returns 0 on success, <0 on failure. */ -static int mei_irq_thread_write_handler(struct mei_device *dev, +int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list) { @@ -666,112 +665,3 @@ out: mutex_unlock(&dev->device_lock); } -/** - * mei_interrupt_thread_handler - function called after ISR to handle the interrupt - * processing. - * - * @irq: The irq number - * @dev_id: pointer to the device structure - * - * returns irqreturn_t - * - */ -irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) -{ - struct mei_device *dev = (struct mei_device *) dev_id; - struct mei_cl_cb complete_list; - struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; - struct mei_cl *cl; - s32 slots; - int rets; - bool bus_message_received; - - - dev_dbg(&dev->pdev->dev, "function called after ISR to handle the interrupt processing.\n"); - /* initialize our complete list */ - mutex_lock(&dev->device_lock); - mei_io_list_init(&complete_list); - - /* Ack the interrupt here - * In case of MSI we don't go through the quick handler */ - if (pci_dev_msi_enabled(dev->pdev)) - mei_clear_interrupts(dev); - - /* check if ME wants a reset */ - if (!mei_hw_is_ready(dev) && - dev->dev_state != MEI_DEV_RESETING && - dev->dev_state != MEI_DEV_INITIALIZING) { - dev_dbg(&dev->pdev->dev, "FW not ready.\n"); - mei_reset(dev, 1); - mutex_unlock(&dev->device_lock); - return IRQ_HANDLED; - } - - /* check if we need to start the dev */ - if (!mei_host_is_ready(dev)) { - if (mei_hw_is_ready(dev)) { - dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); - - mei_host_set_ready(dev); - - dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); - /* link is established * start sending messages. */ - - dev->dev_state = MEI_DEV_INIT_CLIENTS; - - mei_hbm_start_req(dev); - mutex_unlock(&dev->device_lock); - return IRQ_HANDLED; - } else { - dev_dbg(&dev->pdev->dev, "FW not ready.\n"); - mutex_unlock(&dev->device_lock); - return IRQ_HANDLED; - } - } - /* check slots available for reading */ - slots = mei_count_full_read_slots(dev); - while (slots > 0) { - /* we have urgent data to send so break the read */ - if (dev->wr_ext_msg.hdr.length) - break; - dev_dbg(&dev->pdev->dev, "slots =%08x\n", slots); - dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_handler.\n"); - rets = mei_irq_thread_read_handler(&complete_list, dev, &slots); - if (rets) - goto end; - } - rets = mei_irq_thread_write_handler(dev, &complete_list); -end: - dev_dbg(&dev->pdev->dev, "end of bottom half function.\n"); - dev->mei_host_buffer_is_empty = mei_hbuf_is_ready(dev); - - bus_message_received = false; - if (dev->recvd_msg && waitqueue_active(&dev->wait_recvd_msg)) { - dev_dbg(&dev->pdev->dev, "received waiting bus message\n"); - bus_message_received = true; - } - mutex_unlock(&dev->device_lock); - if (bus_message_received) { - dev_dbg(&dev->pdev->dev, "wake up dev->wait_recvd_msg\n"); - wake_up_interruptible(&dev->wait_recvd_msg); - bus_message_received = false; - } - if (list_empty(&complete_list.list)) - return IRQ_HANDLED; - - - list_for_each_entry_safe(cb_pos, cb_next, &complete_list.list, list) { - cl = cb_pos->cl; - list_del(&cb_pos->list); - if (cl) { - if (cl != &dev->iamthif_cl) { - dev_dbg(&dev->pdev->dev, "completing call back.\n"); - _mei_cmpl(cl, cb_pos); - cb_pos = NULL; - } else if (cl == &dev->iamthif_cl) { - mei_amthif_complete(dev, cb_pos); - } - } - } - return IRQ_HANDLED; -} diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index c974292f16d6..7d07cef75664 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -379,9 +379,14 @@ int mei_hw_init(struct mei_device *dev); /* * MEI interrupt functions prototype */ -irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id); -irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id); + void mei_timer(struct work_struct *work); +int mei_irq_read_handler(struct mei_device *dev, + struct mei_cl_cb *cmpl_list, s32 *slots); + +int mei_irq_write_handler(struct mei_device *dev, struct mei_cl_cb *cmpl_list); + +void mei_irq_complete_handler(struct mei_cl *cl, struct mei_cl_cb *cb_pos); /* * AMTHIF - AMT Host Interface Functions diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 27ac71767981..b40ec0601ab0 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -170,12 +170,12 @@ static int mei_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (pci_dev_msi_enabled(pdev)) err = request_threaded_irq(pdev->irq, NULL, - mei_interrupt_thread_handler, + mei_me_irq_thread_handler, IRQF_ONESHOT, KBUILD_MODNAME, dev); else err = request_threaded_irq(pdev->irq, - mei_interrupt_quick_handler, - mei_interrupt_thread_handler, + mei_me_irq_quick_handler, + mei_me_irq_thread_handler, IRQF_SHARED, KBUILD_MODNAME, dev); if (err) { @@ -348,12 +348,12 @@ static int mei_pci_resume(struct device *device) if (pci_dev_msi_enabled(pdev)) err = request_threaded_irq(pdev->irq, NULL, - mei_interrupt_thread_handler, + mei_me_irq_thread_handler, IRQF_ONESHOT, KBUILD_MODNAME, dev); else err = request_threaded_irq(pdev->irq, - mei_interrupt_quick_handler, - mei_interrupt_thread_handler, + mei_me_irq_quick_handler, + mei_me_irq_thread_handler, IRQF_SHARED, KBUILD_MODNAME, dev); if (err) { -- cgit v1.2.1 From 330dd7da5ec80e2c49c66bf353d8b4fa4fb8f5a9 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 6 Feb 2013 14:06:43 +0200 Subject: mei: rename to mei_host_buffer_is_empty to hbuf_is_ready we rename the mei_host_buffer_is_empty to keep naming convention of hbuf and also make the query more generic to be correct also for other under laying hardware Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 8 ++++---- drivers/misc/mei/client.c | 13 ++++++------- drivers/misc/mei/hw-me.c | 2 +- drivers/misc/mei/main.c | 4 ++-- drivers/misc/mei/mei_dev.h | 14 ++++++++++---- drivers/misc/mei/wd.c | 9 ++++----- 6 files changed, 27 insertions(+), 23 deletions(-) diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index a7c483850083..c86d7e3839a4 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -297,9 +297,9 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) if (ret < 0) return ret; - if (ret && dev->mei_host_buffer_is_empty) { + if (ret && dev->hbuf_is_ready) { ret = 0; - dev->mei_host_buffer_is_empty = false; + dev->hbuf_is_ready = false; if (cb->request_buffer.size > mei_hbuf_max_len(dev)) { mei_hdr.length = mei_hbuf_max_len(dev); mei_hdr.msg_complete = 0; @@ -330,7 +330,7 @@ static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) list_add_tail(&cb->list, &dev->write_list.list); } } else { - if (!(dev->mei_host_buffer_is_empty)) + if (!dev->hbuf_is_ready) dev_dbg(&dev->pdev->dev, "host buffer is not empty"); dev_dbg(&dev->pdev->dev, "No flow control credentials, so add iamthif cb to write list.\n"); @@ -583,7 +583,7 @@ int mei_amthif_irq_read(struct mei_device *dev, s32 *slots) dev->iamthif_msg_buf_index = 0; dev->iamthif_msg_buf_size = 0; dev->iamthif_stall_timer = MEI_IAMTHIF_STALL_TIMER; - dev->mei_host_buffer_is_empty = mei_hbuf_is_ready(dev); + dev->hbuf_is_ready = mei_hbuf_is_ready(dev); return 0; } diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index a921001053ba..e46663ee76de 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -393,8 +393,8 @@ int mei_cl_disconnect(struct mei_cl *cl) return -ENOMEM; cb->fop_type = MEI_FOP_CLOSE; - if (dev->mei_host_buffer_is_empty) { - dev->mei_host_buffer_is_empty = false; + if (dev->hbuf_is_ready) { + dev->hbuf_is_ready = false; if (mei_hbm_cl_disconnect_req(dev, cl)) { rets = -ENODEV; dev_err(&dev->pdev->dev, "failed to disconnect.\n"); @@ -496,9 +496,8 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) cb->fop_type = MEI_FOP_IOCTL; - if (dev->mei_host_buffer_is_empty && - !mei_cl_is_other_connecting(cl)) { - dev->mei_host_buffer_is_empty = false; + if (dev->hbuf_is_ready && !mei_cl_is_other_connecting(cl)) { + dev->hbuf_is_ready = false; if (mei_hbm_cl_connect_req(dev, cl)) { rets = -ENODEV; @@ -661,8 +660,8 @@ int mei_cl_read_start(struct mei_cl *cl) cb->fop_type = MEI_FOP_READ; cl->read_cb = cb; - if (dev->mei_host_buffer_is_empty) { - dev->mei_host_buffer_is_empty = false; + if (dev->hbuf_is_ready) { + dev->hbuf_is_ready = false; if (mei_hbm_cl_flow_control_req(dev, cl)) { rets = -ENODEV; goto err; diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 3bebf8d85ff9..45ea7185c003 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -486,7 +486,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) rets = mei_irq_write_handler(dev, &complete_list); end: dev_dbg(&dev->pdev->dev, "end of bottom half function.\n"); - dev->mei_host_buffer_is_empty = mei_hbuf_is_ready(dev); + dev->hbuf_is_ready = mei_hbuf_is_ready(dev); bus_message_received = false; if (dev->recvd_msg && waitqueue_active(&dev->wait_recvd_msg)) { diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 843ae2febc70..903f809b21f7 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -454,14 +454,14 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, if (rets < 0) goto err; - if (rets == 0 || dev->mei_host_buffer_is_empty == false) { + if (rets == 0 || !dev->hbuf_is_ready) { write_cb->buf_idx = 0; mei_hdr.msg_complete = 0; cl->writing_state = MEI_WRITING; goto out; } - dev->mei_host_buffer_is_empty = false; + dev->hbuf_is_ready = false; if (length > mei_hbuf_max_len(dev)) { mei_hdr.length = mei_hbuf_max_len(dev); mei_hdr.msg_complete = 0; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 7d07cef75664..cb80166161f0 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -265,9 +265,13 @@ struct mei_hw_ops { /** * struct mei_device - MEI private device struct + * @mem_addr - mem mapped base register address - * @hbuf_depth - depth of host(write) buffer - * @wr_ext_msg - buffer for hbm control responses (set in read cycle) + + * @hbuf_depth - depth of hardware host/write buffer is slots + * @hbuf_is_ready - query if the host host/write buffer is ready + * @wr_msg - the buffer for hbm control messages + * @wr_ext_msg - the buffer for hbm control responses (set in read cycle) */ struct mei_device { struct pci_dev *pdev; /* pointer to pci device struct */ @@ -294,7 +298,6 @@ struct mei_device { struct delayed_work timer_work; /* MEI timer delayed work (timeouts) */ bool recvd_msg; - u8 hbuf_depth; /* * waiting queue for receive message from FW */ @@ -311,6 +314,10 @@ struct mei_device { unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */ u32 rd_msg_hdr; + /* write buffer */ + u8 hbuf_depth; + bool hbuf_is_ready; + /* used for control messages */ struct { struct mei_msg_hdr hdr; @@ -330,7 +337,6 @@ struct mei_device { u8 me_clients_num; u8 me_client_presentation_num; u8 me_client_index; - bool mei_host_buffer_is_empty; struct mei_cl wd_cl; enum mei_wd_states wd_state; diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index 77b3820380b0..2413247fc392 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -157,9 +157,9 @@ int mei_wd_stop(struct mei_device *dev) if (ret < 0) goto out; - if (ret && dev->mei_host_buffer_is_empty) { + if (ret && dev->hbuf_is_ready) { ret = 0; - dev->mei_host_buffer_is_empty = false; + dev->hbuf_is_ready = false; if (!mei_wd_send(dev)) { ret = mei_cl_flow_ctrl_reduce(&dev->wd_cl); @@ -282,10 +282,9 @@ static int mei_wd_ops_ping(struct watchdog_device *wd_dev) dev->wd_state = MEI_WD_RUNNING; /* Check if we can send the ping to HW*/ - if (dev->mei_host_buffer_is_empty && - mei_cl_flow_ctrl_creds(&dev->wd_cl) > 0) { + if (dev->hbuf_is_ready && mei_cl_flow_ctrl_creds(&dev->wd_cl) > 0) { - dev->mei_host_buffer_is_empty = false; + dev->hbuf_is_ready = false; dev_dbg(&dev->pdev->dev, "wd: sending ping\n"); if (mei_wd_send(dev)) { -- cgit v1.2.1 From 074b4c01abb68c6767612a01f41e9b4ed93d5fb8 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 6 Feb 2013 14:06:44 +0200 Subject: mei: move clients cleanup code from init.c to client.c during reset we clean up client data structures we move that code into wrappers in client and call the wrappers Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/misc/mei/client.h | 5 +++++ drivers/misc/mei/init.c | 28 ++++++-------------------- 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index e46663ee76de..1569afe935de 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -676,3 +676,54 @@ err: return rets; } +/** + * mei_cl_all_disconnect - disconnect forcefully all connected clients + * + * @dev - mei device + */ + +void mei_cl_all_disconnect(struct mei_device *dev) +{ + struct mei_cl *cl, *next; + + list_for_each_entry_safe(cl, next, &dev->file_list, link) { + cl->state = MEI_FILE_DISCONNECTED; + cl->mei_flow_ctrl_creds = 0; + cl->read_cb = NULL; + cl->timer_count = 0; + } +} + + +/** + * mei_cl_all_read_wakeup - wake up all readings so they can be interrupted + * + * @dev - mei device + */ +void mei_cl_all_read_wakeup(struct mei_device *dev) +{ + struct mei_cl *cl, *next; + list_for_each_entry_safe(cl, next, &dev->file_list, link) { + if (waitqueue_active(&cl->rx_wait)) { + dev_dbg(&dev->pdev->dev, "Waking up client!\n"); + wake_up_interruptible(&cl->rx_wait); + } + } +} + +/** + * mei_cl_all_write_clear - clear all pending writes + + * @dev - mei device + */ +void mei_cl_all_write_clear(struct mei_device *dev) +{ + struct mei_cl_cb *cb, *next; + + list_for_each_entry_safe(cb, next, &dev->write_list.list, list) { + list_del(&cb->list); + mei_io_cb_free(cb); + } +} + + diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index 240a1f321342..214b2397ec3e 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -94,4 +94,9 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file); void mei_host_client_init(struct work_struct *work); +void mei_cl_all_disconnect(struct mei_device *dev); +void mei_cl_all_read_wakeup(struct mei_device *dev); +void mei_cl_all_write_clear(struct mei_device *dev); + + #endif /* _MEI_CLIENT_H_ */ diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 51a005e80952..6ec530168afb 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -135,10 +135,6 @@ err: */ void mei_reset(struct mei_device *dev, int interrupts_enabled) { - struct mei_cl *cl_pos = NULL; - struct mei_cl *cl_next = NULL; - struct mei_cl_cb *cb_pos = NULL; - struct mei_cl_cb *cb_next = NULL; bool unexpected; if (dev->dev_state == MEI_DEV_RECOVERING_FROM_RESET) @@ -157,13 +153,8 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) dev->dev_state != MEI_DEV_POWER_DOWN) dev->dev_state = MEI_DEV_RESETING; - list_for_each_entry_safe(cl_pos, - cl_next, &dev->file_list, link) { - cl_pos->state = MEI_FILE_DISCONNECTED; - cl_pos->mei_flow_ctrl_creds = 0; - cl_pos->read_cb = NULL; - cl_pos->timer_count = 0; - } + mei_cl_all_disconnect(dev); + /* remove entry if already in list */ dev_dbg(&dev->pdev->dev, "remove iamthif and wd from the file list.\n"); mei_cl_unlink(&dev->wd_cl); @@ -185,18 +176,11 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n", mei_dev_state_str(dev->dev_state)); - /* Wake up all readings so they can be interrupted */ - list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { - if (waitqueue_active(&cl_pos->rx_wait)) { - dev_dbg(&dev->pdev->dev, "Waking up client!\n"); - wake_up_interruptible(&cl_pos->rx_wait); - } - } + /* wake up all readings so they can be interrupted */ + mei_cl_all_read_wakeup(dev); + /* remove all waiting requests */ - list_for_each_entry_safe(cb_pos, cb_next, &dev->write_list.list, list) { - list_del(&cb_pos->list); - mei_io_cb_free(cb_pos); - } + mei_cl_all_write_clear(dev); } -- cgit v1.2.1 From 2e033db5ddf299de2ae568919d78b0258a5a6423 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 21 Jan 2013 17:36:33 +0900 Subject: extcon: arizona: Support additional configuration of microphone detection Allow systems to tune detection rate and debounce suitably for their mechanical parameters. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 12 ++++++++++++ include/linux/mfd/arizona/pdata.h | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index ab8b9c7359fb..d7e1047ad68e 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -907,6 +907,18 @@ static int arizona_extcon_probe(struct platform_device *pdev) arizona->pdata.micd_bias_start_time << ARIZONA_MICD_BIAS_STARTTIME_SHIFT); + if (arizona->pdata.micd_rate) + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_RATE_MASK, + arizona->pdata.micd_rate + << ARIZONA_MICD_RATE_SHIFT); + + if (arizona->pdata.micd_dbtime) + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_DBTIME_MASK, + arizona->pdata.micd_dbtime + << ARIZONA_MICD_DBTIME_SHIFT); + /* * If we have a clamp use it, activating in conjunction with * GPIO5 if that is connected for jack detect operation. diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index bcbe4fda87cb..2f5f08e10b79 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -111,6 +111,12 @@ struct arizona_pdata { /** Mic detect ramp rate */ int micd_bias_start_time; + /** Mic detect sample rate */ + int micd_rate; + + /** Mic detect debounce level */ + int micd_dbtime; + /** Headset polarity configurations */ struct arizona_micd_config *micd_configs; int num_micd_configs; -- cgit v1.2.1 From bbbd46e3d7fcdf1c8362bf1c83bcc08a93676cc9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 10 Jan 2013 19:38:43 +0000 Subject: extcon: arizona: Use regulated mode for microphone supply when detecting When starting microphone detection some headsets should be exposed to the fully regulated microphone bias in order to ensure that they behave in an optimal fashion. Signed-off-by: Mark Brown --- drivers/extcon/Kconfig | 2 +- drivers/extcon/extcon-arizona.c | 93 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/arizona/pdata.h | 3 ++ 3 files changed, 97 insertions(+), 1 deletion(-) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 07122a9ef36e..9377050e5335 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -47,7 +47,7 @@ config EXTCON_MAX8997 config EXTCON_ARIZONA tristate "Wolfson Arizona EXTCON support" - depends on MFD_ARIZONA && INPUT + depends on MFD_ARIZONA && INPUT && SND_SOC help Say Y here to enable support for external accessory detection with Wolfson Arizona devices. These are audio CODECs with diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index d7e1047ad68e..aa724314677a 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -27,6 +27,8 @@ #include #include +#include + #include #include #include @@ -113,6 +115,52 @@ static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode) dev_dbg(arizona->dev, "Set jack polarity to %d\n", mode); } +static const char *arizona_extcon_get_micbias(struct arizona_extcon_info *info) +{ + switch (info->micd_modes[0].bias >> ARIZONA_MICD_BIAS_SRC_SHIFT) { + case 1: + return "MICBIAS1"; + case 2: + return "MICBIAS2"; + case 3: + return "MICBIAS3"; + default: + return "MICVDD"; + } +} + +static void arizona_extcon_pulse_micbias(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + const char *widget = arizona_extcon_get_micbias(info); + struct snd_soc_dapm_context *dapm = arizona->dapm; + int ret; + + mutex_lock(&dapm->card->dapm_mutex); + + ret = snd_soc_dapm_force_enable_pin(dapm, widget); + if (ret != 0) + dev_warn(arizona->dev, "Failed to enable %s: %d\n", + widget, ret); + + mutex_unlock(&dapm->card->dapm_mutex); + + snd_soc_dapm_sync(dapm); + + if (!arizona->pdata.micd_force_micbias) { + mutex_lock(&dapm->card->dapm_mutex); + + ret = snd_soc_dapm_disable_pin(arizona->dapm, widget); + if (ret != 0) + dev_warn(arizona->dev, "Failed to disable %s: %d\n", + widget, ret); + + mutex_unlock(&dapm->card->dapm_mutex); + + snd_soc_dapm_sync(dapm); + } +} + static void arizona_start_mic(struct arizona_extcon_info *info) { struct arizona *arizona = info->arizona; @@ -122,6 +170,15 @@ static void arizona_start_mic(struct arizona_extcon_info *info) /* Microphone detection can't use idle mode */ pm_runtime_get(info->dev); + if (info->detecting) { + ret = regulator_allow_bypass(info->micvdd, false); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to regulate MICVDD: %d\n", + ret); + } + } + ret = regulator_enable(info->micvdd); if (ret != 0) { dev_err(arizona->dev, "Failed to enable MICVDD: %d\n", @@ -138,6 +195,8 @@ static void arizona_start_mic(struct arizona_extcon_info *info) ARIZONA_ACCESSORY_DETECT_MODE_1, ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); + arizona_extcon_pulse_micbias(info); + regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, ARIZONA_MICD_ENA, ARIZONA_MICD_ENA, &change); @@ -150,18 +209,39 @@ static void arizona_start_mic(struct arizona_extcon_info *info) static void arizona_stop_mic(struct arizona_extcon_info *info) { struct arizona *arizona = info->arizona; + const char *widget = arizona_extcon_get_micbias(info); + struct snd_soc_dapm_context *dapm = arizona->dapm; bool change; + int ret; regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, ARIZONA_MICD_ENA, 0, &change); + mutex_lock(&dapm->card->dapm_mutex); + + ret = snd_soc_dapm_disable_pin(dapm, widget); + if (ret != 0) + dev_warn(arizona->dev, + "Failed to disable %s: %d\n", + widget, ret); + + mutex_unlock(&dapm->card->dapm_mutex); + + snd_soc_dapm_sync(dapm); + if (info->micd_reva) { regmap_write(arizona->regmap, 0x80, 0x3); regmap_write(arizona->regmap, 0x294, 2); regmap_write(arizona->regmap, 0x80, 0x0); } + ret = regulator_allow_bypass(info->micvdd, true); + if (ret != 0) { + dev_err(arizona->dev, "Failed to bypass MICVDD: %d\n", + ret); + } + if (change) { regulator_disable(info->micvdd); pm_runtime_mark_last_busy(info->dev); @@ -564,6 +644,8 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) info->hpdet_active = true; + arizona_extcon_pulse_micbias(info); + ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0x4000); if (ret != 0) dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); @@ -649,6 +731,13 @@ static irqreturn_t arizona_micdet(int irq, void *data) dev_err(arizona->dev, "Headset report failed: %d\n", ret); + /* Don't need to regulate for button detection */ + ret = regulator_allow_bypass(info->micvdd, false); + if (ret != 0) { + dev_err(arizona->dev, "Failed to bypass MICVDD: %d\n", + ret); + } + info->mic = true; info->detecting = false; goto handled; @@ -716,6 +805,7 @@ static irqreturn_t arizona_micdet(int irq, void *data) input_report_key(info->input, arizona_lvl_to_key[i].report, 0); input_sync(info->input); + arizona_extcon_pulse_micbias(info); } handled: @@ -817,6 +907,9 @@ static int arizona_extcon_probe(struct platform_device *pdev) int jack_irq_fall, jack_irq_rise; int ret, mode, i; + if (!arizona->dapm || !arizona->dapm->card) + return -EPROBE_DEFER; + pdata = dev_get_platdata(arizona->dev); info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 2f5f08e10b79..f8241753415c 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -117,6 +117,9 @@ struct arizona_pdata { /** Mic detect debounce level */ int micd_dbtime; + /** Force MICBIAS on for mic detect */ + bool micd_force_micbias; + /** Headset polarity configurations */ struct arizona_micd_config *micd_configs; int num_micd_configs; -- cgit v1.2.1 From b95d788ac72b86515842a4eb92bb58a8da76a975 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Thu, 7 Feb 2013 22:27:18 +0200 Subject: mei: fix undefined wd symbols when MEI_ME is not set Currently watchdog client is compiled with MEI and not with MEI_ME Fixes error: When CONFIG_WATCHDOG is not enabled: ERROR: "watchdog_unregister_device" [drivers/misc/mei/mei.ko] undefined! ERROR: "watchdog_register_device" [drivers/misc/mei/mei.ko] undefined Reported-by: Randy Dunlap Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index fa5c24982c88..d21b4d006a55 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -1,6 +1,6 @@ config INTEL_MEI tristate "Intel Management Engine Interface" - depends on X86 && PCI + depends on X86 && PCI && WATCHDOG_CORE help The Intel Management Engine (Intel ME) provides Manageability, Security and Media services for system containing Intel chipsets. -- cgit v1.2.1 From d34138d057628211b1c835e13a64d0cc2423c969 Mon Sep 17 00:00:00 2001 From: Alexey Khoroshilov Date: Thu, 7 Feb 2013 01:00:34 +0100 Subject: pcmcia: synclink_cs: fix error handling in mgslpc_probe() mgslpc_probe() ignores errors in mgslpc_add_device() and does not release all resource if mgslpc_config() failed. The patch adds returned code to mgslpc_add_device() and fixes the both issues. Found by Linux Driver Verification project (linuxtesting.org). Signed-off-by: Alexey Khoroshilov Signed-off-by: Greg Kroah-Hartman --- drivers/char/pcmcia/synclink_cs.c | 51 +++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/drivers/char/pcmcia/synclink_cs.c b/drivers/char/pcmcia/synclink_cs.c index b66eaa04f8cb..56e4e940fa19 100644 --- a/drivers/char/pcmcia/synclink_cs.c +++ b/drivers/char/pcmcia/synclink_cs.c @@ -397,7 +397,7 @@ static int adapter_test(MGSLPC_INFO *info); static int claim_resources(MGSLPC_INFO *info); static void release_resources(MGSLPC_INFO *info); -static void mgslpc_add_device(MGSLPC_INFO *info); +static int mgslpc_add_device(MGSLPC_INFO *info); static void mgslpc_remove_device(MGSLPC_INFO *info); static bool rx_get_frame(MGSLPC_INFO *info, struct tty_struct *tty); @@ -549,14 +549,21 @@ static int mgslpc_probe(struct pcmcia_device *link) /* Initialize the struct pcmcia_device structure */ ret = mgslpc_config(link); - if (ret) { - tty_port_destroy(&info->port); - return ret; - } + if (ret != 0) + goto failed; - mgslpc_add_device(info); + ret = mgslpc_add_device(info); + if (ret != 0) + goto failed_release; return 0; + +failed_release: + mgslpc_release((u_long)link); +failed: + tty_port_destroy(&info->port); + kfree(info); + return ret; } /* Card has been inserted. @@ -2706,8 +2713,12 @@ static void release_resources(MGSLPC_INFO *info) * * Arguments: info pointer to device instance data */ -static void mgslpc_add_device(MGSLPC_INFO *info) +static int mgslpc_add_device(MGSLPC_INFO *info) { + MGSLPC_INFO *current_dev = NULL; + struct device *tty_dev; + int ret; + info->next_device = NULL; info->line = mgslpc_device_count; sprintf(info->device_name,"ttySLP%d",info->line); @@ -2722,7 +2733,7 @@ static void mgslpc_add_device(MGSLPC_INFO *info) if (!mgslpc_device_list) mgslpc_device_list = info; else { - MGSLPC_INFO *current_dev = mgslpc_device_list; + current_dev = mgslpc_device_list; while( current_dev->next_device ) current_dev = current_dev->next_device; current_dev->next_device = info; @@ -2737,10 +2748,30 @@ static void mgslpc_add_device(MGSLPC_INFO *info) info->device_name, info->io_base, info->irq_level); #if SYNCLINK_GENERIC_HDLC - hdlcdev_init(info); + ret = hdlcdev_init(info); + if (ret != 0) + goto failed; #endif - tty_port_register_device(&info->port, serial_driver, info->line, + + tty_dev = tty_port_register_device(&info->port, serial_driver, info->line, &info->p_dev->dev); + if (IS_ERR(tty_dev)) { + ret = PTR_ERR(tty_dev); +#if SYNCLINK_GENERIC_HDLC + hdlcdev_exit(info); +#endif + goto failed; + } + + return 0; + +failed: + if (current_dev) + current_dev->next_device = NULL; + else + mgslpc_device_list = NULL; + mgslpc_device_count--; + return ret; } static void mgslpc_remove_device(MGSLPC_INFO *remove_info) -- cgit v1.2.1 From 3d55399391c8ecb5bb3d1c426bafa2580a889c4e Mon Sep 17 00:00:00 2001 From: Alexey Khoroshilov Date: Thu, 7 Feb 2013 01:00:35 +0100 Subject: pcmcia: synclink_cs: cleanup checkpatch warnings ERROR: open brace '{' following struct go on the same line ERROR: space required after that ',' ERROR: space prohibited after that open parenthesis '(' WARNING: please, no spaces at the start of a line WARNING: please, no space before tabs Signed-off-by: Alexey Khoroshilov Signed-off-by: Greg Kroah-Hartman --- drivers/char/pcmcia/synclink_cs.c | 616 +++++++++++++++++++------------------- 1 file changed, 310 insertions(+), 306 deletions(-) diff --git a/drivers/char/pcmcia/synclink_cs.c b/drivers/char/pcmcia/synclink_cs.c index 56e4e940fa19..29f6bec9d489 100644 --- a/drivers/char/pcmcia/synclink_cs.c +++ b/drivers/char/pcmcia/synclink_cs.c @@ -102,8 +102,7 @@ static MGSL_PARAMS default_params = { ASYNC_PARITY_NONE /* unsigned char parity; */ }; -typedef struct -{ +typedef struct { int count; unsigned char status; char data[1]; @@ -326,10 +325,10 @@ typedef struct _mgslpc_info { #define write_reg16(info, reg, val) outw((val), (info)->io_base + (reg)) #define set_reg_bits(info, reg, mask) \ - write_reg(info, (reg), \ + write_reg(info, (reg), \ (unsigned char) (read_reg(info, (reg)) | (mask))) #define clear_reg_bits(info, reg, mask) \ - write_reg(info, (reg), \ + write_reg(info, (reg), \ (unsigned char) (read_reg(info, (reg)) & ~(mask))) /* * interrupt enable/disable routines @@ -356,10 +355,10 @@ static void irq_enable(MGSLPC_INFO *info, unsigned char channel, unsigned short } #define port_irq_disable(info, mask) \ - { info->pim_value |= (mask); write_reg(info, PIM, info->pim_value); } + { info->pim_value |= (mask); write_reg(info, PIM, info->pim_value); } #define port_irq_enable(info, mask) \ - { info->pim_value &= ~(mask); write_reg(info, PIM, info->pim_value); } + { info->pim_value &= ~(mask); write_reg(info, PIM, info->pim_value); } static void rx_start(MGSLPC_INFO *info); static void rx_stop(MGSLPC_INFO *info); @@ -514,56 +513,56 @@ static const struct tty_port_operations mgslpc_port_ops = { static int mgslpc_probe(struct pcmcia_device *link) { - MGSLPC_INFO *info; - int ret; - - if (debug_level >= DEBUG_LEVEL_INFO) - printk("mgslpc_attach\n"); - - info = kzalloc(sizeof(MGSLPC_INFO), GFP_KERNEL); - if (!info) { - printk("Error can't allocate device instance data\n"); - return -ENOMEM; - } - - info->magic = MGSLPC_MAGIC; - tty_port_init(&info->port); - info->port.ops = &mgslpc_port_ops; - INIT_WORK(&info->task, bh_handler); - info->max_frame_size = 4096; - info->port.close_delay = 5*HZ/10; - info->port.closing_wait = 30*HZ; - init_waitqueue_head(&info->status_event_wait_q); - init_waitqueue_head(&info->event_wait_q); - spin_lock_init(&info->lock); - spin_lock_init(&info->netlock); - memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS)); - info->idle_mode = HDLC_TXIDLE_FLAGS; - info->imra_value = 0xffff; - info->imrb_value = 0xffff; - info->pim_value = 0xff; - - info->p_dev = link; - link->priv = info; - - /* Initialize the struct pcmcia_device structure */ - - ret = mgslpc_config(link); - if (ret != 0) - goto failed; - - ret = mgslpc_add_device(info); - if (ret != 0) - goto failed_release; - - return 0; + MGSLPC_INFO *info; + int ret; + + if (debug_level >= DEBUG_LEVEL_INFO) + printk("mgslpc_attach\n"); + + info = kzalloc(sizeof(MGSLPC_INFO), GFP_KERNEL); + if (!info) { + printk("Error can't allocate device instance data\n"); + return -ENOMEM; + } + + info->magic = MGSLPC_MAGIC; + tty_port_init(&info->port); + info->port.ops = &mgslpc_port_ops; + INIT_WORK(&info->task, bh_handler); + info->max_frame_size = 4096; + info->port.close_delay = 5*HZ/10; + info->port.closing_wait = 30*HZ; + init_waitqueue_head(&info->status_event_wait_q); + init_waitqueue_head(&info->event_wait_q); + spin_lock_init(&info->lock); + spin_lock_init(&info->netlock); + memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS)); + info->idle_mode = HDLC_TXIDLE_FLAGS; + info->imra_value = 0xffff; + info->imrb_value = 0xffff; + info->pim_value = 0xff; + + info->p_dev = link; + link->priv = info; + + /* Initialize the struct pcmcia_device structure */ + + ret = mgslpc_config(link); + if (ret != 0) + goto failed; + + ret = mgslpc_add_device(info); + if (ret != 0) + goto failed_release; + + return 0; failed_release: - mgslpc_release((u_long)link); + mgslpc_release((u_long)link); failed: - tty_port_destroy(&info->port); - kfree(info); - return ret; + tty_port_destroy(&info->port); + kfree(info); + return ret; } /* Card has been inserted. @@ -576,35 +575,35 @@ static int mgslpc_ioprobe(struct pcmcia_device *p_dev, void *priv_data) static int mgslpc_config(struct pcmcia_device *link) { - MGSLPC_INFO *info = link->priv; - int ret; + MGSLPC_INFO *info = link->priv; + int ret; - if (debug_level >= DEBUG_LEVEL_INFO) - printk("mgslpc_config(0x%p)\n", link); + if (debug_level >= DEBUG_LEVEL_INFO) + printk("mgslpc_config(0x%p)\n", link); - link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; - ret = pcmcia_loop_config(link, mgslpc_ioprobe, NULL); - if (ret != 0) - goto failed; + ret = pcmcia_loop_config(link, mgslpc_ioprobe, NULL); + if (ret != 0) + goto failed; - link->config_index = 8; - link->config_regs = PRESENT_OPTION; + link->config_index = 8; + link->config_regs = PRESENT_OPTION; - ret = pcmcia_request_irq(link, mgslpc_isr); - if (ret) - goto failed; - ret = pcmcia_enable_device(link); - if (ret) - goto failed; + ret = pcmcia_request_irq(link, mgslpc_isr); + if (ret) + goto failed; + ret = pcmcia_enable_device(link); + if (ret) + goto failed; - info->io_base = link->resource[0]->start; - info->irq_level = link->irq; - return 0; + info->io_base = link->resource[0]->start; + info->irq_level = link->irq; + return 0; failed: - mgslpc_release((u_long)link); - return -ENODEV; + mgslpc_release((u_long)link); + return -ENODEV; } /* Card has been removed. @@ -710,12 +709,12 @@ static void tx_pause(struct tty_struct *tty) if (mgslpc_paranoia_check(info, tty->name, "tx_pause")) return; if (debug_level >= DEBUG_LEVEL_INFO) - printk("tx_pause(%s)\n",info->device_name); + printk("tx_pause(%s)\n", info->device_name); - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (info->tx_enabled) - tx_stop(info); - spin_unlock_irqrestore(&info->lock,flags); + tx_stop(info); + spin_unlock_irqrestore(&info->lock, flags); } static void tx_release(struct tty_struct *tty) @@ -726,12 +725,12 @@ static void tx_release(struct tty_struct *tty) if (mgslpc_paranoia_check(info, tty->name, "tx_release")) return; if (debug_level >= DEBUG_LEVEL_INFO) - printk("tx_release(%s)\n",info->device_name); + printk("tx_release(%s)\n", info->device_name); - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (!info->tx_enabled) - tx_start(info, tty); - spin_unlock_irqrestore(&info->lock,flags); + tx_start(info, tty); + spin_unlock_irqrestore(&info->lock, flags); } /* Return next bottom half action to perform. @@ -742,7 +741,7 @@ static int bh_action(MGSLPC_INFO *info) unsigned long flags; int rc = 0; - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (info->pending_bh & BH_RECEIVE) { info->pending_bh &= ~BH_RECEIVE; @@ -761,7 +760,7 @@ static int bh_action(MGSLPC_INFO *info) info->bh_requested = false; } - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return rc; } @@ -776,7 +775,7 @@ static void bh_handler(struct work_struct *work) return; if (debug_level >= DEBUG_LEVEL_BH) - printk( "%s(%d):bh_handler(%s) entry\n", + printk("%s(%d):bh_handler(%s) entry\n", __FILE__,__LINE__,info->device_name); info->bh_running = true; @@ -785,8 +784,8 @@ static void bh_handler(struct work_struct *work) while((action = bh_action(info)) != 0) { /* Process work item */ - if ( debug_level >= DEBUG_LEVEL_BH ) - printk( "%s(%d):bh_handler() work item action=%d\n", + if (debug_level >= DEBUG_LEVEL_BH) + printk("%s(%d):bh_handler() work item action=%d\n", __FILE__,__LINE__,action); switch (action) { @@ -809,7 +808,7 @@ static void bh_handler(struct work_struct *work) tty_kref_put(tty); if (debug_level >= DEBUG_LEVEL_BH) - printk( "%s(%d):bh_handler(%s) exit\n", + printk("%s(%d):bh_handler(%s) exit\n", __FILE__,__LINE__,info->device_name); } @@ -838,7 +837,7 @@ static void rx_ready_hdlc(MGSLPC_INFO *info, int eom) RXBUF *buf = (RXBUF*)(info->rx_buf + (info->rx_put * info->rx_buf_size)); if (debug_level >= DEBUG_LEVEL_ISR) - printk("%s(%d):rx_ready_hdlc(eom=%d)\n",__FILE__,__LINE__,eom); + printk("%s(%d):rx_ready_hdlc(eom=%d)\n", __FILE__, __LINE__, eom); if (!info->rx_enabled) return; @@ -854,7 +853,8 @@ static void rx_ready_hdlc(MGSLPC_INFO *info, int eom) if (eom) { /* end of frame, get FIFO count from RBCL register */ - if (!(fifo_count = (unsigned char)(read_reg(info, CHA+RBCL) & 0x1f))) + fifo_count = (unsigned char)(read_reg(info, CHA+RBCL) & 0x1f); + if (fifo_count == 0) fifo_count = 32; } else fifo_count = 32; @@ -898,13 +898,13 @@ static void rx_ready_async(MGSLPC_INFO *info, int tcd, struct tty_struct *tty) unsigned char data, status, flag; int fifo_count; int work = 0; - struct mgsl_icount *icount = &info->icount; + struct mgsl_icount *icount = &info->icount; if (!tty) { /* tty is not available anymore */ issue_command(info, CHA, CMD_RXRESET); if (debug_level >= DEBUG_LEVEL_ISR) - printk("%s(%d):rx_ready_async(tty=NULL)\n",__FILE__,__LINE__); + printk("%s(%d):rx_ready_async(tty=NULL)\n", __FILE__, __LINE__); return; } @@ -1011,7 +1011,7 @@ static void tx_ready(MGSLPC_INFO *info, struct tty_struct *tty) int c; if (debug_level >= DEBUG_LEVEL_ISR) - printk("%s(%d):tx_ready(%s)\n", __FILE__,__LINE__,info->device_name); + printk("%s(%d):tx_ready(%s)\n", __FILE__, __LINE__, info->device_name); if (info->params.mode == MGSL_MODE_HDLC) { if (!info->tx_active) @@ -1256,7 +1256,7 @@ static irqreturn_t mgslpc_isr(int dummy, void *dev_id) */ if (info->pending_bh && !info->bh_running && !info->bh_requested) { - if ( debug_level >= DEBUG_LEVEL_ISR ) + if (debug_level >= DEBUG_LEVEL_ISR) printk("%s(%d):%s queueing bh task.\n", __FILE__,__LINE__,info->device_name); schedule_work(&info->task); @@ -1280,7 +1280,7 @@ static int startup(MGSLPC_INFO * info, struct tty_struct *tty) int retval = 0; if (debug_level >= DEBUG_LEVEL_INFO) - printk("%s(%d):startup(%s)\n",__FILE__,__LINE__,info->device_name); + printk("%s(%d):startup(%s)\n", __FILE__, __LINE__, info->device_name); if (info->port.flags & ASYNC_INITIALIZED) return 0; @@ -1290,7 +1290,7 @@ static int startup(MGSLPC_INFO * info, struct tty_struct *tty) info->tx_buf = (unsigned char *)get_zeroed_page(GFP_KERNEL); if (!info->tx_buf) { printk(KERN_ERR"%s(%d):%s can't allocate transmit buffer\n", - __FILE__,__LINE__,info->device_name); + __FILE__, __LINE__, info->device_name); return -ENOMEM; } } @@ -1305,15 +1305,15 @@ static int startup(MGSLPC_INFO * info, struct tty_struct *tty) retval = claim_resources(info); /* perform existence check and diagnostics */ - if ( !retval ) + if (!retval) retval = adapter_test(info); - if ( retval ) { - if (capable(CAP_SYS_ADMIN) && tty) + if (retval) { + if (capable(CAP_SYS_ADMIN) && tty) set_bit(TTY_IO_ERROR, &tty->flags); release_resources(info); - return retval; - } + return retval; + } /* program hardware for current parameters */ mgslpc_change_params(info, tty); @@ -1337,7 +1337,7 @@ static void shutdown(MGSLPC_INFO * info, struct tty_struct *tty) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_shutdown(%s)\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); /* clear status wait queue because status changes */ /* can't happen after shutting down the hardware */ @@ -1351,7 +1351,7 @@ static void shutdown(MGSLPC_INFO * info, struct tty_struct *tty) info->tx_buf = NULL; } - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); rx_stop(info); tx_stop(info); @@ -1359,12 +1359,12 @@ static void shutdown(MGSLPC_INFO * info, struct tty_struct *tty) /* TODO:disable interrupts instead of reset to preserve signal states */ reset_device(info); - if (!tty || tty->termios.c_cflag & HUPCL) { - info->serial_signals &= ~(SerialSignal_DTR + SerialSignal_RTS); + if (!tty || tty->termios.c_cflag & HUPCL) { + info->serial_signals &= ~(SerialSignal_DTR + SerialSignal_RTS); set_signals(info); } - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); release_resources(info); @@ -1378,7 +1378,7 @@ static void mgslpc_program_hw(MGSLPC_INFO *info, struct tty_struct *tty) { unsigned long flags; - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); rx_stop(info); tx_stop(info); @@ -1403,7 +1403,7 @@ static void mgslpc_program_hw(MGSLPC_INFO *info, struct tty_struct *tty) if (info->netcount || (tty && (tty->termios.c_cflag & CREAD))) rx_start(info); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); } /* Reconfigure adapter based on new parameters @@ -1418,13 +1418,13 @@ static void mgslpc_change_params(MGSLPC_INFO *info, struct tty_struct *tty) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_change_params(%s)\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); cflag = tty->termios.c_cflag; /* if B0 rate (hangup) specified then negate DTR and RTS */ /* otherwise assert DTR and RTS */ - if (cflag & CBAUD) + if (cflag & CBAUD) info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR; else info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR); @@ -1470,7 +1470,7 @@ static void mgslpc_change_params(MGSLPC_INFO *info, struct tty_struct *tty) info->params.data_rate = tty_get_baud_rate(tty); } - if ( info->params.data_rate ) { + if (info->params.data_rate) { info->timeout = (32*HZ*bits_per_char) / info->params.data_rate; } @@ -1505,8 +1505,8 @@ static int mgslpc_put_char(struct tty_struct *tty, unsigned char ch) unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) { - printk( "%s(%d):mgslpc_put_char(%d) on %s\n", - __FILE__,__LINE__,ch,info->device_name); + printk("%s(%d):mgslpc_put_char(%d) on %s\n", + __FILE__, __LINE__, ch, info->device_name); } if (mgslpc_paranoia_check(info, tty->name, "mgslpc_put_char")) @@ -1515,7 +1515,7 @@ static int mgslpc_put_char(struct tty_struct *tty, unsigned char ch) if (!info->tx_buf) return 0; - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (info->params.mode == MGSL_MODE_ASYNC || !info->tx_active) { if (info->tx_count < TXBUFSIZE - 1) { @@ -1525,7 +1525,7 @@ static int mgslpc_put_char(struct tty_struct *tty, unsigned char ch) } } - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return 1; } @@ -1538,8 +1538,8 @@ static void mgslpc_flush_chars(struct tty_struct *tty) unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) - printk( "%s(%d):mgslpc_flush_chars() entry on %s tx_count=%d\n", - __FILE__,__LINE__,info->device_name,info->tx_count); + printk("%s(%d):mgslpc_flush_chars() entry on %s tx_count=%d\n", + __FILE__, __LINE__, info->device_name, info->tx_count); if (mgslpc_paranoia_check(info, tty->name, "mgslpc_flush_chars")) return; @@ -1549,13 +1549,13 @@ static void mgslpc_flush_chars(struct tty_struct *tty) return; if (debug_level >= DEBUG_LEVEL_INFO) - printk( "%s(%d):mgslpc_flush_chars() entry on %s starting transmitter\n", - __FILE__,__LINE__,info->device_name); + printk("%s(%d):mgslpc_flush_chars() entry on %s starting transmitter\n", + __FILE__, __LINE__, info->device_name); - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (!info->tx_active) - tx_start(info, tty); - spin_unlock_irqrestore(&info->lock,flags); + tx_start(info, tty); + spin_unlock_irqrestore(&info->lock, flags); } /* Send a block of data @@ -1576,8 +1576,8 @@ static int mgslpc_write(struct tty_struct * tty, unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) - printk( "%s(%d):mgslpc_write(%s) count=%d\n", - __FILE__,__LINE__,info->device_name,count); + printk("%s(%d):mgslpc_write(%s) count=%d\n", + __FILE__, __LINE__, info->device_name, count); if (mgslpc_paranoia_check(info, tty->name, "mgslpc_write") || !info->tx_buf) @@ -1603,26 +1603,26 @@ static int mgslpc_write(struct tty_struct * tty, memcpy(info->tx_buf + info->tx_put, buf, c); - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); info->tx_put = (info->tx_put + c) & (TXBUFSIZE-1); info->tx_count += c; - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); buf += c; count -= c; ret += c; } start: - if (info->tx_count && !tty->stopped && !tty->hw_stopped) { - spin_lock_irqsave(&info->lock,flags); + if (info->tx_count && !tty->stopped && !tty->hw_stopped) { + spin_lock_irqsave(&info->lock, flags); if (!info->tx_active) - tx_start(info, tty); - spin_unlock_irqrestore(&info->lock,flags); - } + tx_start(info, tty); + spin_unlock_irqrestore(&info->lock, flags); + } cleanup: if (debug_level >= DEBUG_LEVEL_INFO) - printk( "%s(%d):mgslpc_write(%s) returning=%d\n", - __FILE__,__LINE__,info->device_name,ret); + printk("%s(%d):mgslpc_write(%s) returning=%d\n", + __FILE__, __LINE__, info->device_name, ret); return ret; } @@ -1650,7 +1650,7 @@ static int mgslpc_write_room(struct tty_struct *tty) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_write_room(%s)=%d\n", - __FILE__,__LINE__, info->device_name, ret); + __FILE__, __LINE__, info->device_name, ret); return ret; } @@ -1663,7 +1663,7 @@ static int mgslpc_chars_in_buffer(struct tty_struct *tty) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_chars_in_buffer(%s)\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); if (mgslpc_paranoia_check(info, tty->name, "mgslpc_chars_in_buffer")) return 0; @@ -1675,7 +1675,7 @@ static int mgslpc_chars_in_buffer(struct tty_struct *tty) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_chars_in_buffer(%s)=%d\n", - __FILE__,__LINE__, info->device_name, rc); + __FILE__, __LINE__, info->device_name, rc); return rc; } @@ -1689,15 +1689,15 @@ static void mgslpc_flush_buffer(struct tty_struct *tty) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_flush_buffer(%s) entry\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); if (mgslpc_paranoia_check(info, tty->name, "mgslpc_flush_buffer")) return; - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); info->tx_count = info->tx_put = info->tx_get = 0; del_timer(&info->tx_timer); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); wake_up_interruptible(&tty->write_wait); tty_wakeup(tty); @@ -1712,17 +1712,17 @@ static void mgslpc_send_xchar(struct tty_struct *tty, char ch) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_send_xchar(%s,%d)\n", - __FILE__,__LINE__, info->device_name, ch ); + __FILE__, __LINE__, info->device_name, ch); if (mgslpc_paranoia_check(info, tty->name, "mgslpc_send_xchar")) return; info->x_char = ch; if (ch) { - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (!info->tx_enabled) - tx_start(info, tty); - spin_unlock_irqrestore(&info->lock,flags); + tx_start(info, tty); + spin_unlock_irqrestore(&info->lock, flags); } } @@ -1735,7 +1735,7 @@ static void mgslpc_throttle(struct tty_struct * tty) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_throttle(%s) entry\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); if (mgslpc_paranoia_check(info, tty->name, "mgslpc_throttle")) return; @@ -1743,11 +1743,11 @@ static void mgslpc_throttle(struct tty_struct * tty) if (I_IXOFF(tty)) mgslpc_send_xchar(tty, STOP_CHAR(tty)); - if (tty->termios.c_cflag & CRTSCTS) { - spin_lock_irqsave(&info->lock,flags); + if (tty->termios.c_cflag & CRTSCTS) { + spin_lock_irqsave(&info->lock, flags); info->serial_signals &= ~SerialSignal_RTS; - set_signals(info); - spin_unlock_irqrestore(&info->lock,flags); + set_signals(info); + spin_unlock_irqrestore(&info->lock, flags); } } @@ -1760,7 +1760,7 @@ static void mgslpc_unthrottle(struct tty_struct * tty) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_unthrottle(%s) entry\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); if (mgslpc_paranoia_check(info, tty->name, "mgslpc_unthrottle")) return; @@ -1772,11 +1772,11 @@ static void mgslpc_unthrottle(struct tty_struct * tty) mgslpc_send_xchar(tty, START_CHAR(tty)); } - if (tty->termios.c_cflag & CRTSCTS) { - spin_lock_irqsave(&info->lock,flags); + if (tty->termios.c_cflag & CRTSCTS) { + spin_lock_irqsave(&info->lock, flags); info->serial_signals |= SerialSignal_RTS; - set_signals(info); - spin_unlock_irqrestore(&info->lock,flags); + set_signals(info); + spin_unlock_irqrestore(&info->lock, flags); } } @@ -1814,33 +1814,33 @@ static int get_params(MGSLPC_INFO * info, MGSL_PARAMS __user *user_params) * * Arguments: * - * info pointer to device instance data - * new_params user buffer containing new serial params + * info pointer to device instance data + * new_params user buffer containing new serial params * * Returns: 0 if success, otherwise error code */ static int set_params(MGSLPC_INFO * info, MGSL_PARAMS __user *new_params, struct tty_struct *tty) { - unsigned long flags; + unsigned long flags; MGSL_PARAMS tmp_params; int err; if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):set_params %s\n", __FILE__,__LINE__, - info->device_name ); + info->device_name); COPY_FROM_USER(err,&tmp_params, new_params, sizeof(MGSL_PARAMS)); if (err) { - if ( debug_level >= DEBUG_LEVEL_INFO ) - printk( "%s(%d):set_params(%s) user buffer copy failed\n", - __FILE__,__LINE__,info->device_name); + if (debug_level >= DEBUG_LEVEL_INFO) + printk("%s(%d):set_params(%s) user buffer copy failed\n", + __FILE__, __LINE__, info->device_name); return -EFAULT; } - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); memcpy(&info->params,&tmp_params,sizeof(MGSL_PARAMS)); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); - mgslpc_change_params(info, tty); + mgslpc_change_params(info, tty); return 0; } @@ -1858,13 +1858,13 @@ static int get_txidle(MGSLPC_INFO * info, int __user *idle_mode) static int set_txidle(MGSLPC_INFO * info, int idle_mode) { - unsigned long flags; + unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) printk("set_txidle(%s,%d)\n", info->device_name, idle_mode); - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); info->idle_mode = idle_mode; tx_set_idle(info); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return 0; } @@ -1881,11 +1881,11 @@ static int get_interface(MGSLPC_INFO * info, int __user *if_mode) static int set_interface(MGSLPC_INFO * info, int if_mode) { - unsigned long flags; + unsigned long flags; unsigned char val; if (debug_level >= DEBUG_LEVEL_INFO) printk("set_interface(%s,%d)\n", info->device_name, if_mode); - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); info->if_mode = if_mode; val = read_reg(info, PVR) & 0x0f; @@ -1897,18 +1897,18 @@ static int set_interface(MGSLPC_INFO * info, int if_mode) } write_reg(info, PVR, val); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return 0; } static int set_txenable(MGSLPC_INFO * info, int enable, struct tty_struct *tty) { - unsigned long flags; + unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) printk("set_txenable(%s,%d)\n", info->device_name, enable); - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (enable) { if (!info->tx_enabled) tx_start(info, tty); @@ -1916,18 +1916,18 @@ static int set_txenable(MGSLPC_INFO * info, int enable, struct tty_struct *tty) if (info->tx_enabled) tx_stop(info); } - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return 0; } static int tx_abort(MGSLPC_INFO * info) { - unsigned long flags; + unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) printk("tx_abort(%s)\n", info->device_name); - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (info->tx_active && info->tx_count && info->params.mode == MGSL_MODE_HDLC) { /* clear data count so FIFO is not filled on next IRQ. @@ -1936,18 +1936,18 @@ static int tx_abort(MGSLPC_INFO * info) info->tx_count = info->tx_put = info->tx_get = 0; info->tx_aborting = true; } - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return 0; } static int set_rxenable(MGSLPC_INFO * info, int enable) { - unsigned long flags; + unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) printk("set_rxenable(%s,%d)\n", info->device_name, enable); - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (enable) { if (!info->rx_enabled) rx_start(info); @@ -1955,21 +1955,21 @@ static int set_rxenable(MGSLPC_INFO * info, int enable) if (info->rx_enabled) rx_stop(info); } - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return 0; } /* wait for specified event to occur * - * Arguments: info pointer to device instance data - * mask pointer to bitmask of events to wait for - * Return Value: 0 if successful and bit mask updated with + * Arguments: info pointer to device instance data + * mask pointer to bitmask of events to wait for + * Return Value: 0 if successful and bit mask updated with * of events triggerred, - * otherwise error code + * otherwise error code */ static int wait_events(MGSLPC_INFO * info, int __user *mask_ptr) { - unsigned long flags; + unsigned long flags; int s; int rc=0; struct mgsl_icount cprev, cnow; @@ -1985,18 +1985,18 @@ static int wait_events(MGSLPC_INFO * info, int __user *mask_ptr) if (debug_level >= DEBUG_LEVEL_INFO) printk("wait_events(%s,%d)\n", info->device_name, mask); - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); /* return immediately if state matches requested events */ get_signals(info); s = info->serial_signals; events = mask & ( ((s & SerialSignal_DSR) ? MgslEvent_DsrActive:MgslEvent_DsrInactive) + - ((s & SerialSignal_DCD) ? MgslEvent_DcdActive:MgslEvent_DcdInactive) + + ((s & SerialSignal_DCD) ? MgslEvent_DcdActive:MgslEvent_DcdInactive) + ((s & SerialSignal_CTS) ? MgslEvent_CtsActive:MgslEvent_CtsInactive) + ((s & SerialSignal_RI) ? MgslEvent_RiActive :MgslEvent_RiInactive) ); if (events) { - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); goto exit; } @@ -2011,7 +2011,7 @@ static int wait_events(MGSLPC_INFO * info, int __user *mask_ptr) set_current_state(TASK_INTERRUPTIBLE); add_wait_queue(&info->event_wait_q, &wait); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); for(;;) { @@ -2022,11 +2022,11 @@ static int wait_events(MGSLPC_INFO * info, int __user *mask_ptr) } /* get current irq counts */ - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); cnow = info->icount; newsigs = info->input_signal_events; set_current_state(TASK_INTERRUPTIBLE); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); /* if no change, wait aborted for some reason */ if (newsigs.dsr_up == oldsigs.dsr_up && @@ -2065,10 +2065,10 @@ static int wait_events(MGSLPC_INFO * info, int __user *mask_ptr) set_current_state(TASK_RUNNING); if (mask & MgslEvent_ExitHuntMode) { - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (!waitqueue_active(&info->event_wait_q)) irq_disable(info, CHA, IRQ_EXITHUNT); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); } exit: if (rc == 0) @@ -2078,17 +2078,17 @@ exit: static int modem_input_wait(MGSLPC_INFO *info,int arg) { - unsigned long flags; + unsigned long flags; int rc; struct mgsl_icount cprev, cnow; DECLARE_WAITQUEUE(wait, current); /* save current irq counts */ - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); cprev = info->icount; add_wait_queue(&info->status_event_wait_q, &wait); set_current_state(TASK_INTERRUPTIBLE); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); for(;;) { schedule(); @@ -2098,10 +2098,10 @@ static int modem_input_wait(MGSLPC_INFO *info,int arg) } /* get new irq counts */ - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); cnow = info->icount; set_current_state(TASK_INTERRUPTIBLE); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); /* if no change, wait aborted for some reason */ if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && @@ -2132,11 +2132,11 @@ static int tiocmget(struct tty_struct *tty) { MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data; unsigned int result; - unsigned long flags; + unsigned long flags; - spin_lock_irqsave(&info->lock,flags); - get_signals(info); - spin_unlock_irqrestore(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); + get_signals(info); + spin_unlock_irqrestore(&info->lock, flags); result = ((info->serial_signals & SerialSignal_RTS) ? TIOCM_RTS:0) + ((info->serial_signals & SerialSignal_DTR) ? TIOCM_DTR:0) + @@ -2147,7 +2147,7 @@ static int tiocmget(struct tty_struct *tty) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):%s tiocmget() value=%08X\n", - __FILE__,__LINE__, info->device_name, result ); + __FILE__, __LINE__, info->device_name, result); return result; } @@ -2157,11 +2157,11 @@ static int tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) { MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data; - unsigned long flags; + unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):%s tiocmset(%x,%x)\n", - __FILE__,__LINE__,info->device_name, set, clear); + __FILE__, __LINE__, info->device_name, set, clear); if (set & TIOCM_RTS) info->serial_signals |= SerialSignal_RTS; @@ -2172,9 +2172,9 @@ static int tiocmset(struct tty_struct *tty, if (clear & TIOCM_DTR) info->serial_signals &= ~SerialSignal_DTR; - spin_lock_irqsave(&info->lock,flags); - set_signals(info); - spin_unlock_irqrestore(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); + set_signals(info); + spin_unlock_irqrestore(&info->lock, flags); return 0; } @@ -2191,17 +2191,17 @@ static int mgslpc_break(struct tty_struct *tty, int break_state) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_break(%s,%d)\n", - __FILE__,__LINE__, info->device_name, break_state); + __FILE__, __LINE__, info->device_name, break_state); if (mgslpc_paranoia_check(info, tty->name, "mgslpc_break")) return -EINVAL; - spin_lock_irqsave(&info->lock,flags); - if (break_state == -1) + spin_lock_irqsave(&info->lock, flags); + if (break_state == -1) set_reg_bits(info, CHA+DAFO, BIT6); else clear_reg_bits(info, CHA+DAFO, BIT6); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return 0; } @@ -2212,9 +2212,9 @@ static int mgslpc_get_icount(struct tty_struct *tty, struct mgsl_icount cnow; /* kernel counter temps */ unsigned long flags; - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); cnow = info->icount; - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); icount->cts = cnow.cts; icount->dsr = cnow.dsr; @@ -2235,9 +2235,9 @@ static int mgslpc_get_icount(struct tty_struct *tty, * * Arguments: * - * tty pointer to tty instance data - * cmd IOCTL command code - * arg command argument/context + * tty pointer to tty instance data + * cmd IOCTL command code + * arg command argument/context * * Return Value: 0 if success, otherwise error code */ @@ -2248,8 +2248,8 @@ static int mgslpc_ioctl(struct tty_struct *tty, void __user *argp = (void __user *)arg; if (debug_level >= DEBUG_LEVEL_INFO) - printk("%s(%d):mgslpc_ioctl %s cmd=%08X\n", __FILE__,__LINE__, - info->device_name, cmd ); + printk("%s(%d):mgslpc_ioctl %s cmd=%08X\n", __FILE__, __LINE__, + info->device_name, cmd); if (mgslpc_paranoia_check(info, tty->name, "mgslpc_ioctl")) return -ENODEV; @@ -2295,8 +2295,8 @@ static int mgslpc_ioctl(struct tty_struct *tty, * * Arguments: * - * tty pointer to tty structure - * termios pointer to buffer to hold returned old termios + * tty pointer to tty structure + * termios pointer to buffer to hold returned old termios */ static void mgslpc_set_termios(struct tty_struct *tty, struct ktermios *old_termios) { @@ -2304,8 +2304,8 @@ static void mgslpc_set_termios(struct tty_struct *tty, struct ktermios *old_term unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) - printk("%s(%d):mgslpc_set_termios %s\n", __FILE__,__LINE__, - tty->driver->name ); + printk("%s(%d):mgslpc_set_termios %s\n", __FILE__, __LINE__, + tty->driver->name); /* just return if nothing has changed */ if ((tty->termios.c_cflag == old_termios->c_cflag) @@ -2319,22 +2319,22 @@ static void mgslpc_set_termios(struct tty_struct *tty, struct ktermios *old_term if (old_termios->c_cflag & CBAUD && !(tty->termios.c_cflag & CBAUD)) { info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR); - spin_lock_irqsave(&info->lock,flags); - set_signals(info); - spin_unlock_irqrestore(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); + set_signals(info); + spin_unlock_irqrestore(&info->lock, flags); } /* Handle transition away from B0 status */ if (!(old_termios->c_cflag & CBAUD) && tty->termios.c_cflag & CBAUD) { info->serial_signals |= SerialSignal_DTR; - if (!(tty->termios.c_cflag & CRTSCTS) || - !test_bit(TTY_THROTTLED, &tty->flags)) { + if (!(tty->termios.c_cflag & CRTSCTS) || + !test_bit(TTY_THROTTLED, &tty->flags)) { info->serial_signals |= SerialSignal_RTS; - } - spin_lock_irqsave(&info->lock,flags); - set_signals(info); - spin_unlock_irqrestore(&info->lock,flags); + } + spin_lock_irqsave(&info->lock, flags); + set_signals(info); + spin_unlock_irqrestore(&info->lock, flags); } /* Handle turning off CRTSCTS */ @@ -2355,15 +2355,15 @@ static void mgslpc_close(struct tty_struct *tty, struct file * filp) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_close(%s) entry, count=%d\n", - __FILE__,__LINE__, info->device_name, port->count); + __FILE__, __LINE__, info->device_name, port->count); WARN_ON(!port->count); if (tty_port_close_start(port, tty, filp) == 0) goto cleanup; - if (port->flags & ASYNC_INITIALIZED) - mgslpc_wait_until_sent(tty, info->timeout); + if (port->flags & ASYNC_INITIALIZED) + mgslpc_wait_until_sent(tty, info->timeout); mgslpc_flush_buffer(tty); @@ -2374,7 +2374,7 @@ static void mgslpc_close(struct tty_struct *tty, struct file * filp) tty_port_tty_set(port, NULL); cleanup: if (debug_level >= DEBUG_LEVEL_INFO) - printk("%s(%d):mgslpc_close(%s) exit, count=%d\n", __FILE__,__LINE__, + printk("%s(%d):mgslpc_close(%s) exit, count=%d\n", __FILE__, __LINE__, tty->driver->name, port->count); } @@ -2385,12 +2385,12 @@ static void mgslpc_wait_until_sent(struct tty_struct *tty, int timeout) MGSLPC_INFO * info = (MGSLPC_INFO *)tty->driver_data; unsigned long orig_jiffies, char_time; - if (!info ) + if (!info) return; if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_wait_until_sent(%s) entry\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); if (mgslpc_paranoia_check(info, tty->name, "mgslpc_wait_until_sent")) return; @@ -2406,8 +2406,8 @@ static void mgslpc_wait_until_sent(struct tty_struct *tty, int timeout) * Note: use tight timings here to satisfy the NIST-PCTS. */ - if ( info->params.data_rate ) { - char_time = info->timeout/(32 * 5); + if (info->params.data_rate) { + char_time = info->timeout/(32 * 5); if (!char_time) char_time++; } else @@ -2438,7 +2438,7 @@ static void mgslpc_wait_until_sent(struct tty_struct *tty, int timeout) exit: if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_wait_until_sent(%s) exit\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); } /* Called by tty_hangup() when a hangup is signaled. @@ -2450,7 +2450,7 @@ static void mgslpc_hangup(struct tty_struct *tty) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_hangup(%s)\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); if (mgslpc_paranoia_check(info, tty->name, "mgslpc_hangup")) return; @@ -2465,9 +2465,9 @@ static int carrier_raised(struct tty_port *port) MGSLPC_INFO *info = container_of(port, MGSLPC_INFO, port); unsigned long flags; - spin_lock_irqsave(&info->lock,flags); - get_signals(info); - spin_unlock_irqrestore(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); + get_signals(info); + spin_unlock_irqrestore(&info->lock, flags); if (info->serial_signals & SerialSignal_DCD) return 1; @@ -2479,13 +2479,13 @@ static void dtr_rts(struct tty_port *port, int onoff) MGSLPC_INFO *info = container_of(port, MGSLPC_INFO, port); unsigned long flags; - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (onoff) info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR; else info->serial_signals &= ~SerialSignal_RTS + SerialSignal_DTR; set_signals(info); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); } @@ -2493,14 +2493,14 @@ static int mgslpc_open(struct tty_struct *tty, struct file * filp) { MGSLPC_INFO *info; struct tty_port *port; - int retval, line; - unsigned long flags; + int retval, line; + unsigned long flags; /* verify range of specified line number */ line = tty->index; if (line >= mgslpc_device_count) { printk("%s(%d):mgslpc_open with invalid line #%d.\n", - __FILE__,__LINE__,line); + __FILE__, __LINE__, line); return -ENODEV; } @@ -2517,7 +2517,7 @@ static int mgslpc_open(struct tty_struct *tty, struct file * filp) if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_open(%s), old ref count = %d\n", - __FILE__,__LINE__,tty->driver->name, port->count); + __FILE__, __LINE__, tty->driver->name, port->count); /* If port is closing, signal caller to try again */ if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING){ @@ -2552,13 +2552,13 @@ static int mgslpc_open(struct tty_struct *tty, struct file * filp) if (retval) { if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):block_til_ready(%s) returned %d\n", - __FILE__,__LINE__, info->device_name, retval); + __FILE__, __LINE__, info->device_name, retval); goto cleanup; } if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):mgslpc_open(%s) success\n", - __FILE__,__LINE__, info->device_name); + __FILE__, __LINE__, info->device_name); retval = 0; cleanup: @@ -2578,9 +2578,9 @@ static inline void line_info(struct seq_file *m, MGSLPC_INFO *info) info->device_name, info->io_base, info->irq_level); /* output current serial signal states */ - spin_lock_irqsave(&info->lock,flags); - get_signals(info); - spin_unlock_irqrestore(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); + get_signals(info); + spin_unlock_irqrestore(&info->lock, flags); stat_buf[0] = 0; stat_buf[1] = 0; @@ -2642,7 +2642,7 @@ static int mgslpc_proc_show(struct seq_file *m, void *v) seq_printf(m, "synclink driver:%s\n", driver_version); info = mgslpc_device_list; - while( info ) { + while (info) { line_info(m, info); info = info->next_device; } @@ -2693,8 +2693,8 @@ static void rx_free_buffers(MGSLPC_INFO *info) static int claim_resources(MGSLPC_INFO *info) { - if (rx_alloc_buffers(info) < 0 ) { - printk( "Can't allocate rx buffer %s\n", info->device_name); + if (rx_alloc_buffers(info) < 0) { + printk("Can't allocate rx buffer %s\n", info->device_name); release_resources(info); return -ENODEV; } @@ -2734,7 +2734,7 @@ static int mgslpc_add_device(MGSLPC_INFO *info) mgslpc_device_list = info; else { current_dev = mgslpc_device_list; - while( current_dev->next_device ) + while (current_dev->next_device) current_dev = current_dev->next_device; current_dev->next_device = info; } @@ -2744,7 +2744,7 @@ static int mgslpc_add_device(MGSLPC_INFO *info) else if (info->max_frame_size > 65535) info->max_frame_size = 65535; - printk( "SyncLink PC Card %s:IO=%04X IRQ=%d\n", + printk("SyncLink PC Card %s:IO=%04X IRQ=%d\n", info->device_name, info->io_base, info->irq_level); #if SYNCLINK_GENERIC_HDLC @@ -3293,7 +3293,7 @@ static void rx_stop(MGSLPC_INFO *info) { if (debug_level >= DEBUG_LEVEL_ISR) printk("%s(%d):rx_stop(%s)\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); /* MODE:03 RAC Receiver Active, 0=inactive */ clear_reg_bits(info, CHA + MODE, BIT3); @@ -3306,7 +3306,7 @@ static void rx_start(MGSLPC_INFO *info) { if (debug_level >= DEBUG_LEVEL_ISR) printk("%s(%d):rx_start(%s)\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); rx_reset_buffers(info); info->rx_enabled = false; @@ -3322,7 +3322,7 @@ static void tx_start(MGSLPC_INFO *info, struct tty_struct *tty) { if (debug_level >= DEBUG_LEVEL_ISR) printk("%s(%d):tx_start(%s)\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); if (info->tx_count) { /* If auto RTS enabled and RTS is inactive, then assert */ @@ -3360,7 +3360,7 @@ static void tx_stop(MGSLPC_INFO *info) { if (debug_level >= DEBUG_LEVEL_ISR) printk("%s(%d):tx_stop(%s)\n", - __FILE__,__LINE__, info->device_name ); + __FILE__, __LINE__, info->device_name); del_timer(&info->tx_timer); @@ -3712,7 +3712,7 @@ static bool rx_get_frame(MGSLPC_INFO *info, struct tty_struct *tty) if (debug_level >= DEBUG_LEVEL_BH) printk("%s(%d):rx_get_frame(%s) status=%04X size=%d\n", - __FILE__,__LINE__,info->device_name,status,framesize); + __FILE__, __LINE__, info->device_name, status, framesize); if (debug_level >= DEBUG_LEVEL_DATA) trace_block(info, buf->data, framesize, 0); @@ -3740,13 +3740,13 @@ static bool rx_get_frame(MGSLPC_INFO *info, struct tty_struct *tty) } } - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); buf->status = buf->count = 0; info->rx_frame_count--; info->rx_get++; if (info->rx_get >= info->rx_buf_count) info->rx_get = 0; - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return true; } @@ -3760,7 +3760,7 @@ static bool register_test(MGSLPC_INFO *info) bool rc = true; unsigned long flags; - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); reset_device(info); for (i = 0; i < count; i++) { @@ -3773,7 +3773,7 @@ static bool register_test(MGSLPC_INFO *info) } } - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return rc; } @@ -3782,7 +3782,7 @@ static bool irq_test(MGSLPC_INFO *info) unsigned long end_time; unsigned long flags; - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); reset_device(info); info->testing_irq = true; @@ -3796,7 +3796,7 @@ static bool irq_test(MGSLPC_INFO *info) write_reg(info, CHA + TIMR, 0); /* 512 cycles */ issue_command(info, CHA, CMD_START_TIMER); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); end_time=100; while(end_time-- && !info->irq_occurred) { @@ -3805,9 +3805,9 @@ static bool irq_test(MGSLPC_INFO *info) info->testing_irq = false; - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); reset_device(info); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return info->irq_occurred; } @@ -3816,21 +3816,21 @@ static int adapter_test(MGSLPC_INFO *info) { if (!register_test(info)) { info->init_error = DiagStatus_AddressFailure; - printk( "%s(%d):Register test failure for device %s Addr=%04X\n", - __FILE__,__LINE__,info->device_name, (unsigned short)(info->io_base) ); + printk("%s(%d):Register test failure for device %s Addr=%04X\n", + __FILE__, __LINE__, info->device_name, (unsigned short)(info->io_base)); return -ENODEV; } if (!irq_test(info)) { info->init_error = DiagStatus_IrqFailure; - printk( "%s(%d):Interrupt test failure for device %s IRQ=%d\n", - __FILE__,__LINE__,info->device_name, (unsigned short)(info->irq_level) ); + printk("%s(%d):Interrupt test failure for device %s IRQ=%d\n", + __FILE__, __LINE__, info->device_name, (unsigned short)(info->irq_level)); return -ENODEV; } if (debug_level >= DEBUG_LEVEL_INFO) printk("%s(%d):device %s passed diagnostics\n", - __FILE__,__LINE__,info->device_name); + __FILE__, __LINE__, info->device_name); return 0; } @@ -3839,9 +3839,9 @@ static void trace_block(MGSLPC_INFO *info,const char* data, int count, int xmit) int i; int linecount; if (xmit) - printk("%s tx data:\n",info->device_name); + printk("%s tx data:\n", info->device_name); else - printk("%s rx data:\n",info->device_name); + printk("%s rx data:\n", info->device_name); while(count) { if (count > 16) @@ -3850,12 +3850,12 @@ static void trace_block(MGSLPC_INFO *info,const char* data, int count, int xmit) linecount = count; for(i=0;i=040 && data[i]<=0176) - printk("%c",data[i]); + printk("%c", data[i]); else printk("."); } @@ -3874,18 +3874,18 @@ static void tx_timeout(unsigned long context) MGSLPC_INFO *info = (MGSLPC_INFO*)context; unsigned long flags; - if ( debug_level >= DEBUG_LEVEL_INFO ) - printk( "%s(%d):tx_timeout(%s)\n", - __FILE__,__LINE__,info->device_name); - if(info->tx_active && - info->params.mode == MGSL_MODE_HDLC) { + if (debug_level >= DEBUG_LEVEL_INFO) + printk("%s(%d):tx_timeout(%s)\n", + __FILE__, __LINE__, info->device_name); + if (info->tx_active && + info->params.mode == MGSL_MODE_HDLC) { info->icount.txtimeout++; } - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); info->tx_active = false; info->tx_count = info->tx_put = info->tx_get = 0; - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); #if SYNCLINK_GENERIC_HDLC if (info->netcount) @@ -3967,7 +3967,7 @@ static netdev_tx_t hdlcdev_xmit(struct sk_buff *skb, unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) - printk(KERN_INFO "%s:hdlc_xmit(%s)\n",__FILE__,dev->name); + printk(KERN_INFO "%s:hdlc_xmit(%s)\n", __FILE__, dev->name); /* stop sending until this frame completes */ netif_stop_queue(dev); @@ -3988,13 +3988,13 @@ static netdev_tx_t hdlcdev_xmit(struct sk_buff *skb, dev->trans_start = jiffies; /* start hardware transmitter if necessary */ - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); if (!info->tx_active) { struct tty_struct *tty = tty_port_tty_get(&info->port); - tx_start(info, tty); - tty_kref_put(tty); + tx_start(info, tty); + tty_kref_put(tty); } - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); return NETDEV_TX_OK; } @@ -4015,10 +4015,11 @@ static int hdlcdev_open(struct net_device *dev) unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) - printk("%s:hdlcdev_open(%s)\n",__FILE__,dev->name); + printk("%s:hdlcdev_open(%s)\n", __FILE__, dev->name); /* generic HDLC layer open processing */ - if ((rc = hdlc_open(dev))) + rc = hdlc_open(dev); + if (rc != 0) return rc; /* arbitrate between network and tty opens */ @@ -4033,7 +4034,8 @@ static int hdlcdev_open(struct net_device *dev) tty = tty_port_tty_get(&info->port); /* claim resources and init adapter */ - if ((rc = startup(info, tty)) != 0) { + rc = startup(info, tty); + if (rc != 0) { tty_kref_put(tty); spin_lock_irqsave(&info->netlock, flags); info->netcount=0; @@ -4075,7 +4077,7 @@ static int hdlcdev_close(struct net_device *dev) unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) - printk("%s:hdlcdev_close(%s)\n",__FILE__,dev->name); + printk("%s:hdlcdev_close(%s)\n", __FILE__, dev->name); netif_stop_queue(dev); @@ -4109,7 +4111,7 @@ static int hdlcdev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) unsigned int flags; if (debug_level >= DEBUG_LEVEL_INFO) - printk("%s:hdlcdev_ioctl(%s)\n",__FILE__,dev->name); + printk("%s:hdlcdev_ioctl(%s)\n", __FILE__, dev->name); /* return error if TTY interface open */ if (info->port.count) @@ -4210,14 +4212,14 @@ static void hdlcdev_tx_timeout(struct net_device *dev) unsigned long flags; if (debug_level >= DEBUG_LEVEL_INFO) - printk("hdlcdev_tx_timeout(%s)\n",dev->name); + printk("hdlcdev_tx_timeout(%s)\n", dev->name); dev->stats.tx_errors++; dev->stats.tx_aborted_errors++; - spin_lock_irqsave(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); tx_stop(info); - spin_unlock_irqrestore(&info->lock,flags); + spin_unlock_irqrestore(&info->lock, flags); netif_wake_queue(dev); } @@ -4248,7 +4250,7 @@ static void hdlcdev_rx(MGSLPC_INFO *info, char *buf, int size) struct net_device *dev = info->netdev; if (debug_level >= DEBUG_LEVEL_INFO) - printk("hdlcdev_rx(%s)\n",dev->name); + printk("hdlcdev_rx(%s)\n", dev->name); if (skb == NULL) { printk(KERN_NOTICE "%s: can't alloc skb, dropping packet\n", dev->name); @@ -4291,8 +4293,9 @@ static int hdlcdev_init(MGSLPC_INFO *info) /* allocate and initialize network and HDLC layer objects */ - if (!(dev = alloc_hdlcdev(info))) { - printk(KERN_ERR "%s:hdlc device allocation failure\n",__FILE__); + dev = alloc_hdlcdev(info); + if (dev == NULL) { + printk(KERN_ERR "%s:hdlc device allocation failure\n", __FILE__); return -ENOMEM; } @@ -4311,8 +4314,9 @@ static int hdlcdev_init(MGSLPC_INFO *info) hdlc->xmit = hdlcdev_xmit; /* register objects with HDLC layer */ - if ((rc = register_hdlc_device(dev))) { - printk(KERN_WARNING "%s:unable to register hdlc device\n",__FILE__); + rc = register_hdlc_device(dev); + if (rc) { + printk(KERN_WARNING "%s:unable to register hdlc device\n", __FILE__); free_netdev(dev); return rc; } -- cgit v1.2.1 From e1e0a9e6991ec2c611f13dfc0a6bc1a214a22374 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Wed, 6 Feb 2013 17:23:56 +0100 Subject: drivers/misc/cb710: add missing GENERIC_HARDIRQS dependency CB710_CORE (drivers/misc/cb710/core.c) calls devm_request_irq() and therefore needs a GENERIC_HARDIRQS dependency to prevent a link error on s390. Cc: Arnd Bergmann Signed-off-by: Heiko Carstens Signed-off-by: Greg Kroah-Hartman --- drivers/misc/cb710/Kconfig | 2 +- drivers/mmc/host/Kconfig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/cb710/Kconfig b/drivers/misc/cb710/Kconfig index 22429b8b1068..5acb9c5b49c4 100644 --- a/drivers/misc/cb710/Kconfig +++ b/drivers/misc/cb710/Kconfig @@ -1,6 +1,6 @@ config CB710_CORE tristate "ENE CB710/720 Flash memory card reader support" - depends on PCI + depends on PCI && GENERIC_HARDIRQS help This option enables support for PCI ENE CB710/720 Flash memory card reader found in some laptops (ie. some versions of HP Compaq nx9500). diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 8d13c6594520..30041272ab96 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -461,7 +461,7 @@ config MMC_SDHI config MMC_CB710 tristate "ENE CB710 MMC/SD Interface support" - depends on PCI + depends on PCI && GENERIC_HARDIRQS select CB710_CORE help This option enables support for MMC/SD part of ENE CB710/720 Flash -- cgit v1.2.1 From e500d158fb07794724f12655f2eb5702fec613c4 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Fri, 8 Feb 2013 15:57:15 -0800 Subject: Drivers: hv: balloon: Add a parameter to delay pressure reporting Delay reporting memory pressure by a specified amount of time. This addresses the problem where the host may take memory balancing decisions based on incorrect memory pressure data that will be posted as soon as the balloon driver is loaded. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv_balloon.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 32a96f164dc8..f8fc99600de8 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -414,10 +414,17 @@ struct dm_info_msg { static bool hot_add; static bool do_hot_add; +/* + * Delay reporting memory pressure by + * the specified number of seconds. + */ +static uint pressure_report_delay = 30; module_param(hot_add, bool, (S_IRUGO | S_IWUSR)); MODULE_PARM_DESC(hot_add, "If set attempt memory hot_add"); +module_param(pressure_report_delay, uint, (S_IRUGO | S_IWUSR)); +MODULE_PARM_DESC(pressure_report_delay, "Delay in secs in reporting pressure"); static atomic_t trans_id = ATOMIC_INIT(0); static int dm_ring_size = (5 * PAGE_SIZE); @@ -531,6 +538,10 @@ static void post_status(struct hv_dynmem_device *dm) struct dm_status status; struct sysinfo val; + if (pressure_report_delay > 0) { + --pressure_report_delay; + return; + } si_meminfo(&val); memset(&status, 0, sizeof(struct dm_status)); status.hdr.type = DM_STATUS_REPORT; @@ -552,8 +563,6 @@ static void post_status(struct hv_dynmem_device *dm) } - - static void free_balloon_pages(struct hv_dynmem_device *dm, union dm_mem_page_range *range_array) { -- cgit v1.2.1 From 1c7db96f6feac95d90200ddd0f9b5d94614ea759 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Fri, 8 Feb 2013 15:57:16 -0800 Subject: Drivers: hv: balloon: Prevent the host from ballooning the guest too low Based on the amount of memory being managed set a floor on how low the guest can be ballooned. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv_balloon.c | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index f8fc99600de8..37873213e24f 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -523,6 +523,34 @@ static void process_info(struct hv_dynmem_device *dm, struct dm_info_msg *msg) } } +unsigned long compute_balloon_floor(void) +{ + unsigned long min_pages; +#define MB2PAGES(mb) ((mb) << (20 - PAGE_SHIFT)) + /* Simple continuous piecewiese linear function: + * max MiB -> min MiB gradient + * 0 0 + * 16 16 + * 32 24 + * 128 72 (1/2) + * 512 168 (1/4) + * 2048 360 (1/8) + * 8192 552 (1/32) + * 32768 1320 + * 131072 4392 + */ + if (totalram_pages < MB2PAGES(128)) + min_pages = MB2PAGES(8) + (totalram_pages >> 1); + else if (totalram_pages < MB2PAGES(512)) + min_pages = MB2PAGES(40) + (totalram_pages >> 2); + else if (totalram_pages < MB2PAGES(2048)) + min_pages = MB2PAGES(104) + (totalram_pages >> 3); + else + min_pages = MB2PAGES(296) + (totalram_pages >> 5); +#undef MB2PAGES + return min_pages; +} + /* * Post our status as it relates memory pressure to the * host. Host expects the guests to post this status @@ -552,9 +580,14 @@ static void post_status(struct hv_dynmem_device *dm) * The host expects the guest to report free memory. * Further, the host expects the pressure information to * include the ballooned out pages. + * For a given amount of memory that we are managing, we + * need to compute a floor below which we should not balloon. + * Compute this and add it to the pressure report. */ status.num_avail = val.freeram; - status.num_committed = vm_memory_committed() + dm->num_pages_ballooned; + status.num_committed = vm_memory_committed() + + dm->num_pages_ballooned + + compute_balloon_floor(); vmbus_sendpacket(dm->dev->channel, &status, sizeof(struct dm_status), -- cgit v1.2.1 From e339af1c4567b1e63209329e2665781e598709f2 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 4 Feb 2013 19:29:41 +0000 Subject: extcon: arizona: Remove extra jack flip increment Signed-off-by: Charles Keepax Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index aa724314677a..c17a41ff60ea 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -750,8 +750,6 @@ static irqreturn_t arizona_micdet(int irq, void *data) * impedence then give up and report headphones. */ if (info->detecting && (val & 0x3f8)) { - info->jack_flips++; - if (info->jack_flips >= info->micd_num_modes) { dev_dbg(arizona->dev, "Detected HP/line\n"); arizona_identify_headphone(info); -- cgit v1.2.1 From 0e27bd3137778ac9e856fec99b1752bf054a987c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 5 Feb 2013 21:00:15 +0000 Subject: extcon: arizona: Add some debounce time before starting HPDET identification The HPDET identification method does not have the same natural debounce built into it that the standard MICDET method does so add some extra on top of what the jack detection does in hardware to make sure we get a robust result. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index c17a41ff60ea..2ad0e4a35a23 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -53,6 +53,8 @@ struct arizona_extcon_info { bool micd_reva; bool micd_clamp; + struct delayed_work hpdet_work; + bool hpdet_active; int num_hpdet_res; @@ -640,7 +642,7 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) dev_dbg(arizona->dev, "Starting identification via HPDET\n"); /* Make sure we keep the device enabled during the measurement */ - pm_runtime_get(info->dev); + pm_runtime_get_sync(info->dev); info->hpdet_active = true; @@ -813,6 +815,17 @@ handled: return IRQ_HANDLED; } +static void arizona_hpdet_work(struct work_struct *work) +{ + struct arizona_extcon_info *info = container_of(work, + struct arizona_extcon_info, + hpdet_work.work); + + mutex_lock(&info->lock); + arizona_start_hpdet_acc_id(info); + mutex_unlock(&info->lock); +} + static irqreturn_t arizona_jackdet(int irq, void *data) { struct arizona_extcon_info *info = data; @@ -822,6 +835,8 @@ static irqreturn_t arizona_jackdet(int irq, void *data) pm_runtime_get_sync(info->dev); + cancel_delayed_work_sync(&info->hpdet_work); + mutex_lock(&info->lock); if (arizona->pdata.jd_gpio5) { @@ -857,7 +872,8 @@ static irqreturn_t arizona_jackdet(int irq, void *data) arizona_start_mic(info); } else { - arizona_start_hpdet_acc_id(info); + schedule_delayed_work(&info->hpdet_work, + msecs_to_jiffies(250)); } regmap_update_bits(arizona->regmap, @@ -927,6 +943,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) mutex_init(&info->lock); info->arizona = arizona; info->dev = &pdev->dev; + INIT_DELAYED_WORK(&info->hpdet_work, arizona_hpdet_work); platform_set_drvdata(pdev, info); switch (arizona->type) { @@ -1173,6 +1190,7 @@ static int arizona_extcon_remove(struct platform_device *pdev) arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); arizona_free_irq(arizona, jack_irq_rise, info); arizona_free_irq(arizona, jack_irq_fall, info); + cancel_delayed_work_sync(&info->hpdet_work); regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, ARIZONA_JD1_ENA, 0); arizona_clk32k_disable(arizona); -- cgit v1.2.1 From 903aa56fdf930fd17fc4f35610bb1c818b5a9327 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 7 Feb 2013 15:47:40 +0000 Subject: extcon: arizona: Don't HPDET magic when headphones are enabled The magic is already done as part of enabling the headphone output so does not need to be done when the headphone outputs are enabled. We hold the DAPM lock so the headphone status can't be changed underneath us. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 58 ++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 2ad0e4a35a23..cfd206c4797c 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -489,6 +489,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) struct arizona *arizona = info->arizona; int id_gpio = arizona->pdata.hpdet_id_gpio; int report = ARIZONA_CABLE_HEADPHONE; + unsigned int val; int ret, reading; mutex_lock(&info->lock); @@ -543,13 +544,28 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) dev_err(arizona->dev, "Failed to report HP/line: %d\n", ret); - ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0); - if (ret != 0) - dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); + mutex_lock(&arizona->dapm->card->dapm_mutex); - ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0); - if (ret != 0) - dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); + ret = regmap_read(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read output enables: %d\n", + ret); + val = 0; + } + + if (!(val & (ARIZONA_OUT1L_ENA | ARIZONA_OUT1R_ENA))) { + ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0); + if (ret != 0) + dev_warn(arizona->dev, "Failed to undo magic: %d\n", + ret); + + ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0); + if (ret != 0) + dev_warn(arizona->dev, "Failed to undo magic: %d\n", + ret); + } + + mutex_unlock(&arizona->dapm->card->dapm_mutex); done: if (id_gpio) @@ -637,6 +653,7 @@ err: static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) { struct arizona *arizona = info->arizona; + unsigned int val; int ret; dev_dbg(arizona->dev, "Starting identification via HPDET\n"); @@ -648,13 +665,30 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) arizona_extcon_pulse_micbias(info); - ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0x4000); - if (ret != 0) - dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); + mutex_lock(&arizona->dapm->card->dapm_mutex); - ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0x4000); - if (ret != 0) - dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); + ret = regmap_read(arizona->regmap, ARIZONA_OUTPUT_ENABLES_1, &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read output enables: %d\n", + ret); + val = 0; + } + + if (!(val & (ARIZONA_OUT1L_ENA | ARIZONA_OUT1R_ENA))) { + ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, + 0x4000); + if (ret != 0) + dev_warn(arizona->dev, "Failed to do magic: %d\n", + ret); + + ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, + 0x4000); + if (ret != 0) + dev_warn(arizona->dev, "Failed to do magic: %d\n", + ret); + } + + mutex_unlock(&arizona->dapm->card->dapm_mutex); ret = regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, -- cgit v1.2.1 From 5d9ab708200fefc3ec6e4454c65584d14ce716b0 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 5 Feb 2013 10:13:38 +0000 Subject: extcon: arizona: Clear _trig_sts bits after jack detection It is important to clear the wake trigger status bits otherwise DCVDD will be held high independent of the state of the LDOENA line. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 7 +++++++ include/linux/mfd/arizona/registers.h | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index cfd206c4797c..aeaf217a05ee 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -939,6 +939,13 @@ static irqreturn_t arizona_jackdet(int irq, void *data) ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); } + /* Clear trig_sts to make sure DCVDD is not forced up */ + regmap_write(arizona->regmap, ARIZONA_AOD_WKUP_AND_TRIG, + ARIZONA_MICD_CLAMP_FALL_TRIG_STS | + ARIZONA_MICD_CLAMP_RISE_TRIG_STS | + ARIZONA_JD1_FALL_TRIG_STS | + ARIZONA_JD1_RISE_TRIG_STS); + mutex_unlock(&info->lock); pm_runtime_mark_last_busy(info->dev); diff --git a/include/linux/mfd/arizona/registers.h b/include/linux/mfd/arizona/registers.h index 79e9dd4073d8..188d89abd963 100644 --- a/include/linux/mfd/arizona/registers.h +++ b/include/linux/mfd/arizona/registers.h @@ -5267,6 +5267,14 @@ /* * R3408 (0xD50) - AOD wkup and trig */ +#define ARIZONA_MICD_CLAMP_FALL_TRIG_STS 0x0080 /* MICD_CLAMP_FALL_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_FALL_TRIG_STS_MASK 0x0080 /* MICD_CLAMP_FALL_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_FALL_TRIG_STS_SHIFT 7 /* MICD_CLAMP_FALL_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_FALL_TRIG_STS_WIDTH 1 /* MICD_CLAMP_FALL_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_RISE_TRIG_STS 0x0040 /* MICD_CLAMP_RISE_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_RISE_TRIG_STS_MASK 0x0040 /* MICD_CLAMP_RISE_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_RISE_TRIG_STS_SHIFT 6 /* MICD_CLAMP_RISE_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_RISE_TRIG_STS_WIDTH 1 /* MICD_CLAMP_RISE_TRIG_STS */ #define ARIZONA_GP5_FALL_TRIG_STS 0x0020 /* GP5_FALL_TRIG_STS */ #define ARIZONA_GP5_FALL_TRIG_STS_MASK 0x0020 /* GP5_FALL_TRIG_STS */ #define ARIZONA_GP5_FALL_TRIG_STS_SHIFT 5 /* GP5_FALL_TRIG_STS */ -- cgit v1.2.1 From c37b387f077c54c5a01fa240dc8448b60bd731c1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 5 Feb 2013 17:48:49 +0000 Subject: extcon: arizona: Always take the first HPDET reading as the final one This should always be the most accurate reading for supported accessory configurations. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index aeaf217a05ee..d9918421e80b 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -451,6 +451,10 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) info->hpdet_res[0], info->hpdet_res[1], info->hpdet_res[2]); + + /* Take the headphone impedance for the main report */ + *reading = info->hpdet_res[0]; + /* * Either the two grounds measure differently or we * measure the mic as high impedance. @@ -466,9 +470,6 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) dev_err(arizona->dev, "Failed to report mic: %d\n", ret); } - - /* Take the headphone impedance for the main report */ - *reading = info->hpdet_res[1]; } else { dev_dbg(arizona->dev, "Detected headphone\n"); } -- cgit v1.2.1 From bf14ee5a460276a99ed35f9034bae9e74b01600f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 5 Feb 2013 20:20:17 +0000 Subject: extcon: arizona: Use MICDET for final microphone identification When using HPDET to identify the accessory still run MICDET before we report a microphone in order to ensure that the accessory identified is compatible with the MICDET detection ranges after having confirmed that the device is not using a headphone. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index d9918421e80b..dc357a4051f6 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -56,6 +56,7 @@ struct arizona_extcon_info { struct delayed_work hpdet_work; bool hpdet_active; + bool hpdet_done; int num_hpdet_res; unsigned int hpdet_res[3]; @@ -394,7 +395,6 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) { struct arizona *arizona = info->arizona; int id_gpio = arizona->pdata.hpdet_id_gpio; - int ret; /* * If we're using HPDET for accessory identification we need @@ -463,13 +463,7 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) (id_gpio && info->hpdet_res[2] > 10)) { dev_dbg(arizona->dev, "Detected mic\n"); info->mic = true; - ret = extcon_set_cable_state_(&info->edev, - ARIZONA_CABLE_MICROPHONE, - true); - if (ret != 0) { - dev_err(arizona->dev, - "Failed to report mic: %d\n", ret); - } + info->detecting = true; } else { dev_dbg(arizona->dev, "Detected headphone\n"); } @@ -586,6 +580,8 @@ done: info->hpdet_active = false; } + info->hpdet_done = true; + out: mutex_unlock(&info->lock); @@ -597,6 +593,9 @@ static void arizona_identify_headphone(struct arizona_extcon_info *info) struct arizona *arizona = info->arizona; int ret; + if (info->hpdet_done) + return; + dev_dbg(arizona->dev, "Starting HPDET\n"); /* Make sure we keep the device enabled during the measurement */ @@ -923,6 +922,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) info->hpdet_res[i] = 0; info->mic = false; + info->hpdet_done = false; for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) input_report_key(info->input, -- cgit v1.2.1 From be9bb0232322d73329d6dbf004c1222258a04a57 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Wed, 13 Feb 2013 14:00:36 +0100 Subject: CREDITS: update email and address of Harald Hoyer Signed-off-by: Harald Hoyer Signed-off-by: Greg Kroah-Hartman --- CREDITS | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CREDITS b/CREDITS index 2346b09ca8bb..948e0fb9a70e 100644 --- a/CREDITS +++ b/CREDITS @@ -1572,12 +1572,12 @@ S: Wantage, New Jersey 07461 S: USA N: Harald Hoyer -E: harald.hoyer@parzelle.de -W: http://parzelle.de/ +E: harald@redhat.com +W: http://www.harald-hoyer.de D: ip_masq_quake D: md boot support -S: Hohe Strasse 30 -S: D-70176 Stuttgart +S: Am Strand 5 +S: D-19063 Schwerin S: Germany N: Jan Hubicka -- cgit v1.2.1 From fd116066d966549211b41d929feea5474d57461b Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 18:35:00 +0900 Subject: extcon: gpio: Rename filename of extcon-gpio.c according to kernel naming style Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon-gpio.c | 2 +- include/linux/extcon/extcon-gpio.h | 52 ++++++++++++++++++++++++++++++++++++++ include/linux/extcon/extcon_gpio.h | 52 -------------------------------------- 3 files changed, 53 insertions(+), 53 deletions(-) create mode 100644 include/linux/extcon/extcon-gpio.h delete mode 100644 include/linux/extcon/extcon_gpio.h diff --git a/drivers/extcon/extcon-gpio.c b/drivers/extcon/extcon-gpio.c index 1b14bfcdc176..02bec32adde4 100644 --- a/drivers/extcon/extcon-gpio.c +++ b/drivers/extcon/extcon-gpio.c @@ -29,7 +29,7 @@ #include #include #include -#include +#include struct gpio_extcon_data { struct extcon_dev edev; diff --git a/include/linux/extcon/extcon-gpio.h b/include/linux/extcon/extcon-gpio.h new file mode 100644 index 000000000000..2d8307f7d67d --- /dev/null +++ b/include/linux/extcon/extcon-gpio.h @@ -0,0 +1,52 @@ +/* + * External connector (extcon) class generic GPIO driver + * + * Copyright (C) 2012 Samsung Electronics + * Author: MyungJoo Ham + * + * based on switch class driver + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that 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. + * +*/ +#ifndef __EXTCON_GPIO_H__ +#define __EXTCON_GPIO_H__ __FILE__ + +#include + +/** + * struct gpio_extcon_platform_data - A simple GPIO-controlled extcon device. + * @name The name of this GPIO extcon device. + * @gpio Corresponding GPIO. + * @debounce Debounce time for GPIO IRQ in ms. + * @irq_flags IRQ Flags (e.g., IRQF_TRIGGER_LOW). + * @state_on print_state is overriden with state_on if attached. If Null, + * default method of extcon class is used. + * @state_off print_state is overriden with state_on if detached. If Null, + * default method of extcon class is used. + * + * Note that in order for state_on or state_off to be valid, both state_on + * and state_off should be not NULL. If at least one of them is NULL, + * the print_state is not overriden. + */ +struct gpio_extcon_platform_data { + const char *name; + unsigned gpio; + unsigned long debounce; + unsigned long irq_flags; + + /* if NULL, "0" or "1" will be printed */ + const char *state_on; + const char *state_off; +}; + +#endif /* __EXTCON_GPIO_H__ */ diff --git a/include/linux/extcon/extcon_gpio.h b/include/linux/extcon/extcon_gpio.h deleted file mode 100644 index 2d8307f7d67d..000000000000 --- a/include/linux/extcon/extcon_gpio.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * External connector (extcon) class generic GPIO driver - * - * Copyright (C) 2012 Samsung Electronics - * Author: MyungJoo Ham - * - * based on switch class driver - * Copyright (C) 2008 Google, Inc. - * Author: Mike Lockwood - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that 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. - * -*/ -#ifndef __EXTCON_GPIO_H__ -#define __EXTCON_GPIO_H__ __FILE__ - -#include - -/** - * struct gpio_extcon_platform_data - A simple GPIO-controlled extcon device. - * @name The name of this GPIO extcon device. - * @gpio Corresponding GPIO. - * @debounce Debounce time for GPIO IRQ in ms. - * @irq_flags IRQ Flags (e.g., IRQF_TRIGGER_LOW). - * @state_on print_state is overriden with state_on if attached. If Null, - * default method of extcon class is used. - * @state_off print_state is overriden with state_on if detached. If Null, - * default method of extcon class is used. - * - * Note that in order for state_on or state_off to be valid, both state_on - * and state_off should be not NULL. If at least one of them is NULL, - * the print_state is not overriden. - */ -struct gpio_extcon_platform_data { - const char *name; - unsigned gpio; - unsigned long debounce; - unsigned long irq_flags; - - /* if NULL, "0" or "1" will be printed */ - const char *state_on; - const char *state_off; -}; - -#endif /* __EXTCON_GPIO_H__ */ -- cgit v1.2.1 From eff7d74f5b88ef31ac47c6d96b1494db199705f5 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 18:35:05 +0900 Subject: extcon: max77693: Convert to devm_input_allocate_device() Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon-max77693.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index fb6527607a11..597f33c1f868 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1066,7 +1066,7 @@ static int max77693_muic_probe(struct platform_device *pdev) } /* Register input device for button of dock device */ - info->dock = input_allocate_device(); + info->dock = devm_input_allocate_device(&pdev->dev); if (!info->dock) { dev_err(&pdev->dev, "%s: failed to allocate input\n", __func__); return -ENOMEM; -- cgit v1.2.1 From 19d3243e797c2abc02a214d3cec9fefa5dc048ff Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 18:35:08 +0900 Subject: extcon: max77693: Remove unnecessary goto statement to improve readability Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon-max77693.c | 77 ++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 597f33c1f868..ad6e9a466855 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -224,16 +224,17 @@ static int max77693_muic_set_debounce_time(struct max77693_muic_info *info, MAX77693_MUIC_REG_CTRL3, time << CONTROL3_ADCDBSET_SHIFT, CONTROL3_ADCDBSET_MASK); - if (ret) + if (ret) { dev_err(info->dev, "failed to set ADC debounce time\n"); + return -EAGAIN; + } break; default: dev_err(info->dev, "invalid ADC debounce time\n"); - ret = -EINVAL; - break; + return -EINVAL; } - return ret; + return 0; }; /* @@ -261,7 +262,7 @@ static int max77693_muic_set_path(struct max77693_muic_info *info, MAX77693_MUIC_REG_CTRL1, ctrl1, COMP_SW_MASK); if (ret < 0) { dev_err(info->dev, "failed to update MUIC register\n"); - goto out; + return -EAGAIN; } if (attached) @@ -274,14 +275,14 @@ static int max77693_muic_set_path(struct max77693_muic_info *info, CONTROL2_LOWPWR_MASK | CONTROL2_CPEN_MASK); if (ret < 0) { dev_err(info->dev, "failed to update MUIC register\n"); - goto out; + return -EAGAIN; } dev_info(info->dev, "CONTROL1 : 0x%02x, CONTROL2 : 0x%02x, state : %s\n", ctrl1, ctrl2, attached ? "attached" : "detached"); -out: - return ret; + + return 0; } /* @@ -503,6 +504,10 @@ static int max77693_muic_dock_handler(struct max77693_muic_info *info, if (!attached) extcon_set_cable_state(info->edev, "USB", false); break; + default: + dev_err(info->dev, "failed to detect %s dock device\n", + attached ? "attached" : "detached"); + return -EINVAL; } /* Dock-Car/Desk/Audio, PATH:AUDIO */ @@ -520,7 +525,6 @@ static int max77693_muic_dock_button_handler(struct max77693_muic_info *info, { struct input_dev *dock = info->dock; unsigned int code; - int ret = 0; switch (button_type) { case MAX77693_MUIC_ADC_REMOTE_S3_BUTTON-1 @@ -550,14 +554,12 @@ static int max77693_muic_dock_button_handler(struct max77693_muic_info *info, dev_err(info->dev, "failed to detect %s key (adc:0x%x)\n", attached ? "pressed" : "released", button_type); - ret = -EINVAL; - goto out; + return -EINVAL; } input_event(dock, EV_KEY, code, attached); input_sync(dock); -out: return 0; } @@ -576,14 +578,14 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info) /* USB_OTG, PATH: AP_USB */ ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); if (ret < 0) - goto out; + return ret; extcon_set_cable_state(info->edev, "USB-Host", attached); break; case MAX77693_MUIC_GND_AV_CABLE_LOAD: /* Audio Video Cable with load, PATH:AUDIO */ ret = max77693_muic_set_path(info, CONTROL1_SW_AUDIO, attached); if (ret < 0) - goto out; + return ret; extcon_set_cable_state(info->edev, "Audio-video-load", attached); break; @@ -593,14 +595,12 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info) extcon_set_cable_state(info->edev, "MHL", attached); break; default: - dev_err(info->dev, "failed to detect %s accessory\n", + dev_err(info->dev, "failed to detect %s cable of gnd type\n", attached ? "attached" : "detached"); - ret = -EINVAL; - break; + return -EINVAL; } -out: - return ret; + return 0; } static int max77693_muic_jig_handler(struct max77693_muic_info *info, @@ -630,15 +630,19 @@ static int max77693_muic_jig_handler(struct max77693_muic_info *info, strcpy(cable_name, "JIG-UART-OFF"); path = CONTROL1_SW_UART; break; + default: + dev_err(info->dev, "failed to detect %s jig cable\n", + attached ? "attached" : "detached"); + return -EINVAL; } ret = max77693_muic_set_path(info, path, attached); if (ret < 0) - goto out; + return ret; extcon_set_cable_state(info->edev, cable_name, attached); -out: - return ret; + + return 0; } static int max77693_muic_adc_handler(struct max77693_muic_info *info) @@ -668,7 +672,7 @@ static int max77693_muic_adc_handler(struct max77693_muic_info *info) /* JIG */ ret = max77693_muic_jig_handler(info, cable_type, attached); if (ret < 0) - goto out; + return ret; break; case MAX77693_MUIC_ADC_RESERVED_ACC_3: /* Dock-Smart */ case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: /* Dock-Car */ @@ -685,7 +689,7 @@ static int max77693_muic_adc_handler(struct max77693_muic_info *info) */ ret = max77693_muic_dock_handler(info, cable_type, attached); if (ret < 0) - goto out; + return ret; break; case MAX77693_MUIC_ADC_REMOTE_S3_BUTTON: /* DOCK_KEY_PREV */ case MAX77693_MUIC_ADC_REMOTE_S7_BUTTON: /* DOCK_KEY_NEXT */ @@ -710,7 +714,7 @@ static int max77693_muic_adc_handler(struct max77693_muic_info *info) ret = max77693_muic_dock_button_handler(info, button_type, attached); if (ret < 0) - goto out; + return ret; break; case MAX77693_MUIC_ADC_SEND_END_BUTTON: case MAX77693_MUIC_ADC_REMOTE_S1_BUTTON: @@ -738,17 +742,15 @@ static int max77693_muic_adc_handler(struct max77693_muic_info *info) dev_info(info->dev, "accessory is %s but it isn't used (adc:0x%x)\n", attached ? "attached" : "detached", cable_type); - goto out; + return -EAGAIN; default: dev_err(info->dev, "failed to detect %s accessory (adc:0x%x)\n", attached ? "attached" : "detached", cable_type); - ret = -EINVAL; - goto out; + return -EINVAL; } -out: - return ret; + return 0; } static int max77693_muic_chg_handler(struct max77693_muic_info *info) @@ -959,7 +961,8 @@ static void max77693_muic_irq_work(struct work_struct *work) default: dev_err(info->dev, "muic interrupt: irq %d occurred\n", irq_type); - break; + mutex_unlock(&info->mutex); + return; } if (ret < 0) @@ -1007,21 +1010,27 @@ static int max77693_muic_detect_accessory(struct max77693_muic_info *info) &attached); if (attached && adc != MAX77693_MUIC_ADC_OPEN) { ret = max77693_muic_adc_handler(info); - if (ret < 0) + if (ret < 0) { dev_err(info->dev, "Cannot detect accessory\n"); + mutex_unlock(&info->mutex); + return ret; + } } chg_type = max77693_muic_get_cable_type(info, MAX77693_CABLE_GROUP_CHG, &attached); if (attached && chg_type != MAX77693_CHARGER_TYPE_NONE) { ret = max77693_muic_chg_handler(info); - if (ret < 0) + if (ret < 0) { dev_err(info->dev, "Cannot detect charger accessory\n"); + mutex_unlock(&info->mutex); + return ret; + } } mutex_unlock(&info->mutex); - return ret; + return 0; } static void max77693_muic_detect_cable_wq(struct work_struct *work) -- cgit v1.2.1 From cae93db3111a524c234397fc53b6a2fc51099528 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Thu, 31 Jan 2013 09:29:29 +0900 Subject: extcon: max8997: Make max8997_extcon_cable static 'max8997_extcon_cable' is used only in this file. Hence make it static. Signed-off-by: Sachin Kamat Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index df9358e30814..7039541e837f 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -109,7 +109,7 @@ struct max8997_muic_info { struct extcon_dev *edev; }; -const char *max8997_extcon_cable[] = { +static const char *max8997_extcon_cable[] = { [0] = "USB", [1] = "USB-Host", [2] = "TA", -- cgit v1.2.1 From 6a462e1d007a6eecb18c44a2fef3ba4953a3f4b2 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Thu, 31 Jan 2013 09:30:00 +0900 Subject: extcon: max8997: Remove unreachable code 'break' after 'return' is never executed and hence can be deleted. Signed-off-by: Sachin Kamat Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 7039541e837f..d16090ddd65a 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -289,7 +289,6 @@ static int max8997_muic_handle_charger_type_detach( break; default: return -EINVAL; - break; } return 0; -- cgit v1.2.1 From 45d4a4e6f5e5dcdd00b7c9d370cfb9694358e4e9 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Thu, 31 Jan 2013 09:31:47 +0900 Subject: extcon: max77693: Make max77693_extcon_cable static 'max77693_extcon_cable' is used only in this file. Hence make it static. Signed-off-by: Sachin Kamat Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index ad6e9a466855..b70e3815c459 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -185,7 +185,7 @@ enum { _EXTCON_CABLE_NUM, }; -const char *max77693_extcon_cable[] = { +static const char *max77693_extcon_cable[] = { [EXTCON_CABLE_USB] = "USB", [EXTCON_CABLE_USB_HOST] = "USB-Host", [EXTCON_CABLE_TA] = "TA", -- cgit v1.2.1 From e3e5bc02d2365d3e09164fd9a559011399ca2a4f Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Tue, 12 Feb 2013 20:44:19 +0900 Subject: extcon: max8997: Move defined constant to header file This patch move defined constants to header file(max77693-private.h) because of mask/unmask selectively interrupt of MUIC device according to attribute of H/W board. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 92 +++++++++++++------------------------ include/linux/mfd/max8997-private.h | 49 ++++++++++++++++++++ 2 files changed, 80 insertions(+), 61 deletions(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index d16090ddd65a..0fb1d48b0741 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -30,51 +30,6 @@ #define DEV_NAME "max8997-muic" -/* MAX8997-MUIC STATUS1 register */ -#define STATUS1_ADC_SHIFT 0 -#define STATUS1_ADCLOW_SHIFT 5 -#define STATUS1_ADCERR_SHIFT 6 -#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) -#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) -#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) - -/* MAX8997-MUIC STATUS2 register */ -#define STATUS2_CHGTYP_SHIFT 0 -#define STATUS2_CHGDETRUN_SHIFT 3 -#define STATUS2_DCDTMR_SHIFT 4 -#define STATUS2_DBCHG_SHIFT 5 -#define STATUS2_VBVOLT_SHIFT 6 -#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) -#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) -#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) -#define STATUS2_DBCHG_MASK (0x1 << STATUS2_DBCHG_SHIFT) -#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) - -/* MAX8997-MUIC STATUS3 register */ -#define STATUS3_OVP_SHIFT 2 -#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) - -/* MAX8997-MUIC CONTROL1 register */ -#define COMN1SW_SHIFT 0 -#define COMP2SW_SHIFT 3 -#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) -#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) -#define SW_MASK (COMP2SW_MASK | COMN1SW_MASK) - -#define MAX8997_SW_USB ((1 << COMP2SW_SHIFT) | (1 << COMN1SW_SHIFT)) -#define MAX8997_SW_AUDIO ((2 << COMP2SW_SHIFT) | (2 << COMN1SW_SHIFT)) -#define MAX8997_SW_UART ((3 << COMP2SW_SHIFT) | (3 << COMN1SW_SHIFT)) -#define MAX8997_SW_OPEN ((0 << COMP2SW_SHIFT) | (0 << COMN1SW_SHIFT)) - -#define MAX8997_ADC_GROUND 0x00 -#define MAX8997_ADC_MHL 0x01 -#define MAX8997_ADC_JIG_USB_1 0x18 -#define MAX8997_ADC_JIG_USB_2 0x19 -#define MAX8997_ADC_DESKDOCK 0x1a -#define MAX8997_ADC_JIG_UART 0x1c -#define MAX8997_ADC_CARDOCK 0x1d -#define MAX8997_ADC_OPEN 0x1f - struct max8997_muic_irq { unsigned int irq; const char *name; @@ -109,17 +64,32 @@ struct max8997_muic_info { struct extcon_dev *edev; }; +enum { + EXTCON_CABLE_USB = 0, + EXTCON_CABLE_USB_HOST, + EXTCON_CABLE_TA, + EXTCON_CABLE_FAST_CHARGER, + EXTCON_CABLE_SLOW_CHARGER, + EXTCON_CABLE_CHARGE_DOWNSTREAM, + EXTCON_CABLE_MHL, + EXTCON_CABLE_DOCK_DESK, + EXTCON_CABLE_DOCK_CARD, + EXTCON_CABLE_JIG, + + _EXTCON_CABLE_NUM, +}; + static const char *max8997_extcon_cable[] = { - [0] = "USB", - [1] = "USB-Host", - [2] = "TA", - [3] = "Fast-charger", - [4] = "Slow-charger", - [5] = "Charge-downstream", - [6] = "MHL", - [7] = "Dock-desk", - [8] = "Dock-card", - [9] = "JIG", + [EXTCON_CABLE_USB] = "USB", + [EXTCON_CABLE_USB_HOST] = "USB-Host", + [EXTCON_CABLE_TA] = "TA", + [EXTCON_CABLE_FAST_CHARGER] = "Fast-charger", + [EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger", + [EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream", + [EXTCON_CABLE_MHL] = "MHL", + [EXTCON_CABLE_DOCK_DESK] = "Dock-Desk", + [EXTCON_CABLE_DOCK_CARD] = "Dock-Card", + [EXTCON_CABLE_JIG] = "JIG", NULL, }; @@ -132,8 +102,8 @@ static int max8997_muic_handle_usb(struct max8997_muic_info *info, if (usb_type == MAX8997_USB_HOST) { /* switch to USB */ ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_USB : MAX8997_SW_OPEN, - SW_MASK); + attached ? CONTROL1_SW_USB : CONTROL1_SW_OPEN, + CONTROL1_SW_MASK); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; @@ -163,8 +133,8 @@ static int max8997_muic_handle_dock(struct max8997_muic_info *info, /* switch to AUDIO */ ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_AUDIO : MAX8997_SW_OPEN, - SW_MASK); + attached ? CONTROL1_SW_AUDIO : CONTROL1_SW_OPEN, + CONTROL1_SW_MASK); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; @@ -192,8 +162,8 @@ static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, /* switch to UART */ ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_UART : MAX8997_SW_OPEN, - SW_MASK); + attached ? CONTROL1_SW_UART : CONTROL1_SW_OPEN, + CONTROL1_SW_MASK); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h index 6ae21bf47d64..acf42e960320 100644 --- a/include/linux/mfd/max8997-private.h +++ b/include/linux/mfd/max8997-private.h @@ -194,6 +194,55 @@ enum max8997_muic_reg { MAX8997_MUIC_REG_END = 0xf, }; +/* MAX8997-MUIC STATUS1 register */ +#define STATUS1_ADC_SHIFT 0 +#define STATUS1_ADCLOW_SHIFT 5 +#define STATUS1_ADCERR_SHIFT 6 +#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) +#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) +#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) + +/* MAX8997-MUIC STATUS2 register */ +#define STATUS2_CHGTYP_SHIFT 0 +#define STATUS2_CHGDETRUN_SHIFT 3 +#define STATUS2_DCDTMR_SHIFT 4 +#define STATUS2_DBCHG_SHIFT 5 +#define STATUS2_VBVOLT_SHIFT 6 +#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) +#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) +#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) +#define STATUS2_DBCHG_MASK (0x1 << STATUS2_DBCHG_SHIFT) +#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) + +/* MAX8997-MUIC STATUS3 register */ +#define STATUS3_OVP_SHIFT 2 +#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) + +/* MAX8997-MUIC CONTROL1 register */ +#define COMN1SW_SHIFT 0 +#define COMP2SW_SHIFT 3 +#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) +#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) +#define CONTROL1_SW_MASK (COMP2SW_MASK | COMN1SW_MASK) + +#define CONTROL1_SW_USB ((1 << COMP2SW_SHIFT) \ + | (1 << COMN1SW_SHIFT)) +#define CONTROL1_SW_AUDIO ((2 << COMP2SW_SHIFT) \ + | (2 << COMN1SW_SHIFT)) +#define CONTROL1_SW_UART ((3 << COMP2SW_SHIFT) \ + | (3 << COMN1SW_SHIFT)) +#define CONTROL1_SW_OPEN ((0 << COMP2SW_SHIFT) \ + | (0 << COMN1SW_SHIFT)) + +#define MAX8997_ADC_GROUND 0x00 +#define MAX8997_ADC_MHL 0x01 +#define MAX8997_ADC_JIG_USB_1 0x18 +#define MAX8997_ADC_JIG_USB_2 0x19 +#define MAX8997_ADC_DESKDOCK 0x1a +#define MAX8997_ADC_JIG_UART 0x1c +#define MAX8997_ADC_CARDOCK 0x1d +#define MAX8997_ADC_OPEN 0x1f + enum max8997_haptic_reg { MAX8997_HAPTIC_REG_GENERAL = 0x00, MAX8997_HAPTIC_REG_CONF1 = 0x01, -- cgit v1.2.1 From 07c70503a420d48402b3859e2c1c4c847a130a8b Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 08:42:37 +0900 Subject: extcon: max8997: Remove duplicate code related to set H/W line path Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 62 ++++++++++++++++++++++++++++++------- include/linux/mfd/max8997-private.h | 19 +++++++++++- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 0fb1d48b0741..8739b50c2b36 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -94,16 +94,61 @@ static const char *max8997_extcon_cable[] = { NULL, }; +/* + * max8997_muic_set_path - Set hardware line according to attached cable + * @info: the instance including private data of max8997 MUIC + * @value: the path according to attached cable + * @attached: the state of cable (true:attached, false:detached) + * + * The max8997 MUIC device share outside H/W line among a varity of cables, + * so this function set internal path of H/W line according to the type of + * attached cable. + */ +static int max8997_muic_set_path(struct max8997_muic_info *info, + u8 val, bool attached) +{ + int ret = 0; + u8 ctrl1, ctrl2 = 0; + + if (attached) + ctrl1 = val; + else + ctrl1 = CONTROL1_SW_OPEN; + + ret = max8997_update_reg(info->muic, + MAX8997_MUIC_REG_CONTROL1, ctrl1, COMP_SW_MASK); + if (ret < 0) { + dev_err(info->dev, "failed to update MUIC register\n"); + return -EAGAIN; + } + + if (attached) + ctrl2 |= CONTROL2_CPEN_MASK; /* LowPwr=0, CPEn=1 */ + else + ctrl2 |= CONTROL2_LOWPWR_MASK; /* LowPwr=1, CPEn=0 */ + + ret = max8997_update_reg(info->muic, + MAX8997_MUIC_REG_CONTROL2, ctrl2, + CONTROL2_LOWPWR_MASK | CONTROL2_CPEN_MASK); + if (ret < 0) { + dev_err(info->dev, "failed to update MUIC register\n"); + return -EAGAIN; + } + + dev_info(info->dev, + "CONTROL1 : 0x%02x, CONTROL2 : 0x%02x, state : %s\n", + ctrl1, ctrl2, attached ? "attached" : "detached"); + + return 0; +} + static int max8997_muic_handle_usb(struct max8997_muic_info *info, enum max8997_muic_usb_type usb_type, bool attached) { int ret = 0; if (usb_type == MAX8997_USB_HOST) { - /* switch to USB */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? CONTROL1_SW_USB : CONTROL1_SW_OPEN, - CONTROL1_SW_MASK); + ret = max8997_muic_set_path(info, CONTROL1_SW_USB, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; @@ -131,10 +176,7 @@ static int max8997_muic_handle_dock(struct max8997_muic_info *info, { int ret = 0; - /* switch to AUDIO */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? CONTROL1_SW_AUDIO : CONTROL1_SW_OPEN, - CONTROL1_SW_MASK); + ret = max8997_muic_set_path(info, CONTROL1_SW_AUDIO, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; @@ -161,9 +203,7 @@ static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, int ret = 0; /* switch to UART */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? CONTROL1_SW_UART : CONTROL1_SW_OPEN, - CONTROL1_SW_MASK); + ret = max8997_muic_set_path(info, CONTROL1_SW_UART, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h index acf42e960320..010173a92274 100644 --- a/include/linux/mfd/max8997-private.h +++ b/include/linux/mfd/max8997-private.h @@ -223,7 +223,7 @@ enum max8997_muic_reg { #define COMP2SW_SHIFT 3 #define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) #define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) -#define CONTROL1_SW_MASK (COMP2SW_MASK | COMN1SW_MASK) +#define COMP_SW_MASK (COMP2SW_MASK | COMN1SW_MASK) #define CONTROL1_SW_USB ((1 << COMP2SW_SHIFT) \ | (1 << COMN1SW_SHIFT)) @@ -234,6 +234,23 @@ enum max8997_muic_reg { #define CONTROL1_SW_OPEN ((0 << COMP2SW_SHIFT) \ | (0 << COMN1SW_SHIFT)) +#define CONTROL2_LOWPWR_SHIFT (0) +#define CONTROL2_ADCEN_SHIFT (1) +#define CONTROL2_CPEN_SHIFT (2) +#define CONTROL2_SFOUTASRT_SHIFT (3) +#define CONTROL2_SFOUTORD_SHIFT (4) +#define CONTROL2_ACCDET_SHIFT (5) +#define CONTROL2_USBCPINT_SHIFT (6) +#define CONTROL2_RCPS_SHIFT (7) +#define CONTROL2_LOWPWR_MASK (0x1 << CONTROL2_LOWPWR_SHIFT) +#define CONTROL2_ADCEN_MASK (0x1 << CONTROL2_ADCEN_SHIFT) +#define CONTROL2_CPEN_MASK (0x1 << CONTROL2_CPEN_SHIFT) +#define CONTROL2_SFOUTASRT_MASK (0x1 << CONTROL2_SFOUTASRT_SHIFT) +#define CONTROL2_SFOUTORD_MASK (0x1 << CONTROL2_SFOUTORD_SHIFT) +#define CONTROL2_ACCDET_MASK (0x1 << CONTROL2_ACCDET_SHIFT) +#define CONTROL2_USBCPINT_MASK (0x1 << CONTROL2_USBCPINT_SHIFT) +#define CONTROL2_RCPS_MASK (0x1 << CONTROL2_RCPS_SHIFT) + #define MAX8997_ADC_GROUND 0x00 #define MAX8997_ADC_MHL 0x01 #define MAX8997_ADC_JIG_USB_1 0x18 -- cgit v1.2.1 From 027fcd50500fd87847891d5c2f341c1f002de3e8 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 08:50:00 +0900 Subject: extcon: max8997: Set default of ADC debounce time during initialization This patch set default of ADC Debounce Time(25ms) during probe step. Also, can possible change ADC Debounce Time according to H/W situation by using max8997_set_adc_debounce_time() Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 42 +++++++++++++++++++++++++++++++++++++ include/linux/mfd/max8997-private.h | 7 +++++++ 2 files changed, 49 insertions(+) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 8739b50c2b36..3206daaf8e08 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -30,6 +30,13 @@ #define DEV_NAME "max8997-muic" +enum max8997_muic_adc_debounce_time { + ADC_DEBOUNCE_TIME_0_5MS = 0, /* 0.5ms */ + ADC_DEBOUNCE_TIME_10MS, /* 10ms */ + ADC_DEBOUNCE_TIME_25MS, /* 25ms */ + ADC_DEBOUNCE_TIME_38_62MS, /* 38.62ms */ +}; + struct max8997_muic_irq { unsigned int irq; const char *name; @@ -94,6 +101,38 @@ static const char *max8997_extcon_cable[] = { NULL, }; +/* + * max8997_muic_set_debounce_time - Set the debounce time of ADC + * @info: the instance including private data of max8997 MUIC + * @time: the debounce time of ADC + */ +static int max8997_muic_set_debounce_time(struct max8997_muic_info *info, + enum max8997_muic_adc_debounce_time time) +{ + int ret; + + switch (time) { + case ADC_DEBOUNCE_TIME_0_5MS: + case ADC_DEBOUNCE_TIME_10MS: + case ADC_DEBOUNCE_TIME_25MS: + case ADC_DEBOUNCE_TIME_38_62MS: + ret = max8997_update_reg(info->muic, + MAX8997_MUIC_REG_CONTROL3, + time << CONTROL3_ADCDBSET_SHIFT, + CONTROL3_ADCDBSET_MASK); + if (ret) { + dev_err(info->dev, "failed to set ADC debounce time\n"); + return -EAGAIN; + } + break; + default: + dev_err(info->dev, "invalid ADC debounce time\n"); + return -EINVAL; + } + + return 0; +}; + /* * max8997_muic_set_path - Set hardware line according to attached cable * @info: the instance including private data of max8997 MUIC @@ -507,6 +546,9 @@ static int max8997_muic_probe(struct platform_device *pdev) } } + /* Set ADC debounce time */ + max8997_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); + /* Initial device detection */ max8997_muic_detect_dev(info); diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h index 010173a92274..cd37a92ccba9 100644 --- a/include/linux/mfd/max8997-private.h +++ b/include/linux/mfd/max8997-private.h @@ -251,6 +251,13 @@ enum max8997_muic_reg { #define CONTROL2_USBCPINT_MASK (0x1 << CONTROL2_USBCPINT_SHIFT) #define CONTROL2_RCPS_MASK (0x1 << CONTROL2_RCPS_SHIFT) +#define CONTROL3_JIGSET_SHIFT (0) +#define CONTROL3_BTLDSET_SHIFT (2) +#define CONTROL3_ADCDBSET_SHIFT (4) +#define CONTROL3_JIGSET_MASK (0x3 << CONTROL3_JIGSET_SHIFT) +#define CONTROL3_BTLDSET_MASK (0x3 << CONTROL3_BTLDSET_SHIFT) +#define CONTROL3_ADCDBSET_MASK (0x3 << CONTROL3_ADCDBSET_SHIFT) + #define MAX8997_ADC_GROUND 0x00 #define MAX8997_ADC_MHL 0x01 #define MAX8997_ADC_JIG_USB_1 0x18 -- cgit v1.2.1 From f73f6232af9131f7b6fc6e377267e4a441727eb3 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 12:05:42 +0900 Subject: extcon: max8997: Consolidate duplicate code for checking ADC/CHG cable type This patch make max8997_muic_get_cable_type() function to remove duplicate code for checking ADC/Charger cable type because almost internal function need to read adc/chg_type value of MUIC register. Also, remove *_detach() function, extcon-max8997 driver treat attach/detach operation of cable in max8997_*_handler() function. Lastly, this patch move defined constant in header file(include/ linux/mfd/max8997.h, max8997-private.h) because defined constant is only used in the 'extcon-max8997.c'. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 492 +++++++++++++++++++++++------------- include/linux/mfd/max8997-private.h | 9 - include/linux/mfd/max8997.h | 15 -- 3 files changed, 323 insertions(+), 193 deletions(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 3206daaf8e08..35338a090c06 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -44,31 +44,89 @@ struct max8997_muic_irq { }; static struct max8997_muic_irq muic_irqs[] = { - { MAX8997_MUICIRQ_ADCError, "muic-ADC_error" }, - { MAX8997_MUICIRQ_ADCLow, "muic-ADC_low" }, - { MAX8997_MUICIRQ_ADC, "muic-ADC" }, - { MAX8997_MUICIRQ_VBVolt, "muic-VB_voltage" }, - { MAX8997_MUICIRQ_DBChg, "muic-DB_charger" }, - { MAX8997_MUICIRQ_DCDTmr, "muic-DCD_timer" }, - { MAX8997_MUICIRQ_ChgDetRun, "muic-CDR_status" }, - { MAX8997_MUICIRQ_ChgTyp, "muic-charger_type" }, - { MAX8997_MUICIRQ_OVP, "muic-over_voltage" }, + { MAX8997_MUICIRQ_ADCError, "muic-ADCERROR" }, + { MAX8997_MUICIRQ_ADCLow, "muic-ADCLOW" }, + { MAX8997_MUICIRQ_ADC, "muic-ADC" }, + { MAX8997_MUICIRQ_VBVolt, "muic-VBVOLT" }, + { MAX8997_MUICIRQ_DBChg, "muic-DBCHG" }, + { MAX8997_MUICIRQ_DCDTmr, "muic-DCDTMR" }, + { MAX8997_MUICIRQ_ChgDetRun, "muic-CHGDETRUN" }, + { MAX8997_MUICIRQ_ChgTyp, "muic-CHGTYP" }, + { MAX8997_MUICIRQ_OVP, "muic-OVP" }, +}; + +/* Define supported cable type */ +enum max8997_muic_acc_type { + MAX8997_MUIC_ADC_GROUND = 0x0, + MAX8997_MUIC_ADC_MHL, /* MHL*/ + MAX8997_MUIC_ADC_REMOTE_S1_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S2_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S3_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S4_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S5_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S6_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S7_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S8_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S9_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S10_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S11_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S12_BUTTON, + MAX8997_MUIC_ADC_RESERVED_ACC_1, + MAX8997_MUIC_ADC_RESERVED_ACC_2, + MAX8997_MUIC_ADC_RESERVED_ACC_3, + MAX8997_MUIC_ADC_RESERVED_ACC_4, + MAX8997_MUIC_ADC_RESERVED_ACC_5, + MAX8997_MUIC_ADC_CEA936_AUDIO, + MAX8997_MUIC_ADC_PHONE_POWERED_DEV, + MAX8997_MUIC_ADC_TTY_CONVERTER, + MAX8997_MUIC_ADC_UART_CABLE, + MAX8997_MUIC_ADC_CEA936A_TYPE1_CHG, + MAX8997_MUIC_ADC_FACTORY_MODE_USB_OFF, /* JIG-USB-OFF */ + MAX8997_MUIC_ADC_FACTORY_MODE_USB_ON, /* JIG-USB-ON */ + MAX8997_MUIC_ADC_AV_CABLE_NOLOAD, /* DESKDOCK */ + MAX8997_MUIC_ADC_CEA936A_TYPE2_CHG, + MAX8997_MUIC_ADC_FACTORY_MODE_UART_OFF, /* JIG-UART */ + MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON, /* CARDOCK */ + MAX8997_MUIC_ADC_AUDIO_MODE_REMOTE, + MAX8997_MUIC_ADC_OPEN, /* OPEN */ +}; + +enum max8997_muic_cable_group { + MAX8997_CABLE_GROUP_ADC = 0, + MAX8997_CABLE_GROUP_ADC_GND, + MAX8997_CABLE_GROUP_CHG, + MAX8997_CABLE_GROUP_VBVOLT, +}; + +enum max8997_muic_usb_type { + MAX8997_USB_HOST, + MAX8997_USB_DEVICE, +}; + +enum max8997_muic_charger_type { + MAX8997_CHARGER_TYPE_NONE = 0, + MAX8997_CHARGER_TYPE_USB, + MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT, + MAX8997_CHARGER_TYPE_DEDICATED_CHG, + MAX8997_CHARGER_TYPE_500MA, + MAX8997_CHARGER_TYPE_1A, + MAX8997_CHARGER_TYPE_DEAD_BATTERY = 7, }; struct max8997_muic_info { struct device *dev; struct i2c_client *muic; - struct max8997_muic_platform_data *muic_pdata; + struct extcon_dev *edev; + int prev_cable_type; + int prev_chg_type; + u8 status[2]; int irq; struct work_struct irq_work; - - enum max8997_muic_charger_type pre_charger_type; - int pre_adc; - struct mutex mutex; - struct extcon_dev *edev; + struct max8997_muic_platform_data *muic_pdata; + enum max8997_muic_charger_type pre_charger_type; }; enum { @@ -181,6 +239,83 @@ static int max8997_muic_set_path(struct max8997_muic_info *info, return 0; } +/* + * max8997_muic_get_cable_type - Return cable type and check cable state + * @info: the instance including private data of max8997 MUIC + * @group: the path according to attached cable + * @attached: store cable state and return + * + * This function check the cable state either attached or detached, + * and then divide precise type of cable according to cable group. + * - MAX8997_CABLE_GROUP_ADC + * - MAX8997_CABLE_GROUP_CHG + */ +static int max8997_muic_get_cable_type(struct max8997_muic_info *info, + enum max8997_muic_cable_group group, bool *attached) +{ + int cable_type = 0; + int adc; + int chg_type; + + switch (group) { + case MAX8997_CABLE_GROUP_ADC: + /* + * Read ADC value to check cable type and decide cable state + * according to cable type + */ + adc = info->status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + /* + * Check current cable state/cable type and store cable type + * (info->prev_cable_type) for handling cable when cable is + * detached. + */ + if (adc == MAX8997_MUIC_ADC_OPEN) { + *attached = false; + + cable_type = info->prev_cable_type; + info->prev_cable_type = MAX8997_MUIC_ADC_OPEN; + } else { + *attached = true; + + cable_type = info->prev_cable_type = adc; + } + break; + case MAX8997_CABLE_GROUP_CHG: + /* + * Read charger type to check cable type and decide cable state + * according to type of charger cable. + */ + chg_type = info->status[1] & STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + if (chg_type == MAX8997_CHARGER_TYPE_NONE) { + *attached = false; + + cable_type = info->prev_chg_type; + info->prev_chg_type = MAX8997_CHARGER_TYPE_NONE; + } else { + *attached = true; + + /* + * Check current cable state/cable type and store cable + * type(info->prev_chg_type) for handling cable when + * charger cable is detached. + */ + cable_type = info->prev_chg_type = chg_type; + } + + break; + default: + dev_err(info->dev, "Unknown cable group (%d)\n", group); + cable_type = -EINVAL; + break; + } + + return cable_type; +} + static int max8997_muic_handle_usb(struct max8997_muic_info *info, enum max8997_muic_usb_type usb_type, bool attached) { @@ -188,9 +323,9 @@ static int max8997_muic_handle_usb(struct max8997_muic_info *info, if (usb_type == MAX8997_USB_HOST) { ret = max8997_muic_set_path(info, CONTROL1_SW_USB, attached); - if (ret) { + if (ret < 0) { dev_err(info->dev, "failed to update muic register\n"); - goto out; + return ret; } } @@ -202,38 +337,39 @@ static int max8997_muic_handle_usb(struct max8997_muic_info *info, extcon_set_cable_state(info->edev, "USB", attached); break; default: - ret = -EINVAL; - break; + dev_err(info->dev, "failed to detect %s usb cable\n", + attached ? "attached" : "detached"); + return -EINVAL; } -out: - return ret; + return 0; } static int max8997_muic_handle_dock(struct max8997_muic_info *info, - int adc, bool attached) + int cable_type, bool attached) { int ret = 0; ret = max8997_muic_set_path(info, CONTROL1_SW_AUDIO, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); - goto out; + return ret; } - switch (adc) { - case MAX8997_ADC_DESKDOCK: + switch (cable_type) { + case MAX8997_MUIC_ADC_AV_CABLE_NOLOAD: extcon_set_cable_state(info->edev, "Dock-desk", attached); break; - case MAX8997_ADC_CARDOCK: + case MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON: extcon_set_cable_state(info->edev, "Dock-card", attached); break; default: - ret = -EINVAL; - break; + dev_err(info->dev, "failed to detect %s dock device\n", + attached ? "attached" : "detached"); + return -EINVAL; } -out: - return ret; + + return 0; } static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, @@ -245,193 +381,185 @@ static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, ret = max8997_muic_set_path(info, CONTROL1_SW_UART, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); - goto out; + return -EINVAL; } extcon_set_cable_state(info->edev, "JIG", attached); -out: - return ret; -} -static int max8997_muic_handle_adc_detach(struct max8997_muic_info *info) -{ - int ret = 0; - - switch (info->pre_adc) { - case MAX8997_ADC_GROUND: - ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, false); - break; - case MAX8997_ADC_MHL: - extcon_set_cable_state(info->edev, "MHL", false); - break; - case MAX8997_ADC_JIG_USB_1: - case MAX8997_ADC_JIG_USB_2: - ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, false); - break; - case MAX8997_ADC_DESKDOCK: - case MAX8997_ADC_CARDOCK: - ret = max8997_muic_handle_dock(info, info->pre_adc, false); - break; - case MAX8997_ADC_JIG_UART: - ret = max8997_muic_handle_jig_uart(info, false); - break; - default: - break; - } - - return ret; + return 0; } -static int max8997_muic_handle_adc(struct max8997_muic_info *info, int adc) +static int max8997_muic_adc_handler(struct max8997_muic_info *info) { + int cable_type; + bool attached; int ret = 0; - switch (adc) { - case MAX8997_ADC_GROUND: - ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, true); - break; - case MAX8997_ADC_MHL: - extcon_set_cable_state(info->edev, "MHL", true); - break; - case MAX8997_ADC_JIG_USB_1: - case MAX8997_ADC_JIG_USB_2: - ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, true); - break; - case MAX8997_ADC_DESKDOCK: - case MAX8997_ADC_CARDOCK: - ret = max8997_muic_handle_dock(info, adc, true); - break; - case MAX8997_ADC_JIG_UART: - ret = max8997_muic_handle_jig_uart(info, true); - break; - case MAX8997_ADC_OPEN: - ret = max8997_muic_handle_adc_detach(info); - break; - default: - ret = -EINVAL; - goto out; - } - - info->pre_adc = adc; -out: - return ret; -} - -static int max8997_muic_handle_charger_type_detach( - struct max8997_muic_info *info) -{ - switch (info->pre_charger_type) { - case MAX8997_CHARGER_TYPE_USB: - extcon_set_cable_state(info->edev, "USB", false); - break; - case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: - extcon_set_cable_state(info->edev, "Charge-downstream", false); - break; - case MAX8997_CHARGER_TYPE_DEDICATED_CHG: - extcon_set_cable_state(info->edev, "TA", false); - break; - case MAX8997_CHARGER_TYPE_500MA: - extcon_set_cable_state(info->edev, "Slow-charger", false); - break; - case MAX8997_CHARGER_TYPE_1A: - extcon_set_cable_state(info->edev, "Fast-charger", false); - break; + /* Check cable state which is either detached or attached */ + cable_type = max8997_muic_get_cable_type(info, + MAX8997_CABLE_GROUP_ADC, &attached); + + switch (cable_type) { + case MAX8997_MUIC_ADC_GROUND: + ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, attached); + if (ret < 0) + return ret; + break; + case MAX8997_MUIC_ADC_MHL: + extcon_set_cable_state(info->edev, "MHL", attached); + break; + case MAX8997_MUIC_ADC_FACTORY_MODE_USB_OFF: + case MAX8997_MUIC_ADC_FACTORY_MODE_USB_ON: + ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, attached); + if (ret < 0) + return ret; + break; + case MAX8997_MUIC_ADC_AV_CABLE_NOLOAD: + case MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON: + ret = max8997_muic_handle_dock(info, cable_type, attached); + if (ret < 0) + return ret; + break; + case MAX8997_MUIC_ADC_FACTORY_MODE_UART_OFF: + ret = max8997_muic_handle_jig_uart(info, attached); + break; + case MAX8997_MUIC_ADC_REMOTE_S1_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S2_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S3_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S4_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S5_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S6_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S7_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S8_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S9_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S10_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S11_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S12_BUTTON: + case MAX8997_MUIC_ADC_RESERVED_ACC_1: + case MAX8997_MUIC_ADC_RESERVED_ACC_2: + case MAX8997_MUIC_ADC_RESERVED_ACC_3: + case MAX8997_MUIC_ADC_RESERVED_ACC_4: + case MAX8997_MUIC_ADC_RESERVED_ACC_5: + case MAX8997_MUIC_ADC_CEA936_AUDIO: + case MAX8997_MUIC_ADC_PHONE_POWERED_DEV: + case MAX8997_MUIC_ADC_TTY_CONVERTER: + case MAX8997_MUIC_ADC_UART_CABLE: + case MAX8997_MUIC_ADC_CEA936A_TYPE1_CHG: + case MAX8997_MUIC_ADC_CEA936A_TYPE2_CHG: + case MAX8997_MUIC_ADC_AUDIO_MODE_REMOTE: + /* + * This cable isn't used in general case if it is specially + * needed to detect additional cable, should implement + * proper operation when this cable is attached/detached. + */ + dev_info(info->dev, + "cable is %s but it isn't used (type:0x%x)\n", + attached ? "attached" : "detached", cable_type); + return -EAGAIN; default: + dev_err(info->dev, + "failed to detect %s unknown cable (type:0x%x)\n", + attached ? "attached" : "detached", cable_type); return -EINVAL; } return 0; } -static int max8997_muic_handle_charger_type(struct max8997_muic_info *info, - enum max8997_muic_charger_type charger_type) +static int max8997_muic_chg_handler(struct max8997_muic_info *info) { - u8 adc; - int ret; + int chg_type; + bool attached; + int adc; - ret = max8997_read_reg(info->muic, MAX8997_MUIC_REG_STATUS1, &adc); - if (ret) { - dev_err(info->dev, "failed to read muic register\n"); - goto out; - } + chg_type = max8997_muic_get_cable_type(info, + MAX8997_CABLE_GROUP_CHG, &attached); - switch (charger_type) { + switch (chg_type) { case MAX8997_CHARGER_TYPE_NONE: - ret = max8997_muic_handle_charger_type_detach(info); break; case MAX8997_CHARGER_TYPE_USB: - if ((adc & STATUS1_ADC_MASK) == MAX8997_ADC_OPEN) { + adc = info->status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + if ((adc & STATUS1_ADC_MASK) == MAX8997_MUIC_ADC_OPEN) { max8997_muic_handle_usb(info, - MAX8997_USB_DEVICE, true); + MAX8997_USB_DEVICE, attached); } break; case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: - extcon_set_cable_state(info->edev, "Charge-downstream", true); + extcon_set_cable_state(info->edev, "Charge-downstream", attached); break; case MAX8997_CHARGER_TYPE_DEDICATED_CHG: - extcon_set_cable_state(info->edev, "TA", true); + extcon_set_cable_state(info->edev, "TA", attached); break; case MAX8997_CHARGER_TYPE_500MA: - extcon_set_cable_state(info->edev, "Slow-charger", true); + extcon_set_cable_state(info->edev, "Slow-charger", attached); break; case MAX8997_CHARGER_TYPE_1A: - extcon_set_cable_state(info->edev, "Fast-charger", true); + extcon_set_cable_state(info->edev, "Fast-charger", attached); break; default: - ret = -EINVAL; - goto out; + dev_err(info->dev, + "failed to detect %s unknown chg cable (type:0x%x)\n", + attached ? "attached" : "detached", chg_type); + return -EINVAL; } - info->pre_charger_type = charger_type; -out: - return ret; + return 0; } static void max8997_muic_irq_work(struct work_struct *work) { struct max8997_muic_info *info = container_of(work, struct max8997_muic_info, irq_work); - u8 status[2]; - u8 adc, chg_type; int irq_type = 0; int i, ret; + if (!info->edev) + return; + mutex_lock(&info->mutex); + for (i = 0 ; i < ARRAY_SIZE(muic_irqs) ; i++) + if (info->irq == muic_irqs[i].virq) + irq_type = muic_irqs[i].irq; + ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, - 2, status); + 2, info->status); if (ret) { dev_err(info->dev, "failed to read muic register\n"); mutex_unlock(&info->mutex); return; } - dev_dbg(info->dev, "%s: STATUS1:0x%x, 2:0x%x\n", __func__, - status[0], status[1]); - - for (i = 0 ; i < ARRAY_SIZE(muic_irqs) ; i++) - if (info->irq == muic_irqs[i].virq) - irq_type = muic_irqs[i].irq; - switch (irq_type) { + case MAX8997_MUICIRQ_ADCError: + case MAX8997_MUICIRQ_ADCLow: case MAX8997_MUICIRQ_ADC: - adc = status[0] & STATUS1_ADC_MASK; - adc >>= STATUS1_ADC_SHIFT; - - max8997_muic_handle_adc(info, adc); + /* Handle all of cable except for charger cable */ + ret = max8997_muic_adc_handler(info); break; + case MAX8997_MUICIRQ_VBVolt: + case MAX8997_MUICIRQ_DBChg: + case MAX8997_MUICIRQ_DCDTmr: + case MAX8997_MUICIRQ_ChgDetRun: case MAX8997_MUICIRQ_ChgTyp: - chg_type = status[1] & STATUS2_CHGTYP_MASK; - chg_type >>= STATUS2_CHGTYP_SHIFT; - - max8997_muic_handle_charger_type(info, chg_type); + /* Handle charger cable */ + ret = max8997_muic_chg_handler(info); + break; + case MAX8997_MUICIRQ_OVP: break; default: dev_info(info->dev, "misc interrupt: irq %d occurred\n", irq_type); - break; + mutex_unlock(&info->mutex); + return; } + if (ret < 0) + dev_err(info->dev, "failed to handle MUIC interrupt\n"); + mutex_unlock(&info->mutex); return; @@ -449,29 +577,49 @@ static irqreturn_t max8997_muic_irq_handler(int irq, void *data) return IRQ_HANDLED; } -static void max8997_muic_detect_dev(struct max8997_muic_info *info) +static int max8997_muic_detect_dev(struct max8997_muic_info *info) { - int ret; - u8 status[2], adc, chg_type; + int ret = 0; + int adc; + int chg_type; + bool attached; - ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, - 2, status); + mutex_lock(&info->mutex); + + /* Read STATUSx register to detect accessory */ + ret = max8997_bulk_read(info->muic, + MAX8997_MUIC_REG_STATUS1, 2, info->status); if (ret) { - dev_err(info->dev, "failed to read muic register\n"); - return; + dev_err(info->dev, "failed to read MUIC register\n"); + mutex_unlock(&info->mutex); + return -EINVAL; } - dev_info(info->dev, "STATUS1:0x%x, STATUS2:0x%x\n", - status[0], status[1]); + adc = max8997_muic_get_cable_type(info, MAX8997_CABLE_GROUP_ADC, + &attached); + if (attached && adc != MAX8997_MUIC_ADC_OPEN) { + ret = max8997_muic_adc_handler(info); + if (ret < 0) { + dev_err(info->dev, "Cannot detect ADC cable\n"); + mutex_unlock(&info->mutex); + return ret; + } + } - adc = status[0] & STATUS1_ADC_MASK; - adc >>= STATUS1_ADC_SHIFT; + chg_type = max8997_muic_get_cable_type(info, MAX8997_CABLE_GROUP_CHG, + &attached); + if (attached && chg_type != MAX8997_CHARGER_TYPE_NONE) { + ret = max8997_muic_chg_handler(info); + if (ret < 0) { + dev_err(info->dev, "Cannot detect charger cable\n"); + mutex_unlock(&info->mutex); + return ret; + } + } - chg_type = status[1] & STATUS2_CHGTYP_MASK; - chg_type >>= STATUS2_CHGTYP_SHIFT; + mutex_unlock(&info->mutex); - max8997_muic_handle_adc(info, adc); - max8997_muic_handle_charger_type(info, chg_type); + return 0; } static int max8997_muic_probe(struct platform_device *pdev) @@ -550,10 +698,16 @@ static int max8997_muic_probe(struct platform_device *pdev) max8997_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); /* Initial device detection */ - max8997_muic_detect_dev(info); + ret = max8997_muic_detect_dev(info); + if (ret < 0) { + dev_err(&pdev->dev, "failed to detect cable type\n"); + goto err_extcon; + } return ret; +err_extcon: + extcon_dev_unregister(info->edev); err_irq: while (--i >= 0) free_irq(muic_irqs[i].virq, info); diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h index cd37a92ccba9..fb465dfbb59e 100644 --- a/include/linux/mfd/max8997-private.h +++ b/include/linux/mfd/max8997-private.h @@ -258,15 +258,6 @@ enum max8997_muic_reg { #define CONTROL3_BTLDSET_MASK (0x3 << CONTROL3_BTLDSET_SHIFT) #define CONTROL3_ADCDBSET_MASK (0x3 << CONTROL3_ADCDBSET_SHIFT) -#define MAX8997_ADC_GROUND 0x00 -#define MAX8997_ADC_MHL 0x01 -#define MAX8997_ADC_JIG_USB_1 0x18 -#define MAX8997_ADC_JIG_USB_2 0x19 -#define MAX8997_ADC_DESKDOCK 0x1a -#define MAX8997_ADC_JIG_UART 0x1c -#define MAX8997_ADC_CARDOCK 0x1d -#define MAX8997_ADC_OPEN 0x1f - enum max8997_haptic_reg { MAX8997_HAPTIC_REG_GENERAL = 0x00, MAX8997_HAPTIC_REG_CONF1 = 0x01, diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h index 1d4a4fe6ac33..65f8d6a3a608 100644 --- a/include/linux/mfd/max8997.h +++ b/include/linux/mfd/max8997.h @@ -78,21 +78,6 @@ struct max8997_regulator_data { struct device_node *reg_node; }; -enum max8997_muic_usb_type { - MAX8997_USB_HOST, - MAX8997_USB_DEVICE, -}; - -enum max8997_muic_charger_type { - MAX8997_CHARGER_TYPE_NONE = 0, - MAX8997_CHARGER_TYPE_USB, - MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT, - MAX8997_CHARGER_TYPE_DEDICATED_CHG, - MAX8997_CHARGER_TYPE_500MA, - MAX8997_CHARGER_TYPE_1A, - MAX8997_CHARGER_TYPE_DEAD_BATTERY = 7, -}; - struct max8997_muic_reg_data { u8 addr; u8 data; -- cgit v1.2.1 From 685dc9a7dbfede28cc4a0fe4e65804194ec04fa5 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 15:04:15 +0900 Subject: extcon: max8997: Set default UART/USB path on probe This patch set default H/W line path according to platfomr data. The MAX8997 MUIC device can possibly set UART/USB or UART_AUX /USB_AUX to internal H/W line path of MUIC device. Namely, only one H/W line is used for two operation. For example, if H/W line path of MAX8997 device set UART/USB, micro usb cable is connected to AP(Application Processor) and if H/W line path set UART_AUX/USB_AUX, micro usb cable is connected to CP(Coprocessor). Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 28 ++++++++++++++++++++++++++-- include/linux/mfd/max8997.h | 7 +++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 35338a090c06..349f65f6a4a4 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -127,6 +127,13 @@ struct max8997_muic_info { struct max8997_muic_platform_data *muic_pdata; enum max8997_muic_charger_type pre_charger_type; + + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + int path_usb; + int path_uart; }; enum { @@ -322,7 +329,7 @@ static int max8997_muic_handle_usb(struct max8997_muic_info *info, int ret = 0; if (usb_type == MAX8997_USB_HOST) { - ret = max8997_muic_set_path(info, CONTROL1_SW_USB, attached); + ret = max8997_muic_set_path(info, info->path_usb, attached); if (ret < 0) { dev_err(info->dev, "failed to update muic register\n"); return ret; @@ -378,7 +385,7 @@ static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, int ret = 0; /* switch to UART */ - ret = max8997_muic_set_path(info, CONTROL1_SW_UART, attached); + ret = max8997_muic_set_path(info, info->path_uart, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); return -EINVAL; @@ -694,6 +701,23 @@ static int max8997_muic_probe(struct platform_device *pdev) } } + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + if (pdata->muic_pdata->path_uart) + info->path_uart = pdata->muic_pdata->path_uart; + else + info->path_uart = CONTROL1_SW_UART; + + if (pdata->muic_pdata->path_usb) + info->path_usb = pdata->muic_pdata->path_usb; + else + info->path_usb = CONTROL1_SW_USB; + + /* Set initial path for UART */ + max8997_muic_set_path(info, info->path_uart, true); + /* Set ADC debounce time */ max8997_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h index 65f8d6a3a608..2d2d67b6a393 100644 --- a/include/linux/mfd/max8997.h +++ b/include/linux/mfd/max8997.h @@ -92,6 +92,13 @@ struct max8997_muic_reg_data { struct max8997_muic_platform_data { struct max8997_muic_reg_data *init_data; int num_init_data; + + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + int path_usb; + int path_uart; }; enum max8997_haptic_motor_type { -- cgit v1.2.1 From af5eb1a13273447c5708cd5425696f3b6f79dd9b Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 15:10:00 +0900 Subject: extcon: max8997: Use workqueue to check cable state after completing boot of platform This patch use delayed workqueue to check cable state after a certain time. If extcon-max8997 driver check cable state during booting of platform, this couldn't send the correct notification of cable state to extcon consumer. Alwasys, this driver should check cable state after the completion of platform initialization Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 45 ++++++++++++++++++++++++++++++++--------- include/linux/mfd/max8997.h | 3 +++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 349f65f6a4a4..e636d950ad6c 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -29,6 +29,7 @@ #include #define DEV_NAME "max8997-muic" +#define DELAY_MS_DEFAULT 20000 /* unit: millisecond */ enum max8997_muic_adc_debounce_time { ADC_DEBOUNCE_TIME_0_5MS = 0, /* 0.5ms */ @@ -128,6 +129,14 @@ struct max8997_muic_info { struct max8997_muic_platform_data *muic_pdata; enum max8997_muic_charger_type pre_charger_type; + /* + * Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + struct delayed_work wq_detcable; + /* * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB * h/w path of COMP2/COMN1 on CONTROL1 register. @@ -629,11 +638,23 @@ static int max8997_muic_detect_dev(struct max8997_muic_info *info) return 0; } +static void max8997_muic_detect_cable_wq(struct work_struct *work) +{ + struct max8997_muic_info *info = container_of(to_delayed_work(work), + struct max8997_muic_info, wq_detcable); + int ret; + + ret = max8997_muic_detect_dev(info); + if (ret < 0) + pr_err("failed to detect cable type\n"); +} + static int max8997_muic_probe(struct platform_device *pdev) { struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent); struct max8997_platform_data *pdata = dev_get_platdata(max8997->dev); struct max8997_muic_info *info; + int delay_jiffies; int ret, i; info = devm_kzalloc(&pdev->dev, sizeof(struct max8997_muic_info), @@ -721,17 +742,23 @@ static int max8997_muic_probe(struct platform_device *pdev) /* Set ADC debounce time */ max8997_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); - /* Initial device detection */ - ret = max8997_muic_detect_dev(info); - if (ret < 0) { - dev_err(&pdev->dev, "failed to detect cable type\n"); - goto err_extcon; - } + /* + * Detect accessory after completing the initialization of platform + * + * - Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + INIT_DELAYED_WORK(&info->wq_detcable, max8997_muic_detect_cable_wq); + if (pdata->muic_pdata->detcable_delay_ms) + delay_jiffies = msecs_to_jiffies(pdata->muic_pdata->detcable_delay_ms); + else + delay_jiffies = msecs_to_jiffies(DELAY_MS_DEFAULT); + schedule_delayed_work(&info->wq_detcable, delay_jiffies); - return ret; + return 0; -err_extcon: - extcon_dev_unregister(info->edev); err_irq: while (--i >= 0) free_irq(muic_irqs[i].virq, info); diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h index 2d2d67b6a393..cf815577bd68 100644 --- a/include/linux/mfd/max8997.h +++ b/include/linux/mfd/max8997.h @@ -93,6 +93,9 @@ struct max8997_muic_platform_data { struct max8997_muic_reg_data *init_data; int num_init_data; + /* Check cable state after certain delay */ + int detcable_delay_ms; + /* * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB * h/w path of COMP2/COMN1 on CONTROL1 register. -- cgit v1.2.1 From def1820d25fa93cf5fca10bf45f22cdb11be41f2 Mon Sep 17 00:00:00 2001 From: "Emilio G. Cota" Date: Wed, 13 Feb 2013 13:47:54 -0500 Subject: vme: add missing put_device() after device_register() fails put_device() must be called after device_register() fails, since device_register() always initializes the refcount on the device structure. Signed-off-by: Emilio G. Cota Signed-off-by: Greg Kroah-Hartman --- drivers/vme/vme.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/vme/vme.c b/drivers/vme/vme.c index 95a9f71d793e..5e6c7d74e19f 100644 --- a/drivers/vme/vme.c +++ b/drivers/vme/vme.c @@ -1376,6 +1376,7 @@ static int __vme_register_driver_bus(struct vme_driver *drv, return 0; err_reg: + put_device(&vdev->dev); kfree(vdev); err_devalloc: list_for_each_entry_safe(vdev, tmp, &drv->devices, drv_list) { -- cgit v1.2.1 From 9c95bb6f25ff802081125f24bf0c756252fa27b2 Mon Sep 17 00:00:00 2001 From: Michael Arndt Date: Sun, 17 Feb 2013 20:31:27 +0100 Subject: w1: ds2482: Added 1-Wire pull-up support to the driver Signed-off-by: Michael Arndt Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/ds2482.c | 51 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/drivers/w1/masters/ds2482.c b/drivers/w1/masters/ds2482.c index 6429b9e9fb82..e033491fe308 100644 --- a/drivers/w1/masters/ds2482.c +++ b/drivers/w1/masters/ds2482.c @@ -51,10 +51,10 @@ * The top 4 bits always read 0. * To write, the top nibble must be the 1's compl. of the low nibble. */ -#define DS2482_REG_CFG_1WS 0x08 -#define DS2482_REG_CFG_SPU 0x04 -#define DS2482_REG_CFG_PPM 0x02 -#define DS2482_REG_CFG_APU 0x01 +#define DS2482_REG_CFG_1WS 0x08 /* 1-wire speed */ +#define DS2482_REG_CFG_SPU 0x04 /* strong pull-up */ +#define DS2482_REG_CFG_PPM 0x02 /* presence pulse masking */ +#define DS2482_REG_CFG_APU 0x01 /* active pull-up */ /** @@ -131,6 +131,17 @@ struct ds2482_data { }; +/** + * Helper to calculate values for configuration register + * @param conf the raw config value + * @return the value w/ complements that can be written to register + */ +static inline u8 ds2482_calculate_config(u8 conf) +{ + return conf | ((~conf & 0x0f) << 4); +} + + /** * Sets the read pointer. * @param pdev The ds2482 client pointer @@ -399,7 +410,7 @@ static u8 ds2482_w1_reset_bus(void *data) /* If the chip did reset since detect, re-config it */ if (err & DS2482_REG_STS_RST) ds2482_send_cmd_data(pdev, DS2482_CMD_WRITE_CONFIG, - 0xF0); + ds2482_calculate_config(0x00)); } mutex_unlock(&pdev->access_lock); @@ -407,6 +418,32 @@ static u8 ds2482_w1_reset_bus(void *data) return retval; } +static u8 ds2482_w1_set_pullup(void *data, int delay) +{ + struct ds2482_w1_chan *pchan = data; + struct ds2482_data *pdev = pchan->pdev; + u8 retval = 1; + + /* if delay is non-zero activate the pullup, + * the strong pullup will be automatically deactivated + * by the master, so do not explicitly deactive it + */ + if (delay) { + /* both waits are crucial, otherwise devices might not be + * powered long enough, causing e.g. a w1_therm sensor to + * provide wrong conversion results + */ + ds2482_wait_1wire_idle(pdev); + /* note: it seems like both SPU and APU have to be set! */ + retval = ds2482_send_cmd_data(pdev, DS2482_CMD_WRITE_CONFIG, + ds2482_calculate_config(DS2482_REG_CFG_SPU | + DS2482_REG_CFG_APU)); + ds2482_wait_1wire_idle(pdev); + } + + return retval; +} + static int ds2482_probe(struct i2c_client *client, const struct i2c_device_id *id) @@ -452,7 +489,8 @@ static int ds2482_probe(struct i2c_client *client, data->w1_count = 8; /* Set all config items to 0 (off) */ - ds2482_send_cmd_data(data, DS2482_CMD_WRITE_CONFIG, 0xF0); + ds2482_send_cmd_data(data, DS2482_CMD_WRITE_CONFIG, + ds2482_calculate_config(0x00)); mutex_init(&data->access_lock); @@ -468,6 +506,7 @@ static int ds2482_probe(struct i2c_client *client, data->w1_ch[idx].w1_bm.touch_bit = ds2482_w1_touch_bit; data->w1_ch[idx].w1_bm.triplet = ds2482_w1_triplet; data->w1_ch[idx].w1_bm.reset_bus = ds2482_w1_reset_bus; + data->w1_ch[idx].w1_bm.set_pullup = ds2482_w1_set_pullup; err = w1_add_master_device(&data->w1_ch[idx].w1_bm); if (err) { -- cgit v1.2.1 From 29e5507ae4ab34397f538f06b7070c81a4e4a2bf Mon Sep 17 00:00:00 2001 From: Michael Arndt Date: Sun, 17 Feb 2013 20:51:20 +0100 Subject: w1: w1_therm: Add force-pullup option for "broken" sensors Signed-off-by: Michael Arndt Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- Documentation/w1/slaves/w1_therm | 13 ++++++++++--- drivers/w1/slaves/w1_therm.c | 11 ++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Documentation/w1/slaves/w1_therm b/Documentation/w1/slaves/w1_therm index 874a8ca93feb..cc62a95e4776 100644 --- a/Documentation/w1/slaves/w1_therm +++ b/Documentation/w1/slaves/w1_therm @@ -34,9 +34,16 @@ currently supported. The driver also doesn't support reduced precision (which would also reduce the conversion time). The module parameter strong_pullup can be set to 0 to disable the -strong pullup or 1 to enable. If enabled the 5V strong pullup will be -enabled when the conversion is taking place provided the master driver -must support the strong pullup (or it falls back to a pullup +strong pullup, 1 to enable autodetection or 2 to force strong pullup. +In case of autodetection, the driver will use the "READ POWER SUPPLY" +command to check if there are pariste powered devices on the bus. +If so, it will activate the master's strong pullup. +In case the detection of parasite devices using this command fails +(seems to be the case with some DS18S20) the strong pullup can +be force-enabled. +If the strong pullup is enabled, the master's strong pullup will be +driven when the conversion is taking place, provided the master driver +does support the strong pullup (or it falls back to a pullup resistor). The DS18b20 temperature sensor specification lists a maximum current draw of 1.5mA and that a 5k pullup resistor is not sufficient. The strong pullup is designed to provide the additional diff --git a/drivers/w1/slaves/w1_therm.c b/drivers/w1/slaves/w1_therm.c index 5ef583d520fa..c1a702f8c803 100644 --- a/drivers/w1/slaves/w1_therm.c +++ b/drivers/w1/slaves/w1_therm.c @@ -41,6 +41,14 @@ MODULE_DESCRIPTION("Driver for 1-wire Dallas network protocol, temperature famil * If it was disabled a parasite powered device might not get the require * current to do a temperature conversion. If it is enabled parasite powered * devices have a better chance of getting the current required. + * In case the parasite power-detection is not working (seems to be the case + * for some DS18S20) the strong pullup can also be forced, regardless of the + * power state of the devices. + * + * Summary of options: + * - strong_pullup = 0 Disable strong pullup completely + * - strong_pullup = 1 Enable automatic strong pullup detection + * - strong_pullup = 2 Force strong pullup */ static int w1_strong_pullup = 1; module_param_named(strong_pullup, w1_strong_pullup, int, 0); @@ -197,7 +205,8 @@ static ssize_t w1_therm_read(struct device *device, continue; /* 750ms strong pullup (or delay) after the convert */ - if (!external_power && w1_strong_pullup) + if (w1_strong_pullup == 2 || + (!external_power && w1_strong_pullup)) w1_next_pullup(dev, tm); w1_write_8(dev, W1_CONVERT_TEMP); -- cgit v1.2.1