From a7da72eeec78b8ce08a99d132b3e269942b977eb Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 18 Jul 2016 16:16:29 +0900 Subject: extcon: adc-jack: Remove the usage of extcon_set_state() This patch removes the usage of extcon_set_state() because it uses the bit masking to change the state of external connectors. The extcon framework should handle the state by extcon_set/get_cable_state_() with extcon id. Signed-off-by: Chanwoo Choi --- include/linux/extcon/extcon-adc-jack.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/extcon/extcon-adc-jack.h b/include/linux/extcon/extcon-adc-jack.h index ac85f2061351..a0e03b13b449 100644 --- a/include/linux/extcon/extcon-adc-jack.h +++ b/include/linux/extcon/extcon-adc-jack.h @@ -20,8 +20,8 @@ /** * struct adc_jack_cond - condition to use an extcon state - * @state: the corresponding extcon state (if 0, this struct * denotes the last adc_jack_cond element among the array) + * @id: the unique id of each external connector * @min_adc: min adc value for this condition * @max_adc: max adc value for this condition * @@ -33,7 +33,7 @@ * because when no adc_jack_cond is met, state = 0 is automatically chosen. */ struct adc_jack_cond { - u32 state; /* extcon state value. 0 if invalid */ + unsigned int id; u32 min_adc; u32 max_adc; }; -- cgit v1.2.3 From 912465bcf869660900cf77c4761869048f3ff063 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Fri, 1 Jul 2016 02:41:18 +0900 Subject: extcon: Block the bit masking operation for cable state except for extcon core This patch restrict the usage of extcon_update_state() in the extcon core because the extcon_update_state() use the bit masking to change the state of external connector. When this function is used in device drivers, it may occur the probelm with the handling mistake of bit masking. Also, this patch removes the extcon_get/set_state() functions because these functions use the bit masking which is reluctant way. Instead, extcon provides the extcon_set/get_cable_state_() functions. Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon.c | 20 +------------------- include/linux/extcon.h | 30 ------------------------------ 2 files changed, 1 insertion(+), 49 deletions(-) (limited to 'include/linux') diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index ad5e4546f82c..5b61000cde26 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -224,7 +224,7 @@ static ssize_t cable_state_show(struct device *dev, * Note that the notifier provides which bits are changed in the state * variable with the val parameter (second) to the callback. */ -int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) +static int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) { char name_buf[120]; char state_buf[120]; @@ -300,24 +300,6 @@ int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) return 0; } -EXPORT_SYMBOL_GPL(extcon_update_state); - -/** - * extcon_set_state() - Set the cable attach states of the extcon device. - * @edev: the extcon device - * @state: new cable attach status for @edev - * - * Note that notifier provides which bits are changed in the state - * variable with the val parameter (second) to the callback. - */ -int extcon_set_state(struct extcon_dev *edev, u32 state) -{ - if (!edev) - return -EINVAL; - - return extcon_update_state(edev, 0xffffffff, state); -} -EXPORT_SYMBOL_GPL(extcon_set_state); /** * extcon_get_cable_state_() - Get the status of a specific cable. diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 61004413dc64..667b1d35af12 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -149,20 +149,6 @@ extern struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, const unsigned int *cable); extern void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev); -/* - * get/set/update_state access the 32b encoded state value, which represents - * states of all possible cables of the multistate port. For example, if one - * calls extcon_set_state(edev, 0x7), it may mean that all the three cables - * are attached to the port. - */ -static inline u32 extcon_get_state(struct extcon_dev *edev) -{ - return edev->state; -} - -extern int extcon_set_state(struct extcon_dev *edev, u32 state); -extern int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state); - /* * get/set_cable_state access each bit of the 32b encoded state value. * They are used to access the status of each cable based on the cable id. @@ -232,22 +218,6 @@ static inline struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, static inline void devm_extcon_dev_free(struct extcon_dev *edev) { } -static inline u32 extcon_get_state(struct extcon_dev *edev) -{ - return 0; -} - -static inline int extcon_set_state(struct extcon_dev *edev, u32 state) -{ - return 0; -} - -static inline int extcon_update_state(struct extcon_dev *edev, u32 mask, - u32 state) -{ - return 0; -} - static inline int extcon_get_cable_state_(struct extcon_dev *edev, unsigned int id) { -- cgit v1.2.3 From 55e4e2f129c6664c14166a30f4e0e933ebb61d9b Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 11 Jul 2016 16:34:52 +0900 Subject: extcon: Add the extcon_type to gather each connector into five category This patch adds the new extcon type to group the each connecotr into following five category. This type would be used to handle the connectors as a group unit instead of a connector unit. - EXTCON_TYPE_USB : USB connector - EXTCON_TYPE_CHG : Charger connector - EXTCON_TYPE_JACK : Jack connector - EXTCON_TYPE_DISP : Display connector - EXTCON_TYPE_MISC : Miscellaneous connector Also, each external connector is possible to belong to one more extcon type. In caes of EXTCON_CHG_USB_SDP, it have the EXTCON_TYPE_CHG and EXTCON_TYPE_USB. Signed-off-by: Chanwoo Choi Tested-by: Chris Zhong Tested-by: Guenter Roeck Signed-off-by: MyungJoo Ham Reviewed-by: Guenter Roeck --- drivers/extcon/extcon.c | 159 +++++++++++++++++++++++++++++++++++++++--------- include/linux/extcon.h | 9 +++ 2 files changed, 139 insertions(+), 29 deletions(-) (limited to 'include/linux') diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 9a266e5c7e10..f209a6959fed 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -38,43 +38,144 @@ #define SUPPORTED_CABLE_MAX 32 #define CABLE_NAME_MAX 30 -static const char *extcon_name[] = { - [EXTCON_NONE] = "NONE", +struct __extcon_info { + unsigned int type; + unsigned int id; + const char *name; + +} extcon_info[] = { + [EXTCON_NONE] = { + .type = EXTCON_TYPE_MISC, + .id = EXTCON_NONE, + .name = "NONE", + }, /* USB external connector */ - [EXTCON_USB] = "USB", - [EXTCON_USB_HOST] = "USB-HOST", + [EXTCON_USB] = { + .type = EXTCON_TYPE_USB, + .id = EXTCON_USB, + .name = "USB", + }, + [EXTCON_USB_HOST] = { + .type = EXTCON_TYPE_USB, + .id = EXTCON_USB_HOST, + .name = "USB_HOST", + }, /* Charging external connector */ - [EXTCON_CHG_USB_SDP] = "SDP", - [EXTCON_CHG_USB_DCP] = "DCP", - [EXTCON_CHG_USB_CDP] = "CDP", - [EXTCON_CHG_USB_ACA] = "ACA", - [EXTCON_CHG_USB_FAST] = "FAST-CHARGER", - [EXTCON_CHG_USB_SLOW] = "SLOW-CHARGER", + [EXTCON_CHG_USB_SDP] = { + .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, + .id = EXTCON_CHG_USB_SDP, + .name = "SDP", + }, + [EXTCON_CHG_USB_DCP] = { + .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, + .id = EXTCON_CHG_USB_DCP, + .name = "DCP", + }, + [EXTCON_CHG_USB_CDP] = { + .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, + .id = EXTCON_CHG_USB_CDP, + .name = "CDP", + }, + [EXTCON_CHG_USB_ACA] = { + .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, + .id = EXTCON_CHG_USB_ACA, + .name = "ACA", + }, + [EXTCON_CHG_USB_FAST] = { + .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, + .id = EXTCON_CHG_USB_FAST, + .name = "FAST-CHARGER", + }, + [EXTCON_CHG_USB_SLOW] = { + .type = EXTCON_TYPE_CHG | EXTCON_TYPE_USB, + .id = EXTCON_CHG_USB_SLOW, + .name = "SLOW-CHARGER", + }, /* Jack external connector */ - [EXTCON_JACK_MICROPHONE] = "MICROPHONE", - [EXTCON_JACK_HEADPHONE] = "HEADPHONE", - [EXTCON_JACK_LINE_IN] = "LINE-IN", - [EXTCON_JACK_LINE_OUT] = "LINE-OUT", - [EXTCON_JACK_VIDEO_IN] = "VIDEO-IN", - [EXTCON_JACK_VIDEO_OUT] = "VIDEO-OUT", - [EXTCON_JACK_SPDIF_IN] = "SPDIF-IN", - [EXTCON_JACK_SPDIF_OUT] = "SPDIF-OUT", + [EXTCON_JACK_MICROPHONE] = { + .type = EXTCON_TYPE_JACK, + .id = EXTCON_JACK_MICROPHONE, + .name = "MICROPHONE", + }, + [EXTCON_JACK_HEADPHONE] = { + .type = EXTCON_TYPE_JACK, + .id = EXTCON_JACK_HEADPHONE, + .name = "HEADPHONE", + }, + [EXTCON_JACK_LINE_IN] = { + .type = EXTCON_TYPE_JACK, + .id = EXTCON_JACK_LINE_IN, + .name = "LINE-IN", + }, + [EXTCON_JACK_LINE_OUT] = { + .type = EXTCON_TYPE_JACK, + .id = EXTCON_JACK_LINE_OUT, + .name = "LINE-OUT", + }, + [EXTCON_JACK_VIDEO_IN] = { + .type = EXTCON_TYPE_JACK, + .id = EXTCON_JACK_VIDEO_IN, + .name = "VIDEO-IN", + }, + [EXTCON_JACK_VIDEO_OUT] = { + .type = EXTCON_TYPE_JACK, + .id = EXTCON_JACK_VIDEO_OUT, + .name = "VIDEO-OUT", + }, + [EXTCON_JACK_SPDIF_IN] = { + .type = EXTCON_TYPE_JACK, + .id = EXTCON_JACK_SPDIF_IN, + .name = "SPDIF-IN", + }, + [EXTCON_JACK_SPDIF_OUT] = { + .type = EXTCON_TYPE_JACK, + .id = EXTCON_JACK_SPDIF_OUT, + .name = "SPDIF-OUT", + }, /* Display external connector */ - [EXTCON_DISP_HDMI] = "HDMI", - [EXTCON_DISP_MHL] = "MHL", - [EXTCON_DISP_DVI] = "DVI", - [EXTCON_DISP_VGA] = "VGA", + [EXTCON_DISP_HDMI] = { + .type = EXTCON_TYPE_DISP, + .id = EXTCON_DISP_HDMI, + .name = "HDMI", + }, + [EXTCON_DISP_MHL] = { + .type = EXTCON_TYPE_DISP, + .id = EXTCON_DISP_MHL, + .name = "MHL", + }, + [EXTCON_DISP_DVI] = { + .type = EXTCON_TYPE_DISP, + .id = EXTCON_DISP_DVI, + .name = "DVI", + }, + [EXTCON_DISP_VGA] = { + .type = EXTCON_TYPE_DISP, + .id = EXTCON_DISP_VGA, + .name = "VGA", + }, /* Miscellaneous external connector */ - [EXTCON_DOCK] = "DOCK", - [EXTCON_JIG] = "JIG", - [EXTCON_MECHANICAL] = "MECHANICAL", - - NULL, + [EXTCON_DOCK] = { + .type = EXTCON_TYPE_MISC, + .id = EXTCON_DOCK, + .name = "DOCK", + }, + [EXTCON_JIG] = { + .type = EXTCON_TYPE_MISC, + .id = EXTCON_JIG, + .name = "JIG", + }, + [EXTCON_MECHANICAL] = { + .type = EXTCON_TYPE_MISC, + .id = EXTCON_MECHANICAL, + .name = "MECHANICAL", + }, + + { /* sentinel */ } }; /** @@ -168,7 +269,7 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr, for (i = 0; i < edev->max_supported; i++) { count += sprintf(buf + count, "%s=%d\n", - extcon_name[edev->supported_cable[i]], + extcon_info[edev->supported_cable[i]].name, !!(edev->state & (1 << i))); } @@ -193,7 +294,7 @@ static ssize_t cable_name_show(struct device *dev, int i = cable->cable_index; return sprintf(buf, "%s\n", - extcon_name[cable->edev->supported_cable[i]]); + extcon_info[cable->edev->supported_cable[i]].name); } static ssize_t cable_state_show(struct device *dev, diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 667b1d35af12..46d802892c82 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -28,6 +28,15 @@ #include +/* + * Define the type of supported external connectors + */ +#define EXTCON_TYPE_USB BIT(0) /* USB connector */ +#define EXTCON_TYPE_CHG BIT(1) /* Charger connector */ +#define EXTCON_TYPE_JACK BIT(2) /* Jack connector */ +#define EXTCON_TYPE_DISP BIT(3) /* Display connector */ +#define EXTCON_TYPE_MISC BIT(4) /* Miscellaneous connector */ + /* * Define the unique id of supported external connectors */ -- cgit v1.2.3 From 792e7e9e5d4358bc6157152b2c07b94eb9e261b0 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 11 Jul 2016 19:30:43 +0900 Subject: extcon: Add the support for extcon property according to extcon type This patch support the extcon property for the external connector because each external connector might have the property according to the H/W design and the specific characteristics. - EXTCON_PROP_USB_[property name] - EXTCON_PROP_CHG_[property name] - EXTCON_PROP_JACK_[property name] - EXTCON_PROP_DISP_[property name] Add the new extcon APIs to get/set the property value as following: - int extcon_get_property(struct extcon_dev *edev, unsigned int id, unsigned int prop, union extcon_property_value *prop_val) - int extcon_set_property(struct extcon_dev *edev, unsigned int id, unsigned int prop, union extcon_property_value prop_val) Signed-off-by: Chanwoo Choi Tested-by: Chris Zhong Tested-by: Guenter Roeck Reviewed-by: Guenter Roeck --- drivers/extcon/extcon.c | 201 +++++++++++++++++++++++++++++++++++++++++++++++- include/linux/extcon.h | 81 +++++++++++++++++++ 2 files changed, 281 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index f209a6959fed..37fa4b51c5a1 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -196,6 +196,11 @@ struct extcon_cable { struct device_attribute attr_state; struct attribute *attrs[3]; /* to be fed to attr_g.attrs */ + + union extcon_property_value usb_propval[EXTCON_PROP_USB_CNT]; + union extcon_property_value chg_propval[EXTCON_PROP_CHG_CNT]; + union extcon_property_value jack_propval[EXTCON_PROP_JACK_CNT]; + union extcon_property_value disp_propval[EXTCON_PROP_DISP_CNT]; }; static struct class *extcon_class; @@ -248,6 +253,27 @@ static int find_cable_index_by_id(struct extcon_dev *edev, const unsigned int id return -EINVAL; } +static int get_extcon_type(unsigned int prop) +{ + switch (prop) { + case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX: + return EXTCON_TYPE_USB; + case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX: + return EXTCON_TYPE_CHG; + case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX: + return EXTCON_TYPE_JACK; + case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX: + return EXTCON_TYPE_DISP; + default: + return -EINVAL; + } +} + +static bool is_extcon_attached(struct extcon_dev *edev, unsigned int index) +{ + return !!(edev->state & BIT(index)); +} + static bool is_extcon_changed(u32 prev, u32 new, int idx, bool *attached) { if (((prev >> idx) & 0x1) != ((new >> idx) & 0x1)) { @@ -258,6 +284,34 @@ static bool is_extcon_changed(u32 prev, u32 new, int idx, bool *attached) return false; } +static bool is_extcon_property_supported(unsigned int id, unsigned int prop) +{ + int type; + + /* Check whether the property is supported or not. */ + type = get_extcon_type(prop); + if (type < 0) + return false; + + /* Check whether a specific extcon id supports the property or not. */ + return !!(extcon_info[id].type & type); +} + +static void init_property(struct extcon_dev *edev, unsigned int id, int index) +{ + unsigned int type = extcon_info[id].type; + struct extcon_cable *cable = &edev->cables[index]; + + if (EXTCON_TYPE_USB & type) + memset(cable->usb_propval, 0, sizeof(cable->usb_propval)); + if (EXTCON_TYPE_CHG & type) + memset(cable->chg_propval, 0, sizeof(cable->chg_propval)); + if (EXTCON_TYPE_JACK & type) + memset(cable->jack_propval, 0, sizeof(cable->jack_propval)); + if (EXTCON_TYPE_DISP & type) + memset(cable->disp_propval, 0, sizeof(cable->disp_propval)); +} + static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -421,7 +475,7 @@ int extcon_get_cable_state_(struct extcon_dev *edev, const unsigned int id) if (edev->max_supported && edev->max_supported <= index) return -EINVAL; - return !!(edev->state & (1 << index)); + return is_extcon_attached(edev, index); } EXPORT_SYMBOL_GPL(extcon_get_cable_state_); @@ -449,11 +503,156 @@ int extcon_set_cable_state_(struct extcon_dev *edev, unsigned int id, if (edev->max_supported && edev->max_supported <= index) return -EINVAL; + /* + * Initialize the value of extcon property before setting + * the detached state for an external connector. + */ + if (!cable_state) + init_property(edev, id, index); + state = cable_state ? (1 << index) : 0; return extcon_update_state(edev, 1 << index, state); } EXPORT_SYMBOL_GPL(extcon_set_cable_state_); +/** + * extcon_get_property() - Get the property value of a specific cable. + * @edev: the extcon device that has the cable. + * @id: the unique id of each external connector + * in extcon enumeration. + * @prop: the property id among enum extcon_property. + * @prop_val: the pointer which store the value of property. + * + * When getting the property value of external connector, the external connector + * should be attached. If detached state, function just return 0 without + * property value. Also, the each property should be included in the list of + * supported properties according to the type of external connectors. + * + * Returns 0 if success or error number if fail + */ +int extcon_get_property(struct extcon_dev *edev, unsigned int id, + unsigned int prop, + union extcon_property_value *prop_val) +{ + struct extcon_cable *cable; + unsigned long flags; + int index, ret = 0; + + *prop_val = (union extcon_property_value)(0); + + if (!edev) + return -EINVAL; + + /* Check whether the property is supported or not */ + if (!is_extcon_property_supported(id, prop)) + return -EINVAL; + + /* Find the cable index of external connector by using id */ + index = find_cable_index_by_id(edev, id); + if (index < 0) + return index; + + spin_lock_irqsave(&edev->lock, flags); + + /* + * Check whether the external connector is attached. + * If external connector is detached, the user can not + * get the property value. + */ + if (!is_extcon_attached(edev, index)) { + spin_unlock_irqrestore(&edev->lock, flags); + return 0; + } + + cable = &edev->cables[index]; + + /* Get the property value according to extcon type */ + switch (prop) { + case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX: + *prop_val = cable->usb_propval[prop - EXTCON_PROP_USB_MIN]; + break; + case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX: + *prop_val = cable->chg_propval[prop - EXTCON_PROP_CHG_MIN]; + break; + case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX: + *prop_val = cable->jack_propval[prop - EXTCON_PROP_JACK_MIN]; + break; + case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX: + *prop_val = cable->disp_propval[prop - EXTCON_PROP_DISP_MIN]; + break; + default: + ret = -EINVAL; + break; + } + + spin_unlock_irqrestore(&edev->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(extcon_get_property); + +/** + * extcon_set_property() - Set the property value of a specific cable. + * @edev: the extcon device that has the cable. + * @id: the unique id of each external connector + * in extcon enumeration. + * @prop: the property id among enum extcon_property. + * @prop_val: the pointer including the new value of property. + * + * The each property should be included in the list of supported properties + * according to the type of external connectors. + * + * Returns 0 if success or error number if fail + */ +int extcon_set_property(struct extcon_dev *edev, unsigned int id, + unsigned int prop, + union extcon_property_value prop_val) +{ + struct extcon_cable *cable; + unsigned long flags; + int index, ret = 0; + + if (!edev) + return -EINVAL; + + /* Check whether the property is supported or not */ + if (!is_extcon_property_supported(id, prop)) + return -EINVAL; + + /* Find the cable index of external connector by using id */ + index = find_cable_index_by_id(edev, id); + if (index < 0) + return index; + + spin_lock_irqsave(&edev->lock, flags); + + cable = &edev->cables[index]; + + /* Set the property value according to extcon type */ + switch (prop) { + case EXTCON_PROP_USB_MIN ... EXTCON_PROP_USB_MAX: + cable->usb_propval[prop - EXTCON_PROP_USB_MIN] = prop_val; + break; + case EXTCON_PROP_CHG_MIN ... EXTCON_PROP_CHG_MAX: + cable->chg_propval[prop - EXTCON_PROP_CHG_MIN] = prop_val; + break; + case EXTCON_PROP_JACK_MIN ... EXTCON_PROP_JACK_MAX: + cable->jack_propval[prop - EXTCON_PROP_JACK_MIN] = prop_val; + break; + case EXTCON_PROP_DISP_MIN ... EXTCON_PROP_DISP_MAX: + cable->disp_propval[prop - EXTCON_PROP_DISP_MIN] = prop_val; + break; + default: + ret = -EINVAL; + break; + } + + spin_unlock_irqrestore(&edev->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(extcon_set_property); + /** * extcon_get_extcon_dev() - Get the extcon device instance from the name * @extcon_name: The extcon name provided with extcon_dev_register() diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 46d802892c82..f9d4a44e86d3 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -77,6 +77,63 @@ #define EXTCON_NUM 63 +/* + * Define the property of supported external connectors. + * + * When adding the new extcon property, they *must* have + * the type/value/default information. Also, you *have to* + * modify the EXTCON_PROP_[type]_START/END definitions + * which mean the range of the supported properties + * for each extcon type. + * + * The naming style of property + * : EXTCON_PROP_[type]_[property name] + * + * EXTCON_PROP_USB_[property name] : USB property + * EXTCON_PROP_CHG_[property name] : Charger property + * EXTCON_PROP_JACK_[property name] : Jack property + * EXTCON_PROP_DISP_[property name] : Display property + */ + +/* + * Properties of EXTCON_TYPE_USB. + * + * - EXTCON_PROP_USB_VBUS + * @type: integer (intval) + * @value: 0 (low) or 1 (high) + * @default: 0 (low) + */ +#define EXTCON_PROP_USB_VBUS 0 + +#define EXTCON_PROP_USB_MIN 0 +#define EXTCON_PROP_USB_MAX 0 +#define EXTCON_PROP_USB_CNT (EXTCON_PROP_USB_MAX - EXTCON_PROP_USB_MIN + 1) + +/* Properties of EXTCON_TYPE_CHG. */ +#define EXTCON_PROP_CHG_MIN 50 +#define EXTCON_PROP_CHG_MAX 50 +#define EXTCON_PROP_CHG_CNT (EXTCON_PROP_CHG_MAX - EXTCON_PROP_CHG_MIN + 1) + +/* Properties of EXTCON_TYPE_JACK. */ +#define EXTCON_PROP_JACK_MIN 100 +#define EXTCON_PROP_JACK_MAX 100 +#define EXTCON_PROP_JACK_CNT (EXTCON_PROP_JACK_MAX - EXTCON_PROP_JACK_MIN + 1) + +/* Properties of EXTCON_TYPE_DISP. */ +#define EXTCON_PROP_DISP_MIN 150 +#define EXTCON_PROP_DISP_MAX 150 +#define EXTCON_PROP_DISP_CNT (EXTCON_PROP_DISP_MAX - EXTCON_PROP_DISP_MIN + 1) + +/* + * Define the type of property's value. + * + * Define the property's value as union type. Because each property + * would need the different data type to store it. + */ +union extcon_property_value { + int intval; /* type : integer (intval) */ +}; + struct extcon_cable; /** @@ -166,6 +223,17 @@ extern int extcon_get_cable_state_(struct extcon_dev *edev, unsigned int id); extern int extcon_set_cable_state_(struct extcon_dev *edev, unsigned int id, bool cable_state); +/* + * get/set_property access the property value of each external connector. + * They are used to access the property of each cable based on the property id. + */ +extern int extcon_get_property(struct extcon_dev *edev, unsigned int id, + unsigned int prop, + union extcon_property_value *prop_val); +extern int extcon_set_property(struct extcon_dev *edev, unsigned int id, + unsigned int prop, + union extcon_property_value prop_val); + /* * Following APIs are to monitor every action of a notifier. * Registrar gets notified for every external port of a connection device. @@ -239,6 +307,19 @@ static inline int extcon_set_cable_state_(struct extcon_dev *edev, return 0; } +static inline int extcon_get_property(struct extcon_dev *edev, unsigned int id, + unsigned int prop, + union extcon_property_value *prop_val) +{ + return 0; +} +static inline int extcon_set_property(struct extcon_dev *edev, unsigned int id, + unsigned int prop, + union extcon_property_value prop_val) +{ + return 0; +} + static inline struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name) { return NULL; -- cgit v1.2.3 From 7f2a0a1699b51bfd738f1e0b15e057996fe1f259 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 25 Jul 2016 21:15:19 +0900 Subject: extcon: Add the support for the capability of each property This patch adds the support of the property capability setting. This function decides the supported properties of each external connector on extcon provider driver. Ths list of new extcon APIs to get/set the capability of property as following: - int extcon_get_property_capability(struct extcon_dev *edev, unsigned int id, unsigned int prop); - int extcon_set_property_capability(struct extcon_dev *edev, unsigned int id, unsigned int prop); Signed-off-by: Chanwoo Choi Tested-by: Chris Zhong Tested-by: Guenter Roeck Reviewed-by: Guenter Roeck --- drivers/extcon/extcon.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/extcon.h | 22 ++++++++ 2 files changed, 162 insertions(+) (limited to 'include/linux') diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 37fa4b51c5a1..599fc377a965 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -201,6 +201,11 @@ struct extcon_cable { union extcon_property_value chg_propval[EXTCON_PROP_CHG_CNT]; union extcon_property_value jack_propval[EXTCON_PROP_JACK_CNT]; union extcon_property_value disp_propval[EXTCON_PROP_DISP_CNT]; + + unsigned long usb_bits[BITS_TO_LONGS(EXTCON_PROP_USB_CNT)]; + unsigned long chg_bits[BITS_TO_LONGS(EXTCON_PROP_CHG_CNT)]; + unsigned long jack_bits[BITS_TO_LONGS(EXTCON_PROP_JACK_CNT)]; + unsigned long disp_bits[BITS_TO_LONGS(EXTCON_PROP_DISP_CNT)]; }; static struct class *extcon_class; @@ -297,6 +302,39 @@ static bool is_extcon_property_supported(unsigned int id, unsigned int prop) return !!(extcon_info[id].type & type); } +static int is_extcon_property_capability(struct extcon_dev *edev, + unsigned int id, int index,unsigned int prop) +{ + struct extcon_cable *cable; + int type, ret; + + /* Check whether the property is supported or not. */ + type = get_extcon_type(prop); + if (type < 0) + return type; + + cable = &edev->cables[index]; + + switch (type) { + case EXTCON_TYPE_USB: + ret = test_bit(prop - EXTCON_PROP_USB_MIN, cable->usb_bits); + break; + case EXTCON_TYPE_CHG: + ret = test_bit(prop - EXTCON_PROP_CHG_MIN, cable->chg_bits); + break; + case EXTCON_TYPE_JACK: + ret = test_bit(prop - EXTCON_PROP_JACK_MIN, cable->jack_bits); + break; + case EXTCON_TYPE_DISP: + ret = test_bit(prop - EXTCON_PROP_DISP_MIN, cable->disp_bits); + break; + default: + ret = -EINVAL; + } + + return ret; +} + static void init_property(struct extcon_dev *edev, unsigned int id, int index) { unsigned int type = extcon_info[id].type; @@ -554,6 +592,12 @@ int extcon_get_property(struct extcon_dev *edev, unsigned int id, spin_lock_irqsave(&edev->lock, flags); + /* Check whether the property is available or not. */ + if (!is_extcon_property_capability(edev, id, index, prop)) { + spin_unlock_irqrestore(&edev->lock, flags); + return -EPERM; + } + /* * Check whether the external connector is attached. * If external connector is detached, the user can not @@ -626,6 +670,12 @@ int extcon_set_property(struct extcon_dev *edev, unsigned int id, spin_lock_irqsave(&edev->lock, flags); + /* Check whether the property is available or not. */ + if (!is_extcon_property_capability(edev, id, index, prop)) { + spin_unlock_irqrestore(&edev->lock, flags); + return -EPERM; + } + cable = &edev->cables[index]; /* Set the property value according to extcon type */ @@ -653,6 +703,96 @@ int extcon_set_property(struct extcon_dev *edev, unsigned int id, } EXPORT_SYMBOL_GPL(extcon_set_property); +/** + * extcon_get_property_capability() - Get the capability of property + * of an external connector. + * @edev: the extcon device that has the cable. + * @id: the unique id of each external connector + * in extcon enumeration. + * @prop: the property id among enum extcon_property. + * + * Returns 1 if the property is available or 0 if not available. + */ +int extcon_get_property_capability(struct extcon_dev *edev, unsigned int id, + unsigned int prop) +{ + int index; + + if (!edev) + return -EINVAL; + + /* Check whether the property is supported or not */ + if (!is_extcon_property_supported(id, prop)) + return -EINVAL; + + /* Find the cable index of external connector by using id */ + index = find_cable_index_by_id(edev, id); + if (index < 0) + return index; + + return is_extcon_property_capability(edev, id, index, prop); +} +EXPORT_SYMBOL_GPL(extcon_get_property_capability); + +/** + * extcon_set_property_capability() - Set the capability of a property + * of an external connector. + * @edev: the extcon device that has the cable. + * @id: the unique id of each external connector + * in extcon enumeration. + * @prop: the property id among enum extcon_property. + * + * This function set the capability of a property for an external connector + * to mark the bit in capability bitmap which mean the available state of + * a property. + * + * Returns 0 if success or error number if fail + */ +int extcon_set_property_capability(struct extcon_dev *edev, unsigned int id, + unsigned int prop) +{ + struct extcon_cable *cable; + int index, type, ret = 0; + + if (!edev) + return -EINVAL; + + /* Check whether the property is supported or not. */ + if (!is_extcon_property_supported(id, prop)) + return -EINVAL; + + /* Find the cable index of external connector by using id. */ + index = find_cable_index_by_id(edev, id); + if (index < 0) + return index; + + type = get_extcon_type(prop); + if (type < 0) + return type; + + cable = &edev->cables[index]; + + switch (type) { + case EXTCON_TYPE_USB: + __set_bit(prop - EXTCON_PROP_USB_MIN, cable->usb_bits); + break; + case EXTCON_TYPE_CHG: + __set_bit(prop - EXTCON_PROP_CHG_MIN, cable->chg_bits); + break; + case EXTCON_TYPE_JACK: + __set_bit(prop - EXTCON_PROP_JACK_MIN, cable->jack_bits); + break; + case EXTCON_TYPE_DISP: + __set_bit(prop - EXTCON_PROP_DISP_MIN, cable->disp_bits); + break; + default: + ret = -EINVAL; + } + + return ret; +} +EXPORT_SYMBOL_GPL(extcon_set_property_capability); + /** * extcon_get_extcon_dev() - Get the extcon device instance from the name * @extcon_name: The extcon name provided with extcon_dev_register() diff --git a/include/linux/extcon.h b/include/linux/extcon.h index f9d4a44e86d3..f08469089f74 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -234,6 +234,16 @@ extern int extcon_set_property(struct extcon_dev *edev, unsigned int id, unsigned int prop, union extcon_property_value prop_val); +/* + * get/set_property_capability set the capability of the property for each + * external connector. They are used to set the capability of the property + * of each external connector based on the id and property. + */ +extern int extcon_get_property_capability(struct extcon_dev *edev, + unsigned int id, unsigned int prop); +extern int extcon_set_property_capability(struct extcon_dev *edev, + unsigned int id, unsigned int prop); + /* * Following APIs are to monitor every action of a notifier. * Registrar gets notified for every external port of a connection device. @@ -320,6 +330,18 @@ static inline int extcon_set_property(struct extcon_dev *edev, unsigned int id, return 0; } +static inline int extcon_get_property_capability(struct extcon_dev *edev, + unsigned int id, unsigned int prop) +{ + return 0; +} + +static inline int extcon_set_property_capability(struct extcon_dev *edev, + unsigned int id, unsigned int prop) +{ + return 0; +} + static inline struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name) { return NULL; -- cgit v1.2.3 From 575c2b867ee0c2affdd309f375c032c0c7dc219c Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Fri, 22 Jul 2016 13:03:17 +0900 Subject: extcon: Rename the extcon_set/get_state() to maintain the function naming pattern This patch just renames the existing extcon_get/set_cable_state_() as following because of maintaining the function naming pattern like as extcon APIs for property. - extcon_set_cable_state_() -> extcon_set_state() - extcon_get_cable_state_() -> extcon_get_state() But, this patch remains the old extcon_set/get_cable_state_() functions to prevent the build break. After altering new APIs, remove the old APIs. Signed-off-by: Chanwoo Choi Tested-by: Chris Zhong Tested-by: Guenter Roeck Reviewed-by: Guenter Roeck --- drivers/extcon/extcon.c | 28 +++++++++++++--------------- include/linux/extcon.h | 25 ++++++++++++++++++------- 2 files changed, 31 insertions(+), 22 deletions(-) (limited to 'include/linux') diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 599fc377a965..d66adfd21159 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -398,8 +398,7 @@ static ssize_t cable_state_show(struct device *dev, int i = cable->cable_index; return sprintf(buf, "%d\n", - extcon_get_cable_state_(cable->edev, - cable->edev->supported_cable[i])); + extcon_get_state(cable->edev, cable->edev->supported_cable[i])); } /** @@ -495,13 +494,14 @@ static int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) } /** - * extcon_get_cable_state_() - Get the status of a specific cable. + * extcon_get_state() - Get the state of a external connector. * @edev: the extcon device that has the cable. * @id: the unique id of each external connector in extcon enumeration. */ -int extcon_get_cable_state_(struct extcon_dev *edev, const unsigned int id) +int extcon_get_state(struct extcon_dev *edev, const unsigned int id) { - int index; + int index, state; + unsigned long flags; if (!edev) return -EINVAL; @@ -510,22 +510,23 @@ int extcon_get_cable_state_(struct extcon_dev *edev, const unsigned int id) if (index < 0) return index; - if (edev->max_supported && edev->max_supported <= index) - return -EINVAL; + spin_lock_irqsave(&edev->lock, flags); + state = is_extcon_attached(edev, index); + spin_unlock_irqrestore(&edev->lock, flags); - return is_extcon_attached(edev, index); + return state; } -EXPORT_SYMBOL_GPL(extcon_get_cable_state_); +EXPORT_SYMBOL_GPL(extcon_get_state); /** - * extcon_set_cable_state_() - Set the status of a specific cable. + * extcon_set_state() - Set the state of a external connector. * @edev: the extcon device that has the cable. * @id: the unique id of each external connector * in extcon enumeration. * @state: the new cable status. The default semantics is * true: attached / false: detached. */ -int extcon_set_cable_state_(struct extcon_dev *edev, unsigned int id, +int extcon_set_state(struct extcon_dev *edev, unsigned int id, bool cable_state) { u32 state; @@ -538,9 +539,6 @@ int extcon_set_cable_state_(struct extcon_dev *edev, unsigned int id, if (index < 0) return index; - if (edev->max_supported && edev->max_supported <= index) - return -EINVAL; - /* * Initialize the value of extcon property before setting * the detached state for an external connector. @@ -551,7 +549,7 @@ int extcon_set_cable_state_(struct extcon_dev *edev, unsigned int id, state = cable_state ? (1 << index) : 0; return extcon_update_state(edev, 1 << index, state); } -EXPORT_SYMBOL_GPL(extcon_set_cable_state_); +EXPORT_SYMBOL_GPL(extcon_set_state); /** * extcon_get_property() - Get the property value of a specific cable. diff --git a/include/linux/extcon.h b/include/linux/extcon.h index f08469089f74..4fa37385c97a 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -216,11 +216,11 @@ extern struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, extern void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev); /* - * get/set_cable_state access each bit of the 32b encoded state value. + * get/set_state access each bit of the 32b encoded state value. * They are used to access the status of each cable based on the cable id. */ -extern int extcon_get_cable_state_(struct extcon_dev *edev, unsigned int id); -extern int extcon_set_cable_state_(struct extcon_dev *edev, unsigned int id, +extern int extcon_get_state(struct extcon_dev *edev, unsigned int id); +extern int extcon_set_state(struct extcon_dev *edev, unsigned int id, bool cable_state); /* @@ -305,14 +305,14 @@ static inline struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, static inline void devm_extcon_dev_free(struct extcon_dev *edev) { } -static inline int extcon_get_cable_state_(struct extcon_dev *edev, - unsigned int id) + +static inline int extcon_get_state(struct extcon_dev *edev, unsigned int id) { return 0; } -static inline int extcon_set_cable_state_(struct extcon_dev *edev, - unsigned int id, bool cable_state) +static inline int extcon_set_state(struct extcon_dev *edev, unsigned int id, + bool cable_state) { return 0; } @@ -402,4 +402,15 @@ static inline int extcon_unregister_interest(struct extcon_specific_cable_nb { return -EINVAL; } + +static inline int extcon_get_cable_state_(struct extcon_dev *edev, unsigned int id) +{ + return extcon_get_state(edev, id); +} + +static inline int extcon_set_cable_state_(struct extcon_dev *edev, unsigned int id, + bool cable_state) +{ + return extcon_set_state(edev, id, cable_state); +} #endif /* __LINUX_EXTCON_H__ */ -- cgit v1.2.3 From ab11af049f88f059a73f679fb050bd7abb98d24b Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Fri, 22 Jul 2016 13:16:34 +0900 Subject: extcon: Add the synchronization extcon APIs to support the notification This patch adds the synchronization extcon APIs to support the notifications for both state and property. When extcon_*_sync() functions is called, the extcon informs the information from extcon provider to extcon client. The extcon driver may need to change the both state and multiple properties at the same time. After setting the data of a external connector, the extcon send the notification to client driver with the extcon_*_sync(). The list of new extcon APIs as following: - extcon_sync() : Send the notification for each external connector to synchronize the information between extcon provider driver and extcon client driver. - extcon_set_state_sync() : Set the state of external connector with noti. - extcon_set_property_sync() : Set the property of external connector with noti. For example, case 1, change the state of external connector and synchronized the data. extcon_set_state_sync(edev, EXTCON_USB, 1); case 2, change both the state and property of external connector and synchronized the data. extcon_set_state(edev, EXTCON_USB, 1); extcon_set_property(edev, EXTCON_USB, EXTCON_PROP_USB_VBUS 1); extcon_sync(edev, EXTCON_USB); case 3, change the property of external connector and synchronized the data. extcon_set_property(edev, EXTCON_USB, EXTCON_PROP_USB_VBUS, 0); extcon_sync(edev, EXTCON_USB); case 4, change the property of external connector and synchronized the data. extcon_set_property_sync(edev, EXTCON_USB, EXTCON_PROP_USB_VBUS, 0); Signed-off-by: Chanwoo Choi Tested-by: Chris Zhong Tested-by: Guenter Roeck Reviewed-by: Guenter Roeck --- drivers/extcon/extcon.c | 210 +++++++++++++++++++++++++++++++----------------- include/linux/extcon.h | 30 ++++++- 2 files changed, 164 insertions(+), 76 deletions(-) (limited to 'include/linux') diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index d66adfd21159..8fde4befaa51 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -279,14 +279,11 @@ static bool is_extcon_attached(struct extcon_dev *edev, unsigned int index) return !!(edev->state & BIT(index)); } -static bool is_extcon_changed(u32 prev, u32 new, int idx, bool *attached) +static bool is_extcon_changed(struct extcon_dev *edev, int index, + bool new_state) { - if (((prev >> idx) & 0x1) != ((new >> idx) & 0x1)) { - *attached = ((new >> idx) & 0x1) ? true : false; - return true; - } - - return false; + int state = !!(edev->state & BIT(index)); + return (state != new_state); } static bool is_extcon_property_supported(unsigned int id, unsigned int prop) @@ -402,21 +399,13 @@ static ssize_t cable_state_show(struct device *dev, } /** - * extcon_update_state() - Update the cable attach states of the extcon device - * only for the masked bits. - * @edev: the extcon device - * @mask: the bit mask to designate updated bits. - * @state: new cable attach status for @edev - * - * Changing the state sends uevent with environment variable containing - * the name of extcon device (envp[0]) and the state output (envp[1]). - * Tizen uses this format for extcon device to get events from ports. - * Android uses this format as well. + * extcon_sync() - Synchronize the states for both the attached/detached + * @edev: the extcon device that has the cable. * - * Note that the notifier provides which bits are changed in the state - * variable with the val parameter (second) to the callback. + * This function send a notification to synchronize the all states of a + * specific external connector */ -static int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) +int extcon_sync(struct extcon_dev *edev, unsigned int id) { char name_buf[120]; char state_buf[120]; @@ -425,73 +414,58 @@ static int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state) int env_offset = 0; int length; int index; + int state; unsigned long flags; - bool attached; if (!edev) return -EINVAL; + index = find_cable_index_by_id(edev, id); + if (index < 0) + return index; + spin_lock_irqsave(&edev->lock, flags); - if (edev->state != ((edev->state & ~mask) | (state & mask))) { - u32 old_state; + state = !!(edev->state & BIT(index)); + raw_notifier_call_chain(&edev->nh[index], state, edev); - if (check_mutually_exclusive(edev, (edev->state & ~mask) | - (state & mask))) { - spin_unlock_irqrestore(&edev->lock, flags); - return -EPERM; - } + /* This could be in interrupt handler */ + prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); + if (!prop_buf) { + /* Unlock early before uevent */ + spin_unlock_irqrestore(&edev->lock, flags); - old_state = edev->state; - edev->state &= ~mask; - edev->state |= state & mask; + dev_err(&edev->dev, "out of memory in extcon_set_state\n"); + kobject_uevent(&edev->dev.kobj, KOBJ_CHANGE); - for (index = 0; index < edev->max_supported; index++) { - if (is_extcon_changed(old_state, edev->state, index, - &attached)) - raw_notifier_call_chain(&edev->nh[index], - attached, edev); - } - - /* This could be in interrupt handler */ - prop_buf = (char *)get_zeroed_page(GFP_ATOMIC); - if (prop_buf) { - length = name_show(&edev->dev, NULL, prop_buf); - if (length > 0) { - if (prop_buf[length - 1] == '\n') - prop_buf[length - 1] = 0; - snprintf(name_buf, sizeof(name_buf), - "NAME=%s", prop_buf); - envp[env_offset++] = name_buf; - } - length = state_show(&edev->dev, NULL, prop_buf); - if (length > 0) { - if (prop_buf[length - 1] == '\n') - prop_buf[length - 1] = 0; - snprintf(state_buf, sizeof(state_buf), - "STATE=%s", prop_buf); - envp[env_offset++] = state_buf; - } - envp[env_offset] = NULL; - /* Unlock early before uevent */ - spin_unlock_irqrestore(&edev->lock, flags); + return 0; + } - kobject_uevent_env(&edev->dev.kobj, KOBJ_CHANGE, envp); - free_page((unsigned long)prop_buf); - } else { - /* Unlock early before uevent */ - spin_unlock_irqrestore(&edev->lock, flags); + length = name_show(&edev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(name_buf, sizeof(name_buf), "NAME=%s", prop_buf); + envp[env_offset++] = name_buf; + } - dev_err(&edev->dev, "out of memory in extcon_set_state\n"); - kobject_uevent(&edev->dev.kobj, KOBJ_CHANGE); - } - } else { - /* No changes */ - spin_unlock_irqrestore(&edev->lock, flags); + length = state_show(&edev->dev, NULL, prop_buf); + if (length > 0) { + if (prop_buf[length - 1] == '\n') + prop_buf[length - 1] = 0; + snprintf(state_buf, sizeof(state_buf), "STATE=%s", prop_buf); + envp[env_offset++] = state_buf; } + envp[env_offset] = NULL; + + /* Unlock early before uevent */ + spin_unlock_irqrestore(&edev->lock, flags); + kobject_uevent_env(&edev->dev.kobj, KOBJ_CHANGE, envp); + free_page((unsigned long)prop_buf); return 0; } +EXPORT_SYMBOL_GPL(extcon_sync); /** * extcon_get_state() - Get the state of a external connector. @@ -520,17 +494,22 @@ EXPORT_SYMBOL_GPL(extcon_get_state); /** * extcon_set_state() - Set the state of a external connector. + * without a notification. * @edev: the extcon device that has the cable. * @id: the unique id of each external connector * in extcon enumeration. * @state: the new cable status. The default semantics is * true: attached / false: detached. + * + * This function only set the state of a external connector without + * a notification. To synchronize the data of a external connector, + * use extcon_set_state_sync() and extcon_sync(). */ int extcon_set_state(struct extcon_dev *edev, unsigned int id, bool cable_state) { - u32 state; - int index; + unsigned long flags; + int index, ret = 0; if (!edev) return -EINVAL; @@ -539,6 +518,18 @@ int extcon_set_state(struct extcon_dev *edev, unsigned int id, if (index < 0) return index; + spin_lock_irqsave(&edev->lock, flags); + + /* Check whether the external connector's state is changed. */ + if (!is_extcon_changed(edev, index, cable_state)) + goto out; + + if (check_mutually_exclusive(edev, + (edev->state & ~BIT(index)) | (cable_state & BIT(index)))) { + ret = -EPERM; + goto out; + } + /* * Initialize the value of extcon property before setting * the detached state for an external connector. @@ -546,11 +537,55 @@ int extcon_set_state(struct extcon_dev *edev, unsigned int id, if (!cable_state) init_property(edev, id, index); - state = cable_state ? (1 << index) : 0; - return extcon_update_state(edev, 1 << index, state); + /* Update the state for a external connector. */ + if (cable_state) + edev->state |= BIT(index); + else + edev->state &= ~(BIT(index)); +out: + spin_unlock_irqrestore(&edev->lock, flags); + + return ret; } EXPORT_SYMBOL_GPL(extcon_set_state); +/** + * extcon_set_state_sync() - Set the state of a external connector + * with a notification. + * @edev: the extcon device that has the cable. + * @id: the unique id of each external connector + * in extcon enumeration. + * @state: the new cable status. The default semantics is + * true: attached / false: detached. + * + * This function set the state of external connector and synchronize the data + * by usning a notification. + */ +int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, + bool cable_state) +{ + int ret, index; + unsigned long flags; + + index = find_cable_index_by_id(edev, id); + if (index < 0) + return index; + + /* Check whether the external connector's state is changed. */ + spin_lock_irqsave(&edev->lock, flags); + ret = is_extcon_changed(edev, index, cable_state); + spin_unlock_irqrestore(&edev->lock, flags); + if (!ret) + return 0; + + ret = extcon_set_state(edev, id, cable_state); + if (ret < 0) + return ret; + + return extcon_sync(edev, id); +} +EXPORT_SYMBOL_GPL(extcon_set_state_sync); + /** * extcon_get_property() - Get the property value of a specific cable. * @edev: the extcon device that has the cable. @@ -701,6 +736,31 @@ int extcon_set_property(struct extcon_dev *edev, unsigned int id, } EXPORT_SYMBOL_GPL(extcon_set_property); +/** + * extcon_set_property_sync() - Set the property value of a specific cable + with a notification. + * @prop_val: the pointer including the new value of property. + * + * When setting the property value of external connector, the external connector + * should be attached. The each property should be included in the list of + * supported properties according to the type of external connectors. + * + * Returns 0 if success or error number if fail + */ +int extcon_set_property_sync(struct extcon_dev *edev, unsigned int id, + unsigned int prop, + union extcon_property_value prop_val) +{ + int ret; + + ret = extcon_set_property(edev, id, prop, prop_val); + if (ret < 0) + return ret; + + return extcon_sync(edev, id); +} +EXPORT_SYMBOL_GPL(extcon_set_property_sync); + /** * extcon_get_property_capability() - Get the capability of property * of an external connector. diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 4fa37385c97a..162c46a42bac 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -222,6 +222,13 @@ extern void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev); extern int extcon_get_state(struct extcon_dev *edev, unsigned int id); extern int extcon_set_state(struct extcon_dev *edev, unsigned int id, bool cable_state); +extern int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, + bool cable_state); + +/* + * Synchronize the state and property data for a specific external connector. + */ +extern int extcon_sync(struct extcon_dev *edev, unsigned int id); /* * get/set_property access the property value of each external connector. @@ -233,6 +240,9 @@ extern int extcon_get_property(struct extcon_dev *edev, unsigned int id, extern int extcon_set_property(struct extcon_dev *edev, unsigned int id, unsigned int prop, union extcon_property_value prop_val); +extern int extcon_set_property_sync(struct extcon_dev *edev, unsigned int id, + unsigned int prop, + union extcon_property_value prop_val); /* * get/set_property_capability set the capability of the property for each @@ -317,6 +327,17 @@ static inline int extcon_set_state(struct extcon_dev *edev, unsigned int id, return 0; } +static inline int extcon_set_state_sync(struct extcon_dev *edev, unsigned int id, + bool cable_state) +{ + return 0; +} + +static inline int extcon_sync(struct extcon_dev *edev, unsigned int id) +{ + return 0; +} + static inline int extcon_get_property(struct extcon_dev *edev, unsigned int id, unsigned int prop, union extcon_property_value *prop_val) @@ -330,6 +351,13 @@ static inline int extcon_set_property(struct extcon_dev *edev, unsigned int id, return 0; } +static inline int extcon_set_property_sync(struct extcon_dev *edev, + unsigned int id, unsigned int prop, + union extcon_property_value prop_val) +{ + return 0; +} + static inline int extcon_get_property_capability(struct extcon_dev *edev, unsigned int id, unsigned int prop) { @@ -411,6 +439,6 @@ static inline int extcon_get_cable_state_(struct extcon_dev *edev, unsigned int static inline int extcon_set_cable_state_(struct extcon_dev *edev, unsigned int id, bool cable_state) { - return extcon_set_state(edev, id, cable_state); + return extcon_set_state_sync(edev, id, cable_state); } #endif /* __LINUX_EXTCON_H__ */ -- cgit v1.2.3 From 2164188d57f8862f1b69e2c5b7c0585f01077bee Mon Sep 17 00:00:00 2001 From: Chris Zhong Date: Fri, 22 Jul 2016 01:13:02 +0900 Subject: extcon: Add EXTCON_DISP_DP and the property for USB Type-C Add EXTCON_DISP_DP for the Display external connector. For Type-C connector the DisplayPort can work as an Alternate Mode(VESA DisplayPort Alt Mode on USB Type-C Standard). The Type-C support both normal and flipped orientation, so add a property to extcon. Signed-off-by: Chris Zhong Signed-off-by: Chanwoo Choi Tested-by: Chris Zhong Tested-by: Guenter Roeck Reviewed-by: Guenter Roeck --- drivers/extcon/extcon.c | 5 +++++ include/linux/extcon.h | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 8fde4befaa51..a0a1eea18727 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -157,6 +157,11 @@ struct __extcon_info { .id = EXTCON_DISP_VGA, .name = "VGA", }, + [EXTCON_DISP_DP] = { + .type = EXTCON_TYPE_DISP | EXTCON_TYPE_USB, + .id = EXTCON_DISP_DP, + .name = "DP", + }, /* Miscellaneous external connector */ [EXTCON_DOCK] = { diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 162c46a42bac..ad7a1606a7f3 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -69,6 +69,7 @@ #define EXTCON_DISP_MHL 41 /* Mobile High-Definition Link */ #define EXTCON_DISP_DVI 42 /* Digital Visual Interface */ #define EXTCON_DISP_VGA 43 /* Video Graphics Array */ +#define EXTCON_DISP_DP 44 /* Display Port */ /* Miscellaneous external connector */ #define EXTCON_DOCK 60 @@ -102,11 +103,16 @@ * @type: integer (intval) * @value: 0 (low) or 1 (high) * @default: 0 (low) + * - EXTCON_PROP_USB_TYPEC_POLARITY + * @type: integer (intval) + * @value: 0 (normal) or 1 (flip) + * @default: 0 (normal) */ #define EXTCON_PROP_USB_VBUS 0 +#define EXTCON_PROP_USB_TYPEC_POLARITY 1 #define EXTCON_PROP_USB_MIN 0 -#define EXTCON_PROP_USB_MAX 0 +#define EXTCON_PROP_USB_MAX 1 #define EXTCON_PROP_USB_CNT (EXTCON_PROP_USB_MAX - EXTCON_PROP_USB_MIN + 1) /* Properties of EXTCON_TYPE_CHG. */ -- cgit v1.2.3 From 9c0595d688e9deb337ff8901bb25281cdb19050b Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Fri, 5 Aug 2016 17:49:23 +0900 Subject: extcon: Add new EXTCON_DISP_HMD for Head-mounted Display device This patch adds the new EXTCON_DISP_HMD id for Head-mounted Display[1] device. The HMD device is usually for USB connector type So, the HMD connector has the two extcon types of both EXTCON_TYPE_DISP and EXTCON_TYPE_USB. [1] https://en.wikipedia.org/wiki/Head-mounted_display Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon.c | 5 +++++ include/linux/extcon.h | 1 + 2 files changed, 6 insertions(+) (limited to 'include/linux') diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index a0a1eea18727..10ba876ea77a 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -162,6 +162,11 @@ struct __extcon_info { .id = EXTCON_DISP_DP, .name = "DP", }, + [EXTCON_DISP_HMD] = { + .type = EXTCON_TYPE_DISP | EXTCON_TYPE_USB, + .id = EXTCON_DISP_HMD, + .name = "HMD", + }, /* Miscellaneous external connector */ [EXTCON_DOCK] = { diff --git a/include/linux/extcon.h b/include/linux/extcon.h index ad7a1606a7f3..e79b644f41a7 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -70,6 +70,7 @@ #define EXTCON_DISP_DVI 42 /* Digital Visual Interface */ #define EXTCON_DISP_VGA 43 /* Video Graphics Array */ #define EXTCON_DISP_DP 44 /* Display Port */ +#define EXTCON_DISP_HMD 45 /* Head-Mounted Display */ /* Miscellaneous external connector */ #define EXTCON_DOCK 60 -- cgit v1.2.3 From 7fe95fb889faf07ee438384858d38b820973397e Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Fri, 5 Aug 2016 18:15:46 +0900 Subject: extcon: Add new EXTCON_CHG_WPT for Wireless Power Transfer device This patchs add the new EXTCON_CHG_WPT for Wireless Power Transfer[1]. The Wireless Power Transfer is the transmission of electronical energy from a power source. The EXTCON_CHG_WPT has the EXTCON_TYPE_CHG. [1] https://en.wikipedia.org/wiki/Wireless_power_transfer Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon.c | 5 +++++ include/linux/extcon.h | 1 + 2 files changed, 6 insertions(+) (limited to 'include/linux') diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index 10ba876ea77a..78298460d168 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -93,6 +93,11 @@ struct __extcon_info { .id = EXTCON_CHG_USB_SLOW, .name = "SLOW-CHARGER", }, + [EXTCON_CHG_WPT] = { + .type = EXTCON_TYPE_CHG, + .id = EXTCON_CHG_WPT, + .name = "WPT", + }, /* Jack external connector */ [EXTCON_JACK_MICROPHONE] = { diff --git a/include/linux/extcon.h b/include/linux/extcon.h index e79b644f41a7..461abee969b7 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -53,6 +53,7 @@ #define EXTCON_CHG_USB_ACA 8 /* Accessory Charger Adapter */ #define EXTCON_CHG_USB_FAST 9 #define EXTCON_CHG_USB_SLOW 10 +#define EXTCON_CHG_WPT 11 /* Wireless Power Transfer */ /* Jack external connector */ #define EXTCON_JACK_MICROPHONE 20 -- cgit v1.2.3 From f067025bc676ba8d18fba5f959598339e39b86db Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 20 Jul 2016 13:13:50 -0700 Subject: dmaengine: add support to provide error result from a DMA transation Adding a new callback that will provide the error result for a transaction. The result is allocated on the stack and the callback should create a copy if it wishes to retain the information after exiting. The result parameter is now defined and takes over the dummy void pointer we placed in the helper functions previously. dmaengine drivers should start converting to the new "callback_result" callback in order to receive transaction results. Signed-off-by: Dave Jiang Reviewed-by: Lars-Peter Clausen Signed-off-by: Vinod Koul --- drivers/dma/dmaengine.h | 22 +++++++++++++++++----- include/linux/dmaengine.h | 16 ++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) (limited to 'include/linux') diff --git a/drivers/dma/dmaengine.h b/drivers/dma/dmaengine.h index 94a4379c7639..882ff9448c3b 100644 --- a/drivers/dma/dmaengine.h +++ b/drivers/dma/dmaengine.h @@ -88,6 +88,7 @@ static inline void dma_set_residue(struct dma_tx_state *state, u32 residue) struct dmaengine_desc_callback { dma_async_tx_callback callback; + dma_async_tx_callback_result callback_result; void *callback_param; }; @@ -105,13 +106,14 @@ dmaengine_desc_get_callback(struct dma_async_tx_descriptor *tx, struct dmaengine_desc_callback *cb) { cb->callback = tx->callback; + cb->callback_result = tx->callback_result; cb->callback_param = tx->callback_param; } /** * dmaengine_desc_callback_invoke - call the callback function in cb struct * @cb: temp struct that is holding the callback info - * @result: dummy pointer for now + * @result: transaction result * * Call the callback function provided in the cb struct with the parameter * in the cb struct. @@ -119,17 +121,27 @@ dmaengine_desc_get_callback(struct dma_async_tx_descriptor *tx, */ static inline void dmaengine_desc_callback_invoke(struct dmaengine_desc_callback *cb, - const void *result) + const struct dmaengine_result *result) { - if (cb->callback) + struct dmaengine_result dummy_result = { + .result = DMA_TRANS_NOERROR, + .residue = 0 + }; + + if (cb->callback_result) { + if (!result) + result = &dummy_result; + cb->callback_result(cb->callback_param, result); + } else if (cb->callback) { cb->callback(cb->callback_param); + } } /** * dmaengine_desc_get_callback_invoke - get the callback in tx descriptor and * then immediately call the callback. * @tx: dma async tx descriptor - * @result: dummy pointer for now + * @result: transaction result * * Call dmaengine_desc_get_callback() and dmaengine_desc_callback_invoke() * in a single function since no work is necessary in between for the driver. @@ -137,7 +149,7 @@ dmaengine_desc_callback_invoke(struct dmaengine_desc_callback *cb, */ static inline void dmaengine_desc_get_callback_invoke(struct dma_async_tx_descriptor *tx, - const void *result) + const struct dmaengine_result *result) { struct dmaengine_desc_callback cb; diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h index 30de0197263a..cc535a478bae 100644 --- a/include/linux/dmaengine.h +++ b/include/linux/dmaengine.h @@ -441,6 +441,21 @@ typedef bool (*dma_filter_fn)(struct dma_chan *chan, void *filter_param); typedef void (*dma_async_tx_callback)(void *dma_async_param); +enum dmaengine_tx_result { + DMA_TRANS_NOERROR = 0, /* SUCCESS */ + DMA_TRANS_READ_FAILED, /* Source DMA read failed */ + DMA_TRANS_WRITE_FAILED, /* Destination DMA write failed */ + DMA_TRANS_ABORTED, /* Op never submitted / aborted */ +}; + +struct dmaengine_result { + enum dmaengine_tx_result result; + u32 residue; +}; + +typedef void (*dma_async_tx_callback_result)(void *dma_async_param, + const struct dmaengine_result *result); + struct dmaengine_unmap_data { u8 map_cnt; u8 to_cnt; @@ -478,6 +493,7 @@ struct dma_async_tx_descriptor { dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx); int (*desc_free)(struct dma_async_tx_descriptor *tx); dma_async_tx_callback callback; + dma_async_tx_callback_result callback_result; void *callback_param; struct dmaengine_unmap_data *unmap; #ifdef CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH -- cgit v1.2.3 From 585083c539ca3f5fb3d00057b25f9be3304d54c6 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Fri, 8 Jul 2016 22:33:37 +0800 Subject: mfd: ac100: Add driver for X-Powers AC100 audio codec / RTC combo IC The AC100 is a multifunction device with an audio codec subsystem and an RTC subsystem. These two subsystems share a common register space and host interface. Signed-off-by: Chen-Yu Tsai Signed-off-by: Lee Jones --- drivers/mfd/Kconfig | 10 +++ drivers/mfd/Makefile | 2 + drivers/mfd/ac100.c | 137 +++++++++++++++++++++++++++++++++++ include/linux/mfd/ac100.h | 178 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 327 insertions(+) create mode 100644 drivers/mfd/ac100.c create mode 100644 include/linux/mfd/ac100.h (limited to 'include/linux') diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 2d1fb6420592..ccf73aab5384 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -112,6 +112,16 @@ config MFD_BCM590XX help Support for the BCM590xx PMUs from Broadcom +config MFD_AC100 + tristate "X-Powers AC100" + select MFD_CORE + depends on SUNXI_RSB + help + If you say Y here you get support for the X-Powers AC100 audio codec + IC. + This driver include only the core APIs. You have to select individual + components like codecs or RTC under the corresponding menus. + config MFD_AXP20X tristate select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 2ba3ba35f745..bca83dbb5ed8 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -113,6 +113,8 @@ obj-$(CONFIG_PMIC_DA9052) += da9052-irq.o obj-$(CONFIG_PMIC_DA9052) += da9052-core.o obj-$(CONFIG_MFD_DA9052_SPI) += da9052-spi.o obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o + +obj-$(CONFIG_MFD_AC100) += ac100.o obj-$(CONFIG_MFD_AXP20X) += axp20x.o obj-$(CONFIG_MFD_AXP20X_I2C) += axp20x-i2c.o obj-$(CONFIG_MFD_AXP20X_RSB) += axp20x-rsb.o diff --git a/drivers/mfd/ac100.c b/drivers/mfd/ac100.c new file mode 100644 index 000000000000..9bc69cd7807d --- /dev/null +++ b/drivers/mfd/ac100.c @@ -0,0 +1,137 @@ +/* + * MFD core driver for X-Powers' AC100 Audio Codec IC + * + * The AC100 is a highly integrated audio codec and RTC subsystem designed + * for mobile applications. It has 3 I2S/PCM interfaces, a 2 channel DAC, + * a 2 channel ADC with 5 inputs and a builtin mixer. The RTC subsystem has + * 3 clock outputs. + * + * The audio codec and RTC parts are completely separate, sharing only the + * host interface for access to its registers. + * + * Copyright (2016) Chen-Yu Tsai + * + * Author: Chen-Yu Tsai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct regmap_range ac100_writeable_ranges[] = { + regmap_reg_range(AC100_CHIP_AUDIO_RST, AC100_I2S_SR_CTRL), + regmap_reg_range(AC100_I2S1_CLK_CTRL, AC100_I2S1_MXR_GAIN), + regmap_reg_range(AC100_I2S2_CLK_CTRL, AC100_I2S2_MXR_GAIN), + regmap_reg_range(AC100_I2S3_CLK_CTRL, AC100_I2S3_SIG_PATH_CTRL), + regmap_reg_range(AC100_ADC_DIG_CTRL, AC100_ADC_VOL_CTRL), + regmap_reg_range(AC100_HMIC_CTRL1, AC100_HMIC_STATUS), + regmap_reg_range(AC100_DAC_DIG_CTRL, AC100_DAC_MXR_GAIN), + regmap_reg_range(AC100_ADC_APC_CTRL, AC100_LINEOUT_CTRL), + regmap_reg_range(AC100_ADC_DAP_L_CTRL, AC100_ADC_DAP_OPT), + regmap_reg_range(AC100_DAC_DAP_CTRL, AC100_DAC_DAP_OPT), + regmap_reg_range(AC100_ADC_DAP_ENA, AC100_DAC_DAP_ENA), + regmap_reg_range(AC100_SRC1_CTRL1, AC100_SRC1_CTRL2), + regmap_reg_range(AC100_SRC2_CTRL1, AC100_SRC2_CTRL2), + regmap_reg_range(AC100_CLK32K_ANALOG_CTRL, AC100_CLKOUT_CTRL3), + regmap_reg_range(AC100_RTC_RST, AC100_RTC_UPD), + regmap_reg_range(AC100_ALM_INT_ENA, AC100_ALM_INT_STA), + regmap_reg_range(AC100_ALM_SEC, AC100_RTC_GP(15)), +}; + +static const struct regmap_range ac100_volatile_ranges[] = { + regmap_reg_range(AC100_CHIP_AUDIO_RST, AC100_PLL_CTRL2), + regmap_reg_range(AC100_HMIC_STATUS, AC100_HMIC_STATUS), + regmap_reg_range(AC100_ADC_DAP_L_STA, AC100_ADC_DAP_L_STA), + regmap_reg_range(AC100_SRC1_CTRL1, AC100_SRC1_CTRL1), + regmap_reg_range(AC100_SRC1_CTRL3, AC100_SRC2_CTRL1), + regmap_reg_range(AC100_SRC2_CTRL3, AC100_SRC2_CTRL4), + regmap_reg_range(AC100_RTC_RST, AC100_RTC_RST), + regmap_reg_range(AC100_RTC_SEC, AC100_ALM_INT_STA), + regmap_reg_range(AC100_ALM_SEC, AC100_ALM_UPD), +}; + +static const struct regmap_access_table ac100_writeable_table = { + .yes_ranges = ac100_writeable_ranges, + .n_yes_ranges = ARRAY_SIZE(ac100_writeable_ranges), +}; + +static const struct regmap_access_table ac100_volatile_table = { + .yes_ranges = ac100_volatile_ranges, + .n_yes_ranges = ARRAY_SIZE(ac100_volatile_ranges), +}; + +static const struct regmap_config ac100_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .wr_table = &ac100_writeable_table, + .volatile_table = &ac100_volatile_table, + .max_register = AC100_RTC_GP(15), + .cache_type = REGCACHE_RBTREE, +}; + +static struct mfd_cell ac100_cells[] = { + { + .name = "ac100-codec", + .of_compatible = "x-powers,ac100-codec", + }, { + .name = "ac100-rtc", + .of_compatible = "x-powers,ac100-rtc", + }, +}; + +static int ac100_rsb_probe(struct sunxi_rsb_device *rdev) +{ + struct ac100_dev *ac100; + int ret; + + ac100 = devm_kzalloc(&rdev->dev, sizeof(*ac100), GFP_KERNEL); + if (!ac100) + return -ENOMEM; + + ac100->dev = &rdev->dev; + sunxi_rsb_device_set_drvdata(rdev, ac100); + + ac100->regmap = devm_regmap_init_sunxi_rsb(rdev, &ac100_regmap_config); + if (IS_ERR(ac100->regmap)) { + ret = PTR_ERR(ac100->regmap); + dev_err(ac100->dev, "regmap init failed: %d\n", ret); + return ret; + } + + ret = devm_mfd_add_devices(ac100->dev, PLATFORM_DEVID_NONE, ac100_cells, + ARRAY_SIZE(ac100_cells), NULL, 0, NULL); + if (ret) { + dev_err(ac100->dev, "failed to add MFD devices: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct of_device_id ac100_of_match[] = { + { .compatible = "x-powers,ac100" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ac100_of_match); + +static struct sunxi_rsb_driver ac100_rsb_driver = { + .driver = { + .name = "ac100", + .of_match_table = of_match_ptr(ac100_of_match), + }, + .probe = ac100_rsb_probe, +}; +module_sunxi_rsb_driver(ac100_rsb_driver); + +MODULE_DESCRIPTION("Audio codec MFD core driver for AC100"); +MODULE_AUTHOR("Chen-Yu Tsai "); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/ac100.h b/include/linux/mfd/ac100.h new file mode 100644 index 000000000000..3c148f196b9f --- /dev/null +++ b/include/linux/mfd/ac100.h @@ -0,0 +1,178 @@ +/* + * Functions and registers to access AC100 codec / RTC combo IC. + * + * Copyright (C) 2016 Chen-Yu Tsai + * + * Chen-Yu Tsai + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LINUX_MFD_AC100_H +#define __LINUX_MFD_AC100_H + +#include + +struct ac100_dev { + struct device *dev; + struct regmap *regmap; +}; + +/* Audio codec related registers */ +#define AC100_CHIP_AUDIO_RST 0x00 +#define AC100_PLL_CTRL1 0x01 +#define AC100_PLL_CTRL2 0x02 +#define AC100_SYSCLK_CTRL 0x03 +#define AC100_MOD_CLK_ENA 0x04 +#define AC100_MOD_RST_CTRL 0x05 +#define AC100_I2S_SR_CTRL 0x06 + +/* I2S1 interface */ +#define AC100_I2S1_CLK_CTRL 0x10 +#define AC100_I2S1_SND_OUT_CTRL 0x11 +#define AC100_I2S1_SND_IN_CTRL 0x12 +#define AC100_I2S1_MXR_SRC 0x13 +#define AC100_I2S1_VOL_CTRL1 0x14 +#define AC100_I2S1_VOL_CTRL2 0x15 +#define AC100_I2S1_VOL_CTRL3 0x16 +#define AC100_I2S1_VOL_CTRL4 0x17 +#define AC100_I2S1_MXR_GAIN 0x18 + +/* I2S2 interface */ +#define AC100_I2S2_CLK_CTRL 0x20 +#define AC100_I2S2_SND_OUT_CTRL 0x21 +#define AC100_I2S2_SND_IN_CTRL 0x22 +#define AC100_I2S2_MXR_SRC 0x23 +#define AC100_I2S2_VOL_CTRL1 0x24 +#define AC100_I2S2_VOL_CTRL2 0x25 +#define AC100_I2S2_VOL_CTRL3 0x26 +#define AC100_I2S2_VOL_CTRL4 0x27 +#define AC100_I2S2_MXR_GAIN 0x28 + +/* I2S3 interface */ +#define AC100_I2S3_CLK_CTRL 0x30 +#define AC100_I2S3_SND_OUT_CTRL 0x31 +#define AC100_I2S3_SND_IN_CTRL 0x32 +#define AC100_I2S3_SIG_PATH_CTRL 0x33 + +/* ADC digital controls */ +#define AC100_ADC_DIG_CTRL 0x40 +#define AC100_ADC_VOL_CTRL 0x41 + +/* HMIC plug sensing / key detection */ +#define AC100_HMIC_CTRL1 0x44 +#define AC100_HMIC_CTRL2 0x45 +#define AC100_HMIC_STATUS 0x46 + +/* DAC digital controls */ +#define AC100_DAC_DIG_CTRL 0x48 +#define AC100_DAC_VOL_CTRL 0x49 +#define AC100_DAC_MXR_SRC 0x4c +#define AC100_DAC_MXR_GAIN 0x4d + +/* Analog controls */ +#define AC100_ADC_APC_CTRL 0x50 +#define AC100_ADC_SRC 0x51 +#define AC100_ADC_SRC_BST_CTRL 0x52 +#define AC100_OUT_MXR_DAC_A_CTRL 0x53 +#define AC100_OUT_MXR_SRC 0x54 +#define AC100_OUT_MXR_SRC_BST 0x55 +#define AC100_HPOUT_CTRL 0x56 +#define AC100_ERPOUT_CTRL 0x57 +#define AC100_SPKOUT_CTRL 0x58 +#define AC100_LINEOUT_CTRL 0x59 + +/* ADC digital audio processing (high pass filter & auto gain control */ +#define AC100_ADC_DAP_L_STA 0x80 +#define AC100_ADC_DAP_R_STA 0x81 +#define AC100_ADC_DAP_L_CTRL 0x82 +#define AC100_ADC_DAP_R_CTRL 0x83 +#define AC100_ADC_DAP_L_T_L 0x84 /* Left Target Level */ +#define AC100_ADC_DAP_R_T_L 0x85 /* Right Target Level */ +#define AC100_ADC_DAP_L_H_A_C 0x86 /* Left High Avg. Coef */ +#define AC100_ADC_DAP_L_L_A_C 0x87 /* Left Low Avg. Coef */ +#define AC100_ADC_DAP_R_H_A_C 0x88 /* Right High Avg. Coef */ +#define AC100_ADC_DAP_R_L_A_C 0x89 /* Right Low Avg. Coef */ +#define AC100_ADC_DAP_L_D_T 0x8a /* Left Decay Time */ +#define AC100_ADC_DAP_L_A_T 0x8b /* Left Attack Time */ +#define AC100_ADC_DAP_R_D_T 0x8c /* Right Decay Time */ +#define AC100_ADC_DAP_R_A_T 0x8d /* Right Attack Time */ +#define AC100_ADC_DAP_N_TH 0x8e /* Noise Threshold */ +#define AC100_ADC_DAP_L_H_N_A_C 0x8f /* Left High Noise Avg. Coef */ +#define AC100_ADC_DAP_L_L_N_A_C 0x90 /* Left Low Noise Avg. Coef */ +#define AC100_ADC_DAP_R_H_N_A_C 0x91 /* Right High Noise Avg. Coef */ +#define AC100_ADC_DAP_R_L_N_A_C 0x92 /* Right Low Noise Avg. Coef */ +#define AC100_ADC_DAP_H_HPF_C 0x93 /* High High-Pass-Filter Coef */ +#define AC100_ADC_DAP_L_HPF_C 0x94 /* Low High-Pass-Filter Coef */ +#define AC100_ADC_DAP_OPT 0x95 /* AGC Optimum */ + +/* DAC digital audio processing (high pass filter & dynamic range control) */ +#define AC100_DAC_DAP_CTRL 0xa0 +#define AC100_DAC_DAP_H_HPF_C 0xa1 /* High High-Pass-Filter Coef */ +#define AC100_DAC_DAP_L_HPF_C 0xa2 /* Low High-Pass-Filter Coef */ +#define AC100_DAC_DAP_L_H_E_A_C 0xa3 /* Left High Energy Avg Coef */ +#define AC100_DAC_DAP_L_L_E_A_C 0xa4 /* Left Low Energy Avg Coef */ +#define AC100_DAC_DAP_R_H_E_A_C 0xa5 /* Right High Energy Avg Coef */ +#define AC100_DAC_DAP_R_L_E_A_C 0xa6 /* Right Low Energy Avg Coef */ +#define AC100_DAC_DAP_H_G_D_T_C 0xa7 /* High Gain Delay Time Coef */ +#define AC100_DAC_DAP_L_G_D_T_C 0xa8 /* Low Gain Delay Time Coef */ +#define AC100_DAC_DAP_H_G_A_T_C 0xa9 /* High Gain Attack Time Coef */ +#define AC100_DAC_DAP_L_G_A_T_C 0xaa /* Low Gain Attack Time Coef */ +#define AC100_DAC_DAP_H_E_TH 0xab /* High Energy Threshold */ +#define AC100_DAC_DAP_L_E_TH 0xac /* Low Energy Threshold */ +#define AC100_DAC_DAP_H_G_K 0xad /* High Gain K parameter */ +#define AC100_DAC_DAP_L_G_K 0xae /* Low Gain K parameter */ +#define AC100_DAC_DAP_H_G_OFF 0xaf /* High Gain offset */ +#define AC100_DAC_DAP_L_G_OFF 0xb0 /* Low Gain offset */ +#define AC100_DAC_DAP_OPT 0xb1 /* DRC optimum */ + +/* Digital audio processing enable */ +#define AC100_ADC_DAP_ENA 0xb4 +#define AC100_DAC_DAP_ENA 0xb5 + +/* SRC control */ +#define AC100_SRC1_CTRL1 0xb8 +#define AC100_SRC1_CTRL2 0xb9 +#define AC100_SRC1_CTRL3 0xba +#define AC100_SRC1_CTRL4 0xbb +#define AC100_SRC2_CTRL1 0xbc +#define AC100_SRC2_CTRL2 0xbd +#define AC100_SRC2_CTRL3 0xbe +#define AC100_SRC2_CTRL4 0xbf + +/* RTC clk control */ +#define AC100_CLK32K_ANALOG_CTRL 0xc0 +#define AC100_CLKOUT_CTRL1 0xc1 +#define AC100_CLKOUT_CTRL2 0xc2 +#define AC100_CLKOUT_CTRL3 0xc3 + +/* RTC module */ +#define AC100_RTC_RST 0xc6 +#define AC100_RTC_CTRL 0xc7 +#define AC100_RTC_SEC 0xc8 /* second */ +#define AC100_RTC_MIN 0xc9 /* minute */ +#define AC100_RTC_HOU 0xca /* hour */ +#define AC100_RTC_WEE 0xcb /* weekday */ +#define AC100_RTC_DAY 0xcc /* day */ +#define AC100_RTC_MON 0xcd /* month */ +#define AC100_RTC_YEA 0xce /* year */ +#define AC100_RTC_UPD 0xcf /* update trigger */ + +/* RTC alarm */ +#define AC100_ALM_INT_ENA 0xd0 +#define AC100_ALM_INT_STA 0xd1 +#define AC100_ALM_SEC 0xd8 +#define AC100_ALM_MIN 0xd9 +#define AC100_ALM_HOU 0xda +#define AC100_ALM_WEE 0xdb +#define AC100_ALM_DAY 0xdc +#define AC100_ALM_MON 0xdd +#define AC100_ALM_YEA 0xde +#define AC100_ALM_UPD 0xdf + +/* RTC general purpose register 0 ~ 15 */ +#define AC100_RTC_GP(x) (0xe0 + (x)) + +#endif /* __LINUX_MFD_AC100_H */ -- cgit v1.2.3 From 13bcc6a2853435bb5dad368bcbaa9d2a5b9c0ac4 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Sat, 16 Jul 2016 15:22:55 -0500 Subject: sysctl: Stop implicitly passing current into sysctl_table_root.lookup Passing nsproxy into sysctl_table_root.lookup was a premature optimization in attempt to avoid depending on current. The directory /proc/self/sys has not appeared and if and when it does this code will need to be reviewed closely and reworked anyway. So remove the premature optimization. Acked-by: Kees Cook Acked-by: Serge Hallyn Signed-off-by: "Eric W. Biederman" --- fs/proc/proc_sysctl.c | 14 +++++++------- include/linux/sysctl.h | 3 +-- net/sysctl_net.c | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) (limited to 'include/linux') diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index 1b93650dda2f..a80acdfbe180 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -72,7 +72,7 @@ static DEFINE_SPINLOCK(sysctl_lock); static void drop_sysctl_table(struct ctl_table_header *header); static int sysctl_follow_link(struct ctl_table_header **phead, - struct ctl_table **pentry, struct nsproxy *namespaces); + struct ctl_table **pentry); static int insert_links(struct ctl_table_header *head); static void put_links(struct ctl_table_header *header); @@ -319,11 +319,11 @@ static void sysctl_head_finish(struct ctl_table_header *head) } static struct ctl_table_set * -lookup_header_set(struct ctl_table_root *root, struct nsproxy *namespaces) +lookup_header_set(struct ctl_table_root *root) { struct ctl_table_set *set = &root->default_set; if (root->lookup) - set = root->lookup(root, namespaces); + set = root->lookup(root); return set; } @@ -491,7 +491,7 @@ static struct dentry *proc_sys_lookup(struct inode *dir, struct dentry *dentry, goto out; if (S_ISLNK(p->mode)) { - ret = sysctl_follow_link(&h, &p, current->nsproxy); + ret = sysctl_follow_link(&h, &p); err = ERR_PTR(ret); if (ret) goto out; @@ -659,7 +659,7 @@ static bool proc_sys_link_fill_cache(struct file *file, if (S_ISLNK(table->mode)) { /* It is not an error if we can not follow the link ignore it */ - int err = sysctl_follow_link(&head, &table, current->nsproxy); + int err = sysctl_follow_link(&head, &table); if (err) goto out; } @@ -976,7 +976,7 @@ static struct ctl_dir *xlate_dir(struct ctl_table_set *set, struct ctl_dir *dir) } static int sysctl_follow_link(struct ctl_table_header **phead, - struct ctl_table **pentry, struct nsproxy *namespaces) + struct ctl_table **pentry) { struct ctl_table_header *head; struct ctl_table_root *root; @@ -988,7 +988,7 @@ static int sysctl_follow_link(struct ctl_table_header **phead, ret = 0; spin_lock(&sysctl_lock); root = (*pentry)->data; - set = lookup_header_set(root, namespaces); + set = lookup_header_set(root); dir = xlate_dir(set, (*phead)->parent); if (IS_ERR(dir)) ret = PTR_ERR(dir); diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 697e160c78d0..f166ca0203e2 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -155,8 +155,7 @@ struct ctl_table_set { struct ctl_table_root { struct ctl_table_set default_set; - struct ctl_table_set *(*lookup)(struct ctl_table_root *root, - struct nsproxy *namespaces); + struct ctl_table_set *(*lookup)(struct ctl_table_root *root); int (*permissions)(struct ctl_table_header *head, struct ctl_table *table); }; diff --git a/net/sysctl_net.c b/net/sysctl_net.c index 46a71c701e7c..ba9b5d1a31df 100644 --- a/net/sysctl_net.c +++ b/net/sysctl_net.c @@ -27,9 +27,9 @@ #endif static struct ctl_table_set * -net_ctl_header_lookup(struct ctl_table_root *root, struct nsproxy *namespaces) +net_ctl_header_lookup(struct ctl_table_root *root) { - return &namespaces->net_ns->sysctls; + return ¤t->nsproxy->net_ns->sysctls; } static int is_seen(struct ctl_table_set *set) -- cgit v1.2.3 From b032132c3c218f4a09e9499b3674299a752581c6 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Sat, 30 Jul 2016 13:53:37 -0500 Subject: userns: Free user namespaces in process context Add the necessary boiler plate to move freeing of user namespaces into work queue and thus into process context where things can sleep. This is a necessary precursor to per user namespace sysctls. Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 5 +++-- kernel/user_namespace.c | 14 +++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) (limited to 'include/linux') diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 9217169c64cb..4e79b3c64dee 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -39,6 +39,7 @@ struct user_namespace { struct key *persistent_keyring_register; struct rw_semaphore persistent_keyring_register_sem; #endif + struct work_struct work; }; extern struct user_namespace init_user_ns; @@ -54,12 +55,12 @@ static inline struct user_namespace *get_user_ns(struct user_namespace *ns) extern int create_user_ns(struct cred *new); extern int unshare_userns(unsigned long unshare_flags, struct cred **new_cred); -extern void free_user_ns(struct user_namespace *ns); +extern void __put_user_ns(struct user_namespace *ns); static inline void put_user_ns(struct user_namespace *ns) { if (ns && atomic_dec_and_test(&ns->count)) - free_user_ns(ns); + __put_user_ns(ns); } struct seq_operations; diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 68f594212759..5247cdb24e62 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -29,6 +29,7 @@ static DEFINE_MUTEX(userns_state_mutex); static bool new_idmap_permitted(const struct file *file, struct user_namespace *ns, int cap_setid, struct uid_gid_map *map); +static void free_user_ns(struct work_struct *work); static void set_cred_user_ns(struct cred *cred, struct user_namespace *user_ns) { @@ -101,6 +102,7 @@ int create_user_ns(struct cred *new) ns->level = parent_ns->level + 1; ns->owner = owner; ns->group = group; + INIT_WORK(&ns->work, free_user_ns); /* Inherit USERNS_SETGROUPS_ALLOWED from our parent */ mutex_lock(&userns_state_mutex); @@ -135,9 +137,10 @@ int unshare_userns(unsigned long unshare_flags, struct cred **new_cred) return err; } -void free_user_ns(struct user_namespace *ns) +static void free_user_ns(struct work_struct *work) { - struct user_namespace *parent; + struct user_namespace *parent, *ns = + container_of(work, struct user_namespace, work); do { parent = ns->parent; @@ -149,7 +152,12 @@ void free_user_ns(struct user_namespace *ns) ns = parent; } while (atomic_dec_and_test(&parent->count)); } -EXPORT_SYMBOL(free_user_ns); + +void __put_user_ns(struct user_namespace *ns) +{ + schedule_work(&ns->work); +} +EXPORT_SYMBOL(__put_user_ns); static u32 map_id_range_down(struct uid_gid_map *map, u32 id, u32 count) { -- cgit v1.2.3 From dbec28460a89aa7c02c3301e9e108d98272549d2 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Sat, 30 Jul 2016 13:58:49 -0500 Subject: userns: Add per user namespace sysctls. Limit per userns sysctls to only be opened for write by a holder of CAP_SYS_RESOURCE. Add all of the necessary boilerplate for having per user namespace sysctls. Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 4 ++ kernel/Makefile | 2 +- kernel/ucount.c | 99 ++++++++++++++++++++++++++++++++++++++++++ kernel/user_namespace.c | 18 +++++++- 4 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 kernel/ucount.c (limited to 'include/linux') diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 4e79b3c64dee..e5697eaf6bf9 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -40,6 +40,10 @@ struct user_namespace { struct rw_semaphore persistent_keyring_register_sem; #endif struct work_struct work; +#ifdef CONFIG_SYSCTL + struct ctl_table_set set; + struct ctl_table_header *sysctls; +#endif }; extern struct user_namespace init_user_ns; diff --git a/kernel/Makefile b/kernel/Makefile index e2ec54e2b952..eb26e12c6c2a 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -9,7 +9,7 @@ obj-y = fork.o exec_domain.o panic.o \ extable.o params.o \ kthread.o sys_ni.o nsproxy.o \ notifier.o ksysfs.o cred.o reboot.o \ - async.o range.o smpboot.o + async.o range.o smpboot.o ucount.o obj-$(CONFIG_MULTIUSER) += groups.o diff --git a/kernel/ucount.c b/kernel/ucount.c new file mode 100644 index 000000000000..cbde1dc87851 --- /dev/null +++ b/kernel/ucount.c @@ -0,0 +1,99 @@ +/* + * 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 of the + * License. + */ + +#include +#include +#include +#include + +#ifdef CONFIG_SYSCTL +static struct ctl_table_set * +set_lookup(struct ctl_table_root *root) +{ + return ¤t_user_ns()->set; +} + +static int set_is_seen(struct ctl_table_set *set) +{ + return ¤t_user_ns()->set == set; +} + +static int set_permissions(struct ctl_table_header *head, + struct ctl_table *table) +{ + struct user_namespace *user_ns = + container_of(head->set, struct user_namespace, set); + int mode; + + /* Allow users with CAP_SYS_RESOURCE unrestrained access */ + if (ns_capable(user_ns, CAP_SYS_RESOURCE)) + mode = (table->mode & S_IRWXU) >> 6; + else + /* Allow all others at most read-only access */ + mode = table->mode & S_IROTH; + return (mode << 6) | (mode << 3) | mode; +} + +static struct ctl_table_root set_root = { + .lookup = set_lookup, + .permissions = set_permissions, +}; + +static struct ctl_table userns_table[] = { + { } +}; +#endif /* CONFIG_SYSCTL */ + +bool setup_userns_sysctls(struct user_namespace *ns) +{ +#ifdef CONFIG_SYSCTL + struct ctl_table *tbl; + setup_sysctl_set(&ns->set, &set_root, set_is_seen); + tbl = kmemdup(userns_table, sizeof(userns_table), GFP_KERNEL); + if (tbl) { + ns->sysctls = __register_sysctl_table(&ns->set, "userns", tbl); + } + if (!ns->sysctls) { + kfree(tbl); + retire_sysctl_set(&ns->set); + return false; + } +#endif + return true; +} + +void retire_userns_sysctls(struct user_namespace *ns) +{ +#ifdef CONFIG_SYSCTL + struct ctl_table *tbl; + + tbl = ns->sysctls->ctl_table_arg; + unregister_sysctl_table(ns->sysctls); + retire_sysctl_set(&ns->set); + kfree(tbl); +#endif +} + +static __init int user_namespace_sysctl_init(void) +{ +#ifdef CONFIG_SYSCTL + static struct ctl_table_header *userns_header; + static struct ctl_table empty[1]; + /* + * It is necessary to register the userns directory in the + * default set so that registrations in the child sets work + * properly. + */ + userns_header = register_sysctl("userns", empty); + BUG_ON(!userns_header); + BUG_ON(!setup_userns_sysctls(&init_user_ns)); +#endif + return 0; +} +subsys_initcall(user_namespace_sysctl_init); + + diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 5247cdb24e62..a63332253c7e 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -23,6 +23,9 @@ #include #include +extern bool setup_userns_sysctls(struct user_namespace *ns); +extern void retire_userns_sysctls(struct user_namespace *ns); + static struct kmem_cache *user_ns_cachep __read_mostly; static DEFINE_MUTEX(userns_state_mutex); @@ -109,12 +112,22 @@ int create_user_ns(struct cred *new) ns->flags = parent_ns->flags; mutex_unlock(&userns_state_mutex); - set_cred_user_ns(new, ns); - #ifdef CONFIG_PERSISTENT_KEYRINGS init_rwsem(&ns->persistent_keyring_register_sem); #endif + ret = -ENOMEM; + if (!setup_userns_sysctls(ns)) + goto fail_keyring; + + set_cred_user_ns(new, ns); return 0; +fail_keyring: +#ifdef CONFIG_PERSISTENT_KEYRINGS + key_put(ns->persistent_keyring_register); +#endif + ns_free_inum(&ns->ns); + kmem_cache_free(user_ns_cachep, ns); + return ret; } int unshare_userns(unsigned long unshare_flags, struct cred **new_cred) @@ -144,6 +157,7 @@ static void free_user_ns(struct work_struct *work) do { parent = ns->parent; + retire_userns_sysctls(ns); #ifdef CONFIG_PERSISTENT_KEYRINGS key_put(ns->persistent_keyring_register); #endif -- cgit v1.2.3 From b376c3e1b6770ddcb4f0782be16358095fcea0b6 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 13:41:24 -0500 Subject: userns: Add a limit on the number of user namespaces Export the export the maximum number of user namespaces as /proc/sys/userns/max_user_namespaces. Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 6 +++++ kernel/fork.c | 2 ++ kernel/ucount.c | 53 ++++++++++++++++++++++++++++++++++++++++++ kernel/user_namespace.c | 31 +++++++++++++++--------- 4 files changed, 81 insertions(+), 11 deletions(-) (limited to 'include/linux') diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index e5697eaf6bf9..6421cca2daa9 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -44,9 +44,15 @@ struct user_namespace { struct ctl_table_set set; struct ctl_table_header *sysctls; #endif + int max_user_namespaces; + atomic_t user_namespaces; }; extern struct user_namespace init_user_ns; +extern bool setup_userns_sysctls(struct user_namespace *ns); +extern void retire_userns_sysctls(struct user_namespace *ns); +extern bool inc_user_namespaces(struct user_namespace *ns); +extern void dec_user_namespaces(struct user_namespace *ns); #ifdef CONFIG_USER_NS diff --git a/kernel/fork.c b/kernel/fork.c index 52e725d4a866..daa6a82b4900 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -321,6 +321,8 @@ void __init fork_init(void) init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2; init_task.signal->rlim[RLIMIT_SIGPENDING] = init_task.signal->rlim[RLIMIT_NPROC]; + + init_user_ns.max_user_namespaces = max_threads; } int __weak arch_dup_task_struct(struct task_struct *dst, diff --git a/kernel/ucount.c b/kernel/ucount.c index cbde1dc87851..6c2205c0befd 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -43,7 +43,18 @@ static struct ctl_table_root set_root = { .permissions = set_permissions, }; +static int zero = 0; +static int int_max = INT_MAX; static struct ctl_table userns_table[] = { + { + .procname = "max_user_namespaces", + .data = &init_user_ns.max_user_namespaces, + .maxlen = sizeof(init_user_ns.max_user_namespaces), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = &zero, + .extra2 = &int_max, + }, { } }; #endif /* CONFIG_SYSCTL */ @@ -55,6 +66,8 @@ bool setup_userns_sysctls(struct user_namespace *ns) setup_sysctl_set(&ns->set, &set_root, set_is_seen); tbl = kmemdup(userns_table, sizeof(userns_table), GFP_KERNEL); if (tbl) { + tbl[0].data = &ns->max_user_namespaces; + ns->sysctls = __register_sysctl_table(&ns->set, "userns", tbl); } if (!ns->sysctls) { @@ -78,6 +91,46 @@ void retire_userns_sysctls(struct user_namespace *ns) #endif } +static inline bool atomic_inc_below(atomic_t *v, int u) +{ + int c, old; + c = atomic_read(v); + for (;;) { + if (unlikely(c >= u)) + return false; + old = atomic_cmpxchg(v, c, c+1); + if (likely(old == c)) + return true; + c = old; + } +} + +bool inc_user_namespaces(struct user_namespace *ns) +{ + struct user_namespace *pos, *bad; + for (pos = ns; pos; pos = pos->parent) { + int max = READ_ONCE(pos->max_user_namespaces); + if (!atomic_inc_below(&pos->user_namespaces, max)) + goto fail; + } + return true; +fail: + bad = pos; + for (pos = ns; pos != bad; pos = pos->parent) + atomic_dec(&pos->user_namespaces); + + return false; +} + +void dec_user_namespaces(struct user_namespace *ns) +{ + struct user_namespace *pos; + for (pos = ns; pos; pos = pos->parent) { + int dec = atomic_dec_if_positive(&pos->user_namespaces); + WARN_ON_ONCE(dec < 0); + } +} + static __init int user_namespace_sysctl_init(void) { #ifdef CONFIG_SYSCTL diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index a63332253c7e..7d87017a0040 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -23,9 +23,6 @@ #include #include -extern bool setup_userns_sysctls(struct user_namespace *ns); -extern void retire_userns_sysctls(struct user_namespace *ns); - static struct kmem_cache *user_ns_cachep __read_mostly; static DEFINE_MUTEX(userns_state_mutex); @@ -34,6 +31,7 @@ static bool new_idmap_permitted(const struct file *file, struct uid_gid_map *map); static void free_user_ns(struct work_struct *work); + static void set_cred_user_ns(struct cred *cred, struct user_namespace *user_ns) { /* Start with the same capabilities as init but useless for doing @@ -68,8 +66,12 @@ int create_user_ns(struct cred *new) kgid_t group = new->egid; int ret; + ret = -EUSERS; if (parent_ns->level > 32) - return -EUSERS; + goto fail; + + if (!inc_user_namespaces(parent_ns)) + goto fail; /* * Verify that we can not violate the policy of which files @@ -77,26 +79,27 @@ int create_user_ns(struct cred *new) * by verifing that the root directory is at the root of the * mount namespace which allows all files to be accessed. */ + ret = -EPERM; if (current_chrooted()) - return -EPERM; + goto fail_dec; /* The creator needs a mapping in the parent user namespace * or else we won't be able to reasonably tell userspace who * created a user_namespace. */ + ret = -EPERM; if (!kuid_has_mapping(parent_ns, owner) || !kgid_has_mapping(parent_ns, group)) - return -EPERM; + goto fail_dec; + ret = -ENOMEM; ns = kmem_cache_zalloc(user_ns_cachep, GFP_KERNEL); if (!ns) - return -ENOMEM; + goto fail_dec; ret = ns_alloc_inum(&ns->ns); - if (ret) { - kmem_cache_free(user_ns_cachep, ns); - return ret; - } + if (ret) + goto fail_free; ns->ns.ops = &userns_operations; atomic_set(&ns->count, 1); @@ -106,6 +109,7 @@ int create_user_ns(struct cred *new) ns->owner = owner; ns->group = group; INIT_WORK(&ns->work, free_user_ns); + ns->max_user_namespaces = INT_MAX; /* Inherit USERNS_SETGROUPS_ALLOWED from our parent */ mutex_lock(&userns_state_mutex); @@ -126,7 +130,11 @@ fail_keyring: key_put(ns->persistent_keyring_register); #endif ns_free_inum(&ns->ns); +fail_free: kmem_cache_free(user_ns_cachep, ns); +fail_dec: + dec_user_namespaces(parent_ns); +fail: return ret; } @@ -163,6 +171,7 @@ static void free_user_ns(struct work_struct *work) #endif ns_free_inum(&ns->ns); kmem_cache_free(user_ns_cachep, ns); + dec_user_namespaces(parent); ns = parent; } while (atomic_dec_and_test(&parent->count)); } -- cgit v1.2.3 From f6b2db1a3e8d141dd144df58900fb0444d5d7c53 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 13:54:50 -0500 Subject: userns: Make the count of user namespaces per user Add a structure that is per user and per user ns and use it to hold the count of user namespaces. This makes prevents one user from creating denying service to another user by creating the maximum number of user namespaces. Rename the sysctl export of the maximum count from /proc/sys/userns/max_user_namespaces to /proc/sys/user/max_user_namespaces to reflect that the count is now per user. Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 18 +++++-- kernel/fork.c | 2 +- kernel/ucount.c | 116 +++++++++++++++++++++++++++++++++-------- kernel/user_namespace.c | 11 ++-- 4 files changed, 117 insertions(+), 30 deletions(-) (limited to 'include/linux') diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 6421cca2daa9..826de7a12a20 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -22,6 +22,7 @@ struct uid_gid_map { /* 64 bytes -- 1 cache line */ #define USERNS_INIT_FLAGS USERNS_SETGROUPS_ALLOWED +struct ucounts; struct user_namespace { struct uid_gid_map uid_map; struct uid_gid_map gid_map; @@ -44,15 +45,24 @@ struct user_namespace { struct ctl_table_set set; struct ctl_table_header *sysctls; #endif + struct ucounts *ucounts; int max_user_namespaces; +}; + +struct ucounts { + struct hlist_node node; + struct user_namespace *ns; + kuid_t uid; + atomic_t count; atomic_t user_namespaces; }; extern struct user_namespace init_user_ns; -extern bool setup_userns_sysctls(struct user_namespace *ns); -extern void retire_userns_sysctls(struct user_namespace *ns); -extern bool inc_user_namespaces(struct user_namespace *ns); -extern void dec_user_namespaces(struct user_namespace *ns); + +bool setup_userns_sysctls(struct user_namespace *ns); +void retire_userns_sysctls(struct user_namespace *ns); +struct ucounts *inc_user_namespaces(struct user_namespace *ns, kuid_t uid); +void dec_user_namespaces(struct ucounts *ucounts); #ifdef CONFIG_USER_NS diff --git a/kernel/fork.c b/kernel/fork.c index daa6a82b4900..d8cde533ace3 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -322,7 +322,7 @@ void __init fork_init(void) init_task.signal->rlim[RLIMIT_SIGPENDING] = init_task.signal->rlim[RLIMIT_NPROC]; - init_user_ns.max_user_namespaces = max_threads; + init_user_ns.max_user_namespaces = max_threads/2; } int __weak arch_dup_task_struct(struct task_struct *dst, diff --git a/kernel/ucount.c b/kernel/ucount.c index 6c2205c0befd..33c418718304 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -8,8 +8,20 @@ #include #include #include +#include #include +#define UCOUNTS_HASHTABLE_BITS 10 +static struct hlist_head ucounts_hashtable[(1 << UCOUNTS_HASHTABLE_BITS)]; +static DEFINE_SPINLOCK(ucounts_lock); + +#define ucounts_hashfn(ns, uid) \ + hash_long((unsigned long)__kuid_val(uid) + (unsigned long)(ns), \ + UCOUNTS_HASHTABLE_BITS) +#define ucounts_hashentry(ns, uid) \ + (ucounts_hashtable + ucounts_hashfn(ns, uid)) + + #ifdef CONFIG_SYSCTL static struct ctl_table_set * set_lookup(struct ctl_table_root *root) @@ -45,7 +57,7 @@ static struct ctl_table_root set_root = { static int zero = 0; static int int_max = INT_MAX; -static struct ctl_table userns_table[] = { +static struct ctl_table user_table[] = { { .procname = "max_user_namespaces", .data = &init_user_ns.max_user_namespaces, @@ -64,11 +76,11 @@ bool setup_userns_sysctls(struct user_namespace *ns) #ifdef CONFIG_SYSCTL struct ctl_table *tbl; setup_sysctl_set(&ns->set, &set_root, set_is_seen); - tbl = kmemdup(userns_table, sizeof(userns_table), GFP_KERNEL); + tbl = kmemdup(user_table, sizeof(user_table), GFP_KERNEL); if (tbl) { tbl[0].data = &ns->max_user_namespaces; - ns->sysctls = __register_sysctl_table(&ns->set, "userns", tbl); + ns->sysctls = __register_sysctl_table(&ns->set, "user", tbl); } if (!ns->sysctls) { kfree(tbl); @@ -91,6 +103,61 @@ void retire_userns_sysctls(struct user_namespace *ns) #endif } +static struct ucounts *find_ucounts(struct user_namespace *ns, kuid_t uid, struct hlist_head *hashent) +{ + struct ucounts *ucounts; + + hlist_for_each_entry(ucounts, hashent, node) { + if (uid_eq(ucounts->uid, uid) && (ucounts->ns == ns)) + return ucounts; + } + return NULL; +} + +static struct ucounts *get_ucounts(struct user_namespace *ns, kuid_t uid) +{ + struct hlist_head *hashent = ucounts_hashentry(ns, uid); + struct ucounts *ucounts, *new; + + spin_lock(&ucounts_lock); + ucounts = find_ucounts(ns, uid, hashent); + if (!ucounts) { + spin_unlock(&ucounts_lock); + + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) + return NULL; + + new->ns = ns; + new->uid = uid; + atomic_set(&new->count, 0); + + spin_lock(&ucounts_lock); + ucounts = find_ucounts(ns, uid, hashent); + if (ucounts) { + kfree(new); + } else { + hlist_add_head(&new->node, hashent); + ucounts = new; + } + } + if (!atomic_add_unless(&ucounts->count, 1, INT_MAX)) + ucounts = NULL; + spin_unlock(&ucounts_lock); + return ucounts; +} + +static void put_ucounts(struct ucounts *ucounts) +{ + if (atomic_dec_and_test(&ucounts->count)) { + spin_lock(&ucounts_lock); + hlist_del_init(&ucounts->node); + spin_unlock(&ucounts_lock); + + kfree(ucounts); + } +} + static inline bool atomic_inc_below(atomic_t *v, int u) { int c, old; @@ -105,44 +172,51 @@ static inline bool atomic_inc_below(atomic_t *v, int u) } } -bool inc_user_namespaces(struct user_namespace *ns) +struct ucounts *inc_user_namespaces(struct user_namespace *ns, kuid_t uid) { - struct user_namespace *pos, *bad; - for (pos = ns; pos; pos = pos->parent) { - int max = READ_ONCE(pos->max_user_namespaces); - if (!atomic_inc_below(&pos->user_namespaces, max)) + struct ucounts *ucounts, *iter, *bad; + struct user_namespace *tns; + ucounts = get_ucounts(ns, uid); + for (iter = ucounts; iter; iter = tns->ucounts) { + int max; + tns = iter->ns; + max = READ_ONCE(tns->max_user_namespaces); + if (!atomic_inc_below(&iter->user_namespaces, max)) goto fail; } - return true; + return ucounts; fail: - bad = pos; - for (pos = ns; pos != bad; pos = pos->parent) - atomic_dec(&pos->user_namespaces); + bad = iter; + for (iter = ucounts; iter != bad; iter = iter->ns->ucounts) + atomic_dec(&iter->user_namespaces); - return false; + put_ucounts(ucounts); + return NULL; } -void dec_user_namespaces(struct user_namespace *ns) +void dec_user_namespaces(struct ucounts *ucounts) { - struct user_namespace *pos; - for (pos = ns; pos; pos = pos->parent) { - int dec = atomic_dec_if_positive(&pos->user_namespaces); + struct ucounts *iter; + for (iter = ucounts; iter; iter = iter->ns->ucounts) { + int dec = atomic_dec_if_positive(&iter->user_namespaces); WARN_ON_ONCE(dec < 0); } + put_ucounts(ucounts); } + static __init int user_namespace_sysctl_init(void) { #ifdef CONFIG_SYSCTL - static struct ctl_table_header *userns_header; + static struct ctl_table_header *user_header; static struct ctl_table empty[1]; /* - * It is necessary to register the userns directory in the + * It is necessary to register the user directory in the * default set so that registrations in the child sets work * properly. */ - userns_header = register_sysctl("userns", empty); - BUG_ON(!userns_header); + user_header = register_sysctl("user", empty); + BUG_ON(!user_header); BUG_ON(!setup_userns_sysctls(&init_user_ns)); #endif return 0; diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 7d87017a0040..58c67e5f851c 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -31,7 +31,6 @@ static bool new_idmap_permitted(const struct file *file, struct uid_gid_map *map); static void free_user_ns(struct work_struct *work); - static void set_cred_user_ns(struct cred *cred, struct user_namespace *user_ns) { /* Start with the same capabilities as init but useless for doing @@ -64,13 +63,15 @@ int create_user_ns(struct cred *new) struct user_namespace *ns, *parent_ns = new->user_ns; kuid_t owner = new->euid; kgid_t group = new->egid; + struct ucounts *ucounts; int ret; ret = -EUSERS; if (parent_ns->level > 32) goto fail; - if (!inc_user_namespaces(parent_ns)) + ucounts = inc_user_namespaces(parent_ns, owner); + if (!ucounts) goto fail; /* @@ -110,6 +111,7 @@ int create_user_ns(struct cred *new) ns->group = group; INIT_WORK(&ns->work, free_user_ns); ns->max_user_namespaces = INT_MAX; + ns->ucounts = ucounts; /* Inherit USERNS_SETGROUPS_ALLOWED from our parent */ mutex_lock(&userns_state_mutex); @@ -133,7 +135,7 @@ fail_keyring: fail_free: kmem_cache_free(user_ns_cachep, ns); fail_dec: - dec_user_namespaces(parent_ns); + dec_user_namespaces(ucounts); fail: return ret; } @@ -164,6 +166,7 @@ static void free_user_ns(struct work_struct *work) container_of(work, struct user_namespace, work); do { + struct ucounts *ucounts = ns->ucounts; parent = ns->parent; retire_userns_sysctls(ns); #ifdef CONFIG_PERSISTENT_KEYRINGS @@ -171,7 +174,7 @@ static void free_user_ns(struct work_struct *work) #endif ns_free_inum(&ns->ns); kmem_cache_free(user_ns_cachep, ns); - dec_user_namespaces(parent); + dec_user_namespaces(ucounts); ns = parent; } while (atomic_dec_and_test(&parent->count)); } -- cgit v1.2.3 From 25f9c0817c535a728c1088542230fa327c577c9e Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:41:52 -0500 Subject: userns: Generalize the user namespace count into ucount The same kind of recursive sane default limit and policy countrol that has been implemented for the user namespace is desirable for the other namespaces, so generalize the user namespace refernce count into a ucount. Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 14 ++++++++++---- kernel/fork.c | 5 ++++- kernel/ucount.c | 39 +++++++++++++++++++++------------------ kernel/user_namespace.c | 16 ++++++++++++++-- 4 files changed, 49 insertions(+), 25 deletions(-) (limited to 'include/linux') diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 826de7a12a20..9b676ead35c3 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -23,6 +23,12 @@ struct uid_gid_map { /* 64 bytes -- 1 cache line */ #define USERNS_INIT_FLAGS USERNS_SETGROUPS_ALLOWED struct ucounts; + +enum ucount_type { + UCOUNT_USER_NAMESPACES, + UCOUNT_COUNTS, +}; + struct user_namespace { struct uid_gid_map uid_map; struct uid_gid_map gid_map; @@ -46,7 +52,7 @@ struct user_namespace { struct ctl_table_header *sysctls; #endif struct ucounts *ucounts; - int max_user_namespaces; + int ucount_max[UCOUNT_COUNTS]; }; struct ucounts { @@ -54,15 +60,15 @@ struct ucounts { struct user_namespace *ns; kuid_t uid; atomic_t count; - atomic_t user_namespaces; + atomic_t ucount[UCOUNT_COUNTS]; }; extern struct user_namespace init_user_ns; bool setup_userns_sysctls(struct user_namespace *ns); void retire_userns_sysctls(struct user_namespace *ns); -struct ucounts *inc_user_namespaces(struct user_namespace *ns, kuid_t uid); -void dec_user_namespaces(struct ucounts *ucounts); +struct ucounts *inc_ucount(struct user_namespace *ns, kuid_t uid, enum ucount_type type); +void dec_ucount(struct ucounts *ucounts, enum ucount_type type); #ifdef CONFIG_USER_NS diff --git a/kernel/fork.c b/kernel/fork.c index d8cde533ace3..3cb4853a59aa 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -302,6 +302,7 @@ int arch_task_struct_size __read_mostly; void __init fork_init(void) { + int i; #ifndef CONFIG_ARCH_TASK_STRUCT_ALLOCATOR #ifndef ARCH_MIN_TASKALIGN #define ARCH_MIN_TASKALIGN L1_CACHE_BYTES @@ -322,7 +323,9 @@ void __init fork_init(void) init_task.signal->rlim[RLIMIT_SIGPENDING] = init_task.signal->rlim[RLIMIT_NPROC]; - init_user_ns.max_user_namespaces = max_threads/2; + for (i = 0; i < UCOUNT_COUNTS; i++) { + init_user_ns.ucount_max[i] = max_threads/2; + } } int __weak arch_dup_task_struct(struct task_struct *dst, diff --git a/kernel/ucount.c b/kernel/ucount.c index 33c418718304..0f9ab3b26185 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -57,16 +57,17 @@ static struct ctl_table_root set_root = { static int zero = 0; static int int_max = INT_MAX; +#define UCOUNT_ENTRY(name) \ + { \ + .procname = name, \ + .maxlen = sizeof(int), \ + .mode = 0644, \ + .proc_handler = proc_dointvec_minmax, \ + .extra1 = &zero, \ + .extra2 = &int_max, \ + } static struct ctl_table user_table[] = { - { - .procname = "max_user_namespaces", - .data = &init_user_ns.max_user_namespaces, - .maxlen = sizeof(init_user_ns.max_user_namespaces), - .mode = 0644, - .proc_handler = proc_dointvec_minmax, - .extra1 = &zero, - .extra2 = &int_max, - }, + UCOUNT_ENTRY("max_user_namespaces"), { } }; #endif /* CONFIG_SYSCTL */ @@ -78,8 +79,10 @@ bool setup_userns_sysctls(struct user_namespace *ns) setup_sysctl_set(&ns->set, &set_root, set_is_seen); tbl = kmemdup(user_table, sizeof(user_table), GFP_KERNEL); if (tbl) { - tbl[0].data = &ns->max_user_namespaces; - + int i; + for (i = 0; i < UCOUNT_COUNTS; i++) { + tbl[i].data = &ns->ucount_max[i]; + } ns->sysctls = __register_sysctl_table(&ns->set, "user", tbl); } if (!ns->sysctls) { @@ -172,7 +175,8 @@ static inline bool atomic_inc_below(atomic_t *v, int u) } } -struct ucounts *inc_user_namespaces(struct user_namespace *ns, kuid_t uid) +struct ucounts *inc_ucount(struct user_namespace *ns, kuid_t uid, + enum ucount_type type) { struct ucounts *ucounts, *iter, *bad; struct user_namespace *tns; @@ -180,31 +184,30 @@ struct ucounts *inc_user_namespaces(struct user_namespace *ns, kuid_t uid) for (iter = ucounts; iter; iter = tns->ucounts) { int max; tns = iter->ns; - max = READ_ONCE(tns->max_user_namespaces); - if (!atomic_inc_below(&iter->user_namespaces, max)) + max = READ_ONCE(tns->ucount_max[type]); + if (!atomic_inc_below(&iter->ucount[type], max)) goto fail; } return ucounts; fail: bad = iter; for (iter = ucounts; iter != bad; iter = iter->ns->ucounts) - atomic_dec(&iter->user_namespaces); + atomic_dec(&iter->ucount[type]); put_ucounts(ucounts); return NULL; } -void dec_user_namespaces(struct ucounts *ucounts) +void dec_ucount(struct ucounts *ucounts, enum ucount_type type) { struct ucounts *iter; for (iter = ucounts; iter; iter = iter->ns->ucounts) { - int dec = atomic_dec_if_positive(&iter->user_namespaces); + int dec = atomic_dec_if_positive(&iter->ucount[type]); WARN_ON_ONCE(dec < 0); } put_ucounts(ucounts); } - static __init int user_namespace_sysctl_init(void) { #ifdef CONFIG_SYSCTL diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 58c67e5f851c..0edafe305861 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -31,6 +31,16 @@ static bool new_idmap_permitted(const struct file *file, struct uid_gid_map *map); static void free_user_ns(struct work_struct *work); +static struct ucounts *inc_user_namespaces(struct user_namespace *ns, kuid_t uid) +{ + return inc_ucount(ns, uid, UCOUNT_USER_NAMESPACES); +} + +static void dec_user_namespaces(struct ucounts *ucounts) +{ + return dec_ucount(ucounts, UCOUNT_USER_NAMESPACES); +} + static void set_cred_user_ns(struct cred *cred, struct user_namespace *user_ns) { /* Start with the same capabilities as init but useless for doing @@ -64,7 +74,7 @@ int create_user_ns(struct cred *new) kuid_t owner = new->euid; kgid_t group = new->egid; struct ucounts *ucounts; - int ret; + int ret, i; ret = -EUSERS; if (parent_ns->level > 32) @@ -110,7 +120,9 @@ int create_user_ns(struct cred *new) ns->owner = owner; ns->group = group; INIT_WORK(&ns->work, free_user_ns); - ns->max_user_namespaces = INT_MAX; + for (i = 0; i < UCOUNT_COUNTS; i++) { + ns->ucount_max[i] = INT_MAX; + } ns->ucounts = ucounts; /* Inherit USERNS_SETGROUPS_ALLOWED from our parent */ -- cgit v1.2.3 From f333c700c6100b53050980986be922bb21466e29 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:08:36 -0500 Subject: pidns: Add a limit on the number of pid namespaces Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/pid_namespace.h | 1 + include/linux/user_namespace.h | 1 + kernel/pid_namespace.c | 25 +++++++++++++++++++++---- kernel/ucount.c | 1 + 4 files changed, 24 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/include/linux/pid_namespace.h b/include/linux/pid_namespace.h index 918b117a7cd3..34cce96741bc 100644 --- a/include/linux/pid_namespace.h +++ b/include/linux/pid_namespace.h @@ -40,6 +40,7 @@ struct pid_namespace { struct fs_pin *bacct; #endif struct user_namespace *user_ns; + struct ucounts *ucounts; struct work_struct proc_work; kgid_t pid_gid; int hide_pid; diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 9b676ead35c3..9ee94827728d 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -26,6 +26,7 @@ struct ucounts; enum ucount_type { UCOUNT_USER_NAMESPACES, + UCOUNT_PID_NAMESPACES, UCOUNT_COUNTS, }; diff --git a/kernel/pid_namespace.c b/kernel/pid_namespace.c index a65ba137fd15..30a7f3351932 100644 --- a/kernel/pid_namespace.c +++ b/kernel/pid_namespace.c @@ -79,23 +79,36 @@ static void proc_cleanup_work(struct work_struct *work) /* MAX_PID_NS_LEVEL is needed for limiting size of 'struct pid' */ #define MAX_PID_NS_LEVEL 32 +static struct ucounts *inc_pid_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_PID_NAMESPACES); +} + +static void dec_pid_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_PID_NAMESPACES); +} + static struct pid_namespace *create_pid_namespace(struct user_namespace *user_ns, struct pid_namespace *parent_pid_ns) { struct pid_namespace *ns; unsigned int level = parent_pid_ns->level + 1; + struct ucounts *ucounts; int i; int err; - if (level > MAX_PID_NS_LEVEL) { - err = -EINVAL; + err = -EINVAL; + if (level > MAX_PID_NS_LEVEL) + goto out; + ucounts = inc_pid_namespaces(user_ns); + if (!ucounts) goto out; - } err = -ENOMEM; ns = kmem_cache_zalloc(pid_ns_cachep, GFP_KERNEL); if (ns == NULL) - goto out; + goto out_dec; ns->pidmap[0].page = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!ns->pidmap[0].page) @@ -114,6 +127,7 @@ static struct pid_namespace *create_pid_namespace(struct user_namespace *user_ns ns->level = level; ns->parent = get_pid_ns(parent_pid_ns); ns->user_ns = get_user_ns(user_ns); + ns->ucounts = ucounts; ns->nr_hashed = PIDNS_HASH_ADDING; INIT_WORK(&ns->proc_work, proc_cleanup_work); @@ -129,6 +143,8 @@ out_free_map: kfree(ns->pidmap[0].page); out_free: kmem_cache_free(pid_ns_cachep, ns); +out_dec: + dec_pid_namespaces(ucounts); out: return ERR_PTR(err); } @@ -146,6 +162,7 @@ static void destroy_pid_namespace(struct pid_namespace *ns) ns_free_inum(&ns->ns); for (i = 0; i < PIDMAP_ENTRIES; i++) kfree(ns->pidmap[i].page); + dec_pid_namespaces(ns->ucounts); put_user_ns(ns->user_ns); call_rcu(&ns->rcu, delayed_free_pidns); } diff --git a/kernel/ucount.c b/kernel/ucount.c index 0f9ab3b26185..66eca94e4ada 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -68,6 +68,7 @@ static int int_max = INT_MAX; } static struct ctl_table user_table[] = { UCOUNT_ENTRY("max_user_namespaces"), + UCOUNT_ENTRY("max_pid_namespaces"), { } }; #endif /* CONFIG_SYSCTL */ -- cgit v1.2.3 From f7af3d1c03136275b876f58644599b120cf4ffdd Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:11:25 -0500 Subject: utsns: Add a limit on the number of uts namespaces Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 1 + include/linux/utsname.h | 1 + kernel/ucount.c | 1 + kernel/utsname.c | 34 +++++++++++++++++++++++++++++----- 4 files changed, 32 insertions(+), 5 deletions(-) (limited to 'include/linux') diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 9ee94827728d..f9df7dd2609a 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -27,6 +27,7 @@ struct ucounts; enum ucount_type { UCOUNT_USER_NAMESPACES, UCOUNT_PID_NAMESPACES, + UCOUNT_UTS_NAMESPACES, UCOUNT_COUNTS, }; diff --git a/include/linux/utsname.h b/include/linux/utsname.h index 5093f58ae192..60f0bb83b313 100644 --- a/include/linux/utsname.h +++ b/include/linux/utsname.h @@ -24,6 +24,7 @@ struct uts_namespace { struct kref kref; struct new_utsname name; struct user_namespace *user_ns; + struct ucounts *ucounts; struct ns_common ns; }; extern struct uts_namespace init_uts_ns; diff --git a/kernel/ucount.c b/kernel/ucount.c index 66eca94e4ada..866850e2eb92 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -69,6 +69,7 @@ static int int_max = INT_MAX; static struct ctl_table user_table[] = { UCOUNT_ENTRY("max_user_namespaces"), UCOUNT_ENTRY("max_pid_namespaces"), + UCOUNT_ENTRY("max_uts_namespaces"), { } }; #endif /* CONFIG_SYSCTL */ diff --git a/kernel/utsname.c b/kernel/utsname.c index 831ea7108232..f3b0bb4ac3ba 100644 --- a/kernel/utsname.c +++ b/kernel/utsname.c @@ -17,6 +17,16 @@ #include #include +static struct ucounts *inc_uts_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_UTS_NAMESPACES); +} + +static void dec_uts_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_UTS_NAMESPACES); +} + static struct uts_namespace *create_uts_ns(void) { struct uts_namespace *uts_ns; @@ -36,18 +46,24 @@ static struct uts_namespace *clone_uts_ns(struct user_namespace *user_ns, struct uts_namespace *old_ns) { struct uts_namespace *ns; + struct ucounts *ucounts; int err; + err = -ENFILE; + ucounts = inc_uts_namespaces(user_ns); + if (!ucounts) + goto fail; + + err = -ENOMEM; ns = create_uts_ns(); if (!ns) - return ERR_PTR(-ENOMEM); + goto fail_dec; err = ns_alloc_inum(&ns->ns); - if (err) { - kfree(ns); - return ERR_PTR(err); - } + if (err) + goto fail_free; + ns->ucounts = ucounts; ns->ns.ops = &utsns_operations; down_read(&uts_sem); @@ -55,6 +71,13 @@ static struct uts_namespace *clone_uts_ns(struct user_namespace *user_ns, ns->user_ns = get_user_ns(user_ns); up_read(&uts_sem); return ns; + +fail_free: + kfree(ns); +fail_dec: + dec_uts_namespaces(ucounts); +fail: + return ERR_PTR(err); } /* @@ -85,6 +108,7 @@ void free_uts_ns(struct kref *kref) struct uts_namespace *ns; ns = container_of(kref, struct uts_namespace, kref); + dec_uts_namespaces(ns->ucounts); put_user_ns(ns->user_ns); ns_free_inum(&ns->ns); kfree(ns); -- cgit v1.2.3 From aba356616386e6e573a34c6d64ed12443686e5c8 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:20:23 -0500 Subject: ipcns: Add a limit on the number of ipc namespaces Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/ipc_namespace.h | 1 + include/linux/user_namespace.h | 1 + ipc/namespace.c | 45 +++++++++++++++++++++++++++++++----------- kernel/ucount.c | 1 + 4 files changed, 37 insertions(+), 11 deletions(-) (limited to 'include/linux') diff --git a/include/linux/ipc_namespace.h b/include/linux/ipc_namespace.h index d10e54f03c09..848e5796400e 100644 --- a/include/linux/ipc_namespace.h +++ b/include/linux/ipc_namespace.h @@ -58,6 +58,7 @@ struct ipc_namespace { /* user_ns which owns the ipc ns */ struct user_namespace *user_ns; + struct ucounts *ucounts; struct ns_common ns; }; diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index f9df7dd2609a..e1d672186f00 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -28,6 +28,7 @@ enum ucount_type { UCOUNT_USER_NAMESPACES, UCOUNT_PID_NAMESPACES, UCOUNT_UTS_NAMESPACES, + UCOUNT_IPC_NAMESPACES, UCOUNT_COUNTS, }; diff --git a/ipc/namespace.c b/ipc/namespace.c index d87e6baa1323..730914214135 100644 --- a/ipc/namespace.c +++ b/ipc/namespace.c @@ -16,39 +16,61 @@ #include "util.h" +static struct ucounts *inc_ipc_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_IPC_NAMESPACES); +} + +static void dec_ipc_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_IPC_NAMESPACES); +} + static struct ipc_namespace *create_ipc_ns(struct user_namespace *user_ns, struct ipc_namespace *old_ns) { struct ipc_namespace *ns; + struct ucounts *ucounts; int err; + err = -ENFILE; + ucounts = inc_ipc_namespaces(user_ns); + if (!ucounts) + goto fail; + + err = -ENOMEM; ns = kmalloc(sizeof(struct ipc_namespace), GFP_KERNEL); if (ns == NULL) - return ERR_PTR(-ENOMEM); + goto fail_dec; err = ns_alloc_inum(&ns->ns); - if (err) { - kfree(ns); - return ERR_PTR(err); - } + if (err) + goto fail_free; ns->ns.ops = &ipcns_operations; atomic_set(&ns->count, 1); ns->user_ns = get_user_ns(user_ns); + ns->ucounts = ucounts; err = mq_init_ns(ns); - if (err) { - put_user_ns(ns->user_ns); - ns_free_inum(&ns->ns); - kfree(ns); - return ERR_PTR(err); - } + if (err) + goto fail_put; sem_init_ns(ns); msg_init_ns(ns); shm_init_ns(ns); return ns; + +fail_put: + put_user_ns(ns->user_ns); + ns_free_inum(&ns->ns); +fail_free: + kfree(ns); +fail_dec: + dec_ipc_namespaces(ucounts); +fail: + return ERR_PTR(err); } struct ipc_namespace *copy_ipcs(unsigned long flags, @@ -96,6 +118,7 @@ static void free_ipc_ns(struct ipc_namespace *ns) msg_exit_ns(ns); shm_exit_ns(ns); + dec_ipc_namespaces(ns->ucounts); put_user_ns(ns->user_ns); ns_free_inum(&ns->ns); kfree(ns); diff --git a/kernel/ucount.c b/kernel/ucount.c index 866850e2eb92..fbab75424da6 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -70,6 +70,7 @@ static struct ctl_table user_table[] = { UCOUNT_ENTRY("max_user_namespaces"), UCOUNT_ENTRY("max_pid_namespaces"), UCOUNT_ENTRY("max_uts_namespaces"), + UCOUNT_ENTRY("max_ipc_namespaces"), { } }; #endif /* CONFIG_SYSCTL */ -- cgit v1.2.3 From d08311dd6fd8444e39710dd2fb97562895aed8fa Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:25:30 -0500 Subject: cgroupns: Add a limit on the number of cgroup namespaces Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/cgroup.h | 1 + include/linux/user_namespace.h | 1 + kernel/cgroup.c | 18 ++++++++++++++++++ kernel/ucount.c | 1 + 4 files changed, 21 insertions(+) (limited to 'include/linux') diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h index 984f73b719a9..1ed92812785a 100644 --- a/include/linux/cgroup.h +++ b/include/linux/cgroup.h @@ -621,6 +621,7 @@ struct cgroup_namespace { atomic_t count; struct ns_common ns; struct user_namespace *user_ns; + struct ucounts *ucounts; struct css_set *root_cset; }; diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index e1d672186f00..d067f0d3038e 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -29,6 +29,7 @@ enum ucount_type { UCOUNT_PID_NAMESPACES, UCOUNT_UTS_NAMESPACES, UCOUNT_IPC_NAMESPACES, + UCOUNT_CGROUP_NAMESPACES, UCOUNT_COUNTS, }; diff --git a/kernel/cgroup.c b/kernel/cgroup.c index d1c51b7f5221..e9e4427fec46 100644 --- a/kernel/cgroup.c +++ b/kernel/cgroup.c @@ -6295,6 +6295,16 @@ void cgroup_sk_free(struct sock_cgroup_data *skcd) /* cgroup namespaces */ +static struct ucounts *inc_cgroup_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_CGROUP_NAMESPACES); +} + +static void dec_cgroup_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_CGROUP_NAMESPACES); +} + static struct cgroup_namespace *alloc_cgroup_ns(void) { struct cgroup_namespace *new_ns; @@ -6316,6 +6326,7 @@ static struct cgroup_namespace *alloc_cgroup_ns(void) void free_cgroup_ns(struct cgroup_namespace *ns) { put_css_set(ns->root_cset); + dec_cgroup_namespaces(ns->ucounts); put_user_ns(ns->user_ns); ns_free_inum(&ns->ns); kfree(ns); @@ -6327,6 +6338,7 @@ struct cgroup_namespace *copy_cgroup_ns(unsigned long flags, struct cgroup_namespace *old_ns) { struct cgroup_namespace *new_ns; + struct ucounts *ucounts; struct css_set *cset; BUG_ON(!old_ns); @@ -6340,6 +6352,10 @@ struct cgroup_namespace *copy_cgroup_ns(unsigned long flags, if (!ns_capable(user_ns, CAP_SYS_ADMIN)) return ERR_PTR(-EPERM); + ucounts = inc_cgroup_namespaces(user_ns); + if (!ucounts) + return ERR_PTR(-ENFILE); + /* It is not safe to take cgroup_mutex here */ spin_lock_irq(&css_set_lock); cset = task_css_set(current); @@ -6349,10 +6365,12 @@ struct cgroup_namespace *copy_cgroup_ns(unsigned long flags, new_ns = alloc_cgroup_ns(); if (IS_ERR(new_ns)) { put_css_set(cset); + dec_cgroup_namespaces(ucounts); return new_ns; } new_ns->user_ns = get_user_ns(user_ns); + new_ns->ucounts = ucounts; new_ns->root_cset = cset; return new_ns; diff --git a/kernel/ucount.c b/kernel/ucount.c index fbab75424da6..335cc5d2cdd7 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -71,6 +71,7 @@ static struct ctl_table user_table[] = { UCOUNT_ENTRY("max_pid_namespaces"), UCOUNT_ENTRY("max_uts_namespaces"), UCOUNT_ENTRY("max_ipc_namespaces"), + UCOUNT_ENTRY("max_cgroup_namespaces"), { } }; #endif /* CONFIG_SYSCTL */ -- cgit v1.2.3 From 703286608a220d53584cca5986aad5305eec75ed Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 8 Aug 2016 14:33:23 -0500 Subject: netns: Add a limit on the number of net namespaces Acked-by: Kees Cook Signed-off-by: "Eric W. Biederman" --- include/linux/user_namespace.h | 1 + include/net/net_namespace.h | 1 + kernel/ucount.c | 1 + net/core/net_namespace.c | 22 +++++++++++++++++++++- 4 files changed, 24 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index d067f0d3038e..c6bc980b06a9 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -29,6 +29,7 @@ enum ucount_type { UCOUNT_PID_NAMESPACES, UCOUNT_UTS_NAMESPACES, UCOUNT_IPC_NAMESPACES, + UCOUNT_NET_NAMESPACES, UCOUNT_CGROUP_NAMESPACES, UCOUNT_COUNTS, }; diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index 0933c7455a30..fc4f757107df 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -60,6 +60,7 @@ struct net { struct list_head exit_list; /* Use only net_mutex */ struct user_namespace *user_ns; /* Owning user namespace */ + struct ucounts *ucounts; spinlock_t nsid_lock; struct idr netns_ids; diff --git a/kernel/ucount.c b/kernel/ucount.c index 335cc5d2cdd7..205f1a07faac 100644 --- a/kernel/ucount.c +++ b/kernel/ucount.c @@ -71,6 +71,7 @@ static struct ctl_table user_table[] = { UCOUNT_ENTRY("max_pid_namespaces"), UCOUNT_ENTRY("max_uts_namespaces"), UCOUNT_ENTRY("max_ipc_namespaces"), + UCOUNT_ENTRY("max_net_namespaces"), UCOUNT_ENTRY("max_cgroup_namespaces"), { } }; diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index 2c2eb1b629b1..3e2812aeceb7 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -266,6 +266,16 @@ struct net *get_net_ns_by_id(struct net *net, int id) return peer; } +static struct ucounts *inc_net_namespaces(struct user_namespace *ns) +{ + return inc_ucount(ns, current_euid(), UCOUNT_NET_NAMESPACES); +} + +static void dec_net_namespaces(struct ucounts *ucounts) +{ + dec_ucount(ucounts, UCOUNT_NET_NAMESPACES); +} + /* * setup_net runs the initializers for the network namespace object. */ @@ -351,19 +361,27 @@ void net_drop_ns(void *p) struct net *copy_net_ns(unsigned long flags, struct user_namespace *user_ns, struct net *old_net) { + struct ucounts *ucounts; struct net *net; int rv; if (!(flags & CLONE_NEWNET)) return get_net(old_net); + ucounts = inc_net_namespaces(user_ns); + if (!ucounts) + return ERR_PTR(-ENFILE); + net = net_alloc(); - if (!net) + if (!net) { + dec_net_namespaces(ucounts); return ERR_PTR(-ENOMEM); + } get_user_ns(user_ns); mutex_lock(&net_mutex); + net->ucounts = ucounts; rv = setup_net(net, user_ns); if (rv == 0) { rtnl_lock(); @@ -372,6 +390,7 @@ struct net *copy_net_ns(unsigned long flags, } mutex_unlock(&net_mutex); if (rv < 0) { + dec_net_namespaces(ucounts); put_user_ns(user_ns); net_drop_ns(net); return ERR_PTR(rv); @@ -444,6 +463,7 @@ static void cleanup_net(struct work_struct *work) /* Finally it is safe to free my network namespace structure */ list_for_each_entry_safe(net, tmp, &net_exit_list, exit_list) { list_del_init(&net->exit_list); + dec_net_namespaces(net->ucounts); put_user_ns(net->user_ns); net_drop_ns(net); } -- cgit v1.2.3 From d8ad8b49618410ddeafd78465b63a6cedd6c9484 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Wed, 13 Jul 2016 11:13:56 -0400 Subject: security, overlayfs: provide copy up security hook for unioned files Provide a security hook to label new file correctly when a file is copied up from lower layer to upper layer of a overlay/union mount. This hook can prepare a new set of creds which are suitable for new file creation during copy up. Caller will use new creds to create file and then revert back to old creds and release new creds. Signed-off-by: Vivek Goyal Acked-by: Stephen Smalley [PM: whitespace cleanup to appease checkpatch.pl] Signed-off-by: Paul Moore --- fs/overlayfs/copy_up.c | 15 +++++++++++++++ include/linux/lsm_hooks.h | 11 +++++++++++ include/linux/security.h | 6 ++++++ security/security.c | 8 ++++++++ 4 files changed, 40 insertions(+) (limited to 'include/linux') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 54e5d6681786..c297b1f5a05e 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -246,6 +246,8 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, struct dentry *upper = NULL; umode_t mode = stat->mode; int err; + const struct cred *old_creds = NULL; + struct cred *new_creds = NULL; newdentry = ovl_lookup_temp(workdir, dentry); err = PTR_ERR(newdentry); @@ -258,10 +260,23 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, if (IS_ERR(upper)) goto out1; + err = security_inode_copy_up(dentry, &new_creds); + if (err < 0) + goto out2; + + if (new_creds) + old_creds = override_creds(new_creds); + /* Can't properly set mode on creation because of the umask */ stat->mode &= S_IFMT; err = ovl_create_real(wdir, newdentry, stat, link, NULL, true); stat->mode = mode; + + if (new_creds) { + revert_creds(old_creds); + put_cred(new_creds); + } + if (err) goto out2; diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 101bf19c0f41..cb69fc829053 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -401,6 +401,15 @@ * @inode contains a pointer to the inode. * @secid contains a pointer to the location where result will be saved. * In case of failure, @secid will be set to zero. + * @inode_copy_up: + * A file is about to be copied up from lower layer to upper layer of + * overlay filesystem. Security module can prepare a set of new creds + * and modify as need be and return new creds. Caller will switch to + * new creds temporarily to create new file and release newly allocated + * creds. + * @src indicates the union dentry of file that is being copied up. + * @new pointer to pointer to return newly allocated creds. + * Returns 0 on success or a negative error code on error. * * Security hooks for file operations * @@ -1425,6 +1434,7 @@ union security_list_options { int (*inode_listsecurity)(struct inode *inode, char *buffer, size_t buffer_size); void (*inode_getsecid)(struct inode *inode, u32 *secid); + int (*inode_copy_up)(struct dentry *src, struct cred **new); int (*file_permission)(struct file *file, int mask); int (*file_alloc_security)(struct file *file); @@ -1696,6 +1706,7 @@ struct security_hook_heads { struct list_head inode_setsecurity; struct list_head inode_listsecurity; struct list_head inode_getsecid; + struct list_head inode_copy_up; struct list_head file_permission; struct list_head file_alloc_security; struct list_head file_free_security; diff --git a/include/linux/security.h b/include/linux/security.h index 7831cd57bcf7..c5b0ccd6c8b6 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -282,6 +282,7 @@ int security_inode_getsecurity(struct inode *inode, const char *name, void **buf int security_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags); int security_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size); void security_inode_getsecid(struct inode *inode, u32 *secid); +int security_inode_copy_up(struct dentry *src, struct cred **new); int security_file_permission(struct file *file, int mask); int security_file_alloc(struct file *file); void security_file_free(struct file *file); @@ -758,6 +759,11 @@ static inline void security_inode_getsecid(struct inode *inode, u32 *secid) *secid = 0; } +static inline int security_inode_copy_up(struct dentry *src, struct cred **new) +{ + return 0; +} + static inline int security_file_permission(struct file *file, int mask) { return 0; diff --git a/security/security.c b/security/security.c index 4838e7fefa1f..f2a7f27bd3e9 100644 --- a/security/security.c +++ b/security/security.c @@ -748,6 +748,12 @@ void security_inode_getsecid(struct inode *inode, u32 *secid) call_void_hook(inode_getsecid, inode, secid); } +int security_inode_copy_up(struct dentry *src, struct cred **new) +{ + return call_int_hook(inode_copy_up, 0, src, new); +} +EXPORT_SYMBOL(security_inode_copy_up); + int security_file_permission(struct file *file, int mask) { int ret; @@ -1684,6 +1690,8 @@ struct security_hook_heads security_hook_heads = { LIST_HEAD_INIT(security_hook_heads.inode_listsecurity), .inode_getsecid = LIST_HEAD_INIT(security_hook_heads.inode_getsecid), + .inode_copy_up = + LIST_HEAD_INIT(security_hook_heads.inode_copy_up), .file_permission = LIST_HEAD_INIT(security_hook_heads.file_permission), .file_alloc_security = -- cgit v1.2.3 From 121ab822ef21914adac2fa3730efeeb8fd762473 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Wed, 13 Jul 2016 10:44:49 -0400 Subject: security,overlayfs: Provide security hook for copy up of xattrs for overlay file Provide a security hook which is called when xattrs of a file are being copied up. This hook is called once for each xattr and LSM can return 0 if the security module wants the xattr to be copied up, 1 if the security module wants the xattr to be discarded on the copy, -EOPNOTSUPP if the security module does not handle/manage the xattr, or a -errno upon an error. Signed-off-by: David Howells Signed-off-by: Vivek Goyal Acked-by: Stephen Smalley [PM: whitespace cleanup for checkpatch.pl] Signed-off-by: Paul Moore --- fs/overlayfs/copy_up.c | 7 +++++++ include/linux/lsm_hooks.h | 10 ++++++++++ include/linux/security.h | 6 ++++++ security/security.c | 8 ++++++++ 4 files changed, 31 insertions(+) (limited to 'include/linux') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index c297b1f5a05e..cd65f12b3464 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -103,6 +103,13 @@ retry: goto retry; } + error = security_inode_copy_up_xattr(name); + if (error < 0 && error != -EOPNOTSUPP) + break; + if (error == 1) { + error = 0; + continue; /* Discard */ + } error = vfs_setxattr(new, name, value, size, 0); if (error) break; diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index cb69fc829053..57971229551b 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -410,6 +410,14 @@ * @src indicates the union dentry of file that is being copied up. * @new pointer to pointer to return newly allocated creds. * Returns 0 on success or a negative error code on error. + * @inode_copy_up_xattr: + * Filter the xattrs being copied up when a unioned file is copied + * up from a lower layer to the union/overlay layer. + * @name indicates the name of the xattr. + * Returns 0 to accept the xattr, 1 to discard the xattr, -EOPNOTSUPP if + * security module does not know about attribute or a negative error code + * to abort the copy up. Note that the caller is responsible for reading + * and writing the xattrs as this hook is merely a filter. * * Security hooks for file operations * @@ -1435,6 +1443,7 @@ union security_list_options { size_t buffer_size); void (*inode_getsecid)(struct inode *inode, u32 *secid); int (*inode_copy_up)(struct dentry *src, struct cred **new); + int (*inode_copy_up_xattr)(const char *name); int (*file_permission)(struct file *file, int mask); int (*file_alloc_security)(struct file *file); @@ -1707,6 +1716,7 @@ struct security_hook_heads { struct list_head inode_listsecurity; struct list_head inode_getsecid; struct list_head inode_copy_up; + struct list_head inode_copy_up_xattr; struct list_head file_permission; struct list_head file_alloc_security; struct list_head file_free_security; diff --git a/include/linux/security.h b/include/linux/security.h index c5b0ccd6c8b6..536fafdfa10a 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -283,6 +283,7 @@ int security_inode_setsecurity(struct inode *inode, const char *name, const void int security_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size); void security_inode_getsecid(struct inode *inode, u32 *secid); int security_inode_copy_up(struct dentry *src, struct cred **new); +int security_inode_copy_up_xattr(const char *name); int security_file_permission(struct file *file, int mask); int security_file_alloc(struct file *file); void security_file_free(struct file *file); @@ -764,6 +765,11 @@ static inline int security_inode_copy_up(struct dentry *src, struct cred **new) return 0; } +static inline int security_inode_copy_up_xattr(const char *name) +{ + return -EOPNOTSUPP; +} + static inline int security_file_permission(struct file *file, int mask) { return 0; diff --git a/security/security.c b/security/security.c index f2a7f27bd3e9..a9e2bb9fb9d3 100644 --- a/security/security.c +++ b/security/security.c @@ -754,6 +754,12 @@ int security_inode_copy_up(struct dentry *src, struct cred **new) } EXPORT_SYMBOL(security_inode_copy_up); +int security_inode_copy_up_xattr(const char *name) +{ + return call_int_hook(inode_copy_up_xattr, -EOPNOTSUPP, name); +} +EXPORT_SYMBOL(security_inode_copy_up_xattr); + int security_file_permission(struct file *file, int mask) { int ret; @@ -1692,6 +1698,8 @@ struct security_hook_heads security_hook_heads = { LIST_HEAD_INIT(security_hook_heads.inode_getsecid), .inode_copy_up = LIST_HEAD_INIT(security_hook_heads.inode_copy_up), + .inode_copy_up_xattr = + LIST_HEAD_INIT(security_hook_heads.inode_copy_up_xattr), .file_permission = LIST_HEAD_INIT(security_hook_heads.file_permission), .file_alloc_security = -- cgit v1.2.3 From 2602625b7e46576b00db619ac788c508ba3bcb2c Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Wed, 13 Jul 2016 10:44:52 -0400 Subject: security, overlayfs: Provide hook to correctly label newly created files During a new file creation we need to make sure new file is created with the right label. New file is created in upper/ so effectively file should get label as if task had created file in upper/. We switched to mounter's creds for actual file creation. Also if there is a whiteout present, then file will be created in work/ dir first and then renamed in upper. In none of the cases file will be labeled as we want it to be. This patch introduces a new hook dentry_create_files_as(), which determines the label/context dentry will get if it had been created by task in upper and modify passed set of creds appropriately. Caller makes use of these new creds for file creation. Signed-off-by: Vivek Goyal Acked-by: Stephen Smalley [PM: fix whitespace issues found with checkpatch.pl] [PM: changes to use stat->mode in ovl_create_or_link()] Signed-off-by: Paul Moore --- fs/overlayfs/dir.c | 10 ++++++++++ include/linux/lsm_hooks.h | 15 +++++++++++++++ include/linux/security.h | 12 ++++++++++++ security/security.c | 11 +++++++++++ 4 files changed, 48 insertions(+) (limited to 'include/linux') diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 12bcd07b9e32..a155e52db9fe 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -435,6 +435,15 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, if (override_cred) { override_cred->fsuid = inode->i_uid; override_cred->fsgid = inode->i_gid; + if (!hardlink) { + err = security_dentry_create_files_as(dentry, + stat->mode, &dentry->d_name, old_cred, + override_cred); + if (err) { + put_cred(override_cred); + goto out_revert_creds; + } + } put_cred(override_creds(override_cred)); put_cred(override_cred); @@ -445,6 +454,7 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, err = ovl_create_over_whiteout(dentry, inode, stat, link, hardlink); } +out_revert_creds: revert_creds(old_cred); if (!err) { struct inode *realinode = d_inode(ovl_dentry_upper(dentry)); diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 57971229551b..f2af2af131ac 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -151,6 +151,16 @@ * @name name of the last path component used to create file * @ctx pointer to place the pointer to the resulting context in. * @ctxlen point to place the length of the resulting context. + * @dentry_create_files_as: + * Compute a context for a dentry as the inode is not yet available + * and set that context in passed in creds so that new files are + * created using that context. Context is calculated using the + * passed in creds and not the creds of the caller. + * @dentry dentry to use in calculating the context. + * @mode mode used to determine resource type. + * @name name of the last path component used to create file + * @old creds which should be used for context calculation + * @new creds to modify * * * Security hooks for inode operations. @@ -1375,6 +1385,10 @@ union security_list_options { int (*dentry_init_security)(struct dentry *dentry, int mode, const struct qstr *name, void **ctx, u32 *ctxlen); + int (*dentry_create_files_as)(struct dentry *dentry, int mode, + struct qstr *name, + const struct cred *old, + struct cred *new); #ifdef CONFIG_SECURITY_PATH @@ -1675,6 +1689,7 @@ struct security_hook_heads { struct list_head sb_clone_mnt_opts; struct list_head sb_parse_opts_str; struct list_head dentry_init_security; + struct list_head dentry_create_files_as; #ifdef CONFIG_SECURITY_PATH struct list_head path_unlink; struct list_head path_mkdir; diff --git a/include/linux/security.h b/include/linux/security.h index 536fafdfa10a..a6c6d5d0fa5d 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -242,6 +242,10 @@ int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts); int security_dentry_init_security(struct dentry *dentry, int mode, const struct qstr *name, void **ctx, u32 *ctxlen); +int security_dentry_create_files_as(struct dentry *dentry, int mode, + struct qstr *name, + const struct cred *old, + struct cred *new); int security_inode_alloc(struct inode *inode); void security_inode_free(struct inode *inode); @@ -600,6 +604,14 @@ static inline int security_dentry_init_security(struct dentry *dentry, return -EOPNOTSUPP; } +static inline int security_dentry_create_files_as(struct dentry *dentry, + int mode, struct qstr *name, + const struct cred *old, + struct cred *new) +{ + return 0; +} + static inline int security_inode_init_security(struct inode *inode, struct inode *dir, diff --git a/security/security.c b/security/security.c index a9e2bb9fb9d3..f825304f04a7 100644 --- a/security/security.c +++ b/security/security.c @@ -364,6 +364,15 @@ int security_dentry_init_security(struct dentry *dentry, int mode, } EXPORT_SYMBOL(security_dentry_init_security); +int security_dentry_create_files_as(struct dentry *dentry, int mode, + struct qstr *name, + const struct cred *old, struct cred *new) +{ + return call_int_hook(dentry_create_files_as, 0, dentry, mode, + name, old, new); +} +EXPORT_SYMBOL(security_dentry_create_files_as); + int security_inode_init_security(struct inode *inode, struct inode *dir, const struct qstr *qstr, const initxattrs initxattrs, void *fs_data) @@ -1635,6 +1644,8 @@ struct security_hook_heads security_hook_heads = { LIST_HEAD_INIT(security_hook_heads.sb_parse_opts_str), .dentry_init_security = LIST_HEAD_INIT(security_hook_heads.dentry_init_security), + .dentry_create_files_as = + LIST_HEAD_INIT(security_hook_heads.dentry_create_files_as), #ifdef CONFIG_SECURITY_PATH .path_unlink = LIST_HEAD_INIT(security_hook_heads.path_unlink), .path_mkdir = LIST_HEAD_INIT(security_hook_heads.path_mkdir), -- cgit v1.2.3 From a4f4528a3174646e654989262afdc8303835fcd5 Mon Sep 17 00:00:00 2001 From: Mickaël Salaün Date: Sat, 9 Jul 2016 20:19:15 +0200 Subject: module: Fully remove the kernel_module_from_file hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove remaining kernel_module_from_file hook left by commit a1db74209483 ("module: replace copy_module_from_fd with kernel version") Signed-off-by: Mickaël Salaün Cc: Rusty Russell Acked-by: Kees Cook Signed-off-by: Mimi Zohar Signed-off-by: James Morris --- include/linux/lsm_hooks.h | 1 - include/linux/security.h | 1 - 2 files changed, 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 101bf19c0f41..dc56ae3a68cd 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -1455,7 +1455,6 @@ union security_list_options { int (*kernel_act_as)(struct cred *new, u32 secid); int (*kernel_create_files_as)(struct cred *new, struct inode *inode); int (*kernel_module_request)(char *kmod_name); - int (*kernel_module_from_file)(struct file *file); int (*kernel_read_file)(struct file *file, enum kernel_read_file_id id); int (*kernel_post_read_file)(struct file *file, char *buf, loff_t size, enum kernel_read_file_id id); diff --git a/include/linux/security.h b/include/linux/security.h index 7831cd57bcf7..4c7412943b81 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -307,7 +307,6 @@ void security_transfer_creds(struct cred *new, const struct cred *old); int security_kernel_act_as(struct cred *new, u32 secid); int security_kernel_create_files_as(struct cred *new, struct inode *inode); int security_kernel_module_request(char *kmod_name); -int security_kernel_module_from_file(struct file *file); int security_kernel_read_file(struct file *file, enum kernel_read_file_id id); int security_kernel_post_read_file(struct file *file, char *buf, loff_t size, enum kernel_read_file_id id); -- cgit v1.2.3 From 4b394a232df78414442778b02ca4a388d947d059 Mon Sep 17 00:00:00 2001 From: Gary R Hook Date: Tue, 26 Jul 2016 19:10:21 -0500 Subject: crypto: ccp - Let a v5 CCP provide the same function as v3 Enable equivalent function on a v5 CCP. Add support for a version 5 CCP which enables AES/XTS/SHA services. Also, more work on the data structures to virtualize functionality. Signed-off-by: Gary R Hook Signed-off-by: Herbert Xu --- drivers/crypto/ccp/Makefile | 1 + drivers/crypto/ccp/ccp-crypto-sha.c | 18 +- drivers/crypto/ccp/ccp-dev-v3.c | 28 +- drivers/crypto/ccp/ccp-dev-v5.c | 961 ++++++++++++++++++++++++++++++++++++ drivers/crypto/ccp/ccp-dev.h | 164 +++++- drivers/crypto/ccp/ccp-ops.c | 279 +++++++---- drivers/crypto/ccp/ccp-pci.c | 6 +- include/linux/ccp.h | 3 - 8 files changed, 1340 insertions(+), 120 deletions(-) create mode 100644 drivers/crypto/ccp/ccp-dev-v5.c (limited to 'include/linux') diff --git a/drivers/crypto/ccp/Makefile b/drivers/crypto/ccp/Makefile index ee4d2741b3ab..346ceb8f17bd 100644 --- a/drivers/crypto/ccp/Makefile +++ b/drivers/crypto/ccp/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_CRYPTO_DEV_CCP_DD) += ccp.o ccp-objs := ccp-dev.o \ ccp-ops.o \ ccp-dev-v3.o \ + ccp-dev-v5.o \ ccp-platform.o \ ccp-dmaengine.o ccp-$(CONFIG_PCI) += ccp-pci.o diff --git a/drivers/crypto/ccp/ccp-crypto-sha.c b/drivers/crypto/ccp/ccp-crypto-sha.c index 8f36af62fe95..84a652be4274 100644 --- a/drivers/crypto/ccp/ccp-crypto-sha.c +++ b/drivers/crypto/ccp/ccp-crypto-sha.c @@ -4,6 +4,7 @@ * Copyright (C) 2013,2016 Advanced Micro Devices, Inc. * * Author: Tom Lendacky + * Author: Gary R Hook * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -134,7 +135,22 @@ static int ccp_do_sha_update(struct ahash_request *req, unsigned int nbytes, rctx->cmd.engine = CCP_ENGINE_SHA; rctx->cmd.u.sha.type = rctx->type; rctx->cmd.u.sha.ctx = &rctx->ctx_sg; - rctx->cmd.u.sha.ctx_len = sizeof(rctx->ctx); + + switch (rctx->type) { + case CCP_SHA_TYPE_1: + rctx->cmd.u.sha.ctx_len = SHA1_DIGEST_SIZE; + break; + case CCP_SHA_TYPE_224: + rctx->cmd.u.sha.ctx_len = SHA224_DIGEST_SIZE; + break; + case CCP_SHA_TYPE_256: + rctx->cmd.u.sha.ctx_len = SHA256_DIGEST_SIZE; + break; + default: + /* Should never get here */ + break; + } + rctx->cmd.u.sha.src = sg; rctx->cmd.u.sha.src_len = rctx->hash_cnt; rctx->cmd.u.sha.opad = ctx->u.sha.key_len ? diff --git a/drivers/crypto/ccp/ccp-dev-v3.c b/drivers/crypto/ccp/ccp-dev-v3.c index 02c8c95fdc2d..ff2d2a4de16a 100644 --- a/drivers/crypto/ccp/ccp-dev-v3.c +++ b/drivers/crypto/ccp/ccp-dev-v3.c @@ -405,6 +405,7 @@ static int ccp_init(struct ccp_device *ccp) init_waitqueue_head(&ccp->sb_queue); init_waitqueue_head(&ccp->suspend_queue); + dev_dbg(dev, "Starting threads...\n"); /* Create a kthread for each queue */ for (i = 0; i < ccp->cmd_q_count; i++) { struct task_struct *kthread; @@ -424,6 +425,13 @@ static int ccp_init(struct ccp_device *ccp) wake_up_process(kthread); } + dev_dbg(dev, "Enabling interrupts...\n"); + /* Enable interrupts */ + iowrite32(qim, ccp->io_regs + IRQ_MASK_REG); + + dev_dbg(dev, "Registering device...\n"); + ccp_add_device(ccp); + /* Register the RNG */ ccp->hwrng.name = ccp->rngname; ccp->hwrng.read = ccp_trng_read; @@ -438,11 +446,6 @@ static int ccp_init(struct ccp_device *ccp) if (ret) goto e_hwrng; - ccp_add_device(ccp); - - /* Enable interrupts */ - iowrite32(qim, ccp->io_regs + IRQ_MASK_REG); - return 0; e_hwrng: @@ -468,7 +471,13 @@ static void ccp_destroy(struct ccp_device *ccp) struct ccp_cmd *cmd; unsigned int qim, i; - /* Remove this device from the list of available units first */ + /* Unregister the DMA engine */ + ccp_dmaengine_unregister(ccp); + + /* Unregister the RNG */ + hwrng_unregister(&ccp->hwrng); + + /* Remove this device from the list of available units */ ccp_del_device(ccp); /* Build queue interrupt mask (two interrupt masks per queue) */ @@ -488,12 +497,6 @@ static void ccp_destroy(struct ccp_device *ccp) } iowrite32(qim, ccp->io_regs + IRQ_STATUS_REG); - /* Unregister the DMA engine */ - ccp_dmaengine_unregister(ccp); - - /* Unregister the RNG */ - hwrng_unregister(&ccp->hwrng); - /* Stop the queue kthreads */ for (i = 0; i < ccp->cmd_q_count; i++) if (ccp->cmd_q[i].kthread) @@ -570,6 +573,7 @@ static const struct ccp_actions ccp3_actions = { struct ccp_vdata ccpv3 = { .version = CCP_VERSION(3, 0), + .setup = NULL, .perform = &ccp3_actions, .bar = 2, .offset = 0x20000, diff --git a/drivers/crypto/ccp/ccp-dev-v5.c b/drivers/crypto/ccp/ccp-dev-v5.c new file mode 100644 index 000000000000..16dad9633754 --- /dev/null +++ b/drivers/crypto/ccp/ccp-dev-v5.c @@ -0,0 +1,961 @@ +/* + * AMD Cryptographic Coprocessor (CCP) driver + * + * Copyright (C) 2016 Advanced Micro Devices, Inc. + * + * Author: Gary R Hook + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ccp-dev.h" + +static u32 ccp_lsb_alloc(struct ccp_cmd_queue *cmd_q, unsigned int count) +{ + struct ccp_device *ccp; + int start; + + /* First look at the map for the queue */ + if (cmd_q->lsb >= 0) { + start = (u32)bitmap_find_next_zero_area(cmd_q->lsbmap, + LSB_SIZE, + 0, count, 0); + if (start < LSB_SIZE) { + bitmap_set(cmd_q->lsbmap, start, count); + return start + cmd_q->lsb * LSB_SIZE; + } + } + + /* No joy; try to get an entry from the shared blocks */ + ccp = cmd_q->ccp; + for (;;) { + mutex_lock(&ccp->sb_mutex); + + start = (u32)bitmap_find_next_zero_area(ccp->lsbmap, + MAX_LSB_CNT * LSB_SIZE, + 0, + count, 0); + if (start <= MAX_LSB_CNT * LSB_SIZE) { + bitmap_set(ccp->lsbmap, start, count); + + mutex_unlock(&ccp->sb_mutex); + return start * LSB_ITEM_SIZE; + } + + ccp->sb_avail = 0; + + mutex_unlock(&ccp->sb_mutex); + + /* Wait for KSB entries to become available */ + if (wait_event_interruptible(ccp->sb_queue, ccp->sb_avail)) + return 0; + } +} + +static void ccp_lsb_free(struct ccp_cmd_queue *cmd_q, unsigned int start, + unsigned int count) +{ + int lsbno = start / LSB_SIZE; + + if (!start) + return; + + if (cmd_q->lsb == lsbno) { + /* An entry from the private LSB */ + bitmap_clear(cmd_q->lsbmap, start % LSB_SIZE, count); + } else { + /* From the shared LSBs */ + struct ccp_device *ccp = cmd_q->ccp; + + mutex_lock(&ccp->sb_mutex); + bitmap_clear(ccp->lsbmap, start, count); + ccp->sb_avail = 1; + mutex_unlock(&ccp->sb_mutex); + wake_up_interruptible_all(&ccp->sb_queue); + } +} + +/* CCP version 5: Union to define the function field (cmd_reg1/dword0) */ +union ccp_function { + struct { + u16 size:7; + u16 encrypt:1; + u16 mode:5; + u16 type:2; + } aes; + struct { + u16 size:7; + u16 encrypt:1; + u16 rsvd:5; + u16 type:2; + } aes_xts; + struct { + u16 rsvd1:10; + u16 type:4; + u16 rsvd2:1; + } sha; + struct { + u16 mode:3; + u16 size:12; + } rsa; + struct { + u16 byteswap:2; + u16 bitwise:3; + u16 reflect:2; + u16 rsvd:8; + } pt; + struct { + u16 rsvd:13; + } zlib; + struct { + u16 size:10; + u16 type:2; + u16 mode:3; + } ecc; + u16 raw; +}; + +#define CCP_AES_SIZE(p) ((p)->aes.size) +#define CCP_AES_ENCRYPT(p) ((p)->aes.encrypt) +#define CCP_AES_MODE(p) ((p)->aes.mode) +#define CCP_AES_TYPE(p) ((p)->aes.type) +#define CCP_XTS_SIZE(p) ((p)->aes_xts.size) +#define CCP_XTS_ENCRYPT(p) ((p)->aes_xts.encrypt) +#define CCP_SHA_TYPE(p) ((p)->sha.type) +#define CCP_RSA_SIZE(p) ((p)->rsa.size) +#define CCP_PT_BYTESWAP(p) ((p)->pt.byteswap) +#define CCP_PT_BITWISE(p) ((p)->pt.bitwise) +#define CCP_ECC_MODE(p) ((p)->ecc.mode) +#define CCP_ECC_AFFINE(p) ((p)->ecc.one) + +/* Word 0 */ +#define CCP5_CMD_DW0(p) ((p)->dw0) +#define CCP5_CMD_SOC(p) (CCP5_CMD_DW0(p).soc) +#define CCP5_CMD_IOC(p) (CCP5_CMD_DW0(p).ioc) +#define CCP5_CMD_INIT(p) (CCP5_CMD_DW0(p).init) +#define CCP5_CMD_EOM(p) (CCP5_CMD_DW0(p).eom) +#define CCP5_CMD_FUNCTION(p) (CCP5_CMD_DW0(p).function) +#define CCP5_CMD_ENGINE(p) (CCP5_CMD_DW0(p).engine) +#define CCP5_CMD_PROT(p) (CCP5_CMD_DW0(p).prot) + +/* Word 1 */ +#define CCP5_CMD_DW1(p) ((p)->length) +#define CCP5_CMD_LEN(p) (CCP5_CMD_DW1(p)) + +/* Word 2 */ +#define CCP5_CMD_DW2(p) ((p)->src_lo) +#define CCP5_CMD_SRC_LO(p) (CCP5_CMD_DW2(p)) + +/* Word 3 */ +#define CCP5_CMD_DW3(p) ((p)->dw3) +#define CCP5_CMD_SRC_MEM(p) ((p)->dw3.src_mem) +#define CCP5_CMD_SRC_HI(p) ((p)->dw3.src_hi) +#define CCP5_CMD_LSB_ID(p) ((p)->dw3.lsb_cxt_id) +#define CCP5_CMD_FIX_SRC(p) ((p)->dw3.fixed) + +/* Words 4/5 */ +#define CCP5_CMD_DW4(p) ((p)->dw4) +#define CCP5_CMD_DST_LO(p) (CCP5_CMD_DW4(p).dst_lo) +#define CCP5_CMD_DW5(p) ((p)->dw5.fields.dst_hi) +#define CCP5_CMD_DST_HI(p) (CCP5_CMD_DW5(p)) +#define CCP5_CMD_DST_MEM(p) ((p)->dw5.fields.dst_mem) +#define CCP5_CMD_FIX_DST(p) ((p)->dw5.fields.fixed) +#define CCP5_CMD_SHA_LO(p) ((p)->dw4.sha_len_lo) +#define CCP5_CMD_SHA_HI(p) ((p)->dw5.sha_len_hi) + +/* Word 6/7 */ +#define CCP5_CMD_DW6(p) ((p)->key_lo) +#define CCP5_CMD_KEY_LO(p) (CCP5_CMD_DW6(p)) +#define CCP5_CMD_DW7(p) ((p)->dw7) +#define CCP5_CMD_KEY_HI(p) ((p)->dw7.key_hi) +#define CCP5_CMD_KEY_MEM(p) ((p)->dw7.key_mem) + +static inline u32 low_address(unsigned long addr) +{ + return (u64)addr & 0x0ffffffff; +} + +static inline u32 high_address(unsigned long addr) +{ + return ((u64)addr >> 32) & 0x00000ffff; +} + +static unsigned int ccp5_get_free_slots(struct ccp_cmd_queue *cmd_q) +{ + unsigned int head_idx, n; + u32 head_lo, queue_start; + + queue_start = low_address(cmd_q->qdma_tail); + head_lo = ioread32(cmd_q->reg_head_lo); + head_idx = (head_lo - queue_start) / sizeof(struct ccp5_desc); + + n = head_idx + COMMANDS_PER_QUEUE - cmd_q->qidx - 1; + + return n % COMMANDS_PER_QUEUE; /* Always one unused spot */ +} + +static int ccp5_do_cmd(struct ccp5_desc *desc, + struct ccp_cmd_queue *cmd_q) +{ + u32 *mP; + __le32 *dP; + u32 tail; + int i; + int ret = 0; + + if (CCP5_CMD_SOC(desc)) { + CCP5_CMD_IOC(desc) = 1; + CCP5_CMD_SOC(desc) = 0; + } + mutex_lock(&cmd_q->q_mutex); + + mP = (u32 *) &cmd_q->qbase[cmd_q->qidx]; + dP = (__le32 *) desc; + for (i = 0; i < 8; i++) + mP[i] = cpu_to_le32(dP[i]); /* handle endianness */ + + cmd_q->qidx = (cmd_q->qidx + 1) % COMMANDS_PER_QUEUE; + + /* The data used by this command must be flushed to memory */ + wmb(); + + /* Write the new tail address back to the queue register */ + tail = low_address(cmd_q->qdma_tail + cmd_q->qidx * Q_DESC_SIZE); + iowrite32(tail, cmd_q->reg_tail_lo); + + /* Turn the queue back on using our cached control register */ + iowrite32(cmd_q->qcontrol | CMD5_Q_RUN, cmd_q->reg_control); + mutex_unlock(&cmd_q->q_mutex); + + if (CCP5_CMD_IOC(desc)) { + /* Wait for the job to complete */ + ret = wait_event_interruptible(cmd_q->int_queue, + cmd_q->int_rcvd); + if (ret || cmd_q->cmd_error) { + /* A version 5 device doesn't use Job IDs... */ + if (!ret) + ret = -EIO; + } + cmd_q->int_rcvd = 0; + } + + return 0; +} + +static int ccp5_perform_aes(struct ccp_op *op) +{ + struct ccp5_desc desc; + union ccp_function function; + u32 key_addr = op->sb_key * LSB_ITEM_SIZE; + + /* Zero out all the fields of the command desc */ + memset(&desc, 0, Q_DESC_SIZE); + + CCP5_CMD_ENGINE(&desc) = CCP_ENGINE_AES; + + CCP5_CMD_SOC(&desc) = op->soc; + CCP5_CMD_IOC(&desc) = 1; + CCP5_CMD_INIT(&desc) = op->init; + CCP5_CMD_EOM(&desc) = op->eom; + CCP5_CMD_PROT(&desc) = 0; + + function.raw = 0; + CCP_AES_ENCRYPT(&function) = op->u.aes.action; + CCP_AES_MODE(&function) = op->u.aes.mode; + CCP_AES_TYPE(&function) = op->u.aes.type; + if (op->u.aes.mode == CCP_AES_MODE_CFB) + CCP_AES_SIZE(&function) = 0x7f; + + CCP5_CMD_FUNCTION(&desc) = function.raw; + + CCP5_CMD_LEN(&desc) = op->src.u.dma.length; + + CCP5_CMD_SRC_LO(&desc) = ccp_addr_lo(&op->src.u.dma); + CCP5_CMD_SRC_HI(&desc) = ccp_addr_hi(&op->src.u.dma); + CCP5_CMD_SRC_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + + CCP5_CMD_DST_LO(&desc) = ccp_addr_lo(&op->dst.u.dma); + CCP5_CMD_DST_HI(&desc) = ccp_addr_hi(&op->dst.u.dma); + CCP5_CMD_DST_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + + CCP5_CMD_KEY_LO(&desc) = lower_32_bits(key_addr); + CCP5_CMD_KEY_HI(&desc) = 0; + CCP5_CMD_KEY_MEM(&desc) = CCP_MEMTYPE_SB; + CCP5_CMD_LSB_ID(&desc) = op->sb_ctx; + + return ccp5_do_cmd(&desc, op->cmd_q); +} + +static int ccp5_perform_xts_aes(struct ccp_op *op) +{ + struct ccp5_desc desc; + union ccp_function function; + u32 key_addr = op->sb_key * LSB_ITEM_SIZE; + + /* Zero out all the fields of the command desc */ + memset(&desc, 0, Q_DESC_SIZE); + + CCP5_CMD_ENGINE(&desc) = CCP_ENGINE_XTS_AES_128; + + CCP5_CMD_SOC(&desc) = op->soc; + CCP5_CMD_IOC(&desc) = 1; + CCP5_CMD_INIT(&desc) = op->init; + CCP5_CMD_EOM(&desc) = op->eom; + CCP5_CMD_PROT(&desc) = 0; + + function.raw = 0; + CCP_XTS_ENCRYPT(&function) = op->u.xts.action; + CCP_XTS_SIZE(&function) = op->u.xts.unit_size; + CCP5_CMD_FUNCTION(&desc) = function.raw; + + CCP5_CMD_LEN(&desc) = op->src.u.dma.length; + + CCP5_CMD_SRC_LO(&desc) = ccp_addr_lo(&op->src.u.dma); + CCP5_CMD_SRC_HI(&desc) = ccp_addr_hi(&op->src.u.dma); + CCP5_CMD_SRC_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + + CCP5_CMD_DST_LO(&desc) = ccp_addr_lo(&op->dst.u.dma); + CCP5_CMD_DST_HI(&desc) = ccp_addr_hi(&op->dst.u.dma); + CCP5_CMD_DST_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + + CCP5_CMD_KEY_LO(&desc) = lower_32_bits(key_addr); + CCP5_CMD_KEY_HI(&desc) = 0; + CCP5_CMD_KEY_MEM(&desc) = CCP_MEMTYPE_SB; + CCP5_CMD_LSB_ID(&desc) = op->sb_ctx; + + return ccp5_do_cmd(&desc, op->cmd_q); +} + +static int ccp5_perform_sha(struct ccp_op *op) +{ + struct ccp5_desc desc; + union ccp_function function; + + /* Zero out all the fields of the command desc */ + memset(&desc, 0, Q_DESC_SIZE); + + CCP5_CMD_ENGINE(&desc) = CCP_ENGINE_SHA; + + CCP5_CMD_SOC(&desc) = op->soc; + CCP5_CMD_IOC(&desc) = 1; + CCP5_CMD_INIT(&desc) = 1; + CCP5_CMD_EOM(&desc) = op->eom; + CCP5_CMD_PROT(&desc) = 0; + + function.raw = 0; + CCP_SHA_TYPE(&function) = op->u.sha.type; + CCP5_CMD_FUNCTION(&desc) = function.raw; + + CCP5_CMD_LEN(&desc) = op->src.u.dma.length; + + CCP5_CMD_SRC_LO(&desc) = ccp_addr_lo(&op->src.u.dma); + CCP5_CMD_SRC_HI(&desc) = ccp_addr_hi(&op->src.u.dma); + CCP5_CMD_SRC_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + + CCP5_CMD_LSB_ID(&desc) = op->sb_ctx; + + if (op->eom) { + CCP5_CMD_SHA_LO(&desc) = lower_32_bits(op->u.sha.msg_bits); + CCP5_CMD_SHA_HI(&desc) = upper_32_bits(op->u.sha.msg_bits); + } else { + CCP5_CMD_SHA_LO(&desc) = 0; + CCP5_CMD_SHA_HI(&desc) = 0; + } + + return ccp5_do_cmd(&desc, op->cmd_q); +} + +static int ccp5_perform_rsa(struct ccp_op *op) +{ + struct ccp5_desc desc; + union ccp_function function; + + /* Zero out all the fields of the command desc */ + memset(&desc, 0, Q_DESC_SIZE); + + CCP5_CMD_ENGINE(&desc) = CCP_ENGINE_RSA; + + CCP5_CMD_SOC(&desc) = op->soc; + CCP5_CMD_IOC(&desc) = 1; + CCP5_CMD_INIT(&desc) = 0; + CCP5_CMD_EOM(&desc) = 1; + CCP5_CMD_PROT(&desc) = 0; + + function.raw = 0; + CCP_RSA_SIZE(&function) = op->u.rsa.mod_size; + CCP5_CMD_FUNCTION(&desc) = function.raw; + + CCP5_CMD_LEN(&desc) = op->u.rsa.input_len; + + /* Source is from external memory */ + CCP5_CMD_SRC_LO(&desc) = ccp_addr_lo(&op->src.u.dma); + CCP5_CMD_SRC_HI(&desc) = ccp_addr_hi(&op->src.u.dma); + CCP5_CMD_SRC_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + + /* Destination is in external memory */ + CCP5_CMD_DST_LO(&desc) = ccp_addr_lo(&op->dst.u.dma); + CCP5_CMD_DST_HI(&desc) = ccp_addr_hi(&op->dst.u.dma); + CCP5_CMD_DST_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + + /* Key (Exponent) is in external memory */ + CCP5_CMD_KEY_LO(&desc) = ccp_addr_lo(&op->exp.u.dma); + CCP5_CMD_KEY_HI(&desc) = ccp_addr_hi(&op->exp.u.dma); + CCP5_CMD_KEY_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + + return ccp5_do_cmd(&desc, op->cmd_q); +} + +static int ccp5_perform_passthru(struct ccp_op *op) +{ + struct ccp5_desc desc; + union ccp_function function; + struct ccp_dma_info *saddr = &op->src.u.dma; + struct ccp_dma_info *daddr = &op->dst.u.dma; + + memset(&desc, 0, Q_DESC_SIZE); + + CCP5_CMD_ENGINE(&desc) = CCP_ENGINE_PASSTHRU; + + CCP5_CMD_SOC(&desc) = 0; + CCP5_CMD_IOC(&desc) = 1; + CCP5_CMD_INIT(&desc) = 0; + CCP5_CMD_EOM(&desc) = op->eom; + CCP5_CMD_PROT(&desc) = 0; + + function.raw = 0; + CCP_PT_BYTESWAP(&function) = op->u.passthru.byte_swap; + CCP_PT_BITWISE(&function) = op->u.passthru.bit_mod; + CCP5_CMD_FUNCTION(&desc) = function.raw; + + /* Length of source data is always 256 bytes */ + if (op->src.type == CCP_MEMTYPE_SYSTEM) + CCP5_CMD_LEN(&desc) = saddr->length; + else + CCP5_CMD_LEN(&desc) = daddr->length; + + if (op->src.type == CCP_MEMTYPE_SYSTEM) { + CCP5_CMD_SRC_LO(&desc) = ccp_addr_lo(&op->src.u.dma); + CCP5_CMD_SRC_HI(&desc) = ccp_addr_hi(&op->src.u.dma); + CCP5_CMD_SRC_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + + if (op->u.passthru.bit_mod != CCP_PASSTHRU_BITWISE_NOOP) + CCP5_CMD_LSB_ID(&desc) = op->sb_key; + } else { + u32 key_addr = op->src.u.sb * CCP_SB_BYTES; + + CCP5_CMD_SRC_LO(&desc) = lower_32_bits(key_addr); + CCP5_CMD_SRC_HI(&desc) = 0; + CCP5_CMD_SRC_MEM(&desc) = CCP_MEMTYPE_SB; + } + + if (op->dst.type == CCP_MEMTYPE_SYSTEM) { + CCP5_CMD_DST_LO(&desc) = ccp_addr_lo(&op->dst.u.dma); + CCP5_CMD_DST_HI(&desc) = ccp_addr_hi(&op->dst.u.dma); + CCP5_CMD_DST_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + } else { + u32 key_addr = op->dst.u.sb * CCP_SB_BYTES; + + CCP5_CMD_DST_LO(&desc) = lower_32_bits(key_addr); + CCP5_CMD_DST_HI(&desc) = 0; + CCP5_CMD_DST_MEM(&desc) = CCP_MEMTYPE_SB; + } + + return ccp5_do_cmd(&desc, op->cmd_q); +} + +static int ccp5_perform_ecc(struct ccp_op *op) +{ + struct ccp5_desc desc; + union ccp_function function; + + /* Zero out all the fields of the command desc */ + memset(&desc, 0, Q_DESC_SIZE); + + CCP5_CMD_ENGINE(&desc) = CCP_ENGINE_ECC; + + CCP5_CMD_SOC(&desc) = 0; + CCP5_CMD_IOC(&desc) = 1; + CCP5_CMD_INIT(&desc) = 0; + CCP5_CMD_EOM(&desc) = 1; + CCP5_CMD_PROT(&desc) = 0; + + function.raw = 0; + function.ecc.mode = op->u.ecc.function; + CCP5_CMD_FUNCTION(&desc) = function.raw; + + CCP5_CMD_LEN(&desc) = op->src.u.dma.length; + + CCP5_CMD_SRC_LO(&desc) = ccp_addr_lo(&op->src.u.dma); + CCP5_CMD_SRC_HI(&desc) = ccp_addr_hi(&op->src.u.dma); + CCP5_CMD_SRC_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + + CCP5_CMD_DST_LO(&desc) = ccp_addr_lo(&op->dst.u.dma); + CCP5_CMD_DST_HI(&desc) = ccp_addr_hi(&op->dst.u.dma); + CCP5_CMD_DST_MEM(&desc) = CCP_MEMTYPE_SYSTEM; + + return ccp5_do_cmd(&desc, op->cmd_q); +} + +static int ccp_find_lsb_regions(struct ccp_cmd_queue *cmd_q, u64 status) +{ + int q_mask = 1 << cmd_q->id; + int queues = 0; + int j; + + /* Build a bit mask to know which LSBs this queue has access to. + * Don't bother with segment 0 as it has special privileges. + */ + for (j = 1; j < MAX_LSB_CNT; j++) { + if (status & q_mask) + bitmap_set(cmd_q->lsbmask, j, 1); + status >>= LSB_REGION_WIDTH; + } + queues = bitmap_weight(cmd_q->lsbmask, MAX_LSB_CNT); + dev_info(cmd_q->ccp->dev, "Queue %d can access %d LSB regions\n", + cmd_q->id, queues); + + return queues ? 0 : -EINVAL; +} + + +static int ccp_find_and_assign_lsb_to_q(struct ccp_device *ccp, + int lsb_cnt, int n_lsbs, + unsigned long *lsb_pub) +{ + DECLARE_BITMAP(qlsb, MAX_LSB_CNT); + int bitno; + int qlsb_wgt; + int i; + + /* For each queue: + * If the count of potential LSBs available to a queue matches the + * ordinal given to us in lsb_cnt: + * Copy the mask of possible LSBs for this queue into "qlsb"; + * For each bit in qlsb, see if the corresponding bit in the + * aggregation mask is set; if so, we have a match. + * If we have a match, clear the bit in the aggregation to + * mark it as no longer available. + * If there is no match, clear the bit in qlsb and keep looking. + */ + for (i = 0; i < ccp->cmd_q_count; i++) { + struct ccp_cmd_queue *cmd_q = &ccp->cmd_q[i]; + + qlsb_wgt = bitmap_weight(cmd_q->lsbmask, MAX_LSB_CNT); + + if (qlsb_wgt == lsb_cnt) { + bitmap_copy(qlsb, cmd_q->lsbmask, MAX_LSB_CNT); + + bitno = find_first_bit(qlsb, MAX_LSB_CNT); + while (bitno < MAX_LSB_CNT) { + if (test_bit(bitno, lsb_pub)) { + /* We found an available LSB + * that this queue can access + */ + cmd_q->lsb = bitno; + bitmap_clear(lsb_pub, bitno, 1); + dev_info(ccp->dev, + "Queue %d gets LSB %d\n", + i, bitno); + break; + } + bitmap_clear(qlsb, bitno, 1); + bitno = find_first_bit(qlsb, MAX_LSB_CNT); + } + if (bitno >= MAX_LSB_CNT) + return -EINVAL; + n_lsbs--; + } + } + return n_lsbs; +} + +/* For each queue, from the most- to least-constrained: + * find an LSB that can be assigned to the queue. If there are N queues that + * can only use M LSBs, where N > M, fail; otherwise, every queue will get a + * dedicated LSB. Remaining LSB regions become a shared resource. + * If we have fewer LSBs than queues, all LSB regions become shared resources. + */ +static int ccp_assign_lsbs(struct ccp_device *ccp) +{ + DECLARE_BITMAP(lsb_pub, MAX_LSB_CNT); + DECLARE_BITMAP(qlsb, MAX_LSB_CNT); + int n_lsbs = 0; + int bitno; + int i, lsb_cnt; + int rc = 0; + + bitmap_zero(lsb_pub, MAX_LSB_CNT); + + /* Create an aggregate bitmap to get a total count of available LSBs */ + for (i = 0; i < ccp->cmd_q_count; i++) + bitmap_or(lsb_pub, + lsb_pub, ccp->cmd_q[i].lsbmask, + MAX_LSB_CNT); + + n_lsbs = bitmap_weight(lsb_pub, MAX_LSB_CNT); + + if (n_lsbs >= ccp->cmd_q_count) { + /* We have enough LSBS to give every queue a private LSB. + * Brute force search to start with the queues that are more + * constrained in LSB choice. When an LSB is privately + * assigned, it is removed from the public mask. + * This is an ugly N squared algorithm with some optimization. + */ + for (lsb_cnt = 1; + n_lsbs && (lsb_cnt <= MAX_LSB_CNT); + lsb_cnt++) { + rc = ccp_find_and_assign_lsb_to_q(ccp, lsb_cnt, n_lsbs, + lsb_pub); + if (rc < 0) + return -EINVAL; + n_lsbs = rc; + } + } + + rc = 0; + /* What's left of the LSBs, according to the public mask, now become + * shared. Any zero bits in the lsb_pub mask represent an LSB region + * that can't be used as a shared resource, so mark the LSB slots for + * them as "in use". + */ + bitmap_copy(qlsb, lsb_pub, MAX_LSB_CNT); + + bitno = find_first_zero_bit(qlsb, MAX_LSB_CNT); + while (bitno < MAX_LSB_CNT) { + bitmap_set(ccp->lsbmap, bitno * LSB_SIZE, LSB_SIZE); + bitmap_set(qlsb, bitno, 1); + bitno = find_first_zero_bit(qlsb, MAX_LSB_CNT); + } + + return rc; +} + +static int ccp5_init(struct ccp_device *ccp) +{ + struct device *dev = ccp->dev; + struct ccp_cmd_queue *cmd_q; + struct dma_pool *dma_pool; + char dma_pool_name[MAX_DMAPOOL_NAME_LEN]; + unsigned int qmr, qim, i; + u64 status; + u32 status_lo, status_hi; + int ret; + + /* Find available queues */ + qim = 0; + qmr = ioread32(ccp->io_regs + Q_MASK_REG); + for (i = 0; i < MAX_HW_QUEUES; i++) { + + if (!(qmr & (1 << i))) + continue; + + /* Allocate a dma pool for this queue */ + snprintf(dma_pool_name, sizeof(dma_pool_name), "%s_q%d", + ccp->name, i); + dma_pool = dma_pool_create(dma_pool_name, dev, + CCP_DMAPOOL_MAX_SIZE, + CCP_DMAPOOL_ALIGN, 0); + if (!dma_pool) { + dev_err(dev, "unable to allocate dma pool\n"); + ret = -ENOMEM; + } + + cmd_q = &ccp->cmd_q[ccp->cmd_q_count]; + ccp->cmd_q_count++; + + cmd_q->ccp = ccp; + cmd_q->id = i; + cmd_q->dma_pool = dma_pool; + mutex_init(&cmd_q->q_mutex); + + /* Page alignment satisfies our needs for N <= 128 */ + BUILD_BUG_ON(COMMANDS_PER_QUEUE > 128); + cmd_q->qsize = Q_SIZE(Q_DESC_SIZE); + cmd_q->qbase = dma_zalloc_coherent(dev, cmd_q->qsize, + &cmd_q->qbase_dma, + GFP_KERNEL); + if (!cmd_q->qbase) { + dev_err(dev, "unable to allocate command queue\n"); + ret = -ENOMEM; + goto e_pool; + } + + cmd_q->qidx = 0; + /* Preset some register values and masks that are queue + * number dependent + */ + cmd_q->reg_control = ccp->io_regs + + CMD5_Q_STATUS_INCR * (i + 1); + cmd_q->reg_tail_lo = cmd_q->reg_control + CMD5_Q_TAIL_LO_BASE; + cmd_q->reg_head_lo = cmd_q->reg_control + CMD5_Q_HEAD_LO_BASE; + cmd_q->reg_int_enable = cmd_q->reg_control + + CMD5_Q_INT_ENABLE_BASE; + cmd_q->reg_interrupt_status = cmd_q->reg_control + + CMD5_Q_INTERRUPT_STATUS_BASE; + cmd_q->reg_status = cmd_q->reg_control + CMD5_Q_STATUS_BASE; + cmd_q->reg_int_status = cmd_q->reg_control + + CMD5_Q_INT_STATUS_BASE; + cmd_q->reg_dma_status = cmd_q->reg_control + + CMD5_Q_DMA_STATUS_BASE; + cmd_q->reg_dma_read_status = cmd_q->reg_control + + CMD5_Q_DMA_READ_STATUS_BASE; + cmd_q->reg_dma_write_status = cmd_q->reg_control + + CMD5_Q_DMA_WRITE_STATUS_BASE; + + init_waitqueue_head(&cmd_q->int_queue); + + dev_dbg(dev, "queue #%u available\n", i); + } + if (ccp->cmd_q_count == 0) { + dev_notice(dev, "no command queues available\n"); + ret = -EIO; + goto e_pool; + } + dev_notice(dev, "%u command queues available\n", ccp->cmd_q_count); + + /* Turn off the queues and disable interrupts until ready */ + for (i = 0; i < ccp->cmd_q_count; i++) { + cmd_q = &ccp->cmd_q[i]; + + cmd_q->qcontrol = 0; /* Start with nothing */ + iowrite32(cmd_q->qcontrol, cmd_q->reg_control); + + /* Disable the interrupts */ + iowrite32(0x00, cmd_q->reg_int_enable); + ioread32(cmd_q->reg_int_status); + ioread32(cmd_q->reg_status); + + /* Clear the interrupts */ + iowrite32(ALL_INTERRUPTS, cmd_q->reg_interrupt_status); + } + + dev_dbg(dev, "Requesting an IRQ...\n"); + /* Request an irq */ + ret = ccp->get_irq(ccp); + if (ret) { + dev_err(dev, "unable to allocate an IRQ\n"); + goto e_pool; + } + + /* Initialize the queue used to suspend */ + init_waitqueue_head(&ccp->suspend_queue); + + dev_dbg(dev, "Loading LSB map...\n"); + /* Copy the private LSB mask to the public registers */ + status_lo = ioread32(ccp->io_regs + LSB_PRIVATE_MASK_LO_OFFSET); + status_hi = ioread32(ccp->io_regs + LSB_PRIVATE_MASK_HI_OFFSET); + iowrite32(status_lo, ccp->io_regs + LSB_PUBLIC_MASK_LO_OFFSET); + iowrite32(status_hi, ccp->io_regs + LSB_PUBLIC_MASK_HI_OFFSET); + status = ((u64)status_hi<<30) | (u64)status_lo; + + dev_dbg(dev, "Configuring virtual queues...\n"); + /* Configure size of each virtual queue accessible to host */ + for (i = 0; i < ccp->cmd_q_count; i++) { + u32 dma_addr_lo; + u32 dma_addr_hi; + + cmd_q = &ccp->cmd_q[i]; + + cmd_q->qcontrol &= ~(CMD5_Q_SIZE << CMD5_Q_SHIFT); + cmd_q->qcontrol |= QUEUE_SIZE_VAL << CMD5_Q_SHIFT; + + cmd_q->qdma_tail = cmd_q->qbase_dma; + dma_addr_lo = low_address(cmd_q->qdma_tail); + iowrite32((u32)dma_addr_lo, cmd_q->reg_tail_lo); + iowrite32((u32)dma_addr_lo, cmd_q->reg_head_lo); + + dma_addr_hi = high_address(cmd_q->qdma_tail); + cmd_q->qcontrol |= (dma_addr_hi << 16); + iowrite32(cmd_q->qcontrol, cmd_q->reg_control); + + /* Find the LSB regions accessible to the queue */ + ccp_find_lsb_regions(cmd_q, status); + cmd_q->lsb = -1; /* Unassigned value */ + } + + dev_dbg(dev, "Assigning LSBs...\n"); + ret = ccp_assign_lsbs(ccp); + if (ret) { + dev_err(dev, "Unable to assign LSBs (%d)\n", ret); + goto e_irq; + } + + /* Optimization: pre-allocate LSB slots for each queue */ + for (i = 0; i < ccp->cmd_q_count; i++) { + ccp->cmd_q[i].sb_key = ccp_lsb_alloc(&ccp->cmd_q[i], 2); + ccp->cmd_q[i].sb_ctx = ccp_lsb_alloc(&ccp->cmd_q[i], 2); + } + + dev_dbg(dev, "Starting threads...\n"); + /* Create a kthread for each queue */ + for (i = 0; i < ccp->cmd_q_count; i++) { + struct task_struct *kthread; + + cmd_q = &ccp->cmd_q[i]; + + kthread = kthread_create(ccp_cmd_queue_thread, cmd_q, + "%s-q%u", ccp->name, cmd_q->id); + if (IS_ERR(kthread)) { + dev_err(dev, "error creating queue thread (%ld)\n", + PTR_ERR(kthread)); + ret = PTR_ERR(kthread); + goto e_kthread; + } + + cmd_q->kthread = kthread; + wake_up_process(kthread); + } + + dev_dbg(dev, "Enabling interrupts...\n"); + /* Enable interrupts */ + for (i = 0; i < ccp->cmd_q_count; i++) { + cmd_q = &ccp->cmd_q[i]; + iowrite32(ALL_INTERRUPTS, cmd_q->reg_int_enable); + } + + dev_dbg(dev, "Registering device...\n"); + /* Put this on the unit list to make it available */ + ccp_add_device(ccp); + + return 0; + +e_kthread: + for (i = 0; i < ccp->cmd_q_count; i++) + if (ccp->cmd_q[i].kthread) + kthread_stop(ccp->cmd_q[i].kthread); + +e_irq: + ccp->free_irq(ccp); + +e_pool: + for (i = 0; i < ccp->cmd_q_count; i++) + dma_pool_destroy(ccp->cmd_q[i].dma_pool); + + return ret; +} + +static void ccp5_destroy(struct ccp_device *ccp) +{ + struct device *dev = ccp->dev; + struct ccp_cmd_queue *cmd_q; + struct ccp_cmd *cmd; + unsigned int i; + + /* Remove this device from the list of available units first */ + ccp_del_device(ccp); + + /* Disable and clear interrupts */ + for (i = 0; i < ccp->cmd_q_count; i++) { + cmd_q = &ccp->cmd_q[i]; + + /* Turn off the run bit */ + iowrite32(cmd_q->qcontrol & ~CMD5_Q_RUN, cmd_q->reg_control); + + /* Disable the interrupts */ + iowrite32(ALL_INTERRUPTS, cmd_q->reg_interrupt_status); + + /* Clear the interrupt status */ + iowrite32(0x00, cmd_q->reg_int_enable); + ioread32(cmd_q->reg_int_status); + ioread32(cmd_q->reg_status); + } + + /* Stop the queue kthreads */ + for (i = 0; i < ccp->cmd_q_count; i++) + if (ccp->cmd_q[i].kthread) + kthread_stop(ccp->cmd_q[i].kthread); + + ccp->free_irq(ccp); + + for (i = 0; i < ccp->cmd_q_count; i++) { + cmd_q = &ccp->cmd_q[i]; + dma_free_coherent(dev, cmd_q->qsize, cmd_q->qbase, + cmd_q->qbase_dma); + } + + /* Flush the cmd and backlog queue */ + while (!list_empty(&ccp->cmd)) { + /* Invoke the callback directly with an error code */ + cmd = list_first_entry(&ccp->cmd, struct ccp_cmd, entry); + list_del(&cmd->entry); + cmd->callback(cmd->data, -ENODEV); + } + while (!list_empty(&ccp->backlog)) { + /* Invoke the callback directly with an error code */ + cmd = list_first_entry(&ccp->backlog, struct ccp_cmd, entry); + list_del(&cmd->entry); + cmd->callback(cmd->data, -ENODEV); + } +} + +static irqreturn_t ccp5_irq_handler(int irq, void *data) +{ + struct device *dev = data; + struct ccp_device *ccp = dev_get_drvdata(dev); + u32 status; + unsigned int i; + + for (i = 0; i < ccp->cmd_q_count; i++) { + struct ccp_cmd_queue *cmd_q = &ccp->cmd_q[i]; + + status = ioread32(cmd_q->reg_interrupt_status); + + if (status) { + cmd_q->int_status = status; + cmd_q->q_status = ioread32(cmd_q->reg_status); + cmd_q->q_int_status = ioread32(cmd_q->reg_int_status); + + /* On error, only save the first error value */ + if ((status & INT_ERROR) && !cmd_q->cmd_error) + cmd_q->cmd_error = CMD_Q_ERROR(cmd_q->q_status); + + cmd_q->int_rcvd = 1; + + /* Acknowledge the interrupt and wake the kthread */ + iowrite32(ALL_INTERRUPTS, cmd_q->reg_interrupt_status); + wake_up_interruptible(&cmd_q->int_queue); + } + } + + return IRQ_HANDLED; +} + +static void ccp5_config(struct ccp_device *ccp) +{ + /* Public side */ + iowrite32(0x00001249, ccp->io_regs + CMD5_REQID_CONFIG_OFFSET); +} + +static const struct ccp_actions ccp5_actions = { + .aes = ccp5_perform_aes, + .xts_aes = ccp5_perform_xts_aes, + .sha = ccp5_perform_sha, + .rsa = ccp5_perform_rsa, + .passthru = ccp5_perform_passthru, + .ecc = ccp5_perform_ecc, + .sballoc = ccp_lsb_alloc, + .sbfree = ccp_lsb_free, + .init = ccp5_init, + .destroy = ccp5_destroy, + .get_free_slots = ccp5_get_free_slots, + .irqhandler = ccp5_irq_handler, +}; + +struct ccp_vdata ccpv5 = { + .version = CCP_VERSION(5, 0), + .setup = ccp5_config, + .perform = &ccp5_actions, + .bar = 2, + .offset = 0x0, +}; diff --git a/drivers/crypto/ccp/ccp-dev.h b/drivers/crypto/ccp/ccp-dev.h index de907029c6ee..5ff4a73e3bd4 100644 --- a/drivers/crypto/ccp/ccp-dev.h +++ b/drivers/crypto/ccp/ccp-dev.h @@ -61,7 +61,62 @@ #define CMD_Q_ERROR(__qs) ((__qs) & 0x0000003f) #define CMD_Q_DEPTH(__qs) (((__qs) >> 12) & 0x0000000f) -/****** REQ0 Related Values ******/ +/* ------------------------ CCP Version 5 Specifics ------------------------ */ +#define CMD5_QUEUE_MASK_OFFSET 0x00 +#define CMD5_REQID_CONFIG_OFFSET 0x08 +#define LSB_PUBLIC_MASK_LO_OFFSET 0x18 +#define LSB_PUBLIC_MASK_HI_OFFSET 0x1C +#define LSB_PRIVATE_MASK_LO_OFFSET 0x20 +#define LSB_PRIVATE_MASK_HI_OFFSET 0x24 + +#define CMD5_Q_CONTROL_BASE 0x0000 +#define CMD5_Q_TAIL_LO_BASE 0x0004 +#define CMD5_Q_HEAD_LO_BASE 0x0008 +#define CMD5_Q_INT_ENABLE_BASE 0x000C +#define CMD5_Q_INTERRUPT_STATUS_BASE 0x0010 + +#define CMD5_Q_STATUS_BASE 0x0100 +#define CMD5_Q_INT_STATUS_BASE 0x0104 +#define CMD5_Q_DMA_STATUS_BASE 0x0108 +#define CMD5_Q_DMA_READ_STATUS_BASE 0x010C +#define CMD5_Q_DMA_WRITE_STATUS_BASE 0x0110 +#define CMD5_Q_ABORT_BASE 0x0114 +#define CMD5_Q_AX_CACHE_BASE 0x0118 + +/* Address offset between two virtual queue registers */ +#define CMD5_Q_STATUS_INCR 0x1000 + +/* Bit masks */ +#define CMD5_Q_RUN 0x1 +#define CMD5_Q_HALT 0x2 +#define CMD5_Q_MEM_LOCATION 0x4 +#define CMD5_Q_SIZE 0x1F +#define CMD5_Q_SHIFT 3 +#define COMMANDS_PER_QUEUE 16 +#define QUEUE_SIZE_VAL ((ffs(COMMANDS_PER_QUEUE) - 2) & \ + CMD5_Q_SIZE) +#define Q_PTR_MASK (2 << (QUEUE_SIZE_VAL + 5) - 1) +#define Q_DESC_SIZE sizeof(struct ccp5_desc) +#define Q_SIZE(n) (COMMANDS_PER_QUEUE*(n)) + +#define INT_COMPLETION 0x1 +#define INT_ERROR 0x2 +#define INT_QUEUE_STOPPED 0x4 +#define ALL_INTERRUPTS (INT_COMPLETION| \ + INT_ERROR| \ + INT_QUEUE_STOPPED) + +#define LSB_REGION_WIDTH 5 +#define MAX_LSB_CNT 8 + +#define LSB_SIZE 16 +#define LSB_ITEM_SIZE 32 +#define PLSB_MAP_SIZE (LSB_SIZE) +#define SLSB_MAP_SIZE (MAX_LSB_CNT * LSB_SIZE) + +#define LSB_ENTRY_NUMBER(LSB_ADDR) (LSB_ADDR / LSB_ITEM_SIZE) + +/* ------------------------ CCP Version 3 Specifics ------------------------ */ #define REQ0_WAIT_FOR_WRITE 0x00000004 #define REQ0_INT_ON_COMPLETE 0x00000002 #define REQ0_STOP_ON_COMPLETE 0x00000001 @@ -115,6 +170,8 @@ #define CCP_JOBID_MASK 0x0000003f +/* ------------------------ General CCP Defines ------------------------ */ + #define CCP_DMAPOOL_MAX_SIZE 64 #define CCP_DMAPOOL_ALIGN BIT(5) @@ -149,6 +206,7 @@ struct ccp_op; struct ccp_device; struct ccp_cmd; +struct ccp_fns; struct ccp_dma_cmd { struct list_head entry; @@ -192,10 +250,30 @@ struct ccp_cmd_queue { /* Queue dma pool */ struct dma_pool *dma_pool; + /* Queue base address (not neccessarily aligned)*/ + struct ccp5_desc *qbase; + + /* Aligned queue start address (per requirement) */ + struct mutex q_mutex ____cacheline_aligned; + unsigned int qidx; + + /* Version 5 has different requirements for queue memory */ + unsigned int qsize; + dma_addr_t qbase_dma; + dma_addr_t qdma_tail; + /* Per-queue reserved storage block(s) */ u32 sb_key; u32 sb_ctx; + /* Bitmap of LSBs that can be accessed by this queue */ + DECLARE_BITMAP(lsbmask, MAX_LSB_CNT); + /* Private LSB that is assigned to this queue, or -1 if none. + * Bitmap for my private LSB, unused otherwise + */ + unsigned int lsb; + DECLARE_BITMAP(lsbmap, PLSB_MAP_SIZE); + /* Queue processing thread */ struct task_struct *kthread; unsigned int active; @@ -209,8 +287,17 @@ struct ccp_cmd_queue { u32 int_err; /* Register addresses for queue */ + void __iomem *reg_control; + void __iomem *reg_tail_lo; + void __iomem *reg_head_lo; + void __iomem *reg_int_enable; + void __iomem *reg_interrupt_status; void __iomem *reg_status; void __iomem *reg_int_status; + void __iomem *reg_dma_status; + void __iomem *reg_dma_read_status; + void __iomem *reg_dma_write_status; + u32 qcontrol; /* Cached control register */ /* Status values from job */ u32 int_status; @@ -306,6 +393,9 @@ struct ccp_device { unsigned int sb_count; u32 sb_start; + /* Bitmap of shared LSBs, if any */ + DECLARE_BITMAP(lsbmap, SLSB_MAP_SIZE); + /* Suspend support */ unsigned int suspending; wait_queue_head_t suspend_queue; @@ -320,6 +410,7 @@ enum ccp_memtype { CCP_MEMTYPE_LOCAL, CCP_MEMTYPE__LAST, }; +#define CCP_MEMTYPE_LSB CCP_MEMTYPE_KSB struct ccp_dma_info { dma_addr_t address; @@ -407,6 +498,7 @@ struct ccp_op { struct ccp_mem src; struct ccp_mem dst; + struct ccp_mem exp; union { struct ccp_aes_op aes; @@ -416,6 +508,7 @@ struct ccp_op { struct ccp_passthru_op passthru; struct ccp_ecc_op ecc; } u; + struct ccp_mem key; }; static inline u32 ccp_addr_lo(struct ccp_dma_info *info) @@ -428,6 +521,70 @@ static inline u32 ccp_addr_hi(struct ccp_dma_info *info) return upper_32_bits(info->address + info->offset) & 0x0000ffff; } +/** + * descriptor for version 5 CPP commands + * 8 32-bit words: + * word 0: function; engine; control bits + * word 1: length of source data + * word 2: low 32 bits of source pointer + * word 3: upper 16 bits of source pointer; source memory type + * word 4: low 32 bits of destination pointer + * word 5: upper 16 bits of destination pointer; destination memory type + * word 6: low 32 bits of key pointer + * word 7: upper 16 bits of key pointer; key memory type + */ +struct dword0 { + __le32 soc:1; + __le32 ioc:1; + __le32 rsvd1:1; + __le32 init:1; + __le32 eom:1; /* AES/SHA only */ + __le32 function:15; + __le32 engine:4; + __le32 prot:1; + __le32 rsvd2:7; +}; + +struct dword3 { + __le32 src_hi:16; + __le32 src_mem:2; + __le32 lsb_cxt_id:8; + __le32 rsvd1:5; + __le32 fixed:1; +}; + +union dword4 { + __le32 dst_lo; /* NON-SHA */ + __le32 sha_len_lo; /* SHA */ +}; + +union dword5 { + struct { + __le32 dst_hi:16; + __le32 dst_mem:2; + __le32 rsvd1:13; + __le32 fixed:1; + } fields; + __le32 sha_len_hi; +}; + +struct dword7 { + __le32 key_hi:16; + __le32 key_mem:2; + __le32 rsvd1:14; +}; + +struct ccp5_desc { + struct dword0 dw0; + __le32 length; + __le32 src_lo; + struct dword3 dw3; + union dword4 dw4; + union dword5 dw5; + __le32 key_lo; + struct dword7 dw7; +}; + int ccp_pci_init(void); void ccp_pci_exit(void); @@ -466,13 +623,14 @@ struct ccp_actions { /* Structure to hold CCP version-specific values */ struct ccp_vdata { - unsigned int version; - int (*init)(struct ccp_device *); + const unsigned int version; + void (*setup)(struct ccp_device *); const struct ccp_actions *perform; const unsigned int bar; const unsigned int offset; }; extern struct ccp_vdata ccpv3; +extern struct ccp_vdata ccpv5; #endif diff --git a/drivers/crypto/ccp/ccp-ops.c b/drivers/crypto/ccp/ccp-ops.c index fdab0ae4f7c9..50fae4442801 100644 --- a/drivers/crypto/ccp/ccp-ops.c +++ b/drivers/crypto/ccp/ccp-ops.c @@ -21,26 +21,29 @@ #include "ccp-dev.h" /* SHA initial context values */ -static const __be32 ccp_sha1_init[CCP_SHA_CTXSIZE / sizeof(__be32)] = { +static const __be32 ccp_sha1_init[SHA1_DIGEST_SIZE / sizeof(__be32)] = { cpu_to_be32(SHA1_H0), cpu_to_be32(SHA1_H1), cpu_to_be32(SHA1_H2), cpu_to_be32(SHA1_H3), - cpu_to_be32(SHA1_H4), 0, 0, 0, + cpu_to_be32(SHA1_H4), }; -static const __be32 ccp_sha224_init[CCP_SHA_CTXSIZE / sizeof(__be32)] = { +static const __be32 ccp_sha224_init[SHA256_DIGEST_SIZE / sizeof(__be32)] = { cpu_to_be32(SHA224_H0), cpu_to_be32(SHA224_H1), cpu_to_be32(SHA224_H2), cpu_to_be32(SHA224_H3), cpu_to_be32(SHA224_H4), cpu_to_be32(SHA224_H5), cpu_to_be32(SHA224_H6), cpu_to_be32(SHA224_H7), }; -static const __be32 ccp_sha256_init[CCP_SHA_CTXSIZE / sizeof(__be32)] = { +static const __be32 ccp_sha256_init[SHA256_DIGEST_SIZE / sizeof(__be32)] = { cpu_to_be32(SHA256_H0), cpu_to_be32(SHA256_H1), cpu_to_be32(SHA256_H2), cpu_to_be32(SHA256_H3), cpu_to_be32(SHA256_H4), cpu_to_be32(SHA256_H5), cpu_to_be32(SHA256_H6), cpu_to_be32(SHA256_H7), }; +#define CCP_NEW_JOBID(ccp) ((ccp->vdata->version == CCP_VERSION(3, 0)) ? \ + ccp_gen_jobid(ccp) : 0) + static u32 ccp_gen_jobid(struct ccp_device *ccp) { return atomic_inc_return(&ccp->current_id) & CCP_JOBID_MASK; @@ -487,7 +490,7 @@ static int ccp_run_aes_cmac_cmd(struct ccp_cmd_queue *cmd_q, ret = -EIO; memset(&op, 0, sizeof(op)); op.cmd_q = cmd_q; - op.jobid = ccp_gen_jobid(cmd_q->ccp); + op.jobid = CCP_NEW_JOBID(cmd_q->ccp); op.sb_key = cmd_q->sb_key; op.sb_ctx = cmd_q->sb_ctx; op.init = 1; @@ -640,7 +643,7 @@ static int ccp_run_aes_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) ret = -EIO; memset(&op, 0, sizeof(op)); op.cmd_q = cmd_q; - op.jobid = ccp_gen_jobid(cmd_q->ccp); + op.jobid = CCP_NEW_JOBID(cmd_q->ccp); op.sb_key = cmd_q->sb_key; op.sb_ctx = cmd_q->sb_ctx; op.init = (aes->mode == CCP_AES_MODE_ECB) ? 0 : 1; @@ -679,7 +682,7 @@ static int ccp_run_aes_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) goto e_key; if (aes->mode != CCP_AES_MODE_ECB) { - /* Load the AES context - conver to LE */ + /* Load the AES context - convert to LE */ dm_offset = CCP_SB_BYTES - AES_BLOCK_SIZE; ccp_set_dm_area(&ctx, dm_offset, aes->iv, 0, aes->iv_len); ret = ccp_copy_to_sb(cmd_q, &ctx, op.jobid, op.sb_ctx, @@ -817,7 +820,7 @@ static int ccp_run_xts_aes_cmd(struct ccp_cmd_queue *cmd_q, ret = -EIO; memset(&op, 0, sizeof(op)); op.cmd_q = cmd_q; - op.jobid = ccp_gen_jobid(cmd_q->ccp); + op.jobid = CCP_NEW_JOBID(cmd_q->ccp); op.sb_key = cmd_q->sb_key; op.sb_ctx = cmd_q->sb_ctx; op.init = 1; @@ -936,98 +939,154 @@ static int ccp_run_sha_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) struct ccp_dm_workarea ctx; struct ccp_data src; struct ccp_op op; + unsigned int ioffset, ooffset; + unsigned int digest_size; + int sb_count; + const void *init; + u64 block_size; + int ctx_size; int ret; - if (sha->ctx_len != CCP_SHA_CTXSIZE) + switch (sha->type) { + case CCP_SHA_TYPE_1: + if (sha->ctx_len < SHA1_DIGEST_SIZE) + return -EINVAL; + block_size = SHA1_BLOCK_SIZE; + break; + case CCP_SHA_TYPE_224: + if (sha->ctx_len < SHA224_DIGEST_SIZE) + return -EINVAL; + block_size = SHA224_BLOCK_SIZE; + break; + case CCP_SHA_TYPE_256: + if (sha->ctx_len < SHA256_DIGEST_SIZE) + return -EINVAL; + block_size = SHA256_BLOCK_SIZE; + break; + default: return -EINVAL; + } if (!sha->ctx) return -EINVAL; - if (!sha->final && (sha->src_len & (CCP_SHA_BLOCKSIZE - 1))) + if (!sha->final && (sha->src_len & (block_size - 1))) return -EINVAL; - if (!sha->src_len) { - const u8 *sha_zero; + /* The version 3 device can't handle zero-length input */ + if (cmd_q->ccp->vdata->version == CCP_VERSION(3, 0)) { - /* Not final, just return */ - if (!sha->final) - return 0; + if (!sha->src_len) { + unsigned int digest_len; + const u8 *sha_zero; - /* CCP can't do a zero length sha operation so the caller - * must buffer the data. - */ - if (sha->msg_bits) - return -EINVAL; + /* Not final, just return */ + if (!sha->final) + return 0; - /* The CCP cannot perform zero-length sha operations so the - * caller is required to buffer data for the final operation. - * However, a sha operation for a message with a total length - * of zero is valid so known values are required to supply - * the result. - */ - switch (sha->type) { - case CCP_SHA_TYPE_1: - sha_zero = sha1_zero_message_hash; - break; - case CCP_SHA_TYPE_224: - sha_zero = sha224_zero_message_hash; - break; - case CCP_SHA_TYPE_256: - sha_zero = sha256_zero_message_hash; - break; - default: - return -EINVAL; - } + /* CCP can't do a zero length sha operation so the + * caller must buffer the data. + */ + if (sha->msg_bits) + return -EINVAL; - scatterwalk_map_and_copy((void *)sha_zero, sha->ctx, 0, - sha->ctx_len, 1); + /* The CCP cannot perform zero-length sha operations + * so the caller is required to buffer data for the + * final operation. However, a sha operation for a + * message with a total length of zero is valid so + * known values are required to supply the result. + */ + switch (sha->type) { + case CCP_SHA_TYPE_1: + sha_zero = sha1_zero_message_hash; + digest_len = SHA1_DIGEST_SIZE; + break; + case CCP_SHA_TYPE_224: + sha_zero = sha224_zero_message_hash; + digest_len = SHA224_DIGEST_SIZE; + break; + case CCP_SHA_TYPE_256: + sha_zero = sha256_zero_message_hash; + digest_len = SHA256_DIGEST_SIZE; + break; + default: + return -EINVAL; + } - return 0; + scatterwalk_map_and_copy((void *)sha_zero, sha->ctx, 0, + digest_len, 1); + + return 0; + } } - if (!sha->src) - return -EINVAL; + /* Set variables used throughout */ + switch (sha->type) { + case CCP_SHA_TYPE_1: + digest_size = SHA1_DIGEST_SIZE; + init = (void *) ccp_sha1_init; + ctx_size = SHA1_DIGEST_SIZE; + sb_count = 1; + if (cmd_q->ccp->vdata->version != CCP_VERSION(3, 0)) + ooffset = ioffset = CCP_SB_BYTES - SHA1_DIGEST_SIZE; + else + ooffset = ioffset = 0; + break; + case CCP_SHA_TYPE_224: + digest_size = SHA224_DIGEST_SIZE; + init = (void *) ccp_sha224_init; + ctx_size = SHA256_DIGEST_SIZE; + sb_count = 1; + ioffset = 0; + if (cmd_q->ccp->vdata->version != CCP_VERSION(3, 0)) + ooffset = CCP_SB_BYTES - SHA224_DIGEST_SIZE; + else + ooffset = 0; + break; + case CCP_SHA_TYPE_256: + digest_size = SHA256_DIGEST_SIZE; + init = (void *) ccp_sha256_init; + ctx_size = SHA256_DIGEST_SIZE; + sb_count = 1; + ooffset = ioffset = 0; + break; + default: + ret = -EINVAL; + goto e_data; + } - BUILD_BUG_ON(CCP_SHA_SB_COUNT != 1); + /* For zero-length plaintext the src pointer is ignored; + * otherwise both parts must be valid + */ + if (sha->src_len && !sha->src) + return -EINVAL; memset(&op, 0, sizeof(op)); op.cmd_q = cmd_q; - op.jobid = ccp_gen_jobid(cmd_q->ccp); - op.sb_ctx = cmd_q->sb_ctx; + op.jobid = CCP_NEW_JOBID(cmd_q->ccp); + op.sb_ctx = cmd_q->sb_ctx; /* Pre-allocated */ op.u.sha.type = sha->type; op.u.sha.msg_bits = sha->msg_bits; - /* The SHA context fits in a single (32-byte) SB entry and - * must be in little endian format. Use the 256-bit byte swap - * passthru option to convert from big endian to little endian. - */ - ret = ccp_init_dm_workarea(&ctx, cmd_q, - CCP_SHA_SB_COUNT * CCP_SB_BYTES, + ret = ccp_init_dm_workarea(&ctx, cmd_q, sb_count * CCP_SB_BYTES, DMA_BIDIRECTIONAL); if (ret) return ret; - if (sha->first) { - const __be32 *init; - switch (sha->type) { case CCP_SHA_TYPE_1: - init = ccp_sha1_init; - break; case CCP_SHA_TYPE_224: - init = ccp_sha224_init; - break; case CCP_SHA_TYPE_256: - init = ccp_sha256_init; + memcpy(ctx.address + ioffset, init, ctx_size); break; default: ret = -EINVAL; goto e_ctx; } - memcpy(ctx.address, init, CCP_SHA_CTXSIZE); } else { - ccp_set_dm_area(&ctx, 0, sha->ctx, 0, sha->ctx_len); + /* Restore the context */ + ccp_set_dm_area(&ctx, 0, sha->ctx, 0, + sb_count * CCP_SB_BYTES); } ret = ccp_copy_to_sb(cmd_q, &ctx, op.jobid, op.sb_ctx, @@ -1037,24 +1096,33 @@ static int ccp_run_sha_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) goto e_ctx; } - /* Send data to the CCP SHA engine */ - ret = ccp_init_data(&src, cmd_q, sha->src, sha->src_len, - CCP_SHA_BLOCKSIZE, DMA_TO_DEVICE); - if (ret) - goto e_ctx; + if (sha->src) { + /* Send data to the CCP SHA engine; block_size is set above */ + ret = ccp_init_data(&src, cmd_q, sha->src, sha->src_len, + block_size, DMA_TO_DEVICE); + if (ret) + goto e_ctx; - while (src.sg_wa.bytes_left) { - ccp_prepare_data(&src, NULL, &op, CCP_SHA_BLOCKSIZE, false); - if (sha->final && !src.sg_wa.bytes_left) - op.eom = 1; + while (src.sg_wa.bytes_left) { + ccp_prepare_data(&src, NULL, &op, block_size, false); + if (sha->final && !src.sg_wa.bytes_left) + op.eom = 1; + + ret = cmd_q->ccp->vdata->perform->sha(&op); + if (ret) { + cmd->engine_error = cmd_q->cmd_error; + goto e_data; + } + ccp_process_data(&src, NULL, &op); + } + } else { + op.eom = 1; ret = cmd_q->ccp->vdata->perform->sha(&op); if (ret) { cmd->engine_error = cmd_q->cmd_error; goto e_data; } - - ccp_process_data(&src, NULL, &op); } /* Retrieve the SHA context - convert from LE to BE using @@ -1067,32 +1135,31 @@ static int ccp_run_sha_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) goto e_data; } - ccp_get_dm_area(&ctx, 0, sha->ctx, 0, sha->ctx_len); - - if (sha->final && sha->opad) { - /* HMAC operation, recursively perform final SHA */ - struct ccp_cmd hmac_cmd; - struct scatterlist sg; - u64 block_size, digest_size; - u8 *hmac_buf; - + if (sha->final) { + /* Finishing up, so get the digest */ switch (sha->type) { case CCP_SHA_TYPE_1: - block_size = SHA1_BLOCK_SIZE; - digest_size = SHA1_DIGEST_SIZE; - break; case CCP_SHA_TYPE_224: - block_size = SHA224_BLOCK_SIZE; - digest_size = SHA224_DIGEST_SIZE; - break; case CCP_SHA_TYPE_256: - block_size = SHA256_BLOCK_SIZE; - digest_size = SHA256_DIGEST_SIZE; + ccp_get_dm_area(&ctx, ooffset, + sha->ctx, 0, + digest_size); break; default: ret = -EINVAL; - goto e_data; + goto e_ctx; } + } else { + /* Stash the context */ + ccp_get_dm_area(&ctx, 0, sha->ctx, 0, + sb_count * CCP_SB_BYTES); + } + + if (sha->final && sha->opad) { + /* HMAC operation, recursively perform final SHA */ + struct ccp_cmd hmac_cmd; + struct scatterlist sg; + u8 *hmac_buf; if (sha->opad_len != block_size) { ret = -EINVAL; @@ -1107,7 +1174,18 @@ static int ccp_run_sha_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) sg_init_one(&sg, hmac_buf, block_size + digest_size); scatterwalk_map_and_copy(hmac_buf, sha->opad, 0, block_size, 0); - memcpy(hmac_buf + block_size, ctx.address, digest_size); + switch (sha->type) { + case CCP_SHA_TYPE_1: + case CCP_SHA_TYPE_224: + case CCP_SHA_TYPE_256: + memcpy(hmac_buf + block_size, + ctx.address + ooffset, + digest_size); + break; + default: + ret = -EINVAL; + goto e_ctx; + } memset(&hmac_cmd, 0, sizeof(hmac_cmd)); hmac_cmd.engine = CCP_ENGINE_SHA; @@ -1130,7 +1208,8 @@ static int ccp_run_sha_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) } e_data: - ccp_free_data(&src, cmd_q); + if (sha->src) + ccp_free_data(&src, cmd_q); e_ctx: ccp_dm_free(&ctx); @@ -1261,7 +1340,7 @@ static int ccp_run_passthru_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_op op; bool in_place = false; unsigned int i; - int ret; + int ret = 0; if (!pt->final && (pt->src_len & (CCP_PASSTHRU_BLOCKSIZE - 1))) return -EINVAL; @@ -1280,7 +1359,7 @@ static int ccp_run_passthru_cmd(struct ccp_cmd_queue *cmd_q, memset(&op, 0, sizeof(op)); op.cmd_q = cmd_q; - op.jobid = ccp_gen_jobid(cmd_q->ccp); + op.jobid = CCP_NEW_JOBID(cmd_q->ccp); if (pt->bit_mod != CCP_PASSTHRU_BITWISE_NOOP) { /* Load the mask */ @@ -1469,7 +1548,7 @@ static int ccp_run_ecc_mm_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) memset(&op, 0, sizeof(op)); op.cmd_q = cmd_q; - op.jobid = ccp_gen_jobid(cmd_q->ccp); + op.jobid = CCP_NEW_JOBID(cmd_q->ccp); /* Concatenate the modulus and the operands. Both the modulus and * the operands must be in little endian format. Since the input @@ -1594,7 +1673,7 @@ static int ccp_run_ecc_pm_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) memset(&op, 0, sizeof(op)); op.cmd_q = cmd_q; - op.jobid = ccp_gen_jobid(cmd_q->ccp); + op.jobid = CCP_NEW_JOBID(cmd_q->ccp); /* Concatenate the modulus and the operands. Both the modulus and * the operands must be in little endian format. Since the input @@ -1632,7 +1711,7 @@ static int ccp_run_ecc_pm_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) goto e_src; src.address += CCP_ECC_OPERAND_SIZE; - /* Set the first point Z coordianate to 1 */ + /* Set the first point Z coordinate to 1 */ *src.address = 0x01; src.address += CCP_ECC_OPERAND_SIZE; @@ -1651,7 +1730,7 @@ static int ccp_run_ecc_pm_cmd(struct ccp_cmd_queue *cmd_q, struct ccp_cmd *cmd) goto e_src; src.address += CCP_ECC_OPERAND_SIZE; - /* Set the second point Z coordianate to 1 */ + /* Set the second point Z coordinate to 1 */ *src.address = 0x01; src.address += CCP_ECC_OPERAND_SIZE; } else { diff --git a/drivers/crypto/ccp/ccp-pci.c b/drivers/crypto/ccp/ccp-pci.c index 072bcedef386..064e20f78b10 100644 --- a/drivers/crypto/ccp/ccp-pci.c +++ b/drivers/crypto/ccp/ccp-pci.c @@ -141,10 +141,11 @@ static void ccp_free_irqs(struct ccp_device *ccp) free_irq(ccp_pci->msix[ccp_pci->msix_count].vector, dev); pci_disable_msix(pdev); - } else { + } else if (ccp->irq) { free_irq(ccp->irq, dev); pci_disable_msi(pdev); } + ccp->irq = 0; } static int ccp_find_mmio_area(struct ccp_device *ccp) @@ -229,6 +230,8 @@ static int ccp_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) dev_set_drvdata(dev, ccp); + if (ccp->vdata->setup) + ccp->vdata->setup(ccp); ret = ccp->vdata->perform->init(ccp); if (ret) goto e_iomap; @@ -321,6 +324,7 @@ static int ccp_pci_resume(struct pci_dev *pdev) static const struct pci_device_id ccp_pci_table[] = { { PCI_VDEVICE(AMD, 0x1537), (kernel_ulong_t)&ccpv3 }, + { PCI_VDEVICE(AMD, 0x1456), (kernel_ulong_t)&ccpv5 }, /* Last entry must be zero */ { 0, } }; diff --git a/include/linux/ccp.h b/include/linux/ccp.h index 7c2bb27c067c..a7653339fedb 100644 --- a/include/linux/ccp.h +++ b/include/linux/ccp.h @@ -238,9 +238,6 @@ struct ccp_xts_aes_engine { }; /***** SHA engine *****/ -#define CCP_SHA_BLOCKSIZE SHA256_BLOCK_SIZE -#define CCP_SHA_CTXSIZE SHA256_DIGEST_SIZE - /** * ccp_sha_type - type of SHA operation * -- cgit v1.2.3 From 1ebe88d38ddec6b05f7b5e64acef30a98a4ad17e Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Sat, 25 Jun 2016 22:38:21 -0700 Subject: usb: ulpi: Automatically set driver::owner with ulpi_driver_register() Let's follow other driver registration functions and automatically set the driver's owner member to THIS_MODULE when ulpi_driver_register() is called. This allows ulpi driver writers to forget about this boiler plate detail and avoids common bugs in the process. Signed-off-by: Stephen Boyd Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/common/ulpi.c | 5 +++-- include/linux/ulpi/driver.h | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'include/linux') diff --git a/drivers/usb/common/ulpi.c b/drivers/usb/common/ulpi.c index 01c0c0477a9e..e04a34e7a341 100644 --- a/drivers/usb/common/ulpi.c +++ b/drivers/usb/common/ulpi.c @@ -127,16 +127,17 @@ static struct device_type ulpi_dev_type = { * * Registers a driver with the ULPI bus. */ -int ulpi_register_driver(struct ulpi_driver *drv) +int __ulpi_register_driver(struct ulpi_driver *drv, struct module *module) { if (!drv->probe) return -EINVAL; + drv->driver.owner = module; drv->driver.bus = &ulpi_bus; return driver_register(&drv->driver); } -EXPORT_SYMBOL_GPL(ulpi_register_driver); +EXPORT_SYMBOL_GPL(__ulpi_register_driver); /** * ulpi_unregister_driver - unregister a driver with the ULPI bus diff --git a/include/linux/ulpi/driver.h b/include/linux/ulpi/driver.h index 388f6e08b9d4..80b36ca12e80 100644 --- a/include/linux/ulpi/driver.h +++ b/include/linux/ulpi/driver.h @@ -47,7 +47,11 @@ struct ulpi_driver { #define to_ulpi_driver(d) container_of(d, struct ulpi_driver, driver) -int ulpi_register_driver(struct ulpi_driver *drv); +/* + * use a macro to avoid include chaining to get THIS_MODULE + */ +#define ulpi_register_driver(drv) __ulpi_register_driver(drv, THIS_MODULE) +int __ulpi_register_driver(struct ulpi_driver *drv, struct module *module); void ulpi_unregister_driver(struct ulpi_driver *drv); #define module_ulpi_driver(__ulpi_driver) \ -- cgit v1.2.3 From 0f4be8cf637ea4637faba8a0e4bf2270287c6ba0 Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Wed, 10 Aug 2016 09:39:06 +0200 Subject: mfd: stmpe: Add STMPE_IDX_SYS_CTRL/2 enum As STMPE1801/1601/24xx has a SYS_CTRL register and STMPE1601/2403 has even a SYS_CTRL2 register, add STMPE_IDX_SYS_CTRL/2 and update driver code accordingly This update prepares the ground for not yet supported STMPE1600 which share similar REG_SYS_CTRL register. Signed-off-by: Patrice Chotard Acked-by: Linus Walleij Signed-off-by: Lee Jones --- drivers/mfd/stmpe.c | 21 ++++++++++++++------- drivers/mfd/stmpe.h | 2 ++ include/linux/mfd/stmpe.h | 2 ++ 3 files changed, 18 insertions(+), 7 deletions(-) (limited to 'include/linux') diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index 94c7cc02fdab..c053b2b67bad 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -469,6 +469,8 @@ static const struct mfd_cell stmpe_ts_cell = { static const u8 stmpe811_regs[] = { [STMPE_IDX_CHIP_ID] = STMPE811_REG_CHIP_ID, + [STMPE_IDX_SYS_CTRL] = STMPE811_REG_SYS_CTRL, + [STMPE_IDX_SYS_CTRL2] = STMPE811_REG_SYS_CTRL2, [STMPE_IDX_ICR_LSB] = STMPE811_REG_INT_CTRL, [STMPE_IDX_IER_LSB] = STMPE811_REG_INT_EN, [STMPE_IDX_ISR_MSB] = STMPE811_REG_INT_STA, @@ -511,7 +513,7 @@ static int stmpe811_enable(struct stmpe *stmpe, unsigned int blocks, if (blocks & STMPE_BLOCK_TOUCHSCREEN) mask |= STMPE811_SYS_CTRL2_TSC_OFF; - return __stmpe_set_bits(stmpe, STMPE811_REG_SYS_CTRL2, mask, + return __stmpe_set_bits(stmpe, stmpe->regs[STMPE_IDX_SYS_CTRL2], mask, enable ? 0 : mask); } @@ -556,6 +558,8 @@ static struct stmpe_variant_info stmpe610 = { static const u8 stmpe1601_regs[] = { [STMPE_IDX_CHIP_ID] = STMPE1601_REG_CHIP_ID, + [STMPE_IDX_SYS_CTRL] = STMPE1601_REG_SYS_CTRL, + [STMPE_IDX_SYS_CTRL2] = STMPE1601_REG_SYS_CTRL2, [STMPE_IDX_ICR_LSB] = STMPE1601_REG_ICR_LSB, [STMPE_IDX_IER_LSB] = STMPE1601_REG_IER_LSB, [STMPE_IDX_ISR_MSB] = STMPE1601_REG_ISR_MSB, @@ -640,13 +644,13 @@ static int stmpe1601_autosleep(struct stmpe *stmpe, return timeout; } - ret = __stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL2, + ret = __stmpe_set_bits(stmpe, stmpe->regs[STMPE_IDX_SYS_CTRL2], STMPE1601_AUTOSLEEP_TIMEOUT_MASK, timeout); if (ret < 0) return ret; - return __stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL2, + return __stmpe_set_bits(stmpe, stmpe->regs[STMPE_IDX_SYS_CTRL2], STPME1601_AUTOSLEEP_ENABLE, STPME1601_AUTOSLEEP_ENABLE); } @@ -671,7 +675,7 @@ static int stmpe1601_enable(struct stmpe *stmpe, unsigned int blocks, else mask &= ~STMPE1601_SYS_CTRL_ENABLE_SPWM; - return __stmpe_set_bits(stmpe, STMPE1601_REG_SYS_CTRL, mask, + return __stmpe_set_bits(stmpe, stmpe->regs[STMPE_IDX_SYS_CTRL], mask, enable ? mask : 0); } @@ -710,6 +714,7 @@ static struct stmpe_variant_info stmpe1601 = { */ static const u8 stmpe1801_regs[] = { [STMPE_IDX_CHIP_ID] = STMPE1801_REG_CHIP_ID, + [STMPE_IDX_SYS_CTRL] = STMPE1801_REG_SYS_CTRL, [STMPE_IDX_ICR_LSB] = STMPE1801_REG_INT_CTRL_LOW, [STMPE_IDX_IER_LSB] = STMPE1801_REG_INT_EN_MASK_LOW, [STMPE_IDX_ISR_LSB] = STMPE1801_REG_INT_STA_LOW, @@ -756,14 +761,14 @@ static int stmpe1801_reset(struct stmpe *stmpe) unsigned long timeout; int ret = 0; - ret = __stmpe_set_bits(stmpe, STMPE1801_REG_SYS_CTRL, + ret = __stmpe_set_bits(stmpe, stmpe->regs[STMPE_IDX_SYS_CTRL], STMPE1801_MSK_SYS_CTRL_RESET, STMPE1801_MSK_SYS_CTRL_RESET); if (ret < 0) return ret; timeout = jiffies + msecs_to_jiffies(100); while (time_before(jiffies, timeout)) { - ret = __stmpe_reg_read(stmpe, STMPE1801_REG_SYS_CTRL); + ret = __stmpe_reg_read(stmpe, stmpe->regs[STMPE_IDX_SYS_CTRL]); if (ret < 0) return ret; if (!(ret & STMPE1801_MSK_SYS_CTRL_RESET)) @@ -794,6 +799,8 @@ static struct stmpe_variant_info stmpe1801 = { static const u8 stmpe24xx_regs[] = { [STMPE_IDX_CHIP_ID] = STMPE24XX_REG_CHIP_ID, + [STMPE_IDX_SYS_CTRL] = STMPE24XX_REG_SYS_CTRL, + [STMPE_IDX_SYS_CTRL2] = STMPE24XX_REG_SYS_CTRL2, [STMPE_IDX_ICR_LSB] = STMPE24XX_REG_ICR_LSB, [STMPE_IDX_IER_LSB] = STMPE24XX_REG_IER_LSB, [STMPE_IDX_ISR_MSB] = STMPE24XX_REG_ISR_MSB, @@ -840,7 +847,7 @@ static int stmpe24xx_enable(struct stmpe *stmpe, unsigned int blocks, if (blocks & STMPE_BLOCK_KEYPAD) mask |= STMPE24XX_SYS_CTRL_ENABLE_KPC; - return __stmpe_set_bits(stmpe, STMPE24XX_REG_SYS_CTRL, mask, + return __stmpe_set_bits(stmpe, stmpe->regs[STMPE_IDX_SYS_CTRL], mask, enable ? mask : 0); } diff --git a/drivers/mfd/stmpe.h b/drivers/mfd/stmpe.h index 84adb46b3e2f..406f9f2d8935 100644 --- a/drivers/mfd/stmpe.h +++ b/drivers/mfd/stmpe.h @@ -138,6 +138,7 @@ int stmpe_remove(struct stmpe *stmpe); #define STMPE811_NR_INTERNAL_IRQS 8 #define STMPE811_REG_CHIP_ID 0x00 +#define STMPE811_REG_SYS_CTRL 0x03 #define STMPE811_REG_SYS_CTRL2 0x04 #define STMPE811_REG_SPI_CFG 0x08 #define STMPE811_REG_INT_CTRL 0x09 @@ -264,6 +265,7 @@ int stmpe_remove(struct stmpe *stmpe); #define STMPE24XX_NR_INTERNAL_IRQS 9 #define STMPE24XX_REG_SYS_CTRL 0x02 +#define STMPE24XX_REG_SYS_CTRL2 0x03 #define STMPE24XX_REG_ICR_LSB 0x11 #define STMPE24XX_REG_IER_LSB 0x13 #define STMPE24XX_REG_ISR_MSB 0x14 diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h index de748bc7525e..eb8b73bd139f 100644 --- a/include/linux/mfd/stmpe.h +++ b/include/linux/mfd/stmpe.h @@ -39,6 +39,8 @@ enum stmpe_partnum { */ enum { STMPE_IDX_CHIP_ID, + STMPE_IDX_SYS_CTRL, + STMPE_IDX_SYS_CTRL2, STMPE_IDX_ICR_LSB, STMPE_IDX_IER_LSB, STMPE_IDX_ISR_LSB, -- cgit v1.2.3 From 897ac6674c64ca94df5b70ea5c6815a296e1d32a Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Wed, 10 Aug 2016 09:39:11 +0200 Subject: mfd: stmpe: Rework registers access this update allows to use registers map as following : regs[reg_index + offset] instead of regs[reg_index] + offset This makes code clearer and will facilitate the addition of STMPE1600 on which LSB and MSB registers are respectively located at addr and addr + 1. Despite for all others STMPE variant, LSB and MSB registers are respectively located in reverse order at addr + 1 and addr. For variant which have 3 registers's bank, we use LSB,CSB and MSB indexes which contains respectively LSB (or LOW), CSB (or MID) and MSB (or HIGH) register addresses (STMPE1801/STMPE24xx). For variant which have 2 registers's bank, we use LSB and CSB indexes only. In this case the CSB index contains the MSB regs address (STMPE 1601). Signed-off-by: Patrice Chotard Reviewed-by: Linus Walleij Signed-off-by: Lee Jones --- drivers/mfd/stmpe.c | 48 ++++++++++++++++++++++++++++++++++++++++++---- drivers/mfd/stmpe.h | 49 +++++++++++++++++++++++++++++++++++++++++------ include/linux/mfd/stmpe.h | 18 +++++++++++++++++ 3 files changed, 105 insertions(+), 10 deletions(-) (limited to 'include/linux') diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index a0704767a050..1877d1ea2e7c 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -483,7 +483,7 @@ static const u8 stmpe811_regs[] = { [STMPE_IDX_GPAFR_U_MSB] = STMPE811_REG_GPIO_AF, [STMPE_IDX_IEGPIOR_LSB] = STMPE811_REG_GPIO_INT_EN, [STMPE_IDX_ISGPIOR_MSB] = STMPE811_REG_GPIO_INT_STA, - [STMPE_IDX_GPEDR_MSB] = STMPE811_REG_GPIO_ED, + [STMPE_IDX_GPEDR_LSB] = STMPE811_REG_GPIO_ED, }; static struct stmpe_variant_block stmpe811_blocks[] = { @@ -561,19 +561,28 @@ static const u8 stmpe1601_regs[] = { [STMPE_IDX_SYS_CTRL] = STMPE1601_REG_SYS_CTRL, [STMPE_IDX_SYS_CTRL2] = STMPE1601_REG_SYS_CTRL2, [STMPE_IDX_ICR_LSB] = STMPE1601_REG_ICR_LSB, + [STMPE_IDX_IER_MSB] = STMPE1601_REG_IER_MSB, [STMPE_IDX_IER_LSB] = STMPE1601_REG_IER_LSB, [STMPE_IDX_ISR_MSB] = STMPE1601_REG_ISR_MSB, [STMPE_IDX_GPMR_LSB] = STMPE1601_REG_GPIO_MP_LSB, + [STMPE_IDX_GPMR_CSB] = STMPE1601_REG_GPIO_MP_MSB, [STMPE_IDX_GPSR_LSB] = STMPE1601_REG_GPIO_SET_LSB, + [STMPE_IDX_GPSR_CSB] = STMPE1601_REG_GPIO_SET_MSB, [STMPE_IDX_GPCR_LSB] = STMPE1601_REG_GPIO_CLR_LSB, + [STMPE_IDX_GPCR_CSB] = STMPE1601_REG_GPIO_CLR_MSB, [STMPE_IDX_GPDR_LSB] = STMPE1601_REG_GPIO_SET_DIR_LSB, + [STMPE_IDX_GPDR_CSB] = STMPE1601_REG_GPIO_SET_DIR_MSB, + [STMPE_IDX_GPEDR_LSB] = STMPE1601_REG_GPIO_ED_LSB, + [STMPE_IDX_GPEDR_CSB] = STMPE1601_REG_GPIO_ED_MSB, [STMPE_IDX_GPRER_LSB] = STMPE1601_REG_GPIO_RE_LSB, + [STMPE_IDX_GPRER_CSB] = STMPE1601_REG_GPIO_RE_MSB, [STMPE_IDX_GPFER_LSB] = STMPE1601_REG_GPIO_FE_LSB, + [STMPE_IDX_GPFER_CSB] = STMPE1601_REG_GPIO_FE_MSB, [STMPE_IDX_GPPUR_LSB] = STMPE1601_REG_GPIO_PU_LSB, [STMPE_IDX_GPAFR_U_MSB] = STMPE1601_REG_GPIO_AF_U_MSB, [STMPE_IDX_IEGPIOR_LSB] = STMPE1601_REG_INT_EN_GPIO_MASK_LSB, + [STMPE_IDX_IEGPIOR_CSB] = STMPE1601_REG_INT_EN_GPIO_MASK_MSB, [STMPE_IDX_ISGPIOR_MSB] = STMPE1601_REG_INT_STA_GPIO_MSB, - [STMPE_IDX_GPEDR_MSB] = STMPE1601_REG_GPIO_ED_MSB, }; static struct stmpe_variant_block stmpe1601_blocks[] = { @@ -719,14 +728,28 @@ static const u8 stmpe1801_regs[] = { [STMPE_IDX_IER_LSB] = STMPE1801_REG_INT_EN_MASK_LOW, [STMPE_IDX_ISR_LSB] = STMPE1801_REG_INT_STA_LOW, [STMPE_IDX_GPMR_LSB] = STMPE1801_REG_GPIO_MP_LOW, + [STMPE_IDX_GPMR_CSB] = STMPE1801_REG_GPIO_MP_MID, + [STMPE_IDX_GPMR_MSB] = STMPE1801_REG_GPIO_MP_HIGH, [STMPE_IDX_GPSR_LSB] = STMPE1801_REG_GPIO_SET_LOW, + [STMPE_IDX_GPSR_CSB] = STMPE1801_REG_GPIO_SET_MID, + [STMPE_IDX_GPSR_MSB] = STMPE1801_REG_GPIO_SET_HIGH, [STMPE_IDX_GPCR_LSB] = STMPE1801_REG_GPIO_CLR_LOW, + [STMPE_IDX_GPCR_CSB] = STMPE1801_REG_GPIO_CLR_MID, + [STMPE_IDX_GPCR_MSB] = STMPE1801_REG_GPIO_CLR_HIGH, [STMPE_IDX_GPDR_LSB] = STMPE1801_REG_GPIO_SET_DIR_LOW, + [STMPE_IDX_GPDR_CSB] = STMPE1801_REG_GPIO_SET_DIR_MID, + [STMPE_IDX_GPDR_MSB] = STMPE1801_REG_GPIO_SET_DIR_HIGH, [STMPE_IDX_GPRER_LSB] = STMPE1801_REG_GPIO_RE_LOW, + [STMPE_IDX_GPRER_CSB] = STMPE1801_REG_GPIO_RE_MID, + [STMPE_IDX_GPRER_MSB] = STMPE1801_REG_GPIO_RE_HIGH, [STMPE_IDX_GPFER_LSB] = STMPE1801_REG_GPIO_FE_LOW, + [STMPE_IDX_GPFER_CSB] = STMPE1801_REG_GPIO_FE_MID, + [STMPE_IDX_GPFER_MSB] = STMPE1801_REG_GPIO_FE_HIGH, [STMPE_IDX_GPPUR_LSB] = STMPE1801_REG_GPIO_PULL_UP_LOW, [STMPE_IDX_IEGPIOR_LSB] = STMPE1801_REG_INT_EN_GPIO_MASK_LOW, - [STMPE_IDX_ISGPIOR_LSB] = STMPE1801_REG_INT_STA_GPIO_LOW, + [STMPE_IDX_IEGPIOR_CSB] = STMPE1801_REG_INT_EN_GPIO_MASK_MID, + [STMPE_IDX_IEGPIOR_MSB] = STMPE1801_REG_INT_EN_GPIO_MASK_HIGH, + [STMPE_IDX_ISGPIOR_MSB] = STMPE1801_REG_INT_STA_GPIO_HIGH, }; static struct stmpe_variant_block stmpe1801_blocks[] = { @@ -811,19 +834,36 @@ static const u8 stmpe24xx_regs[] = { [STMPE_IDX_SYS_CTRL] = STMPE24XX_REG_SYS_CTRL, [STMPE_IDX_SYS_CTRL2] = STMPE24XX_REG_SYS_CTRL2, [STMPE_IDX_ICR_LSB] = STMPE24XX_REG_ICR_LSB, + [STMPE_IDX_IER_MSB] = STMPE24XX_REG_IER_MSB, [STMPE_IDX_IER_LSB] = STMPE24XX_REG_IER_LSB, [STMPE_IDX_ISR_MSB] = STMPE24XX_REG_ISR_MSB, [STMPE_IDX_GPMR_LSB] = STMPE24XX_REG_GPMR_LSB, + [STMPE_IDX_GPMR_CSB] = STMPE24XX_REG_GPMR_CSB, + [STMPE_IDX_GPMR_MSB] = STMPE24XX_REG_GPMR_MSB, [STMPE_IDX_GPSR_LSB] = STMPE24XX_REG_GPSR_LSB, + [STMPE_IDX_GPSR_CSB] = STMPE24XX_REG_GPSR_CSB, + [STMPE_IDX_GPSR_MSB] = STMPE24XX_REG_GPSR_MSB, [STMPE_IDX_GPCR_LSB] = STMPE24XX_REG_GPCR_LSB, + [STMPE_IDX_GPCR_CSB] = STMPE24XX_REG_GPCR_CSB, + [STMPE_IDX_GPCR_MSB] = STMPE24XX_REG_GPCR_MSB, [STMPE_IDX_GPDR_LSB] = STMPE24XX_REG_GPDR_LSB, + [STMPE_IDX_GPDR_CSB] = STMPE24XX_REG_GPDR_CSB, + [STMPE_IDX_GPDR_MSB] = STMPE24XX_REG_GPDR_MSB, [STMPE_IDX_GPRER_LSB] = STMPE24XX_REG_GPRER_LSB, + [STMPE_IDX_GPRER_CSB] = STMPE24XX_REG_GPRER_CSB, + [STMPE_IDX_GPRER_MSB] = STMPE24XX_REG_GPRER_MSB, [STMPE_IDX_GPFER_LSB] = STMPE24XX_REG_GPFER_LSB, + [STMPE_IDX_GPFER_CSB] = STMPE24XX_REG_GPFER_CSB, + [STMPE_IDX_GPFER_MSB] = STMPE24XX_REG_GPFER_MSB, [STMPE_IDX_GPPUR_LSB] = STMPE24XX_REG_GPPUR_LSB, [STMPE_IDX_GPPDR_LSB] = STMPE24XX_REG_GPPDR_LSB, [STMPE_IDX_GPAFR_U_MSB] = STMPE24XX_REG_GPAFR_U_MSB, [STMPE_IDX_IEGPIOR_LSB] = STMPE24XX_REG_IEGPIOR_LSB, + [STMPE_IDX_IEGPIOR_CSB] = STMPE24XX_REG_IEGPIOR_CSB, + [STMPE_IDX_IEGPIOR_MSB] = STMPE24XX_REG_IEGPIOR_MSB, [STMPE_IDX_ISGPIOR_MSB] = STMPE24XX_REG_ISGPIOR_MSB, + [STMPE_IDX_GPEDR_LSB] = STMPE24XX_REG_GPEDR_LSB, + [STMPE_IDX_GPEDR_CSB] = STMPE24XX_REG_GPEDR_CSB, [STMPE_IDX_GPEDR_MSB] = STMPE24XX_REG_GPEDR_MSB, }; @@ -998,7 +1038,7 @@ static void stmpe_irq_sync_unlock(struct irq_data *data) continue; stmpe->oldier[i] = new; - stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_IER_LSB] - i, new); + stmpe_reg_write(stmpe, stmpe->regs[STMPE_IDX_IER_LSB + i], new); } mutex_unlock(&stmpe->irq_lock); diff --git a/drivers/mfd/stmpe.h b/drivers/mfd/stmpe.h index 4ba112362167..f1273420c8e8 100644 --- a/drivers/mfd/stmpe.h +++ b/drivers/mfd/stmpe.h @@ -179,19 +179,32 @@ int stmpe_remove(struct stmpe *stmpe); #define STMPE1601_REG_SYS_CTRL 0x02 #define STMPE1601_REG_SYS_CTRL2 0x03 +#define STMPE1601_REG_ICR_MSB 0x10 #define STMPE1601_REG_ICR_LSB 0x11 +#define STMPE1601_REG_IER_MSB 0x12 #define STMPE1601_REG_IER_LSB 0x13 #define STMPE1601_REG_ISR_MSB 0x14 -#define STMPE1601_REG_CHIP_ID 0x80 +#define STMPE1601_REG_ISR_LSB 0x15 +#define STMPE1601_REG_INT_EN_GPIO_MASK_MSB 0x16 #define STMPE1601_REG_INT_EN_GPIO_MASK_LSB 0x17 #define STMPE1601_REG_INT_STA_GPIO_MSB 0x18 -#define STMPE1601_REG_GPIO_MP_LSB 0x87 +#define STMPE1601_REG_INT_STA_GPIO_LSB 0x19 +#define STMPE1601_REG_CHIP_ID 0x80 +#define STMPE1601_REG_GPIO_SET_MSB 0x82 #define STMPE1601_REG_GPIO_SET_LSB 0x83 +#define STMPE1601_REG_GPIO_CLR_MSB 0x84 #define STMPE1601_REG_GPIO_CLR_LSB 0x85 +#define STMPE1601_REG_GPIO_MP_MSB 0x86 +#define STMPE1601_REG_GPIO_MP_LSB 0x87 +#define STMPE1601_REG_GPIO_SET_DIR_MSB 0x88 #define STMPE1601_REG_GPIO_SET_DIR_LSB 0x89 #define STMPE1601_REG_GPIO_ED_MSB 0x8A +#define STMPE1601_REG_GPIO_ED_LSB 0x8B +#define STMPE1601_REG_GPIO_RE_MSB 0x8C #define STMPE1601_REG_GPIO_RE_LSB 0x8D +#define STMPE1601_REG_GPIO_FE_MSB 0x8E #define STMPE1601_REG_GPIO_FE_LSB 0x8F +#define STMPE1601_REG_GPIO_PU_MSB 0x90 #define STMPE1601_REG_GPIO_PU_LSB 0x91 #define STMPE1601_REG_GPIO_AF_U_MSB 0x92 @@ -267,23 +280,47 @@ int stmpe_remove(struct stmpe *stmpe); #define STMPE24XX_REG_SYS_CTRL 0x02 #define STMPE24XX_REG_SYS_CTRL2 0x03 +#define STMPE24XX_REG_ICR_MSB 0x10 #define STMPE24XX_REG_ICR_LSB 0x11 +#define STMPE24XX_REG_IER_MSB 0x12 #define STMPE24XX_REG_IER_LSB 0x13 #define STMPE24XX_REG_ISR_MSB 0x14 -#define STMPE24XX_REG_CHIP_ID 0x80 +#define STMPE24XX_REG_ISR_LSB 0x15 +#define STMPE24XX_REG_IEGPIOR_MSB 0x16 +#define STMPE24XX_REG_IEGPIOR_CSB 0x17 #define STMPE24XX_REG_IEGPIOR_LSB 0x18 #define STMPE24XX_REG_ISGPIOR_MSB 0x19 -#define STMPE24XX_REG_GPMR_LSB 0xA4 +#define STMPE24XX_REG_ISGPIOR_CSB 0x1A +#define STMPE24XX_REG_ISGPIOR_LSB 0x1B +#define STMPE24XX_REG_CHIP_ID 0x80 +#define STMPE24XX_REG_GPSR_MSB 0x83 +#define STMPE24XX_REG_GPSR_CSB 0x84 #define STMPE24XX_REG_GPSR_LSB 0x85 +#define STMPE24XX_REG_GPCR_MSB 0x86 +#define STMPE24XX_REG_GPCR_CSB 0x87 #define STMPE24XX_REG_GPCR_LSB 0x88 +#define STMPE24XX_REG_GPDR_MSB 0x89 +#define STMPE24XX_REG_GPDR_CSB 0x8A #define STMPE24XX_REG_GPDR_LSB 0x8B #define STMPE24XX_REG_GPEDR_MSB 0x8C +#define STMPE24XX_REG_GPEDR_CSB 0x8D +#define STMPE24XX_REG_GPEDR_LSB 0x8E +#define STMPE24XX_REG_GPRER_MSB 0x8F +#define STMPE24XX_REG_GPRER_CSB 0x90 #define STMPE24XX_REG_GPRER_LSB 0x91 +#define STMPE24XX_REG_GPFER_MSB 0x92 +#define STMPE24XX_REG_GPFER_CSB 0x93 #define STMPE24XX_REG_GPFER_LSB 0x94 +#define STMPE24XX_REG_GPPUR_MSB 0x95 +#define STMPE24XX_REG_GPPUR_CSB 0x96 #define STMPE24XX_REG_GPPUR_LSB 0x97 -#define STMPE24XX_REG_GPPDR_LSB 0x9a +#define STMPE24XX_REG_GPPDR_MSB 0x98 +#define STMPE24XX_REG_GPPDR_CSB 0x99 +#define STMPE24XX_REG_GPPDR_LSB 0x9A #define STMPE24XX_REG_GPAFR_U_MSB 0x9B - +#define STMPE24XX_REG_GPMR_MSB 0xA2 +#define STMPE24XX_REG_GPMR_CSB 0xA3 +#define STMPE24XX_REG_GPMR_LSB 0xA4 #define STMPE24XX_SYS_CTRL_ENABLE_GPIO (1 << 3) #define STMPE24XX_SYSCON_ENABLE_PWM (1 << 2) #define STMPE24XX_SYS_CTRL_ENABLE_KPC (1 << 1) diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h index eb8b73bd139f..6b26661a640e 100644 --- a/include/linux/mfd/stmpe.h +++ b/include/linux/mfd/stmpe.h @@ -43,20 +43,38 @@ enum { STMPE_IDX_SYS_CTRL2, STMPE_IDX_ICR_LSB, STMPE_IDX_IER_LSB, + STMPE_IDX_IER_MSB, STMPE_IDX_ISR_LSB, STMPE_IDX_ISR_MSB, STMPE_IDX_GPMR_LSB, + STMPE_IDX_GPMR_CSB, + STMPE_IDX_GPMR_MSB, STMPE_IDX_GPSR_LSB, + STMPE_IDX_GPSR_CSB, + STMPE_IDX_GPSR_MSB, STMPE_IDX_GPCR_LSB, + STMPE_IDX_GPCR_CSB, + STMPE_IDX_GPCR_MSB, STMPE_IDX_GPDR_LSB, + STMPE_IDX_GPDR_CSB, + STMPE_IDX_GPDR_MSB, + STMPE_IDX_GPEDR_LSB, + STMPE_IDX_GPEDR_CSB, STMPE_IDX_GPEDR_MSB, STMPE_IDX_GPRER_LSB, + STMPE_IDX_GPRER_CSB, + STMPE_IDX_GPRER_MSB, STMPE_IDX_GPFER_LSB, + STMPE_IDX_GPFER_CSB, + STMPE_IDX_GPFER_MSB, STMPE_IDX_GPPUR_LSB, STMPE_IDX_GPPDR_LSB, STMPE_IDX_GPAFR_U_MSB, STMPE_IDX_IEGPIOR_LSB, + STMPE_IDX_IEGPIOR_CSB, + STMPE_IDX_IEGPIOR_MSB, STMPE_IDX_ISGPIOR_LSB, + STMPE_IDX_ISGPIOR_CSB, STMPE_IDX_ISGPIOR_MSB, STMPE_IDX_MAX, }; -- cgit v1.2.3 From 6bb9f0d93399cbde14fc6a1532341a14a85d2df4 Mon Sep 17 00:00:00 2001 From: Patrice Chotard Date: Wed, 10 Aug 2016 09:39:14 +0200 Subject: mfd: Add STMPE1600 support STMPE1600 is a 16-bit port expander. Datasheet is available here : http://www2.st.com/content/st_com/en/products/interfaces-and-transceivers/ i-o-expanders-and-level-translators/i-o-expanders/stmpe1600.html Signed-off-by: Amelie DELAUNAY Signed-off-by: Patrice Chotard Acked-by: Linus Walleij Signed-off-by: Lee Jones --- drivers/mfd/stmpe-i2c.c | 2 ++ drivers/mfd/stmpe.c | 65 +++++++++++++++++++++++++++++++++++++++++++---- drivers/mfd/stmpe.h | 21 +++++++++++++++ include/linux/mfd/stmpe.h | 1 + 4 files changed, 84 insertions(+), 5 deletions(-) (limited to 'include/linux') diff --git a/drivers/mfd/stmpe-i2c.c b/drivers/mfd/stmpe-i2c.c index c3f4aab53b07..863c39a3353c 100644 --- a/drivers/mfd/stmpe-i2c.c +++ b/drivers/mfd/stmpe-i2c.c @@ -57,6 +57,7 @@ static const struct of_device_id stmpe_of_match[] = { { .compatible = "st,stmpe610", .data = (void *)STMPE610, }, { .compatible = "st,stmpe801", .data = (void *)STMPE801, }, { .compatible = "st,stmpe811", .data = (void *)STMPE811, }, + { .compatible = "st,stmpe1600", .data = (void *)STMPE1600, }, { .compatible = "st,stmpe1601", .data = (void *)STMPE1601, }, { .compatible = "st,stmpe1801", .data = (void *)STMPE1801, }, { .compatible = "st,stmpe2401", .data = (void *)STMPE2401, }, @@ -101,6 +102,7 @@ static const struct i2c_device_id stmpe_i2c_id[] = { { "stmpe610", STMPE610 }, { "stmpe801", STMPE801 }, { "stmpe811", STMPE811 }, + { "stmpe1600", STMPE1600 }, { "stmpe1601", STMPE1601 }, { "stmpe1801", STMPE1801 }, { "stmpe2401", STMPE2401 }, diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index 1877d1ea2e7c..cfdae8a3d779 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -552,6 +552,59 @@ static struct stmpe_variant_info stmpe610 = { .get_altfunc = stmpe811_get_altfunc, }; +/* + * STMPE1600 + * Compared to all others STMPE variant, LSB and MSB regs are located in this + * order : LSB addr + * MSB addr + 1 + * As there is only 2 * 8bits registers for GPMR/GPSR/IEGPIOPR, CSB index is MSB registers + */ + +static const u8 stmpe1600_regs[] = { + [STMPE_IDX_CHIP_ID] = STMPE1600_REG_CHIP_ID, + [STMPE_IDX_SYS_CTRL] = STMPE1600_REG_SYS_CTRL, + [STMPE_IDX_ICR_LSB] = STMPE1600_REG_SYS_CTRL, + [STMPE_IDX_GPMR_LSB] = STMPE1600_REG_GPMR_LSB, + [STMPE_IDX_GPMR_CSB] = STMPE1600_REG_GPMR_MSB, + [STMPE_IDX_GPSR_LSB] = STMPE1600_REG_GPSR_LSB, + [STMPE_IDX_GPSR_CSB] = STMPE1600_REG_GPSR_MSB, + [STMPE_IDX_GPDR_LSB] = STMPE1600_REG_GPDR_LSB, + [STMPE_IDX_GPDR_CSB] = STMPE1600_REG_GPDR_MSB, + [STMPE_IDX_IEGPIOR_LSB] = STMPE1600_REG_IEGPIOR_LSB, + [STMPE_IDX_IEGPIOR_CSB] = STMPE1600_REG_IEGPIOR_MSB, + [STMPE_IDX_ISGPIOR_LSB] = STMPE1600_REG_ISGPIOR_LSB, +}; + +static struct stmpe_variant_block stmpe1600_blocks[] = { + { + .cell = &stmpe_gpio_cell, + .irq = 0, + .block = STMPE_BLOCK_GPIO, + }, +}; + +static int stmpe1600_enable(struct stmpe *stmpe, unsigned int blocks, + bool enable) +{ + if (blocks & STMPE_BLOCK_GPIO) + return 0; + else + return -EINVAL; +} + +static struct stmpe_variant_info stmpe1600 = { + .name = "stmpe1600", + .id_val = STMPE1600_ID, + .id_mask = 0xffff, + .num_gpios = 16, + .af_bits = 0, + .regs = stmpe1600_regs, + .blocks = stmpe1600_blocks, + .num_blocks = ARRAY_SIZE(stmpe1600_blocks), + .num_irqs = STMPE1600_NR_INTERNAL_IRQS, + .enable = stmpe1600_enable, +}; + /* * STMPE1601 */ @@ -949,6 +1002,7 @@ static struct stmpe_variant_info *stmpe_variant_info[STMPE_NBR_PARTS] = { [STMPE610] = &stmpe610, [STMPE801] = &stmpe801, [STMPE811] = &stmpe811, + [STMPE1600] = &stmpe1600, [STMPE1601] = &stmpe1601, [STMPE1801] = &stmpe1801, [STMPE2401] = &stmpe2401, @@ -975,7 +1029,8 @@ static irqreturn_t stmpe_irq(int irq, void *data) int ret; int i; - if (variant->id_val == STMPE801_ID) { + if (variant->id_val == STMPE801_ID || + variant->id_val == STMPE1600_ID) { int base = irq_create_mapping(stmpe->domain, 0); handle_nested_irq(base); @@ -1149,13 +1204,13 @@ static int stmpe_chip_init(struct stmpe *stmpe) return ret; if (stmpe->irq >= 0) { - if (id == STMPE801_ID) + if (id == STMPE801_ID || id == STMPE1600_ID) icr = STMPE_SYS_CTRL_INT_EN; else icr = STMPE_ICR_LSB_GIM; - /* STMPE801 doesn't support Edge interrupts */ - if (id != STMPE801_ID) { + /* STMPE801 and STMPE1600 don't support Edge interrupts */ + if (id != STMPE801_ID && id != STMPE1600_ID) { if (irq_trigger == IRQF_TRIGGER_FALLING || irq_trigger == IRQF_TRIGGER_RISING) icr |= STMPE_ICR_LSB_EDGE; @@ -1163,7 +1218,7 @@ static int stmpe_chip_init(struct stmpe *stmpe) if (irq_trigger == IRQF_TRIGGER_RISING || irq_trigger == IRQF_TRIGGER_HIGH) { - if (id == STMPE801_ID) + if (id == STMPE801_ID || id == STMPE1600_ID) icr |= STMPE_SYS_CTRL_INT_HI; else icr |= STMPE_ICR_LSB_HIGH; diff --git a/drivers/mfd/stmpe.h b/drivers/mfd/stmpe.h index f1273420c8e8..f7efdd8a5fc6 100644 --- a/drivers/mfd/stmpe.h +++ b/drivers/mfd/stmpe.h @@ -163,6 +163,27 @@ int stmpe_remove(struct stmpe *stmpe); #define STMPE811_SYS_CTRL2_GPIO_OFF (1 << 2) #define STMPE811_SYS_CTRL2_TS_OFF (1 << 3) +/* + * STMPE1600 + */ +#define STMPE1600_ID 0x0016 +#define STMPE1600_NR_INTERNAL_IRQS 16 + +#define STMPE1600_REG_CHIP_ID 0x00 +#define STMPE1600_REG_SYS_CTRL 0x03 +#define STMPE1600_REG_IEGPIOR_LSB 0x08 +#define STMPE1600_REG_IEGPIOR_MSB 0x09 +#define STMPE1600_REG_ISGPIOR_LSB 0x0A +#define STMPE1600_REG_ISGPIOR_MSB 0x0B +#define STMPE1600_REG_GPMR_LSB 0x10 +#define STMPE1600_REG_GPMR_MSB 0x11 +#define STMPE1600_REG_GPSR_LSB 0x12 +#define STMPE1600_REG_GPSR_MSB 0x13 +#define STMPE1600_REG_GPDR_LSB 0x14 +#define STMPE1600_REG_GPDR_MSB 0x15 +#define STMPE1600_REG_GPPIR_LSB 0x16 +#define STMPE1600_REG_GPPIR_MSB 0x17 + /* * STMPE1601 */ diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h index 6b26661a640e..4a827af17e59 100644 --- a/include/linux/mfd/stmpe.h +++ b/include/linux/mfd/stmpe.h @@ -26,6 +26,7 @@ enum stmpe_partnum { STMPE610, STMPE801, STMPE811, + STMPE1600, STMPE1601, STMPE1801, STMPE2401, -- cgit v1.2.3 From e48c178814b4a33f84f62d01f5a601ebd57fbba8 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Wed, 6 Jul 2016 09:18:30 +0200 Subject: perf/core: Optimize perf_pmu_sched_task() For perf record -b, which requires the pmu::sched_task callback the current code is rather expensive: 7.68% sched-pipe [kernel.vmlinux] [k] perf_pmu_sched_task 5.95% sched-pipe [kernel.vmlinux] [k] __switch_to 5.20% sched-pipe [kernel.vmlinux] [k] __intel_pmu_disable_all 3.95% sched-pipe perf [.] worker_thread The problem is that it will iterate all registered PMUs, most of which will not have anything to do. Avoid this by keeping an explicit list of PMUs that have requested the callback. The perf_sched_cb_{inc,dec}() functions already takes the required pmu argument, and now that these functions are no longer called from NMI context we can use them to manage a list. With this patch applied the function doesn't show up in the top 4 anymore (it dropped to 18th place). 6.67% sched-pipe [kernel.vmlinux] [k] __switch_to 6.18% sched-pipe [kernel.vmlinux] [k] __intel_pmu_disable_all 3.92% sched-pipe [kernel.vmlinux] [k] switch_mm_irqs_off 3.71% sched-pipe perf [.] worker_thread Signed-off-by: Peter Zijlstra (Intel) Cc: Alexander Shishkin Cc: Arnaldo Carvalho de Melo Cc: Jiri Olsa Cc: Linus Torvalds Cc: Peter Zijlstra Cc: Stephane Eranian Cc: Thomas Gleixner Cc: Vince Weaver Cc: linux-kernel@vger.kernel.org Signed-off-by: Ingo Molnar --- include/linux/perf_event.h | 3 +++ kernel/events/core.c | 43 ++++++++++++++++++++++++------------------- 2 files changed, 27 insertions(+), 19 deletions(-) (limited to 'include/linux') diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 2b6b43cc0dd5..529c41fa73c8 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -774,6 +774,9 @@ struct perf_cpu_context { #ifdef CONFIG_CGROUP_PERF struct perf_cgroup *cgrp; #endif + + struct list_head sched_cb_entry; + int sched_cb_usage; }; struct perf_output_handle { diff --git a/kernel/events/core.c b/kernel/events/core.c index 57aff715039f..803481cb6cbd 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -2805,13 +2805,26 @@ unlock: } } +static DEFINE_PER_CPU(struct list_head, sched_cb_list); + void perf_sched_cb_dec(struct pmu *pmu) { + struct perf_cpu_context *cpuctx = this_cpu_ptr(pmu->pmu_cpu_context); + this_cpu_dec(perf_sched_cb_usages); + + if (!--cpuctx->sched_cb_usage) + list_del(&cpuctx->sched_cb_entry); } + void perf_sched_cb_inc(struct pmu *pmu) { + struct perf_cpu_context *cpuctx = this_cpu_ptr(pmu->pmu_cpu_context); + + if (!cpuctx->sched_cb_usage++) + list_add(&cpuctx->sched_cb_entry, this_cpu_ptr(&sched_cb_list)); + this_cpu_inc(perf_sched_cb_usages); } @@ -2829,34 +2842,24 @@ static void perf_pmu_sched_task(struct task_struct *prev, { struct perf_cpu_context *cpuctx; struct pmu *pmu; - unsigned long flags; if (prev == next) return; - local_irq_save(flags); - - rcu_read_lock(); - - list_for_each_entry_rcu(pmu, &pmus, entry) { - if (pmu->sched_task) { - cpuctx = this_cpu_ptr(pmu->pmu_cpu_context); - - perf_ctx_lock(cpuctx, cpuctx->task_ctx); + list_for_each_entry(cpuctx, this_cpu_ptr(&sched_cb_list), sched_cb_entry) { + pmu = cpuctx->unique_pmu; /* software PMUs will not have sched_task */ - perf_pmu_disable(pmu); + if (WARN_ON_ONCE(!pmu->sched_task)) + continue; - pmu->sched_task(cpuctx->task_ctx, sched_in); + perf_ctx_lock(cpuctx, cpuctx->task_ctx); + perf_pmu_disable(pmu); - perf_pmu_enable(pmu); + pmu->sched_task(cpuctx->task_ctx, sched_in); - perf_ctx_unlock(cpuctx, cpuctx->task_ctx); - } + perf_pmu_enable(pmu); + perf_ctx_unlock(cpuctx, cpuctx->task_ctx); } - - rcu_read_unlock(); - - local_irq_restore(flags); } static void perf_event_switch(struct task_struct *task, @@ -10393,6 +10396,8 @@ static void __init perf_event_init_all_cpus(void) INIT_LIST_HEAD(&per_cpu(pmu_sb_events.list, cpu)); raw_spin_lock_init(&per_cpu(pmu_sb_events.lock, cpu)); + + INIT_LIST_HEAD(&per_cpu(sched_cb_list, cpu)); } } -- cgit v1.2.3 From bd425d4bfc7a1a6064dbbadfbac9c7eec0e426ec Mon Sep 17 00:00:00 2001 From: Morten Rasmussen Date: Wed, 22 Jun 2016 18:03:12 +0100 Subject: sched/core: Fix power to capacity renaming in comment It is seems that this one escaped Nico's renaming of cpu_power to cpu_capacity a while back. Signed-off-by: Morten Rasmussen Signed-off-by: Peter Zijlstra (Intel) Cc: Linus Torvalds Cc: Mike Galbraith Cc: Peter Zijlstra Cc: Thomas Gleixner Cc: dietmar.eggemann@arm.com Cc: linux-kernel@vger.kernel.org Cc: mgalbraith@suse.de Cc: vincent.guittot@linaro.org Cc: yuyang.du@intel.com Link: http://lkml.kernel.org/r/1466615004-3503-2-git-send-email-morten.rasmussen@arm.com Signed-off-by: Ingo Molnar --- include/linux/sched.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/sched.h b/include/linux/sched.h index 62c68e513e39..f3db596efd2c 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1022,7 +1022,7 @@ extern void wake_up_q(struct wake_q_head *head); #define SD_BALANCE_FORK 0x0008 /* Balance on fork, clone */ #define SD_BALANCE_WAKE 0x0010 /* Balance on wakeup */ #define SD_WAKE_AFFINE 0x0020 /* Wake task to waking CPU */ -#define SD_SHARE_CPUCAPACITY 0x0080 /* Domain members share cpu power */ +#define SD_SHARE_CPUCAPACITY 0x0080 /* Domain members share cpu capacity */ #define SD_SHARE_POWERDOMAIN 0x0100 /* Domain members share power domain */ #define SD_SHARE_PKG_RESOURCES 0x0200 /* Domain members share cpu pkg resources */ #define SD_SERIALIZE 0x0400 /* Only a single load balancing instance */ -- cgit v1.2.3 From 80127a39681bd68c959f0953f84a830cbd7c3b1c Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Thu, 14 Jul 2016 20:08:46 +0200 Subject: locking/percpu-rwsem: Optimize readers and reduce global impact Currently the percpu-rwsem switches to (global) atomic ops while a writer is waiting; which could be quite a while and slows down releasing the readers. This patch cures this problem by ordering the reader-state vs reader-count (see the comments in __percpu_down_read() and percpu_down_write()). This changes a global atomic op into a full memory barrier, which doesn't have the global cacheline contention. This also enables using the percpu-rwsem with rcu_sync disabled in order to bias the implementation differently, reducing the writer latency by adding some cost to readers. Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Oleg Nesterov Cc: Andrew Morton Cc: Linus Torvalds Cc: Paul E. McKenney Cc: Paul McKenney Cc: Peter Zijlstra Cc: Thomas Gleixner Cc: linux-kernel@vger.kernel.org [ Fixed modular build. ] Signed-off-by: Ingo Molnar --- include/linux/percpu-rwsem.h | 84 ++++++++++++++-- kernel/locking/percpu-rwsem.c | 228 ++++++++++++++++++++++++------------------ kernel/rcu/sync.c | 2 + 3 files changed, 208 insertions(+), 106 deletions(-) (limited to 'include/linux') diff --git a/include/linux/percpu-rwsem.h b/include/linux/percpu-rwsem.h index c2fa3ecb0dce..146efefde2a1 100644 --- a/include/linux/percpu-rwsem.h +++ b/include/linux/percpu-rwsem.h @@ -10,30 +10,96 @@ struct percpu_rw_semaphore { struct rcu_sync rss; - unsigned int __percpu *fast_read_ctr; + unsigned int __percpu *read_count; struct rw_semaphore rw_sem; - atomic_t slow_read_ctr; - wait_queue_head_t write_waitq; + wait_queue_head_t writer; + int readers_block; }; -extern void percpu_down_read(struct percpu_rw_semaphore *); -extern int percpu_down_read_trylock(struct percpu_rw_semaphore *); -extern void percpu_up_read(struct percpu_rw_semaphore *); +extern int __percpu_down_read(struct percpu_rw_semaphore *, int); +extern void __percpu_up_read(struct percpu_rw_semaphore *); + +static inline void percpu_down_read(struct percpu_rw_semaphore *sem) +{ + might_sleep(); + + rwsem_acquire_read(&sem->rw_sem.dep_map, 0, 0, _RET_IP_); + + preempt_disable(); + /* + * We are in an RCU-sched read-side critical section, so the writer + * cannot both change sem->state from readers_fast and start checking + * counters while we are here. So if we see !sem->state, we know that + * the writer won't be checking until we're past the preempt_enable() + * and that one the synchronize_sched() is done, the writer will see + * anything we did within this RCU-sched read-size critical section. + */ + __this_cpu_inc(*sem->read_count); + if (unlikely(!rcu_sync_is_idle(&sem->rss))) + __percpu_down_read(sem, false); /* Unconditional memory barrier */ + preempt_enable(); + /* + * The barrier() from preempt_enable() prevents the compiler from + * bleeding the critical section out. + */ +} + +static inline int percpu_down_read_trylock(struct percpu_rw_semaphore *sem) +{ + int ret = 1; + + preempt_disable(); + /* + * Same as in percpu_down_read(). + */ + __this_cpu_inc(*sem->read_count); + if (unlikely(!rcu_sync_is_idle(&sem->rss))) + ret = __percpu_down_read(sem, true); /* Unconditional memory barrier */ + preempt_enable(); + /* + * The barrier() from preempt_enable() prevents the compiler from + * bleeding the critical section out. + */ + + if (ret) + rwsem_acquire_read(&sem->rw_sem.dep_map, 0, 1, _RET_IP_); + + return ret; +} + +static inline void percpu_up_read(struct percpu_rw_semaphore *sem) +{ + /* + * The barrier() in preempt_disable() prevents the compiler from + * bleeding the critical section out. + */ + preempt_disable(); + /* + * Same as in percpu_down_read(). + */ + if (likely(rcu_sync_is_idle(&sem->rss))) + __this_cpu_dec(*sem->read_count); + else + __percpu_up_read(sem); /* Unconditional memory barrier */ + preempt_enable(); + + rwsem_release(&sem->rw_sem.dep_map, 1, _RET_IP_); +} extern void percpu_down_write(struct percpu_rw_semaphore *); extern void percpu_up_write(struct percpu_rw_semaphore *); extern int __percpu_init_rwsem(struct percpu_rw_semaphore *, const char *, struct lock_class_key *); + extern void percpu_free_rwsem(struct percpu_rw_semaphore *); -#define percpu_init_rwsem(brw) \ +#define percpu_init_rwsem(sem) \ ({ \ static struct lock_class_key rwsem_key; \ - __percpu_init_rwsem(brw, #brw, &rwsem_key); \ + __percpu_init_rwsem(sem, #sem, &rwsem_key); \ }) - #define percpu_rwsem_is_held(sem) lockdep_is_held(&(sem)->rw_sem) static inline void percpu_rwsem_release(struct percpu_rw_semaphore *sem, diff --git a/kernel/locking/percpu-rwsem.c b/kernel/locking/percpu-rwsem.c index bec0b647f9cc..ce182599cf2e 100644 --- a/kernel/locking/percpu-rwsem.c +++ b/kernel/locking/percpu-rwsem.c @@ -8,152 +8,186 @@ #include #include -int __percpu_init_rwsem(struct percpu_rw_semaphore *brw, +int __percpu_init_rwsem(struct percpu_rw_semaphore *sem, const char *name, struct lock_class_key *rwsem_key) { - brw->fast_read_ctr = alloc_percpu(int); - if (unlikely(!brw->fast_read_ctr)) + sem->read_count = alloc_percpu(int); + if (unlikely(!sem->read_count)) return -ENOMEM; /* ->rw_sem represents the whole percpu_rw_semaphore for lockdep */ - __init_rwsem(&brw->rw_sem, name, rwsem_key); - rcu_sync_init(&brw->rss, RCU_SCHED_SYNC); - atomic_set(&brw->slow_read_ctr, 0); - init_waitqueue_head(&brw->write_waitq); + rcu_sync_init(&sem->rss, RCU_SCHED_SYNC); + __init_rwsem(&sem->rw_sem, name, rwsem_key); + init_waitqueue_head(&sem->writer); + sem->readers_block = 0; return 0; } EXPORT_SYMBOL_GPL(__percpu_init_rwsem); -void percpu_free_rwsem(struct percpu_rw_semaphore *brw) +void percpu_free_rwsem(struct percpu_rw_semaphore *sem) { /* * XXX: temporary kludge. The error path in alloc_super() * assumes that percpu_free_rwsem() is safe after kzalloc(). */ - if (!brw->fast_read_ctr) + if (!sem->read_count) return; - rcu_sync_dtor(&brw->rss); - free_percpu(brw->fast_read_ctr); - brw->fast_read_ctr = NULL; /* catch use after free bugs */ + rcu_sync_dtor(&sem->rss); + free_percpu(sem->read_count); + sem->read_count = NULL; /* catch use after free bugs */ } EXPORT_SYMBOL_GPL(percpu_free_rwsem); -/* - * This is the fast-path for down_read/up_read. If it succeeds we rely - * on the barriers provided by rcu_sync_enter/exit; see the comments in - * percpu_down_write() and percpu_up_write(). - * - * If this helper fails the callers rely on the normal rw_semaphore and - * atomic_dec_and_test(), so in this case we have the necessary barriers. - */ -static bool update_fast_ctr(struct percpu_rw_semaphore *brw, unsigned int val) +int __percpu_down_read(struct percpu_rw_semaphore *sem, int try) { - bool success; + /* + * Due to having preemption disabled the decrement happens on + * the same CPU as the increment, avoiding the + * increment-on-one-CPU-and-decrement-on-another problem. + * + * If the reader misses the writer's assignment of readers_block, then + * the writer is guaranteed to see the reader's increment. + * + * Conversely, any readers that increment their sem->read_count after + * the writer looks are guaranteed to see the readers_block value, + * which in turn means that they are guaranteed to immediately + * decrement their sem->read_count, so that it doesn't matter that the + * writer missed them. + */ - preempt_disable(); - success = rcu_sync_is_idle(&brw->rss); - if (likely(success)) - __this_cpu_add(*brw->fast_read_ctr, val); - preempt_enable(); + smp_mb(); /* A matches D */ - return success; -} + /* + * If !readers_block the critical section starts here, matched by the + * release in percpu_up_write(). + */ + if (likely(!smp_load_acquire(&sem->readers_block))) + return 1; -/* - * Like the normal down_read() this is not recursive, the writer can - * come after the first percpu_down_read() and create the deadlock. - * - * Note: returns with lock_is_held(brw->rw_sem) == T for lockdep, - * percpu_up_read() does rwsem_release(). This pairs with the usage - * of ->rw_sem in percpu_down/up_write(). - */ -void percpu_down_read(struct percpu_rw_semaphore *brw) -{ - might_sleep(); - rwsem_acquire_read(&brw->rw_sem.dep_map, 0, 0, _RET_IP_); + /* + * Per the above comment; we still have preemption disabled and + * will thus decrement on the same CPU as we incremented. + */ + __percpu_up_read(sem); - if (likely(update_fast_ctr(brw, +1))) - return; + if (try) + return 0; - /* Avoid rwsem_acquire_read() and rwsem_release() */ - __down_read(&brw->rw_sem); - atomic_inc(&brw->slow_read_ctr); - __up_read(&brw->rw_sem); -} -EXPORT_SYMBOL_GPL(percpu_down_read); + /* + * We either call schedule() in the wait, or we'll fall through + * and reschedule on the preempt_enable() in percpu_down_read(). + */ + preempt_enable_no_resched(); -int percpu_down_read_trylock(struct percpu_rw_semaphore *brw) -{ - if (unlikely(!update_fast_ctr(brw, +1))) { - if (!__down_read_trylock(&brw->rw_sem)) - return 0; - atomic_inc(&brw->slow_read_ctr); - __up_read(&brw->rw_sem); - } - - rwsem_acquire_read(&brw->rw_sem.dep_map, 0, 1, _RET_IP_); + /* + * Avoid lockdep for the down/up_read() we already have them. + */ + __down_read(&sem->rw_sem); + this_cpu_inc(*sem->read_count); + __up_read(&sem->rw_sem); + + preempt_disable(); return 1; } +EXPORT_SYMBOL_GPL(__percpu_down_read); -void percpu_up_read(struct percpu_rw_semaphore *brw) +void __percpu_up_read(struct percpu_rw_semaphore *sem) { - rwsem_release(&brw->rw_sem.dep_map, 1, _RET_IP_); - - if (likely(update_fast_ctr(brw, -1))) - return; + smp_mb(); /* B matches C */ + /* + * In other words, if they see our decrement (presumably to aggregate + * zero, as that is the only time it matters) they will also see our + * critical section. + */ + __this_cpu_dec(*sem->read_count); - /* false-positive is possible but harmless */ - if (atomic_dec_and_test(&brw->slow_read_ctr)) - wake_up_all(&brw->write_waitq); + /* Prod writer to recheck readers_active */ + wake_up(&sem->writer); } -EXPORT_SYMBOL_GPL(percpu_up_read); +EXPORT_SYMBOL_GPL(__percpu_up_read); + +#define per_cpu_sum(var) \ +({ \ + typeof(var) __sum = 0; \ + int cpu; \ + compiletime_assert_atomic_type(__sum); \ + for_each_possible_cpu(cpu) \ + __sum += per_cpu(var, cpu); \ + __sum; \ +}) -static int clear_fast_ctr(struct percpu_rw_semaphore *brw) +/* + * Return true if the modular sum of the sem->read_count per-CPU variable is + * zero. If this sum is zero, then it is stable due to the fact that if any + * newly arriving readers increment a given counter, they will immediately + * decrement that same counter. + */ +static bool readers_active_check(struct percpu_rw_semaphore *sem) { - unsigned int sum = 0; - int cpu; + if (per_cpu_sum(*sem->read_count) != 0) + return false; + + /* + * If we observed the decrement; ensure we see the entire critical + * section. + */ - for_each_possible_cpu(cpu) { - sum += per_cpu(*brw->fast_read_ctr, cpu); - per_cpu(*brw->fast_read_ctr, cpu) = 0; - } + smp_mb(); /* C matches B */ - return sum; + return true; } -void percpu_down_write(struct percpu_rw_semaphore *brw) +void percpu_down_write(struct percpu_rw_semaphore *sem) { + /* Notify readers to take the slow path. */ + rcu_sync_enter(&sem->rss); + + down_write(&sem->rw_sem); + /* - * Make rcu_sync_is_idle() == F and thus disable the fast-path in - * percpu_down_read() and percpu_up_read(), and wait for gp pass. - * - * The latter synchronises us with the preceding readers which used - * the fast-past, so we can not miss the result of __this_cpu_add() - * or anything else inside their criticial sections. + * Notify new readers to block; up until now, and thus throughout the + * longish rcu_sync_enter() above, new readers could still come in. */ - rcu_sync_enter(&brw->rss); + WRITE_ONCE(sem->readers_block, 1); - /* exclude other writers, and block the new readers completely */ - down_write(&brw->rw_sem); + smp_mb(); /* D matches A */ - /* nobody can use fast_read_ctr, move its sum into slow_read_ctr */ - atomic_add(clear_fast_ctr(brw), &brw->slow_read_ctr); + /* + * If they don't see our writer of readers_block, then we are + * guaranteed to see their sem->read_count increment, and therefore + * will wait for them. + */ - /* wait for all readers to complete their percpu_up_read() */ - wait_event(brw->write_waitq, !atomic_read(&brw->slow_read_ctr)); + /* Wait for all now active readers to complete. */ + wait_event(sem->writer, readers_active_check(sem)); } EXPORT_SYMBOL_GPL(percpu_down_write); -void percpu_up_write(struct percpu_rw_semaphore *brw) +void percpu_up_write(struct percpu_rw_semaphore *sem) { - /* release the lock, but the readers can't use the fast-path */ - up_write(&brw->rw_sem); /* - * Enable the fast-path in percpu_down_read() and percpu_up_read() - * but only after another gp pass; this adds the necessary barrier - * to ensure the reader can't miss the changes done by us. + * Signal the writer is done, no fast path yet. + * + * One reason that we cannot just immediately flip to readers_fast is + * that new readers might fail to see the results of this writer's + * critical section. + * + * Therefore we force it through the slow path which guarantees an + * acquire and thereby guarantees the critical section's consistency. + */ + smp_store_release(&sem->readers_block, 0); + + /* + * Release the write lock, this will allow readers back in the game. + */ + up_write(&sem->rw_sem); + + /* + * Once this completes (at least one RCU-sched grace period hence) the + * reader fast path will be available again. Safe to use outside the + * exclusive write lock because its counting. */ - rcu_sync_exit(&brw->rss); + rcu_sync_exit(&sem->rss); } EXPORT_SYMBOL_GPL(percpu_up_write); diff --git a/kernel/rcu/sync.c b/kernel/rcu/sync.c index be922c9f3d37..198473d90f81 100644 --- a/kernel/rcu/sync.c +++ b/kernel/rcu/sync.c @@ -68,6 +68,8 @@ void rcu_sync_lockdep_assert(struct rcu_sync *rsp) RCU_LOCKDEP_WARN(!gp_ops[rsp->gp_type].held(), "suspicious rcu_sync_is_idle() usage"); } + +EXPORT_SYMBOL_GPL(rcu_sync_lockdep_assert); #endif /** -- cgit v1.2.3 From d1c6d149cf04d6c7c3c3ebf4b66c82500cbcf6e1 Mon Sep 17 00:00:00 2001 From: Vegard Nossum Date: Sat, 23 Jul 2016 09:46:39 +0200 Subject: sched/debug: Make the "Preemption disabled at ..." message more useful This message is currently really useless since it always prints a value that comes from the printk() we just did, e.g.: BUG: sleeping function called from invalid context at mm/slab.h:388 in_atomic(): 0, irqs_disabled(): 0, pid: 31996, name: trinity-c1 Preemption disabled at:[] down_trylock+0x13/0x80 BUG: sleeping function called from invalid context at include/linux/freezer.h:56 in_atomic(): 0, irqs_disabled(): 0, pid: 31996, name: trinity-c1 Preemption disabled at:[] console_unlock+0x2f7/0x930 Here, both down_trylock() and console_unlock() is somewhere in the printk() path. We should save the value before calling printk() and use the saved value instead. That immediately reveals the offending callsite: BUG: sleeping function called from invalid context at mm/slab.h:388 in_atomic(): 0, irqs_disabled(): 0, pid: 14971, name: trinity-c2 Preemption disabled at:[] rhashtable_walk_start+0x46/0x150 Bug report: http://marc.info/?l=linux-netdev&m=146925979821849&w=2 Signed-off-by: Vegard Nossum Cc: Andrew Morton Cc: Linus Torvalds Cc: Mike Galbraith Cc: Paul E. McKenney Cc: Peter Zijlstra Cc: Rusty Russel Cc: Thomas Gleixner Signed-off-by: Ingo Molnar --- include/linux/sched.h | 9 +++++++++ kernel/sched/core.c | 21 +++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) (limited to 'include/linux') diff --git a/include/linux/sched.h b/include/linux/sched.h index f3db596efd2c..7f64e89a5873 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -3236,6 +3236,15 @@ static inline void cond_resched_rcu(void) #endif } +static inline unsigned long get_preempt_disable_ip(struct task_struct *p) +{ +#ifdef CONFIG_DEBUG_PREEMPT + return p->preempt_disable_ip; +#else + return 0; +#endif +} + /* * Does a critical section need to be broken due to another * task waiting?: (technically does not depend on CONFIG_PREEMPT, diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 10f2595c408a..a65681605aef 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -3171,6 +3171,9 @@ static inline void preempt_latency_stop(int val) { } */ static noinline void __schedule_bug(struct task_struct *prev) { + /* Save this before calling printk(), since that will clobber it */ + unsigned long preempt_disable_ip = get_preempt_disable_ip(current); + if (oops_in_progress) return; @@ -3181,13 +3184,12 @@ static noinline void __schedule_bug(struct task_struct *prev) print_modules(); if (irqs_disabled()) print_irqtrace_events(prev); -#ifdef CONFIG_DEBUG_PREEMPT - if (in_atomic_preempt_off()) { + if (IS_ENABLED(CONFIG_DEBUG_PREEMPT) + && in_atomic_preempt_off()) { pr_err("Preemption disabled at:"); - print_ip_sym(current->preempt_disable_ip); + print_ip_sym(preempt_disable_ip); pr_cont("\n"); } -#endif if (panic_on_warn) panic("scheduling while atomic\n"); @@ -7571,6 +7573,7 @@ EXPORT_SYMBOL(__might_sleep); void ___might_sleep(const char *file, int line, int preempt_offset) { static unsigned long prev_jiffy; /* ratelimiting */ + unsigned long preempt_disable_ip; rcu_sleep_check(); /* WARN_ON_ONCE() by default, no rate limit reqd. */ if ((preempt_count_equals(preempt_offset) && !irqs_disabled() && @@ -7581,6 +7584,9 @@ void ___might_sleep(const char *file, int line, int preempt_offset) return; prev_jiffy = jiffies; + /* Save this before calling printk(), since that will clobber it */ + preempt_disable_ip = get_preempt_disable_ip(current); + printk(KERN_ERR "BUG: sleeping function called from invalid context at %s:%d\n", file, line); @@ -7595,13 +7601,12 @@ void ___might_sleep(const char *file, int line, int preempt_offset) debug_show_held_locks(current); if (irqs_disabled()) print_irqtrace_events(current); -#ifdef CONFIG_DEBUG_PREEMPT - if (!preempt_count_equals(preempt_offset)) { + if (IS_ENABLED(CONFIG_DEBUG_PREEMPT) + && !preempt_count_equals(preempt_offset)) { pr_err("Preemption disabled at:"); - print_ip_sym(current->preempt_disable_ip); + print_ip_sym(preempt_disable_ip); pr_cont("\n"); } -#endif dump_stack(); } EXPORT_SYMBOL(___might_sleep); -- cgit v1.2.3 From 0e0b2afdf644aa523f5eb10ce1f9e3c6cd8362ec Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 10 Aug 2016 11:23:43 -0400 Subject: kernfs: add dummy implementation of kernfs_path_from_node() The dummy version of kernfs_path_from_node() was missing. This currently doesn't break anything. Let's add it for consistency and to ease adding wrappers around it. v2: Removed stray ';' which was causing build failures. Signed-off-by: Tejun Heo Acked-by: Greg Kroah-Hartman --- include/linux/kernfs.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'include/linux') diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h index 96356ef012de..7d2efd2128bb 100644 --- a/include/linux/kernfs.h +++ b/include/linux/kernfs.h @@ -344,6 +344,11 @@ static inline int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen) static inline size_t kernfs_path_len(struct kernfs_node *kn) { return 0; } +static inline int kernfs_path_from_node(struct kernfs_node *root_kn, + struct kernfs_node *kn, + char *buf, size_t buflen) +{ return -ENOSYS; } + static inline char *kernfs_path(struct kernfs_node *kn, char *buf, size_t buflen) { return NULL; } -- cgit v1.2.3 From 3abb1d90f5d930c6183534a624aa0158a71bc5eb Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 10 Aug 2016 11:23:44 -0400 Subject: kernfs: make kernfs_path*() behave in the style of strlcpy() kernfs_path*() functions always return the length of the full path but the path content is undefined if the length is larger than the provided buffer. This makes its behavior different from strlcpy() and requires error handling in all its users even when they don't care about truncation. In addition, the implementation can actully be simplified by making it behave properly in strlcpy() style. * Update kernfs_path_from_node_locked() to always fill up the buffer with path. If the buffer is not large enough, the output is truncated and terminated. * kernfs_path() no longer needs error handling. Make it a simple inline wrapper around kernfs_path_from_node(). * sysfs_warn_dup()'s use of kernfs_path() doesn't need error handling. Updated accordingly. * cgroup_path()'s use of kernfs_path() updated to retain the old behavior. Signed-off-by: Tejun Heo Acked-by: Greg Kroah-Hartman Acked-by: Serge Hallyn --- fs/kernfs/dir.c | 61 ++++++++++++++------------------------------------ fs/sysfs/dir.c | 6 ++--- include/linux/cgroup.h | 7 +++++- include/linux/kernfs.h | 21 ++++++++++++----- 4 files changed, 42 insertions(+), 53 deletions(-) (limited to 'include/linux') diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index e57174d43683..09242aaa8829 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -110,8 +110,9 @@ static struct kernfs_node *kernfs_common_ancestor(struct kernfs_node *a, * kn_to: /n1/n2/n3 [depth=3] * result: /../.. * - * return value: length of the string. If greater than buflen, - * then contents of buf are undefined. On error, -1 is returned. + * Returns the length of the full path. If the full length is equal to or + * greater than @buflen, @buf contains the truncated path with the trailing + * '\0'. On error, -errno is returned. */ static int kernfs_path_from_node_locked(struct kernfs_node *kn_to, struct kernfs_node *kn_from, @@ -119,9 +120,8 @@ static int kernfs_path_from_node_locked(struct kernfs_node *kn_to, { struct kernfs_node *kn, *common; const char parent_str[] = "/.."; - size_t depth_from, depth_to, len = 0, nlen = 0; - char *p; - int i; + size_t depth_from, depth_to, len = 0; + int i, j; if (!kn_from) kn_from = kernfs_root(kn_to)->kn; @@ -131,7 +131,7 @@ static int kernfs_path_from_node_locked(struct kernfs_node *kn_to, common = kernfs_common_ancestor(kn_from, kn_to); if (WARN_ON(!common)) - return -1; + return -EINVAL; depth_to = kernfs_depth(common, kn_to); depth_from = kernfs_depth(common, kn_from); @@ -144,22 +144,16 @@ static int kernfs_path_from_node_locked(struct kernfs_node *kn_to, len < buflen ? buflen - len : 0); /* Calculate how many bytes we need for the rest */ - for (kn = kn_to; kn != common; kn = kn->parent) - nlen += strlen(kn->name) + 1; - - if (len + nlen >= buflen) - return len + nlen; - - p = buf + len + nlen; - *p = '\0'; - for (kn = kn_to; kn != common; kn = kn->parent) { - size_t tmp = strlen(kn->name); - p -= tmp; - memcpy(p, kn->name, tmp); - *(--p) = '/'; + for (i = depth_to - 1; i >= 0; i--) { + for (kn = kn_to, j = 0; j < i; j++) + kn = kn->parent; + len += strlcpy(buf + len, "/", + len < buflen ? buflen - len : 0); + len += strlcpy(buf + len, kn->name, + len < buflen ? buflen - len : 0); } - return len + nlen; + return len; } /** @@ -220,8 +214,9 @@ size_t kernfs_path_len(struct kernfs_node *kn) * path (which includes '..'s) as needed to reach from @from to @to is * returned. * - * If @buf isn't long enough, the return value will be greater than @buflen - * and @buf contents are undefined. + * Returns the length of the full path. If the full length is equal to or + * greater than @buflen, @buf contains the truncated path with the trailing + * '\0'. On error, -errno is returned. */ int kernfs_path_from_node(struct kernfs_node *to, struct kernfs_node *from, char *buf, size_t buflen) @@ -236,28 +231,6 @@ int kernfs_path_from_node(struct kernfs_node *to, struct kernfs_node *from, } EXPORT_SYMBOL_GPL(kernfs_path_from_node); -/** - * kernfs_path - build full path of a given node - * @kn: kernfs_node of interest - * @buf: buffer to copy @kn's name into - * @buflen: size of @buf - * - * Builds and returns the full path of @kn in @buf of @buflen bytes. The - * path is built from the end of @buf so the returned pointer usually - * doesn't match @buf. If @buf isn't long enough, @buf is nul terminated - * and %NULL is returned. - */ -char *kernfs_path(struct kernfs_node *kn, char *buf, size_t buflen) -{ - int ret; - - ret = kernfs_path_from_node(kn, NULL, buf, buflen); - if (ret < 0 || ret >= buflen) - return NULL; - return buf; -} -EXPORT_SYMBOL_GPL(kernfs_path); - /** * pr_cont_kernfs_name - pr_cont name of a kernfs_node * @kn: kernfs_node of interest diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 94374e435025..2b67bda2021b 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -21,14 +21,14 @@ DEFINE_SPINLOCK(sysfs_symlink_target_lock); void sysfs_warn_dup(struct kernfs_node *parent, const char *name) { - char *buf, *path = NULL; + char *buf; buf = kzalloc(PATH_MAX, GFP_KERNEL); if (buf) - path = kernfs_path(parent, buf, PATH_MAX); + kernfs_path(parent, buf, PATH_MAX); WARN(1, KERN_WARNING "sysfs: cannot create duplicate filename '%s/%s'\n", - path, name); + buf, name); kfree(buf); } diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h index 984f73b719a9..5a9abdee43fe 100644 --- a/include/linux/cgroup.h +++ b/include/linux/cgroup.h @@ -541,7 +541,12 @@ static inline int cgroup_name(struct cgroup *cgrp, char *buf, size_t buflen) static inline char * __must_check cgroup_path(struct cgroup *cgrp, char *buf, size_t buflen) { - return kernfs_path(cgrp->kn, buf, buflen); + int ret; + + ret = kernfs_path(cgrp->kn, buf, buflen); + if (ret < 0 || ret >= buflen) + return NULL; + return buf; } static inline void pr_cont_cgroup_name(struct cgroup *cgrp) diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h index 7d2efd2128bb..4a02b1b49821 100644 --- a/include/linux/kernfs.h +++ b/include/linux/kernfs.h @@ -272,7 +272,6 @@ int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen); size_t kernfs_path_len(struct kernfs_node *kn); int kernfs_path_from_node(struct kernfs_node *root_kn, struct kernfs_node *kn, char *buf, size_t buflen); -char *kernfs_path(struct kernfs_node *kn, char *buf, size_t buflen); void pr_cont_kernfs_name(struct kernfs_node *kn); void pr_cont_kernfs_path(struct kernfs_node *kn); struct kernfs_node *kernfs_get_parent(struct kernfs_node *kn); @@ -349,10 +348,6 @@ static inline int kernfs_path_from_node(struct kernfs_node *root_kn, char *buf, size_t buflen) { return -ENOSYS; } -static inline char *kernfs_path(struct kernfs_node *kn, char *buf, - size_t buflen) -{ return NULL; } - static inline void pr_cont_kernfs_name(struct kernfs_node *kn) { } static inline void pr_cont_kernfs_path(struct kernfs_node *kn) { } @@ -441,6 +436,22 @@ static inline void kernfs_init(void) { } #endif /* CONFIG_KERNFS */ +/** + * kernfs_path - build full path of a given node + * @kn: kernfs_node of interest + * @buf: buffer to copy @kn's name into + * @buflen: size of @buf + * + * Builds and returns the full path of @kn in @buf of @buflen bytes. The + * path is built from the end of @buf so the returned pointer usually + * doesn't match @buf. If @buf isn't long enough, @buf is nul terminated + * and %NULL is returned. + */ +static inline int kernfs_path(struct kernfs_node *kn, char *buf, size_t buflen) +{ + return kernfs_path_from_node(kn, NULL, buf, buflen); +} + static inline struct kernfs_node * kernfs_find_and_get(struct kernfs_node *kn, const char *name) { -- cgit v1.2.3 From bb09c8634b1e484b8840fb2384d55739bfcb68bd Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 10 Aug 2016 11:23:44 -0400 Subject: kernfs: remove kernfs_path_len() It doesn't have any in-kernel user and the same result can be obtained from kernfs_path(@kn, NULL, 0). Remove it. Signed-off-by: Tejun Heo Acked-by: Greg Kroah-Hartman Cc: Serge Hallyn --- fs/kernfs/dir.c | 23 ----------------------- include/linux/kernfs.h | 4 ---- 2 files changed, 27 deletions(-) (limited to 'include/linux') diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 09242aaa8829..6e7fd37615f8 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -179,29 +179,6 @@ int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen) return ret; } -/** - * kernfs_path_len - determine the length of the full path of a given node - * @kn: kernfs_node of interest - * - * The returned length doesn't include the space for the terminating '\0'. - */ -size_t kernfs_path_len(struct kernfs_node *kn) -{ - size_t len = 0; - unsigned long flags; - - spin_lock_irqsave(&kernfs_rename_lock, flags); - - do { - len += strlen(kn->name) + 1; - kn = kn->parent; - } while (kn && kn->parent); - - spin_unlock_irqrestore(&kernfs_rename_lock, flags); - - return len; -} - /** * kernfs_path_from_node - build path of node @to relative to @from. * @from: parent kernfs_node relative to which we need to build the path diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h index 4a02b1b49821..7056238fd9f5 100644 --- a/include/linux/kernfs.h +++ b/include/linux/kernfs.h @@ -269,7 +269,6 @@ static inline bool kernfs_ns_enabled(struct kernfs_node *kn) } int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen); -size_t kernfs_path_len(struct kernfs_node *kn); int kernfs_path_from_node(struct kernfs_node *root_kn, struct kernfs_node *kn, char *buf, size_t buflen); void pr_cont_kernfs_name(struct kernfs_node *kn); @@ -340,9 +339,6 @@ static inline bool kernfs_ns_enabled(struct kernfs_node *kn) static inline int kernfs_name(struct kernfs_node *kn, char *buf, size_t buflen) { return -ENOSYS; } -static inline size_t kernfs_path_len(struct kernfs_node *kn) -{ return 0; } - static inline int kernfs_path_from_node(struct kernfs_node *root_kn, struct kernfs_node *kn, char *buf, size_t buflen) -- cgit v1.2.3 From 4c737b41de7f4eef2a593803bad1b918dd718b10 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 10 Aug 2016 11:23:44 -0400 Subject: cgroup: make cgroup_path() and friends behave in the style of strlcpy() cgroup_path() and friends used to format the path from the end and thus the resulting path usually didn't start at the start of the passed in buffer. Also, when the buffer was too small, the partial result was truncated from the head rather than tail and there was no way to tell how long the full path would be. These make the functions less robust and more awkward to use. With recent updates to kernfs_path(), cgroup_path() and friends can be made to behave in strlcpy() style. * cgroup_path(), cgroup_path_ns[_locked]() and task_cgroup_path() now always return the length of the full path. If buffer is too small, it contains nul terminated truncated output. * All users updated accordingly. v2: cgroup_path() usage in kernel/sched/debug.c converted. Signed-off-by: Tejun Heo Acked-by: Greg Kroah-Hartman Cc: Serge Hallyn Cc: Peter Zijlstra --- include/linux/blk-cgroup.h | 11 +---------- include/linux/cgroup.h | 16 +++++----------- kernel/cgroup.c | 48 +++++++++++++++++++++------------------------- kernel/cpuset.c | 12 ++++++------ kernel/sched/debug.c | 3 ++- 5 files changed, 36 insertions(+), 54 deletions(-) (limited to 'include/linux') diff --git a/include/linux/blk-cgroup.h b/include/linux/blk-cgroup.h index 10648e300c93..4e8c215e185c 100644 --- a/include/linux/blk-cgroup.h +++ b/include/linux/blk-cgroup.h @@ -343,16 +343,7 @@ static inline struct blkcg *cpd_to_blkcg(struct blkcg_policy_data *cpd) */ static inline int blkg_path(struct blkcg_gq *blkg, char *buf, int buflen) { - char *p; - - p = cgroup_path(blkg->blkcg->css.cgroup, buf, buflen); - if (!p) { - strncpy(buf, "", buflen); - return -ENAMETOOLONG; - } - - memmove(buf, p, buf + buflen - p); - return 0; + return cgroup_path(blkg->blkcg->css.cgroup, buf, buflen); } /** diff --git a/include/linux/cgroup.h b/include/linux/cgroup.h index 5a9abdee43fe..6df36361a492 100644 --- a/include/linux/cgroup.h +++ b/include/linux/cgroup.h @@ -97,7 +97,7 @@ int cgroup_add_legacy_cftypes(struct cgroup_subsys *ss, struct cftype *cfts); int cgroup_rm_cftypes(struct cftype *cfts); void cgroup_file_notify(struct cgroup_file *cfile); -char *task_cgroup_path(struct task_struct *task, char *buf, size_t buflen); +int task_cgroup_path(struct task_struct *task, char *buf, size_t buflen); int cgroupstats_build(struct cgroupstats *stats, struct dentry *dentry); int proc_cgroup_show(struct seq_file *m, struct pid_namespace *ns, struct pid *pid, struct task_struct *tsk); @@ -538,15 +538,9 @@ static inline int cgroup_name(struct cgroup *cgrp, char *buf, size_t buflen) return kernfs_name(cgrp->kn, buf, buflen); } -static inline char * __must_check cgroup_path(struct cgroup *cgrp, char *buf, - size_t buflen) +static inline int cgroup_path(struct cgroup *cgrp, char *buf, size_t buflen) { - int ret; - - ret = kernfs_path(cgrp->kn, buf, buflen); - if (ret < 0 || ret >= buflen) - return NULL; - return buf; + return kernfs_path(cgrp->kn, buf, buflen); } static inline void pr_cont_cgroup_name(struct cgroup *cgrp) @@ -639,8 +633,8 @@ struct cgroup_namespace *copy_cgroup_ns(unsigned long flags, struct user_namespace *user_ns, struct cgroup_namespace *old_ns); -char *cgroup_path_ns(struct cgroup *cgrp, char *buf, size_t buflen, - struct cgroup_namespace *ns); +int cgroup_path_ns(struct cgroup *cgrp, char *buf, size_t buflen, + struct cgroup_namespace *ns); #else /* !CONFIG_CGROUPS */ diff --git a/kernel/cgroup.c b/kernel/cgroup.c index d1c51b7f5221..3861161e460f 100644 --- a/kernel/cgroup.c +++ b/kernel/cgroup.c @@ -2315,22 +2315,18 @@ static struct file_system_type cgroup2_fs_type = { .fs_flags = FS_USERNS_MOUNT, }; -static char *cgroup_path_ns_locked(struct cgroup *cgrp, char *buf, size_t buflen, - struct cgroup_namespace *ns) +static int cgroup_path_ns_locked(struct cgroup *cgrp, char *buf, size_t buflen, + struct cgroup_namespace *ns) { struct cgroup *root = cset_cgroup_from_root(ns->root_cset, cgrp->root); - int ret; - ret = kernfs_path_from_node(cgrp->kn, root->kn, buf, buflen); - if (ret < 0 || ret >= buflen) - return NULL; - return buf; + return kernfs_path_from_node(cgrp->kn, root->kn, buf, buflen); } -char *cgroup_path_ns(struct cgroup *cgrp, char *buf, size_t buflen, - struct cgroup_namespace *ns) +int cgroup_path_ns(struct cgroup *cgrp, char *buf, size_t buflen, + struct cgroup_namespace *ns) { - char *ret; + int ret; mutex_lock(&cgroup_mutex); spin_lock_irq(&css_set_lock); @@ -2357,12 +2353,12 @@ EXPORT_SYMBOL_GPL(cgroup_path_ns); * * Return value is the same as kernfs_path(). */ -char *task_cgroup_path(struct task_struct *task, char *buf, size_t buflen) +int task_cgroup_path(struct task_struct *task, char *buf, size_t buflen) { struct cgroup_root *root; struct cgroup *cgrp; int hierarchy_id = 1; - char *path = NULL; + int ret; mutex_lock(&cgroup_mutex); spin_lock_irq(&css_set_lock); @@ -2371,16 +2367,15 @@ char *task_cgroup_path(struct task_struct *task, char *buf, size_t buflen) if (root) { cgrp = task_cgroup_from_root(task, root); - path = cgroup_path_ns_locked(cgrp, buf, buflen, &init_cgroup_ns); + ret = cgroup_path_ns_locked(cgrp, buf, buflen, &init_cgroup_ns); } else { /* if no hierarchy exists, everyone is in "/" */ - if (strlcpy(buf, "/", buflen) < buflen) - path = buf; + ret = strlcpy(buf, "/", buflen); } spin_unlock_irq(&css_set_lock); mutex_unlock(&cgroup_mutex); - return path; + return ret; } EXPORT_SYMBOL_GPL(task_cgroup_path); @@ -5716,7 +5711,7 @@ core_initcall(cgroup_wq_init); int proc_cgroup_show(struct seq_file *m, struct pid_namespace *ns, struct pid *pid, struct task_struct *tsk) { - char *buf, *path; + char *buf; int retval; struct cgroup_root *root; @@ -5759,18 +5754,18 @@ int proc_cgroup_show(struct seq_file *m, struct pid_namespace *ns, * " (deleted)" is appended to the cgroup path. */ if (cgroup_on_dfl(cgrp) || !(tsk->flags & PF_EXITING)) { - path = cgroup_path_ns_locked(cgrp, buf, PATH_MAX, + retval = cgroup_path_ns_locked(cgrp, buf, PATH_MAX, current->nsproxy->cgroup_ns); - if (!path) { + if (retval >= PATH_MAX) { retval = -ENAMETOOLONG; goto out_unlock; } + + seq_puts(m, buf); } else { - path = "/"; + seq_puts(m, "/"); } - seq_puts(m, path); - if (cgroup_on_dfl(cgrp) && cgroup_is_dead(cgrp)) seq_puts(m, " (deleted)\n"); else @@ -6035,8 +6030,9 @@ static void cgroup_release_agent(struct work_struct *work) { struct cgroup *cgrp = container_of(work, struct cgroup, release_agent_work); - char *pathbuf = NULL, *agentbuf = NULL, *path; + char *pathbuf = NULL, *agentbuf = NULL; char *argv[3], *envp[3]; + int ret; mutex_lock(&cgroup_mutex); @@ -6046,13 +6042,13 @@ static void cgroup_release_agent(struct work_struct *work) goto out; spin_lock_irq(&css_set_lock); - path = cgroup_path_ns_locked(cgrp, pathbuf, PATH_MAX, &init_cgroup_ns); + ret = cgroup_path_ns_locked(cgrp, pathbuf, PATH_MAX, &init_cgroup_ns); spin_unlock_irq(&css_set_lock); - if (!path) + if (ret >= PATH_MAX) goto out; argv[0] = agentbuf; - argv[1] = path; + argv[1] = pathbuf; argv[2] = NULL; /* minimal command environment */ diff --git a/kernel/cpuset.c b/kernel/cpuset.c index c7fd2778ed50..793ae6fd96dc 100644 --- a/kernel/cpuset.c +++ b/kernel/cpuset.c @@ -2689,7 +2689,7 @@ void __cpuset_memory_pressure_bump(void) int proc_cpuset_show(struct seq_file *m, struct pid_namespace *ns, struct pid *pid, struct task_struct *tsk) { - char *buf, *p; + char *buf; struct cgroup_subsys_state *css; int retval; @@ -2700,18 +2700,18 @@ int proc_cpuset_show(struct seq_file *m, struct pid_namespace *ns, retval = -ENAMETOOLONG; css = task_get_css(tsk, cpuset_cgrp_id); - p = cgroup_path_ns(css->cgroup, buf, PATH_MAX, - current->nsproxy->cgroup_ns); + retval = cgroup_path_ns(css->cgroup, buf, PATH_MAX, + current->nsproxy->cgroup_ns); css_put(css); - if (!p) + if (retval >= PATH_MAX) goto out_free; - seq_puts(m, p); + seq_puts(m, buf); seq_putc(m, '\n'); retval = 0; out_free: kfree(buf); out: - return retval; + return 0; } #endif /* CONFIG_PROC_PID_CPUSET */ diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c index 2a0a9995256d..23cb609ba4eb 100644 --- a/kernel/sched/debug.c +++ b/kernel/sched/debug.c @@ -410,7 +410,8 @@ static char *task_group_path(struct task_group *tg) if (autogroup_path(tg, group_path, PATH_MAX)) return group_path; - return cgroup_path(tg->css.cgroup, group_path, PATH_MAX); + cgroup_path(tg->css.cgroup, group_path, PATH_MAX); + return group_path; } #endif -- cgit v1.2.3 From f11fa1796a4b4f8c6d4ced37e8824276ec57057d Mon Sep 17 00:00:00 2001 From: Tero Kristo Date: Wed, 10 Aug 2016 17:53:54 +0530 Subject: mfd: tps65218: add version check to the PMIC probe Version information will be needed to handle some error cases under the regulator driver, so store the information once during MFD probe. Signed-off-by: Tero Kristo Signed-off-by: Dave Gerlach Signed-off-by: Keerthy Acked-by: Lee Jones Signed-off-by: Mark Brown --- drivers/mfd/tps65218.c | 9 +++++++++ include/linux/mfd/tps65218.h | 1 + 2 files changed, 10 insertions(+) (limited to 'include/linux') diff --git a/drivers/mfd/tps65218.c b/drivers/mfd/tps65218.c index 80b9dc363cd8..ba610adbdbff 100644 --- a/drivers/mfd/tps65218.c +++ b/drivers/mfd/tps65218.c @@ -219,6 +219,7 @@ static int tps65218_probe(struct i2c_client *client, struct tps65218 *tps; const struct of_device_id *match; int ret; + unsigned int chipid; match = of_match_device(of_tps65218_match_table, &client->dev); if (!match) { @@ -250,6 +251,14 @@ static int tps65218_probe(struct i2c_client *client, if (ret < 0) return ret; + ret = tps65218_reg_read(tps, TPS65218_REG_CHIPID, &chipid); + if (ret) { + dev_err(tps->dev, "Failed to read chipid: %d\n", ret); + return ret; + } + + tps->rev = chipid & TPS65218_CHIPID_REV_MASK; + ret = of_platform_populate(client->dev.of_node, NULL, NULL, &client->dev); if (ret < 0) diff --git a/include/linux/mfd/tps65218.h b/include/linux/mfd/tps65218.h index 7fdf5326f34e..85e464e32c43 100644 --- a/include/linux/mfd/tps65218.h +++ b/include/linux/mfd/tps65218.h @@ -267,6 +267,7 @@ struct tps_info { struct tps65218 { struct device *dev; unsigned int id; + u8 rev; struct mutex tps_lock; /* lock guarding the data structure */ /* IRQ Data */ -- cgit v1.2.3 From 23a34f9d03a5d40a6234855bc069da370708cc9e Mon Sep 17 00:00:00 2001 From: Tero Kristo Date: Wed, 10 Aug 2016 17:53:55 +0530 Subject: regulator: tps65218: do not disable DCDC3 during poweroff on broken PMICs Some versions of tps65218 do not seem to support poweroff modes properly if DCDC3 regulator is shut-down. Thus, keep it enabled even during poweroff if the version info matches the broken silicon revision. Signed-off-by: Tero Kristo Signed-off-by: Dave Gerlach Signed-off-by: Keerthy Acked-by: Lee Jones Signed-off-by: Mark Brown --- drivers/regulator/tps65218-regulator.c | 8 ++++++++ include/linux/mfd/tps65218.h | 5 +++++ 2 files changed, 13 insertions(+) (limited to 'include/linux') diff --git a/drivers/regulator/tps65218-regulator.c b/drivers/regulator/tps65218-regulator.c index d1e631d64a20..eb0f5b13841a 100644 --- a/drivers/regulator/tps65218-regulator.c +++ b/drivers/regulator/tps65218-regulator.c @@ -180,6 +180,14 @@ static int tps65218_pmic_set_suspend_disable(struct regulator_dev *dev) if (rid < TPS65218_DCDC_1 || rid > TPS65218_LDO_1) return -EINVAL; + /* + * Certain revisions of TPS65218 will need to have DCDC3 regulator + * enabled always, otherwise an immediate system reboot will occur + * during poweroff. + */ + if (rid == TPS65218_DCDC_3 && tps->rev == TPS65218_REV_2_1) + return 0; + if (!tps->info[rid]->strobe) { if (rid == TPS65218_DCDC_3) tps->info[rid]->strobe = 3; diff --git a/include/linux/mfd/tps65218.h b/include/linux/mfd/tps65218.h index 85e464e32c43..d1db9527fab5 100644 --- a/include/linux/mfd/tps65218.h +++ b/include/linux/mfd/tps65218.h @@ -63,6 +63,11 @@ #define TPS65218_CHIPID_CHIP_MASK 0xF8 #define TPS65218_CHIPID_REV_MASK 0x07 +#define TPS65218_REV_1_0 0x0 +#define TPS65218_REV_1_1 0x1 +#define TPS65218_REV_2_0 0x2 +#define TPS65218_REV_2_1 0x3 + #define TPS65218_INT1_VPRG BIT(5) #define TPS65218_INT1_AC BIT(4) #define TPS65218_INT1_PB BIT(3) -- cgit v1.2.3 From 59cc1f61f09c26ce82c308e24b76141e1efe99f8 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Wed, 10 Aug 2016 11:05:15 +0200 Subject: net: sched: convert qdisc linked list to hashtable Convert the per-device linked list into a hashtable. The primary motivation for this change is that currently, we're not tracking all the qdiscs in hierarchy (e.g. excluding default qdiscs), as the lookup performed over the linked list by qdisc_match_from_root() is rather expensive. The ultimate goal is to get rid of hidden qdiscs completely, which will bring much more determinism in user experience. Reviewed-by: Cong Wang Signed-off-by: Jiri Kosina Signed-off-by: David S. Miller --- include/linux/netdevice.h | 4 ++++ include/net/pkt_sched.h | 4 ++-- include/net/sch_generic.h | 2 +- net/core/dev.c | 3 +++ net/sched/sch_api.c | 23 +++++++++++++---------- net/sched/sch_generic.c | 8 +++++--- net/sched/sch_mq.c | 2 +- net/sched/sch_mqprio.c | 2 +- 8 files changed, 30 insertions(+), 18 deletions(-) (limited to 'include/linux') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 076df5360ba5..96e0b6cd964e 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -52,6 +52,7 @@ #include #include #include +#include struct netpoll_info; struct device; @@ -1800,6 +1801,9 @@ struct net_device { unsigned int num_tx_queues; unsigned int real_num_tx_queues; struct Qdisc *qdisc; +#ifdef CONFIG_NET_SCHED + DECLARE_HASHTABLE (qdisc_hash, 4); +#endif unsigned long tx_queue_len; spinlock_t tx_global_lock; int watchdog_timeo; diff --git a/include/net/pkt_sched.h b/include/net/pkt_sched.h index 7caa99b482c6..cd334c9584e9 100644 --- a/include/net/pkt_sched.h +++ b/include/net/pkt_sched.h @@ -90,8 +90,8 @@ int unregister_qdisc(struct Qdisc_ops *qops); void qdisc_get_default(char *id, size_t len); int qdisc_set_default(const char *id); -void qdisc_list_add(struct Qdisc *q); -void qdisc_list_del(struct Qdisc *q); +void qdisc_hash_add(struct Qdisc *q); +void qdisc_hash_del(struct Qdisc *q); struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle); struct Qdisc *qdisc_lookup_class(struct net_device *dev, u32 handle); struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index 909aff2db2b3..0d501779cc68 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -61,7 +61,7 @@ struct Qdisc { u32 limit; const struct Qdisc_ops *ops; struct qdisc_size_table __rcu *stab; - struct list_head list; + struct hlist_node hash; u32 handle; u32 parent; void *u32_node; diff --git a/net/core/dev.c b/net/core/dev.c index 4ce07dc25573..936ea0054f57 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -7629,6 +7629,9 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, INIT_LIST_HEAD(&dev->all_adj_list.lower); INIT_LIST_HEAD(&dev->ptype_all); INIT_LIST_HEAD(&dev->ptype_specific); +#ifdef CONFIG_NET_SCHED + hash_init(dev->qdisc_hash); +#endif dev->priv_flags = IFF_XMIT_DST_RELEASE | IFF_XMIT_DST_RELEASE_PERM; setup(dev); diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 12ebde845523..25aada7b095c 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -263,33 +264,33 @@ static struct Qdisc *qdisc_match_from_root(struct Qdisc *root, u32 handle) root->handle == handle) return root; - list_for_each_entry_rcu(q, &root->list, list) { + hash_for_each_possible_rcu(qdisc_dev(root)->qdisc_hash, q, hash, handle) { if (q->handle == handle) return q; } return NULL; } -void qdisc_list_add(struct Qdisc *q) +void qdisc_hash_add(struct Qdisc *q) { if ((q->parent != TC_H_ROOT) && !(q->flags & TCQ_F_INGRESS)) { struct Qdisc *root = qdisc_dev(q)->qdisc; WARN_ON_ONCE(root == &noop_qdisc); ASSERT_RTNL(); - list_add_tail_rcu(&q->list, &root->list); + hash_add_rcu(qdisc_dev(q)->qdisc_hash, &q->hash, q->handle); } } -EXPORT_SYMBOL(qdisc_list_add); +EXPORT_SYMBOL(qdisc_hash_add); -void qdisc_list_del(struct Qdisc *q) +void qdisc_hash_del(struct Qdisc *q) { if ((q->parent != TC_H_ROOT) && !(q->flags & TCQ_F_INGRESS)) { ASSERT_RTNL(); - list_del_rcu(&q->list); + hash_del_rcu(&q->hash); } } -EXPORT_SYMBOL(qdisc_list_del); +EXPORT_SYMBOL(qdisc_hash_del); struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle) { @@ -998,7 +999,7 @@ qdisc_create(struct net_device *dev, struct netdev_queue *dev_queue, goto err_out4; } - qdisc_list_add(sch); + qdisc_hash_add(sch); return sch; } @@ -1435,6 +1436,7 @@ static int tc_dump_qdisc_root(struct Qdisc *root, struct sk_buff *skb, { int ret = 0, q_idx = *q_idx_p; struct Qdisc *q; + int b; if (!root) return 0; @@ -1449,7 +1451,7 @@ static int tc_dump_qdisc_root(struct Qdisc *root, struct sk_buff *skb, goto done; q_idx++; } - list_for_each_entry(q, &root->list, list) { + hash_for_each(qdisc_dev(root)->qdisc_hash, b, q, hash) { if (q_idx < s_q_idx) { q_idx++; continue; @@ -1765,6 +1767,7 @@ static int tc_dump_tclass_root(struct Qdisc *root, struct sk_buff *skb, int *t_p, int s_t) { struct Qdisc *q; + int b; if (!root) return 0; @@ -1772,7 +1775,7 @@ static int tc_dump_tclass_root(struct Qdisc *root, struct sk_buff *skb, if (tc_dump_tclass_qdisc(root, skb, tcm, cb, t_p, s_t) < 0) return -1; - list_for_each_entry(q, &root->list, list) { + hash_for_each(qdisc_dev(root)->qdisc_hash, b, q, hash) { if (tc_dump_tclass_qdisc(q, skb, tcm, cb, t_p, s_t) < 0) return -1; } diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index e95b67cd5718..18faecc3f13e 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -423,7 +423,6 @@ struct Qdisc noop_qdisc = { .dequeue = noop_dequeue, .flags = TCQ_F_BUILTIN, .ops = &noop_qdisc_ops, - .list = LIST_HEAD_INIT(noop_qdisc.list), .q.lock = __SPIN_LOCK_UNLOCKED(noop_qdisc.q.lock), .dev_queue = &noop_netdev_queue, .running = SEQCNT_ZERO(noop_qdisc.running), @@ -613,7 +612,6 @@ struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue, sch = (struct Qdisc *) QDISC_ALIGN((unsigned long) p); sch->padded = (char *) sch - (char *) p; } - INIT_LIST_HEAD(&sch->list); skb_queue_head_init(&sch->q); spin_lock_init(&sch->busylock); @@ -700,7 +698,7 @@ void qdisc_destroy(struct Qdisc *qdisc) return; #ifdef CONFIG_NET_SCHED - qdisc_list_del(qdisc); + qdisc_hash_del(qdisc); qdisc_put_stab(rtnl_dereference(qdisc->stab)); #endif @@ -788,6 +786,10 @@ static void attach_default_qdiscs(struct net_device *dev) qdisc->ops->attach(qdisc); } } +#ifdef CONFIG_NET_SCHED + if (dev->qdisc) + qdisc_hash_add(dev->qdisc); +#endif } static void transition_one_qdisc(struct net_device *dev, diff --git a/net/sched/sch_mq.c b/net/sched/sch_mq.c index b9439827c172..2bc8d7f8df16 100644 --- a/net/sched/sch_mq.c +++ b/net/sched/sch_mq.c @@ -88,7 +88,7 @@ static void mq_attach(struct Qdisc *sch) qdisc_destroy(old); #ifdef CONFIG_NET_SCHED if (ntx < dev->real_num_tx_queues) - qdisc_list_add(qdisc); + qdisc_hash_add(qdisc); #endif } diff --git a/net/sched/sch_mqprio.c b/net/sched/sch_mqprio.c index 549c66359924..b5c502c78143 100644 --- a/net/sched/sch_mqprio.c +++ b/net/sched/sch_mqprio.c @@ -182,7 +182,7 @@ static void mqprio_attach(struct Qdisc *sch) if (old) qdisc_destroy(old); if (ntx < dev->real_num_tx_queues) - qdisc_list_add(qdisc); + qdisc_hash_add(qdisc); } kfree(priv->qdiscs); priv->qdiscs = NULL; -- cgit v1.2.3 From 054c67d1c82afde13e475cdd8b7117a5e40bebb1 Mon Sep 17 00:00:00 2001 From: Sudarsana Reddy Kalluru Date: Tue, 9 Aug 2016 03:51:23 -0400 Subject: qed*: Add support for ethtool link_ksettings callbacks. This patch adds the driver implementation for ethtool link_ksettings callbacks. qed driver now defines/uses the qed specific masks for representing link capability values. qede driver maps these values to to new link modes defined by the kernel implementation of link_ksettings. Please consider applying this to 'net-next' branch. Signed-off-by: Sudarsana Reddy Kalluru Signed-off-by: Yuval Mintz Signed-off-by: David S. Miller --- drivers/net/ethernet/qlogic/qed/qed_main.c | 107 ++++++++++--------- drivers/net/ethernet/qlogic/qed/qed_mcp.c | 3 + drivers/net/ethernet/qlogic/qed/qed_mcp.h | 7 +- drivers/net/ethernet/qlogic/qede/qede_ethtool.c | 132 ++++++++++++++++++------ include/linux/qed/qed_if.h | 15 +++ 5 files changed, 180 insertions(+), 84 deletions(-) (limited to 'include/linux') diff --git a/drivers/net/ethernet/qlogic/qed/qed_main.c b/drivers/net/ethernet/qlogic/qed/qed_main.c index c7dc34bfdd0a..d6e1dc5fac94 100644 --- a/drivers/net/ethernet/qlogic/qed/qed_main.c +++ b/drivers/net/ethernet/qlogic/qed/qed_main.c @@ -1025,20 +1025,23 @@ static int qed_set_link(struct qed_dev *cdev, struct qed_link_params *params) link_params->speed.autoneg = params->autoneg; if (params->override_flags & QED_LINK_OVERRIDE_SPEED_ADV_SPEEDS) { link_params->speed.advertised_speeds = 0; - if ((params->adv_speeds & SUPPORTED_1000baseT_Half) || - (params->adv_speeds & SUPPORTED_1000baseT_Full)) + if ((params->adv_speeds & QED_LM_1000baseT_Half_BIT) || + (params->adv_speeds & QED_LM_1000baseT_Full_BIT)) link_params->speed.advertised_speeds |= - NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_1G; - if (params->adv_speeds & SUPPORTED_10000baseKR_Full) + NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_1G; + if (params->adv_speeds & QED_LM_10000baseKR_Full_BIT) link_params->speed.advertised_speeds |= - NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_10G; - if (params->adv_speeds & SUPPORTED_40000baseLR4_Full) + NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_10G; + if (params->adv_speeds & QED_LM_25000baseKR_Full_BIT) link_params->speed.advertised_speeds |= - NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_40G; - if (params->adv_speeds & 0) + NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_25G; + if (params->adv_speeds & QED_LM_40000baseLR4_Full_BIT) link_params->speed.advertised_speeds |= - NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_50G; - if (params->adv_speeds & 0) + NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_40G; + if (params->adv_speeds & QED_LM_50000baseKR2_Full_BIT) + link_params->speed.advertised_speeds |= + NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_50G; + if (params->adv_speeds & QED_LM_100000baseKR4_Full_BIT) link_params->speed.advertised_speeds |= NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_BB_100G; } @@ -1168,50 +1171,56 @@ static void qed_fill_link(struct qed_hwfn *hwfn, if_link->link_up = true; /* TODO - at the moment assume supported and advertised speed equal */ - if_link->supported_caps = SUPPORTED_FIBRE; + if_link->supported_caps = QED_LM_FIBRE_BIT; if (params.speed.autoneg) - if_link->supported_caps |= SUPPORTED_Autoneg; + if_link->supported_caps |= QED_LM_Autoneg_BIT; if (params.pause.autoneg || (params.pause.forced_rx && params.pause.forced_tx)) - if_link->supported_caps |= SUPPORTED_Asym_Pause; + if_link->supported_caps |= QED_LM_Asym_Pause_BIT; if (params.pause.autoneg || params.pause.forced_rx || params.pause.forced_tx) - if_link->supported_caps |= SUPPORTED_Pause; + if_link->supported_caps |= QED_LM_Pause_BIT; if_link->advertised_caps = if_link->supported_caps; if (params.speed.advertised_speeds & NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_1G) - if_link->advertised_caps |= SUPPORTED_1000baseT_Half | - SUPPORTED_1000baseT_Full; + if_link->advertised_caps |= QED_LM_1000baseT_Half_BIT | + QED_LM_1000baseT_Full_BIT; if (params.speed.advertised_speeds & NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_10G) - if_link->advertised_caps |= SUPPORTED_10000baseKR_Full; + if_link->advertised_caps |= QED_LM_10000baseKR_Full_BIT; + if (params.speed.advertised_speeds & + NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_25G) + if_link->advertised_caps |= QED_LM_25000baseKR_Full_BIT; if (params.speed.advertised_speeds & - NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_40G) - if_link->advertised_caps |= SUPPORTED_40000baseLR4_Full; + NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_40G) + if_link->advertised_caps |= QED_LM_40000baseLR4_Full_BIT; if (params.speed.advertised_speeds & - NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_50G) - if_link->advertised_caps |= 0; + NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_50G) + if_link->advertised_caps |= QED_LM_50000baseKR2_Full_BIT; if (params.speed.advertised_speeds & NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_BB_100G) - if_link->advertised_caps |= 0; + if_link->advertised_caps |= QED_LM_100000baseKR4_Full_BIT; if (link_caps.speed_capabilities & NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_1G) - if_link->supported_caps |= SUPPORTED_1000baseT_Half | - SUPPORTED_1000baseT_Full; + if_link->supported_caps |= QED_LM_1000baseT_Half_BIT | + QED_LM_1000baseT_Full_BIT; if (link_caps.speed_capabilities & NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_10G) - if_link->supported_caps |= SUPPORTED_10000baseKR_Full; + if_link->supported_caps |= QED_LM_10000baseKR_Full_BIT; + if (link_caps.speed_capabilities & + NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_25G) + if_link->supported_caps |= QED_LM_25000baseKR_Full_BIT; if (link_caps.speed_capabilities & - NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_40G) - if_link->supported_caps |= SUPPORTED_40000baseLR4_Full; + NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_40G) + if_link->supported_caps |= QED_LM_40000baseLR4_Full_BIT; if (link_caps.speed_capabilities & - NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_50G) - if_link->supported_caps |= 0; + NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_50G) + if_link->supported_caps |= QED_LM_50000baseKR2_Full_BIT; if (link_caps.speed_capabilities & NVM_CFG1_PORT_DRV_SPEED_CAPABILITY_MASK_BB_100G) - if_link->supported_caps |= 0; + if_link->supported_caps |= QED_LM_100000baseKR4_Full_BIT; if (link.link_up) if_link->speed = link.speed; @@ -1231,33 +1240,29 @@ static void qed_fill_link(struct qed_hwfn *hwfn, if_link->pause_config |= QED_LINK_PAUSE_TX_ENABLE; /* Link partner capabilities */ - if (link.partner_adv_speed & - QED_LINK_PARTNER_SPEED_1G_HD) - if_link->lp_caps |= SUPPORTED_1000baseT_Half; - if (link.partner_adv_speed & - QED_LINK_PARTNER_SPEED_1G_FD) - if_link->lp_caps |= SUPPORTED_1000baseT_Full; - if (link.partner_adv_speed & - QED_LINK_PARTNER_SPEED_10G) - if_link->lp_caps |= SUPPORTED_10000baseKR_Full; - if (link.partner_adv_speed & - QED_LINK_PARTNER_SPEED_40G) - if_link->lp_caps |= SUPPORTED_40000baseLR4_Full; - if (link.partner_adv_speed & - QED_LINK_PARTNER_SPEED_50G) - if_link->lp_caps |= 0; - if (link.partner_adv_speed & - QED_LINK_PARTNER_SPEED_100G) - if_link->lp_caps |= 0; + if (link.partner_adv_speed & QED_LINK_PARTNER_SPEED_1G_HD) + if_link->lp_caps |= QED_LM_1000baseT_Half_BIT; + if (link.partner_adv_speed & QED_LINK_PARTNER_SPEED_1G_FD) + if_link->lp_caps |= QED_LM_1000baseT_Full_BIT; + if (link.partner_adv_speed & QED_LINK_PARTNER_SPEED_10G) + if_link->lp_caps |= QED_LM_10000baseKR_Full_BIT; + if (link.partner_adv_speed & QED_LINK_PARTNER_SPEED_25G) + if_link->lp_caps |= QED_LM_25000baseKR_Full_BIT; + if (link.partner_adv_speed & QED_LINK_PARTNER_SPEED_40G) + if_link->lp_caps |= QED_LM_40000baseLR4_Full_BIT; + if (link.partner_adv_speed & QED_LINK_PARTNER_SPEED_50G) + if_link->lp_caps |= QED_LM_50000baseKR2_Full_BIT; + if (link.partner_adv_speed & QED_LINK_PARTNER_SPEED_100G) + if_link->lp_caps |= QED_LM_100000baseKR4_Full_BIT; if (link.an_complete) - if_link->lp_caps |= SUPPORTED_Autoneg; + if_link->lp_caps |= QED_LM_Autoneg_BIT; if (link.partner_adv_pause) - if_link->lp_caps |= SUPPORTED_Pause; + if_link->lp_caps |= QED_LM_Pause_BIT; if (link.partner_adv_pause == QED_LINK_PARTNER_ASYMMETRIC_PAUSE || link.partner_adv_pause == QED_LINK_PARTNER_BOTH_PAUSE) - if_link->lp_caps |= SUPPORTED_Asym_Pause; + if_link->lp_caps |= QED_LM_Asym_Pause_BIT; } static void qed_get_current_link(struct qed_dev *cdev, diff --git a/drivers/net/ethernet/qlogic/qed/qed_mcp.c b/drivers/net/ethernet/qlogic/qed/qed_mcp.c index a240f26344a4..ce4b08a5fe99 100644 --- a/drivers/net/ethernet/qlogic/qed/qed_mcp.c +++ b/drivers/net/ethernet/qlogic/qed/qed_mcp.c @@ -634,6 +634,9 @@ static void qed_mcp_handle_link_change(struct qed_hwfn *p_hwfn, p_link->partner_adv_speed |= (status & LINK_STATUS_LINK_PARTNER_20G_CAPABLE) ? QED_LINK_PARTNER_SPEED_20G : 0; + p_link->partner_adv_speed |= + (status & LINK_STATUS_LINK_PARTNER_25G_CAPABLE) ? + QED_LINK_PARTNER_SPEED_25G : 0; p_link->partner_adv_speed |= (status & LINK_STATUS_LINK_PARTNER_40G_CAPABLE) ? QED_LINK_PARTNER_SPEED_40G : 0; diff --git a/drivers/net/ethernet/qlogic/qed/qed_mcp.h b/drivers/net/ethernet/qlogic/qed/qed_mcp.h index 7f319aa1b229..013d1b92ed63 100644 --- a/drivers/net/ethernet/qlogic/qed/qed_mcp.h +++ b/drivers/net/ethernet/qlogic/qed/qed_mcp.h @@ -60,9 +60,10 @@ struct qed_mcp_link_state { #define QED_LINK_PARTNER_SPEED_1G_FD BIT(1) #define QED_LINK_PARTNER_SPEED_10G BIT(2) #define QED_LINK_PARTNER_SPEED_20G BIT(3) -#define QED_LINK_PARTNER_SPEED_40G BIT(4) -#define QED_LINK_PARTNER_SPEED_50G BIT(5) -#define QED_LINK_PARTNER_SPEED_100G BIT(6) +#define QED_LINK_PARTNER_SPEED_25G BIT(4) +#define QED_LINK_PARTNER_SPEED_40G BIT(5) +#define QED_LINK_PARTNER_SPEED_50G BIT(6) +#define QED_LINK_PARTNER_SPEED_100G BIT(7) u32 partner_adv_speed; bool partner_tx_flow_ctrl_en; diff --git a/drivers/net/ethernet/qlogic/qede/qede_ethtool.c b/drivers/net/ethernet/qlogic/qede/qede_ethtool.c index f8492cac9290..0e0acfb5c1ed 100644 --- a/drivers/net/ethernet/qlogic/qede/qede_ethtool.c +++ b/drivers/net/ethernet/qlogic/qede/qede_ethtool.c @@ -249,78 +249,150 @@ static u32 qede_get_priv_flags(struct net_device *dev) return (!!(edev->dev_info.common.num_hwfns > 1)) << QEDE_PRI_FLAG_CMT; } -static int qede_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +struct qede_link_mode_mapping { + u32 qed_link_mode; + u32 ethtool_link_mode; +}; + +static const struct qede_link_mode_mapping qed_lm_map[] = { + {QED_LM_FIBRE_BIT, ETHTOOL_LINK_MODE_FIBRE_BIT}, + {QED_LM_Autoneg_BIT, ETHTOOL_LINK_MODE_Autoneg_BIT}, + {QED_LM_Asym_Pause_BIT, ETHTOOL_LINK_MODE_Asym_Pause_BIT}, + {QED_LM_Pause_BIT, ETHTOOL_LINK_MODE_Pause_BIT}, + {QED_LM_1000baseT_Half_BIT, ETHTOOL_LINK_MODE_1000baseT_Half_BIT}, + {QED_LM_1000baseT_Full_BIT, ETHTOOL_LINK_MODE_1000baseT_Full_BIT}, + {QED_LM_10000baseKR_Full_BIT, ETHTOOL_LINK_MODE_10000baseKR_Full_BIT}, + {QED_LM_25000baseKR_Full_BIT, ETHTOOL_LINK_MODE_25000baseKR_Full_BIT}, + {QED_LM_40000baseLR4_Full_BIT, ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT}, + {QED_LM_50000baseKR2_Full_BIT, ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT}, + {QED_LM_100000baseKR4_Full_BIT, + ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT}, +}; + +#define QEDE_DRV_TO_ETHTOOL_CAPS(caps, lk_ksettings, name) \ +{ \ + int i; \ + \ + for (i = 0; i < QED_LM_COUNT; i++) { \ + if ((caps) & (qed_lm_map[i].qed_link_mode)) \ + __set_bit(qed_lm_map[i].ethtool_link_mode,\ + lk_ksettings->link_modes.name); \ + } \ +} + +#define QEDE_ETHTOOL_TO_DRV_CAPS(caps, lk_ksettings, name) \ +{ \ + int i; \ + \ + for (i = 0; i < QED_LM_COUNT; i++) { \ + if (test_bit(qed_lm_map[i].ethtool_link_mode, \ + lk_ksettings->link_modes.name)) \ + caps |= qed_lm_map[i].qed_link_mode; \ + } \ +} + +static int qede_get_link_ksettings(struct net_device *dev, + struct ethtool_link_ksettings *cmd) { + struct ethtool_link_settings *base = &cmd->base; struct qede_dev *edev = netdev_priv(dev); struct qed_link_output current_link; memset(¤t_link, 0, sizeof(current_link)); edev->ops->common->get_link(edev->cdev, ¤t_link); - cmd->supported = current_link.supported_caps; - cmd->advertising = current_link.advertised_caps; + ethtool_link_ksettings_zero_link_mode(cmd, supported); + QEDE_DRV_TO_ETHTOOL_CAPS(current_link.supported_caps, cmd, supported) + + ethtool_link_ksettings_zero_link_mode(cmd, advertising); + QEDE_DRV_TO_ETHTOOL_CAPS(current_link.advertised_caps, cmd, advertising) + + ethtool_link_ksettings_zero_link_mode(cmd, lp_advertising); + QEDE_DRV_TO_ETHTOOL_CAPS(current_link.lp_caps, cmd, lp_advertising) + if ((edev->state == QEDE_STATE_OPEN) && (current_link.link_up)) { - ethtool_cmd_speed_set(cmd, current_link.speed); - cmd->duplex = current_link.duplex; + base->speed = current_link.speed; + base->duplex = current_link.duplex; } else { - cmd->duplex = DUPLEX_UNKNOWN; - ethtool_cmd_speed_set(cmd, SPEED_UNKNOWN); + base->speed = SPEED_UNKNOWN; + base->duplex = DUPLEX_UNKNOWN; } - cmd->port = current_link.port; - cmd->autoneg = (current_link.autoneg) ? AUTONEG_ENABLE : - AUTONEG_DISABLE; - cmd->lp_advertising = current_link.lp_caps; + base->port = current_link.port; + base->autoneg = (current_link.autoneg) ? AUTONEG_ENABLE : + AUTONEG_DISABLE; return 0; } -static int qede_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) +static int qede_set_link_ksettings(struct net_device *dev, + const struct ethtool_link_ksettings *cmd) { + const struct ethtool_link_settings *base = &cmd->base; struct qede_dev *edev = netdev_priv(dev); struct qed_link_output current_link; struct qed_link_params params; - u32 speed; if (!edev->ops || !edev->ops->common->can_link_change(edev->cdev)) { - DP_INFO(edev, - "Link settings are not allowed to be changed\n"); + DP_INFO(edev, "Link settings are not allowed to be changed\n"); return -EOPNOTSUPP; } - memset(¤t_link, 0, sizeof(current_link)); memset(¶ms, 0, sizeof(params)); edev->ops->common->get_link(edev->cdev, ¤t_link); - speed = ethtool_cmd_speed(cmd); params.override_flags |= QED_LINK_OVERRIDE_SPEED_ADV_SPEEDS; params.override_flags |= QED_LINK_OVERRIDE_SPEED_AUTONEG; - if (cmd->autoneg == AUTONEG_ENABLE) { + if (base->autoneg == AUTONEG_ENABLE) { params.autoneg = true; params.forced_speed = 0; - params.adv_speeds = cmd->advertising; - } else { /* forced speed */ + QEDE_ETHTOOL_TO_DRV_CAPS(params.adv_speeds, cmd, advertising) + } else { /* forced speed */ params.override_flags |= QED_LINK_OVERRIDE_SPEED_FORCED_SPEED; params.autoneg = false; - params.forced_speed = speed; - switch (speed) { + params.forced_speed = base->speed; + switch (base->speed) { case SPEED_10000: if (!(current_link.supported_caps & - SUPPORTED_10000baseKR_Full)) { + QED_LM_10000baseKR_Full_BIT)) { DP_INFO(edev, "10G speed not supported\n"); return -EINVAL; } - params.adv_speeds = SUPPORTED_10000baseKR_Full; + params.adv_speeds = QED_LM_10000baseKR_Full_BIT; + break; + case SPEED_25000: + if (!(current_link.supported_caps & + QED_LM_25000baseKR_Full_BIT)) { + DP_INFO(edev, "25G speed not supported\n"); + return -EINVAL; + } + params.adv_speeds = QED_LM_25000baseKR_Full_BIT; break; case SPEED_40000: if (!(current_link.supported_caps & - SUPPORTED_40000baseLR4_Full)) { + QED_LM_40000baseLR4_Full_BIT)) { DP_INFO(edev, "40G speed not supported\n"); return -EINVAL; } - params.adv_speeds = SUPPORTED_40000baseLR4_Full; + params.adv_speeds = QED_LM_40000baseLR4_Full_BIT; + break; + case 0xdead: + if (!(current_link.supported_caps & + QED_LM_50000baseKR2_Full_BIT)) { + DP_INFO(edev, "50G speed not supported\n"); + return -EINVAL; + } + params.adv_speeds = QED_LM_50000baseKR2_Full_BIT; + break; + case 0xbeef: + if (!(current_link.supported_caps & + QED_LM_100000baseKR4_Full_BIT)) { + DP_INFO(edev, "100G speed not supported\n"); + return -EINVAL; + } + params.adv_speeds = QED_LM_100000baseKR4_Full_BIT; break; default: - DP_INFO(edev, "Unsupported speed %u\n", speed); + DP_INFO(edev, "Unsupported speed %u\n", base->speed); return -EINVAL; } } @@ -1228,8 +1300,8 @@ static int qede_get_tunable(struct net_device *dev, } static const struct ethtool_ops qede_ethtool_ops = { - .get_settings = qede_get_settings, - .set_settings = qede_set_settings, + .get_link_ksettings = qede_get_link_ksettings, + .set_link_ksettings = qede_set_link_ksettings, .get_drvinfo = qede_get_drvinfo, .get_msglevel = qede_get_msglevel, .set_msglevel = qede_set_msglevel, @@ -1260,7 +1332,7 @@ static const struct ethtool_ops qede_ethtool_ops = { }; static const struct ethtool_ops qede_vf_ethtool_ops = { - .get_settings = qede_get_settings, + .get_link_ksettings = qede_get_link_ksettings, .get_drvinfo = qede_get_drvinfo, .get_msglevel = qede_get_msglevel, .set_msglevel = qede_set_msglevel, diff --git a/include/linux/qed/qed_if.h b/include/linux/qed/qed_if.h index b1e3c57c7117..737fc4c8db49 100644 --- a/include/linux/qed/qed_if.h +++ b/include/linux/qed/qed_if.h @@ -268,6 +268,21 @@ enum qed_protocol { QED_PROTOCOL_ISCSI, }; +enum qed_link_mode_bits { + QED_LM_FIBRE_BIT = BIT(0), + QED_LM_Autoneg_BIT = BIT(1), + QED_LM_Asym_Pause_BIT = BIT(2), + QED_LM_Pause_BIT = BIT(3), + QED_LM_1000baseT_Half_BIT = BIT(4), + QED_LM_1000baseT_Full_BIT = BIT(5), + QED_LM_10000baseKR_Full_BIT = BIT(6), + QED_LM_25000baseKR_Full_BIT = BIT(7), + QED_LM_40000baseLR4_Full_BIT = BIT(8), + QED_LM_50000baseKR2_Full_BIT = BIT(9), + QED_LM_100000baseKR4_Full_BIT = BIT(10), + QED_LM_COUNT = 11 +}; + struct qed_link_params { bool link_up; -- cgit v1.2.3 From e1aaf311dbe82221910cc0e0809c988de210cc3c Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Fri, 5 Aug 2016 10:39:34 -0300 Subject: dma-buf/fence-array: add fence_is_array() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add helper to check if fence is array. v2: Comments from Chris Wilson - remove ternary if from ops comparison - add EXPORT_SYMBOL(fence_array_ops) Cc: Chris Wilson Cc: Christian König Signed-off-by: Gustavo Padovan Reviewed-by: Chris Wilson Reviewed-by: Christian König Signed-off-by: Sumit Semwal --- drivers/dma-buf/fence-array.c | 1 + include/linux/fence-array.h | 10 ++++++++++ 2 files changed, 11 insertions(+) (limited to 'include/linux') diff --git a/drivers/dma-buf/fence-array.c b/drivers/dma-buf/fence-array.c index a8731c853da6..ee500226197b 100644 --- a/drivers/dma-buf/fence-array.c +++ b/drivers/dma-buf/fence-array.c @@ -99,6 +99,7 @@ const struct fence_ops fence_array_ops = { .wait = fence_default_wait, .release = fence_array_release, }; +EXPORT_SYMBOL(fence_array_ops); /** * fence_array_create - Create a custom fence array diff --git a/include/linux/fence-array.h b/include/linux/fence-array.h index 86baaa45567c..a44794e508df 100644 --- a/include/linux/fence-array.h +++ b/include/linux/fence-array.h @@ -51,6 +51,16 @@ struct fence_array { extern const struct fence_ops fence_array_ops; +/** + * fence_is_array - check if a fence is from the array subsclass + * + * Return true if it is a fence_array and false otherwise. + */ +static inline bool fence_is_array(struct fence *fence) +{ + return fence->ops == &fence_array_ops; +} + /** * to_fence_array - cast a fence to a fence_array * @fence: fence to cast to a fence_array -- cgit v1.2.3 From a02b9dc90d844cc7df7b63264e7920cc425052d9 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Fri, 5 Aug 2016 10:39:35 -0300 Subject: dma-buf/sync_file: refactor fence storage in struct sync_file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create sync_file->fence to abstract the type of fence we are using for each sync_file. If only one fence is present we use a normal struct fence but if there is more fences to be added to the sync_file a fence_array is created. This change cleans up sync_file a bit. We don't need to have sync_file_cb array anymore. Instead, as we always have one fence, only one fence callback is registered per sync_file. v2: Comments from Chris Wilson and Christian König - Not using fence_ops anymore - fence_is_array() was created to differentiate fence from fence_array - fence_array_teardown() is now exported and used under fence_is_array() - struct sync_file lost num_fences member v3: Comments from Chris Wilson and Christian König - struct sync_file lost status member in favor of fence_is_signaled() - drop use of fence_array_teardown() - use sizeof(*fence) to allocate only an array on fence pointers v4: Comments from Chris Wilson - use sizeof(*fence) to reallocate array - fix typo in comments - protect num_fences sum against overflows - use array->base instead of casting the to struct fence v5: fixes checkpatch warnings v6: fix case where all fences are signaled. Signed-off-by: Gustavo Padovan Reviewed-by: Chris Wilson Acked-by: Christian König Acked-by: Greg Kroah-Hartman Signed-off-by: Sumit Semwal --- drivers/dma-buf/sync_file.c | 174 +++++++++++++++++++++++------------ drivers/staging/android/sync_debug.c | 12 ++- include/linux/sync_file.h | 17 +--- 3 files changed, 129 insertions(+), 74 deletions(-) (limited to 'include/linux') diff --git a/drivers/dma-buf/sync_file.c b/drivers/dma-buf/sync_file.c index 9aaa608dfe01..ac9c250af302 100644 --- a/drivers/dma-buf/sync_file.c +++ b/drivers/dma-buf/sync_file.c @@ -28,11 +28,11 @@ static const struct file_operations sync_file_fops; -static struct sync_file *sync_file_alloc(int size) +static struct sync_file *sync_file_alloc(void) { struct sync_file *sync_file; - sync_file = kzalloc(size, GFP_KERNEL); + sync_file = kzalloc(sizeof(*sync_file), GFP_KERNEL); if (!sync_file) return NULL; @@ -45,6 +45,8 @@ static struct sync_file *sync_file_alloc(int size) init_waitqueue_head(&sync_file->wq); + INIT_LIST_HEAD(&sync_file->cb.node); + return sync_file; err: @@ -54,14 +56,11 @@ err: static void fence_check_cb_func(struct fence *f, struct fence_cb *cb) { - struct sync_file_cb *check; struct sync_file *sync_file; - check = container_of(cb, struct sync_file_cb, cb); - sync_file = check->sync_file; + sync_file = container_of(cb, struct sync_file, cb); - if (atomic_dec_and_test(&sync_file->status)) - wake_up_all(&sync_file->wq); + wake_up_all(&sync_file->wq); } /** @@ -76,22 +75,18 @@ struct sync_file *sync_file_create(struct fence *fence) { struct sync_file *sync_file; - sync_file = sync_file_alloc(offsetof(struct sync_file, cbs[1])); + sync_file = sync_file_alloc(); if (!sync_file) return NULL; - sync_file->num_fences = 1; - atomic_set(&sync_file->status, 1); + sync_file->fence = fence; + snprintf(sync_file->name, sizeof(sync_file->name), "%s-%s%llu-%d", fence->ops->get_driver_name(fence), fence->ops->get_timeline_name(fence), fence->context, fence->seqno); - sync_file->cbs[0].fence = fence; - sync_file->cbs[0].sync_file = sync_file; - if (fence_add_callback(fence, &sync_file->cbs[0].cb, - fence_check_cb_func)) - atomic_dec(&sync_file->status); + fence_add_callback(fence, &sync_file->cb, fence_check_cb_func); return sync_file; } @@ -121,14 +116,49 @@ err: return NULL; } -static void sync_file_add_pt(struct sync_file *sync_file, int *i, - struct fence *fence) +static int sync_file_set_fence(struct sync_file *sync_file, + struct fence **fences, int num_fences) { - sync_file->cbs[*i].fence = fence; - sync_file->cbs[*i].sync_file = sync_file; + struct fence_array *array; + + /* + * The reference for the fences in the new sync_file and held + * in add_fence() during the merge procedure, so for num_fences == 1 + * we already own a new reference to the fence. For num_fence > 1 + * we own the reference of the fence_array creation. + */ + if (num_fences == 1) { + sync_file->fence = fences[0]; + } else { + array = fence_array_create(num_fences, fences, + fence_context_alloc(1), 1, false); + if (!array) + return -ENOMEM; + + sync_file->fence = &array->base; + } - if (!fence_add_callback(fence, &sync_file->cbs[*i].cb, - fence_check_cb_func)) { + return 0; +} + +static struct fence **get_fences(struct sync_file *sync_file, int *num_fences) +{ + if (fence_is_array(sync_file->fence)) { + struct fence_array *array = to_fence_array(sync_file->fence); + + *num_fences = array->num_fences; + return array->fences; + } + + *num_fences = 1; + return &sync_file->fence; +} + +static void add_fence(struct fence **fences, int *i, struct fence *fence) +{ + fences[*i] = fence; + + if (!fence_is_signaled(fence)) { fence_get(fence); (*i)++; } @@ -147,16 +177,24 @@ static void sync_file_add_pt(struct sync_file *sync_file, int *i, static struct sync_file *sync_file_merge(const char *name, struct sync_file *a, struct sync_file *b) { - int num_fences = a->num_fences + b->num_fences; struct sync_file *sync_file; - int i, i_a, i_b; - unsigned long size = offsetof(struct sync_file, cbs[num_fences]); + struct fence **fences, **nfences, **a_fences, **b_fences; + int i, i_a, i_b, num_fences, a_num_fences, b_num_fences; - sync_file = sync_file_alloc(size); + sync_file = sync_file_alloc(); if (!sync_file) return NULL; - atomic_set(&sync_file->status, num_fences); + a_fences = get_fences(a, &a_num_fences); + b_fences = get_fences(b, &b_num_fences); + if (a_num_fences > INT_MAX - b_num_fences) + return NULL; + + num_fences = a_num_fences + b_num_fences; + + fences = kcalloc(num_fences, sizeof(*fences), GFP_KERNEL); + if (!fences) + goto err; /* * Assume sync_file a and b are both ordered and have no @@ -165,55 +203,73 @@ static struct sync_file *sync_file_merge(const char *name, struct sync_file *a, * If a sync_file can only be created with sync_file_merge * and sync_file_create, this is a reasonable assumption. */ - for (i = i_a = i_b = 0; i_a < a->num_fences && i_b < b->num_fences; ) { - struct fence *pt_a = a->cbs[i_a].fence; - struct fence *pt_b = b->cbs[i_b].fence; + for (i = i_a = i_b = 0; i_a < a_num_fences && i_b < b_num_fences; ) { + struct fence *pt_a = a_fences[i_a]; + struct fence *pt_b = b_fences[i_b]; if (pt_a->context < pt_b->context) { - sync_file_add_pt(sync_file, &i, pt_a); + add_fence(fences, &i, pt_a); i_a++; } else if (pt_a->context > pt_b->context) { - sync_file_add_pt(sync_file, &i, pt_b); + add_fence(fences, &i, pt_b); i_b++; } else { if (pt_a->seqno - pt_b->seqno <= INT_MAX) - sync_file_add_pt(sync_file, &i, pt_a); + add_fence(fences, &i, pt_a); else - sync_file_add_pt(sync_file, &i, pt_b); + add_fence(fences, &i, pt_b); i_a++; i_b++; } } - for (; i_a < a->num_fences; i_a++) - sync_file_add_pt(sync_file, &i, a->cbs[i_a].fence); + for (; i_a < a_num_fences; i_a++) + add_fence(fences, &i, a_fences[i_a]); + + for (; i_b < b_num_fences; i_b++) + add_fence(fences, &i, b_fences[i_b]); + + if (i == 0) { + add_fence(fences, &i, a_fences[0]); + i++; + } - for (; i_b < b->num_fences; i_b++) - sync_file_add_pt(sync_file, &i, b->cbs[i_b].fence); + if (num_fences > i) { + nfences = krealloc(fences, i * sizeof(*fences), + GFP_KERNEL); + if (!nfences) + goto err; + + fences = nfences; + } + + if (sync_file_set_fence(sync_file, fences, i) < 0) { + kfree(fences); + goto err; + } - if (num_fences > i) - atomic_sub(num_fences - i, &sync_file->status); - sync_file->num_fences = i; + fence_add_callback(sync_file->fence, &sync_file->cb, + fence_check_cb_func); strlcpy(sync_file->name, name, sizeof(sync_file->name)); return sync_file; + +err: + fput(sync_file->file); + return NULL; + } static void sync_file_free(struct kref *kref) { struct sync_file *sync_file = container_of(kref, struct sync_file, kref); - int i; - - for (i = 0; i < sync_file->num_fences; ++i) { - fence_remove_callback(sync_file->cbs[i].fence, - &sync_file->cbs[i].cb); - fence_put(sync_file->cbs[i].fence); - } + fence_remove_callback(sync_file->fence, &sync_file->cb); + fence_put(sync_file->fence); kfree(sync_file); } @@ -232,9 +288,9 @@ static unsigned int sync_file_poll(struct file *file, poll_table *wait) poll_wait(file, &sync_file->wq, wait); - status = atomic_read(&sync_file->status); + status = fence_is_signaled(sync_file->fence); - if (!status) + if (status) return POLLIN; if (status < 0) return POLLERR; @@ -315,8 +371,9 @@ static long sync_file_ioctl_fence_info(struct sync_file *sync_file, { struct sync_file_info info; struct sync_fence_info *fence_info = NULL; + struct fence **fences; __u32 size; - int ret, i; + int num_fences, ret, i; if (copy_from_user(&info, (void __user *)arg, sizeof(info))) return -EFAULT; @@ -324,6 +381,8 @@ static long sync_file_ioctl_fence_info(struct sync_file *sync_file, if (info.flags || info.pad) return -EINVAL; + fences = get_fences(sync_file, &num_fences); + /* * Passing num_fences = 0 means that userspace doesn't want to * retrieve any sync_fence_info. If num_fences = 0 we skip filling @@ -333,16 +392,16 @@ static long sync_file_ioctl_fence_info(struct sync_file *sync_file, if (!info.num_fences) goto no_fences; - if (info.num_fences < sync_file->num_fences) + if (info.num_fences < num_fences) return -EINVAL; - size = sync_file->num_fences * sizeof(*fence_info); + size = num_fences * sizeof(*fence_info); fence_info = kzalloc(size, GFP_KERNEL); if (!fence_info) return -ENOMEM; - for (i = 0; i < sync_file->num_fences; ++i) - sync_fill_fence_info(sync_file->cbs[i].fence, &fence_info[i]); + for (i = 0; i < num_fences; i++) + sync_fill_fence_info(fences[i], &fence_info[i]); if (copy_to_user(u64_to_user_ptr(info.sync_fence_info), fence_info, size)) { @@ -352,11 +411,8 @@ static long sync_file_ioctl_fence_info(struct sync_file *sync_file, no_fences: strlcpy(info.name, sync_file->name, sizeof(info.name)); - info.status = atomic_read(&sync_file->status); - if (info.status >= 0) - info.status = !info.status; - - info.num_fences = sync_file->num_fences; + info.status = fence_is_signaled(sync_file->fence); + info.num_fences = num_fences; if (copy_to_user((void __user *)arg, &info, sizeof(info))) ret = -EFAULT; diff --git a/drivers/staging/android/sync_debug.c b/drivers/staging/android/sync_debug.c index 4c5a85595a85..961a94bf156c 100644 --- a/drivers/staging/android/sync_debug.c +++ b/drivers/staging/android/sync_debug.c @@ -135,10 +135,16 @@ static void sync_print_sync_file(struct seq_file *s, int i; seq_printf(s, "[%p] %s: %s\n", sync_file, sync_file->name, - sync_status_str(atomic_read(&sync_file->status))); + sync_status_str(!fence_is_signaled(sync_file->fence))); - for (i = 0; i < sync_file->num_fences; ++i) - sync_print_fence(s, sync_file->cbs[i].fence, true); + if (fence_is_array(sync_file->fence)) { + struct fence_array *array = to_fence_array(sync_file->fence); + + for (i = 0; i < array->num_fences; ++i) + sync_print_fence(s, array->fences[i], true); + } else { + sync_print_fence(s, sync_file->fence, true); + } } static int sync_debugfs_show(struct seq_file *s, void *unused) diff --git a/include/linux/sync_file.h b/include/linux/sync_file.h index c6ffe8b0725c..2efc5ec60575 100644 --- a/include/linux/sync_file.h +++ b/include/linux/sync_file.h @@ -19,12 +19,7 @@ #include #include #include - -struct sync_file_cb { - struct fence_cb cb; - struct fence *fence; - struct sync_file *sync_file; -}; +#include /** * struct sync_file - sync file to export to the userspace @@ -32,10 +27,9 @@ struct sync_file_cb { * @kref: reference count on fence. * @name: name of sync_file. Useful for debugging * @sync_file_list: membership in global file list - * @num_fences: number of sync_pts in the fence * @wq: wait queue for fence signaling - * @status: 0: signaled, >0:active, <0: error - * @cbs: sync_pts callback information + * @fence: fence with the fences in the sync_file + * @cb: fence callback information */ struct sync_file { struct file *file; @@ -44,12 +38,11 @@ struct sync_file { #ifdef CONFIG_DEBUG_FS struct list_head sync_file_list; #endif - int num_fences; wait_queue_head_t wq; - atomic_t status; - struct sync_file_cb cbs[]; + struct fence *fence; + struct fence_cb cb; }; struct sync_file *sync_file_create(struct fence *fence); -- cgit v1.2.3 From 972526a4093243fdaf77dd7c6f8b11fba5b15864 Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Fri, 5 Aug 2016 10:39:36 -0300 Subject: dma-buf/sync_file: add sync_file_get_fence() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates a function that given an sync file descriptor returns a fence containing all fences in the sync_file. v2: Comments by Daniel Vetter - Adapt to new version of fence_collection_init() - Hold a reference for the fence we return v3: - Adapt to use fput() directly - rename to sync_file_get_fence() as we always return one fence v4: Adapt to use fence_array v5: set fence through fence_get() Signed-off-by: Gustavo Padovan Reviewed-by: Chris Wilson Acked-by: Christian König Signed-off-by: Sumit Semwal --- drivers/dma-buf/sync_file.c | 23 +++++++++++++++++++++++ include/linux/sync_file.h | 1 + 2 files changed, 24 insertions(+) (limited to 'include/linux') diff --git a/drivers/dma-buf/sync_file.c b/drivers/dma-buf/sync_file.c index ac9c250af302..2873760e02a9 100644 --- a/drivers/dma-buf/sync_file.c +++ b/drivers/dma-buf/sync_file.c @@ -116,6 +116,29 @@ err: return NULL; } +/** + * sync_file_get_fence - get the fence related to the sync_file fd + * @fd: sync_file fd to get the fence from + * + * Ensures @fd references a valid sync_file and returns a fence that + * represents all fence in the sync_file. On error NULL is returned. + */ +struct fence *sync_file_get_fence(int fd) +{ + struct sync_file *sync_file; + struct fence *fence; + + sync_file = sync_file_fdget(fd); + if (!sync_file) + return NULL; + + fence = fence_get(sync_file->fence); + fput(sync_file->file); + + return fence; +} +EXPORT_SYMBOL(sync_file_get_fence); + static int sync_file_set_fence(struct sync_file *sync_file, struct fence **fences, int num_fences) { diff --git a/include/linux/sync_file.h b/include/linux/sync_file.h index 2efc5ec60575..f7de5a0b3d12 100644 --- a/include/linux/sync_file.h +++ b/include/linux/sync_file.h @@ -46,5 +46,6 @@ struct sync_file { }; struct sync_file *sync_file_create(struct fence *fence); +struct fence *sync_file_get_fence(int fd); #endif /* _LINUX_SYNC_H */ -- cgit v1.2.3 From e24165537312723e2900831dd6e7415b8d85278c Mon Sep 17 00:00:00 2001 From: Gustavo Padovan Date: Fri, 5 Aug 2016 10:39:38 -0300 Subject: dma-buf/sync_file: only enable fence signalling on poll() Signalling doesn't need to be enabled at sync_file creation, it is only required if userspace waiting the fence to signal through poll(). Thus we delay fence_add_callback() until poll is called. It only adds the callback the first time poll() is called. This avoid re-adding the same callback multiple times. v2: rebase and update to work with new fence support for sync_file v3: use atomic operation to set enabled and protect fence_add_callback() v4: use user bit from fence flags (comment from Chris Wilson) v5: use ternary if on poll return (comment from Chris Wilson) Signed-off-by: Gustavo Padovan Reviewed-by: Chris Wilson Signed-off-by: Sumit Semwal [sumits: remove unused var status] Link: http://patchwork.freedesktop.org/patch/msgid/1470404378-27961-1-git-send-email-gustavo@padovan.org --- drivers/dma-buf/sync_file.c | 21 ++++++++------------- include/linux/sync_file.h | 2 ++ 2 files changed, 10 insertions(+), 13 deletions(-) (limited to 'include/linux') diff --git a/drivers/dma-buf/sync_file.c b/drivers/dma-buf/sync_file.c index 2873760e02a9..486d29c1a830 100644 --- a/drivers/dma-buf/sync_file.c +++ b/drivers/dma-buf/sync_file.c @@ -86,8 +86,6 @@ struct sync_file *sync_file_create(struct fence *fence) fence->ops->get_timeline_name(fence), fence->context, fence->seqno); - fence_add_callback(fence, &sync_file->cb, fence_check_cb_func); - return sync_file; } EXPORT_SYMBOL(sync_file_create); @@ -274,9 +272,6 @@ static struct sync_file *sync_file_merge(const char *name, struct sync_file *a, goto err; } - fence_add_callback(sync_file->fence, &sync_file->cb, - fence_check_cb_func); - strlcpy(sync_file->name, name, sizeof(sync_file->name)); return sync_file; @@ -291,7 +286,8 @@ static void sync_file_free(struct kref *kref) struct sync_file *sync_file = container_of(kref, struct sync_file, kref); - fence_remove_callback(sync_file->fence, &sync_file->cb); + if (test_bit(POLL_ENABLED, &sync_file->fence->flags)) + fence_remove_callback(sync_file->fence, &sync_file->cb); fence_put(sync_file->fence); kfree(sync_file); } @@ -307,17 +303,16 @@ static int sync_file_release(struct inode *inode, struct file *file) static unsigned int sync_file_poll(struct file *file, poll_table *wait) { struct sync_file *sync_file = file->private_data; - int status; poll_wait(file, &sync_file->wq, wait); - status = fence_is_signaled(sync_file->fence); + if (!test_and_set_bit(POLL_ENABLED, &sync_file->fence->flags)) { + if (fence_add_callback(sync_file->fence, &sync_file->cb, + fence_check_cb_func) < 0) + wake_up_all(&sync_file->wq); + } - if (status) - return POLLIN; - if (status < 0) - return POLLERR; - return 0; + return fence_is_signaled(sync_file->fence) ? POLLIN : 0; } static long sync_file_ioctl_merge(struct sync_file *sync_file, diff --git a/include/linux/sync_file.h b/include/linux/sync_file.h index f7de5a0b3d12..aa17ccfc2f57 100644 --- a/include/linux/sync_file.h +++ b/include/linux/sync_file.h @@ -45,6 +45,8 @@ struct sync_file { struct fence_cb cb; }; +#define POLL_ENABLED FENCE_FLAG_USER_BITS + struct sync_file *sync_file_create(struct fence *fence); struct fence *sync_file_get_fence(int fd); -- cgit v1.2.3 From c38162be301d59278f568e0b34be31915b6fe3bb Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 16 Jun 2016 11:36:13 +0200 Subject: video: ARM CLCD: backlight support for OF If the device is probed from device tree, we can support backlight. This is used with some systems such as the ST Microelectronics Nomadik. We have to add HAS_IOMEM to the dependencies of CLCD since the backlight class device will now be selected, and if it gets selected on an arch that does not have IOMEM, compilation will fail. Cc: Pawel Moll Cc: Rob Herring Cc: Russell King Signed-off-by: Linus Walleij Signed-off-by: Tomi Valkeinen --- drivers/video/fbdev/Kconfig | 4 +++- drivers/video/fbdev/amba-clcd.c | 40 ++++++++++++++++++++++++++++++++++++++++ include/linux/amba/clcd.h | 3 +++ 3 files changed, 46 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig index 88b008fb8a4e..9b9a76b82c04 100644 --- a/drivers/video/fbdev/Kconfig +++ b/drivers/video/fbdev/Kconfig @@ -284,12 +284,14 @@ config FB_PM2_FIFO_DISCONNECT config FB_ARMCLCD tristate "ARM PrimeCell PL110 support" depends on ARM || ARM64 || COMPILE_TEST - depends on FB && ARM_AMBA + depends on FB && ARM_AMBA && HAS_IOMEM select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT select FB_MODE_HELPERS if OF select VIDEOMODE_HELPERS if OF + select BACKLIGHT_LCD_SUPPORT if OF + select BACKLIGHT_CLASS_DEVICE if OF help This framebuffer device driver is for the ARM PrimeCell PL110 Colour LCD controller. ARM PrimeCells provide the building diff --git a/drivers/video/fbdev/amba-clcd.c b/drivers/video/fbdev/amba-clcd.c index 9b158869cb89..52a33d3926ac 100644 --- a/drivers/video/fbdev/amba-clcd.c +++ b/drivers/video/fbdev/amba-clcd.c @@ -30,6 +30,7 @@ #include #include #include +#include #include